115 lines
3.0 KiB
Markdown
115 lines
3.0 KiB
Markdown
# 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.
|