fix led-matrix-viz: use select.poll() instead of settimeout, fix hang

The O_NONBLOCK flag set by setblocking(False) was not cleared by
settimeout(n) on this CPython/kernel combo, causing recvfrom to never
return data. Switched to explicit select.poll() with frame-interval
based timeout — simpler, more portable, and avoids the O_NONBLOCK trap.

Also fixes midi-bridge: add time.sleep(0.01) in no-data paths to
prevent 100% CPU spin from spurious POLLIN on f_midi rawmidi.
This commit is contained in:
2026-06-23 00:34:33 -07:00
parent dbd7c2cece
commit f1b65eb4d9
2 changed files with 37 additions and 26 deletions
+35 -26
View File
@@ -1,14 +1,14 @@
#!/usr/bin/env python3
"""Listen for MIDI events via UDP and display note activity on LED matrix.
Uses RPC requests (not notifications) for reliability, with rate limiting.
Uses select() for polling both UDP and router RPC responses.
"""
import socket, msgpack, time, os, re
import socket, msgpack, time, os, select
VIZ_PORT = int(os.environ.get("VIZ_PORT", "8082"))
ROUTER_SOCK = os.environ.get("ROUTER_SOCK", "/var/run/arduino-router.sock")
DECAY_FRAMES = int(os.environ.get("DECAY_FRAMES", "8"))
MIN_INTERVAL = 1 / 15
FPS = 15
COLS = 13
ROWS = 8
@@ -35,35 +35,45 @@ def main():
active_notes = {}
last_frame = None
last_send = 0
buf = b""
msg_id = 0
frame_interval = 1.0 / FPS
next_frame = time.monotonic()
poller = select.poll()
poller.register(sock, select.POLLIN)
while True:
now = time.monotonic()
timeout = max(0, int((next_frame - time.monotonic()) * 1000))
try:
while True:
data, _ = sock.recvfrom(1024)
buf += data
except BlockingIOError:
pass
events = poller.poll(timeout)
except OSError:
events = []
while b";" in buf:
msg, buf = buf.split(b";", 1)
msg = msg.decode().strip()
parts = msg.split()
if len(parts) >= 3 and parts[0] == "midi":
status = int(parts[1])
note = int(parts[2])
vel = int(parts[3]) if len(parts) >= 4 else 0
msg_type = status & 0xF0
if msg_type == 0x90 and vel > 0:
active_notes[note] = DECAY_FRAMES
elif msg_type == 0x80 or (msg_type == 0x90 and vel == 0):
active_notes.pop(note, None)
for fd, flags in events:
if fd == sock.fileno() and flags & select.POLLIN:
try:
data, _ = sock.recvfrom(1024)
buf += data
while b";" in buf:
msg, buf = buf.split(b";", 1)
msg = msg.decode().strip()
parts = msg.split()
if len(parts) >= 3 and parts[0] == "midi":
status = int(parts[1])
note = int(parts[2])
vel = int(parts[3]) if len(parts) >= 4 else 0
msg_type = status & 0xF0
if msg_type == 0x90 and vel > 0:
active_notes[note] = DECAY_FRAMES
elif msg_type == 0x80 or (msg_type == 0x90 and vel == 0):
active_notes.pop(note, None)
except BlockingIOError:
pass
if time.monotonic() >= next_frame:
next_frame = time.monotonic() + frame_interval
if now - last_send >= MIN_INTERVAL:
pixels = [[0] * COLS for _ in range(ROWS)]
for note, decay in list(active_notes.items()):
if decay <= 0:
@@ -94,7 +104,6 @@ def main():
router.settimeout(2)
router.connect(ROUTER_SOCK)
last_frame = frame
last_send = now
if __name__ == "__main__":
main()