# Build and deploy Target: Windows, Arduino IDE 2.x, board package **esp32-bluepad32 4.1.0**, original ESP32 (WROOM/WROVER). S3/C3/C6/H2 have no Bluetooth Classic, so a PS4 pad will not pair with them. ## 1. Install the board package Preferences -> Additional board manager URLs, add: ``` https://raw.githubusercontent.com/ricardoquesada/esp32-arduino-lib-builder/master/bluepad32_files/package_esp32_bluepad32_index.json ``` Boards Manager -> install **esp32_bluepad32**. Then Tools -> Board -> **esp32_bluepad32** -> ESP32 Dev Module. Picking the board out of the normal `esp32` package is what gives you `fatal error: Bluepad32.h: No such file or directory`. Bluepad32 is a replacement board package, not a Library Manager entry, and adding it as a ZIP library will not work. Do **not** install Legoino or NimBLE-Arduino. If either ends up in the build you are back to two Bluetooth host stacks fighting over one radio, which is the whole problem this project exists to avoid. ## 2. Put the BTstack headers on the include path Package 4.1.0 ships the BTstack and Bluepad32 headers under `tools\sdk\\include\`, but does not add them to the compiler's include path. Without this step you get: ``` fatal error: btstack.h: No such file or directory ``` 4.1.0 is built on the arduino-esp32 **2.x** layout, so there is no `flags\includes` file to edit (that only exists on core 3.x). The include paths live in `compiler.cpreprocessor.flags` in `platform.txt`. Rather than edit that file, drop a `platform.local.txt` beside it — Arduino reads it as an override, the original stays untouched, and rolling back is deleting one file. In PowerShell: ```powershell $plat = "$env:LOCALAPPDATA\Arduino15\packages\esp32-bluepad32\hardware\esp32\4.1.0" $flags = 'compiler.cpreprocessor.flags={compiler.cpreprocessor.flags.{build.mcu}} ' + '"-I{compiler.sdk.path}/{build.memory_type}/include" ' + '"-I{compiler.sdk.path}/include/btstack/src" ' + '"-I{compiler.sdk.path}/include/btstack/include" ' + '"-I{compiler.sdk.path}/include/btstack/platform/freertos" ' + '"-I{compiler.sdk.path}/include/bluepad32/include"' $flags | Out-File "$plat\platform.local.txt" -Encoding ascii Get-Content "$plat\platform.local.txt" ``` It has to be a **single line**. The `Get-Content` at the end is there to confirm it did not wrap. The first two tokens restate what `platform.txt` already had — the override replaces the whole value, so they have to be repeated. `{compiler.sdk.path}` resolves to `tools\sdk\`, so these entries work for every MCU in the package, not just esp32. What each added path is for: | Path | Provides | | --- | --- | | `btstack/src` | `btstack.h`, `gatt_client.h`, `btstack_run_loop.h` | | `btstack/include` | `btstack_config.h` — separate directory, and BTstack includes it from `btstack.h`, so missing it gives you a confusing second error right after fixing the first | | `btstack/platform/freertos` | the ESP32 run loop implementation | | `bluepad32/include` | `uni.h`, if you later want the `uni_bt_allowlist` API | Duplicate `-I` flags are harmless, so it does not matter if `bluepad32/include` was already covered. Two things to know about this edit: * A Bluepad32 package update **wipes it**. Keep a copy of `platform.local.txt` somewhere outside `Arduino15` — there is one in `docs/` here. * A malformed value breaks compilation for *every* ESP32 sketch on the machine, not just this one. If things go strange, delete the file. ## 3. Board settings * Board: ESP32 Dev Module (under esp32_bluepad32) * Partition Scheme: **Huge APP (3MB No OTA/1MB SPIFFS)**. BTstack with both transports plus the GATT client does not fit the default partition table. * Upload speed 921600 is fine; drop to 115200 if uploads fail. ## 4. Before flashing After creating `platform.local.txt`, fully quit the Arduino IDE — closing the sketch window is not enough, it has to re-read the platform — and clear the build cache: ```powershell Remove-Item "$env:LOCALAPPDATA\Temp\arduino\sketches\*" -Recurse -Force -ErrorAction SilentlyContinue ``` Then set your two hub addresses in `HUB_ADDR_STR` at the top of the sketch and compile. ## 5. First run 1. Power both hubs and press their green buttons so they are advertising. 2. Flash, open Serial Monitor at 115200. 3. Expect `Connecting hubs, then opening for the gamepad`. Each hub's LED goes green as it is claimed. 4. Once both are up you get `Hubs up - put the PS4 pad in pairing mode`. Hold SHARE + PS on the controller until the light bar flashes. 5. Onboard LED on GPIO2 goes solid when the pad and both hubs are all connected. The order matters and is deliberate: gamepad discovery stays off until the hubs are connected, so Bluepad32's inquiry and LE scan are not running at the same time as our LE create-connection. ## Troubleshooting **A missing header from `btstack/3rd-party`.** Add another `-I` line for that directory and redo step 4. **Hubs never connect, no LE connection-complete in the log.** In order of likelihood: the hubs went back to sleep (they time out after a couple of minutes — press the buttons again); the addresses are wrong; or the address type is wrong. For the last one, switch `HUB_ADDR_TYPE` to `BD_ADDR_TYPE_LE_RANDOM`. **One hub connects, the other never does.** Expected if the first one is stuck mid-discovery. `serviceHubConnections()` only runs one at a time by design; check `gActive` is being released. **Motors stutter, or a hub drops off after a few seconds.** Commands are going out faster than the hub can absorb. Raise `MOTOR_MIN_GAP_MS`. **Random crashes at unrelated places.** Almost certainly a BTstack call added to `loop()`. BTstack is not thread-safe and `loop()` is not the BTstack thread — see the header comment in the sketch. Turn on verbose BTstack logging before guessing at any of it. Nearly every failure mode here surfaces as a specific HCI event you can read. --- Created by: Jess Rogerson (yelling commands at Claude.AI)