Files
apress/deploy/uno-q/matrix-relay/relay.py
T
Omar SobhandClaude Opus 4.8 8ce8474d33 feat(uno-q): wire i2c_scan into the relay, allowlist, and modulino skill
- matrix-relay/relay.py: handle the `i2c` command → Bridge.call("i2c_scan").
- config.template: allow + auto-approve i2c_scan on the default risk profile.
- modulino skill: point "confirm a module is present" at i2c_scan (MCU Qwiic
  bus) instead of the Linux i2cdetect.

Completes the container-native path: the three canned prompts (list I2C /
matrix pattern / scroll text) all run through the RouterBridge responder —
no /dev/i2c, no flash — so they work in the App Lab container node.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-22 12:59:18 -07:00

69 lines
2.6 KiB
Python

# matrix-relay — a tiny TCP:9999 → RouterBridge relay for the resident MCU sketch.
#
# The ZeroClaw daemon runs on the HOST (for full hardware access), but its
# matrix_pattern / matrix_text tools speak a simple line protocol to :9999, and
# the actual MCU is reached via the Arduino RouterBridge (msgpack-rpc over
# /run/arduino-router.sock) whose python binding (arduino.app_utils) only ships
# in the App Lab container image. So we run JUST this relay in a minimal
# container that mounts the router socket and publishes :9999 to the host.
#
# Protocol (one line per connection):
# ping -> pong
# matrix <0-7> -> Bridge.call("matrix_set", id) preset animation
# text <words...> -> Bridge.call("matrix_text", str) scroll text
# i2c -> Bridge.call("i2c_scan") list I2C devices (MCU)
# gpio_write <p> <v>-> Bridge.call("digitalWrite", p, v)
# gpio_read <p> -> Bridge.call("digitalRead", p) -> value
import socket
import threading
from arduino.app_utils import Bridge # RouterBridge client (container-only binding)
PORT = 9999
def handle(conn):
try:
data = conn.recv(256).decode().strip()
parts = data.split()
cmd = parts[0].lower() if parts else ""
if cmd == "ping":
conn.sendall(b"pong\n")
elif cmd == "matrix" and len(parts) >= 2:
Bridge.call("matrix_set", int(parts[1]))
conn.sendall(b"ok\n")
elif cmd == "text" and len(parts) >= 2:
Bridge.call("matrix_text", " ".join(parts[1:]))
conn.sendall(b"ok\n")
elif cmd == "i2c":
r = Bridge.call("i2c_scan")
conn.sendall(f"{r}\n".encode())
elif cmd == "gpio_write" and len(parts) >= 3:
Bridge.call("digitalWrite", int(parts[1]), int(parts[2]))
conn.sendall(b"ok\n")
elif cmd == "gpio_read" and len(parts) >= 2:
conn.sendall(f"{Bridge.call('digitalRead', int(parts[1]))}\n".encode())
else:
conn.sendall(b"error: invalid command\n")
except Exception as e:
try:
conn.sendall(f"error: {e}\n".encode())
except Exception:
pass
finally:
conn.close()
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("0.0.0.0", PORT))
s.listen(5)
print(f"matrix-relay listening on :{PORT}", flush=True)
while True:
conn, _ = s.accept()
threading.Thread(target=handle, args=(conn,), daemon=True).start()
if __name__ == "__main__":
main()