3.0 KiB
ESP-NOW binary protocol
See also: espnow-architecture.md (diagrams, flows, configuration).
All ESP-NOW datagrams and Pi↔bridge WebSocket frames use binary only (no JSON on the wire). Maximum ESP-NOW payload length: 250 bytes.
ESP-NOW packet
| Offset | Field |
|---|---|
| 0 | Magic 0x4C ('L') |
| 1 | Message type |
| 2… | Type-specific body |
Message types
| Value | Name | Direction |
|---|---|---|
0x01 |
ANNOUNCE |
Driver → broadcast |
0x02 |
GROUPS |
Controller → driver |
0x03 |
CMD |
Controller → driver |
0x04 |
GROUP_CMD |
Controller → broadcast |
0x05 |
PING_REQ |
Controller → broadcast |
0x06 |
PING_RSP |
Driver → controller (unicast) |
0x10 |
BRIDGE_CH |
Controller → broadcast |
ANNOUNCE (0x01)
Driver settings at boot. Sender MAC is taken from the ESP-NOW peer address (not repeated in the body).
| Field | Type |
|---|---|
| name_len | u8 |
| name | UTF-8 |
| num_leds | u16 LE |
| color_order | u8 enum: 0=rgb, 1=rbg, 2=grb, 3=gbr, 4=brg, 5=bgr |
| startup_mode | u8: 0=default, 1=last, 2=off |
| brightness | u8 0–255 |
| device_type | u8: 0=led |
GROUPS (0x02)
| Field | Type |
|---|---|
| count | u8 |
| × count | u8 id_len + UTF-8 group id |
CMD (0x03)
Bytes 2… are a v2 binary envelope (see src/util/binary_envelope.py): 5-byte header + presets/select/default blobs. Total packet ≤ 250 bytes.
GROUP_CMD (0x04)
| Field | Type |
|---|---|
| group_id_len | u8 |
| group_id | UTF-8 |
| cmd_envelope | v2 binary envelope |
Drivers apply the nested envelope only if group_id is in their stored group list.
PING_REQ (0x05)
Controller discovery ping (broadcast). Drivers reply with PING_RSP after a random delay (50–500 ms) to reduce ESP-NOW collisions.
| Field | Type |
|---|---|
| ping_id | u32 LE |
PING_RSP (0x06)
Unicast to the bridge/controller peer that sent the request (ESP-NOW source MAC of the received PING_REQ).
| Field | Type |
|---|---|
| ping_id | u32 LE |
| name_len | u8 |
| name | UTF-8 |
BRIDGE_CH (0x10)
| Field | Type |
|---|---|
| channel | u8 (1–11) |
Sets the bridge ESP32 STA channel (not forwarded to LED drivers as a command).
Pi ↔ bridge WebSocket frame
Binary WebSocket messages only.
| Offset | Field |
|---|---|
| 0 | flags: bit0 = broadcast destination; bit1 reserved |
| 1–6 | peer MAC (6 bytes); ignored if broadcast |
| 7… | ESP-NOW packet (magic + type + body) |
Broadcast destination uses peer ff:ff:ff:ff:ff:ff.
The bridge maintains at most 20 ESP-NOW peers (LRU eviction).
v2 command envelope
Native binary sections (no JSON). Header:
| Byte | Meaning |
|---|---|
| 0 | Version 2 |
| 1 | Brightness wire 0–127 (maps to 0–255); 128–255 = unchanged |
| 2 | Presets section length |
| 3 | Select section length |
| 4 | Default section length |
See binary_envelope.py for blob layouts.