feat(uno-q): expose participant files to the agent + document mux scan
- Mount each student's own App Lab files into the app container so the agent can read/fix them: mount-user-workspace.sh binds ~/sketches (rw), ~/ArduinoApps/* (rw), ~/Arduino/libraries (ro) under /app/workspace via a root oneshot ordered before arduino-app-cli.service (the /app bind is rprivate, so binds must precede container start; App Lab has no app.yaml volumes field). Wired into provision-node-app, provision-fleet (systemd, best-effort sudo), and package-onboard-app (bundled under host-setup/). - uno-q-hardware skill: document the mux-aware i2c_scan output format (0x70:mux, 0x70.2=0x1d) and tell the agent its student's files live at /app/workspace. See USER-WORKSPACE.md. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a9a5176f7c
commit
0156f97b36
@@ -0,0 +1,80 @@
|
|||||||
|
# Exposing participant files to the agent (`/app/workspace`)
|
||||||
|
|
||||||
|
Goal: let the containerized agent **read, correct, and complete a student's own
|
||||||
|
Arduino code**. By default it can't — the App Lab container only mounts its own app
|
||||||
|
directory. This wires the student's files in.
|
||||||
|
|
||||||
|
## Where participants store their files (Uno Q, via App Lab)
|
||||||
|
|
||||||
|
| Location on the board | What it holds | Created when… |
|
||||||
|
|---|---|---|
|
||||||
|
| `~/sketches/<name>/<name>.ino` | **Sketches** from App Lab's sketch editor — the primary place implementations live | student opens the sketch editor and saves |
|
||||||
|
| `~/ArduinoApps/<name>/` | Full **App Lab apps** (`app.yaml` + `python/` + `sketch/` + `web/`) | student runs *New App* |
|
||||||
|
| `~/Arduino/libraries/` | Installed Arduino **libraries** | library manager / `arduino-cli lib install` |
|
||||||
|
|
||||||
|
(Our own node is `~/ArduinoApps/apess-onboard` — excluded from the mount to avoid a
|
||||||
|
recursive self-mount.)
|
||||||
|
|
||||||
|
## Why a bind-mount, and why at boot
|
||||||
|
|
||||||
|
- App Lab's `app.yaml` has **no `volumes` field**; it generates the compose itself
|
||||||
|
(`.cache/app-compose.yaml`) with a fixed hardware mount profile — no injection point.
|
||||||
|
- The container's `/app` bind is **`rprivate`**, so a host submount added *after* the
|
||||||
|
container starts does **not** propagate in. The binds must exist **before** the app
|
||||||
|
container is created.
|
||||||
|
- `mount(2)` is privileged → this runs as **root at boot, ordered before
|
||||||
|
`arduino-app-cli.service`** (the App Lab daemon that launches the default app).
|
||||||
|
|
||||||
|
Result inside the container:
|
||||||
|
|
||||||
|
```
|
||||||
|
/app/workspace/sketches/ <- ~/sketches (rw)
|
||||||
|
/app/workspace/apps/<name>/ <- ~/ArduinoApps/<name> (rw, minus apess-onboard)
|
||||||
|
/app/workspace/libraries/ <- ~/Arduino/libraries (ro)
|
||||||
|
```
|
||||||
|
|
||||||
|
The agent is told about this in the `uno-q-hardware` skill ("The student's own files").
|
||||||
|
|
||||||
|
## Install (on the board, once — needs root)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# copy the mount script + unit onto the board
|
||||||
|
adb push deploy/uno-q/mount-user-workspace.sh /home/arduino/mount-user-workspace.sh
|
||||||
|
adb shell 'chmod +x /home/arduino/mount-user-workspace.sh'
|
||||||
|
adb push deploy/uno-q/systemd/apess-user-workspace.service /tmp/apess-user-workspace.service
|
||||||
|
|
||||||
|
adb shell 'sudo install /tmp/apess-user-workspace.service /etc/systemd/system/ \
|
||||||
|
&& sudo systemctl daemon-reload \
|
||||||
|
&& sudo systemctl enable --now apess-user-workspace.service'
|
||||||
|
|
||||||
|
# the binds only reach the ALREADY-running container after it is recreated
|
||||||
|
# (rprivate), so restart the app once:
|
||||||
|
adb shell 'arduino-app-cli app restart /home/arduino/ArduinoApps/apess-onboard'
|
||||||
|
|
||||||
|
# verify
|
||||||
|
adb shell 'docker exec apess-onboard-main-1 ls -la /app/workspace/sketches'
|
||||||
|
```
|
||||||
|
|
||||||
|
After this it survives reboots (the unit runs before the app each boot).
|
||||||
|
|
||||||
|
## Live vs. restart
|
||||||
|
|
||||||
|
- **New sketches** (`~/sketches/...`) appear **live** — they're files inside the
|
||||||
|
single `~/sketches` bind, not new mounts. No restart needed.
|
||||||
|
- A **new sibling App Lab app** is a new mount → re-run the script and restart the
|
||||||
|
app: `sudo /home/arduino/mount-user-workspace.sh && arduino-app-cli app restart …`.
|
||||||
|
|
||||||
|
## Wired into provisioning
|
||||||
|
|
||||||
|
- **`provision-fleet.sh`** (`MODE=systemd`) — pushes the script + unit, `sudo -n`
|
||||||
|
installs/enables it, and restarts the app, per board. Falls back to a staged-file
|
||||||
|
message if root isn't available (mount can't run cron-only).
|
||||||
|
- **`provision-node-app.sh`** — same, for a single dev board (targets whichever app
|
||||||
|
it provisions via `.apess-workspace.env` → `APP_DIR`).
|
||||||
|
- **`package-onboard-app.sh`** — bundles `mount-user-workspace.sh` +
|
||||||
|
`apess-user-workspace.service` into the app under `host-setup/` (plus this doc as
|
||||||
|
`host-setup/README.md`), since App Lab self-import can't run root steps. The
|
||||||
|
epilogue prints the one-time enable command for imported boards.
|
||||||
|
|
||||||
|
The unit reads `APP_DIR` from `/home/arduino/.apess-workspace.env` (default
|
||||||
|
`…/apess-onboard`), so the same unit works for both the distributable and the dev app.
|
||||||
Executable
+65
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Expose each participant's own Arduino files INTO the apess-onboard app container
|
||||||
|
# so the agent can read, correct, and complete their implementation.
|
||||||
|
#
|
||||||
|
# WHY THIS EXISTS
|
||||||
|
# The App Lab container only bind-mounts the app's own directory
|
||||||
|
# (/home/arduino/ArduinoApps/apess-onboard -> /app). A student's real work lives
|
||||||
|
# elsewhere and is invisible to the agent:
|
||||||
|
# ~/sketches/<name>/<name>.ino App Lab "sketch editor" projects (primary)
|
||||||
|
# ~/ArduinoApps/<name>/ full App Lab apps
|
||||||
|
# ~/Arduino/libraries/ installed libraries
|
||||||
|
# App Lab has NO volumes field in app.yaml and generates the compose itself, so
|
||||||
|
# we can't declare these there. Instead we bind the user paths UNDERNEATH the app
|
||||||
|
# dir (which /app already maps). The catch: the /app bind is `rprivate`, so a
|
||||||
|
# submount added AFTER the container starts does NOT propagate in — the binds must
|
||||||
|
# exist BEFORE the app container is created. Hence a root oneshot ordered
|
||||||
|
# Before=arduino-app-cli.service (see apess-user-workspace.service).
|
||||||
|
#
|
||||||
|
# RESULT INSIDE THE CONTAINER
|
||||||
|
# /app/workspace/sketches/ <- ~/sketches (rw)
|
||||||
|
# /app/workspace/apps/<name>/ <- ~/ArduinoApps/<name> (rw, minus ourselves)
|
||||||
|
# /app/workspace/libraries/ <- ~/Arduino/libraries (ro, reference)
|
||||||
|
#
|
||||||
|
# Idempotent — safe to re-run. Requires root (mount(2) is privileged).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
USER_HOME=${USER_HOME:-/home/arduino}
|
||||||
|
APP_DIR=${APP_DIR:-$USER_HOME/ArduinoApps/apess-onboard}
|
||||||
|
WS="$APP_DIR/workspace"
|
||||||
|
SELF=$(basename "$APP_DIR")
|
||||||
|
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "must run as root (mount is privileged) — try: sudo $0" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
bind() { # src dst [ro]
|
||||||
|
local src=$1 dst=$2 ro=${3:-}
|
||||||
|
if [ ! -d "$src" ]; then echo "skip (no source dir): $src"; return 0; fi
|
||||||
|
mkdir -p "$dst"
|
||||||
|
if mountpoint -q "$dst"; then echo "already mounted: $dst"; return 0; fi
|
||||||
|
mount --bind "$src" "$dst"
|
||||||
|
[ "$ro" = ro ] && mount -o remount,ro,bind "$dst"
|
||||||
|
echo "mounted: $src -> $dst${ro:+ (ro)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$WS" "$WS/apps"
|
||||||
|
chown "$(stat -c '%u:%g' "$USER_HOME")" "$WS" "$WS/apps" 2>/dev/null || true
|
||||||
|
|
||||||
|
bind "$USER_HOME/sketches" "$WS/sketches"
|
||||||
|
bind "$USER_HOME/Arduino/libraries" "$WS/libraries" ro
|
||||||
|
|
||||||
|
# Each OTHER App Lab app — skip ourselves so we don't recursively self-mount.
|
||||||
|
if [ -d "$USER_HOME/ArduinoApps" ]; then
|
||||||
|
for d in "$USER_HOME/ArduinoApps"/*/; do
|
||||||
|
[ -d "$d" ] || continue
|
||||||
|
name=$(basename "$d")
|
||||||
|
[ "$name" = "$SELF" ] && continue
|
||||||
|
bind "$d" "$WS/apps/$name"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "workspace ready: $WS"
|
||||||
|
echo "note: a NEW sibling app created after boot needs a re-run + app restart to appear"
|
||||||
|
echo " (rprivate /app); new *sketches* in ~/sketches appear live, no restart needed."
|
||||||
@@ -58,6 +58,17 @@ ok "config (single 'default' agent, matrix + i2c_scan)"
|
|||||||
cp -r "$HERE/skills" "$OUT/.zeroclaw/shared/skills"
|
cp -r "$HERE/skills" "$OUT/.zeroclaw/shared/skills"
|
||||||
ok "skills ($(ls "$HERE/skills" | wc -l | tr -d ' ') bundles)"
|
ok "skills ($(ls "$HERE/skills" | wc -l | tr -d ' ') bundles)"
|
||||||
|
|
||||||
|
# Host-setup helpers that CAN'T ride the container: the participant-workspace
|
||||||
|
# bind-mount (root, before app start) that exposes ~/sketches + ~/ArduinoApps +
|
||||||
|
# libraries at /app/workspace so the agent can fix student code. App Lab import
|
||||||
|
# can't run these (no root), so they travel in host-setup/ for a one-time enable.
|
||||||
|
mkdir -p "$OUT/host-setup/systemd"
|
||||||
|
cp "$HERE/mount-user-workspace.sh" "$OUT/host-setup/mount-user-workspace.sh"
|
||||||
|
cp "$HERE/systemd/apess-user-workspace.service" "$OUT/host-setup/systemd/apess-user-workspace.service"
|
||||||
|
cp "$HERE/USER-WORKSPACE.md" "$OUT/host-setup/README.md"
|
||||||
|
chmod +x "$OUT/host-setup/mount-user-workspace.sh"
|
||||||
|
ok "host-setup/ (participant-workspace mount — enable once per board, needs root)"
|
||||||
|
|
||||||
# BAKED cloud token (per the workshop decision) — the instructor's Max token,
|
# BAKED cloud token (per the workshop decision) — the instructor's Max token,
|
||||||
# shared across the fleet. Kept in the app bundle only, never in the repo.
|
# shared across the fleet. Kept in the app bundle only, never in the repo.
|
||||||
printf '%s' "$ANTHROPIC_OAUTH_TOKEN" > "$OUT/.zeroclaw/oauth_token"
|
printf '%s' "$ANTHROPIC_OAUTH_TOKEN" > "$OUT/.zeroclaw/oauth_token"
|
||||||
@@ -104,6 +115,15 @@ Distribute it:
|
|||||||
Students download it, open App Lab → "Import an app" → pick the zip → Run.
|
Students download it, open App Lab → "Import an app" → pick the zip → Run.
|
||||||
• Instructor smoke-test on a board:
|
• Instructor smoke-test on a board:
|
||||||
arduino-app-cli app import "$ZIP"
|
arduino-app-cli app import "$ZIP"
|
||||||
|
• Expose participant files to the agent (/app/workspace) — one-time, needs root
|
||||||
|
(App Lab import can't do this itself). On each board after import:
|
||||||
|
adb push <app>/host-setup/mount-user-workspace.sh /home/arduino/ && \\
|
||||||
|
adb shell 'chmod +x /home/arduino/mount-user-workspace.sh' && \\
|
||||||
|
adb push <app>/host-setup/systemd/apess-user-workspace.service /tmp/ && \\
|
||||||
|
adb shell 'sudo install /tmp/apess-user-workspace.service /etc/systemd/system/ \\
|
||||||
|
&& sudo systemctl enable --now apess-user-workspace.service \\
|
||||||
|
&& arduino-app-cli app restart /home/arduino/ArduinoApps/apess-onboard'
|
||||||
|
Fleet boards: provision-fleet.sh does this automatically (MODE=systemd). See host-setup/README.md.
|
||||||
• APESS_URL: default is mDNS apess-api.local. Set per team by editing
|
• APESS_URL: default is mDNS apess-api.local. Set per team by editing
|
||||||
.zeroclaw/apess-node.env before packaging, or pass APESS_URL=http://<laptop>:3000.
|
.zeroclaw/apess-node.env before packaging, or pass APESS_URL=http://<laptop>:3000.
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -77,6 +77,23 @@ 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
|
||||||
|
|
||||||
|
# participant workspace — bind ~/sketches + ~/ArduinoApps + ~/Arduino/libraries
|
||||||
|
# into the app container (/app/workspace) so the agent can read/fix student code.
|
||||||
|
# mount(2) is root-only and can't fall back to cron, so this is systemd-only,
|
||||||
|
# best-effort. See USER-WORKSPACE.md.
|
||||||
|
if [ -r "$HERE/mount-user-workspace.sh" ]; then
|
||||||
|
adb -s "$serial" push "$HERE/mount-user-workspace.sh" /home/arduino/ >/dev/null 2>&1
|
||||||
|
adb -s "$serial" shell 'chmod +x /home/arduino/mount-user-workspace.sh' >/dev/null 2>&1
|
||||||
|
adb -s "$serial" push "$HERE/systemd/apess-user-workspace.service" /tmp/ >/dev/null 2>&1
|
||||||
|
if adb -s "$serial" shell 'sudo -n cp /tmp/apess-user-workspace.service /etc/systemd/system/ \
|
||||||
|
&& sudo -n systemctl daemon-reload && sudo -n systemctl enable --now apess-user-workspace.service' >/dev/null 2>&1; then
|
||||||
|
adb -s "$serial" shell 'TMPDIR=/tmp arduino-app-cli app restart /home/arduino/ArduinoApps/apess-onboard >/dev/null 2>&1 || true' >/dev/null 2>&1
|
||||||
|
echo " ok — participant workspace mounted (/app/workspace)"
|
||||||
|
else
|
||||||
|
echo " ! workspace mount needs root (sudo -n failed) — staged; enable apess-user-workspace.service on the board"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# default skills — install every bundled skill (the comprehensive arduino-uno-q
|
# default skills — install every bundled skill (the comprehensive arduino-uno-q
|
||||||
# reference + the fork's granular set) into every agent's workspace, so each
|
# reference + the fork's granular set) into every agent's workspace, so each
|
||||||
# node has them by default. Best-effort.
|
# node has them by default. Best-effort.
|
||||||
|
|||||||
@@ -10,11 +10,13 @@
|
|||||||
#
|
#
|
||||||
# Env: SERIAL (65301572), NODE_APP_DIR (repo app dir), plus the node-env vars above.
|
# Env: SERIAL (65301572), NODE_APP_DIR (repo app dir), plus the node-env vars above.
|
||||||
set -u
|
set -u
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
SERIAL="${SERIAL:-65301572}"
|
SERIAL="${SERIAL:-65301572}"
|
||||||
NODE_APP_DIR="${NODE_APP_DIR:-$HOME/projects/zeroclaw/firmware/zeroclaw-node}"
|
NODE_APP_DIR="${NODE_APP_DIR:-$HOME/projects/zeroclaw/firmware/zeroclaw-node}"
|
||||||
DEST=/home/arduino/ArduinoApps/zeroclaw-node
|
DEST=/home/arduino/ArduinoApps/zeroclaw-node
|
||||||
S(){ adb -s "$SERIAL" shell "$@"; }
|
S(){ adb -s "$SERIAL" shell "$@"; }
|
||||||
ok(){ printf ' \033[32m✓\033[0m %s\n' "$*"; }
|
ok(){ printf ' \033[32m✓\033[0m %s\n' "$*"; }
|
||||||
|
warn(){ printf ' \033[33m!\033[0m %s\n' "$*"; }
|
||||||
bad(){ printf ' \033[31m✗\033[0m %s\n' "$*"; }
|
bad(){ printf ' \033[31m✗\033[0m %s\n' "$*"; }
|
||||||
|
|
||||||
adb -s "$SERIAL" get-state >/dev/null 2>&1 || { bad "board $SERIAL not attached"; exit 1; }
|
adb -s "$SERIAL" get-state >/dev/null 2>&1 || { bad "board $SERIAL not attached"; exit 1; }
|
||||||
@@ -91,6 +93,28 @@ S "cd $DEST && TMPDIR=/tmp timeout 300 arduino-app-cli app start $DEST 2>&1 | ta
|
|||||||
S 'crontab -l 2>/dev/null | grep -v "zeroclaw-supervisor" | crontab - 2>/dev/null; for p in $(pgrep -f "[z]eroclaw-supervisor"); do kill -9 $p 2>/dev/null; done'
|
S 'crontab -l 2>/dev/null | grep -v "zeroclaw-supervisor" | crontab - 2>/dev/null; for p in $(pgrep -f "[z]eroclaw-supervisor"); do kill -9 $p 2>/dev/null; done'
|
||||||
ok "removed legacy supervisor @reboot cron (App Lab app owns boot now)"
|
ok "removed legacy supervisor @reboot cron (App Lab app owns boot now)"
|
||||||
|
|
||||||
|
echo "→ exposing participant files to the agent (/app/workspace)"
|
||||||
|
# Bind ~/sketches + ~/ArduinoApps + ~/Arduino/libraries under this app dir so the
|
||||||
|
# agent can read/fix student code. mount(2) is root-only and the App Lab /app bind
|
||||||
|
# is rprivate (submounts must precede the container), so this is a root oneshot
|
||||||
|
# ordered before arduino-app-cli.service. See USER-WORKSPACE.md.
|
||||||
|
adb -s "$SERIAL" push "$HERE/mount-user-workspace.sh" /home/arduino/mount-user-workspace.sh >/dev/null 2>&1
|
||||||
|
S "chmod +x /home/arduino/mount-user-workspace.sh"
|
||||||
|
adb -s "$SERIAL" push "$HERE/systemd/apess-user-workspace.service" /tmp/apess-user-workspace.service >/dev/null 2>&1
|
||||||
|
printf 'APP_DIR=%s\n' "$DEST" | S "cat > /home/arduino/.apess-workspace.env" # this app dir maps to /app
|
||||||
|
if S 'sudo -n cp /tmp/apess-user-workspace.service /etc/systemd/system/ \
|
||||||
|
&& sudo -n systemctl daemon-reload \
|
||||||
|
&& sudo -n systemctl enable --now apess-user-workspace.service' >/dev/null 2>&1; then
|
||||||
|
# rprivate: the running container must be recreated to pick up the new binds.
|
||||||
|
S "cd $DEST && TMPDIR=/tmp arduino-app-cli app restart $DEST >/dev/null 2>&1 || true"
|
||||||
|
ok "workspace mounted → agent sees ~/sketches, ~/ArduinoApps, ~/Arduino/libraries at /app/workspace"
|
||||||
|
else
|
||||||
|
warn "workspace mount needs root — sudo unavailable over adb. Files are staged; enable once on the board:"
|
||||||
|
echo " sudo install /tmp/apess-user-workspace.service /etc/systemd/system/ \\"
|
||||||
|
echo " && sudo systemctl enable --now apess-user-workspace.service \\"
|
||||||
|
echo " && arduino-app-cli app restart $DEST"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "→ enable Run-at-startup for boot persistence:"
|
echo "→ enable Run-at-startup for boot persistence:"
|
||||||
echo " adb -s $SERIAL shell 'arduino-app-cli properties set default $DEST'"
|
echo " adb -s $SERIAL shell 'arduino-app-cli properties set default $DEST'"
|
||||||
ok "provisioned. In App Lab, open 'ZeroClaw Node' → Run."
|
ok "provisioned. In App Lab, open 'ZeroClaw Node' → Run."
|
||||||
|
|||||||
@@ -32,6 +32,42 @@ Sketches always target the MCU (`arduino:zephyr:unoq`).
|
|||||||
- `analogRead()` returns 0–1023; volts = `raw * 3.3 / 1023.0`.
|
- `analogRead()` returns 0–1023; volts = `raw * 3.3 / 1023.0`.
|
||||||
- For a 5 V sensor, divide down: 5 V → 10 kΩ → A0 → 20 kΩ → GND.
|
- For a 5 V sensor, divide down: 5 V → 10 kΩ → A0 → 20 kΩ → GND.
|
||||||
|
|
||||||
|
## Checking sensors — the `i2c_scan` tool (mux-aware)
|
||||||
|
|
||||||
|
To see what's wired to the board's I2C, call **`i2c_scan`**. It probes the MCU's
|
||||||
|
Arduino Wire bus (Qwiic + I2C headers) — this is where student sensors hang, NOT
|
||||||
|
Linux `/dev/i2c-*` (those are MPU-side and unreachable from the app container).
|
||||||
|
|
||||||
|
The APESS kit hangs its ADXL355s behind a **PCA9548A I2C mux at `0x70`**, and two
|
||||||
|
sensors can share address `0x1d` on different channels — so the scan walks the mux
|
||||||
|
too. Read the comma-separated result like this:
|
||||||
|
|
||||||
|
- `0x1d` — a device directly on the bus (e.g. a lone ADXL355 wired to Qwiic).
|
||||||
|
- `0x70:mux` — an I2C mux is present at `0x70`.
|
||||||
|
- `0x70.2=0x1d` — a device at `0x1d` behind mux `0x70` on **channel 2**.
|
||||||
|
- `none` — nothing ACKed.
|
||||||
|
|
||||||
|
So `0x70:mux,0x70.2=0x1d,0x70.5=0x1d` = the mux plus two ADXL355s, one on channel 2
|
||||||
|
and one on channel 5. If a student sees only `0x70:mux`, their sensors aren't wired
|
||||||
|
to the mux channels (or aren't powered) — a mux with nothing behind it. If they see
|
||||||
|
nothing at all, check power and SDA/SCL. **ADXL355** = `0x1d` (or `0x1e` if ADDR is
|
||||||
|
pulled high); the FabLab kit reads it at `0x1d`.
|
||||||
|
|
||||||
|
## The student's own files — help fix their implementation
|
||||||
|
|
||||||
|
The participant's Arduino work is mounted into this container under **`/app/workspace/`**:
|
||||||
|
|
||||||
|
- `/app/workspace/sketches/<name>/<name>.ino` — sketches they wrote in App Lab's
|
||||||
|
sketch editor (this is where most implementations live).
|
||||||
|
- `/app/workspace/apps/<name>/` — full App Lab apps they built.
|
||||||
|
- `/app/workspace/libraries/` — installed Arduino libraries (read-only reference).
|
||||||
|
|
||||||
|
Read these to review, correct, and complete a student's code when they ask for help
|
||||||
|
("why doesn't my sensor read?", "fix my sketch"). You can edit files under
|
||||||
|
`sketches/` and `apps/`; `libraries/` is reference only. If `/app/workspace/` is
|
||||||
|
empty, the workspace mounts aren't set up on this board yet — say so rather than
|
||||||
|
guessing at their code.
|
||||||
|
|
||||||
## On-board LEDs
|
## On-board LEDs
|
||||||
|
|
||||||
- RGB LED 1/2 are MPU-owned (`/sys/class/leds/*`, use the `sysfs_led` tool).
|
- RGB LED 1/2 are MPU-owned (`/sys/class/leds/*`, use the `sysfs_led` tool).
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=APESS — bind participant workspace into the apess-onboard app container
|
||||||
|
# The app's /app bind is rprivate, so these submounts must exist BEFORE the App Lab
|
||||||
|
# daemon starts the default app container. Order strictly before it.
|
||||||
|
Before=arduino-app-cli.service
|
||||||
|
After=home-arduino.mount local-fs.target
|
||||||
|
RequiresMountsFor=/home/arduino
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
# Which app dir maps to /app. Default = the distributable apess-onboard; the dev
|
||||||
|
# provisioner overrides it (to zeroclaw-node) by writing .apess-workspace.env.
|
||||||
|
Environment=APP_DIR=/home/arduino/ArduinoApps/apess-onboard
|
||||||
|
EnvironmentFile=-/home/arduino/.apess-workspace.env
|
||||||
|
ExecStart=/home/arduino/mount-user-workspace.sh
|
||||||
|
# Clean unmount on stop so the next start rebinds fresh.
|
||||||
|
ExecStop=/bin/sh -c 'for m in "$APP_DIR"/workspace/sketches "$APP_DIR"/workspace/libraries "$APP_DIR"/workspace/apps/*; do mountpoint -q "$m" && umount "$m" || true; done; exit 0'
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in New Issue
Block a user