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
27918 skills

Claude Code

$ npx -y skills add SensorsIot/Embedded-AI-Harness --skill testbench-logging --agent claude-code

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
@sensorsiot
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.

Stars on GitHub

Momentum

stars in total
279
Star history of SensorsIot/Embedded-AI-Harness on GitHub, a chart by star-history.com

Other skills on embedded-ai-harness.

build

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…

define

define

Phase 0 of AI Closed-Loop Programming — Definition: engineers the WHAT the loop converges on. Writes and evolves the FSD —…

grill-me

grill-me

Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision…

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills11d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about testbench-logging.

Which coding agents can use testbench-logging?
testbench-logging installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.