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
6.6 KiB
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
mcbluetoothMCP 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:
# 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
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
idf.py build
Or using the project Makefile from the repository root:
make build
4. Flash
idf.py -p /dev/ttyUSB4 flash
Or:
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:
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
make flash-monitor SERIAL_PORT=/dev/ttyUSB4
Quick Verification
Using the MCP server
# 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:
esp32_connect-- opens the serial linkesp32_ping-- should return{"pong": true}esp32_get_info-- should return chip model, firmware version, MAC address
Using the Makefile ping target
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:
screen /dev/ttyUSB4 115200
Type (all on one line, then press Enter):
{"type":"cmd","id":"1","cmd":"ping"}
You should see:
{"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):
# 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:
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:
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
-
Check baud rate. Both sides must use 115200. Verify in
screenor your terminal emulator. -
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. -
Send valid JSON. The firmware expects complete JSON objects terminated by
\n. A barepingwon't work -- send{"type":"cmd","id":"1","cmd":"ping"}\n. -
Verify the firmware booted. After flashing, the firmware should emit a
bootevent 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.