# 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 -> Bridge.call("matrix_text", str) scroll text # i2c -> Bridge.call("i2c_scan") list I2C devices (MCU) # gpio_write

-> Bridge.call("digitalWrite", p, v) # gpio_read

-> 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 == "count" and len(parts) >= 2: Bridge.call("matrix_count", int(parts[1])) conn.sendall(b"ok\n") elif cmd == "matrixget": r = Bridge.call("matrix_get") conn.sendall(f"{r}\n".encode()) 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()