From edadb40cb695547930124479dc0893e839b72831 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sat, 21 Mar 2026 23:15:44 +1300 Subject: [PATCH] docs: rewrite API reference for current HTTP and driver flows Made-with: Cursor --- docs/API.md | 446 ++++++++++++++++++++++++++++------------------------ 1 file changed, 240 insertions(+), 206 deletions(-) diff --git a/docs/API.md b/docs/API.md index 2914ca6..735b48b 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,263 +1,297 @@ -# LED Driver ESPNow API Documentation +# LED Controller API -This document describes the ESPNow message format for controlling LED driver devices. +This document covers: -## Message Format +1. **HTTP and WebSocket** exposed by the Raspberry Pi app (`src/main.py`) — profiles, presets, transport send, and related resources. +2. **LED driver JSON** — the compact message format sent over the serial→ESP-NOW bridge to devices (same logical API as ESP-NOW payloads). -All messages are JSON objects sent via ESPNow with the following structure: +Default listen address: `0.0.0.0`. Port defaults to **80**; override with the `PORT` environment variable (see `pipenv run run`). + +All JSON APIs use `Content-Type: application/json` for bodies and responses unless noted. + +--- + +## Session and scoping + +Several routes use **`@with_session`**: the server stores a **current profile** in the session (cookie). Endpoints that scope data to “the current profile” (notably **`/presets`**) only return or mutate presets whose `profile_id` matches that session value. + +Profiles are selected with **`POST /profiles//apply`**, which sets `current_profile` in the session. + +--- + +## Static pages and assets + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/` | Main UI (`templates/index.html`) | +| GET | `/settings` | Settings page (`templates/settings.html`) | +| GET | `/favicon.ico` | Empty response (204) | +| GET | `/static/` | Static files under `src/static/` | + +--- + +## WebSocket: `/ws` + +Connect to **`ws://:/ws`**. + +- Send **JSON**: the object is forwarded to the transport (serial bridge → ESP-NOW) as JSON. Optional key **`to`**: 12-character hex MAC address; if present it is removed from the object and the payload is sent to that peer; otherwise the default destination is used. +- Send **non-JSON text**: forwarded as raw bytes with the default address. +- On send failure, the server may reply with `{"error": "Send failed"}`. + +--- + +## HTTP API by resource + +Below, `` values are string identifiers used by the JSON stores (numeric strings in practice). + +### Settings — `/settings` + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/settings` | Full settings object (from `settings.json` / `Settings` model). | +| PUT | `/settings/settings` | Merge keys into settings and save. Returns `{"message": "Settings updated successfully"}`. | +| GET | `/settings/wifi/ap` | Saved Wi‑Fi AP fields: `saved_ssid`, `saved_password`, `saved_channel`, `active` (Pi: `active` is always false). | +| POST | `/settings/wifi/ap` | Body: `ssid` (required), `password`, `channel` (1–11). Persists AP-related settings. | +| GET | `/settings/page` | Serves `templates/settings.html` (same page as `GET /settings` from the root app, for convenience). | + +### Profiles — `/profiles` + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/profiles` | `{"profiles": {...}, "current_profile_id": ""}`. Ensures a default current profile when possible. | +| GET | `/profiles/current` | `{"id": "...", "profile": {...}}` | +| GET | `/profiles/` | Single profile. If `` is `current`, same as `/profiles/current`. | +| POST | `/profiles` | Create profile. Body may include `name` and other fields. Returns `{ "": { ... } }` with status 201. | +| POST | `/profiles//apply` | Sets session current profile to ``. | +| POST | `/profiles//clone` | Clone profile (tabs, palettes, presets). Body may include `name`. | +| PUT | `/profiles/current` | Update the current profile (from session). | +| PUT | `/profiles/` | Update profile by id. | +| DELETE | `/profiles/` | Delete profile. | + +### Presets — `/presets` + +Scoped to **current profile** in session (see above). + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/presets` | Map of preset id → preset object for the current profile only. | +| GET | `/presets/` | One preset, 404 if missing or wrong profile. | +| POST | `/presets` | Create preset; server assigns id and sets `profile_id`. Body fields stored on the preset. Returns `{ "": { ... } }`, 201. | +| PUT | `/presets/` | Update preset (must belong to current profile). | +| DELETE | `/presets/` | Delete preset. | +| POST | `/presets/send` | Push presets to the LED driver over the configured transport (see below). | + +**`POST /presets/send` body:** + +```json +{ + "preset_ids": ["1", "2"], + "save": true, + "default": "1", + "destination_mac": "aabbccddeeff" +} +``` + +- **`preset_ids`** (or **`ids`**): non-empty list of preset ids to include. +- **`save`**: if true, the outgoing message includes `"save": true` so the driver may persist presets (default true). +- **`default`**: optional preset id string; forwarded as top-level `"default"` in the driver message (startup selection on device). +- **`destination_mac`** (or **`to`**): optional 12-character hex MAC for unicast; omitted uses the transport default (e.g. broadcast). + +Response on success includes `presets_sent`, `messages_sent` (chunking splits payloads so each JSON string stays ≤ 240 bytes). + +### Tabs — `/tabs` + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/tabs` | `tabs`, `tab_order`, `current_tab_id`, `profile_id` for the session-backed profile. | +| GET | `/tabs/current` | Current tab from cookie/session. | +| POST | `/tabs` | Create tab; optional JSON `name`, `names`, `presets`; can append to current profile’s tab list. | +| GET | `/tabs/` | Tab JSON. | +| PUT | `/tabs/` | Update tab. | +| DELETE | `/tabs/` | Delete tab; can delete `current` to remove the active tab; updates profile tab list. | +| POST | `/tabs//set-current` | Sets `current_tab` cookie. | +| POST | `/tabs//clone` | Clone tab into current profile. | + +### Palettes — `/palettes` + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/palettes` | Map of id → color list. | +| GET | `/palettes/` | `{"colors": [...], "id": ""}` | +| POST | `/palettes` | Body may include `colors`. Returns palette object with `id`, 201. | +| PUT | `/palettes/` | Update colors (`name` ignored). | +| DELETE | `/palettes/` | Delete palette. | + +### Groups — `/groups` + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/groups` | All groups. | +| GET | `/groups/` | One group. | +| POST | `/groups` | Create; optional `name` and fields. | +| PUT | `/groups/` | Update. | +| DELETE | `/groups/` | Delete. | + +### Scenes — `/scenes` + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/scenes` | All scenes. | +| GET | `/scenes/` | One scene. | +| POST | `/scenes` | Create (body JSON stored on scene). | +| PUT | `/scenes/` | Update. | +| DELETE | `/scenes/` | Delete. | + +### Sequences — `/sequences` + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/sequences` | All sequences. | +| GET | `/sequences/` | One sequence. | +| POST | `/sequences` | Create; may use `group_name`, `presets` in body. | +| PUT | `/sequences/` | Update. | +| DELETE | `/sequences/` | Delete. | + +### Patterns — `/patterns` + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/patterns/definitions` | Contents of `pattern.json` (pattern metadata for the UI). | +| GET | `/patterns` | All pattern records. | +| GET | `/patterns/` | One pattern. | +| POST | `/patterns` | Create (`name`, optional `data`). | +| PUT | `/patterns/` | Update. | +| DELETE | `/patterns/` | Delete. | + +--- + +## LED driver message format (transport / ESP-NOW) + +Messages are JSON objects. The Pi **`build_message()`** helper (`src/util/espnow_message.py`) produces the same shape sent over serial and forwarded by the ESP32 bridge. + +### Top-level fields ```json { "v": "1", - "presets": { ... }, - "select": { ... } + "presets": { }, + "select": { }, + "save": true, + "default": "preset_id", + "b": 255 } ``` -### Version Field +- **`v`** (required): Must be `"1"` or the driver ignores the message. +- **`presets`**: Map of **preset id** (string) → preset object (see below). Optional **`name`** field on each value is accepted for display; the driver keys presets by map key. +- **`select`**: Map of **device name** (as in device settings) → `[ "preset_id" ]` or `[ "preset_id", step ]`. +- **`save`**: If present (e.g. true), the driver may persist presets to flash after applying. +- **`default`**: Preset id string to use as startup default on the device. +- **`b`**: Optional **global** brightness 0–255 (driver applies this in addition to per-preset brightness). -- **`v`** (required): Message version, must be `"1"`. Messages with other versions are ignored. +### Preset object (wire / driver keys) -## Presets +On the wire, presets use **short keys** (saves space in the ≤240-byte chunks): -Presets define LED patterns with their configuration. Each preset has a name and contains pattern-specific settings. +| Key | Meaning | Notes | +|-----|---------|--------| +| `p` | Pattern id | `off`, `on`, `blink`, `rainbow`, `pulse`, `transition`, `chase`, `circle` | +| `c` | Colors | Array of `"#RRGGBB"` hex strings; converted to RGB on device | +| `d` | Delay ms | Default 100 | +| `b` | Preset brightness | 0–255; combined with global `b` on the device | +| `a` | Auto | `true`: run continuously; `false`: one step/cycle per “beat” | +| `n1`–`n6` | Pattern parameters | See below | -### Preset Structure +The HTTP app’s **`POST /presets/send`** path builds this from stored presets via **`build_preset_dict()`** (long names like `pattern` / `colors` in the DB are translated to `p` / `c` / …). -```json -{ - "presets": { - "preset_name": { - "pattern": "pattern_type", - "colors": ["#RRGGBB", ...], - "delay": 100, - "brightness": 127, - "auto": true, - "n1": 0, - "n2": 0, - "n3": 0, - "n4": 0, - "n5": 0, - "n6": 0 - } - } -} -``` - -### Preset Fields - -- **`pattern`** (required): Pattern type. Options: - - `"off"` - Turn off all LEDs - - `"on"` - Solid color - - `"blink"` - Blinking pattern - - `"rainbow"` - Rainbow color cycle - - `"pulse"` - Pulse/fade pattern - - `"transition"` - Color transition - - `"chase"` - Chasing pattern - - `"circle"` - Circle loading pattern - -- **`colors`** (optional): Array of hex color strings (e.g., `"#FF0000"` for red). Default: `["#FFFFFF"]` - - Colors are automatically converted from hex to RGB and reordered based on device color order setting - - Supports multiple colors for patterns that use them - -- **`delay`** (optional): Delay in milliseconds between pattern updates. Default: `100` - -- **`brightness`** (optional): Brightness level (0-255). Default: `127` - -- **`auto`** (optional): Auto mode flag. Default: `true` - - `true`: Pattern runs continuously - - `false`: Pattern advances one step per beat (manual mode) - -- **`n1` through `n6`** (optional): Pattern-specific numeric parameters. Default: `0` - - See pattern-specific documentation below - -### Pattern-Specific Parameters +### Pattern-specific parameters (`n1`–`n6`) #### Rainbow -- **`n1`**: Step increment (how many color wheel positions to advance per update). Default: `1` +- **`n1`**: Step increment on the color wheel per update (default 1). #### Pulse -- **`n1`**: Attack time in milliseconds (fade in) -- **`n2`**: Hold time in milliseconds (full brightness) -- **`n3`**: Decay time in milliseconds (fade out) -- **`delay`**: Delay time in milliseconds (off between pulses) +- **`n1`**: Attack (fade in) ms +- **`n2`**: Hold ms +- **`n3`**: Decay (fade out) ms +- **`d`**: Off time between pulses ms #### Transition -- **`delay`**: Transition duration in milliseconds +- **`d`**: Transition duration ms #### Chase -- **`n1`**: Number of LEDs with first color -- **`n2`**: Number of LEDs with second color -- **`n3`**: Movement amount on even steps (can be negative) -- **`n4`**: Movement amount on odd steps (can be negative) +- **`n1`**: LEDs with first color +- **`n2`**: LEDs with second color +- **`n3`**: Movement on even steps (may be negative) +- **`n4`**: Movement on odd steps (may be negative) #### Circle -- **`n1`**: Head movement rate (LEDs per second) -- **`n2`**: Maximum length -- **`n3`**: Tail movement rate (LEDs per second) -- **`n4`**: Minimum length +- **`n1`**: Head speed (LEDs/s) +- **`n2`**: Max length +- **`n3`**: Tail speed (LEDs/s) +- **`n4`**: Min length -## Select Messages - -Select messages control which preset is active on which device. The format uses a list to support step synchronization. - -### Select Format +### Select messages ```json { "select": { - "device_name": ["preset_name"], - "device_name2": ["preset_name2", step_value] + "device_name": ["preset_id"], + "other_device": ["preset_id", 10] } } ``` -### Select Fields +- One element: select preset; step behavior follows driver rules (reset on `off`, etc.). +- Two elements: explicit **step** for sync. -- **`select`**: Object mapping device names to selection lists - - **Key**: Device name (as configured in device settings) - - **Value**: List with one or two elements: - - `["preset_name"]` - Select preset (uses default step behavior) - - `["preset_name", step]` - Select preset with explicit step value (for synchronization) +### Beat and sync behavior -### Step Synchronization +- Sending **`select`** again with the **same** preset name acts as a **beat** (advances manual patterns / restarts generators per driver logic). +- Choosing **`off`** resets step as a sync point; then selecting a pattern aligns step 0 across devices unless a step is passed explicitly. -The step value allows precise synchronization across multiple devices: - -- **Without step**: `["preset_name"]` - - If switching to different preset: step resets to 0 - - If selecting "off" pattern: step resets to 0 - - If selecting same preset (beat): step is preserved, pattern restarts - -- **With step**: `["preset_name", 10]` - - Explicitly sets step to the specified value - - Useful for synchronizing multiple devices to the same step - -### Beat Functionality - -Calling `select()` again with the same preset name acts as a "beat" - it restarts the pattern generator: - -- **Single-tick patterns** (rainbow, chase in manual mode): Advance one step per beat -- **Multi-tick patterns** (pulse in manual mode): Run through full cycle per beat - -Example beat sequence: -```json -// Beat 1 -{"select": {"device1": ["rainbow_preset"]}} - -// Beat 2 (same preset = beat) -{"select": {"device1": ["rainbow_preset"]}} - -// Beat 3 -{"select": {"device1": ["rainbow_preset"]}} -``` - -## Synchronization - -### Using "off" Pattern - -Selecting the "off" pattern resets the step counter to 0, providing a synchronization point: - -```json -{ - "select": { - "device1": ["off"], - "device2": ["off"] - } -} -``` - -After all devices are "off", switching to a pattern ensures they all start from step 0: - -```json -{ - "select": { - "device1": ["rainbow_preset"], - "device2": ["rainbow_preset"] - } -} -``` - -### Using Step Parameter - -For precise synchronization, use the step parameter: - -```json -{ - "select": { - "device1": ["rainbow_preset", 10], - "device2": ["rainbow_preset", 10], - "device3": ["rainbow_preset", 10] - } -} -``` - -All devices will start at step 10 and advance together on subsequent beats. - -## Complete Example +### Example (compact preset map) ```json { "v": "1", + "save": true, "presets": { - "red_blink": { - "pattern": "blink", - "colors": ["#FF0000"], - "delay": 200, - "brightness": 255, - "auto": true - }, - "rainbow_manual": { - "pattern": "rainbow", - "delay": 100, - "n1": 2, - "auto": false - }, - "pulse_slow": { - "pattern": "pulse", - "colors": ["#00FF00"], - "delay": 500, - "n1": 1000, - "n2": 500, - "n3": 1000, - "auto": false + "1": { + "name": "Red blink", + "p": "blink", + "c": ["#FF0000"], + "d": 200, + "b": 255, + "a": true, + "n1": 0, "n2": 0, "n3": 0, "n4": 0, "n5": 0, "n6": 0 } }, "select": { - "device1": ["red_blink"], - "device2": ["rainbow_manual", 0], - "device3": ["pulse_slow"] + "living-room": ["1"] } } ``` -## Message Processing +--- -1. **Version Check**: Messages with `v != "1"` are rejected -2. **Preset Processing**: Presets are created or updated (upsert behavior) -3. **Color Conversion**: Hex colors are converted to RGB tuples and reordered based on device color order -4. **Selection**: Devices select their assigned preset, optionally with step value +## Processing summary (driver) -## Best Practices +1. Reject if `v != "1"`. +2. Apply optional top-level **`b`** (global brightness). +3. For each entry in **`presets`**, normalize colors and upsert preset by id. +4. If this device’s **`name`** appears in **`select`**, run selection (optional step). +5. If **`default`** is set, store startup preset id. +6. If **`save`** is set, persist presets. -1. **Always include version**: Set `"v": "1"` in all messages -2. **Use "off" for sync**: Select "off" pattern to synchronize devices before starting patterns -3. **Beats for manual mode**: Send select messages repeatedly with same preset name to advance manual patterns -4. **Step for precision**: Use step parameter when exact synchronization is required -5. **Color format**: Always use hex strings (`"#RRGGBB"`), conversion is automatic +--- -## Error Handling +## Error handling (HTTP) -- Invalid version: Message is ignored -- Missing preset: Selection fails, device keeps current preset -- Invalid pattern: Selection fails, device keeps current preset -- Missing colors: Pattern uses default white color -- Invalid step: Step value is used as-is (may cause unexpected behavior) +Controllers typically return JSON with an **`error`** string and 4xx/5xx status codes. Invalid JSON bodies often yield `{"error": "Invalid JSON"}`. + +--- ## Notes -- Colors are automatically converted from hex strings to RGB tuples -- Color order reordering happens automatically based on device settings -- Step counter wraps around (0-255 for rainbow, unbounded for others) -- Manual mode patterns stop after one step/cycle, waiting for next beat -- Auto mode patterns run continuously until changed +- **Human-readable preset fields** (`pattern`, `colors`, `delay`, …) are fine in the **web app / database**; the **send path** converts them to **`p` / `c` / `d`** for the driver. +- For a copy of the older long-key reference, see **`led-driver/docs/API.md`** in this repo (conceptually the same behavior; wire format prefers short keys).