Compare commits
6
Commits
05207ba986
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e2f6c846c | ||
|
|
0a50476bb5 | ||
|
|
139f2b25e5 | ||
|
|
b0f47ec271 | ||
|
|
6642691436 | ||
|
|
77373c6fc7 |
@@ -0,0 +1,8 @@
|
|||||||
|
@echo off
|
||||||
|
REM connect-board.bat - double-click launcher for connect-board.ps1 on Windows.
|
||||||
|
REM Runs the PowerShell script with the execution policy bypassed for this run
|
||||||
|
REM only (nothing is changed system-wide). Pass -Watch to keep re-attaching:
|
||||||
|
REM connect-board.bat -Watch
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0connect-board.ps1" %*
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Attach the USB Uno Q board to your LOCAL self-host stack (Windows 11).
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
PowerShell twin of connect-board.sh. Runs adb ON the Windows host so Docker
|
||||||
|
Desktop's host.docker.internal reaches the forwarded port. Forwards the tunnels
|
||||||
|
and registers the board with the containerized API; in LOCAL_MODE the API
|
||||||
|
auto-binds the board to your team in the browser — no claim code.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\connect-board.ps1 # attach once
|
||||||
|
.\connect-board.ps1 -Watch # re-attach on every (re)connect (leave running)
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Env overrides: WEB_URL, FLEET_SECRET, KIT_ID, NODE_URL, SERIAL
|
||||||
|
If Windows blocks the script, run it as:
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File .\connect-board.ps1
|
||||||
|
(or just double-click connect-board.bat).
|
||||||
|
#>
|
||||||
|
param([switch]$Watch)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$Web = if ($env:WEB_URL) { $env:WEB_URL } else { 'http://localhost:8090' }
|
||||||
|
$Api = "$Web/api"
|
||||||
|
$Secret = if ($env:FLEET_SECRET) { $env:FLEET_SECRET } else { 'apess2026' }
|
||||||
|
$KitId = if ($env:KIT_ID) { $env:KIT_ID } else { 'crimson-node' }
|
||||||
|
# How the API *container* reaches the board: adb binds the Windows host loopback,
|
||||||
|
# and Docker Desktop maps host.docker.internal to the Windows host.
|
||||||
|
$NodeUrl = if ($env:NODE_URL) { $env:NODE_URL } else { 'http://host.docker.internal:8080' }
|
||||||
|
$Ports = @(8080, 9999)
|
||||||
|
|
||||||
|
function Log($m) { Write-Host "[connect] $m" -ForegroundColor Cyan }
|
||||||
|
function Ok($m) { Write-Host " [OK] $m" -ForegroundColor Green }
|
||||||
|
function Warn($m) { Write-Host " [!] $m" -ForegroundColor Yellow }
|
||||||
|
|
||||||
|
if (-not (Get-Command adb -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Host "connect-board: adb not found on PATH. Install Android platform-tools and reopen the terminal." -ForegroundColor Red
|
||||||
|
exit 127
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-Serial {
|
||||||
|
if ($env:SERIAL) { return $env:SERIAL }
|
||||||
|
foreach ($line in (& adb devices)) {
|
||||||
|
if ($line -match '^(\S+)\s+device$') { return $Matches[1] }
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Connect-Once {
|
||||||
|
$serial = Get-Serial
|
||||||
|
if (-not $serial) { Warn 'no board attached over USB'; return $false }
|
||||||
|
Ok "board $serial attached"
|
||||||
|
|
||||||
|
# 1 - forward tunnels (they vanish on re-plug)
|
||||||
|
$existing = (& adb -s $serial forward --list) -join "`n"
|
||||||
|
foreach ($p in $Ports) {
|
||||||
|
if ($existing -notmatch "tcp:$p") { & adb -s $serial forward "tcp:$p" "tcp:$p" | Out-Null }
|
||||||
|
}
|
||||||
|
Ok "tunnels forwarded ($($Ports -join ' '))"
|
||||||
|
|
||||||
|
# 2 - wait for the board daemon (App Lab app auto-starts on boot)
|
||||||
|
$n = 0
|
||||||
|
while ($true) {
|
||||||
|
try { Invoke-WebRequest -Uri 'http://127.0.0.1:8080/health' -TimeoutSec 2 -UseBasicParsing | Out-Null; break }
|
||||||
|
catch {
|
||||||
|
$n++
|
||||||
|
if ($n -gt 90) { Warn 'board daemon never came up'; return $false }
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok 'board daemon healthy'
|
||||||
|
|
||||||
|
# 3 - register with the LOCAL stack (LOCAL_MODE auto-binds it in the browser)
|
||||||
|
$body = @{ kitId = $KitId; claimCode = 'local'; url = $NodeUrl; token = 'open-lan' } | ConvertTo-Json -Compress
|
||||||
|
try {
|
||||||
|
$r = Invoke-RestMethod -Uri "$Api/nodes/self-register" -Method Post `
|
||||||
|
-Headers @{ 'x-fleet-secret' = $Secret; 'content-type' = 'application/json' } `
|
||||||
|
-Body $body -TimeoutSec 5
|
||||||
|
if ($r.url) {
|
||||||
|
Ok 'registered with the local stack'
|
||||||
|
Log 'attached — it auto-connects in the browser (no code needed)'
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
Warn "register returned an unexpected response: $($r | ConvertTo-Json -Compress)"
|
||||||
|
return $false
|
||||||
|
} catch {
|
||||||
|
Warn "register failed — is the stack up? ($Web) · $($_.Exception.Message)"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Watch) {
|
||||||
|
Log 'watching for the board — will attach on every (re)connect (Ctrl-C to stop)'
|
||||||
|
while ($true) {
|
||||||
|
& adb wait-for-device | Out-Null
|
||||||
|
Start-Sleep -Seconds 3 # let Linux + the App Lab app finish booting
|
||||||
|
if (-not (Connect-Once)) { Warn 'attach incomplete; will retry on next reconnect' }
|
||||||
|
while (Get-Serial) { Start-Sleep -Seconds 2 }
|
||||||
|
Log 'board disconnected — waiting for re-plug'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
[void](Connect-Once)
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
# Linear actuator — bring-up & run-book
|
||||||
|
|
||||||
|
The workshop board can drive a **linear actuator** (stepper on a lead screw/belt) as the
|
||||||
|
**"Adapt"** half of the Sense→Forecast→Adapt loop: the ADXL355 senses, the agent decides, the
|
||||||
|
actuator moves. This is open-loop motion with a **calibrated software safety envelope** so it can
|
||||||
|
never overrun its ends.
|
||||||
|
|
||||||
|
> **Read this before powering an actuator-equipped board.** The one rule that bites: a restart
|
||||||
|
> resets the position zero (see [Operating rules](#operating-rules)).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
|
||||||
|
| Part | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Driver | **TB6600 / PB6600** (PUL / DIR / ENA, optically isolated). No feedback, no stall detection. |
|
||||||
|
| Wiring | **PUL → digital pin 4**, **DIR → digital pin 5** (common-cathode: signal `-` pins to GND). ENA left free (driver enabled). |
|
||||||
|
| Motion | `dir=1` = **into the rail** (away from the zero end) · `dir=0` = **back toward zero**. |
|
||||||
|
| Speed | ~830 steps/s (600 µs half-period), moderate — safe for most drivers without missed steps. |
|
||||||
|
| Sensor (same board) | ADXL355 @ `0x1d` behind a **PCA9548A mux** (`0x70`) on channel 0. Unrelated bus (I²C on SDA/SCL); doesn't compete with D4/D5. |
|
||||||
|
|
||||||
|
The **TB6600 has no way to sense position or the ends** — that's why travel is bounded in
|
||||||
|
firmware, not hardware. If you ever add physical limit switches, that becomes the robust upgrade;
|
||||||
|
until then the envelope below is the guard.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How it's controlled
|
||||||
|
|
||||||
|
The resident MCU sketch (`firmware/zeroclaw-node/sketch/sketch.ino`) runs the pulse train and
|
||||||
|
enforces the limits. Two ways to reach it:
|
||||||
|
|
||||||
|
**Agent tools** (cloud brain, within limits):
|
||||||
|
- `stepper(steps, dir)` — move `steps` (1–4000) in direction `dir` (1 into rail / 0 toward zero).
|
||||||
|
- `stepper_status()` — read position + limit without moving.
|
||||||
|
|
||||||
|
**Relay commands** (`:9999`, for setup/calibration from the host — `nc`/socket):
|
||||||
|
- `step <count> <dir>` · `zero` · `pos` · `setmax <n>`
|
||||||
|
- `osc <amp_steps> <freq_cHz> <cycles>` — open-loop sinusoidal excitation (shaker mode)
|
||||||
|
- `oscm <amp> <freq_cHz> <cycles>` — oscillate while measuring the ADXL355 (per-axis p-p mg)
|
||||||
|
- `accel` — one ADXL355 sample (mg); `dvf <gain> <secs>` — closed-loop damping (below)
|
||||||
|
|
||||||
|
### Direct Velocity Feedback (`dvf`) — active damping
|
||||||
|
Closes the loop: ADXL355 X-accel @ ~250 Hz → leaky-integrated velocity → stepper
|
||||||
|
commands `-g·v` (velocity feedback adds damping, c → c+g). Envelope-clamped and
|
||||||
|
slew-limited. **Runs must be ≤ 8 s** — the RouterBridge RPC times out at 10 s.
|
||||||
|
|
||||||
|
Bench-measured gain range:
|
||||||
|
| gain | behaviour |
|
||||||
|
|---|---|
|
||||||
|
| 500 | gentle |
|
||||||
|
| **1000–2000** | **authoritative and stable — recommended** |
|
||||||
|
| 4000 | **UNSTABLE** — self-excites off its own step-vibration (velPk 30→1442, 26k steps); the soft-limit envelope catches the runaway |
|
||||||
|
|
||||||
|
Actuator FRF note: open-loop amplitude rolls off with frequency — full ~10 mm
|
||||||
|
holds to ~2–3 Hz, only a few mm by 10 Hz.
|
||||||
|
|
||||||
|
**Shaker-table campaign findings (2 Hz base excitation):**
|
||||||
|
- The control law is **position-target DVF**: carriage position target =
|
||||||
|
`center − K·v` (reaction force ∝ −v = true damping). A velocity-command law
|
||||||
|
(carriage velocity ∝ v) is force ∝ −a = *added mass* — no dissipation; it
|
||||||
|
amplified the response at every gain/sign. Don't regress to it.
|
||||||
|
- **Safe reference setting: `dvf 30 8`** with the slew limit at 8 steps/tick
|
||||||
|
(~2000 steps/s). Ran 2 min continuous, silent, self-centering, no runaway.
|
||||||
|
Slew 25 grinds the motor (lost steps → position corrupted).
|
||||||
|
- **Sensor placement is the binding constraint:** a deck-mounted sensor near
|
||||||
|
the rail reads the carriage's own motion (~856 mg open-loop) louder than the
|
||||||
|
structure sway (~435 mg), so closed-loop damping can't be scored (or cleanly
|
||||||
|
fed back). Mount the feedback sensor at the structure's max-sway point (tower
|
||||||
|
top), ideally a second ADXL355 on mux channel 1.
|
||||||
|
- `listen <secs>` (≤8 s) = passive baseline/ring-down instrument. Old numbers:
|
||||||
|
tap tests gave stable-looking g up to ~2000, but that predates the shaker
|
||||||
|
campaign — trust the shaker findings.
|
||||||
|
|
||||||
|
Every move is **clamped to `[0, stepMax]`** and the reply reports position, e.g. `pos=1234
|
||||||
|
max=9635`, ending in `LIMIT` if it hit the soft limit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Calibrated envelope (this actuator)
|
||||||
|
|
||||||
|
```
|
||||||
|
0 ─────────────────────────────── 9635 ····· 9685
|
||||||
|
zero (right end) armed safe max hard end
|
||||||
|
↑ 50-step margin ↑
|
||||||
|
```
|
||||||
|
|
||||||
|
`stepMax = 9635` is **baked into the sketch** (armed on every boot). Hard end measured at ~9685
|
||||||
|
steps; armed 50 short so a move never reaches the physical stop.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Daily bring-up (safe sequence)
|
||||||
|
|
||||||
|
1. **Wire the actuator first, then start the app.** (Touching the bus on a running board resets the
|
||||||
|
MCU and crashes the app container — wire cold.)
|
||||||
|
2. Bring the app up (`arduino-app-cli app start …`); confirm `pos` reports `max=9635`.
|
||||||
|
3. **Home it:** manually park the carriage at the **right end**, then send **`zero`**.
|
||||||
|
- Now `pos=0` matches reality; the envelope is already armed. Ready.
|
||||||
|
|
||||||
|
That's it — the agent can now drive it safely.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Operating rules
|
||||||
|
|
||||||
|
- **Park at the right end and `zero` BEFORE any restart.** A container `app restart` **resets the
|
||||||
|
MCU position to 0** while the carriage stays where it is. If it was parked anywhere but the right
|
||||||
|
end, firmware `pos` and reality now disagree — and a `dir=1` move would drive into the far stop.
|
||||||
|
The **envelope (max) survives** a restart; the **zero does not**.
|
||||||
|
- **Don't stall it.** Open-loop means a stall against a stop **loses steps**, so the zero drifts.
|
||||||
|
The soft limit exists precisely to avoid this — keep it armed.
|
||||||
|
- **One session = one home.** Re-`zero` at the start of each session (there's no home switch).
|
||||||
|
|
||||||
|
### Recovery — firmware/reality mismatch
|
||||||
|
If a restart left `pos=0` but the carriage isn't at the right end:
|
||||||
|
1. `setmax -1` — disarm the clamp temporarily.
|
||||||
|
2. Jog **`dir=0`** in bursts back to the **right end** (watch it; stop at the end).
|
||||||
|
3. `zero`, then `setmax 9635` to re-arm.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Calibrating a *different* actuator
|
||||||
|
|
||||||
|
If the rail, motor, or TB6600 microstep DIP changes, re-measure:
|
||||||
|
1. Park at the right end → `zero`.
|
||||||
|
2. Jog `dir=1` toward the far end — coarse (`step 200 1`) then fine (`step 10 1`) as it nears —
|
||||||
|
watching. The firmware sums position for you; read it with `pos`.
|
||||||
|
3. Stop a hair short of the hard stop. Take that `pos`, subtract a ~50-step margin → that's the max.
|
||||||
|
4. Bake it: set `long stepMax = <value>;` in the sketch and reflash (push sketch → `app restart`,
|
||||||
|
~50s = a real recompile+flash).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rebuilding the binary (when the agent tools change)
|
||||||
|
|
||||||
|
The `stepper` / `stepper_status` tools live in the ZeroClaw binary
|
||||||
|
(`crates/zeroclaw-hardware/src/peripherals/uno_q_bridge.rs`). Cross-build for the board:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd <zeroclaw>
|
||||||
|
cargo zigbuild --target aarch64-unknown-linux-gnu --profile release-fast --features hardware --bin zeroclaw
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo-zigbuild` + zig is the working cross path on macOS (the `aarch64-linux-gnu-gcc` linker isn't
|
||||||
|
installed). `--features hardware` is **required** or the peripheral tools are stripped. Then push
|
||||||
|
the binary to `…/apess-onboard/bin/zeroclaw` and `app restart`, or repackage the distributable app
|
||||||
|
with `package-onboard-app.sh`.
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
> **The setup runbook.** For the participant *journey* (each screen's job, the
|
> **The setup runbook.** For the participant *journey* (each screen's job, the
|
||||||
> module→ADD-layer map, open design questions), see
|
> module→ADD-layer map, open design questions), see
|
||||||
> [`WORKSHOP-FLOW.md`](./WORKSHOP-FLOW.md).
|
> [`WORKSHOP-FLOW.md`](./WORKSHOP-FLOW.md). For a board with the **linear
|
||||||
|
> actuator** (stepper bring-up, calibration, and the park-and-`zero`-before-restart
|
||||||
|
> rule), see [`ACTUATOR.md`](./ACTUATOR.md).
|
||||||
|
|
||||||
A team needs two things running: the **APESS stack on their laptop** and the
|
A team needs two things running: the **APESS stack on their laptop** and the
|
||||||
**ZeroClaw node on their Uno Q**. Both are containers. Nothing installs to a host.
|
**ZeroClaw node on their Uno Q**. Both are containers. Nothing installs to a host.
|
||||||
|
|||||||
@@ -11,10 +11,14 @@ Do these **before you arrive** so we spend the session building, not installing.
|
|||||||
## How it runs (so the prerequisites make sense)
|
## How it runs (so the prerequisites make sense)
|
||||||
Each team runs the **whole platform on its own laptop** — a small Docker stack (web + API) that
|
Each team runs the **whole platform on its own laptop** — a small Docker stack (web + API) that
|
||||||
comes up with **one command**. Your **Arduino Uno Q** plugs into that same laptop over **USB**.
|
comes up with **one command**. Your **Arduino Uno Q** plugs into that same laptop over **USB**.
|
||||||
Everything is **localhost**: the browser, the API, and the board all talk on your machine. Once
|
Everything on your **laptop** is **localhost**: the browser, the API, and the board all talk on your
|
||||||
the stack is up and the board is plugged in, it **auto-connects to your team — no codes, no
|
machine. Once the stack is up and the board is plugged in, it **auto-connects to your team — no
|
||||||
accounts, nothing over the network.** (WiFi isn't used during the workshop; it's only for a future
|
codes, no accounts.** The one thing that leaves the box: the **board** reaches its **AI cloud brain
|
||||||
step that registers boards with our production cloud.)
|
over the venue WiFi** — but *we* pre-join each board to that network before you get it, so there's
|
||||||
|
nothing for you to set up.
|
||||||
|
|
||||||
|
> **Most teams are on Windows 11** (a few Macs). Both work the same way; the only difference is the
|
||||||
|
> command you run to attach the board — see the Windows / macOS notes below.
|
||||||
|
|
||||||
So each team needs **one "board laptop"** with a few things pre-installed. Extra teammates just
|
So each team needs **one "board laptop"** with a few things pre-installed. Extra teammates just
|
||||||
need a browser pointed at that laptop.
|
need a browser pointed at that laptop.
|
||||||
@@ -32,6 +36,8 @@ need a browser pointed at that laptop.
|
|||||||
- **Arduino Uno Q (4 GB)** board + **USB-C cable** — one per team.
|
- **Arduino Uno Q (4 GB)** board + **USB-C cable** — one per team.
|
||||||
- **ADXL355 accelerometer(s)** + wiring — the FabLab kit.
|
- **ADXL355 accelerometer(s)** + wiring — the FabLab kit.
|
||||||
- **Cloud AI access** — baked into the board app. **No Anthropic/Claude account needed.**
|
- **Cloud AI access** — baked into the board app. **No Anthropic/Claude account needed.**
|
||||||
|
- **Boards pre-joined to the venue WiFi** — the board uses it only to reach the AI cloud; you don't
|
||||||
|
configure any network.
|
||||||
- The **web app** itself (you run it locally from the bundle below).
|
- The **web app** itself (you run it locally from the bundle below).
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -52,6 +58,30 @@ need a browser pointed at that laptop.
|
|||||||
```
|
```
|
||||||
(Also pre-pull the App Lab base image on the board — it's fetched on first Run.)
|
(Also pre-pull the App Lab base image on the board — it's fetched on first Run.)
|
||||||
|
|
||||||
|
### On Windows 11 (most teams)
|
||||||
|
- **Docker Desktop** with the **WSL2 backend** (enable it in the installer). `docker run hello-world`.
|
||||||
|
- **adb** — download **Android SDK platform-tools for Windows**, unzip it, and add the folder to your
|
||||||
|
**PATH** (so `adb version` works in a new terminal). *Run adb on Windows itself — not inside WSL.*
|
||||||
|
- **Git for Windows** — gives you `git` + `curl` (used by the stack).
|
||||||
|
- **Uno Q USB driver** — plug the board in; Windows usually installs a driver automatically. It's
|
||||||
|
working when **`adb devices`** lists the board as `device` (see the check below). If it shows
|
||||||
|
nothing or `unauthorized`, reinstall the driver / re-plug and accept any prompt on the board.
|
||||||
|
- To attach the board you'll run **`connect-board.bat`** (double-click) or **`connect-board.ps1`** —
|
||||||
|
both live in `deploy\lan\`.
|
||||||
|
|
||||||
|
### On macOS (a few teams)
|
||||||
|
- Docker Desktop, `brew install android-platform-tools` (adb), `git`. Attach with
|
||||||
|
`./deploy/lan/connect-board.sh`.
|
||||||
|
|
||||||
|
### First check: does adb see your board?
|
||||||
|
The #1 thing to get right up front. Plug the Uno Q in over USB and run:
|
||||||
|
```
|
||||||
|
adb devices
|
||||||
|
```
|
||||||
|
You want a line ending in **`device`**, e.g. `65301572 device`. If it's empty, `offline`, or
|
||||||
|
`unauthorized`: re-plug, try a different USB port/cable, and on Windows reinstall the USB driver.
|
||||||
|
**Get this working before the day** — everything else assumes adb sees the board.
|
||||||
|
|
||||||
## Everyone else on the team
|
## Everyone else on the team
|
||||||
- A **current browser** (Chrome/Edge recommended; Firefox works). That's it — you'll open the board
|
- A **current browser** (Chrome/Edge recommended; Firefox works). That's it — you'll open the board
|
||||||
laptop's local URL.
|
laptop's local URL.
|
||||||
@@ -62,8 +92,10 @@ need a browser pointed at that laptop.
|
|||||||
1. **Bring the stack up:** `cd deploy/lan && docker compose up -d` → open **`http://localhost:8090/`**.
|
1. **Bring the stack up:** `cd deploy/lan && docker compose up -d` → open **`http://localhost:8090/`**.
|
||||||
2. **Get the board app:** in Team Registration, click **Download the board app** (served by your own
|
2. **Get the board app:** in Team Registration, click **Download the board app** (served by your own
|
||||||
stack), then on the Uno Q: **App Lab → Import an app → pick the zip → Run.** `[instructor: confirm the App Lab access flow for the room]`
|
stack), then on the Uno Q: **App Lab → Import an app → pick the zip → Run.** `[instructor: confirm the App Lab access flow for the room]`
|
||||||
3. **Attach the board:** plug the Uno Q into the board laptop over USB and run
|
3. **Attach the board:** plug the Uno Q into the board laptop over USB, then:
|
||||||
**`./deploy/lan/connect-board.sh`** (or `--watch` to keep it auto-attaching).
|
- **Windows:** double-click **`deploy\lan\connect-board.bat`** (or run `.\connect-board.ps1 -Watch`
|
||||||
|
to keep it auto-attaching on re-plug).
|
||||||
|
- **macOS:** run **`./deploy/lan/connect-board.sh`** (or `--watch`).
|
||||||
4. **It just connects:** type your team name and the board **auto-binds to your team** — no code.
|
4. **It just connects:** type your team name and the board **auto-binds to your team** — no code.
|
||||||
Unplug/replug is handled automatically; a **Disconnect / Reconnect** control is there if you need it.
|
Unplug/replug is handled automatically; a **Disconnect / Reconnect** control is there if you need it.
|
||||||
5. **Build:** walk Modules 1–3, submit your Agent Design Document.
|
5. **Build:** walk Modules 1–3, submit your Agent Design Document.
|
||||||
|
|||||||
@@ -203,8 +203,8 @@ prompt_injection_mode = "compact"
|
|||||||
|
|
||||||
[risk_profiles.default]
|
[risk_profiles.default]
|
||||||
level = "supervised"
|
level = "supervised"
|
||||||
allowed_tools = ["matrix_pattern", "matrix_text", "matrix_count", "i2c_scan", "uno_q_flash", "sysfs_led", "camera", "network", "i2cdetect", "read_skill", "file_read", "content_search"]
|
allowed_tools = ["matrix_pattern", "matrix_text", "matrix_count", "i2c_scan", "stepper", "stepper_status", "uno_q_flash", "sysfs_led", "camera", "network", "i2cdetect", "read_skill", "file_read", "content_search"]
|
||||||
auto_approve = ["matrix_pattern", "matrix_text", "matrix_count", "i2c_scan", "uno_q_flash", "sysfs_led", "camera", "network", "i2cdetect", "read_skill", "file_read", "content_search", "memory_recall", "web_search_tool", "web_fetch", "calculator", "glob_search", "image_info", "weather", "tool_search", "browser", "browser_open"]
|
auto_approve = ["matrix_pattern", "matrix_text", "matrix_count", "i2c_scan", "stepper", "stepper_status", "uno_q_flash", "sysfs_led", "camera", "network", "i2cdetect", "read_skill", "file_read", "content_search", "memory_recall", "web_search_tool", "web_fetch", "calculator", "glob_search", "image_info", "weather", "tool_search", "browser", "browser_open"]
|
||||||
allowed_commands = ["git", "npm", "cargo", "ls", "cat", "grep", "find", "echo", "pwd", "wc", "head", "tail", "date", "df", "du", "uname", "uptime", "hostname", "python", "python3", "pip", "node", "free"]
|
allowed_commands = ["git", "npm", "cargo", "ls", "cat", "grep", "find", "echo", "pwd", "wc", "head", "tail", "date", "df", "du", "uname", "uptime", "hostname", "python", "python3", "pip", "node", "free"]
|
||||||
allowed_roots = []
|
allowed_roots = []
|
||||||
always_ask = []
|
always_ask = []
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ fi
|
|||||||
provision() { # kit serial -> 0 ok / 1 fail
|
provision() { # kit serial -> 0 ok / 1 fail
|
||||||
local kit="$1" serial="$2" env="$ENVDIR/$1.env"
|
local kit="$1" serial="$2" env="$ENVDIR/$1.env"
|
||||||
[ -r "$env" ] || { echo " ! no env file for $kit ($env)"; return 1; }
|
[ -r "$env" ] || { echo " ! no env file for $kit ($env)"; return 1; }
|
||||||
|
|
||||||
|
# workshop WiFi so the board can reach the cloud brain (persisted by NetworkManager).
|
||||||
|
# Baked default is FabLab Torino; override with WIFI_SSID/WIFI_PASS. Best-effort.
|
||||||
|
if [ -x "$HERE/provision-wifi.sh" ]; then
|
||||||
|
if "$HERE/provision-wifi.sh" "$serial" >/dev/null 2>&1; then
|
||||||
|
echo " ok — WiFi joined (${WIFI_SSID:-Fablab_Torino})"
|
||||||
|
else
|
||||||
|
echo " ! WiFi join failed — check creds/coverage (agent cloud brain needs it)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
adb -s "$serial" shell 'mkdir -p /home/arduino/.zeroclaw' >/dev/null 2>&1 || return 1
|
adb -s "$serial" shell 'mkdir -p /home/arduino/.zeroclaw' >/dev/null 2>&1 || return 1
|
||||||
adb -s "$serial" push "$env" /home/arduino/.zeroclaw/apess-node.env >/dev/null 2>&1 || return 1
|
adb -s "$serial" push "$env" /home/arduino/.zeroclaw/apess-node.env >/dev/null 2>&1 || return 1
|
||||||
adb -s "$serial" push "$HERE/apess-selfregister.sh" /home/arduino/ >/dev/null 2>&1 || return 1
|
adb -s "$serial" push "$HERE/apess-selfregister.sh" /home/arduino/ >/dev/null 2>&1 || return 1
|
||||||
|
|||||||
Executable
+41
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# provision-wifi.sh — join a Uno Q board to the workshop WiFi over adb and persist
|
||||||
|
# it. NetworkManager saves the connection profile, so the board auto-reconnects on
|
||||||
|
# every boot. The board reaches the cloud brain (api.anthropic.com) NAT'd out
|
||||||
|
# through this WiFi, so every workshop board needs it.
|
||||||
|
#
|
||||||
|
# The venue network is baked in as the default (override with WIFI_SSID/WIFI_PASS).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./provision-wifi.sh # first attached board
|
||||||
|
# ./provision-wifi.sh <adb-serial> # a specific board
|
||||||
|
# # every attached board at once:
|
||||||
|
# for s in $(adb devices | awk 'NR>1 && $2=="device"{print $1}'); do ./provision-wifi.sh "$s"; done
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
# ── workshop WiFi (FabLab Torino) — override per venue with WIFI_SSID / WIFI_PASS ──
|
||||||
|
WIFI_SSID="${WIFI_SSID:-Fablab_Torino}"
|
||||||
|
WIFI_PASS="${WIFI_PASS:-Fablab.Torino!}"
|
||||||
|
|
||||||
|
S="${1:-$(adb devices 2>/dev/null | awk '/\tdevice$/{print $1; exit}')}"
|
||||||
|
[ -n "$S" ] || { echo "provision-wifi: no board attached over USB" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "==> [$S] joining WiFi '$WIFI_SSID'"
|
||||||
|
# Idempotent: if a saved profile already exists, just bring it up; otherwise scan
|
||||||
|
# and create it (a persistent NetworkManager profile that auto-reconnects on boot).
|
||||||
|
adb -s "$S" shell "nmcli radio wifi on >/dev/null 2>&1; sleep 1
|
||||||
|
if nmcli -t -f NAME connection show 2>/dev/null | grep -qx '$WIFI_SSID'; then
|
||||||
|
nmcli connection up '$WIFI_SSID'
|
||||||
|
else
|
||||||
|
nmcli device wifi rescan >/dev/null 2>&1; sleep 4
|
||||||
|
nmcli device wifi connect '$WIFI_SSID' password '$WIFI_PASS'
|
||||||
|
fi" 2>&1 | sed 's/^/ /'
|
||||||
|
|
||||||
|
# verify link + that the cloud is reachable through it
|
||||||
|
adb -s "$S" shell 'ip -brief addr show wlan0 2>/dev/null | sed "s/^/ wlan0: /"'
|
||||||
|
if adb -s "$S" shell 'getent hosts api.anthropic.com >/dev/null 2>&1'; then
|
||||||
|
echo " cloud DNS: resolves ✓ — board can reach the agent brain"
|
||||||
|
else
|
||||||
|
echo " cloud DNS: FAILS — check WiFi coverage / credentials" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user