基于bluez的树莓派低功耗蓝牙开发:与多个低功耗蓝牙模块连接

Linux BLE编程——广播、扫描

一.广播与扫描

低功耗蓝牙通过广播信道来发现其他设备,从机设备通过广播信道发送广播数据包,主机设备通过扫描可以接收到范围内所有广播数据包,然后对感兴趣设备发送扫描请求报文,从机接收到该报文后可选择是否向主机发送扫描响应报文(包含自身ID、BAADDR等信息,可设置)。
广播能够发送两种数据:广播数据和扫描响应数据。主机在发送广播数据是需要设置广播参数,配置参数包括:
1.最小间隔时间,范围20ms—10.24s。
2.最大间隔时间,范围20ms—10.24s。
3.广播类型,广播类型一共四种:

  1. 可连接的非定向广播,表示设备可以接受任何设备的连接请求。

  2. 可连接的定向广播,表示仅仅能接受某一特定设备连接请求。

  3. 可扫描的非定向广播,发送广播数据和扫描响应数据,用来激活 扫描者。

  4. 不可连接的非定向广播,仅仅发送广播数据。

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);
}



  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值