- docs/automated-e2e-testing.md: Guide for running headless Claude CLI
tests with both mcbluetooth and mcbluetooth-esp32 MCP servers
- tests/prompts/test-prompt-v4.md: 71-test suite covering Classic BT,
BLE GATT, HCI capture, device management
- tests/prompts/test-prompt-v5.md: 76-test suite adding Battery Service
(0x180F) and bt_ble_battery verification
Test results from v4: 71/71 PASS with 143 HCI packets captured
Two fixes for the E2E test failures:
1. event_reporter_init() was never called in app_main(), so the
FreeRTOS queue and reporter task were never created. Every BT
event (pair_request, gatt_read, gatt_write, gatt_subscribe)
was silently dropped at the NULL-queue guard.
2. SSP Numeric Comparison requires both sides to confirm, but
bt_pair blocks until completion — creating a deadlock since
the LLM can't send classic_pair_respond while waiting. Added
auto_accept flag to set_ssp_mode that auto-confirms numeric
comparison requests in the GAP callback.
The boot event fires early in app_main before the UART command
handler task is fully initialised. This means the first command
after connect can get lost, causing transient ping timeouts.
Now esp32_connect retries a ping (up to 5 attempts, 1s timeout
each) after the boot-event wait, so it only returns "connected"
when the firmware is actually responsive.
The previous commit only fixed Response.from_json(), but the serial
client's read loop uses parse_message() which constructs Response
directly. Apply the same string-to-dict normalisation there.
- Make get_client() sync (was async but did no async work). Callers
that omitted await silently got a coroutine object instead of the
SerialClient, causing "'coroutine' object has no attribute 'connect'"
errors on every tool call.
- Fix esp32_connect: use get_client_or_none() for init check and
client.event_queue.wait_for() for boot event (wait_event() didn't
exist on SerialClient).
- Normalise Response.data to dict at parse time — firmware returns
bare strings on some error paths, which broke .get() calls in tool
error handlers.
- Remove stale await from ble.py (9 calls) and classic.py (4 calls).
Tested with dual-MCP headless claude session: 26/27 PASS.
The firmware uses UART0 (via USB bridge) with ESP-IDF console disabled,
not UART1 on GPIO4/GPIO5 as originally documented. Updated both docs to
reflect the actual hardware-verified configuration:
- protocol-spec.md: UART peripheral description
- hardware-setup.md: wiring section, monitor section, sdkconfig table,
troubleshooting steps
Rewrite test-scenarios.md with detailed per-step instructions
including exact tool calls, expected responses, negative test
cases, teardown procedures, and an environment variable reference.