增加蓝牙扫描指令 2846/2 2846/head 2847/target/1
authorpenglei <1624952103@qq.com>
Wed, 3 Dec 2025 03:45:11 +0000 (11:45 +0800)
committerpenglei <1624952103@qq.com>
Wed, 3 Dec 2025 03:45:11 +0000 (11:45 +0800)
code/sdk/zhihui/utils/custom_utils_at_cmd.c
code/sdk/zhihui/utilscpp/CMakeLists.txt
code/sdk/zhihui/utilscpp/bt_scan_test.cpp [new file with mode: 0644]

index 02dce28..f283b8d 100644 (file)
@@ -1700,6 +1700,64 @@ static uint32_t custom_at_gpio(uint8_t *para, uint32_t paraLen, char *strCommonC
     return ERRCODE_SUCC;
 }
 
+// 蓝牙扫描测试AT指令实现
+// 用法:
+//   AT^BTSCANTEST=1  - 开始扫描
+//   AT^BTSCANTEST=2  - 停止扫描
+static uint32_t custom_bt_scan_test(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+    UNUSED(strCommonCmd);
+    uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN] = { 0 };
+    uint32_t argc = 0;
+
+    int ret = ConvertCmdToParam(para, paraLen, argv, &argc);
+    if (ret != CUSTOM_UTILS_SUCCESS) {
+        wstp_print("BT SCAN TEST ATCMD Convert Command Failed\n");
+        return ERRCODE_FAIL;
+    }
+
+    if (argc < 1) {
+        wstp_print("BT SCAN TEST ATCMD Set Parameter Failed argc %d\n", argc);
+        wstp_print("Usage: AT^BTSCANTEST=cmd\n");
+        wstp_print("  cmd: 1=start scan, 2=stop scan\n");
+        return ERRCODE_FAIL;
+    }
+
+    uint8_t cmd = (uint8_t)atoi((char*)argv[0]);
+    
+    switch (cmd) {
+        case 1: // 开始扫描
+            {
+                extern int custom_bt_scan_test_start(void);
+                int ret = custom_bt_scan_test_start();
+                if (ret == 0) {
+                    wstp_print("BT SCAN TEST START: Success\n");
+                } else {
+                    wstp_print("BT SCAN TEST START: Failed, ret=%d\n", ret);
+                }
+            }
+            break;
+        case 2: // 停止扫描
+            {
+                extern int custom_bt_scan_test_stop(void);
+                int ret = custom_bt_scan_test_stop();
+                if (ret == 0) {
+                    wstp_print("BT SCAN TEST STOP: Success\n");
+                } else {
+                    wstp_print("BT SCAN TEST STOP: Failed, ret=%d\n", ret);
+                }
+            }
+            break;
+        default:
+            wstp_print("BT SCAN TEST ATCMD Unknown command %d\n", cmd);
+            wstp_print("Usage: AT^BTSCANTEST=cmd\n");
+            wstp_print("  cmd: 1=start scan, 2=stop scan\n");
+            return ERRCODE_FAIL;
+    }
+
+    return ERRCODE_SUCC;
+}
+
 static uint32_t custom_factory_bt_test_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
 {
     UNUSED(strCommonCmd);
@@ -2230,6 +2288,7 @@ static const at_cmd_table_t g_custom_utils_cmd[] = {
     // { (char *)"AT^TPTEST",      tp_test_control, (char *)" " },
     { (char *)"AT^BTTESTSTART",      custom_factory_bt_test_start, (char *)" " },
     { (char *)"AT^BTTESTSTOP",      custom_factory_bt_test_stop, (char *)" " },
+    { (char *)"AT^BTSCANTEST",      custom_bt_scan_test, (char *)" " },
     
     // 录音相关AT指令
     { (char *)"AT^AUDIOTEST",      custom_audio_test, (char *)" " },
index 9099957..1c02c17 100644 (file)
@@ -13,6 +13,8 @@ set(SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/spi_protocol_test.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/audio_test.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/pcm_volume_calculator.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/bt_scan_test.cpp
+
 )
 
 set(PUBLIC_HEADER
diff --git a/code/sdk/zhihui/utilscpp/bt_scan_test.cpp b/code/sdk/zhihui/utilscpp/bt_scan_test.cpp
new file mode 100644 (file)
index 0000000..ef9dfe8
--- /dev/null
@@ -0,0 +1,517 @@
+/**
+ * @file bt_scan_test.cpp
+ * @brief 蓝牙扫描测试程序
+ * @description 扫描蓝牙设备并打印MAC地址和RSSI值
+ */
+
+#include "custom_utilscpp.h"
+#include "debug_print.h"
+#include "securec.h"
+#include "media_thread_adapt.h"
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// 蓝牙GAP相关头文件
+#include "bts_br_gap.h"
+#include "bts_def.h"
+
+// 蓝牙设备信息结构
+typedef struct {
+    uint8_t addr[BD_ADDR_LEN];  // MAC地址
+    int rssi;                    // RSSI值
+    char name[BD_NAME_LEN];     // 设备名称
+    bool has_name;               // 是否已获取名称
+    bool has_rssi;               // 是否已获取RSSI
+    bool is_new_device;          // 是否是新发现的设备(用于控制打印)
+} bt_device_info_t;
+
+// 蓝牙扫描测试上下文
+typedef struct {
+    bool is_running;                    // 扫描是否正在运行
+    bt_device_info_t* devices;           // 扫描到的设备列表
+    uint32_t device_count;              // 设备数量
+    uint32_t device_capacity;           // 设备列表容量
+    MediaMutexHandle mutex;              // 互斥锁
+    
+    // 回调函数指针(保存原始回调,以便恢复)
+    gap_discovery_result_callback original_discovery_callback;
+    gap_read_remote_rssi_event_callback original_rssi_callback;
+    gap_discovery_state_changed_callback original_state_callback;
+    gap_remote_name_changed_callback original_name_callback;
+} BtScanTestContext;
+
+static BtScanTestContext g_bt_scan_ctx = {0};
+
+// 前向声明
+static void BtScanDiscoveryResultCallback(const bd_addr_t *bd_addr);
+static void BtScanReadRemoteRssiCallback(const bd_addr_t *bd_addr, int rssi, int status);
+static void BtScanDiscoveryStateChangedCallback(int status);
+static void BtScanRemoteNameChangedCallback(const bd_addr_t *bd_addr, const unsigned char *deviceName, unsigned char length);
+static int BtScanAddOrUpdateDevice(const bd_addr_t *bd_addr, int rssi, const char *name, bool has_name);
+static void BtScanPrintDevices(void);
+
+/**
+ * @brief 添加或更新设备信息
+ */
+static int BtScanAddOrUpdateDevice(const bd_addr_t *bd_addr, int rssi, const char *name, bool has_name)
+{
+    if (bd_addr == NULL) {
+        return -1;
+    }
+    
+    MediaMutexLock(g_bt_scan_ctx.mutex);
+    
+    // 查找是否已存在该设备
+    int found_index = -1;
+    for (uint32_t i = 0; i < g_bt_scan_ctx.device_count; i++) {
+        if (memcmp(g_bt_scan_ctx.devices[i].addr, bd_addr->addr, BD_ADDR_LEN) == 0) {
+            found_index = i;
+            break;
+        }
+    }
+    
+    if (found_index >= 0) {
+        // 更新现有设备
+        bt_device_info_t *device = &g_bt_scan_ctx.devices[found_index];
+        if (rssi != 0) {
+            device->rssi = rssi;
+            device->has_rssi = true;
+        }
+        if (has_name && name != NULL) {
+            strncpy_s(device->name, sizeof(device->name), name, sizeof(device->name) - 1);
+            device->has_name = true;
+        }
+    } else {
+        // 添加新设备
+        if (g_bt_scan_ctx.device_count >= g_bt_scan_ctx.device_capacity) {
+            // 扩容
+            uint32_t new_capacity = (g_bt_scan_ctx.device_capacity == 0) ? 32 : g_bt_scan_ctx.device_capacity * 2;
+            bt_device_info_t *new_devices = (bt_device_info_t*)realloc(g_bt_scan_ctx.devices, 
+                                                                        new_capacity * sizeof(bt_device_info_t));
+            if (new_devices == NULL) {
+                PRINT("[BtScan] Failed to reallocate device list\r\n");
+                MediaMutexUnLock(g_bt_scan_ctx.mutex);
+                return -1;
+            }
+            g_bt_scan_ctx.devices = new_devices;
+            g_bt_scan_ctx.device_capacity = new_capacity;
+        }
+        
+        bt_device_info_t *device = &g_bt_scan_ctx.devices[g_bt_scan_ctx.device_count];
+        memset_s(device, sizeof(bt_device_info_t), 0, sizeof(bt_device_info_t));
+        memcpy_s(device->addr, BD_ADDR_LEN, bd_addr->addr, BD_ADDR_LEN);
+        device->rssi = rssi;
+        device->has_rssi = (rssi != 0);
+        if (has_name && name != NULL) {
+            strncpy_s(device->name, sizeof(device->name), name, sizeof(device->name) - 1);
+            device->has_name = true;
+        } else {
+            device->name[0] = '\0';
+            device->has_name = false;
+        }
+        g_bt_scan_ctx.device_count++;
+    }
+    
+    MediaMutexUnLock(g_bt_scan_ctx.mutex);
+    return 0;
+}
+
+/**
+ * @brief 打印扫描到的设备列表
+ */
+static void BtScanPrintDevices(void)
+{
+    MediaMutexLock(g_bt_scan_ctx.mutex);
+    
+    if (g_bt_scan_ctx.device_count == 0) {
+        PRINT("[BtScan] No devices found\r\n");
+        MediaMutexUnLock(g_bt_scan_ctx.mutex);
+        return;
+    }
+    
+    PRINT("[BtScan] Found %u device(s):\r\n", g_bt_scan_ctx.device_count);
+    for (uint32_t i = 0; i < g_bt_scan_ctx.device_count; i++) {
+        bt_device_info_t *device = &g_bt_scan_ctx.devices[i];
+        PRINT("[BtScan] Device[%u]: MAC=%02X:%02X:%02X:%02X:%02X:%02X, RSSI=%d dBm",
+              i + 1,
+              device->addr[5], device->addr[4], device->addr[3],
+              device->addr[2], device->addr[1], device->addr[0],
+              device->has_rssi ? device->rssi : 0);
+        
+        if (device->has_name && strlen(device->name) > 0) {
+            PRINT(", Name=%s", device->name);
+        }
+        PRINT("\r\n");
+    }
+    
+    MediaMutexUnLock(g_bt_scan_ctx.mutex);
+}
+
+/**
+ * @brief 蓝牙设备发现回调
+ */
+static void BtScanDiscoveryResultCallback(const bd_addr_t *bd_addr)
+{
+    if (bd_addr == NULL || !g_bt_scan_ctx.is_running) {
+        return;
+    }
+    
+    // 检查是否是新设备,以及是否已有名称
+    MediaMutexLock(g_bt_scan_ctx.mutex);
+    bool is_new = true;
+    bool has_name_already = false;
+    for (uint32_t i = 0; i < g_bt_scan_ctx.device_count; i++) {
+        if (memcmp(g_bt_scan_ctx.devices[i].addr, bd_addr->addr, BD_ADDR_LEN) == 0) {
+            is_new = false;
+            has_name_already = g_bt_scan_ctx.devices[i].has_name;
+            break;
+        }
+    }
+    MediaMutexUnLock(g_bt_scan_ctx.mutex);
+    
+    if (is_new) {
+        PRINT("[BtScan] New device discovered: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
+              bd_addr->addr[5], bd_addr->addr[4], bd_addr->addr[3],
+              bd_addr->addr[2], bd_addr->addr[1], bd_addr->addr[0]);
+    }
+    
+    // 添加设备(暂时没有名称和RSSI)
+    BtScanAddOrUpdateDevice(bd_addr, 0, NULL, false);
+    
+    // 尝试获取设备名称(只在设备没有名称时才获取和打印)
+    if (!has_name_already) {
+        unsigned char nameLen = BD_NAME_LEN;
+        unsigned char deviceName[BD_NAME_LEN] = {0};
+        bool getNameRet = gap_get_device_name(bd_addr, deviceName, &nameLen);
+        if (getNameRet && nameLen > 0) {
+            PRINT("[BtScan] Device name: %s\r\n", deviceName);
+            BtScanAddOrUpdateDevice(bd_addr, 0, (const char*)deviceName, true);
+        }
+    }
+    
+    // 请求RSSI值(只在设备还没有RSSI时请求)
+    MediaMutexLock(g_bt_scan_ctx.mutex);
+    bool need_rssi = true;
+    for (uint32_t i = 0; i < g_bt_scan_ctx.device_count; i++) {
+        if (memcmp(g_bt_scan_ctx.devices[i].addr, bd_addr->addr, BD_ADDR_LEN) == 0) {
+            if (g_bt_scan_ctx.devices[i].has_rssi) {
+                need_rssi = false;
+            }
+            break;
+        }
+    }
+    MediaMutexUnLock(g_bt_scan_ctx.mutex);
+    
+    if (need_rssi) {
+        gap_read_remote_rssi_value(bd_addr);
+    }
+    
+    // 调用原始回调(如果存在)
+    if (g_bt_scan_ctx.original_discovery_callback != NULL) {
+        g_bt_scan_ctx.original_discovery_callback(bd_addr);
+    }
+}
+
+/**
+ * @brief 蓝牙RSSI读取回调
+ */
+static void BtScanReadRemoteRssiCallback(const bd_addr_t *bd_addr, int rssi, int status)
+{
+    if (bd_addr == NULL || !g_bt_scan_ctx.is_running) {
+        return;
+    }
+    
+    if (status == 0) {
+        // 检查RSSI是否有显著变化(只在新设备或RSSI变化超过5dBm时打印)
+        MediaMutexLock(g_bt_scan_ctx.mutex);
+        bool should_print = false;
+        for (uint32_t i = 0; i < g_bt_scan_ctx.device_count; i++) {
+            if (memcmp(g_bt_scan_ctx.devices[i].addr, bd_addr->addr, BD_ADDR_LEN) == 0) {
+                if (!g_bt_scan_ctx.devices[i].has_rssi || 
+                    abs(g_bt_scan_ctx.devices[i].rssi - rssi) > 5) {
+                    should_print = true;
+                }
+                break;
+            }
+        }
+        MediaMutexUnLock(g_bt_scan_ctx.mutex);
+        
+        if (should_print) {
+            printf("fac:[BtScan] Device: %02X:%02X:%02X:%02X:%02X:%02X, RSSI=%d dBm\r\n",
+                  bd_addr->addr[5], bd_addr->addr[4], bd_addr->addr[3],
+                  bd_addr->addr[2], bd_addr->addr[1], bd_addr->addr[0],
+                  rssi);
+        }
+        
+        // 更新设备RSSI
+        BtScanAddOrUpdateDevice(bd_addr, rssi, NULL, false);
+    }
+    // 不打印RSSI读取失败的信息,避免日志过多
+    
+    // 调用原始回调(如果存在)
+    if (g_bt_scan_ctx.original_rssi_callback != NULL) {
+        g_bt_scan_ctx.original_rssi_callback(bd_addr, rssi, status);
+    }
+}
+
+/**
+ * @brief 蓝牙扫描状态变化回调
+ */
+static void BtScanDiscoveryStateChangedCallback(int status)
+{
+    PRINT("[BtScan] Discovery state changed: %d\r\n", status);
+    
+    if (g_bt_scan_ctx.is_running) {
+        // 只在扫描完成或状态变化时打印设备列表
+        if (status == 0 || status == 1) {  // 扫描完成或开始
+            BtScanPrintDevices();
+        }
+        
+        // 如果扫描结束,自动重新启动扫描(持续扫描)
+        if (status == 0) {  // 扫描完成
+            PRINT("[BtScan] Scan completed, restarting discovery...\r\n");
+            usleep(500000);  // 等待500ms后重新启动
+            gap_br_start_discovery();
+        }
+    }
+    
+    // 调用原始回调(如果存在)
+    if (g_bt_scan_ctx.original_state_callback != NULL) {
+        g_bt_scan_ctx.original_state_callback(status);
+    }
+}
+
+/**
+ * @brief 蓝牙设备名称变化回调
+ */
+static void BtScanRemoteNameChangedCallback(const bd_addr_t *bd_addr, const unsigned char *deviceName, unsigned char length)
+{
+    if (bd_addr == NULL || !g_bt_scan_ctx.is_running) {
+        return;
+    }
+    
+    if (deviceName != NULL && length > 0) {
+        char name[BD_NAME_LEN] = {0};
+        strncpy_s(name, sizeof(name), (const char*)deviceName, 
+                  (length < sizeof(name)) ? length : sizeof(name) - 1);
+        
+        // 检查设备是否已有名称,避免重复打印
+        MediaMutexLock(g_bt_scan_ctx.mutex);
+        bool has_name = false;
+        bool name_changed = false;
+        for (uint32_t i = 0; i < g_bt_scan_ctx.device_count; i++) {
+            if (memcmp(g_bt_scan_ctx.devices[i].addr, bd_addr->addr, BD_ADDR_LEN) == 0) {
+                has_name = g_bt_scan_ctx.devices[i].has_name;
+                if (has_name && strcmp(g_bt_scan_ctx.devices[i].name, name) != 0) {
+                    name_changed = true;
+                }
+                break;
+            }
+        }
+        MediaMutexUnLock(g_bt_scan_ctx.mutex);
+        
+        // 只在设备没有名称或名称发生变化时打印
+        if (!has_name) {
+            PRINT("[BtScan] Device name: %02X:%02X:%02X:%02X:%02X:%02X, Name=%s\r\n",
+                  bd_addr->addr[5], bd_addr->addr[4], bd_addr->addr[3],
+                  bd_addr->addr[2], bd_addr->addr[1], bd_addr->addr[0],
+                  name);
+        } else if (name_changed) {
+            PRINT("[BtScan] Device name changed: %02X:%02X:%02X:%02X:%02X:%02X, New Name=%s\r\n",
+                  bd_addr->addr[5], bd_addr->addr[4], bd_addr->addr[3],
+                  bd_addr->addr[2], bd_addr->addr[1], bd_addr->addr[0],
+                  name);
+        }
+        
+        // 更新设备名称
+        BtScanAddOrUpdateDevice(bd_addr, 0, name, true);
+    }
+    
+    // 调用原始回调(如果存在)
+    if (g_bt_scan_ctx.original_name_callback != NULL) {
+        g_bt_scan_ctx.original_name_callback(bd_addr, deviceName, length);
+    }
+}
+
+/**
+ * @brief 启动蓝牙扫描测试
+ * @return 0 成功, -1 失败
+ */
+int custom_bt_scan_test_start(void)
+{
+    PRINT("[BtScan] Starting Bluetooth scan test...\r\n");
+    
+    // 先启用蓝牙栈(使用 enable_bt_stack,对应 AT^BTENABLE 指令的功能)
+    // extern errcode_t enable_bt_stack(void);
+    
+    PRINT("[BtScan] Enabling Bluetooth stack (same as AT^BTENABLE)...\r\n");
+    // errcode_t bt_enable_ret = enable_bt_stack();
+    // if (bt_enable_ret != ERRCODE_SUCC && bt_enable_ret != 0) {
+    //     PRINT("[BtScan] Warning: enable_bt_stack returned %d (0x%X)\r\n", 
+    //           bt_enable_ret, (unsigned int)bt_enable_ret);
+    //     PRINT("[BtScan] Note: You may need to run AT^BTENABLE first\r\n");
+    // } else {
+    //     PRINT("[BtScan] Bluetooth stack enabled successfully\r\n");
+    // }
+    
+    // 等待蓝牙栈初始化完成
+    // usleep(2000000);  // 2秒,确保蓝牙栈完全初始化
+    PRINT("[BtScan] Waiting for Bluetooth stack ready...\r\n");
+    
+    MediaMutexLock(g_bt_scan_ctx.mutex);
+    
+    if (g_bt_scan_ctx.is_running) {
+        PRINT("[BtScan] Scan already running\r\n");
+        MediaMutexUnLock(g_bt_scan_ctx.mutex);
+        return -1;
+    }
+    
+    // 初始化互斥锁
+    if (g_bt_scan_ctx.mutex == NULL) {
+        g_bt_scan_ctx.mutex = MediaMutexCreate(NULL);
+        if (g_bt_scan_ctx.mutex == NULL) {
+            PRINT("[BtScan] Failed to create mutex\r\n");
+            return -1;
+        }
+    }
+    
+    // 清空设备列表
+    if (g_bt_scan_ctx.devices != NULL) {
+        free(g_bt_scan_ctx.devices);
+        g_bt_scan_ctx.devices = NULL;
+    }
+    g_bt_scan_ctx.device_count = 0;
+    g_bt_scan_ctx.device_capacity = 0;
+    
+    // 注册回调函数
+    gap_call_backs_t callbacks = {0};
+    
+    // 保存原始回调(如果已注册)
+    // 注意:这里假设回调已经通过其他方式注册,我们需要获取并保存它们
+    // 由于无法直接获取已注册的回调,我们直接设置我们的回调
+    callbacks.discovery_result_callback = BtScanDiscoveryResultCallback;
+    callbacks.read_remote_rssi_event_callback = BtScanReadRemoteRssiCallback;
+    callbacks.discovery_state_changed_callback = BtScanDiscoveryStateChangedCallback;
+    callbacks.remote_name_changed_callback = BtScanRemoteNameChangedCallback;
+    
+    // 注册回调函数(返回值0表示成功)
+    int ret = gap_register_callbacks(&callbacks);
+    if (ret != 0) {
+        PRINT("[BtScan] Failed to register callbacks: %d\r\n", ret);
+        MediaMutexUnLock(g_bt_scan_ctx.mutex);
+        return -1;
+    }
+    
+    // 检查是否已经在扫描,如果是则先停止
+    bool is_discovering = is_bt_discovering(BT_TRANSPORT_BR_EDR);
+    if (is_discovering) {
+        PRINT("[BtScan] Bluetooth is already discovering, canceling first...\r\n");
+        gap_br_cancel_discovery();
+        // 等待一小段时间让扫描完全停止
+        usleep(200000);  // 200ms
+    }
+    
+    // 设置扫描参数(扫描所有设备类型,最多20个设备)
+    // 参考 custom_factory_bt_scan_start 的实现
+    errcode_t err = gap_br_set_inquiry_paramters(0, 20);
+    if (err != ERRCODE_SUCC) {
+        PRINT("[BtScan] Warning: Failed to set inquiry parameters: %d (0x%X)\r\n", 
+              err, (unsigned int)err);
+        // 继续尝试启动扫描(参考其他代码实现,即使设置参数失败也继续)
+    } else {
+        PRINT("[BtScan] Inquiry parameters set successfully\r\n");
+    }
+    
+    // 启动扫描(参考 custom_factory_bt_scan_start 的实现)
+    bool scan_ret = gap_br_start_discovery();
+    if (!scan_ret) {
+        PRINT("[BtScan] Failed to start discovery\r\n");
+        PRINT("[BtScan] Note: This may indicate Bluetooth stack is not fully ready\r\n");
+        MediaMutexUnLock(g_bt_scan_ctx.mutex);
+        return -1;
+    }
+    
+    PRINT("[BtScan] Discovery started successfully\r\n");
+    
+    g_bt_scan_ctx.is_running = true;
+    MediaMutexUnLock(g_bt_scan_ctx.mutex);
+    
+    PRINT("[BtScan] Bluetooth scan started successfully\r\n");
+    PRINT("[BtScan] Scanning for devices...\r\n");
+    
+    return 0;
+}
+
+/**
+ * @brief 停止蓝牙扫描测试
+ * @return 0 成功, -1 失败
+ */
+int custom_bt_scan_test_stop(void)
+{
+    PRINT("[BtScan] Stopping Bluetooth scan test...\r\n");
+    
+    MediaMutexLock(g_bt_scan_ctx.mutex);
+    
+    if (!g_bt_scan_ctx.is_running) {
+        PRINT("[BtScan] Scan not running\r\n");
+        MediaMutexUnLock(g_bt_scan_ctx.mutex);
+        return 0;
+    }
+    
+    // 停止扫描
+    bool ret = gap_br_cancel_discovery();
+    if (!ret) {
+        PRINT("[BtScan] Failed to cancel discovery\r\n");
+    }
+    
+    // 打印最终结果
+    PRINT("[BtScan] Final scan results:\r\n");
+    BtScanPrintDevices();
+    
+    // 清理设备列表
+    if (g_bt_scan_ctx.devices != NULL) {
+        free(g_bt_scan_ctx.devices);
+        g_bt_scan_ctx.devices = NULL;
+    }
+    g_bt_scan_ctx.device_count = 0;
+    g_bt_scan_ctx.device_capacity = 0;
+    
+    g_bt_scan_ctx.is_running = false;
+    MediaMutexUnLock(g_bt_scan_ctx.mutex);
+    
+    PRINT("[BtScan] Bluetooth scan stopped\r\n");
+    
+    return 0;
+}
+
+/**
+ * @brief 获取扫描测试状态
+ * @return true 正在运行, false 未运行
+ */
+bool custom_bt_scan_test_is_running(void)
+{
+    MediaMutexLock(g_bt_scan_ctx.mutex);
+    bool is_running = g_bt_scan_ctx.is_running;
+    MediaMutexUnLock(g_bt_scan_ctx.mutex);
+    return is_running;
+}
+
+/**
+ * @brief 初始化蓝牙扫描测试模块
+ */
+void custom_bt_scan_test_init(void)
+{
+    if (g_bt_scan_ctx.mutex == NULL) {
+        g_bt_scan_ctx.mutex = MediaMutexCreate(NULL);
+    }
+}
+
+#ifdef __cplusplus
+}
+#endif
+