当前位置:首页 > 芯闻号 > 充电吧
[导读]作者:Sam (甄峰) sam_code@hotmail.com (HCI协议简介,HCI在BlueZ中的实现以及HCI编程接口)   1. HCI层协议概述: HCI提供一套统一的方法来访问Blue

作者:Sam (甄峰) sam_code@hotmail.com

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

 

1. HCI层协议概述:

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

 

Bluetooth编程(三) HCI层编程" title="实战Linux Bluetooth编程(三) HCI层编程" />

 

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

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

而HCI则比较特殊,它一部分在软件中实现,用来给上层协议和程序提供访问接口(Bluez中,hci.chci_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结构如下:

Bluetooth编程(三) HCI层编程" title="实战Linux Bluetooth编程(三) HCI层编程" />

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

 

1.1.1: OpCode Group:

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

#define OGF_LINK_CTL 0x01

#defineOGF_LINK_POLICY 0x02  

#define OGF_HOST_CTL 0x03

#defineOGF_INFO_PARAM 0x04

#defineOGF_STATUS_PARAM 0x05

它们代表了不同的Command Group:

 

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

OGF_LINK_POLICY :LinkPolicy。这个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结构如下:

Bluetooth编程(三) HCI层编程" title="实战Linux Bluetooth编程(三) HCI层编程" />

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

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

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

HCI Connect。

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

 

下图是一个Command-Event例子:

Bluetooth编程(三) HCI层编程" title="实战Linux Bluetooth编程(三) HCI层编程" />

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

 

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

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

Bluetooth编程(三) HCI层编程" title="实战Linux Bluetooth编程(三) HCI层编程" />

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

 

 

2. HCIprotocol的实现:

(稍后添加)

 

 

3. HCI 层的编程:

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

 

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

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

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

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

当使用socket() 打开一个HCIprotocol的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(structhci_dev_req) + sizeof(uint16_t)))) {
  perror("Can't allocatememory");
  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 HCIsocket.");
  exit(1);
 }

 

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

 if (ioctl(ctl, HCIGETDEVLIST, (void *) dl)< 0) {
  perror("Can't get devicelist");
  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_tdev_id;   //dongle DeviceID
 char    name[8];  //Dongle name

 bdaddr_tbdaddr;   //Dongle bdaddr

 uint32_tflags;   //Dongle Flags:如:UP,RUNING,Down等。
 uint8_t type;   //Dongle连接方式:如USB,PCCard,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的数据信息,如发送多少个ACLPacket,正确多少,错误多少,等等。
};

 

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.1BlueZ提供的HCI编程接口一(针对本地Dongle的API系列):

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

这个function用来打开一个HCI Socket。它首先打开一个HCIprotocol的Socket(房间),并将此Socket与deviceID=参数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, structhci_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_togf;    //OpcodeGroup
 uint16_tocf;    //OpcodeCommand
 int     event;  //此Command产生的Event类型。
 void    *cparam; //Command 参数
 int     clen;   //Command参数长度
 void    *rparam;  //Response参数
 int     rlen;   //Response 参数长度
};

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

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

 

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

HCISpec以及~/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; //CommandID
 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:HCISocket,使用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:HCISocket,使用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, intto)

 

 

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。则返回此设备DeviceID。 

 

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

int hci_get_route(bdaddr_t *bdaddr)

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

所以,如果: 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.2BlueZ提供的HCI编程接口二(针对Remote Device的API系列):

3.2.1  inquiry 远程BluetoothDevice:

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

hci_inquiry()用来命令指定的Dongle去搜索周围所有bluetoothdevice.并将搜索到的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:存放搜索到BluetoothDevice的地方。给一个存放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, intlen, 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, intto)

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


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭