feat(ble_audio): Support running ISO & LE Audio with Bluedroid Host

This commit is contained in:
Liu Linyan
2026-05-29 17:24:54 +08:00
parent 6cd0e56ae2
commit 8dbef3bad4
223 changed files with 14714 additions and 2347 deletions
@@ -9,7 +9,7 @@
This example acts as a **BAP Broadcast Source**. It creates a BAP broadcast source from the LC3 `16_2_1` broadcast preset, encodes the BASE into the periodic advertising payload, places the Broadcast Audio Announcement Service UUID and a fixed 24-bit Broadcast ID (`0x123456`) in the extended advertising payload, and then starts the BIG so that BIS streams transmit synthetic SDU data on a fixed cadence.
The build runs on top of the NimBLE host stack and the ESP BLE Audio component set. Source-side APIs used: `esp_ble_audio_common_init` / `esp_ble_audio_common_start` (common layer), `esp_ble_audio_bap_broadcast_source_create` / `_get_base` / `_start` (BAP), `esp_ble_audio_bap_broadcast_adv_add` (BAP/ISO glue), and the BAP stream callbacks (`started`, `stopped`, `sent`, `disconnected`). PACS, GAP scanner, and the scan delegator are **not** used on this side. Encryption is enabled because the broadcast code `"1234"` is non-empty; packing is `ESP_BLE_ISO_PACKING_SEQUENTIAL`. Channel allocation per stream is hard-coded as `FRONT_LEFT` for stream 0 and `FRONT_RIGHT` for stream 1.
The build runs on top of the selected BLE host stack (Bluedroid by default; NimBLE via the `sdkconfig.defaults.nimble` overlay) and the ESP BLE Audio component set. Source-side APIs used: `esp_ble_audio_common_init` / `esp_ble_audio_common_start` (common layer), `esp_ble_audio_bap_broadcast_source_create` / `_get_base` / `_start` (BAP), `esp_ble_audio_bap_broadcast_adv_add` (BAP/ISO glue), and the BAP stream callbacks (`started`, `stopped`, `sent`, `disconnected`). PACS, GAP scanner, and the scan delegator are **not** used on this side. Encryption is enabled because the broadcast code `"1234"` is non-empty; packing is `ESP_BLE_ISO_PACKING_SEQUENTIAL`. Channel allocation per stream is hard-coded as `FRONT_LEFT` for stream 0 and `FRONT_RIGHT` for stream 1.
The TX scheduler is built on `example_audio_tx_scheduler_*` helpers; the source comment notes that ESP timer resolution is not accurate enough for the SDU interval, so a `k_work_delayable`-based scheduler is used instead.
@@ -34,11 +34,24 @@ The example inherits a Just-Works pairing model (LE Secure Connections, no MITM,
## Build & Flash
The base `sdkconfig.defaults` defaults to the **Bluedroid** host; idf.py automatically merges the per-target overlay (`sdkconfig.defaults.$IDF_TARGET`). To build with **NimBLE** host instead, layer `sdkconfig.defaults.nimble` on top via `-DSDKCONFIG_DEFAULTS`.
### Bluedroid host (default)
```bash
idf.py set-target esp32h4 # or esp32s31
idf.py set-target esp32h4
idf.py -p PORT flash monitor
```
### NimBLE host
```bash
idf.py set-target esp32h4
idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.esp32h4;sdkconfig.defaults.nimble" -p PORT flash monitor
```
For `esp32s31`, replace the chip overlay accordingly.
(Exit serial monitor with `Ctrl-]`.)
## Example Flow
@@ -46,7 +59,7 @@ idf.py -p PORT flash monitor
1. `app_main` initializes NVS, calls `bluetooth_init()`, and calls `esp_ble_audio_common_init(NULL)` (no GAP callback for this role).
2. `broadcast_source_setup()` registers `source_started` / `source_stopped` callbacks, populates per-stream channel-allocation BIS metadata (left/right), registers `stream_ops` on each stream, and calls `esp_ble_audio_bap_broadcast_source_create()` with the encrypted preset.
3. `esp_ble_audio_common_start(NULL)` starts the audio stack; each stream's `example_audio_tx_scheduler_init()` is wired with `tx_scheduler_cb`.
4. `ext_adv_start()` configures non-connectable extended adv (1M primary / 2M secondary, 200 ms interval), writes Broadcast Audio Service Data + Broadcast ID + complete name, configures periodic adv (100 ms), writes the BASE returned by `esp_ble_audio_bap_broadcast_source_get_base()`, then starts periodic and extended advertising.
4. `adv_init()` performs host-specific GAP setup (Bluedroid: registers the GAP callback for `*_COMPLETE_EVT` semaphore signalling; NimBLE: no-op — adv-instance callback is passed at configure time). `ext_adv_start()` builds the extended (Broadcast Audio Service Data + Broadcast ID + complete name) and periodic (BASE from `esp_ble_audio_bap_broadcast_source_get_base()`) payloads in host-agnostic bytes, then calls `adv_start(ext_data, ext_len, per_data, per_len)`. The host-specific implementation in `main/bluedroid/adv.c` or `main/nimble/adv.c` configures non-connectable extended adv (1M primary / 2M secondary, 200 ms interval), configures periodic adv (100 ms), writes both payloads, and starts periodic and extended advertising.
5. `broadcast_start()` calls `esp_ble_audio_bap_broadcast_adv_add()` and `esp_ble_audio_bap_broadcast_source_start()` on the same `ADV_HANDLE`, which kicks off BIGInfo + BIS creation.
6. When each BIS stream goes streaming, `stream_started_cb` allocates an SDU-sized buffer and calls `example_audio_tx_scheduler_start()` at `preset_active.qos.interval`; the scheduler invokes `broadcast_source_tx()`, which fills the buffer with the low byte of `seq_num` and calls `esp_ble_audio_bap_stream_send()`.
7. `stream_sent_cb` forwards completions to `example_audio_tx_scheduler_on_sent()` for drift accounting; `stream_stopped_cb` and `stream_disconnected_cb` stop the scheduler; `source_stopped_cb` frees all per-stream buffers.
@@ -1,4 +1,11 @@
set(srcs "main.c")
idf_component_register(SRCS "${srcs}"
if(CONFIG_BT_BLUEDROID_ENABLED)
list(APPEND srcs "bluedroid/adv.c")
else()
list(APPEND srcs "nimble/adv.c")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "."
REQUIRES bt nvs_flash)
@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "ble_audio_example_utils.h"
#define TAG "BAP_BSRC"
#define ADV_HANDLE 0
#define ADV_SID 0
#define ADV_TX_POWER 127
#define ADV_INTERVAL_MS 200
#define PER_ADV_INTERVAL_MS 100
int app_host_init(void);
int ext_adv_start(const uint8_t *ext_data, uint8_t ext_len,
const uint8_t *per_data, uint8_t per_len);
@@ -0,0 +1,122 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "esp_log.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_bt_defs.h"
#include "esp_gap_ble_api.h"
#include "adv.h"
static SemaphoreHandle_t adv_sem;
/* Controller status latched by gap_event_handler for EXAMPLE_WAIT_API_CHECK. */
static esp_bt_status_t adv_op_status;
#define WAIT_API(_call) EXAMPLE_WAIT_API_CHECK(_call, adv_sem, portMAX_DELAY, adv_op_status)
static esp_ble_gap_ext_adv_params_t ext_adv_params = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED,
.interval_min = ESP_BLE_GAP_ADV_ITVL_MS(ADV_INTERVAL_MS),
.interval_max = ESP_BLE_GAP_ADV_ITVL_MS(ADV_INTERVAL_MS),
.channel_map = ADV_CHNL_ALL,
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_2M,
.sid = ADV_SID,
.scan_req_notif = false,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.tx_power = ADV_TX_POWER,
};
static esp_ble_gap_periodic_adv_params_t periodic_adv_params = {
.interval_min = ESP_BLE_GAP_PERIODIC_ADV_ITVL_MS(PER_ADV_INTERVAL_MS),
.interval_max = ESP_BLE_GAP_PERIODIC_ADV_ITVL_MS(PER_ADV_INTERVAL_MS),
.properties = 0,
};
static esp_ble_gap_ext_adv_t ext_adv_inst[1] = {
[0] = { ADV_HANDLE, 0, 0 },
};
static void gap_event_handler(esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
adv_op_status = param->ext_adv_set_params.status;
xSemaphoreGive(adv_sem);
break;
case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
adv_op_status = param->ext_adv_data_set.status;
xSemaphoreGive(adv_sem);
break;
case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
adv_op_status = param->ext_adv_start.status;
xSemaphoreGive(adv_sem);
break;
case ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT:
adv_op_status = param->peroid_adv_set_params.status;
xSemaphoreGive(adv_sem);
break;
case ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT:
adv_op_status = param->period_adv_data_set.status;
xSemaphoreGive(adv_sem);
break;
case ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT:
adv_op_status = param->period_adv_start.status;
xSemaphoreGive(adv_sem);
break;
default:
break;
}
}
int app_host_init(void)
{
esp_err_t err;
adv_sem = xSemaphoreCreateBinary();
if (adv_sem == NULL) {
ESP_LOGE(TAG, "Failed to create adv semaphore");
return -1;
}
err = esp_ble_gap_register_callback(gap_event_handler);
if (err) {
ESP_LOGE(TAG, "Failed to register GAP callback, err %d", err);
vSemaphoreDelete(adv_sem);
return err;
}
return 0;
}
int ext_adv_start(const uint8_t *ext_data, uint8_t ext_len,
const uint8_t *per_data, uint8_t per_len)
{
WAIT_API(esp_ble_gap_ext_adv_set_params(ADV_HANDLE, &ext_adv_params));
WAIT_API(esp_ble_gap_config_ext_adv_data_raw(ADV_HANDLE, ext_len, ext_data));
WAIT_API(esp_ble_gap_periodic_adv_set_params(ADV_HANDLE, &periodic_adv_params));
#if CONFIG_BT_BLE_FEAT_PERIODIC_ADV_ENH
WAIT_API(esp_ble_gap_config_periodic_adv_data_raw(ADV_HANDLE, per_len, per_data, false));
WAIT_API(esp_ble_gap_periodic_adv_start(ADV_HANDLE, true));
#else
WAIT_API(esp_ble_gap_config_periodic_adv_data_raw(ADV_HANDLE, per_len, per_data));
WAIT_API(esp_ble_gap_periodic_adv_start(ADV_HANDLE));
#endif
WAIT_API(esp_ble_gap_ext_adv_start(1, ext_adv_inst));
ESP_LOGI(TAG, "Advertising started (handle %u)", ADV_HANDLE);
return 0;
}
@@ -9,17 +9,10 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_system.h"
#include "esp_random.h"
#include "esp_timer.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
#include "esp_ble_audio_lc3_defs.h"
#include "esp_ble_audio_bap_api.h"
@@ -29,7 +22,7 @@
#include "ble_audio_example_init.h"
#include "ble_audio_example_utils.h"
#define TAG "BAP_BSRC"
#include "adv.h"
ESP_BLE_AUDIO_BAP_LC3_BROADCAST_PRESET_16_2_1_DEFINE(preset_active,
ESP_BLE_AUDIO_LOCATION_FRONT_LEFT |
@@ -42,16 +35,6 @@ ESP_BLE_AUDIO_BAP_LC3_BROADCAST_PRESET_16_2_1_DEFINE(preset_active,
#define LOCAL_BROADCAST_CODE "1234" /* Maximum length is 16 */
#define LOCAL_BROADCAST_ID 0x123456
#define ADV_HANDLE 0x00
#define ADV_SID 0
#define ADV_TX_POWER 127
#define ADV_ADDRESS BLE_OWN_ADDR_PUBLIC
#define ADV_PRIMARY_PHY BLE_HCI_LE_PHY_1M
#define ADV_SECONDARY_PHY BLE_HCI_LE_PHY_2M
#define ADV_INTERVAL BLE_GAP_ADV_ITVL_MS(200)
#define PER_ADV_INTERVAL BLE_GAP_ADV_ITVL_MS(100)
#define STREAM_COUNT CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT
#define SUBGROUP_COUNT CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT
@@ -369,138 +352,55 @@ static uint8_t *per_adv_data_get(uint8_t *data_len)
return data;
}
static int ext_adv_start(void)
static int broadcast_start(void)
{
struct ble_gap_periodic_adv_params per_params = {0};
struct ble_gap_ext_adv_params ext_params = {0};
struct os_mbuf *data = NULL;
esp_ble_audio_bap_broadcast_adv_info_t info = {
.adv_handle = ADV_HANDLE,
};
/* BASE (per_adv_data_get) needs broadcast_source to exist — built earlier
* by broadcast_source_setup(), so safe to call here. */
uint8_t *ext_data = NULL;
uint8_t *per_data = NULL;
uint8_t data_len = 0;
int err;
uint8_t ext_len = 0;
uint8_t per_len = 0;
int err = 0;
ext_params.connectable = 0;
ext_params.scannable = 0;
ext_params.legacy_pdu = 0;
ext_params.own_addr_type = ADV_ADDRESS;
ext_params.primary_phy = ADV_PRIMARY_PHY;
ext_params.secondary_phy = ADV_SECONDARY_PHY;
ext_params.tx_power = ADV_TX_POWER;
ext_params.sid = ADV_SID;
ext_params.itvl_min = ADV_INTERVAL;
ext_params.itvl_max = ADV_INTERVAL;
err = ble_gap_ext_adv_configure(ADV_HANDLE, &ext_params, NULL,
example_audio_gap_event_cb, NULL);
if (err) {
ESP_LOGE(TAG, "Failed to configure ext adv params, err %d", err);
goto end;
}
ext_data = ext_adv_data_get(&data_len);
ext_data = ext_adv_data_get(&ext_len);
if (ext_data == NULL) {
err = -ENOMEM;
goto end;
}
data = os_msys_get_pkthdr(data_len, 0);
if (data == NULL) {
ESP_LOGE(TAG, "Failed to get ext adv mbuf");
err = -ENOMEM;
goto end;
}
err = os_mbuf_append(data, ext_data, data_len);
if (err) {
ESP_LOGE(TAG, "Failed to append ext adv data, err %d", err);
os_mbuf_free_chain(data);
goto end;
}
err = ble_gap_ext_adv_set_data(ADV_HANDLE, data);
if (err) {
ESP_LOGE(TAG, "Failed to set ext adv data, err %d", err);
goto end;
}
per_params.include_tx_power = 0;
per_params.itvl_min = PER_ADV_INTERVAL;
per_params.itvl_max = PER_ADV_INTERVAL;
err = ble_gap_periodic_adv_configure(ADV_HANDLE, &per_params);
if (err) {
ESP_LOGE(TAG, "Failed to configure per adv params, err %d", err);
goto end;
}
per_data = per_adv_data_get(&data_len);
per_data = per_adv_data_get(&per_len);
if (per_data == NULL) {
err = -ENOMEM;
goto end;
}
data = os_msys_get_pkthdr(data_len, 0);
if (data == NULL) {
ESP_LOGE(TAG, "Failed to get per adv mbuf");
err = -ENOMEM;
goto end;
}
err = os_mbuf_append(data, per_data, data_len);
err = ext_adv_start(ext_data, ext_len, per_data, per_len);
if (err) {
ESP_LOGE(TAG, "Failed to append per adv data, err %d", err);
os_mbuf_free_chain(data);
goto end;
}
err = ble_gap_periodic_adv_set_data(ADV_HANDLE, data);
if (err) {
ESP_LOGE(TAG, "Failed to set per adv data, err %d", err);
goto end;
}
err = ble_gap_periodic_adv_start(ADV_HANDLE);
if (err) {
ESP_LOGE(TAG, "Failed to start per advertising, err %d", err);
goto end;
}
err = ble_gap_ext_adv_start(ADV_HANDLE, 0, 0);
if (err) {
ESP_LOGE(TAG, "Failed to start ext advertising, err %d", err);
goto end;
}
ESP_LOGI(TAG, "Advertising started (handle %u)", ADV_HANDLE);
end:
if (ext_data) {
free(ext_data);
}
if (per_data) {
free(per_data);
}
return err;
}
static void broadcast_start(void)
{
esp_ble_audio_bap_broadcast_adv_info_t info = {
.adv_handle = ADV_HANDLE,
};
int err;
err = esp_ble_audio_bap_broadcast_adv_add(&info);
if (err) {
ESP_LOGE(TAG, "Failed to add adv for broadcast source, err %d", err);
return;
goto end;
}
err = esp_ble_audio_bap_broadcast_source_start(broadcast_source, ADV_HANDLE);
if (err) {
ESP_LOGE(TAG, "Failed to start broadcast source, err %d", err);
return;
}
end:
if (ext_data != NULL) {
free(ext_data);
}
if (per_data != NULL) {
free(per_data);
}
return err;
}
void app_main(void)
@@ -521,6 +421,12 @@ void app_main(void)
return;
}
err = app_host_init();
if (err) {
ESP_LOGE(TAG, "Failed to init host, err %d", err);
return;
}
err = esp_ble_audio_common_init(NULL);
if (err) {
ESP_LOGE(TAG, "Failed to initialize audio, err %d", err);
@@ -548,10 +454,9 @@ void app_main(void)
}
}
err = ext_adv_start();
err = broadcast_start();
if (err) {
ESP_LOGE(TAG, "Failed to start broadcast, err %d", err);
return;
}
broadcast_start();
}
@@ -0,0 +1,115 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "esp_log.h"
#include "host/ble_gap.h"
#include "adv.h"
/* Non-connectable broadcaster: no GAP events for the application. */
static int gap_event_cb(struct ble_gap_event *event, void *arg)
{
return 0;
}
int app_host_init(void)
{
return 0;
}
int ext_adv_start(const uint8_t *ext_data, uint8_t ext_len,
const uint8_t *per_data, uint8_t per_len)
{
struct ble_gap_periodic_adv_params per_params = {0};
struct ble_gap_ext_adv_params ext_params = {0};
struct os_mbuf *data;
int err;
ext_params.connectable = 0;
ext_params.scannable = 0;
ext_params.legacy_pdu = 0;
ext_params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
ext_params.primary_phy = BLE_HCI_LE_PHY_1M;
ext_params.secondary_phy = BLE_HCI_LE_PHY_2M;
ext_params.tx_power = ADV_TX_POWER;
ext_params.sid = ADV_SID;
ext_params.itvl_min = BLE_GAP_ADV_ITVL_MS(ADV_INTERVAL_MS);
ext_params.itvl_max = BLE_GAP_ADV_ITVL_MS(ADV_INTERVAL_MS);
err = ble_gap_ext_adv_configure(ADV_HANDLE, &ext_params, NULL,
gap_event_cb, NULL);
if (err) {
ESP_LOGE(TAG, "Failed to configure ext adv params, err %d", err);
return err;
}
data = os_msys_get_pkthdr(ext_len, 0);
if (data == NULL) {
ESP_LOGE(TAG, "Failed to get ext adv mbuf");
return -1;
}
err = os_mbuf_append(data, ext_data, ext_len);
if (err) {
ESP_LOGE(TAG, "Failed to append ext adv data, err %d", err);
os_mbuf_free_chain(data);
return err;
}
err = ble_gap_ext_adv_set_data(ADV_HANDLE, data);
if (err) {
ESP_LOGE(TAG, "Failed to set ext adv data, err %d", err);
return err;
}
per_params.include_tx_power = 0;
per_params.itvl_min = BLE_GAP_PERIODIC_ITVL_MS(PER_ADV_INTERVAL_MS);
per_params.itvl_max = BLE_GAP_PERIODIC_ITVL_MS(PER_ADV_INTERVAL_MS);
err = ble_gap_periodic_adv_configure(ADV_HANDLE, &per_params);
if (err) {
ESP_LOGE(TAG, "Failed to configure per adv params, err %d", err);
return err;
}
data = os_msys_get_pkthdr(per_len, 0);
if (data == NULL) {
ESP_LOGE(TAG, "Failed to get per adv mbuf");
return -1;
}
err = os_mbuf_append(data, per_data, per_len);
if (err) {
ESP_LOGE(TAG, "Failed to append per adv data, err %d", err);
os_mbuf_free_chain(data);
return err;
}
err = ble_gap_periodic_adv_set_data(ADV_HANDLE, data);
if (err) {
ESP_LOGE(TAG, "Failed to set per adv data, err %d", err);
return err;
}
err = ble_gap_periodic_adv_start(ADV_HANDLE);
if (err) {
ESP_LOGE(TAG, "Failed to start per advertising, err %d", err);
return err;
}
err = ble_gap_ext_adv_start(ADV_HANDLE, 0, 0);
if (err) {
ESP_LOGE(TAG, "Failed to start ext advertising, err %d", err);
return err;
}
ESP_LOGI(TAG, "Advertising started (handle %u)", ADV_HANDLE);
return 0;
}
@@ -3,14 +3,19 @@
#
CONFIG_BT_ENABLED=y
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_ISO=y
CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y
CONFIG_BT_NIMBLE_ENABLED=n
CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_CLASSIC_ENABLED=n
CONFIG_BT_CONTROLLER_ENABLED=y
CONFIG_BT_BLE_ENABLED=y
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y
CONFIG_BT_ACL_CONNECTIONS=1
CONFIG_BT_BLE_FEAT_ISO_EN=y
CONFIG_BT_ISO_MAX_CHAN=2
CONFIG_BT_BAP_BROADCAST_SOURCE=y
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
CONFIG_FREERTOS_HZ=1000
@@ -0,0 +1,9 @@
# NimBLE host overlay for this example.
# Use with:
# idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.$IDF_TARGET;sdkconfig.defaults.nimble" build
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_ISO=y
CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y