Skip to content
Development
Skill

/testbench-logging

Use this skill whenever you need to read serial output or debug logs from ESP32 devices on the testbench. Covers serial monitor with pattern matching (wait for boot messages, WiFi connected, crash dumps), UDP debug log retrieval when USB is occupied (e.g. HID keyboard), boot

From plugin
embedded-ai-harness
18018 skills
Install
$ npx -y skills add SensorsIot/Embedded-AI-Harness --skill testbench-logging --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-logging

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use this skill whenever you need to read serial output or debug logs from ESP32 devices on the testbench. Covers serial monitor with pattern matching (wait for boot messages, WiFi connected, crash dumps), UDP debug log retrieval when USB is occupied (e.g. HID keyboard), boot

SKILL.md

testbench-logging.SKILL.md
name: testbench-logging
description: Use this skill whenever you need to read serial output or debug logs from ESP32 devices on the testbench. Covers serial monitor with pattern matching (wait for boot messages, WiFi connected, crash dumps), UDP debug log retrieval when USB is occupied (e.g. HID keyboard), boot capture, and crash analysis. Use for verifying firmware started correctly, checking WiFi connection status, or diagnosing boot loops. Triggers on "serial monitor", "log", "debug log", "UDP log", "boot output", "crash", "monitor", "pattern", "serial output".

ESP32 Debug Logging

Base URL: `$TESTBENCH_URL` — see Step 0

Step 0: Point at a bench

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.

Two logging methods are available. Choose based on your situation:

| | Serial Monitor | UDP Logs | |---|---|---| | **Works without WiFi** | Yes | No | | **Boot/crash output** | Yes | No | | **Pattern matching** | Built-in (regex + timeout) | Manual (poll + grep) | | **Blocks serial port** | Yes (one session per slot) | No | | **Multiple devices** | One slot at a time | All devices simultaneously | | **Long-running** | Limited by timeout | Continuous (buffer persists) |

Endpoints

Request and response shapes: serial in [FSD Appendix D.2](../../../docs/Harness-FSD.md#d2-serial-management), UDP log in [D.7](../../../docs/Harness-FSD.md#d7-udp-log), the portal's own activity log in [D.14](../../../docs/Harness-FSD.md#d14-activity-log).

Three different logs, and picking the wrong one wastes a test run: `/api/serial/*` is what the device printed over USB, `/api/udplog` is what it sent over the network, and `/api/log` is what the *portal* did — never device output.

Serial Monitor

Reads serial output via RFC2217 proxy. Optionally waits for a regex pattern.

# Wait up to 10s for a pattern match
curl -X POST $TESTBENCH_URL/api/serial/monitor \
  -H 'Content-Type: application/json' \
  -d '{"slot": "SLOT1", "pattern": "WiFi connected", "timeout": 10}'

# Just capture output for 5s (no pattern)
curl -X POST $TESTBENCH_URL/api/serial/monitor \
  -H 'Content-Type: application/json' \
  -d '{"slot": "SLOT1", "timeout": 5}'

Response: `{"ok": true, "matched": true, "line": "WiFi connected to MyAP", "output": [...]}`

A debug session silently changes what reset returns

`POST /api/serial/reset` picks its method from the slot. With no debug session it pulses DTR/RTS and returns the boot log as **a list of lines**. With OpenOCD attached it issues a JTAG `reset run` instead and returns **a single string** of OpenOCD's reply — `JTAG tap: esp32c3.tap0 ...` — which is not the device's output at all. Same endpoint, same request, two response shapes and two meanings.

Nothing in the response announces this except a `"method": "jtag"` key that is absent on the serial path. Client code that iterates `output` gets characters instead of lines and quietly finds no boot markers.

**OpenOCD attaches by itself when a device is plugged in**, so this is the default state of a board on a JTAG-capable slot, not something you opted into. Stop it before capturing boot output:

curl -X POST $TESTBENCH_URL/api/debug/stop \
  -H 'Content-Type: application/json' -d '{"slot": "SLOT3"}'

Check first with `debugging` in `/api/devices` — a slot reads `idle` either way.

**Serial is the lifeline.** Never decide whether a device is alive by pinging it or calling its HTTP endpoint — a device that boots fine but never joins WiFi looks identical to a dead one. Reading what it printed tells you which. Boot-marker patterns for running / download-mode / unknown are in [`references/state-detection.md`](references/state-detection.md).

Serial cannot count restarts on a native-USB part

On a chip whose console is USB-CDC (ESP32-C3/S3/C6), `esp_restart()` resets the USB-JTAG peripheral, the devnode re-enumerates, and the RFC2217 proxy's file descriptor breaks. The capture socket **stays open and simply goes quiet** — and on reconnect it can replay stale scrollback from before the reset. A test that counted "boots seen" from serial once read 25 lines then nothing for 30 minutes while the device restarted six times, and a filtered view showed a clean pass.

Serial is authoritative only **within one boot**. For anything that counts restarts — a 5-minute-rule reboot, a watchdog reset, a crash loop — read a monotonic **boot counter the firmware publishes** (e.g. an MQTT status payload or UDP telemetry), not the console. A silent device and a dead probe look identical in any filtered serial view, so make the harness exit loud (non-zero with the raw captured-line count) rather than reporting `boots: 0`.

Use serial monitor when:

  • You need **boot messages** (before WiFi is up)
  • You need to **wait for a specific log line** (pattern matching with timeout)
  • Device has **no WiFi** or UDP logging is not compiled in
  • You want **crash/panic output** from the UART

Dual-USB hub boards

  • **Reset** via the JTAG slot (triggers DTR/RTS auto-download circuit)
  • **Monitor** via the UART slot (where ESP_LOGI output appears)
  • Boot output on the JTAG slot will be empty or minimal — the actual boot log appears on the UART slot
# Reset via JTAG slot
curl -X POST $TESTBENCH_URL/api/serial/reset \
  -H 'Content-Type: application/json' \
  -d '{"slot": "<JTAG-slot>"}'

# Capture boot output from UART slot
curl -X POST $TESTBENCH_URL/api/serial/mon
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.