精华内容
下载资源
问答
  • 蓝牙 4.0 ATT属性协议

    千次阅读 2019-04-01 22:33:15
    属性协议数据包,是用来对属性数据库操作的实体。这一节几乎全部用图来描述。具体怎么发送数据库中的服务,下节中会讲到ATT协议怎么对应GATT服务发现。 3.4.3.1 、交换MTU    图3-27 为交换MTU包格式。 图3-...

    转自:https://mp.weixin.qq.com/s?__biz=MjM5MzM4MDM3Mw%3D%3D&chksm=b28fa48285f82d94f2b1ac8a7486a9c7582ad88cc29b3c14d16969929142a429e7c0bd4b7177&idx=1&mid=2448221688&scene=21&sn=a0238ec6dab2b2760dc3bbff71749596

    本节主要内容: ble4.0协议讲解 13 之“快递员”属性协议(ATT)

    预习知识:

    属性包括三种类型:服务项、特征值和描述符。三者之间存在树状包含关系,服务项包含一个或多个特征值,特征值包含一个或多个描述符,多个服务项组织在一起,构成属性规范(Attribute Profile)。对于常用的属性规范,比如体重计、心率计,BLE协会做了具体定义,这样的话,只要BLE主从设备均遵守某个Profile来进行设计,那么二者就能够优雅的通信。

    BLE的属性类型是有限的,有四个大类:

    • Primary Service(首要服务项)
    • Secondary Service(次要服务项)
    • Include(包含服务项)
    • Characteristic(特征值)

    这些属性类型分别对应了指定的UUID,BLE对这些UUID与属性类型的映射关系做了规定:

    • 0x1800 – 0x26FF :服务项类型
    • 0x2700 – 0x27FF :单位
    • 0x2800 – 0x28FF :属性类型
    • 0x2900 – 0x29FF :描述符类型
    • 0x2A00 – 0x7FFF :特征值类型

    权限(Permission)花样比较多:

            ​​​​​​​访问权限(Access Permission)- 只读、只写、读写

            加密权限(Encryption Permission) – 加密、不加密

            认证权限(Authentication Permission) – 需要认证、无需认证

            授权权限(Authorization Permission) – 需要授权、无需授权

     

    属性协议规定了客户端(Client)和服务器(Server)之间通信的方式,共六种:

    • Request(请求)
    • Response(响应)
    • Command(命令)
    • Indication(指示)
    • Confirmation(确认)
    • Notification(通知)

    客户端发送Request,服务器需要返回一个Response,表明服务器收到了。

    服务器发送Indication,客户端需要返回一个Confirmation,表明客户端收到了。

    以上两种方式,均是单线程操作,即下一个Request/Indication操作需要在上一个操作收到Response/Confirmation之后才能开始。

    客户端发送Command,服务器无需任何返回。

    服务器发送Notification,客户端无需任何返回。

    因此Command和Notification是不可靠的通信。当通信环境不佳,客户端频繁发送Command,可能发生服务器接收不到或丢弃的情况,Notification也类似。

     

    3.4、属性协议(ATT)

    上节是通过Profile搭建数据库,这节定义通信协议进行数据库的数据读写操作。通信协议主要3个方面:通信方式、通信包格式、具体通信数据包。

    3.4.1、通信协议方法

    属性协议主要用来发现、读写、通知和指示属性。具体如表3-12。

    这里涉及到“原子操作”,上面的通信方式中Request和Response是一对,Indication和Confirmation是一对,在没有得到对方的应答或者确认信息之前是不能进行第2包的请求或者指示数据。命令和通知因为没有应答信息,所以可以在任何时候进行包传输。

    3.4.2、属性协议包格式

        协议包格式如图3-26所示。包含有1字节的操作码和其他参数,其他参数有两种格式,一种是不包含加密认证信息的参数,另一种是带有12字节的加密认证信息的协议包。

     

    需要注意的是,如果链路本身是加密的,那么包含认证标示的包将不被发送。因为加密链路已经进行一次安全认证,不需要再次进行认证。协议原文如下:

    An Attribute PDU  that includes an Authentication Signature should not be sent on an encrypted  link. Note: an encrypted link already includes authentication data on every  packet and therefore adding more authentication data is not required.

    3.4.3、属性协议PDUs

    属性协议数据包,是用来对属性数据库操作的实体。这一节几乎全部用图来描述。具体怎么发送数据库中的服务,下节中会讲到ATT协议怎么对应GATT服务发现。

    3.4.3.1、交换MTU

        图3-27为交换MTU包格式。图3-28为交换MTU示意。图3-29为Sniffer采集空中交换MTU数据。

    图3-27 交换MTU包格式

     

    图3-28交换MTU示意

    图3-29 Sniffer采集空中交换MTU数据

     

    在低功耗蓝牙连接中,属性协议默认的MTU为23字节。一般客户端是不会发起这个请求的,它的默认值就是23,当双方交换的值不同时,用较小的那个作为最终使用的值。

    3.4.3.2、找信息请求\应答(Find InformationRequest\Response)

        图3-30为找信息请求\应答包格式。图3-31为找信息请求\应答示意。图3-32为Sniffer采集空中找信息请求\应答数据。

     

    查找信息请求和回复用来查找一系列属性的句柄和类型信息。这是唯一一个能让客户端发现任意属性类型的消息。

    查找信息请求包含有两个句柄:起始句柄和结束句柄。它们定义了该请求用到的属性句柄范围。为了找到所有数值的属性,该请求的起始句柄将是0x0001,结束句柄设为0xFFFF。但是回复的信息中因为长度限制,并不能包含所有的属性,所以需要再次请求,只是将起始句柄改为查找到的最大句柄的后一个句柄开始。

    查找信息响应包含句柄-类型对。它有两种格式,一种是基于16位的UUID格式,允许在一个包中最多包含有5个属性句柄-类型对;另一种是基于128位的UUID,允许包含有1个属性句柄-类型对。在同一应答包中不能包含有这两种格式。

    3.4.3.3、按类型值查找请求\应答(Find By Type Value Request\Response)

         图3-33为按类型值查找请求\应答包格式。图3-34为按类型值查找请求\应答示意。图3-35为Sniffer采集空中按类型值查找请求\应答数据。

    按类型值查找请求和回复可以根据给定的类型与数值查找相应的属性。该请求包含有两个句柄:起始句柄和结束句柄,规定查找范围。对于这一范围类的所有属性,如果和请求中所指定的类型和数值一样,那么这个属性就要在响应中返回。

     

    这个命令主要用来查找特定的首要服务。发送请求时,客户端将其中的类型设置为首要服务,并将数值设为该服务的UUID。随后的响应将包含查找到的各个首要服务的实例的句柄范围。某些特殊的服务只会在服务器中实现一次,针对它们的响应只会包含一个句柄范围。

    3.4.3.4、按类型读请求\应答(Read ByType Request\Response)

    图3-36为按类型读请求\应答包格式。图3-37为按类型读请求\应答示意。图3-38为Sniffer采集空中按类型读请求\应答数据。

    这个命令能在句柄范围内读取某个属性值。当客户端仅知道属性的类型而非句柄时可以使用该请求。请求包含有起始、结束句柄和需要读取的属性的类型。响应将给出符合的句柄和数值。

    这个请求用于搜索被包含的服务,并通过特性类型来发现服务中的所有的特性。它也被用来读取已知类型的特性值。

     

    3.4.3.5、读请求\应答(Read Request\Response)

    图3-39为读请求\应答格式。图3-40为读请求\应答示意。图3-41为Sniffer采集空中读请求\应答数据。

    读取请求是属性协议总最简单的请求。该请求包含一个句柄,响应将返回该句柄对应的属性值。只有在客户端已知属性句柄的情况下,才能使用该请求读取属性值。

     

    3.4.3.6、大对象读请求\应答(Read BlobRequest\Response)

    图3-42为大对象读请求\应答格式。图3-43为大对象读请求\应答示意。

    有时属性值太长,无法装入一个读取响应,须使用大对象读取请求来获取剩余字节。大对象读取请求不光包含属性句柄,还包含属性值的这个数据中的偏移量。其响应将从属性偏移量开始,包含尽可能多的属性值。

    在获得了属性值的前22个字节后,假如客户端还想获取后续的属性值,则使用大对象读取请求。下一条响应将返回第23个字节到44个字节;如此继续,直到客户端读取到完整的属性值。

    该请求用于读取长特征值与长特征描述符。

    3.4.3.7、多重读取请求\应答(Read Multiple Request\Response)

    图3-44为多重读取请求\应答包格式。图3-45为多重读取请求\应答示意。

    用来在一个操作中读取多个属性值。该请求包含一个或多个属性句柄,响应则安之请求的顺序返回相应的属性值。然而因为响应中的数值之间没有界限,因此,在请求中除了最后一个属性允许可变的长度,其他属性必须为定长的属性值。也就是说,如果客户端用一个多重读取请求来读取三个属性,前两个属性的长度必须固定,最后一个属性的长度则可以变化。

    如果客户端请求读取的属性值的长度超过了响应数据包所能承载的最大长度,那么无法放入响应数据包的数值将被丢弃。

     

    3.4.3.8、按组类型读取请求\应答(Read ByGroup Type Request\Response)

    图3-46为按组类型读取请求\应答包格式。图3-47为按组类型读取请求\应答示意。图3-48为Sniffer采集空中按组类型读取请求\应答数据。

    它和按类型读取请求类型,也包含有一个句柄范围,读取时将其视为一个属性的类型来处理,只不过属性的类型必须为分组属性。其响应包含所读取的属性句柄、属性分组中最后一个属性以及属性的数值。

    这意味着,如果分组类型是首要服务,它将返回所有首要服务声明的属性句柄、该首要服务中最后一个属性以及首要服务声明的数值。因此,可以仅凭单个请求来发现设备上的所有首要服务、与之关联的属性句柄的范围以及这些服务的类型。

    如同其他返回对个句柄-数值对的响应,如果返回的值长度可变,那么只有长度相同的属性值将在第一个响应中被返回。因此,客户端必须再次发起请求,更新起始句柄来发现想要获取的下一个属性。

    3.4.3.9、写请求\应答(WriteRequest\Response)

    图3-49为写请求\应答包格式。图3-50为写请求\应答示意。图3-51为Sniffer采集空中写请求\应答数据。

    3.4.3.10、写命令(Write Command)

    图3-52为写命令包格式。图3-53为写命令示意。图3-54为Sniffer采集空中写命令数据。

    写入命令类似于读取请求,区别是写入命令没有响应。写入命令包含要写入的属性的句柄和要写入的数值。

    当无需响应时,可以使用写入命令。此外,因为该命令可以在任何时刻发送,即使刚发送了一条请求还未收到相应的响应,对命令的发送时延有较高的要求时,该请求也适合。

     

    3.4.3.11、签名写命令(Signed Write Command)

    图3-55为签名写命令包格式。图3-56为签名写命令示意。

     

    签名写命令和写命令类似,只是前者包含认证签名。通过这样的机制,发送端可以向服务器发送写入命令时认证自己,而无需加密通信连接。签名写命令适用于以下两种场合:

     发起加密将显著增加数据连接的延迟

     发起加密将显著增加简短且无需加密的数据的送达成本

    认证签名由签名计数器和消息认证码构成。签名计数器对设备间发送的每一条消息赋予不同的值,不论消息发送的间隙连接中断与否。使用签名计数器可以抵御消息重放攻击,因此,假如签名写入命令的签名计数器值和先前的值相同,该命令会被忽略。消息认证码长度为64位,该码位于句柄、数值和签名计数器之后。注意,服务器需要为每一个客户端保存其最后使用的签名计数器。

    3.4.3.12、准备写请求\应答(Prepare Write Request\Response) 和执行写请求\应答(ExecuteWrite Request\Response)

    准备写请求和执行写请求实现两种功能:

     它们提供了长属性值的写入功能

     它们允许在一个单独执行的原子操作中写入多个值

    属性服务器包含一个准备写入队列,其中保存有准备写入请求。队列的大小可独立配置,但通常它足够储存所需要准备写入的服务。只有在收到执行写入请求时,准备写入的值才会写入属性,也就是说执行写入请求给出了执行这些准备写入操作的开始信号。

    准备写请求包含句柄、偏移量和部分属性值,这和大对象读取类似。这说明客户端即可以在队列中准备多个属性值,有可以准备一个长属性值的各个部分。这样,在真正执行准备队列之前,客户端可以确定某属性的所有部分都能写入服务器。

    准备写入响应也包含请求中的句柄、偏移量和部分属性值。之所以这样做是为了数据传递的可靠性。客户端可以对比响应和请求的字段值,保证准备的数据被正确接收。

    图3-57为准备写请求\应答包格式。图3-58为准备写请求\应答示意。

    可以这么理解,准备写入请求,之所以是“准备写”,也就是没有真正的写到属性中去,上文提到准备写是写在服务器中的单独的一个缓冲器中,当所有需要写入的数据都发送给服务器后,那么就要下一个命令,即为执行写,“执行写”也就是真正的执行写入属性的操作。

    完整过程是,一旦接收完所有的准备写入请求,服务器将拥有一个随时可以执行的准备写入队列。客户端发送表示位为“立刻写入”的执行写入请求,随后服务器将在一次原子操作中写入所有值。属性将按照其准备的顺序写入。如果客户端多次准备了同一个属性值,那么服务器将按照顺序向该属性写入这些值。这意味着如果使用准备队列配置硬件状态,例如硬件需要先后执行禁用、配置、重新启用操作,那么可以用一个准备队列来实现:在队列总先将对应的属性写入“禁用”,接着写入“配置”,之后在准备队列的末尾写入“启用”,随后执行该队列。这样便能在一个原子操作中实现该硬件的重新配置。

    图3-59为执行写请求\应答包格式。图3-60为可靠写请求\应答示意。

     

    3.4.3.13、句柄通知(Handle Value Notification)

    图3-61为句柄通知包格式。图3-62为句柄通知示意。

     

    当服务器想要向客户端发送快速得属性状态更新时,可以发送一条句柄值通知。这个是服务器能够发给客户端的两种消息中的一种,并且是不要求响应的那种。服务器可以在任何时刻发送该通知,同时该通知也是不可靠的。

    句柄值通知包含属性句柄和数值。因此该通知是服务器发往客户端的一条消息,用来告知某属性的当前值。它是属性协议中最重要的消息之一。它不仅让客户端能够有效地从服务器获取当前属性数据库的更新,而且也被用来通知客户端有限状态机的变化。

    一般来说,服务器会为所有的绑定设备配置通知。这样做的好处在于当某客户端重新与设备建立连接时,该设备上的服务能够立刻将自己当前的状态通知该客户端。例如,将某设备的电池电量配置为通知。

    因为通知不具备任何确认机制,它们可以在任何时刻发送,而不管当时正在进行什么用的操作,一次,通知适用于必须立刻向客户端发送信息的情况。

    3.4.3.14、句柄指示\确认(HandleValue Indication\Confirmation)

    句柄值指示类似于句柄值通知。它有着相同的属性句柄字段和数值,不同的客户端收到指示以后应回复。服务器一次只能发送一条指示,并且只有收到确认响应后才能发起下一条指示。

    句柄值确认不含任何数据,主要用于流控。因为具备了确认机制,指示被视为可靠传输。一旦服务器收到确认信息,它便能确定客户端收到了该信息。

    图3-63为句柄指示\确认包格式。图3-64为句柄指示\确认示意。

     

     

    当某设备无法完成请求所要求的操作时,它可以发送错误响应。例如某设备通过发送写入请求试图写入某个属性,但该属性是只读的,那么服务器应发送错误响应来给出请求失败的原因,而不是发送写入响应正常。

    错误响应包含导致错误的与请求相关的所有信息,例如请求失败的属性、导致错误的首要原因等。一旦客户端收到错误响应,它会认为该响应与其发送的最后一条请求相对应。因此,错误响应是另一种利用响应消息来终止请求操作的方法。这也说明对每一条可以发送的请求都会有两种可能的响应:失败错误响应和成功响应。

    图3-65为句柄指示\确认包格式。表3-13为错误代码表。

    下面对表中的代码进行一一说明:

     无效句柄  请求中的属性句柄是无线的,它在服务器中不存在。该错误是由于服务器不使用或者不具备请求读取或者写入的属性句柄。当属性句柄被设置为0x0000时也可以发送该错误。

     不允许读取  该属性不允许读取其属性值。例如当客户端试图读取某控制点的只写是属性时,可以发送该错误。

     不允许昔写入  该属性不允许写入其属性值。例如试图写入只读属性值将导致该错误。

     PDU无效  服务器不理解收到的请求。通常当客户端发送了错误格式的请求时,将导致该错误。例如,读取请求必须有两字节的句柄作为其唯一参数。当读取请求不具备两字节的参数时将导致该错误。

     认证不足  当两设备缺少相互认证时,便无法执行某属性值的读取或写入操作。为了进行认证,可以将连接加密,或是在设备还未配对时使用安全管理配对规程对设备进行配对与连接。

     请求不支持  服务器理解发送的请求但当前无法执行。当服务器未实行某个已知的请求,或是不理解某个未来的请求时,可以使用该错误。该错误可以作为设备无法执行请求时的默认错误。

     偏移量无效  请求包含偏移量,但偏移量是无效的。偏移量和属性值的长度相同不会导致该错误,但会得到一个长度为0的值。

     准备队列已满  由于用于存储等待写入队列的空间已满,准备写入请求无法被接收。

     属性不存在  属性经遍历不存在。该错误仅用于在一个属性范围内查找一种或几种特定的属性类型时。只有查找信息请求、按类型读取请求与按组类型读取请求会导致该错误。对于查找信息请求而言,该错误意味着没有找到给定的句柄范围内的属性。对按类型值查找请求、按类型读取请求和按组类型读取请求而言,该错误说明在某句柄范围内没有找到给定类型的属性。

     属性非大对象  当大对象读取请求所要读取的属性不是大对象类型时,该请求被拒绝,客户端必须改用读取请求。该错误仅和定长的属性值相关,并且该长度小于大对象读取请求当前用到的MTU长度。由此简单的做法是先使用读取请求读取属性值的前22字节,随后使用大对象读取请求读取后续的字节。

     秘钥长度不足  该错误是由于在连接已经加密、认证与授权充足的情况下,配对期间协商的秘钥长度不满足服务的要求。有些属性值需要使用高强度的秘钥来充分保证数据的机密性。

     未知错误  这或许是个最棒的错误码了。如果导致该错误的情形事先没有考虑到过,便是“未知错误”。

     加密不足  可以读取或写入属性值,但连接未加密。一些属性值需要加密连接来保证数据的机密性。

     组类型不支持  服务器不认为请求中包含的属性类型是组类型。按组类型读取请求只能使用服务器已知的组类型。

     资源不足  服务器的资源不足以接收和执行某特定的请求。例如,某服务配置某设备广播数据时,若当前广播的数据加上需要增加的广播的数据后,数据总长度过长,则会导致该错误。

     应用错误  某请求对服务中的属性执行了不允许的操作,服务分配了错误码来报告错误的所在。这是一个128-255范围内的错误,实际的含义有包含该属性的服务规格书来定义。

    展开全文
  • ATT(Attribute Protocol)属性层是GATT和GAP的基础,它定义了BLE协议栈上层的数据结构和组织方式。 属性(Attribute)概念是ATT层的核心,ATT层定义了属性的内容,规定了访问属性的方法和权限。以编程的眼光来看,...

    ATT(Attribute Protocol)属性层是GATT和GAP的基础,它定义了BLE协议栈上层的数据结构和组织方式。

    属性(Attribute)概念是ATT层的核心,ATT层定义了属性的内容,规定了访问属性的方法和权限。以编程的眼光来看,属性是一个数据结构,它包括了数据类型和数据值,就如同C语言结构体的概念,开发者可以设计独特的结构,来描述外部世界实体。

    属性包括三种类型:服务项特征值描述符。三者之间存在树状包含关系,服务项包含一个或多个特征值,特征值包含一个或多个描述符,多个服务项组织在一起,构成属性规范(Attribute Profile)。对于常用的属性规范,比如体重计、心率计,SIG(蓝牙技术联盟)做了具体定义,这样的话,只要BLE主从设备均遵守某个Profile来进行设计,那么二者就能够优雅的通信。

    ATT层相关的东西与开发者比较近,易于理解,但是章节内容图表较少,阐述偏多。

    一. 属性的组成(数据结构)

    属性主要由以下四部分组成:属性句柄(Attribute Handler)、属性类型(Attribute Type)、属性值(Attribute Value)、属性权限(Attribute Permissions)。

    1.1 属性句柄

    属性句柄(Attribute Handle)犹如指向属性实体的指针,对端设备可通过属性句柄来访问该属性,它是一个2字节长度的十六进制码,起始于0x0001,在系统初始化时候,各个属性的句柄逐步加一,最大不超过0xFFFF。

    1.2 属性类型

    作用是用以区分当前属性是服务项或是特征值等,它用UUID来表示。UUID(universally unique identifier,通用唯一识别码)是一个软件构建标准,并非BLE独有的概念,一个合法的UUID,一定是随机的、全球唯一的,不应该出现两个相同的UUID(出现了,就说明它们俩是同一个UUID)。标准的UUID是一串16字节十六进制字符串,如f6257d37-34e5-41dd-8f40-e308210498b4,在网上可以方便的生成一个UUID。
    BLE的属性类型是有限的,有四个大类:

    • Primary Service(首要服务项)
    • Secondary Service(次要服务项)
    • Include(包含服务项)
    • Characteristic(特征值)

    这些属性类型分别对应了指定的UUID,BLE对这些UUID与属性类型的映射关系做了规定:

    • 0x1800 – 0x26FF :服务项类型
    • 0x2700 – 0x27FF :单位
    • 0x2800 – 0x28FF :属性类型
    • 0x2900 – 0x29FF :描述符类型
    • 0x2A00 – 0x7FFF :特征值类型

    假如UUID=0x1800,就表示它是一个首要服务项。

    UUID是16个字节的字符串,为什么这里只使用了2字节?

    因为这些是常用的UUID,为了减少传输的数据量,BLE协议做了一个转换约定,给定一个固定的16字节模板,只设置2个字节为变化量,其他为常量,2字节的UUID在系统内部会被替换,进而转换成标准的16字节UUID。UUID模板为:

    0000XXXX-0000-1000-8000-00805F9B34FB

    其中从左数第3、4个字节“XXXX”就是变化位,其他为固定位。如:UUID=0x2A00在系统内部会转换成00002A00-0000-1000-8000-00805F9B34FB。

    反之,如果一个特征值的UUID是16字节的,在系统内部它的属性类型也可能写成第3、4字节组成的双字节,比如UUID=1234ABCD-0000-1000-8000-00805F9B34FB,它的属性类型在内部表示为ABCD。主机端扫描到该属性类型,会将其当做是“用户自定义”的类型,然后从其他位置获取该UUID的真实值。

    1.3 属性值

    用于存放数据。如果该属性是服务项类型或者是特征值声明类型,那么它的属性值就是UUID等信息。如果是普通的特征值,则属性值是用户的数据。属性值需要预留空间以保存用户数据。为了方便理解,我们可以将属性值的空间看做I2C的数据空间,操作特征值里的用户数据,就是对那块内存空间进行读写。

    1.4 属性权限

    属性权限主要有以下四种:

    • 访问权限(Access Permission)- 只读、只写、读写
    • 加密权限(Encryption Permission) – 加密、不加密
    • 认证权限(Authentication Permission) – 需要认证、无需认证
    • 授权权限(Authorization Permission) – 需要授权、无需授权

    访问(Access)权限好理解,如果是只读权限,就不能对其写数据,其他类似。

    加密(Encryption)权限也好理解,就是对数据进行加密。

    认证(Authentication)是指相互确认对方身份。完成认证流程的两个设备,双方建立信任关系,二者之间的通信通道即可以认为是安全的。BLE中,“认证”过程就是配对。

    授权(Authorization)是指对授信设备开放权利。

    认证和授权功能容易混淆,其英文拼写也很相似。从上面的概念上看,授权要求设备必须是可信任的,因此授权的管控等级要高于认证——认证的设备未必被授权,授权的设备一定是认证的。理解二者关系,需要引入一个概念:Trusted Device(可信任设备)一个没有经过认证的设备,被称为Unknown Device(未知设备);经过了认证该设备会在绑定信息中被标记为Untrusted,被称为Untrusted Device(不可信设备);经过了认证,并且在绑定信息中被标记为Trusted的设备被称为Trusted Device(可信设备)。

    授权要求设备为Trusted Device(可信任设备)。在实际使用中,经过配对以后设备即为Untrusted Device——认证,在代码中调用API可以设置设备为Trusted Device——授权。

    二. 属性的种类和分组(属性的层级)

    属性大致可以分为三种类型:服务项特征值描述符。它们的层级关系为:最顶级为Profile, 下面是多个服务项(Service), 服务项下面是多个特征值(Characteristic), 特征值下面是多个描述符(Descriptor)。

    每个设备都包含以下必要的特征值和服务项:

    PROFILE

    • Generic Access Service(Primary Service)
      • Device Name(Characteristic)
      • Appearance(Characteristic)
    • Generic Attribute Service(Primary Service)
      • Service Changed(Characteristic)
        • CCCD(Descriptor)

    服务项这种类型本身并不包含数据,仅仅相当于是一个容器,用来容纳特征值。特征值用于保存用户数据,但它也有自己的UUID, 有点像C语言中的变量int var=0xFF,整形变量var携带了用户数据0xFF,但是它自身还有地址信息(&var),因此在使用时需要先定义再赋值两个步骤。类似的,在处理特征值所携带的用户数据之前,需要先对特征值自身进行声明。

    特征值在系统中的表达形式是:声明 + 特征值属性。比如Device Name,它在GATT数据库中表示方式是:

    Characteristic 声明: 0x0002, 0x2803, access_property, 0x2A00
    Characteristic 项:   0x0003, 0x2A00, access_property, data

    其中第一列双字节数代表句柄,第二列双字节数代表属性类型(UUID),0x2803表示该项为“特征值的声明”,0x2A00表示该特征值是Device Name。在第一行特征值声明中,最后一项的属性值就是该特征值的UUID。可以注意到,声明里面的属性值为0x2A00,特征值自己的属性类型也是0x2A00,显然信息冗余了。原因是,假如特征值的UUID为自定义的16字节UUID,在特征值的属性类型中,只能存放2个字节,而UUID的真实值,就存放在特征值声明的属性值项中。

    BLE的属性体系在系统中以GattDB表示,即属性数据库。打开CyBle_gatt.c文件,找到cyBle_gattDB变量,如下图

     

    const CYBLE_GATTS_DB_T cyBle_gattDB[0x10u] = { 
        { 0x0001u, 0x2800u /* Primary service                     */, 0x00000001u /*           */, 0x0007u, {{0x1800u, NULL}}                           }, 
        { 0x0002u, 0x2803u /* Characteristic                      */, 0x00000201u /* rd        */, 0x0003u, {{0x2A00u, NULL}}                           }, 
        { 0x0003u, 0x2A00u /* Device Name                         */, 0x00000201u /* rd        */, 0x0003u, {{0x0009u, (void *)&cyBle_attValuesLen[0]}} }, 
        { 0x0004u, 0x2803u /* Characteristic                      */, 0x00000201u /* rd        */, 0x0005u, {{0x2A01u, NULL}}                           }, 
        { 0x0005u, 0x2A01u /* Appearance                          */, 0x00000201u /* rd        */, 0x0005u, {{0x0002u, (void *)&cyBle_attValuesLen[1]}} }, 
        { 0x0006u, 0x2803u /* Characteristic                      */, 0x00000201u /* rd        */, 0x0007u, {{0x2A04u, NULL}}                           }, 
        { 0x0007u, 0x2A04u /* Peripheral Preferred Connection Par */, 0x00000201u /* rd        */, 0x0007u, {{0x0008u, (void *)&cyBle_attValuesLen[2]}} }, 
        { 0x0008u, 0x2800u /* Primary service                     */, 0x00000001u /*           */, 0x000Bu, {{0x1801u, NULL}}                           }, 
        { 0x0009u, 0x2803u /* Characteristic                      */, 0x00002201u /* rd,ind    */, 0x000Bu, {{0x2A05u, NULL}}                           }, 
        { 0x000Au, 0x2A05u /* Service Changed                     */, 0x00002201u /* rd,ind    */, 0x000Bu, {{0x0004u, (void *)&cyBle_attValuesLen[3]}} }, 
        { 0x000Bu, 0x2902u /* Client Characteristic Configuration */, 0x00000A04u /* rd,wr     */, 0x000Bu, {{0x0002u, (void *)&cyBle_attValuesLen[4]}} }, 
        { 0x000Cu, 0x2800u /* Primary service                     */, 0x00000001u /*           */, 0x0010u, {{0xCBBBu, NULL}}                           }, 
        { 0x000Du, 0x2803u /* Characteristic                      */, 0x00001A01u /* rd,wr,ntf */, 0x0010u, {{0xCBB1u, NULL}}                           }, 
        { 0x000Eu, 0xCBB1u /* Custom Buffer                       */, 0x00011A04u /* rd,wr,ntf */, 0x0010u, {{0x00C8u, (void *)&cyBle_attValuesLen[5]}} }, 
        { 0x000Fu, 0x2901u /* Custom Descriptor                   */, 0x00010001u /*           */, 0x000Fu, {{0x001Cu, (void *)&cyBle_attValuesLen[6]}} }, 
        { 0x0010u, 0x2902u /* Client Characteristic Configuration */, 0x00010A04u /* rd,wr     */, 0x0010u, {{0x0002u, (void *)&cyBle_attValuesLen[7]}} }, 
    }; 

     

    通过注释可以看到,每个特征值都有声明和特征值项两部分组成。

    由于各个属性的句柄是递增的,因此属性的声明顺序会影响句柄的计算。

    描述符是特征值的补充信息,挂载在特征值之下,它可以开辟一段数据空间以携带数据,客户端可以像操作特征值一样对其进行读写,但是描述符弱于特征值,它不具备Notify/Write等读写属性,远不如特征值灵活。有一种描述符叫CCCD(Client Characteristic Configuration Description),当对特征值设置Notify或Indication时,用以控制Notify和Indication的使能情况。

    关于gattDB,值得注意的一点是,gattDB是BLE协议栈在内存中开辟的一段专有区域,它会在特定的时候写入Flash进行保存,并在启动时读取出来回写到内存中去。并非所有的BLE数据通信是操作gattDB!比如手机向BLE设备发送一串字符串,从机收到这串字符串并打印出来,这个过程中,这个字符串并没有写入gattDB。那么,即使开发者限制某个特征值为认证、只读等权限,仍然不能阻止这个字符串的发送和打印,因为特征值的权限,都是针对gattDB而言的。进一步,假如BLE从机收到字符串后,希望写入gattDB进行保存,那么就会触发权限问题。以往有开发者说为什么我勾选了属性需要认证,但是配对失败后仍然能够看到手机发来的数据,原因就在这里,就是手机端传来的数据没有涉及到gattDB,当尝试写入gattDB的时候,就会发现报错了。

    三. ATT PDU(属性协议)

    在ATT层协议框架内,拥有一组属性的设备称为服务端(Server),读写该属性值的设备称为客户端(Client),Server和Client通过ATT PDU进行交互。属性协议共有6种:

    属性PDU方向触发响应
    CommandClient -> Server
    RequestClient -> ServerResponse
    ResponseServer -> Client
    NotificationServer -> Client
    IndicationServer -> ClientConfirmation
    ConfirmationClient -> Server

    它们的区别如下:

    客户端发送Request,服务器需要返回一个Response,表明服务器收到了。

    服务器发送Indication,客户端需要返回一个Confirmation,表明客户端收到了。

    以上两种方式,均是单线程操作,即下一个Request/Indication操作需要在上一个操作收到Response/Confirmation之后才能开始。

    客户端发送Command,服务器无需任何返回。

    服务器发送Notification,客户端无需任何返回。

    因此Command和Notification是不可靠的通信。当通信环境不佳,客户端频繁发送Command,可能发生服务器接收不到或丢弃的情况,Notification也类似。

    PDU的具体格式定义如下:

     参数说明:

     

    Opcode:
    bit 0-5:操作属性的方法
    bit 6:Command 标识位
    bit 7:Authentication Signature标识位
    Attribute Parameters:
    如果Attribute Opcode中身份验证签名标记位为0,则X = 1;
    如果Attribute Opcode中身份验证签名标记位为1,则X = 13;
    Authentication Signature:
     
    展开全文
  • 蓝牙BLE GATT完全分析和运用

    万次阅读 多人点赞 2016-02-26 20:34:10
    本文结合BLE的服务发现协议标准和DA14580平台、CC2541平台的应用实践来深入分析GATT,让大家能够自如地构建一个BLE的属性数据库。网上几乎没有类似这样对底层协议结构进行分析的文章,做蓝牙开发的童鞋要好好收藏。

    很多人都做过蓝牙开发,很多人也能够通过仿照GATT例程的方式添加一个属性服务,但是很多人都未必能够清晰地理解BLE的属性profile,也很容易被属性Attribute和特性characteristic所混淆。本文结合BLE的服务发现协议标准和DA14580平台、CC2541平台的应用实践来深入分析GATT,让大家能够自如地构建一个BLE的属性数据库。

    更多蓝牙设备开发的经验原创总结敬请关注微信公众号:嵌入式企鹅圈。 

    一、 BLE GATT(Generic Attribute Profile)规范

    1.   GATT定义

    GATT是低功耗蓝牙属性应用规范,应用于主机和从设备之间的数据传输。其与GAP并列为BLE两大profile。

    Attribute是属性的意思。何为属性?在各蓝牙单芯片平台的SDK实际使用中,属性是指一条带有标签的、可以被寻址的数据。在蓝牙实际的规范中,寻址即用handle句柄来表示。每个属性都对应一个唯一的handle。


    2.   对属性协议需求的思考

    蓝牙是无线通信,BLE利用属性协议进行传输,其如此重要,如果我们不理解其需求,那么我们也很难从真正去理解其规范。尽管在实际的蓝牙单芯片SDK中很容易通过模仿的方法进行应用,但是如果想深入地理解其为什么要设计呢?

    1)连接的参数是一个设备的固有参数,一般会作为一个服务来提供,如GAP服务;而假设这个设备是一个温度采集器,那么这个温度采集明显跟设备的参数不属于一类,因此可以再作为一个服务。所以属性协议应该支持多个服务。如何来区分这些不同的服务?这即对应蓝牙标准规范规定的UUID。我们可以认为不同的UUID对应不同的确定的服务。

    2)连接的参数可以有多个,如Connection Interval、Slave Latency等等,我们如何区分这是一个服务,又如何区分服务包含了这些特性参数。我们可以认为一个服务包含了多个特性(参数)。在蓝牙标准里面,同样是用不同的UUID来区分服务类型、特性类型等等。

    3)对于每个特性characteristic,要让对方获取这个特性,就必须要分别告诉对方这个特性的长度是多少,值是多少,而不能只给数值。除此之外,特性还可能有描述值(说明特性名称或者作用等)、特性单位等(国际单位,如米是公里/每小时还是米/秒)。后面这两个是非必选的。

    4)属性还应该有一个访问控制,如可读可写还是读写、或者是通知notify/indicate等等,这是数据通信必须具有的权限控制,不管是服务还是特性,它都具有访问控制属性。


    3.   属性和属性类型

    属性由属性句柄、属性类型、属性值组成。如下图:

    1)  属性句柄在实际的运用中可以认为是属性在属性数组中的下标。我们都知道在实际的编程中,下标并不需要专门存储,而只是通过元素的结构体来进行索引即可。因此可以认为属性句柄是一个无形的东西,它只能被所在的设备程序所认识,而不能用于无线传输。

    2)  属性类型是真实存在的,其和属性值都会被实际存储。属性类型是由蓝牙标准组织所规范,其一般通过128位的UUID来表征一个具体的属性。由于BLE的GATT可以认为是蓝牙标准规范的精简版,所以BLE被允许只传输前面2字节(16位)的UUID,所有的BLE的UUID的基数都是一样的,如下,只有前面两字节不同。

    利用2字节(16位)也可以定义65536种属性了。事实上,蓝牙标准组织对这些UUID进行了分类。如下:

    属性类型即是0x2800~0x28ff,在实际的应用中,属性类型主要包括:我们主要使用服务和特性定义两种,其他两个很少用到。

    3)  蓝牙标准不仅通过UUID来进行属性分类,而且还用UUID来确定各种具体的服务和特性。所以我们会看到UUID可能会出现在属性的属性类型和属性值两个地方。

    4)  蓝牙标准组织规定两个ATT_DECL_PRIMARY_SERVICE服务之间的特性都隶属于第一个服务。这样可以理解在蓝牙服务发现协议中先通过UUID找到目标服务,然后通过ATT_DECL_PRIMARY_SERVICE这个属性类型找到下一个服务,接着即可以在这两个服务中进行特性的遍历,遍历的结果即是目标服务的所有特性。


    4.   属性值

    属性值的长度可以最长到512字节,但对于某些属性,其长度是固定的。对于蓝牙标准里面规定的UUID所对应的属性(包括服务、特性定义、特性值、特性描述等等),服务、特性定义的长度是确定的,而特性值则是不固定长度的。

    所以,对于不同的属性,其属性值是不一样的。也即对于以上五类(通用服务、单位、属性类型、特性描述和区分特性类型)等属性,其属性值的规范是不一样,具体到不同的特性类型,其属性值也是不同的。


    1)通用服务类通过唯一的UUID(0x1800~0x26ff)来标识一种明确的服务。好比,0x180f代表电池电量服务。

    2)计量单位类通过唯一的UUID来标识一种单位。

    3)区分属性类型类通过唯一的UUID来标识该属性是首要服务定义、次要服务、包含服务还是特性定义等。其好比程序中的变量的类型,是整型、字节型、还是确定的结构体。

    4)特性描述类除了描述特性的名称、作用之外,还有一个非常重要的配置作用。例如如果提供的特性服务需要主动告知对方,那么对方就必须在连接时进行订阅配置。这样在该特性的数据值发生变更时能够主动地进行notify或者indicate。

    5)区分特性类型用于用户定义不同的特性,用于区分该设备里面所有的特性。


    5.   特性

    把特性理解为一个程序中的一个变量是最好理解的。变量有变量类型和值,变量类型有int整型、字节型等等(其实就是变量的存储长度),值即具体的数值。相应地,而特性则有值和存储值的长度的概念。如同变量的声明和定义,特性characteristic也有声明和定义(赋值)的概念。

    一般地,在蓝牙标准里面,特性一般包括三个要素:声明、数值和描述。前两者都是必须的。作为通信交互,一个特性必须要告诉对方声明(存储长度和访问控制)、定义(具体赋值)。在某些特性(如notify或者indicate)里面,特性还需要告知对方附加的配置属性(提供订阅等)。

    特性声明必须作为服务属性之后的第一条属性,而数值必须紧随其后。

    1)  特性声明

    性质为一个8位字段,指示访问控制权限,包括读、写、notify或者indicate等。对于特性声明而言,其一般是只读的(这里只针对声明这条属性本身,而不是针对对应的特性数值)。数值句柄即用于直接寻址接下来的特性数值。其对于通信的对方是很有好处的,因为对方只需要记录该句柄即可在后续的访问中直接寻址,否则每次通信都要遍历。在实际的编程应用中,我们往往在初始化时填入0,代表由底层逻辑来自动更新该handle。而属性UUID和接下来的特性数值属性的区分特性类型值是一致的。

    2)特性数值

    特性数值也是一个属性,其属性类型填入特性声明的属性UUID。属性值要填入特性数值的访问权限、长度和数值。

    3)特性描述

          其可以是字符串表示的特性名称,或者是notify/indicate要求的配置等等。


    二、对属性协议重要的理解原则

    1)无论是服务还是特性,它们都是一条条属性;特性的各个要素也是一条条属性。只不过,不同的服务是独立的;而一个服务如果有多个特性,那么不同的特性也是独立的;一个特性包含的多条属性则是关联的。

    2)对于蓝牙通信来说,其都是通过一个个不同的UUID来标识区分不同的服务,区分不同的特性,甚至服务/特性之间的类别。

    3)对于各个蓝牙单芯片SDK平台,其上层应用对于属性协议的支持并不一致,它们只需要保证底层的蓝牙数据格式一致即可。


    三、属性协议范例说明

    例如一个电池服务包括一个特性(电池电量),那么其至少包括以下属性:首要服务定义(电池服务)、当前电量的特性定义(定义值长度)、当前电量的特性值。如果希望电池电量在低于某个水平时主动告知对方,那么这个电量特性值不仅应该是可读的,还应该是能够notify的。由于有主动告知,因此该特性还需要包括配置要素,用于对方来订阅。

    1.对于电池服务这个属性,其属性类型是ATT_DECL_PRIMARY_SERVICE(0x2800),属性值是访问可读和蓝牙标准组织规定的0x180f(位于0x1800到0x26ff之间);

    2.电池当前电量的特性定义这个属性,其属性类型是ATT_DECL_CHARACTERISTIC(0x2803),属性值是可读、特性值句柄和特性值的UUID(0x2A19)。

    3.对于当前电量的特性值这个属性,其属性类型是0x2A19(0x2A00~0x7fff之间,区分特性类型),其用于区分多种不同的特性(如一个温度采集器可能要采集多个温度,这里就要用户通过不同的UUID来区分不同的特性了),属性值即访问控制(可读/indicate)、长度(1字节)、数值。

    4.配置属性,用于notify的订阅配置。其属性类型是0x2902,属性值是可读/可写(要能写入订阅方的handle)、长度(2个字节)、handle值。

          根据以上分析,我们来重构这个蓝牙的数据底层数据库。

    四、DA14580平台SDK属性结构定义

    1.单个属性定义

    对于不同的属性,其length长度由value的类型来决定。例如服务的属性值对应的结构就是uint16_t;而特性声明的属性值对应的结构就是:

     

    2.电池电量服务定义

    五、CC254X平台SDK属性结构定义

    1.单个属性定义

    同样的,属性值pValue的数据结构由其对应的UUID来决定。例如服务属性的pValue对应的结构是gattAttrType_t(包括长度2和具体的服务UUID)。而特性声明的pValue对应的结构是一个字节的访问权限控制。特性数值的pValue对应的结构则是直接的变量定义,其可能是一个字节或者是字节数组,其长度由底层通过判断数组来决定。

    2.电池电量服务定义

     

    六、参考文献

    1.   BLE标准core_V4.0,英文规格书

    2.   低功耗蓝牙开发权威指南,蓝牙标准起草人撰写,中译本。

    如需要这两本电子书请关注微信公众号:嵌入式企鹅圈 之后发消息获取。​

    做蓝牙开发的童鞋们记得点赞啊,网上几乎没有类似这样对底层协议结构进行分析的文章,最多就是写写属性、服务、特性之类的定义,看了过眼就忘。这篇文章应该可以好好收藏,不记得的时候就翻出来看看:-)

    更多嵌入式Linux和物联网原创技术总结敬请关注微信公众号:嵌入式企鹅圈



    展开全文
  • Android蓝牙

    千次阅读 2014-10-27 15:22:10
    Android平台提供了对蓝牙网络协议栈的支持,它允许设备通过无线的方式与其它设备进行数据交换。应用程序框架通过Android Bluetooth APIs提供了对蓝牙功能的访问。这些API允许应用以无线的方式连接其它蓝牙设备,启用...


    Android平台提供了对蓝牙网络协议栈的支持,它允许设备通过无线的方式与其它设备进行数据交换。应用程序框架通过Android Bluetooth APIs提供了对蓝牙功能的访问。这些API允许应用以无线的方式连接其它蓝牙设备,启用点对点或者多点对多点的无线功能。使用蓝牙的API,Android应用程序可以执行以下工作:


    * 扫描其它蓝牙设备
    * 查询本地蓝牙适配器以配对蓝牙设备
    * 建立RFCOMM通道
    * 通过服务搜索连接其它设备
    * 传输或者接收其它设备的数据
    * 管理多个连接

    本文档描述了如何使用Classic Bluetooth(传统蓝牙)。传统蓝牙是耗电操作的理想选择,例如Android设备之间的数据流传输和通讯。为了适应低能耗蓝牙设备,Android 4.3(API Level 18)的API开始支持Bluetooth Low Energy(BLE)。更多信息,请参考Bluetooth Low Energy。

    1、The Basics(基础部分)


    本文档描述了如何使用Android Bluetooth APIs来完成四种需要使用蓝牙通信的任务:设置蓝牙,搜索已经配对或者附近区域允许访问的设备,连接设备,还有在设备之间传输数据。

    所有的Bluetooth API都可以在android.bluetooth包中访问到。下面是你创建蓝牙连接需要使用到的类和接口的摘要:

    1.1 BluetoothAdapter


    代表Local Bluetooth Adapter(本地蓝牙适配器、蓝牙发送接收器)。BluetoothAdapter是所有蓝牙交互的入口点。使用这个对象,你可以搜索发现其他蓝牙设备,查询已配对配备列表,使用已知MAC地址实例化一个BluetoothDevice对象,创建 一个BluetoothServiceSocket来监听来自其它设备的通信。


    1.2 BluetoothDevice


    代表Remote Bluetooth Device(远程的蓝牙设备)。使用这个对象可以通过一个BluetoothSocket来请求一个远程设备的连接,或者查询这个远程设备的信息,例如名字、地址、类别、以及配对状态。

    1.3 BluetoothSocket


    代表Bluetooth socket(蓝牙套接字)的接口(类似于一个TCP socket)。这是一个连接点,它允许一个应用程序通过InputStream和OutputStream与另一个蓝牙设备交换数据。

    1.4 BluetoothServerSocket


    代表一个open server socket(开放的服务套接字),用于监听即将传入的请求(类似于一个TCP的ServerSocket)。为了能够连接两台Android设备,一台设备必须使用这个类开放一个server socket。当一个远程蓝牙设备发送一个连接请求给这台设备的时候,BluetoothServerSocket接受了这个请求就会返回一个已连接的BluetoothSocket。

    1.5 BluetoothClass


    这个类描述了一台蓝牙设备的一般特征(characteristic)和功能(capabilities)。这是一串只读的属性,定义了这台设备的主要和次要设备类型,以及它的服务。然而,它对这台设备所支持的全部Bluetooth Profile和蓝牙功能的描述并不可靠,但作为对设备类型的一种提示还是非常有用的。

    1.6 BluetoothProfile


    代表一个Bluetooth Profile的接口。Bluetooth Profile是一个针对设备之间基础蓝牙通讯的无线接口规范。例如Hands-Free profile(免提规范)。更多关于profile的讨论,请参考后面的Working with Profiles章节。

    1.7 BluetoothHeadset


    提供了对手机蓝牙耳机的支持。它包括蓝牙耳机和Hands-Free(v1.5)profiles。

    1.8 BluetoothA2dp


    定义了高质量音频流怎样通过蓝牙连接以数据流的方式从一台设备传输到另一个设备。“A2DP”代表Advanced Audio Distribution Profile(高级音频分发)。

    1.9 BluetoothHealth


    代表一个Health Device Profile(健康设备)的代理,用来控制蓝牙服务。

    1.10 BluetoothHealthCallback


    用于实现BluetoothHealth回调函数的虚拟类。你必须继承这个类并实现其中的回调函数,才能够接收到关于应用程序的注册状态和蓝牙通道状态的更新。

    1.11 BluetoothHealthAppConfiguration


    代表一个第三方蓝牙健康应用程序在注册后并与一个远程蓝牙健康设备通讯的应用配置。

    1.12 BluetoothProfile.ServiceListener


    一个用于通知BluetoothProfile IPC客户端连接或者断开服务的接口(这意味着有一个内部服务运行着一个指定的Profile)。

    2、Bluetooth Permission(蓝牙权限)


    为了在你的应用程序上可以使用蓝牙功能,你必须声明蓝牙权限 android.permission.BLUETOOTH。你需要这个权限才能执行一些蓝牙通讯,例如请求一个连接,接受一个连接,传输数据。
    如果你的应用还要启动设备搜索或者管理蓝牙设置,那还需要声明 android.permission.BLUETOOTH_ADMIN权限。大多数应用需要这个权限只是为了搜索附近的蓝牙设备。这个权限提供的其它功能不应该去使用,除非这个应用程序是一个类似“电池管理”的应用,用户期望通过这个应用去管理蓝牙设置。注意:如果你使用BLUETOOTH_ADMIN权限,那么你必须同样声明BLUETOOTH权限。

    在你的应用manifest文件中声明蓝牙权限,例如:
    <manifest ... >
      <uses-permission android:name="android.permission.BLUETOOTH" />
      ...
    </manifest>

    参考 <uses-permission>获取更多关于声明应用权限的信息。

    3、Setting up Bluetooth(设置蓝牙)


    在你的应用程序通过蓝牙通讯之前,你需要确认设备是否支持蓝牙。如果支持,还要再确认是否有启用蓝牙。

    如果不支持蓝牙,那么你应该友好的禁用一些蓝牙功能。如果支持蓝牙,但是没有启用,你应该在不离开你的应用程序情况下请求用户启用蓝牙。这种设置需要使用BluetoothAdapter,通过两个步骤完成:

    3.1 获取BluetoothAdapter


    几乎所有使用蓝牙的Activity都要求有BluetoothAdapter。可以通过调用静态的getDefaultAdapter()方法获取BluetoothAdapter。这个方法会返回一个代表设备自身蓝牙适配器的BluetoothAdapter。这个BluetoothAdapter适用于整个系统,你的应用程序可以通过这个对象与系统交互。如果getDefaultAdapter()返回null,可能是设备不支持蓝牙,你的操作到此为止。例如:
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
        // 设备不支持蓝牙
    }

    3.2 启用蓝牙


    下一步,你需要确认蓝牙已经启用。调用isEnable()方法检查当前蓝牙是否已经启用。如果这个方法返回false,表示蓝牙已经禁用。请求启用蓝牙,要调用startActivityForResult()和使用action为 ACTION_REQUEST_ENABLE的Intent。这里将会通过系统设置弹出一个启用蓝牙的请求(不会停止你的应用程序)。例如:
    if (!mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }

    将会出现一个对话框请求用户授权启用蓝牙,如图。如果用户点击“Yes”,系统将会启用蓝牙,焦点也会在程序完成(或者失败)之后返回到你的应用程序。


    传递到startActivityForRequest()的REQUEST_ENABLE_BT常量是自己定义的一个整数(必须大于0),系统会将其作为requestCode参数传递到你的onActivityResult()方法中。
    如果启用蓝牙成功,你的activity就会在onActivityResult()中接收到一个 RESULT_OK的result code。如果蓝牙由于一个错误(或者是用户点击了“No”)而启用失败,那么result code将会是RESULT_CANCELED。

    另外,你的应用程序同样可以使用广播监听 ACTION_STATE_CHANGED这种类型的Intent,因为系统在蓝牙状态发生改变的时候会发送广播。这种广播包含extra数据 EXTRA_STATEEXTRA_PREVIOUS_STATE,分别表示新的和旧的的蓝牙状态。这些extra数据可能值包括 STATE_TURNING_ONSTATE_ON、STATE_TURNING_OFFSTATE_OFF。在你的应用程序运行的时候监听这个广播对于检测蓝牙状态引起的变化非常有用。

    提示:启用设备可被发现模式同样会自动启用蓝牙。如果你计划在进入蓝牙功能的Activity之前始终启用设备可被发现,你可以跳过上面步骤2。阅读关于下面启用可被发现模式部分。

    4、Finding Devices(搜索设备)


    你可以使用BluetoothAdapter通过设备搜索或者查询已配对设备列表来发现远程设备。

    设备搜索是一个扫描程序,它会搜索本地附近启用了蓝牙的设备,并从这些设备上获取一些信息(这个过程有时简称为“发现中”、“查询中”或者“扫描中”)。然而,在本地区域范围内的蓝牙设备只有在它是允许被发现的模式下才会响应其它设备扫描发现的请求。如果一个设备是可被发现的状态模式,它会通过共享一些信息来响应搜索发现的请求,例如设备名字,设备类型和它唯一的MAC地址。使用这些信息,执行扫描的设备才能创建一个连接连接上被发现的设备。

    如果是第一次与远程设备建立连接,(本地设备)将会自动提示用户一个配对的请求。当设备配对成功之后,远程设备的基本信息(例如设备名字,类型和MAC地址)将会被保存,并且可以使用Bluetooth APIs读取。使用已知的远程设备的MAC地址,可以在任何时候建立与远程设备的连接,而不需要预先执行扫描(即使远程设备现在不在附近范围内)。

    注意设备配对和设备连接两者之间有点不同。设备配对意味着两个设备彼此之间都知道对方的存在,并且共享一个连接key可以进行互相验证和创建一个加密的连接。设备连接意味着设备之间当前共享一个RFCOMM通道,能够互相传输数据。当前的Android Bluetooth API要求在创建一个RFCOMM连接之前要先配对。(当你使用Bluetooth APIs创建一个加密连接的时候,配对是自动执行的)

    下面的章节阐述如何发现一个已经配对的设备,或者使用设备扫描发现一个新设备。

    注意:Android设备默认是不可被发现的。用户可以通过系统设置让设备在短时间内可被发现,或者一个应用程序可以要求用户在不离开应用程序的情况启用设备的可被发现模式。如何启用设备可被发现模式请参考下面讨论。

    4.1 Querying paired devices(查询配对设备)


    在执行设备扫描之前,查询已配对设备列表看看目标设备是否已经被发现过是非常值得的。可以通过调用getBondedDevice()方法来实现,它会返回一系列代表已配对设备的BluetoothDevice。例如,你可以查询所有已配对设备,然后使用ArrayAdapter显示所有设备的名字给用户:
    Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
    // 如果有已配对设备
    if (pairedDevices.size() > 0) {
        // 循环查看已配对设备
        for (BluetoothDevice device : pairedDevices) {
            // 添加名字和地址到一个ArrayAdapter中,以便在ListView中显示
            mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
        }
    }

    为了能够建立一个连接,只需要从BluetoothDevice中获取MAC地址。在这个例子中,它被保存为ArrayAdapter的一部分,然后展示给用户。MAC地址可以在稍后再提取出来以进行连接。你可以从下面Connecting Device章节了解更多关于创建连接的内容。

    4.2 Discovering devices(搜索设备)


    开始搜索设备,只需要简单调用startDiscovery()方法。这个方法是异步的,调用这个方法后会马上返回一个Boolean值表示搜素过程有没有成功开始执行。搜索过程通常会扫描12秒,之后在查询页面中扫描每一个发现的设备获取它们的蓝牙名称。

    你的应用程序必须注册一个BroadcastReceiver监听 ACTION_FOUND类型的Intent,以接收每一个被发现设备的信息。每发现一个设备,系统都会广播一个 ACTION_FOUND类型的Intent。这个Intent携带了extra数据 EXTRA_DEVICEEXTRA_CLASS,分别包含了一个BluetoothDevice和一个BluetoothClass。例如,下面注册了一个广播来处理被发现的设备:
    // 创建一个监听ACTION_FOUND的BroadcastReceiver
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            // 当发现了一个设备
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                // 从Intent中获取BluetoothDevice对象
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // 将名字和地址添加到ListView的ArrayAdapter中
                mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
            }
        }
    };
    // 注册这个BroadcastReceiver
    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    registerReceiver(mReceiver, filter); // 不要忘记在onDestroy方法中注销这个广播

    为了能够建立一个连接,只需要从BluetoothDevice中获取MAC地址。在这个例子中,它被保存为ArrayAdapter的一部分,然后展示给用户。MAC地址可以在稍后再提取出来进行连接。你可以从下面Connecting Device章节了解更多关于创建连接的内容。

    注意:执行设备搜索对于BluetoothAdapter是一个重量级的程序,它会消耗大量的资源。一旦你发现了一个设备想要建立连接,在尝试连接之前应该确保已经调用cancelDiscovery()方法停止扫描。同样,如果你已经与一个设备建立了连接再执行搜索程序,这个连接的可用带宽会显著减少,因此你不应该在建立连接之后还进行设备搜索。

    4.3 Enabling discoverability(启用可被发现模式)


    如果你想让本机设备可被其它设备发现,要使用Action为 ACTION_REQUEST_DISCOVERABLE的Intent调用startActivityForResult(Intent, int)方法。这将会通过系统设置发出启用可被发现模式的请求(并且不会停止你的应用程序)。默认情况下,设备会进入可被发现模式120秒。你可以为Intent添加 EXTRA_DISCOVERABLE_DURATION附加数据自定义时长。在一个应用程序中最多可以设置3600秒,如果将值设置为0就表示设备永远进入可被发现模式。任何小于0或者大于3600的值都会自动设置为120秒。例如下面的代码片段将时长设置为300:
    Intent discoverableIntent = new
    Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
    startActivityForResult(discoverableIntent, REQUEST_DISCOVERABLE);//REQUEST_DISCOVERABLE为自定义的requestcode值

    如图,会显示一个对话框请求用户授权让设备进入可被发现模式。如果用户点击“Yes”,设备将会在执行的时间内进入可被发现模式。你的Activity将会在onActivityResult()方法中得到回调,并且resultCode等于设备可被发现的时长。如果用户点击“NO”或者遇到了错误,resultCode将会是 RESULT_CANCELED


    注意:如果设备没有启用蓝牙,那么启用设备的蓝牙可被发现模式会自动启用设备蓝牙。


    设备将会在配置的时间内一直进入可被发现模式。如果你想要在可被发现模式发生改变的时候得到通知,你可以注册一个BroadcastReceiver监听 ACTION_SCAN_MODE_CHANGE类型的Intent。这种Intent包含了 EXTRA_SCAN_MODEEXTRA_PREVIOUS_SCAN_MODE附加数据,分别代表新的和旧的扫描模式。它们的值可能为 SCAN_MODE_CONNECTABLE_DISCOVERABLESCAN_MODE_CONNECTABLE或者 SCAN_MODE_NONE,本别代表设备进入可被发现可连接模式、不可被发现但是可连接模式、不可被发现也不可连接模式。

    如果你只是要与远程设备建立连接,那么你不需要启用设备的可被发现模式。只有当你希望你的应用程序托管一个Server socket来接收传入的连接才需要启用设备可被发现模式,因为远程设备在建立连接之前必须要能够发现这个设备。

    5、Connecting Devices(连接设备)


    为了能够让你的应用程序在两台设备之间建立连接,你必须实现服务端和客户端的机制,因为一台设备必须启动一个server socket,另一台设备必须创建连接(使用服务端设备的MAC地址来创建一个连接)。只有服务端设备和客户端各自都有一个连接在同一个RFCOMM通道上的BluetoothSocket才算完成连接。在这个时候,每一台设备才能获得一个输入和输出流,数据传输才可以开始进行,这部分会在下面Managing a Connection章节讨论。这个章节讨论如何在两台设备间建立连接。

    服务端设备和客户端设备获取BluetoothSocket的方法不同。服务端设备将会在接受一个传入的连接请求时获得。客户端设备会在它打开与服务端设备的RFCOMM通道时获得。

    一种解决方案是自动将每一台设备作为服务端,这样每一台设备都拥有一个server socket可以监听连接。之后,每一台设备都可以启动一个与其它设备的连接并变成客户端设备。另外,也可以明确一台设备作为服务端根据需要启动一个server socket,其它设备只要简单的建立连接。

    注意:如果两台设备之前没有配对,那么Android Framework将会在连接过程中自动提示配对请求信息或者对话框,如图。因此尝试连接其它设备的时候,你的应用程序不需要考虑设备是否已经配对。你的RFCOMM连接将会被阻塞,直到用户配对成功或者失败(用户拒绝配对,或者配对失败,或者超时)。



    5.1 Connecting as server(作为服务端连接)


    当你尝试去连接两台设备,其中一台设备必须作为服务端持有一个BluetoothServerSocket对象。server socket的目的是为了监听传入的连接请求,并在接收请求之后提供一个已连接的BluetoothSocket。当从BluetoothServerSocket获取到BluetoothSocket之后,BluetoothServerSocket应该回收掉,除非你还要接收其它连接。

    下面是设置一个server socket并接收一个连接的一般过程:

    5.1.1 调用listenUsingRfcommWithServiceRecord(String, UUID)方法获取一个BluetoothServerSocket。方法中的字符串参数是一个表示你的服务的名字,系统将会自动把这个字符串写入到设备上一个新的Service Discovery Protocol(SDP)数据库实体中(名字可以是任意的,可以简单使用你的应用程序名字)。另一个参数UUID也会被包含到SDP实体中,并且会作为与客户端设备连接协议的基础。也就是,当客户端设备尝试连接服务端设备的时候,它会携带一个UUID来标识服务端设备上的哪一个服务才是客户端设备要连接的。这些UUID必须匹配后才能接受连接请求(在下一步中讨论)。

    About UUID:一个Universally Unique Identifier(UUID,通用唯一的ID)是一个标准128格式的字符串ID,用于唯一标识的信息。UUID的关键是它是一个足够大的值,你可以选择任意随机值也不会引起冲突。在这个例子中,它被用来唯一标识你的应用程序的蓝牙服务。为你的应用程序获取一个UUID,你可以使用网上随机生成的众多UUID中的一个,然后使用UUID类的fromString(String)方法生成一个UUID。

    5.1.2 调用accept()方法开始监听连接请求。这个方法会阻塞,直到接受了一个连接请求或者出现异常才会返回。只有当一个远程设备发送一个携带UUID的连接请求,并且请求的UUID能够匹配注册server socket时的UUID,这个连接请求才会被接受。连接成功后,accept()方法就会返回一个已经连接上的BluetoothSocket。

    5.1.3  除非你想要接受其它连接,否则你应该马上调用close()方法。这样才能够释放server socket和它所占用的资源,但是不会关闭accept()方法返回已连接的BluetoothSocket。不像TCP/IP协议,RFCOMM在同一时间同一条通道上只允许连接一个客户端,因此在BluetoothServerSocket接受一个连接socket之后立即调用close()方法是有意义的。

    accept()方法不应该在UI线程中执行,因为这个方法会被阻塞,妨碍其它应用程序的交互。通常都是由你的应用程序新开一个线程执行所有BluetoothServerSocket或者BluetoothSocket的操作。终止一个会阻塞的方法,例如accept(),可以在其他线程调用BluetoothServerSocket或者BluetoothSocket的close()方法,阻塞的方法会立即返回。注意所有BluetoothServerSocket或者BluetoothSocket中的方法都是线程安全的。

    例子:
    以下是关于服务端机制接收传入连接的简单线程:
    private class AcceptThread extends Thread {
        private final BluetoothServerSocket mmServerSocket;
     
        public AcceptThread() {
            // 使用一个临时对象,稍后赋值给mmServerSocket,
            // 因为mmServerSocket被声明为final
            BluetoothServerSocket tmp = null;
            try {
                // MY_UUID是应用的UUID字符串,同样也会在客户端的代码中使用到
                tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
            } catch (IOException e) { }
            mmServerSocket = tmp;
        }
     
        public void run() {
            BluetoothSocket socket = null;
            // 保持监听知道出现异常或者返回一个socket
            while (true) {
                try {
                    socket = mmServerSocket.accept();
                } catch (IOException e) {
                    break;
                }
                // 如果接受了一个连接
                if (socket != null) {
                    // 执行连接的管理操作(在一个独立的线程中)
                    manageConnectedSocket(socket);
                    mmServerSocket.close();
                    break;
                }
            }
        }
     
        /** 将会终止socket的监听,并让线程结束 */
        public void cancel() {
            try {
                mmServerSocket.close();
            } catch (IOException e) { }
        }
    }

    在这个例子中只会接受一个传入的连接,因此只要接受了一个连接就能获取一个BluetoothSocket,应用程序会将获取到的BluetoothSocket发送到一个独立的线程中,然后关闭BluetoothServerSocket并终止循环。

    注意accept()方法返回的BluetoothSocket已经连接成功,你不能再调用它的connect()方法(这是你在客户端中需要调用的方法)。

    manageConnectedSocket()方法是应用程序假定的方法,它应该要为传输数据创建一个线程,这会在后面Managing a Connection章节讨论。

    你应该尽可能在你监听完传入的连接之后关闭你的BluetoothServerSocket。在这个例子中,获取到BluetoothSocket之后就马上调用了close()方法。你同样可能需要为你的线程提供一个公共的方法来关闭私有的BluetoothServerSocket,以便在你需要停止server socket监听的时候停止。

    5.2 Connecting as a client(作为客户端连接)


    为了能够与一个远程设备(一个持有server socket的设备)建立连接,你首先需要获取一个代表远程设备的BluetoothDevice对象。(获取一个BluetoothDevice对象的方法已经在上面Finding Device章节阐述过)。之后你需要使用BluetoothDevice来请求一个BluetoothSocket并建立连接。

    以下是基本步骤:

    5.2.1 使用BluetoothDevice对象调用createRfcommSocketToServiceRecord(UUID)方法获取一个BluetoothSocket对象。这里获取的BluetoothSocket对象将会连接到对应的BluetoothDevice。这里的UUID必须匹配服务端设备启用BluetoothServerSocket(调用listenUsingRfcommWithServiceRecord(String, UUID))时所用的UUID。使用相同的UUID可以在你的应用程序中写死一个UUID字符串,然后在服务端和客户端中使用。

    5.2.2 调用connect()方法创建连接。在这里调用这个方法后,系统将会在远程设备上执行SDP查询匹配UUID。如果查询成功,远程设备就会接受这个连接,它会在连接期间共享RFCOMM通道,然后connect()返回。这个方法也是个阻塞的方法。如果因为某种原因连接失败或者connect()方法超时(大概12秒后),这个方法就会抛出一个异常。因为connect()方法也是个阻塞的方法,所以执行这个方法也要在一个在主线程之外独立一个线程执行。

    注意:你需要经常确保你在调用connect()方法的时候设备没有执行设备搜索。如果正在搜索设备,那么尝试连接的过程将会显著变慢,连接失败的几率也会更大。

    例子
    以下是创建一个蓝牙连接的简单线程例子:
    private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;
     
        public ConnectThread(BluetoothDevice device) {
            // 使用临时对象稍候赋值给mmSocket,
            // 因为mmSocket的类型是final
            BluetoothSocket tmp = null;
            mmDevice = device;
     
            // 获取一个BluetoothSocket来连接给定的BluetoothDevice
            try {
                // MY_UUID是应用的UUID字符串, 也是服务端使用的UUID字符串
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
            } catch (IOException e) { }
            mmSocket = tmp;
        }
     
        public void run() {
            // 取消扫描设备,因为扫描会让连接速度变慢
            mBluetoothAdapter.cancelDiscovery();
     
            try {
                // 通过socket连接设备,这将会阻塞
                // 直到连接成功或者抛出异常
                mmSocket.connect();
            } catch (IOException connectException) {
                // 连接失败;关闭socket并退出
                try {
                    mmSocket.close();
                } catch (IOException closeException) { }
                return;
            }
     
            // 进行连接管理的操作 (在一个指定的线程中)
            manageConnectedSocket(mmSocket);
        }
     
        /** 将会取消正在进行中的连接,并关闭socket */
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) { }
        }
    }

    注意cancelDiscovery()方法需要在连接之前调用。你应该经常在连接之前调用这个方法,调用这个方法的时候不需要确定当前是否正在扫描其它设备(但是如果你真的要检查当前是否正在扫描设备,你也可以调用isDiscovery()方法)。

    manageConnectedSocket()方法是应用程序假定的方法,它应该要为传输数据创建一个线程,这会在后面Managing a Connection章节讨论。

    当你对BluetoothSocket的操作完成之后,你应该调用close()方法来释放资源。当你调用这个方法后,会立刻关闭连接中的socket并回收所有网络资源。

    6、Managing a Connection(管理连接)


    当你成功连接两台或者更多设备,每一台设备都会有一个已连接的BluetoothSocket。这就是所有有趣事情的开端,因为你可以开始在设备之间共享数据。使用BluetoothSocket,一般的传出数据过程是很简单的:

    6.1 通过socket用getInputStream()和getOutputStream()方法分别获取InputStream和OutputStream对象来处理数据传输。
    6.2 使用read(byte[])和write(byte[])方法从数据流中读写数据。


    以上就是整个过程。

    当然,也有一些细节的东西需要考虑。首先,你需要为所有数据流的读写操作分派一个特定的线程。这是非常重要的,因为read(byte[])和write(byte[])方法都是会阻塞的方法。read(byte[])方法在从数据流中读取到数据之前会一直阻塞。write(byte[])方法不会经常阻塞,但是在远程设备没有及时调用read(byte[])方法并且中间缓冲区满的时候也会进行阻塞。因此,你的线程中的主循环应该专门用来从InputStream中读取数据。线程中再指定一个公共方法将数据写入到OutputStream中。

    例子
    以下是整个流程大概怎么进行的例子:
    private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;
     
        public ConnectedThread(BluetoothSocket socket) {
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;
     
            // 使用临时对象获取输入输出流,
            // 因为成员输入输出流的类型是fianl
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) { }
     
            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }
     
        public void run() {
            byte[] buffer = new byte[1024];  // 数据流的数据缓冲区
            int bytes; // 从read()方法返回的字节数
     
            // 保持对InputStream的监听,直到有异常抛出
            while (true) {
                try {
                    // 从InputStream中读取数据
                    bytes = mmInStream.read(buffer);
                    // 将获取的字节发送到UI activity
                    mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                            .sendToTarget();
                } catch (IOException e) {
                    break;
                }
            }
        }
     
        /* 在main activity中调用这个方法将数据发送到远程设备 */
        public void write(byte[] bytes) {
            try {
                mmOutStream.write(bytes);
            } catch (IOException e) { }
        }
     
        /* 在main activity中调用这个方法来停止连接 */
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) { }
        }
    }

    构造方法中获取必要的数据流,并且一旦执行,线程就会开始等待从InputStream中过来的数据。当read(byte[])方法从数据流中获取到字节数据后,这些数据就会被从main activity得到的Handle对象发送到main activity中。然后线程循环阻塞,继续等待从数据流中传输过来的更多字节数据。

    发送输出数据只需要在main activity中简单调用线程的write()方法,并将字节数据传递进去。这个方法会简单的调用write(byte[])方法将数据发送到远程设备。

    线程中的cancel()方法是非常重要的,有这个方法才能在任何时候通过关闭BluetoothSocket来中止连接。当你用完蓝牙连接之后,你应该要调用这个方法。

    使用Bluetooth APIs的实例,请参考 Bluetooth Chat sample app

    7、Working with Profile(使用Profile)


    从Android 3.0开始,Bluetooth API已经支持Bluetooth profiles。一个Bluetooth profile就是一种设备之间基于蓝牙通信的无线接口规范。比如Hands-Free profile(免提)。当一部手机想要连接无线耳机的时候,两台设备都必须支持Hands-Free profile。

    你可以实现BluetoothProfile接口定义自己的类来指定一种特定的Bluetooth profile。Android Bluetooth API已经提供了以下Bluetooth profile的实现:

    * Headset。Headset profile对用于手机的蓝牙耳机提供了支持。Android提供了BluetoothHeadset类,这个类是一个代理,通过进程间通信(IPC)来控制蓝牙耳机服务。这里面包括了蓝牙耳机和Hands-Free(v1.5) profile。BluetoothHeadset类同样提供了对AT命令的支持。更多关于这方面主题的讨论,请参考后面Vendor-specific AT commands章节。

    * A2DP。Advanced Audio Distribution Profile(A2DP,高级音频分发),它定义了高质量音频如何通过蓝牙连接以数据流的方式从一台设备到另一台设备进行传输。Androd提供了BluetoothA2dp类,同样是个代理,通过进程通信控制Bluetooth A2DP。

    * Health Device。Android 4.0开始支持Bluetooth Health Device Profile(HDP,蓝牙健康设备)。这允许你使用蓝牙创建与支持蓝牙的健康设备进行通信的应用程序,例如心律监控器、血压计、温度计、体重计等等。查看所支持的设备,以及它们相应的专业测量数据编码,请参考 www.bluetooth.org中的Bluetooth Assigned Numbers。注意这些数值同样在ISO/IEEE 11073-20601[7]规范中作为MDC_DEV_SPEC_PROFILE_*命名编码附件引用。更多关于HDP的讨论,请参考后面Health Device Profile章节。

    以下是使用profile的基本步骤:
    (1)获取一个默认的BluetoothAdapter,在上面Setting Up Bluetooth(设置蓝牙)部分已经提到。
    (2)使用getProfileProxy()方法建立与profile相关联代理对象的连接。在下面的例子中,这个profile代理对象是一个BluetoothHeadset对象的实例。
    (3)配置一个BluetoothProfile.ServiceListener对象。这个监听器对象会通知BluetoothProfile的IPC客户端是否已经连接或者断开服务。
    (4)在onServiceConnected()方法中获取一个profile代理对象。
    (5)一旦你获得profile代理对象,你就可以使用它来监视连接的状态和执行其它一些与profile相关的方法。

    例如,以下的代码片段展示了如何连接一个BluetoothHeadset代理对象然后控制Headset profile。
    BluetoothHeadset mBluetoothHeadset;
     
    // 获取默认的adapter
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     
    // 建立与代理的连接
    mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
     
    private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            if (profile == BluetoothProfile.HEADSET) {
                mBluetoothHeadset = (BluetoothHeadset) proxy;
            }
        }
        public void onServiceDisconnected(int profile) {
            if (profile == BluetoothProfile.HEADSET) {
                mBluetoothHeadset = null;
            }
        }
    };
     
    // ... 调用mBluetoothHeadset的方法
     
    // 使用完之后关闭代理连接
    mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);

    7.1 Vendor-specific AT commands(特定于供应商的AT命令)


    从android 3.0开始,应用程序可以注册系统广播接收耳机预定义的特定于供应商的AT命令发出的广播(例如Plantronics +XEVENT命令)。例如,一个应用程序可以接收关于连接设备电池情况的广播,然后通知用户或者执行一些需要的操作。创建接收 ACTION_VENDOR_SPECIFIC_HEADSET_EVENT类型Intent的广播来处理耳机的vendor-specific AT命令。

    7.2 Health Device Profile(健康设备的Profile)


    Android 4.0开始支持Bluetooth Health Device Profile(HDP,健康设备的profile)。这允许你创建使用蓝牙与支持蓝牙的健康设备进行通信的应用程序,例如心律监控器、血压计、温度计、体重计等等。Bluetooth Health API包括了BluetoothHealth类、BluetoothHealthCallback类和BluetoothHealthAppConfiguration类,这些都在上面The Basic(基础部分)章节提到过。

    使用Bluetooth Health API,了解以下HDP关键概念是非常有用的:

    概念描述
    SourceHDP定义的一个角色。一个source就是一个健康设备,它能够发送医学数据(体重、血糖指标、体温等等)到一个智能设备,例如android手机或者平板。
    SinkHDP定义的一个角色。一个sink就是一个智能设备,能够接收医学数据。在一个Android HDP应用程序中,sink由一个BluetoothHealthAppConfiguration对象担任。
    Registration指的是为一个特定的健康设备注册一个sink。
    Connection指的是打开一条健康设备和智能设备(例如Android手机或平板)的通信通道。


    7.3 Creating an HDP Application(创建一个HDP应用程序)


    以下是创建一个Android HDP应用程序需要调用的基本步骤:
    (1)获取一个BluetoothHealth代理对象的引用。类似于耳机和A2DP profile设备,你必须使用一个BluetoothProfile.ServiceListener和 BluetoothProfile.HEALTH的profile类型调用getProfileProxy()方法,创建一个于profile代理对象的连接。
    (2)创建一个BluetoothHealthCallback,并注册一个应用程序配置(BluetoothHealthAppConfiguration)作为health sink。
    (3)创建一个与健康设备的连接。一些设备会启动这个连接,对于这种设备不需要进行此步骤。
    (4)当成功连接上一个健康设备之后,使用文件描述读写这个健康设备。接收到的数据需要使用实现IEEE 11073-xxxxx规范的健康管理器解析。

    (5)完成之后,关闭健康通道并注销应用程序。当有扩展活动的时候,这个通道同样会被关闭。


    本步骤中完整的代码示例请参考 Bluetooth HDP(Health Device Profile)


    原文地址:http://developer.android.com/guide/topics/connectivity/bluetooth.html



    展开全文
  • BLE的应用本质就是用来传输数据,而数据的传输最终利用就是属性条目中的属性值。... 因为服务器中的数据库本质上都是一条条属性条目,但是每个属性条目都具有自己的意义,像是前面说的用来声明一个服务的属性条目,就称
  • 蓝牙

    千次阅读 2017-03-25 12:43:35
    Android 平台包含蓝牙网络堆栈支持,凭借此项支持,设备能以无线方式与其他蓝牙设备交换数据。应用框架提供了通过 Android Bluetooth API 访问蓝牙功能的途径。 这些 API 允许应用以无线方式连接到其他蓝牙设备,...
  • 传统蓝牙SDP协议详细介绍

    千次阅读 2020-08-20 08:38:18
    第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些概念,产生背景,发展轨迹,市面蓝牙介绍,以及蓝牙开发板介绍。 第二篇:Transport层介绍,主要介绍蓝牙协议栈跟蓝牙芯片之前的硬件传输协议,比如基于UART的H4,H5,BCSP,...
  • android蓝牙开发之经典蓝牙

    千次阅读 2016-02-04 13:41:43
    android开发之经典蓝牙 安卓平台包括支持蓝牙网络协议栈,它允许设备与其他蓝牙设备进行无线交换数据。应用程序框架提供通过安卓蓝牙接口访问蓝牙功能。这些应用程序的无线连接到其他蓝牙设备,使点对点和多点...
  • 蓝牙笔记

    千次阅读 2017-01-04 14:53:26
    一、蓝牙4.0BLE简介1.1 什么是蓝牙4.0BLE蓝牙低功耗技术(Bluetooth Low energy),拥有超低运行功耗和待机功耗。1.2 蓝牙4.0BLE特点 高可靠性 低成本。低功耗 快速启动,瞬间连接 传输距离提高 高安全性 1.3 蓝牙...
  • Android 蓝牙

    2015-11-03 16:47:10
    Android平台提供了对蓝牙网络协议栈的支持,它允许设备通过无线的方式与其它设备进行数据交换。应用程序框架通过Android Bluetooth APIs提供了对蓝牙功能的访问。这些API允许应用以无线的方式连接其它蓝牙设备,启用...
  • java怎么与蓝牙交互数据TL;DR: Try the Web Bluetooth API in the browser or check out the sample React app: https://github.com/rdeprey/web-bluetooth-starter. TL; DR: 在浏览器中尝试Web蓝牙API 或查看示例...
  • Android平台支持蓝牙设备通过蓝牙协议栈来实现无线通信。应用层提供了Bluetooth APIs来使用蓝牙功能, 这些API可以使应用可以无线连接其他蓝牙设备实现点对点及多点间通信。 使用Bluetooth APIs,应用可以实现以下...
  • Android 蓝牙开发之经典蓝牙
  • android 十八 蓝牙及Wi-Fi

    千次阅读 2017-05-07 14:55:58
    学习内容Ø 蓝牙的基本概念Ø Android中蓝牙的应用 能力目标Ø 了解蓝牙的基本概念Ø 掌握Android中蓝牙的应用 Ø 掌握如何使用Android中Wi-Fi本章简介 蓝牙是一种重要的短距离无线通信技术,它被广泛应用于...
  • 有时候我们一些设置属性要保存到本地,防止软件卸载后配置属性消失。 比如我今天就有个修改手机蓝牙名称显示的功能,但是只需要修改一次,并且进行标记,并保存这个标识,如果程序卸载,重新安装也不需要再修改,...
  • 蓝牙mesh

    2019-06-28 17:03:44
    蓝牙mesh学习笔记 主机:就绪态,扫描态,发起态,连接态 从机:就绪态,广播态,连接态 前导:     训列序列,防止白化。 接入地址: 广播接入地址:广播信道接入地址为0x8E89BED6   &...
  • android专题-数据库Room

    2021-02-02 10:21:18
    android数据库 room
  • WINCE蓝牙学习一---WINCE6.0蓝牙协议栈

    千次阅读 2013-02-19 17:07:15
    蓝牙系统中,为了支持不同应用,需要使用多个协议,这些协议按层次组合在一起,构成了蓝牙协议栈.蓝牙协议栈是蓝牙技术的核心组成部分,它能使设备之间互相定位并建立连接,通过这个连接,设备间能通过各种各样的应用程序...
  • 蓝牙篇之蓝牙核心规范(V5.2)深入详解汇总 1.GATT定义 通用属性协议(GATT)使用属性协议定义了一个服务框架。该框架定义了服务的程序和格式及其特征。所定义的过程包括发现、读、写、通知和指示特征,以及配置...
  • 蓝牙(三)蓝牙协议的初始化

    千次阅读 2018-04-16 10:45:16
    介绍蓝牙协议的初始化之前首先要简单讲一下蓝牙一些常用协议以及各个协议的作用,同时还要讲以下Application的知识以便对蓝牙协议的初始化有一个更清晰的认识。一、蓝牙协议简介:HSP(手机规格)– 提供手机(移动...
  • 蓝牙4.0实际是个三位一体的蓝牙技术,它将三种规格合而为一,分别是传统蓝牙、低功耗蓝牙和高速蓝牙技术,这三个规格可以组合或者单独使用。 (二)BLE的体系结构 (三)BLE设备链路层状态 (四)信道 ...
  • 过去,系统的软件设计是以数据库设计为核心,当需求确定下来以后,团队首先开始进行数据库设计。因为数据库是各个模块唯一的接口,当整个团队将数据库设计确定下来以后,就可以按照模块各自独立地进行开...
  • 低功耗蓝牙协议栈基础知识

    千次阅读 2017-02-22 18:56:46
    低功耗蓝牙协议栈包含两部分共8层:主机(Host)和控制器(Controller)。 控制器部分包括: 物理层(Physical Layer) 链路层(Link Layer) 主机控制接口层(Host Controller Interface) 主机部分包括: L2CAP ...
  • 蓝牙IBEACON协议详细解析

    千次阅读 2020-10-14 11:38:16
    蓝牙IBEACON协议详细解析 ...4C 00 这两位代表beacon的公司名称,4C就是苹果的ibeacon,nordic的是0x59,其他公司的需要查询蓝牙联盟的数据库。 02 15 这个代表了是ibeacon的服务类型,这个也是固定的,就是说我们
  • BlueTooth: 蓝牙协议栈实现模式分析

    千次阅读 2016-02-27 19:34:01
    蓝牙协议栈实现模式分析 蓝牙技术是一项新兴的技术。它的主要目的就是要在全世界范围内建立一个短距离的无线通信标准 。它使用 2.4-2.5 GHz 的 ISM( Industrion Scientifc Medical ) 频段来传送话音和数据。...
  • 蓝牙协议

    千次阅读 2020-05-20 21:15:13
    蓝牙相关: 1.蓝牙连接的设备: UI侧 在BtHelper:执行getHFPConnectedAddress,调用BTManager.hfpGetConnectedDevices()触发蓝牙连接 BT侧 BTManager调用BluetoothHeadsetClient.getConnectedDevices(),通过...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,818
精华内容 2,727
关键字:

蓝牙属性数据库