Add ESPNow preset send backend support

Implement ESPNow helper model, WebSocket forwarding, and /presets/send endpoint that chunks and broadcasts presets to devices.
This commit is contained in:
2026-01-28 04:43:45 +13:00
parent 928263fbd8
commit 8503315bef
3 changed files with 154 additions and 7 deletions

View File

@@ -1,5 +1,7 @@
from microdot import Microdot
from models.preset import Preset
from models.espnow import ESPNow
from util.espnow_message import build_message, build_preset_dict
import json
controller = Microdot()
@@ -48,3 +50,79 @@ async def delete_preset(request, id):
if presets.delete(id):
return json.dumps({"message": "Preset deleted successfully"}), 200
return json.dumps({"error": "Preset not found"}), 404
@controller.post('/send')
async def send_presets(request):
"""
Send one or more presets over ESPNow.
Body JSON:
{"preset_ids": ["1", "2", ...]} or {"ids": ["1", "2", ...]}
The controller:
- looks up each preset in the Preset model
- converts them to API-compliant format
- splits into <= 240-byte ESPNow messages
- sends each message to all configured ESPNow peers.
"""
try:
data = request.json or {}
except Exception:
return json.dumps({"error": "Invalid JSON"}), 400, {'Content-Type': 'application/json'}
preset_ids = data.get('preset_ids') or data.get('ids')
if not isinstance(preset_ids, list) or not preset_ids:
return json.dumps({"error": "preset_ids must be a non-empty list"}), 400, {'Content-Type': 'application/json'}
# Build API-compliant preset map keyed by preset name
presets_by_name = {}
for pid in preset_ids:
preset_data = presets.read(str(pid))
if not preset_data:
continue
name_key = preset_data.get('name') or str(pid)
presets_by_name[name_key] = build_preset_dict(preset_data)
if not presets_by_name:
return json.dumps({"error": "No matching presets found"}), 404, {'Content-Type': 'application/json'}
# Use shared ESPNow singleton
esp = ESPNow()
async def send_chunk(chunk_presets):
msg = build_message(presets=chunk_presets)
await esp.send(msg)
MAX_BYTES = 240
entries = list(presets_by_name.items())
total_presets = len(entries)
messages_sent = 0
batch = {}
last_msg = None
for name, preset_obj in entries:
test_batch = dict(batch)
test_batch[name] = preset_obj
test_msg = build_message(presets=test_batch)
size = len(test_msg)
if size <= MAX_BYTES or not batch:
batch = test_batch
last_msg = test_msg
else:
await send_chunk(batch)
messages_sent += 1
batch = {name: preset_obj}
last_msg = build_message(presets=batch)
if batch:
await send_chunk(batch)
messages_sent += 1
return json.dumps({
"message": "Presets sent via ESPNow",
"presets_sent": total_presets,
"messages_sent": messages_sent
}), 200, {'Content-Type': 'application/json'}