#!/usr/bin/env python3 """ Local development web server - imports and runs main.py with port 5000 """ import sys import os import asyncio # Add src and lib to path sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib')) # Import the main module from src import main as main_module # Override the port in the main function async def run_local(): """Run main with port 5000 for local development.""" from settings import Settings import gc # Mock MicroPython modules for local development class MockMachine: class WDT: def __init__(self, timeout): pass def feed(self): pass import sys as sys_module sys_module.modules['machine'] = MockMachine() class MockESPNow: def __init__(self): self.active_value = False self.peers = [] def active(self, value): self.active_value = value print(f"[MOCK] ESPNow active: {value}") def add_peer(self, peer): self.peers.append(peer) print(f"[MOCK] Added peer: {peer.hex() if hasattr(peer, 'hex') else peer}") async def asend(self, peer, data): print(f"[MOCK] Would send to {peer.hex() if hasattr(peer, 'hex') else peer}: {data}") class MockAIOESPNow: def __init__(self): pass def active(self, value): return MockESPNow() def add_peer(self, peer): pass class MockNetwork: class WLAN: def __init__(self, interface): self.interface = interface def active(self, value): print(f"[MOCK] WLAN({self.interface}) active: {value}") STA_IF = 0 # Replace MicroPython modules with mocks sys_module.modules['aioespnow'] = type('module', (), {'AIOESPNow': MockESPNow})() sys_module.modules['network'] = MockNetwork() # Mock gc if needed if not hasattr(gc, 'collect'): class MockGC: def collect(self): pass gc = MockGC() settings = Settings() print("Starting LED Controller Web Server (Local Development)") print("=" * 60) # Mock network import network network.WLAN(network.STA_IF).active(True) # Mock ESPNow import aioespnow e = aioespnow.AIOESPNow() e.active(True) e.add_peer(b"\xbb\xbb\xbb\xbb\xbb\xbb") from microdot import Microdot, send_file from microdot.websocket import with_websocket from microdot.session import Session import controllers.preset as preset import controllers.profile as profile import controllers.group as group import controllers.sequence as sequence import controllers.tab as tab import controllers.palette as palette import controllers.scene as scene import controllers.pattern as pattern import controllers.settings as settings_controller app = Microdot() # Initialize sessions with a secret key from settings secret_key = settings.get('session_secret_key', 'led-controller-secret-key-change-in-production') Session(app, secret_key=secret_key) # Mount model controllers as subroutes app.mount(preset.controller, '/presets') app.mount(profile.controller, '/profiles') app.mount(group.controller, '/groups') app.mount(sequence.controller, '/sequences') app.mount(tab.controller, '/tabs') app.mount(palette.controller, '/palettes') app.mount(scene.controller, '/scenes') app.mount(pattern.controller, '/patterns') app.mount(settings_controller.controller, '/settings') # Serve index.html at root @app.route('/') def index(request): """Serve the main web UI.""" return send_file('src/templates/index.html') # Serve settings page @app.route('/settings') def settings_page(request): """Serve the settings page.""" return send_file('src/templates/settings.html') # Static file route @app.route("/static/") def static_handler(request, path): """Serve static files.""" if '..' in path: return 'Not found', 404 return send_file('src/static/' + path) @app.route('/ws') @with_websocket async def ws(request, ws): while True: data = await ws.receive() if data: await e.asend(b"\xbb\xbb\xbb\xbb\xbb\xbb", data) print(data) else: break # Use port 5000 for local development port = 5000 print(f"Starting server on http://0.0.0.0:{port}") print(f"Open http://localhost:{port} in your browser") print("=" * 60) try: await app.start_server(host="0.0.0.0", port=port, debug=True) except KeyboardInterrupt: print("\nShutting down server...") if __name__ == '__main__': # Change to project root os.chdir(os.path.dirname(os.path.abspath(__file__))) # Override settings path for local development import settings as settings_module settings_module.Settings.SETTINGS_FILE = os.path.join(os.getcwd(), 'settings.json') asyncio.run(run_local())