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

This commit is contained in:
Liu Linyan
2026-05-18 20:31:24 +08:00
parent 6cd0e56ae2
commit 8dbef3bad4
223 changed files with 14714 additions and 2347 deletions

View File

@@ -29,13 +29,24 @@ endif()
list(APPEND ble_audio_include_dirs
"${CMAKE_CURRENT_LIST_DIR}/api/include"
"${CMAKE_CURRENT_LIST_DIR}/host/common/include"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/include"
"${CMAKE_CURRENT_LIST_DIR}/include"
)
if(CONFIG_BT_NIMBLE_ENABLED)
set(audio_adapter "host/adapter/nimble")
elseif(CONFIG_BT_BLUEDROID_ENABLED)
set(audio_adapter "host/adapter/bluedroid")
endif()
list(APPEND ble_audio_include_dirs
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/include"
)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/server.c"
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/init.c"
)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/profiles/server.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/init.c"
"${CMAKE_CURRENT_LIST_DIR}/host/common/init.c"
"${CMAKE_CURRENT_LIST_DIR}/api/esp_ble_audio_aics_api.c"
"${CMAKE_CURRENT_LIST_DIR}/api/esp_ble_audio_bap_api.c"
@@ -57,69 +68,71 @@ list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/api/esp_ble_audio_vocs_api.c"
)
# Profile glue files live under the host adapter; same set of profile sources
# for either host, dispatched via ${audio_adapter}.
if(CONFIG_BT_ASCS)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/profiles/ascs.c"
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/ascs.c"
)
endif()
if(CONFIG_BT_PACS)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/profiles/pacs.c"
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/pacs.c"
)
endif()
if(CONFIG_BT_BAP_SCAN_DELEGATOR)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/profiles/bass.c"
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/bass.c"
)
endif()
if(CONFIG_BT_CAP_ACCEPTOR)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/profiles/cas.c"
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/cas.c"
)
endif()
if(CONFIG_BT_CSIP_SET_MEMBER)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/profiles/csis.c"
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/csis.c"
)
endif()
if(CONFIG_BT_TBS)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/profiles/tbs.c"
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/tbs.c"
)
endif()
if(CONFIG_BT_TMAP)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/profiles/tmas.c"
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/tmas.c"
)
endif()
if(CONFIG_BT_VCP_VOL_REND)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/profiles/vcs.c"
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/vcs.c"
)
endif()
if(CONFIG_BT_MICP_MIC_DEV)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/profiles/mics.c"
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/mics.c"
)
endif()
if(CONFIG_BT_MCS)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/profiles/mcs.c"
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/mcs.c"
)
endif()
if(CONFIG_BT_HAS)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/profiles/has.c"
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/has.c"
)
endif()

View File

@@ -61,8 +61,8 @@ if BT_MCC
it.
config BT_MCC_OTS
bool "Media Control Client support for Object Transfer Service (depends on OTS Client)"
depends on BT_OTS_CLIENT
bool "Media Control Client support for Object Transfer Service"
select BT_OTS_CLIENT
help
Use this option to configure support in the Media Control Client for
an included Object Transfer Service in the Media Control Service.

View File

@@ -71,9 +71,10 @@ if BT_MPL
config BT_MPL_OBJECTS
bool "Support for media player objects"
depends on BT_OTS
select BT_OTS
# TODO: Temporarily depends also on BT_MCS, to avoid issues with the
# bt_mcs_get_ots() call
# bt_mcs_get_ots() call. BT_MCS stays as `depends on` because it has
# non-leaf prerequisites (BT_MCTL_LOCAL_PLAYER_REMOTE_CONTROL ...).
depends on BT_MCS
# OTS shall be instantiated as a Secondary Service and shall be
# included in MCS or GMCS.

View File

@@ -70,10 +70,12 @@ void esp_ble_audio_gap_app_post_event(uint8_t type, void *param)
bt_le_gap_app_post_event(type, param);
}
#if !CONFIG_BT_BLUEDROID_ENABLED
void esp_ble_audio_gatt_app_post_event(uint8_t type, void *param)
{
bt_le_gatt_app_post_event(type, param);
}
#endif /* !CONFIG_BT_BLUEDROID_ENABLED */
esp_err_t esp_ble_audio_common_init(esp_ble_audio_init_info_t *info)
{
@@ -156,3 +158,10 @@ esp_err_t esp_ble_audio_common_start(esp_ble_audio_start_info_t *info)
return ESP_OK;
}
#if CONFIG_BT_BLUEDROID_ENABLED
uint8_t esp_ble_audio_bluedroid_get_gattc_if(void)
{
return bt_le_bluedroid_gattc_get_if();
}
#endif /* CONFIG_BT_BLUEDROID_ENABLED */

View File

@@ -128,15 +128,20 @@ typedef struct {
*/
void esp_ble_audio_gap_app_post_event(uint8_t type, void *param);
#if !CONFIG_BT_BLUEDROID_ENABLED
/**
* @brief Post an application-layer GATT event for audio internal usage.
*
* @note This function is only needed while using NimBLE Host.
* @note NimBLE-only. Bluedroid dispatches GATT events directly inside the
* adapter (BTA callbacks), so no app-level post is needed. This
* declaration is hidden from Bluedroid builds to make misuse a
* compile-time error.
*
* @param type Event type.
* @param param Event parameters.
*/
void esp_ble_audio_gatt_app_post_event(uint8_t type, void *param);
#endif /* !CONFIG_BT_BLUEDROID_ENABLED */
/**
* @brief Initialize BLE Audio common functionality.
@@ -167,6 +172,21 @@ typedef struct {
*/
esp_err_t esp_ble_audio_common_start(esp_ble_audio_start_info_t *info);
#if CONFIG_BT_BLUEDROID_ENABLED
/**
* @brief Get the engine's internal GATTC interface handle (Bluedroid only).
*
* Pass this to esp_ble_gattc_aux_open() / esp_ble_gattc_open() so the
* resulting ACL events route back to the engine, avoiding the need for the
* application to register a second BTA GATTC app for connection initiation.
*
* @return Engine's gattc_if (ABI-compatible with esp_gatt_if_t), or
* ESP_GATT_IF_NONE (0xFF) if GATTC registration has not completed —
* callers must bail rather than pass it to aux_open.
*/
uint8_t esp_ble_audio_bluedroid_get_gattc_if(void);
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_INIT_H_
#define HOST_BLUEDROID_INIT_H_
#include <stdint.h>
#include "bluedroid/server.h"
#include "bluedroid/profiles/ascs.h"
#include "bluedroid/profiles/bass.h"
#include "bluedroid/profiles/cas.h"
#include "bluedroid/profiles/csis.h"
#include "bluedroid/profiles/mcs.h"
#include "bluedroid/profiles/mics.h"
#include "bluedroid/profiles/pacs.h"
#include "bluedroid/profiles/tbs.h"
#include "bluedroid/profiles/tmas.h"
#include "bluedroid/profiles/vcs.h"
#include "bluedroid/profiles/has.h"
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_audio_init(void);
int bt_le_bluedroid_media_proxy_pl_init(void);
int bt_le_bluedroid_vcp_vol_rend_init(void);
int bt_le_bluedroid_micp_mic_dev_init(void);
int bt_le_bluedroid_audio_start(void *info);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_INIT_H_ */

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_PROFILE_ASCS_H_
#define HOST_BLUEDROID_PROFILE_ASCS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_ascs_init(void);
int bt_le_bluedroid_ascs_start(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_PROFILE_ASCS_H_ */

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_PROFILE_BASS_H_
#define HOST_BLUEDROID_PROFILE_BASS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_bass_init(void);
int bt_le_bluedroid_bass_start(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_PROFILE_BASS_H_ */

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_PROFILE_CAS_H_
#define HOST_BLUEDROID_PROFILE_CAS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_cas_init(void *csis_svc_p);
int bt_le_bluedroid_cas_start(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_PROFILE_CAS_H_ */

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_PROFILE_CSIS_H_
#define HOST_BLUEDROID_PROFILE_CSIS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_csis_init(void *csis_svc, uint8_t count);
int bt_le_bluedroid_csis_start(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_PROFILE_CSIS_H_ */

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_PROFILE_HAS_H_
#define HOST_BLUEDROID_PROFILE_HAS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_has_init(void);
int bt_le_bluedroid_has_start(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_PROFILE_HAS_H_ */

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_PROFILE_MCS_H_
#define HOST_BLUEDROID_PROFILE_MCS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_gmcs_init(void);
int bt_le_bluedroid_gmcs_start(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_PROFILE_MCS_H_ */

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_PROFILE_MICS_H_
#define HOST_BLUEDROID_PROFILE_MICS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_mics_init(void *micp_inc);
int bt_le_bluedroid_mics_start(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_PROFILE_MICS_H_ */

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_PROFILE_PACS_H_
#define HOST_BLUEDROID_PROFILE_PACS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_pacs_init(void);
int bt_le_bluedroid_pacs_start(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_PROFILE_PACS_H_ */

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_PROFILE_TBS_H_
#define HOST_BLUEDROID_PROFILE_TBS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_gtbs_init(void);
int bt_le_bluedroid_gtbs_start(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_PROFILE_TBS_H_ */

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_PROFILE_TMAS_H_
#define HOST_BLUEDROID_PROFILE_TMAS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_tmas_init(void);
int bt_le_bluedroid_tmas_start(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_PROFILE_TMAS_H_ */

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_PROFILE_VCS_H_
#define HOST_BLUEDROID_PROFILE_VCS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_vcs_init(void *vcp_inc);
int bt_le_bluedroid_vcs_start(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_PROFILE_VCS_H_ */

View File

@@ -0,0 +1,172 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_SERVER_H_
#define HOST_BLUEDROID_SERVER_H_
#include <stdint.h>
#include <stdbool.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#ifdef __cplusplus
extern "C" {
#endif
static inline char *audio_svc_uuid_to_str(uint16_t uuid)
{
switch (uuid) {
case BT_UUID_AICS_VAL: return "AICS";
case BT_UUID_CAS_VAL: return "CAS";
case BT_UUID_VCS_VAL: return "VCS";
case BT_UUID_VOCS_VAL: return "VOCS";
case BT_UUID_CSIS_VAL: return "CSIS";
case BT_UUID_MCS_VAL: return "MCS";
case BT_UUID_GMCS_VAL: return "GMCS";
case BT_UUID_TBS_VAL: return "TBS";
case BT_UUID_GTBS_VAL: return "GTBS";
case BT_UUID_MICS_VAL: return "MICS";
case BT_UUID_ASCS_VAL: return "ASCS";
case BT_UUID_BASS_VAL: return "BASS";
case BT_UUID_PACS_VAL: return "PACS";
case BT_UUID_BASIC_AUDIO_VAL: return "BASIC_AUDIO";
case BT_UUID_HAS_VAL: return "HAS";
case BT_UUID_TMAS_VAL: return "TMAS";
case BT_UUID_PBA_VAL: return "PBA";
case BT_UUID_GMAS_VAL: return "GMAS";
case BT_UUID_OTS_VAL: return "OTS";
default: return "UnknownSvc";
}
}
static inline char *audio_chrc_uuid_to_str(uint16_t uuid)
{
switch (uuid) {
case BT_UUID_OTS_FEATURE_VAL: return "OTS_FEATURE";
case BT_UUID_OTS_NAME_VAL: return "OTS_NAME";
case BT_UUID_OTS_TYPE_VAL: return "OTS_TYPE";
case BT_UUID_OTS_SIZE_VAL: return "OTS_SIZE";
case BT_UUID_OTS_FIRST_CREATED_VAL: return "OTS_FIRST_CREATED";
case BT_UUID_OTS_LAST_MODIFIED_VAL: return "OTS_LAST_MODIFIED";
case BT_UUID_OTS_ID_VAL: return "OTS_ID";
case BT_UUID_OTS_PROPERTIES_VAL: return "OTS_PROPERTIES";
case BT_UUID_OTS_ACTION_CP_VAL: return "OTS_ACTION_CP";
case BT_UUID_OTS_LIST_CP_VAL: return "OTS_LIST_CP";
case BT_UUID_OTS_LIST_FILTER_VAL: return "OTS_LIST_FILTER";
case BT_UUID_OTS_CHANGED_VAL: return "OTS_CHANGED";
case BT_UUID_GATT_TMAPR_VAL: return "TMAP_ROLE";
case BT_UUID_AICS_STATE_VAL: return "AICS_STATE";
case BT_UUID_AICS_GAIN_SETTINGS_VAL: return "AICS_GAIN_SETTINGS";
case BT_UUID_AICS_INPUT_TYPE_VAL: return "AICS_INPUT_TYPE";
case BT_UUID_AICS_INPUT_STATUS_VAL: return "AICS_INPUT_STATUS";
case BT_UUID_AICS_CONTROL_VAL: return "AICS_CONTROL";
case BT_UUID_AICS_DESCRIPTION_VAL: return "AICS_DESCRIPTION";
case BT_UUID_VCS_STATE_VAL: return "VCS_STATE";
case BT_UUID_VCS_CONTROL_VAL: return "VCS_CONTROL";
case BT_UUID_VCS_FLAGS_VAL: return "VCS_FLAGS";
case BT_UUID_VOCS_STATE_VAL: return "VOCS_STATE";
case BT_UUID_VOCS_LOCATION_VAL: return "VOCS_LOCATION";
case BT_UUID_VOCS_CONTROL_VAL: return "VOCS_CONTROL";
case BT_UUID_VOCS_DESCRIPTION_VAL: return "VOCS_DESCRIPTION";
case BT_UUID_CSIS_SIRK_VAL: return "CSIS_SIRK";
case BT_UUID_CSIS_SET_SIZE_VAL: return "CSIS_SET_SIZE";
case BT_UUID_CSIS_SET_LOCK_VAL: return "CSIS_SET_LOCK";
case BT_UUID_CSIS_RANK_VAL: return "CSIS_RANK";
case BT_UUID_MCS_PLAYER_NAME_VAL: return "MCS_PLAYER_NAME";
case BT_UUID_MCS_ICON_OBJ_ID_VAL: return "MCS_ICON_OBJ_ID";
case BT_UUID_MCS_ICON_URL_VAL: return "MCS_ICON_URL";
case BT_UUID_MCS_TRACK_CHANGED_VAL: return "MCS_TRACK_CHANGED";
case BT_UUID_MCS_TRACK_TITLE_VAL: return "MCS_TRACK_TITLE";
case BT_UUID_MCS_TRACK_DURATION_VAL: return "MCS_TRACK_DURATION";
case BT_UUID_MCS_TRACK_POSITION_VAL: return "MCS_TRACK_POSITION";
case BT_UUID_MCS_PLAYBACK_SPEED_VAL: return "MCS_PLAYBACK_SPEED";
case BT_UUID_MCS_SEEKING_SPEED_VAL: return "MCS_SEEKING_SPEED";
case BT_UUID_MCS_TRACK_SEGMENTS_OBJ_ID_VAL: return "MCS_TRACK_SEGMENTS_OBJ_ID";
case BT_UUID_MCS_CURRENT_TRACK_OBJ_ID_VAL: return "MCS_CURRENT_TRACK_OBJ_ID";
case BT_UUID_MCS_NEXT_TRACK_OBJ_ID_VAL: return "MCS_NEXT_TRACK_OBJ_ID";
case BT_UUID_MCS_PARENT_GROUP_OBJ_ID_VAL: return "MCS_PARENT_GROUP_OBJ_ID";
case BT_UUID_MCS_CURRENT_GROUP_OBJ_ID_VAL: return "MCS_CURRENT_GROUP_OBJ_ID";
case BT_UUID_MCS_PLAYING_ORDER_VAL: return "MCS_PLAYING_ORDER";
case BT_UUID_MCS_PLAYING_ORDERS_VAL: return "MCS_PLAYING_ORDERS";
case BT_UUID_MCS_MEDIA_STATE_VAL: return "MCS_MEDIA_STATE";
case BT_UUID_MCS_MEDIA_CONTROL_POINT_VAL: return "MCS_MEDIA_CONTROL_POINT";
case BT_UUID_MCS_MEDIA_CONTROL_OPCODES_VAL: return "MCS_MEDIA_CONTROL_OPCODES";
case BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID_VAL: return "MCS_SEARCH_RESULTS_OBJ_ID";
case BT_UUID_MCS_SEARCH_CONTROL_POINT_VAL: return "MCS_SEARCH_CONTROL_POINT";
case BT_UUID_TBS_PROVIDER_NAME_VAL: return "TBS_PROVIDER_NAME";
case BT_UUID_TBS_UCI_VAL: return "TBS_UCI";
case BT_UUID_TBS_TECHNOLOGY_VAL: return "TBS_TECHNOLOGY";
case BT_UUID_TBS_URI_LIST_VAL: return "TBS_URI_LIST";
case BT_UUID_TBS_SIGNAL_STRENGTH_VAL: return "TBS_SIGNAL_STRENGTH";
case BT_UUID_TBS_SIGNAL_INTERVAL_VAL: return "TBS_SIGNAL_INTERVAL";
case BT_UUID_TBS_LIST_CURRENT_CALLS_VAL: return "TBS_LIST_CURRENT_CALLS";
case BT_UUID_CCID_VAL: return "CCID";
case BT_UUID_TBS_STATUS_FLAGS_VAL: return "TBS_STATUS_FLAGS";
case BT_UUID_TBS_INCOMING_URI_VAL: return "TBS_INCOMING_URI";
case BT_UUID_TBS_CALL_STATE_VAL: return "TBS_CALL_STATE";
case BT_UUID_TBS_CALL_CONTROL_POINT_VAL: return "TBS_CALL_CONTROL_POINT";
case BT_UUID_TBS_OPTIONAL_OPCODES_VAL: return "TBS_OPTIONAL_OPCODES";
case BT_UUID_TBS_TERMINATE_REASON_VAL: return "TBS_TERMINATE_REASON";
case BT_UUID_TBS_INCOMING_CALL_VAL: return "TBS_INCOMING_CALL";
case BT_UUID_TBS_FRIENDLY_NAME_VAL: return "TBS_FRIENDLY_NAME";
case BT_UUID_MICS_MUTE_VAL: return "MICS_MUTE";
case BT_UUID_ASCS_ASE_SNK_VAL: return "ASCS_ASE_SNK";
case BT_UUID_ASCS_ASE_SRC_VAL: return "ASCS_ASE_SRC";
case BT_UUID_ASCS_ASE_CP_VAL: return "ASCS_ASE_CP";
case BT_UUID_BASS_CONTROL_POINT_VAL: return "BASS_CP";
case BT_UUID_BASS_RECV_STATE_VAL: return "BASS_RECV_STATE";
case BT_UUID_PACS_SNK_VAL: return "PACS_SNK";
case BT_UUID_PACS_SNK_LOC_VAL: return "PACS_SNK_LOC";
case BT_UUID_PACS_SRC_VAL: return "PACS_SRC";
case BT_UUID_PACS_SRC_LOC_VAL: return "PACS_SRC_LOC";
case BT_UUID_PACS_AVAILABLE_CONTEXT_VAL: return "PACS_AVAILABLE_CONTEXT";
case BT_UUID_PACS_SUPPORTED_CONTEXT_VAL: return "PACS_SUPPORTED_CONTEXT";
case BT_UUID_HAS_HEARING_AID_FEATURES_VAL: return "HAS_HEARING_AID_FEATURES";
case BT_UUID_HAS_PRESET_CONTROL_POINT_VAL: return "HAS_PRESET_CONTROL_POINT";
case BT_UUID_HAS_ACTIVE_PRESET_INDEX_VAL: return "HAS_ACTIVE_PRESET_INDEX";
case BT_UUID_GMAP_ROLE_VAL: return "GMAP_ROLE";
case BT_UUID_GMAP_UGG_FEAT_VAL: return "GMAP_UGG_FEAT";
case BT_UUID_GMAP_UGT_FEAT_VAL: return "GMAP_UGT_FEAT";
case BT_UUID_GMAP_BGS_FEAT_VAL: return "GMAP_BGS_FEAT";
case BT_UUID_GMAP_BGR_FEAT_VAL: return "GMAP_BGR_FEAT";
default: return "UnknownChrc";
}
}
enum {
AICS_IN_PROGRESS,
ASCS_IN_PROGRESS,
BASS_IN_PROGRESS,
CAS_IN_PROGRESS,
CSIS_IN_PROGRESS,
HAS_IN_PROGRESS,
GMCS_IN_PROGRESS,
MCS_IN_PROGRESS,
MICS_IN_PROGRESS,
PACS_IN_PROGRESS,
GTBS_IN_PROGRESS,
TBS_IN_PROGRESS,
TMAS_IN_PROGRESS,
VCS_IN_PROGRESS,
VOCS_IN_PROGRESS,
MAX_IN_PROGRESS,
};
void bt_le_bluedroid_audio_gatts_init(void);
int bt_le_bluedroid_set_svc_in_progress(uint8_t value);
int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc);
int bt_le_bluedroid_svc_start(struct bt_gatt_service *svc);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_SERVER_H_ */

View File

@@ -0,0 +1,342 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <errno.h>
#include <assert.h>
#include "sdkconfig.h"
#include "bta/bta_gatt_common.h"
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/audio/micp.h>
#include <zephyr/bluetooth/audio/vcp.h>
#include "bluedroid/init.h"
#include "common/init.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_BINIT, CONFIG_BT_ISO_LOG_LEVEL);
#if CONFIG_BT_CSIP_SET_MEMBER
#define CSIS_SVC_COUNT CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT
#else /* CONFIG_BT_CSIP_SET_MEMBER */
#define CSIS_SVC_COUNT 0
#endif /* CONFIG_BT_CSIP_SET_MEMBER */
/* 3 is reserved for other GATT services */
#define TOTAL_SERVICE_COUNT (3 + \
(IS_ENABLED(CONFIG_BT_ASCS) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_PACS) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_BAP_SCAN_DELEGATOR) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_TMAP) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_MCS) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER) ? CSIS_SVC_COUNT : 0) + \
(IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_VCP_VOL_REND) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_MICP_MIC_DEV) ? 1 : 0) + \
CONFIG_BT_VOCS_MAX_INSTANCE_COUNT + \
CONFIG_BT_AICS_MAX_INSTANCE_COUNT + \
(IS_ENABLED(CONFIG_BT_TBS) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_HAS) ? 1 : 0))
_Static_assert(TOTAL_SERVICE_COUNT <= CONFIG_BT_GATT_MAX_SR_PROFILES, "Too small BT_GATT_MAX_SR_PROFILES");
int bt_le_bluedroid_audio_init(void)
{
int err = 0;
BTA_GATT_SetLocalMTU(BLE_AUDIO_ATT_MTU_MIN);
bt_le_bluedroid_audio_gatts_init();
#if CONFIG_BT_PACS
err = bt_le_bluedroid_pacs_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_PACS */
#if (BLE_AUDIO_SVC_DEFERRED_ADD == 0)
#if CONFIG_BT_ASCS
err = bt_le_bluedroid_ascs_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_ASCS */
#if CONFIG_BT_BAP_SCAN_DELEGATOR
err = bt_le_bluedroid_bass_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_BAP_SCAN_DELEGATOR */
#if CONFIG_BT_TMAP
err = bt_le_bluedroid_tmas_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_TMAP */
#if CONFIG_BT_TBS
err = bt_le_bluedroid_gtbs_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_TBS */
#if CONFIG_BT_HAS
err = bt_le_bluedroid_has_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_HAS */
#endif /* (BLE_AUDIO_SVC_DEFERRED_ADD == 0) */
return err;
}
#if CONFIG_BT_CSIP_SET_MEMBER
static int bluedroid_gatt_csis_init(struct bt_le_audio_start_info *info,
struct bt_gatt_service **inc_csis_svc)
{
struct bt_gatt_service *csis_svc[CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT];
uint8_t count;
int err;
if (info == NULL) {
return 0;
}
*inc_csis_svc = NULL;
count = 0;
for (size_t i = 0; i < ARRAY_SIZE(info->csis_insts); i++) {
if (info->csis_insts[i].svc_inst == NULL) {
continue;
}
csis_svc[count] = lib_csip_set_member_svc_get(info->csis_insts[i].svc_inst);
if (!csis_svc[count]) {
LOG_ERR("[B]CsisSvcGetFail[%u]", i);
return -ENODEV;
}
if (info->csis_insts[i].included_by_cas) {
if (*inc_csis_svc == NULL) {
*inc_csis_svc = csis_svc[count];
} else {
/* CAS may include at most one CSIS — caller misconfigured. */
LOG_ERR("[B]CsisMultiIncByCas");
return -EINVAL;
}
}
count++;
}
err = bt_le_bluedroid_csis_init(csis_svc, count);
if (err) {
return err;
}
return 0;
}
#endif /* CONFIG_BT_CSIP_SET_MEMBER */
#if CONFIG_BT_MCS
int bt_le_bluedroid_media_proxy_pl_init(void)
{
int err;
#if CONFIG_BT_MPL_OBJECTS
#endif /* CONFIG_BT_MPL_OBJECTS */
err = bt_le_bluedroid_gmcs_init();
if (err) {
return err;
}
return 0;
}
#endif /* CONFIG_BT_MCS */
#if CONFIG_BT_VCP_VOL_REND
int bt_le_bluedroid_vcp_vol_rend_init(void)
{
struct bt_vcp_included vcp_included;
int err;
memset(&vcp_included, 0, sizeof(vcp_included));
err = bt_vcp_vol_rend_included_get_safe(&vcp_included);
if (err) {
return err;
}
err = bt_le_bluedroid_vcs_init(&vcp_included);
if (err) {
return err;
}
return 0;
}
#endif /* CONFIG_BT_VCP_VOL_REND */
#if CONFIG_BT_MICP_MIC_DEV
int bt_le_bluedroid_micp_mic_dev_init(void)
{
struct bt_micp_included micp_included;
int err;
memset(&micp_included, 0, sizeof(micp_included));
err = bt_micp_mic_dev_included_get_safe(&micp_included);
if (err) {
return err;
}
err = bt_le_bluedroid_mics_init(&micp_included);
if (err) {
return err;
}
return 0;
}
#endif /* CONFIG_BT_MICP_MIC_DEV */
int bt_le_bluedroid_audio_start(void *info)
{
struct bt_gatt_service *inc_csis_svc = NULL;
int err = 0;
#if CONFIG_BT_CSIP_SET_MEMBER
/* Note:
* Should add CSIS before CAS in case one CSIS instance
* is included by CAS.
*/
err = bluedroid_gatt_csis_init(info, &inc_csis_svc);
if (err) {
return err;
}
#endif /* CONFIG_BT_CSIP_SET_MEMBER */
#if CONFIG_BT_CAP_ACCEPTOR
err = bt_le_bluedroid_cas_init(inc_csis_svc);
if (err) {
return err;
}
#else /* CONFIG_BT_CAP_ACCEPTOR */
ARG_UNUSED(inc_csis_svc);
#endif /* CONFIG_BT_CAP_ACCEPTOR */
#if (BLE_AUDIO_SVC_DEFERRED_ADD == 0)
#if CONFIG_BT_MCS
err = bt_le_bluedroid_media_proxy_pl_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_MCS */
#if CONFIG_BT_VCP_VOL_REND
err = bt_le_bluedroid_vcp_vol_rend_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_VCP_VOL_REND */
#if CONFIG_BT_MICP_MIC_DEV
err = bt_le_bluedroid_micp_mic_dev_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_MICP_MIC_DEV */
#endif /* (BLE_AUDIO_SVC_DEFERRED_ADD == 0) */
#if CONFIG_BT_ASCS
err = bt_le_bluedroid_ascs_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_ASCS */
#if CONFIG_BT_PACS
err = bt_le_bluedroid_pacs_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_PACS */
#if CONFIG_BT_BAP_SCAN_DELEGATOR
err = bt_le_bluedroid_bass_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_BAP_SCAN_DELEGATOR */
#if CONFIG_BT_TMAP
err = bt_le_bluedroid_tmas_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_TMAP */
#if CONFIG_BT_CSIP_SET_MEMBER
err = bt_le_bluedroid_csis_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_CSIP_SET_MEMBER */
#if CONFIG_BT_CAP_ACCEPTOR
err = bt_le_bluedroid_cas_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_CAP_ACCEPTOR */
#if CONFIG_BT_VCP_VOL_REND
err = bt_le_bluedroid_vcs_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_VCP_VOL_REND */
#if CONFIG_BT_MICP_MIC_DEV
err = bt_le_bluedroid_mics_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_MICP_MIC_DEV */
#if CONFIG_BT_MCS
err = bt_le_bluedroid_gmcs_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_MCS */
#if CONFIG_BT_TBS
err = bt_le_bluedroid_gtbs_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_TBS */
#if CONFIG_BT_HAS
err = bt_le_bluedroid_has_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_HAS */
return err;
}

View File

@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include "bluedroid/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_ASCS, CONFIG_BT_ISO_LOG_LEVEL);
int bt_le_bluedroid_ascs_init(void)
{
struct bt_gatt_service *ascs_svc;
LOG_DBG("[B]AscsInit");
ascs_svc = lib_ascs_svc_get();
if (!ascs_svc) {
LOG_ERR("[B]AscsSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(ASCS_IN_PROGRESS);
return bt_le_bluedroid_svc_init(ascs_svc);
}
int bt_le_bluedroid_ascs_start(void)
{
struct bt_gatt_service *ascs_svc;
LOG_DBG("[B]AscsStart");
ascs_svc = lib_ascs_svc_get();
if (!ascs_svc) {
LOG_ERR("[B]AscsSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(ASCS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(ascs_svc);
}

View File

@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include "bluedroid/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_BASS, CONFIG_BT_ISO_LOG_LEVEL);
int bt_le_bluedroid_bass_init(void)
{
struct bt_gatt_service *bass_svc;
LOG_DBG("[B]BassInit");
bass_svc = lib_bap_bass_svc_get();
if (!bass_svc) {
LOG_ERR("[B]BassSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(BASS_IN_PROGRESS);
return bt_le_bluedroid_svc_init(bass_svc);
}
int bt_le_bluedroid_bass_start(void)
{
struct bt_gatt_service *bass_svc;
LOG_DBG("[B]BassStart");
bass_svc = lib_bap_bass_svc_get();
if (!bass_svc) {
LOG_ERR("[B]BassSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(BASS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(bass_svc);
}

View File

@@ -0,0 +1,105 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include "bluedroid/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_CAS, CONFIG_BT_ISO_LOG_LEVEL);
#if CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER
static struct inc_svc_inst inc_csis_inst;
struct inc_svc_inst *cas_not_included_inst(void)
{
if (inc_csis_inst.included == false) {
return &inc_csis_inst;
}
return NULL;
}
#endif /* CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER */
int bt_le_bluedroid_cas_init(void *csis_svc_p)
{
struct bt_gatt_service *cas_svc;
int err = 0;
LOG_DBG("[B]CasInit");
#if CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER
/* csis_svc_p is non-NULL in practice (SET_MEMBER depends on CSIP_SET_MEMBER,
* which populates it), but guard for parity with the nimble adapter. With
* no CSIS, mark included=true so cas_not_included_inst() skips it and
* svc_init never feeds a NULL svc_p into BTA_GATTS_AddIncludeService. */
if (csis_svc_p) {
/* The instance of CSIS is included by CAS */
inc_csis_inst.svc_p = csis_svc_p;
/* Reset included before svc_init; see mics.c for rationale. */
inc_csis_inst.included = false;
} else {
inc_csis_inst.included = true;
}
#else /* CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER */
ARG_UNUSED(csis_svc_p);
#endif /* CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER */
cas_svc = lib_cas_svc_get();
if (!cas_svc) {
LOG_ERR("[B]CasSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(CAS_IN_PROGRESS);
err = bt_le_bluedroid_svc_init(cas_svc);
if (err) {
return err;
}
#if CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER
if (cas_not_included_inst()) {
LOG_ERR("[B]CasCsisNotInc");
return -EIO;
}
#else /* CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER */
/* Insert CAS to the GATT db list */
err = bt_gatt_service_register_safe(cas_svc);
if (err) {
LOG_ERR("[B]CasSvcRegFail[%d]", err);
}
#endif /* CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER */
return err;
}
int bt_le_bluedroid_cas_start(void)
{
struct bt_gatt_service *cas_svc;
LOG_DBG("[B]CasStart");
cas_svc = lib_cas_svc_get();
if (!cas_svc) {
LOG_ERR("[B]CasSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(CAS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(cas_svc);
}

View File

@@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include "bluedroid/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL);
#define CSIS_SVC_COUNT CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT
static struct csis_inst {
struct bt_gatt_service *svc_p;
} csis_insts[CSIS_SVC_COUNT];
static uint8_t csis_svc_count;
int bt_le_bluedroid_csis_init(void *svc, uint8_t count)
{
int err;
LOG_DBG("[B]CsisInit");
if (count > CSIS_SVC_COUNT) {
return -1;
}
bt_le_bluedroid_set_svc_in_progress(CSIS_IN_PROGRESS);
for (size_t i = 0; i < count; i++) {
csis_insts[i].svc_p = ((struct bt_gatt_service **)svc)[i];
err = bt_le_bluedroid_svc_init(csis_insts[i].svc_p);
if (err) {
return err;
}
}
/* Commit the count only after the loop succeeds, so a mid-loop failure
* doesn't leave csis_start() iterating uninitialized entries. */
csis_svc_count = count;
return 0;
}
int bt_le_bluedroid_csis_start(void)
{
int err;
LOG_DBG("[B]CsisStart");
bt_le_bluedroid_set_svc_in_progress(CSIS_IN_PROGRESS);
for (size_t i = 0; i < csis_svc_count; i++) {
err = bt_le_bluedroid_svc_start(csis_insts[i].svc_p);
if (err) {
return err;
}
}
return 0;
}

View File

@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include "bluedroid/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_HAS, CONFIG_BT_ISO_LOG_LEVEL);
int bt_le_bluedroid_has_init(void)
{
struct bt_gatt_service *has_svc;
LOG_DBG("[B]HasInit");
has_svc = lib_has_svc_get();
if (!has_svc) {
LOG_ERR("[B]HasSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(HAS_IN_PROGRESS);
return bt_le_bluedroid_svc_init(has_svc);
}
int bt_le_bluedroid_has_start(void)
{
struct bt_gatt_service *has_svc;
LOG_DBG("[B]HasStart");
has_svc = lib_has_svc_get();
if (!has_svc) {
LOG_ERR("[B]HasSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(HAS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(has_svc);
}

View File

@@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include "bluedroid/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_MCS, CONFIG_BT_ISO_LOG_LEVEL);
int bt_le_bluedroid_gmcs_init(void)
{
struct bt_gatt_service *gmcs_svc;
gmcs_svc = lib_mcs_svc_get();
if (!gmcs_svc) {
LOG_ERR("[B]GmcsSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(GMCS_IN_PROGRESS);
return bt_le_bluedroid_svc_init(gmcs_svc);
}
int bt_le_bluedroid_gmcs_start(void)
{
struct bt_gatt_service *gmcs_svc;
gmcs_svc = lib_mcs_svc_get();
if (!gmcs_svc) {
LOG_ERR("[B]GmcsSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(GMCS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(gmcs_svc);
}

View File

@@ -0,0 +1,136 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/audio/micp.h>
#include "bluedroid/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_MICS, CONFIG_BT_ISO_LOG_LEVEL);
#define AICS_INST_COUNT CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT
static uint8_t inc_aics_svc_count;
static struct inc_svc_inst inc_aics_insts[AICS_INST_COUNT];
struct inc_svc_inst *mics_not_included_inst(void)
{
for (size_t i = 0; i < inc_aics_svc_count; i++) {
if (inc_aics_insts[i].included == false) {
return &inc_aics_insts[i];
}
}
return NULL;
}
int bt_le_bluedroid_mics_init(void *micp_inc)
{
struct bt_micp_included *micp_included;
struct bt_gatt_service *mics_svc;
int err;
LOG_DBG("[B]MicsInit");
mics_svc = lib_mics_svc_get();
if (!mics_svc) {
LOG_ERR("[B]MicsSvcGetFail");
return -ENODEV;
}
/* Reset before the NULL check so a re-init with micp_included == NULL
* doesn't iterate over stale entries from a previous non-NULL call. */
inc_aics_svc_count = 0;
micp_included = micp_inc;
if (micp_included) {
if (micp_included->aics_cnt > AICS_INST_COUNT) {
return -EINVAL;
}
bt_le_bluedroid_set_svc_in_progress(AICS_IN_PROGRESS);
/* MICS may include zero or more instances of AICS */
for (size_t i = 0; i < micp_included->aics_cnt; i++) {
inc_aics_insts[i].svc_p = lib_aics_svc_get(micp_included->aics[i]);
if (!inc_aics_insts[i].svc_p) {
LOG_ERR("[B]AicsSvcGetFail[%u]", i);
return -ENODEV;
}
/* Reset included before svc_init: server.c sets it to true when
* the include attribute is processed. Without this reset, a
* re-init where the include attribute is missing would leave
* the stale true from a previous init and mics_not_included_inst()
* would falsely report all instances as included. */
inc_aics_insts[i].included = false;
err = bt_le_bluedroid_svc_init(inc_aics_insts[i].svc_p);
if (err) {
return err;
}
}
/* Commit the count only after every instance initialized — a mid-loop
* failure above leaves it at 0 (reset on entry) so mics_start() never
* iterates partially-initialized entries. */
inc_aics_svc_count = micp_included->aics_cnt;
}
bt_le_bluedroid_set_svc_in_progress(MICS_IN_PROGRESS);
err = bt_le_bluedroid_svc_init(mics_svc);
if (err) {
return err;
}
if (mics_not_included_inst()) {
LOG_ERR("[B]MicsAicsNotAllInc");
return -EIO;
}
return 0;
}
int bt_le_bluedroid_mics_start(void)
{
struct bt_gatt_service *mics_svc;
int err;
LOG_DBG("[B]MicsStart");
mics_svc = lib_mics_svc_get();
if (!mics_svc) {
LOG_ERR("[B]MicsSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(AICS_IN_PROGRESS);
for (size_t i = 0; i < inc_aics_svc_count; i++) {
err = bt_le_bluedroid_svc_start(inc_aics_insts[i].svc_p);
if (err) {
return err;
}
}
bt_le_bluedroid_set_svc_in_progress(MICS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(mics_svc);
}

View File

@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include "bluedroid/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_PACS, CONFIG_BT_ISO_LOG_LEVEL);
int bt_le_bluedroid_pacs_init(void)
{
struct bt_gatt_service *pacs_svc;
LOG_DBG("[B]PacsInit");
pacs_svc = lib_pacs_svc_get();
if (!pacs_svc) {
LOG_ERR("[B]PacsSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(PACS_IN_PROGRESS);
return bt_le_bluedroid_svc_init(pacs_svc);
}
int bt_le_bluedroid_pacs_start(void)
{
struct bt_gatt_service *pacs_svc;
LOG_DBG("[B]PacsStart");
pacs_svc = lib_pacs_svc_get();
if (!pacs_svc) {
LOG_ERR("[B]PacsSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(PACS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(pacs_svc);
}

View File

@@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include "bluedroid/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_TBS, CONFIG_BT_ISO_LOG_LEVEL);
int bt_le_bluedroid_gtbs_init(void)
{
struct bt_gatt_service *gtbs_svc;
gtbs_svc = lib_gtbs_svc_get();
if (!gtbs_svc) {
LOG_ERR("[B]GtbsSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(GTBS_IN_PROGRESS);
return bt_le_bluedroid_svc_init(gtbs_svc);
}
int bt_le_bluedroid_gtbs_start(void)
{
struct bt_gatt_service *gtbs_svc;
gtbs_svc = lib_gtbs_svc_get();
if (!gtbs_svc) {
LOG_ERR("[B]GtbsSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(GTBS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(gtbs_svc);
}

View File

@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include "bluedroid/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_TMAS, CONFIG_BT_ISO_LOG_LEVEL);
int bt_le_bluedroid_tmas_init(void)
{
struct bt_gatt_service *tmas_svc;
LOG_DBG("[B]TmasInit");
tmas_svc = lib_tmas_svc_get();
if (!tmas_svc) {
LOG_ERR("[B]TmasSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(TMAS_IN_PROGRESS);
return bt_le_bluedroid_svc_init(tmas_svc);
}
int bt_le_bluedroid_tmas_start(void)
{
struct bt_gatt_service *tmas_svc;
LOG_DBG("[B]TmasStart");
tmas_svc = lib_tmas_svc_get();
if (!tmas_svc) {
LOG_ERR("[B]TmasSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(TMAS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(tmas_svc);
}

View File

@@ -0,0 +1,169 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/audio/vcp.h>
#include "bluedroid/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_VCS, CONFIG_BT_ISO_LOG_LEVEL);
#define VOCS_INST_COUNT CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT
#define AICS_INST_COUNT CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT
static uint8_t inc_vocs_svc_count;
static uint8_t inc_aics_svc_count;
static struct inc_svc_inst inc_vocs_insts[VOCS_INST_COUNT];
static struct inc_svc_inst inc_aics_insts[AICS_INST_COUNT];
struct inc_svc_inst *vcs_not_included_inst(void)
{
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
if (inc_vocs_insts[i].included == false) {
return &inc_vocs_insts[i];
}
}
for (size_t i = 0; i < inc_aics_svc_count; i++) {
if (inc_aics_insts[i].included == false) {
return &inc_aics_insts[i];
}
}
return NULL;
}
int bt_le_bluedroid_vcs_init(void *vcp_inc)
{
struct bt_vcp_included *vcp_included;
struct bt_gatt_service *vcs_svc;
int err;
LOG_DBG("[B]VcsInit");
vcs_svc = lib_vcs_svc_get();
if (!vcs_svc) {
LOG_ERR("[B]VcsSvcGetFail");
return -ENODEV;
}
/* Reset before the NULL check so a re-init with vcp_included == NULL
* doesn't iterate over stale entries from a previous non-NULL call. */
inc_vocs_svc_count = 0;
inc_aics_svc_count = 0;
vcp_included = vcp_inc;
if (vcp_included) {
if (vcp_included->vocs_cnt > VOCS_INST_COUNT ||
vcp_included->aics_cnt > AICS_INST_COUNT) {
return -EINVAL;
}
inc_vocs_svc_count = vcp_included->vocs_cnt;
inc_aics_svc_count = vcp_included->aics_cnt;
bt_le_bluedroid_set_svc_in_progress(VOCS_IN_PROGRESS);
/* VCS may include zero or more instances of VOCS */
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
inc_vocs_insts[i].svc_p = lib_vocs_svc_get(vcp_included->vocs[i]);
if (!inc_vocs_insts[i].svc_p) {
LOG_ERR("[B]VocsSvcGetFail[%u]", i);
return -ENODEV;
}
/* Reset included before svc_init; see mics.c for rationale. */
inc_vocs_insts[i].included = false;
err = bt_le_bluedroid_svc_init(inc_vocs_insts[i].svc_p);
if (err) {
return err;
}
}
bt_le_bluedroid_set_svc_in_progress(AICS_IN_PROGRESS);
/* VCS may include zero or more instances of AICS */
for (size_t i = 0; i < inc_aics_svc_count; i++) {
inc_aics_insts[i].svc_p = lib_aics_svc_get(vcp_included->aics[i]);
if (!inc_aics_insts[i].svc_p) {
LOG_ERR("[B]AicsSvcGetFail[%u]", i);
return -ENODEV;
}
/* Reset included before svc_init; see mics.c for rationale. */
inc_aics_insts[i].included = false;
err = bt_le_bluedroid_svc_init(inc_aics_insts[i].svc_p);
if (err) {
return err;
}
}
}
bt_le_bluedroid_set_svc_in_progress(VCS_IN_PROGRESS);
err = bt_le_bluedroid_svc_init(vcs_svc);
if (err) {
return err;
}
if (vcs_not_included_inst()) {
LOG_ERR("[B]VcsVocsAicsNotAllInc");
return -EIO;
}
return 0;
}
int bt_le_bluedroid_vcs_start(void)
{
struct bt_gatt_service *vcs_svc;
int err;
LOG_DBG("[B]VcsStart");
vcs_svc = lib_vcs_svc_get();
if (!vcs_svc) {
LOG_ERR("[B]VcsSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(VOCS_IN_PROGRESS);
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
err = bt_le_bluedroid_svc_start(inc_vocs_insts[i].svc_p);
if (err) {
return err;
}
}
bt_le_bluedroid_set_svc_in_progress(AICS_IN_PROGRESS);
for (size_t i = 0; i < inc_aics_svc_count; i++) {
err = bt_le_bluedroid_svc_start(inc_aics_insts[i].svc_p);
if (err) {
return err;
}
}
bt_le_bluedroid_set_svc_in_progress(VCS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(vcs_svc);
}

View File

@@ -0,0 +1,596 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/audio/micp.h>
#include <zephyr/bluetooth/audio/vcp.h>
#include <../host/conn_internal.h>
#include "bta/bta_gatt_api.h"
#include "bluedroid/gatt.h"
#include "bluedroid/server.h"
#include "common/host.h"
LOG_MODULE_REGISTER(LEA_GSRV, CONFIG_BT_ISO_LOG_LEVEL);
static uint8_t svc_in_progress;
static uint16_t inc_svc_handle;
static uint16_t svc_handle;
static uint16_t chrc_handle;
static bool is_primary_svc(void)
{
if (svc_in_progress == ASCS_IN_PROGRESS ||
svc_in_progress == BASS_IN_PROGRESS ||
svc_in_progress == CAS_IN_PROGRESS ||
svc_in_progress == CSIS_IN_PROGRESS ||
svc_in_progress == HAS_IN_PROGRESS ||
svc_in_progress == GMCS_IN_PROGRESS ||
svc_in_progress == MCS_IN_PROGRESS ||
svc_in_progress == MICS_IN_PROGRESS ||
svc_in_progress == PACS_IN_PROGRESS ||
svc_in_progress == GTBS_IN_PROGRESS ||
svc_in_progress == TBS_IN_PROGRESS ||
svc_in_progress == TMAS_IN_PROGRESS ||
svc_in_progress == VCS_IN_PROGRESS) {
return true;
}
return false;
}
static bool is_secondary_svc(void)
{
if (svc_in_progress == AICS_IN_PROGRESS ||
svc_in_progress == VOCS_IN_PROGRESS) {
return true;
}
return false;
}
static bool any_included_svc(void)
{
if (svc_in_progress == CAS_IN_PROGRESS ||
svc_in_progress == MICS_IN_PROGRESS ||
svc_in_progress == VCS_IN_PROGRESS) {
return true;
}
return false;
}
static bool is_svc_uuid_valid(uint16_t uuid)
{
switch (svc_in_progress) {
case AICS_IN_PROGRESS:
return (uuid == BT_UUID_AICS_VAL);
case ASCS_IN_PROGRESS:
return (uuid == BT_UUID_ASCS_VAL);
case BASS_IN_PROGRESS:
return (uuid == BT_UUID_BASS_VAL);
case CAS_IN_PROGRESS:
return (uuid == BT_UUID_CAS_VAL);
case CSIS_IN_PROGRESS:
return (uuid == BT_UUID_CSIS_VAL);
case HAS_IN_PROGRESS:
return (uuid == BT_UUID_HAS_VAL);
case GMCS_IN_PROGRESS:
return (uuid == BT_UUID_GMCS_VAL);
case MCS_IN_PROGRESS:
return (uuid == BT_UUID_MCS_VAL);
case MICS_IN_PROGRESS:
return (uuid == BT_UUID_MICS_VAL);
case PACS_IN_PROGRESS:
return (uuid == BT_UUID_PACS_VAL);
case GTBS_IN_PROGRESS:
return (uuid == BT_UUID_GTBS_VAL);
case TBS_IN_PROGRESS:
return (uuid == BT_UUID_TBS_VAL);
case TMAS_IN_PROGRESS:
return (uuid == BT_UUID_TMAS_VAL);
case VCS_IN_PROGRESS:
return (uuid == BT_UUID_VCS_VAL);
case VOCS_IN_PROGRESS:
return (uuid == BT_UUID_VOCS_VAL);
default:
return false;
}
}
static void svc_create_cb(uint16_t service_id, uint16_t svc_instance,
bool is_primary, uint8_t status, void *uuid)
{
int result = status;
if ((is_primary_svc() && is_primary == false) ||
(is_secondary_svc() && is_primary == true) ||
((tBT_UUID *)uuid)->len != 2 ||
is_svc_uuid_valid(((tBT_UUID *)uuid)->uu.uuid16) == false) {
/* uuid16 is only meaningful when len == 2; for 32/128-bit UUIDs the
* union access would log the first 2 bytes of a wider value. */
if (((tBT_UUID *)uuid)->len == 2) {
LOG_ERR("[B]SvcCreateMismatch[%u][%u][2][0x%04x]",
svc_in_progress, is_primary,
((tBT_UUID *)uuid)->uu.uuid16);
} else {
LOG_ERR("[B]SvcCreateMismatch[%u][%u][%u][nonU16]",
svc_in_progress, is_primary,
((tBT_UUID *)uuid)->len);
}
result = -1;
goto end;
}
svc_handle = service_id;
end:
bt_le_bluedroid_gatts_sem_give(result);
}
static void inc_svc_add_cb(uint16_t service_id, uint16_t attr_id, uint8_t status)
{
int result = status;
if (service_id != svc_handle) {
LOG_ERR("[B]IncSvcAddMismatch[%u][%u][%u]",
svc_in_progress, service_id, svc_handle);
result = -1;
goto end;
}
inc_svc_handle = attr_id;
end:
bt_le_bluedroid_gatts_sem_give(result);
}
static void chrc_add_cb(uint16_t service_id, uint16_t attr_id,
uint8_t status, void *uuid)
{
int result = status;
if (service_id != svc_handle || ((tBT_UUID *)uuid)->len != 2) {
/* uuid16 is only meaningful when len == 2; for 32/128-bit UUIDs the
* union access would log the first 2 bytes of a wider value. */
if (((tBT_UUID *)uuid)->len == 2) {
LOG_ERR("[B]ChrcAddMismatch[%u][%u][%u][2][0x%04x]",
svc_in_progress, service_id, svc_handle,
((tBT_UUID *)uuid)->uu.uuid16);
} else {
LOG_ERR("[B]ChrcAddMismatch[%u][%u][%u][%u][nonU16]",
svc_in_progress, service_id, svc_handle,
((tBT_UUID *)uuid)->len);
}
result = -1;
goto end;
}
chrc_handle = attr_id;
end:
bt_le_bluedroid_gatts_sem_give(result);
}
static void svc_start_cb(uint16_t service_id, uint8_t status)
{
int result = status;
if (service_id != svc_handle) {
LOG_ERR("[B]SvcStartMismatch[%u][%u]", service_id, svc_handle);
result = -1;
}
bt_le_bluedroid_gatts_sem_give(result);
}
static struct gatts_svc_cb svc_cb = {
.svc_create_cb = svc_create_cb,
.inc_svc_add_cb = inc_svc_add_cb,
.chrc_add_cb = chrc_add_cb,
.svc_start_cb = svc_start_cb,
};
void bt_le_bluedroid_audio_gatts_init(void)
{
bt_le_bluedroid_gatts_svc_cb_register(&svc_cb);
}
int bt_le_bluedroid_set_svc_in_progress(uint8_t value)
{
if (value >= MAX_IN_PROGRESS) {
LOG_ERR("[B]SvcInProgressInv[%u]", value);
return -1;
}
svc_in_progress = value;
return 0;
}
static bool is_svc_attr_uuid_valid(uint16_t attr_uuid)
{
switch (svc_in_progress) {
case AICS_IN_PROGRESS:
return (attr_uuid == BT_UUID_AICS_STATE_VAL ||
attr_uuid == BT_UUID_AICS_GAIN_SETTINGS_VAL ||
attr_uuid == BT_UUID_AICS_INPUT_TYPE_VAL ||
attr_uuid == BT_UUID_AICS_INPUT_STATUS_VAL ||
attr_uuid == BT_UUID_AICS_CONTROL_VAL ||
attr_uuid == BT_UUID_AICS_DESCRIPTION_VAL);
case ASCS_IN_PROGRESS:
return (attr_uuid == BT_UUID_ASCS_ASE_SNK_VAL ||
attr_uuid == BT_UUID_ASCS_ASE_SRC_VAL ||
attr_uuid == BT_UUID_ASCS_ASE_CP_VAL);
case BASS_IN_PROGRESS:
return (attr_uuid == BT_UUID_BASS_CONTROL_POINT_VAL ||
attr_uuid == BT_UUID_BASS_RECV_STATE_VAL);
case CAS_IN_PROGRESS:
/* No characteristic in CAS */
return false;
case CSIS_IN_PROGRESS:
return (attr_uuid == BT_UUID_CSIS_SIRK_VAL ||
attr_uuid == BT_UUID_CSIS_SET_SIZE_VAL ||
attr_uuid == BT_UUID_CSIS_SET_LOCK_VAL ||
attr_uuid == BT_UUID_CSIS_RANK_VAL);
case HAS_IN_PROGRESS:
return (attr_uuid == BT_UUID_HAS_HEARING_AID_FEATURES_VAL ||
attr_uuid == BT_UUID_HAS_PRESET_CONTROL_POINT_VAL ||
attr_uuid == BT_UUID_HAS_ACTIVE_PRESET_INDEX_VAL);
case GMCS_IN_PROGRESS:
case MCS_IN_PROGRESS:
return (attr_uuid == BT_UUID_MCS_PLAYER_NAME_VAL ||
attr_uuid == BT_UUID_MCS_ICON_OBJ_ID_VAL ||
attr_uuid == BT_UUID_MCS_ICON_URL_VAL ||
attr_uuid == BT_UUID_MCS_TRACK_CHANGED_VAL ||
attr_uuid == BT_UUID_MCS_TRACK_TITLE_VAL ||
attr_uuid == BT_UUID_MCS_TRACK_DURATION_VAL ||
attr_uuid == BT_UUID_MCS_TRACK_POSITION_VAL ||
attr_uuid == BT_UUID_MCS_PLAYBACK_SPEED_VAL ||
attr_uuid == BT_UUID_MCS_SEEKING_SPEED_VAL ||
attr_uuid == BT_UUID_MCS_TRACK_SEGMENTS_OBJ_ID_VAL ||
attr_uuid == BT_UUID_MCS_CURRENT_TRACK_OBJ_ID_VAL ||
attr_uuid == BT_UUID_MCS_NEXT_TRACK_OBJ_ID_VAL ||
attr_uuid == BT_UUID_MCS_PARENT_GROUP_OBJ_ID_VAL ||
attr_uuid == BT_UUID_MCS_CURRENT_GROUP_OBJ_ID_VAL ||
attr_uuid == BT_UUID_MCS_PLAYING_ORDER_VAL ||
attr_uuid == BT_UUID_MCS_PLAYING_ORDERS_VAL ||
attr_uuid == BT_UUID_MCS_MEDIA_STATE_VAL ||
attr_uuid == BT_UUID_MCS_MEDIA_CONTROL_POINT_VAL ||
attr_uuid == BT_UUID_MCS_MEDIA_CONTROL_OPCODES_VAL ||
attr_uuid == BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID_VAL ||
attr_uuid == BT_UUID_MCS_SEARCH_CONTROL_POINT_VAL ||
attr_uuid == BT_UUID_CCID_VAL);
case MICS_IN_PROGRESS:
return (attr_uuid == BT_UUID_MICS_MUTE_VAL);
case PACS_IN_PROGRESS:
return (attr_uuid == BT_UUID_PACS_SNK_VAL ||
attr_uuid == BT_UUID_PACS_SNK_LOC_VAL ||
attr_uuid == BT_UUID_PACS_SRC_VAL ||
attr_uuid == BT_UUID_PACS_SRC_LOC_VAL ||
attr_uuid == BT_UUID_PACS_AVAILABLE_CONTEXT_VAL ||
attr_uuid == BT_UUID_PACS_SUPPORTED_CONTEXT_VAL);
case GTBS_IN_PROGRESS:
case TBS_IN_PROGRESS:
return (attr_uuid == BT_UUID_TBS_PROVIDER_NAME_VAL ||
attr_uuid == BT_UUID_TBS_UCI_VAL ||
attr_uuid == BT_UUID_TBS_TECHNOLOGY_VAL ||
attr_uuid == BT_UUID_TBS_URI_LIST_VAL ||
attr_uuid == BT_UUID_TBS_SIGNAL_STRENGTH_VAL ||
attr_uuid == BT_UUID_TBS_SIGNAL_INTERVAL_VAL ||
attr_uuid == BT_UUID_TBS_LIST_CURRENT_CALLS_VAL ||
attr_uuid == BT_UUID_CCID_VAL ||
attr_uuid == BT_UUID_TBS_STATUS_FLAGS_VAL ||
attr_uuid == BT_UUID_TBS_INCOMING_URI_VAL ||
attr_uuid == BT_UUID_TBS_CALL_STATE_VAL ||
attr_uuid == BT_UUID_TBS_CALL_CONTROL_POINT_VAL ||
attr_uuid == BT_UUID_TBS_OPTIONAL_OPCODES_VAL ||
attr_uuid == BT_UUID_TBS_TERMINATE_REASON_VAL ||
attr_uuid == BT_UUID_TBS_INCOMING_CALL_VAL ||
attr_uuid == BT_UUID_TBS_FRIENDLY_NAME_VAL);
case TMAS_IN_PROGRESS:
return (attr_uuid == BT_UUID_GATT_TMAPR_VAL);
case VCS_IN_PROGRESS:
return (attr_uuid == BT_UUID_VCS_STATE_VAL ||
attr_uuid == BT_UUID_VCS_CONTROL_VAL ||
attr_uuid == BT_UUID_VCS_FLAGS_VAL);
case VOCS_IN_PROGRESS:
return (attr_uuid == BT_UUID_VOCS_STATE_VAL ||
attr_uuid == BT_UUID_VOCS_LOCATION_VAL ||
attr_uuid == BT_UUID_VOCS_CONTROL_VAL ||
attr_uuid == BT_UUID_VOCS_DESCRIPTION_VAL);
default:
return false;
}
}
static struct inc_svc_inst *get_not_included_inst(void)
{
switch (svc_in_progress) {
#if CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER
case CAS_IN_PROGRESS:
extern struct inc_svc_inst *cas_not_included_inst(void);
return cas_not_included_inst();
#endif /* CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER */
#if CONFIG_BT_MICP_MIC_DEV
case MICS_IN_PROGRESS:
extern struct inc_svc_inst *mics_not_included_inst(void);
return mics_not_included_inst();
#endif /* CONFIG_BT_MICP_MIC_DEV */
#if CONFIG_BT_VCP_VOL_REND
case VCS_IN_PROGRESS:
extern struct inc_svc_inst *vcs_not_included_inst(void);
return vcs_not_included_inst();
#endif /* CONFIG_BT_VCP_VOL_REND */
default:
return NULL;
}
}
static uint8_t get_svc_inst_id(uint16_t svc_uuid)
{
/* Note:
* For LE Audio, some service could have multiple instances.
*/
static uint8_t aics_count;
static uint8_t csis_count;
static uint8_t vocs_count;
static uint8_t mcs_count;
static uint8_t ots_count;
static uint8_t tbs_count;
switch (svc_uuid) {
case BT_UUID_AICS_VAL:
return aics_count++;
case BT_UUID_CSIS_VAL:
return csis_count++;
case BT_UUID_VOCS_VAL:
return vocs_count++;
case BT_UUID_MCS_VAL:
return mcs_count++;
case BT_UUID_OTS_VAL:
return ots_count++;
case BT_UUID_TBS_VAL:
return tbs_count++;
default:
return 0;
}
}
int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
{
struct bt_gatt_attr *curr_attr;
struct bt_gatt_attr *next_attr;
struct inc_svc_inst *inc_inst;
struct bt_gatt_chrc *chrc;
tBTA_GATT_PERM perm;
uint16_t attr_uuid;
uint8_t inst_id;
tBT_UUID uuid;
assert(svc);
for (size_t i = 0; i < svc->attr_count; i++) {
curr_attr = &svc->attrs[i];
attr_uuid = BT_UUID_16(curr_attr->uuid)->val;
switch (attr_uuid) {
case BT_UUID_GATT_PRIMARY_VAL:
if (is_primary_svc() == false) {
LOG_ERR("[B]NotCreatingPrimary");
return -1;
}
assert(curr_attr->user_data);
bt_le_bluedroid_gatt_uuid_convert(curr_attr->user_data, &uuid);
inst_id = get_svc_inst_id(uuid.uu.uuid16);
bt_le_bluedroid_gatts_sem_reset();
BTA_GATTS_CreateService(bt_le_bluedroid_gatts_get_if(), &uuid, inst_id, svc->attr_count, true);
if (bt_le_bluedroid_gatts_sem_take()) {
LOG_ERR("[B]PrimaryCreateFail");
return -1;
}
curr_attr->handle = svc_handle;
LOG_INF("[B]PrimarySvc[%s][0x%04x][%u][0x%04x][%d]",
audio_svc_uuid_to_str(uuid.uu.uuid16), uuid.uu.uuid16,
inst_id, curr_attr->perm, svc_handle);
break;
case BT_UUID_GATT_SECONDARY_VAL:
if (is_secondary_svc() == false) {
LOG_ERR("[B]NotCreatingSecondary");
return -1;
}
assert(curr_attr->user_data);
bt_le_bluedroid_gatt_uuid_convert(curr_attr->user_data, &uuid);
inst_id = get_svc_inst_id(uuid.uu.uuid16);
bt_le_bluedroid_gatts_sem_reset();
BTA_GATTS_CreateService(bt_le_bluedroid_gatts_get_if(), &uuid, inst_id, svc->attr_count, false);
if (bt_le_bluedroid_gatts_sem_take()) {
LOG_ERR("[B]SecondaryCreateFail");
return -1;
}
curr_attr->handle = svc_handle;
LOG_INF("[B]SecondarySvc[%s][0x%04x][%u][0x%04x][%d]",
audio_svc_uuid_to_str(uuid.uu.uuid16), uuid.uu.uuid16,
inst_id, curr_attr->perm, svc_handle);
break;
case BT_UUID_GATT_INCLUDE_VAL:
if (any_included_svc() == false) {
LOG_ERR("[B]NoSvcToInc[%u]", svc_in_progress);
return -1;
}
inc_inst = get_not_included_inst();
if (inc_inst == NULL) {
LOG_ERR("[B]IncSvcNotFound[%u]", svc_in_progress);
return -1;
}
bt_le_bluedroid_gatts_sem_reset();
BTA_GATTS_AddIncludeService(svc_handle, inc_inst->svc_p->attrs[0].handle);
if (bt_le_bluedroid_gatts_sem_take()) {
LOG_ERR("[B]IncSvcAddFail");
return -1;
}
curr_attr->handle = inc_svc_handle;
inc_inst->included = true; /* Mark the svc as included */
LOG_INF("[B]IncSvc[%s][0x%04x][0x%04x][%d]",
audio_svc_uuid_to_str(BT_UUID_16(inc_inst->svc_p->attrs[0].user_data)->val),
BT_UUID_16(inc_inst->svc_p->attrs[0].user_data)->val,
curr_attr->perm, inc_svc_handle);
break;
case BT_UUID_GATT_CHRC_VAL:
/* Chrc declaration must be followed by its value attribute.
* Standard BT_GATT_CHARACTERISTIC pairs them, but guard against
* a malformed table where it is the last entry. */
if (i + 1 >= svc->attr_count) {
LOG_ERR("[B]ChrcDeclLastAttr[%u]", svc_in_progress);
return -1;
}
next_attr = &svc->attrs[i + 1];
perm = bt_le_bluedroid_gatt_perm_convert(next_attr->perm);
assert(curr_attr->user_data);
chrc = curr_attr->user_data;
bt_le_bluedroid_gatt_uuid_convert(chrc->uuid, &uuid);
bt_le_bluedroid_gatts_sem_reset();
BTA_GATTS_AddCharacteristic(svc_handle, &uuid, perm, chrc->properties, NULL, NULL);
if (bt_le_bluedroid_gatts_sem_take()) {
LOG_ERR("[B]ChrcAddFail");
return -1;
}
/* Characteristic declaration handle and Characteristic value handle */
curr_attr->handle = chrc_handle - 1;
next_attr->handle = chrc_handle;
LOG_INF("[B]Chrc[%s][0x%04x][%u][%u][0x%04x][0x%02x]",
audio_chrc_uuid_to_str(uuid.uu.uuid16), uuid.uu.uuid16,
chrc_handle - 1, chrc_handle, next_attr->perm, chrc->properties);
break;
case BT_UUID_GATT_CCC_VAL:
perm = bt_le_bluedroid_gatt_perm_convert(curr_attr->perm);
bt_le_bluedroid_gatt_uuid_convert(curr_attr->uuid, &uuid);
bt_le_bluedroid_gatts_sem_reset();
BTA_GATTS_AddCharDescriptor(svc_handle, perm, &uuid, NULL, NULL);
if (bt_le_bluedroid_gatts_sem_take()) {
LOG_ERR("[B]ChrcCccdAddFail");
return -1;
}
curr_attr->handle = chrc_handle;
LOG_INF("[B]ChrcCccd[%u][0x%04x]", chrc_handle, curr_attr->perm);
break;
default:
if (is_svc_attr_uuid_valid(attr_uuid) == false) {
LOG_ERR("[B]InvSvcAttrUuid[0x%04x][%u]", attr_uuid, svc_in_progress);
return -1;
}
break;
}
}
return 0;
}
int bt_le_bluedroid_svc_start(struct bt_gatt_service *svc)
{
assert(svc);
svc_handle = svc->attrs[0].handle;
/* App may not register this svc (e.g. CAP Acceptor single mode keeps
* unused capability built). Skip rather than fail audio_start.
*/
if (svc_handle == 0) {
LOG_DBG("[B]SvcNotInit[%u]", svc_in_progress);
return 0;
}
bt_le_bluedroid_gatts_sem_reset();
BTA_GATTS_StartService(svc_handle, BTA_GATT_TRANSPORT_LE);
if (bt_le_bluedroid_gatts_sem_take()) {
LOG_ERR("[B]SvcStartFail");
return -1;
}
return 0;
}

View File

@@ -313,13 +313,18 @@ static int nimble_gatt_csis_init(struct bt_le_audio_start_info *info,
}
csis_svc[count] = lib_csip_set_member_svc_get(info->csis_insts[i].svc_inst);
assert(csis_svc[count]);
if (!csis_svc[count]) {
LOG_ERR("[N]CsisSvcGetFail[%u]", i);
return -ENODEV;
}
if (info->csis_insts[i].included_by_cas) {
if (*inc_csis_svc == NULL) {
*inc_csis_svc = csis_svc[count];
} else {
assert(0);
/* CAS may include at most one CSIS — caller misconfigured. */
LOG_ERR("[N]CsisMultiIncByCas");
return -EINVAL;
}
}

View File

@@ -23,7 +23,7 @@
#include "host/ble_gatt.h"
#include "host/ble_hs_mbuf.h"
#include "nimble/profiles/server.h"
#include "nimble/server.h"
#include "common/host.h"
@@ -122,7 +122,10 @@ int bt_le_nimble_ascs_attr_handle_set(void)
}
ascs_svc = lib_ascs_svc_get();
assert(ascs_svc);
if (!ascs_svc) {
LOG_ERR("[N]AscsSvcGetFail");
return -ENODEV;
}
assert(ase_control_point_handle >= 2);
start_handle = ase_control_point_handle - 2; /* server attr handle & char def handle */
@@ -164,7 +167,10 @@ static int ascs_svc_check(void)
*/
ascs_svc = lib_ascs_svc_get();
assert(ascs_svc);
if (!ascs_svc) {
LOG_ERR("[N]AscsSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]AscsSvcCheck");

View File

@@ -23,7 +23,7 @@
#include "host/ble_gatt.h"
#include "host/ble_hs_mbuf.h"
#include "nimble/profiles/server.h"
#include "nimble/server.h"
#include "common/host.h"
@@ -98,7 +98,10 @@ int bt_le_nimble_bass_attr_handle_set(void)
}
bass_svc = lib_bap_bass_svc_get();
assert(bass_svc);
if (!bass_svc) {
LOG_ERR("[N]BassSvcGetFail");
return -ENODEV;
}
assert(bass_control_point_handle >= 2);
start_handle = bass_control_point_handle - 2; /* server attr handle & char def handle */
@@ -134,7 +137,10 @@ static int bass_svc_check(void)
*/
bass_svc = lib_bap_bass_svc_get();
assert(bass_svc);
if (!bass_svc) {
LOG_ERR("[N]BassSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]BassSvcCheck");

View File

@@ -60,7 +60,10 @@ int bt_le_nimble_cas_attr_handle_set(void)
}
cas_svc = lib_cas_svc_get();
assert(cas_svc);
if (!cas_svc) {
LOG_ERR("[N]CasSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]CasAttrHdlSet[%u][%u]", handle, cas_svc->attr_count);
@@ -113,7 +116,10 @@ int bt_le_nimble_cas_init(void *csis_svc_p)
struct bt_gatt_service *cas_svc;
cas_svc = lib_cas_svc_get();
assert(cas_svc);
if (!cas_svc) {
LOG_ERR("[N]CasSvcGetFail");
return -ENODEV;
}
/* Insert CAS to the GATT db list */
rc = bt_gatt_service_register_safe(cas_svc);

View File

@@ -25,7 +25,7 @@
#include "host/ble_gatt.h"
#include "host/ble_hs_mbuf.h"
#include "nimble/profiles/server.h"
#include "nimble/server.h"
#include "common/host.h"

View File

@@ -23,7 +23,7 @@
#include "host/ble_gatt.h"
#include "host/ble_hs_mbuf.h"
#include "nimble/profiles/server.h"
#include "nimble/server.h"
#include "common/host.h"
@@ -120,7 +120,10 @@ int bt_le_nimble_has_attr_handle_set(void)
}
has_svc = lib_has_svc_get();
assert(has_svc);
if (!has_svc) {
LOG_ERR("[N]HasSvcGetFail");
return -ENODEV;
}
assert(has_svc->attr_count > 0);
end_handle = start_handle + has_svc->attr_count - 1;
@@ -136,7 +139,7 @@ int bt_le_nimble_has_attr_handle_set(void)
attr = has_svc->attrs + has_svc->attr_count - 1;
if (attr->handle != end_handle) {
LOG_ERR("[N]HasMismatchAttrHdl (%u %u %u %u)",
LOG_ERR("[N]HasMismatchAttrHdl[%u][%u][%u][%u]",
start_handle, end_handle, attr->handle, has_svc->attr_count);
return -1;
}
@@ -155,7 +158,10 @@ static int has_svc_check(void)
*/
has_svc = lib_has_svc_get();
assert(has_svc);
if (!has_svc) {
LOG_ERR("[N]HasSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]HasSvcCheck");

View File

@@ -28,7 +28,7 @@
#include "host/ble_gatt.h"
#include "host/ble_hs_mbuf.h"
#include "nimble/profiles/server.h"
#include "nimble/server.h"
#include "common/host.h"
@@ -403,7 +403,10 @@ int bt_le_nimble_gmcs_attr_handle_set(void)
}
gmcs_svc = lib_mcs_svc_get();
assert(gmcs_svc);
if (!gmcs_svc) {
LOG_ERR("[N]GmcsSvcGetFail");
return -ENODEV;
}
end_handle = start_handle + gmcs_svc->attr_count - 1;
@@ -446,7 +449,10 @@ static int gmcs_svc_check(void)
*/
gmcs_svc = lib_mcs_svc_get();
assert(gmcs_svc);
if (!gmcs_svc) {
LOG_ERR("[N]GmcsSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]GmcsSvcCheck");

View File

@@ -23,7 +23,7 @@
#include "host/ble_gatt.h"
#include "host/ble_hs_mbuf.h"
#include "nimble/profiles/server.h"
#include "nimble/server.h"
#include "common/host.h"
@@ -159,7 +159,10 @@ static int mics_svc_check(void)
bool chr_found;
mics_svc = lib_mics_svc_get();
assert(mics_svc);
if (!mics_svc) {
LOG_ERR("[N]MicsSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]MicsSvcCheck");
@@ -244,7 +247,10 @@ int bt_le_nimble_mics_attr_handle_set(void)
}
mics_svc = lib_mics_svc_get();
assert(mics_svc);
if (!mics_svc) {
LOG_ERR("[N]MicsSvcGetFail");
return -ENODEV;
}
end_handle = start_handle + mics_svc->attr_count - 1;
@@ -379,6 +385,11 @@ int bt_le_nimble_mics_init(void *micp_inc)
inc_aics_svc_init(&inc_aics_insts[i], &gatt_svc_inc_aics[i]);
inc_aics_insts[i].svc_p = lib_aics_svc_get(micp_included->aics[i]);
if (!inc_aics_insts[i].svc_p) {
LOG_ERR("[N]AicsSvcGetFail[%u]", i);
rc = -ENODEV;
goto free;
}
mics_inc_svcs[i] = &gatt_svc_inc_aics[i];
}

View File

@@ -21,7 +21,7 @@
#include "host/ble_gatt.h"
#include "host/ble_hs_mbuf.h"
#include "nimble/profiles/server.h"
#include "nimble/server.h"
#include "common/host.h"
@@ -166,7 +166,10 @@ int bt_le_nimble_pacs_attr_handle_set(void)
uint16_t end_handle = 0;
pacs_svc = lib_pacs_svc_get();
assert(pacs_svc);
if (!pacs_svc) {
LOG_ERR("[N]PacsSvcGetFail");
return -ENODEV;
}
#if CONFIG_BT_PAC_SNK
assert(pacs_snk_handle >= 2);
@@ -214,7 +217,10 @@ static int pacs_svc_check(void)
*/
pacs_svc = lib_pacs_svc_get();
assert(pacs_svc);
if (!pacs_svc) {
LOG_ERR("[N]PacsSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]PacsSvcCheck");

View File

@@ -21,7 +21,7 @@
#include "host/ble_gatt.h"
#include "host/ble_hs_mbuf.h"
#include "nimble/profiles/server.h"
#include "nimble/server.h"
#include "common/host.h"
@@ -178,7 +178,10 @@ int bt_le_nimble_gtbs_attr_handle_set(void)
}
gtbs_svc = lib_gtbs_svc_get();
assert(gtbs_svc);
if (!gtbs_svc) {
LOG_ERR("[N]GtbsSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]GtbsAttrHdlSet[%u][%u]", handle, gtbs_svc->attr_count);
@@ -200,7 +203,10 @@ static int gtbs_svc_check(void)
*/
gtbs_svc = lib_gtbs_svc_get();
assert(gtbs_svc);
if (!gtbs_svc) {
LOG_ERR("[N]GtbsSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]GtbsSvcCheck");

View File

@@ -21,7 +21,7 @@
#include "host/ble_gatt.h"
#include "host/ble_hs_mbuf.h"
#include "nimble/profiles/server.h"
#include "nimble/server.h"
#include "common/host.h"
@@ -71,7 +71,10 @@ int bt_le_nimble_tmas_attr_handle_set(void)
}
tmas_svc = lib_tmas_svc_get();
assert(tmas_svc);
if (!tmas_svc) {
LOG_ERR("[N]TmasSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]TmasAttrHdlSet[%u][%u]", handle, tmas_svc->attr_count);
@@ -93,7 +96,10 @@ static int tmas_svc_check(void)
*/
tmas_svc = lib_tmas_svc_get();
assert(tmas_svc);
if (!tmas_svc) {
LOG_ERR("[N]TmasSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]TmasSvcCheck");

View File

@@ -23,7 +23,7 @@
#include "host/ble_gatt.h"
#include "host/ble_hs_mbuf.h"
#include "nimble/profiles/server.h"
#include "nimble/server.h"
#include "common/host.h"
@@ -261,7 +261,10 @@ static int vcs_svc_check(void)
*/
vcs_svc = lib_vcs_svc_get();
assert(vcs_svc);
if (!vcs_svc) {
LOG_ERR("[N]VcsSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]VCSSvcCheck");
@@ -380,7 +383,10 @@ int bt_le_nimble_vcs_attr_handle_set(void)
}
vcs_svc = lib_vcs_svc_get();
assert(vcs_svc);
if (!vcs_svc) {
LOG_ERR("[N]VcsSvcGetFail");
return -ENODEV;
}
end_handle = start_handle + vcs_svc->attr_count - 1;
@@ -576,6 +582,11 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
inc_vocs_svc_init(&inc_vocs_insts[i], &gatt_svc_inc_vocs[i]);
inc_vocs_insts[i].svc_p = lib_vocs_svc_get(vcp_included->vocs[i]);
if (!inc_vocs_insts[i].svc_p) {
LOG_ERR("[N]VocsSvcGetFail[%u]", i);
rc = -ENODEV;
goto free;
}
vcs_inc_svcs[i] = &gatt_svc_inc_vocs[i];
}
@@ -608,6 +619,11 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
inc_aics_svc_init(&inc_aics_insts[i], &gatt_svc_inc_aics[i]);
inc_aics_insts[i].svc_p = lib_aics_svc_get(vcp_included->aics[i]);
if (!inc_aics_insts[i].svc_p) {
LOG_ERR("[N]AicsSvcGetFail[%u]", i);
rc = -ENODEV;
goto free;
}
vcs_inc_svcs[inc_vocs_svc_count + i] = &gatt_svc_inc_aics[i];
}
@@ -670,13 +686,17 @@ free:
}
if (inc_aics_svc_count) {
for (size_t i = 0; i < inc_aics_svc_count; i++) {
free((void *)gatt_svc_inc_aics[i].characteristics);
gatt_svc_inc_aics[i].characteristics = NULL;
}
/* A VOCS-phase failure reaches here with the count already set
* but gatt_svc_inc_aics not yet allocated (still NULL). */
if (gatt_svc_inc_aics) {
for (size_t i = 0; i < inc_aics_svc_count; i++) {
free((void *)gatt_svc_inc_aics[i].characteristics);
gatt_svc_inc_aics[i].characteristics = NULL;
}
free(gatt_svc_inc_aics);
gatt_svc_inc_aics = NULL;
free(gatt_svc_inc_aics);
gatt_svc_inc_aics = NULL;
}
inc_aics_svc_count = 0;
}

View File

@@ -44,7 +44,11 @@
#include <../host/conn_internal.h>
#include <../host/hci_core.h>
#if CONFIG_BT_BLUEDROID_ENABLED
#include "bluedroid/init.h"
#else
#include "nimble/init.h"
#endif
#include "../../../lib/include/audio.h"
@@ -2091,7 +2095,11 @@ int bt_le_audio_init(void)
return err;
}
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_audio_init();
#else
return bt_le_nimble_audio_init();
#endif
}
#if BLE_AUDIO_SVC_DEFERRED_ADD
@@ -2100,7 +2108,11 @@ int bt_le_ascs_init(void)
{
LOG_DBG("AscsInit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_ascs_init();
#else
return bt_le_nimble_ascs_init();
#endif
}
#endif /* CONFIG_BT_ASCS */
@@ -2109,7 +2121,11 @@ int bt_le_bass_init(void)
{
LOG_DBG("BassInit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_bass_init();
#else
return bt_le_nimble_bass_init();
#endif
}
#endif /* CONFIG_BT_BAP_SCAN_DELEGATOR */
@@ -2118,7 +2134,11 @@ int bt_le_tmas_init(void)
{
LOG_DBG("TmasInit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_tmas_init();
#else
return bt_le_nimble_tmas_init();
#endif
}
#endif /* CONFIG_BT_TMAP */
@@ -2127,7 +2147,11 @@ int bt_le_gtbs_init(void)
{
LOG_DBG("GtbsInit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_gtbs_init();
#else
return bt_le_nimble_gtbs_init();
#endif
}
#endif /* CONFIG_BT_TBS */
@@ -2136,7 +2160,11 @@ int bt_le_has_init(void)
{
LOG_DBG("HasInit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_has_init();
#else
return bt_le_nimble_has_init();
#endif
}
#endif /* CONFIG_BT_HAS */
@@ -2145,7 +2173,11 @@ int bt_le_media_proxy_pl_init(void)
{
LOG_DBG("MprxPlInit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_media_proxy_pl_init();
#else
return bt_le_nimble_media_proxy_pl_init();
#endif
}
#endif /* CONFIG_BT_MCS */
@@ -2154,7 +2186,11 @@ int bt_le_vcp_vol_rend_init(void)
{
LOG_DBG("VcpVolRendInit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_vcp_vol_rend_init();
#else
return bt_le_nimble_vcp_vol_rend_init();
#endif
}
#endif /* CONFIG_BT_VCP_VOL_REND */
@@ -2163,7 +2199,11 @@ int bt_le_micp_mic_dev_init(void)
{
LOG_DBG("MicpMicDevInit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_micp_mic_dev_init();
#else
return bt_le_nimble_micp_mic_dev_init();
#endif
}
#endif /* CONFIG_BT_MICP_MIC_DEV */
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
@@ -2172,7 +2212,11 @@ int bt_le_audio_start(void *info)
{
LOG_DBG("AudioStart");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_audio_start(info);
#else
return bt_le_nimble_audio_start(info);
#endif
}
void ble_audio_lib_compressed_out(uint8_t log_level, uint32_t log_index, size_t arg_cnt, ...)

View File

@@ -15,12 +15,24 @@ if(CONFIG_BT_CONTROLLER_ONLY OR NOT CONFIG_BT_ISO)
return()
endif()
if(CONFIG_BT_NIMBLE_ENABLED)
list(APPEND ble_iso_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/gatt/gatt.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/gatt/gatt.db.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/gatt/gatt.nrp.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/gap.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/iso.c"
)
elseif(CONFIG_BT_BLUEDROID_ENABLED)
list(APPEND ble_iso_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/bluedroid/hci.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/bluedroid/iso.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/bluedroid/gap.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/bluedroid/gatt/gatt.c"
)
endif()
list(APPEND ble_iso_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/gatt/gatt.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/gatt/gatt.db.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/gatt/gatt.nrp.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/gap.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/iso.c"
"${CMAKE_CURRENT_LIST_DIR}/host/common/adv.c"
"${CMAKE_CURRENT_LIST_DIR}/host/common/conn.c"
"${CMAKE_CURRENT_LIST_DIR}/host/common/gatt.c"
@@ -45,23 +57,37 @@ list(APPEND ble_iso_srcs
)
# L2CAP host shim wiring is currently only consumed by the OTS service
# (which lives in esp_ble_audio). Keep the same gate so build output
# matches the pre-split behavior; the .c files belong to the host shim.
# (which lives in esp_ble_audio). common/l2cap.c has both host branches
# (Bluedroid returns -ENOTSUP), so compile it under either host whenever
# OTS is enabled; the NimBLE-only adapter glue stays NimBLE-gated.
if(CONFIG_BT_OTS OR CONFIG_BT_OTS_CLIENT)
list(APPEND ble_iso_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/common/l2cap.c"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/l2cap.c"
)
if(CONFIG_BT_NIMBLE_ENABLED)
list(APPEND ble_iso_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/l2cap.c"
)
endif()
endif()
list(APPEND ble_iso_include_dirs
"${CMAKE_CURRENT_LIST_DIR}/api/include"
"${CMAKE_CURRENT_LIST_DIR}/host/common/include"
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/include"
"${CMAKE_CURRENT_LIST_DIR}/include/subsys/bluetooth"
"${CMAKE_CURRENT_LIST_DIR}/include/subsys/bluetooth/host"
"${CMAKE_CURRENT_LIST_DIR}/include"
)
if(CONFIG_BT_NIMBLE_ENABLED)
list(APPEND ble_iso_include_dirs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/include"
)
elseif(CONFIG_BT_BLUEDROID_ENABLED)
list(APPEND ble_iso_include_dirs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/bluedroid/include"
)
endif()
set(ble_iso_srcs "${ble_iso_srcs}" PARENT_SCOPE)
set(ble_iso_include_dirs "${ble_iso_include_dirs}" PARENT_SCOPE)

View File

@@ -6,6 +6,7 @@
config BT_ISO
bool
select BT_NIMBLE_ISO if BT_NIMBLE_ENABLED
select BT_BLE_FEAT_ISO_EN if BT_BLUEDROID_ENABLED
config BT_ISO_TX
bool
@@ -44,7 +45,6 @@ config BT_ISO_BROADCASTER
bool "Bluetooth Isochronous Broadcaster Support"
select BT_ISO_BROADCAST
select BT_ISO_TX
select BT_BROADCASTER
help
This option enables support for the Bluetooth Isochronous Broadcaster.
@@ -68,6 +68,7 @@ if BT_ISO
config BT_ISO_TEST_PARAMS
bool "ISO test parameters support"
default y if BT_BLUEDROID_ENABLED
default BT_NIMBLE_ISO_TEST if BT_NIMBLE_ENABLED
help
Enabling advanced ISO parameters will allow the use of the ISO test
@@ -79,7 +80,10 @@ if BT_ISO
config BT_ISO_MAX_CIG
int "Maximum number of Connected Isochronous Groups (CIGs) to support"
range 1 BT_NIMBLE_ISO_CIG
range 1 1 if BT_BLUEDROID_ENABLED
range 1 BT_NIMBLE_ISO_CIG if BT_NIMBLE_ENABLED
default 1 if BT_BLUEDROID_ENABLED
default BT_NIMBLE_ISO_CIG if BT_NIMBLE_ENABLED
help
Maximum number of CIGs that are supported by the host. A CIG can be
used for either transmitting or receiving.
@@ -90,7 +94,10 @@ if BT_ISO
config BT_ISO_MAX_BIG
int "Maximum number of Broadcast Isochronous Groups (BIGs) to support"
range 1 BT_NIMBLE_ISO_BIG
range 1 1 if BT_BLUEDROID_ENABLED
range 1 BT_NIMBLE_ISO_BIG if BT_NIMBLE_ENABLED
default 1 if BT_BLUEDROID_ENABLED
default BT_NIMBLE_ISO_BIG if BT_NIMBLE_ENABLED
help
Maximum number of BIGs that are supported by the host. A BIG can be
used for either transmitting or receiving, but not at the same time.

View File

@@ -430,3 +430,10 @@ unregister_gap:
}
return ESP_FAIL;
}
#if CONFIG_BT_BLUEDROID_ENABLED
uint8_t esp_ble_iso_bluedroid_get_gattc_if(void)
{
return bt_le_bluedroid_gattc_get_if();
}
#endif /* CONFIG_BT_BLUEDROID_ENABLED */

View File

@@ -591,6 +591,21 @@ void esp_ble_iso_gap_app_post_event(uint8_t type, void *param);
*/
esp_err_t esp_ble_iso_common_init(esp_ble_iso_init_info_t *info);
#if CONFIG_BT_BLUEDROID_ENABLED
/**
* @brief Get the engine's internal GATTC interface handle (Bluedroid only).
*
* Pass this to esp_ble_gattc_aux_open() / esp_ble_gattc_open() so the
* resulting ACL events route back to the engine, avoiding the need for the
* application to register a second BTA GATTC app for connection initiation.
*
* @return Engine's gattc_if (ABI-compatible with esp_gatt_if_t), or
* ESP_GATT_IF_NONE (0xFF) if GATTC registration has not completed —
* callers must bail rather than pass it to aux_open.
*/
uint8_t esp_ble_iso_bluedroid_get_gattc_if(void);
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,506 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/buf.h>
#include <zephyr/bluetooth/hci.h>
#include <../host/hci_core.h>
#include <../host/iso_internal.h>
#include "bta/bta_api.h"
#include "stack/btm_ble_api.h"
#include "stack/hcidefs.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatts_api.h"
#include "bluedroid/btm_error.h"
#include "bluedroid/hci.h"
#include "common/host.h"
#include "common/app/gap.h"
LOG_MODULE_REGISTER(ISO_BGAP, CONFIG_BT_ISO_LOG_LEVEL);
extern void btc_ble_5_gap_callback(tBTA_DM_BLE_5_GAP_EVENT event,
tBTA_DM_BLE_5_GAP_CB_PARAMS *params);
#if (BLE_50_EXTEND_SYNC_EN == TRUE)
/* Active PA sync handle tracker. Used to synthesize a PA_SYNC_LOST event
* to the iso task when the application calls
* esp_ble_gap_periodic_adv_sync_terminate(): Bluedroid's
* BTM_BLE_5_GAP_PERIODIC_ADV_SYNC_TERMINATE_COMPLETE_EVT only carries a
* status byte and lacks sync_handle, so we recover it from here. This
* mirrors NimBLE host behavior, where ble_gap_periodic_adv_sync_terminate
* internally enqueues a sync_lost event.
*
* The tracker is populated by both PA_SYNC_ESTAB (direct scan-and-sync)
* and PA_SYNC_TRANS_RECV (PAST-delivered sync) on success, and cleared
* by real PA_SYNC_LOST or by the synthesized terminate path below.
*
* Single-slot — assumes one active PA sync (and at most one in-flight
* terminate) at a time. The audio examples never hold multiple PA syncs
* concurrently. Multi-sync acceptors / Broadcast Assistants will need an
* N-slot tracker plus per-terminate intent capture.
*
* TODO(bluedroid): drop this once
* BTM_BLE_5_GAP_PERIODIC_ADV_SYNC_TERMINATE_COMPLETE_EVT also surfaces
* sync_handle (BTM_BlePeriodicAdvSyncTerm in btm_ble_5_gap.c knows the
* handle, it just isn't propagated into cb_params). Ideally Bluedroid
* would directly synthesize a SYNC_LOST event on successful terminate,
* matching the NimBLE host contract; then both this tracker and the
* synthesis below become dead code. */
#define ISO_PA_SYNC_HANDLE_NONE 0xFFFF
static uint16_t active_pa_sync_handle = ISO_PA_SYNC_HANDLE_NONE;
#endif /* BLE_50_EXTEND_SYNC_EN == TRUE */
/* Fast-path BTA → iso-queue post.
*
* BTU calls gap_app_cb directly; this function bypasses the BTC dispatch
* hop for the audio-critical events so they reach the iso task in the same
* order BTU received them. BIGINFO_ADV_REPORT already posts directly from
* iso_evt_handler; routing PA_SYNC_ESTAB / PA_SYNC_LOST / EXT_ADV_REPORT /
* PAST_RECV the same way keeps both event streams ordered. */
static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
tBTA_DM_BLE_5_GAP_CB_PARAMS *params);
static void gap_app_cb(tBTA_DM_BLE_5_GAP_EVENT event, tBTA_DM_BLE_5_GAP_CB_PARAMS *params)
{
/* Audio-critical data events: post directly to iso task here to keep
* ordering with BIGINFO_ADV_REPORT (which iso_evt_handler also posts
* directly). The BTC dispatch below still runs for non-audio recipients
* of esp_ble_gap_register_callback. */
switch (event) {
#if (BLE_50_EXTEND_SCAN_EN == TRUE)
case BTM_BLE_5_GAP_EXT_ADV_REPORT_EVT:
#endif
#if (BLE_50_EXTEND_SYNC_EN == TRUE)
case BTM_BLE_5_GAP_PERIODIC_ADV_SYNC_ESTAB_EVT:
case BTM_BLE_5_GAP_PERIODIC_ADV_SYNC_LOST_EVT:
case BTM_BLE_5_GAP_PERIODIC_ADV_SYNC_TERMINATE_COMPLETE_EVT:
case BTM_BLE_5_GAP_PERIODIC_ADV_REPORT_EVT:
#endif
#if (BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER == TRUE)
case BTM_BLE_GAP_PERIODIC_ADV_SYNC_TRANS_RECV_EVT:
#endif
bt_le_bluedroid_gap_post_event_bta(event, params);
break;
default:
break;
}
/* Forward to BTC so esp_ble_gap_register_callback() recipients see it. */
btc_ble_5_gap_callback(event, params);
}
void bt_le_bluedroid_gap_post_event(uint16_t event, void *param)
{
const esp_ble_gap_cb_param_t *p = param;
struct bt_le_gap_app_param *qev = NULL;
int err;
qev = calloc(1, sizeof(*qev));
assert(qev);
/* Only AUTH_CMPL reaches here from the application's
* esp_ble_gap_register_callback path. EXT_ADV_REPORT / PA_SYNC_ESTAB /
* PA_SYNC_LOST / PAST_RECV are posted directly by gap_app_cb on the BTM
* BLE 5 channel; SMP events have no such channel so AUTH_CMPL is the
* only one the application must forward. */
switch (event) {
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
const esp_ble_auth_cmpl_t *a = &p->ble_security.auth_cmpl;
struct gatt_conn *gatt_conn;
uint8_t sec_level;
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(a->addr_type, a->bd_addr, false);
if (gatt_conn == NULL) {
LOG_ERR("[B]UnknownDevForEnc");
free(qev);
return;
}
qev->type = BT_LE_GAP_APP_PARAM_SECURITY_CHANGE;
qev->security_change.status = (a->success ? 0x00 : 0xFF);
/* Populate connection identity unconditionally so failure events
* carry valid conn_handle / role / dst to the application. */
qev->security_change.conn_handle = gatt_conn->conn_handle;
qev->security_change.role = gatt_conn->role;
qev->security_change.dst.type = gatt_conn->peer.type;
memcpy(qev->security_change.dst.val, gatt_conn->peer.val, BT_ADDR_SIZE);
if (qev->security_change.status == 0) {
/* Derive level from auth_mode bits (mirrors NimBLE's sec_state
* mapping). esp_ble_auth_cmpl_t exposes neither encrypted/
* authenticated flags nor a key_size, so MITM presence is the
* only authenticated-vs-Just-Works signal we have. */
if (!(a->auth_mode & ESP_LE_AUTH_REQ_MITM)) {
sec_level = BT_SECURITY_L2;
} else if (!(a->auth_mode & ESP_LE_AUTH_REQ_SC_ONLY)) {
sec_level = BT_SECURITY_L3;
} else {
sec_level = BT_SECURITY_L4;
}
qev->security_change.sec_level = sec_level;
qev->security_change.bonded = (a->auth_mode & ESP_LE_AUTH_BOND) ? 1 : 0;
}
break;
}
default:
LOG_WRN("[B]GapPostEvtUnexp[%u]", event);
free(qev);
return;
}
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GAP_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GapPostEvtFail[%d][%u]", err, qev->type);
free(qev);
}
}
static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
tBTA_DM_BLE_5_GAP_CB_PARAMS *params)
{
struct bt_le_gap_app_param *qev = NULL;
int err;
qev = calloc(1, sizeof(*qev));
assert(qev);
switch (event) {
#if (BLE_50_EXTEND_SCAN_EN == TRUE)
case BTM_BLE_5_GAP_EXT_ADV_REPORT_EVT: {
const tBTM_BLE_EXT_ADV_REPORT *r = &params->ext_adv_report;
qev->type = BT_LE_GAP_APP_PARAM_EXT_SCAN_RECV;
qev->ext_scan_recv.event_type = (r->data_status << 5) | r->event_type;
qev->ext_scan_recv.addr.type = r->addr_type;
memcpy(qev->ext_scan_recv.addr.val, r->addr, BT_ADDR_SIZE);
/* BTM declares rssi/tx_power as UINT8; the bytes are spec-defined as
* signed (RSSI typically negative). Cast preserves the bit pattern. */
qev->ext_scan_recv.rssi = (int8_t)r->rssi;
qev->ext_scan_recv.tx_power = (int8_t)r->tx_power;
qev->ext_scan_recv.sid = r->sid;
qev->ext_scan_recv.pri_phy = r->primary_phy;
qev->ext_scan_recv.sec_phy = r->secondry_phy;
qev->ext_scan_recv.per_adv_itvl = r->per_adv_interval;
qev->ext_scan_recv.data_len = r->adv_data_len;
if (qev->ext_scan_recv.data_len) {
qev->ext_scan_recv.data = calloc(1, qev->ext_scan_recv.data_len);
assert(qev->ext_scan_recv.data);
memcpy(qev->ext_scan_recv.data, r->adv_data, qev->ext_scan_recv.data_len);
}
break;
}
#endif /* BLE_50_EXTEND_SCAN_EN == TRUE */
#if (BLE_50_EXTEND_SYNC_EN == TRUE)
case BTM_BLE_5_GAP_PERIODIC_ADV_SYNC_ESTAB_EVT: {
const tBTM_BLE_PERIOD_ADV_SYNC_ESTAB *e = &params->sync_estab;
qev->type = BT_LE_GAP_APP_PARAM_PA_SYNC;
qev->pa_sync.status = e->status;
qev->pa_sync.sync_handle = e->sync_handle;
qev->pa_sync.addr.type = e->adv_addr_type;
memcpy(qev->pa_sync.addr.val, e->adv_addr, BT_ADDR_SIZE);
qev->pa_sync.sid = e->sid;
qev->pa_sync.adv_phy = e->adv_phy;
qev->pa_sync.per_adv_itvl = e->period_adv_interval;
qev->pa_sync.adv_ca = e->adv_clk_accuracy;
if (e->status == 0) {
if (active_pa_sync_handle != ISO_PA_SYNC_HANDLE_NONE) {
/* Concurrent PA sync: single-slot tracker overwrites the
* existing handle, so a later terminate on the first sync
* would synthesize SYNC_LOST against the wrong one.
* See ISO_PA_SYNC_HANDLE_NONE doc for the multi-sync TODO. */
LOG_WRN("[B]PaSyncEstabOverwrite[%04x->%04x]",
active_pa_sync_handle, e->sync_handle);
} else {
LOG_INF("[B]PaSyncEstab[%04x]", e->sync_handle);
}
active_pa_sync_handle = e->sync_handle;
}
break;
}
case BTM_BLE_5_GAP_PERIODIC_ADV_SYNC_LOST_EVT:
qev->type = BT_LE_GAP_APP_PARAM_PA_SYNC_LOST;
/* Public API exposes only sync_handle; reason isn't surfaced. */
qev->pa_sync_lost.sync_handle = params->sync_lost.sync_handle;
qev->pa_sync_lost.reason = 0;
if (active_pa_sync_handle == qev->pa_sync_lost.sync_handle) {
LOG_INF("[B]PaSyncLost[%04x]", qev->pa_sync_lost.sync_handle);
active_pa_sync_handle = ISO_PA_SYNC_HANDLE_NONE;
}
break;
case BTM_BLE_5_GAP_PERIODIC_ADV_SYNC_TERMINATE_COMPLETE_EVT:
/* Host-commanded terminate: synthesize PA_SYNC_LOST so the iso
* task sees a unified sync-end event regardless of whether the
* sync was lost on-air or torn down via
* esp_ble_gap_periodic_adv_sync_terminate(). The complete event
* only carries status; recover sync_handle from the tracker.
* See ISO_PA_SYNC_HANDLE_NONE definition for limitations and
* the Bluedroid TODO. */
if (params->per_adv_sync_term.status != BTM_SUCCESS ||
active_pa_sync_handle == ISO_PA_SYNC_HANDLE_NONE) {
LOG_WRN("[B]PaSyncTermNoSynth[%02x][%04x]",
params->per_adv_sync_term.status, active_pa_sync_handle);
free(qev);
return;
}
qev->type = BT_LE_GAP_APP_PARAM_PA_SYNC_LOST;
qev->pa_sync_lost.sync_handle = active_pa_sync_handle;
qev->pa_sync_lost.reason = 0;
LOG_INF("[B]PaSyncTermSynth[%04x]", active_pa_sync_handle);
active_pa_sync_handle = ISO_PA_SYNC_HANDLE_NONE;
break;
case BTM_BLE_5_GAP_PERIODIC_ADV_REPORT_EVT: {
const tBTM_PERIOD_ADV_REPORT *r = &params->period_adv_report;
qev->type = BT_LE_GAP_APP_PARAM_PA_SYNC_RECV;
qev->pa_sync_recv.sync_handle = r->sync_handle;
qev->pa_sync_recv.tx_power = r->tx_power;
qev->pa_sync_recv.rssi = r->rssi;
qev->pa_sync_recv.data_status = r->data_status;
qev->pa_sync_recv.data_len = r->data_length;
if (qev->pa_sync_recv.data_len) {
qev->pa_sync_recv.data = calloc(1, qev->pa_sync_recv.data_len);
assert(qev->pa_sync_recv.data);
memcpy(qev->pa_sync_recv.data, r->data, qev->pa_sync_recv.data_len);
}
break;
}
#endif /* BLE_50_EXTEND_SYNC_EN == TRUE */
#if (BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER == TRUE)
case BTM_BLE_GAP_PERIODIC_ADV_SYNC_TRANS_RECV_EVT: {
const tBTM_BLE_PERIOD_ADV_SYNC_TRANS_RECV *r = &params->past_recv;
struct gatt_conn *gatt_conn;
/* Look up the PAST-delivering ACL handle by peer BDA — past_recv
* doesn't carry addr type. */
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(0, r->addr, true);
if (gatt_conn == NULL) {
LOG_ERR("[B]UnknownPastSrc");
free(qev);
return;
}
qev->type = BT_LE_GAP_APP_PARAM_PA_SYNC_PAST;
qev->pa_sync_past.status = r->status;
qev->pa_sync_past.sync_handle = r->sync_handle;
qev->pa_sync_past.addr.type = r->adv_addr_type;
memcpy(qev->pa_sync_past.addr.val, r->adv_addr, BT_ADDR_SIZE);
qev->pa_sync_past.sid = r->adv_sid;
qev->pa_sync_past.adv_phy = r->adv_phy;
qev->pa_sync_past.per_adv_itvl = r->adv_interval;
qev->pa_sync_past.adv_ca = r->adv_clk_accuracy;
qev->pa_sync_past.conn_handle = gatt_conn->conn_handle;
#if (BLE_50_EXTEND_SYNC_EN == TRUE)
/* PAST-established syncs need tracker too: a later app-initiated
* terminate hits TERMINATE_COMPLETE_EVT regardless of how the sync
* was originally created. Gated by BLE_50_EXTEND_SYNC_EN since
* that's where the tracker decl + LOST/TERMINATE consumers live. */
if (r->status == 0) {
if (active_pa_sync_handle != ISO_PA_SYNC_HANDLE_NONE) {
LOG_WRN("[B]PaSyncTransOverwrite[%04x->%04x]",
active_pa_sync_handle, r->sync_handle);
} else {
LOG_INF("[B]PaSyncTrans[%04x]", r->sync_handle);
}
active_pa_sync_handle = r->sync_handle;
}
#endif /* BLE_50_EXTEND_SYNC_EN == TRUE */
break;
}
#endif /* BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER == TRUE */
default:
free(qev);
return;
}
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GAP_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GapPostEvtBtaFail[%d][%u]", err, qev->type);
goto free;
}
return;
free:
/* Mirror nimble/gap.c cleanup: free nested data buffers carried by
* specific event types before freeing the qev container itself. */
switch (qev->type) {
case BT_LE_GAP_APP_PARAM_EXT_SCAN_RECV:
if (qev->ext_scan_recv.data) {
free(qev->ext_scan_recv.data);
qev->ext_scan_recv.data = NULL;
}
break;
case BT_LE_GAP_APP_PARAM_PA_SYNC_RECV:
if (qev->pa_sync_recv.data) {
free(qev->pa_sync_recv.data);
qev->pa_sync_recv.data = NULL;
}
break;
default:
break;
}
free(qev);
}
int bt_le_bluedroid_scan_start(const struct bt_le_scan_param *param)
{
tBTM_STATUS status;
LOG_DBG("[B]ScanStart[%u][%u][%u]", param->type, param->interval, param->window);
#if USE_DIRECT_HCI
{
/* HCI LE Set Extended Scan Parameters (uncoded only):
* own_addr_type(1) | scan_filter_policy(1) | scanning_phys(1)
* | per-phy { scan_type(1) | scan_interval(2) | scan_window(2) } */
uint8_t cmd_params[8];
cmd_params[0] = BLE_ADDR_PUBLIC;
cmd_params[1] = 0; /* filter_policy: accept all */
cmd_params[2] = 0x01; /* scanning_phys: LE 1M only */
cmd_params[3] = param->type;
sys_put_le16(param->interval, cmd_params + 4);
sys_put_le16(param->window, cmd_params + 6);
status = bt_le_bluedroid_hci_send_sync(HCI_BLE_SET_EXT_SCAN_PARAMS,
cmd_params, sizeof(cmd_params),
NULL, 0);
}
#else /* USE_DIRECT_HCI */
{
tBTM_BLE_EXT_SCAN_PARAMS scan_params = {0};
scan_params.own_addr_type = BLE_ADDR_PUBLIC;
scan_params.filter_policy = 0;
scan_params.scan_duplicate = 0;
scan_params.cfg_mask = BTM_BLE_GAP_EXT_SCAN_UNCODE_MASK;
scan_params.uncoded_cfg.scan_type = param->type;
scan_params.uncoded_cfg.scan_interval = param->interval;
scan_params.uncoded_cfg.scan_window = param->window;
bt_le_host_lock();
status = BTM_BleSetExtendedScanParams(&scan_params);
bt_le_host_unlock();
}
#endif /* USE_DIRECT_HCI */
if (status != BTM_SUCCESS) {
LOG_ERR("[B]SetScanParamsFail[%02x]", status);
return bluedroid_err_to_errno(status);
}
#if USE_DIRECT_HCI
{
/* HCI LE Set Extended Scan Enable:
* enable(1) | filter_duplicates(1) | duration(2) | period(2)
* duration=period=0 → continuous scan. */
uint8_t cmd_params[6] = { 1, 0, 0, 0, 0, 0 };
status = bt_le_bluedroid_hci_send_sync(HCI_BLE_SET_EXT_SCAN_ENABLE,
cmd_params, sizeof(cmd_params),
NULL, 0);
}
#else /* USE_DIRECT_HCI */
bt_le_host_lock();
status = BTM_BleExtendedScan(true, 0, 0);
bt_le_host_unlock();
#endif /* USE_DIRECT_HCI */
if (status != BTM_SUCCESS) {
LOG_ERR("[B]ScanStartFail[%02x]", status);
}
return bluedroid_err_to_errno(status);
}
int bt_le_bluedroid_scan_stop(void)
{
tBTM_STATUS status;
LOG_DBG("[B]ScanStop");
#if USE_DIRECT_HCI
{
/* HCI LE Set Extended Scan Enable with enable=0; other fields
* are ignored by the controller per spec but must be present. */
uint8_t cmd_params[6] = { 0, 0, 0, 0, 0, 0 };
status = bt_le_bluedroid_hci_send_sync(HCI_BLE_SET_EXT_SCAN_ENABLE,
cmd_params, sizeof(cmd_params),
NULL, 0);
}
#else /* USE_DIRECT_HCI */
bt_le_host_lock();
status = BTM_BleExtendedScan(false, 0, 0);
bt_le_host_unlock();
#endif /* USE_DIRECT_HCI */
if (status != BTM_SUCCESS) {
LOG_ERR("[B]ScanStopFail[%02x]", status);
}
return bluedroid_err_to_errno(status);
}
int bt_le_bluedroid_iso_disconnect(uint16_t conn_handle, uint8_t reason)
{
tBTM_STATUS status;
LOG_DBG("[B]IsoDisconn[0x%03x][%02x]", conn_handle, reason);
/* No direct_hci variant: HCI Disconnect returns Command_Status;
* outcome arrives via BTM_BLE_ISO_CIS_DISCONNECTED_EVT. */
status = BTM_BleDisconCis(conn_handle, reason);
if (status != BTM_SUCCESS) {
LOG_ERR("[B]IsoDisconnFail[0x%03x][%02x]", conn_handle, status);
}
return bluedroid_err_to_errno(status);
}
int bt_le_bluedroid_gap_init(void)
{
BTM_BleGapRegisterCallback(gap_app_cb);
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,198 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "bluedroid/hci.h"
#if USE_DIRECT_HCI
/* hci/hci_layer.h pulls in osi/osi.h which defines a 2-arg CONCAT(a, b)
* macro that conflicts with the variadic CONCAT(...) from Zephyr's
* <zephyr/sys/util.h>. hci.c does not use CONCAT itself, so drop the
* Zephyr definition before pulling in osi's. */
#undef CONCAT
#include "hci/hci_layer.h"
#include "stack/hcimsgs.h"
#include "stack/btm_ble_api.h"
#include "bluedroid/btm_error.h"
#include "common/host.h"
LOG_MODULE_REGISTER(ISO_BHCI, CONFIG_BT_ISO_LOG_LEVEL);
/* Adapter-private sync cmd path. Decouples sync HCI cmds from BTU's global
* ble_sync_info entirely: each cmd carries its own command_complete_cb (this
* file's direct_hci_complete_cb) and the caller waits on direct_hci_sem,
* not ble_sync_info.sync_sem. Two-task race on sync_info->opcode disappears.
*
* Caller sets direct_hci_rsp.opcode before transmit; cb verifies the
* response opcode matches before giving the sem (guards against unexpected
* stray Command_Complete dispatches).
*
* direct_hci_complete_cb runs on the hci_layer task — keep it tiny and
* non-blocking, no host_lock, no further dispatch.
*
* Concurrent safety: send_sync is serialized by callers via bt_le_host_lock,
* so the static rsp_buf pointer / opcode latch are single-slot. */
static struct k_sem direct_hci_sem;
/* Set by deinit before deleting the sem. Late-arriving cb's must check
* this before touching the sem. Residual race exists (cb past the check
* but pre-give vs. deinit completing the delete) — accepted in practice
* because the BTU/HCI layer offers no way to cancel an in-flight cmd. */
static volatile bool direct_hci_shutting_down;
static struct {
uint16_t opcode; /* expected — set by caller, verified by cb */
uint8_t status; /* HCI status from Command_Complete */
} direct_hci_rsp;
/* Internal sink for the cmd-specific return payload (bytes after the status
* byte). The cb copies HERE, never into the caller's stack buffer — a
* timed-out caller may already have freed its stack frame by the time a late
* cb runs on the hci_layer task. The caller copies this out only after a
* successful take. Static storage: a late cb always has a valid target, so no
* teardown race. Sized for the largest payload any direct-HCI caller reads
* back (SET_CIG: 2 + 2*cis_count, ISO_READ_TX_SYNC: 11). */
#define DIRECT_HCI_RSP_MAX MAX(2 + 2 * CONFIG_BT_ISO_MAX_CHAN, 11)
static uint8_t direct_hci_rsp_data[DIRECT_HCI_RSP_MAX];
static uint8_t direct_hci_rsp_data_len;
static void direct_hci_complete_cb(BT_HDR *response, void *context)
{
/* Command_Complete event layout after BT_HDR data/offset:
* [0] event code (0x0E) [1] param len [2] num_cmd_packets
* [3..4] opcode (LE) [5] status [6..] cmd-specific params
*/
uint8_t *stream = response->data + response->offset + 3;
uint16_t opcode;
uint8_t status;
uint8_t event_param_len;
ARG_UNUSED(context);
event_param_len = response->data[response->offset + 1];
STREAM_TO_UINT16(opcode, stream);
STREAM_TO_UINT8(status, stream);
if (opcode != direct_hci_rsp.opcode) {
LOG_ERR("[B]DirectHciOpcodeMismatch[exp=0x%04x][got=0x%04x]",
direct_hci_rsp.opcode, opcode);
/* Drop — don't give sem, caller times out. */
osi_free(response);
return;
}
direct_hci_rsp.status = status;
/* Copy cmd-specific return params (everything after the status byte)
* into the caller's buffer. event_param_len counts:
* num_cmd_packets(1) + opcode(2) + status(1) + cmd-specific(N)
* so the payload available to caller is event_param_len - 4. */
if (event_param_len > 4) {
direct_hci_rsp_data_len = MIN(event_param_len - 4, sizeof(direct_hci_rsp_data));
memcpy(direct_hci_rsp_data, stream, direct_hci_rsp_data_len);
}
osi_free(response);
/* deinit may have set the shutdown flag and be about to delete the
* sem. Skip the give to avoid asserting on a NULL handle. */
if (direct_hci_shutting_down) {
return;
}
k_sem_give(&direct_hci_sem);
}
tBTM_STATUS bt_le_bluedroid_hci_send_sync(uint16_t opcode,
const uint8_t *cmd_params,
uint8_t cmd_params_len,
uint8_t *rsp_buf,
uint8_t rsp_buf_len)
{
BT_HDR *p;
UINT8 *pp;
hci_cmd_metadata_t *metadata;
p = HCI_GET_CMD_BUF(cmd_params_len);
if (p == NULL) {
return BTM_NO_RESOURCES;
}
pp = p->data;
UINT16_TO_STREAM(pp, opcode);
UINT8_TO_STREAM(pp, cmd_params_len);
if (cmd_params_len > 0 && cmd_params != NULL) {
memcpy(pp, cmd_params, cmd_params_len);
}
metadata = HCI_GET_CMD_METAMSG(p);
metadata->command_complete_cb = direct_hci_complete_cb;
metadata->command_status_cb = NULL;
metadata->opcode = opcode;
metadata->context = NULL;
/* Set expected opcode and rsp sink BEFORE transmit — cb may run
* before transmit returns if response is already queued. */
direct_hci_rsp.opcode = opcode;
direct_hci_rsp.status = 0;
direct_hci_rsp_data_len = 0;
/* Drop any stale give from a previous timed-out cmd whose cb arrived
* late. Must run AFTER opcode update (so any cb arriving after this
* drain hits the mismatch path and doesn't give) and BEFORE transmit
* (so this cmd's own cb-give isn't accidentally consumed here). */
k_sem_reset(&direct_hci_sem);
hci_layer_get_interface()->transmit_command(p, direct_hci_complete_cb,
NULL, NULL);
if (k_sem_take(&direct_hci_sem, K_SEM_SHORT) != 0) {
LOG_ERR("[B]DirectHciTimeout[0x%04x]", opcode);
return BTM_ERR_PROCESSING;
}
/* The cb gives the sem only after its memcpy, so the payload is complete;
* copy it into the caller's (now-confirmed-live) sink. */
if (rsp_buf != NULL && rsp_buf_len > 0 && direct_hci_rsp_data_len > 0) {
memcpy(rsp_buf, direct_hci_rsp_data, MIN(direct_hci_rsp_data_len, rsp_buf_len));
}
if (direct_hci_rsp.status != HCI_SUCCESS) {
LOG_ERR("[B]DirectHciStatus[0x%04x][%02x]",
opcode, direct_hci_rsp.status);
return BTM_HCI_ERROR | direct_hci_rsp.status;
}
return BTM_SUCCESS;
}
int bt_le_bluedroid_hci_init(void)
{
direct_hci_shutting_down = false;
k_sem_create(&direct_hci_sem);
return 0;
}
void bt_le_bluedroid_hci_deinit(void)
{
/* Flip the gate first so any cb past this point skips the give.
* Drain any give that was already posted before the flag flip so
* the sem is clean before deletion. */
direct_hci_shutting_down = true;
k_sem_reset(&direct_hci_sem);
k_sem_delete(&direct_hci_sem);
}
#endif /* USE_DIRECT_HCI */

View File

@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <errno.h>
#include "stack/btm_api.h"
/**
* Translate a Bluedroid BTM status (tBTM_STATUS, see stack/btm_api.h) into
* a standard negative POSIX errno.
*
* Counterpart to nimble_err_to_errno() — both adapters return errno values
* to common/ and lib code so the audio layer can branch on -EAGAIN /
* -EBUSY / -ENOMEM etc. without knowing which host it ran on.
*
* Unknown codes map to -EIO so caller logic doesn't accidentally match a
* known errno.
*/
static inline int bluedroid_err_to_errno(tBTM_STATUS status)
{
switch (status) {
case BTM_SUCCESS: return 0;
case BTM_CMD_STARTED: return 0;
case BTM_CMD_STORED: return 0;
case BTM_SUCCESS_NO_SECURITY: return 0;
case BTM_BUSY: return -EBUSY;
case BTM_NO_RESOURCES: return -ENOMEM;
case BTM_MODE_UNSUPPORTED: return -ENOTSUP;
case BTM_ILLEGAL_VALUE: return -EINVAL;
case BTM_WRONG_MODE: return -EPERM;
case BTM_UNKNOWN_ADDR: return -ENOENT;
case BTM_DEVICE_TIMEOUT: return -ETIMEDOUT;
case BTM_BAD_VALUE_RET: return -EBADMSG;
case BTM_ERR_PROCESSING: return -EIO;
case BTM_NOT_AUTHORIZED: return -EACCES;
case BTM_DEV_RESET: return -ECONNRESET;
case BTM_ILLEGAL_ACTION: return -EPERM;
case BTM_FAILED_ON_SECURITY: return -EPERM;
case BTM_REPEATED_ATTEMPTS: return -EAGAIN;
default: return -EIO;
}
}

View File

@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_APP_GAP_H_
#define HOST_BLUEDROID_APP_GAP_H_
#include <stdint.h>
#include <zephyr/bluetooth/bluetooth.h>
#ifdef __cplusplus
extern "C" {
#endif
void bt_le_bluedroid_gap_post_event(uint16_t event, void *param);
int bt_le_bluedroid_scan_start(const struct bt_le_scan_param *param);
int bt_le_bluedroid_scan_stop(void);
int bt_le_bluedroid_iso_disconnect(uint16_t conn_handle, uint8_t reason);
int bt_le_bluedroid_gap_init(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_APP_GAP_H_ */

View File

@@ -0,0 +1,133 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_GATT_H_
#define HOST_BLUEDROID_GATT_H_
#include <stdint.h>
#include <assert.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/slist.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#ifdef __cplusplus
extern "C" {
#endif
struct gatt_conn {
uint8_t used : 1;
uint8_t conn_create : 1;
uint8_t gattc_open : 1;
uint8_t cfg_mtu : 1;
uint8_t mtu_posted : 1;
uint8_t status;
uint8_t gatt_if;
uint16_t conn_handle;
uint8_t role;
struct {
uint8_t type;
uint8_t val[6];
} peer;
uint16_t mtu;
/* FIFO of in-flight GATTC read/write ops; mirrors BTA p_cmd_list ordering.
* BTA EVTs deliver attr_handle/status only — we need this to recover the
* caller's params on each cmpl. See struct gattc_list_node in gatt.c. */
sys_slist_t gattc_list;
/* FIFO of pending GATTS indications. GATT spec restricts per-conn to a
* single outstanding indication; this queue absorbs that limit so callers
* never see -EBUSY. See struct gatts_list_node in gatt.c. Mirrors
* NimBLE adapter NRP indicate behavior. */
sys_slist_t gatts_list;
/* FIFO of in-flight GATTS notify markers. BTA fires CONF_EVT for notify
* too (immediately after send) — tBTA_GATTS_REQ carries no notify/indicate
* flag so the handler pops this list first to disambiguate from the
* indication acks tracked in gatts_list. See struct gatts_notify_node. */
sys_slist_t gatts_notify_list;
};
uint8_t bt_le_bluedroid_gattc_get_if(void);
uint8_t bt_le_bluedroid_gatts_get_if(void);
void bt_le_bluedroid_gatts_sem_reset(void);
int bt_le_bluedroid_gatts_sem_take(void);
void bt_le_bluedroid_gatts_sem_give(int result);
struct gatt_conn *bt_le_bluedroid_find_gatt_conn_with_addr(uint8_t addr_type,
const uint8_t addr[6],
bool ignore_type);
struct gatt_conn *bt_le_bluedroid_find_gatt_conn_with_handle(uint16_t conn_handle);
struct gatt_conn *bt_le_bluedroid_find_free_gatt_conn(void);
void bt_le_bluedroid_gatt_handle_event(uint8_t *data, size_t data_len);
void bt_le_bluedroid_gatt_uuid_convert(const struct bt_uuid *uuid_in, void *uuid_out);
uint16_t bt_le_bluedroid_gatt_perm_convert(uint16_t perm_in);
uint16_t bt_le_bluedroid_gatt_get_mtu(struct bt_conn *conn);
ssize_t bt_le_bluedroid_gatts_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t buf_len, uint16_t offset,
const void *value, uint16_t value_len);
int bt_le_bluedroid_gatts_notify(struct bt_conn *conn, struct bt_gatt_notify_params *params);
int bt_le_bluedroid_gatts_indicate(struct bt_conn *conn, struct bt_gatt_indicate_params *params);
int bt_le_bluedroid_gattc_disc_start(uint16_t conn_handle);
int bt_le_bluedroid_gattc_discover(struct bt_conn *conn, struct bt_gatt_discover_params *params);
int bt_le_bluedroid_gattc_read(struct bt_conn *conn, struct bt_gatt_read_params *params);
int bt_le_bluedroid_gattc_write(struct bt_conn *conn, struct bt_gatt_write_params *params);
int bt_le_bluedroid_gattc_write_without_rsp(struct bt_conn *conn, uint16_t handle,
const void *data, uint16_t length);
int bt_le_bluedroid_gattc_write_ccc(struct bt_conn *conn, struct bt_gatt_subscribe_params *params);
int bt_le_bluedroid_gatt_init(void);
void bt_le_bluedroid_gatt_deinit(void);
struct inc_svc_inst {
struct bt_gatt_service *svc_p;
bool included;
};
struct gatts_svc_cb {
void (*svc_create_cb)(uint16_t service_id, uint16_t svc_instance,
bool is_primary, uint8_t status, void *uuid);
void (*inc_svc_add_cb)(uint16_t service_id, uint16_t attr_id, uint8_t status);
void (*chrc_add_cb)(uint16_t service_id, uint16_t attr_id,
uint8_t status, void *uuid);
void (*svc_start_cb)(uint16_t service_id, uint8_t status);
};
void bt_le_bluedroid_gatts_svc_cb_register(struct gatts_svc_cb *cb);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_GATT_H_ */

View File

@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_APP_HCI_H_
#define HOST_BLUEDROID_APP_HCI_H_
#include <stdint.h>
#include "stack/btm_ble_api.h"
#ifdef __cplusplus
extern "C" {
#endif
/* When set to 1, sync HCI commands issued by the iso adapter bypass BTM
* and the global ble_sync_info to avoid the sync_info opcode race with
* concurrent BTC/BTA sync cmds. Experimental. Toggle is shared with
* iso.c and gap.c via this header so the BTM fallback paths in both
* files stay in sync. */
#define USE_DIRECT_HCI 1
int bt_le_bluedroid_hci_init(void);
void bt_le_bluedroid_hci_deinit(void);
/* Send a sync HCI command, bypassing BTM and ble_sync_info. `cmd_params`
* is the cmd parameter payload (without opcode/length preamble).
*
* Returns BTM_SUCCESS, BTM_HCI_ERROR | hci_status on controller failure,
* or BTM_ERR_PROCESSING on host-side timeout.
*
* If `rsp_buf` is non-NULL, up to `rsp_buf_len` bytes of the
* cmd-specific return params (the bytes after the status byte in the
* HCI Command_Complete event) are copied in. If the actual payload is
* shorter, the tail of rsp_buf is left untouched; if longer, the excess
* is silently dropped. Callers parse opcode-specific structure.
*
* Serialization: NOT thread-safe — internally uses single-slot static
* state (sem, opcode latch, rsp buffer pointer). Callers must serialize
* externally; in practice all current callers (iso.c hci_cmd_* and
* gap.c scan_start/stop) run on the iso task, so single-task affinity
* provides serialization without explicit locking. */
tBTM_STATUS bt_le_bluedroid_hci_send_sync(uint16_t opcode,
const uint8_t *cmd_params,
uint8_t cmd_params_len,
uint8_t *rsp_buf,
uint8_t rsp_buf_len);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_APP_HCI_H_ */

View File

@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_ISO_H_
#define HOST_BLUEDROID_ISO_H_
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct net_buf;
int bt_le_bluedroid_hci_iso_cmd_send_sync(uint16_t opcode,
struct net_buf *buf,
struct net_buf **rsp);
int bt_le_bluedroid_iso_init(void);
void bt_le_bluedroid_iso_deinit(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_ISO_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -217,9 +217,9 @@ void bt_le_nimble_gap_post_event(void *param)
break;
default:
LOG_WRN("[N]GapPostEvtUnexp[%u]", ev->type);
free(qev);
assert(0);
break;
return;
}
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GAP_EVENT, qev, sizeof(*qev));
@@ -253,22 +253,39 @@ free:
int bt_le_nimble_scan_start(const struct bt_le_scan_param *param, ble_gap_event_fn *cb)
{
struct ble_gap_disc_params scan_param = {0};
struct ble_gap_ext_disc_params uncoded = {0};
int rc;
scan_param.itvl = param->interval;
scan_param.window = param->window;
scan_param.filter_policy = 0;
scan_param.limited = 0;
scan_param.passive = !param->type;
scan_param.filter_duplicates = 0;
LOG_DBG("[N]ScanStart[%u][%u][%u]", param->type, param->interval, param->window);
return nimble_err_to_errno(ble_gap_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER,
&scan_param, cb, NULL));
uncoded.itvl = param->interval;
uncoded.window = param->window;
uncoded.passive = !param->type;
/* LE Audio sources broadcast via extended advertising; legacy
* ble_gap_disc would miss them. Uncoded-only mirrors the Bluedroid
* side which sets BTM_BLE_GAP_EXT_SCAN_UNCODE_MASK. */
rc = ble_gap_ext_disc(BLE_OWN_ADDR_PUBLIC, 0, 0, 0, 0, 0,
&uncoded, NULL, cb, NULL);
if (rc) {
LOG_ERR("[N]ScanStartFail[%d]", rc);
}
return nimble_err_to_errno(rc);
}
int bt_le_nimble_scan_stop(void)
{
return nimble_err_to_errno(ble_gap_disc_cancel());
int rc;
LOG_DBG("[N]ScanStop");
rc = ble_gap_disc_cancel();
if (rc) {
LOG_ERR("[N]ScanStopFail[%d]", rc);
}
return nimble_err_to_errno(rc);
}
int bt_le_nimble_iso_disconnect(uint16_t conn_handle, uint8_t reason)

View File

@@ -95,9 +95,9 @@ void bt_le_nimble_gatt_post_event(void *param)
break;
default:
LOG_WRN("[N]GattPostEvtUnexp[%u]", ev->type);
free(qev);
assert(0);
break;
return;
}
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
@@ -209,7 +209,7 @@ static void handle_gattc_notify_rx_event_safe(struct bt_le_gattc_notify_rx_event
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("[N]NotConn[%d]", __LINE__);
LOG_INF("[N]GattcNtfRxNotConn[%u][%u]", event->conn_handle, BT_CONN_STATE_GET(conn));
goto end;
}
@@ -222,6 +222,12 @@ static void handle_gattc_notify_rx_event_safe(struct bt_le_gattc_notify_rx_event
if (params->ccc_handle != BT_GATT_AUTO_DISCOVER_CCC_HANDLE &&
params->value_handle == event->attr_handle) {
if (params->notify) {
/* Return value intentionally ignored. Zephyr unsubscribes on
* BT_GATT_ITER_STOP, but lib clients return STOP on a single
* malformed/short notify (e.g. unicast_client_cp_notify);
* tearing down a core subscription like the ASCS control point
* over one bad PDU would drop every later notification. Tolerate
* the bad PDU and keep the subscription. */
params->notify(conn, params, event->value, event->len);
}
}
@@ -446,6 +452,16 @@ int bt_le_nimble_gattc_discover(struct bt_conn *conn, struct bt_gatt_discover_pa
LOG_DBG("[N]GattcDisc[%u]", conn->handle);
if (params->uuid) {
/* Downstream gattc_db_disc_*_by_uuid takes ble_uuid16_t — reject
* non-16-bit caller UUIDs explicitly. BT_UUID_16 would otherwise
* reinterpret a 32/128-bit struct and feed garbage to the lookup.
* LE Audio profiles only use SIG-assigned 16-bit UUIDs, so this
* gate stays a compile-time-style guard in practice. */
if (params->uuid->type != BT_UUID_TYPE_16) {
LOG_ERR("[N]DiscNon16BitUuid[%u]", params->uuid->type);
return -ENOTSUP;
}
uuid.u.type = BLE_UUID_TYPE_16;
uuid.value = BT_UUID_16(params->uuid)->val;
}

View File

@@ -529,9 +529,11 @@ static void gattc_db_disc(struct gattc_db *adb, uint8_t status)
* to the upper layer.
*/
if (status == DISC_FAIL) {
uint16_t conn_handle = adb->conn_handle;
gattc_db_del(adb);
bt_le_nimble_gatt_post_disc_cmpl_event(adb->conn_handle, 0x01);
bt_le_nimble_gatt_post_disc_cmpl_event(conn_handle, 0x01);
return;
}
@@ -553,9 +555,11 @@ static void gattc_db_disc(struct gattc_db *adb, uint8_t status)
* we will delete the gatt client database and post the
* discovery completion event.
*/
uint16_t conn_handle = adb->conn_handle;
gattc_db_del(adb);
bt_le_nimble_gatt_post_disc_cmpl_event(adb->conn_handle, 0x01);
bt_le_nimble_gatt_post_disc_cmpl_event(conn_handle, 0x01);
break;
}
@@ -590,8 +594,8 @@ static int gattc_db_disc_all_svcs_cb_safe(uint16_t conn_handle,
case 0:
assert(svc);
LOG_DBG("[N]GattcDbDiscAllSvcs[%s][%u][%u]",
audio_svc_uuid_to_str(svc->uuid.u16.value),
LOG_DBG("[N]GattcDbDiscAllSvcs[0x%04x][%u][%u]",
svc->uuid.u16.value,
svc->start_handle, svc->end_handle);
gattc_db_svc_insert(adb, svc);
@@ -637,7 +641,7 @@ static int gattc_db_find_inc_svcs_cb_safe(uint16_t conn_handle,
adb = gattc_db_find(conn_handle);
if (adb == NULL) {
LOG_ERR("[N]GattcDbNotFound");
LOG_WRN("[N]GattcDbFindIncSvcsCbNotFound");
rc = -ENODEV;
goto end;
}
@@ -646,8 +650,8 @@ static int gattc_db_find_inc_svcs_cb_safe(uint16_t conn_handle,
case 0:
assert(svc);
LOG_DBG("[N]GattcDbFindIncSvcs[%s][%u][%u]",
audio_svc_uuid_to_str(svc->uuid.u16.value),
LOG_DBG("[N]GattcDbFindIncSvcs[0x%04x][%u][%u]",
svc->uuid.u16.value,
svc->start_handle, svc->end_handle);
gattc_db_inc_svc_insert(asvc, svc);
@@ -725,7 +729,7 @@ static int gattc_db_disc_all_inc_chrs_cb_safe(uint16_t conn_handle,
adb = gattc_db_find(conn_handle);
if (adb == NULL) {
LOG_ERR("[N]GattcDbNotFound");
LOG_WRN("[N]GattcDbDiscAllIncChrsCbNotFound");
rc = -ENODEV;
goto end;
}
@@ -783,7 +787,7 @@ static int gattc_db_disc_all_chrs_cb_safe(uint16_t conn_handle,
adb = gattc_db_find(conn_handle);
if (adb == NULL) {
LOG_ERR("[N]GattcDbNotFound");
LOG_WRN("[N]GattcDbDiscAllChrsCbNotFound");
rc = -ENODEV;
goto end;
}
@@ -843,7 +847,7 @@ static int gattc_db_disc_all_inc_dscs_cb_safe(uint16_t conn_handle,
adb = gattc_db_find(conn_handle);
if (adb == NULL) {
LOG_ERR("[N]GattcDbNotFound");
LOG_WRN("[N]GattcDbDiscAllIncDscsCbNotFound");
rc = -ENODEV;
goto end;
}
@@ -906,7 +910,7 @@ static int gattc_db_disc_all_dscs_cb_safe(uint16_t conn_handle,
adb = gattc_db_find(conn_handle);
if (adb == NULL) {
LOG_ERR("[N]GattcDbNotFound");
LOG_WRN("[N]GattcDbDiscAllDscsCbNotFound");
rc = -ENODEV;
goto end;
}
@@ -967,7 +971,7 @@ static int gattc_db_enable_notify_cb_safe(uint16_t conn_handle,
adb = gattc_db_find(conn_handle);
if (adb == NULL) {
LOG_ERR("[N]GattcDbNotFound");
LOG_WRN("[N]GattcDbEnableNotifyCbNotFound");
rc = -ENODEV;
goto end;
}
@@ -987,10 +991,17 @@ static int gattc_db_enable_notify_cb_safe(uint16_t conn_handle,
break;
}
/* Mark the CCCD write as completed */
achrc->cccd_write = 1;
gattc_db_disc(adb, DISC_SUCCESS);
if (rc) {
/* Terminal failure (ENOTCONN/ETIMEOUT) — bail via DISC_FAIL so the
* next CCCD write isn't queued; it would otherwise sit on NimBLE's
* pending list and only resolve after the 30s ATT timeout, by which
* time the adb has been removed. */
gattc_db_disc(adb, DISC_FAIL);
} else {
/* Mark the CCCD write as completed */
achrc->cccd_write = 1;
gattc_db_disc(adb, DISC_SUCCESS);
}
end:
bt_le_host_unlock();
@@ -1014,7 +1025,7 @@ static int handle_gattc_disc_svc_by_uuid(struct bt_conn *conn, ble_uuid16_t *uui
adb = gattc_db_find(conn->handle);
if (adb == NULL) {
LOG_ERR("[N]GattcDbNotFound");
LOG_WRN("[N]GattcDiscSvcByUuidNotFound");
goto end;
}
@@ -1068,7 +1079,7 @@ static int handle_gattc_find_inc_svcs(struct bt_conn *conn,
adb = gattc_db_find(conn->handle);
if (adb == NULL) {
LOG_ERR("[N]GattcDbNotFound");
LOG_WRN("[N]GattcFindIncSvcsNotFound");
goto end;
}
@@ -1087,6 +1098,9 @@ static int handle_gattc_find_inc_svcs(struct bt_conn *conn,
inc_svc.start_handle = ainc_svc->svc.start_handle;
inc_svc.end_handle = ainc_svc->svc.end_handle;
/* Include declaration's own attribute type — same rationale
* as the Bluedroid side: caller may dereference attr->uuid. */
attr.uuid = BT_UUID_GATT_INCLUDE;
attr.user_data = &inc_svc;
/* TODO:
* When CONFIG_BT_NIMBLE_INCL_SVC_DISCOVERY is enabled, use ainc_svc->attr_handle here.
@@ -1158,7 +1172,7 @@ static int handle_gattc_disc_chrs(struct bt_conn *conn, ble_uuid16_t *uuid,
adb = gattc_db_find(conn->handle);
if (adb == NULL) {
LOG_ERR("[N]GattcDbNotFound");
LOG_WRN("[N]GattcDiscChrsNotFound");
goto end;
}
@@ -1234,7 +1248,7 @@ void handle_gattc_db_disc_event_safe(struct bt_le_gattc_discover_event *event)
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("[N]GattcDbNotConn[%d]", __LINE__);
LOG_INF("[N]GattcDbDiscNotConn[%u][%u]", event->conn_handle, BT_CONN_STATE_GET(conn));
goto end;
}
@@ -1341,7 +1355,7 @@ static int handle_gattc_disc_all_dscs(struct bt_conn *conn,
adb = gattc_db_find(conn->handle);
if (adb == NULL) {
LOG_ERR("[N]GattcDbNotFound");
LOG_WRN("[N]GattcDiscAllDscsNotFound");
rc = -ENODEV;
goto end;
}

View File

@@ -117,7 +117,7 @@ static int gattc_nrp_read_by_uuid_cb_safe(uint16_t conn_handle,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("[N]GattcNrpNotConn[%d]", __LINE__);
LOG_INF("[N]GattcNrpRdByUuidNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
rc = -ENOTCONN;
goto end;
}
@@ -207,7 +207,7 @@ static int gattc_nrp_read_long_cb_safe(uint16_t conn_handle,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("[N]GattcNrpNotConn[%d]", __LINE__);
LOG_INF("[N]GattcNrpRdLongNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
rc = -ENOTCONN;
goto end;
}
@@ -287,7 +287,7 @@ static int gattc_nrp_read_single_cb_safe(uint16_t conn_handle,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("[N]GattcNrpNotConn[%d]", __LINE__);
LOG_INF("[N]GattcNrpRdSingleNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
rc = -ENOTCONN;
goto end;
}
@@ -404,7 +404,7 @@ static int gattc_nrp_write_cb_safe(uint16_t conn_handle,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("[N]GattcNrpNotConn[%d]", __LINE__);
LOG_INF("[N]GattcNrpWrNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
rc = -ENOTCONN;
goto end;
}
@@ -480,7 +480,7 @@ static int gattc_nrp_subscribe_cb_safe(uint16_t conn_handle,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("[N]GattcNrpNotConn[%d]", __LINE__);
LOG_INF("[N]GattcNrpSubNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
rc = -ENOTCONN;
goto end;
}
@@ -560,7 +560,7 @@ void bt_le_nimble_gatts_nrp_indicate_cb(uint16_t conn_handle,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("[N]GattsNrpNotConn[%d]", __LINE__);
LOG_INF("[N]GattsNrpIndNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return;
}
@@ -902,17 +902,22 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
case GATTS_NRP_INDICATE:
assert(nrp_head->indicate.params);
/* params is the deep copy taken at insert time; cb sees the copy's
* address, not the caller's original (which may have been reused).
* On match: err propagates the mapped NimBLE status (0 on EDONE,
* else err). On handle mismatch the cb event doesn't correspond
* to this queue head — we cannot report this head's real outcome,
* so fire with BT_ATT_ERR_UNLIKELY to signal failure rather than
* silently passing the wrong op's status (could be 0/success and
* mislead the lib state machine). */
if (nrp_head->indicate.params->attr->handle !=
((struct bt_gatt_indicate_params *)params)->attr->handle) {
LOG_ERR("[N]GattNrpMismatchIndHdl[%u][%u]",
((struct bt_gatt_indicate_params *)params)->attr->handle,
nrp_head->indicate.params->attr->handle);
assert(0); /* Should not happen */
err = BT_ATT_ERR_UNLIKELY;
}
/* params is the deep copy taken at insert time; cb sees the copy's
* address, not the caller's original (which may have been reused).
* err propagates the mapped NimBLE status (0 on EDONE, else err). */
nrp_head->indicate.params->func(conn, nrp_head->indicate.params, err);
free(nrp_head->indicate.data_copy);

View File

@@ -13,6 +13,8 @@
extern "C" {
#endif
struct net_buf;
int bt_le_nimble_iso_cmd_send_sync(uint16_t opcode,
struct net_buf *buf,
struct net_buf **rsp);

View File

@@ -842,15 +842,20 @@ int bt_le_nimble_iso_init(void)
{
int err;
/* nimble's setters reject NULL and refuse re-registration (EALREADY), and
* expose no unregister path — so deinit cannot clear these. On a host
* re-enable the callback is still our own iso_evt_rx / bt_le_iso_rx, so
* treat EALREADY as success instead of failing the whole init. (evt setter
* returns -BLE_HS_EALREADY, pkt setter returns BLE_HS_EALREADY.) */
err = ble_hs_iso_evt_rx_cb_set(iso_evt_rx);
if (err) {
if (err && err != -BLE_HS_EALREADY) {
LOG_ERR("[N]IsoEvtRxCbSetFail[%d]", err);
return err;
}
#if CONFIG_BT_ISO_RX
err = ble_hs_iso_pkt_rx_cb_set(bt_le_iso_rx);
if (err) {
if (err && err != BLE_HS_EALREADY) {
LOG_ERR("[N]IsoPktRxCbSetFail[%d]", err);
return err;
}
@@ -869,14 +874,11 @@ int bt_le_nimble_iso_init(void)
void bt_le_nimble_iso_deinit(void)
{
LOG_DBG("IsoDeinit");
ble_hs_iso_evt_rx_cb_set(NULL);
#if CONFIG_BT_ISO_RX
ble_hs_iso_pkt_rx_cb_set(NULL);
#endif /* CONFIG_BT_ISO_RX */
LOG_DBG("[N]IsoDeinit");
/* nimble exposes no way to unregister iso_evt_rx / bt_le_iso_rx (the
* setters reject NULL); they harmlessly persist until the next init,
* which tolerates the resulting EALREADY. */
#if CONFIG_BT_ISO_UNICAST
iso_disable_cis();
#endif /* CONFIG_BT_ISO_UNICAST */

View File

@@ -87,7 +87,11 @@ static int ots_l2cap_event_cb(struct ble_l2cap_event *event, void *arg)
ots_chan = event->connect.chan;
if (ble_l2cap_get_chan_info(event->connect.chan, &chan_info)) {
assert(0);
LOG_ERR("[N]CocGetChanInfoFail");
/* Roll back the latch so the next COC_CONNECTED isn't refused
* by the if (ots_chan) guard above. */
ots_chan = NULL;
return -EIO;
}
LOG_DBG("[N]CocConnect[%u][%04x][%04x][%04x][%u][%u][%u][%u]",
@@ -320,7 +324,7 @@ int bt_le_nimble_l2cap_init(void)
void bt_le_nimble_l2cap_deinit(void)
{
LOG_DBG("NimbleL2capDeinit");
LOG_DBG("[N]L2capDeinit");
/* TODO: free the ots_mbuf_pool and ots_mbuf_mempool */

View File

@@ -517,8 +517,11 @@ void bt_le_gap_handle_event(uint8_t *data, size_t data_len)
void bt_le_gap_app_post_event(uint8_t type, void *param)
{
/* Currently type is not used */
#if CONFIG_BT_BLUEDROID_ENABLED
/* For Bluedroid, post the typed event to the ISO task instead. */
bt_le_bluedroid_gap_post_event(type, param);
#else
ARG_UNUSED(type);
bt_le_nimble_gap_post_event(param);
#endif
}

View File

@@ -106,6 +106,7 @@ void bt_le_gatts_app_subscribe_event(struct bt_le_gatts_subscribe_event *param)
bt_le_gatt_app_cb_evt(&event);
}
#if !CONFIG_BT_BLUEDROID_ENABLED
void bt_le_gatt_app_post_event(uint8_t type, void *param)
{
/* Currently type is not used */
@@ -113,3 +114,24 @@ void bt_le_gatt_app_post_event(uint8_t type, void *param)
bt_le_nimble_gatt_post_event(param);
}
#endif /* !CONFIG_BT_BLUEDROID_ENABLED */
#if CONFIG_BT_BLUEDROID_ENABLED
/* Bluedroid-only: GATTC open completion carries the BTA gattc_if, which has
* no NimBLE analogue. MTU and disc-cmpl events reuse the host-agnostic
* helpers above (bt_le_gatt_app_mtu_change_event / bt_le_gattc_app_disc_cmpl_event). */
void bt_le_gattc_app_open_event(struct bt_le_gattc_open_event *param, uint8_t gattc_if)
{
struct bt_le_gatt_app_event event = {
.type = BT_LE_GATT_APP_EVENT_GATTC_OPEN,
};
LOG_DBG("GattcOpenAppEvt[%u][%02x][%u]", param->conn_handle, param->status, gattc_if);
event.gattc_open.status = param->status;
event.gattc_open.conn_handle = param->conn_handle;
event.gattc_open.gattc_if = gattc_if;
bt_le_gatt_app_cb_evt(&event);
}
#endif /* CONFIG_BT_BLUEDROID_ENABLED */

View File

@@ -382,11 +382,12 @@ int bt_le_acl_conn_delete(uint16_t conn_handle)
{
struct bt_conn *conn;
LOG_DBG("AclConnDel[%u]", conn_handle);
LOG_INF("AclConnDel[%u]", conn_handle);
/* disconnected_listener already flipped state to DISCONNECTED. */
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
if (conn == NULL || conn->state != BT_CONN_DISCONNECTED) {
LOG_ERR("AclConnDelNotDisc[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return -ENOTCONN;
}
@@ -406,7 +407,7 @@ int bt_le_acl_conn_update(uint16_t conn_handle,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("AclConnUpdNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return -ENOTCONN;
}
@@ -543,7 +544,7 @@ int bt_le_acl_conn_connected_listener(uint16_t conn_handle)
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("AclConnConnectedListenerNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return -ENOTCONN;
}
@@ -566,10 +567,15 @@ int bt_le_acl_conn_disconnected_listener(uint16_t conn_handle, uint8_t reason)
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("AclConnDisconnectedListenerNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return -ENOTCONN;
}
/* Flip before dispatch — lib disconnect cbs guard late notifies on
* bt_conn_get_info().state. Otherwise BTA queues the send and the conn
* is gone by the time BTU drains it ("Unknown connection ID"). */
conn->state = BT_CONN_DISCONNECTED;
SYS_SLIST_FOR_EACH_CONTAINER(&conn_cbs, listener, _node) {
if (listener->disconnected) {
listener->disconnected(conn, reason);
@@ -593,7 +599,7 @@ int bt_le_acl_conn_security_changed_listener(uint16_t conn_handle, bt_security_t
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("AclConnSecChgListenerNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return -ENOTCONN;
}
@@ -621,7 +627,7 @@ int bt_le_acl_conn_identity_resolved_listener(uint16_t conn_handle,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("AclConnIdResolvedListenerNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return -ENOTCONN;
}
@@ -644,7 +650,7 @@ int bt_le_acl_conn_pairing_completed_listener(uint16_t conn_handle, bool bonded)
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("AclConnPairingCompletedListenerNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return -ENOTCONN;
}

View File

@@ -100,7 +100,11 @@ uint16_t bt_gatt_get_mtu(struct bt_conn *conn)
assert(conn);
#if CONFIG_BT_BLUEDROID_ENABLED
mtu = bt_le_bluedroid_gatt_get_mtu(conn);
#else
mtu = bt_le_nimble_gatt_get_mtu(conn);
#endif
LOG_DBG("GattGetMtu[%u]", mtu);
@@ -141,13 +145,12 @@ uint16_t bt_gatt_attr_get_handle(const struct bt_gatt_attr *attr)
{
uint16_t handle = 0;
LOG_DBG("GattAttrGetHdl");
if (attr && attr->handle) {
handle = attr->handle;
}
LOG_DBG("Hdl[%u]", handle);
LOG_DBG("GattAttrGetHdl[%u]", handle);
return handle;
}
@@ -162,32 +165,29 @@ uint16_t bt_gatt_attr_value_handle(const struct bt_gatt_attr *attr)
* Currently this function is only used by TMAP.
*/
LOG_DBG("GattAttrValHdl");
if (attr) {
LOG_DBG("GattAttrUuid[%s]", attr->uuid ? bt_uuid_str(attr->uuid) : "Null");
if (attr->uuid == NULL) {
handle = (attr->handle + 1);
} else {
LOG_DBG("Uuid[%s]", bt_uuid_str(attr->uuid));
if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC) == 0) {
struct bt_gatt_chrc *chrc = attr->user_data;
assert(chrc);
handle = chrc->value_handle;
LOG_DBG("ValHdl[%u]", handle);
if (handle == 0) {
/* Fall back to Zephyr value handle policy */
handle = bt_gatt_attr_get_handle(attr) + 1;
LOG_INF("ValHdlNew[%u]", handle);
LOG_INF("GattAttrValHdlNew[%u]", handle);
}
}
}
}
LOG_DBG("Hdl[%u]", handle);
LOG_DBG("GattAttrValHdl[%u]", handle);
return handle;
}
@@ -401,8 +401,13 @@ ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_gatts_attr_read(conn, attr, buf, buf_len,
offset, value, value_len);
#else
return bt_le_nimble_gatts_attr_read(conn, attr, buf, buf_len,
offset, value, value_len);
#endif
}
_LIB_ONLY
@@ -415,11 +420,15 @@ int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params
LOG_DBG("GattNtfCb[%u]", params->len);
if (conn && conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("GattNtfNotConn[%u][%u]", conn->handle, conn->state);
return -ENOTCONN;
}
#if CONFIG_BT_BLUEDROID_ENABLED
err = bt_le_bluedroid_gatts_notify(conn, params);
#else
err = bt_le_nimble_gatts_notify(conn, params);
#endif
/* gatts_notify is synchronous (mbuf-copy + dispatch on return); fire the
* caller's completion cb here so state machines like PACS_FLAG_NOTIFY_RDY
@@ -440,7 +449,7 @@ int bt_gatt_indicate(struct bt_conn *conn, struct bt_gatt_indicate_params *param
LOG_DBG("GattInd[%s]", bt_uuid_str(params->attr->uuid));
if (conn && conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("GattIndNotConn[%u][%u]", conn->handle, conn->state);
return -ENOTCONN;
}
@@ -454,7 +463,11 @@ int bt_gatt_indicate(struct bt_conn *conn, struct bt_gatt_indicate_params *param
return -ENOTSUP;
}
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_gatts_indicate(conn, params);
#else
return bt_le_nimble_gatts_indicate(conn, params);
#endif
}
_LIB_IDF
@@ -774,7 +787,8 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
LOG_DBG("GattIsSub[%04x][%s]", ccc_type, bt_uuid_str(attr->uuid));
if (conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
/* Query — disconnected conn is "not subscribed", not an error. */
LOG_INF("GattIsSubNotConn[%u][%u]", conn->handle, conn->state);
return false;
}
@@ -952,7 +966,7 @@ int bt_gatts_sub_changed(uint16_t conn_handle,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("GattsSubChgNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return -ENOTCONN;
}
@@ -975,7 +989,11 @@ int bt_gattc_disc_start_safe(uint16_t conn_handle)
int err;
LOG_DBG("GattcDiscStart[%u]", conn_handle);
bt_le_host_lock();
#if CONFIG_BT_BLUEDROID_ENABLED
err = bt_le_bluedroid_gattc_disc_start(conn_handle);
#else
err = bt_le_nimble_gattc_disc_start(conn_handle);
#endif
bt_le_host_unlock();
return err;
}
@@ -991,7 +1009,7 @@ int bt_gatt_discover(struct bt_conn *conn, struct bt_gatt_discover_params *param
LOG_DBG("GattDisc[%u][%u]", params->start_handle, params->end_handle);
if (conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("GattDiscNotConn[%u][%u]", conn->handle, conn->state);
return -ENOTCONN;
}
@@ -1000,7 +1018,11 @@ int bt_gatt_discover(struct bt_conn *conn, struct bt_gatt_discover_params *param
return -ENOTSUP;
}
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_gattc_discover(conn, params);
#else
return bt_le_nimble_gattc_discover(conn, params);
#endif
}
static struct gattc_sub *gattc_sub_find(struct bt_conn *conn)
@@ -1141,7 +1163,11 @@ static int gattc_write_ccc(struct bt_conn *conn, struct bt_gatt_subscribe_params
{
LOG_DBG("GattcWrCcc[%u][%04x]", params->ccc_handle, params->value);
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_gattc_write_ccc(conn, params);
#else
return bt_le_nimble_gattc_write_ccc(conn, params);
#endif
}
_LIB_ONLY
@@ -1167,7 +1193,7 @@ int bt_gatt_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *par
* can resubscribe.
*/
if (conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("GattSubNotConn[%u][%u]", conn->handle, conn->state);
params->value_handle = 0; /* unlinked: clear retry guard */
return -ENOTCONN;
}
@@ -1251,7 +1277,7 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p
params->value, params->value_handle, params->ccc_handle, params->end_handle);
if (conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("GattUnsubNotConn[%u][%u]", conn->handle, conn->state);
return -ENOTCONN;
}
@@ -1327,7 +1353,7 @@ int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
LOG_DBG("GattRd[%u]", params->handle_count);
if (conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("GattRdNotConn[%u][%u]", conn->handle, conn->state);
return -ENOTCONN;
}
@@ -1345,7 +1371,11 @@ int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
}
}
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_gattc_read(conn, params);
#else
return bt_le_nimble_gattc_read(conn, params);
#endif
}
_LIB_IDF
@@ -1360,7 +1390,7 @@ int bt_gatt_write(struct bt_conn *conn, struct bt_gatt_write_params *params)
LOG_DBG("GattWr[%u][%u][%u]", params->handle, params->length, params->offset);
if (conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("GattWrNotConn[%u][%u]", conn->handle, conn->state);
return -ENOTCONN;
}
@@ -1369,7 +1399,11 @@ int bt_gatt_write(struct bt_conn *conn, struct bt_gatt_write_params *params)
return -ENOTSUP;
}
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_gattc_write(conn, params);
#else
return bt_le_nimble_gattc_write(conn, params);
#endif
}
_LIB_ONLY
@@ -1388,7 +1422,7 @@ int bt_gatt_write_without_response_cb(struct bt_conn *conn, uint16_t handle,
LOG_DBG("GattWrCmd[%u][%u][%u]", handle, length, sign);
if (conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_ERR("GattWrCmdNotConn[%u][%u]", conn->handle, conn->state);
return -ENOTCONN;
}
@@ -1397,7 +1431,11 @@ int bt_gatt_write_without_response_cb(struct bt_conn *conn, uint16_t handle,
return -ENOTSUP;
}
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_gattc_write_without_rsp(conn, handle, data, length);
#else
return bt_le_nimble_gattc_write_without_rsp(conn, handle, data, length);
#endif
}
_LIB_ONLY
@@ -1407,6 +1445,7 @@ void bt_le_acl_conn_disconnected_gatt_listener(uint16_t conn_handle)
LOG_DBG("AclConnDisconnectedGattListener[%u]", conn_handle);
#if !CONFIG_BT_BLUEDROID_ENABLED
bt_le_nimble_gatt_nrp_clear(conn_handle);
/* Drop the cached GATT client database so the next connection runs a
@@ -1420,10 +1459,12 @@ void bt_le_acl_conn_disconnected_gatt_listener(uint16_t conn_handle)
* and Service Changed handling end-to-end.
*/
bt_le_nimble_gattc_db_remove(conn_handle);
#endif
/* The cached DB is dropped above, so the app will rediscover and call
* bt_gatt_subscribe() again on reconnect with the BAP library's reused
* `bt_gatt_subscribe_params` pointers. Drop those nodes from the
/* On NimBLE the cached DB was dropped above; on Bluedroid no client-side
* DB cache cleanup is performed here. Either way the app will rediscover
* and call bt_gatt_subscribe() again on reconnect with the BAP library's
* reused `bt_gatt_subscribe_params` pointers. Drop those nodes from the
* tracking list (and zero their value) so the duplicate check in
* bt_gatt_subscribe() doesn't short-circuit with -EALREADY, which
* would otherwise skip the CCCD write and starve notifications.
@@ -1438,5 +1479,9 @@ void bt_le_gatt_handle_event(uint8_t *data, size_t data_len)
{
assert(data && data_len);
#if CONFIG_BT_BLUEDROID_ENABLED
bt_le_bluedroid_gatt_handle_event(data, data_len);
#else
bt_le_nimble_gatt_handle_event(data, data_len);
#endif
}

View File

@@ -70,5 +70,9 @@ int bt_hci_cmd_send_sync(uint16_t opcode,
{
LOG_DBG("HciCmdSendSync[%04x]", opcode);
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_hci_iso_cmd_send_sync(opcode, buf, rsp);
#else
return bt_le_nimble_iso_cmd_send_sync(opcode, buf, rsp);
#endif
}

View File

@@ -17,12 +17,15 @@
#include "common/app/gap.h"
#include "common/app/gatt.h"
#if CONFIG_BT_BLUEDROID_ENABLED
#include "bluedroid/gap.h"
#include "bluedroid/gatt.h"
#endif
LOG_MODULE_REGISTER(ISO_HOST, CONFIG_BT_ISO_LOG_LEVEL);
static struct k_mutex host_mutex;
#define TIMEOUT_MS (5000 / portTICK_PERIOD_MS) /* 5s */
#if HOST_LOCK_DEBUG
void bt_le_host_lock_debug(const char *func, int line)
#else /* HOST_LOCK_DEBUG */
@@ -31,9 +34,9 @@ void bt_le_host_lock(void)
{
/* LOG_DBG("%s: %d", func, line); */
int err = k_mutex_lock(&host_mutex, TIMEOUT_MS);
int err = k_mutex_lock(&host_mutex, K_MUTEX_SHORT);
if (err) {
/* 5s wait failed: the host stack is wedged. k_mutex_lock has
/* K_MUTEX_SHORT wait failed: the host stack is wedged. k_mutex_lock has
* already logged self/holder task names. Use libc abort() rather
* than assert(0) — assert is a no-op under NDEBUG, which would
* let the caller enter the critical section without the mutex
@@ -84,11 +87,23 @@ int bt_le_host_init(void)
}
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
err = bt_le_iso_init();
#if CONFIG_BT_BLUEDROID_ENABLED
err = bt_le_bluedroid_gap_init();
if (err) {
goto deinit_l2cap;
}
err = bt_le_bluedroid_gatt_init();
if (err) {
goto deinit_bluedroid_gatt;
}
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
err = bt_le_iso_init();
if (err) {
goto deinit_bluedroid_gatt;
}
err = bt_le_iso_task_init();
if (err) {
goto deinit_iso;
@@ -98,7 +113,11 @@ int bt_le_host_init(void)
deinit_iso:
bt_le_iso_deinit();
deinit_bluedroid_gatt:
#if CONFIG_BT_BLUEDROID_ENABLED
bt_le_bluedroid_gatt_deinit();
deinit_l2cap:
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
bt_le_l2cap_deinit();
deinit_scan: /* only reachable when OTS path is compiled in */
@@ -116,6 +135,9 @@ void bt_le_host_deinit(void)
bt_le_iso_task_deinit();
bt_le_iso_deinit();
#if CONFIG_BT_BLUEDROID_ENABLED
bt_le_bluedroid_gatt_deinit();
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
bt_le_l2cap_deinit();
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */

View File

@@ -11,7 +11,11 @@
#include "sdkconfig.h"
#if CONFIG_BT_BLUEDROID_ENABLED
#include "bluedroid/gap.h"
#else
#include "nimble/gap.h"
#endif
#ifdef __cplusplus
extern "C" {

View File

@@ -11,7 +11,11 @@
#include "sdkconfig.h"
#if CONFIG_BT_BLUEDROID_ENABLED
/* ref bluedroid/app/gatt.h was an empty header; nothing to include here. */
#else
#include "nimble/gatt.h"
#endif
#ifdef __cplusplus
extern "C" {
@@ -37,6 +41,14 @@ struct bt_le_gatt_app_event_gatts_subscribe {
uint8_t reason;
};
/* Bluedroid-only: surfaces the GATTC open completion (carries the gattc_if
* the BTA dispatcher used, which NimBLE has no analogue of). */
struct bt_le_gatt_app_event_gattc_open {
uint8_t status;
uint16_t conn_handle;
uint8_t gattc_if;
};
struct bt_le_gatt_app_event {
uint8_t type;
@@ -44,6 +56,7 @@ struct bt_le_gatt_app_event {
struct bt_le_gatt_app_event_gatt_mtu_change gatt_mtu_change;
struct bt_le_gatt_app_event_gattc_disc_cmpl gattc_disc_cmpl;
struct bt_le_gatt_app_event_gatts_subscribe gatts_subscribe;
struct bt_le_gatt_app_event_gattc_open gattc_open;
};
};
@@ -51,6 +64,7 @@ enum bt_le_gatt_app_event_type {
BT_LE_GATT_APP_EVENT_GATT_MTU_CHANGE,
BT_LE_GATT_APP_EVENT_GATTC_DISC_CMPL,
BT_LE_GATT_APP_EVENT_GATTS_SUBSCRIBE,
BT_LE_GATT_APP_EVENT_GATTC_OPEN,
BT_LE_GATT_APP_EVENT_MAX,
};
@@ -67,7 +81,18 @@ void bt_le_gattc_app_disc_cmpl_event(struct bt_le_gattc_disc_cmpl_event *param);
void bt_le_gatts_app_subscribe_event(struct bt_le_gatts_subscribe_event *param);
#if CONFIG_BT_BLUEDROID_ENABLED
void bt_le_gattc_app_open_event(struct bt_le_gattc_open_event *param, uint8_t gattc_if);
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
#if !CONFIG_BT_BLUEDROID_ENABLED
/* NimBLE-only: callers post the raw ble_gap_event so the adapter can
* translate it into the host-agnostic bt_le_gatt_app_event. Bluedroid has
* no analogous app-level event stream (BTA dispatches directly to BTC),
* so this is hidden from the API surface to make misuse a compile error.
*/
void bt_le_gatt_app_post_event(uint8_t type, void *param);
#endif /* !CONFIG_BT_BLUEDROID_ENABLED */
#ifdef __cplusplus
}

View File

@@ -13,13 +13,21 @@
#include "sdkconfig.h"
#if CONFIG_BT_BLUEDROID_ENABLED
/* TODO */
#else
#include "nimble/gap.h"
#include "nimble/iso.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* 0xff is out of bt_conn_state_t range — distinguishes NULL conn from any
* real state in logs. */
#define BT_CONN_STATE_GET(_c) ((_c) ? (_c)->state : 0xff)
void bt_conn_get_acl_conns(struct bt_conn **conns, uint8_t *count);
struct bt_conn *bt_le_acl_conn_find(uint16_t conn_handle);

View File

@@ -14,7 +14,11 @@
#include <zephyr/autoconf.h>
#include <zephyr/bluetooth/gatt.h>
#if CONFIG_BT_BLUEDROID_ENABLED
#include "bluedroid/gatt.h"
#else
#include "nimble/gatt.h"
#endif
#ifdef __cplusplus
extern "C" {
@@ -73,16 +77,115 @@ struct bt_le_gatts_notify_tx_event {
uint8_t status;
};
/* Bluedroid-side adapter events. NimBLE produces ACL connect/disconnect via
* the GAP event path (BT_LE_GAP_APP_PARAM_ACL_*), but Bluedroid's BTA
* surfaces these through GATTS/GATTC callbacks. The bluedroid adapter posts
* these typed events into the iso task and translates them into ACL/MTU/
* discovery events for the LE Audio profile dispatcher. */
struct bt_le_addr_simple {
uint8_t type;
uint8_t val[6];
};
struct bt_le_gattc_connect_event {
uint16_t conn_handle;
uint8_t role;
struct bt_le_addr_simple peer;
};
struct bt_le_gattc_disconnect_event {
uint16_t conn_handle;
uint8_t reason;
};
struct bt_le_gattc_open_event {
uint8_t status;
uint16_t conn_handle;
};
struct bt_le_gattc_mtu_event {
uint8_t status;
uint16_t conn_handle;
uint16_t mtu;
};
struct bt_le_gattc_read_chrc_event {
uint8_t status;
uint16_t conn_handle;
uint16_t attr_handle;
uint16_t len;
uint8_t *value;
};
struct bt_le_gattc_write_chrc_event {
uint8_t status;
uint16_t conn_handle;
uint16_t attr_handle;
uint16_t offset;
};
struct bt_le_gatts_connect_event {
uint16_t conn_handle;
uint8_t role;
struct bt_le_addr_simple peer;
};
struct bt_le_gatts_disconnect_event {
uint16_t conn_handle;
uint8_t reason;
};
struct bt_le_gatts_mtu_event {
uint16_t conn_handle;
uint16_t mtu;
};
struct bt_le_gatts_read_event {
uint16_t conn_handle;
uint32_t trans_id;
uint8_t peer[6];
uint16_t attr_handle;
uint16_t offset;
bool is_long;
bool need_rsp;
};
struct bt_le_gatts_write_event {
uint16_t conn_handle;
uint32_t trans_id;
uint8_t peer[6];
uint16_t attr_handle;
uint16_t offset;
bool is_prep;
bool need_rsp;
uint16_t len;
uint8_t *value;
};
struct bt_le_gatt_event_param {
uint8_t type;
union {
struct bt_le_gatt_mtu_change_event gatt_mtu_change;
struct bt_le_gattc_discover_event gattc_discover;
struct bt_le_gattc_disc_cmpl_event gattc_disc_cmpl;
struct bt_le_gatts_subscribe_event gatts_subscribe;
struct bt_le_gattc_notify_rx_event gattc_notify_rx;
struct bt_le_gatts_notify_tx_event gatts_notify_tx;
struct bt_le_gatt_mtu_change_event gatt_mtu_change;
struct bt_le_gattc_discover_event gattc_discover;
struct bt_le_gattc_disc_cmpl_event gattc_disc_cmpl;
struct bt_le_gatts_subscribe_event gatts_subscribe;
struct bt_le_gattc_notify_rx_event gattc_notify_rx;
struct bt_le_gatts_notify_tx_event gatts_notify_tx;
/* Bluedroid-only paths (see comment above). */
struct bt_le_gattc_connect_event gattc_connect;
struct bt_le_gattc_disconnect_event gattc_disconnect;
struct bt_le_gattc_open_event gattc_open;
struct bt_le_gattc_mtu_event gattc_mtu;
struct bt_le_gattc_read_chrc_event gattc_read_chrc;
struct bt_le_gattc_write_chrc_event gattc_write_chrc;
struct bt_le_gatts_connect_event gatts_connect;
struct bt_le_gatts_disconnect_event gatts_disconnect;
struct bt_le_gatts_mtu_event gatts_mtu;
struct bt_le_gatts_read_event gatts_read;
struct bt_le_gatts_write_event gatts_write;
};
};
@@ -94,231 +197,22 @@ enum {
BT_LE_GATTC_NOTIFY_RX_EVENT,
BT_LE_GATTS_NOTIFY_TX_EVENT,
/* Bluedroid-only paths (see comment above). */
BT_LE_GATTC_CONNECT_EVENT,
BT_LE_GATTC_DISCONNECT_EVENT,
BT_LE_GATTC_OPEN_EVENT,
BT_LE_GATTC_MTU_EVENT,
BT_LE_GATTC_READ_CHRC_EVENT,
BT_LE_GATTC_WRITE_CHRC_EVENT,
BT_LE_GATTS_CONNECT_EVENT,
BT_LE_GATTS_DISCONNECT_EVENT,
BT_LE_GATTS_MTU_EVENT,
BT_LE_GATTS_READ_EVENT,
BT_LE_GATTS_WRITE_EVENT,
BT_LE_GATT_EVENT_MAX,
};
static inline char *audio_svc_uuid_to_str(uint16_t uuid)
{
switch (uuid) {
case BT_UUID_GAP_VAL:
return "GAP";
case BT_UUID_GATT_VAL:
return "GATT";
case BT_UUID_AICS_VAL:
return "AICS";
case BT_UUID_CAS_VAL:
return "CAS";
case BT_UUID_VCS_VAL:
return "VCS";
case BT_UUID_VOCS_VAL:
return "VOCS";
case BT_UUID_CSIS_VAL:
return "CSIS";
case BT_UUID_MCS_VAL:
return "MCS";
case BT_UUID_GMCS_VAL:
return "GMCS";
case BT_UUID_TBS_VAL:
return "TBS";
case BT_UUID_GTBS_VAL:
return "GTBS";
case BT_UUID_MICS_VAL:
return "MICS";
case BT_UUID_ASCS_VAL:
return "ASCS";
case BT_UUID_BASS_VAL:
return "BASS";
case BT_UUID_PACS_VAL:
return "PACS";
case BT_UUID_BASIC_AUDIO_VAL:
return "BASIC_AUDIO";
case BT_UUID_HAS_VAL:
return "HAS";
case BT_UUID_TMAS_VAL:
return "TMAS";
case BT_UUID_PBA_VAL:
return "PBA";
case BT_UUID_GMAS_VAL:
return "GMAS";
case BT_UUID_OTS_VAL:
return "OTS";
default:
return "Unknown";
}
}
static inline char *audio_chrc_uuid_to_str(uint16_t uuid)
{
switch (uuid) {
case BT_UUID_OTS_FEATURE_VAL:
return "OTS_FEATURE";
case BT_UUID_OTS_NAME_VAL:
return "OTS_NAME";
case BT_UUID_OTS_TYPE_VAL:
return "OTS_TYPE";
case BT_UUID_OTS_SIZE_VAL:
return "OTS_SIZE";
case BT_UUID_OTS_FIRST_CREATED_VAL:
return "OTS_FIRST_CREATED";
case BT_UUID_OTS_LAST_MODIFIED_VAL:
return "OTS_LAST_MODIFIED";
case BT_UUID_OTS_ID_VAL:
return "OTS_ID";
case BT_UUID_OTS_PROPERTIES_VAL:
return "OTS_PROPERTIES";
case BT_UUID_OTS_ACTION_CP_VAL:
return "OTS_ACTION_CP";
case BT_UUID_OTS_LIST_CP_VAL:
return "OTS_LIST_CP";
case BT_UUID_OTS_LIST_FILTER_VAL:
return "OTS_LIST_FILTER";
case BT_UUID_OTS_CHANGED_VAL:
return "OTS_CHANGED";
case BT_UUID_GATT_TMAPR_VAL:
return "TMAP_ROLE";
case BT_UUID_AICS_STATE_VAL:
return "AICS_STATE";
case BT_UUID_AICS_GAIN_SETTINGS_VAL:
return "AICS_GAIN_SETTINGS";
case BT_UUID_AICS_INPUT_TYPE_VAL:
return "AICS_INPUT_TYPE";
case BT_UUID_AICS_INPUT_STATUS_VAL:
return "AICS_INPUT_STATUS";
case BT_UUID_AICS_CONTROL_VAL:
return "AICS_CONTROL";
case BT_UUID_AICS_DESCRIPTION_VAL:
return "AICS_DESCRIPTION";
case BT_UUID_VCS_STATE_VAL:
return "VCS_STATE";
case BT_UUID_VCS_CONTROL_VAL:
return "VCS_CONTROL";
case BT_UUID_VCS_FLAGS_VAL:
return "VCS_FLAGS";
case BT_UUID_VOCS_STATE_VAL:
return "VOCS_STATE";
case BT_UUID_VOCS_LOCATION_VAL:
return "VOCS_LOCATION";
case BT_UUID_VOCS_CONTROL_VAL:
return "VOCS_CONTROL";
case BT_UUID_VOCS_DESCRIPTION_VAL:
return "VOCS_DESCRIPTION";
case BT_UUID_CSIS_SIRK_VAL:
return "CSIS_SIRK";
case BT_UUID_CSIS_SET_SIZE_VAL:
return "CSIS_SET_SIZE";
case BT_UUID_CSIS_SET_LOCK_VAL:
return "CSIS_SET_LOCK";
case BT_UUID_CSIS_RANK_VAL:
return "CSIS_RANK";
case BT_UUID_MCS_PLAYER_NAME_VAL:
return "MCS_PLAYER_NAME";
case BT_UUID_MCS_ICON_OBJ_ID_VAL:
return "MCS_ICON_OBJ_ID";
case BT_UUID_MCS_ICON_URL_VAL:
return "MCS_ICON_URL";
case BT_UUID_MCS_TRACK_CHANGED_VAL:
return "MCS_TRACK_CHANGED";
case BT_UUID_MCS_TRACK_TITLE_VAL:
return "MCS_TRACK_TITLE";
case BT_UUID_MCS_TRACK_DURATION_VAL:
return "MCS_TRACK_DURATION";
case BT_UUID_MCS_TRACK_POSITION_VAL:
return "MCS_TRACK_POSITION";
case BT_UUID_MCS_PLAYBACK_SPEED_VAL:
return "MCS_PLAYBACK_SPEED";
case BT_UUID_MCS_SEEKING_SPEED_VAL:
return "MCS_SEEKING_SPEED";
case BT_UUID_MCS_TRACK_SEGMENTS_OBJ_ID_VAL:
return "MCS_TRACK_SEGMENTS_OBJ_ID";
case BT_UUID_MCS_CURRENT_TRACK_OBJ_ID_VAL:
return "MCS_CURRENT_TRACK_OBJ_ID";
case BT_UUID_MCS_NEXT_TRACK_OBJ_ID_VAL:
return "MCS_NEXT_TRACK_OBJ_ID";
case BT_UUID_MCS_PARENT_GROUP_OBJ_ID_VAL:
return "MCS_PARENT_GROUP_OBJ_ID";
case BT_UUID_MCS_CURRENT_GROUP_OBJ_ID_VAL:
return "MCS_CURRENT_GROUP_OBJ_ID";
case BT_UUID_MCS_PLAYING_ORDER_VAL:
return "MCS_PLAYING_ORDER";
case BT_UUID_MCS_PLAYING_ORDERS_VAL:
return "MCS_PLAYING_ORDERS";
case BT_UUID_MCS_MEDIA_STATE_VAL:
return "MCS_MEDIA_STATE";
case BT_UUID_MCS_MEDIA_CONTROL_POINT_VAL:
return "MCS_MEDIA_CONTROL_POINT";
case BT_UUID_MCS_MEDIA_CONTROL_OPCODES_VAL:
return "MCS_MEDIA_CONTROL_OPCODES";
case BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID_VAL:
return "MCS_SEARCH_RESULTS_OBJ_ID";
case BT_UUID_MCS_SEARCH_CONTROL_POINT_VAL:
return "MCS_SEARCH_CONTROL_POINT";
case BT_UUID_TBS_PROVIDER_NAME_VAL:
return "TBS_PROVIDER_NAME";
case BT_UUID_TBS_UCI_VAL:
return "TBS_UCI";
case BT_UUID_TBS_TECHNOLOGY_VAL:
return "TBS_TECHNOLOGY";
case BT_UUID_TBS_URI_LIST_VAL:
return "TBS_URI_LIST";
case BT_UUID_TBS_SIGNAL_STRENGTH_VAL:
return "TBS_SIGNAL_STRENGTH";
case BT_UUID_TBS_SIGNAL_INTERVAL_VAL:
return "TBS_SIGNAL_INTERVAL";
case BT_UUID_TBS_LIST_CURRENT_CALLS_VAL:
return "TBS_LIST_CURRENT_CALLS";
case BT_UUID_CCID_VAL:
return "CCID";
case BT_UUID_TBS_STATUS_FLAGS_VAL:
return "TBS_STATUS_FLAGS";
case BT_UUID_TBS_INCOMING_URI_VAL:
return "TBS_INCOMING_URI";
case BT_UUID_TBS_CALL_STATE_VAL:
return "TBS_CALL_STATE";
case BT_UUID_TBS_CALL_CONTROL_POINT_VAL:
return "TBS_CALL_CONTROL_POINT";
case BT_UUID_TBS_OPTIONAL_OPCODES_VAL:
return "TBS_OPTIONAL_OPCODES";
case BT_UUID_TBS_TERMINATE_REASON_VAL:
return "TBS_TERMINATE_REASON";
case BT_UUID_TBS_INCOMING_CALL_VAL:
return "TBS_INCOMING_CALL";
case BT_UUID_TBS_FRIENDLY_NAME_VAL:
return "TBS_FRIENDLY_NAME";
case BT_UUID_MICS_MUTE_VAL:
return "MICS_MUTE";
case BT_UUID_ASCS_ASE_SNK_VAL:
return "ASCS_ASE_SNK";
case BT_UUID_ASCS_ASE_SRC_VAL:
return "ASCS_ASE_SRC";
case BT_UUID_ASCS_ASE_CP_VAL:
return "ASCS_ASE_CP";
case BT_UUID_BASS_CONTROL_POINT_VAL:
return "BASS_CP";
case BT_UUID_BASS_RECV_STATE_VAL:
return "BASS_RECV_STATE";
case BT_UUID_PACS_SNK_VAL:
return "PACS_SNK";
case BT_UUID_PACS_SNK_LOC_VAL:
return "PACS_SNK_LOC";
case BT_UUID_PACS_SRC_VAL:
return "PACS_SRC";
case BT_UUID_PACS_SRC_LOC_VAL:
return "PACS_SRC_LOC";
case BT_UUID_PACS_AVAILABLE_CONTEXT_VAL:
return "PACS_AVAILABLE_CONTEXT";
case BT_UUID_PACS_SUPPORTED_CONTEXT_VAL:
return "PACS_SUPPORTED_CONTEXT";
case BT_UUID_HAS_HEARING_AID_FEATURES_VAL:
return "HAS_HEARING_AID_FEATURES";
case BT_UUID_HAS_PRESET_CONTROL_POINT_VAL:
return "HAS_PRESET_CONTROL_POINT";
case BT_UUID_HAS_ACTIVE_PRESET_INDEX_VAL:
return "HAS_ACTIVE_PRESET_INDEX";
default:
return "Unknown";
}
}
struct gattc_sub {
uint8_t id;
bt_addr_le_t peer;

View File

@@ -13,12 +13,18 @@
#include "sdkconfig.h"
#if CONFIG_BT_BLUEDROID_ENABLED
#include "bluedroid/iso.h"
#else
#include "nimble/iso.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
struct net_buf;
struct bt_le_iso_cb {
void (*cis_dis)(struct net_buf *buf);
void (*cis_est)(struct net_buf *buf);

View File

@@ -13,7 +13,11 @@
#include "sdkconfig.h"
#if CONFIG_BT_BLUEDROID_ENABLED
/* TODO */
#else
#include "nimble/l2cap.h"
#endif
#ifdef __cplusplus
extern "C" {

View File

@@ -13,7 +13,11 @@
#include "sdkconfig.h"
#if CONFIG_BT_BLUEDROID_ENABLED
#include "bluedroid/gap.h"
#else
#include "nimble/gap.h"
#endif
#ifdef __cplusplus
extern "C" {

View File

@@ -20,18 +20,32 @@
extern "C" {
#endif
#if CONFIG_BT_BLUEDROID_ENABLED
#if CONFIG_BT_BLUEDROID_PINNED_TO_CORE
#define ISO_TASK_CORE CONFIG_BT_BLUEDROID_PINNED_TO_CORE
#else /* CONFIG_BT_BLUEDROID_PINNED_TO_CORE */
#define ISO_TASK_CORE (0)
#endif /* CONFIG_BT_BLUEDROID_PINNED_TO_CORE */
#else /* CONFIG_BT_BLUEDROID_ENABLED */
#if CONFIG_BT_NIMBLE_PINNED_TO_CORE
#define ISO_TASK_CORE CONFIG_BT_NIMBLE_PINNED_TO_CORE
#else /* CONFIG_BT_NIMBLE_PINNED_TO_CORE */
#define ISO_TASK_CORE (0)
#endif /* CONFIG_BT_NIMBLE_PINNED_TO_CORE */
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
#define ISO_TASK_STACK_SIZE 4096
#define ISO_TASK_NAME "iso_task"
/* Ref:
* - Bluedroid BTC task: configMAX_PRIORITIES - 6
* - Bluedroid BTU task: configMAX_PRIORITIES - 5
* - NimBLE Host task: configMAX_PRIORITIES - 4
*/
#if CONFIG_BT_BLUEDROID_ENABLED
#define ISO_TASK_PRIO (configMAX_PRIORITIES - 5)
#else
#define ISO_TASK_PRIO (configMAX_PRIORITIES - 4)
#endif
enum iso_queue_item_type {
ISO_QUEUE_ITEM_TYPE_TIMER_EVENT,

View File

@@ -25,11 +25,19 @@
LOG_MODULE_REGISTER(ISO_SHIM, CONFIG_BT_ISO_LOG_LEVEL);
#if CONFIG_BT_BLUEDROID_ENABLED
#ifdef CONFIG_BT_BLE_ISO_STD_FLOW_CTRL
#define ISO_STD_FLOW_CTRL true
#else /* CONFIG_BT_BLE_ISO_STD_FLOW_CTRL */
#define ISO_STD_FLOW_CTRL false
#endif /* CONFIG_BT_BLE_ISO_STD_FLOW_CTRL */
#else /* CONFIG_BT_BLUEDROID_ENABLED */
#ifdef CONFIG_BT_NIMBLE_ISO_STD_FLOW_CTRL
#define ISO_STD_FLOW_CTRL true
#else /* CONFIG_BT_NIMBLE_ISO_STD_FLOW_CTRL */
#define ISO_STD_FLOW_CTRL false
#endif /* CONFIG_BT_NIMBLE_ISO_STD_FLOW_CTRL */
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
#define ISO_PKT_FIRST_FRAG (0b00)
#define ISO_PKT_CONT_FRAG (0b01)
@@ -757,7 +765,11 @@ void bt_le_iso_handle_rx_data(uint8_t *data, size_t data_len)
int bt_le_iso_disconnect(uint16_t conn_handle, uint8_t reason)
{
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_iso_disconnect(conn_handle, reason);
#else
return bt_le_nimble_iso_disconnect(conn_handle, reason);
#endif
}
static void iso_features_set(void)
@@ -819,7 +831,11 @@ int bt_le_iso_init(void)
r_ble_ll_isoal_tx_comp_cb_set(iso_tx_comp_cb);
#endif /* CONFIG_BT_ISO_TX */
#if CONFIG_BT_BLUEDROID_ENABLED
err = bt_le_bluedroid_iso_init();
#else
err = bt_le_nimble_iso_init();
#endif
if (err) {
return err;
}
@@ -839,5 +855,9 @@ void bt_le_iso_deinit(void)
r_ble_ll_isoal_tx_comp_cb_set(NULL);
#endif /* CONFIG_BT_ISO_TX */
#if CONFIG_BT_BLUEDROID_ENABLED
bt_le_bluedroid_iso_deinit();
#else
bt_le_nimble_iso_deinit();
#endif
}

View File

@@ -146,7 +146,7 @@ int bt_le_l2cap_accept(uint16_t conn_handle, uint16_t psm,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_INF("L2capAcceptNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
*result = L2CAP_LE_ERR_INVALID_PARAMS;
return -ENOTCONN;
@@ -219,7 +219,7 @@ void bt_le_l2cap_connected(uint16_t conn_handle, uint16_t psm,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_INF("L2capConnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return;
}
@@ -255,7 +255,7 @@ void bt_le_l2cap_disconnected(uint16_t conn_handle, uint16_t psm)
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_INF("L2capDisconnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return;
}
@@ -294,7 +294,7 @@ void bt_le_l2cap_received(uint16_t conn_handle, uint16_t psm,
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("NotConn[%d]", __LINE__);
LOG_INF("L2capReceivedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return;
}
@@ -315,7 +315,9 @@ void bt_le_l2cap_received(uint16_t conn_handle, uint16_t psm,
_IDF_ONLY
int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm)
{
#if !CONFIG_BT_BLUEDROID_ENABLED
int err;
#endif
LOG_DBG("L2capChanConnect[%04x]", psm);
@@ -334,6 +336,12 @@ int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint
return -EINVAL;
}
#if CONFIG_BT_BLUEDROID_ENABLED
/* L2CAP COC is not yet implemented in the Bluedroid adapter (no
* bt_le_bluedroid_l2cap_chan_connect exists). Return early so callers
* like bt_gatt_ots_l2cap_connect see a clean -ENOTSUP. */
return -ENOTSUP;
#else
err = bt_le_nimble_l2cap_chan_connect(conn->handle);
if (err) {
return err;
@@ -342,12 +350,15 @@ int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint
l2cap_chan_add(conn, chan, psm);
return 0;
#endif
}
_IDF_ONLY
int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
{
#if !CONFIG_BT_BLUEDROID_ENABLED
int err;
#endif
LOG_DBG("L2capChanDisconnect");
@@ -361,6 +372,9 @@ int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
return -ENOTCONN;
}
#if CONFIG_BT_BLUEDROID_ENABLED
return -ENOTSUP;
#else
err = bt_le_nimble_l2cap_chan_disconnect(chan);
if (err) {
/* If the disconnect failed, remove the channel from the connection.
@@ -371,6 +385,7 @@ int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
}
return err;
#endif
}
_IDF_ONLY
@@ -393,7 +408,11 @@ int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
return -EMSGSIZE;
}
#if CONFIG_BT_BLUEDROID_ENABLED
return -ENOTSUP;
#else
return bt_le_nimble_l2cap_chan_send(chan, buf);
#endif
}
_IDF_ONLY
@@ -490,10 +509,18 @@ int bt_le_l2cap_init(void)
}
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
#if CONFIG_BT_BLUEDROID_ENABLED
/* No adapter-level L2CAP init exists (COC not implemented). Don't fail
* host init here — the host-agnostic OTS registrations above are still
* valid; any actual COC connect attempt later returns -ENOTSUP in
* bt_l2cap_chan_connect, so OTS features fail gracefully at use time
* instead of preventing the whole host from coming up. */
#else
err = bt_le_nimble_l2cap_init();
if (err) {
return err;
}
#endif
return 0;
}

View File

@@ -484,7 +484,12 @@ int bt_le_scan_start(const struct bt_le_scan_param *param, void *cb)
int err = 0;
if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING) == false) {
#if CONFIG_BT_BLUEDROID_ENABLED
ARG_UNUSED(cb);
err = bt_le_bluedroid_scan_start(param);
#else
err = bt_le_nimble_scan_start(param, cb);
#endif
if (err == 0) {
atomic_set_bit(bt_dev.flags, BT_DEV_SCANNING);
}
@@ -501,7 +506,11 @@ int bt_le_scan_stop(void)
int err = 0;
if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) {
#if CONFIG_BT_BLUEDROID_ENABLED
err = bt_le_bluedroid_scan_stop();
#else
err = bt_le_nimble_scan_stop();
#endif
if (err == 0) {
atomic_clear_bit(bt_dev.flags, BT_DEV_SCANNING);
}

View File

@@ -13,6 +13,49 @@
#define CONFIG_LITTLE_ENDIAN 1
#define CONFIG_BT_CONN_TX_USER_DATA_SIZE 8
#if CONFIG_BT_BLUEDROID_ENABLED
#define CONFIG_BT_ISO_EXT_ADV CONFIG_BT_BLE_50_EXTEND_ADV_EN
#define CONFIG_BT_ISO_PER_ADV CONFIG_BT_BLE_50_PERIODIC_ADV_EN
/* Bluedroid host does not expose ext-adv/periodic-sync slot counts;
* keep using the controller-side limits as the source of truth. */
#if CONFIG_BT_ISO_EXT_ADV
#define CONFIG_BT_EXT_ADV_MAX_ADV_SET CONFIG_BT_LE_MAX_EXT_ADV_INSTANCES
#else /* CONFIG_BT_ISO_EXT_ADV */
#define CONFIG_BT_EXT_ADV_MAX_ADV_SET 0
#endif /* CONFIG_BT_ISO_EXT_ADV */
#if CONFIG_BT_ISO_PER_ADV
#define CONFIG_BT_PER_ADV_SYNC_MAX CONFIG_BT_LE_MAX_PERIODIC_SYNCS
#else /* CONFIG_BT_ISO_PER_ADV */
#define CONFIG_BT_PER_ADV_SYNC_MAX 0
#endif /* CONFIG_BT_ISO_PER_ADV */
#if CONFIG_BT_BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER
#define CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER CONFIG_BT_BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER
#define CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER CONFIG_BT_BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER
#else /* CONFIG_BT_BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER */
#define CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER 0
#define CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER 0
#endif /* CONFIG_BT_BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER */
#define CONFIG_BT_MAX_CONN CONFIG_BT_ACL_CONNECTIONS
#define CONFIG_BT_SMP CONFIG_BT_BLE_SMP_ENABLE
#define CONFIG_BT_MAX_PAIRED CONFIG_BT_SMP_MAX_BONDS
#if CONFIG_BT_ISO_UNICAST && CONFIG_BT_ISO_BROADCAST
_Static_assert(CONFIG_BT_ISO_MAX_CHAN <= CONFIG_BT_BLE_ISO_CIS_MAX_COUNT + CONFIG_BT_BLE_ISO_BIS_MAX_COUNT, "Too large ISO channels");
#elif CONFIG_BT_ISO_UNICAST && !CONFIG_BT_ISO_BROADCAST
_Static_assert(CONFIG_BT_ISO_MAX_CHAN <= CONFIG_BT_BLE_ISO_CIS_MAX_COUNT, "Too large ISO channels");
#elif !CONFIG_BT_ISO_UNICAST && CONFIG_BT_ISO_BROADCAST
_Static_assert(CONFIG_BT_ISO_MAX_CHAN <= CONFIG_BT_BLE_ISO_BIS_MAX_COUNT, "Too large ISO channels");
#else /* CONFIG_BT_ISO_UNICAST && CONFIG_BT_ISO_BROADCAST */
_Static_assert(CONFIG_BT_ISO_MAX_CHAN == 0, "Too large ISO channels");
#endif /* CONFIG_BT_ISO_UNICAST && CONFIG_BT_ISO_BROADCAST */
#else /* CONFIG_BT_BLUEDROID_ENABLED */
#define CONFIG_BT_MAX_CONN CONFIG_BT_NIMBLE_MAX_CONNECTIONS
#define CONFIG_BT_SMP CONFIG_BT_NIMBLE_SECURITY_ENABLE
#define CONFIG_BT_MAX_PAIRED CONFIG_BT_NIMBLE_MAX_BONDS
@@ -46,3 +89,5 @@ _Static_assert(CONFIG_BT_ISO_MAX_CHAN <= CONFIG_BT_NIMBLE_ISO_BIS, "Too large IS
#else /* CONFIG_BT_ISO_UNICAST && CONFIG_BT_ISO_BROADCAST */
_Static_assert(CONFIG_BT_ISO_MAX_CHAN == 0, "Too large ISO channels");
#endif /* CONFIG_BT_ISO_UNICAST && CONFIG_BT_ISO_BROADCAST */
#endif /* CONFIG_BT_BLUEDROID_ENABLED */

View File

@@ -28,6 +28,7 @@ extern "C" {
/* Mutex */
#define K_MUTEX_FOREVER portMAX_DELAY
#define K_MUTEX_SHORT (5000 / portTICK_PERIOD_MS)
struct k_mutex {
SemaphoreHandle_t handle;
@@ -99,6 +100,94 @@ static inline int k_mutex_unlock(struct k_mutex *mutex)
return 0;
}
/* Semaphore */
#define K_SEM_FOREVER portMAX_DELAY
#define K_SEM_SHORT (5000 / portTICK_PERIOD_MS)
struct k_sem {
SemaphoreHandle_t handle;
int result;
};
static inline void k_sem_create(struct k_sem *sem)
{
assert(sem);
assert(sem->handle == NULL);
sem->handle = xSemaphoreCreateBinary();
assert(sem->handle);
sem->result = 0;
}
static inline void k_sem_delete(struct k_sem *sem)
{
assert(sem);
assert(sem->handle);
vSemaphoreDelete(sem->handle);
sem->handle = NULL;
}
/* Inline log helper with a fixed tag, mirroring K_MUTEX_LOG_ERR. The sem
* helpers are inlined across many TUs, not all of which call
* LOG_MODULE_REGISTER, so we cannot reference the per-TU __iso_log_tag.
* Hardcode the "ISO_SEM" tag and keep the same level gating. */
#if CONFIG_BT_ISO_NO_LOG || (CONFIG_BT_ISO_LOG_LEVEL < BT_ISO_LOG_ERROR)
#define K_SEM_LOG_ERR(fmt, args...)
#else
#define K_SEM_LOG_ERR(fmt, args...) BT_ISO_LOGE("ISO_SEM", fmt, ## args)
#endif
static inline int k_sem_take(struct k_sem *sem, uint32_t timeout)
{
assert(sem);
assert(sem->handle);
/* Do NOT touch sem->result here. The producer may have already written
* it and called k_sem_give before this take ran (BTU/HCI cb on a
* higher-priority task). Clearing now would race-overwrite the
* producer's value. Caller must k_sem_reset() before initiating the
* async op that will produce the result. */
if (xSemaphoreTake(sem->handle, timeout) == pdTRUE) {
return 0;
}
#if !CONFIG_BT_ISO_NO_LOG && (CONFIG_BT_ISO_LOG_LEVEL >= BT_ISO_LOG_ERROR)
K_SEM_LOG_ERR("TakeFail[self=%s]", pcTaskGetName(NULL));
#else
K_SEM_LOG_ERR("TakeFail");
#endif
return -EIO;
}
static inline int k_sem_give(struct k_sem *sem)
{
assert(sem);
assert(sem->handle);
if (xSemaphoreGive(sem->handle) != pdTRUE) {
K_SEM_LOG_ERR("GiveFail");
return -EIO;
}
return 0;
}
/* Discard any pending give. Used by callers that reuse the same sem across
* cmd/response cycles where a previous cycle timed out — without this, a
* late give from the timed-out cycle would unblock the next take and the
* caller would see uninitialized response data. */
static inline void k_sem_reset(struct k_sem *sem)
{
assert(sem);
assert(sem->handle);
xQueueReset(sem->handle);
sem->result = 0;
}
/* Timer */
typedef uint32_t k_timeout_t;

View File

@@ -9,9 +9,9 @@
This example acts as a **BAP Broadcast Sink**. It scans for extended advertisements that carry the Broadcast Audio Announcement Service UUID and whose complete-name AD matches the hard-coded `"BAP Broadcast Source"` string; on a hit, it creates a periodic-advertising sync, builds a BAP broadcast sink for that PA handle and broadcast ID, decodes the BASE / BIGInfo from the PA channel, and then synchronizes to the BIG to receive BIS streams.
The build runs on top of the NimBLE host stack and the ESP BLE Audio component set. Sink-side APIs used: `esp_ble_audio_common_init` / `_start`, `esp_ble_audio_pacs_register` + `esp_ble_audio_pacs_cap_register` (sink PAC and sink location enabled), `esp_ble_audio_bap_scan_delegator_register` (so a Broadcast Assistant can drive PA-sync, broadcast-code, and BIS-sync requests via BASS), `esp_ble_audio_bap_broadcast_sink_register_cb`, `esp_ble_audio_bap_broadcast_sink_create` / `_sync` / `_stop` / `_delete`, and `esp_ble_audio_bap_base_get_subgroup_count` / `_get_bis_indexes`. PAC capabilities are LC3 with sample rates 16 kHz + 24 kHz, frame duration 10 ms, 1 channel, 4060 octets/frame, 1 frame/SDU. The fallback broadcast code is `"1234"`; if a Broadcast Assistant has supplied one through BASS it is used instead.
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. Sink-side APIs used: `esp_ble_audio_common_init` / `_start`, `esp_ble_audio_pacs_register` + `esp_ble_audio_pacs_cap_register` (sink PAC and sink location enabled), `esp_ble_audio_bap_scan_delegator_register` (so a Broadcast Assistant can drive PA-sync, broadcast-code, and BIS-sync requests via BASS), `esp_ble_audio_bap_broadcast_sink_register_cb`, `esp_ble_audio_bap_broadcast_sink_create` / `_sync` / `_stop` / `_delete`, and `esp_ble_audio_bap_base_get_subgroup_count` / `_get_bis_indexes`. PAC capabilities are LC3 with sample rates 16 kHz + 24 kHz, frame duration 10 ms, 1 channel, 4060 octets/frame, 1 frame/SDU. The fallback broadcast code is `"1234"`; if a Broadcast Assistant has supplied one through BASS it is used instead.
After PA sync is established, `ble_gap_disc_cancel()` stops the extended scanner — BASE/BIGInfo arrive over the PA channel — and `pa_sync_lost()` re-arms the scanner.
After PA sync is established, `scan_stop()` halts the extended scanner — BASE/BIGInfo arrive over the PA channel — and `pa_sync_lost()` calls `scan_start()` to re-arm. The host-specific GAP/PA-sync plumbing lives in `main/bluedroid/scan.c` and `main/nimble/scan.c`; `main.c` only sees the host-agnostic interface in `scan.h`.
## Requirements
@@ -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,8 +59,8 @@ idf.py -p PORT flash monitor
1. `app_main` initializes NVS, calls `bluetooth_init()`, and calls `esp_ble_audio_common_init(&info)` with `info.gap_cb = iso_gap_app_cb`.
2. PACS is registered (`snk_pac` + `snk_loc`), each stream's `ops` field is wired to `stream_ops`, and the LC3 sink capability is registered via `esp_ble_audio_pacs_cap_register(ESP_BLE_AUDIO_DIR_SINK, ...)`.
3. The scan delegator (`scan_delegator_cbs`: `recv_state_updated`, `pa_sync_req`, `pa_sync_term_req`, `broadcast_code`, `bis_sync_req`) and broadcast-sink callbacks (`base_recv`, `syncable`) are registered, then `esp_ble_audio_common_start(NULL)` runs.
4. `ext_scan_start()` runs passive extended discovery (`itvl=window=160`). For each `ESP_BLE_AUDIO_GAP_EVENT_EXT_SCAN_RECV`, `data_cb` matches the complete/short/broadcast name AD type against `"BAP Broadcast Source"` and records the Broadcast ID from the Broadcast Audio Service Data.
5. On match (and only when not already PA-syncing and no scan-delegator state is pinned), `pa_sync_create()` calls `ble_gap_periodic_adv_sync_create` with `skip=0`, `sync_timeout=10s`.
4. `scan_init()` performs host-specific GAP setup (Bluedroid: registers the GAP callback for `*_COMPLETE_EVT` semaphore signalling + posts `EXT_ADV_REPORT` / `PERIODIC_ADV_SYNC_ESTAB` / `PERIODIC_ADV_REPORT` / `PERIODIC_ADV_SYNC_LOST` to the audio engine; NimBLE: no-op — the scan-instance callback is passed at `ble_gap_disc` / `ble_gap_periodic_adv_sync_create` time). `scan_start()` then runs passive extended discovery (`itvl=window=160`). For each `ESP_BLE_AUDIO_GAP_EVENT_EXT_SCAN_RECV`, `data_cb` matches the complete/short/broadcast name AD type against `"BAP Broadcast Source"` and records the Broadcast ID from the Broadcast Audio Service Data.
5. On match (and only when not already PA-syncing and no scan-delegator state is pinned), `pa_sync_create(addr_type, addr, sid)` invokes the host-specific PA-sync create routine (`ble_gap_periodic_adv_sync_create` / `esp_ble_gap_periodic_adv_create_sync`) with `skip=0`, `sync_timeout=10s`.
6. `ESP_BLE_AUDIO_GAP_EVENT_PA_SYNC` clears `pa_syncing`, cancels discovery, stores `sync_handle`, and calls `esp_ble_audio_bap_broadcast_sink_create()`.
7. `base_recv_cb` extracts the subgroup count and BIS index bitfield (masked by `bis_index_mask`); when no Broadcast Assistant is connected, `requested_bis_sync` defaults to `ESP_BLE_AUDIO_BAP_BIS_SYNC_NO_PREF`.
8. `syncable_cb` AND-masks the BASE bitfield with the requested mask, copies `TARGET_BROADCAST_CODE` if the BIG is encrypted (unless BASS already supplied one), and calls `esp_ble_audio_bap_broadcast_sink_sync()` with the chosen mask and `streams_p`.

View File

@@ -1,4 +1,11 @@
set(srcs "main.c")
idf_component_register(SRCS "${srcs}"
if(CONFIG_BT_BLUEDROID_ENABLED)
list(APPEND srcs "bluedroid/scan.c")
else()
list(APPEND srcs "nimble/scan.c")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "."
REQUIRES bt nvs_flash)

View File

@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <string.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_bt_defs.h"
#include "esp_gap_ble_api.h"
#include "scan.h"
static SemaphoreHandle_t scan_sem;
/* Controller status latched by gap_event_handler for EXAMPLE_WAIT_API_CHECK. */
static esp_bt_status_t scan_op_status;
#define WAIT_API(_call) EXAMPLE_WAIT_API_CHECK(_call, scan_sem, portMAX_DELAY, scan_op_status)
static esp_ble_ext_scan_params_t ext_scan_params = {
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE,
.cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK,
.uncoded_cfg = {
.scan_type = BLE_SCAN_TYPE_PASSIVE,
.scan_interval = SCAN_INTERVAL,
.scan_window = SCAN_WINDOW,
},
};
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_SET_EXT_SCAN_PARAMS_COMPLETE_EVT:
scan_op_status = param->set_ext_scan_params.status;
xSemaphoreGive(scan_sem);
break;
case ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT:
scan_op_status = param->ext_scan_start.status;
xSemaphoreGive(scan_sem);
break;
case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
scan_op_status = param->ext_scan_stop.status;
xSemaphoreGive(scan_sem);
break;
/* PA sync / ext adv events: forwarded by adapter's BTA path, not here. */
default:
break;
}
}
int app_host_init(void)
{
esp_err_t err;
scan_sem = xSemaphoreCreateBinary();
if (scan_sem == NULL) {
ESP_LOGE(TAG, "Failed to create scan 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(scan_sem);
return err;
}
return 0;
}
int ext_scan_start(void)
{
WAIT_API(esp_ble_gap_set_ext_scan_params(&ext_scan_params));
WAIT_API(esp_ble_gap_start_ext_scan(0, 0));
ESP_LOGI(TAG, "Scanning for broadcast source...");
return ESP_OK;
}
int ext_scan_stop(void)
{
WAIT_API(esp_ble_gap_stop_ext_scan());
return ESP_OK;
}
int pa_sync_create(uint8_t addr_type, const uint8_t addr[6], uint8_t sid)
{
esp_ble_gap_periodic_adv_sync_params_t params = {
.filter_policy = 0,
.sid = sid,
.addr_type = addr_type,
.skip = PA_SYNC_SKIP,
.sync_timeout = PA_SYNC_TIMEOUT,
};
memcpy(params.addr, addr, sizeof(params.addr));
/* Fire-and-forget: sync establishment (PERIODIC_ADV_SYNC_ESTAB_EVT) is
* air-dependent and surfaces asynchronously. */
return esp_ble_gap_periodic_adv_create_sync(&params);
}
int pa_sync_terminate(uint16_t sync_handle)
{
return esp_ble_gap_periodic_adv_sync_terminate(sync_handle);
}

View File

@@ -13,10 +13,6 @@
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_system.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"
@@ -25,18 +21,13 @@
#include "ble_audio_example_init.h"
#include "ble_audio_example_utils.h"
#define TAG "BAP_BSNK"
#include "scan.h"
#define TARGET_DEVICE_NAME "BAP Broadcast Source"
#define TARGET_DEVICE_NAME_LEN (sizeof(TARGET_DEVICE_NAME) - 1)
#define TARGET_BROADCAST_CODE "1234"
#define SCAN_INTERVAL 160 /* 100ms */
#define SCAN_WINDOW 160 /* 100ms */
#define PA_SYNC_SKIP 0
#define PA_SYNC_TIMEOUT 1000 /* 1000 * 10ms = 10s */
#define PA_SYNC_HANDLE_INIT UINT16_MAX
#define CONN_HANDLE_INIT UINT16_MAX
@@ -90,61 +81,6 @@ static esp_ble_audio_pacs_cap_t cap = {
.codec_cap = &codec_cap,
};
static void ext_scan_start(void)
{
struct ble_gap_disc_params params = {0};
uint8_t own_addr_type;
int err;
err = ble_hs_id_infer_auto(0, &own_addr_type);
if (err) {
ESP_LOGE(TAG, "Failed to determine own addr type, err %d", err);
return;
}
params.passive = 1;
params.itvl = SCAN_INTERVAL;
params.window = SCAN_WINDOW;
err = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &params,
example_audio_gap_event_cb, NULL);
if (err) {
ESP_LOGE(TAG, "Failed to start scanning, err %d", err);
return;
}
ESP_LOGI(TAG, "Scanning for broadcast source...");
}
static int pa_sync_create(const bt_addr_le_t *addr, uint8_t adv_sid)
{
struct ble_gap_periodic_sync_params params = {0};
ble_addr_t sync_addr = {0};
sync_addr.type = addr->type;
memcpy(sync_addr.val, addr->a.val, sizeof(sync_addr.val));
params.skip = PA_SYNC_SKIP;
params.sync_timeout = PA_SYNC_TIMEOUT;
return ble_gap_periodic_adv_sync_create(&sync_addr, adv_sid, &params,
example_audio_gap_event_cb, NULL);
}
static int pa_sync_terminate(void)
{
int err;
err = ble_gap_periodic_adv_sync_terminate(sync_handle);
if (err) {
ESP_LOGE(TAG, "Failed to terminate PA sync, err %d", err);
return err;
}
ESP_LOGI(TAG, "PA sync terminated");
return 0;
}
static void recv_state_updated_cb(esp_ble_conn_t *conn,
const esp_ble_audio_bap_scan_delegator_recv_state_t *recv_state)
{
@@ -199,13 +135,27 @@ static int pa_sync_term_req_cb(esp_ble_conn_t *conn,
req_recv_state = recv_state;
err = pa_sync_terminate();
/* Nothing to terminate if PAST/PA-sync never landed (e.g., PAST setup failed
* earlier). Issuing the HCI terminate with the sentinel handle gets 0x12
* (Invalid HCI Command Parameters) back from the controller. Mirrors
* cap_acceptor_broadcast.c. */
if (sync_handle == PA_SYNC_HANDLE_INIT) {
ESP_LOGI(TAG, "PA sync never established, skip terminate");
return 0;
}
err = pa_sync_terminate(sync_handle);
if (err) {
ESP_LOGE(TAG, "Failed to terminate PA sync, err %d", err);
return -EIO;
}
sync_handle = PA_SYNC_HANDLE_INIT;
ESP_LOGI(TAG, "PA sync terminated");
/* Let the synthesized PA_SYNC_LOST event drive cleanup via pa_sync_lost
* (it gates on the original sync_handle). Resetting sync_handle here would
* make that gate miss and leak broadcast_sink. Mirrors
* cap_acceptor_broadcast.c. */
return 0;
}
@@ -481,7 +431,6 @@ static bool data_cb(uint8_t type, const uint8_t *data,
static void ext_scan_recv(esp_ble_audio_gap_app_event_t *event)
{
struct scan_recv_data sr = {0};
bt_addr_le_t addr;
int err;
/* Periodic advertising interval. 0 if no periodic advertising. */
@@ -500,10 +449,9 @@ static void ext_scan_recv(esp_ble_audio_gap_app_event_t *event)
if (pa_syncing == false && req_recv_state == NULL) {
broadcaster_broadcast_id = sr.broadcast_id;
addr.type = event->ext_scan_recv.addr.type;
memcpy(addr.a.val, event->ext_scan_recv.addr.val, sizeof(addr.a.val));
err = pa_sync_create(&addr, event->ext_scan_recv.sid);
err = pa_sync_create(event->ext_scan_recv.addr.type,
event->ext_scan_recv.addr.val,
event->ext_scan_recv.sid);
if (err) {
ESP_LOGE(TAG, "Failed to create PA sync, err %d", err);
return;
@@ -533,7 +481,7 @@ static void pa_sync(esp_ble_audio_gap_app_event_t *event)
* via the PA sync channel, so the extended scanner is no longer
* needed. Stop it now — pa_sync_lost() will restart it on loss.
*/
rc = ble_gap_disc_cancel();
rc = ext_scan_stop();
if (rc) {
ESP_LOGW(TAG, "Failed to stop scanning, err %d", rc);
}
@@ -618,6 +566,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(&info);
if (err) {
ESP_LOGE(TAG, "Failed to initialize audio, err %d", err);

View File

@@ -0,0 +1,90 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <string.h>
#include "esp_log.h"
#include "host/ble_gap.h"
#include "host/ble_hs.h"
#include "esp_ble_audio_common_api.h"
#include "scan.h"
/* Forward only the GAP events the application consumes. */
static int gap_event_cb(struct ble_gap_event *event, void *arg)
{
switch (event->type) {
case BLE_GAP_EVENT_EXT_DISC:
case BLE_GAP_EVENT_PERIODIC_SYNC:
case BLE_GAP_EVENT_PERIODIC_REPORT:
case BLE_GAP_EVENT_PERIODIC_SYNC_LOST:
esp_ble_audio_gap_app_post_event(event->type, event);
break;
default:
break;
}
return 0;
}
int app_host_init(void)
{
return 0;
}
int ext_scan_start(void)
{
struct ble_gap_disc_params params = {0};
uint8_t own_addr_type;
int err;
err = ble_hs_id_infer_auto(0, &own_addr_type);
if (err) {
ESP_LOGE(TAG, "Failed to determine own addr type, err %d", err);
return err;
}
params.passive = 1;
params.itvl = SCAN_INTERVAL;
params.window = SCAN_WINDOW;
err = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &params,
gap_event_cb, NULL);
if (err) {
ESP_LOGE(TAG, "Failed to start scanning, err %d", err);
return err;
}
ESP_LOGI(TAG, "Scanning for broadcast source...");
return 0;
}
int ext_scan_stop(void)
{
return ble_gap_disc_cancel();
}
int pa_sync_create(uint8_t addr_type, const uint8_t addr[6], uint8_t sid)
{
struct ble_gap_periodic_sync_params params = {0};
ble_addr_t sync_addr = {0};
sync_addr.type = addr_type;
memcpy(sync_addr.val, addr, sizeof(sync_addr.val));
params.skip = PA_SYNC_SKIP;
params.sync_timeout = PA_SYNC_TIMEOUT;
return ble_gap_periodic_adv_sync_create(&sync_addr, sid, &params,
gap_event_cb, NULL);
}
int pa_sync_terminate(uint16_t sync_handle)
{
return ble_gap_periodic_adv_sync_terminate(sync_handle);
}

View File

@@ -0,0 +1,27 @@
/*
* 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_BSNK"
#define SCAN_INTERVAL 160 /* 100ms */
#define SCAN_WINDOW 160 /* 100ms */
#define PA_SYNC_SKIP 0
#define PA_SYNC_TIMEOUT 1000 /* 1000 * 10ms = 10s */
int app_host_init(void);
int ext_scan_start(void);
int ext_scan_stop(void);
int pa_sync_create(uint8_t addr_type, const uint8_t addr[6], uint8_t sid);
int pa_sync_terminate(uint16_t sync_handle);

View File

@@ -3,14 +3,14 @@
#
CONFIG_BT_ENABLED=y
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_NVS_PERSIST=y
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
CONFIG_BT_NIMBLE_MAX_CCCDS=20
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
@@ -24,6 +24,8 @@ CONFIG_BT_PAC_SRC_NOTIFIABLE=y
CONFIG_BT_PAC_SRC_LOC_WRITEABLE=y
CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE=y
CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE=y
CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE=30
CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE=60
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
CONFIG_FREERTOS_HZ=1000

View File

@@ -0,0 +1,12 @@
# 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_NVS_PERSIST=y
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
CONFIG_BT_NIMBLE_MAX_CCCDS=20
CONFIG_BT_NIMBLE_ISO=y
CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y

View File

@@ -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.

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More