# UNO Q — The Bridge (Linux ↔ MCU communication) This is the single most important capability of the board and the thing people get wrong most often. **"Connecting the Linux side to the Arduino side" means using the Bridge/RPC over the arduino-router. It never means wiring pins together.** ## The model: transparent remote procedure calls (RPC) The two processors exchange data over a dedicated internal serial link. On top of it, a **Remote Procedure Call** system lets a function on one processor be invoked from the other *as if it were a local call*. So Python on Linux can call a function that actually runs on the STM32 (and vice-versa), passing arguments and getting results back. Building blocks: - **`arduino-router`** — a background **Linux systemd service** implementing a **star-topology** network using **MessagePack-RPC**. Multiple Linux processes can talk to the MCU *and to each other* through it simultaneously (multipoint). Clients register the functions they offer; the router keeps a directory and routes calls (service discovery). - **The Bridge library** — on the MCU side, `Arduino_RouterBridge.h` (built on `Arduino_RPClite`) gives you the `Bridge` object. On the Linux side, App Lab's `arduino.app_utils` exposes the matching `Bridge` in Python. Source repos (for reference): `github.com/arduino/arduino-router` and `github.com/arduino-libraries/Arduino_RouterBridge`. ## Reserved resources — DO NOT TOUCH The router owns the physical link. **Never open these in your own code:** - **Linux:** `/dev/ttyHS1` - **MCU:** the `Serial1` hardware port (Opening them yourself breaks the bridge. Note this is distinct from ordinary `Serial` debug output, which is fine — see the I/O reference.) ## The MCU-side API (`Bridge`) - `Bridge.begin()` — initialize the bridge + serial transport (call in `setup()`; it can return false if it fails). - `Bridge.provide(name, fn)` — expose an MCU function so Linux can call it. **Runs in a high-priority RPC thread.** - `Bridge.provide_safe(name, fn)` — same, but the function runs inside the main `loop()` context, which is safe for using the normal Arduino APIs. **Prefer `provide_safe` when your handler calls Arduino functions** (`digitalWrite`, `analogRead`, most libraries). - `Bridge.call(method, args...)` — call a Linux-side function and wait for the result. - `Bridge.notify(method, args...)` — fire-and-forget call (no result awaited). - **`Monitor`** — a predefined object to stream text from Linux to the MCU (via the `mon/write` RPC method); legacy, `Serial` is now preferred for console output. - Concurrency is handled with Zephyr mutexes (`k_mutex`); a background thread services incoming updates. ⚠️ **Do not call `Bridge.call()`, `Monitor.print()`, or `Serial.print()` inside a `provide()` handler** — the handler runs in the RPC thread and these will misbehave/deadlock. Do the work in the handler, and do any printing/bridge-calling from `loop()` instead. (This is exactly why `provide_safe()` exists.) ## The Python-side API (App Lab) `from arduino.app_utils import *` gives you `App` and `Bridge`. - `Bridge.call("method_name", args...)` — invoke a function the MCU registered with `provide()`. - `App.run(user_loop=loop)` — App Lab's main loop runner for the Python program. ## Canonical example — Linux drives an MCU-owned pin The MCU owns `LED_BUILTIN`; Python decides when to toggle it and calls across the bridge. **Linux side — `main.py` (runs on the MPU):** ```python from arduino.app_utils import * import time led_state = False def loop(): global led_state time.sleep(1) led_state = not led_state Bridge.call("set_led_state", led_state) # invoke the MCU function App.run(user_loop=loop) ``` **MCU side — `sketch.ino` (runs on the STM32):** ```cpp #include "Arduino_RouterBridge.h" void setup() { pinMode(LED_BUILTIN, OUTPUT); Bridge.begin(); Bridge.provide("set_led_state", set_led_state); // expose to Linux } void loop() {} void set_led_state(bool state) { digitalWrite(LED_BUILTIN, state ? LOW : HIGH); // built-in LED is active-low } ``` That is the whole pattern for **controlling anything attached to the board from the Linux/Python side**: the sketch owns the pin and `provide()`s a function; Python `call()`s it. To go the other direction (MCU reads a sensor and pushes to Linux), have Python `provide` a function and the MCU `Bridge.call()` it — or have the MCU `notify()` events. ## Managing the router service (troubleshooting the bridge) ```bash systemctl status arduino-router # is it running? sudo systemctl restart arduino-router # restart it journalctl -u arduino-router -f # live logs ``` Enable verbose logging by appending `--verbose` to the `ExecStart=` line in `/etc/systemd/system/arduino-router.service`: ``` ExecStart=/usr/bin/arduino-router --unix-port /var/run/arduino-router.sock --serial-port /dev/ttyHS1 --serial-baudrate 115200 --verbose ``` then: ```bash sudo systemctl daemon-reload sudo systemctl restart arduino-router journalctl -u arduino-router -f ``` ## Advanced: talk to the router from any language via the Unix socket The router listens on a **Unix domain socket** and speaks **MessagePack-RPC**, so any language (Python, C++, Rust, Go…) can drive the MCU without App Lab: ``` /var/run/arduino-router.sock ``` MCU sketch (note `provide_safe`, since the handler uses `digitalWrite`): ```cpp #include "Arduino_RouterBridge.h" void setup() { pinMode(LED_BUILTIN, OUTPUT); Bridge.begin(); Bridge.provide_safe("set_led_state", set_led_state); } void loop() {} void set_led_state(bool state) { digitalWrite(LED_BUILTIN, state ? LOW : HIGH); } ``` Raw Python client (no App Lab) — install `sudo apt install python3-msgpack`: ```python import socket, msgpack, sys SOCKET_PATH = "/var/run/arduino-router.sock" led_state = True if len(sys.argv) > 1: led_state = (sys.argv[1] == "1") # MessagePack-RPC request: [type=0 (request), msgid, method, params] request = [0, 1, "set_led_state", [led_state]] packed_req = msgpack.packb(request) try: with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client: client.connect(SOCKET_PATH) client.sendall(packed_req) response = msgpack.unpackb(client.recv(1024)) print(f"Router Response: {response}") except Exception as e: print(f"Connection failed: {e}") ``` ```bash python3 msgpack_test.py 1 # LED ON python3 msgpack_test.py 0 # LED OFF ``` ## Mental checklist when wiring the two sides together 1. Which side owns the physical resource? → that side `provide()`s the function. 2. Does the handler touch Arduino APIs? → use `provide_safe()`. 3. Never print or `call()` from inside a `provide()` handler. 4. Leave `/dev/ttyHS1` and `Serial1` alone. 5. If nothing gets through, check `systemctl status arduino-router` and the verbose logs first.