mcbluetooth-esp32/README.md
Ryan Malloy 61da375a0c Add SPP (Serial Port Profile) support for bidirectional data transfer
Firmware:
- Add spp_connect, spp_disconnect, spp_data events
- Add spp_send, spp_disconnect, spp_status commands
- Track remote address for connected SPP peer
- Report received data as hex + optional text decode

Python MCP:
- esp32_spp_send(data/data_hex) - send text or binary
- esp32_spp_disconnect() - close SPP connection
- esp32_spp_status() - query connection state

Tested: Linux rfcomm connect → ESP32, bidirectional data transfer works
2026-02-03 13:39:17 -07:00

7.8 KiB

mcbluetooth-esp32

ESP32 Bluetooth test harness MCP server — UART-controlled peripheral for automated E2E Bluetooth testing.

Overview

This project turns an ESP32 into a programmable Bluetooth peripheral that can be controlled via MCP (Model Context Protocol). Combined with mcbluetooth (Linux BlueZ MCP server), it enables fully automated end-to-end Bluetooth testing orchestrated by an LLM.

┌─────────────────────────────────────────────────────────────────┐
│                    LLM (Claude, etc.)                           │
│              Orchestrates both MCP servers                      │
└─────────────────────────┬───────────────────────────────────────┘
                          │
          ┌───────────────┴───────────────┐
          │                               │
  ┌───────┴───────┐               ┌───────┴────────┐
  │  mcbluetooth  │               │mcbluetooth-esp32│
  │   (bt_* tools)│               │ (esp32_* tools) │
  └───────┬───────┘               └───────┬────────┘
          │                               │
    D-Bus/BlueZ                     Serial/UART
          │                               │
  ┌───────┴───────┐               ┌───────┴────────┐
  │   Linux Host  │◄── Bluetooth ──►│     ESP32      │
  │    (hci0/1)   │    (over air)   │  (peripheral)  │
  └───────────────┘               └────────────────┘

ESP32 Capabilities

The ESP32 can emulate various Bluetooth devices for testing:

Capability Description
Classic BT Pairing All 4 SSP modes: Just Works, Numeric Comparison, Passkey Entry, Legacy PIN
SPP (Serial Port) Bidirectional data transfer over Classic Bluetooth virtual serial port
BLE GATT Server Dynamic service/characteristic creation at runtime
Device Personas Presets for headset, speaker, keyboard, sensor, phone
IO Capabilities Configurable: no_io, display_only, display_yesno, keyboard_only, keyboard_display
Event Reporting Real-time events: pair_request, pair_complete, spp_data, gatt_read, gatt_write, gatt_subscribe

Hardware Requirements

  • Original ESP32 (ESP32-D0WD or similar) — must have Classic Bluetooth support
  • ESP32-S3, C3, H2, S2 will NOT work (no BR/EDR radio)
  • USB cable for serial communication

Verified Hardware

Property Value
Chip ESP32-D0WD-V3 (rev 3.1)
Features Wi-Fi, BT Classic + BLE, Dual Core 240MHz
Flash 4MB
Protocol NDJSON over UART @ 115200 baud

Quick Start

1. Flash the firmware

# Install ESP-IDF v5.x first (see docs/hardware-setup.md)
cd firmware
idf.py set-target esp32
idf.py -p /dev/ttyUSB0 flash

2. Run the MCP server

# Install and run
uvx mcbluetooth-esp32

# Or with explicit port
ESP32_SERIAL_PORT=/dev/ttyUSB0 uvx mcbluetooth-esp32

3. Add to Claude Code

claude mcp add mcbluetooth-esp32 -- uvx mcbluetooth-esp32

4. Verify connection

esp32_connect()      # → connected=true, ready=true
esp32_ping()         # → {pong: true}
esp32_get_info()     # → chip, fw_version, bt_mac

MCP Tools

Connection & System

Tool Description
esp32_connect Open serial connection to ESP32
esp32_disconnect Close serial connection
esp32_ping Verify UART link is alive
esp32_status Get current BT/BLE state
esp32_get_info Get chip model, firmware version, MAC
esp32_reset Reboot the ESP32

Configuration

Tool Description
esp32_configure Set device name, IO capabilities, PIN
esp32_set_ssp_mode Configure SSP pairing mode with optional auto-accept
esp32_load_persona Load preset device profile (headset, speaker, etc.)

Classic Bluetooth

Tool Description
esp32_classic_enable Enable BR/EDR radio
esp32_classic_disable Disable BR/EDR radio
esp32_classic_set_discoverable Make device visible for pairing
esp32_classic_pair_respond Accept/reject incoming pairing

SPP (Serial Port Profile)

Tool Description
esp32_spp_send Send data (text or hex) over SPP connection
esp32_spp_disconnect Close the active SPP connection
esp32_spp_status Get SPP connection status and remote address

BLE / GATT

Tool Description
esp32_ble_enable Enable BLE radio
esp32_ble_disable Disable BLE radio
esp32_ble_advertise Start/stop BLE advertising
esp32_ble_set_adv_data Configure advertisement data
esp32_gatt_add_service Create GATT service
esp32_gatt_add_characteristic Add characteristic to service
esp32_gatt_set_value Set characteristic value
esp32_gatt_notify Send notification to subscribers
esp32_gatt_clear Remove all GATT services

Events

Tool Description
esp32_get_events Retrieve event history
esp32_wait_event Block until specific event occurs
esp32_clear_events Clear event history

E2E Testing

Run automated tests using Claude CLI in headless mode:

# Setup test environment
mkdir -p /tmp/bt-e2e-test && cd /tmp/bt-e2e-test
git init

# Create MCP config with both servers
cat > .mcp.json << 'EOF'
{
  "mcpServers": {
    "esp32": {
      "type": "stdio",
      "command": "uvx",
      "args": ["mcbluetooth-esp32"],
      "env": {"ESP32_SERIAL_PORT": "/dev/ttyUSB0"}
    },
    "bluez": {
      "type": "stdio",
      "command": "uvx",
      "args": ["mcbluetooth"]
    }
  }
}
EOF

# Run test suite
PROMPT=$(cat tests/prompts/test-prompt-v5.md)
claude -p "$PROMPT" \
  --mcp-config .mcp.json \
  --allowedTools "mcp__esp32__*,mcp__bluez__*" \
  --output-format json

Test Coverage (v5 - 76 tests)

Phase Tests Coverage
ESP32 Connection 1-4 connect, ping, info, status
BlueZ Adapter 5-8 list, info, pairable, discoverable
Classic BT + SSP 9-24 Full pairing workflow with auto-accept
BLE GATT Setup 30-42 Battery Service, Environmental Sensing
HCI Capture 43-55 btsnoop capture and analysis
GATT Operations 56-63 read, write, notify, subscribe
Cleanup 64-76 Adapter management, reset

Recent Test Results

Version Tests Result Duration
v4 71 66 PASS, 5 PARTIAL 8.6 min
v5 76 76/76 PASS (100%) 7.6 min

Documentation

Document Description
hardware-setup.md ESP-IDF installation, flashing, wiring
protocol-spec.md NDJSON UART protocol specification
test-scenarios.md Manual E2E test procedures
automated-e2e-testing.md Claude CLI headless testing guide

Development

# Install dev dependencies
uv sync --all-extras

# Run unit tests
uv run pytest tests/ -v --ignore=tests/integration

# Run integration tests (requires ESP32)
ESP32_SERIAL_PORT=/dev/ttyUSB0 uv run pytest tests/integration/ -v

# Lint
uv run ruff check src/

License

MIT