Linux BLE编程——广播、扫描
一.广播与扫描
低功耗蓝牙通过广播信道来发现其他设备,从机设备通过广播信道发送广播数据包,主机设备通过扫描可以接收到范围内所有广播数据包,然后对感兴趣设备发送扫描请求报文,从机接收到该报文后可选择是否向主机发送扫描响应报文(包含自身ID、BAADDR等信息,可设置)。
广播能够发送两种数据:广播数据和扫描响应数据。主机在发送广播数据是需要设置广播参数,配置参数包括:
1.最小间隔时间,范围20ms—10.24s。
2.最大间隔时间,范围20ms—10.24s。
3.广播类型,广播类型一共四种:
-
可连接的非定向广播,表示设备可以接受任何设备的连接请求。
-
可连接的定向广播,表示仅仅能接受某一特定设备连接请求。
-
可扫描的非定向广播,发送广播数据和扫描响应数据,用来激活 扫描者。
-
不可连接的非定向广播,仅仅发送广播数据。
4 .地址类型,随机地址或固定地址
5. 对端设备地址类型,可选项,定向广播需要设置,
6. 对端设备地址,同上。
7. 信道映射。使用那三个广播信道。
8. 过滤政策,用来过滤不符合规则的广播数据包,过滤政策如下:
1. 接收任何设备的扫描请求和连接请求。
2. 仅仅接收白名单中设备的扫描请求,但接收任何设备的连接请求
3. 接收任何设备的扫描请求,但仅接受白名单中设备的连接请求。
4. 仅接受白名单中特定设备的扫描请求和连接请求。
扫描分为被动扫描和主动扫描,接收对端设备的广播数据包,可以使用被动扫描,主动扫描不仅可以捕获广播数据包,还能捕获扫描响应数据包。扫描需要配置的参数如下:
1.扫描类型,主动扫描或被动扫描。
2.扫描间隔,多长时间扫描一次。
3.扫描窗口,每次扫描持续多久。
4.个人地址类型,主动扫描才需配置,固定地址或随机地址
5.扫描策略,接收任何广播数据包或仅接收白名单设备广播数据包。
主机通过HCI层向控制器发送发送HCI命令数据包是蓝牙进行广播。
二、HCI协议概述
主机控制接口(HCI)是主机与控制器之间的接口,主要完成两个任务:一个是发送命令给控制器和接收来自控制器的事件,另一个是发送和接收来自对端设备的数据。HCI屏蔽了不同控制器之间的差异,规定一套统一的数据格式来与控制器进行交互,其有4种不同数据包格式:命令数据包(HCI Command)、事件数据包(Event)、数据包(ACL data)、流控。
1.命令数据包
主机通过向控制器发送命令数据包来执行命令。这些命令通常用于适配控制器状态,或者请求适配器完成某种操作。HCI命令数据包格式如下:
图1 HCI命令数据包格式
OpCode:操作码,用来确认发送的命令,由10bit的OCF(OpCode命令域)和6bit的OGF(OpCode组域)组成。OGF确定了所发送命令的组域,OCF确定了该组域中一个具体命令。低功耗蓝牙命令的组域为0x08。
Parameter Total Length:参数总长度。
Parameter:参数。每一个命令都会带有一些参数。
2.事件数据包
事件数据包是由控制器发往主机,主要用于反馈输入命令的结果和发送信息。其格式如下
Event Code:事件码。用来区分不同事件。
Parameter Length表示所带参数的长度,以字节数为单位,随后就是所带的参数列表。
三、HCI编程
1)int hci_open_dev(int dev_id) 函数
该函数用于创建一个HCI socket,并与指定的蓝牙设备ID绑定,这样就这个HCI socket来进行控制器与主机的数据传输。
2)int hci_close_dev(int dd)
关闭一个HCI socket
3)int hci_send_req(int dd, struct hci_request *r, int to)
该函数用于向控制器发送HCI命令数据包。参数:
dd:hci_open_dev()函数创建的HCI socket。
r :hci命令数据包。其数据结构如下:
to :超时时间,单位:milliseconds
4)int hci_get_route(bdaddr_t *bdaddr)
根据物理地址得出设备ID ,输入参数为NULL时默认返回第一个可用设备的设备ID.
5)int ba2str(const bdaddr_t *ba, char *str)
将物理地址转换为字符串.
6)int str2ba(const char *str, bdaddr_t *ba)
将字符串转换为bdaddr_t.
7)int hci_le_set_scan_parameters(int dd, uint8_t type,
uint16_t interval, uint16_t window,
uint8_t own_type, uint8_t filter, int to)
用于配置扫描参数.
8)int hci_le_set_scan_enable(int dd, uint8_t enable, uint8_t filter_dup, int to)
用于向控制器发送扫描指令.
代码实例
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <signal.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/hci.h"
#include "bluetooth/hci_lib.h"
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
/* Unofficial value, might still change */
#define LE_LINK 0x80
#define FLAGS_AD_TYPE 0x01
#define FLAGS_LIMITED_MODE_BIT 0x01
#define FLAGS_GENERAL_MODE_BIT 0x02
#define EIR_NAME_SHORT 0x08 /* shortened local name */
#define EIR_NAME_COMPLETE 0x09 /* complete local name */
static volatile int signal_received = 0;
static void sigint_handler(int sig)
{
signal_received = sig;
}
static void eir_parse_name(uint8_t *eir, size_t eir_len, char *buf, size_t buf_len)
{
size_t offset;
offset = 0;
while (offset < eir_len) {
uint8_t field_len = eir[0];
size_t name_len;
/* Check for the end of EIR */
if (field_len == 0)
break;
if (offset + field_len > eir_len)
goto failed;
switch (eir[1]) {
case EIR_NAME_SHORT:
case EIR_NAME_COMPLETE:
name_len = field_len - 1;
if (name_len > buf_len)
goto failed;
memcpy(buf, &eir[2], name_len);
return;
}
offset += field_len + 1;
eir += field_len + 1;
}
failed:
snprintf(buf, buf_len, "(unknown)");
}
static int print_advertising_devices(int dd)
{
unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
struct hci_filter nf, of;
struct sigaction sa;
socklen_t olen;
int len;
olen = sizeof(of);
if (getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen) < 0) {
printf("Could not get socket options\n");
return -1;
}
hci_filter_clear(&nf);
hci_filter_set_ptype(HCI_EVENT_PKT, &nf); //hci数据包类型,此处设置为事件数据包
hci_filter_set_event(EVT_LE_META_EVENT, &nf); //hci事件数据包中具体事件类型。EVT_LE_META_EVENT为事件码。
if (setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) {
printf("Could not set socket options\n");
return -1;
}
memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_NOCLDSTOP;
sa.sa_handler = sigint_handler;
sigaction(SIGINT, &sa, NULL);
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;
}
if (errno == EAGAIN || errno == EINTR)
continue;
goto done;
}
ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
len -= (1 + HCI_EVENT_HDR_SIZE);
meta = (void *) ptr;
if (meta->subevent != 0x02)
goto done;
/* Ignoring multiple reports */
info = (le_advertising_info *) (meta->data + 1);
{
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;
}
/*
* 主机发送广播数据用于扫描设备是需要hci_le_set_scan_parameters函数来设置广播参数:
* scan_type:扫描分为主动扫描(0x01)和被动扫描(0x00)
* 被动扫描:接收对端设备的广播数据包。
* 主动扫描:不仅可以捕获广播数据包,还能捕获扫描响应包
* own_type:地址类型,固定设备地址或随机地址·
* filter_policy:扫描策略,接收任何广播数据包(0x00)还是仅接收白名单设备的广播数据包(0x01)
* intervl:扫描间隔,单位1.25ms,扫描频率
* window:扫描窗口,扫描时间长短。
* filter_dup: 是(0x00)否(0x01)显示重复扫描到的设备信息。
* */
int lescan(int dev_id, int argc, char **argv)
{
int err, opt, dd;
uint8_t own_type = LE_PUBLIC_ADDRESS;
uint8_t scan_type = 0x00;
uint8_t filter_policy = 0x00;
uint16_t interval = htobs(0x0010);
uint16_t window = htobs(0x0010);
uint8_t filter_dup = 0x01;
dd = hci_open_dev(dev_id);
if (dd < 0) {
perror("Could not open device");
exit(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);
}
/*发送指令包,使能扫描*/
err = hci_le_set_scan_enable(dd, 0x01, filter_dup, 10000);
if (err < 0) {
perror("Enable scan failed");
exit(1);
}
printf("LE Scan ...\n");
err = print_advertising_devices(dd);
if (err < 0) {
perror("Could not receive advertising events");
exit(1);
}
err = hci_le_set_scan_enable(dd, 0x00, filter_dup, 10000);
if (err < 0) {
perror("Disable scan failed");
exit(1);
}
hci_close_dev(dd);
}