Add additional configuration and utility files

- Add install script and message configuration
- Add settings controller and templates
- Add ESP-NOW message utility
- Update API documentation
This commit is contained in:
2026-01-27 13:05:09 +13:00
parent e74ef6d64f
commit 7e33f7db6a
9 changed files with 1064 additions and 450 deletions

80
src/util/README.md Normal file
View File

@@ -0,0 +1,80 @@
# ESPNow Message Builder
This utility module provides functions to build ESPNow messages according to the LED Driver API specification.
## Usage
### Basic Message Building
```python
from util.espnow_message import build_message, build_preset_dict, build_select_dict
# Build a message with presets and select
presets = {
"red_blink": build_preset_dict({
"pattern": "blink",
"colors": ["#FF0000"],
"delay": 200,
"brightness": 255,
"auto": True
})
}
select = build_select_dict({
"device1": "red_blink"
})
message = build_message(presets=presets, select=select)
# Result: {"v": "1", "presets": {...}, "select": {...}}
```
### Building Select Messages with Step Synchronization
```python
from util.espnow_message import build_message, build_select_dict
# Select with step for synchronization
select = build_select_dict(
{"device1": "rainbow_preset", "device2": "rainbow_preset"},
step_mapping={"device1": 10, "device2": 10}
)
message = build_message(select=select)
# Result: {"v": "1", "select": {"device1": ["rainbow_preset", 10], "device2": ["rainbow_preset", 10]}}
```
### Converting Presets
```python
from util.espnow_message import build_preset_dict, build_presets_dict
# Single preset
preset = build_preset_dict({
"name": "my_preset",
"pattern": "rainbow",
"colors": ["#FF0000", "#00FF00"], # Can be hex strings or RGB tuples
"delay": 100,
"brightness": 127,
"auto": False,
"n1": 2
})
# Multiple presets
presets_data = {
"preset1": {"pattern": "on", "colors": ["#FF0000"]},
"preset2": {"pattern": "blink", "colors": ["#00FF00"]}
}
presets = build_presets_dict(presets_data)
```
## API Specification
See `docs/API.md` for the complete ESPNow API specification.
## Key Features
- **Version Field**: All messages include `"v": "1"` for version tracking
- **Preset Format**: Presets use hex color strings (`#RRGGBB`), not RGB tuples
- **Select Format**: Select values are always lists: `["preset_name"]` or `["preset_name", step]`
- **Color Conversion**: Automatically converts RGB tuples to hex strings
- **Default Values**: Provides sensible defaults for missing fields

185
src/util/espnow_message.py Normal file
View File

@@ -0,0 +1,185 @@
"""
ESPNow message builder utility for LED driver communication.
This module provides utilities to build ESPNow messages according to the API specification.
"""
import json
def build_message(presets=None, select=None):
"""
Build an ESPNow message according to the API specification.
Args:
presets: Dictionary mapping preset names to preset objects, or None
select: Dictionary mapping device names to select lists, or None
Returns:
JSON string ready to send via ESPNow
Example:
message = build_message(
presets={
"red_blink": {
"pattern": "blink",
"colors": ["#FF0000"],
"delay": 200,
"brightness": 255,
"auto": True
}
},
select={
"device1": ["red_blink"]
}
)
"""
message = {
"v": "1"
}
if presets:
message["presets"] = presets
if select:
message["select"] = select
return json.dumps(message)
def build_select_message(device_name, preset_name, step=None):
"""
Build a select message for a single device.
Args:
device_name: Name of the device
preset_name: Name of the preset to select
step: Optional step value for synchronization
Returns:
Dictionary with select field ready to use in build_message
Example:
select = build_select_message("device1", "rainbow_preset", step=10)
message = build_message(select=select)
"""
select_list = [preset_name]
if step is not None:
select_list.append(step)
return {device_name: select_list}
def build_preset_dict(preset_data):
"""
Convert preset data to API-compliant format.
Args:
preset_data: Dictionary with preset fields (may include name, pattern, colors, etc.)
Returns:
Dictionary with preset in API-compliant format (without name field)
Example:
preset = build_preset_dict({
"name": "red_blink",
"pattern": "blink",
"colors": ["#FF0000"],
"delay": 200,
"brightness": 255,
"auto": True,
"n1": 0,
"n2": 0,
"n3": 0,
"n4": 0,
"n5": 0,
"n6": 0
})
"""
# Ensure colors are in hex format
colors = preset_data.get("colors", ["#FFFFFF"])
if colors:
# Convert RGB tuples to hex strings if needed
if isinstance(colors[0], list) and len(colors[0]) == 3:
# RGB tuple format [r, g, b]
colors = [f"#{r:02x}{g:02x}{b:02x}" for r, g, b in colors]
elif not isinstance(colors[0], str):
# Handle other formats - convert to hex
colors = ["#FFFFFF"]
# Ensure all colors start with #
colors = [c if c.startswith("#") else f"#{c}" for c in colors]
else:
colors = ["#FFFFFF"]
preset = {
"pattern": preset_data.get("pattern", "off"),
"colors": colors,
"delay": preset_data.get("delay", 100),
"brightness": preset_data.get("brightness", preset_data.get("br", 127)),
"auto": preset_data.get("auto", True),
"n1": preset_data.get("n1", 0),
"n2": preset_data.get("n2", 0),
"n3": preset_data.get("n3", 0),
"n4": preset_data.get("n4", 0),
"n5": preset_data.get("n5", 0),
"n6": preset_data.get("n6", 0)
}
return preset
def build_presets_dict(presets_data):
"""
Convert multiple presets to API-compliant format.
Args:
presets_data: Dictionary mapping preset names to preset data
Returns:
Dictionary mapping preset names to API-compliant preset objects
Example:
presets = build_presets_dict({
"red_blink": {
"pattern": "blink",
"colors": ["#FF0000"],
"delay": 200
},
"blue_pulse": {
"pattern": "pulse",
"colors": ["#0000FF"],
"delay": 100
}
})
"""
result = {}
for preset_name, preset_data in presets_data.items():
result[preset_name] = build_preset_dict(preset_data)
return result
def build_select_dict(device_preset_mapping, step_mapping=None):
"""
Build a select dictionary mapping device names to select lists.
Args:
device_preset_mapping: Dictionary mapping device names to preset names
step_mapping: Optional dictionary mapping device names to step values
Returns:
Dictionary with select field ready to use in build_message
Example:
select = build_select_dict(
{"device1": "rainbow_preset", "device2": "pulse_preset"},
step_mapping={"device1": 10}
)
message = build_message(select=select)
"""
select = {}
for device_name, preset_name in device_preset_mapping.items():
select_list = [preset_name]
if step_mapping and device_name in step_mapping:
select_list.append(step_mapping[device_name])
select[device_name] = select_list
return select