import asyncio import socket import select import time import json from protocol import classify_packet, parse_animation_cmd from led_driver import create_strip from animations import create_animation # --- Global state --- config = None strip = None current_mode = 'idle' # 'idle', 'animation', 'frame' current_animation = None # dict from parse_animation_cmd current_anim_instance = None # animation object with render() animation_start_ms = 0 frame_pixels = None # set by frame packets def load_config(): try: with open('config.json') as f: return json.load(f) except OSError: return {'led_count': 300, 'strip_type': 'sk6812', 'led_pin': 5, 'spi_id': 1, 'udp_port': 21324} async def udp_listener_task(port): """Non-blocking UDP listener using select.poll(0) inside asyncio.""" global current_mode, current_animation, current_anim_instance global animation_start_ms, frame_pixels sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(('0.0.0.0', port)) sock.setblocking(False) poller = select.poll() poller.register(sock, select.POLLIN) print('[udp] Listening on port', port) while True: events = poller.poll(0) if events: try: data, addr = sock.recvfrom(1500) ptype = classify_packet(data) if ptype == 'anim_cmd': cmd = parse_animation_cmd(data) if cmd: current_animation = cmd current_anim_instance = create_animation(cmd) current_mode = 'animation' animation_start_ms = time.ticks_ms() print('[udp] Animation:', cmd['animation']) elif ptype in ('drgb', 'drgbw', 'dnrgb'): # Frame handling added by Plan 07-03 current_mode = 'frame' print('[udp] Frame packet:', ptype, len(data), 'bytes') else: print('[udp] Unknown packet type:', data[0] if data else '?') except OSError: pass await asyncio.sleep_ms(5) async def render_task(): """Animation render loop at ~60fps.""" global current_mode, frame_pixels while True: if current_mode == 'animation' and current_anim_instance: t = time.ticks_diff(time.ticks_ms(), animation_start_ms) / 1000.0 pixels = current_anim_instance.render(t, config['led_count']) strip.write_rgb(pixels) elif current_mode == 'frame' and frame_pixels: # Plan 07-03 adds frame write logic here pass await asyncio.sleep_ms(16) # ~60 fps async def main(): global config, strip config = load_config() strip = create_strip(config) strip.clear() print('[main] LightSync firmware ready') print('[main] Strip:', config['strip_type'], '/', config['led_count'], 'LEDs') asyncio.create_task(udp_listener_task(config['udp_port'])) asyncio.create_task(render_task()) while True: await asyncio.sleep(1) asyncio.run(main())