Restructure the workshop flow into the designer's cockpit: a persistent shell (header + 5-step stepper + sticky instrument rail) wrapping the phase routes via a React-Router layout route, so the rail stays mounted across navigation. - Design system: IBM Plex Mono + Newsreader; the full cockpit token set (light + dark) in index.css; a working light/dark theme toggle (store `theme` + useApplyTheme); a `switch` ui primitive. - Shell: CockpitLayout, Stepper (forward-gated), PanelChrome helpers. Every phase page restyled to the editorial panels + the WORKSHOP-FLOW fixes (channels-after-bind, domain framing + L1 prefill, L2/L3 prefill at 3/3, in-place submission finale). Store gains `tried` + `channels.saidHi` (v4). - Live rail (CockpitRail): real node heartbeat + agent activity log + ADD progress; sim telemetry (useTelemetry) for the waveform/accel/I2C behind a seam, marked SIM. - LED-matrix PIXEL MIRROR (real): the rail shows exactly what the physical matrix displays — API GET /nodes/:team/matrix reads the board's framebuffer off the :9999 relay (readMatrixFrame + the `matrixget` relay command); useMatrixMirror polls it and unpacks the 104 bits. Co-Authored-By: Claude Opus 4.8 <[email protected]>
75 lines
2.8 KiB
Python
75 lines
2.8 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 == "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()
|