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>
129 lines
4.3 KiB
Python
129 lines
4.3 KiB
Python
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 1–11 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 0–255 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')
|
||
|