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 tests involve MQTT communication — starting/stopping the mosquitto broker on the testbench Pi, publishing test messages, subscribing to topics, or verifying ESP32 MQTT client behavior. The broker runs on the Pi's WiFi AP network (192.168.4.1:1883) so DUTs
$ npx -y skills add SensorsIot/Embedded-AI-Harness --skill testbench-mqtt --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/testbench-mqttContext preview
The summary Claude sees to decide when to auto-load this skill.
Use this skill whenever tests involve MQTT communication — starting/stopping the mosquitto broker on the testbench Pi, publishing test messages, subscribing to topics, or verifying ESP32 MQTT client behavior. The broker runs on the Pi's WiFi AP network (192.168.4.1:1883) so DUTs
name: testbench-mqtt description: Use this skill whenever tests involve MQTT communication — starting/stopping the mosquitto broker on the testbench Pi, publishing test messages, subscribing to topics, or verifying ESP32 MQTT client behavior. The broker runs on the Pi's WiFi AP network (192.168.4.1:1883) so DUTs can reach it without internet. Use for MQTT integration tests, pub/sub verification, and broker lifecycle management. Triggers on "MQTT", "broker", "mosquitto", "publish", "subscribe", "topic", "MQTT test".
Base URL: `$TESTBENCH_URL` — see Step 0
There are several benches and their addresses move, so nothing here writes one down. `$BENCH` is not usable either — a container cannot resolve mDNS. Discover the bench and export its URL:
export TESTBENCH_URL=$(sudo python3 .claude/skills/esp-idf-handling/discover-testbench.py \
--url --name <bench-hostname>)
curl -s "$TESTBENCH_URL/api/info" # confirm before anything else`--url` refuses to guess when more than one bench answers, so `--name` is required whenever a second bench is powered on. `TESTBENCH_URL` is the same variable `pytest --wt-url` falls back to.
The testbench can run an MQTT broker (mosquitto) for testing ESP32 devices that use MQTT for communication. The broker is accessible to devices connected to the testbench's WiFi AP.
Request and response shapes: [FSD Appendix D.12](../../../docs/Harness-FSD.md#d12-mqtt-test-broker).
# Start the MQTT broker curl -X POST $TESTBENCH_URL/api/mqtt/start # Check broker status curl $TESTBENCH_URL/api/mqtt/status # Stop the MQTT broker curl -X POST $TESTBENCH_URL/api/mqtt/stop
| Property | Value | |----------|-------| | Broker | the bench address (from the LAN) or `192.168.4.1` (from the testbench AP) | | Default port | `1883` | | Authentication | None (open broker for testing) |
1. **Test ESP32 MQTT client:**
2. **Test MQTT disconnect/reconnect:**
**On a *shared* broker, don't stop it — isolate the one device.** If the broker also carries other devices (or the device under test uses a production broker over a tunnel rather than the bench mosquitto), stopping it is collateral damage. Cut MQTT for a single device with a source-scoped firewall rule on the broker host instead (needs root there):
# black hole (timeout): device sees silence, its keepalive eventually fires the will iptables -I INPUT -s <device-ip> -p tcp --dport 1883 -j DROP # connection refused (TCP reset): device sees an immediate refusal iptables -I INPUT -s <device-ip> -p tcp --dport 1883 -j REJECT --reject-with tcp-reset
The two are different tests: `DROP` is the "broker went away" black hole that exercises the last-will keepalive timeout; `REJECT` is the "broker actively refuses" path. Delete the rule (`-D` instead of `-I`) to restore. Prefer this over `/api/mqtt/stop` whenever the broker is not exclusively the DUT's.
3. **Test MQTT + WiFi together:**
The MQTT Last Will is published by the **broker**, on the device's behalf, only when the connection drops uncleanly. A device typically registers a retained will like `<id>/LWT = offline` and signals *liveness* on a **separate** birth or status topic (e.g. `<id>/STATUS {"online":true}`, republished on every connect and periodically). Nothing ever writes `LWT = online` — the will topic stays `offline` from the last drop until the next one.
So when checking recovery after an outage, watch for the **birth/status message to be republished**, not for the will topic to flip back. And because the retained will persists, a stale `offline` cannot be told apart from a freshly fired one by reading the retained value — to prove a will *fired*, seed a sentinel first (`mosquitto_pub -t <id>/LWT -r -m sentinel`), then confirm the broker overwrites it with `offline` once the keepalive expires.
`mosquitto_sub -C 1` on a retained status/telemetry topic returns the **last published value**, which during (and right after) an outage is the copy from *before* the device dropped — its counters are frozen. Reading it to judge recovery gives a false negative: e.g. `mqtt_disconnects` still shows the pre-outage number because the device has not reconnected and republished yet.
Judge recovery by the device **resuming live publishing**, not by the retained value. Subscribe for longer than the publish cadence and require ≥2 arrivals (the immediate retained copy, then at least one fresh publish); only then read counters. An MQTT reconnect does **not** reset uptime, so "small uptime" cannot stand in for "fresh" — only a live arrival can.
A spec that says "cut MQTT for 30 minutes" is usually testing "the device never restarts on MQTT loss." Unlike WiFi (which often has a restart-after-N-minutes rule), **MQTT loss typically has no restart timer at all** — so a few minutes proves "never restarts" exactly as well as 30, and the extra 25 just confirm the same nothing. Reserve long windows for behaviour actually gated on a long timer (a WiFi r
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…