hcitool 工具:HCI 层指令实操 + 源码中指令的封装逻辑

本文基于BlueZ 5.x源码,系统剖析hcitool工具的HCI层指令实操与封装逻辑。涵盖全套HCI命令使用、三层报文构造机制、hci_send_req通信流程、事件过滤器设计、超时与错误处理,并对比hciconfig和bluetoothctl的层级定位。重点揭示其通过HCI socket直接收发HCI命令/事件的核心机制,适用于嵌入式蓝牙调试与二次开发。

hcitool 是 BlueZ 提供的 HCI 层原生指令工具,直接通过 HCI 套接字向控制器发送蓝牙规范定义的 HCI 命令,并接收 HCI 事件响应。如果说 hciconfig 是"适配器配置工具"(类似 ifconfig),那么 hcitool 就是"HCI 协议操作工具"(类似 ping/arping)——它的操作对象是远程设备和连接,而非本地适配器。本文基于 BlueZ 5.x 原生源码,全面汇总 hcitool 实操指令,深度剖析其源码架构、HCI 指令封装、报文构造、命令下发、事件接收、数据解析全套逻辑,并对比三大工具的层级差异。


目录

一、hcitool 全套实操指令

二、源码总览与工程结构

三、程序入口与命令解析

四、HCI 指令封装与报文构造

五、命令实现深度拆解

六、HCI Socket 通信机制

七、超时处理与错误机制

八、hcitool vs hciconfig vs bluetoothctl 层级对比

九、常见报错与解决方案

十、调试技巧与二次开发

十一、核心命令 → 源码函数 → HCI 机制速查

十二、总结


一、hcitool 全套实操指令

1.1 基本用法

# 查看帮助
hcitool --help

# 指定适配器
hcitool -i hci0 <command>

# 用设备地址指定适配器
hcitool -i 00:1A:7D:DA:71:13 <command>

# 查看命令子帮助
hcitool scan --help
hcitool lescan --help

1.2 本地设备查询

# 列出本地蓝牙设备
hcitool dev
# 输出:
# hci0    00:1A:7D:DA:71:13

1.3 BR/EDR 设备发现

# 基础查询(Inquiry),约 10 秒
hcitool scan
# 输出:
# Scanning ...
#     AA:BB:CC:DD:EE:FF    Device Name

# 仅查询(不读名称),显示地址/时钟偏移/设备类
hcitool inq

# 指定查询时长(1.28s * length)
hcitool scan --length=5

# 指定响应数量
hcitool scan --num=10

# 使用 GIAC/LIAC
hcitool scan --iap=giac    # General Inquiry Access Code (0x9e8b33)
hcitool scan --iap=liac    # Limited Inquiry Access Code (0x9e8b00)

# 刷新缓存
hcitool scan --flush

# 扩展信息(设备类 + OUI 厂商 + 完整信息)
hcitool scan --class
hcitool scan --oui
hcitool scan --info
hcitool scan --all          # 等价于 --class --oui --info

1.4 LE 设备扫描

# 基础 LE 扫描(持续,Ctrl+C 停止)
hcitool lescan

# 使用随机地址扫描
hcitool lescan --static

# 隐私模式
hcitool lescan --privacy

# 被动扫描(不发送 SCAN_REQ)
hcitool lescan --passive

# 仅扫描 Accept List 中的设备
hcitool lescan --acceptlist

# 指定发现过程(g=General / l=Limited)
hcitool lescan --discovery=g
hcitool lescan --discovery=l

# 不过滤重复广播
hcitool lescan --duplicates

1.5 远程设备信息查询

# 读取远程设备名称
hcitool name AA:BB:CC:DD:EE:FF

# 读取远程设备详细信息(LMP 版本/特性/时钟偏移/设备类)
hcitool info AA:BB:CC:DD:EE:FF

# 读取 LE 远程设备信息(自动连接→查询→断开)
hcitool leinfo AA:BB:CC:DD:EE:FF
hcitool leinfo --static AA:BB:CC:DD:EE:FF   # 使用随机地址
hcitool leinfo --random AA:BB:CC:DD:EE:FF   # 对端为随机地址

1.6 连接管理

# 创建 ACL 连接(BR/EDR)
hcitool cc AA:BB:CC:DD:EE:FF
hcitool cc --role=c AA:BB:CC:DD:EE:FF        # 强制 Central 角色
hcitool cc --role=p AA:BB:CC:DD:EE:FF        # 强制 Peripheral 角色
hcitool cc --ptype=dm1,dh3,dh5 AA:BB:CC:DD:EE:FF  # 指定包类型

# 断开连接
hcitool dc AA:BB:CC:DD:EE:FF
hcitool dc AA:BB:CC:DD:EE:FF 13              # 指定断开原因码

# 查看活动连接
hcitool con

# 创建 LE 连接
hcitool lecc AA:BB:CC:DD:EE:FF
hcitool lecc --static AA:BB:CC:DD:EE:FF
hcitool lecc --random AA:BB:CC:DD:EE:FF

# 断开 LE 连接(通过 handle)
hcitool ledc <handle> [reason]

# LE 连接参数更新
hcitool lecup --handle=<handle> --min=<interval> --max=<interval> \
    --latency=<latency> --timeout=<timeout>

1.7 连接质量与链路信息

# 读取 RSSI
hcitool rssi AA:BB:CC:DD:EE:FF

# 读取链路质量
hcitool lq AA:BB:CC:DD:EE:FF

# 读取发射功率
hcitool tpl AA:BB:CC:DD:EE:FF          # 默认 current
hcitool tpl AA:BB:CC:DD:EE:FF max      # 最大功率

# 读取 AFH 信道图
hcitool afh AA:BB:CC:DD:EE:FF

# 读取时钟偏移
hcitool clkoff AA:BB:CC:DD:EE:FF

# 读取本地/远程时钟
hcitool clock                        # 本地时钟
hcitool clock AA:BB:CC:DD:EE:FF      # 远程时钟

1.8 链路策略与安全管理

# 链路策略设置
hcitool lp AA:BB:CC:DD:EE:FF              # 查看
hcitool lp AA:BB:CC:DD:EE:FF HOLD SNIFF   # 设置

# 链路监督超时
hcitool lst AA:BB:CC:DD:EE:FF             # 查看
hcitool lst AA:BB:CC:DD:EE:FF 3200        # 设置(单位: 0.625ms)

# 认证请求
hcitool auth AA:BB:CC:DD:EE:FF

# 加密设置
hcitool enc AA:BB:CC:DD:EE:FF             # 查看加密状态
hcitool enc AA:BB:CC:DD:EE:FF on          # 开启加密

# 更换链路密钥
hcitool key AA:BB:CC:DD:EE:FF

# 切换角色
hcitool sr AA:BB:CC:DD:EE:FF master
hcitool sr AA:BB:CC:DD:EE:FF slave

# 更改连接包类型
hcitool cpt AA:BB:CC:DD:EE:FF dm1,dh3,dh5

1.9 LE Accept List(原白名单)管理

# 添加设备到 Accept List
hcitool lealadd AA:BB:CC:DD:EE:FF
hcitool lealadd --random AA:BB:CC:DD:EE:FF   # 对端为随机地址

# 从 Accept List 移除
hcitool lealrm AA:BB:CC:DD:EE:FF
hcitool lealrm --random AA:BB:CC:DD:EE:FF

# 查看 Accept List 大小
hcitool lealsz

# 清空 Accept List
hcitool lealclr

1.10 LE Resolving List(地址解析列表)管理

# 添加设备到 Resolving List
hcitool lerladd --local=<local_irk> --peer=<peer_irk> AA:BB:CC:DD:EE:FF

# 从 Resolving List 移除
hcitool lerlrm AA:BB:CC:DD:EE:FF

# 清空 Resolving List
hcitool lerlclr

# 查看 Resolving List 大小
hcitool lerlsz

# 启用/禁用地址解析
hcitool lerlon
hcitool lerloff

1.11 任意 HCI 命令发送

# 发送任意 HCI 命令(ogf ocf [params...])
# 示例:读取本地名称 (OGF=0x03, OCF=0x0014)
hcitool cmd 0x03 0x0014

# 示例:写本地名称 "Test" (OGF=0x03, OCF=0x0013)
hcitool cmd 0x03 0x0013 0x54 0x65 0x73 0x74

# 示例:LE Set Advertising Enable (OGF=0x08, OCF=0x000A)
hcitool cmd 0x08 0x000a 0x01

1.12 周期性查询

# 启动周期性查询
hcitool spinq

# 停止周期性查询
hcitool epinq

1.13 实战速查:完整 BLE 设备探测流程

# 1. 确认适配器就绪
hcitool dev
hciconfig hci0 up

# 2. LE 扫描发现设备
hcitool lescan
# (Ctrl+C 停止)

# 3. 查询 LE 设备信息(自动连接+查询+断开)
hcitool leinfo AA:BB:CC:DD:EE:FF

# 4. 创建 LE 连接
hcitool lecc AA:BB:CC:DD:EE:FF
# 输出: Connection handle 64

# 5. 查看连接
hcitool con

# 6. 读取 RSSI
hcitool rssi AA:BB:CC:DD:EE:FF

# 7. 读取链路质量
hcitool lq AA:BB:CC:DD:EE:FF

# 8. 断开 LE 连接(使用 handle)
hcitool ledc 64

二、源码总览与工程结构

hcitool 的全部源码集中在 [tools/hcitool.c],与 hciconfig 类似,依赖 BlueZ 公共库 lib/hci.c。

tools/hcitool.c              # 全部源码:命令表 + 各 cmd_* 函数 + main()
    │
    ├── lib/hci.h             # HCI 常量、结构体定义
    ├── lib/hci.c             # hci_open_dev / hci_send_req / hci_inquiry / hci_create_connection 等
    ├── lib/hci_lib.h         # struct hci_request、HCI 操作函数声明
    ├── lib/bluetooth.h       # bdaddr_t、ba2str、str2ba
    └── src/oui.c             # OUI 厂商名查询(scan --oui)

与 hciconfig 的关键区别:hcitool 几乎不使用 ioctl(仅用于获取连接句柄),所有操作都通过 hci_open_dev() + hci_send_req() / hci_send_cmd()` 直接收发 HCI 命令/事件包。

三、程序入口与命令解析

3.1 main() 全貌

入口位于 [tools/hcitool.c]第 3448 行:

int main(int argc, char *argv[])
{
    int opt, i, dev_id = -1;
    bdaddr_t ba;

    /* 1. 解析全局选项:-i (设备), -h (帮助) */
    while ((opt = getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) {
        switch (opt) {
        case 'i':
            dev_id = hci_devid(optarg);    /* "hci0" 或 MAC → dev_id */
            if (dev_id < 0) {
                perror("Invalid device");
                exit(1);
            }
            break;
        case 'h':
        default:
            usage();
            exit(0);
        }
    }

    argc -= optind;
    argv += optind;
    optind = 0;

    if (argc < 1) {
        usage();
        exit(0);
    }

    /* 2. 验证设备可用性 */
    if (dev_id != -1 && hci_devba(dev_id, &ba) < 0) {
        perror("Device is not available");
        exit(1);
    }

    /* 3. 在命令表中查找并执行 */
    for (i = 0; command[i].cmd; i++) {
        if (strncmp(command[i].cmd, argv[0], strlen(command[i].cmd)))
            continue;

        command[i].func(dev_id, argc, argv);
        break;
    }

    if (command[i].cmd == 0) {
        fprintf(stderr, "Unknown command - \"%s\"\n", *argv);
        exit(1);
    }

    return 0;
}

3.2 命令表数据结构

static struct {
    char *cmd;                                       /* 命令名 */
    void (*func)(int dev_id, int argc, char **argv); /* 处理函数 */
    char *doc;                                       /* 描述 */
} command[] = {
    { "dev",      cmd_dev,     "Display local devices"                },
    { "inq",      cmd_inq,     "Inquire remote devices"               },
    { "scan",     cmd_scan,    "Scan for remote devices"              },
    { "name",     cmd_name,    "Get name from remote device"          },
    { "info",     cmd_info,    "Get information from remote device"   },
    { "cmd",      cmd_cmd,     "Submit arbitrary HCI commands"        },
    { "con",      cmd_con,     "Display active connections"           },
    { "cc",       cmd_cc,      "Create connection to remote device"   },
    { "dc",       cmd_dc,      "Disconnect from remote device"        },
    { "rssi",     cmd_rssi,    "Display connection RSSI"              },
    { "lq",       cmd_lq,      "Display link quality"                 },
    { "lescan",   cmd_lescan,  "Start LE scan"                        },
    { "leinfo",   cmd_leinfo,  "Get LE remote information"            },
    { "lecc",     cmd_lecc,    "Create a LE Connection"               },
    /* ... 共 44 个命令 ... */
    { NULL, NULL, 0 }
};

与 hciconfig 的命令表结构略有不同:hcitool 的命令处理函数签名是 func(int dev_id, int argc, char **argv),支持 getopt 子命令解析;而 hciconfig 是 func(int ctl, int hdev, char *opt),只接收单个字符串参数。

3.3 命令子参数解析:getopt_long

每个命令内部使用 for_each_opt 宏解析自己的选项:

#define for_each_opt(opt, long, short) \
    while ((opt = getopt_long(argc, argv, short ? short : "+", long, NULL)) != -1)

以 cmd_lescan 为例:

static struct option lescan_options[] = {
    { "help",      0, 0, 'h' },
    { "static",    0, 0, 's' },
    { "privacy",   0, 0, 'p' },
    { "passive",   0, 0, 'P' },
    { "acceptlist",0, 0, 'a' },
    { "discovery", 1, 0, 'd' },
    { "duplicates",0, 0, 'D' },
    { 0, 0, 0, 0 }
};

static void cmd_lescan(int dev_id, int argc, char **argv)
{
    uint8_t own_type = LE_PUBLIC_ADDRESS;
    uint8_t scan_type = 0x01;    /* Active scan */
    uint8_t filter_type = 0;
    uint8_t filter_policy = 0x00;

    for_each_opt(opt, lescan_options, NULL) {
        switch (opt) {
        case 's': own_type = LE_RANDOM_ADDRESS; break;
        case 'P': scan_type = 0x00; break;     /* Passive */
        case 'a': filter_policy = 0x01; break; /* Accept list */
        case 'd': filter_type = optarg[0]; break; /* 'g' or 'l' */
        case 'D': filter_dup = 0x00; break;    /* No duplicate filter */
        /* ... */
        }
    }
    helper_arg(0, 1, &argc, &argv, lescan_help);
    /* ... */
}

3.4 参数校验:helper_arg

static void helper_arg(int min_num_arg, int max_num_arg, int *argc,
                       char ***argv, const char *usage)
{
    *argc -= optind;

    /* 参数过多 */
    if ((*argc > max_num_arg) && (max_num_arg >= min_num_arg)) {
        fprintf(stderr, "%s: too many arguments (maximal: %i)\n",
                *argv[0], max_num_arg);
        printf("%s", usage);
        exit(1);
    }

    /* 参数不足 */
    if (*argc < min_num_arg) {
        fprintf(stderr, "%s: too few arguments (minimal: %i)\n",
                *argv[0], min_num_arg);
        printf("%s", usage);
        exit(0);
    }

    *argv += optind;
}

helper_arg(1, 1, ...) 表示必须有且仅有 1 个参数,helper_arg(0, 0, ...) 表示无参数,helper_arg(2, -1, ...) 表示至少 2 个参数(-1 表示无上限)。

3.5 参数解析流程图

命令行: hcitool -i hci0 lescan --passive --discovery=g
            │
            ├─ main(): getopt_long("-i:h")
            │   ├─ -i hci0 → dev_id = hci_devid("hci0") = 0
            │   └─ hci_devba(0, &ba) → 验证设备可用
            │
            ├─ argv[0]="lescan" → 匹配 command[24] "lescan"
            │   └─ cmd_lescan(0, argc, argv)
            │       ├─ for_each_opt: getopt_long(lescan_options)
            │       │   ├─ --passive → scan_type = 0x00
            │       │   └─ --discovery=g → filter_type = 'g'
            │       ├─ helper_arg(0, 1, ...) → 无位置参数,通过
            │       └─ hci_open_dev(0) → dd
            │           ├─ hci_le_set_scan_parameters(...)
            │           ├─ hci_le_set_scan_enable(...)
            │           └─ print_advertising_devices(dd, 'g')
            │
            └─ (Ctrl+C → SIGINT → sigint_handler → signal_received)

四、HCI 指令封装与报文构造

4.1 三层封装体系

hcitool 的 HCI 指令发送采用三层封装:

4.2 HCI Command Packet 格式

蓝牙规范定义的 HCI Command Packet 格式:

hci_send_cmd() 的报文构造([lib/hci.c] 第 1090 行):

int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param)
{
    uint8_t type = HCI_COMMAND_PKT;      /* 0x01 */
    hci_command_hdr hc;
    struct iovec iv[3];
    int ivn;

    /* 打包 Opcode: OGF(高6位) | OCF(低10位) */
    hc.opcode = htobs(cmd_opcode_pack(ogf, ocf));
    hc.plen = plen;

    /* iovec 分段写入 */
    iv[0].iov_base = &type;              /* Packet Type: 0x01 */
    iv[0].iov_len  = 1;
    iv[1].iov_base = &hc;                /* Command Header: opcode + plen */
    iv[1].iov_len  = HCI_COMMAND_HDR_SIZE; /* 3 bytes */
    ivn = 2;

    if (plen) {
        iv[2].iov_base = param;          /* Parameter Data */
        iv[2].iov_len  = plen;
        ivn = 3;
    }

    /* writev 一次写入完整报文 */
    return writev(dd, iv, ivn);
}

4.3 hci_send_req() — 请求-响应完整流程

[lib/hci.c] :

int hci_send_req(int dd, struct hci_request *r, int to)
{
    unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
    uint16_t opcode = htobs(cmd_opcode_pack(r->ogf, r->ocf));
    struct hci_filter nf, of;
    socklen_t olen;
    hci_event_hdr *hdr;
    int err, try;

    /* 1. 设置事件过滤器:只接收目标命令的响应 */
    olen = sizeof(of);
    getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen);

    hci_filter_clear(&nf);
    hci_filter_set_ptype(HCI_EVENT_PKT,  &nf);
    hci_filter_set_event(EVT_CMD_STATUS, &nf);
    hci_filter_set_event(EVT_CMD_COMPLETE, &nf);
    hci_filter_set_event(EVT_LE_META_EVENT, &nf);
    hci_filter_set_event(r->event, &nf);
    hci_filter_set_opcode(opcode, &nf);
    setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf));

    /* 2. 发送 HCI 命令 */
    if (hci_send_cmd(dd, r->ogf, r->ocf, r->clen, r->cparam) < 0)
        goto failed;

    /* 3. 轮询等待响应事件 */
    try = 10;
    while (try--) {
        evt_cmd_complete *cc;
        evt_cmd_status *cs;
        int len;

        if (to) {
            struct pollfd p;
            int n;

            p.fd = dd;
            p.events = POLLIN;
            while ((n = poll(&p, 1, to)) < 0) {
                if (errno == EAGAIN || errno == EINTR)
                    continue;
                goto failed;
            }

            if (!n) {           /* 超时 */
                errno = ETIMEDOUT;
                goto failed;
            }

            to -= 10;
            if (to < 0) to = 0;
        }

        /* 读取事件包 */
        while ((len = read(dd, buf, sizeof(buf))) < 0) {
            if (errno == EAGAIN || errno == EINTR)
                continue;
            goto failed;
        }

        /* 解析事件头 */
        hdr = (void *) (buf + 1);  /* 跳过 Packet Type */
        ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
        len -= (1 + HCI_EVENT_HDR_SIZE);

        switch (hdr->evt) {
        case EVT_CMD_STATUS:
            cs = (void *) ptr;
            if (cs->opcode != opcode)
                continue;       /* 不是本命令的响应,忽略 */
            if (r->event != EVT_CMD_STATUS) {
                if (cs->status) {
                    errno = EIO;
                    goto failed;
                }
                break;          /* Status OK,继续等待 Complete */
            }
            if (r->rparam && r->rlen)
                memcpy(r->rparam, ptr, r->rlen);
            goto done;

        case EVT_CMD_COMPLETE:
            cc = (void *) ptr;
            if (cc->opcode != opcode)
                continue;
            ptr += EVT_CMD_COMPLETE_SIZE;
            len -= EVT_CMD_COMPLETE_SIZE;
            if (r->rparam && r->rlen)
                memcpy(r->rparam, ptr, r->rlen);
            goto done;
        }
    }

    errno = ETIMEDOUT;

failed:
    err = errno;
    setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of));
    errno = err;
    return -1;

done:
    setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of));
    return 0;
}

4.4 封装函数通用模板

lib/hci.c 中所有 hci_read_* / hci_write_* / hci_le_* 函数遵循统一模板:

/* 通用模板 */
int hci_some_operation(int dd, /* 参数 */, int to)
{
    /* 1. 定义命令参数和响应变量 */
    some_command_cp cp;      /* 命令参数结构 */
    some_response_rp rp;     /* 响应参数结构 */
    struct hci_request rq;   /* 请求结构 */

    /* 2. 填充命令参数 */
    memset(&cp, 0, sizeof(cp));
    cp.field1 = value1;
    cp.field2 = value2;

    /* 3. 构造请求 */
    memset(&rq, 0, sizeof(rq));
    rq.ogf = OGF_xxx;           /* 命令组 */
    rq.ocf = OCF_xxx;           /* 命令码 */
    rq.event = EVT_CMD_COMPLETE;/* 期望事件 */
    rq.cparam = &cp;            /* 命令参数 */
    rq.clen = SOME_COMMAND_CP_SIZE;
    rq.rparam = &rp;            /* 响应缓冲 */
    rq.rlen = SOME_RESPONSE_RP_SIZE;

    /* 4. 发送请求并等待响应 */
    if (hci_send_req(dd, &rq, to) < 0)
        return -1;

    /* 5. 检查响应状态 */
    if (rp.status) {
        errno = EIO;
        return -1;
    }

    /* 6. 提取返回值 */
    *output = rp.value;

    return 0;
}

4.5 hci_request 结构

struct hci_request {
    uint16_t ogf;       /* Opcode Group Field — 命令组 */
    uint16_t ocf;       /* Opcode Command Field — 命令码 */
    int      event;     /* 期望的响应事件类型(通常 EVT_CMD_COMPLETE) */
    void     *cparam;   /* Command Parameter — 命令参数指针 */
    int      clen;      /* Command Length — 命令参数长度 */
    void     *rparam;   /* Return Parameter — 响应参数缓冲 */
    int      rlen;      /* Return Length — 响应参数长度 */
};

五、命令实现深度拆解

5.1 cmd_cmd — 任意 HCI 命令发送

cmd_cmd 是 hcitool 最灵活的命令,直接发送任意 ogf/ocf 的 HCI 命令:

static void cmd_cmd(int dev_id, int argc, char **argv)
{
    unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr = buf;
    struct hci_filter flt;
    hci_event_hdr *hdr;
    uint16_t ocf;
    uint8_t ogf;
    int i, len, dd;

    helper_arg(2, -1, &argc, &argv, cmd_help);  /* 至少 2 个参数 */

    /* 1. 解析 ogf 和 ocf(十六进制) */
    ogf = strtol(argv[0], NULL, 16);
    ocf = strtol(argv[1], NULL, 16);
    if (errno == ERANGE || (ogf > 0x3f) || (ocf > 0x3ff)) {
        printf("%s", cmd_help);
        return;
    }

    /* 2. 解析参数字节(十六进制) */
    for (i = 2, len = 0; i < argc && len < (int) sizeof(buf); i++, len++)
        *ptr++ = (uint8_t) strtol(argv[i], NULL, 16);

    dd = hci_open_dev(dev_id);

    /* 3. 设置过滤器:接收所有事件 */
    hci_filter_clear(&flt);
    hci_filter_set_ptype(HCI_EVENT_PKT, &flt);
    hci_filter_all_events(&flt);
    setsockopt(dd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt));

    /* 4. 发送 HCI 命令(不等响应) */
    printf("< HCI Command: ogf 0x%02x, ocf 0x%04x, plen %d\n", ogf, ocf, len);
    hex_dump("  ", 20, buf, len);

    hci_send_cmd(dd, ogf, ocf, len, buf);

    /* 5. 读取一次事件(阻塞) */
    len = read(dd, buf, sizeof(buf));

    /* 6. 解析并打印事件 */
    hdr = (void *)(buf + 1);
    ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
    len -= (1 + HCI_EVENT_HDR_SIZE);

    printf("> HCI Event: 0x%02x plen %d\n", hdr->evt, hdr->plen);
    hex_dump("  ", 20, ptr, len);

    hci_close_dev(dd);
}

注意 cmd_cmd 与封装函数的区别:它不使用 hci_send_req(不设 opcode 过滤、不自动匹配响应),而是设置 hci_filter_all_events 接收所有事件,手动 read 一次。这使其能发送任意命令,但也意味着只读取一个事件包。

5.2 cmd_scan — BR/EDR 设备扫描

static void cmd_scan(int dev_id, int argc, char **argv)
{
    inquiry_info *info = NULL;
    uint8_t lap[3] = { 0x33, 0x8b, 0x9e };  /* GIAC: 0x9e8b33 */
    int num_rsp, length, flags;

    length  = 8;    /* 8 * 1.28s ≈ 10 秒 */
    num_rsp = 0;    /* 0 = 无限制 */
    flags   = 0;

    /* 解析 -l/-n/-i/-f/-C/-I/-O/-A 选项 */
    for_each_opt(opt, scan_options, NULL) { /* ... */ }
    helper_arg(0, 0, &argc, &argv, scan_help);

    if (dev_id < 0)
        dev_id = hci_get_route(NULL);

    /* 1. 执行 Inquiry(阻塞,内核完成 HCI 查询流程) */
    printf("Scanning ...\n");
    num_rsp = hci_inquiry(dev_id, length, num_rsp, lap, &info, flags);
    if (num_rsp < 0) {
        perror("Inquiry failed");
        exit(1);
    }

    /* 2. 打开设备套接字,读取每个设备的名称 */
    dd = hci_open_dev(dev_id);

    for (i = 0; i < num_rsp; i++) {
        ba2str(&(info+i)->bdaddr, addr);

        /* 读取远程名称(带时钟偏移) */
        if (hci_read_remote_name_with_clock_offset(dd,
                &(info+i)->bdaddr,
                (info+i)->pscan_rep_mode,
                (info+i)->clock_offset | 0x8000,
                sizeof(name), name, 100000) < 0)
            strcpy(name, "n/a");

        printf("\t%s\t%s\n", addr, name);
    }

    bt_free(info);
    hci_close_dev(dd);
}

hci_inquiry 是特殊的——它不通过 hci_send_req 实现,而是通过 ioctl HCIINQUIRY 触发内核执行完整的 Inquiry 流程,内核在内部完成发送 Inquiry 命令、收集响应、返回结果列表。

5.3 cmd_lescan — LE 设备扫描

static void cmd_lescan(int dev_id, int argc, char **argv)
{
    uint8_t own_type = LE_PUBLIC_ADDRESS;
    uint8_t scan_type = 0x01;     /* Active scan */
    uint8_t filter_type = 0;
    uint8_t filter_policy = 0x00;
    uint16_t interval = htobs(0x0010);
    uint16_t window = htobs(0x0010);
    uint8_t filter_dup = 0x01;
    int err, dd;

    /* 解析 --static/--privacy/--passive/--acceptlist/--discovery/--duplicates */
    for_each_opt(opt, lescan_options, NULL) { /* ... */ }

    if (dev_id < 0)
        dev_id = hci_get_route(NULL);

    dd = hci_open_dev(dev_id);

    /* 1. 设置扫描参数 */
    err = hci_le_set_scan_parameters(dd, scan_type, interval, window,
                                     own_type, filter_policy, 10000);
    if (err < 0) {
        perror("Set scan parameters failed");
        exit(1);
    }

    /* 2. 使能扫描 */
    err = hci_le_set_scan_enable(dd, 0x01, filter_dup, 10000);
    if (err < 0) {
        perror("Enable scan failed");
        exit(1);
    }

    printf("LE Scan ...\n");

    /* 3. 循环接收广播事件(阻塞,Ctrl+C 停止) */
    err = print_advertising_devices(dd, filter_type);

    /* 4. 关闭扫描 */
    hci_le_set_scan_enable(dd, 0x00, filter_dup, 10000);

    hci_close_dev(dd);
}

5.4 print_advertising_devices — 广播事件接收

这是 LE 扫描的核心——一个事件接收循环:

static int print_advertising_devices(int dd, uint8_t filter_type)
{
    unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
    struct hci_filter nf, of;
    struct sigaction sa;
    socklen_t olen;
    int len;

    /* 1. 设置过滤器:只接收 LE Meta Event */
    olen = sizeof(of);
    getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen);

    hci_filter_clear(&nf);
    hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
    hci_filter_set_event(EVT_LE_META_EVENT, &nf);

    setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf));

    /* 2. 注册 SIGINT 处理(Ctrl+C 优雅退出) */
    memset(&sa, 0, sizeof(sa));
    sa.sa_flags = SA_NOCLDSTOP;
    sa.sa_handler = sigint_handler;
    sigaction(SIGINT, &sa, NULL);

    /* 3. 事件接收循环 */
    while (1) {
        evt_le_meta_event *meta;
        le_advertising_info *info;
        char addr[18];

        /* 阻塞读取事件 */
        while ((len = read(dd, buf, sizeof(buf))) < 0) {
            if (errno == EINTR && signal_received == SIGINT) {
                len = 0;
                goto done;          /* Ctrl+C 退出 */
            }
            if (errno == EAGAIN || errno == EINTR)
                continue;
            goto done;
        }

        /* 解析事件结构 */
        ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
        len -= (1 + HCI_EVENT_HDR_SIZE);

        meta = (void *) ptr;

        /* subevent 0x02 = LE Advertising Report */
        if (meta->subevent != 0x02)
            goto done;

        /* 提取广播报告 */
        info = (le_advertising_info *) (meta->data + 1);

        /* 按发现过程过滤 */
        if (check_report_filter(filter_type, info)) {
            char name[30];
            memset(name, 0, sizeof(name));

            ba2str(&info->bdaddr, addr);
            eir_parse_name(info->data, info->length, name, sizeof(name) - 1);

            printf("%s %s\n", addr, name);
        }
    }

done:
    /* 恢复旧过滤器 */
    setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of));

    if (len < 0)
        return -1;
    return 0;
}

5.5 事件数据解析

LE Advertising Report 事件结构:

EIR(Extended Inquiry Response)数据解析:

static void eir_parse_name(uint8_t *eir, size_t eir_len,
                           char *buf, size_t buf_len)
{
    size_t offset = 0;

    while (offset < eir_len) {
        uint8_t field_len = eir[0];
        size_t name_len;

        /* EIR 结束标志 */
        if (field_len == 0)
            break;

        if (offset + field_len > eir_len)
            goto failed;

        /* 检查 AD Type */
        switch (eir[1]) {
        case EIR_NAME_SHORT:      /* 0x08 */
        case EIR_NAME_COMPLETE:   /* 0x09 */
            name_len = field_len - 1;
            if (name_len > buf_len)
                goto failed;
            memcpy(buf, &eir[2], name_len);
            return;
        }

        /* 跳到下一个 EIR 字段 */
        offset += field_len + 1;
        eir += field_len + 1;
    }

failed:
    /* 未找到名称字段 */
    snprintf(buf, buf_len, "(unknown)");
}

5.6 cmd_cc — BR/EDR 创建连接

static void cmd_cc(int dev_id, int argc, char **argv)
{
    bdaddr_t bdaddr;
    uint16_t handle;
    uint8_t role = 0x01;  /* 默认允许角色切换 */
    unsigned int ptype = HCI_DM1 | HCI_DM3 | HCI_DM5 | HCI_DH1 | HCI_DH3 | HCI_DH5;
    int dd;

    /* 解析 --role/--ptype */
    for_each_opt(opt, cc_options, NULL) { /* ... */ }
    helper_arg(1, 1, &argc, &argv, cc_help);

    str2ba(argv[0], &bdaddr);

    if (dev_id < 0) {
        dev_id = hci_get_route(&bdaddr);  /* 自动选择能到达该设备的适配器 */
    }

    dd = hci_open_dev(dev_id);

    /* 发送 Create Connection HCI 命令(阻塞等待完成) */
    if (hci_create_connection(dd, &bdaddr, htobs(ptype),
                              htobs(0x0000), role, &handle, 25000) < 0) {
        perror("Can't create connection");
        exit(1);
    }

    printf("Connection handle %d\n", handle);

    hci_close_dev(dd);
}

5.7 cmd_rssi — 读取连接 RSSI

static void cmd_rssi(int dev_id, int argc, char **argv)
{
    struct hci_conn_info_req *cr;
    bdaddr_t bdaddr;
    int8_t rssi;
    int dd;

    str2ba(argv[0], &bdaddr);

    /* 自动查找连接所在设备 */
    if (dev_id < 0) {
        dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr);
        if (dev_id < 0) {
            fprintf(stderr, "Not connected.\n");
            exit(1);
        }
    }

    dd = hci_open_dev(dev_id);

    /* 1. 通过 ioctl 获取连接句柄(唯一使用 ioctl 的地方) */
    cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
    bacpy(&cr->bdaddr, &bdaddr);
    cr->type = ACL_LINK;
    if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
        perror("Get connection info failed");
        exit(1);
    }

    /* 2. 发送 Read RSSI HCI 命令 */
    if (hci_read_rssi(dd, htobs(cr->conn_info->handle), &rssi, 1000) < 0) {
        perror("Read RSSI failed");
        exit(1);
    }

    printf("RSSI return value: %d\n", rssi);

    free(cr);
    hci_close_dev(dd);
}

HCIGETCONNINFO 是 hcitool 中唯一使用的 ioctl,用于通过 BD 地址查找连接句柄(handle)。这是因为 RSSI/LQ/TPL 等 HCI 命令需要 handle 参数,而用户只知道地址。

六、HCI Socket 通信机制

6.1 套接字创建与绑定

/* lib/hci.c — hci_open_dev() */
int hci_open_dev(int dev_id)
{
    struct sockaddr_hci a;
    int dd;

    /* 创建 HCI 原始套接字 */
    dd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI);

    /* 绑定到指定设备 */
    memset(&a, 0, sizeof(a));
    a.hci_family = AF_BLUETOOTH;
    a.hci_dev = dev_id;       /* 绑定到 hci0/hci1... */
    bind(dd, (struct sockaddr *) &a, sizeof(a));

    return dd;
}

6.2 事件过滤器机制

HCI 套接字使用 setsockopt(SOL_HCI, HCI_FILTER) 设置事件过滤器,决定哪些事件包会被 read() 读取:

struct hci_filter {
    uint32_t type_mask;     /* Packet Type 过滤(Command/ACL/SCO/Event) */
    uint32_t event_mask[2]; /* Event Code 过滤(位图) */
    uint16_t opcode;        /* Command Complete/Status 的 Opcode 匹配 */
};

/* 常用设置方式 */
hci_filter_clear(&flt);                           /* 清空所有过滤 */
hci_filter_set_ptype(HCI_EVENT_PKT, &flt);        /* 只接收事件包 */
hci_filter_set_event(EVT_CMD_COMPLETE, &flt);     /* 只接收 Command Complete */
hci_filter_set_event(EVT_LE_META_EVENT, &flt);    /* 接收 LE Meta Event */
hci_filter_set_opcode(opcode, &flt);              /* 匹配特定 Opcode 的响应 */
hci_filter_all_events(&flt);                      /* 接收所有事件 */

6.3 通信时序图

6.4 两种事件接收模式

七、超时处理与错误机制

7.1 超时参数体系

hcitool 中每个 HCI 操作都有独立的超时参数:

命令

超时值

单位

含义

hci_inquiry

length * 1.28s

秒

查询持续时间

hci_read_remote_name

100000

ms

远程名称读取

hci_create_connection

25000

ms

连接建立

hci_disconnect

10000

ms

断开连接

hci_read_rssi

1000

ms

RSSI 读取

hci_le_set_scan_*

10000

ms

LE 扫描参数设置

hci_le_create_conn

25000

ms

LE 连接建立

hci_le_read_remote_features

20000

ms

LE 特性读取

hci_read_remote_version

20000

ms

远程版本读取

7.2 超时处理逻辑

hci_send_req 中的超时实现:

if (to) {
    struct pollfd p;
    int n;

    p.fd = dd;
    p.events = POLLIN;

    /* poll 等待数据,超时返回 0 */
    while ((n = poll(&p, 1, to)) < 0) {
        if (errno == EAGAIN || errno == EINTR)
            continue;
        goto failed;
    }

    if (!n) {           /* poll 返回 0 = 超时 */
        errno = ETIMEDOUT;
        goto failed;
    }

    /* 递减超时(每次 poll 后减去 10ms) */
    to -= 10;
    if (to < 0) to = 0;
}

7.3 错误处理模式

/* hcitool 中统一的错误处理模式 */
dd = hci_open_dev(dev_id);
if (dd < 0) {
    perror("Could not open device");
    exit(1);
}

err = hci_some_operation(dd, ...);
if (err < 0) {
    perror("Operation failed");   /* 打印 errno 对应的错误信息 */
    exit(1);
}

hci_close_dev(dd);

hci_send_req 失败时设置 errno:

  • ETIMEDOUT:命令超时,控制器未响应

  • EIO:命令执行失败(status != 0)

  • EAGAIN/EINTR:被信号中断,通常自动重试

八、hcitool vs hciconfig vs bluetoothctl 层级对比

8.1 三层架构定位

8.2 功能对比表

维度

hcitool

hciconfig

bluetoothctl

操作对象

远程设备+连接

本地适配器

适配器+设备+服务

通信方式

HCI 命令/事件包

ioctl + HCI 命令

D-Bus

依赖 bluetoothd

不依赖

不依赖

强依赖

设备扫描

scan/inq/lescan

piscan/iscan(扫描模式)

scan on

创建连接

cc/lecc

不支持

connect

断开连接

dc/ledc

不支持

disconnect

配对

不支持

不支持

pair

GATT 读写

不支持

不支持

gatt read/write

RSSI/LQ

rssi/lq/tpl/afh

不支持

不支持(需通过属性)

任意 HCI 命令

cmd(支持)

不支持

不支持

上电/下电

不支持

up/down

power on/off

MAC 修改

不支持

lerandaddr

不支持

广播配置

不支持

leadv/noleadv

advertise on

链路策略

lp/lst/sr/cpt

lp/lm(查看设置)

不支持

认证/加密

auth/enc/key

auth/encrypt

不支持(自动)

权限

root

root

root/policykit

维护状态

deprecated

deprecated

活跃

8.3 使用场景选择

需要配对/GATT/Profile 操作?
├── 是 → bluetoothctl
└── 否 → 操作对象是本地适配器?
    ├── 是 → hciconfig(上电/MAC/硬件信息)
    └── 否 → 操作对象是远程设备/连接?
        ├── 是 → 需要读 RSSI/LQ/链路质量?
        │   ├── 是 → hcitool rssi/lq/tpl/afh
        │   └── 否 → 需要发任意 HCI 命令?
        │       ├── 是 → hcitool cmd
        │       └── 否 → hcitool scan/lescan/cc/dc
        └── 否 → 使用 bluetoothctl

九、常见报错与解决方案

9.1 设备不可用

$ hcitool dev
# 无输出或无设备

解决:

# 检查设备是否存在
hciconfig -a

# 上电
sudo hciconfig hci0 up

9.2 扫描无结果

sudo hciconfig hci0 up
sudo hciconfig hci0 piscan
sudo rfkill unblock bluetooth

根因:适配器未上电、未开启可发现模式、或被 rfkill 阻止。

解决:

$ hcitool cc AA:BB:CC:DD:EE:FF
Can't create connection: Connection refused (111)

9.3 连接失败

$ hcitool cc AA:BB:CC:DD:EE:FF
Can't create connection: Connection refused (111)

根因:目标设备不可连接、信号弱、或已有其他连接占用。

解决:

# 检查目标设备是否在范围内
hcitool scan

# 检查当前连接
hcitool con

# 使用 bluetoothctl 配对后连接(hcitool 不支持配对)
sudo bluetoothctl
[bluetooth]# pair AA:BB:CC:DD:EE:FF
[bluetooth]# connect AA:BB:CC:DD:EE:FF

9.4 Read RSSI 失败

$ hcitool rssi AA:BB:CC:DD:EE:FF
Not connected.

根因:hcitool rssi 需要已有活跃连接。

解决:

# 先创建连接
hcitool cc AA:BB:CC:DD:EE:FF

# 再读取 RSSI
hcitool rssi AA:BB:CC:DD:EE:FF

9.5 LE 扫描被占用

$ hcitool lescan
Set scan parameters failed: Input/output error (5)

根因:bluetoothd 正在使用 LE 扫描(如 bluetoothctl scan on)。

解决:

# 停止 bluetoothd 的扫描
bluetoothctl scan off

# 或停止 bluetoothd
sudo systemctl stop bluetooth

# 再执行 lescan
sudo hcitool lescan

9.6 任意命令无响应

$ hcitool cmd 0x03 0x0014
# 无输出或卡住

根因:cmd_cmd 中 read() 是阻塞的,如果命令不产生事件响应会一直阻塞。

解决:

# 使用 btmon 监控 HCI 流量,确认命令是否发出
sudo btmon &
# 另一终端执行
sudo hcitool cmd 0x03 0x0014

# 如果确实卡住,Ctrl+C 终止

十、调试技巧与二次开发

10.1 btmon 配合 hcitool

# 终端1: 启动 btmon 抓包
sudo btmon

# 终端2: 执行 hcitool 命令
sudo hcitool lescan --passive
sudo hcitool cmd 0x08 0x000a 0x01  # LE Set Advertising Enable

# btmon 输出:
# < HCI Command: LE Set Advertising Enable (0x08|0x000a) plen 1
#   Advertising: Enabled (0x01)
# > HCI Event: Command Complete (0x0e) plen 4
#   LE Set Advertising Enable (0x08|0x000a) ncmd 1
#   Status: Success (0x00)

10.2 用 hcitool cmd 探测控制器能力

# 读取本地支持特性 (OGF=0x04 Link Policy, OCF=0x0003 Read Local Supported Features)
# 实际: OGF=0x03 Host Controller, OCF=0x0003
hcitool cmd 0x03 0x0003

# 读取本地版本信息 (OGF=0x03, OCF=0x0001 Read Local Version Information)
hcitool cmd 0x03 0x0001

# 读取 BD_ADDR (OGF=0x04 Informational, OCF=0x0009 Read BD_ADDR)
hcitool cmd 0x04 0x0009

# 读取 LE 支持状态 (OGF=0x03, OCF=0x0056 Read LE Host Supported)
hcitool cmd 0x03 0x0056

10.3 strace 追踪系统调用

# 追踪 hcitool lescan 的完整系统调用链
sudo strace -e trace=socket,bind,setsockopt,write,read,poll,close \
    -f hcitool lescan

# 关键调用链:
# socket(AF_BLUETOOTH, SOCK_RAW|SOCK_CLOEXEC, BTPROTO_HCI) = 3
# bind(3, {sa_family=AF_BLUETOOTH, hci_dev=0}, 6) = 0
# setsockopt(3, SOL_HCI, HCI_FILTER, ...)   # 设置扫描参数响应过滤
# write(3, "\1\x08\x0b\x01\x10\x00\x10\x00...", 8)  # LE Set Scan Parameters
# poll([{fd=3, events=POLLIN}], 1, 10000)   # 等待响应
# read(3, "\4\x0e\x04\x01\x0b\x08\x00", 258)  # Command Complete
# setsockopt(3, SOL_HCI, HCI_FILTER, ...)   # 切换为 LE Meta Event 过滤
# write(3, "\1\x08\x0c\x01\x01\x01", 5)      # LE Set Scan Enable
# poll([{fd=3, events=POLLIN}], 1, 10000)   # 等待响应
# read(3, ...)                                # 接收广播事件

10.4 自定义 HCI 工具开发模板

基于 hcitool 的源码模式,开发自定义 HCI 工具的模板:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "lib/bluetooth.h"
#include "lib/hci.h"
#include "lib/hci_lib.h"

int main(int argc, char *argv[])
{
    int dd, dev_id;
    uint8_t status;
    struct hci_request rq;
    
    /* 1. 选择设备 */
    dev_id = hci_get_route(NULL);
    if (dev_id < 0) {
        perror("No device");
        exit(1);
    }
    
    /* 2. 打开设备套接字 */
    dd = hci_open_dev(dev_id);
    if (dd < 0) {
        perror("Open device");
        exit(1);
    }
    
    /* 3. 构造命令参数 */
    /* 例如: LE Set Random Address */
    le_set_random_address_cp cp;
    memset(&cp, 0, sizeof(cp));
    str2ba("AA:BB:CC:DD:EE:FF", &cp.bdaddr);
    
    /* 4. 构造 hci_request */
    memset(&rq, 0, sizeof(rq));
    rq.ogf = OGF_LE_CTL;                    /* 0x08 */
    rq.ocf = OCF_LE_SET_RANDOM_ADDRESS;     /* 0x0005 */
    rq.event = EVT_CMD_COMPLETE;
    rq.cparam = &cp;
    rq.clen = LE_SET_RANDOM_ADDRESS_CP_SIZE;
    rq.rparam = &status;
    rq.rlen = 1;
    
    /* 5. 发送请求 */
    if (hci_send_req(dd, &rq, 1000) < 0) {
        perror("Send request");
        exit(1);
    }
    
    if (status) {
        fprintf(stderr, "Command failed: 0x%02x\n", status);
        exit(1);
    }
    
    printf("Success\n");
    
    /* 6. 关闭 */
    hci_close_dev(dd);
    return 0;
}

十一、核心命令 → 源码函数 → HCI 机制速查

hcitool 命令

源码函数

底层 HCI 机制

HCI Opcode

dev

cmd_dev

ioctl(HCIGETDEVINFO)

—

inq

cmd_inq

hci_inquiry (ioctl HCIINQUIRY)

Inquiry

scan

cmd_scan

hci_inquiry + hci_read_remote_name

Inquiry + Remote Name Request

name

cmd_name

hci_read_remote_name

Remote Name Request

info

cmd_info

hci_read_remote_version + hci_read_remote_features

Read Remote Version/Features

cmd

cmd_cmd

hci_send_cmd + read (无过滤)

任意 Opcode

con

cmd_con

ioctl(HCIGETCONNLIST)

—

cc

cmd_cc

hci_create_connection

Create Connection (0x0405)

dc

cmd_dc

ioctl(HCIGETCONNINFO) + hci_disconnect

Disconnect (0x0406)

sr

cmd_sr

ioctl(HCIGETCONNINFO) + hci_switch_role

Switch Role (0x080B)

rssi

cmd_rssi

ioctl(HCIGETCONNINFO) + hci_read_rssi

Read RSSI (0x1405)

lq

cmd_lq

ioctl(HCIGETCONNINFO) + hci_get_link_quality

Get Link Quality (0x1403)

tpl

cmd_tpl

ioctl(HCIGETCONNINFO) + hci_read_transmit_power_level

Read Transmit Power Level (0x140D)

afh

cmd_afh

ioctl(HCIGETCONNINFO) + hci_read_afh_channel_map

Read AFH Channel Map (0x1407)

lp

cmd_lp

ioctl(HCIGETCONNINFO) + hci_write_link_policy

Write Link Policy (0x080D)

lst

cmd_lst

ioctl(HCIGETCONNINFO) + hci_write_link_supervision_timeout

Write Link Supervision Timeout (0x0806)

auth

cmd_auth

ioctl(HCIGETCONNINFO) + hci_authenticate_link

Authentication Requested (0x0407)

enc

cmd_enc

ioctl(HCIGETCONNINFO) + hci_set_link_encryption

Set Connection Encryption (0x0413)

key

cmd_key

ioctl(HCIGETCONNINFO) + hci_change_link_key

Change Connection Link Key (0x0417)

clkoff

cmd_clkoff

ioctl(HCIGETCONNINFO) + hci_read_clock_offset

Read Clock Offset (0x040F)

clock

cmd_clock

ioctl(HCIGETCONNINFO) + hci_read_clock

Read Clock (0x1407)

lescan

cmd_lescan

hci_le_set_scan_parameters + hci_le_set_scan_enable + read 循环

LE Set Scan Params (0x200B) + Enable (0x200C)

leinfo

cmd_leinfo

hci_le_create_conn + hci_read_remote_version + hci_le_read_remote_features + hci_disconnect

LE Create Connection (0x200D)

lecc

cmd_lecc

hci_le_create_conn

LE Create Connection (0x200D)

ledc

cmd_ledc

hci_disconnect

Disconnect (0x0406)

lecup

cmd_lecup

hci_le_conn_update

LE Connection Update (0x2013)

lealadd

cmd_lealadd

hci_le_add_accept_list

LE Add Device To Accept List (0x2011)

lealrm

cmd_lealrm

hci_le_rm_accept_list

LE Remove Device From Accept List (0x2012)

lealsz

cmd_lealsz

hci_le_read_accept_list_size

LE Read Accept List Size (0x200F)

lealclr

cmd_lealclr

hci_le_clear_accept_list

LE Clear Accept List (0x2010)

lerladd

cmd_lerladd

hci_le_add_resolving_list

LE Add Device To Resolving List (0x2027)

spinq

cmd_spinq

hci_send_req

Periodic Inquiry Mode (0x0403)

epinq

cmd_epinq

hci_send_req

Exit Periodic Inquiry Mode (0x0404)

十二、总结

12.1 核心设计要点

1. 纯 HCI 命令通信:hcitool几乎所有操作通过 hci_send_req/hci_send_cmd 收发 HCI 命令/事件包,唯一例外是 HCIGETCONNINFO(获取连接句柄)和 HCIINQUIRY(内核代为执行查询)。

2. 三层封装架构:

  • cmd_* 函数层:参数解析、设备打开、结果输出

  • hci_* 封装函数层:填充 hci_request,调用 hci_send_req

  • hci_send_req/hci_send_cmd 核心层:报文构造、socket 读写、事件过滤

3. 事件过滤器机制:通过 setsockopt(SOL_HCI, HCI_FILTER) 精确控制接收哪些事件,避免无关事件干扰。请求-响应模式按 opcode 过滤,事件流模式按 event code 过滤

4. 两种事件接收模式:

- 请求-响应模式(hci_send_req):发命令→poll等待→读响应→返回,适用于 rssi/lq/cc 等单次操作

- 事件流监听模式(read 循环):设过滤器→while(1) read→解析→打印,适用于 lescan 持续扫描

5. getopt 子命令解析:每个命令定义自己的 struct option 数组,支持 --long 和 -short 选项,比 hciconfig 的单字符串参数更灵活。

12.2 三大工具定位总结

- hciconfig = 适配器管理:操作本地 hciX 设备(上电/MAC/硬件信息),主要通过 ioctl

- hcitool = HCI 协议操作:操作远程设备和连接(扫描/连接/RSSI/链路策略),主要通过 hci_send_req

- bluetoothctl = 完整蓝牙服务:通过 bluetoothd 管理全部蓝牙功能(配对/GATT/Profile),通过 D-Bus

三者互补:hciconfig 管设备、hcitool 管协议、bluetoothctl 管服务。底层调试用 hciconfig + hcitool,日常使用用 bluetoothctl。

12.3 二次开发要点

  • 使用 hci_open_dev + hci_send_req 模板可快速开发自定义 HCI 工具

  • 事件过滤器是关键:不设过滤器会收到所有 HCI 事件(包括其他进程的命令响应)

  • 超时设置要合理:连接类操作通常 20-25s,查询类 1-10s

  • 与 bluetoothd 并行使用需注意:两者可能竞争同一 HCI 命令通道


附录:核心源码文件索引

文件

关键内容

[tools/hcitool.c]

全部源码:main()、命令表、44 个 cmd_* 函数、EIR 解析

[lib/hci.c]

hci_open_dev / hci_send_req / hci_send_cmd / hci_inquiry / hci_create_connection 等

[lib/hci.h]

HCI 常量、Opcode 定义、结构体(inquiry_info / hci_conn_info 等)

[lib/hci_lib.h]

struct hci_request、所有 hci_* 函数声明

[lib/bluetooth.h]

bdaddr_t、ba2str、str2ba 基础定义

[src/oui.c]

OUI 厂商名查询(scan --oui 使用)

参考文献

  • BlueZ Source Code: tools/hcitool.c, lib/hci.c, lib/hci.h

  • Bluetooth Core Specification Version 5.3: Volume 2, Part E (HCI Commands and Events)

  • Bluetooth Core Specification Version 5.3: Volume 3, Part C (Generic Access Profile)

  • Linux Kernel: net/bluetooth/hci_core.c, net/bluetooth/hci_sock.c

  • BlueZ Documentation: doc/hcidump.1, doc/btmon.1


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byte轻骑兵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值