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:
@@ -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'}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user