ESPHome external component for the Adeo ceiling fan + light (sold by Leroy Merlin), controlled over UART through its internal Adeo MCU.
Tested on an ESP32-C3. The C++ builds cleanly against the current ESPHome dev
branch (the target for upstream contributions).
- Light: on/off, brightness (1–100 %), color temperature (2700–6500 K)
- Fan: on/off, 6 speed levels, direction (forward/reverse),
Breezepreset mode - Bidirectional: state is kept in sync when the unit is operated with its physical remote — the changes are reflected back into Home Assistant
The receiver's WiFi dongle is a SK-WVFC-ESP V1.0 board built around an ESP32-C3-MINI-1 module (4 MB flash). Its stock firmware is a Matter firmware (ADEO Enki SDK).
The dongle plugs into the receiver's WIFI-USB port through a USB-A connector,
but this is not USB. The manufacturer repurposes the USB-A connector as a
plain 4-pin header:
| USB-A pin | Actual role |
|---|---|
| VBUS | Power — 3.3 V (not 5 V) |
| D− / D+ (middle two) | UART to the MCU (GPIO6 / GPIO7) |
| GND | Ground |
⚠️ Never plug the dongle into a computer's USB port. The port's 5 V would destroy the ESP32-C3, whose rail is 3.3 V.
external_components:
- source: github://rclsilver/esphome-adeo-mcu
components: [adeo_mcu]uart:
id: mcu_uart
tx_pin: GPIO7
rx_pin: GPIO6
baud_rate: 115200
adeo_mcu:
id: adeo
uart_id: mcu_uart
light:
- platform: adeo_mcu
name: "Ceiling Light"
adeo_mcu_id: adeo
restore_mode: RESTORE_DEFAULT_OFF
fan:
- platform: adeo_mcu
name: "Ceiling Fan"
adeo_mcu_id: adeo
restore_mode: RESTORE_DEFAULT_OFFThe UART bus must define both tx_pin and rx_pin (the protocol is
bidirectional) and run at 115200 baud.
A complete, ready-to-flash configuration (WiFi, API, OTA, web server, secrets)
is available in examples/ceiling-fan.yaml, with a
matching examples/secrets.yaml.example.
- id (Optional): ID used for code generation, referenced by the light/fan platforms.
- uart_id (Optional): ID of the UART bus the MCU is connected to.
- name (Required): Name of the entity.
- adeo_mcu_id (Optional): ID of the parent
adeo_mcuhub. - All other options from the ESPHome Light and Fan base components.
The dongle ships with stock Matter firmware, so the first flash has to be done over a wired serial connection.
- A 3.3 V USB-to-UART adapter (e.g. CP2102). Make sure its
3V3pin really outputs 3.3 V. - A few female Dupont wires.
esptool.py(installed with ESPHome, orpip install esptool).
The back of the board exposes a header labelled
GND · RXD · TXD · IO9 · IO8 · EN · 3.3V.
Wire it to the CP2102 adapter with TX and RX crossed:
| Adapter | SK-WVFC-ESP board |
|---|---|
GND |
GND |
TXD |
RXD |
RXD |
TXD |
3V3 |
3.3V |
+5V |
do not connect |
The ESP32-C3 enters download mode based on its strapping pins at power-up.
On this board EN is tied high (no hardware reset is possible), so follow this
exact order:
- Adapter unplugged from the PC.
- Connect
IO9→GND(hold). - Connect
IO8→3.3V(hold high). - Double-check the crossed TX/RX wiring above.
- Plug the adapter into the PC — powering up in this state is what arms the bootloader.
- Once connected,
IO9can be released.
Because the CP2102 has no DTR/RTS lines and
ENis fixed high,esptoolcannot auto-reset the chip: you must use--before no_reset --after no_resetand set the strapping pins before applying power.
esptool.py --port /dev/ttyUSB0 --before no_reset --after no_reset chip_idExpected output: Chip is ESP32-C3 (QFN32), Features: WiFi, BLE, Embedded Flash 4MB.
If you get No serial data received, it is almost always because TX/RX are
swapped (swap them and retry) or the chip is not in bootloader mode (redo the
sequence).
The stock firmware is a Matter firmware (ADEO Enki SDK). Backing it up lets you roll back later (e.g. to re-commission the device in Matter). This backup is irreplaceable once the flash is overwritten.
esptool.py --port /dev/ttyUSB0 -b 460800 read_flash 0x0 0x400000 stock-backup.binCheck the file is exactly 4194304 bytes (4 MB) and keep it somewhere safe:
ls -l stock-backup.binWith the chip in bootloader mode, compile and upload over serial:
esphome run your-device.yamlPick the serial port (/dev/ttyUSB0) when prompted. If the upload fails on sync,
power-cycle the board (toggle 3.3 V with IO9 tied to GND) right before retrying.
Subsequent uploads happen wirelessly over OTA (
esphome runfinds the device on the network) — no more wiring needed.
Disconnect the programming rig and plug the dongle back into the receiver's
WIFI-USB port: it gets its 3.3 V power and its UART (GPIO6/GPIO7) reconnects to
the MCU. The device boots ESPHome, joins WiFi, and exposes its entities.
To restore the Matter firmware (bootloader mode required, as above):
esptool.py --port /dev/ttyUSB0 write_flash 0x0 stock-backup.binFrames are exchanged over UART at 115200 baud with the following structure:
| Byte(s) | Description |
|---|---|
0x55 |
Start of frame |
0x00 |
Reserved |
| 2 bytes | Frame type (uint16, little-endian) |
| 4 bytes | Payload length (uint32, little-endian) |
| N bytes | Payload |
| 1 byte | CRC-8 (poly 0x07, init 0x00) over every preceding byte |
Every mapped frame uses a payload of the form [channel][value…]:
- channel — first payload byte:
0x01for light frames,0x02for fan frames. - value — the remaining byte(s), interpreted per frame type below.
| Type | Channel | Direction | Value (payload byte 1+) |
|---|---|---|---|
0x0001 |
0x01 |
TX/RX | Light power (0x01 = on, 0x00 = off) |
0x0002 |
0x01 |
TX/RX | Light brightness (0–100) |
0x0012 |
0x01 |
TX/RX | Light color temperature in Kelvin (uint32 LE) |
0x0020 |
0x02 |
TX/RX | Fan power (0x04 = on, 0x00 = off) |
0x0021 |
0x02 |
TX/RX | Fan speed (0–100, percent) |
0x0023 |
0x02 |
TX/RX | Fan breeze mode (0x02 = on, 0x00 = off) |
0x0024 |
0x02 |
TX/RX | Fan direction (0x01 = reverse, 0x00 = forward) |
For example, turning the light on transmits the payload 01 01, and setting the
fan to its reverse direction transmits 02 01.
This project follows ESPHome's dual-licensing scheme: the C++/runtime files
(.h, .cpp) are under GPLv3, and the Python code and everything else are
under the MIT license. See LICENSE.