Skip to content

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; use POST /start to 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:

{
  "running": true,
  "version": "1.0.0"
}

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)

{
  "status": "started"
}

Notes:

  • Config is read from disk at the time of the call. Use this (or POST /restart) after changing config.json to run with the new config.
  • Idempotent: calling /start again 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)

{
  "status": "stopped"
}

Notes:

  • After POST /stop, GET /status will report "running": false.
  • Idempotent: calling /stop again 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)

{
  "status": "restarted"
}

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)

{
  "files": [
    { "name": "video.mp4", "size": 12345, "modified": 1234567890.0 }
  ]
}

POST /uploads

Upload a file. Request body must be multipart/form-data with a file field.

Response (200 OK)

{
  "name": "video.mp4",
  "size": 12345,
  "modified": 1234567890.0
}

DELETE /uploads/{filename}

Deletes the named file from uploads/. Returns 404 if the file does not exist.

Response (200 OK)

{
  "status": "deleted",
  "name": "video.mp4"
}

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)

{
  "file": "server.log",
  "lines": ["line 1", "line 2"]
}

GET /devices/midi

Returns the list of USB MIDI devices (path and name) detected on the system.

Response (200 OK)

{
  "devices": [
    { "path": "1-1.1", "name": "Instruo Scion" }
  ]
}

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)

{
  "bulbs": ["10.42.0.81", "10.42.0.192"]
}

GET /bulbs/config

Returns the bulbs array from config.json (name and IP per bulb).

Response (200 OK)

{
  "bulbs": [
    { "name": "Red", "ip": "10.42.0.81" }
  ]
}

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)

{
  "status": "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 /status before or after sending start/stop/restart.
  • Start after editing config: POST /restart (or POST /stop then POST /start) so the new config is loaded.
  • Run server without processes: Start with --paused; use POST /start when 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 call POST /stop first for normal shutdown.