diff --git a/CMakeLists.txt b/CMakeLists.txt index e76d30bdd..27746516a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,10 @@ if(CONFIG_BLUETOOTH) list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_le_scan.c) endif() + if(CONFIG_BLUETOOTH_PA_SYNC) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_pa_sync.c) + endif() + if(CONFIG_BLUETOOTH_PAN) list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_pan.c) list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_pan.c) @@ -252,6 +256,10 @@ if(CONFIG_BLUETOOTH) ${BLUETOOTH_DIR}/service/src/scan_filter.c) endif() + if(CONFIG_BLUETOOTH_PA_SYNC) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/src/pa_sync_service.c) + endif() + if(CONFIG_BLUETOOTH_L2CAP) list(APPEND CSRCS ${BLUETOOTH_DIR}/service/src/l2cap_service.c) endif() @@ -318,6 +326,10 @@ if(CONFIG_BLUETOOTH) list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_le_scan_interface.c) endif() + if(CONFIG_BLUETOOTH_PA_SYNC) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_pa_sync_interface.c) + endif() + if(CONFIG_BLUETOOTH_GATT_CLIENT) list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_gatt_client_interface.c) endif() @@ -643,6 +655,10 @@ if(CONFIG_BLUETOOTH) list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/lea_tbs.c) endif() + if(CONFIG_BLUETOOTH_AURACAST_SINK) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/auracast_sink.c) + endif() + if(CONFIG_BLUETOOTH_STORAGE_UPDATE) list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/storage_update/storage_tool.c) endif() diff --git a/Kconfig b/Kconfig index abe3e1062..b3f0d3c8f 100644 --- a/Kconfig +++ b/Kconfig @@ -104,10 +104,16 @@ config BLUETOOTH_BLE_SCAN_FILTER depends on BLUETOOTH_BLE_SCAN config BLUETOOTH_HCI_FILTER - bool "Enable Bluetooth HCI filter" - default y - help - Enable Bluetooth HCI filter + bool "Enable Bluetooth HCI filter" + default y + help + Enable Bluetooth HCI filter + +config BLUETOOTH_PA_SYNC + bool "LE Periodic Advertising sync support" + default n + depends on BLUETOOTH_BLE_SUPPORT + depends on BLUETOOTH_BLE_SCAN config BLUETOOTH_L2CAP bool "L2CAP dynamic channel support" @@ -614,6 +620,11 @@ config BLUETOOTH_LEAUDIO_SERVER_CSIS_RANK endif # BLUETOOTH_LEAUDIO_SERVER +config BLUETOOTH_AURACAST_SINK + bool "Auracast Sink role" + default n + depends on BLUETOOTH_PA_SYNC + endmenu #Bluetooth LE Audio menu "Bluetooth Audio Transport" diff --git a/Makefile b/Makefile index dfc14bfed..49a302cbb 100644 --- a/Makefile +++ b/Makefile @@ -150,6 +150,10 @@ CSRCS += framework/socket/bt_le_scan.c CSRCS += service/ipc/socket/src/bt_socket_scan.c endif #CONFIG_BLUETOOTH_BLE_SCAN +ifeq ($(CONFIG_BLUETOOTH_PA_SYNC), y) +CSRCS += framework/socket/bt_pa_sync.c +endif #CONFIG_BLUETOOTH_PA_SYNC + ifeq ($(CONFIG_BLUETOOTH_PAN), y) CSRCS += framework/socket/bt_pan.c CSRCS += service/ipc/socket/src/bt_socket_pan.c @@ -212,6 +216,9 @@ ifeq ($(CONFIG_BLUETOOTH_BLE_SCAN), y) CSRCS += service/src/scan_record.c CSRCS += service/src/scan_filter.c endif #CONFIG_BLUETOOTH_BLE_SCAN +ifeq ($(CONFIG_BLUETOOTH_PA_SYNC), y) + CSRCS += service/src/pa_sync_service.c +endif #CONFIG_BLUETOOTH_PA_SYNC ifeq ($(CONFIG_BLUETOOTH_L2CAP), y) CSRCS += service/src/l2cap_service.c endif #CONFIG_BLUETOOTH_L2CAP @@ -259,6 +266,9 @@ endif #CONFIG_BLUETOOTH_BLE_ADV ifeq ($(CONFIG_BLUETOOTH_BLE_SCAN), y) CSRCS += service/stacks/zephyr/sal_le_scan_interface.c endif #CONFIG_BLUETOOTH_BLE_SCAN +ifeq ($(CONFIG_BLUETOOTH_PA_SYNC), y) + CSRCS += service/stacks/zephyr/sal_pa_sync_interface.c +endif #CONFIG_BLUETOOTH_PA_SYNC ifeq ($(CONFIG_BLUETOOTH_GATT_CLIENT), y) CSRCS += service/stacks/zephyr/sal_gatt_client_interface.c endif #CONFIG_BLUETOOTH_GATT_CLIENT @@ -504,6 +514,10 @@ ifeq ($(CONFIG_BLUETOOTH_LEAUDIO_TBS), y) CSRCS += tools/lea_tbs.c endif +ifeq ($(CONFIG_BLUETOOTH_AURACAST_SINK), y) + CSRCS += tools/auracast_sink.c +endif + ifeq ($(CONFIG_BLUETOOTH_STORAGE_UPDATE), y) CSRCS += tools/storage_update/storage_tool.c endif #CONFIG_BLUETOOTH_STORAGE_UPDATE diff --git a/framework/common/advertiser_data_helper.c b/framework/common/advertiser_data_helper.c index 35f8b804d..11a462ad3 100644 --- a/framework/common/advertiser_data_helper.c +++ b/framework/common/advertiser_data_helper.c @@ -18,6 +18,10 @@ #include "advertiser_data.h" #include "bt_debug.h" +#ifdef CONFIG_BLUETOOTH_PA_SYNC +#include "bt_pa_sync.h" +#endif +#include "bt_utils.h" typedef struct { uint8_t ad_type; @@ -213,3 +217,92 @@ bool advertiser_data_dump(uint8_t* data, uint16_t len, ad_dump_cb_t dump) return true; } + +bool advertiser_data_parse(const uint8_t* data, uint8_t len, ad_parse_cb_t cb, void* context) +{ + adv_data_t* ad; + uint16_t offset = 0; + + if (!cb) + return false; + + while (offset < len) { + ad = (adv_data_t*)&data[offset]; + if (ad->len == 0) { /**< AD Type does not exist */ + offset += sizeof(ad->len); /**< Skip this entry */ + continue; /**< Goto the next item */ + } + + offset += sizeof(ad->len) + ad->len; + if (offset > len) + return false; /**< Incomplete AD Data */ + + cb(ad, context); + }; + + return true; +} + +#ifdef CONFIG_BLUETOOTH_PA_SYNC +static void adv_data_parse_uuid_16(bt_pa_sync_info_t* info, uint16_t uuid_16, + const adv_data_t* data) +{ + const uint8_t* p = data->data + sizeof(uint16_t); + + switch (uuid_16) { +#ifdef CONFIG_BLUETOOTH_AURACAST_SINK + case BT_UUID_BROADCAST_AUDIO_ANNOUNCEMENT: + if (data->len < 1 + sizeof(uuid_16) + 3) + break; /* less than AD Type (1 octet) + UUID16 (2 octets) + Broadcast ID (3 octets)*/ + + STREAM_TO_UINT24(info->broadcast_id, p); + break; +#endif + default: + break; + } +} + +static void adv_data_parsed(const adv_data_t* data, void* context) +{ + bt_pa_sync_info_t* info = (bt_pa_sync_info_t*)context; + const uint8_t* p = data->data; + uint16_t uuid_16; + + switch (data->type) { + case BT_AD_NAME_SHORT: + case BT_AD_NAME_COMPLETE: + strlcpy(info->name, (char*)data->data, MIN(sizeof(info->name), data->len)); + break; + +#ifdef CONFIG_BLUETOOTH_AURACAST_SINK + case BT_AD_BROADCAST_NAME: + strlcpy(info->broadcast_name, (char*)data->data, + MIN(sizeof(info->broadcast_name), data->len)); + break; +#endif + + case BT_AD_SERVICE_DATA16: + if (data->len < 1 + sizeof(uuid_16)) + break; /**< less than AD Type (1 octet) + UUID16 (2 octets) */ + + STREAM_TO_UINT16(uuid_16, p); + adv_data_parse_uuid_16(info, uuid_16, data); + break; + default: + break; + } +} + +bt_status_t bt_pa_sync_parse_adv_data(bt_pa_sync_info_t* info, const ble_scan_result_t* result) +{ + memset(info, 0x00, sizeof(bt_pa_sync_info_t)); + info->broadcast_id = BT_INVALID_BROADCAST_ID; + if (!advertiser_data_parse(result->adv_data, result->length, adv_data_parsed, info)) + return BT_STATUS_FAIL; + + return result->flags & SCAN_RESULT_FLAG_PERIODIC_ADVERTISING + ? BT_STATUS_SUCCESS + : BT_STATUS_NOT_FOUND; +} +#endif /* CONFIG_BLUETOOTH_PA_SYNC */ diff --git a/framework/include/advertiser_data.h b/framework/include/advertiser_data.h index 649e9ef95..37ed639f8 100644 --- a/framework/include/advertiser_data.h +++ b/framework/include/advertiser_data.h @@ -105,6 +105,16 @@ typedef struct advertiser_data_ advertiser_data_t; */ typedef void (*ad_dump_cb_t)(const char* str); +/** + * @brief Advertising data parsing callback function. + * + * Function prototype for parsing advertising data. + * + * @param data - The parsed advertising data + * @param context - User context + */ +typedef void (*ad_parse_cb_t)(const adv_data_t* data, void* context); + /** * @brief Dump advertising data. * @@ -131,6 +141,19 @@ static void on_scan_result_cb(bt_scanner_t* scanner, ble_scan_result_t* result) */ bool advertiser_data_dump(uint8_t* data, uint16_t len, ad_dump_cb_t dump); +/** + * @brief Parse advertising data. + * + * @param data - Pointer to the advertising data buffer + * @param len - Length of the advertising data buffer + * @param cb - Callback function to output the parsed data + * @param context - User context to be carried in `cb` + * + * @return true - Parsing and output were successful + * @return false - Parsing failed + */ +bool advertiser_data_parse(const uint8_t* data, uint8_t len, ad_parse_cb_t cb, void* context); + /** * @brief Create a new advertiser data object. * diff --git a/framework/include/bluetooth.h b/framework/include/bluetooth.h index 9ce8e5be2..df9124b02 100644 --- a/framework/include/bluetooth.h +++ b/framework/include/bluetooth.h @@ -349,6 +349,11 @@ typedef uint8_t bt_128key_t[16]; #define BT_UUID_MAX_NUM (32) #define BT_UUID_128_LEN (16) +/* `Broadcast_ID` is 24-bit (0x000000-0xFFFFFF). Other values are invalid. */ +#define BT_INVALID_BROADCAST_ID (0xFFFFFFFF) + +#define BT_BROADCAST_NAME_MAX_LEN (128) + typedef struct { bt_address_t addr; int8_t rssi; diff --git a/framework/include/bt_le_scan.h b/framework/include/bt_le_scan.h index c34abf2f2..eec5b0ce3 100644 --- a/framework/include/bt_le_scan.h +++ b/framework/include/bt_le_scan.h @@ -65,6 +65,8 @@ enum { #define SCAN_MODE_LOW_LATENCY_INTERVAL 0xA0 #define SCAN_MODE_LOW_LATENCY_WINDOW 0xA0 +#define SCAN_RESULT_FLAG_PERIODIC_ADVERTISING 0x01 + typedef void bt_scanner_t; typedef enum { @@ -79,11 +81,14 @@ typedef enum { typedef struct { bt_address_t addr; uint8_t dev_type; /* bt_device_type_t */ - int8_t rssi; + int8_t rssi; /* RSSI in dBm (-127, +20). 0x7F if unavailable */ uint8_t addr_type; /* ble_addr_type_t */ uint8_t adv_type; /* ble_adv_type_t */ - uint8_t length; - uint8_t pad[1]; + uint8_t length; /* length of `adv_data` */ + uint8_t sid; /* advertising set identifier, valid from 0x00 to 0x0F, 0xFF if not provided */ + uint16_t interval; /* periodic advertising interval in 1.25 milliseconds, 0 if not presented */ + int8_t tx_power; /* transmit power of the advertiser in dBm (-127, +20). 0x7F if unavailable */ + uint8_t flags; /* e.g., `SCAN_RESULT_FLAG_PERIODIC_ADVERTISING` */ uint8_t adv_data[1]; } ble_scan_result_t; diff --git a/framework/include/bt_pa_sync.h b/framework/include/bt_pa_sync.h new file mode 100644 index 000000000..387b0265f --- /dev/null +++ b/framework/include/bt_pa_sync.h @@ -0,0 +1,60 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_PA_SYNC_H__ +#define __BT_PA_SYNC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_le_scan.h" + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +/** + * @brief Information about the periodic advertising sync. + */ +typedef struct bt_pa_sync_info { + /** Present if available: Broadcast_ID from Broadcast Audio Announcement. + * Otherwise, `BT_INVALID_BROADCAST_ID` */ + uint32_t broadcast_id; + + /** UTF-8 string of the remote device name */ + char name[BT_REM_NAME_MAX_LEN + 1]; + + /** UTF-8 string of the `Broadcast_Name` field in `AdvData` (if present) */ + char broadcast_name[BT_BROADCAST_NAME_MAX_LEN + 1]; +} bt_pa_sync_info_t; + +/** + * @brief Parse an advertising report and check if periodic advertising is present. + * + * @param[out] info Buffer to store the parsed periodic advertising info + * @param[in] result Advertising report from the scan result callback + * + * @return `BT_STATUS_SUCCESS` if periodic advertising is found + * @return `BT_STATUS_NOT_FOUND` if no periodic advertising is found + * @return Other negative `bt_status_t` error codes on failure. + */ +bt_status_t bt_pa_sync_parse_adv_data(bt_pa_sync_info_t* info, const ble_scan_result_t* result); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/framework/include/bt_uuid.h b/framework/include/bt_uuid.h index c8e12eed1..d137b50c8 100644 --- a/framework/include/bt_uuid.h +++ b/framework/include/bt_uuid.h @@ -31,6 +31,7 @@ extern "C" { #define BT_UUID_A2DP_SNK 0x110B #define BT_UUID_HFP 0x111E #define BT_UUID_HFP_AG 0x111F +#define BT_UUID_BROADCAST_AUDIO_ANNOUNCEMENT 0x1852 typedef enum { BT_UUID16_TYPE = 2, diff --git a/framework/socket/bt_pa_sync.c b/framework/socket/bt_pa_sync.c new file mode 100644 index 000000000..630c038ef --- /dev/null +++ b/framework/socket/bt_pa_sync.c @@ -0,0 +1,17 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "bt_pa_sync.h" diff --git a/service/ipc/socket/include/bt_message_scan.h b/service/ipc/socket/include/bt_message_scan.h index f60694221..28133cfe7 100644 --- a/service/ipc/socket/include/bt_message_scan.h +++ b/service/ipc/socket/include/bt_message_scan.h @@ -26,7 +26,7 @@ BT_SCAN_MESSAGE_START, #ifdef __BT_CALLBACK_CODE__ BT_SCAN_CALLBACK_START, - BT_LE_ON_SCAN_RESULT, + BT_LE_ON_SCAN_RESULT_UNUSED, // Unused, but kept to maintain enum stability BT_LE_ON_SCAN_START_STATUS, BT_LE_ON_SCAN_STOPPED, BT_SCAN_CALLBACK_END, @@ -46,7 +46,9 @@ BT_SCAN_MESSAGE_START, enum { BLE_SCAN_SUBCODE_START = 0, - BLE_SCAN_SUBCODE_BATCH_SCAN_CALLBACK = 1, + BLE_SCAN_SUBCODE_BATCH_SCAN_CALLBACK_UNUSED = 1, // Unused + BLE_SCAN_SUBCODE_SCAN_CALLBACK = 2, + BLE_SCAN_SUBCODE_BATCH_SCAN_CALLBACK = 3, BLE_SCAN_SUBCODE_END = BT_IPC_CODE_SUBCODE_MAX_NUM, }; @@ -56,6 +58,7 @@ BT_SCAN_MESSAGE_START, #define BT_IPC_CODE_CALLBACK_BLE_SCAN_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_BLE_SCAN, 0) // TODO: Add new BT IPC Code sequentially +#define BT_LE_ON_SCAN_RESULT BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_BLE_SCAN, BLE_SCAN_SUBCODE_SCAN_CALLBACK) #define BT_LE_ON_BATCH_SCAN_RESULT BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_BLE_SCAN, BLE_SCAN_SUBCODE_BATCH_SCAN_CALLBACK) #define BT_IPC_CODE_CALLBACK_BLE_SCAN_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_BLE_SCAN, BT_IPC_CODE_SUBCODE_MAX_NUM) diff --git a/service/ipc/socket/src/bt_socket_scan.c b/service/ipc/socket/src/bt_socket_scan.c index fbd50a939..337a11ab4 100644 --- a/service/ipc/socket/src/bt_socket_scan.c +++ b/service/ipc/socket/src/bt_socket_scan.c @@ -271,14 +271,7 @@ int bt_socket_client_scan_callback(service_poll_t* poll, int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) { switch (packet->code) { - case BT_LE_ON_SCAN_RESULT: { - bt_scan_remote_t* scan = INT2PTR(bt_scan_remote_t*) packet->scan_cb._on_scan_result_cb.scanner; - ble_scan_result_t* result = &packet->scan_cb._on_scan_result_cb.result; - ble_scan_result_t* tmp = malloc(sizeof(ble_scan_result_t) + result->length); - memcpy(tmp, result, sizeof(ble_scan_result_t)); - memcpy(tmp->adv_data, packet->scan_cb._on_scan_result_cb.adv_data, result->length); - scan->callback->on_scan_result(scan, tmp); - free(tmp); + case BT_LE_ON_SCAN_RESULT_UNUSED: { break; } case BT_LE_ON_SCAN_START_STATUS: { @@ -320,6 +313,16 @@ int bt_socket_client_scan_callback(service_poll_t* poll, } break; } + case BLE_SCAN_SUBCODE_SCAN_CALLBACK: { + bt_scan_remote_t* scan = INT2PTR(bt_scan_remote_t*) packet->scan_cb._on_scan_result_cb.scanner; + ble_scan_result_t* result = &packet->scan_cb._on_scan_result_cb.result; + ble_scan_result_t* tmp = malloc(sizeof(ble_scan_result_t) + result->length); + memcpy(tmp, result, sizeof(ble_scan_result_t)); + memcpy(tmp->adv_data, packet->scan_cb._on_scan_result_cb.adv_data, result->length); + scan->callback->on_scan_result(scan, tmp); + free(tmp); + break; + } default: return BT_STATUS_PARM_INVALID; } diff --git a/service/src/adapter_service.c b/service/src/adapter_service.c index b248daa72..bf22492b8 100644 --- a/service/src/adapter_service.c +++ b/service/src/adapter_service.c @@ -33,6 +33,9 @@ #ifdef CONFIG_BLUETOOTH_L2CAP #include "l2cap_service.h" #endif +#ifdef CONFIG_BLUETOOTH_PA_SYNC +#include "pa_sync_service.h" +#endif #include "advertising.h" #include "bluetooth.h" #include "bluetooth_define.h" @@ -1441,6 +1444,10 @@ void adapter_on_le_enabled(bool enablebt) /* enable scan manager */ #ifdef CONFIG_BLUETOOTH_BLE_SCAN scan_manager_init(); +#endif + /* enable periodic advertising sync service */ +#ifdef CONFIG_BLUETOOTH_PA_SYNC + pa_sync_init(); #endif /* enable L2CAP service */ #ifdef CONFIG_BLUETOOTH_L2CAP @@ -1460,6 +1467,9 @@ void adapter_on_le_disabled(void) #ifdef CONFIG_BLUETOOTH_BLE_ADV adv_manager_cleanup(); #endif +#ifdef CONFIG_BLUETOOTH_PA_SYNC + pa_sync_cleanup(); +#endif #ifdef CONFIG_BLUETOOTH_BLE_SCAN adapter_lock(); bt_list_clear(g_adapter_service.le_devices); diff --git a/service/src/pa_sync_event.h b/service/src/pa_sync_event.h new file mode 100644 index 000000000..70b93908a --- /dev/null +++ b/service/src/pa_sync_event.h @@ -0,0 +1,79 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __PA_SYNC_EVENT_H__ +#define __PA_SYNC_EVENT_H__ + +#include "bluetooth.h" + +/** AoA Constant Tone Extension */ +#define BT_LE_PA_SYNC_EVENT_CTE_TYPE_AOA (0x00) +/** AoD Constant Tone Extension with 1 μs slots */ +#define BT_LE_PA_SYNC_EVENT_CTE_TYPE_AOD_1US (0x01) +/** AoD Constant Tone Extension with 2 μs slots */ +#define BT_LE_PA_SYNC_EVENT_CTE_TYPE_AOD_2US (0x02) +/** No Constant Tone Extension */ +#define BT_LE_PA_SYNC_EVENT_CTE_TYPE_NONE (0xFF) + +/** Data complete */ +#define BT_LE_PA_SYNC_EVENT_DATA_COMPLETE (0x00) +/** Data incomplete, more data to come */ +#define BT_LE_PA_SYNC_EVENT_DATA_MORE (0x01) +/** Data incomplete, data truncated, no more to come */ +#define BT_LE_PA_SYNC_EVENT_DATA_TRUNCATED (0x02) +/** Failed to receive an AUX_SYNC_SUBEVENT_IND PDU */ +#define BT_LE_PA_SYNC_EVENT_DATA_FAILED (0xFF) + +typedef enum { + SYNC_ESTABLISHED, + SYNC_TERMINATED, + SYNC_REPORT, +} pa_sync_event_type_t; + +typedef struct { + /** TxPower in dBm (-127 to +20). 0x7F if unavailable */ + int8_t tx_power; + + /** RSSI in dBm (-127 to +20). 0x7F if unavailable */ + int8_t rssi; + + /** CTE_Type, e.g., `BT_LE_PA_SYNC_EVENT_CTE_TYPE_NONE` */ + uint8_t cte; + + /** Periodic_Event_Counter */ + uint16_t cnt; + + /** The subevent number, range from 0x00 to 0x7F. 0xFF if no subevents */ + uint8_t subevent; + + /** Data_Status, e.g., `BT_LE_PA_SYNC_EVENT_DATA_COMPLETE` */ + uint8_t status; + + /** Data_Length */ + uint8_t adv_data_len; + + /** Data received from a Periodic Advertising packet */ + uint8_t adv_data[0]; +} pa_sync_event_report_data_t; + +typedef struct { + pa_sync_event_type_t event; + bt_controller_id_t id; + bt_le_address_t addr; + uint8_t data[0]; /**< Event specific data, e.g., @ref pa_sync_event_report_data_t */ +} pa_sync_event_t; + +#endif /* __PA_SYNC_EVENT_H__ */ \ No newline at end of file diff --git a/service/src/pa_sync_service.c b/service/src/pa_sync_service.c new file mode 100644 index 000000000..2abac63c3 --- /dev/null +++ b/service/src/pa_sync_service.c @@ -0,0 +1,67 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "pa_sync" + +#include "pa_sync_service.h" + +#include "bluetooth.h" + +#include "utils/log.h" + +typedef struct pa_sync_info { + int nothing_but_wait_for_the_first_variable; +} pa_sync_info_t; + +static pa_sync_info_t* g_pa_sync_info = NULL; + +bt_status_t pa_sync_init(void) +{ + BT_LOGD("%s", __func__); + + if (g_pa_sync_info) + return BT_STATUS_BUSY; + + g_pa_sync_info = zalloc(sizeof(pa_sync_info_t)); + + return BT_STATUS_SUCCESS; +} + +bt_status_t pa_sync_cleanup(void) +{ + BT_LOGD("%s", __func__); + + if (!g_pa_sync_info) + return BT_STATUS_DONE; + + free(g_pa_sync_info); + g_pa_sync_info = NULL; + + return BT_STATUS_SUCCESS; +} + +bt_status_t pa_sync_create(void) +{ + BT_LOGD("%s", __func__); + + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t pa_sync_terminate(void) +{ + BT_LOGD("%s", __func__); + + return BT_STATUS_UNSUPPORTED; +} diff --git a/service/src/pa_sync_service.h b/service/src/pa_sync_service.h new file mode 100644 index 000000000..8909bfba0 --- /dev/null +++ b/service/src/pa_sync_service.h @@ -0,0 +1,27 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __PA_SYNC_SERVICE_H__ +#define __PA_SYNC_SERVICE_H__ + +#include "bt_status.h" + +bt_status_t pa_sync_init(void); +bt_status_t pa_sync_cleanup(void); +bt_status_t pa_sync_create(void); +bt_status_t pa_sync_terminate(void); + +#endif /* __PA_SYNC_SERVICE_H__ */ \ No newline at end of file diff --git a/service/src/scan_manager.c b/service/src/scan_manager.c index 55d867207..993f8873d 100644 --- a/service/src/scan_manager.c +++ b/service/src/scan_manager.c @@ -459,7 +459,7 @@ void scan_on_state_changed(uint8_t state) BT_LOGD("%s, state:%d", __func__, state); } -void scan_on_result_data_update(ble_scan_result_t* result_info, char* adv_data) +void scan_on_result_data_update(ble_scan_result_t* result_info, const uint8_t* adv_data) { ble_scan_result_t* result = malloc(sizeof(ble_scan_result_t) + result_info->length); @@ -470,6 +470,11 @@ void scan_on_result_data_update(ble_scan_result_t* result_info, char* adv_data) memcpy(result, result_info, sizeof(ble_scan_result_t)); memcpy(result->adv_data, adv_data, result_info->length); + if (result->interval) + result->flags |= SCAN_RESULT_FLAG_PERIODIC_ADVERTISING; + else + result->flags &= ~SCAN_RESULT_FLAG_PERIODIC_ADVERTISING; + do_in_service_loop(notify_scanners_scan_result, result); } diff --git a/service/src/scan_manager.h b/service/src/scan_manager.h index 4c86199ef..4795702b6 100644 --- a/service/src/scan_manager.h +++ b/service/src/scan_manager.h @@ -26,7 +26,7 @@ enum scan_state { }; void scan_on_state_changed(uint8_t state); -void scan_on_result_data_update(ble_scan_result_t* result_info, char* adv_data); +void scan_on_result_data_update(ble_scan_result_t* result_info, const uint8_t* adv_data); bt_scanner_t* scanner_start_scan(void* remote, const scanner_callbacks_t* cbs); bt_scanner_t* scanner_start_scan_settings(void* remote, ble_scan_settings_t* settings, diff --git a/service/stacks/include/sal_pa_sync_interface.h b/service/stacks/include/sal_pa_sync_interface.h new file mode 100644 index 000000000..2acb261b5 --- /dev/null +++ b/service/stacks/include/sal_pa_sync_interface.h @@ -0,0 +1,156 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __SAL_PA_SYNC_INTERFACE_H__ +#define __SAL_PA_SYNC_INTERFACE_H__ + +#include "bluetooth.h" + +#ifndef SAL_BIT +#define SAL_BIT(x) (1UL << (x)) +#endif + +/** Bit 0 in Options: + * - 0: Use the Advertising_SID, Advertiser_Address_Type, and Advertiser_Address parameters to + * determine which advertiser to listen to + * - 1: Use the Periodic Advertiser List to determine which advertiser to listen to + * See @ref bt_sal_pa_list_add */ +#define BT_SAL_PA_SYNC_OPTION_USE_LIST SAL_BIT(0) + +/** Bit 1 in Options: + * - 0: Reporting initially enabled + * - 1: Reporting initially disabled */ +#define BT_SAL_PA_SYNC_OPTION_REPORTING_DISABLED SAL_BIT(1) + +/** Bit 2 in Options: + * - 0: Duplicate filtering initially disabled + * - 1: Duplicate filtering initially enabled */ +#define BT_SAL_PA_SYNC_OPTION_FILTER_ENABLED SAL_BIT(2) + +/** Bit 0 in Sync_CTE_Type: + * - 1: Do not sync to packets with an AoA Constant Tone Extension */ +#define BT_SAL_PA_SYNC_CTE_TYPE_NO_AOA SAL_BIT(0) + +/** Bit 1 in Sync_CTE_Type: + * - 1: Do not sync to packets with an AoD Constant Tone Extension with 1 μs slots */ +#define BT_SAL_PA_SYNC_CTE_TYPE_NO_AOD_1US SAL_BIT(1) + +/** Bit 2 in Sync_CTE_Type: + * - 1: Do not sync to packets with an AoD Constant Tone Extension with 2 μs slots */ +#define BT_SAL_PA_SYNC_CTE_TYPE_NO_AOD_2US SAL_BIT(2) + +/** Bit 3 in Sync_CTE_Type: (RFU) + * - 1: Do not sync to packets with a type 3 Constant Tone Extension */ +#define BT_SAL_PA_SYNC_CTE_TYPE_NO_AOD_TYPE3 SAL_BIT(3) + +/** Bit 4 in Sync_CTE_Type: + * - 1: Do not sync to packets without a Constant Tone Extension */ +#define BT_SAL_PA_SYNC_CTE_TYPE_CTE_ONLY SAL_BIT(4) + +typedef struct bt_sal_pa_sync_param { + /** Options, e.g., BT_SAL_PA_SYNC_OPTION_USE_LIST */ + uint8_t ops; + + /** `Advertising_SID`, refers to the Advertising SID subfield in the `ADI` field used to + * identify the Periodic Advertising. Range from 0x00 to 0x0F. Only valid when + * `BT_SAL_PA_SYNC_OPTION_USE_LIST` is not set in `ops`, otherwise ignored */ + uint8_t sid; + + /** `Advertiser_Address` and `Advertiser_Address_Type` of the remote device. Only valid when + * `BT_SAL_PA_SYNC_OPTION_USE_LIST` is not set in `ops`, otherwise ignored */ + bt_le_address_t addr; + + /** The maximum number of periodic advertising events that can be skipped after a successful + * reception. Range from 0x0000 to 0x01F3 */ + uint16_t skip; + + /** Synchronization timeout for the periodic advertising train, measured in 10 milliseconds. + * Range from 0x000A to 0x4000 */ + uint16_t timeout; + + /** Sync_CTE_Type, e.g., BT_SAL_PA_SYNC_CTE_TYPE_NO_AOA */ + uint8_t cte; +} bt_sal_pa_sync_param_t; + +/** + * @brief LE Periodic Advertising Create Sync command. + * + * This command is used to synchronize with a single periodic advertising train from an advertiser + * and begin receiving periodic advertising packets. + * + * @param[in] id The Controller ID. + * @param[in] params The parameters used to synchronize the periodic advertising train. + * + * @note A @ref SYNC_ESTABLISHED event, associated with a `handle`, shall be generated when starting + * to receive periodic advertising packets. + * @note A @ref SYNC_TERMINATED event shall be generated if the sync fails to establish. + * @note A @ref SYNC_REPORT event may be generated when a periodic advertising packet is received. + */ +bt_status_t bt_sal_pa_create_sync(bt_controller_id_t id, const bt_sal_pa_sync_param_t* params); + +/** + * @brief LE Periodic Advertising Terminate Sync command. + * + * This command is used to stop reception of the periodic advertising train identified by the + * `handle`. + * + * @param[in] id The Controller ID. + * @param[in] sid The Advertising SID. + * @param[in] addr Public device address, random device address, public identity address, or random + * (static) identity address of the advertiser. + * + * @note A @ref SYNC_TERMINATED event shall be generated when this command is completed. + */ +bt_status_t bt_sal_pa_terminate_sync(bt_controller_id_t id, uint8_t sid, + const bt_le_address_t* addr); + +/** + * @brief LE Add Device To Periodic Advertiser List command. + * + * This command is used to add an entry, consisting of a single device address and SID, to the + * Periodic Advertiser list stored in the Controller. + * + * @param[in] id The Controller ID. + * @param[in] sid The Advertising SID. + * @param[in] addr Public device address, random device address, public identity address, or random + * (static) identity address of the advertiser. + */ +bt_status_t bt_sal_pa_list_add(bt_controller_id_t id, uint8_t sid, const bt_le_address_t* addr); + +/** + * @brief LE Remove Device From Periodic Advertiser List command. + * + * This command is used to remove one entry from the list of Periodic Advertisers stored in the + * Controller. + * + * @param[in] id The Controller ID. + * @param[in] sid The Advertising SID. + * @param[in] addr Public device address, random device address, public identity address, or random + * (static) identity address of the advertiser. + */ +bt_status_t bt_sal_pa_list_remove(bt_controller_id_t id, uint8_t sid, const bt_le_address_t* addr); + +/** + * @brief LE Clear Periodic Advertiser List command. + * + * This command is used to remove all entries from the list of periodic advertisers in the + * Controller. + * + * @param[in] id The Controller ID. + */ +bt_status_t bt_sal_pa_list_clear(bt_controller_id_t id); + +#endif /* __SAL_PA_SYNC_INTERFACE_H__ */ \ No newline at end of file diff --git a/service/stacks/zephyr/sal_le_scan_interface.c b/service/stacks/zephyr/sal_le_scan_interface.c index c79d72e2c..3a03227f9 100644 --- a/service/stacks/zephyr/sal_le_scan_interface.c +++ b/service/stacks/zephyr/sal_le_scan_interface.c @@ -38,11 +38,6 @@ typedef struct { sal_func_t func; } sal_scan_req_t; -typedef struct { - char value[256]; - uint8_t length; -} le_eir_data_t; - static struct bt_le_scan_param scan_param; static sal_scan_req_t* sal_scan_req(bt_controller_id_t id, sal_func_t func) @@ -85,41 +80,81 @@ static bt_status_t sal_send_req(sal_scan_req_t* req) return BT_STATUS_SUCCESS; } -static bool zblue_on_eir_found(struct bt_data* data, void* user_data) +static ble_adv_type_t parse_adv_type(const struct bt_le_scan_recv_info* info) { - le_eir_data_t* eir = user_data; + switch (info->adv_type) { + case BT_GAP_ADV_TYPE_ADV_IND: + return BT_LE_ADV_IND; + case BT_GAP_ADV_TYPE_ADV_DIRECT_IND: + return BT_LE_ADV_DIRECT_IND; + case BT_GAP_ADV_TYPE_ADV_SCAN_IND: + return BT_LE_ADV_SCAN_IND; + case BT_GAP_ADV_TYPE_ADV_NONCONN_IND: + return BT_LE_ADV_NONCONN_IND; + case BT_GAP_ADV_TYPE_SCAN_RSP: + return BT_LE_SCAN_RSP; + case BT_GAP_ADV_TYPE_EXT_ADV: + break; /**< Determined via `info->adv_props` */ + default: + break; /**< Unrecognized */ + } - eir->value[eir->length++] = data->data_len + 1; - eir->value[eir->length++] = data->type; - memcpy(&eir->value[eir->length], data->data, data->data_len); - eir->length += data->data_len; - return true; + if (info->adv_props & BT_GAP_ADV_PROP_SCAN_RESPONSE) + return BT_LE_EXT_SCAN_RSP; + if (info->adv_props & BT_GAP_ADV_PROP_DIRECTED) + return BT_LE_EXT_ADV_DIRECT_IND; + if (!(info->adv_props & (BT_GAP_ADV_PROP_CONNECTABLE | BT_GAP_ADV_PROP_SCANNABLE))) + return BT_LE_EXT_ADV_NONCONN_IND; + if (info->adv_props & BT_GAP_ADV_PROP_SCANNABLE) + return BT_LE_EXT_ADV_SCAN_IND; + if (info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) + return BT_LE_EXT_ADV_IND; + + return BT_LE_EXT_ADV_IND; /**< Unknown */ } -static void zblue_on_device_found(const bt_addr_le_t* addr, int8_t rssi, uint8_t type, struct net_buf_simple* ad) +static void scan_recv_cb(const struct bt_le_scan_recv_info* info, struct net_buf_simple* ad) { ble_scan_result_t result_info = { 0 }; - le_eir_data_t eir = { 0 }; - bt_data_parse(ad, zblue_on_eir_found, &eir); - - result_info.length = eir.length; - result_info.adv_type = type; - result_info.rssi = rssi; result_info.dev_type = BT_DEVICE_DEVTYPE_BLE; - result_info.addr_type = addr->type; - memcpy(&result_info.addr, &addr->a, sizeof(result_info.addr)); - - scan_on_result_data_update(&result_info, eir.value); + result_info.adv_type = parse_adv_type(info); + result_info.length = ad->len; + result_info.rssi = info->rssi; + result_info.tx_power = info->tx_power; + result_info.sid = info->sid; + result_info.interval = info->interval; + result_info.addr_type = info->addr->type; + memcpy(&result_info.addr, &info->addr->a, sizeof(result_info.addr)); + + scan_on_result_data_update(&result_info, ad->data); } +static struct bt_le_scan_cb scan_cbs = { + .recv = scan_recv_cb, + .timeout = NULL, +}; + static void STACK_CALL(start_scan)(void* args) { - SAL_CHECK(bt_le_scan_start(&scan_param, zblue_on_device_found), 0); + int err; + + err = bt_le_scan_cb_register(&scan_cbs); + if (err != 0 && err != -EEXIST) { + BT_LOGE("%s, register failed, ret = %d", __func__, err); + return; + } + + err = bt_le_scan_start(&scan_param, NULL); + if (err) { + BT_LOGE("%s, failed, ret = %d", __func__, err); + bt_le_scan_cb_unregister(&scan_cbs); + } } static void STACK_CALL(stop_scan)(void* args) { + bt_le_scan_cb_unregister(&scan_cbs); SAL_CHECK(bt_le_scan_stop(), 0); } diff --git a/service/stacks/zephyr/sal_pa_sync_interface.c b/service/stacks/zephyr/sal_pa_sync_interface.c new file mode 100644 index 000000000..253839592 --- /dev/null +++ b/service/stacks/zephyr/sal_pa_sync_interface.c @@ -0,0 +1,15 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ \ No newline at end of file diff --git a/tools/auracast_sink.c b/tools/auracast_sink.c new file mode 100644 index 000000000..d4bd4fa39 --- /dev/null +++ b/tools/auracast_sink.c @@ -0,0 +1,210 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "bt_le_scan.h" +#include "bt_pa_sync.h" +#include "bt_tools.h" + +#define BTTOOL_AURACAST_SINK_LOG_SIZE (256) +typedef struct { + bt_scanner_t* scanner; +} bttool_auracast_sink_t; + +static int scan_start_cmd(void* handle, int argc, char* argv[]); +static int scan_stop_cmd(void* handle, int argc, char* argv[]); + +static bttool_auracast_sink_t* g_auracast_sink = NULL; +static bt_command_t g_auracast_sink_tables[] = { + { "scan", scan_start_cmd, 0, "\"Search for nearby Auracast sources\"" }, + { "stopscan", scan_stop_cmd, 0, "\"Stop searching\"" }, +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_auracast_sink_tables); i++) { + printf("\t%-8s\t%s\n", g_auracast_sink_tables[i].cmd, g_auracast_sink_tables[i].help); + } +} + +static void on_scan_result(bt_scanner_t* scanner, ble_scan_result_t* result) +{ + bt_status_t status; + bt_pa_sync_info_t* info = NULL; + char* log = NULL; + size_t size = BTTOOL_AURACAST_SINK_LOG_SIZE; + + if (!g_auracast_sink || g_auracast_sink->scanner != scanner) + return; + + info = malloc(sizeof(bt_pa_sync_info_t)); + if (info == NULL) + return; + + status = bt_pa_sync_parse_adv_data(info, result); + if (status != BT_STATUS_SUCCESS) + goto exit; + + log = zalloc(size); /**< for print log */ + if (!log) + goto exit; + + BTTOOL_STRCAT(log, size, "%s", __func__); + + if (info->name[0] != '\0') + BTTOOL_STRCAT(log, size, ", device:%s", info->name); + + if (info->broadcast_name[0] != '\0') + BTTOOL_STRCAT(log, size, ", broadcast name:%s", info->broadcast_name); + + if (info->broadcast_id != BT_INVALID_BROADCAST_ID) + BTTOOL_STRCAT(log, size, ", id:0x%06" PRIx32, info->broadcast_id); + + if (result->sid != 0xFF) + BTTOOL_STRCAT(log, size, ", sid:0x%x", result->sid); + + if (result->tx_power != 0x7F) + BTTOOL_STRCAT(log, size, ", txpower:%d", result->tx_power); + + if (result->rssi != 0x7F) + BTTOOL_STRCAT(log, size, ", rssi:%d", result->rssi); + + PRINT("%s", log); +exit: + free(info); + free(log); +} + +static void on_scan_status(bt_scanner_t* scanner, uint8_t status) +{ + PRINT("%s, status = %d", __func__, status); + + if (!g_auracast_sink || g_auracast_sink->scanner != scanner) { + PRINT("%s, scanner(%p) mismatch", __func__, scanner); + return; + } + + if (status != BT_STATUS_SUCCESS) + g_auracast_sink->scanner = NULL; +} + +static void on_scan_stopped(bt_scanner_t* scanner) +{ + PRINT("%s", __func__); + + if (!g_auracast_sink || g_auracast_sink->scanner != scanner) { + PRINT("%s, scanner(%p) mismatch", __func__, scanner); + return; + } + + g_auracast_sink->scanner = NULL; +} + +static const scanner_callbacks_t scanner_cbs = { + .size = sizeof(scanner_cbs), + .on_scan_result = on_scan_result, + .on_scan_start_status = on_scan_status, + .on_scan_stopped = on_scan_stopped, +}; + +static const ble_scan_settings_t default_scan_settings = { + .scan_mode = BT_SCAN_MODE_LOW_LATENCY, + .legacy = false, + .scan_type = BT_LE_SCAN_TYPE_PASSIVE, + .scan_phy = BT_LE_1M_PHY, + .policy.policy = 0, /**< Unfiltered */ +}; + +int scan_start_cmd(void* handle, int argc, char* argv[]) +{ + ble_scan_settings_t settings; + + if (!g_auracast_sink) { + PRINT("Not initialized"); + return CMD_INVALID_OPT; + } + + if (g_auracast_sink->scanner) { + PRINT("Already scanning"); + return CMD_USAGE_FAULT; + } + + memcpy(&settings, &default_scan_settings, sizeof(settings)); + + g_auracast_sink->scanner = bt_le_start_scan_settings(handle, &settings, &scanner_cbs); + if (g_auracast_sink->scanner == NULL) { + PRINT("Failed to start a scan"); + return CMD_ERROR; + } + + PRINT("Starting scan, scanner = %p", g_auracast_sink->scanner); + + return CMD_OK; +} + +int scan_stop_cmd(void* handle, int argc, char* argv[]) +{ + if (!g_auracast_sink) { + PRINT("Not initialized"); + return CMD_INVALID_OPT; + } + + if (!g_auracast_sink->scanner) { + PRINT("Not scanning"); + return CMD_USAGE_FAULT; + } + + PRINT("Stop scan, scanner = %p", g_auracast_sink->scanner); + + bt_le_stop_scan(handle, g_auracast_sink->scanner); + g_auracast_sink->scanner = NULL; + + return CMD_OK; +} + +int auracast_sink_command_init(void* handle) +{ + g_auracast_sink = zalloc(sizeof(bttool_auracast_sink_t)); + if (!g_auracast_sink) + return CMD_ERROR; + + return CMD_OK; +} + +void auracast_sink_command_uninit(void* handle) +{ + if (!g_auracast_sink) + return; + + free(g_auracast_sink); + g_auracast_sink = NULL; +} + +int auracast_sink_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table_offset(handle, g_auracast_sink_tables, + ARRAY_SIZE(g_auracast_sink_tables), argc, argv, 0); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/bt_tools.c b/tools/bt_tools.c index d720f74a3..ac9bc5baa 100644 --- a/tools/bt_tools.c +++ b/tools/bt_tools.c @@ -232,6 +232,9 @@ static bt_command_t g_cmd_tables[] = { #ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICP { "vmicp", vmicp_command_exec, 0, "vcp/micp client cmd, input \'vmicp\' show usage" }, #endif +#ifdef CONFIG_BLUETOOTH_AURACAST_SINK + { "aurasnk", auracast_sink_command_exec, 0, "auracast sink cmd, input \'aurasnk\' show usage" }, +#endif #ifdef CONFIG_BLUETOOTH_STORAGE_UPDATE { "storage", storage_command_exec, 0, "storage update cmd, input \'storage\' show usage" }, #endif @@ -358,6 +361,9 @@ static void bt_tool_init(void* handle) #ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICP lea_vmicp_command_init(handle); #endif +#ifdef CONFIG_BLUETOOTH_AURACAST_SINK + auracast_sink_command_init(handle); +#endif #ifdef CONFIG_BLUETOOTH_STORAGE_UPDATE storage_command_init(handle); #endif @@ -426,6 +432,9 @@ static void bt_tool_uninit(void* handle) #ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICP lea_vmicp_command_uninit(handle); #endif +#ifdef CONFIG_BLUETOOTH_AURACAST_SINK + auracast_sink_command_uninit(handle); +#endif #ifdef CONFIG_BLUETOOTH_STORAGE_UPDATE storage_command_uninit(handle); #endif diff --git a/tools/bt_tools.h b/tools/bt_tools.h index e28fc350f..d5224fa28 100644 --- a/tools/bt_tools.h +++ b/tools/bt_tools.h @@ -190,6 +190,10 @@ int lea_vmicp_command_init(void* handle); void lea_vmicp_command_uninit(void* handle); int vmicp_command_exec(void* handle, int argc, char* argv[]); +int auracast_sink_command_init(void* handle); +void auracast_sink_command_uninit(void* handle); +int auracast_sink_command_exec(void* handle, int argc, char* argv[]); + int storage_command_init(void* handle); void storage_command_uninit(void* handle); int storage_command_exec(void* handle, int argc, char* argv[]); diff --git a/tools/utils.h b/tools/utils.h index e12424f03..eb4eadb95 100644 --- a/tools/utils.h +++ b/tools/utils.h @@ -19,6 +19,15 @@ #include "bluetooth.h" +#define BTTOOL_STRCAT(dst, size, src, ...) \ + do { \ + size_t _len = strlen(dst); \ + size_t _size = (size); \ + if (_len + 1 >= _size) \ + break; \ + snprintf(dst + _len, _size - _len, src, ##__VA_ARGS__); \ + } while (0) + bool phy_is_vaild(uint8_t phy); int le_addr_type(const char* str, ble_addr_type_t* type); bool bttool_allocator(void** data, uint32_t size);