This tutorial will introduce how to integrate the Chain DualKey programmable dual-key development board into Home Assistant to achieve button and expansion module control.

In this tutorial, the firmware is compiled and uploaded under ESPHome 2025.1.2. If you encounter compilation/upload issues, please consider switching ESPHome to this version.
CONTINUE.
New Device Setup.
NEXT.
ESP32-S3.
SKIP.
EDIT; we can customize device functions through the YAML file.
Master serves as the system's main controller. When connecting expansion sensors, it is necessary to correctly distinguish the connection direction and ID numbering sequence.
Depending on which side of the Master the expansion sensor is connected to, select the corresponding uart_id:
chain_uart_left.chain_uart_right.chain_id indicates the position of the expansion sensor relative to the Master:
uart_id: chain_uart_left.chain_id: 1.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: 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: gpio
name: "SWITCH_1"
pin:
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
id: chain_key_1
name: "Chain Key Button"
uart_id: xx
chain_id: xx
update_interval: 50ms
output:
- platform: m5stack_chain_key
id: chain_key_rgb_r
chain_key_id: chain_key_1
channel: rgb_red
- platform: m5stack_chain_key
id: chain_key_rgb_g
chain_key_id: chain_key_1
channel: rgb_green
- platform: m5stack_chain_key
id: chain_key_rgb_b
chain_key_id: chain_key_1
channel: rgb_blue
light:
- platform: rgb
name: "Key RGB"
red: chain_key_rgb_r
green: chain_key_rgb_g
blue: chain_key_rgb_b external_components:
- source: github://m5stack/esphome-yaml/components
components: [m5stack_chain_angle]
refresh: 0s
sensor:
- platform: m5stack_chain_angle
id: chain_angle_1
name: "Chain Angle"
uart_id: xx
chain_id: xx
update_interval: 50ms
output:
- platform: m5stack_chain_angle
id: chain_angle_rgb_r
chain_angle_id: chain_angle_1
channel: rgb_red
- platform: m5stack_chain_angle
id: chain_angle_rgb_g
chain_angle_id: chain_angle_1
channel: rgb_green
- platform: m5stack_chain_angle
id: chain_angle_rgb_b
chain_angle_id: chain_angle_1
channel: rgb_blue
light:
- platform: rgb
name: "Angle RGB"
red: chain_angle_rgb_r
green: chain_angle_rgb_g
blue: chain_angle_rgb_b external_components:
- source: github://m5stack/esphome-yaml/components
components: [m5stack_chain_encoder]
refresh: 0s
sensor:
- platform: m5stack_chain_encoder
id: chain_encoder_1
name: "Chain Encoder"
uart_id: xx
chain_id: xx
update_interval: 100ms
output:
- platform: m5stack_chain_encoder
id: chain_encoder_rgb_r
chain_encoder_id: chain_encoder_1
channel: rgb_red
- platform: m5stack_chain_encoder
id: chain_encoder_rgb_g
chain_encoder_id: chain_encoder_1
channel: rgb_green
- platform: m5stack_chain_encoder
id: chain_encoder_rgb_b
chain_encoder_id: chain_encoder_1
channel: rgb_blue
light:
- platform: rgb
name: "Encoder RGB"
red: chain_encoder_rgb_r
green: chain_encoder_rgb_g
blue: chain_encoder_rgb_b
binary_sensor:
- platform: m5stack_chain_encoder
name: "Encoder Button"
chain_encoder_id: chain_encoder_1 external_components:
- source: github://m5stack/esphome-yaml/components
components: [m5stack_chain_joystick]
refresh: 0s
sensor:
- platform: m5stack_chain_joystick
id: chain_joystick_x
name: "Chain Joystick X"
uart_id: xx
chain_id: xx
axis: x
update_interval: 50ms
- platform: m5stack_chain_joystick
name: "Chain Joystick Y"
uart_id: xx
chain_id: xx
axis: y
update_interval: 50ms
output:
- platform: m5stack_chain_joystick
id: chain_joystick_rgb_r
chain_joystick_id: chain_joystick_x
channel: rgb_red
- platform: m5stack_chain_joystick
id: chain_joystick_rgb_g
chain_joystick_id: chain_joystick_x
channel: rgb_green
- platform: m5stack_chain_joystick
id: chain_joystick_rgb_b
chain_joystick_id: chain_joystick_x
channel: rgb_blue
light:
- platform: rgb
name: "Joystick RGB"
red: chain_joystick_rgb_r
green: chain_joystick_rgb_g
blue: chain_joystick_rgb_b
binary_sensor:
- platform: m5stack_chain_joystick
name: "Joystick Button"
chain_joystick_id: chain_joystick_x external_components:
- source: github://m5stack/esphome-yaml/components
components: [m5stack_chain_tof]
refresh: 0s
sensor:
- platform: m5stack_chain_tof
id: chain_tof_1
name: "Chain ToF"
uart_id: xx
chain_id: xx
update_interval: 100ms
output:
- platform: m5stack_chain_tof
id: chain_tof_rgb_r
m5stack_chain_tof_id: chain_tof_1
channel: rgb_red
- platform: m5stack_chain_tof
id: chain_tof_rgb_g
m5stack_chain_tof_id: chain_tof_1
channel: rgb_green
- platform: m5stack_chain_tof
id: chain_tof_rgb_b
m5stack_chain_tof_id: chain_tof_1
channel: rgb_blue
light:
- platform: rgb
name: "ToF RGB"
red: chain_tof_rgb_r
green: chain_tof_rgb_g
blue: chain_tof_rgb_b The following code example is configured according to the connection sequence shown in the image above.
Modules used: Chain Angle, Chain Encoder, Chain ToF, Chain Joystick, Chain Key.
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 Modules used: The UART bus shared by all Chain series modules on the Master's left/right HY2.0 interfaces.
chain_uart_right and chain_uart_left correspond to the HY2.0 interfaces on the right and left sides of the Master, respectively. Subsequent Chain module uart_id settings need to match the actual connection direction.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 Modules used: Chain Encoder, Chain Angle, Chain ToF, Chain Joystick (X/Y), DualKey battery-related ADC sensors.
sensor:
- platform: m5stack_chain_encoder
id: chain_encoder_1
name: "Encoder"
uart_id: chain_uart_right
chain_id: 1
update_interval: 100ms
- platform: m5stack_chain_tof
id: chain_tof_1
name: "ToF Distance"
uart_id: chain_uart_right
chain_id: 3
update_interval: 100ms
- platform: m5stack_chain_angle
id: chain_angle_1
name: "Angle"
uart_id: chain_uart_right
chain_id: 2
update_interval: 100ms
- platform: m5stack_chain_joystick
id: chain_joystick_x
name: "Joystick X"
uart_id: chain_uart_left
chain_id: 1
axis: x
update_interval: 100ms
- platform: m5stack_chain_joystick
name: "Joystick 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 Modules used: RGB lights of Chain Encoder, Chain Key, Chain Joystick, Chain Angle, Chain ToF, and DualKey power control.
pwr_en GPIO output is used to control power supply to the Chain expansion bus, which usually needs to remain on to ensure that modules connected to the bus can operate normally.output:
- platform: gpio
id: pwr_en
pin: GPIO40
- platform: m5stack_chain_encoder
id: chain_encoder_rgb_r
chain_encoder_id: chain_encoder_1
channel: rgb_red
- platform: m5stack_chain_encoder
id: chain_encoder_rgb_g
chain_encoder_id: chain_encoder_1
channel: rgb_green
- platform: m5stack_chain_encoder
id: chain_encoder_rgb_b
chain_encoder_id: chain_encoder_1
channel: rgb_blue
- platform: m5stack_chain_key
id: chain_key_rgb_r
chain_key_id: chain_key_1
channel: rgb_red
- platform: m5stack_chain_key
id: chain_key_rgb_g
chain_key_id: chain_key_1
channel: rgb_green
- platform: m5stack_chain_key
id: chain_key_rgb_b
chain_key_id: chain_key_1
channel: rgb_blue
- platform: m5stack_chain_joystick
id: chain_joystick_rgb_r
chain_joystick_id: chain_joystick_x
channel: rgb_red
- platform: m5stack_chain_joystick
id: chain_joystick_rgb_g
chain_joystick_id: chain_joystick_x
channel: rgb_green
- platform: m5stack_chain_joystick
id: chain_joystick_rgb_b
chain_joystick_id: chain_joystick_x
channel: rgb_blue
- platform: m5stack_chain_angle
id: chain_angle_rgb_r
chain_angle_id: chain_angle_1
channel: rgb_red
- platform: m5stack_chain_angle
id: chain_angle_rgb_g
chain_angle_id: chain_angle_1
channel: rgb_green
- platform: m5stack_chain_angle
id: chain_angle_rgb_b
chain_angle_id: chain_angle_1
channel: rgb_blue
- platform: m5stack_chain_tof
id: chain_tof_rgb_r
m5stack_chain_tof_id: chain_tof_1
channel: rgb_red
- platform: m5stack_chain_tof
id: chain_tof_rgb_g
m5stack_chain_tof_id: chain_tof_1
channel: rgb_green
- platform: m5stack_chain_tof
id: chain_tof_rgb_b
m5stack_chain_tof_id: chain_tof_1
channel: rgb_blue Modules used: WS2812 key lights on DualKey, and RGB indicator lights on each Chain module.
key_light_raw into key_light_1 and key_light_2), while also defining corresponding RGB light entities for each Chain module for separate control in Home Assistant.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: "Key1 LED"
id: key_light_1
segments:
- id: key_light_raw
from: 1
to: 1
- platform: partition
name: "Key2 LED"
id: key_light_2
segments:
- id: key_light_raw
from: 0
to: 0
- platform: rgb
name: "Encoder RGB"
red: chain_encoder_rgb_r
green: chain_encoder_rgb_g
blue: chain_encoder_rgb_b
- platform: rgb
name: "Key RGB"
red: chain_key_rgb_r
green: chain_key_rgb_g
blue: chain_key_rgb_b
- platform: rgb
name: "Joystick RGB"
red: chain_joystick_rgb_r
green: chain_joystick_rgb_g
blue: chain_joystick_rgb_b
- platform: rgb
name: "Angle RGB"
red: chain_angle_rgb_r
green: chain_angle_rgb_g
blue: chain_angle_rgb_b
- platform: rgb
name: "ToF RGB"
red: chain_tof_rgb_r
green: chain_tof_rgb_g
blue: chain_tof_rgb_b Modules used: Mechanical buttons and side DIP switches on DualKey, as well as buttons on Chain Key, Chain Encoder, and Chain Joystick modules.
KEY 1, KEY 2) and their corresponding backlight interaction, the button of a Chain Key module on the bus, the encoder button, the joystick button, and two side switches (SWITCH 1, SWITCH 2). You can rename these entities in Home Assistant based on your actual use cases.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
id: chain_key_1
name: "Key Module Button"
uart_id: chain_uart_left
chain_id: 2
update_interval: 50ms
- platform: m5stack_chain_encoder
name: "Encoder Button"
chain_encoder_id: chain_encoder_1
- platform: m5stack_chain_joystick
name: "Joystick Button"
chain_joystick_id: chain_joystick_x
- platform: gpio
name: "SWITCH 1"
pin:
number: GPIO7
mode: INPUT
- platform: gpio
name: "SWITCH 2"
pin:
number: GPIO8
mode: INPUT SAVE and INSTALL in the top right corner, then select Manual Download in the pop-up dialog.
Download and select the Factory format(Previously Modern) option to download the firmware.
CONNECT to connect the device.
INSTALL.
Settings -> Device & services to view the device.
Discover section.
