feat(uno-q): ship the arduino-uno-q expert skill on every node by default
Vendors the comprehensive UNO Q skill (SKILL.md + 7 references/*.md) and installs it into EVERY agent's workspace on each board, so agents know this board's specifics (dual-brain arch, Bridge/RPC, pin tables, LED matrix + the ArduinoGraphics-not-installed gotcha) instead of guessing generic Arduino. Why per-agent workspace: ZeroClaw's read_skill returns only SKILL.md; the agent reads references/*.md via the workspace-sandboxed file_read tool, so references are only reachable under ~/.zeroclaw/agents/<alias>/workspace/skills/. A shared/skills bundle surfaces the skill but its references get sandbox-blocked. - push-skill.sh installs a SKILL.md+references skill into every agent workspace (discovers aliases from the board); provision-fleet runs it per board. - config.template risk profile now allows + auto-approves read_skill + file_read so agents load skills without a human approver (webhook path is non-interactive). - Flattened the folded 'description: >-' to single-line (ZeroClaw's frontmatter parser is a flat scanner, not full YAML). Verified on board 65301572 with cloud/Sonnet-5: discovered arduino-uno-q → read_skill(SKILL.md) → file_read references/04-bridge-rpc.md → correct board-specific answer citing the file. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7233a4b1c0
commit
d2135a1938
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: arduino-uno-q
|
||||
description: Expert working knowledge of the Arduino UNO Q dual-brain single-board computer (Qualcomm Dragonwing QRB2210 Linux microprocessor + STM32U585 microcontroller). Use this skill whenever the user works with, teaches, prototypes on, or troubleshoots an Arduino UNO Q — any mention of 'UNO Q', App Lab, Bricks, the Bridge/RPC library, the arduino-router, connecting the Linux side to the Arduino/MCU side, running Python plus a sketch together, SSHing into the board, Network Mode, flashing or running apps, wiring or controlling sensors/actuators/LEDs/motors/shields, GPIO/ADC/DAC/PWM/SPI/I2C/Qwiic/UART pins, Wi-Fi or Bluetooth on the board, or preparing a workshop around it. Trigger even when the user doesn't say 'UNO Q' but is clearly on this board. Prefer this over generic Arduino advice — the UNO Q's two-processor architecture makes most single-MCU instructions wrong or incomplete.
|
||||
---
|
||||
|
||||
# Arduino UNO Q
|
||||
|
||||
The UNO Q is not a normal Arduino. It is **two computers on one classic UNO-shaped board**, and almost every mistake people make with it comes from forgetting that. Internalize the architecture first; everything else follows from it.
|
||||
|
||||
## The one mental model that matters
|
||||
|
||||
The board carries two processors that run at the same time:
|
||||
|
||||
- **The MPU (the "Linux side")** — a Qualcomm Dragonwing **QRB2210**, quad-core Arm Cortex-A53 @ 2.0 GHz, running a full **Debian Linux** OS. This is where Python, AI/vision, networking, the desktop, SSH, and the **Arduino App Lab** live. Think of it as a Raspberry-Pi-class computer.
|
||||
- **The MCU (the "Arduino side")** — an **STM32U585**, Arm Cortex-M33 @ 160 MHz, running your Arduino **sketch** on top of **Zephyr OS**. This is where real-time, deterministic pin control lives: GPIO, ADC, DAC, PWM, SPI, I2C, UART. Think of it as a classic Arduino.
|
||||
|
||||
They are wired together internally and talk to each other over a dedicated serial link managed by a Linux service called the **`arduino-router`**. The library layer on top of that link is the **Bridge (RPC)** library. **This bridge is the heart of the board** — "connect the Linux side to the Arduino side" always means "use the Bridge / arduino-router," never physical jumper wires.
|
||||
|
||||
Decision rule you will use constantly:
|
||||
|
||||
- Needs precise timing, or reads/writes a physical pin (a sensor, a motor driver, an LED, a shield)? → **MCU / sketch**.
|
||||
- Needs Python, the network, a filesystem, heavy compute, a camera, or AI? → **MPU / Linux**.
|
||||
- Needs both to cooperate (the usual case)? → run code on **each** side and connect them with the **Bridge**.
|
||||
|
||||
If you only remember one thing: **you don't control an attached component "from Linux" directly. You write a sketch on the MCU that owns the pin, expose a function over the Bridge, and call it from Python on the Linux side.** The [Bridge reference](references/04-bridge-rpc.md) shows exactly how.
|
||||
|
||||
## Two ways to develop, and where the code runs
|
||||
|
||||
- **Arduino App Lab** is the primary environment and comes **pre-installed** on the board. An "App" bundles a Python program (Linux/MPU) *and* an Arduino sketch (MCU) together, plus reusable building blocks called **Bricks**. Use App Lab whenever the project uses the Linux side at all (Python, AI, networking) or needs both processors. It can run **on the board itself** (Single-Board-Computer mode, with a monitor/keyboard) or **from your PC** over the network (**Network Mode**).
|
||||
- **Arduino IDE (beta)** programs **only the MCU (STM32)**. It cannot touch the Qualcomm processor. Use it only for pure-microcontroller sketches when you don't need Linux at all.
|
||||
|
||||
So: if a workshop exercise involves Python or the network, it is an App Lab project. If it is "just blink an LED / read a sensor with a sketch," either tool works, but App Lab keeps everything in one place.
|
||||
|
||||
## How to help with this board
|
||||
|
||||
1. **Locate the work on the right processor(s)** using the decision rule above. State it explicitly to the user — it prevents 80% of confusion.
|
||||
2. **If the two sides must cooperate, design the Bridge contract first**: which functions does the MCU `provide()`, and what does the Python side `call()`? Then write each side.
|
||||
3. **For anything physical** (wiring a component, choosing a pin), go to the pin/peripheral reference for exact pin mappings, voltage levels, and library usage — the UNO Q has real gotchas (3.3 V logic, active-low LEDs, reserved `Serial1`/`/dev/ttyHS1`, fixed 500 Hz PWM).
|
||||
4. **For remote/headless workshop setups**, use SSH + Network Mode + the CLI so students can work from their own laptops.
|
||||
5. **Verify against the reference files** rather than reciting generic Arduino knowledge from memory — the details on this board are specific and easy to get wrong.
|
||||
|
||||
## Reference map — read the file that fits the task
|
||||
|
||||
Load these as needed; each is self-contained. Don't dump all of them into context at once — pick the one the current question is about.
|
||||
|
||||
- **[references/01-hardware-specs.md](references/01-hardware-specs.md)** — Full architecture, exact chip/model numbers, memory/storage variants, power options, USB-C capabilities, dimensions, onboard UI (8×13 LED matrix, 4 RGB LEDs, power button, debug UART). Read for "what is on the board / what can it do / how do I power it."
|
||||
- **[references/02-setup-and-os.md](references/02-setup-and-os.md)** — First boot, Debian, installing/using App Lab, Bricks & Apps model, **Linux host udev setup (required for Linux PCs)**, running an App at startup, the `arduino-app-cli`, Arduino IDE core install. Read for "getting started / install / it won't connect / autostart."
|
||||
- **[references/03-remote-access.md](references/03-remote-access.md)** — **SSH**, Network Mode (mDNS), finding the board's IP, `adb`, copying files with `scp`, running/stopping apps remotely. Read for "access the board remotely / headless / from my laptop / over the network."
|
||||
- **[references/04-bridge-rpc.md](references/04-bridge-rpc.md)** — **The Linux↔MCU bridge**: the arduino-router service, the Bridge/RPC API, `provide()` vs `provide_safe()`, `call()`/`notify()`, complete Python-side and sketch-side examples, the raw Unix-socket/MessagePack path, and the reserved-resource warnings. Read for "make Python talk to the sketch / call an Arduino function from Linux / control a pin from Python."
|
||||
- **[references/05-io-and-peripherals.md](references/05-io-and-peripherals.md)** — Every pin table (digital/analog/PWM), ADC/DAC, SPI, I2C, **Qwiic**, UART, and how to actually drive attached components (sensors, actuators, LEDs, shields). Read for "wire up / control / read a component / which pin."
|
||||
- **[references/06-wireless.md](references/06-wireless.md)** — Wi-Fi (incl. WPA2-Enterprise / eduroam), Bluetooth, and reaching the network from the MCU via the Bridge. Read for "get it online / connect to Wi-Fi / Bluetooth."
|
||||
- **[references/07-workshop-playbook.md](references/07-workshop-playbook.md)** — Opinionated setup checklist, common failure modes and fixes, and a canonical "Python button → Arduino LED" starter that ties the whole model together. Read when planning or running a hands-on session.
|
||||
|
||||
## Quick reference (the things people look up most)
|
||||
|
||||
- **Board name over the network:** the hostname you set during setup, reachable as `<name>.local` via mDNS; default Linux user is **`arduino`**.
|
||||
- **SSH in:** `ssh arduino@<BOARD_IP>` (find IP with `ip addr show` → look under `wlan0`).
|
||||
- **Router service:** `systemctl status arduino-router` · logs: `journalctl -u arduino-router -f`. **Never** open `/dev/ttyHS1` (Linux) or `Serial1` (MCU) yourself — the router owns them.
|
||||
- **Bridge socket (advanced):** `/var/run/arduino-router.sock` (MessagePack-RPC).
|
||||
- **Logic level on the pins:** the MCU I/O is **3.3 V**; the Qwiic connector is 3.3 V only. The onboard RGB LEDs are **active-low**.
|
||||
- **Serial for debugging:** on the MCU, plain `Serial.print()` shows up in the App Lab console (platform ≥ 0.55.0); `Serial1` is the physical D0/D1 UART.
|
||||
@@ -0,0 +1,108 @@
|
||||
# UNO Q — Hardware & Specifications
|
||||
|
||||
## Architecture at a glance
|
||||
|
||||
The UNO Q combines two processors on one classic UNO form-factor board:
|
||||
|
||||
| Role | Chip | Details | Runs |
|
||||
|---|---|---|---|
|
||||
| **MPU (Linux side)** | Qualcomm **QRB2210** (Dragonwing) | Quad-core Arm Cortex-A53 @ **2.0 GHz**; Adreno 702 GPU @ 845 MHz; dual ISP up to 25 MP @ 30 fps | **Debian Linux** (full OS, upstream support) |
|
||||
| **MCU (Arduino side)** | **STM32U585** | Arm Cortex-M33 @ up to **160 MHz**, **2 MB flash**, **786 KB SRAM** | Arduino sketches on **Zephyr OS** |
|
||||
| **Radio** | **WCBN3536A** | Dual-band **Wi-Fi 5** (2.4/5 GHz) + **Bluetooth 5.1**, onboard antennas | — |
|
||||
| **Multimedia codec** | **ANX7625** | Video/audio out over the USB-C connector (DisplayPort) | — |
|
||||
| **PMIC** | Qualcomm **PM4145** | Power management | — |
|
||||
|
||||
The two processors are internally linked by a dedicated serial connection managed by the `arduino-router` service (see the Bridge reference). There is no need — and it is a mistake — to bridge them with external wires.
|
||||
|
||||
## Memory & storage variants
|
||||
|
||||
- **RAM:** 2 GB or 4 GB **LPDDR4**.
|
||||
- **Storage:** 16 GB or 32 GB **eMMC**.
|
||||
- Product SKU family: **ABX00162 … ABX00173**.
|
||||
- Arduino recommends the **4 GB** variant for standalone / single-board-computer use (running the desktop + App Lab on-device). The 2 GB variant is fine when driven from a PC in Network Mode.
|
||||
|
||||
## Powering the board
|
||||
|
||||
Three supported ways to power it (choose one):
|
||||
|
||||
1. **USB-C** cable providing **5 VDC / 3 A** (cable not included). 15 W sink.
|
||||
2. External **+5 VDC** to the **5V** pin.
|
||||
3. External **+7–24 VDC** to the **VIN** pin.
|
||||
|
||||
For a standalone desk setup the usual path is a USB-C multiport (dongle) adapter with external power delivery feeding the board while also breaking out HDMI/USB. **Apple's USB-C dongle has been tested and found incompatible** — use a non-Apple multiport adapter.
|
||||
|
||||
The board **boots automatically when powered** — you do not press the power button to start it.
|
||||
|
||||
## USB-C connector — it does far more than power/programming
|
||||
|
||||
| Feature | Capability |
|
||||
|---|---|
|
||||
| USB power (sink) | 5 VDC / 3 A (15 W) |
|
||||
| USB standard | USB 3.1 Gen 1 (5 Gb/s) |
|
||||
| Display over USB-C | DisplayPort output |
|
||||
|
||||
With a USB-C **dongle** you additionally get: **HDMI** video out, **USB camera** input, **audio** (USB or 3.5 mm headset), **Ethernet**, **HID** (keyboard/mouse), and **storage** (microSD/USB drive). This is what makes SBC mode possible.
|
||||
|
||||
## Form factor & connectors
|
||||
|
||||
- **Classic Arduino UNO form factor** → compatible with the huge existing range of UNO **shields**.
|
||||
- **Two connector tiers:**
|
||||
- **UNO-style headers** on top — prototyping/debugging, shield-compatible.
|
||||
- **High-speed header connectors** on the bottom (JMISC, JDIGITAL, JANALOG, JCTL, etc.) exposing extra digital pins and low-level interfaces.
|
||||
- Pinout PDF, datasheet, schematics, and STEP files are published on the Arduino docs product page (model ABX00162). Point users there for mechanical/CAD detail.
|
||||
|
||||
## Onboard user interface
|
||||
|
||||
### 8×13 blue LED matrix (MCU-controlled)
|
||||
- 104 LEDs (8 rows × 13 cols), driven by the STM32.
|
||||
- Library: `Arduino_LED_Matrix.h`. Draw a frame from a `uint8_t[104]` array.
|
||||
- Supports **grayscale/dimming**: `matrix.setGrayscaleBits(bits)` — e.g. `3` → 8 levels (0–7), `8` → 256 levels (0–255).
|
||||
|
||||
```cpp
|
||||
#include <Arduino_LED_Matrix.h>
|
||||
Arduino_LED_Matrix matrix;
|
||||
uint8_t frame[104] = { /* 8 rows of 13 values */ };
|
||||
void setup() {
|
||||
matrix.begin();
|
||||
matrix.setGrayscaleBits(1); // 1 bit = on/off
|
||||
matrix.draw(frame);
|
||||
}
|
||||
void loop() {}
|
||||
```
|
||||
|
||||
### 4× RGB LEDs — split across both processors
|
||||
- **LED 1 & LED 2 → controlled by the MPU (Linux)** via the sysfs LED class at `/sys/class/leds/...`.
|
||||
- **LED 3 & LED 4 → controlled by the MCU (sketch)** via `digitalWrite()` on named pins.
|
||||
- **All onboard RGB LEDs are ACTIVE-LOW** — logic `0` / `LOW` turns a segment **ON**.
|
||||
|
||||
MPU (Linux) sysfs names:
|
||||
- LED 1: `red:user`, `green:user`, `blue:user`
|
||||
- LED 2: `red:panic`, `green:wlan`, `blue:bt`
|
||||
|
||||
```bash
|
||||
echo 1 | tee /sys/class/leds/red:user/brightness # ON
|
||||
echo 0 | tee /sys/class/leds/red:user/brightness # OFF
|
||||
```
|
||||
|
||||
Or from Python via App Lab's helper:
|
||||
```python
|
||||
from arduino.app_utils import App, Leds
|
||||
Leds.set_led1_color(1, 0, 0) # LED1 red on
|
||||
Leds.set_led1_color(0, 0, 0) # LED1 off
|
||||
```
|
||||
|
||||
MCU (sketch) pin names: `LED3_R/LED3_G/LED3_B`, `LED4_R/LED4_G/LED4_B` — remember active-low:
|
||||
```cpp
|
||||
pinMode(LED3_R, OUTPUT);
|
||||
digitalWrite(LED3_R, LOW); // red ON (active-low)
|
||||
```
|
||||
|
||||
### Power button
|
||||
- **Long press (5+ seconds) → reboots the Linux system.**
|
||||
- Not needed to power on (auto-boots on power).
|
||||
|
||||
### Hardware debug UART (system console)
|
||||
- A dedicated low-level UART on the **JCTL** connector exposes the SoC's main console (bootloader messages + Linux shell login).
|
||||
- **Parameters: 115200 bps, 1.8 V logic.**
|
||||
- ⚠️ **1.8 V logic only** — you must use a **1.8 V** USB-to-TTL converter (e.g. DSD Tech SH-U09C5). Using a 3.3 V/5 V adapter can damage the board.
|
||||
- Use for deep debugging when the board won't boot or the network is unavailable; ordinary work should use SSH instead.
|
||||
@@ -0,0 +1,96 @@
|
||||
# UNO Q — Setup, Operating System & App Lab
|
||||
|
||||
## The operating systems, restated
|
||||
|
||||
- **MPU:** full **Debian Linux** with a desktop. It has a package manager (`apt`), systemd services, users/permissions, a filesystem, networking — treat it like any Debian machine. Arduino **App Lab** is pre-installed.
|
||||
- **MCU:** **Zephyr OS** running your Arduino sketch. You rarely interact with Zephyr directly; you write ordinary Arduino code and App Lab / the IDE handles the rest.
|
||||
|
||||
## Two development environments
|
||||
|
||||
### Arduino App Lab (primary)
|
||||
- **Pre-installed on the board.** Also installable on your PC (Windows/macOS/Linux) from the Arduino software page for Network-Mode development.
|
||||
- An **App** = a Python program (runs on Linux/MPU) **+** an Arduino sketch (runs on MCU) **+ Bricks**.
|
||||
- **Bricks** are reusable, pre-packaged building blocks (e.g. vision, audio, I/O helpers) you compose into an App — this is how App Lab gets you to AI/vision features quickly without writing everything from scratch.
|
||||
- Runs two ways:
|
||||
- **Single-Board-Computer (SBC) mode** — App Lab runs *on the board* with a monitor/keyboard/mouse attached via the dongle.
|
||||
- **Network Mode** — App Lab runs on your PC and talks to the board over the LAN (see remote-access reference).
|
||||
|
||||
### Arduino IDE (beta) — MCU only
|
||||
- Programs **only the STM32 microcontroller**. It **cannot** program the Qualcomm processor. For anything touching Linux/Python, use App Lab.
|
||||
- Install the core: **Tools → Board → Boards Manager → search "UNO Q" → install "Arduino UNO Q Zephyr Core."**
|
||||
- If it doesn't appear, add this to **File → Preferences → Additional Boards Manager URLs**:
|
||||
```
|
||||
https://downloads.arduino.cc/packages/package_zephyr_index.json
|
||||
```
|
||||
- Then select **Tools → Board → Arduino UNO Q Board**, pick the **Port**, and upload as usual. The classic `File → Examples → 01.Basics → Blink` works (`LED_BUILTIN` = the red channel of the built-in RGB LED).
|
||||
|
||||
## First boot as a Single-Board Computer
|
||||
|
||||
1. Plug the USB-C dongle into the board's USB-C port.
|
||||
2. Keyboard + mouse into the dongle's USB-A ports; monitor into the dongle's HDMI; power supply (**+5 VDC / 3 A min**) into the dongle.
|
||||
3. It boots automatically. At the Debian login the default user is **`arduino`**; you set a password.
|
||||
4. App Lab's first-run wizard walks through keyboard layout, **board name**, **Wi-Fi**, and login credentials. The board name becomes the mDNS hostname (`<name>.local`) used for Network Mode and SSH.
|
||||
|
||||
The preloaded desktop includes Chromium, a file manager, a terminal, Vim, and media playback. Install more software the normal Debian way, e.g. `sudo apt install vlc`.
|
||||
|
||||
## Linux host setup (REQUIRED when driving the board from a Linux PC)
|
||||
|
||||
On a Linux PC, App Lab talks to the board over USB and needs udev permissions, or **it fails silently** and flashing throws permission errors. The board presents two USB identities:
|
||||
|
||||
- **Operating mode:** VID `2341`, PID `0078`
|
||||
- **Emergency Download (EDL) mode:** VID `05c6`, PID `9008`
|
||||
|
||||
### Install the udev rules (canonical one-liner)
|
||||
```bash
|
||||
echo \
|
||||
'# Operating mode
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0078", MODE="0660", TAG+="uaccess"
|
||||
# EDL mode
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9008", MODE="0660", TAG+="uaccess"' \
|
||||
| sudo tee "/etc/udev/rules.d/60-Arduino-UNO-Q.rules" \
|
||||
&& sudo udevadm control --reload-rules \
|
||||
&& sudo udevadm trigger
|
||||
```
|
||||
`MODE="0660"` sets the device-file permissions; `TAG+="uaccess"` grants the logged-in user access via systemd-logind.
|
||||
|
||||
### Verify
|
||||
```bash
|
||||
cat /etc/udev/rules.d/60-Arduino-UNO-Q.rules
|
||||
lsusb | grep -E "2341:0078|05c6:9008"
|
||||
adb devices # board should be listed
|
||||
adb shell # drops you into a shell on the board
|
||||
```
|
||||
|
||||
### Apply
|
||||
**Disconnect and reconnect the board** (wait a few seconds), then restart App Lab so it re-reads permissions.
|
||||
|
||||
### Alternative: official post-install script
|
||||
From the ArduinoCore-zephyr repo — creates `/etc/udev/rules.d/60-arduino-zephyr.rules`:
|
||||
```bash
|
||||
cd ~/Downloads
|
||||
wget https://raw.githubusercontent.com/arduino/ArduinoCore-zephyr/main/post_install.sh
|
||||
chmod +x post_install.sh
|
||||
sudo ./post_install.sh
|
||||
```
|
||||
(or `curl -O <same URL>`, or `git clone https://github.com/arduino/ArduinoCore-zephyr.git` then run `post_install.sh`). Reconnect the board afterward.
|
||||
|
||||
## Hello World (Blink) to confirm the toolchain
|
||||
In App Lab the Examples section opens on launch → open **Blink LED** → **Run**. Success = the **red** channel of the built-in RGB LED blinks 1 s on / 1 s off. That LED is driven by the STM32 via the sketch, so a successful blink proves the MCU side is programmable.
|
||||
|
||||
## Running an App at startup (kiosk / standalone projects)
|
||||
- You **cannot** set a built-in *Example* as the startup app directly — first **copy it to a new App** or create your own.
|
||||
- In the App: click the **▼** arrow next to the **Run** button (top-right) → toggle **Run at startup** ON. A **DEFAULT** badge confirms it.
|
||||
- CLI equivalent:
|
||||
```bash
|
||||
arduino-app-cli properties set default user:<NAME_OF_YOUR_APP>
|
||||
```
|
||||
|
||||
## The App Lab CLI (`arduino-app-cli`)
|
||||
Runs on the board (use it over SSH for headless control):
|
||||
```bash
|
||||
arduino-app-cli app start ~/ArduinoApps/<project-name> # start an app
|
||||
arduino-app-cli app logs ~/ArduinoApps/<project-name> # stream its logs
|
||||
arduino-app-cli app stop ~/ArduinoApps/<project-name> # stop it
|
||||
arduino-app-cli properties set default user:<app-name> # set autostart app
|
||||
```
|
||||
Apps live under `~/ArduinoApps/` on the board.
|
||||
@@ -0,0 +1,67 @@
|
||||
# UNO Q — Remote & Headless Access
|
||||
|
||||
The Linux side is a real networked computer, so you can work with the board without a monitor. Three complementary paths: **Network Mode** (App Lab GUI over LAN), **SSH** (terminal), and **`adb`** (over USB). For a workshop where students use their own laptops, combine Network Mode for the GUI and SSH for the terminal.
|
||||
|
||||
## Prerequisite: get the board on the network
|
||||
The board must be on Wi-Fi (or Ethernet via dongle) and you need either its **IP** or its **mDNS hostname**. SSH is **enabled automatically** during the App Lab first-run setup once you configure a username, password, and Wi-Fi.
|
||||
|
||||
## Find the board's IP address
|
||||
Open a terminal on the board (App Lab bottom-left `>_` icon, or a desktop terminal) and run:
|
||||
```bash
|
||||
ip addr show
|
||||
```
|
||||
Look under the **`wlan0`** interface (or `eth0` if using Ethernet) for the `inet` address.
|
||||
|
||||
## Network Mode (App Lab over the LAN)
|
||||
- Run App Lab on your PC; boards discovered on the local network appear tagged **"Network."** Select yours and enter credentials — you now develop from your PC as if local.
|
||||
- Discovery uses **mDNS** (Bonjour/zeroconf). The board advertises `<board-name>.local`.
|
||||
- **Firewall/OS notes:**
|
||||
- Allow **UDP port 5353** (mDNS) through the firewall.
|
||||
- On Windows, approve the `mdns-discovery.exe` prompt from Windows Defender.
|
||||
- Reaching the board directly by browser, SSH, or raw IP does **not** guarantee it shows up in Network Mode — mDNS must be working for the App Lab discovery.
|
||||
|
||||
## SSH (terminal access)
|
||||
Default Linux user is **`arduino`**. Connect with either the IP or the mDNS name:
|
||||
```bash
|
||||
ssh arduino@<BOARD_IP> # e.g. ssh [email protected]
|
||||
ssh arduino@<board-name>.local # if mDNS resolves on your network
|
||||
```
|
||||
On first connect, accept the host-key fingerprint (`yes`) and enter the board password you set during setup.
|
||||
|
||||
For passwordless login, install your key the standard way:
|
||||
```bash
|
||||
ssh-copy-id arduino@<BOARD_IP>
|
||||
```
|
||||
(or append your public key to `~/.ssh/authorized_keys` on the board). After that, `ssh arduino@<BOARD_IP>` won't prompt for a password.
|
||||
|
||||
Once in over SSH you have the full board: `apt`, `systemctl` (e.g. manage `arduino-router`), the `arduino-app-cli`, the LED sysfs, etc.
|
||||
|
||||
## Copy files to the board
|
||||
Use `scp` (or `rsync`) from your PC. Apps live in `~/ArduinoApps/` on the board:
|
||||
```bash
|
||||
mkdir -p ~/ArduinoApps/<project-name> # (run on the board)
|
||||
scp -r * arduino@<BOARD_IP>:~/ArduinoApps/<project-name> # (run on your PC, from the project dir)
|
||||
```
|
||||
Then start/stop it remotely with the CLI (see setup reference):
|
||||
```bash
|
||||
arduino-app-cli app start ~/ArduinoApps/<project-name>
|
||||
arduino-app-cli app logs ~/ArduinoApps/<project-name>
|
||||
arduino-app-cli app stop ~/ArduinoApps/<project-name>
|
||||
```
|
||||
|
||||
## `adb` (over USB, no network needed)
|
||||
With the Linux host udev rules installed (see setup reference), the board also speaks `adb`:
|
||||
```bash
|
||||
adb devices # confirm the board is attached
|
||||
adb shell # shell into the board over USB
|
||||
```
|
||||
Handy when Wi-Fi isn't configured yet, for first-time provisioning, or for recovery.
|
||||
|
||||
## Hardware serial console (last resort)
|
||||
If the board won't boot or has no network, the **JCTL** debug UART gives the raw SoC console at **115200 bps, 1.8 V logic** (use a 1.8 V USB-TTL adapter — see hardware reference). It shows bootloader/kernel messages and offers a login prompt with your Linux credentials.
|
||||
|
||||
## Picking the right one
|
||||
- **GUI development from your laptop** → Network Mode.
|
||||
- **Scripting, service management, autostart, file transfer, headless control** → SSH (+ `scp`, `arduino-app-cli`).
|
||||
- **No network yet / provisioning / recovery** → `adb` over USB.
|
||||
- **Won't boot / diagnosing early startup** → 1.8 V hardware debug UART.
|
||||
@@ -0,0 +1,145 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,143 @@
|
||||
# UNO Q — Pins, Buses & Controlling Attached Components
|
||||
|
||||
**All physical I/O belongs to the MCU (STM32).** You control attached components by writing an Arduino sketch that owns the pin/bus. If Linux/Python needs to be involved, expose the control as a Bridge function and call it from Python (see the Bridge reference). MCU I/O is **3.3 V logic**.
|
||||
|
||||
## Digital pins
|
||||
47 digital pins total (22 on the UNO-style header, 25 on the JMISC connector). UNO-style header mapping:
|
||||
|
||||
| MCU pin | Arduino | Function |
|
||||
|---|---|---|
|
||||
| PB7 | D0 / RX | GPIO / UART RX |
|
||||
| PB6 | D1 / TX | GPIO / UART TX |
|
||||
| PB3 | D2 | GPIO |
|
||||
| PB0 | D3 | GPIO / OPAMP OUT / **PWM** |
|
||||
| PA12 | D4 / FDCAN1_TX | GPIO / CAN TX |
|
||||
| PA11 | D5 / FDCAN1_RX | GPIO / CAN RX / **PWM** |
|
||||
| PB1 | D6 | GPIO / **PWM** |
|
||||
| PB2 | D7 | GPIO |
|
||||
| PB4 | D8 | GPIO |
|
||||
| PB8 | D9 | GPIO / **PWM** |
|
||||
| PB9 | D10 / SS | GPIO / SPI SS / **PWM** |
|
||||
| PB15 | D11 / MOSI | GPIO / SPI MOSI / **PWM** |
|
||||
| PB14 | D12 / MISO | GPIO / SPI MISO |
|
||||
| PB13 | D13 / SCK | GPIO / SPI SCK |
|
||||
| PA4 | D14 / DAC0 | GPIO / ADC / DAC |
|
||||
| PA5 | D15 / DAC1 | GPIO / ADC / DAC |
|
||||
| PA6 | D16 | GPIO / ADC / OPAMP IN+ |
|
||||
| PA7 | D17 | GPIO / ADC / OPAMP IN- |
|
||||
| PC1 | D18 / SDA2 | GPIO / ADC / I2C SDA |
|
||||
| PC0 | D19 / SCL2 | GPIO / ADC / I2C SCL |
|
||||
| PB11 | D20 / SDA | GPIO / I2C SDA |
|
||||
| PB10 | D21 / SCL | GPIO / I2C SCL |
|
||||
|
||||
Usage:
|
||||
```cpp
|
||||
pinMode(pin, INPUT | OUTPUT | INPUT_PULLUP);
|
||||
state = digitalRead(pin);
|
||||
digitalWrite(pin, HIGH | LOW);
|
||||
```
|
||||
Button-reads-input, LED-follows example: `pinMode(btn, INPUT_PULLUP); if (digitalRead(btn)==LOW) digitalWrite(led,HIGH);`
|
||||
|
||||
## Analog input (ADC) — JANALOG connector
|
||||
6 channels, **14-bit** ADC.
|
||||
|
||||
| MCU pin | Arduino | Function |
|
||||
|---|---|---|
|
||||
| PA4 | A0 | ADC / DAC |
|
||||
| PA5 | A1 | ADC / DAC |
|
||||
| PA6 | A2 | ADC / OPAMP IN+ |
|
||||
| PA7 | A3 | ADC / OPAMP IN- |
|
||||
| PC1 | A4 | ADC / I2C SDA |
|
||||
| PC0 | A5 | ADC / I2C SCL |
|
||||
|
||||
```cpp
|
||||
analogReadResolution(14); // 0–16383
|
||||
analogReference(AR_INTERNAL2V5); // set V_REF+ (see table)
|
||||
int v = analogRead(A0);
|
||||
```
|
||||
Voltage-reference options: `AR_INTERNAL1V5` (1.5 V), `AR_INTERNAL1V8` (1.8 V), `AR_INTERNAL2V05` (2.048 V), `AR_INTERNAL2V5` (2.5 V), `AR_EXTERNAL` (2 V…VDD, external).
|
||||
|
||||
## Analog output (DAC)
|
||||
Two true DAC outputs.
|
||||
|
||||
| MCU pin | Arduino |
|
||||
|---|---|
|
||||
| PA4 | DAC0 |
|
||||
| PA5 | DAC1 |
|
||||
|
||||
```cpp
|
||||
analogWriteResolution(12); // 0–4095
|
||||
analogWrite(DAC0, value);
|
||||
```
|
||||
(Good for generating waveforms — the docs include a 60 Hz sine via a 256-point LUT clocked with `micros()`.)
|
||||
|
||||
## PWM
|
||||
6 PWM-capable pins: **D3, D5, D6, D9, D10, D11**. Default resolution **8-bit (0–255)**; change with `analogWriteResolution(bits)`. **PWM frequency is fixed at 500 Hz.**
|
||||
```cpp
|
||||
analogWriteResolution(10); // 0–1023
|
||||
analogWrite(D3, dutyValue);
|
||||
```
|
||||
|
||||
## SPI
|
||||
| MCU pin | Arduino |
|
||||
|---|---|
|
||||
| PB9 | SS / D10 |
|
||||
| PB15 | MOSI / D11 |
|
||||
| PB14 | MISO / D12 |
|
||||
| PB13 | SCK / D13 |
|
||||
|
||||
```cpp
|
||||
#include <SPI.h>
|
||||
#define SS D10
|
||||
void setup(){ pinMode(SS,OUTPUT); digitalWrite(SS,HIGH); SPI.begin(); }
|
||||
void loop(){
|
||||
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
|
||||
digitalWrite(SS, LOW);
|
||||
SPI.transfer(0x35); SPI.transfer(0xFA);
|
||||
digitalWrite(SS, HIGH);
|
||||
SPI.endTransaction();
|
||||
}
|
||||
```
|
||||
|
||||
## I2C — two buses
|
||||
| Bus / object | SCL | SDA | Where |
|
||||
|---|---|---|---|
|
||||
| `Wire` | D21 (PB10) | D20 (PB11) | UNO-style headers |
|
||||
| `Wire1` | I2C4_SCL (PD12) | I2C4_SDA (PD13) | **Qwiic** connector |
|
||||
|
||||
```cpp
|
||||
#include <Wire.h>
|
||||
void setup(){ Wire.begin(); } // or Wire1.begin() for Qwiic
|
||||
// Wire.beginTransmission(addr); Wire.write(...); Wire.endTransmission();
|
||||
```
|
||||
|
||||
### Qwiic connector
|
||||
Plug-and-play I²C: polarized connector, daisy-chainable, built-in pull-ups, **3.3 V only** — no breadboard/soldering. Use `Wire1`. Works directly with Arduino's **Modulino** sensor/actuator family for solder-free prototyping.
|
||||
|
||||
## UART (hardware serial on the header)
|
||||
| MCU pin | Arduino |
|
||||
|---|---|
|
||||
| PB6 | USART1_TX / D1 |
|
||||
| PB7 | USART1_RX / D0 |
|
||||
|
||||
Use the **`Serial1`** object for the physical D0/D1 pins on the JDIGITAL connector:
|
||||
```cpp
|
||||
Serial1.begin(115200);
|
||||
Serial1.println("Hello UNO Q");
|
||||
while (Serial1.available()) { char c = Serial1.read(); /* ... */ }
|
||||
```
|
||||
> Note: `Serial1` here is the *header UART*. It is **not** the reserved router link — that reserved `Serial1` warning in the Bridge doc refers to the internal MPU↔MCU port; on the exposed headers `Serial1` drives D0/D1. Don't confuse the two: use `Serial1` for external UART devices, and never touch the internal router transport.
|
||||
|
||||
## `Serial` for debugging → App Lab console
|
||||
Since platform **0.55.0**, plain `Serial` prints stream to the **Arduino App Lab Console** — use it like normal:
|
||||
```cpp
|
||||
void setup(){ Serial.begin(9600); }
|
||||
void loop(){ Serial.println("Hello UNO Q"); delay(1000); }
|
||||
```
|
||||
The legacy `Monitor` object (`#include <Arduino_RouterBridge.h>` → `Monitor.begin()/println()`) still works for backward compatibility, but **use `Serial` for new projects.**
|
||||
|
||||
## How to actually control "our node's" attached hardware — recipe
|
||||
1. Identify the interface the component uses (GPIO / ADC / PWM / SPI / I2C / UART) and the matching pins above. Mind **3.3 V** levels.
|
||||
2. Write the **sketch** that drives it (the standard Arduino libraries work: `Servo`, `Wire`-based sensor libs, etc.).
|
||||
3. If Python/AI/networking must control or read it, wrap each action in a `Bridge.provide_safe("name", fn)` on the MCU and `Bridge.call("name", ...)` from Python (see Bridge reference).
|
||||
4. For quick prototyping of sensors/actuators, reach for **Qwiic + Modulino** modules to skip wiring.
|
||||
@@ -0,0 +1,89 @@
|
||||
# UNO Q — Wireless (Wi-Fi & Bluetooth)
|
||||
|
||||
The **WCBN3536A** module gives dual-band **Wi-Fi 5 (2.4/5 GHz)** and **Bluetooth 5.1**, with onboard antennas. The radio is owned by the **Linux/MPU side**, so networking is configured in Debian (via `nmcli` or the desktop). The MCU reaches the network **through the Bridge**, not directly.
|
||||
|
||||
## Wi-Fi from Linux (the normal way)
|
||||
Use the network icon in the top-right of the desktop, or NetworkManager on the CLI (great over SSH):
|
||||
```bash
|
||||
sudo nmcli d wifi connect <SSID> password <YOUR_PASSWORD> # connect
|
||||
sudo nmcli d disconnect wlan0 # disconnect
|
||||
sudo nmcli connection delete <SSID> # forget a network
|
||||
nmcli d wifi list # scan (handy)
|
||||
```
|
||||
|
||||
## WPA2-Enterprise (university / corporate, e.g. eduroam)
|
||||
NetworkManager handles 802.1X. **eduroam (PEAP/MSCHAPv2):**
|
||||
```bash
|
||||
nmcli con add \
|
||||
type wifi \
|
||||
connection.id Eduroam \
|
||||
wifi.ssid eduroam \
|
||||
wifi.mode infrastructure \
|
||||
wifi-sec.key-mgmt wpa-eap \
|
||||
802-1x.eap peap \
|
||||
802-1x.phase2-auth mschapv2 \
|
||||
802-1x.identity <your identity>
|
||||
```
|
||||
**TTLS + PAP variant:**
|
||||
```bash
|
||||
nmcli con add \
|
||||
type wifi \
|
||||
connection.id ExampleNetwork \
|
||||
wifi.ssid <your Wi-Fi SSID> \
|
||||
wifi.mode infrastructure \
|
||||
wifi-sec.key-mgmt wpa-eap \
|
||||
802-1x.eap ttls \
|
||||
802-1x.phase2-auth pap \
|
||||
802-1x.domain-suffix-match example.com \
|
||||
802-1x.identity <your identity>
|
||||
```
|
||||
Bring a connection up and be prompted for the password interactively:
|
||||
```bash
|
||||
nmcli --ask con up <your network name>
|
||||
```
|
||||
This matters for workshops on campus/enterprise networks where a plain SSID+password won't work.
|
||||
|
||||
## Reaching the network from the MCU (via the Bridge)
|
||||
The sketch doesn't have its own IP stack for Wi-Fi; it borrows the Linux side's connectivity through the Bridge. App Lab provides bridged network client classes, e.g. a TCP client:
|
||||
```cpp
|
||||
BridgeTCPClient<> client(Bridge);
|
||||
|
||||
void setup() {
|
||||
if (!Bridge.begin()) { while (true) {} }
|
||||
Serial.begin(9600);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (client.connect("time.nist.gov", 13) < 0) { // Daytime protocol
|
||||
Serial.println("Connection failed!");
|
||||
delay(5000); return;
|
||||
}
|
||||
String line;
|
||||
while (client.connected() || client.available()) {
|
||||
if (client.available()) {
|
||||
char c = client.read();
|
||||
if (c == '\n') break;
|
||||
if (c != '\r') line += c;
|
||||
}
|
||||
}
|
||||
Serial.print("Server says: "); Serial.println(line);
|
||||
client.stop();
|
||||
delay(10000);
|
||||
}
|
||||
```
|
||||
Takeaway: get the board online **on the Linux side first**, then the MCU can make network calls over the Bridge.
|
||||
|
||||
## Bluetooth (Linux side)
|
||||
Manage from the top-right Bluetooth icon in SBC mode, or via CLI:
|
||||
```bash
|
||||
bluetoothctl power on
|
||||
bluetoothctl power off
|
||||
```
|
||||
Inside the `bluetoothctl` prompt:
|
||||
```
|
||||
power on
|
||||
scan on # discover devices
|
||||
scan off
|
||||
connect <MAC_ADDRESS> # pair/connect
|
||||
```
|
||||
Use it to link phones, computers, or BT sensors to the board.
|
||||
@@ -0,0 +1,74 @@
|
||||
# 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 `<name>.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@<IP>:~/ArduinoApps/<name>`.
|
||||
- Start/stop/log via `arduino-app-cli app start|stop|logs ~/ArduinoApps/<name>`.
|
||||
- Autostart the finished one: `arduino-app-cli properties set default user:<name>`.
|
||||
Reference in New Issue
Block a user