pdf-icon

Chain DualKey Home Assistant Integration

Introduction

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.

Preparation

Tip
In this tutorial, the firmware is compiled and uploaded with ESPHome 2025.1.2. If you encounter compile/upload issues, consider switching ESPHome to this version.

Step 1. Create New Device

  • Click the green button in the lower right corner to create a device.

Step 2. Create Device Name

  • Click CONTINUE.

  • Click New Device Setup.

  • Enter the name of the device and click NEXT.

Step 3. Choose Device Type

Tip
Here we use Atom-Lite as the main controller to operate the SwitchC6 relay switch.
  • Click ESP32-S3.

  • Click SKIP.

Step 4. Start Edit YAML File

  • Click EDIT. We can customize device functionality through YAML files.

Device Setup

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:

  • Connected to the left side of the Master → use chain_uart_left
  • Connected to the right side of the Master → use chain_uart_right

ID Numbering Rules

The chain_id represents the position of the expansion sensor relative to the Master:

  • Numbering starts from the module closest to the Master
  • IDs increase sequentially (ID:1 → ID:2 → ID:3 …)
  • The left and right sides are numbered independently
  • Refer to the diagram above to determine the correct order

Configuration Example

  • uart_id: chain_uart_left
  • chain_id: 1

Chain DualKey

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_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

Chain Key

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

Chain Angle

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

Chain Encoder

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

Chain Joystick

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

Chain Tof

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

Example

The following code example is configured according to the connection order shown in the diagram above.

External Components

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

UART Components

  • Add the Uart components
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 Components

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 Components

output:
  - platform: gpio
    id: pwr_en
    pin: GPIO40

Light Components

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 Components

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

Firmware Build

  • Click INSTALL again to flash and wait for it to complete.

  • After making changes, click SAVE and INSTALL in the top-right corner, then choose Manual Download in the popup.

  • After the firmware compilation is complete, click Download and select Factory format(Previously Modern)

Tip
Click m5stack_chain to view the complete example configuration. The first build may take a while, depending on the performance of the Home Assistant host and network quality.

Firmware Upload

  • Connect the device to your host via a USB Type‑C cable. Open ESPHome Web and click CONNECT to connect to the device.

  • Locate the corresponding serial port number

  • Click INSTALL

  • Select the previously compiled firmware to upload.

Tip
After successfully burning the program, power must be cycled to perform a hardware reset.

Home Assistant Integration

  • Click Settings -> Device & services to check the device.

  • We can find the corresponding device in the Discover section.

  • After adding the device, the data will be displayed correctly.

  • Finally, we add these entities to the Dashboard, and the following shows their display results.

On This Page