Merge branch 'idf/ble_audio_past_support' into 'master'

feat(ble_audio): Support using PAST in the cap/acceptor example

See merge request espressif/esp-idf!48449
This commit is contained in:
Island
2026-05-15 10:28:08 +08:00
50 changed files with 1210 additions and 562 deletions

View File

@@ -25,13 +25,13 @@ esp_err_t esp_ble_audio_bap_unicast_server_register(const esp_ble_audio_bap_unic
return ESP_FAIL;
}
#if BLE_AUDIO_SVC_SEP_ADD
#if BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_ascs_init();
if (err) {
bt_bap_unicast_server_unregister_safe();
return ESP_FAIL;
}
#endif /* BLE_AUDIO_SVC_SEP_ADD */
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
}
@@ -723,13 +723,13 @@ esp_err_t esp_ble_audio_bap_scan_delegator_register(esp_ble_audio_bap_scan_deleg
return ESP_FAIL;
}
#if BLE_AUDIO_SVC_SEP_ADD
#if BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_bass_init();
if (err) {
bt_bap_scan_delegator_unregister_safe();
return ESP_FAIL;
}
#endif /* BLE_AUDIO_SVC_SEP_ADD */
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
}

View File

@@ -9,6 +9,7 @@
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include "esp_ble_audio_common_api.h"
@@ -53,7 +54,11 @@ esp_err_t esp_ble_audio_gattc_disc_start(uint16_t conn_handle)
int err;
err = bt_gattc_disc_start_safe(conn_handle);
if (err) {
/* -EALREADY means disc already in progress for this conn (e.g. peer sent
* MTU exchange twice triggering two gatt_mtu_change events). The
* caller's goal is already true, treat as success.
*/
if (err && err != -EALREADY) {
return ESP_FAIL;
}

View File

@@ -133,12 +133,15 @@ esp_err_t esp_ble_audio_has_register(const esp_ble_audio_has_features_param_t *f
return ESP_FAIL;
}
#if BLE_AUDIO_SVC_SEP_ADD
#if BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_has_init();
if (err) {
/* TODO: rollback register_safe once lib exposes an unregister API;
* retry will hit -EALREADY. Only reachable on GATT alloc failure.
*/
return ESP_FAIL;
}
#endif /* BLE_AUDIO_SVC_SEP_ADD */
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
}

View File

@@ -566,12 +566,15 @@ esp_err_t esp_ble_audio_media_proxy_pl_init(void)
return ESP_FAIL;
}
#if CONFIG_BT_MCS && BLE_AUDIO_SVC_SEP_ADD
#if CONFIG_BT_MCS && BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_media_proxy_pl_init();
if (err) {
/* TODO: rollback pl_init_safe once lib exposes an undo API;
* retry will hit -EALREADY. Only reachable on GATT alloc failure.
*/
return ESP_FAIL;
}
#endif /* CONFIG_BT_MCS && BLE_AUDIO_SVC_SEP_ADD */
#endif /* CONFIG_BT_MCS && BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
}

View File

@@ -22,12 +22,15 @@ esp_err_t esp_ble_audio_micp_mic_dev_register(esp_ble_audio_micp_mic_dev_registe
return ESP_FAIL;
}
#if BLE_AUDIO_SVC_SEP_ADD
#if BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_micp_mic_dev_init();
if (err) {
/* TODO: rollback register_safe once lib exposes an unregister API;
* retry will hit -EALREADY. Only reachable on GATT alloc failure.
*/
return ESP_FAIL;
}
#endif /* BLE_AUDIO_SVC_SEP_ADD */
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
}

View File

@@ -318,12 +318,12 @@ esp_err_t esp_ble_audio_tbs_register_bearer(const esp_ble_audio_tbs_register_par
*bearer_index = ret;
#if BLE_AUDIO_SVC_SEP_ADD
#if BLE_AUDIO_SVC_DEFERRED_ADD
if (bt_le_gtbs_init()) {
bt_tbs_unregister_bearer_safe(*bearer_index);
return ESP_FAIL;
}
#endif /* BLE_AUDIO_SVC_SEP_ADD */
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
}

View File

@@ -18,12 +18,15 @@ esp_err_t esp_ble_audio_tmap_register(esp_ble_audio_tmap_role_t role)
return ESP_FAIL;
}
#if BLE_AUDIO_SVC_SEP_ADD
#if BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_tmas_init();
if (err) {
/* TODO: rollback register_safe once lib exposes an unregister API;
* retry will hit -EALREADY. Only reachable on GATT alloc failure.
*/
return ESP_FAIL;
}
#endif /* BLE_AUDIO_SVC_SEP_ADD */
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
}

View File

@@ -49,12 +49,15 @@ esp_err_t esp_ble_audio_vcp_vol_rend_register(esp_ble_audio_vcp_vol_rend_registe
return ESP_FAIL;
}
#if BLE_AUDIO_SVC_SEP_ADD
#if BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_vcp_vol_rend_init();
if (err) {
/* TODO: rollback register_safe once lib exposes an unregister API;
* retry will hit -EALREADY. Only reachable on GATT alloc failure.
*/
return ESP_FAIL;
}
#endif /* BLE_AUDIO_SVC_SEP_ADD */
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
}

View File

@@ -92,6 +92,8 @@ esp_err_t esp_ble_audio_gattc_disc_start(uint16_t conn_handle);
#define ESP_BLE_AUDIO_GAP_EVENT_EXT_SCAN_RECV BT_LE_GAP_APP_EVENT_EXT_SCAN_RECV
/*!< Audio GAP Periodic Sync Established event */
#define ESP_BLE_AUDIO_GAP_EVENT_PA_SYNC BT_LE_GAP_APP_EVENT_PA_SYNC
/*!< Audio GAP Periodic Sync Transfer Received event */
#define ESP_BLE_AUDIO_GAP_EVENT_PA_SYNC_PAST BT_LE_GAP_APP_EVENT_PA_SYNC_PAST
/*!< Audio GAP Periodic Sync Lost event */
#define ESP_BLE_AUDIO_GAP_EVENT_PA_SYNC_LOST BT_LE_GAP_APP_EVENT_PA_SYNC_LOST
/*!< Audio GAP Connection Complete event */

View File

@@ -168,7 +168,7 @@ int bt_le_nimble_audio_init(void)
}
#endif /* CONFIG_BT_PACS */
#if (BLE_AUDIO_SVC_SEP_ADD == 0)
#if (BLE_AUDIO_SVC_DEFERRED_ADD == 0)
#if CONFIG_BT_ASCS
err = bt_le_nimble_ascs_init();
if (err) {
@@ -203,7 +203,7 @@ int bt_le_nimble_audio_init(void)
return err;
}
#endif /* CONFIG_BT_HAS */
#endif /* (BLE_AUDIO_SVC_SEP_ADD == 0) */
#endif /* (BLE_AUDIO_SVC_DEFERRED_ADD == 0) */
return err;
}
@@ -419,7 +419,7 @@ int bt_le_nimble_audio_start(void *info)
ARG_UNUSED(inc_csis_svc);
#endif /* CONFIG_BT_CAP_ACCEPTOR */
#if (BLE_AUDIO_SVC_SEP_ADD == 0)
#if (BLE_AUDIO_SVC_DEFERRED_ADD == 0)
#if CONFIG_BT_MCS
err = bt_le_nimble_media_proxy_pl_init();
if (err) {
@@ -440,7 +440,7 @@ int bt_le_nimble_audio_start(void *info)
return err;
}
#endif /* CONFIG_BT_MICP_MIC_DEV */
#endif /* (BLE_AUDIO_SVC_SEP_ADD == 0) */
#endif /* (BLE_AUDIO_SVC_DEFERRED_ADD == 0) */
err = ble_gatts_start();
if (err) {

View File

@@ -108,8 +108,18 @@ int bt_le_nimble_ascs_attr_handle_set(void)
{
struct bt_gatt_service *ascs_svc;
struct bt_gatt_attr *attr;
uint16_t start_handle;
uint16_t end_handle;
uint16_t start_handle = 0;
uint16_t end_handle = 0;
int rc;
/* App may not register this svc (e.g. CAP Acceptor single mode keeps
* unused capability built). Skip rather than fail audio_start.
*/
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_ASCS_VAL), NULL);
if (rc) {
LOG_DBG("[N]AscsNotInit");
return 0;
}
ascs_svc = lib_ascs_svc_get();
assert(ascs_svc);

View File

@@ -84,8 +84,18 @@ int bt_le_nimble_bass_attr_handle_set(void)
{
struct bt_gatt_service *bass_svc;
struct bt_gatt_attr *attr;
uint16_t start_handle;
uint16_t end_handle;
uint16_t start_handle = 0;
uint16_t end_handle = 0;
int rc;
/* App may not register this svc (e.g. CAP Acceptor single mode keeps
* unused capability built). Skip rather than fail audio_start.
*/
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_BASS_VAL), NULL);
if (rc) {
LOG_DBG("[N]BassNotInit");
return 0;
}
bass_svc = lib_bap_bass_svc_get();
assert(bass_svc);

View File

@@ -50,21 +50,19 @@ static struct ble_gatt_svc_def gatt_svc_cas[] = {
int bt_le_nimble_cas_attr_handle_set(void)
{
struct bt_gatt_service *cas_svc;
uint16_t handle;
uint16_t handle = 0;
int rc;
cas_svc = lib_cas_svc_get();
assert(cas_svc);
LOG_DBG("[N]CasAttrHdlSet[%u]", cas_svc->attr_count);
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_CAS_VAL), &handle);
if (rc) {
LOG_ERR("[N]CasNotFound[%d]", rc);
return rc;
}
LOG_DBG("[N]Hdl[%u]", handle);
cas_svc = lib_cas_svc_get();
assert(cas_svc);
LOG_DBG("[N]CasAttrHdlSet[%u][%u]", handle, cas_svc->attr_count);
for (size_t i = 0; i < cas_svc->attr_count; i++) {
(cas_svc->attrs + i)->handle = handle + i;

View File

@@ -139,8 +139,8 @@ static int csis_svc_check(void)
int bt_le_nimble_csis_attr_handle_set(void)
{
struct bt_gatt_attr *attr;
uint16_t start_handle;
uint16_t end_handle;
uint16_t start_handle = 0;
uint16_t end_handle = 0;
LOG_DBG("[N]CsisAttrHdlSet[%u]", csis_svc_count);

View File

@@ -110,21 +110,23 @@ int bt_le_nimble_has_attr_handle_set(void)
uint16_t end_handle = 0;
int rc;
has_svc = lib_has_svc_get();
assert(has_svc);
LOG_DBG("[N]HasAttrHdlSet[%u]", has_svc->attr_count);
assert(has_svc->attr_count > 0);
/* App may not register this svc (e.g. CAP Acceptor single mode keeps
* unused capability built). Skip rather than fail audio_start.
*/
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_HAS_VAL), &start_handle);
if (rc) {
LOG_ERR("[N]HasNotFound[%d]", rc);
return rc;
LOG_DBG("[N]HasNotInit");
return 0;
}
has_svc = lib_has_svc_get();
assert(has_svc);
assert(has_svc->attr_count > 0);
end_handle = start_handle + has_svc->attr_count - 1;
LOG_DBG("[N]Hdl[%u][%u]", start_handle, end_handle);
LOG_DBG("[N]HasAttrHdlSet[%u][%u][%u]",
start_handle, end_handle, has_svc->attr_count);
for (size_t i = 0; i < has_svc->attr_count; i++) {
(has_svc->attrs + i)->handle = start_handle + i;

View File

@@ -393,20 +393,22 @@ int bt_le_nimble_gmcs_attr_handle_set(void)
uint16_t end_handle = 0;
int rc;
/* App may not register this svc (e.g. CAP Acceptor single mode keeps
* unused capability built). Skip rather than fail audio_start.
*/
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_GMCS_VAL), &start_handle);
if (rc) {
LOG_DBG("[N]GmcsNotInit");
return 0;
}
gmcs_svc = lib_mcs_svc_get();
assert(gmcs_svc);
LOG_DBG("[N]GmcsAttrHdlSet[%u]", gmcs_svc->attr_count);
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_GMCS_VAL), &start_handle);
if (rc) {
LOG_ERR("[N]GmcsNotFound[%d]", rc);
return rc;
}
end_handle = start_handle + gmcs_svc->attr_count - 1;
LOG_DBG("[N]Hdl[%u][%u]", start_handle, end_handle);
LOG_DBG("[N]GmcsAttrHdlSet[%u][%u][%u]",
start_handle, end_handle, gmcs_svc->attr_count);
for (size_t i = 0; i < gmcs_svc->attr_count; i++) {
(gmcs_svc->attrs + i)->handle = start_handle + i;

View File

@@ -234,20 +234,22 @@ int bt_le_nimble_mics_attr_handle_set(void)
uint16_t end_handle = 0;
int rc;
/* App may not register this svc (e.g. CAP Acceptor single mode keeps
* unused capability built). Skip rather than fail audio_start.
*/
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MICS_VAL), &start_handle);
if (rc) {
LOG_DBG("[N]MicsNotInit");
return 0;
}
mics_svc = lib_mics_svc_get();
assert(mics_svc);
LOG_DBG("[N]MicsAttrHdlSet[%u]", mics_svc->attr_count);
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MICS_VAL), &start_handle);
if (rc) {
LOG_ERR("[N]MicsNotFound[%d]", rc);
return rc;
}
end_handle = start_handle + mics_svc->attr_count - 1;
LOG_DBG("[N]Hdl[%u][%u]", start_handle, end_handle);
LOG_DBG("[N]MicsAttrHdlSet[%u][%u][%u]",
start_handle, end_handle, mics_svc->attr_count);
for (size_t i = 0; i < mics_svc->attr_count; i++) {
(mics_svc->attrs + i)->handle = start_handle + i;

View File

@@ -162,8 +162,8 @@ int bt_le_nimble_pacs_attr_handle_set(void)
{
struct bt_gatt_service *pacs_svc;
struct bt_gatt_attr *attr;
uint16_t start_handle;
uint16_t end_handle;
uint16_t start_handle = 0;
uint16_t end_handle = 0;
pacs_svc = lib_pacs_svc_get();
assert(pacs_svc);

View File

@@ -74,7 +74,7 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
case BLE_GATT_ACCESS_OP_READ_CHR:
attr = bt_gatts_find_attr_by_handle(attr_handle);
if (attr == NULL) {
LOG_WRN("[N]RdInvHdl[%u]", attr_handle);
LOG_WRN("[N]RdAttrNotFound[%u]", attr_handle);
return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE);
}
@@ -88,7 +88,7 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
rc = attr->read(conn, attr, (void *)&cb, UINT16_MAX, 0);
if (rc < 0) {
LOG_ERR("[N]RdFail[%u][%d]", attr_handle, rc);
LOG_DBG("[N]RdGattErr[%u][%d]", attr_handle, rc);
return BT_GATT_ERR(rc);
}
@@ -97,7 +97,7 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
case BLE_GATT_ACCESS_OP_WRITE_CHR:
attr = bt_gatts_find_attr_by_handle(attr_handle);
if (attr == NULL) {
LOG_WRN("[N]WrInvHdl[%u]", attr_handle);
LOG_WRN("[N]WrAttrNotFound[%u]", attr_handle);
return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE);
}
@@ -118,7 +118,7 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
LOG_DBG("[N]WrBassControlPoint[%u]", alloc_len);
if (OS_MBUF_PKTLEN(ctx->om) > alloc_len) {
LOG_ERR("[N]WrBassCtrlPtTooLong[%u > %u]",
LOG_WRN("[N]WrBassCtrlPtTooLong[%u > %u]",
OS_MBUF_PKTLEN(ctx->om), alloc_len);
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
@@ -148,7 +148,7 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
data = NULL;
}
if (rc < 0) {
LOG_ERR("[N]WrFail[%u][%d]", attr_handle, rc);
LOG_DBG("[N]WrGattErr[%u][%d]", attr_handle, rc);
return BT_GATT_ERR(rc);
}

View File

@@ -165,21 +165,22 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
int bt_le_nimble_gtbs_attr_handle_set(void)
{
struct bt_gatt_service *gtbs_svc;
uint16_t handle;
uint16_t handle = 0;
int rc;
/* App may not register this svc (e.g. CAP Acceptor single mode keeps
* unused capability built). Skip rather than fail audio_start.
*/
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_GTBS_VAL), &handle);
if (rc) {
LOG_DBG("[N]GtbsNotInit");
return 0;
}
gtbs_svc = lib_gtbs_svc_get();
assert(gtbs_svc);
LOG_DBG("[N]GtbsAttrHdlSet[%u]", gtbs_svc->attr_count);
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_GTBS_VAL), &handle);
if (rc) {
LOG_ERR("[N]GtbsNotFound[%d]", rc);
return rc;
}
LOG_DBG("[N]Hdl[%u]", handle);
LOG_DBG("[N]GtbsAttrHdlSet[%u][%u]", handle, gtbs_svc->attr_count);
for (size_t i = 0; i < gtbs_svc->attr_count; i++) {
(gtbs_svc->attrs + i)->handle = handle + i;

View File

@@ -58,21 +58,22 @@ static const struct ble_gatt_svc_def gatt_svc_tmas[] = {
int bt_le_nimble_tmas_attr_handle_set(void)
{
struct bt_gatt_service *tmas_svc;
uint16_t handle;
uint16_t handle = 0;
int rc;
/* App may not register this svc (e.g. CAP Acceptor single mode keeps
* unused capability built). Skip rather than fail audio_start.
*/
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_TMAS_VAL), &handle);
if (rc) {
LOG_DBG("[N]TmasNotInit");
return 0;
}
tmas_svc = lib_tmas_svc_get();
assert(tmas_svc);
LOG_DBG("[N]TmasAttrHdlSet[%u]", tmas_svc->attr_count);
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_TMAS_VAL), &handle);
if (rc) {
LOG_ERR("[N]TmasNotFound[%d]", rc);
return rc;
}
LOG_DBG("[N]Hdl[%u]", handle);
LOG_DBG("[N]TmasAttrHdlSet[%u][%u]", handle, tmas_svc->attr_count);
for (size_t i = 0; i < tmas_svc->attr_count; i++) {
(tmas_svc->attrs + i)->handle = handle + i;

View File

@@ -370,20 +370,22 @@ int bt_le_nimble_vcs_attr_handle_set(void)
uint16_t end_handle = 0;
int rc;
/* App may not register this svc (e.g. CAP Acceptor single mode keeps
* unused capability built). Skip rather than fail audio_start.
*/
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_VCS_VAL), &start_handle);
if (rc) {
LOG_DBG("[N]VcsNotInit");
return 0;
}
vcs_svc = lib_vcs_svc_get();
assert(vcs_svc);
LOG_DBG("[N]VcsAttrHdlSet[%u]", vcs_svc->attr_count);
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_VCS_VAL), &start_handle);
if (rc) {
LOG_ERR("[N]VcsNotFound[%d]", rc);
return rc;
}
end_handle = start_handle + vcs_svc->attr_count - 1;
LOG_DBG("[N]Hdl[%u][%u]", start_handle, end_handle);
LOG_DBG("[N]VcsAttrHdlSet[%u][%u][%u]",
start_handle, end_handle, vcs_svc->attr_count);
for (size_t i = 0; i < vcs_svc->attr_count; i++) {
(vcs_svc->attrs + i)->handle = start_handle + i;

View File

@@ -18,13 +18,18 @@
extern "C" {
#endif
#define BLE_AUDIO_SVC_SEP_ADD 0 /* Indicate if adding LE Audio services separately */
/* Defer LE Audio service add from host init to the application
* register call (esp_ble_audio_*_register). Lets the firmware keep
* unused capabilities built in without exposing their attributes
* before the app provides backing state.
*/
#define BLE_AUDIO_SVC_DEFERRED_ADD 1
/* Minimum value required by BAP.
* 64 octets is not enough for running 4 ASEs.
* Hence use 128 as the default value here.
*/
#define BLE_AUDIO_ATT_MTU_MIN 128
#define BLE_AUDIO_ATT_MTU_MIN 128
struct bt_csip_set_member_svc_inst;

View File

@@ -178,12 +178,13 @@ static const uint16_t ext_structs[] = {
sizeof(struct bt_bond_info),
};
#define LEA_VERSION (0x20260512)
#define LEA_VERSION (0x20260514)
struct lib_ext_cfgs {
/* BLE */
bool config_past_sender;
bool config_past_receiver;
bool config_past_check;
uint8_t config_max_conn;
uint8_t config_max_paired;
uint16_t config_max_attr_len;
@@ -439,6 +440,7 @@ static const struct lib_ext_cfgs ext_cfgs = {
/* BLE */
.config_past_sender = CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER,
.config_past_receiver = CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER,
.config_past_check = false,
.config_max_conn = CONFIG_BT_MAX_CONN,
.config_max_paired = CONFIG_BT_MAX_PAIRED,
.config_max_attr_len = 251,
@@ -2092,7 +2094,7 @@ int bt_le_audio_init(void)
return bt_le_nimble_audio_init();
}
#if BLE_AUDIO_SVC_SEP_ADD
#if BLE_AUDIO_SVC_DEFERRED_ADD
#if CONFIG_BT_ASCS
int bt_le_ascs_init(void)
{
@@ -2164,7 +2166,7 @@ int bt_le_micp_mic_dev_init(void)
return bt_le_nimble_micp_mic_dev_init();
}
#endif /* CONFIG_BT_MICP_MIC_DEV */
#endif /* BLE_AUDIO_SVC_SEP_ADD */
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
int bt_le_audio_start(void *info)
{

View File

@@ -552,6 +552,8 @@ esp_err_t esp_ble_iso_chan_send_ts(esp_ble_iso_chan_t *chan,
#define ESP_BLE_ISO_GAP_EVENT_EXT_SCAN_RECV BT_LE_GAP_APP_EVENT_EXT_SCAN_RECV
/*!< ISO GAP Periodic Sync Established event */
#define ESP_BLE_ISO_GAP_EVENT_PA_SYNC BT_LE_GAP_APP_EVENT_PA_SYNC
/*!< ISO GAP Periodic Sync Transfer Received event */
#define ESP_BLE_ISO_GAP_EVENT_PA_SYNC_PAST BT_LE_GAP_APP_EVENT_PA_SYNC_PAST
/*!< ISO GAP Periodic Sync Lost event */
#define ESP_BLE_ISO_GAP_EVENT_PA_SYNC_LOST BT_LE_GAP_APP_EVENT_PA_SYNC_LOST
/*!< ISO GAP Connection Complete event */

View File

@@ -135,6 +135,24 @@ void bt_le_nimble_gap_post_event(void *param)
qev->pa_sync.adv_ca = ev->periodic_sync.adv_clk_accuracy;
break;
case BLE_GAP_EVENT_PERIODIC_TRANSFER:
case BLE_GAP_EVENT_PERIODIC_TRANSFER_V2:
/* Both events share the same periodic_transfer union field; V2 only
* adds PAwR-specific tail fields that this handler does not consume.
*/
qev->type = BT_LE_GAP_APP_PARAM_PA_SYNC_PAST;
qev->pa_sync_past.status = ev->periodic_transfer.status;
qev->pa_sync_past.sync_handle = ev->periodic_transfer.sync_handle;
qev->pa_sync_past.addr.type = ev->periodic_transfer.adv_addr.type;
memcpy(qev->pa_sync_past.addr.val, ev->periodic_transfer.adv_addr.val, BT_ADDR_SIZE);
qev->pa_sync_past.sid = ev->periodic_transfer.sid;
qev->pa_sync_past.adv_phy = ev->periodic_transfer.adv_phy;
qev->pa_sync_past.per_adv_itvl = ev->periodic_transfer.per_adv_itvl;
qev->pa_sync_past.adv_ca = ev->periodic_transfer.adv_clk_accuracy;
qev->pa_sync_past.conn_handle = ev->periodic_transfer.conn_handle;
break;
case BLE_GAP_EVENT_PERIODIC_SYNC_LOST:
qev->type = BT_LE_GAP_APP_PARAM_PA_SYNC_LOST;

View File

@@ -1411,7 +1411,10 @@ int bt_le_nimble_gattc_db_auto_disc(uint16_t conn_handle)
adb = gattc_db_find(conn_handle);
if (adb) {
LOG_WRN("[N]GattcDbExist");
/* Idempotent: caller's goal (disc running for this conn) is
* already true. -EALREADY tells the API layer to map to success.
*/
LOG_DBG("[N]GattcDbExist");
return -EALREADY;
}

View File

@@ -168,6 +168,7 @@ static void handle_pa_sync_event_safe(struct bt_le_gap_app_param *param)
event.pa_sync.per_adv_itvl,
event.pa_sync.addr.type,
event.pa_sync.addr.val,
BT_CONN_HANDLE_INVALID,
&per_adv_sync);
if (err) {
goto end;
@@ -182,6 +183,51 @@ end:
bt_le_gap_app_cb_evt(&event);
}
static void handle_pa_sync_past_event_safe(struct bt_le_gap_app_param *param)
{
struct bt_le_gap_app_event event = {
.type = BT_LE_GAP_APP_EVENT_PA_SYNC_PAST,
};
struct bt_le_per_adv_sync *per_adv_sync;
int err;
event.pa_sync_past.status = param->pa_sync_past.status;
event.pa_sync_past.sync_handle = param->pa_sync_past.sync_handle;
event.pa_sync_past.addr.type = param->pa_sync_past.addr.type;
memcpy(event.pa_sync_past.addr.val, param->pa_sync_past.addr.val, BT_ADDR_SIZE);
event.pa_sync_past.sid = param->pa_sync_past.sid;
event.pa_sync_past.adv_phy = param->pa_sync_past.adv_phy;
event.pa_sync_past.per_adv_itvl = param->pa_sync_past.per_adv_itvl;
event.pa_sync_past.adv_ca = param->pa_sync_past.adv_ca;
event.pa_sync_past.conn_handle = param->pa_sync_past.conn_handle;
bt_le_host_lock();
if (event.pa_sync_past.status) {
goto end;
}
err = bt_le_per_adv_sync_new(event.pa_sync_past.sync_handle,
event.pa_sync_past.sid,
event.pa_sync_past.adv_phy,
event.pa_sync_past.per_adv_itvl,
event.pa_sync_past.addr.type,
event.pa_sync_past.addr.val,
event.pa_sync_past.conn_handle,
&per_adv_sync);
if (err) {
goto end;
}
err = bt_le_per_adv_sync_establish_listener(event.pa_sync_past.sync_handle);
assert(err == 0);
end:
bt_le_host_unlock();
bt_le_gap_app_cb_evt(&event);
}
static void handle_pa_sync_lost_event_safe(struct bt_le_gap_app_param *param)
{
struct bt_le_gap_app_event event = {
@@ -437,6 +483,9 @@ void bt_le_gap_handle_event(uint8_t *data, size_t data_len)
case BT_LE_GAP_APP_PARAM_PA_SYNC:
handle_pa_sync_event_safe(param);
break;
case BT_LE_GAP_APP_PARAM_PA_SYNC_PAST:
handle_pa_sync_past_event_safe(param);
break;
case BT_LE_GAP_APP_PARAM_PA_SYNC_LOST:
handle_pa_sync_lost_event_safe(param);
break;

View File

@@ -128,10 +128,9 @@ struct bt_gatt_attr *bt_gatts_find_attr_by_handle(uint16_t handle)
break;
}
}
}
if (attr == NULL) {
LOG_WRN("AttrNotFound[%u]", handle);
if (attr != NULL) {
break;
}
}
return attr;

View File

@@ -72,10 +72,15 @@ int bt_le_host_init(void)
k_mutex_create(&host_mutex);
err = bt_le_scan_init();
if (err) {
goto delete_mutex;
}
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
err = bt_le_l2cap_init();
if (err) {
goto delete_mutex;
goto deinit_scan;
}
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
@@ -96,8 +101,10 @@ deinit_iso:
deinit_l2cap:
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
bt_le_l2cap_deinit();
delete_mutex:
deinit_scan: /* only reachable when OTS path is compiled in */
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
bt_le_scan_deinit();
delete_mutex:
k_mutex_delete(&host_mutex);
return err;
@@ -107,11 +114,12 @@ void bt_le_host_deinit(void)
{
LOG_DBG("HostDeinit");
bt_le_iso_task_deinit();
bt_le_iso_deinit();
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
bt_le_l2cap_deinit();
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
bt_le_iso_deinit();
bt_le_iso_task_deinit();
bt_le_scan_deinit();
k_mutex_delete(&host_mutex);
}

View File

@@ -45,6 +45,18 @@ struct bt_le_gap_app_pa_sync_param {
uint8_t adv_ca;
};
struct bt_le_gap_app_pa_sync_past_param {
uint8_t status;
uint16_t sync_handle;
struct bt_le_addr addr;
uint8_t sid;
uint8_t adv_phy;
uint16_t per_adv_itvl;
uint8_t adv_ca;
/* ACL conn that delivered the PAST. */
uint16_t conn_handle;
};
struct bt_le_gap_app_pa_sync_lost_param {
uint16_t sync_handle;
uint8_t reason;
@@ -113,6 +125,7 @@ struct bt_le_gap_app_param {
union {
struct bt_le_gap_app_ext_scan_recv_param ext_scan_recv;
struct bt_le_gap_app_pa_sync_param pa_sync;
struct bt_le_gap_app_pa_sync_past_param pa_sync_past;
struct bt_le_gap_app_pa_sync_lost_param pa_sync_lost;
struct bt_le_gap_app_pa_sync_recv_param pa_sync_recv;
struct bt_le_gap_app_acl_connect_param acl_connect;
@@ -126,6 +139,7 @@ struct bt_le_gap_app_param {
enum bt_le_gap_app_param_type {
BT_LE_GAP_APP_PARAM_EXT_SCAN_RECV,
BT_LE_GAP_APP_PARAM_PA_SYNC,
BT_LE_GAP_APP_PARAM_PA_SYNC_PAST,
BT_LE_GAP_APP_PARAM_PA_SYNC_LOST,
BT_LE_GAP_APP_PARAM_PA_SYNC_RECV,
BT_LE_GAP_APP_PARAM_ACL_CONNECT,
@@ -160,6 +174,18 @@ struct bt_le_gap_app_event_pa_sync {
uint8_t adv_ca;
};
struct bt_le_gap_app_event_pa_sync_past {
uint8_t status;
uint16_t sync_handle;
struct bt_le_addr addr;
uint8_t sid;
uint8_t adv_phy;
uint16_t per_adv_itvl;
uint8_t adv_ca;
/* ACL conn that delivered the PAST. */
uint16_t conn_handle;
};
struct bt_le_gap_app_event_pa_sync_lost {
uint16_t sync_handle;
uint8_t reason;
@@ -228,6 +254,7 @@ struct bt_le_gap_app_event {
union {
struct bt_le_gap_app_event_ext_scan_recv ext_scan_recv;
struct bt_le_gap_app_event_pa_sync pa_sync;
struct bt_le_gap_app_event_pa_sync_past pa_sync_past;
struct bt_le_gap_app_event_pa_sync_lost pa_sync_lost;
struct bt_le_gap_app_event_pa_sync_recv pa_sync_recv;
struct bt_le_gap_app_event_acl_connect acl_connect;
@@ -242,6 +269,7 @@ struct bt_le_gap_app_event {
enum bt_le_gap_app_event_type {
BT_LE_GAP_APP_EVENT_EXT_SCAN_RECV,
BT_LE_GAP_APP_EVENT_PA_SYNC,
BT_LE_GAP_APP_EVENT_PA_SYNC_PAST,
BT_LE_GAP_APP_EVENT_PA_SYNC_LOST,
BT_LE_GAP_APP_EVENT_PA_SYNC_RECV,
BT_LE_GAP_APP_EVENT_ACL_CONNECT,

View File

@@ -39,6 +39,7 @@ int bt_le_per_adv_sync_new(uint16_t sync_handle,
uint16_t interval,
uint8_t addr_type,
const uint8_t addr[6],
uint16_t conn_handle,
struct bt_le_per_adv_sync **out_sync);
int bt_le_per_adv_sync_delete(uint16_t sync_handle);
@@ -51,6 +52,10 @@ int bt_le_per_adv_sync_report_recv_listener(uint16_t sync_handle,
const uint8_t *data,
size_t data_length);
int bt_le_scan_init(void);
void bt_le_scan_deinit(void);
#ifdef __cplusplus
}
#endif

View File

@@ -36,9 +36,6 @@ LOG_MODULE_REGISTER(ISO_SHIM, CONFIG_BT_ISO_LOG_LEVEL);
#define ISO_PKT_COMP_SDU (0b10)
#define ISO_PKT_LAST_FRAG (0b11)
#define BT_LE_FEAT_SET(feat, n) (feat[(n) >> 3] |= BIT((n) & 7))
#define BT_LE_FEAT_UNSET(feat, n) (feat[(n) >> 3] &= ~BIT((n) & 7))
static sys_slist_t iso_cbs = SYS_SLIST_STATIC_INIT(&iso_cbs);
#if CONFIG_BT_ISO_UNICAST

View File

@@ -14,8 +14,10 @@
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/bluetooth/hci_types.h>
#include <zephyr/bluetooth/common/bt_str.h>
#include <../host/conn_internal.h>
#include <../host/hci_core.h>
#include "common/host.h"
@@ -220,8 +222,7 @@ static struct bt_le_per_adv_sync *per_adv_sync_find(uint16_t handle)
struct bt_le_per_adv_sync *per_adv_sync = NULL;
for (size_t i = 0; i < ARRAY_SIZE(per_adv_sync_pool); i++) {
if (atomic_test_bit(per_adv_sync_pool[i].flags,
BT_PER_ADV_SYNC_SYNCED) &&
if (atomic_test_bit(per_adv_sync_pool[i].flags, BT_PER_ADV_SYNC_SYNCED) &&
per_adv_sync_pool[i].handle == handle) {
per_adv_sync = &per_adv_sync_pool[i];
break;
@@ -269,11 +270,13 @@ int bt_le_per_adv_sync_new(uint16_t sync_handle,
uint16_t interval,
uint8_t addr_type,
const uint8_t addr[6],
uint16_t conn_handle,
struct bt_le_per_adv_sync **out_sync)
{
struct bt_le_per_adv_sync *per_adv_sync;
LOG_DBG("PaSyncNew[%u][%u][%u][%u]", sync_handle, sid, phy, interval);
LOG_DBG("PaSyncNew[%u][%u][%u][%u][%u]",
sync_handle, sid, phy, interval, conn_handle);
if (addr_type > BT_ADDR_LE_RANDOM_ID || addr == NULL) {
LOG_ERR("InvAddr[%02x][%p]", addr_type, addr);
@@ -296,6 +299,7 @@ int bt_le_per_adv_sync_new(uint16_t sync_handle,
per_adv_sync->sid = sid;
per_adv_sync->phy = phy;
per_adv_sync->interval = interval;
per_adv_sync->conn_handle = conn_handle;
per_adv_sync->addr.type = addr_type;
memcpy(per_adv_sync->addr.a.val, addr, BT_ADDR_SIZE);
@@ -345,7 +349,16 @@ int bt_le_per_adv_sync_establish_listener(uint16_t sync_handle)
info.sid = per_adv_sync->sid;
info.interval = per_adv_sync->interval;
info.phy = per_adv_sync->phy;
info.conn = NULL; /* TODO: update for PAST */
/* PAST path: a valid conn_handle means PAST delivered the sync. NULL
* lookup here is a race — the ACL disconnected between PAST event
* enqueue and host processing. Synced cb still fires with conn=NULL.
*/
if (per_adv_sync->conn_handle != BT_CONN_HANDLE_INVALID) {
info.conn = bt_conn_lookup_handle(per_adv_sync->conn_handle, BT_CONN_TYPE_LE);
if (info.conn == NULL) {
LOG_WRN("PaSyncConnGone[%u][%u]", sync_handle, per_adv_sync->conn_handle);
}
}
SYS_SLIST_FOR_EACH_CONTAINER(&pa_sync_cbs, listener, node) {
if (listener->synced) {
@@ -353,6 +366,10 @@ int bt_le_per_adv_sync_establish_listener(uint16_t sync_handle)
}
}
if (info.conn) {
bt_conn_unref(info.conn);
}
return 0;
}
@@ -492,3 +509,47 @@ int bt_le_scan_stop(void)
return err;
}
static void past_features_set(void)
{
#if CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER
BT_LE_FEAT_SET(bt_dev.le.features, BT_LE_FEAT_BIT_PAST_SEND);
#endif /* CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER */
#if CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER
BT_LE_FEAT_SET(bt_dev.le.features, BT_LE_FEAT_BIT_PAST_RECV);
#endif /* CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER */
LOG_DBG("PastFeatSet[%s]", bt_hex(bt_dev.le.features, sizeof(bt_dev.le.features)));
}
static void past_features_unset(void)
{
#if CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER
BT_LE_FEAT_UNSET(bt_dev.le.features, BT_LE_FEAT_BIT_PAST_SEND);
#endif /* CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER */
#if CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER
BT_LE_FEAT_UNSET(bt_dev.le.features, BT_LE_FEAT_BIT_PAST_RECV);
#endif /* CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER */
LOG_DBG("PastFeatUnset[%s]", bt_hex(bt_dev.le.features, sizeof(bt_dev.le.features)));
}
_IDF_ONLY
int bt_le_scan_init(void)
{
LOG_DBG("ScanInit");
past_features_set();
return 0;
}
_IDF_ONLY
void bt_le_scan_deinit(void)
{
LOG_DBG("ScanDeinit");
past_features_unset();
}

View File

@@ -118,7 +118,12 @@ void hci_iso(struct net_buf *buf)
iso = bt_conn_lookup_handle(iso(buf)->handle, BT_CONN_TYPE_ISO);
if (iso == NULL) {
LOG_ERR("ConnNotFoundForHdl[%u]", iso(buf)->handle);
/* Expected during BIG/CIG teardown: the bt_conn is unref'd
* synchronously when the host initiates termination, but ISO packets
* already queued for the iso task may still arrive here. Drop them
* silently.
*/
LOG_INF("IsoRxConnGone[%u]", iso(buf)->handle);
return;
}

View File

@@ -78,10 +78,18 @@ struct bt_le_per_adv_sync {
/** Advertiser PHY */
uint8_t phy;
/** ACL conn that delivered PAST; BT_CONN_HANDLE_INVALID for non-PAST sync. */
uint16_t conn_handle;
/** Flags */
ATOMIC_DEFINE(flags, BT_PER_ADV_SYNC_NUM_FLAGS);
};
/* Port-level invalid conn_handle sentinel; matches NimBLE BLE_HS_CONN_HANDLE_NONE.
* Use in adapter-agnostic code that cannot reference NimBLE headers.
*/
#define BT_CONN_HANDLE_INVALID 0xffff
struct bt_dev_le {
/* LE features */
uint8_t features[8];
@@ -97,6 +105,9 @@ struct bt_dev {
extern struct bt_dev bt_dev;
#define BT_LE_FEAT_SET(feat, n) (feat[(n) >> 3] |= BIT((n) & 7))
#define BT_LE_FEAT_UNSET(feat, n) (feat[(n) >> 3] &= ~BIT((n) & 7))
/* Data type to store state related with command to be updated
* when command completes successfully.
*/

View File

@@ -1245,7 +1245,8 @@ static void security_change(esp_ble_iso_gap_app_event_t *event)
int err;
if (event->security_change.status) {
ESP_LOGE(TAG, "Security change failed, status %d", event->security_change.status);
example_audio_security_failed_recover(TAG, event->security_change.conn_handle,
event->security_change.status);
return;
}

View File

@@ -7,9 +7,9 @@
## Overview
This example implements the **Common Audio Profile (CAP) Acceptor** role on top of the NimBLE host stack with ISO and LE Audio support. It is built in one of two mutually exclusive sub-modes selected at build time: a **CAP Acceptor / BAP Unicast Server**, or a **CAP Acceptor / BAP Broadcast Sink** (with the BAP Scan Delegator role enabled). PACS is registered in both modes with LC3 sink and source capabilities (any sampling frequency, 7.5 ms or 10 ms frame, up to 2 channels, 30..155 octets per frame, up to 2 frames per SDU). Sink and source PAC location are set to `FRONT_LEFT | FRONT_RIGHT` (note in `main.c`: with `MONO_AUDIO`, Samsung S24 declines unicast).
This example implements the **Common Audio Profile (CAP) Acceptor** role on top of the NimBLE host stack with ISO and LE Audio support. It can be built with one or both roles selected at build time: a **CAP Acceptor / BAP Unicast Server**, and / or a **CAP Acceptor / BAP Broadcast Sink** (with the BAP Scan Delegator role enabled). Dual-role builds are supported (BAP spec C.2). PACS is registered in all configurations with LC3 sink and source capabilities (any sampling frequency, 7.5 ms or 10 ms frame, up to 2 channels, 30..155 octets per frame, up to 2 frames per SDU). Sink and source PAC location are set to `FRONT_LEFT | FRONT_RIGHT` (note in `main.c`: with `MONO_AUDIO`, Samsung S24 declines unicast).
In **unicast** mode (`cap_acceptor_unicast.c`) the acceptor registers BAP unicast-server callbacks (config / reconfig / qos / enable / start / metadata / disable / stop / release) and CAP stream ops, and on enable of a sink ASE automatically issues `bap_stream_start` (Receiver Start Ready). When the source ASE starts, an internal TX scheduler begins sending dummy SDUs. In **broadcast** mode (`cap_acceptor_broadcast.c`) the acceptor registers BAP scan-delegator and broadcast-sink callbacks, drives PA sync (without PAST) on request, receives BASE and BIGInfo, then calls `esp_ble_audio_bap_broadcast_sink_sync` on the first BIS index. Optional **self-scan** lets the acceptor scan for a broadcast source named `CAP Broadcast Source` and use a hardcoded broadcast code `1234` instead of waiting for a Broadcast Assistant.
In **unicast** mode (`cap_acceptor_unicast.c`) the acceptor registers BAP unicast-server callbacks (config / reconfig / qos / enable / start / metadata / disable / stop / release) and CAP stream ops, and on enable of a sink ASE automatically issues `bap_stream_start` (Receiver Start Ready). When the source ASE starts, an internal TX scheduler begins sending dummy SDUs. In **broadcast** mode (`cap_acceptor_broadcast.c`) the acceptor registers BAP scan-delegator and broadcast-sink callbacks, drives PA sync (preferring PAST when the Assistant supports it) on request, receives BASE / BIGInfo / broadcast code, then calls `esp_ble_audio_bap_broadcast_sink_sync` on the BIS bitmap selected by the Assistant via BASS Modify Source — up to `CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT` BIS streams. The Broadcast Sink object is created on PA sync and deleted on PA loss; Assistant pause / resume keep the same sink. See **Broadcast Mode Internals** below for sequence, gate flags and sink lifecycle. Optional **self-scan** lets the acceptor scan for a broadcast source named `CAP Broadcast Source` and use a hardcoded broadcast code `1234` instead of waiting for a Broadcast Assistant.
The acceptor advertises connectable extended advertising on handle 0 with flags, the ASCS+CAS UUID list, CAS service data with targeted-announcement byte, ASCS targeted-announcement and contexts (unicast build), BASS service data (broadcast build), and the complete device name `cap_acceptor`. The GAP/GATT device name is set to `CAP Acceptor`.
@@ -26,12 +26,12 @@ Open menuconfig:
idf.py menuconfig
```
Under **Example: CAP Acceptor** -> **CAP Acceptor mode**:
Under **Example: CAP Acceptor**:
* **Unicast** (`EXAMPLE_UNICAST`, default) — act as BAP Unicast Server; advertise CAS + ASCS for a CAP Initiator. Mutually exclusive with Broadcast.
* **Broadcast** (`EXAMPLE_BROADCAST`) — act as BAP Broadcast Sink with Scan Delegator; advertise CAS + BASS. Mutually exclusive with Unicast.
* **Unicast** (`EXAMPLE_UNICAST`, default) — act as BAP Unicast Server; advertise CAS + ASCS for a CAP Initiator.
* **Broadcast** (`EXAMPLE_BROADCAST`) — act as BAP Broadcast Sink with Scan Delegator; advertise CAS + BASS.
When **Broadcast** is selected, an additional option appears:
The two options can be enabled together for a dual-role acceptor (BAP spec C.2). When **Broadcast** is selected and **Unicast** is disabled (dual-role + self-scan coexistence is not yet supported), an additional option appears:
* **Scan for Broadcast Sources without Broadcast Assistant** (`EXAMPLE_SCAN_SELF`) — start scanning at boot for a source advertising the name `CAP Broadcast Source`; on match, create the PA sync directly. The hardcoded broadcast code `1234` is used if the BIG is encrypted. Without this option, a separate Broadcast Assistant must drive PA / BIS sync over BASS.
@@ -57,9 +57,135 @@ idf.py -p PORT flash monitor
5. If `EXAMPLE_SCAN_SELF` is set, `check_start_scan` starts extended scanning for the broadcast source.
6. `ext_adv_start` configures and starts connectable extended advertising on handle 0 with the CAS / ASCS / BASS service data appropriate to the build.
7. **Unicast**: on ACL connect the connection handle is stored. On MTU change the acceptor starts service discovery (acting as GATT client). The unicast server then handles config -> reconfig -> qos -> enable -> start (auto Receiver Start Ready for sink ASEs) -> metadata -> disable -> stop -> release per ASE; when the source ASE starts, the TX scheduler begins sending dummy SDUs.
8. **Broadcast**: on `pa_sync_req` from a Broadcast Assistant (or on a scan match in self-scan mode) the acceptor creates a PA sync without PAST. On PA sync, it creates a broadcast sink for the broadcast ID, receives BASE and BIGInfo, then syncs to the first BIS. Stream `started` enters the synced state; `stopped` and `pa_sync_lost` clear flags and (in self-scan mode) restart scanning.
8. **Broadcast**: on `pa_sync_req` the acceptor establishes PA sync — preferring HCI Periodic Advertising Sync Transfer (PAST) when the Assistant supports it, otherwise scanning for the source by address / SID. On PA sync the sink is created (inlined in `broadcast_pa_synced`) and lives until both PA and BIS are gone. BASE, BIGInfo and (if the BIG is encrypted) the broadcast code accumulate as gate flags. When the Assistant's BASS Modify Source pushes a non-zero `bis_sync` bitmap and all gates are open, `bap_broadcast_sink_sync` is called with a `streams[]` of size = popcount of the bitmap (≤ `CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT`). Assistant pause (`bis_sync = 0`) issues `_stop` but **does not delete** the sink; resume re-`_sync`s the same object so the cached BIGInfo / BASE / QoS stay valid. PA loss does **not** tear down the BIG — per BASS spec the two are independent. See **Broadcast Mode Internals** below for the full sequence and state machine.
9. On ACL disconnect the connection handle is reset and `ext_adv_start` re-arms advertising.
## Broadcast Mode Internals
The broadcast acceptor coordinates three roles — Scan Delegator (BASS server), Broadcast Sink (BAP), and PA / BIG sync at the LE controller — under a small atomic-flag state machine. `check_sync_broadcast()` is the single fan-in that calls `bap_broadcast_sink_sync` once all gates are open; each callback that sets a gate invokes it.
### End-to-end sequence (Assistant-driven)
```
Source (SRC) Assistant (ASS) Acceptor (ACC)
| | |
| |---- ACL connect+pair --->|
| |---- BASS Add Source ---->| pa_sync_req_cb
| | (id, addr, sid) |
| | |
| |
| ----- PAST supported (peer + local) ---- |
| |<--- PA state=INFO_REQ ---| set after subscribe
| |- HCI PA Sync Transfer -->|
| |
| ----------- else (no PAST) ------------- |
|<------- periodic_adv_sync_create (scan) -------|
| |
|============== PA sync established ============>| broadcast_pa_synced
| | -> create_broadcast_sink
|============== PA report (BASE) ===============>| base_recv_cb
| | -> FLAG_BASE_RECEIVED
|============== PA report (BIGInfo) ============>| syncable_cb
| | -> FLAG_BROADCAST_SYNCABLE
| |
| ------------ BIG encrypted ------------- |
| |---- BASS Set BCode ----->| broadcast_code_cb
| | | -> FLAG_..._CODE_RECEIVED
| |
| |---- BASS Modify Source ->| bis_sync_req_cb
| | (bis_sync bitmap) | -> FLAG_..._SYNC_REQUESTED
| | check_sync_broadcast -> _sync
|<========= HCI LE BIG Create Sync ==============|
| |
|========== BIG Sync Established ===============>| stream_started_cb
| | -> FLAG_BROADCAST_SYNCED
|============== ISO data (BIS) =================>|
```
### Sync-gate flags
`check_sync_broadcast()` short-circuits on the first unset gate and logs which one it waited on. All gates use atomic bit ops so callbacks may run on any thread without ordering tricks.
| Flag | Set by | Cleared by |
| --------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `FLAG_PA_SYNCED` | `broadcast_pa_synced` | `broadcast_pa_lost` |
| `FLAG_BROADCAST_SYNCABLE` | `syncable_cb` (BIGInfo) | `broadcast_pa_lost` (BIS idle); `broadcast_sink_reset` |
| `FLAG_BASE_RECEIVED` | `base_recv_cb` (first BASE) | `broadcast_pa_lost` (BIS idle); `broadcast_sink_reset` |
| `FLAG_BROADCAST_CODE_REQUIRED` | `syncable_cb` (`biginfo->encryption=1`) | `syncable_cb` (encryption=0); `broadcast_pa_lost` (BIS idle); `broadcast_sink_reset` |
| `FLAG_BROADCAST_CODE_RECEIVED` | `broadcast_code_cb` (BASS Set Broadcast Code); self-scan local code | `broadcast_sink_reset` |
| `FLAG_BROADCAST_SYNC_REQUESTED` | `bis_sync_req_cb` (bitmap ≠ 0); self-scan PA match | `bis_sync_req_cb` (bitmap = 0); `broadcast_sink_reset` |
| `FLAG_BROADCAST_RESYNC_PENDING` | `bis_sync_req_cb` before `_stop` (bitmap change while streaming) | `stream_stopped_cb` after driving the re-sync; `_stop` failure; `broadcast_sink_reset` |
| `FLAG_BROADCAST_SYNCING` | `check_sync_broadcast` after `_sync` returns OK | `stream_started_cb`; `stream_stopped_cb` |
| `FLAG_BROADCAST_SYNCED` | `stream_started_cb` | `stream_stopped_cb` |
`check_sync_broadcast()` runs `_sync` only when:
```
BASE_RECEIVED && BROADCAST_SYNCABLE
&& (!CODE_REQUIRED || CODE_RECEIVED)
&& BROADCAST_SYNC_REQUESTED
&& PA_SYNCED
&& !(SYNCED || SYNCING)
```
### Sink object lifecycle
```
+---------+
[start] -->| Created |
+----+----+
|
| check_sync_broadcast -> _sync
v
+---------+ Modify Source: new +---------+
| Syncing |<------------------------| Stopped |
+----+----+ bis_sync +----^----+
| |
| stream_started_cb | Assistant bis_sync = 0
v | (source pause) OR
+-----------+ | bitmap change -> _stop
| Streaming |---------------------------+
+-----+-----+ |
| | BIG drops while PA gone
| v
+---------> stream_stopped_cb + !PA_SYNCED
|
| _delete + broadcast_sink_reset
v
[end]
```
Key invariants:
- **PA loss does NOT tear down a running BIS.** Per BASS § 3.2.1.6 / § 3.2.1.9, `PA_Sync_State` and `BIS_Sync_State` are independent. While BIS is streaming/syncing, `broadcast_pa_lost` only notifies the assistant (`PA_Sync_State = 0x00`) and clears PA-only local state (`sync_handle`, `FLAG_PA_SYNCED`); the BIG keeps running and audio continues to flow.
- **PA loss with BIS idle tears down the sink.** The sink is bound to the now-dead sync handle and its cached BASE / BIGInfo are stale. `broadcast_pa_lost` calls `_delete` and clears `FLAG_BASE_RECEIVED` / `FLAG_BROADCAST_SYNCABLE` / `FLAG_BROADCAST_CODE_REQUIRED`. The assistant's subscription (`requested_bis_sync`, `FLAG_BROADCAST_SYNC_REQUESTED`, `FLAG_BROADCAST_CODE_RECEIVED`) is preserved so the next PA sync re-creates a fresh sink and resumes streaming.
- **Sink deletion happens in `stream_stopped_cb` when both PA and BIS are gone.** Triggers: assistant unsubscribes via `Modify Source bis_sync = 0`, or the broadcaster stops the BIG while PA is already gone.
- `bis_sync_req_cb` going `X → 0` (Assistant pause) only issues `_stop`, never `_delete`. Going `X → Y` (BIS bitmap switch) likewise only `_stop`s; the next `check_sync_broadcast` (called from `stream_stopped_cb` when PA still synced) re-`_sync`s the same object.
- `pa_sync_term_req_cb` issues the HCI Periodic Advertising Terminate Sync but does **not** clear `broadcast_sink.sync_handle`. Cleanup runs from `BLE_GAP_EVENT_PERIODIC_SYNC_LOST``broadcast_pa_lost`. Resetting the handle early would make that gate miss.
### Event handling
| Event | Action |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| **PA lost while BIS active** (broadcaster moved mid-stream) | Set `PA_Sync_State = 0x00`; clear PA-only state (`sync_handle`, `FLAG_PA_SYNCED`). Sink + BIS untouched; BIG keeps running. |
| **PA lost while BIS idle** (Assistant `PA_Sync = 0`, or PA dropped after Assistant paused BIS) | Set `PA_Sync_State = 0x00` (skipped if BASS already updated it in-place); clear PA-only state; `_delete` the sink and clear `FLAG_BASE_RECEIVED` / `FLAG_BROADCAST_SYNCABLE` / `FLAG_BROADCAST_CODE_REQUIRED`. The next PA sync starts from a clean sink and lets the lib redeliver BASE / BIGInfo. |
| **Modify Source `bis_sync = 0`** (PA still synced) | `bis_sync_req_cb` clears `FLAG_BROADCAST_SYNC_REQUESTED` then `_stop`s the BIG. Sink retained. |
| **Modify Source bitmap change** (PA still synced, streaming) | Update `requested_bis_sync` + `FLAG_BROADCAST_SYNC_REQUESTED` + set `FLAG_BROADCAST_RESYNC_PENDING`, then `_stop`. `stream_stopped_cb` clears the flag and re-`_sync`s with the new bitmap. |
| **BIG drops while PA still synced** (e.g. broadcaster pause) | `stream_stopped_cb` clears SYNCED/SYNCING and exposes the loss via `BIS_Sync_State`. `FLAG_BROADCAST_RESYNC_PENDING` is not set, so no auto-retry — per BASS § 3.2.1.9 the assistant drives recovery via Modify Source. |
| **BIG drops after PA lost** (broadcaster turned off) | `stream_stopped_cb` of the last active stream sees `!PA_SYNCED``_delete` + `broadcast_sink_reset`. Multi-BIS: earlier callbacks just decrement `active_streams` so `_delete` is not called while the sink is still in use. |
| **Assistant Remove Source** | Spec allows only when BIS not synced; lib handles, app sees no special event. |
Two recurring patterns that drive the above behavior:
- **Update local state before calling `_stop`/`_delete`.** The lib may fire `stream_stopped_cb` synchronously from within `_stop`, so the callback must see the post-stop state. Applies in `bis_sync_req_cb` (updates `requested_bis_sync` + flag before `_stop`) and `broadcast_pa_lost` (no longer calls `_stop`).
- **Sink lifetime is BIS-driven, not PA-driven.** Sink is created on first PA sync and deleted only when the BIG itself stops and PA is also gone. This matches BASS spec's independent PA/BIS state model.
### Multi-BIS (stereo) configuration
`CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT` selects how many BIS streams the acceptor can sync to simultaneously. The `broadcast_sink.cap_streams[N]` array is sized to this value; `bis_sync_req_cb` rejects bitmaps whose popcount exceeds it (`-ENOMEM` back to BASS), and `check_sync_broadcast` builds the `streams[]` argument from the requested bitmap. `broadcast_sink.active_streams` tracks how many of those BIS are currently streaming so that `_delete` / re-sync only run after the *last* stream's `stopped_cb` — calling them while another BIS is still active hits the lib's `BapBsnkNotIdle` path.
For Auracast stereo against a Samsung Galaxy (typical bitmap `0x3` = BIS 1 + BIS 2), set this to `2` in the sdkconfig.
## Expected Log
TAG: `CAP_ACC`.

View File

@@ -4,32 +4,27 @@
menu "Example: CAP Acceptor"
choice EXAMPLE_CAP_ACCEPTOR_MODE
prompt "CAP Acceptor mode"
default EXAMPLE_UNICAST
config EXAMPLE_UNICAST
bool "Unicast"
default y
help
Select exactly one CAP acceptor mode.
If set, advertise as a Unicast acceptor (ASCS) for unicast audio.
config EXAMPLE_UNICAST
bool "Unicast"
help
If selected, the sample will start advertising connectable
for Broadcast Assistants.
config EXAMPLE_BROADCAST
bool "Broadcast"
default y if !EXAMPLE_UNICAST
select BT_NIMBLE_PERIODIC_ADV_SYNC_TRANSFER
help
If set, advertise as a Broadcast acceptor (BASS Scan Delegator)
for syncable broadcast audio. Can coexist with EXAMPLE_UNICAST
for a dual-role CAP Acceptor (BAP spec C.2 allows both).
config EXAMPLE_BROADCAST
bool "Broadcast"
help
If selected, the sample will start advertising syncable
audio streams.
endchoice
if EXAMPLE_BROADCAST
config EXAMPLE_SCAN_SELF
bool "Scan for Broadcast Sources without Broadcast Assistant"
help
If set to true, the sample will start scanning for Broadcast
Sources without waiting for a Broadcast Assistant to connect.
endif
config EXAMPLE_SCAN_SELF
bool "Scan for Broadcast Sources without Broadcast Assistant"
# !EXAMPLE_UNICAST: dual-role + self-scan coexistence not supported yet.
depends on EXAMPLE_BROADCAST && !EXAMPLE_UNICAST
help
If set, the sample will start scanning for Broadcast Sources
without waiting for a Broadcast Assistant to connect.
endmenu

View File

@@ -56,5 +56,7 @@ void broadcast_scan_recv(esp_ble_audio_gap_app_event_t *event);
void broadcast_pa_synced(esp_ble_audio_gap_app_event_t *event);
void broadcast_pa_sync_failed(esp_ble_audio_gap_app_event_t *event);
void broadcast_pa_lost(esp_ble_audio_gap_app_event_t *event);
#endif /* CONFIG_EXAMPLE_BROADCAST */

View File

@@ -56,11 +56,24 @@ static struct peer_config peer = {
static uint8_t ext_adv_data[] = {
/* Flags */
0x02, EXAMPLE_AD_TYPE_FLAGS, (EXAMPLE_AD_FLAGS_GENERAL | EXAMPLE_AD_FLAGS_NO_BREDR),
/* Incomplete List of 16-bit Service UUIDs */
0x05, EXAMPLE_AD_TYPE_UUID16_SOME, (ESP_BLE_AUDIO_UUID_ASCS_VAL & 0xFF),
((ESP_BLE_AUDIO_UUID_ASCS_VAL >> 8) & 0xFF),
(ESP_BLE_AUDIO_UUID_CAS_VAL & 0xFF),
((ESP_BLE_AUDIO_UUID_CAS_VAL >> 8) & 0xFF),
/* Incomplete List of 16-bit Service UUIDs:
* CAS always; ASCS only when unicast role is built;
* BASS only when broadcast role is built.
*/
#if CONFIG_EXAMPLE_UNICAST && CONFIG_EXAMPLE_BROADCAST
0x07, EXAMPLE_AD_TYPE_UUID16_SOME,
(ESP_BLE_AUDIO_UUID_ASCS_VAL & 0xFF), ((ESP_BLE_AUDIO_UUID_ASCS_VAL >> 8) & 0xFF),
(ESP_BLE_AUDIO_UUID_CAS_VAL & 0xFF), ((ESP_BLE_AUDIO_UUID_CAS_VAL >> 8) & 0xFF),
(ESP_BLE_AUDIO_UUID_BASS_VAL & 0xFF), ((ESP_BLE_AUDIO_UUID_BASS_VAL >> 8) & 0xFF),
#elif CONFIG_EXAMPLE_UNICAST
0x05, EXAMPLE_AD_TYPE_UUID16_SOME,
(ESP_BLE_AUDIO_UUID_ASCS_VAL & 0xFF), ((ESP_BLE_AUDIO_UUID_ASCS_VAL >> 8) & 0xFF),
(ESP_BLE_AUDIO_UUID_CAS_VAL & 0xFF), ((ESP_BLE_AUDIO_UUID_CAS_VAL >> 8) & 0xFF),
#elif CONFIG_EXAMPLE_BROADCAST
0x05, EXAMPLE_AD_TYPE_UUID16_SOME,
(ESP_BLE_AUDIO_UUID_CAS_VAL & 0xFF), ((ESP_BLE_AUDIO_UUID_CAS_VAL >> 8) & 0xFF),
(ESP_BLE_AUDIO_UUID_BASS_VAL & 0xFF), ((ESP_BLE_AUDIO_UUID_BASS_VAL >> 8) & 0xFF),
#endif
/* Service Data - 16-bit UUID */
0x04, EXAMPLE_AD_TYPE_SERVICE_DATA16, (ESP_BLE_AUDIO_UUID_CAS_VAL & 0xFF),
((ESP_BLE_AUDIO_UUID_CAS_VAL >> 8) & 0xFF),
@@ -240,6 +253,7 @@ static void pa_sync(esp_ble_audio_gap_app_event_t *event)
{
if (event->pa_sync.status) {
ESP_LOGE(TAG, "PA sync failed, status %d", event->pa_sync.status);
broadcast_pa_sync_failed(event);
return;
}
@@ -291,6 +305,7 @@ static void iso_gap_app_cb(esp_ble_audio_gap_app_event_t *event)
break;
#endif /* CONFIG_EXAMPLE_SCAN_SELF */
case ESP_BLE_AUDIO_GAP_EVENT_PA_SYNC:
case ESP_BLE_AUDIO_GAP_EVENT_PA_SYNC_PAST:
pa_sync(event);
break;
case ESP_BLE_AUDIO_GAP_EVENT_PA_SYNC_LOST:

View File

@@ -603,7 +603,8 @@ static void security_change(esp_ble_iso_gap_app_event_t *event)
int err;
if (event->security_change.status) {
ESP_LOGE(TAG, "Security change failed, status %d", event->security_change.status);
example_audio_security_failed_recover(TAG, event->security_change.conn_handle,
event->security_change.status);
return;
}

View File

@@ -32,6 +32,8 @@ int example_audio_gap_event_cb(struct ble_gap_event *event, void *arg)
event->type == BLE_GAP_EVENT_PERIODIC_SYNC ||
event->type == BLE_GAP_EVENT_PERIODIC_REPORT ||
event->type == BLE_GAP_EVENT_PERIODIC_SYNC_LOST ||
event->type == BLE_GAP_EVENT_PERIODIC_TRANSFER ||
event->type == BLE_GAP_EVENT_PERIODIC_TRANSFER_V2 ||
event->type == BLE_GAP_EVENT_CONNECT ||
event->type == BLE_GAP_EVENT_DISCONNECT ||
event->type == BLE_GAP_EVENT_ENC_CHANGE) {
@@ -41,11 +43,49 @@ int example_audio_gap_event_cb(struct ble_gap_event *event, void *arg)
event->type == BLE_GAP_EVENT_NOTIFY_TX ||
event->type == BLE_GAP_EVENT_SUBSCRIBE) {
esp_ble_audio_gatt_app_post_event(event->type, event);
} else if (event->type == BLE_GAP_EVENT_REPEAT_PAIRING) {
/* Peer wants to re-pair on top of an existing bond (e.g. phone
* cleared its LTK via "Forget device" but our NVS still has it).
* Delete our stale bond and tell NimBLE to retry pairing.
*/
struct ble_gap_conn_desc desc = {0};
int rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
if (rc == 0) {
ble_store_util_delete_peer(&desc.peer_id_addr);
}
return BLE_GAP_REPEAT_PAIRING_RETRY;
}
return 0;
}
void example_audio_security_failed_recover(const char *tag, uint16_t conn_handle, uint8_t status)
{
struct ble_gap_conn_desc desc = {0};
int rc;
/* status 13 = BLE_HS_ETIMEOUT: SMP exchange did not complete. Typical
* cause is asymmetric bond state — the two sides bonded previously,
* then one side erased its NVS while the other kept the LTK. The side
* that still has the bond tries encryption with the cached key; the
* other side has nothing to match it against, so SMP times out. Drop
* our entry for this peer and disconnect; the next connection runs
* fresh pairing (peer side recovers via REPEAT_PAIRING).
*/
ESP_LOGE(tag, "Security change failed, status %u, clearing local bond and reconnecting", status);
rc = ble_gap_conn_find(conn_handle, &desc);
if (rc == 0) {
ble_store_util_delete_peer(&desc.peer_id_addr);
}
/* Tear down the link: SMP cannot be retried on the same connection
* after a failed/timed-out exchange. The next reconnect starts a
* fresh SMP procedure with the now-cleared bond state.
*/
ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
static void print_hex(const uint8_t *ptr, size_t len)
{
while (len--) {

View File

@@ -21,6 +21,7 @@
#include "host/ble_gap.h"
#include "host/ble_hs_adv.h"
#include "host/ble_store.h"
#include "esp_ble_audio_bap_api.h"
@@ -53,6 +54,8 @@
int example_audio_gap_event_cb(struct ble_gap_event *event, void *arg);
void example_audio_security_failed_recover(const char *tag, uint16_t conn_handle, uint8_t status);
void example_print_codec_cfg(const char *tag, const esp_ble_audio_codec_cfg_t *codec_cfg);
void example_print_codec_cap(const char *tag, const esp_ble_audio_codec_cap_t *codec_cap);

View File

@@ -253,7 +253,8 @@ static void security_change(esp_ble_iso_gap_app_event_t *event)
int err;
if (event->security_change.status) {
ESP_LOGE(TAG, "Security change failed, status %d", event->security_change.status);
example_audio_security_failed_recover(TAG, event->security_change.conn_handle,
event->security_change.status);
return;
}

View File

@@ -352,7 +352,8 @@ static void acl_disconnect(esp_ble_iso_gap_app_event_t *event)
static void security_change(esp_ble_iso_gap_app_event_t *event)
{
if (event->security_change.status) {
ESP_LOGE(TAG, "Security change failed, status %d", event->security_change.status);
example_iso_security_failed_recover(TAG, event->security_change.conn_handle,
event->security_change.status);
return;
}

View File

@@ -28,15 +28,55 @@ int example_iso_gap_event_cb(struct ble_gap_event *event, void *arg)
event->type == BLE_GAP_EVENT_PERIODIC_SYNC ||
event->type == BLE_GAP_EVENT_PERIODIC_REPORT ||
event->type == BLE_GAP_EVENT_PERIODIC_SYNC_LOST ||
event->type == BLE_GAP_EVENT_PERIODIC_TRANSFER ||
event->type == BLE_GAP_EVENT_PERIODIC_TRANSFER_V2 ||
event->type == BLE_GAP_EVENT_CONNECT ||
event->type == BLE_GAP_EVENT_DISCONNECT ||
event->type == BLE_GAP_EVENT_ENC_CHANGE) {
esp_ble_iso_gap_app_post_event(event->type, event);
} else if (event->type == BLE_GAP_EVENT_REPEAT_PAIRING) {
/* Peer wants to re-pair on top of an existing bond (e.g. phone
* cleared its LTK via "Forget device" but our NVS still has it).
* Delete our stale bond and tell NimBLE to retry pairing.
*/
struct ble_gap_conn_desc desc = {0};
int rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
if (rc == 0) {
ble_store_util_delete_peer(&desc.peer_id_addr);
}
return BLE_GAP_REPEAT_PAIRING_RETRY;
}
return 0;
}
void example_iso_security_failed_recover(const char *tag, uint16_t conn_handle, uint8_t status)
{
struct ble_gap_conn_desc desc = {0};
int rc;
/* status 13 = BLE_HS_ETIMEOUT: SMP exchange did not complete. Typical
* cause is asymmetric bond state — the two sides bonded previously,
* then one side erased its NVS while the other kept the LTK. The side
* that still has the bond tries encryption with the cached key; the
* other side has nothing to match it against, so SMP times out. Drop
* our entry for this peer and disconnect; the next connection runs
* fresh pairing (peer side recovers via REPEAT_PAIRING).
*/
ESP_LOGE(tag, "Security change failed, status %u, clearing local bond and reconnecting", status);
rc = ble_gap_conn_find(conn_handle, &desc);
if (rc == 0) {
ble_store_util_delete_peer(&desc.peer_id_addr);
}
/* Tear down the link: SMP cannot be retried on the same connection
* after a failed/timed-out exchange. The next reconnect starts a
* fresh SMP procedure with the now-cleared bond state.
*/
ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
static void example_iso_tx_work_handler(struct k_work *work)
{
example_iso_tx_scheduler_t *scheduler = work->user_data;

View File

@@ -20,6 +20,7 @@
#include "host/ble_gap.h"
#include "host/ble_hs_adv.h"
#include "host/ble_store.h"
#include "esp_ble_iso_common_api.h"
@@ -52,6 +53,8 @@
int example_iso_gap_event_cb(struct ble_gap_event *event, void *arg);
void example_iso_security_failed_recover(const char *tag, uint16_t conn_handle, uint8_t status);
/**
* @brief TX scheduler for periodic ISO data transmission.
*