agentleFS
SensorsIotSensorsIot/Embedded-AI-Harness/.claude/skills/testbench-install/SKILL.md

testbench-install

Skill235 starsChanged 3 months agoMITView on GitHub

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 file

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 3 are distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Read the last line rather than assuming a long successful-looking log means it finished.

    line 47
  2. Distinctive:

    Every endpoint the bench serves is listed there with its request and response shape.

    line 51
  3. Distinctive:

    Verify over HTTP — never by reading files over SSH:

    line 61
  4. Distinctive:

    If the bench has moved or you do not know its address:

    line 68
  5. Very specific:

    SSH is for deploying code and nothing else: Never drive the bench over SSH: every operation has an HTTP endpoint and …

    line 125
  6. Very specific:

    If the change altered externally observable behaviour, it belongs in the FSD.

    line 175

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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 §2. This skill is the working procedure plus the things that only bite when you actually do it.

Pick the operation

SituationDo this
New Pi, blank SD cardFresh install below
Bench works, want the latest code + no system changessudo bash install.sh --update
Changed one module, want it live nowSingle-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. 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 bookwormDebian 13 trixie
Python3.113.13
esptool4.x5.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.

Things that only bite in practice

Config files are never overwritten. install.sh writes /etc/rfc2217/signalgen.json and sdr.json only when absent, so changing a default in pi/config/ and re-running the installer does nothing on a bench that already has one. Copy it explicitly, or delete the file on the Pi first. The one exception is /etc/rtl_433/rtl_433.conf, which is copied unconditionally — a hand-edited flex decoder there is lost on every install.

There is no default testbench.json. Slots auto-detect from USB topology at boot. Only create /etc/rfc2217/testbench.json to override labels or pins.

Auto-detection over-reports on a Pi 3B+. It finds five ports where only four are wired; 0:1.4 is the phantom. Pin the real four in testbench.json rather than trusting the count.

Do not usbreset several ESP32s at once on a Pi 3B+. Concurrent resets have hard-hung the bench — suspected undervoltage. Reset one slot at a time.

No overlayroot. It was removed, so the filesystem is writable and there is no power-loss protection. Shut down cleanly.

openocd-esp32 is fetched from GitHub releases for the detected architecture. On an unsupported arch the installer warns and continues, and debug endpoints then fail at runtime rather than at install time — check /api/info if debug misbehaves on a new host.

After a change lands

Re-run the tests against the live bench, and say what actually ran:

pytest pytest/ --wt-url $TESTBENCH_URL            # no DUT needed
pytest pytest/ --wt-url $TESTBENCH_URL --run-dut  # full
pytest pytest/host/                                            # no hardware at all

On a bench with nothing plugged in, expect roughly 47 passed, 42 skipped — that is the whole of what can be checked without a DUT, and it is the right acceptance run for a new build. A test that fails there rather than skipping is usually the test's own bug: one that omits a slot lets the portal auto-select the first present device, so on an empty bench it fails with no device found instead of exercising what it claims to.

If the change altered externally observable behaviour, it belongs in the FSD; if it changed how the bench is built or deployed, it belongs in the Method. See docs/Method/AI-Workflow.md.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.