Skip to content
Development
Skill

/testbench-ble

Use this skill whenever the user needs to interact with BLE peripherals through the testbench — scanning for devices, connecting by address, writing to GATT characteristics, or checking connection status. The Pi acts as a BLE-to-HTTP bridge using bleak. Also use when sending

From plugin
embedded-ai-harness
27918 skills

Claude Code

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

SKILL.md

testbench-ble.SKILL.md
name: testbench-ble
description: Use this skill whenever the user needs to interact with BLE peripherals through the testbench — scanning for devices, connecting by address, writing to GATT characteristics, or checking connection status. The Pi acts as a BLE-to-HTTP bridge using bleak. Also use when sending keystrokes to BLE HID devices, triggering OTA via BLE commands, or debugging BLE connectivity. Triggers on "BLE", "bluetooth", "GATT", "NUS", "Nordic UART", "BLE scan", "BLE write", "BLE connect".

ESP32 Bluetooth LE Proxy

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.

Endpoints

Request and response shapes: [FSD Appendix D.5](../../../docs/Harness-FSD.md#d5-ble-proxy).

**One BLE connection at a time** — connect to a second peripheral without disconnecting the first and the call fails rather than switching.

Examples

# Scan for BLE devices (5s timeout)
curl -X POST $TESTBENCH_URL/api/ble/scan \
  -H 'Content-Type: application/json' \
  -d '{"timeout": 5}'

# Scan with name filter
curl -X POST $TESTBENCH_URL/api/ble/scan \
  -H 'Content-Type: application/json' \
  -d '{"timeout": 5, "name_filter": "<device-name>"}'

# Connect by MAC address
curl -X POST $TESTBENCH_URL/api/ble/connect \
  -H 'Content-Type: application/json' \
  -d '{"address": "AA:BB:CC:DD:EE:FF"}'

# Write hex data to a GATT characteristic
curl -X POST $TESTBENCH_URL/api/ble/write \
  -H 'Content-Type: application/json' \
  -d '{"characteristic": "6e400002-b5a3-f393-e0a9-e50e24dcca9e", "data": "48656c6c6f", "response": true}'

# Check connection status
curl $TESTBENCH_URL/api/ble/status

# Disconnect
curl -X POST $TESTBENCH_URL/api/ble/disconnect

Nordic UART Service (NUS) UUIDs

| UUID | Role | |------|------| | `6e400001-b5a3-f393-e0a9-e50e24dcca9e` | NUS Service | | `6e400002-b5a3-f393-e0a9-e50e24dcca9e` | RX Characteristic (write to this) | | `6e400003-b5a3-f393-e0a9-e50e24dcca9e` | TX Characteristic (notifications from device) |

Common Workflows

1. **Send a command via NUS:**

  • `POST /api/ble/scan` with `name_filter` to find device
  • `POST /api/ble/connect` with the MAC address from scan results
  • `POST /api/ble/write` with NUS RX UUID and hex-encoded command
  • `POST /api/ble/disconnect` when done

2. **Check if device is advertising:**

  • `POST /api/ble/scan` with short timeout and name filter
  • Check `devices` array in response

3. **Send binary command protocol:**

  • Connect to device
  • Encode command bytes as hex (e.g., `0x02` + "Hello" = `0248656c6c6f`)
  • Write to NUS RX characteristic
  • Monitor device response via serial or UDP logs (see testbench-logging)

Troubleshooting

| Problem | Fix | |---------|-----| | "BLE not available" | `bleak` not installed on testbench Pi | | Scan returns empty | Increase timeout; check device is advertising | | Connect fails (409) | Already connected — disconnect first | | Write fails "invalid hex data" | Data must be hex string (e.g., `"48656c6c6f"` for "Hello") | | Device not found by name | Check exact advertised name; BLE names are case-sensitive |

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.

skills10d309k
@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-ble.

Which coding agents can use testbench-ble?
testbench-ble 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.