hcitool 是 BlueZ 提供的 HCI 层原生指令工具,直接通过 HCI 套接字向控制器发送蓝牙规范定义的 HCI 命令,并接收 HCI 事件响应。如果说 hciconfig 是"适配器配置工具"(类似 ifconfig),那么 hcitool 就是"HCI 协议操作工具"(类似 ping/arping)------它的操作对象是远程设备 和连接,而非本地适配器。本文基于 BlueZ 5.x 原生源码,全面汇总 hcitool 实操指令,深度剖析其源码架构、HCI 指令封装、报文构造、命令下发、事件接收、数据解析全套逻辑,并对比三大工具的层级差异。
目录
[一、hcitool 全套实操指令](#一、hcitool 全套实操指令)
[四、HCI 指令封装与报文构造](#四、HCI 指令封装与报文构造)
[六、HCI Socket 通信机制](#六、HCI Socket 通信机制)
[八、hcitool vs hciconfig vs bluetoothctl 层级对比](#八、hcitool vs hciconfig vs bluetoothctl 层级对比)
[十一、核心命令 → 源码函数 → HCI 机制速查](#十一、核心命令 → 源码函数 → HCI 机制速查)
一、hcitool 全套实操 指令
1.1 基本用法
cpp
# 查看帮助
hcitool --help
# 指定适配器
hcitool -i hci0 <command>
# 用设备地址指定适配器
hcitool -i 00:1A:7D:DA:71:13 <command>
# 查看命令子帮助
hcitool scan --help
hcitool lescan --help
1.2 本地设备查询
cpp
# 列出本地蓝牙设备
hcitool dev
# 输出:
# hci0 00:1A:7D:DA:71:13
1.3 BR / EDR 设备发现
cpp
# 基础查询(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 设备扫描
cpp
# 基础 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 远程设备信息查询
cpp
# 读取远程设备名称
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 连接管理
cpp
# 创建 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 连接质量与链路信息
cpp
# 读取 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 链路策略与安全管理
cpp
# 链路策略设置
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(原白名单)管理
cpp
# 添加设备到 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(地址解析列表)管理
cpp
# 添加设备到 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 命令发送
cpp
# 发送任意 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 周期性查询
cpp
# 启动周期性查询
hcitool spinq
# 停止周期性查询
hcitool epinq
1.13 实战速查:完整 BLE 设备探测流程
cpp
# 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。
cpp
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 行:
cpp
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 命令表数据结构
cpp
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 宏解析自己的选项:
cpp
#define for_each_opt(opt, long, short) \
while ((opt = getopt_long(argc, argv, short ? short : "+", long, NULL)) != -1)
以 cmd_lescan 为例:
cpp
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
cpp
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 参数解析流程图
cpp
命令行: 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 行):
cpp
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 :
cpp
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_* 函数遵循统一模板:
cpp
/* 通用模板 */
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 结构
cpp
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 命令:
cpp
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 设备扫描
cpp
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 设备扫描
cpp
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 扫描的核心------一个事件接收循环:
cpp
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)数据解析:
cpp
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 创建连接
cpp
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
cpp
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 套接字创建与绑定
cpp
/* 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() 读取:
cpp
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 中的超时实现:
cpp
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 错误处理模式
cpp
/* 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 使用场景选择
cpp
需要配对/GATT/Profile 操作?
├── 是 → bluetoothctl
└── 否 → 操作对象是本地适配器?
├── 是 → hciconfig(上电/MAC/硬件信息)
└── 否 → 操作对象是远程设备/连接?
├── 是 → 需要读 RSSI/LQ/链路质量?
│ ├── 是 → hcitool rssi/lq/tpl/afh
│ └── 否 → 需要发任意 HCI 命令?
│ ├── 是 → hcitool cmd
│ └── 否 → hcitool scan/lescan/cc/dc
└── 否 → 使用 bluetoothctl
九、常见报错与解决方案
9.1 设备不可用
cpp
$ hcitool dev
# 无输出或无设备
解决:
cpp
# 检查设备是否存在
hciconfig -a
# 上电
sudo hciconfig hci0 up
9.2 扫描无结果
cpp
sudo hciconfig hci0 up
sudo hciconfig hci0 piscan
sudo rfkill unblock bluetooth
根因:适配器未上电、未开启可发现模式、或被 rfkill 阻止。
解决:
cpp
$ hcitool cc AA:BB:CC:DD:EE:FF
Can't create connection: Connection refused (111)
9.3 连接失败
cpp
$ hcitool cc AA:BB:CC:DD:EE:FF
Can't create connection: Connection refused (111)
根因:目标设备不可连接、信号弱、或已有其他连接占用。
解决:
cpp
# 检查目标设备是否在范围内
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 失败
cpp
$ hcitool rssi AA:BB:CC:DD:EE:FF
Not connected.
根因:hcitool rssi 需要已有活跃连接。
解决:
cpp
# 先创建连接
hcitool cc AA:BB:CC:DD:EE:FF
# 再读取 RSSI
hcitool rssi AA:BB:CC:DD:EE:FF
9.5 LE 扫描被占用
cpp
$ hcitool lescan
Set scan parameters failed: Input/output error (5)
根因:bluetoothd 正在使用 LE 扫描(如 bluetoothctl scan on)。
解决:
cpp
# 停止 bluetoothd 的扫描
bluetoothctl scan off
# 或停止 bluetoothd
sudo systemctl stop bluetooth
# 再执行 lescan
sudo hcitool lescan
9.6 任意命令无响应
cpp
$ hcitool cmd 0x03 0x0014
# 无输出或卡住
根因:cmd_cmd 中 read() 是阻塞的,如果命令不产生事件响应会一直阻塞。
解决:
cpp
# 使用 btmon 监控 HCI 流量,确认命令是否发出
sudo btmon &
# 另一终端执行
sudo hcitool cmd 0x03 0x0014
# 如果确实卡住,Ctrl+C 终止
十、调试技巧与二次开发
10.1 btmon 配合 hcitool
cpp
# 终端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 探测控制器能力
cpp
# 读取本地支持特性 (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 追踪系统调用
cpp
# 追踪 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 工具的模板:
cpp
#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 核心设计要点
-
纯 HCI 命令通信:hcitool几乎所有操作通过 hci_send_req/hci_send_cmd 收发 HCI 命令/事件包,唯一例外是 HCIGETCONNINFO(获取连接句柄)和 HCIINQUIRY(内核代为执行查询)。
-
三层封装架构:
-
cmd_*函数层:参数解析、设备打开、结果输出 -
hci_*封装函数层:填充hci_request,调用hci_send_req -
hci_send_req/hci_send_cmd核心层:报文构造、socket 读写、事件过滤
-
事件过滤器机制:通过 setsockopt(SOL_HCI, HCI_FILTER) 精确控制接收哪些事件,避免无关事件干扰。请求-响应模式按 opcode 过滤,事件流模式按 event code 过滤
-
两种事件接收模式:
-
请求-响应模式(hci_send_req):发命令→poll等待→读响应→返回,适用于 rssi/lq/cc 等单次操作
-
事件流监听模式(read 循环):设过滤器→while(1) read→解析→打印,适用于 lescan 持续扫描
- 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