# UNO Q — Workshop Playbook Practical guidance for running a hands-on session with the board. Assumes the reader already understands the two-processor model (see SKILL.md). ## Pre-flight checklist (per station) - [ ] Board + a **known-good USB-C cable** rated for **5 V / 3 A** (or a multiport dongle with external PD — **not Apple's**, it's incompatible). - [ ] For SBC stations: HDMI monitor, USB keyboard/mouse via dongle, power to the dongle. - [ ] Decide the mode up front: **SBC** (self-contained, needs 4 GB variant recommended) vs **Network Mode** (students drive it from their laptops). - [ ] Each board: set a **board name** (becomes `.local`), a password, and Wi-Fi during first-run. SSH turns on automatically after this. - [ ] If any facilitator laptop runs **Linux**, install the **udev rules** first (see setup reference) — otherwise App Lab connects silently to nothing. - [ ] Network allows **mDNS / UDP 5353** (needed for Network Mode discovery). On locked-down campus Wi-Fi this is the usual blocker. - [ ] For enterprise/eduroam Wi-Fi, pre-stage the `nmcli` 802.1X command (see wireless reference). ## Common failure modes → fixes | Symptom | Likely cause | Fix | |---|---|---| | App Lab shows no board (Linux PC, USB) | Missing udev rules | Install rules, reconnect board, restart App Lab | | Board absent in Network Mode | mDNS blocked | Open UDP 5353; approve `mdns-discovery.exe` on Windows; confirm same subnet | | Flash/upload "permission denied" (Linux) | udev not applied | Re-run rules, `udevadm control --reload-rules && udevadm trigger`, replug | | Python can't reach the sketch | router down / wrong function name | `systemctl status arduino-router`; check the `provide()` name matches the `call()` string; verbose logs | | Handler crashes / hangs | Arduino API called from `provide()` (RPC thread) | Use `provide_safe()`; never `Serial.print`/`Bridge.call` inside a handler | | Nothing on a pin | wrong logic level or wrong processor | MCU I/O is 3.3 V; onboard RGB LEDs are active-low; confirm the sketch (not Python) owns the pin | | `Serial1` acting weird | confusing header UART with router link | `Serial1` on D0/D1 is fine for external UART; never open the internal reserved transport / `/dev/ttyHS1` | | Can't SSH | no IP / SSH not set up | `ip addr show` (wlan0) on the board; SSH is enabled once first-run setup completes | ## The canonical teaching example: "Python button decides, Arduino acts" This one App ties the entire board together and is the best first exercise because it forces students to touch **both** processors and the **Bridge**. **Concept:** the sketch owns an LED (a physical pin); Python owns the *logic* and calls across the Bridge to actuate it. Swap the LED for a relay/motor driver and you have real device control; swap the Python timer for a camera/AI Brick and you have an intelligent actuator. **MCU sketch (`sketch.ino`):** ```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); // built-in LED active-low } ``` **Linux Python (`main.py`):** ```python from arduino.app_utils import * import time state = False def loop(): global state time.sleep(1) state = not state Bridge.call("set_led_state", state) App.run(user_loop=loop) ``` **Teaching beats:** 1. Run it → LED blinks, but emphasize *the decision happened in Python on Linux, the action happened on the MCU.* 2. Break it on purpose: rename `"set_led_state"` on one side → show the router log error → fix. Cements the RPC contract idea. 3. Extend: have Python read something real (time of day, a web request, a camera Brick) and gate the LED on it. 4. Reverse it: MCU reads a real sensor and `Bridge.call()`s a Python function that logs/uploads it. ## Progression for a full session 1. **Blink** (App Lab example) — proves the MCU toolchain. 2. **Read a sensor in a sketch**, print with `Serial` → App Lab console — proves I/O + debugging. 3. **Bridge example above** — proves Linux↔MCU. 4. **Add a Brick** (vision/audio) on the Python side — shows the payoff of the Linux half. 5. **Deploy:** set the App to **Run at startup** and unplug the keyboard → it's now an appliance. Manage it over **SSH** + `arduino-app-cli`. ## Deploying student projects headlessly - Copy the App to the board: `scp -r * arduino@:~/ArduinoApps/`. - Start/stop/log via `arduino-app-cli app start|stop|logs ~/ArduinoApps/`. - Autostart the finished one: `arduino-app-cli properties set default user:`.