#!/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 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 app = Microdot() # 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') # Serve index.html at root @app.route('/') def index(request): """Serve the main web UI.""" return send_file('src/templates/index.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())