Skip to content

About

Zigbee repeater for the Seeed XIAO ESP32-C6 (ESPHome): battery monitoring and remote antenna / TX-power control from Zigbee2MQTT. Prebuilt firmware included.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Β 
Β 

Repository files navigation

πŸ“‘ XIAO ESP32-C6 Zigbee Repeater

A standalone Zigbee repeater for the Seeed XIAO ESP32-C6, with battery monitoring and remote antenna / TX-power control from Zigbee2MQTT.

XIAO ESP32-C6 on the wall with its U.FL antenna lead going through the wall The external antenna coming out on the other side of the wall
XIAO side: U.FL lead going through the wall Other side: the external antenna

Build ESPHome Zigbee2MQTT

Why this exists

I wanted a customisable, standalone Zigbee repeater to reach my Linky power meter at the end of a corridor. So I put a XIAO ESP32-C6 on one side of the wall and ran a long PCB antenna I had through to the other side.

The XIAO supports battery operation, so I added battery level monitoring. It needs a little resistor mod (see below): the voltage is read through Seeed Studio's documented 1:2 divider, and the percentage is estimated from a standard single-cell LiPo voltage curve. I also added a setting to switch remotely between the internal and external antenna, so you can compare the range of both. Unsurprisingly, the external antenna worked better in my case.

Linky (Lixee ZLinky) in Zigbee2MQTT, LQI 180 XIAO-ESP32C6-Zigbee-Router in Zigbee2MQTT, LQI 132

Results

Linky before and after. Before the router, I had to move my Zigbee coordinator out of my office to sit next to the front door, and even then the Linky only reached a link quality (LQI) of about 20–40. With the XIAO in the corridor and the coordinator back in the office, the Linky jumped to ~160 straight away, and it has stayed there since (180 in the screenshot above).

Antenna and TX power comparison. Same spot, flipped from Zigbee2MQTT without touching the board. LQI is the router's link quality as seen by Zigbee2MQTT:

External antenna, +20 dBm Internal antenna, +20 dBm External antenna, 0 dBm
External antenna at +20 dBm: LQI 180 Internal antenna at +20 dBm: LQI 136 External antenna at 0 dBm: LQI 92
LQI 180 LQI 136 LQI 92

These are single readings, and LQI moves around a bit. Watch it for a few minutes after each change before drawing conclusions.

This project is a fork of firsttris/esp32c6-zigbee-router. If you just want a simple router on any ESP32-C6, start there. This fork is for XIAO ESP32-C6 owners who want to tune and monitor the router from their coordinator without reflashing.

✨ Features

This fork Original
Zigbee router / range extender, no WiFi or secrets βœ… βœ…
Internal / external antenna selection βœ… at runtime, from Zigbee2MQTT at build time
Zigbee TX power (βˆ’15…+20 dBm), kept across reboots βœ… at runtime, from Zigbee2MQTT –
LiPo battery voltage and % (standard Power Configuration cluster) βœ… –
Zigbee2MQTT external converter βœ… –
Prebuilt firmware, no toolchain needed βœ… –
Hold BOOT 5 s to leave the network and re-pair βœ… βœ…
Status LED on warnings / errors βœ… βœ…
Pinned ESPHome / ESP-IDF / component versions βœ… latest
Zigbee stack luar123/zigbee_esphome ESPHome native zigbee
Boards XIAO ESP32-C6 only any ESP32-C6

Why not ESPHome's native Zigbee component? As of ESPHome 2026.7, the native component on ESP32 only reports read-only sensors. It can't expose writable or custom attributes, so the remote antenna/TX-power controls and the standard battery attributes aren't possible with it yet.

πŸ“‹ What you need

  • Seeed Studio XIAO ESP32-C6 and a USB data cable
  • A Zigbee coordinator. Zigbee2MQTT is recommended; ZHA works for routing only.
  • Optional: an external 2.4 GHz antenna with a U.FL connector
  • Optional: a 1-cell LiPo and two resistors for battery monitoring

The router must stay powered. A backup LiPo does not turn it into a sleepy end device; the Basic cluster still reports mains power.

πŸš€ Step 1: Flash the firmware

Pick one option. The prebuilt image is the easiest. It starts on the internal antenna at +20 dBm, and you can change both later from Zigbee2MQTT, so most people never need to build anything.

Option A: prebuilt image (recommended)

  1. Download xiao-esp32c6-zigbee-router.factory.bin from the latest release. SHA256SUMS in the same release lets you check the download.
  2. Plug the XIAO into your computer with a data cable.
  3. Open web.esphome.io in Chrome or Edge (Web Serial is not available in Firefox or Safari).
  4. Click Connect and pick the XIAO's serial port.
  5. Click Install, select the .factory.bin file, and confirm.
  6. When it finishes, press RESET on the XIAO (or unplug and replug it).

Prefer the command line? Use esptool:

esptool --chip esp32c6 --port /dev/ttyACM0 write-flash 0x0 xiao-esp32c6-zigbee-router.factory.bin

(esptool v4 spells it write_flash.) Always flash the .factory.bin at 0x0. The .ota.bin is an app-only image and won't boot from there.

Option B: build from source

Use this if you want to change the YAML. It requires Python 3.11+ and Git, and the first build downloads ESP-IDF (~600 MB).

python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
esphome run esp32-c6-zigbee-router.yaml --device /dev/ttyACM0

Or with Docker (plug the XIAO in before starting the container):

ESPHOME_DEVICE=/dev/ttyACM0 docker compose up -d
docker compose exec esphome esphome run /config/esp32-c6-zigbee-router.yaml --device=/dev/ttyACM0

The container also serves the ESPHome dashboard on port 6052 without a password. Only run it on a trusted network.

Serial port names and flashing troubleshooting
OS Port
Linux /dev/ttyACM0 (add yourself to dialout if access is denied)
macOS /dev/cu.usbmodem*
Windows COM3, COM4, …

python -m serial.tools.list_ports lists them. If the board isn't detected or the upload fails: hold BOOT, tap RESET, release BOOT, then retry. Tap RESET once more after flashing. Close any other serial monitor first.

πŸ”— Step 2: Pair it

  1. Enable Permit join in Zigbee2MQTT (or Add device in ZHA).
  2. Power the router. A factory-new router starts joining within about a minute.
  3. Wait for the interview to finish and the device type to show Router.
  4. Move it to its final spot and check the network map for neighbours. Joining proves membership, not better coverage.

To move it to another network, remove it from the coordinator, enable pairing, then hold BOOT for 5 seconds. It leaves the network, clears its Zigbee data and rejoins. Antenna and TX power settings are kept. (Holding BOOT while plugging in enters the bootloader instead.)

🧩 Step 3: Install the Zigbee2MQTT converter

Without the converter the router still routes, but Zigbee2MQTT shows it as unsupported and you get no battery or RF controls. With it, you get this:

Zigbee2MQTT exposes tab: battery, voltage, antenna internal/external, TX power slider, link quality

Battery and voltage read N/A here because this board doesn't have the resistor mod fitted, which is the expected behaviour.

  1. Find your Zigbee2MQTT data folder, the one containing configuration.yaml:
    • Home Assistant add-on: /config/zigbee2mqtt/ (or /homeassistant/zigbee2mqtt/ when browsing with the File editor / Samba / SSH add-ons)
    • Docker: the folder you mounted to /app/data
  2. Create an external_converters folder next to configuration.yaml and copy zigbee2mqtt/xiao-esp32c6-zigbee-router.mjs into it:
    zigbee2mqtt/
    β”œβ”€β”€ configuration.yaml
    └── external_converters/
        └── xiao-esp32c6-zigbee-router.mjs
    
  3. Allow external converters. Since Zigbee2MQTT 2.11, new installations have them disabled by default. Add this to configuration.yaml if your advanced: section doesn't already have it (add the line to the existing advanced: block rather than creating a second one):
    advanced:
      enable_external_js: true
  4. Restart Zigbee2MQTT itself. This setting is only read when Zigbee2MQTT starts. On Home Assistant, go to the Zigbee2MQTT add-on page and click Stop, then Start. ⚠️ Settings β†’ Restart Home Assistant is not enough: it only restarts Home Assistant Core, and the add-on keeps running with the old settings.
  5. Check the Zigbee2MQTT log for Loaded external converter 'xiao-esp32c6-zigbee-router.mjs'.
  6. Re-interview the router if it was paired before the converter was installed: use the Interview button on its device page, or remove it and pair again.

The device then shows up as XIAO-ESP32C6-Zigbee-Router with battery, voltage, antenna and tx_power. Through MQTT discovery, these also become Home Assistant entities. You can also send commands to zigbee2mqtt/<friendly-name>/set:

{"antenna": "external"}
{"tx_power": 10}

Converter save times out? If you paste the converter into the Zigbee2MQTT web UI and get bridge/request/converter/save β†’ Request timed out, Zigbee2MQTT is still running with external JavaScript disabled. Check step 3, then Stop/Start the add-on as in step 4. A 401 Unauthorized for the web manifest in the browser console is unrelated noise.

No converter? Use the Dev console

The settings are plain Zigbee attributes, so you can read/write them from Zigbee2MQTT's Dev console tab without installing anything:

Setting Endpoint Cluster Attribute Type Values
Antenna 1 64512 (0xFC00) 0 boolean (0x10) 0 internal, 1 external
TX power 1 64512 (0xFC00) 1 int8 (0x28) -15 … 20 dBm

πŸ“Ά Antenna and TX power

  • The XIAO has an onboard ceramic antenna and a U.FL connector. GPIO3 enables the RF switch; GPIO14 selects internal (low, the default) or external (high).
  • Connect the external antenna before selecting it, or range collapses.
  • Use it to compare: switch antennas, wait a few minutes, and watch the link quality to the router and to the devices behind it.
  • TX power defaults to +20 dBm. Antenna gain adds to radiated power, so choose a value that keeps you within your local EIRP limit.

πŸ”‹ Battery monitoring (resistor mod)

The XIAO ESP32-C6 can run from and charge a single-cell LiPo, but the battery isn't connected to any ADC pin out of the box. To read it, add a 1:2 voltage divider from BAT to A0 (GPIO0) using 200 kΞ© resistors, as described in Seeed's battery-voltage monitoring guide (section "Reading Battery Voltage"):

BAT (+) ──[ 200 kΞ© ]──┬──► A0 / GPIO0
                      β”‚
                  [ 200 kΞ© ]
                      β”‚
GND β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  • A full LiPo (4.2 V) shows up as 2.1 V on A0, well within the ADC range. The firmware doubles the reading to get the battery voltage.
  • Never wire BAT straight to A0.
  • Battery % comes from a standard single-cell LiPo voltage curve (3.3 V = 0 %, 4.2 V = 100 %). It reads high while charging and low under load. Check it against a multimeter once.
  • Without the mod, the readings are meaningless. Usually they show N/A, but a floating pin can produce plausible-looking values. This is not a battery-presence detector.

Readings update every 60 seconds and are reported on the standard Power Configuration cluster, so any coordinator can read them.

πŸ›  Zigbee attributes (endpoint 1)

Cluster / attribute Type Meaning
0x0001 / 0x0020 UINT8, report Battery voltage, 100 mV units (0xFF unknown)
0x0001 / 0x0021 UINT8, report Battery %, half-percent units 0–200 (0xFF unknown)
0xFC00 / 0x0000 BOOL, read/write/report false internal antenna, true external
0xFC00 / 0x0001 INT8, read/write/report TX power in dBm, βˆ’15…+20

Device type RANGE_EXTENDER, model ID xiao-esp32c6-zigbee-router. The converter also matches the older model ID esp32-c6-zigbee-router.

⬆️ Switching from the original project

The original now uses ESPHome's native Zigbee stack and a different partition layout. To switch to this fork (or back), the Zigbee data must be wiped:

  1. Remove the router from your coordinator.
  2. Erase the flash: esptool --port /dev/ttyACM0 erase-flash
  3. Flash this firmware and pair again.

Normal updates of this firmware keep the pairing and RF settings. Don't erase the flash routinely.

⚠️ Known limitations

  • XIAO ESP32-C6 only. The pins in the YAML (GPIO0/3/9/14/15) are XIAO-specific.
  • WiFi is off. WiFi and Zigbee share one radio on the C6; enabling WiFi makes the router miss Zigbee traffic.
  • A new TX power is applied after waiting up to 100 ms for the Zigbee stack; if that times out, the failure isn't reported. The attribute readback alone doesn't prove the radio took it.
  • No custom ZHA quirk is provided. ZHA routes fine but doesn't expose the RF controls.

πŸ™ Credits

πŸ“„ License

MIT Β© framethespace. The original project by firsttris has no license file; the Docker setup adapted from it remains credited to its author.

About

Zigbee repeater for the Seeed XIAO ESP32-C6 (ESPHome): battery monitoring and remote antenna / TX-power control from Zigbee2MQTT. Prebuilt firmware included.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages