
Chain DualKey is a programmable dual-key input development board equipped with the ESP32-S3FN8 main control chip. The front integrates 2 hot-swappable blue switch mechanical keyboard keys and 2 programmable RGB LEDs, providing excellent interactive feedback. It has a built-in 350mAh lithium battery, combining with a low-power design for good battery life. The product comes with pre-installed Chain macro keyboard firmware, supports USB / BLE connections, and can emulate HID input devices. After the device is powered on, you can connect to the device's AP hotspot and configure the HID function mapping for the local device or expansion nodes via the built-in web page to achieve various control functions. This development board adopts the M5Stack Chain series expandable design, featuring two HY2.0-4P expansion ports that support lateral expansion and connection to other sensor devices. With the USB-OTG peripheral function built into ESP32-S3, it is suitable for smart home, keyboard peripherals, macro keyboards, and other scenarios.
Step 1. Create New Device

Step 2. Create Device Name
CONTINUE.
New Device Setup.
NEXT.
Step 3. Choose Device Type
ESP32-S3.
SKIP.
Step 4. Start Edit YAML File
EDIT. We can customize device functionality through YAML files.
The Master acts as the main controller of the system. When connecting expansion sensors, it is important to correctly identify both the connection direction and the ID order.
Direction Selection
Choose the appropriate uart_id based on which side of the Master the expansion sensor is connected:
ID Numbering Rules
The chain_id represents the position of the expansion sensor relative to the Master:
Configuration Example
uart_id: chain_uart_leftchain_id: 1uart:
- id: chain_uart_right
tx_pin: GPIO6
rx_pin: GPIO5
baud_rate: 115200
- id: chain_uart_left
tx_pin: GPIO48
rx_pin: GPIO47
baud_rate: 115200
sensor:
- platform: adc
pin: GPIO10
name: "ADC_BAT"
update_interval: 1s
- platform: adc
pin: GPIO2
name: "ADC_VBUS"
update_interval: 1s
- platform: adc
pin: GPIO9
name: "ADC_CHARGE"
update_interval: 1s
output:
- platform: gpio
id: pwr_en
pin: GPIO40
light:
- platform: esp32_rmt_led_strip
id: key_light_raw
internal: true
pin: GPIO21
num_leds: 2
chipset: ws2812
rgb_order: GRB
restore_mode: ALWAYS_OFF
- platform: partition
name: "Key Light 1"
id: key_light_1
segments:
- id: key_light_raw
from: 0
to: 0
- platform: partition
name: "Key Light 2"
id: key_light_2
segments:
- id: key_light_raw
from: 1
to: 1
binary_sensor:
- platform: gpio
name: "KEY_2"
pin:
number: GPIO17
inverted: true
mode: INPUT_PULLUP
filters:
- delayed_on: 10ms
- delayed_off: 10ms
on_press:
- light.turn_on:
id: key_light_1
transition_length: 0ms
on_release:
- light.turn_off: key_light_1
- platform: gpio
name: "KEY_1"
pin:
number: GPIO0
inverted: true
mode: INPUT_PULLUP
filters:
- delayed_on: 10ms
- delayed_off: 10ms
on_press:
- light.turn_on:
id: key_light_2
transition_length: 0ms
on_release:
- light.turn_off: key_light_2
- platform: gpio
name: "SWITCH_1"
pin:xs
number: GPIO7
mode: INPUT
- platform: gpio
name: "SWITCH_2"
pin:
number: GPIO8
mode: INPUT external_components:
- source: github://m5stack/esphome-yaml/components
components: [m5stack_chain_key]
refresh: 0s
binary_sensor:
- platform: m5stack_chain_key
name: "Chain Key Right 1"
uart_id: xx
chain_id: xx
update_interval: 50ms external_components:
- source: github://m5stack/esphome-yaml/components
components: [m5stack_chain_angle]
refresh: 0s
sensor:
- platform: m5stack_chain_angle
name: "Chain Angle Left"
uart_id: xx
chain_id: xx
update_interval: 50ms external_components:
- source: github://m5stack/esphome-yaml/components
components: [m5stack_chain_encoder]
refresh: 0s
sensor:
- platform: m5stack_chain_encoder
name: "Chain Encoder"
uart_id: xx
chain_id: xx
update_interval: 100ms external_components:
- source: github://m5stack/esphome-yaml/components
components: [m5stack_chain_joystick]
refresh: 0s
sensor:
- platform: m5stack_chain_joystick
name: "Chain Joystick Left X"
uart_id: xx
chain_id: xx
axis: x
update_interval: 50ms
- platform: m5stack_chain_joystick
name: "Chain Joystick Left Y"
uart_id: xx
chain_id: xx
axis: y
update_interval: 50ms external_components:
- source: github://m5stack/esphome-yaml/components
components: [m5stack_chain_tof]
refresh: 0s
sensor:
- platform: m5stack_chain_tof
name: "Chain Tof"
uart_id: xx
chain_id: xx
update_interval: 100ms The following code example is configured according to the connection order shown in the diagram above.

external_components:
- source: github://m5stack/esphome-yaml/components
components: [m5stack_chain_angle,m5stack_chain_encoder,m5stack_chain_tof,m5stack_chain_joystick,m5stack_chain_key]
refresh: 0s captive_portal:
uart:
- id: chain_uart_right
tx_pin: GPIO6
rx_pin: GPIO5
baud_rate: 115200
- id: chain_uart_left
tx_pin: GPIO48
rx_pin: GPIO47
baud_rate: 115200 sensor:
- platform: m5stack_chain_encoder
name: "Chain Encoder"
uart_id: chain_uart_right
chain_id: 1
update_interval: 100ms
- platform: m5stack_chain_tof
name: "Chain Tof"
uart_id: chain_uart_right
chain_id: 3
update_interval: 100ms
- platform: m5stack_chain_angle
name: "Chain Angle Left"
uart_id: chain_uart_right
chain_id: 2
update_interval: 100ms
- platform: m5stack_chain_joystick
name: "Chain Joystick Left X"
uart_id: chain_uart_left
chain_id: 1
axis: x
update_interval: 100ms
- platform: m5stack_chain_joystick
name: "Chain Joystick Left Y"
uart_id: chain_uart_left
chain_id: 1
axis: y
update_interval: 100ms
- platform: adc
pin: GPIO10
name: "ADC_BAT"
update_interval: 1s
- platform: adc
pin: GPIO2
name: "ADC_VBUS"
update_interval: 1s
- platform: adc
pin: GPIO9
name: "ADC_CHARGE"
update_interval: 1s output:
- platform: gpio
id: pwr_en
pin: GPIO40 light:
- platform: esp32_rmt_led_strip
id: key_light_raw
internal: true
pin: GPIO21
num_leds: 2
chipset: ws2812
rgb_order: GRB
restore_mode: ALWAYS_OFF
- platform: partition
name: "Key Light 1"
id: key_light_1
segments:
- id: key_light_raw
from: 0
to: 0
- platform: partition
name: "Key Light 2"
id: key_light_2
segments:
- id: key_light_raw
from: 1
to: 1 binary_sensor:
- platform: gpio
name: "KEY 2"
pin:
number: GPIO17
inverted: true
mode: INPUT_PULLUP
filters:
- delayed_on: 10ms
- delayed_off: 10ms
on_press:
- light.turn_on:
id: key_light_2
transition_length: 0ms
on_release:
- light.turn_off: key_light_2
- platform: gpio
name: "KEY 1"
pin:
number: GPIO0
inverted: true
mode: INPUT_PULLUP
filters:
- delayed_on: 10ms
- delayed_off: 10ms
on_press:
- light.turn_on:
id: key_light_1
transition_length: 0ms
on_release:
- light.turn_off: key_light_1
- platform: m5stack_chain_key
name: "Chain Key Right 1"
uart_id: chain_uart_left
chain_id: 2
update_interval: 50ms
- platform: gpio
name: "SWITCH 1"
pin:
number: GPIO7
mode: INPUT
- platform: gpio
name: "SWITCH 2"
pin:
number: GPIO8
mode: INPUT INSTALL again to flash and wait for it to complete.
SAVE and INSTALL in the top-right corner, then choose Manual Download in the popup.
Factory format(Previously Modern)
CONNECT to connect to the device.

INSTALL

Settings -> Device & services to check the device.
Discover section.

