docs: ADRs 0005-0007 for socket protocol 2

ADR 0005: the web UI and Home Assistant talk to the device over one
websocket protocol (ops on loop(), sync with next, a JSON envelope a
binary encoding can reuse, short keys described by a schema).
ADR 0006: two security modes and eleven HTTP routes on port 80.
ADR 0007: the firmware no longer updates itself from GitHub.

ADR 0002 is amended: its single-writer rule stands, but edits now get
one reply and reads no longer go through a ModelSnapshot.
SECURITY_PLAN.md is revised to match, and keeps the token key, login
backoff, CORS and Origin work. PROTOCOL.md starts as an outline, and
GLOSSARY.md gains the socket protocol and security terms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-22 20:50:33 +02:00
parent 539a49eb77
commit e25747d395
7 changed files with 202 additions and 52 deletions

51
docs/PROTOCOL.md Normal file
View file

@ -0,0 +1,51 @@
# Socket protocol 2
**Status:** outline. The type numbers, field keys and payloads are filled in with the firmware protocol core. Why the protocol looks like this: [ADR 0005](adr/0005-one-websocket-protocol.md). Terms: [GLOSSARY.md](../GLOSSARY.md).
## Connection
- `ws://host/ws` on port 80. Under Secured the upgrade needs `?apikey=<token>` ([ADR 0006](adr/0006-two-security-modes-eleven-http-routes.md)).
- Optional `?schema=<hash>`: the client already has that schema and doesn't need it again.
- Text frames only. A binary encoding, if one comes, would be asked for at the upgrade (for example `?enc=bin`) and use binary frames.
## Envelope
Every frame, in both directions, is one JSON object:
```json
{"v":2,"t":20,"id":17,"d":{}}
```
| Key | Meaning |
|---|---|
| `v` | protocol version |
| `t` | message type, a number from the table below |
| `id` | only on an op and its reply: the client's number for the op |
| `d` | the payload, with short keys from the schema |
Within `d`, booleans are `1`/`0` and fields at their default are left out. Keys are one or two characters from one dictionary shared by every type.
## Message types
| Range | Sender | Kinds |
|---|---|---|
| 1–15 | either | control: `hello`, `reply`, `next`, `sync`, `syncEnd`, `subscribe`, `unsubscribe` |
| 16–127 | client | ops: the commands, reads, model edits and `getSettings`/`saveSettings` |
| 128–255 | device | events: `shadeState`, `shade`, `group`, `room`, `*Removed`, `settingsChanged`, status, feeds |
A number is never reused. A retired type stays reserved.
## Connect sequence
1. The device sends `hello`: protocol version, firmware version, schema hash.
2. Unless the client passed the matching `?schema=`, the sync starts with the schema: types, fields (key, name, kind, default) and error codes.
3. Sync: header, rooms, shades, groups, repeaters, status. Each message is at most 4 KB, items are never split, and the client sends `next` after each one.
4. `syncEnd`. From then on, events keep the client current. Events also arrive during the sync, so the client upserts by id.
## Replies
Every op gets exactly one reply, to its sender only: `{"ok":1, ...}`, or `{"ok":0,"code":<n>,"desc":"..."}`. The error codes are bad request, unknown op, unauthorized, not found, busy, radio unavailable and internal; their numbers are in the schema.
## Feeds
`subscribe`/`unsubscribe` with a feed: `frames` (received remote frames) or `frequencyScan`. Feed events go only to subscribed clients.