build
Phase 3 of AI Closed-Loop Programming — the Build phase, and the driver of the whole loop: locate the project on the chain, name the next act, design and…
Use this skill whenever the testbench Pi itself needs to be built, installed, updated, or have code deployed to it — a fresh SD card, a first `install.sh` run, pushing a changed controller module to a running bench, or diagnosing why a change had no effect. This is about the
$ npx -y skills add SensorsIot/Embedded-AI-Harness --skill testbench-install --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/testbench-installContext preview
The summary Claude sees to decide when to auto-load this skill.
Use this skill whenever the testbench Pi itself needs to be built, installed, updated, or have code deployed to it — a fresh SD card, a first `install.sh` run, pushing a changed controller module to a running bench, or diagnosing why a change had no effect. This is about the
name: testbench-install description: Use this skill whenever the testbench Pi itself needs to be built, installed, updated, or have code deployed to it — a fresh SD card, a first `install.sh` run, pushing a changed controller module to a running bench, or diagnosing why a change had no effect. This is about the machine, not the instruments: the other testbench-* skills drive a bench that already works. Triggers on "install the testbench", "set up the Pi", "deploy", "push this to the bench", "my change didn't take effect", "rebuild the SD card", "update the portal", "install.sh", "systemctl restart rfc2217-portal".
The one fact that explains most confusion: **the service runs from `/usr/local/bin/`, not from the git checkout.** Editing files in the repo on the Pi — or on your laptop — changes nothing until they are copied across and the service is restarted. Every "my fix didn't work" report traces back to this.
Full operator procedure lives in [`docs/Harness-User-Manual.md`](../../../docs/Harness-User-Manual.md) §2. This skill is the working procedure plus the things that only bite when you actually do it.
| Situation | Do this | |---|---| | New Pi, blank SD card | **Fresh install** below | | Bench works, want the latest code + no system changes | `sudo bash install.sh --update` | | Changed one module, want it live now | **Single-file deploy** below | | Changed a config default in `pi/config/` | Fresh-install path won't overwrite it — see *Config files are never overwritten* |
git clone https://github.com/SensorsIot/Embedded-AI-Harness.git cd Embedded-AI-Harness/pi sudo bash install.sh
`install.sh` is idempotent and does eight things: apt packages, standing down the services it manages dynamically (`hostapd` is masked; `dnsmasq` and `mosquitto` are disabled — the portal starts them itself, so leaving them enabled fights it), directories, the Python modules into `/usr/local/bin/`, helper scripts, config defaults, systemd + udev rules, then enable and start.
It fetches `openocd-esp32` from GitHub releases and installs it as `/usr/local/bin/openocd-esp32`, alongside — not replacing — Debian's `openocd`.
**It runs under `set -e`, and the script copy comes before systemd and udev.** So a single missing file aborts the install after the packages are in but before the service exists, and the output ends on a bare `cp: cannot stat` with no summary. Read the last line rather than assuming a long successful-looking log means it finished; `curl /api/info` is the only proof.
**The API contract is [FSD Appendix D](../../../docs/Harness-FSD.md#appendix-d-http-api--mcp-reference).** Every endpoint the bench serves is listed there with its request and response shape. Read it rather than the portal source: the source has carried a dead duplicate handler whose documented form the live one rejects, so guessing from code has already cost a debugging cycle.
**A fresh install exercises code that `--update` never reaches.** An installer bug can therefore sit undiscovered indefinitely on benches that were built before it was introduced — a file deleted from `pi/` but still named in `install.sh` breaks every new bench and no existing one.
Verify over HTTP — never by reading files over SSH:
curl -s $TESTBENCH_URL/api/info # portal version, host info curl -s $TESTBENCH_URL/api/devices # every slot
If the bench has moved or you do not know its address:
sudo python3 .claude/skills/esp-idf-handling/discover-testbench.py --hosts
Then prove the subsystems actually loaded, because an import that fails on a newer Python takes only its own endpoint down and leaves the portal answering:
for ep in sdr/status siggen/status mqtt/status wifi/mode debug/probes gpio/status; do curl -s "$TESTBENCH_URL/api/$ep"; echo done
The installer pins nothing and works across releases. Verified on:
| | Debian 12 bookworm | Debian 13 trixie | |---|---|---| | Python | 3.11 | 3.13 | | esptool | 4.x | 5.x |
**Python 3.13 removed `telnetlib`, `cgi`, `imp` and `distutils`.** No portal module uses them today; check before adding a dependency that might.
**esptool 5 renamed things and warns loudly.** `esptool.py` → `esptool`, `flash_id` → `flash-id`, and its output fields changed (`Chip is` → `Chip type:`, `Crystal is` → `Crystal frequency:`). The old spellings still work, so the portal keeps using them for compatibility with older benches, and anything parsing esptool output must accept both wordings.
The pattern for every module — only the destination name changes:
scp pi/portal.py pi@<bench-hostname>:/tmp/portal.py ssh pi@<bench-hostname> 'sudo cp /tmp/portal.py /usr/local/bin/rfc2217-portal && sudo systemctl restart rfc2217-portal'
`portal.py` is the only one renamed on install (→ `rfc2217-portal`). The rest keep their filenames: `sdr_controller.py`, `wifi_controller.py`, `ble_controller.py`, `mqtt_controller.py`, `debug_controller.py`, `signal_generator.py`, `si5351.py`, `gpclk.py`, `morse.py`, `bcm_gpio.py`, `pe4302.py`, `sniffer.py`, `plain_rfc2217_server.py`.
Restarting `rfc2217-portal` is required even for a module the portal imports — Python has already loaded the old one.
Then prove it took, rather than assuming:
curl -s $TESTBENCH_URL/api/info
**SSH is for deploying code and nothing else.** Never drive the bench over SSH: every operation has an HTTP endpoint and `pytest/testbench_driver.py` wraps them all. Reaching for SSH to *do* something means the API is missing a capability — add the endpoint instead. This is a project rule, not a preference; see [`docs/Method/AI-Workflow.md`](../../../docs/Method/AI-Workflow.md#deploying-a-change-to-the-bench).
**Config files are never overwritten.** `install.sh` writes `/etc/rfc2217/signalgen.json` and `sdr.json` only when absent,
Spec to silicon, hands off. A horse is strong, fast, and willing — and useless for heavy loads until you harness it. The harness is not a part of the horse and not a part of the cart: it is the coupling that turns raw strength into pulled weight.
Phase 3 of AI Closed-Loop Programming — the Build phase, and the driver of the whole loop: locate the project on the chain, name the next act, design and…
Phase 2 of AI Closed-Loop Programming — Commissioning: prove the project's OWN never-seen-working parts (its board, its wiring, its peers/simulators), so that…
Phase 0 of AI Closed-Loop Programming — Definition: engineers the WHAT the loop converges on. Writes and evolves the FSD — atomic, falsifiable,…
Complete ESP-IDF lifecycle: project setup, build, flash, monitor, and OTA. Automatically detects whether a testbench is available or the device is connected…
PlatformIO lifecycle for ESP32 firmware: platformio.ini, environment selection, build, upload and serial monitor, on local USB or through the testbench. Covers…
Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to…