btleplug 0.12.0

A Cross-Platform Rust Bluetooth Low Energy (BLE) GATT library.
Documentation
# Zephyr Test Peripheral - Debugging Findings

Date: 2026-03-01

## Build Issues (FIXED)

1. **`BT_LE_ADV_CONN` removed in Zephyr v4.3** — replaced with `BT_LE_ADV_CONN_FAST_1` (30-60ms intervals). Fix in `src/main.c`.

2. **Missing `intelhex` Python package** — needed by `nrfjprog` flash runner. Fix: `uv pip install intelhex`.

3. **Scan response too large** — adding `BT_DATA_UUID128_ALL` (18 bytes) + `BT_DATA_SVC_DATA128` (19 bytes) exceeds the 31-byte BLE legacy advertising limit. Fix: removed `svc_data`, kept only UUID list.

## Hardware Setup

- Board: nRF52840 DK (S/N 683223459)
- J-Link mass storage disabled via `JLinkExe``MSDDisable` (persistent, fixes macOS "Disk Not Ejected Properly" spam)
- Flash command: `uv run west flash --runner nrfjprog` (from `test-peripheral/zephyr/`)
- Build command: `uv run west build -b nrf52840dk/nrf52840` (from `test-peripheral/zephyr/`)

## UART Serial Issues (UNRESOLVED)

- UART console at 115200 baud produces **garbled output** on this board
- Tried: `CONFIG_LOG_MODE_IMMEDIATE=y`, `CONFIG_UART_INTERRUPT_DRIVEN=y` — still garbled
- The DTS confirms `uart0` at `0x1c200` (115200 baud), pins are correct
- **Root cause likely**: UARTE (DMA-based) + immediate logging causes buffer corruption when log messages are emitted from ISR context
- **Workaround**: Use Segger RTT instead of UART for logging:
  ```
  CONFIG_USE_SEGGER_RTT=y
  CONFIG_LOG_BACKEND_RTT=y
  CONFIG_LOG_BACKEND_UART=n
  CONFIG_RTT_CONSOLE=y
  ```
- RTT capture: `JLinkRTTLogger -Device NRF52840_XXAA -If SWD -Speed 4000 -RTTChannel 0 /tmp/rtt_log.txt`
- **Note**: JLinkRTTLogger can't find RTT Control Block after `nrfjprog --reset` — must connect while board is already running, not immediately after reset.

## Advertising Restart After Disconnect (FIXED)

**Problem**: After a BLE connection + disconnect cycle, the peripheral never re-advertised. Tests that ran after a connecting test would time out scanning for the peripheral.

**Root cause**: `bt_le_adv_start()` was called directly from the `disconnected` callback, which runs in the BLE RX thread context. The BLE controller hasn't fully completed the disconnect procedure at that point, so `bt_le_adv_start()` silently fails.

**Fix**: Defer advertising restart to the system workqueue with a 100ms delay:

```c
static void restart_adv_work_handler(struct k_work *work)
{
    bt_le_adv_stop();
    int err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad),
                               sd, ARRAY_SIZE(sd));
    if (err) {
        LOG_ERR("Advertising restart failed (err %d)", err);
    }
}

static K_WORK_DELAYABLE_DEFINE(restart_adv_work, restart_adv_work_handler);

// In disconnected callback:
k_work_reschedule(&restart_adv_work, K_MSEC(100));
```

This was verified: cross-process tests pass after connect+disconnect with this fix.

## Test Infrastructure Issues (PARTIALLY FIXED)

### CoreBluetooth in-process caching

**Problem**: When multiple tests run in the same process, creating a new `Manager` (→ new `CBCentralManager`) causes CoreBluetooth to stop reporting peripherals discovered by a previous Manager instance.

**Partial fix**: Share a single `Manager` and `Adapter` across all tests using `tokio::sync::OnceCell`:

```rust
static ADAPTER: OnceCell<Adapter> = OnceCell::const_new();
```

The Manager is leaked (`std::mem::forget`) so the underlying `CBCentralManager` stays alive for the process lifetime.

**Status**: This fixes some in-process test failures but not all. Event-based tests (`ServicesAdvertisement`, `ManufacturerDataAdvertisement`) still don't receive events for already-cached peripherals when run after other tests in the same process.

### Connect hangs

**Problem**: `peripheral.connect().await` can hang indefinitely if the peripheral is not advertising or CoreBluetooth is in a bad state.

**Fix**: Added 10-second timeouts around `connect()` and `discover_services()` in `find_and_connect()`.

## Remaining Work

1. **In-process test isolation**: Tests that use `adapter.events()` (advertisement event tests) don't work reliably after other tests in the same process. Options:
   - Run each test as a separate process invocation
   - Accept that advertisement event tests must run first (before any connecting tests)
   - Investigate if `adapter.stop_scan()` + delay + `adapter.start_scan()` forces CoreBluetooth to re-emit events

2. **Connection test suite**: `test_connection.rs` tests hang even after board reset. Need to investigate whether `find_and_connect()` or the test body itself hangs. The connect timeout should now prevent indefinite hangs.

3. **GPIO/LED indicators**: Didn't work — LEDs never turned on despite correct DT aliases and CONFIG_GPIO=y. Removed the GPIO code. Low priority to debug.

4. **Run remaining test suites**: `test_read_write`, `test_notifications`, `test_descriptors`, `test_device_info` have not been tested yet.

## Current prj.conf

```
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="btleplug-test"
CONFIG_BT_DEVICE_NAME_DYNAMIC=n
CONFIG_BT_ATT_PREPARE_COUNT=5
CONFIG_BT_BUF_ACL_RX_SIZE=251
CONFIG_BT_BUF_ACL_TX_SIZE=251
CONFIG_BT_L2CAP_TX_MTU=247
CONFIG_LOG=y
CONFIG_BT_LOG_LEVEL_INF=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_LOG_BACKEND_RTT=y
CONFIG_LOG_BACKEND_UART=n
CONFIG_CONSOLE=y
CONFIG_RTT_CONSOLE=y
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_HEAP_MEM_POOL_SIZE=4096
```

## Files Modified

- `test-peripheral/zephyr/src/main.c``BT_LE_ADV_CONN``BT_LE_ADV_CONN_FAST_1`, added UUID128_ALL to scan response, deferred adv restart on disconnect
- `test-peripheral/zephyr/prj.conf` — RTT logging config
- `tests/common/peripheral_finder.rs` — shared adapter singleton, connect timeouts, ScanFilter::default()
- `tests/test_discovery.rs` — uses shared adapter from peripheral_finder