3. 扫描设备

扫描设备 学习卡片

任务说明

本案例实现经典蓝牙(BR/EDR)的设备发现过程,即Inquiry扫描。目标是通过HCI命令向蓝牙控制器发起查询,接收查询结果事件,并解析出周围可发现设备的蓝牙地址(BD_ADDR)与设备类别(CoD),最终输出设备列表。代码基于Linux BlueZ的HCI套接字接口,直接与控制器交互,不依赖上层BlueZ守护进程。

实现原理

设备发现分为三个步骤:首先发送HCI_Inquiry命令,控制器进入查询状态并广播ID数据包;随后周围设备响应FHS包,控制器通过HCI_Inquiry_Result事件上报发现的设备;最后发送HCI_Inquiry_Cancel命令结束查询。本案例使用原始HCI套接字,通过ioctl绑定指定蓝牙适配器,以同步方式发送命令并阻塞等待事件,解析事件参数中的设备地址与类别字段。

主机 (Host) HCI套接字 控制器 (Controller) BR/EDR 射频 HCI_Inquiry (0x0401) HCI_Inquiry_Result (0x02) HCI_Inquiry_Cancel (0x0402) 周围可发现设备 FHS 响应 ID 数据包 事件解析 num_responses (1B) BD_ADDR (6B) CoD (3B) clock_offset (2B) 重复 num_responses 次

代码实现


// scan_devices.c - 经典蓝牙设备发现(Inquiry)
// 编译: gcc -o scan_devices scan_devices.c
// 运行: sudo ./scan_devices [hciX]   (默认 hci0)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

#define INQUIRY_TIMEOUT 8   // 查询时长(1.28秒单位),8 = 10.24秒

int main(int argc, char *argv[]) {
    int dev_id = 0;
    if (argc > 1) {
        dev_id = hci_devid(argv[1]);  // 解析 "hci0" 格式
        if (dev_id < 0) {
            fprintf(stderr, "无效设备: %s\n", argv[1]);
            return 1;
        }
    }

    // 打开HCI套接字并绑定设备
    int sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
    if (sock < 0) {
        perror("socket");
        return 1;
    }
    if (hci_devba(dev_id, &(struct sockaddr_hci){ .hci_family = AF_BLUETOOTH,
            .hci_dev = dev_id }) < 0) {
        perror("bind");
        close(sock);
        return 1;
    }

    // 发送 Inquiry 命令
    inquiry_info *info = malloc(255 * sizeof(inquiry_info));
    int num_rsp = hci_inquiry(dev_id, INQUIRY_TIMEOUT, 255, NULL, &info,
                              IREQ_CACHE_FLUSH);
    if (num_rsp < 0) {
        perror("hci_inquiry");
        free(info);
        close(sock);
        return 1;
    }

    // 解析并打印结果
    printf("发现 %d 个设备:\n", num_rsp);
    printf("%-18s %-10s %s\n", "BD_ADDR", "CoD(hex)", "Clock Offset");
    for (int i = 0; i < num_rsp; i++) {
        char addr[18];
        ba2str(&info[i].bdaddr, addr);
        uint32_t cod = info[i].dev_class[0] |
                       (info[i].dev_class[1] << 8) |
                       (info[i].dev_class[2] << 16);
        printf("%-18s 0x%06x   0x%04x\n", addr, cod,
               btohs(info[i].clock_offset));
    }

    free(info);
    close(sock);
    return 0;
}

关键说明

hci_inquiry()是BlueZ提供的封装函数,内部依次完成:发送HCI_Inquiry命令、等待HCI_Inquiry_Result事件、在超时后发送HCI_Inquiry_Cancel。第三个参数255表示最大响应数,IREQ_CACHE_FLUSH强制刷新缓存以确保获取实时结果。返回的inquiry_info数组每个元素包含6字节BD_ADDR、3字节设备类别和2字节时钟偏移,代码将其转换为可读格式输出。

验证方法

确保蓝牙适配器可用且周围有可发现模式(通常为可见模式)的蓝牙设备。执行以下命令验证:


# 查看适配器状态
hciconfig hci0 up
# 编译并运行(需要root权限访问HCI套接字)
gcc -o scan_devices scan_devices.c -lbluetooth
sudo ./scan_devices hci0

判定标准:程序在约10秒内完成扫描,输出至少包含表头行。若周围存在可发现设备,则每行显示一个设备的BD_ADDR(格式如AA:BB:CC:DD:EE:FF)、设备类别十六进制值和时钟偏移。若周围无设备,则输出发现 0 个设备且仅显示表头。程序正常退出且无错误信息即视为通过。注意:部分手机需在蓝牙设置中开启“可被检测”选项才能被发现。