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
@@ -76,6 +76,35 @@ does client-side STT (Web Speech API) and plays the streamed `tts_chunk` audio;
|
|||||||
the server `handle_voice_event` path is finished as part of this work. **This is
|
the server `handle_voice_event` path is finished as part of this work. **This is
|
||||||
the one modality that needs an on-board build + hardware test to verify.**
|
the one modality that needs an on-board build + hardware test to verify.**
|
||||||
|
|
||||||
|
## Default agent skill (arduino-uno-q)
|
||||||
|
|
||||||
|
Every node ships with an expert **UNO Q skill** (`skills/arduino-uno-q/` —
|
||||||
|
`SKILL.md` + 7 `references/*.md`) so the agent knows this board's specifics
|
||||||
|
(dual-brain architecture, the Bridge/RPC model, pin tables, the LED matrix, the
|
||||||
|
`ArduinoGraphics`-not-installed gotcha, …) instead of guessing generic Arduino.
|
||||||
|
|
||||||
|
**It must live in each agent's own workspace, not a shared bundle.** ZeroClaw's
|
||||||
|
`read_skill` returns only `SKILL.md`; the agent reads a `references/*.md` on demand
|
||||||
|
via the **workspace-sandboxed `file_read` tool**, so the references are only
|
||||||
|
reachable when the skill sits under `~/.zeroclaw/agents/<alias>/workspace/skills/`.
|
||||||
|
`push-skill.sh` installs it into every agent workspace; `provision-fleet.sh` does
|
||||||
|
this per board. The risk profile (`config.template.toml`) auto-approves
|
||||||
|
`read_skill` + `file_read` so agents load it without a human approver.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./push-skill.sh <serial> # install the default skill into every agent
|
||||||
|
./push-skill.sh <serial> skills/<other-skill> # any SKILL.md + references/ skill
|
||||||
|
# then restart the daemon so it re-discovers skills at agent construction
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes: the skill NAME is the **directory name** (`arduino-uno-q`); keep
|
||||||
|
`description:` single-line (ZeroClaw's frontmatter parser is a flat scanner, not
|
||||||
|
full YAML); no symlinks/scripts inside a skill dir (the auditor rejects them). The
|
||||||
|
fork's eager keyword→skill inliner has no rule for `arduino-uno-q`, so small local
|
||||||
|
models load it via a `read_skill` round-trip rather than auto-inlining — fine for
|
||||||
|
the cloud default; add a `skill_preroute` rule if you want the on-board Qwen to
|
||||||
|
auto-inline it.
|
||||||
|
|
||||||
## Prerequisites on the board
|
## Prerequisites on the board
|
||||||
|
|
||||||
- `/home/arduino/zeroclaw` — the ZeroClaw binary (aarch64), **with the
|
- `/home/arduino/zeroclaw` — the ZeroClaw binary (aarch64), **with the
|
||||||
@@ -261,4 +290,9 @@ cloud-with-fallback. In APESS, the team's provider/fallback toggle picks the ali
|
|||||||
`systemd/zeroclaw-reload-watcher.service`; no-root: launch via `setsid nohup`.
|
`systemd/zeroclaw-reload-watcher.service`; no-root: launch via `setsid nohup`.
|
||||||
- `zeroclaw-lockdown.sh` — on-board: the harden step — flips `require_pairing=true`,
|
- `zeroclaw-lockdown.sh` — on-board: the harden step — flips `require_pairing=true`,
|
||||||
reloads, and mints a pair code (open → locked).
|
reloads, and mints a pair code (open → locked).
|
||||||
|
- `push-skill.sh` — host: install an Anthropic-style skill (`SKILL.md` + `references/`)
|
||||||
|
into **every agent's workspace** on a board. Used to ship the default
|
||||||
|
`skills/arduino-uno-q` skill; `provision-fleet.sh` runs it per board.
|
||||||
|
- `skills/arduino-uno-q/` — the default UNO Q expert skill (`SKILL.md` +
|
||||||
|
7 `references/*.md`). Installed per-agent so each node has it by default.
|
||||||
- `recover-uno-q.sh` — host-side post-disconnect recovery (re-tunnel + health-check).
|
- `recover-uno-q.sh` — host-side post-disconnect recovery (re-tunnel + health-check).
|
||||||
|
|||||||
@@ -67,10 +67,13 @@ prompt_injection_mode = "compact"
|
|||||||
# Risk profile — only the on-board hardware tools, auto-approved so the agent
|
# Risk profile — only the on-board hardware tools, auto-approved so the agent
|
||||||
# can flash without a human in the loop.
|
# can flash without a human in the loop.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
# read_skill + file_read are allowed AND auto-approved so agents can load the
|
||||||
|
# bundled skills (e.g. arduino-uno-q) and read their references/*.md on demand
|
||||||
|
# without a human approver (the webhook path is non-interactive).
|
||||||
[risk_profiles.default]
|
[risk_profiles.default]
|
||||||
level = "supervised"
|
level = "supervised"
|
||||||
allowed_tools = ["uno_q_flash", "sysfs_led", "camera", "network", "i2cdetect"]
|
allowed_tools = ["uno_q_flash", "sysfs_led", "camera", "network", "i2cdetect", "read_skill", "file_read", "content_search"]
|
||||||
auto_approve = ["uno_q_flash", "sysfs_led", "camera", "network", "i2cdetect", "file_read", "content_search"]
|
auto_approve = ["uno_q_flash", "sysfs_led", "camera", "network", "i2cdetect", "read_skill", "file_read", "content_search"]
|
||||||
|
|
||||||
[runtime_profiles.unoq]
|
[runtime_profiles.unoq]
|
||||||
agentic = true
|
agentic = true
|
||||||
|
|||||||
@@ -77,6 +77,16 @@ provision() { # kit serial -> 0 ok / 1 fail
|
|||||||
echo " ok — modalities (reload-watcher up; lockdown staged)"
|
echo " ok — modalities (reload-watcher up; lockdown staged)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# default skill — install the arduino-uno-q skill (SKILL.md + references) into
|
||||||
|
# every agent's workspace so each node has it by default. Best-effort.
|
||||||
|
if [ -x "$HERE/push-skill.sh" ] && [ -r "$HERE/skills/arduino-uno-q/SKILL.md" ]; then
|
||||||
|
if "$HERE/push-skill.sh" "$serial" "$HERE/skills/arduino-uno-q" >/dev/null 2>&1; then
|
||||||
|
echo " ok — skill (arduino-uno-q → agent workspaces)"
|
||||||
|
else
|
||||||
|
echo " ! skill install failed (onboarding still ok)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# default boot animation — best-effort (a flash hiccup doesn't fail onboarding)
|
# default boot animation — best-effort (a flash hiccup doesn't fail onboarding)
|
||||||
if [ "$FLASH_DEFAULT" = 1 ]; then
|
if [ "$FLASH_DEFAULT" = 1 ]; then
|
||||||
if "$HERE/flash-sketch.sh" "$serial" "$FLASH_SKETCH" >/dev/null 2>&1; then
|
if "$HERE/flash-sketch.sh" "$serial" "$FLASH_SKETCH" >/dev/null 2>&1; then
|
||||||
|
|||||||
Executable
+45
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install an Anthropic-style skill (a directory with SKILL.md + a references/
|
||||||
|
# subfolder) into EVERY agent's workspace on a board.
|
||||||
|
#
|
||||||
|
# Why per-agent workspace and not a shared bundle: ZeroClaw's `read_skill`
|
||||||
|
# returns only SKILL.md; the skill's `references/*.md` are read via the general
|
||||||
|
# `file_read` tool, which is workspace-sandboxed. So references are only
|
||||||
|
# reachable when the skill lives under an agent's own workspace
|
||||||
|
# (`~/.zeroclaw/agents/<alias>/workspace/skills/<name>/`). A shared/skills bundle
|
||||||
|
# surfaces the skill but its references get blocked by the file_read sandbox.
|
||||||
|
#
|
||||||
|
# The skill NAME is the directory name (not the frontmatter `name`).
|
||||||
|
#
|
||||||
|
# ./push-skill.sh <adb-serial> [skill-dir] # default skill: skills/arduino-uno-q
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
SERIAL="${1:?usage: push-skill.sh <adb-serial> [skill-dir]}"
|
||||||
|
SKILL_DIR="${2:-$HERE/skills/arduino-uno-q}"
|
||||||
|
ZC="/home/arduino/.zeroclaw"
|
||||||
|
|
||||||
|
[ -r "$SKILL_DIR/SKILL.md" ] || { echo "no SKILL.md in $SKILL_DIR" >&2; exit 1; }
|
||||||
|
NAME="$(basename "$SKILL_DIR")"
|
||||||
|
|
||||||
|
# Discover agent aliases from the board's config-created workspaces; fall back to
|
||||||
|
# the standard set if the listing is empty.
|
||||||
|
agents="$(adb -s "$SERIAL" shell "ls $ZC/agents 2>/dev/null" 2>/dev/null | tr -d '\r' | tr '\n' ' ')"
|
||||||
|
[ -n "${agents// /}" ] || agents="default cloud local"
|
||||||
|
|
||||||
|
ok=0
|
||||||
|
for a in $agents; do
|
||||||
|
a="$(printf '%s' "$a" | tr -d ' \r')"
|
||||||
|
[ -z "$a" ] && continue
|
||||||
|
dest="$ZC/agents/$a/workspace/skills/$NAME"
|
||||||
|
adb -s "$SERIAL" shell "mkdir -p '$dest/references'" >/dev/null 2>&1 || { echo " ! $a: mkdir failed"; continue; }
|
||||||
|
if adb -s "$SERIAL" push "$SKILL_DIR/SKILL.md" "$dest/" >/dev/null 2>&1 \
|
||||||
|
&& adb -s "$SERIAL" push "$SKILL_DIR/references/." "$dest/references/" >/dev/null 2>&1; then
|
||||||
|
echo " ok — $a ($dest)"
|
||||||
|
ok=$((ok + 1))
|
||||||
|
else
|
||||||
|
echo " ! $a: push failed"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "installed skill '$NAME' into $ok agent workspace(s) on $SERIAL"
|
||||||
|
[ "$ok" -gt 0 ]
|
||||||
@@ -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