蓝牙是无线数据和语音传输的开放式标准,它将各种通信设备、计算机及其终端设备、各种数字数据系统、甚至家用电器采用无线方式联接起来。它的传输距离为10cm~10m,如果增加功率或是加上某些外设便可达到100m的传输距离。它采用2.4GHzISM频段和调频、跳频技术,使用权向纠错编码、ARQ、TDD和基带协议。TDMA每时隙为0.625μs,基带符合速率为1Mb/s。蓝牙支持64kb/s实时语音传输和数据传输,语音编码为CVSD,发射功率分别为1mW、2.5mW和100mW,并使用全球统一的48比特的设备识别码。由于蓝牙采用无线接口来代替有线电缆连接,具有很强的移植性,并且适用于多种场合,加上该技术功耗低、对人体危害小,而且应用简单、容易实现,所以易于推广。
蓝牙技术的系统结构分为三大部分:底层硬件模块、中间协议层和高层应用。底层硬件部分包括无线跳频(RF)、基带(BB)和链路管理(LM)。无线GHz无需授权的ISM频段的微波,实现数据位流的过滤和传输,本层协议主要定义了蓝牙收发器在此频带正常工作所需要满足的条件。基带负责跳频以及蓝牙数据和信息帧的传输。链路管理负责连接、建立和拆除链路并进行安全控制。
Android的蓝牙协议栈采用BlueZ来实现,BlueZ分为两部分:内核代码和用户态程序及工具集。
内核代码主要由BlueZ核心协议和驱动程序组成;蓝牙协议实现在内核源代码net/bluetooth中,驱动程序位于内核源代码目录driver/bluetooth中。用户态程序及工具集主要包括应用程序接口和BlueZ工具集,位于Android源代码目录externel/bluetooth(注:Android版本不一样,有的在externel/bluez目录下)中。
链路管理协议(LMP)负责两个或多个设备链路的建立和拆除,及链路的安全和控制,如鉴权和加密、控制和协商基带包的大小等,它为上层软件模块提供了不同的访问入口。
主机控制器接口(HostControllerInterface,HCI)是蓝牙协议中软硬件之间的接口,提供了一个调用下层BB、LMP、状态和控制寄存器等硬件的统一命令,上下两个模块接口之间的消息和数据的传递必须通过HCI的解释才能进行。
L2CAP位于基带(BB)之上,向上层提供面向连接的和无连接的数据服务,它主要完成数据的拆装、服务质量控制、协议的复用、分组的分割和重组,及组提取等功能。
SDP是一个基于客户/服务器结构的协议,它工作在L2CAP层之上,为上层应用程序提供一种机制来发现可用的服务及其属性,服务的属性包括服务的类型及该服务所需的机制或协议信息。
RFCOMM是一个仿真有线链路的无线数据仿真协议,符合ETSI标准的TS07.10串口仿真协议,它在蓝牙基带上仿线的控制和数据信号,为原先使用串行连接的上层业务提供传送能力。
TCS定义了用于蓝牙设备之间建立语音和数据呼叫的控制信令(CallControl Signalling),并负责处理蓝牙设备组的移动管理过程。
PPP定义了串行点对点链路应当如何传输因特网协议数据,主要用于LAN接入、拨号网络及传真等应用规范。
TCP/IP、UDP定义了因特网与网络相关的通信及其他类型计算机设备和外围设备之间的通信。
OBEX支持设备间的数据交换,采用客户/服务器模式提供与HTTP(超文本传输协议)相同的基本功能。可用于交换的电子商务卡、个人日程表、消息和便条等格式。
WAP用于在数字蜂窝电话和其他小型无线设备上实现因特网业务,支持移动电话浏览网页、收取电子邮件和其他基于因特网的协议。
蓝牙系统的核心是BlueZ,因此JNI和上层都围绕跟BlueZ的沟通进行。JNI和Android应用层,跟BlueZ沟通的主要手段是D-BUS,这是一套被广泛采用的IPC通信机制,跟Android框架使用的Binder类似。BlueZ以D-BUS为基础,给其他部分提供主要接口。
蓝牙系统的HCI层是位于蓝牙系统的L2CAP(逻辑链路控制与适配协议)层和LMP(链路管理协议)层之间的一层协议。HCI为上层协议提供了进入LM的统一接口和进入基带的统一方式。在HCI的主机(Host)和HCI主机控制器(HostController)之间会存在若干传输层,这些传输层是透明的,只需完成传输数据的任务,不必清楚数据的具体格式。目前,蓝牙的SIG规定了四种与硬件连接的物理总线、UART和PC卡。其中通过RS232串口线方式进行连接具有差错校验。蓝牙系统的协议模型如图3所示。
HCI是通过包的方式来传送数据、命令和事件的。所有在主机和主机控制器之间的通信都以包的形式进行。包括每个命令的返回参数都通过特定的事件包来传输。HCI有数据、命令和事件三种包,其中数据包是双向的,命令包只能从主机发往主机控制器,而事件包始终是主机控制器发向主机的。主机发出的大多数命令包都会触发主机控制器产生相应的事件包作为响应。
命令包:命令包中的OCF(OpcodeCommand Field)和OGF(OpcodeGroup Field)是用于区分命令种的。ParameterLength表示所带参数的长度,以字节数为单位,随后就是所带的参数列表。下面以Inquiry命令为例对HCI的命令包做具体说明。
事件包:事件包的EventCode用来区分不同的事件包,ParameterLength表示所带参数的长度,以字节数为单位,随后就是所带的参数列表。以CommandStatus Event事件包为例对HCI的事件包进行具体说明。
当主机控制器收到主机发来的如上面所提到的Inquiry命令包并开始处理时,它就会向主机发送CommandStatus Event事件包,此事件包为:0x0f04 00 0a 01 04。0xOf表示此事件包为CommandStatusEvent事件包,0x04表示此事件包带4字节长度的参数,0x00为此事件包的第一个参数即Status,表示命令包正在处理。0x0a为事件包的第二个参数NUM_HCI_Command_Packets,表示主机最多可在向主机控制器发10个命令包。0x0104 为第三个参数Command_Opcode,表示此事件包是对Inquiry命令包的响应。
数据包:ACL和SCO数据包中的ConnectionHandle即连接句柄是一个12比特的标志符,用于唯一确认两台蓝牙设备间的数据或语音连接,可以看作是两台蓝牙设备间唯一的数据通道的标识。两台设备间只能有一条ACL连接,也就是只有一个ACL的连接句柄,相应L2CAP的信道都是建立在这个连接句柄表示的数据通道上;两台设备间可以有多个SCO的连接,则一对设备间会有多个SCO的连接句柄。连接句柄在两设备连接期间一直存在,不管设备处于什么状态。在ACL数据包中,Flags分为PBFlag和BCFlag,PBFlag为包的界限标志,PBFlag=0x00表示此数据包为上层协议包(如L2CAP包)的起始部分;PBFlag=0x01表示此数据包为上层协议包(如L2CAP包)的后续部分。BCFlag为广播发送的标志,BCFlag=0x00表示无广播发送,只是点对点的发送;BCFlag=0x01表示对所有处于激活状态的从设备进行广播发送,BCFlag=0x02表示对所有的从设备包括处于休眠状态的从设备进行广播发送。ACL和SCO数据包中的DataTotal Length 都表示所载荷的数据的长度,以字节位单位。
当主机与基带之间用命令的方式进行通信时,主机向主机控制器发送命令包。主机控制器完成一个命令,大多数情况下,它会向主机发出一个命令完成事件包(CommandComplete Packet),包中携带命令完成的信息。有些命令不会收到命令完成事件,而会收到命令状态事件包(CommandStatusPacket),当收到该事件则表示主机发出的命令已经被主机控制器接收并开始处理,过一段时间该命令被执行完毕时,主机控制器会向主机发出相应的事件包来通知主机。如果命令参数有误,则会在命令状态事件中给出相应错误码。假如错误出现在一个返回CommandComplete事件包的命令中,则此CommandComplete事件包不一定含有此命令所定义的所有参数。状态参数作为解释错误原因同时也是第一个返回的参数,总是要返回的。假如紧随状态参数之后是连接句柄或蓝牙的设备地址,则此参数也总是要返回,这样可判别出此CommandComplete事件包属于那个实例的一个命令。在这种情况下,事件包中连接句柄或蓝牙的设备地址应与命令包种的相应参数一致。假如错误出现在一个不返回CommandComplete事件包的命令中,则事件包包含的所有参数都不一定是有效的。主机必须根据于此命令相联系的事件包中的状态参数来决定它们的有效性。
而HCI则比较特殊,它一部分在软件中实现,用来给上层协议和程序提供访问接口(Bluez中,hci.chci_usb.c,hci_sock.c等). 另一部分也是在Firmware中实现,用来将软件部分的指令等用底层协议明白的方式传递给底层。
arg2:len此次inquiry的时间长度(每增加1,则增加1.25秒时间)
arg5:ii存放搜索到BluetoothDevice的地方。给一个存放inquiry_info指针的地址,它会自动分配空间。并把那个空间头地址放到其中。
注意:如果*ii不是自己分配的,而是让hci_inquiry()自己分配的,则需要调用bt_free()来帮它释放空间。
逻辑连接控制和适配协议(L2CAP)为上层协议提供面向连接和无连接的数据服务,并提供多协议功能和分割重组操作。L2CAP充许上层协议和应用软件传输和接收最大长度为64K的L2CAP数据包。
L2CAP基于通道(channel)的概念。通道(Channel)是位于基带(baseband)连接之上的逻辑连接。每个通道以多对一的方式绑定一个单一协议(singleprotocol)。多个通道可以绑定同一个协议,但一个通道不可以绑定多个协议。每个在通道里接收到的L2CAP数据包被传到相应的上层协议。多个通道可共享同一个基带连接。
服务发现协议(SDP或BluetoothSDP)在蓝牙协议栈中对蓝牙环境中的应用程序有特殊的含意,发现哪个服务是可用的和确定这些可用服务的特征。SDP定义了bluetoothclient发现可用bluetoothserver服务和它们的特征的方法。这个协议定义了客户如何能够寻找基于特定属性的服务而不需要客户知道可用服务的任何知识。SDP提供发现新服务的方法,在当客户登录到正在操作的蓝牙服务器的一个区域时是可用的时。Servicediscovery机制提供client应用程序侦测server应用程序提供的服务的能力,并且能够得到服务的特性。服务的品质包含服务type或服务class.
如果一个client或者依附于client之上的应用程序决定使用某个service. 它创建一个单独的连接到service的提供者。SDP只提供侦测Service的机制,但不提供如何利用这些Service的机制。Sam觉得,这里其实是说:SDP只提供侦测Service的办法,但如何用,SDP不管。
蓝牙无论最底层的硬件驱动如何实现,都会在HCI层进行统一。也就是说,HCI在主机端的驱动主要是为上层提供统一接口,让上层协议不依赖于具体的硬件实现。HCI在硬件中的固件与HCI在主机端的驱动通信方式有多种,比如UART,USB和SDIO等。
hci_dev结构体,因此,无论实际的设备是哪种蓝牙设备、通过什么方式连接到主机,都需要向HCI层和蓝牙核心层注册一个hci_dev设备,注册过程由hci_registe_dev()函数来完成,同时也可以通过hci_unregister_dev()函数卸载一个蓝牙设备。具体的蓝牙驱动有很多,常用的在linux内核都自带有驱动。比如:hci_vhci.c为蓝牙虚拟主控制器驱动程序,hci_uart.c(或者hci_ldisc.c)为串口接口主控制器驱动程序,btusb.c为USB接口主控制器驱动程序,btsdio.c为SDIO主控制器驱动程序。
(1) 串口驱动必须要先就绪(uart蓝牙而言),这是cpu和蓝牙模块之间的桥梁。
但是还没有和上面的协议层建立纽带关系,也就是说从uart收到的数据还没有传给hci层;
如何把uart也就是蓝牙模块传上来的数据交给hci层,在驱动里面是通过一个叫做disc的机制完成的,这个机制本意是用来做过滤或者限制收上来的字符的,但是在蓝牙驱动里面则直接把数据传给了蓝牙协议层,再也不回到串口的控制了;
注:各个平台的board_xxx.c文件名字不同,请客户确认(目前已经被设备树替代)
注掉:高通下载firmware的命令。最后,重新编译system。此时BT应该能运行了。
如果你编译你自己的system.img,除了hcitool扫描不行,hciconfig -a是可以工作的,尝试安装固件到蓝牙芯片。
描述一个远程Bluetooth设备。可以用它通过一个BluetoothSocket请求一个远程设备的连接,或者查询远程设备的名称、地址、类、连接状态等信息。
BluetoothServerSocket是一个开放的socket服务器,用来侦听连接进来的请求(类似于RCPServerSocket)。为了连接两个Android设备,一个设备必须使用该类来开启一个socket做服务器,当另外一个设备对它发起连接请求时并且请求被接受时,BluetoothServerSocket会返回一个连接的BluetoothSocket对象。
BluetoothClass是用来定义设备类和它的服务的只读属性集。然而,它并不是可靠的描述设备支持的所有Bluetooth配置和服务,而只是一些设备类型的有用特征。
BluetoothProfile是两个设备基于蓝牙通讯的无线接口描述。Profile定义了设备如何实现一种连接或者应用,你可以把Profile理解为连接层或者应用层协议。比如,如果一家公司希望它们的Bluetooth芯片支持所有的Bluetooth耳机,那么它只要支持HeadSetProfile即可,而无须考虑该芯片与其它Bluetooth设备的通讯与兼容性问题。如果你想购买Bluetooth产品,你应该了解你的应用需要哪些Profile来完成,并且确保你购买的Bluetooth产品支持这些Profile。
一个接口描述,在与服务连接或者断连接的时候通知BluetoothProfileIPC(这是内部服务运行的一个特定的模式profile)。
BLUETOOTH_ADMIN:用来授权初始化设备搜索或操作Bluetooth设置。大多数应用需要它的唯一场合是用来搜索本地Bluetooth设备。本授权的其他功能不应该被使用,除非是需要修改Bluetooth设置的“powermanager(电源管理)”应用。
Devicediscovery(设备搜索)是一个扫描搜索本地已使能Bluetooth设备并且从搜索到的设备请求一些信息的过程(有时候会收到类似“discovering”,“inquiring”或“scanning”)。但是,搜索到的本地Bluetooth设备只有在打开被发现功能后才会响应一个discovery请求,响应的信息包括设备名,类,唯一的MAC地址。发起搜寻的设备可以使用这些信息来初始化跟被发现的设备的连接。
一旦与远程设备的第一次连接被建立,一个pairing请求就会自动提交给用户。如果设备已配对,配对设备的基本信息(名称,类,MAC地址)就被保存下来了,能够使用BluetoothAPI来读取这些信息。使用已知的远程设备的MAC地址,连接可以在任何时候初始化而不必先完成搜索(当然这是假设远程设备是在可连接的空间范围内)。
配对意思是两个设备相互意识到对方的存在,共享一个用来鉴别身份的链路键(link-key),能够与对方建立一个加密的连接。连接意思是两个设备现在共享一个RFCOMM信道,能够相互传输数据。目前AndroidBluetooth APIs要求设备在建立RFCOMM信道前必须配对(配对是在使用BluetoothAPI初始化一个加密连接时自动完成的)。
注意:Android的电源设备默认是不能被发现的。用户可以通过系统设置让它在有限的时间内可以被发现,或者可以在应用程序中要求用户使能被发现功能。
在搜索设备前,查询配对设备看需要的设备是否已经是已经存在是很值得的,可以调用getBondedDevices()来做到,该函数会返回一个描述配对设备BluetoothDevice的结果集。例如,可以使用ArrayAdapter查询所有配对设备然后显示所有设备名给用户:
要开始搜索设备,只需简单的调用startDiscovery()。该函数时异步的,调用后立即返回,返回值表示搜索是否成功开始。搜索处理通常包括一个12秒钟的查询扫描,然后跟随一个页面显示搜索到设备Bluetooth名称。
如果想让本地设备被其他设备发现,可以带ACTION_REQUEST_DISCOVERABLEaction Intent调用startActivityForResult(Intent,int)方法。该方法会提交一个请求通过系统刚设置使设备出于可以被发现的模式(而不影响应用程序)。默认情况下,设备在120秒后变为可以被发现的。可以通过额外增加EXTRA_DISCOVERABLE_DURATIONIntent自定义一个值,最大值是3600秒,0表示设备总是可以被发现的(小于0或者大于3600则会被自动设置为120秒)。下面示例设置时间为300:
如果只需要连接远程设备就不需要打开设备的可以被发现功能。只在应用作为一个服务器socket的宿主用来接收进来的连接时才需要使能可以被发现功能,因为远程设备在初始化连接前必须先发现了你的设备。
为了建立两个设备之间的应用的连接,需要完成服务器端和客户端,因为一个设备必须打开一个服务器socket而另外一个设备必须初始化连接(用服务器端的MAC地址)。服务器和客户端在各自获得一个基于同一个RFCOMM信道的已连接的BluetoothSocket对象后就被认为连接已经建立。这个时候,双方设备可以获取输入输出流,数据传输可以开始了。本节描述如何在两个设备之间初始化连接。
服务器设备和客户端设备用不同的方式获取各自需要的BluetoothSocket对象。服务器端的在接收一个进来的连接时获取到,客户端的在打开一个与服务器端的RFCOMM信道的时候获取到。
一个实现技巧是自动把每个设备作为服务器,这样就拥有了一个打开的socket用来侦听连接。然后任一设备就能够发起与另一个设备的连接,并成为客户端。另外,一个设备也可以明确的成为“host”,并打开一个服务端socket,另一个设备可以简单的发起连接。
注意:如果两个设备之前没有配对,那么在连接处理过程中Android应用框架会自动显示一个配对请求的通知或对话框给用户。因此,当尝试连接设备时,应用不需要关心设备是否已经配对。RFCOMM连接会阻塞直到用户成功将设备配对(如果用户拒绝配对或者配对超时了连接会失败)。
如果要连接两个设备,其中一个必须充当服务器,通过持有一个打开的BluetoothServerSocket对象。服务器socket的作用是侦听进来的连接,如果一个连接被接受,提供一个连接好的BluetoothSocket对象。从BluetoothServerSocket获取到BluetoothSocket对象之后,BluetoothServerSocket就可以(也应该)丢弃了,除非你还要用它来接收更多的连接。
该字符串为服务的识别名称,系统将自动写入到一个新的服务发现协议(SDP)数据库接入口到设备上的(名字是任意的,可以简单地是应用程序的名称)项。UUID也包括在SDP接入口中,将是客户端设备连接协议的基础。也就是说,当客户端试图连接本设备,它将携带一个UUID用来唯一标识它要连接的服务,UUID必须匹配,连接才会被接受。
这是一个阻塞的调用,知道有连接进来或者产生异常才会返回。只有远程设备发送一个连接请求,并且携带的UUID与侦听它socket注册的UUID匹配,连接请求才会被接受。如果成功,accept()将返回一个连接好的BluetoothSocket对象。
通常应该在处理完侦听到的连接后立即关闭BluetoothServerSocket。在本例中,close()在得到BluetoothSocket后马上被调用。还需要在线程中提供一个公共的方法来关闭私有的BluetoothSocket,停止服务端socket的侦听。
当调用这个方法的时候,系统会在远程设备上完成一个SDP查找来匹配UUID。如果查找成功并且远程设备接受连接,就共享RFCOMM信道,connect()会返回。这也是一个阻塞的调用,不管连接失败还是超时(12秒)都会抛出异常。
注意:要确保在调用connect()时没有同时做设备查找,如果在查找设备,该连接尝试会显著的变慢,慢得类似失败了。
在对BluetoothSocket的处理完成后,记得调用close()来关闭连接的socket和清理所有的内部资源。
当然,还是有很多细节需要考虑的。首要的,需要用一个专门的线程来实现流的读写。只是很重要的,因为read(byte[])和write(byte[])都是阻塞的调用。read(byte[])会阻塞直到流中有数据可读。write(byte[])通常不会阻塞,但是如果远程设备调用read(byte[])不够快导致中间缓冲区满,它也可能阻塞。所以线程中的主循环应该用于读取InputStream。线程中也应该有单独的方法用来完成写OutputStream。
线程的cancel()方法时很重要的,以便连接可以在任何时候通过关闭BluetoothSocket来终止。它应该总在处理完Bluetooth连接后被调用。
⑤ 一旦拥有了profile协议对象,就可以用它来监控连接的状态,完成于该profile相关的其他操作。