mcbluetooth-esp32/docs/hardware-setup.md
Ryan Malloy 0e7b8c2ef5 Fix docs to match UART0 firmware implementation
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
2026-02-02 15:38:20 -07:00

222 lines
6.6 KiB
Markdown

# Hardware Setup Guide
How to set up the ESP32 hardware and build environment for the mcbluetooth-esp32 test harness.
## Requirements
- **ESP32 dev board** -- must be an original ESP32 (ESP32-D0WD or similar) with Classic Bluetooth support. ESP32-S3, ESP32-C3, ESP32-H2, and ESP32-S2 lack the BR/EDR radio and will not work for Classic BT pairing tests.
- **USB cable** (USB-A to micro-USB or USB-C depending on your board)
- **Linux host** with BlueZ installed (for the `mcbluetooth` MCP server on the other side of E2E tests)
- **ESP-IDF v5.x** toolchain
## Verified Hardware
| Property | Value |
|----------|-------|
| Chip | ESP32-D0WD-V3 (rev 3.1) |
| Flash | 4MB |
| Crystal | 40MHz |
| Features | Wi-Fi, Bluetooth (dual-mode), Dual Core, 240MHz |
Any ESP32 board based on the original ESP32 chip should work. Commonly available boards include ESP32-DevKitC, ESP32-WROOM-32, and NodeMCU-32S.
## Wiring
### USB only (default)
A single USB cable handles both flashing and NDJSON protocol communication. The ESP32 dev board's built-in USB-to-UART bridge (typically CP2102 or CH340) connects to UART0 (TX=GPIO1, RX=GPIO3).
The firmware uses **UART0** for the NDJSON protocol. The ESP-IDF console is disabled (`CONFIG_ESP_CONSOLE_NONE=y`) so there is no conflict -- the firmware owns UART0 exclusively. No additional wiring or USB-UART adapters are needed.
The dev board appears as `/dev/ttyUSB*` on the host. Use this device path for `ESP32_SERIAL_PORT`.
## ESP-IDF Setup
### 1. Install ESP-IDF v5.x
Follow the official installation guide: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/
On Arch Linux:
```bash
# Install dependencies
sudo pacman -S cmake ninja python
# Clone ESP-IDF
mkdir -p ~/esp && cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32
# Activate the environment (add to .bashrc or run each session)
. ~/esp/esp-idf/export.sh
```
### 2. Set the target
```bash
cd firmware
idf.py set-target esp32
```
This only needs to be done once per checkout. It configures the build system for the ESP32 chip.
### 3. Build
```bash
idf.py build
```
Or using the project Makefile from the repository root:
```bash
make build
```
### 4. Flash
```bash
idf.py -p /dev/ttyUSB4 flash
```
Or:
```bash
make flash SERIAL_PORT=/dev/ttyUSB4
```
### 5. Monitor (optional)
Open the ESP-IDF serial monitor to watch raw UART traffic. Since the firmware owns UART0 (console is disabled), you will see NDJSON protocol messages rather than ESP-IDF log output:
```bash
idf.py -p /dev/ttyUSB4 monitor
```
Press `Ctrl+]` to exit the monitor. Note: while the monitor is open, the MCP server cannot use the same serial port.
### 6. Flash and monitor in one step
```bash
make flash-monitor SERIAL_PORT=/dev/ttyUSB4
```
## Quick Verification
### Using the MCP server
```bash
# Set the serial port and start the server
ESP32_SERIAL_PORT=/dev/ttyUSB4 uvx mcbluetooth-esp32
```
Then from a Claude Code session with the MCP server configured, call:
1. `esp32_connect` -- opens the serial link
2. `esp32_ping` -- should return `{"pong": true}`
3. `esp32_get_info` -- should return chip model, firmware version, MAC address
### Using the Makefile ping target
```bash
make ping SERIAL_PORT=/dev/ttyUSB4
```
This runs a standalone Python script that connects, sends a `ping` command, prints the response, and disconnects.
### Raw serial check
If everything else fails, use `screen` or `minicom` to send raw JSON:
```bash
screen /dev/ttyUSB4 115200
```
Type (all on one line, then press Enter):
```json
{"type":"cmd","id":"1","cmd":"ping"}
```
You should see:
```json
{"type":"resp","id":"1","status":"ok","data":{"pong":true}}
```
Press `Ctrl+A` then `K` then `Y` to exit screen.
## sdkconfig
The project ships `firmware/sdkconfig.defaults` with the required Bluetooth configuration pre-set:
| Setting | Value | Purpose |
|---------|-------|---------|
| `CONFIG_BT_ENABLED` | y | Enable Bluetooth controller |
| `CONFIG_BT_BLUEDROID_ENABLED` | y | Use Bluedroid host stack |
| `CONFIG_BT_CLASSIC_ENABLED` | y | Enable BR/EDR (Classic BT) |
| `CONFIG_BT_BLE_ENABLED` | y | Enable BLE |
| `CONFIG_BT_SPP_ENABLED` | y | Enable Serial Port Profile |
| `CONFIG_BT_GATTS_ENABLE` | y | Enable GATT Server |
| `CONFIG_BTDM_CTRL_MODE_BTDM` | y | Dual-mode controller (Classic + BLE simultaneously) |
| `CONFIG_ESP_CONSOLE_NONE` | y | Disable ESP-IDF console so firmware owns UART0 |
| `CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE` | y | 1.5MB app partition (dual-mode BT stack needs >1MB) |
Do not modify these unless you understand the implications. Disabling `CONFIG_BT_CLASSIC_ENABLED` breaks all Classic BT pairing tests.
## Troubleshooting
### Permission denied on `/dev/ttyUSB*`
Add your user to the `dialout` group (or `uucp` on Arch Linux):
```bash
# Debian/Ubuntu
sudo usermod -aG dialout $USER
# Arch Linux
sudo usermod -aG uucp $USER
```
Log out and back in for the group change to take effect.
### Device not found
Check what serial devices are present:
```bash
ls -la /dev/ttyUSB* /dev/ttyACM*
```
If nothing appears, verify the USB cable is a data cable (not charge-only) and that the board's USB-UART chip driver is loaded:
```bash
dmesg | tail -20
```
Look for lines mentioning `cp210x`, `ch341`, or `ftdi_sio`.
### Flash fails or hangs
Some ESP32 boards require holding the **BOOT** button during the initial flash sequence. Hold BOOT, press and release EN (reset), then release BOOT. The flash should proceed.
If the board has auto-download circuitry (most DevKitC boards do), this should not be necessary.
### No response over UART
1. **Check baud rate.** Both sides must use 115200. Verify in `screen` or your terminal emulator.
2. **Make sure nothing else is using the port.** The ESP-IDF monitor, `screen`, another MCP server instance, or any other serial tool will lock the device. Only one process can open `/dev/ttyUSB*` at a time.
3. **Send valid JSON.** The firmware expects complete JSON objects terminated by `\n`. A bare `ping` won't work -- send `{"type":"cmd","id":"1","cmd":"ping"}\n`.
4. **Verify the firmware booted.** After flashing, the firmware should emit a `boot` event within ~2 seconds. If you see nothing at all, try pressing the EN (reset) button on the board.
### Build errors about missing Bluetooth headers
Make sure `idf.py set-target esp32` was run. The ESP32-S3 and ESP32-C3 targets do not expose Classic BT APIs, which causes build failures.
### NVS errors on first boot
If the console shows `NVS partition issue, erasing and re-initializing`, this is expected on first flash or after a partition table change. The firmware handles it automatically by erasing and reinitializing NVS.