Skip to content
Development
Skill

/testbench-install

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

From plugin
embedded-ai-harness
18018 skills
Install
$ npx -y skills add SensorsIot/Embedded-AI-Harness --skill testbench-install --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/testbench-install

Context 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

SKILL.md

testbench-install.SKILL.md
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".

Setting up and deploying the testbench

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.

Pick the operation

| 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* |

Fresh install

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

Platform

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.

Single-file deploy

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).

Things that only bite in practice

**Config files are never overwritten.** `install.sh` writes `/etc/rfc2217/signalgen.json` and `sdr.json` only when absent,

Read more
Ships withembedded-ai-harness

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.

Get the whole plugin
Stats
181
Stars
54
Forks
Maintained
Maintenance
Python
Language
MIT
License
1mo ago
Last commit
8mo ago
Created

Repo: SensorsIot/Embedded-AI-Harness

Other skills on embedded-ai-harness.