Signed-off-by: Peter Siegmund <developer@mars3142.org>
23 KiB
System Control - API Documentation
This document describes all REST API endpoints and WebSocket messages required for the ESP32 firmware implementation.
Table of Contents
REST API Endpoints
Capabilities
Get Device Capabilities
Returns the device capabilities. Used to determine which features are available.
- URL:
/api/capabilities - Method:
GET - Response:
{
"thread": true
}
| Field | Type | Description |
|---|---|---|
| thread | boolean | Whether Thread/Matter features are enabled |
Notes:
- If
threadistrue, the UI shows Matter device management and Scenes - If
threadisfalseor the endpoint is unavailable, these features are hidden - The client can also force-enable features via URL parameter
?thread=true
WiFi
Scan Networks
Scans for available WiFi networks.
- URL:
/api/wifi/scan - Method:
GET - Response:
[
{
"ssid": "NetworkName",
"rssi": -45
},
{
"ssid": "AnotherNetwork",
"rssi": -72
}
]
| Field | Type | Description |
|---|---|---|
| ssid | string | Network name (SSID) |
| rssi | number | Signal strength in dBm |
Save WiFi Configuration
Saves WiFi credentials and initiates connection.
- URL:
/api/wifi/config - Method:
POST - Content-Type:
application/json - Request Body:
{
"ssid": "NetworkName",
"password": "SecretPassword123"
}
| Field | Type | Required | Description |
|---|---|---|---|
| ssid | string | Yes | Network name |
| password | string | No | Network password |
- Response:
200 OKon success,400 Bad Requeston error
Get WiFi Status
Returns current WiFi connection status.
- URL:
/api/wifi/status - Method:
GET - Response:
{
"connected": true,
"ssid": "NetworkName",
"ip": "192.168.1.100",
"rssi": -45
}
| Field | Type | Description |
|---|---|---|
| connected | boolean | Whether connected to a network |
| ssid | string | Connected network name (if connected) |
| ip | string | Assigned IP address (if connected) |
| rssi | number | Signal strength in dBm (if connected) |
Light Control
Set Light Power
Turns the main light on or off.
- URL:
/api/light/power - Method:
POST - Content-Type:
application/json - Request Body:
{
"on": true
}
| Field | Type | Required | Description |
|---|---|---|---|
| on | boolean | Yes | true = on, false = off |
- Response:
200 OKon success
Set Thunder Effect
Turns the thunder/lightning effect on or off.
- URL:
/api/light/thunder - Method:
POST - Content-Type:
application/json - Request Body:
{
"on": true
}
| Field | Type | Required | Description |
|---|---|---|---|
| on | boolean | Yes | true = on, false = off |
- Response:
200 OKon success
Notes:
- When enabled, random lightning flashes are triggered
- Can be combined with any light mode
- Thunder effect stops automatically when light is turned off
Set Light Mode
Sets the lighting mode.
- URL:
/api/light/mode - Method:
POST - Content-Type:
application/json - Request Body:
{
"mode": "simulation"
}
| Field | Type | Required | Description |
|---|---|---|---|
| mode | string | Yes | One of: day, night, simulation |
- Response:
200 OKon success
Set Active Schema
Sets the active schema for simulation mode.
- URL:
/api/light/schema - Method:
POST - Content-Type:
application/json - Request Body:
{
"schema": "schema_01.csv"
}
| Field | Type | Required | Description |
|---|---|---|---|
| schema | string | Yes | Schema filename: schema_01.csv, schema_02.csv, etc. |
- Response:
200 OKon success
Get Light Status
Returns current light status (alternative to WebSocket).
- URL:
/api/light/status - Method:
GET - Response:
{
"on": true,
"thunder": false,
"mode": "simulation",
"schema": "schema_01.csv",
"color": {
"r": 255,
"g": 240,
"b": 220
}
}
| Field | Type | Description |
|---|---|---|
| on | boolean | Current power state |
| thunder | boolean | Current thunder effect state |
| mode | string | Current mode (day/night/simulation) |
| schema | string | Active schema filename |
| color | object | Current RGB color being displayed |
LED Configuration
Get LED Configuration
Returns the current LED segment configuration.
- URL:
/api/wled/config - Method:
GET - Response:
{
"segments": [
{
"name": "Main Light",
"start": 0,
"leds": 60
},
{
"name": "Accent Light",
"start": 60,
"leds": 30
}
]
}
| Field | Type | Description |
|---|---|---|
| segments | array | List of LED segments |
| segments[].name | string | Optional segment name |
| segments[].start | number | Start LED index (0-based) |
| segments[].leds | number | Number of LEDs in this segment |
Save LED Configuration
Saves the LED segment configuration.
- URL:
/api/wled/config - Method:
POST - Content-Type:
application/json - Request Body:
{
"segments": [
{
"name": "Main Light",
"start": 0,
"leds": 60
},
{
"name": "Accent Light",
"start": 60,
"leds": 30
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
| segments | array | Yes | List of LED segments (can be empty) |
| segments[].name | string | No | Optional segment name |
| segments[].start | number | Yes | Start LED index (0-based) |
| segments[].leds | number | Yes | Number of LEDs in this segment |
- Response:
200 OKon success,400 Bad Requeston validation error
Notes:
- Segments define how the LED strip is divided into logical groups
- Changes are persisted to NVS (non-volatile storage)
- Each segment can be controlled independently in the light schema
Schema
Load Schema
Loads a schema file.
- URL:
/api/schema/{filename} - Method:
GET - URL Parameters:
filename: Schema file name (e.g.,schema_01.csv)
- Response: CSV text data
255,240,220,0,100,250
255,230,200,0,120,250
...
The CSV format has 48 rows (one per 30-minute interval) with 6 values per row:
| Column | Description | Range |
|---|---|---|
| 1 | Red (R) | 0-255 |
| 2 | Green (G) | 0-255 |
| 3 | Blue (B) | 0-255 |
| 4 | Value 1 (V1) - custom value | 0-255 |
| 5 | Value 2 (V2) - custom value | 0-255 |
| 6 | Value 3 (V3) - custom value | 0-255 |
Save Schema
Saves a schema file.
- URL:
/api/schema/{filename} - Method:
POST - Content-Type:
text/csv - URL Parameters:
filename: Schema file name (e.g.,schema_01.csv)
- Request Body: CSV text data (same format as above)
- Response:
200 OKon success
Devices
Thread device management via the Iris component. Only available when CONFIG_IRIS_ENABLED=y. All device identifiers are 16-character hex EUI-64 strings (e.g., "aabbccddeeff0011").
Scan for New Joiners
Returns Thread devices that have completed the Commissioner/Joiner flow but have not yet been paired (i.e., not in iris_devices.bin). These appear in the OLED menu under "neue Geräte" for manual confirmation.
- URL:
/api/devices/scan - Method:
GET - Response:
[
{
"id": "aabbccddeeff0011",
"name": "H2-eeff0011",
"capabilities": 3
}
]
| Field | Type | Description |
|---|---|---|
| id | string | EUI-64 (16-char hex) |
| name | string | Auto-generated name (H2-<last4>) or from H2 |
| capabilities | number | IRIS_CAP_* bitmask (see README-thread.md) |
Pair Device
Provisions a new joiner into the paired device list. Queries the H2 for its capabilities via CoAP, stores the device in iris_devices.bin. Only available on the Master unit.
- URL:
/api/devices/pair - Method:
POST - Content-Type:
application/json - Request Body:
{
"id": "aabbccddeeff0011",
"name": "Wagen 42"
}
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | EUI-64 from scan |
| name | string | No | User-defined name (defaults to id) |
- Response:
200 OKwith{"status":"ok"}on success - Error:
403if called on Backup unit,500if pairing failed
Get Paired Devices
Returns all devices stored in iris_devices.bin with their current runtime state.
- URL:
/api/devices/paired - Method:
GET - Response:
[
{
"id": "aabbccddeeff0011",
"name": "Wagen 42",
"capabilities": 3,
"state": 1,
"online": true
}
]
| Field | Type | Description |
|---|---|---|
| id | string | EUI-64 (16-char hex) |
| name | string | User-defined display name |
| capabilities | number | IRIS_CAP_* bitmask |
| state | number | IRIS_STATE_* bitmask (last polled value) |
| online | boolean | Whether the device responded in the last poll |
Update Device Name
Renames a paired device (persisted to iris_devices.bin).
- URL:
/api/devices/update - Method:
POST - Content-Type:
application/json - Request Body:
{
"id": "aabbccddeeff0011",
"name": "Wagen 07"
}
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | EUI-64 |
| name | string | Yes | New display name |
- Response:
200 OKwith{"status":"ok"},404if device not found
Unpair Device
Removes a paired device from iris_devices.bin.
- URL:
/api/devices/unpair - Method:
POST - Content-Type:
application/json - Request Body:
{
"id": "aabbccddeeff0011"
}
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | EUI-64 |
- Response:
200 OKwith{"status":"ok"},404if device not found
Toggle Device Capability (Unicast)
Sends a unicast CoAP POST /toggle to one specific device. Intended for individual control from the OLED menu.
- URL:
/api/devices/toggle - Method:
POST - Content-Type:
application/json - Request Body:
{
"id": "aabbccddeeff0011",
"cap": 1
}
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | EUI-64 of the target device |
| cap | number | Yes | Exactly one IRIS_CAP_* bit (e.g., 1 = inner light) |
- Response:
200 OKwith{"status":"ok"},404if device not found
Set All Devices (Multicast)
Sends an explicit on/off state to all devices simultaneously via CoAP multicast (ff03::1). Use this instead of toggle to ensure all devices reach the same state.
- URL:
/api/devices/toggle_all - Method:
POST - Content-Type:
application/json - Request Body:
{
"cap": 1,
"state": 1
}
| Field | Type | Required | Description |
|---|---|---|---|
| cap | number | Yes | Exactly one IRIS_CAP_* bit |
| state | number | Yes | 1 = activate, 0 = deactivate |
- Response:
200 OKwith{"status":"ok"}
Note: This is a NON (non-confirmable) CoAP multicast — delivery is best-effort. No per-device acknowledgement.
Scenes
Not yet implemented. All scene endpoints return
{"status":"not_implemented"}. The schema below describes the planned API.
Get All Scenes
Returns all configured scenes.
- URL:
/api/scenes - Method:
GET - Response:
[
{
"id": "scene-1",
"name": "Evening Mood",
"icon": "🌅",
"actions": {
"light": "on",
"mode": "simulation",
"schema": "schema_02.csv",
"devices": [
{
"id": "matter-001",
"state": "on"
}
]
}
},
{
"id": "scene-2",
"name": "Night Mode",
"icon": "🌙",
"actions": {
"light": "on",
"mode": "night"
}
}
]
Scene Object:
| Field | Type | Description |
|---|---|---|
| id | string | Unique scene identifier |
| name | string | Scene name |
| icon | string | Emoji icon for the scene |
| actions | object | Actions to execute (see below) |
Actions Object:
| Field | Type | Optional | Description |
|---|---|---|---|
| light | string | Yes | "on" or "off" |
| mode | string | Yes | "day", "night", or "simulation" |
| schema | string | Yes | Schema filename (e.g., schema_01.csv) |
| devices | array | Yes | Array of device actions (see below) |
Device Action Object:
| Field | Type | Description |
|---|---|---|
| id | string | Device ID |
| state | string | "on" or "off" |
Create/Update Scene
Creates a new scene or updates an existing one.
- URL:
/api/scenes - Method:
POST - Content-Type:
application/json - Request Body:
{
"id": "scene-1",
"name": "Evening Mood",
"icon": "🌅",
"actions": {
"light": "on",
"mode": "simulation",
"schema": "schema_02.csv",
"devices": [
{
"id": "matter-001",
"state": "on"
}
]
}
}
- Response:
200 OKon success
Delete Scene
Deletes a scene.
- URL:
/api/scenes - Method:
DELETE - Content-Type:
application/json - Request Body:
{
"id": "scene-1"
}
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Scene ID |
- Response:
200 OKon success
Activate Scene
Executes all actions of a scene.
- URL:
/api/scenes/activate - Method:
POST - Content-Type:
application/json - Request Body:
{
"id": "scene-1"
}
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Scene ID |
- Response:
200 OKon success
Input
Simulate Button Input
Remotely triggers a button action as if pressed on the physical device. Useful for controlling the menu UI via the web interface.
- URL:
/api/input - Method:
POST - Content-Type:
application/json - Request Body:
{
"action": "button_up",
"value": ""
}
| Field | Type | Required | Description |
|---|---|---|---|
| action | string | Yes | Action name to execute (see list below) |
| value | string | No | Optional value passed to the action handler (default: empty) |
Available actions:
| Action | Description |
|---|---|
button_up |
Navigate up in the menu |
button_down |
Navigate down in the menu |
button_left |
Adjust value left (e.g. cycle selection) |
button_right |
Adjust value right |
button_select |
Activate the selected menu item |
button_back |
Go back to the previous screen |
- Response:
200 OKon success,400 Bad Requestifactionfield is missing
Notes:
- Actions are dispatched via the heimdall action manager
- Any registered action can be triggered, not just button actions
- The value field is passed through to the action handler but is currently unused for button actions
WebSocket
Connection
- URL:
ws://{host}/wsorwss://{host}/ws - Protocol: JSON messages
The WebSocket connection is used for real-time status updates. The client should reconnect automatically if the connection is lost (recommended: 3 second delay).
Client to Server Messages
Request Status
Requests the current system status.
{
"type": "getStatus"
}
Server to Client Messages
Status Update
Sent in response to getStatus or when status changes.
{
"type": "status",
"on": true,
"mode": "simulation",
"schema": "schema_01.csv",
"color": {
"r": 255,
"g": 240,
"b": 220
}
}
| Field | Type | Description |
|---|---|---|
| type | string | Always "status" |
| on | boolean | Light power state |
| mode | string | Current mode (day/night/simulation) |
| schema | string | Active schema filename |
| color | object | Current RGB color (optional) |
Color Update
Sent when the current color changes (during simulation).
{
"type": "color",
"r": 255,
"g": 200,
"b": 150
}
| Field | Type | Description |
|---|---|---|
| type | string | Always "color" |
| r | number | Red value (0-255) |
| g | number | Green value (0-255) |
| b | number | Blue value (0-255) |
WiFi Status Update
Sent when WiFi connection status changes.
{
"type": "wifi",
"connected": true,
"ip": "192.168.1.100",
"rssi": -45
}
| Field | Type | Description |
|---|---|---|
| type | string | Always "wifi" |
| connected | boolean | Whether connected to a network |
| ip | string | Assigned IP address (if connected) |
| rssi | number | Signal strength in dBm (if connected) |
Static Files
The web interface files should be served from the /spiffs/www/ directory:
| Path | Description |
|---|---|
/ |
Serves index.html (or captive.html in AP mode) |
/index.html |
Main HTML file (full interface) |
/captive.html |
Captive portal (WiFi setup only) |
/css/shared.css |
Shared styles for all pages |
/css/index.css |
Styles for main interface |
/css/captive.css |
Styles for captive portal |
/js/wifi-shared.js |
Shared WiFi configuration logic |
/js/*.js |
JavaScript modules |
Captive Portal
When the ESP32 is in Access Point (AP) mode (no WiFi configured or connection failed), it should serve the captive portal:
Behavior
-
AP Mode Activation:
- ESP32 creates an access point (e.g., "marklin-setup")
- DNS server redirects all requests to the ESP32's IP (captive portal detection)
-
Captive Portal Detection:
- Respond to common captive portal detection URLs:
/generate_204(Android)/hotspot-detect.html(Apple)/connecttest.txt(Windows)
- Return redirect or serve
captive.html
- Respond to common captive portal detection URLs:
-
Serving Files in AP Mode:
/→captive.html/captive.html→ Captive portal page/js/wifi-shared.js→ WiFi functions- API endpoints remain the same (
/api/wifi/*)
-
After Successful Configuration:
- ESP32 attempts to connect to the configured network
- If successful, switch to Station mode and serve
index.html - If failed, remain in AP mode
Recommended AP Settings
| Setting | Value |
|---|---|
| SSID | marklin-setup |
| Password | None (open) or marklin |
| IP Address | 192.168.4.1 |
| Gateway | 192.168.4.1 |
| Subnet | 255.255.255.0 |
Error Handling
All endpoints should return appropriate HTTP status codes:
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request (invalid input) |
| 404 | Not Found (resource doesn't exist) |
| 500 | Internal Server Error |
Error responses should include a JSON body with an error message:
{
"error": "Description of what went wrong"
}