# ESP-NOW binary protocol **See also:** [espnow-architecture.md](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.