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:
+35
-26
@@ -1,14 +1,14 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Listen for MIDI events via UDP and display note activity on LED matrix.
|
"""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"))
|
VIZ_PORT = int(os.environ.get("VIZ_PORT", "8082"))
|
||||||
ROUTER_SOCK = os.environ.get("ROUTER_SOCK", "/var/run/arduino-router.sock")
|
ROUTER_SOCK = os.environ.get("ROUTER_SOCK", "/var/run/arduino-router.sock")
|
||||||
DECAY_FRAMES = int(os.environ.get("DECAY_FRAMES", "8"))
|
DECAY_FRAMES = int(os.environ.get("DECAY_FRAMES", "8"))
|
||||||
MIN_INTERVAL = 1 / 15
|
FPS = 15
|
||||||
|
|
||||||
COLS = 13
|
COLS = 13
|
||||||
ROWS = 8
|
ROWS = 8
|
||||||
@@ -35,35 +35,45 @@ def main():
|
|||||||
|
|
||||||
active_notes = {}
|
active_notes = {}
|
||||||
last_frame = None
|
last_frame = None
|
||||||
last_send = 0
|
|
||||||
buf = b""
|
buf = b""
|
||||||
msg_id = 0
|
msg_id = 0
|
||||||
|
frame_interval = 1.0 / FPS
|
||||||
|
next_frame = time.monotonic()
|
||||||
|
|
||||||
|
poller = select.poll()
|
||||||
|
poller.register(sock, select.POLLIN)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
now = time.monotonic()
|
timeout = max(0, int((next_frame - time.monotonic()) * 1000))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
events = poller.poll(timeout)
|
||||||
data, _ = sock.recvfrom(1024)
|
except OSError:
|
||||||
buf += data
|
events = []
|
||||||
except BlockingIOError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
while b";" in buf:
|
for fd, flags in events:
|
||||||
msg, buf = buf.split(b";", 1)
|
if fd == sock.fileno() and flags & select.POLLIN:
|
||||||
msg = msg.decode().strip()
|
try:
|
||||||
parts = msg.split()
|
data, _ = sock.recvfrom(1024)
|
||||||
if len(parts) >= 3 and parts[0] == "midi":
|
buf += data
|
||||||
status = int(parts[1])
|
while b";" in buf:
|
||||||
note = int(parts[2])
|
msg, buf = buf.split(b";", 1)
|
||||||
vel = int(parts[3]) if len(parts) >= 4 else 0
|
msg = msg.decode().strip()
|
||||||
msg_type = status & 0xF0
|
parts = msg.split()
|
||||||
if msg_type == 0x90 and vel > 0:
|
if len(parts) >= 3 and parts[0] == "midi":
|
||||||
active_notes[note] = DECAY_FRAMES
|
status = int(parts[1])
|
||||||
elif msg_type == 0x80 or (msg_type == 0x90 and vel == 0):
|
note = int(parts[2])
|
||||||
active_notes.pop(note, None)
|
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)]
|
pixels = [[0] * COLS for _ in range(ROWS)]
|
||||||
for note, decay in list(active_notes.items()):
|
for note, decay in list(active_notes.items()):
|
||||||
if decay <= 0:
|
if decay <= 0:
|
||||||
@@ -94,7 +104,6 @@ def main():
|
|||||||
router.settimeout(2)
|
router.settimeout(2)
|
||||||
router.connect(ROUTER_SOCK)
|
router.connect(ROUTER_SOCK)
|
||||||
last_frame = frame
|
last_frame = frame
|
||||||
last_send = now
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ def main():
|
|||||||
elif len(buf) >= 2 and (buf[0] & 0xE0) in (0xC0, 0xD0):
|
elif len(buf) >= 2 and (buf[0] & 0xE0) in (0xC0, 0xD0):
|
||||||
_flush(sock, buf)
|
_flush(sock, buf)
|
||||||
buf = bytearray()
|
buf = bytearray()
|
||||||
|
if not events:
|
||||||
|
time.sleep(0.01)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno == 19:
|
if e.errno == 19:
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
|
|||||||
Reference in New Issue
Block a user