diff --git a/scripts/led-matrix-viz.py b/scripts/led-matrix-viz.py index 5a13002..83693c7 100644 --- a/scripts/led-matrix-viz.py +++ b/scripts/led-matrix-viz.py @@ -12,15 +12,13 @@ ROUTER_SOCK = os.environ.get("ROUTER_SOCK", "/var/run/arduino-router.sock") COLS = 13 ROWS = 8 -REFRESH_INTERVAL = 0.2 # 5 FPS background refresh (catches dropped frames) +REFRESH_INTERVAL = 0.2 STATS_FILE = "/tmp/viz-stats.json" _poll = select.poll() POLLIN = select.POLLIN -POLLOUT = select.POLLOUT SOCK_BUFFER = 4096 -ROUTER_RECONNECT_DELAY = 0.1 def pack_frame(pixels): @@ -29,8 +27,11 @@ def pack_frame(pixels): for col in range(COLS): if pixels[row][col]: bits |= 1 << (row * COLS + col) - return (bits & 0xFFFFFFFF, (bits >> 32) & 0xFFFFFFFF, - (bits >> 64) & 0xFFFFFFFF, (bits >> 96) & 0xFFFFFFFF) + w0 = bits & 0xFFFFFFFF + w1 = (bits >> 32) & 0xFFFFFFFF + w2 = (bits >> 64) & 0xFFFFFFFF + w3 = (bits >> 96) & 0xFFFFFFFF + return [str(w0), str(w1), str(w2), str(w3)] def render(active_notes): @@ -46,7 +47,7 @@ def render(active_notes): def connect_router(): router = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - router.setblocking(False) + router.settimeout(0.3) try: router.connect(ROUTER_SOCK) except OSError: @@ -54,19 +55,6 @@ def connect_router(): return router -def send_frame(router, packer, msg_id, frame): - if frame is None: - return msg_id - msg_id = (msg_id + 1) % 65535 - params = [str(v) for v in frame] - req = packer.pack([0, msg_id, "draw_frame", params]) - try: - router.sendall(req) - except (BlockingIOError, OSError): - pass - return msg_id - - def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -77,18 +65,15 @@ def main(): packer = msgpack.Packer() _poll.register(sock, POLLIN) - router_fd = router.fileno() - _poll.register(router, POLLIN | POLLOUT) active_notes = set() - last_sent_frame = None - pending_frame = None + last_sent_params = None + pending_bytes = None buf = b"" msg_id = 0 frames_sent = 0 frames_dropped = 0 - router_errors = 0 last_refresh = time.monotonic() stats_last_write = time.monotonic() @@ -100,14 +85,11 @@ def main(): now = time.monotonic() refresh_due = (now - last_refresh) >= REFRESH_INTERVAL - timeout = 5 # ms - try: - events = _poll.poll(timeout) + events = _poll.poll(5) except OSError: events = [] - # Process UDP for fd, flags in events: if fd == sock.fileno() and (flags & POLLIN): try: @@ -137,38 +119,34 @@ def main(): changed = True if changed: events_processed += 1 - frame = render(active_notes) - if frame != last_sent_frame: - pending_frame = frame + params = render(active_notes) + if params != last_sent_params: + msg_id = (msg_id + 1) % 65535 + pending_bytes = packer.pack([0, msg_id, "draw_frame", params]) + last_sent_params = params - # Process router readable - if fd == router_fd and (flags & POLLIN): - try: - while True: - d = router.recv(SOCK_BUFFER) - if not d: - break - except BlockingIOError: - pass - - # Router writable: send pending frame immediately - if pending_frame is not None: - if any(fd == router_fd and (flags & POLLOUT) for fd, flags in events): - msg_id = send_frame(router, packer, msg_id, pending_frame) - last_sent_frame = pending_frame - pending_frame = None + # Try to send pending frame — no POLLOUT dependency + if pending_bytes is not None: + try: + router.sendall(pending_bytes) + pending_bytes = None frames_sent += 1 - else: + except (BlockingIOError, OSError): frames_dropped += 1 - # Background refresh: resend last frame to recover from router drops + # Background refresh: resend last params to recover from router drops if refresh_due: last_refresh = now fps_sample_frames += 1 - if last_sent_frame is not None: - msg_id = send_frame(router, packer, msg_id, last_sent_frame) + if last_sent_params is not None and pending_bytes is None: + msg_id = (msg_id + 1) % 65535 + refresh_bytes = packer.pack([0, msg_id, "draw_frame", last_sent_params]) + try: + router.sendall(refresh_bytes) + frames_sent += 1 + except (BlockingIOError, OSError): + pass - # Write stats every 2s if now - stats_last_write >= 2.0: fps = fps_sample_frames / (now - fps_sample_start) if (now - fps_sample_start) > 0 else 0 _write_stats(frames_sent, frames_dropped, fps, len(active_notes), events_processed)