Zigbee firmware for an ESP32-C6 that turns a wired Somfy awning ("marquee") motor into a Zigbee Home Automation cover, controllable from Zigbee2MQTT, ZHA, or any other Zigbee 3.0 coordinator.
The awning has no position feedback and can also be driven by its original remote, so this firmware deliberately does not try to track or report a position — it exposes Open / Close / Stop plus a live motor-status sensor, which is the most that can be reported honestly. See Design notes for the reasoning.
Status: LED test mode. The relay outputs are not wired up yet, so movement is currently indicated on the board's onboard RGB LED instead of switching relays. Everything else — the Zigbee endpoint, command handling, mutual exclusion and the safety timeout — is the real implementation. See Switching to relays.
| Signal | GPIO | Notes |
|---|---|---|
| Extend (Down/Close) | 2 | Relay 1 — Somfy yellow wire to ground. Active high. Not driven in LED test mode. |
| Retract (Up/Open) | 3 | Relay 2 — Somfy green wire to ground. Active high. Not driven in LED test mode. |
| Button | 9 | Onboard BOOT button, active low. Long press = factory reset. |
| Status LED | 8 | Onboard addressable RGB LED. |
- Board: ESP32-C6 DevKitC (4 MB flash — see the note in
sdkconfig.defaults; some variants ship 8 MB, and flashing an 8 MB image to a 4 MB part panics at boot). - Motor interface: Somfy 1822609. White wire is common ground; shorting yellow to ground extends, green to ground retracts. Hold-to-move — the motor runs only while the contact is closed, and stops itself at its end limits.
- The two relay outputs are mutually exclusive in software; they are never energised at the same time.
| LED | Meaning |
|---|---|
| Green | Extending (Down/Close) |
| Blue | Retracting (Up/Open) |
| Off | Stopped |
Single HA endpoint (ID 1), device type Window Covering, joining as an End Device.
| Cluster | Role |
|---|---|
| Basic (0x0000) | Manufacturer Crunchypancake, model MarqueeCover |
| Identify (0x0003) | Standard identify |
| Window Covering (0x0102) | WindowCoveringType = Awning. Handles Up/Open, Down/Close, Stop. No lift-percentage attributes — the device cannot measure position, so it does not advertise one. |
| Multistate Input (0x0012) | PresentValue reports live motor status: 0 stopped, 1 opening (retracting), 2 closing (extending). NumberOfStates = 3. |
Command mapping: Up/Open → retract (roll in), Down/Close → extend (roll out).
A safety timeout of 12 s (SAFETY_TIMEOUT_MS) force-releases the outputs and
reports stopped if no Stop command arrives. The motor's own end-limit switches
are the primary protection; this is the backstop.
Requires ESP-IDF v5.2 or newer (developed against v5.4.1). Zigbee dependencies are pulled automatically by the IDF Component Manager.
. $HOME/esp/esp-idf/export.sh
idf.py set-target esp32c6
idf.py build
idf.py -p /dev/ttyACM0 flash monitorIf your board has 8 MB of flash, change CONFIG_ESPTOOLPY_FLASHSIZE_4MB to
CONFIG_ESPTOOLPY_FLASHSIZE_8MB in sdkconfig.defaults and delete sdkconfig
before rebuilding.
- Put your coordinator into "permit join" mode.
- Power on the device. A factory-new device starts network steering automatically and retries every second until it joins.
- On success the log prints the PAN ID, channel and short address.
Hold the BOOT button (GPIO 9) for 5 seconds or more and release. The device leaves the network, clears its Zigbee persistent data and reboots into pairing mode. Short presses do nothing.
The device uses only standard clusters, so a coordinator will pair it without
help — but Z2M needs a small external converter to expose the motor-status
sensor as an entity. Copy docs/zigbee2mqtt/marquee-cover.mjs
into your Z2M external_converters directory and restart Z2M.
It exposes the standard cover controls plus a motor_status enum
(stopped / opening / closing) reported independently of the commanded state.
The LED indicator lives entirely in set_outputs() in
main/app_driver.c. To drive real relays:
- Replace the
led_strip_*calls inset_outputs()withgpio_set_level()onGPIO_EXTEND/GPIO_RETRACT, and configure those pins as outputs indriver_init(). - Move the safety cut back into
safety_timer_cb()—gpio_set_level()is safe to call there, which drops the queue round-trip. - Drop the
espressif/led_stripdependency frommain/idf_component.yml.
CMakeLists.txt ESP-IDF project definition
partitions.csv Partition table (OTA + Zigbee storage)
sdkconfig.defaults Target, flash size, Zigbee End Device config
main/
app_main.c Zigbee stack init, endpoint/cluster setup, ZCL handlers
app_driver.c/.h Output control, mutual exclusion, safety timer
app_button.c/.h Debounced long-press factory-reset button
docs/plans/ Design and implementation notes (incl. the earlier
Matter-over-Thread design this replaced)
docs/zigbee2mqtt/ External converter for Zigbee2MQTT
- No position reporting. The cover appears as open/close/stop only. Sliders in some dashboards will not be available. This is intentional.
- Attribute reporting for the motor status has to be configured by the coordinator after binding; the supplied Z2M converter does this.
- A factory reset performed while the device is not joined to a network may not reboot the device — it is already in pairing mode, so this is harmless.
MIT — see LICENSE.