# 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 |
| `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.

### 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.
