Linux 蓝牙读写,实战Linux Bluetooth编程(三) HCI层编程

作者:Sam (甄峰)

(HCI协议简介,HCI 在BlueZ中的实现以及HCI编程接口)

1. HCI层协议概述:

HCI提供一套统一的方法来访问Bluetooth底层。如图所示:

uid-31087949-id-5776244.html

从图上可以看出,Host Controller Interface(HCI)  就是用来沟通Host和Module。Host通常就是PC, Module则是以各种物理连接形式(USB,serial,pc-card等)连接到PC上的bluetooth Dongle。

在Host这一端:application,SDP,L2cap等协议都是软件形式提出的(Bluez中是以kernel层程序)。在Module这一端:Link Manager, BB, 等协议都是硬件中firmware提供的。

而HCI则比较特殊,它一部分在软件中实现,用来给上层协议和程序提供访问接口(Bluez中,hci.c hci_usb.c,hci_sock.c等).另一部分也是在Firmware中实现,用来将软件部分的指令等用底层协议明白的方式传递给底层。

居于PC的上层程序与协议和居于Modules的下层协议之间通过HCI沟通,有4种不同形式的传输:Commands, Event, ACL Data, SCO/eSCO Data。

1.1. HCI Command:

HCI Command是Host向Modules发送命令的一种方式。HCI Command Packet结构如下:

uid-31087949-id-5776244.html

OpCode用来唯一标识HCI Command.它由2部分组成,10bit的Opcode Command. 6bit的Opcode Group。

1.1.1: OpCode Group:

Linux Kernel(BlueZ)中,~/include/net/bluetooth/hci.h中定义了OpCode Group。

#define OGF_LINK_CTL 0x01

#define OGF_LINK_POLICY 0x02

#define OGF_HOST_CTL 0x03

#define OGF_INFO_PARAM 0x04

#define OGF_STATUS_PARAM 0x05

它们代表了不同的Command Group:

OGF_LINK_CTL: Link control,这个Command Group中的Command允许Host控制与其它bluetooth device 的连接。

OGF_LINK_POLICY :Link Policy。这个Command Group中的Command允许调整Link Manager control.

OGF_HOST_CTL: Control and Baseband.

1.1.2: Opcode Command:

用来在同一个Group内唯一识别Command。~/include/net/bluetooth/hci.h中定义。

1.2: HCI Event:

Modules向Host发送一些信息,使用HCI Event。Event Packet结构如下:

uid-31087949-id-5776244.html

HCI Event分3种:Command complete Event, Command States Event,Command Subsequently Completend.

Command complete Event: 如果Host发送的Command可以立刻有结果,则会发送此类Event。也就是说,如果发送的Command只与本地Modules有关,不与remote设备打交道,则使用Command complete Event。例如:HCI_Read_Buffer_Size.

Command States Event:如果Host发送的Command不能立刻得知结果,则发送此类Event。Host发送的Command执行要与Remote设备打交道,则必然无法立刻得知结果,所以会发送Command States Event.例如:

HCI Connect。

Command Subsequently Completend:Command延后完成Event。例如:连接已建立。

下图是一个Command-Event例子:

uid-31087949-id-5776244.html

从这里可以看出,如果Host发送的Command是与Remote device有关的,则会先发送Command States Event 。等动作真正完成了,再发送 Command Subsequently Completend。

HCI ACL与SCO数据,这里就不多讲了。只需要明白,l2cap数据是通过ACL数据传输给remote device的。

下图很明白的展示了l2cap数据如何一步一步转化为USB数据并传递给底层协议的。

uid-31087949-id-5776244.html

很明显,一个l2cap包会按照规则先切割为多个HCI数据包。HCI数据包再通过HCI-usb这一层传递给USB设备。每个包又通过USB driver发送到底层。

2. HCI protocol的实现:

(稍后添加)

3. HCI 层的编程:

正如上一节所说,HCI是沟通上层协议以及程序与底层硬件协议的通道。所以,通过HCI发送的Command都是上层协议或者应用程序发送给Bluetooth Dongle的。它命令Bluetooth Dongle(或其中的硬件协议)去做什么何种动作。

3.0:得到Host上插入Dongle数目以及Dongle信息:

我们先复习一下socket的概念:

使用函数socket()建立一个Socket,就如同你有一部电话.bind()则是把这个电话和某个电话号码(网络地址)对应起来。

类似的,我们可以把Host理解为一个房间,这个房间有多部电话(Dongle)。

当使用socket() 打开一个HCI protocol的socket,表明得到这个房间的句柄。HOST可能会有多个Dongle。换句话说,这个房间可以有多个电话号码。所以HCI会提供一套指令去得到这些Dongle。

// 0. 分配一个空间给 hci_dev_list_req。这里面将放所有Dongle信息。

struct hci_dev_list_req *dl;

struct hci_dev_req *dr;

struct hci_dev_info di;

int i;

if (!(dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t)))) {

perror("Can't allocate memory");

exit(1);

}

dl->dev_num = HCI_MAX_DEV;

dr = dl->dev_req;

//1. 打开一个HCI socket.此socket相当于一个房间。

if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) {

perror("Can't open HCI socket.");

exit(1);

}

// 2. 使用HCIGETDEVLIST,得到所有dongle的Device ID。存放在dl中。

if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) {

perror("Can't get device list");

exit(1);

}

// 3 使用HCIGETDEVINFO,得到对应Device ID的Dongle信息。

di.dev_id = (dr+i)->dev_id;

ioctl(ctl, HCIGETDEVINFO, (void *) &di);

这样就能得到所有Dongle信息。

struct hci_dev_info {

uint16_t dev_id;   //dongle Device ID

char     name[8];  //Dongle name

bdaddr_t bdaddr;   //Dongle bdaddr

uint32_t flags;    //Dongle Flags:如:UP,RUNING,Down等。

uint8_t  type;   //Dongle连接方式:如USB,PC Card,UART,RS232等。

uint8_t  features[8];

uint32_t pkt_type;

uint32_t link_policy;

uint32_t link_mode;

uint16_t acl_mtu;

uint16_t acl_pkts;

uint16_t sco_mtu;

uint16_t sco_pkts;

struct   hci_dev_stats stat;  //此Dongle的数据信息,如发送多少个ACL Packet,正确多少,错误多少,等等。

};

3.0.1: UP和Down Bluetooth Dongle:

ioctl(ctl, HCIDEVUP, hdev)

ioctl(ctl, HCIDEVDOWN, hdev)

ctl:为使用socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)打开的Socket.

hdev: Dongle Device ID.(所以上面的Socket不需要bind,因为这边指定了)

3.1 BlueZ提供的HCI编程接口一(针对本地Dongle的API系列):

3.1。1 打开一个HCI Socket---int hci_open_dev(int dev_id):

这个function用来打开一个HCI Socket。它首先打开一个HCI protocol的Socket(房间),并将此Socket与device ID=参数dev_id的Dongle绑定起来。只有bind后,它才将Socket句柄与Dongle对应起来。

注意,所有的HCI Command发送之前,都需要使用 hci_open_dev打开并绑定。

3.1.2: 关闭一个HCI Socket:

int hci_close_dev(int dd) //简单的关闭使用hci_open_dev打开的Socket。

3.1.3: 向HCI Socket(对应一个Dongle)发送 request:

int hci_send_req(int dd, struct hci_request *r, int to)

BlueZ提供这个function非常有用,它可以实现一切Host向Modules发送Command的功能。

参数1:HCI Socket。

参数2:Command内容。

参数3:以milliseconds为单位的timeout.

下面详细解释此function和用法:

当应用程序需要向Dongle(对应为一个bind后的Socket)发送Command时,调用此function.

其中,参数一dd对应一个使用hci_open_dev()打开的Socket(Dongle)。

参数三to则为等待Dongle执行并回复命令结果的timeout.以毫秒为单位。

参数二hci_request * r 最为重要,首先看它的结构:

struct hci_request {

uint16_t ogf;    //Opcode Group

uint16_t ocf;    //Opcode Command

int      event;  //此Command产生的Event类型。

void     *cparam; //Command 参数

int      clen;    //Command参数长度

void     *rparam;  //Response 参数

int      rlen;    //Response 参数长度

};

ogf,ocf不用多说,对应前面的图就明白这是Group Code和Command Code。这两项先确定下来,然后可以查HCI Spec。察看输入参数(cparam)以及输出参数(rparam)含义。至于他们的结构以及参数长度,则在~/include/net/bluetooth/hci.h中有定义。

至于event.如果设置,它会被setsockopt设置于Socket。

例1:得到某个连接的Policy Setting.

HCI Spec以及~/include/net/bluetooth/hci.h中均可看到,OGF=OGF_LINK_POLICY(0x02). OCF=OCF_READ_LINK_POLICY(0x0C).

因为这个Command用来读取某个ACL连接的Policy Setting。所以输入参数即为此连接Handle.

返回参数则包含3部分,status(Command是否顺利执行), handle(连接Handle)。 policy(得到的policy值)

这就又引入了一个新问题,如何得到某个ACL连接的Handle。

可以使用ioctl HCIGETCONNINFO得到ACL 连接Handle。

ioctl(dd, HCIGETCONNINFO, (unsigned long) cr);

Connect_handle = htobs(cr->conn_info->handle);

所以完整的过程如下:

struct hci_request HCI_Request;

read_link_policy_cp Command_Param;

read_link_policy_rp Response_Param;

// 1.得到ACL Connect Handle

if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0)

{

return -1;

}

Connect_handle = htobs(cr->conn_info->handle);

memset(&HCI_Request, 0, sizeof(HCI_Request));

memset(&Command_Param, 0 , sizeof(Command_Param));

memset(&Response_Param, 0 , sizeof(Response_Param));

// 2.填写Command输入参数

Command_Param.handle = Connect_handle;

HCI_Request.ogf = OGF_LINK_POLICY;  //Command组ID

HCI_Request.ocf = OCF_READ_LINK_POLICY; //Command ID

HCI_Request.cparam = &Command_Param;

HCI_Request.clen = READ_LINK_POLICY_CP_SIZE;

HCI_Request.rparam = &Response_Param;

HCI_Request.rlen = READ_LINK_POLICY_RP_SIZE;

if (hci_send_req(dd, &HCI_Request, to) < 0)

{

perror("\nhci_send_req()");

return -1;

}

//如果返回值状态不对

if (Response_Param.status) {

return -1;

}

//得到当前policy

*policy = Response_Param.policy;

3.1.4:几个更基础的function:

static inline void bacpy(bdaddr_t *dst, const bdaddr_t *src) //bdaddr copy

static inline int bacmp(const bdaddr_t *ba1, const bdaddr_t *ba2)//bdaddr 比较

3.1.5: 得到指定Dongle BDAddr:

int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to);

参数1:HCI Socket,使用hci_open_dev()打开的Socket(Dongle)。

参数2:输出参数,其中会放置bdaddr.

参数3:以milliseconds为单位的timeout.

3.1.6: 读写Dongle Name:

int hci_read_local_name(int dd, int len, char *name, int to)

int hci_write_local_name(int dd, const char *name, int to)

参数1:HCI Socket,使用hci_open_dev()打开的Socket(Dongle)。

参数2:读取或设置Name。

参数3:以milliseconds为单位的timeout.

注意:这里的Name与IOCTL HCIGETDEVINFO 得到hci_dev_info中的name不同。

3.1.7:得到HCI Version:

int hci_read_local_version(int dd, struct hci_version *ver, int to)

3.1.8:得到已经UP的Dongle BDaddr:

int hci_devba(int dev_id, bdaddr_t *bdaddr);

dev_id: Dongle Device ID.

bdaddr:输出参数,指定Dongle如果UP, 则放置其BDAddr。

3.1.9: 得到Dongle Info:

int hci_devinfo(int dev_id, struct hci_dev_info *di)

dev_id: Dongle Device ID.

di: 此Dongle信息。

出错返回 -1。

注意,这个Function的做法与3.0的方法完全一致。

3.1.10:从hciX中得到X:

int hci_devid(const char *str)

str: 类似 hci0这样的字串。

如果hciX对应的Device ID(X)是现实存在且UP。则返回此设备Device ID。

3.1.11:得到BDADDR不等于参数bdaddr的Dongle Device ID:

int hci_get_route(bdaddr_t *bdaddr)

查找Dongle,发现Dongle Bdaddr不等于参数bdaddr的第一个Dongle,则返回此Dongle Device ID。

所以,如果: int hci_get_route(NULL),则得到第一个可用的Dongle Device ID。

3.1.12: 将BDADDR转换为字符串:

int ba2str(const bdaddr_t *ba, char *str)

3.1.13: 将自串转换为BDADDR:

int str2ba(const char *str, bdaddr_t *ba)

3.2 BlueZ提供的HCI编程接口二(针对Remote Device的API系列):

3.2.1  inquiry 远程Bluetooth Device:

int hci_inquiry(int dev_id, int len, int nrsp, const uint8_t *lap, inquiry_info **ii, long flags)

hci_inquiry()用来命令指定的Dongle去搜索周围所有bluetooth device.并将搜索到的Bluetooth Device bdaddr 传递回来。

参数1:dev_id:指定Dongle Device ID。如果此值小于0,则会使用第一个可用的Dongle。

参数2:len: 此次inquiry的时间长度(每增加1,则增加1.25秒时间)

参数3:nrsp:此次搜索最大搜索数量,如果给0。则此值会取255。

参数4:lap:BDADDR中LAP部分,Inquiry时这块值缺省为0X9E8B33.通常使用NULL。则自动设置。

参数5:ii:存放搜索到Bluetooth Device的地方。给一个存放inquiry_info指针的地址,它会自动分配空间。并把那个空间头地址放到其中。

参数6:flags:搜索flags.使用IREQ_CACHE_FLUSH,则会真正重新inquiry。否则可能会传回上次的结果。

返回值是这次Inquiry到的Bluetooth Device 数目。

注意:如果*ii不是自己分配的,而是让hci_inquiry()自己分配的,则需要调用bt_free()来帮它释放空间。

3.2.2:得到指定BDAddr的reomte device Name:

int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char *name, int to)

参数1:使用hci_open_dev()打开的Socket。

参数2:对方BDAddr.

参数3:name 长度。

参数4:(out)放置name的位置。

参数5:等待时间。

3.2.3: 读取连接的信号强度:

int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to)

注意,所有对连接的操作,都会有一个参数,handle.这个参数是连接的Handle。前面讲过如何得到连接Handle的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/486701.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python xml

xml是实现不同语言或程序之间进行数据交换的协议&#xff0c;跟json差不多&#xff0c;但json使用起来更简单&#xff0c;不过&#xff0c;古时候&#xff0c;在json还没诞生的黑暗年代&#xff0c;大家只能选择用xml呀&#xff0c;至今很多传统公司如金融行业的很多系统的接口…

碰疼了会躲!这个植入“迷你大脑”的AI机器人,可感知疼痛,还能自我愈合...

来源&#xff1a;雷锋网人类为什么会产生痛觉&#xff1f;没错&#xff0c;是因为大脑中枢神经。和触觉、温度等感觉一样&#xff0c;我们对疼痛非常敏感&#xff0c;当受到外界“轻微”刺激时&#xff0c;体内的神经元产生相应的信号&#xff0c;并传输至大脑中的中枢神经系统…

哈佛大学惊人发现:新冠对人类未来影响巨大!

来源&#xff1a;北美观察家近日&#xff0c;哈佛大学公共卫生学院发了一篇Science&#xff1a;根据论文中的描述&#xff0c;无论是否能研发出疫苗&#xff0c;新冠病毒都可能会陪伴人类到2025年。那么&#xff0c;2025年以后人类能摆脱新冠病毒吗&#xff1f;不&#xff0c;只…

服务的分类与启动

一、服务的分类 查询已安装的服务 RPM包安装的服务 [rootbogon ~]# chkconfig --list# 查看服务自启动状态&#xff0c;可以看到所有RPM包安装的服务 # 此命令不可以查看源码包安装的服务 注&#xff1a;1、2、3、4、5、6是运行级别 源码包安装的服务 查看服务安装位置&#xf…

在Linux里awk与sed的区别,linux awk和sed讲解

sed 工具简介在了解了一些正规表示法的基础应用之后&#xff0c;再来呢&#xff1f;呵呵&#xff5e;两个东西可以玩一玩的&#xff0c;那就是 sed 跟 awk 了&#xff01;这两个家伙可是相当的有用的啊&#xff01;举例来说&#xff0c;鸟哥写的 logfile.sh 分析登录文件的小程…

python编辑器_推荐一款Python编辑器,集Pycharm和Sublime优点于一身的王者

编程里面的编辑器就像是武林大会里面的高手&#xff0c;每一年都有新秀&#xff0c;黑马出现&#xff01;比如有练习霸道的天罡之气的榜首Pycharm&#xff0c;力量雄厚霸道战斗力极强&#xff0c;但是对斗气消耗很大&#xff0c;占内存大而且启动速度有点慢&#xff01;还有练习…

因果解释能够对规则进行解释吗?

来源&#xff1a;《哲学动态》2017年第10期作者&#xff1a;初维峰&#xff08;西安交通大学人文社会科学学院&#xff09;本文受中国博士后科学基金面上资助项目“当代西方因果解释理论研究”(2017M613160)资助。在现实生活中&#xff0c;我们不仅要对某一事件进行解释&#x…

抓取一台电脑linux,教程方法;用来获取Linux主机信息的5个常用命令电脑技巧-琪琪词资源网...

琪琪词资源网-教程方法;用来获取Linux主机信息的5个常用命令电脑技巧&#xff0c;以下是给大家带来的教程方法;用来获取Linux主机信息的5个常用命令&#xff0c;大家可以了解一下哦!有些时候Linux 系统管理员在接手一台新的服务器时&#xff0c;如果没有好的交接文档&#xff0…

软件工程网络15个人阅读作业1(201521123029 郑佳明)

软件工程网络15个人阅读作业1 Task1&#xff1a;博客园地址 茗想 Task2&#xff1a;码云地址 ming Task3&#xff1a;完成博客-阅读与思考 阅读参考材料&#xff0c;并回答下面几个问题&#xff1a; &#xff08;1&#xff09;回想一下你初入大学时对网络工程专业的畅想 当初你…

okhttp post json 数据_使用python抓取App数据

App中的数据可以用网络爬虫抓取么答案是完全肯定的&#xff1a;凡是可以看到的APP数据都可以抓取。下面我就介绍下自己的学习经验和一些方法吧 本篇适合有过web爬虫基础的程序猿看没有的的话学的可能会吃力一些App接口爬取数据过程使用抓包工具手机使用代理&#xff0c;app所有…

JS的typeof力所能及已经力所不及

typeof返回值列表 typeof的作用 返回参数的类型 typeof能判断的类型 String类型: typeof a stringUndefined类型: typeof undefined undefinedBoolean类型: typeof true booleanNumber类型: typeof 22 numberSymbol类型: typeof Symbol() symbolFunction类型:typeof funct…

忆阻器类脑芯片与人工智能

来源&#xff1a;文章转载自期刊《微纳电子与智能制造》作者&#xff1a;陈子龙&#xff0c;程传同&#xff0c;董毅博&#xff0c;张 欢&#xff0c;张恒杰&#xff0c;毛旭瑞&#xff0c;黄北举&#xff0c;谢谢。摘 要现阶段计算与存储分离的“冯诺依曼”体系在功耗和速率方…

linux系统下替换图片,Linux(ubuntu系统)下使用FreeImage库

Linux(ubuntu系统)下使用FreeImage库Linux(ubuntu系统)下使用FreeImage库最近在搞一个图像处理的项目&#xff0c;需要用到FreeImage&#xff0c;之前在Windows下用过&#xff0c;很简单&#xff0c;因为FreeImage官网提供了可供使用的静态库动态库&#xff0c;直接包含就行了。…

t分布f分布与样本均值抽样分布_分布模拟1——MCMC抽样方法

分布是一系列数字的规律组合。如果在收集了历史中的几百个数据后&#xff0c;我想知道这群数据背后的发射机制是什么&#xff0c;那么就得去寻找这个分布。当然这里的重点不是寻找分布&#xff0c;而是在已知分布的情况下&#xff0c;如何模拟这个机制发射出来的一系列数字呢&a…

UWP Composition API - PullToRefresh

UWP Composition API - PullToRefresh 原文:UWP Composition API - PullToRefresh背景&#xff1a; 之前用ScrollViewer 来做过 PullToRefresh的控件&#xff0c;在项目一些特殊的条件下总有一些问题&#xff0c;比如ScrollViewer不会及时到达指定位置。于是便有了使用Composit…

linux 普通io实现pwm,用普通IO口做PWM输出 - 51单片机 - 电子工程世界-论坛 - 手机版...

本人现在想用IO口做PWM输出&#xff0c;频率1KHz&#xff0c;然后用两按键(、-)来调节占空比0-100%&#xff0c;对应数码管显示000-100。现波形是OK了&#xff0c;也可以调占空比&#xff0c;但是出现一个问题数码管显示乱七八糟&#xff0c;调了两天都没有调好&#xff0c;还请…

从城市治理到城市“智”理,AI 不仅是城市管理的“眼睛”

来源&#xff1a;帮尼资讯部分参考来源&#xff1a;中国安防行业网&#xff0c;图片来源网络近年来&#xff0c;随着计算机视觉技术的长足进步&#xff0c;AI在城市管理领域广泛部署。其中&#xff0c;AI视频分析识别技术成为城市场景中规模最大、数量最多、落地最广泛的应用。…

python3 应用 nose_parameterized 实现unittest 参数化

一、读取变量的值&#xff0c;实现unittest 参数化 import nose_parameterized,unittestdef calc(a:int,b:int):return ab case_data [[10,20,30],[12,21,33],[15,21,36] ] class MyClass(unittest.TestCase):nose_parameterized.parameterized.expand(case_data)def test_comp…

vue data数据修改_VUE的数据响应式

什么是数据响应式&#xff1f;const vm newVUE({data:{n:0}})上面的代码中&#xff0c;如果修改vm.n&#xff0c;那么UI中的n就会通过变化来响应我&#xff0c;这就是数据响应式。VUE对data做了什么&#xff1f;当给一个vue实例传入data的时候&#xff0c;vue内部会对传入的dat…

linux使用vim开启文档,linux 配置 直接用VIM默认打开文件

方法一&#xff1a;主要 是把gedit 默认打开的文件 都改成 vim了 此方法有缺陷。推荐使用 方法二 可以指定 哪种类型文件 用VIM 默认打开。两种方法都实现了 方便打开文件 摆脱了用VIM编码 打开文件还要 输入 烦长的路径,配置如下:方法一&#xff1a;把所有 默认 用 gedit 打开…