bt_hub

Example of the component espressif/esp_bt_hub v0.1.0
# Shared Bluetooth Hub Console

- [中文版](./README_CN.md)
- Regular Example: ⭐⭐

## Example Brief

This example demonstrates how multiple application modules share one Bluetooth controller and host stack through `esp_bt_hub`. It can run on any ESP-IDF target with a supported Bluetooth controller and host backend. The commands registered by the interactive serial console follow the capabilities enabled for the selected chip: Bluetooth Classic, BLE, or dual mode.

The console creates independent clients, combines their Classic scan-mode requests, and arbitrates exclusive Classic inquiry, LE extended scan, and LE extended advertising resources when those features are available. It also registers two subscribers for each enabled GAP domain. One subscriber parses discovery reports, while the other observes and counts native GAP events, demonstrating callback fan-out without replacing the backend callback.

### Typical Scenarios

- Integrating several Bluetooth application modules that must share one host stack.
- Verifying ownership and release order before adding Bluetooth operations to an application.
- Exercising Classic-only, BLE-only, or dual-mode configurations on different Bluetooth-capable chips.
- Inspecting Classic discovery and LE extended-advertising reports from the console.

### Run Flow

1. The application initializes NVS and `esp_bt_hub`, then opens the `bt_hub>` console.
2. Each application module opens a named client.
3. A client requests a shared scan mode or acquires an exclusive operation lease.
4. GAP events are delivered to both registered subscribers.
5. The lease owner stops and releases the operation before closing its client.
6. After all clients are closed, the hub can be stopped and restarted.

## Environment Setup

### Hardware Required

- Any ESP development board whose chip and ESP-IDF version support Bluetooth
- A USB cable for flashing and serial-console interaction
- For Classic inquiry results, a nearby discoverable Bluetooth Classic device
- For LE scan results, a nearby device that sends extended advertising packets

### Default IDF Branch

The `esp_bt_hub` component requires ESP-IDF v5.5 or later. Use an ESP-IDF version that supports the selected chip and Bluetooth host backend.

This example does not use `esp_board_manager`.

## Build and Flash

### Build Preparation

Set up the ESP-IDF environment from the ESP-IDF root directory:

```bash
./install.sh
. ./export.sh
```

Then enter the example directory from the esp-adf repository root:

```bash
cd components/esp_bt_hub/examples/bt_hub
```

### Select a Target and Build

Set `TARGET` to a Bluetooth-capable chip supported by the active ESP-IDF version, for example `esp32`, `esp32s3`, or another supported target:

```bash
idf.py set-target TARGET
idf.py build
idf.py -p PORT flash monitor
```

For an ESP-IDF version where ESP32-S31 target selection is still a preview feature, use:

```bash
idf.py --preview set-target esp32s31
```

The target defaults select the Bluetooth capabilities used by each chip: ESP32 uses Bluedroid dual mode, ESP32-S3 uses Bluedroid BLE, and ESP32-S31 uses Bluedroid dual mode with BLE 5.0 extended scan and advertising.

Replace `PORT` with the board's serial port. Use `Ctrl-]` to exit the monitor.

### ESP32-S31 NimBLE LE-Only Build

The example includes a ready-to-use NimBLE defaults file for ESP32-S31. Use a separate build directory and sdkconfig file so the Bluedroid and NimBLE configurations do not overwrite each other:

```bash
idf.py --preview -B build-nimble \
  -D SDKCONFIG=sdkconfig.nimble \
  -D SDKCONFIG_DEFAULTS=sdkconfig.defaults.esp32s31.nimble \
  set-target esp32s31
idf.py -B build-nimble build
idf.py -B build-nimble -p PORT flash monitor
```

This configuration enables the NimBLE central, peripheral, broadcaster, and observer roles together with extended scan and advertising. Bluetooth Classic and all `classic_*` console commands are disabled. Other NimBLE-capable targets can run the example after selecting NimBLE and the required roles in their target configuration.

### Project Configuration

The example has no project-specific menuconfig options. Available console functions are selected at compile time:

- Classic commands require `ESP_BT_HUB_CLASSIC_SUPPORTED`.
- The LE commands demonstrated by this example require `ESP_BT_HUB_LE_EXT_SUPPORTED`, because they use extended scan and advertising.
- Hub lifecycle and client commands are available with either Bluedroid or NimBLE.

Run `idf.py menuconfig` when changing the host backend or enabling Bluetooth features beyond the supplied defaults.

## How to Use the Example

### Start with the Console

After startup, the `bt_hub>` prompt is displayed. Run:

```text
help
hub_status
client_open audio
client_open pan
clients
```

The clients receive IDs `0` and `1`. Commands that acquire a resource use this ID to record and verify ownership.

### Verify Classic Resource Sharing

This section applies when `help` lists the `classic_*` commands.

This workflow gives each client a Classic scan-mode request and lets client `0` own the inquiry:

```text
classic_mode 0 1 general
classic_mode 1 1 limited
classic_inquiry_start 0
classic_inquiry_start 1
client_close 0
classic_inquiry_stop 0
```

The second inquiry start fails because inquiry is exclusive. Closing client `0` also fails while its inquiry lease is active. After `classic_inquiry_stop 0`, the client can be closed.

### Verify LE Scan and Advertising

This section applies when the target and configuration support extended LE scan and advertising and `help` lists the `le_*` commands.

Run an extended scan with client `0`:

```text
le_scan_start 0
hub_status
le_scan_stop 0
```

Run one extended-advertising instance with client `1`:

```text
le_adv_start 1
hub_status
le_adv_stop 1
```

For received advertising packets, the parser prints both the backend event and a normalized `esp_bt_hub_le_adv_report_t` result. After an inquiry or scan receives events, `hub_status` shows non-zero parser and observer callback counts.

### Stop and Restart the Hub

Release all leases and close all clients before stopping the hub:

```text
client_close 0
client_close 1
hub_stop
hub_status
hub_start
```

### Command Reference

Common commands:

- `hub_status`: show hub state, active resources, owners, and GAP callback counts.
- `hub_start` / `hub_stop`: start or stop an idle hub.
- `client_open <name>` / `client_close <id>`: open or close a hub client.
- `clients`: list open clients.

Classic commands, registered only when Classic is supported and enabled:

- `classic_mode <id> <0|1> <none|limited|general>`
- `classic_inquiry_start <id>` / `classic_inquiry_stop <id>`

Extended LE commands, registered only when extended LE features are supported and enabled:

- `le_scan_start <id>` / `le_scan_stop <id>`
- `le_adv_start <id>` / `le_adv_stop <id>`

### Log Output

The following is a continuous excerpt from an ESP32-S31 Bluedroid run:

```text
2026-10-26 10:24:09 bt_hub> client_open audio
2026-10-26 10:24:09 Client opened: id=0 name=audio
2026-10-26 10:24:09 bt_hub> client_open pan
2026-10-26 10:24:10 Client opened: id=1 name=pan
2026-10-26 10:24:10 bt_hub> classic_mode 0 1 general
2026-10-26 10:24:10 Classic mode: success
2026-10-26 10:24:10 bt_hub> classic_mode 1 1 limited
2026-10-26 10:24:10 Classic mode: success
2026-10-26 10:24:10 bt_hub> classic_inquiry_start 0
2026-10-26 10:24:10 I (1972) BT_HUB_EXAMPLE: Classic inquiry state: 1
2026-10-26 10:24:10 Classic inquiry start: success
2026-10-26 10:24:10 bt_hub> classic_inquiry_start 1
2026-10-26 10:24:10 Classic inquiry start failed: ESP_ERR_INVALID_STATE
2026-10-26 10:24:10 Command returned non-zero error code: 0x1 (ERROR)
2026-10-26 10:24:10 bt_hub> client_close 0
2026-10-26 10:24:10 E (2072) ESP_BT_HUB: Close client failed: client still owns a lease
2026-10-26 10:24:10 Client close failed: ESP_ERR_INVALID_STATE
2026-10-26 10:24:10 Command returned non-zero error code: 0x1 (ERROR)
```

The failed commands confirm that only one client can own inquiry and that an owner cannot be closed before releasing its lease.

## Troubleshooting

### A command returns `ESP_ERR_INVALID_STATE`

Run `hub_status` and `clients` to find the active resource and its owner. Stop the resource with the same client ID, then close the client. All clients must be closed before `hub_stop`.

### Classic or LE commands are missing

Run `help` to see commands available in the current build. BLE-only targets do not register `classic_*` commands. A target with BLE but without extended scan and advertising enabled does not register this example's `le_scan_*` or `le_adv_*` commands.

### No discovery results are printed

A successful start only means the scan or inquiry is running. Place a discoverable Classic device or an LE advertiser nearby and confirm that it is actively discoverable or advertising.

### The application does not fit in the partition

Keep `CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y` from the supplied defaults. The dual-mode Bluedroid image can exceed the default 1 MB factory partition.

### Flashing or the console fails

Check the serial port, USB cable, and port permissions. Close other programs using the port. This example enables the UART console by default.

## Technical Support

For technical support, use the following links:

- Technical support: [esp32.com](https://esp32.com/viewforum.php?f=20)
- Issue reports and feature requests: [esp-adf issues](https://github.com/espressif/esp-adf/issues)

To create a project from this example, run:

idf.py create-project-from-example "espressif/esp_bt_hub=0.1.0:bt_hub"

or download archive (~18.99 KB)