1. HCI层协议概述: 编程
HCI提供一套统一的方法来访问Bluetooth底层。如图所示: 网络
从图上能够看出,Host Controller Interface(HCI) 就是用来沟通Host和Module。Host一般就是PC, Module则是以各类物理链接形式(USB,serial,pc-card等)链接到PC上的bluetooth Dongle。 app
在Host这一端:application,SDP,L2cap等协议都是软件形式提出的(Bluez中是以kernel层程序)。在Module这一端:Link Manager, BB, 等协议都是硬件中firmware提供的。 socket
而HCI则比较特殊,它一部分在软件中实现,用来给上层协议和程序提供访问接口(Bluez中,hci.c hci_usb.c,hci_sock.c等).另外一部分也是在Firmware中实现,用来将软件部分的指令等用底层协议明白的方式传递给底层。 函数
居于PC的上层程序与协议和居于Modules的下层协议之间经过HCI沟通,有4种不一样形式的传输:Commands, Event, ACL Data, SCO/eSCO Data。 ui
1.1. HCI Command: spa
HCI Command是Host向Modules发送命令的一种方式。HCI Command Packet结构以下: 指针
OpCode用来惟一标识HCI Command.它由2部分组成,10bit的Opcode Command. 6bit的Opcode Group。 code
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结构以下:
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例子:
从这里能够看出,若是Host发送的Command是与Remote device有关的,则会先发送Command States Event 。等动做真正完成了,再发送 Command Subsequently Completend。
HCI ACL与SCO数据,这里就很少讲了。只须要明白,l2cap数据是经过ACL数据传输给remote device的。
下图很明白的展现了l2cap数据如何一步一步转化为USB数据并传递给底层协议的。
很明显,一个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的。