Files
2026-03-22 00:00:12 +13:00

12 KiB
Raw Permalink Blame History

LED Controller API

This document covers:

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

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.


UI behavior notes

The main UI has two modes controlled by the mode toggle:

  • Run mode: optimized for operation (tab/preset selection and profile apply).
  • Edit mode: shows editing/management controls (tabs, presets, patterns, colour palette, send presets, and profile management actions).

Profiles are available in both modes, but behavior differs:

  • Run mode: profile apply only.
  • Edit mode: profile create/clone/delete/apply.

POST /presets/send is wired to the Send Presets UI action, which is exposed in Edit mode.


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/<id>/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/<path> Static files under src/static/

WebSocket: /ws

Connect to ws://<host>:<port>/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, <id> 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 WiFi AP fields: saved_ssid, saved_password, saved_channel, active (Pi: active is always false).
POST /settings/wifi/ap Body: ssid (required), password, channel (111). 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": "<id>"}. Ensures a default current profile when possible.
GET /profiles/current {"id": "...", "profile": {...}}
GET /profiles/<id> Single profile. If <id> is current, same as /profiles/current.
POST /profiles Create profile. Body may include name and other fields. Optional seed_dj_tab (request-only) seeds a DJ tab + presets. New profiles always get a populated default tab. Returns { "<id>": { ... } } with status 201.
POST /profiles/<id>/apply Sets session current profile to <id>.
POST /profiles/<id>/clone Clone profile (tabs, palettes, presets). Body may include name.
PUT /profiles/current Update the current profile (from session).
PUT /profiles/<id> Update profile by id.
DELETE /profiles/<id> 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/<id> 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 { "<id>": { ... } }, 201.
PUT /presets/<id> Update preset (must belong to current profile).
DELETE /presets/<id> Delete preset.
POST /presets/send Push presets to the LED driver over the configured transport (see below).

POST /presets/send body:

{
  "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).

Stored preset records can include:

  • colors: resolved hex colours for editor/display.
  • palette_refs: optional array of palette indexes parallel to colors. If a slot contains an integer index, the colour is linked to the current profile palette at that index.

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 profiles tab list.
GET /tabs/<id> Tab JSON.
PUT /tabs/<id> Update tab.
DELETE /tabs/<id> Delete tab; can delete current to remove the active tab; updates profile tab list.
POST /tabs/<id>/set-current Sets current_tab cookie.
POST /tabs/<id>/clone Clone tab into current profile.

Palettes — /palettes

Method Path Description
GET /palettes Map of id → colour list.
GET /palettes/<id> {"colors": [...], "id": "<id>"}
POST /palettes Body may include colors. Returns palette object with id, 201.
PUT /palettes/<id> Update colours (name ignored).
DELETE /palettes/<id> Delete palette.

Groups — /groups

Method Path Description
GET /groups All groups.
GET /groups/<id> One group.
POST /groups Create; optional name and fields.
PUT /groups/<id> Update.
DELETE /groups/<id> Delete.

Scenes — /scenes

Method Path Description
GET /scenes All scenes.
GET /scenes/<id> One scene.
POST /scenes Create (body JSON stored on scene).
PUT /scenes/<id> Update.
DELETE /scenes/<id> Delete.

Sequences — /sequences

Method Path Description
GET /sequences All sequences.
GET /sequences/<id> One sequence.
POST /sequences Create; may use group_name, presets in body.
PUT /sequences/<id> Update.
DELETE /sequences/<id> 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/<id> One pattern.
POST /patterns Create (name, optional data).
PUT /patterns/<id> Update.
DELETE /patterns/<id> 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

{
  "v": "1",
  "presets": { },
  "select": { },
  "save": true,
  "default": "preset_id",
  "b": 255
}
  • 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 0255 (driver applies this in addition to per-preset brightness).

Preset object (wire / driver keys)

On the wire, presets use short keys (saves space in the ≤240-byte chunks):

Key Meaning Notes
p Pattern id off, on, blink, rainbow, pulse, transition, chase, circle
c Colours Array of "#RRGGBB" hex strings; converted to RGB on device
d Delay ms Default 100
b Preset brightness 0255; combined with global b on the device
a Auto true: run continuously; false: one step/cycle per “beat”
n1n6 Pattern parameters See below

The HTTP apps 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 / …).

Pattern-specific parameters (n1n6)

Rainbow

  • n1: Step increment on the colour wheel per update (default 1).

Pulse

  • n1: Attack (fade in) ms
  • n2: Hold ms
  • n3: Decay (fade out) ms
  • d: Off time between pulses ms

Transition

  • d: Transition duration ms

Chase

  • n1: LEDs with first colour
  • n2: LEDs with second colour
  • n3: Movement on even steps (may be negative)
  • n4: Movement on odd steps (may be negative)

Circle

  • n1: Head speed (LEDs/s)
  • n2: Max length
  • n3: Tail speed (LEDs/s)
  • n4: Min length

Select messages

{
  "select": {
    "device_name": ["preset_id"],
    "other_device": ["preset_id", 10]
  }
}
  • One element: select preset; step behavior follows driver rules (reset on off, etc.).
  • Two elements: explicit step for sync.

Beat and sync behavior

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

Example (compact preset map)

{
  "v": "1",
  "save": true,
  "presets": {
    "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": {
    "living-room": ["1"]
  }
}

Processing summary (driver)

  1. Reject if v != "1".
  2. Apply optional top-level b (global brightness).
  3. For each entry in presets, normalize colours and upsert preset by id.
  4. If this devices name appears in select, run selection (optional step).
  5. If default is set, store startup preset id.
  6. If save is set, persist presets.

Error handling (HTTP)

Controllers typically return JSON with an error string and 4xx/5xx status codes. Invalid JSON bodies often yield {"error": "Invalid JSON"}.


Notes

  • 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).