API¶
The control application exposes an HTTP API so external services can query and control the installation (start/stop processes, check status). The server runs by default on http://0.0.0.0:8000 and can be started with uv run server.py (see Implementation for the Supervisor and --paused).
All endpoints return JSON. There is no authentication in the current implementation.
Base URL and server options¶
- Default:
http://<host>:8000(host can be the Pi’s IP or hostname). - CLI:
uv run server.py [--host HOST] [--port PORT] [--paused]. --paused— Start the API without starting the installation processes; usePOST /startto run them later.- Root:
GET /serves the static webapp from the bundled webapp directory.
Endpoints¶
GET /status¶
Returns whether the Supervisor is currently running the installation processes.
Response (200 OK)
| Field | Type | Description |
|---|---|---|
running |
boolean | true if processes are running, false if stopped (e.g. after POST /stop or when started with --paused and not yet started). |
version |
string | Application version. |
Example:
POST /start¶
Starts the installation processes: loads config.json, creates shared state, and runs the supervision loop (controller, video, and LED processes). If the Supervisor is already running, this is a no-op.
Response (200 OK)
Notes:
- Config is read from disk at the time of the call. Use this (or
POST /restart) after changingconfig.jsonto run with the new config. - Idempotent: calling
/startagain while running does nothing.
POST /stop¶
Stops all installation processes: signals the supervision loop to exit, terminates controller/video/LED processes, and shuts down shared state. If the Supervisor is already stopped, this is a no-op.
Response (200 OK)
Notes:
- After
POST /stop,GET /statuswill report"running": false. - Idempotent: calling
/stopagain while stopped does nothing.
POST /restart¶
Stops the installation (if running) and then starts it again. Equivalent to POST /stop followed by POST /start. Use this to apply config changes: the new run loads the current config.json and clears any previous in-memory state.
Response (200 OK)
Notes:
- If the installation was already stopped, this still performs a
start()(loads config and runs processes). - Safe to call from automation or scripts when config has been updated.
- The response may include
Connection: close; clients that reuse connections should handle closure.
GET /uploads¶
Returns the list of uploaded files (e.g. videos) in the uploads/ directory.
Response (200 OK)
POST /uploads¶
Upload a file. Request body must be multipart/form-data with a file field.
Response (200 OK)
DELETE /uploads/{filename}¶
Deletes the named file from uploads/. Returns 404 if the file does not exist.
Response (200 OK)
GET /logs¶
Returns the list of log files in the installation’s log directory. Only files whose name ends with .log or contains .log. are listed; path traversal is guarded.
Response (200 OK)
{
"files": [
{ "name": "server.log", "size": 12345, "modified": 1234567890.0, "streamable": true }
]
}
| Field | Description |
|---|---|
streamable |
true only for logs that support streaming (e.g. server.log, http.log, midi-sysex.log). |
Returns {"files": []} if the log directory is missing or empty.
GET /logs/stream¶
Server-Sent Events stream of a log file. Query param file (default "server.log"). Only certain logs can be streamed; the 400 response lists the allowed names. Use this to tail a log in real time.
Errors: 400 if the requested file is not in the streamable set. 404 if the file does not exist.
Response (200 OK) — text/event-stream. Each event: data: {"line": "..."} (JSON with one line of log text).
GET /logs/download/{filename}¶
Downloads the named log file. Invalid filename (e.g. path traversal, .., /, \) returns 400. File must exist under the log directory or 404 is returned.
Response (200 OK) — file body as text/plain with Content-Disposition filename.
GET /logs/{filename}¶
Returns the last lines of the named log file. Query param tail (default 100, clamped between 1 and 5000). Same 400/404 rules as log download for the filename.
Response (200 OK)
GET /devices/midi¶
Returns the list of USB MIDI devices (path and name) detected on the system.
Response (200 OK)
GET /controllers/config¶
Returns the controllers array from config.json.
Response (200 OK)
{
"controllers": [
{ "name": "Red Rock", "usb_path": "1-1.1", "bulb_ips": ["10.42.0.81"], "led_index": 0, "color": [255, 0, 0] }
]
}
PUT /controllers/config¶
Replaces the controllers array in config.json. Body: { "controllers": [...] }. Use POST /restart to apply changes.
Response (200 OK) — returns the same { "controllers": [...] } as sent.
GET /bulbs/discover¶
Discovers WiZ bulbs on the network (broadcast on wlan0). May take a few seconds.
Response (200 OK)
GET /bulbs/config¶
Returns the bulbs array from config.json (name and IP per bulb).
Response (200 OK)
PUT /bulbs/config¶
Replaces the bulbs array in config.json. Body: { "bulbs": [...] }. Entries without a non-empty name are ignored.
Response (200 OK) — returns the saved { "bulbs": [...] }.
POST /bulbs/{ip}/blink¶
Blinks the bulb at the given IP (full white, 0.5 s) for identification.
Response (200 OK)
GET /settings¶
Returns the settings object from config.json (LED count, timeouts, brightness, video filenames, etc.).
Response (200 OK) — JSON object with keys such as led_count, idle_timeout, fade_duration, video_idle_filename, video_active_filename, etc.
PUT /settings¶
Updates settings in config.json. Body is a partial or full settings object; only a fixed set of setting keys are accepted and their types (e.g. int, float, bool) are enforced. Use POST /restart to apply changes to the running installation.
Response (200 OK) — returns the full settings object after update.
Typical usage¶
- Check state:
GET /statusbefore or after sending start/stop/restart. - Start after editing config:
POST /restart(orPOST /stopthenPOST /start) so the new config is loaded. - Run server without processes: Start with
--paused; usePOST /startwhen the installation should run. - Shut down cleanly: Sending SIGTERM/SIGINT to the server triggers lifespan shutdown and runs
supervisor.stop()so all child processes are terminated; no need to callPOST /stopfirst for normal shutdown.