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')