Add complete REST API for lighting control
- Migrated from websockets to aiohttp for unified HTTP/WebSocket server - Added REST endpoints: /api/pattern, /api/parameters, /api/state, /api/tempo/reset - Implemented color palette API with 8-slot system and selected colors - First selected color (index 0) is used as primary RGB for patterns - All operations now available via simple HTTP requests (no WebSocket needed) - Added comprehensive documentation: FRONTEND_API.md, COLOR_PALETTE_API.md - Added test scripts: test_rest_api.sh, test_color_patterns.py - Updated test/test_control_server.py for new /ws WebSocket path - Configuration persistence via lighting_config.json - Pattern parameters (n1-n4, brightness, delay) controllable via API - WebSocket still available at /ws for legacy support
This commit is contained in:
407
COLOR_PALETTE_API.md
Normal file
407
COLOR_PALETTE_API.md
Normal file
@@ -0,0 +1,407 @@
|
||||
# Color Palette REST API - UI Integration Guide
|
||||
|
||||
## Overview
|
||||
The lighting control server provides a REST API for managing an 8-color palette with 2 selected colors. This is designed for UI integration to allow users to:
|
||||
- View the current 8 colors in the palette
|
||||
- Edit any of the 8 colors
|
||||
- Select which 2 colors are active (for patterns that use selected colors)
|
||||
|
||||
Configuration is automatically persisted to `lighting_config.json`.
|
||||
|
||||
## Base URLs
|
||||
|
||||
The API is available on **two ports** for flexibility:
|
||||
|
||||
**Primary (same as WebSocket):**
|
||||
```
|
||||
http://<server-ip>:8765/api/color-palette
|
||||
```
|
||||
|
||||
**Backward Compatibility:**
|
||||
```
|
||||
http://<server-ip>:8766/api/color-palette
|
||||
```
|
||||
|
||||
**Default IPs:**
|
||||
- Remote: `http://10.42.0.1:8765` (Pi server)
|
||||
- Local: `http://localhost:8765` (testing)
|
||||
|
||||
---
|
||||
|
||||
## Quick Start for UI Developers
|
||||
|
||||
### Recommended Usage Pattern
|
||||
|
||||
1. **On UI Load:** `GET /api/color-palette` to populate the color picker
|
||||
2. **When User Edits a Color:** `POST /api/color-palette` with updated palette
|
||||
3. **When User Selects Colors:** `POST /api/color-palette` with new selected_indices
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### GET /api/color-palette
|
||||
|
||||
**Purpose:** Get the current palette state (for populating UI on load)
|
||||
|
||||
#### Request
|
||||
```http
|
||||
GET /api/color-palette HTTP/1.1
|
||||
```
|
||||
|
||||
#### Response (200 OK)
|
||||
```json
|
||||
{
|
||||
"palette": [
|
||||
{"r": 255, "g": 0, "b": 0}, // Slot 0: Red
|
||||
{"r": 0, "g": 255, "b": 0}, // Slot 1: Green
|
||||
{"r": 0, "g": 0, "b": 255}, // Slot 2: Blue
|
||||
{"r": 255, "g": 255, "b": 0}, // Slot 3: Yellow
|
||||
{"r": 255, "g": 0, "b": 255}, // Slot 4: Magenta
|
||||
{"r": 0, "g": 255, "b": 255}, // Slot 5: Cyan
|
||||
{"r": 255, "g": 128, "b": 0}, // Slot 6: Orange
|
||||
{"r": 255, "g": 255, "b": 255} // Slot 7: White
|
||||
],
|
||||
"selected_indices": [0, 1] // Currently selected: Red and Green
|
||||
}
|
||||
```
|
||||
|
||||
#### Response Fields
|
||||
- **`palette`**: Array of exactly 8 color objects
|
||||
- Each color has `r`, `g`, `b` (integers 0-255)
|
||||
- Index corresponds to palette slot (0-7)
|
||||
- **`selected_indices`**: Array of exactly 2 integers (0-7)
|
||||
- Indicates which 2 palette slots are currently active
|
||||
- **The first selected color (index 0) is used as the primary RGB color for patterns**
|
||||
- The second selected color (index 1) is available for future pattern features
|
||||
|
||||
---
|
||||
|
||||
### POST /api/color-palette (or PUT)
|
||||
|
||||
**Purpose:** Update palette colors and/or selected colors
|
||||
|
||||
#### Request Body (JSON)
|
||||
|
||||
Both fields are **optional** - send only what you want to update:
|
||||
|
||||
```json
|
||||
{
|
||||
"palette": [...], // Optional: Update all 8 colors
|
||||
"selected_indices": [0, 3] // Optional: Change selected colors
|
||||
}
|
||||
```
|
||||
|
||||
#### Success Response (200 OK)
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"palette": {
|
||||
"palette": [...],
|
||||
"selected_indices": [0, 3]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Response (400/500)
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Palette must be an array of 8 colors"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Use Case 1: Load Palette on UI Startup
|
||||
|
||||
```javascript
|
||||
async function loadPalette() {
|
||||
const response = await fetch('http://10.42.0.1:8765/api/color-palette');
|
||||
const data = await response.json();
|
||||
|
||||
// data.palette = array of 8 colors
|
||||
// data.selected_indices = [index1, index2]
|
||||
|
||||
return data;
|
||||
}
|
||||
```
|
||||
|
||||
### Use Case 2: User Edits a Single Color
|
||||
|
||||
When user changes color in slot 3 to purple:
|
||||
|
||||
```javascript
|
||||
async function updateColor(slotIndex, r, g, b) {
|
||||
// Get current palette
|
||||
const current = await fetch('http://10.42.0.1:8765/api/color-palette')
|
||||
.then(res => res.json());
|
||||
|
||||
// Update the specific slot
|
||||
const newPalette = [...current.palette];
|
||||
newPalette[slotIndex] = {r, g, b};
|
||||
|
||||
// Send updated palette
|
||||
await fetch('http://10.42.0.1:8765/api/color-palette', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({palette: newPalette})
|
||||
});
|
||||
}
|
||||
|
||||
// Example: Change slot 3 to purple (128, 0, 128)
|
||||
updateColor(3, 128, 0, 128);
|
||||
```
|
||||
|
||||
### Use Case 3: User Selects Different Active Colors
|
||||
|
||||
When user selects slots 2 and 5:
|
||||
|
||||
```javascript
|
||||
async function selectColors(index1, index2) {
|
||||
await fetch('http://10.42.0.1:8765/api/color-palette', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
selected_indices: [index1, index2]
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Example: Select blue (slot 2) and cyan (slot 5)
|
||||
selectColors(2, 5);
|
||||
```
|
||||
|
||||
### Use Case 4: Reset to Default Palette
|
||||
|
||||
```javascript
|
||||
async function resetPalette() {
|
||||
const defaultPalette = [
|
||||
{r: 255, g: 0, b: 0}, // Red
|
||||
{r: 0, g: 255, b: 0}, // Green
|
||||
{r: 0, g: 0, b: 255}, // Blue
|
||||
{r: 255, g: 255, b: 0}, // Yellow
|
||||
{r: 255, g: 0, b: 255}, // Magenta
|
||||
{r: 0, g: 255, b: 255}, // Cyan
|
||||
{r: 255, g: 128, b: 0}, // Orange
|
||||
{r: 255, g: 255, b: 255} // White
|
||||
];
|
||||
|
||||
await fetch('http://10.42.0.1:8765/api/color-palette', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
palette: defaultPalette,
|
||||
selected_indices: [0, 1]
|
||||
})
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Validation & Error Handling
|
||||
|
||||
### Validation Rules
|
||||
|
||||
**Palette Array:**
|
||||
- Must contain **exactly 8** color objects
|
||||
- Each color must have `r`, `g`, `b` fields
|
||||
- RGB values must be **integers between 0-255**
|
||||
|
||||
**Selected Indices:**
|
||||
- Must be an array of **exactly 2** integers
|
||||
- Each index must be **between 0-7** (inclusive)
|
||||
- Can be the same index twice (e.g., `[3, 3]`)
|
||||
|
||||
### Error Responses
|
||||
|
||||
```javascript
|
||||
// Example: Invalid palette length
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Palette must be an array of 8 colors"
|
||||
}
|
||||
|
||||
// Example: Invalid RGB value
|
||||
{
|
||||
"status": "error",
|
||||
"message": "RGB values must be 0-255"
|
||||
}
|
||||
|
||||
// Example: Invalid index
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Selected indices must be 0-7"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Persistence
|
||||
|
||||
- **Automatic Save:** All changes are immediately saved to `lighting_config.json`
|
||||
- **Automatic Load:** Server loads saved config on startup
|
||||
- **Default Values:** If no config file exists, server initializes with default palette
|
||||
|
||||
**Default Palette:**
|
||||
```javascript
|
||||
[
|
||||
{r: 255, g: 0, b: 0}, // Slot 0: Red
|
||||
{r: 0, g: 255, b: 0}, // Slot 1: Green
|
||||
{r: 0, g: 0, b: 255}, // Slot 2: Blue
|
||||
{r: 255, g: 255, b: 0}, // Slot 3: Yellow
|
||||
{r: 255, g: 0, b: 255}, // Slot 4: Magenta
|
||||
{r: 0, g: 255, b: 255}, // Slot 5: Cyan
|
||||
{r: 255, g: 128, b: 0}, // Slot 6: Orange
|
||||
{r: 255, g: 255, b: 255} // Slot 7: White
|
||||
]
|
||||
// Default selected: [0, 1] (Red and Green)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing & Debugging
|
||||
|
||||
### Test API Connection
|
||||
|
||||
```bash
|
||||
# Quick test - get current palette
|
||||
curl http://10.42.0.1:8765/api/color-palette
|
||||
|
||||
# Pretty print with jq
|
||||
curl http://10.42.0.1:8765/api/color-palette | jq
|
||||
|
||||
# Test update
|
||||
curl -X POST http://10.42.0.1:8765/api/color-palette \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"selected_indices": [3, 6]}'
|
||||
```
|
||||
|
||||
### Browser DevTools
|
||||
|
||||
```javascript
|
||||
// Test in browser console
|
||||
fetch('http://10.42.0.1:8765/api/color-palette')
|
||||
.then(r => r.json())
|
||||
.then(console.log);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
### CORS (Cross-Origin Requests)
|
||||
|
||||
**Note:** The API does not currently include CORS headers.
|
||||
|
||||
If accessing from a different origin (e.g., UI on different domain):
|
||||
1. Add CORS middleware to the server (contact backend team)
|
||||
2. Use a proxy server
|
||||
3. Host UI on same origin as API
|
||||
|
||||
### Helper Function: RGB to Hex
|
||||
|
||||
```javascript
|
||||
function rgbToHex({r, g, b}) {
|
||||
return '#' + [r, g, b]
|
||||
.map(x => x.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
||||
|
||||
// Usage
|
||||
const color = {r: 255, g: 128, b: 0};
|
||||
const hex = rgbToHex(color); // "#ff8000"
|
||||
```
|
||||
|
||||
### Helper Function: Hex to RGB
|
||||
|
||||
```javascript
|
||||
function hexToRgb(hex) {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
} : null;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const rgb = hexToRgb('#ff8000'); // {r: 255, g: 128, b: 0}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
### Server Environment Variables (`.env`)
|
||||
|
||||
```bash
|
||||
# Server host (bind to all interfaces)
|
||||
CONTROL_SERVER_HOST=0.0.0.0
|
||||
|
||||
# Primary port (WebSocket + HTTP API)
|
||||
CONTROL_SERVER_PORT=8765
|
||||
|
||||
# Additional HTTP API port (optional, for backward compatibility)
|
||||
HTTP_API_PORT=8766
|
||||
```
|
||||
|
||||
### WebSocket Endpoint
|
||||
|
||||
**Important:** WebSocket is on a separate endpoint from the API:
|
||||
|
||||
- **WebSocket:** `ws://10.42.0.1:8765/ws`
|
||||
- **HTTP API:** `http://10.42.0.1:8765/api/color-palette`
|
||||
|
||||
---
|
||||
|
||||
## Summary for UI Developers
|
||||
|
||||
### Key Points
|
||||
|
||||
✅ **8 color slots**, each with RGB values (0-255)
|
||||
✅ **2 selected colors** (indices 0-7)
|
||||
✅ **First selected color is used as the primary RGB for patterns**
|
||||
✅ **Auto-persistence** to `lighting_config.json`
|
||||
✅ **Optional updates** - send only what changed
|
||||
✅ **Two ports available** - 8765 (primary) and 8766 (backup)
|
||||
|
||||
### Integration Checklist
|
||||
|
||||
- [ ] Load palette on UI startup with `GET`
|
||||
- [ ] Display 8 color slots with current colors
|
||||
- [ ] Highlight the 2 selected colors
|
||||
- [ ] Allow editing individual colors
|
||||
- [ ] Allow selecting which 2 colors are active
|
||||
- [ ] Send updates with `POST` when user makes changes
|
||||
- [ ] Handle errors gracefully
|
||||
- [ ] Test with both local and remote server
|
||||
|
||||
### Quick Reference
|
||||
|
||||
```javascript
|
||||
// GET - Load palette
|
||||
const data = await fetch('http://10.42.0.1:8765/api/color-palette')
|
||||
.then(r => r.json());
|
||||
|
||||
// POST - Update color in slot 3
|
||||
await fetch('http://10.42.0.1:8765/api/color-palette', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
palette: modifiedPaletteArray
|
||||
})
|
||||
});
|
||||
|
||||
// POST - Change selected colors to slots 2 and 5
|
||||
await fetch('http://10.42.0.1:8765/api/color-palette', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
selected_indices: [2, 5]
|
||||
})
|
||||
});
|
||||
```
|
Reference in New Issue
Block a user