# 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, color 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 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": "<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:**

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

Stored preset records can include:

- `colors`: resolved hex colors for editor/display.
- `palette_refs`: optional array of palette indexes parallel to `colors`. If a slot contains an integer index, the color 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 profile’s 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 → color list. |
| GET | `/palettes/<id>` | `{"colors": [...], "id": "<id>"}` |
| POST | `/palettes` | Body may include `colors`. Returns palette object with `id`, 201. |
| PUT | `/palettes/<id>` | Update colors (`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

```json
{
  "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 0–255 (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` | 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 |

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` / …).

### Pattern-specific parameters (`n1`–`n6`)

#### Rainbow
- **`n1`**: Step increment on the color 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 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 speed (LEDs/s)  
- **`n2`**: Max length  
- **`n3`**: Tail speed (LEDs/s)  
- **`n4`**: Min length  

### Select messages

```json
{
  "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)

```json
{
  "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 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.

---

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