220 lines
8.4 KiB
Python
220 lines
8.4 KiB
Python
from microdot import Microdot
|
|
from microdot.session import with_session
|
|
from models.preset import Preset
|
|
from models.profile import Profile
|
|
from models.transport import get_current_sender
|
|
from util.espnow_message import build_message, build_preset_dict
|
|
import asyncio
|
|
import json
|
|
|
|
controller = Microdot()
|
|
presets = Preset()
|
|
profiles = Profile()
|
|
|
|
def get_current_profile_id(session=None):
|
|
"""Get the current active profile ID from session or fallback to first."""
|
|
profile_list = profiles.list()
|
|
session_profile = None
|
|
if session is not None:
|
|
session_profile = session.get('current_profile')
|
|
if session_profile and session_profile in profile_list:
|
|
return session_profile
|
|
if profile_list:
|
|
return profile_list[0]
|
|
return None
|
|
|
|
@controller.get('')
|
|
@with_session
|
|
async def list_presets(request, session):
|
|
"""List presets for the current profile."""
|
|
current_profile_id = get_current_profile_id(session)
|
|
if not current_profile_id:
|
|
return json.dumps({}), 200, {'Content-Type': 'application/json'}
|
|
scoped = {
|
|
pid: pdata for pid, pdata in presets.items()
|
|
if isinstance(pdata, dict) and str(pdata.get("profile_id")) == str(current_profile_id)
|
|
}
|
|
return json.dumps(scoped), 200, {'Content-Type': 'application/json'}
|
|
|
|
@controller.get('/<preset_id>')
|
|
@with_session
|
|
async def get_preset(request, session, preset_id):
|
|
"""Get a specific preset by ID (current profile only)."""
|
|
preset = presets.read(preset_id)
|
|
current_profile_id = get_current_profile_id(session)
|
|
if preset and str(preset.get("profile_id")) == str(current_profile_id):
|
|
return json.dumps(preset), 200, {'Content-Type': 'application/json'}
|
|
return json.dumps({"error": "Preset not found"}), 404
|
|
|
|
@controller.post('')
|
|
@with_session
|
|
async def create_preset(request, session):
|
|
"""Create a new preset for the current profile."""
|
|
try:
|
|
try:
|
|
data = request.json or {}
|
|
except Exception:
|
|
return json.dumps({"error": "Invalid JSON"}), 400, {'Content-Type': 'application/json'}
|
|
current_profile_id = get_current_profile_id(session)
|
|
if not current_profile_id:
|
|
return json.dumps({"error": "No profile available"}), 404
|
|
preset_id = presets.create(current_profile_id)
|
|
if not isinstance(data, dict):
|
|
data = {}
|
|
data = dict(data)
|
|
data["profile_id"] = str(current_profile_id)
|
|
if presets.update(preset_id, data):
|
|
preset_data = presets.read(preset_id)
|
|
return json.dumps({preset_id: preset_data}), 201, {'Content-Type': 'application/json'}
|
|
return json.dumps({"error": "Failed to create preset"}), 400
|
|
except Exception as e:
|
|
return json.dumps({"error": str(e)}), 400
|
|
|
|
@controller.put('/<preset_id>')
|
|
@with_session
|
|
async def update_preset(request, session, preset_id):
|
|
"""Update an existing preset (current profile only)."""
|
|
try:
|
|
preset = presets.read(preset_id)
|
|
current_profile_id = get_current_profile_id(session)
|
|
if not preset or str(preset.get("profile_id")) != str(current_profile_id):
|
|
return json.dumps({"error": "Preset not found"}), 404
|
|
try:
|
|
data = request.json or {}
|
|
except Exception:
|
|
return json.dumps({"error": "Invalid JSON"}), 400, {'Content-Type': 'application/json'}
|
|
if not isinstance(data, dict):
|
|
data = {}
|
|
data = dict(data)
|
|
data["profile_id"] = str(current_profile_id)
|
|
if presets.update(preset_id, data):
|
|
return json.dumps(presets.read(preset_id)), 200, {'Content-Type': 'application/json'}
|
|
return json.dumps({"error": "Preset not found"}), 404
|
|
except Exception as e:
|
|
return json.dumps({"error": str(e)}), 400
|
|
|
|
@controller.delete('/<preset_id>')
|
|
@with_session
|
|
async def delete_preset(request, *args, **kwargs):
|
|
"""Delete a preset (current profile only)."""
|
|
# Be tolerant of wrapper/arg-order variations.
|
|
session = None
|
|
preset_id = None
|
|
if len(args) > 0:
|
|
session = args[0]
|
|
if len(args) > 1:
|
|
preset_id = args[1]
|
|
if 'session' in kwargs and kwargs.get('session') is not None:
|
|
session = kwargs.get('session')
|
|
if 'preset_id' in kwargs and kwargs.get('preset_id') is not None:
|
|
preset_id = kwargs.get('preset_id')
|
|
if 'id' in kwargs and kwargs.get('id') is not None and preset_id is None:
|
|
preset_id = kwargs.get('id')
|
|
if preset_id is None:
|
|
return json.dumps({"error": "Preset ID is required"}), 400
|
|
preset = presets.read(preset_id)
|
|
current_profile_id = get_current_profile_id(session)
|
|
if not preset or str(preset.get("profile_id")) != str(current_profile_id):
|
|
return json.dumps({"error": "Preset not found"}), 404
|
|
if presets.delete(preset_id):
|
|
return json.dumps({"message": "Preset deleted successfully"}), 200
|
|
return json.dumps({"error": "Preset not found"}), 404
|
|
|
|
|
|
@controller.post('/send')
|
|
@with_session
|
|
async def send_presets(request, session):
|
|
"""
|
|
Send one or more presets to the LED driver (via serial transport).
|
|
|
|
Body JSON:
|
|
{"preset_ids": ["1", "2", ...]} or {"ids": ["1", "2", ...]}
|
|
|
|
The controller looks up each preset, converts to API format, chunks into
|
|
<= 240-byte messages, and sends them over the configured transport.
|
|
"""
|
|
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'}
|
|
save_flag = data.get('save', True)
|
|
save_flag = bool(save_flag)
|
|
default_id = data.get('default')
|
|
# Optional 12-char hex MAC to send to one device; omit for default (e.g. broadcast).
|
|
destination_mac = data.get('destination_mac') or data.get('to')
|
|
|
|
# Build API-compliant preset map keyed by preset ID, include name
|
|
current_profile_id = get_current_profile_id(session)
|
|
presets_by_name = {}
|
|
for pid in preset_ids:
|
|
preset_data = presets.read(str(pid))
|
|
if not preset_data:
|
|
continue
|
|
if str(preset_data.get("profile_id")) != str(current_profile_id):
|
|
continue
|
|
preset_key = str(pid)
|
|
preset_payload = build_preset_dict(preset_data)
|
|
preset_payload["name"] = preset_data.get("name", "")
|
|
presets_by_name[preset_key] = preset_payload
|
|
|
|
if not presets_by_name:
|
|
return json.dumps({"error": "No matching presets found"}), 404, {'Content-Type': 'application/json'}
|
|
|
|
if default_id is not None and str(default_id) not in presets_by_name:
|
|
default_id = None
|
|
|
|
sender = get_current_sender()
|
|
if not sender:
|
|
return json.dumps({"error": "Transport not configured"}), 503, {'Content-Type': 'application/json'}
|
|
|
|
async def send_chunk(chunk_presets):
|
|
# Include save flag so the led-driver can persist when desired.
|
|
msg = build_message(presets=chunk_presets, save=save_flag, default=default_id)
|
|
await sender.send(msg, addr=destination_mac)
|
|
|
|
MAX_BYTES = 240
|
|
send_delay_s = 0.1
|
|
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, save=save_flag, default=default_id)
|
|
size = len(test_msg)
|
|
|
|
if size <= MAX_BYTES or not batch:
|
|
batch = test_batch
|
|
last_msg = test_msg
|
|
else:
|
|
try:
|
|
await send_chunk(batch)
|
|
except Exception:
|
|
return json.dumps({"error": "Send failed"}), 503, {'Content-Type': 'application/json'}
|
|
await asyncio.sleep(send_delay_s)
|
|
messages_sent += 1
|
|
batch = {name: preset_obj}
|
|
last_msg = build_message(presets=batch, save=save_flag, default=default_id)
|
|
|
|
if batch:
|
|
try:
|
|
await send_chunk(batch)
|
|
except Exception:
|
|
return json.dumps({"error": "Send failed"}), 503, {'Content-Type': 'application/json'}
|
|
await asyncio.sleep(send_delay_s)
|
|
messages_sent += 1
|
|
|
|
return json.dumps({
|
|
"message": "Presets sent",
|
|
"presets_sent": total_presets,
|
|
"messages_sent": messages_sent
|
|
}), 200, {'Content-Type': 'application/json'}
|
|
|