由于生产蓝牙设备的厂家众多,设备外部接口的标准化问题直接关系着设备弄否方便的在不同的主机上进行应用。针对这一问题SIG在蓝牙规范中定义了HCI层。HCI层保证了蓝牙设备与蓝牙主机接口的标准化。

1、 HCI协议概述
HCI层位于蓝牙协议的中间层与底层,起着沟通两者的作用(图3.3中描述了HCI层在底层软件中的位置)。同时,HCI层也是蓝牙软件与蓝牙硬件之间的交互接口。蓝牙软件将通过HCI层提供的一系列接口完成与蓝牙硬件的通信。具体到本文,就是蓝牙软件将通过HCI层的接口函数实现对蓝牙适配器设置和控制。
HCI也可以读取和设置远程蓝牙设备的相应参数。
 

 

2、HCI程序设计
在HCI层的程序设计中,主要完成的了三项基本功能。第一,完成对本地蓝牙设备参数的读取和设置;第二,负责对周围设备进行扫描,并可以对远程蓝牙设备的基本参数进行读取和设置;第三,提供一个HCI命令的发送接口,可以直接利用HCI命令控制本地蓝牙适配器。
BlueZ的HCI层是基于上文描述的C/S模式进行的。一般来讲,发起会话的蓝牙设备将充当C/S模式的客户端,而被连接的远程设备扮演服务器端角色。在Linux中,蓝牙应用启动以后,其守护进程(守护进程,运行于后台的,用于为系统提供某项服务的程序)bluetoothd起到了C/S模型中服务器的作用,可以对来自远程的查询和链接请求进行响应。
(1) 本地设备参数读取模块实现
本地设备参数的读取将通过get_dev_infor()函数调用完成。这个接口将给出本地设备的bd_addr、dev_name和dev_id等所有包含于hci_dev_info结构中的参数。这个结构体用于描述设备的基本参数。另外,本地参数读取模块还提供了单独针对某一参数的接口。如,获得本地设备名称的get_localnameO,获得本地bd_addr的get_local_addr0等。其实现流程与远程设备参数读取基本类似,只是不需要建立socket连接,不需要对远程设备进行查询,其数据处理方法和实现方法均一致。在此,仅以远程设备的读取流程进行叙述。
(2) 远程设备参数读取模块实现
远程设备参数读取模块的功能与上一模块基本一致。所不同的是,在读取时需要先完成对周围设备查询,得到远程设备的bd_addr,并填充inquiry_info结构。而后,本地设备将与其建立连接,最终实现参数的传递。另外,这里通过调用hciread_remotename0、hciread_remoteversion0和hci_read_remote_featuresO等函数来实现对远程设备其他参数的读取。
 

 

 

3 蓝牙L2CAP协议编程概述
蓝牙协议栈中,位于HCI层之上的是L2CAP层,即逻辑链路控制和适配层(Logical Link Control and Adaptation Protoc01)。本节将对L2CAP层编程进行介绍。
L2CAP在蓝牙协议中的位置如图3.9所示。它处于底层与高层协议之间。L2CAP通过协议多路复用、分段和重组操作以及组的概念,向高层协议提供面向链接和面向无链接的数据服务。L2CAP允许高层协议和应用传输长达64Kb的L2CAP分组。
 

 

 

L2CAP在协议中的位置
(1)、L2CAP PDU格式
下图描述了L2CAP层提供的面向连接和面向无连接的两种PDU(Protocol Data Units,协议数据单元)。
 

面向连接:
长度:2字节,表示数据载荷的长度(不包括L2CAP PDU包头);
CID:Channel ID,2字节,指示数据包的目的端信道
数据载荷:0到65535字节。
面向无链接:
长度:2字节,表示数据载荷的长度(不包括PDU包头,但包括PSM);
CID:Channel ID,0x0002,2字节,指示数据包的目的端信道;
PSM:为高层接收无链接的L2CAP PDU数据提供标识;
数据载荷:O到65535字节。
(2)、分段和重组
分段是指将L2CAP PDU分割成众多较小的分组并传给下层协议。而重组则是将底层协议传来的数据分组重新组合成L2CAP PDU的过程。分段和重组(SAR)操作用于支持最大传输单位(MTU),以提高传输效率。这样可以有效的降低拥塞。
由于L2CAP层不存在有关BB PDU的信息,更不了解它的传输分组长度,所以L2CAP本身并不完成低层PDU的分段和重组,一般SAR在HCI层完成。L2CAP通过在PDU中提供L2CAP PDU的长度信息,使重组机制得以检查出是否已正确
重组了PDU(HCI层根据数据HCI—PDU中的Flags字段进行L2C川P-PDU的重组,该字段指示L2CAP—PDU的分片是开始还是继续),从而使分段和重组更方便。
分组过程在下图中已经给出,而重组的过程就是分组的逆过程。
 

分段和重组过程实例
(3)、L2CAP程序设计
L2CAP作为适配层,在蓝牙主机一段承担了链路管理的任务。凼此,L2CAP程序璺完成蓝牙链路的建立及断肝等链路管理工作。同时,L2CAP是RFCOMM和SDP的传输层协议,他还应该为这两层协议提供数据传输的接VI。BlueZ为L2cAP层封裟了标准socket接口。因此,L2cAP层同样将采用C/S模式。与HCI层的C/S模型一样,L2CAP层同样足由本地蓝牙设备充当客户端.由远程蓝牙设备完成服务器的功能。L2CAP层的所有接口都将是在实现了客户端与服务器连接的前提下进行的,下面将先介绍连接的建立。
(1)L2CAP客户端编程
L2CAP的客户端程序的整体流程就是图3.2中所描述的客户端的部分,只是其中所调用的函数要按照L2CAP层的需要进行设置。首先,将介绍socket中一个表示socket地址的结构:
 

 

 

调用socket(PF_BLUETOOTH,SOCK_RAW,BTPROTO,BTPROTO-_L2CAP),创建基于L2CAP协议的socket。而后将本地蓝牙地址作为参数,调用bind()将其与本地的蓝牙适配器进行绑定。接下来设置远程蓝牙设备(服务器)的sockaddr_12结构,并调用通过cormect0函数向远程蓝牙设备提出连接请求。连接建立之后,对数据的发送和接收缓冲区进行初始化,之后可以利用read()和write0函数实现与服务器的数据接收和发送。
(2)L2CAP服务器
服务器端,调用的socket()的参数与客户端形同。当程序进入listen0之后,当接收到数据后,主程序将创建一个receiver thread()线程完成数据的读入和处理。而主程序将继续进行侦听,等待接收新的数据。
(4)、GAP剖面程序设计
GAP剖面为蓝牙的普通接入剖面,完成后续所有蓝牙服务的设备基本信息设置和链路创建功能。GAP剖面是基于HIC协议和L2CAP协议共同完成的。其中的很多功能都是通过HCI层的接口完成的。GAP剖面主要为下面几个方面提供UI:
(1)蓝牙设备地址bd_addr;
(2)蓝牙设备名称dev_name;
(3)蓝牙密码PIN;
(4)蓝牙设备类型dev_class。
同时,还应提供蓝牙连接的建立、拆除,蓝牙设备的绑定等功能。GAP的功能是调用HCI和L2CAP层的接口共同完成的。其功能模块如下图。

 

 

设备信息:利用HCI层中的接口完成对本地和远程设备名称和设备类型的读取和设置。而设备类型是一个有三个字节长的数组组成。其中共包含着三项内容:服务类型、设备大类和设备子类。设备大类用于区别设备是属于电话、计算机、打印机或者输入设备等。而设备子类用于区别同一类设备中的不同产品,如计算机中的PC机,笔记本电脑等。而服务类型表示着设备可提供的服务种类。这个24bits的信息并不是按照3个字节划分的。从高到底分别是11 bits的服务类型码(每一位表示一种服务,若该位被置位,则表示测设备可以提供这种服务),5bits设备大类,6bits设备小类和两个为0的格式类型。程序中存储了设备类型的类表,可根据具体数值进行查询。
设备配对:设备配对(Pairing)是借助PIN在两个蓝牙设备之间建立一种安全关系的应用。在GAP应用中,利用HCI层的PIN_Code_Request_Reply命令进行PIN码传递的。GAP应用程序响应PIN Code Request,提醒用户进行PIN码输入,而后由用户输入PIN码。输入的PIN码必须和另一蓝牙主机端的一致,否则无法建立配对关系。
设备连接:这个模块完成的是蓝牙连接的建立和撤销。建立蓝牙连接的过程与L2CAP客户端和服务器建立连接的方法基本相同。只是在程序中要通过调用setsockopt0和L2CAP_LM参数对蓝牙进行链路配置。建立连接后调用getsoekopt0对12cap_options和12cap_conninfo结构进行填充。这两个结构中存储着L2CAP链路的参数。
 

btping命令:实际上应该是12ping,是利用L2CAP的回应请求命令L2CAP_ECHO_RSP和回应应答命令L2CAP_ECHO_REQ实现的用于链路测试的命令。他与操作系统中用于网络测试的命令ping功能一样。Ping命令以远程主机的IP地址作为参数,而btping以其bd addr作为参数。其他功能基本一致。
模式设置:模式设置主要针对的是蓝牙设备的发现模式,连接模式和配对模式等三项模式进行的。其中,连接模式和配对模式都仅有“可以"和“不可以"两种模式。而发现模式则提供了不可发现、有限可发现和普通可发现三种。这些设置也是通过调用HCI层的函数完成。
4 蓝牙RFCOMM协议编程概述
RFCOMM协议是基于L2CAP协议的串口仿真协议,旨在提供一个类似9针RS232的仿真串口。本文涉及的LAP和DUN应用都建立在这一层的基础之上。BlueZ同样也定义了这一层的标准socket接口。下面将先对RFCOMM协议进行简要介绍。
(1)、RFCOMM协议概述
RFCOMM协议是基于ETSI标准的TS07.10的串口仿真协议。它不仅可以提供两个蓝牙设备间的多串口仿真,同时也支持多个蓝牙设备之间的串口仿真。下图给出了RFCOMM的参考模型。
 

上图中各要素的功能描述如下:
应用:采用串口通信的程序;
端口仿真实体:端口仿真实体将特定系统通信接口映射到RFCOMM服务。端口仿真实体和RFCOMM构成一个端口驱动器;
服务注册/发现:在本地注册服务信息。并为远程查询者提供服务查询结果。
L2CAP:协议复用和SAR;
基带:蓝牙规范的基带协议。
(2)、RFCOMM程序设计
RFCOMM层程序主要是为上层应用提供数据接口,而没有进行相应的实例化。本文基于对RFCOMM协议的研究,实现了RFCOMM层的基本通信功能。可利用这一层次的数据发送和接收接口实现数据的无线传输。使用SDP的功能完成信道查询等任务。RFCOMM建立在L2CAP信道之上,PSM为0x0003。
RFCOMM层编程同样采用C/S模式。与L2CAP层的C/S模式一样,由本地发起连接的蓝牙主机充当客户端,由远程响应蓝牙连接请求的设备充当服务器。由于程序实现的基本流程与前文所述的C/S模式的流程基本一致,所以此处只叙述不同的地方。这里需要注意的一般有两方面,一个是结构体sockaddr_rc的配置,一个是端口的绑定。
 

前两项的意义与前文一致。由于在一个RFCOMM会话上可以建立60路连接,每路连接都有自己的信道号。因此,在建立连接时,准确填写这个参数是连接实现的前提。在程序中,设计了get_channeloi函数用于获得目标设备所使用的信道号。这里取得的连接就是后来要绑定的目标。也就是说,所有的RFCOMM Socket都应该与RFCOMM的一个信道绑定。
5 蓝牙SDP协议编程概述
SDP,即服务发现协议(Service Discovery Protocol)。服务发现是大多数通信系统都具有的功能。它的存在能保证基于同一通信协议的陌生设备间互相了解对方的功能,并列出两者之间可以采用的通信方式。蓝牙协议栈中的SDP是与RFCOMM协议处于同一层面的协议,为用户提供蓝牙应用的服务查询。
(1)、SDP  C /S结构
与RFCOMM协议一样,SDP协议也是以L2CAP为传输层协议的,并采用C/S模式实现应用(如图3.16)。
 

一般来说,蓝牙设备应该可以充当SDP C/S结构中的任意一端。一般来说,SDP服务由运行在本地设备上的SDP客户端发起。它通过L2CAP协议实现连接和数据传递,从而与运行在远程设备上的SDP服务器完成通信。SDP服务器在远程设备上维护着一个服务记录数据库,记录着该设备可以提供的蓝牙服务的服务属性。由于服务记录数据库和SDP服务器都运行于同一蓝牙设备中,因此蓝牙协议下的服务发现不需要借助第三方设备便可以完成,这给蓝牙应用的简化提供了便利的条件。
(2)、服务记录和服务属性
SDP服务器维护的SDP数据库就是由很多个“服务记录"组成的。服务器将通过服务记录句柄访问这些服务记录。服务记录句柄列表中包含着多个长度为32bits的服务记录句柄。服务记录句柄与服务记录呈一一对应关系。而且,服务句柄将依据SDP服务器的不同而不同。服务属性是用于描述某一服务特征的。服务属性由属性ID和属性值组成。属性ID是一个16bits的无符号整数,用于定义服务类型。而属性值则是一个与属性ID关联的长度不确定的数据段。
(3)、数据表示
SDP协议将属性值用一种很巧妙的方式进行表示,满足了它不定长的特点。一个数据元有数据段和报文头组成。报文头由类型描述符,元素尺寸索引和元素尺寸组成。其具体对应规则请参见参考文献l、2、4、5和16等。在此仅给出一个简单的例子。参见图3.20。
 

(4)、服务搜索
当SDP客户端与远程设备上的SDP服务器完成了前期的L2CAP连接后,SDP客户端便可以在远程SDP服务器上完成相应的查询。查询过程如下:
 

Stepl.服务搜索请求。
客户端向服务器发送SDP ServiceSearchRequest,其包含的第一个参数是ServiceSearchPattem,他是一个数据元序列。而其中的每一个数据元都是一个UUID(Universally Unique Identifiers,通用唯一标识符)。服务器会将这个表中的UUID值与其维护的服务记录表匹配。如果某一UUID匹配成功说明这个UUID对应的服务是服务器具备的,否则将是不可用的应用。UUID在所有可以使用的区域内永远是唯一的。UUID是一个长128bRs的定位符,用于表示一种服务。SDP就是通过UUID实现对不同服务的区分和匹配
Step 2.服务搜索应答。
当远程设备上的服务器接到客户端发来的SDP 后,将以.ServiceSearchRequestSDP ServiceSearchResponse数据包进行恢复。这个数据包中,包含着四个参数:TotalServiceRecordCount、CurrentServiceRecordCount、ServiceRecordHandleList和ContinuationState,分别表示服务器端设备所能提供的服务总数,当前可用的服务记录数,服务记录句柄列表和链接状态。
Step 3.服务属性查询请求。
在完成Step 2的操作之后,客户端得到了服务记录旬柄列表。而后将根据该列表里面的旬柄值进行服务属性查询。客户端会向远程设备上的服务器发送SDP ServiceAttributeRequest,这其中就包含了服务记录句柄。同时客户端还会告知服务器它所需应答的最大长度、其所需服务属性的属性ID和连接状态等参数。
Step 4.服务属性查询应答。
服务器接到SDP ServiceAttributeRequest之后,将根据客户端要求的服务记录句柄值和所需要服务属性,生成应答数据包SDP_ServiceAttributeResponse。并将其发送到客户端。
至此,一个服务查询过程结束。
(5)、SDP程序设计
SDP程序设计是基于BlueZ的sap .和.进行的。其中提供了层_libhsdph SDP的接口函数。本文设计的SDP程序完成的是SDP客户端的作用,主要实现的是对远程设备的所提供的服务及其服务属性的查询。另外,这里也将给出一些接口,供RFCOMM层程序调用。比如,在RFCOMM客户端在建立RFCOMM会话时需要的信道号,就需要通过SDP程序进行读取。而这里的SDP剖面实际上就是SDP
程序实现的功能。
本地设备在查询某种服务时,一般分两步完成。第一步,设备查询。调用HCI层函数对查询周边设备,获得设备基本信息。第二步,服务检索。利用之前搜寻的结果连接相关设备完成服务查询。第一步设备查询的方法在前文中已经进行了叙述,这里不再重复。本节将重点介绍第二个步骤。服务检索仍然可分为两步进行。第一步,服务查询;第二步,查询应答处理。在进行服务查询时,SDP客户端程序,应生成相应服务的UUID,并以此UUD进行搜索。其查询流程如下:
 

查询服务流程
图中连接目标设备时调用的是sdp_connect()函数,这个函数建立了L2CAP连接。因此,在SDP层的编程中,要利用L2CAP的数据读取函数完成数据的发送和接收。生成UUID的时候调用的是sdp_uuid_creat()函数,并将系统定义的UUID宏作为参数,最后生成的是用于发送的UUID数据格式。之后的链表中将存取所查询的服务和相应的服务属性。在发送查询请求sdp_servicesearch_attr_req()时,SDP服务器将根据查询服务列表中的UUID进行相关设备的查询。查询结束后会在response_list中存储查询结果。这个结果将作为服务查询阶段的结果传递给下一步骤的程序,作为输入数据使用。
程序的第二步完成对查询数据的处理和显示工作。在介绍数据处理方法之前,需要先对sdp_list_t、sdp_record_t和sdp_data_t等三个重要的结构加以介绍。这三个结构体描述了整个SDP查询结果的数据结构。
 

sdp_list_t是BlueZ中定义的一个表示链表的结构体,他的数据元素被声明为void类型的指针,这样使它可以支持多种数据格式。sdp_record_t则是一个SDP记录的数据单元。而sdp_data_t则是SDP数据的最终数据格式。这里用联合的概念定义了通用变量。下面,将通过一个SPD数据结构图说明数据处理的方法。
 

上图中response_list链表中的数据均为SDP记录。每一个SDP记录经过sdp_gec_access_protos()函数处理后,都会返回一个protocol sequence链表。这个链表中记录着该设备可提供的连接方式。同时,这个链表的每一个节点都连接这一个protocol链表,这个链表记录着该节点的连接方式所支持的上层协议。而protocol链表中的节点又都指向一个sdp_data_t类型的attribute链表,这里记录的是诸如协议类型,当前应用的端口号的属性。因此,进行的SDP服务搜索,就是将指定的UUID值与上述数据匹配,若匹配成功证明设备具有这种功能,否则不具备。本文用了四层嵌套循环实现了这一数据结构的遍历。另外,根据查询的级别的不同,遍历的层次也不禁相同。当然,在查询时也可设定一定的查询范围,对不关心的服务及属性不进行的读取,这样可以节省程序运行的时间,提高效率。