Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
9cf1855b51 | |||
763a2053ad | |||
324fa463be | |||
aaf515d8f4 | |||
7beca0cf53 | |||
ace47b7835 | |||
9045b10631 | |||
f2e775f6f5 | |||
a654527dc3 | |||
|
0906cb22e6 |
9
.env.example
Normal file
9
.env.example
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Lighting Controller UI Client Configuration
|
||||||
|
|
||||||
|
# WebSocket URI for the control server
|
||||||
|
# For local development (running UI on same machine as control server):
|
||||||
|
CONTROL_SERVER_URI=ws://localhost:8765
|
||||||
|
|
||||||
|
# For remote connection (running UI on desktop, control server on Pi):
|
||||||
|
# Replace with your Raspberry Pi's IP address
|
||||||
|
# CONTROL_SERVER_URI=ws://10.1.1.117:8765
|
5
Pipfile
5
Pipfile
@@ -5,14 +5,13 @@ name = "pypi"
|
|||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
websockets = "*"
|
websockets = "*"
|
||||||
spidev = "*"
|
|
||||||
watchfiles = "*"
|
watchfiles = "*"
|
||||||
async-tkinter-loop = "*"
|
async-tkinter-loop = "*"
|
||||||
mido = "*"
|
mido = "*"
|
||||||
python-rtmidi = "*"
|
python-rtmidi = "*"
|
||||||
pyaudio = "*"
|
|
||||||
aubio = "*"
|
|
||||||
websocket-client = "*"
|
websocket-client = "*"
|
||||||
|
python-dotenv = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
|
||||||
|
751
api.md
Normal file
751
api.md
Normal file
@@ -0,0 +1,751 @@
|
|||||||
|
# Lighting Controller REST API - Frontend Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Complete REST API for controlling the LED lighting system. **No WebSocket required** - all operations use simple HTTP requests.
|
||||||
|
|
||||||
|
**Base URL:** `http://10.42.0.1:8765`
|
||||||
|
**Local Testing:** `http://localhost:8765`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Quick Start](#quick-start)
|
||||||
|
2. [Pattern Control](#pattern-control)
|
||||||
|
3. [Color Palette](#color-palette)
|
||||||
|
4. [Parameters](#parameters)
|
||||||
|
5. [System State](#system-state)
|
||||||
|
6. [Tempo Control](#tempo-control)
|
||||||
|
7. [Complete Examples](#complete-examples)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Load Initial State
|
||||||
|
```javascript
|
||||||
|
// Get everything in one call
|
||||||
|
const response = await fetch('http://10.42.0.1:8765/api/state');
|
||||||
|
const state = await response.json();
|
||||||
|
|
||||||
|
console.log(state.pattern); // Current pattern
|
||||||
|
console.log(state.parameters); // All parameters
|
||||||
|
console.log(state.color_palette); // 8 colors + 2 selected
|
||||||
|
console.log(state.beat_index); // Current beat number
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change Pattern
|
||||||
|
```javascript
|
||||||
|
await fetch('http://10.42.0.1:8765/api/pattern', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({pattern: 'alternating'})
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change Color
|
||||||
|
```javascript
|
||||||
|
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]}) // Blue and Cyan
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern Control
|
||||||
|
|
||||||
|
### GET /api/pattern
|
||||||
|
|
||||||
|
Get the currently active pattern.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```http
|
||||||
|
GET /api/pattern HTTP/1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pattern": "alternating"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### POST /api/pattern
|
||||||
|
|
||||||
|
Change the active pattern.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```http
|
||||||
|
POST /api/pattern HTTP/1.1
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
**Available Patterns:**
|
||||||
|
- `"on"` / `"o"` - Solid color
|
||||||
|
- `"off"` / `"f"` - All LEDs off
|
||||||
|
- `"flicker"` / `"f"` - Flickering effect
|
||||||
|
- `"fill_range"` / `"fr"` - Fill effect
|
||||||
|
- `"n_chase"` / `"nc"` - Chase pattern
|
||||||
|
- `"alternating"` / `"a"` - Alternating on/off
|
||||||
|
- `"pulse"` / `"p"` - Pulsing effect
|
||||||
|
- `"rainbow"` / `"r"` - Rainbow cycle
|
||||||
|
- `"specto"` / `"s"` - Spectograph effect
|
||||||
|
- `"radiate"` / `"rd"` - Radiate from center
|
||||||
|
- `"segmented_movement"` / `"sm"` - Moving segments
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"pattern": "alternating"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**JavaScript Example:**
|
||||||
|
```javascript
|
||||||
|
async function setPattern(patternName) {
|
||||||
|
const response = await fetch('http://10.42.0.1:8765/api/pattern', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({pattern: patternName})
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
await setPattern('alternating');
|
||||||
|
await setPattern('rainbow');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color Palette
|
||||||
|
|
||||||
|
### GET /api/color-palette
|
||||||
|
|
||||||
|
Get the 8-color palette and selected colors.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```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] // [0] = pattern color, [1] = reserved
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** The **first selected color** (index 0) is used for all patterns!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### POST /api/color-palette
|
||||||
|
|
||||||
|
Update palette colors and/or selected colors.
|
||||||
|
|
||||||
|
**Request (Change Selected Colors):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"selected_indices": [2, 5] // Use slot 2 (Blue) for patterns
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request (Update a Color):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"palette": [
|
||||||
|
{"r": 255, "g": 0, "b": 0},
|
||||||
|
{"r": 0, "g": 255, "b": 0},
|
||||||
|
{"r": 128, "g": 0, "b": 128}, // Changed to purple
|
||||||
|
// ... all 8 colors (must send complete array)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"palette": {
|
||||||
|
"palette": [...],
|
||||||
|
"selected_indices": [2, 5]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**JavaScript Example:**
|
||||||
|
```javascript
|
||||||
|
// Change pattern color to slot 5 (Cyan)
|
||||||
|
async function selectColor(slotIndex) {
|
||||||
|
const response = await fetch('http://10.42.0.1:8765/api/color-palette', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({selected_indices: [slotIndex, 1]})
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit a color in the palette
|
||||||
|
async function updatePaletteColor(slotIndex, r, g, b) {
|
||||||
|
// First 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})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
await selectColor(2); // Use blue for patterns
|
||||||
|
await updatePaletteColor(3, 128, 0, 128); // Change slot 3 to purple
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
### GET /api/parameters
|
||||||
|
|
||||||
|
Get all current parameter values.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"brightness": 100, // 0-100
|
||||||
|
"delay": 50, // milliseconds
|
||||||
|
"n1": 10, // Pattern parameter 1
|
||||||
|
"n2": 5, // Pattern parameter 2
|
||||||
|
"n3": 2, // Pattern parameter 3 (forward movement)
|
||||||
|
"n4": 1 // Pattern parameter 4 (backward movement)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### POST /api/parameters
|
||||||
|
|
||||||
|
Update one or more parameters. Only send the parameters you want to change.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"brightness": 75,
|
||||||
|
"n1": 15
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"parameters": {
|
||||||
|
"brightness": 75,
|
||||||
|
"delay": 50,
|
||||||
|
"n1": 15,
|
||||||
|
"n2": 5,
|
||||||
|
"n3": 2,
|
||||||
|
"n4": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameter Descriptions:**
|
||||||
|
|
||||||
|
| Parameter | Range | Description |
|
||||||
|
|-----------|-------|-------------|
|
||||||
|
| `brightness` | 0-100 | LED brightness percentage |
|
||||||
|
| `delay` | 1-1000 | Pattern speed (milliseconds) |
|
||||||
|
| `n1` | 0-255 | Pattern-specific (e.g., segment length) |
|
||||||
|
| `n2` | 0-255 | Pattern-specific (e.g., spacing) |
|
||||||
|
| `n3` | 0-255 | Pattern-specific (e.g., forward steps) |
|
||||||
|
| `n4` | 0-255 | Pattern-specific (e.g., backward steps) |
|
||||||
|
|
||||||
|
**JavaScript Example:**
|
||||||
|
```javascript
|
||||||
|
async function setBrightness(value) {
|
||||||
|
const response = await fetch('http://10.42.0.1:8765/api/parameters', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({brightness: value})
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setSpeed(delayMs) {
|
||||||
|
await fetch('http://10.42.0.1:8765/api/parameters', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({delay: delayMs})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
await setBrightness(75); // Set to 75%
|
||||||
|
await setSpeed(100); // Slow down pattern
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## System State
|
||||||
|
|
||||||
|
### GET /api/state
|
||||||
|
|
||||||
|
Get complete system state in a single call. Perfect for initial UI load.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pattern": "alternating",
|
||||||
|
"parameters": {
|
||||||
|
"brightness": 100,
|
||||||
|
"delay": 50,
|
||||||
|
"n1": 10,
|
||||||
|
"n2": 5,
|
||||||
|
"n3": 2,
|
||||||
|
"n4": 1
|
||||||
|
},
|
||||||
|
"color_palette": {
|
||||||
|
"palette": [...8 colors...],
|
||||||
|
"selected_indices": [0, 1]
|
||||||
|
},
|
||||||
|
"beat_index": 42
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**JavaScript Example:**
|
||||||
|
```javascript
|
||||||
|
// Load all state when UI starts
|
||||||
|
async function loadInitialState() {
|
||||||
|
const response = await fetch('http://10.42.0.1:8765/api/state');
|
||||||
|
const state = await response.json();
|
||||||
|
|
||||||
|
// Update UI with current state
|
||||||
|
updatePatternButtons(state.pattern);
|
||||||
|
updateColorPalette(state.color_palette);
|
||||||
|
updateSliders(state.parameters);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tempo Control
|
||||||
|
|
||||||
|
### POST /api/tempo/reset
|
||||||
|
|
||||||
|
Reset the tempo/beat detection in the sound system.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```http
|
||||||
|
POST /api/tempo/reset HTTP/1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"message": "Tempo reset sent"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**JavaScript Example:**
|
||||||
|
```javascript
|
||||||
|
async function resetTempo() {
|
||||||
|
const response = await fetch('http://10.42.0.1:8765/api/tempo/reset', {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage: Call this when tempo detection seems off
|
||||||
|
await resetTempo();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complete Examples
|
||||||
|
|
||||||
|
### Example 1: Full UI Controller Class
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class LightingController {
|
||||||
|
constructor(baseUrl = 'http://10.42.0.1:8765') {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load complete state
|
||||||
|
async loadState() {
|
||||||
|
const response = await fetch(`${this.baseUrl}/api/state`);
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern control
|
||||||
|
async setPattern(pattern) {
|
||||||
|
const response = await fetch(`${this.baseUrl}/api/pattern`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({pattern})
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color selection
|
||||||
|
async selectColor(slotIndex) {
|
||||||
|
const response = await fetch(`${this.baseUrl}/api/color-palette`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({selected_indices: [slotIndex, 1]})
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Brightness control
|
||||||
|
async setBrightness(value) {
|
||||||
|
const response = await fetch(`${this.baseUrl}/api/parameters`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({brightness: value})
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern parameters
|
||||||
|
async setParameters(params) {
|
||||||
|
const response = await fetch(`${this.baseUrl}/api/parameters`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(params)
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
const lights = new LightingController();
|
||||||
|
|
||||||
|
// On page load
|
||||||
|
const state = await lights.loadState();
|
||||||
|
|
||||||
|
// User interactions
|
||||||
|
await lights.setPattern('rainbow');
|
||||||
|
await lights.selectColor(2); // Blue
|
||||||
|
await lights.setBrightness(75);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Example 2: React Component
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
function LightingControl() {
|
||||||
|
const [state, setState] = useState(null);
|
||||||
|
const BASE_URL = 'http://10.42.0.1:8765';
|
||||||
|
|
||||||
|
// Load initial state
|
||||||
|
useEffect(() => {
|
||||||
|
fetch(`${BASE_URL}/api/state`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(setState);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Change pattern
|
||||||
|
const handlePatternChange = async (pattern) => {
|
||||||
|
await fetch(`${BASE_URL}/api/pattern`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({pattern})
|
||||||
|
});
|
||||||
|
// Reload state
|
||||||
|
const newState = await fetch(`${BASE_URL}/api/state`).then(r => r.json());
|
||||||
|
setState(newState);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Change color
|
||||||
|
const handleColorSelect = async (slotIndex) => {
|
||||||
|
await fetch(`${BASE_URL}/api/color-palette`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({selected_indices: [slotIndex, 1]})
|
||||||
|
});
|
||||||
|
const newState = await fetch(`${BASE_URL}/api/state`).then(r => r.json());
|
||||||
|
setState(newState);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Change brightness
|
||||||
|
const handleBrightnessChange = async (value) => {
|
||||||
|
await fetch(`${BASE_URL}/api/parameters`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({brightness: value})
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!state) return <div>Loading...</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>Pattern: {state.pattern}</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button onClick={() => handlePatternChange('alternating')}>Alternating</button>
|
||||||
|
<button onClick={() => handlePatternChange('rainbow')}>Rainbow</button>
|
||||||
|
<button onClick={() => handlePatternChange('pulse')}>Pulse</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3>Colors</h3>
|
||||||
|
{state.color_palette.palette.map((color, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
onClick={() => handleColorSelect(i)}
|
||||||
|
style={{
|
||||||
|
background: `rgb(${color.r}, ${color.g}, ${color.b})`,
|
||||||
|
border: state.color_palette.selected_indices[0] === i ? '3px solid gold' : '1px solid black',
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
display: 'inline-block',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>Brightness: {state.parameters.brightness}</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
value={state.parameters.brightness}
|
||||||
|
onChange={(e) => handleBrightnessChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Example 3: Simple HTML + Vanilla JS
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Lighting Controller</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>LED Lighting Control</h1>
|
||||||
|
|
||||||
|
<div id="patterns"></div>
|
||||||
|
<div id="colors"></div>
|
||||||
|
|
||||||
|
<label>Brightness: <span id="brightness-value">100</span></label>
|
||||||
|
<input type="range" id="brightness" min="0" max="100" value="100">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const BASE_URL = 'http://10.42.0.1:8765';
|
||||||
|
|
||||||
|
// Load initial state
|
||||||
|
async function init() {
|
||||||
|
const response = await fetch(`${BASE_URL}/api/state`);
|
||||||
|
const state = await response.json();
|
||||||
|
|
||||||
|
// Create pattern buttons
|
||||||
|
const patterns = ['on', 'alternating', 'rainbow', 'pulse', 'segmented_movement'];
|
||||||
|
const patternsDiv = document.getElementById('patterns');
|
||||||
|
patterns.forEach(pattern => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.textContent = pattern;
|
||||||
|
btn.onclick = () => setPattern(pattern);
|
||||||
|
patternsDiv.appendChild(btn);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create color palette
|
||||||
|
const colorsDiv = document.getElementById('colors');
|
||||||
|
state.color_palette.palette.forEach((color, i) => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.style.cssText = `
|
||||||
|
background: rgb(${color.r}, ${color.g}, ${color.b});
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
border: ${state.color_palette.selected_indices[0] === i ? '3px solid gold' : '1px solid black'};
|
||||||
|
`;
|
||||||
|
div.onclick = () => selectColor(i);
|
||||||
|
colorsDiv.appendChild(div);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Brightness slider
|
||||||
|
document.getElementById('brightness').oninput = (e) => {
|
||||||
|
document.getElementById('brightness-value').textContent = e.target.value;
|
||||||
|
setBrightness(e.target.value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setPattern(pattern) {
|
||||||
|
await fetch(`${BASE_URL}/api/pattern`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({pattern})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectColor(index) {
|
||||||
|
await fetch(`${BASE_URL}/api/color-palette`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({selected_indices: [index, 1]})
|
||||||
|
});
|
||||||
|
location.reload(); // Refresh to show updated selection
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setBrightness(value) {
|
||||||
|
await fetch(`${BASE_URL}/api/parameters`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({brightness: parseInt(value)})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Endpoint Summary
|
||||||
|
|
||||||
|
| Method | Endpoint | Purpose |
|
||||||
|
|--------|----------|---------|
|
||||||
|
| `GET` | `/api/state` | Get complete system state |
|
||||||
|
| `GET` | `/api/pattern` | Get current pattern |
|
||||||
|
| `POST` | `/api/pattern` | Change pattern |
|
||||||
|
| `GET` | `/api/color-palette` | Get color palette |
|
||||||
|
| `POST` | `/api/color-palette` | Update palette/selection |
|
||||||
|
| `GET` | `/api/parameters` | Get all parameters |
|
||||||
|
| `POST` | `/api/parameters` | Update parameters |
|
||||||
|
| `POST` | `/api/tempo/reset` | Reset tempo detection |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
All endpoints return standard HTTP status codes:
|
||||||
|
|
||||||
|
- `200 OK` - Success
|
||||||
|
- `400 Bad Request` - Invalid request data
|
||||||
|
- `500 Internal Server Error` - Server error
|
||||||
|
|
||||||
|
**Error Response Format:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"message": "Pattern name required"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**JavaScript Error Handling Example:**
|
||||||
|
```javascript
|
||||||
|
async function safeApiCall(url, options) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status === 'error') {
|
||||||
|
console.error('API Error:', data.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Network Error:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
const result = await safeApiCall('http://10.42.0.1:8765/api/pattern', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({pattern: 'rainbow'})
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Test All Endpoints
|
||||||
|
```bash
|
||||||
|
# Get state
|
||||||
|
curl http://10.42.0.1:8765/api/state | jq
|
||||||
|
|
||||||
|
# Change pattern
|
||||||
|
curl -X POST http://10.42.0.1:8765/api/pattern \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"pattern": "rainbow"}'
|
||||||
|
|
||||||
|
# Change color
|
||||||
|
curl -X POST http://10.42.0.1:8765/api/color-palette \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"selected_indices": [2, 5]}'
|
||||||
|
|
||||||
|
# Update brightness
|
||||||
|
curl -X POST http://10.42.0.1:8765/api/parameters \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"brightness": 75}'
|
||||||
|
|
||||||
|
# Reset tempo
|
||||||
|
curl -X POST http://10.42.0.1:8765/api/tempo/reset
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **No WebSocket needed** - Everything uses simple HTTP REST API
|
||||||
|
- **CORS**: Not currently enabled. Host UI on same domain or add CORS middleware
|
||||||
|
- **Persistence**: Color palette persists to `lighting_config.json`
|
||||||
|
- **Real-time**: Changes take effect immediately (within one beat cycle)
|
||||||
|
- **Pattern Color**: First selected color (index 0) is used for all patterns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support Files
|
||||||
|
|
||||||
|
- Full API details: `COLOR_PALETTE_API.md`
|
||||||
|
- Migration notes: `AIOHTTP_MIGRATION.md`
|
||||||
|
- Test results: `PATTERN_COLOR_TEST_RESULTS.md`
|
||||||
|
|
||||||
|
- Migration notes: `AIOHTTP_MIGRATION.md`
|
||||||
|
- Test results: `PATTERN_COLOR_TEST_RESULTS.md`
|
||||||
|
|
405
colorpallet.md
Normal file
405
colorpallet.md
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
# 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
|
||||||
|
- Patterns may use these selected colors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
✅ **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]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
```
|
1126
src/ui_client.py
1126
src/ui_client.py
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,27 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
import websockets
|
import websockets
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def load_dotenv(filepath: str = ".env"):
|
||||||
|
try:
|
||||||
|
if not os.path.exists(filepath):
|
||||||
|
return
|
||||||
|
with open(filepath, 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith('#'):
|
||||||
|
continue
|
||||||
|
if '=' not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split('=', 1)
|
||||||
|
key = key.strip()
|
||||||
|
value = value.strip().strip('"').strip("'")
|
||||||
|
if key and key not in os.environ:
|
||||||
|
os.environ[key] = value
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def build_messages(args):
|
def build_messages(args):
|
||||||
@@ -76,8 +97,11 @@ async def run_test(uri: str, messages: list[dict], sleep_s: float):
|
|||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
|
|
||||||
p = argparse.ArgumentParser(description="Send UI commands to control_server WebSocket")
|
p = argparse.ArgumentParser(description="Send UI commands to control_server WebSocket")
|
||||||
p.add_argument("--uri", default="ws://localhost:8765", help="WebSocket URI (default ws://localhost:8765)")
|
load_dotenv()
|
||||||
|
default_uri = os.getenv("CONTROL_SERVER_URI", "ws://10.1.1.117:8765")
|
||||||
|
p.add_argument("--uri", default=default_uri, help=f"WebSocket URI (default {default_uri})")
|
||||||
p.add_argument("--pattern", help="Pattern name for pattern_change")
|
p.add_argument("--pattern", help="Pattern name for pattern_change")
|
||||||
p.add_argument("--r", type=int, help="Red 0-255 for color_change")
|
p.add_argument("--r", type=int, help="Red 0-255 for color_change")
|
||||||
p.add_argument("--g", type=int, help="Green 0-255 for color_change")
|
p.add_argument("--g", type=int, help="Green 0-255 for color_change")
|
||||||
|
Reference in New Issue
Block a user