Files
led-controller/src/controllers/settings.py
Jimmy 964cfc6d91 feat(audio-sequences): beat phase sync and aligned playback
Add bar-phase tracking, audio reset/anchor APIs, BPM holdover, beat-phase
sequence switching, sync-phase endpoint, and sample sequence data.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-17 18:32:10 +12:00

129 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import asyncio
import json
from microdot import Microdot, send_file
from models import wifi_ws_clients
from settings import get_settings
controller = Microdot()
settings = get_settings()
@controller.get('')
async def get_settings(request):
"""Get all settings."""
# Settings is already a dict subclass; avoid dict() wrapper which can
# trigger MicroPython's "dict update sequence has wrong length" quirk.
return json.dumps(settings), 200, {'Content-Type': 'application/json'}
@controller.get('/wifi/ap')
async def get_ap_config(request):
"""Get saved AP configuration (Pi: no in-device AP)."""
config = {
'saved_ssid': settings.get('wifi_ap_ssid'),
'saved_password': settings.get('wifi_ap_password'),
'saved_channel': settings.get('wifi_ap_channel'),
'active': False,
}
return json.dumps(config), 200, {'Content-Type': 'application/json'}
@controller.post('/wifi/ap')
async def configure_ap(request):
"""Save AP configuration to settings (Pi: no in-device AP)."""
try:
data = request.json
ssid = data.get('ssid')
password = data.get('password', '')
channel = data.get('channel')
if not ssid:
return json.dumps({"error": "SSID is required"}), 400
# Validate channel (1-11 for 2.4GHz)
if channel is not None:
channel = int(channel)
if channel < 1 or channel > 11:
return json.dumps({"error": "Channel must be between 1 and 11"}), 400
settings['wifi_ap_ssid'] = ssid
settings['wifi_ap_password'] = password
if channel is not None:
settings['wifi_ap_channel'] = channel
settings.save()
return json.dumps({
"message": "AP settings saved",
"ssid": ssid,
"channel": channel
}), 200, {'Content-Type': 'application/json'}
except Exception as e:
return json.dumps({"error": str(e)}), 500
def _validate_wifi_channel(value):
"""Return int 111 or raise ValueError."""
ch = int(value)
if ch < 1 or ch > 11:
raise ValueError("wifi_channel must be between 1 and 11")
return ch
def _validate_global_brightness(value):
"""Return int 0255 or raise ValueError."""
v = int(value)
if v < 0 or v > 255:
raise ValueError("global_brightness must be between 0 and 255")
return v
def _validate_sequence_switch_wait(value):
s = str(value).strip().lower()
if s not in ("beat", "downbeat"):
raise ValueError("sequence_switch_wait must be beat or downbeat")
return s
def _validate_audio_beat_phase_ms(value):
v = int(value)
if v < 0 or v > 500:
raise ValueError("audio_beat_phase_ms must be between 0 and 500")
return v
@controller.put('')
async def update_settings(request):
"""Update general settings."""
try:
data = request.json
global_brightness_changed = False
for key, value in data.items():
if key == 'wifi_channel' and value is not None:
settings[key] = _validate_wifi_channel(value)
elif key == 'global_brightness' and value is not None:
settings[key] = _validate_global_brightness(value)
global_brightness_changed = True
elif key == 'sequence_switch_wait' and value is not None:
settings[key] = _validate_sequence_switch_wait(value)
elif key == 'audio_beat_phase_ms' and value is not None:
settings[key] = _validate_audio_beat_phase_ms(value)
else:
settings[key] = value
settings.save()
if global_brightness_changed:
try:
asyncio.get_running_loop().create_task(
wifi_ws_clients.broadcast_global_brightness_to_tcp_drivers()
)
except RuntimeError:
pass
return json.dumps({"message": "Settings updated successfully"}), 200, {'Content-Type': 'application/json'}
except ValueError as e:
return json.dumps({"error": str(e)}), 400
except Exception as e:
return json.dumps({"error": str(e)}), 500
@controller.get('/page')
async def settings_page(request):
"""Serve the settings page."""
return send_file('templates/settings.html')