Base URL: http://0.0.0.0:3010 (or http://192.168.1.111:3010 on your network)
This server acts as an intermediary between your frontend and multiple WLED devices. It manages WebSocket connections to each board, provides REST endpoints for control, and broadcasts real-time state updates via Server-Sent Events (SSE).
GET /
Check if server is running.
Response:
200 OK
"WLED Server Running"
GET /boards
Retrieve the current state of all registered WLED boards.
Response:
200 OK
[
{
"id": "mikaels-bed",
"ip": "192.168.1.172",
"on": true,
"brightness": 128,
"color": [255, 128, 0],
"effect": 0
},
{
"id": "living-room",
"ip": "192.168.1.175",
"on": false,
"brightness": 200,
"color": [0, 255, 0],
"effect": 5
}
]Notes:
- Returns an array of board states
- Each board queries its actor via oneshot channel
- May return 500 if actor communication fails
POST /boards
Dynamically add a new WLED board to the server.
Request Body:
{
"id": "living-room",
"ip": "192.168.1.175"
}Response:
201 CREATED
Error Responses:
409 CONFLICT- Board with this ID already exists500 INTERNAL_SERVER_ERROR- Failed to acquire lock
Notes:
- Board actor is spawned immediately
- Starts WebSocket connection to WLED device
- Board persists until server restart (not saved to config)
DELETE /boards/:id
Remove a board from the server.
Example:
DELETE /boards/living-room
Response:
204 NO_CONTENT
Error Responses:
404 NOT_FOUND- Board ID does not exist500 INTERNAL_SERVER_ERROR- Failed to acquire lock
Notes:
- Board actor stops automatically when channel is dropped
- WebSocket connection closes gracefully
POST /board/:id/toggle
Toggle the on/off state of a specific board.
Example:
POST /board/mikaels-bed/toggle
Request Body: None (empty body)
Response:
200 OK
Error Responses:
404 NOT_FOUND- Board ID does not exist500 INTERNAL_SERVER_ERROR- Failed to send command
Notes:
- Sends
{"on": true/false}to WLED - State change broadcasts via SSE
POST /board/:id/brightness
Set the brightness level (0-255).
Request Body:
{
"brightness": 180
}Response:
200 OK
Error Responses:
404 NOT_FOUND- Board ID does not exist500 INTERNAL_SERVER_ERROR- Failed to send command
Notes:
- Value clamped to u8 (0-255)
- Sends
{"bri": 180}to WLED - State change broadcasts via SSE
POST /board/:id/color
Set the RGB color.
Request Body:
{
"r": 255,
"g": 128,
"b": 0
}Response:
200 OK
Error Responses:
404 NOT_FOUND- Board ID does not exist500 INTERNAL_SERVER_ERROR- Failed to send command
Notes:
- Each value clamped to u8 (0-255)
- Sends
{"seg":[{"col":[[r,g,b]]}]}to WLED - Applies to first segment only
- State change broadcasts via SSE
POST /board/:id/effect
Set the WLED effect by ID.
Request Body:
{
"effect": 12
}Response:
200 OK
Error Responses:
404 NOT_FOUND- Board ID does not exist500 INTERNAL_SERVER_ERROR- Failed to send command
Notes:
- Effect ID is WLED-specific (0-based index)
- Sends
{"seg":[{"fx": 12}]}to WLED - Applies to first segment only
- State change broadcasts via SSE
POST /board/:id/preset
Apply a saved WLED preset.
Request Body:
{
"preset": 3
}Response:
200 OK
Error Responses:
404 NOT_FOUND- Board ID does not exist500 INTERNAL_SERVER_ERROR- Failed to send command
Notes:
- Preset ID is 1-based (matches WLED UI)
- Sends
{"ps": 3}to WLED - Presets can change multiple settings at once (brightness, color, effect, etc.)
- Configure presets in WLED web interface
- State change broadcasts via SSE
GET /events
Subscribe to real-time state updates from all boards.
Response:
200 OK
Content-Type: text/event-stream
data: {"type":"connected","message":"Connected to WLED server"}
data: {"type":"state_update","board_id":"mikaels-bed","state":{"id":"mikaels-bed","ip":"192.168.1.172","on":true,"brightness":128,"color":[255,128,0],"effect":0}}
data: {"type":"state_update","board_id":"living-room","state":{"id":"living-room","ip":"192.168.1.175","on":false,"brightness":200,"color":[0,255,0],"effect":5}}
Event Types:
-
connected
- Sent immediately on connection
- Confirms SSE stream is active
-
state_update
- Sent whenever a board's state changes
- Includes full board state
- Triggered by:
- API commands (POST endpoints)
- WLED device changes (buttons, app, etc.)
- WebSocket messages from WLED
Notes:
- Keep connection open to receive updates
- Server sends keep-alive pings automatically
- Reconnect if connection drops
- Connect to SSE stream first:
const eventSource = new EventSource('http://127.0.0.1:3010/events');
eventSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.type === 'connected') {
console.log('Connected to WLED server');
} else if (data.type === 'state_update') {
updateBoardUI(data.board_id, data.state);
}
});- Fetch initial board list:
const response = await fetch('http://127.0.0.1:3010/boards');
const boards = await response.json();
boards.forEach(board => renderBoard(board));Add a new board:
async function addBoard(id, ip) {
const response = await fetch('http://127.0.0.1:3010/boards', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, ip })
});
if (response.status === 201) {
console.log('Board added successfully');
} else if (response.status === 409) {
alert('Board already exists');
}
}Remove a board:
async function deleteBoard(id) {
const response = await fetch(`http://127.0.0.1:3010/boards/${id}`, {
method: 'DELETE'
});
if (response.status === 204) {
console.log('Board deleted');
}
}Toggle power:
async function togglePower(boardId) {
await fetch(`http://127.0.0.1:3010/board/${boardId}/toggle`, {
method: 'POST'
});
// State update will arrive via SSE
}Set brightness:
async function setBrightness(boardId, brightness) {
await fetch(`http://127.0.0.1:3010/board/${boardId}/brightness`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ brightness })
});
}Set color:
async function setColor(boardId, r, g, b) {
await fetch(`http://127.0.0.1:3010/board/${boardId}/color`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ r, g, b })
});
}Set effect:
async function setEffect(boardId, effect) {
await fetch(`http://127.0.0.1:3010/board/${boardId}/effect`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ effect })
});
}- Each board has a dedicated actor (tokio task)
- Actors manage WebSocket connections to WLED devices
- Auto-reconnects on connection loss (5-second delay)
- Server maintains in-memory state for each board
- State updates from WLED devices broadcast to all SSE clients
- No persistent storage (boards from config loaded on startup)
- Thread-safe state via
Arc<RwLock<HashMap>> - Message passing via
mpscchannels to actors - Broadcast channel for SSE events
The server handles errors gracefully:
- Missing config file → starts with no boards
- Port in use → exits with error message
- Lock poisoning → returns 500 to client
- Board not found → returns 404
- Duplicate board → returns 409
boards.toml (optional, loads on startup):
[[boards]]
id = "mikaels-bed"
ip = "192.168.1.172"
[[boards]]
id = "living-room"
ip = "192.168.1.175"A SvelteKit-based PWA frontend is available in /frontend.
Features:
- Dark mode interface
- WLED-style circular HSV color picker
- Brightness control (0-255)
- All 186 WLED effects (alphabetically sorted)
- Toggle switches for power control
- Add/delete boards dynamically
- Collapsible board cards
- Touch-friendly for mobile devices
- Network-accessible (works on any device on your local network)
Running the frontend:
cd frontend
bun install
bun run dev -- --hostAccess at: http://<your-ip>:5173
Building for production:
cd frontend
bun run buildStatic files output to /frontend/build
- Server-Sent Events integration in frontend (real-time updates without refresh)
- PWA manifest and service worker (install as app)
- Docker + nginx deployment
- Scenes (multi-board synchronized control)
- Persistent storage for dynamically added boards
- Authentication/authorization
- HTTPS support
- Board discovery via mDNS