微信公众号:
卢同学
关注可了解更多的图解笔记以及编程相关技巧。问题或建议,请公众号留言;
如果你觉得对你有帮助,欢迎赞赏。
分层思想
无论是OSI七层模型,还是在BLE协议,为了能快速理解,可以利用分而治之的思想把大问题分割成多个小问题。各层向上提供服务,向下提供兼容。
从整体来看,低功耗蓝牙体系的整体结构主要分为三个部分:主机host,控制器,以及基于此向上提供的应用层。主机就是开发接触比较多的软件栈部分,用来管理设备间通讯以及如何利用无线电提供服务。
控制器就是负责发送和接收无线电信号,以及如何把携带信息的数据包翻译成无线电信号的物理设备。
BLE协议栈概览
协议封层见下图:
GATT(Generic attribute profile )
GATT用来规范attribute中的数据内容,并运用group(分组)的概念对attribute进行分类管理。其引入了特性、服务、以及他们之间的关系、特征描述符等。还定义了一些
规程
用来发现服务、特性、服务之间的关系,以及用来读取和写入特征值。
ATT(Attribute protocol)
ATT层用来定义用户命令及命令操作的数据,比如读取某个数据或者写某个数据。BLE引入了attribute概念,用来描述一条一条的数据。Attribute除了定义数据,同时定义该数据可以使用的ATT命令,因此这一层被称为ATT层。
SMP(Secure manager protocol)
SMP用来管理BLE连接的加密和安全。
L2CAP(Logic link control and adaptation protocol)
L2CAP对LL进行了一次简单封装,LL只关心传输的数据本身,L2CAP就要区分是加密通道还是普通通道,同时还要对连接间隔进行管理。
GAP(Generic access profile)
GAP简单的对LL payload进行一些规范和定义,目前主要用来进行广播,扫描和发起连接等。
HCI(Host controller interface)
HCI主要用于2颗芯片实现BLE协议栈的场合,用来规范两者之间的通信协议和通信命令等。
LL(Link Layer链路层)
具体选择不同的射频通道进行通信,识别空中数据包,选择在哪个时间点把数据包发送出去,以及保证数据的 完整性,ACK 如何接收,如何实现重传,以及如何对链路进行管理和控制等等。
LL层只负责把数据发出去或者收回来,对数据进行怎样的解析则交给上面的GAP或者GATT。
PHY(Physical layer物理层)
PHY层用来指定BLE所用的无线频段,调制解调方式和方法等。决定整个BLE芯片的功耗,灵敏度以及selectivity等射频指标。
BLE client/server(C/S) 架构
如下图所示:、
客户端要访问某一个数据,就发送一个request/请求(其实就是一条命令或者PDU),服务端再把该数据返回给客户端(一条response/响应命令或者PDU),这就是C/S架构。这与http请求响应机制有相似之处。
GATT,Service(服务)和Characteristic(特征数据)
-
GATT Server通过属性表(Attribute table)来组织数据,就是对数据进行逻辑化表达的规定。
-
GATT,全称generic attribute profile,对数据进行一般化/抽象化的子规范,
attribute是一条一条的数据,GATT将对数据赋予含义,并呈现一定的逻辑结构。
-
在蓝牙规格中,每一个具体的蓝牙应用是由多个service组成的,而每一个service又是由多个characteristic组成的
-
service/characteristic是attribute的逻辑表现形式,而attribute是service/characteristic具体实现方式。尤其要注意的是,一条characteristic不是对应一条attribute,而是由多条attribute组成。虽然一个数据最有价值的部分是它的值(value)。
BLE组织数据形式如下图:
1、Characteristic declaration 就是每个 characteristic 的分隔符,同时characteristic declaration还将包含value attribute的读写属性。
2、Characteristic value就是数据的值了,它也是一个单独的attribute。
3、Characteristic descriptor就是数据的额外信息,比如温度的单位是什么,数据是用小数表示还是百分比表示等之类的数据描述信息。Descriptor属于可选条目,可以为没有也可以有多个。
一种特殊的descriptor:CCCDclient可以通过禁止CCCD以不接收 notify或者indicate命令,client也可以通过使能CCCD以允许notify或者indicate命令。
当characteristic具有notify或者indicate操作功能时,蓝牙规范要求必须为其添加CCCD attribute。
ATT
全称attribute protocol(数据交互协议)。说到底,ATT是由一群ATT命令组成,就是上文所述的request(请求)和response(响应)命令,ATT也是蓝牙空口包中的最上层。
ATT命令,正式名称:ATT PDU(Protocol Data Unit,协议数据交互单元),
4类:读,写,notify(通知)和indicate(指示)
attribute,那么什么是attribute?其实就是一条一条的数据。而服务就是众多数据的合集,这个合集可以称为数据库,数据库里面每个条目都是一个attribute。attribute形式如下图,这些数据条目组成属性表。
Attribute handle
,Attribute句柄,16-bit长度。Client要访问Server的Attribute,都是通过这个句柄来访问的,也就是说ATT PDU一般都包含handle的值。用户在软件代码添加characteristic的时候,系统会自动按顺序地为相关attribute生成句柄。
Attribute type
,Attribute类型,2字节或者16字节长。在BLE中我们使用UUID来定义数据的类型,UUID是128 bit的,所以我们有足够的UUID来表达万事万物。其中有一个UUID非常特殊,它被蓝牙联盟采用为官方UUID,这个UUID:0000xxxx-0000-1000-8000-00805F9B34FB
由于这个UUID众所周知,蓝牙联盟将自己定义的attribute或者数据只用16bit UUID
来表示。 比如0x1234,其实它也是128bit0000xxxx-1234-1000-8000-00805F9B34FB=0x1234
Attribute permissions
,Attribute的权限属性
Open,直接可以读或者写
No Access,禁止读或者写
Authentication,需要配对才能读或者写,由于配对有多种类型,因此authentication,又衍生多种子类型Authorization,跟open一样,不过server返回attribute的值之前需要应用先授权,也就是说应用可以在回调函数里面 去修改读或者写的原始值。
Signed,签名后才能读或者写,这个用得比较少。
BLE Link layer协议解析
BLE链路层(link layer)定义了一种packet(空中包)格式
蓝牙广播包
蓝牙广播包,全名蓝牙广播通道(channel)空中包,即在蓝牙广播通道上传输的空中包,为两种空中包的一种,其具体格式如下所示:
Advertising Header即前述的LL header,长度为一个字节,其每bit定义如下所示:
PDU Type为3bit,具体定义如下。可以看出扫描PDU和发起连接PDU都属于广播包.
TxAdd/RxAdd,各占1bit:
表示随后的Device Address字段代表的蓝牙MAC地址类型,值0代表Public地址,值1代表Random地址。
Payload length定义如下所示,所以广播包PDU最长37个字节。
Device Address,广播包中的强制字段,俗称蓝牙MAC地址,如果是广播包,则是advertiser的MAC地址;如果是scan包或者连接请求包,则是scanner的MAC地址。
蓝牙device address为6个字节,这样Advertising data最长为:37-6 = 31B,
这就是广播包数据最长只能31个字节的由来。
如前所述,device address分public和random两种。
蓝牙数据通道空口包(数据包)
与蓝牙广播包相对应,蓝牙数据包是另一种BLE packet。
蓝牙数据包是蓝牙数据信道空中包的简称,表示空中包只在蓝牙数据信道上传输,即除37/38/39之外的其他37信道。
从格式上来说,蓝牙数据包又分空包(empty packet)和普通数据包(data packet)两种,空包格式如下
空包:由图可见,空包整个payload为空,故名空包。
普通数据包:格式如下,
Data header,即前述的LL header,在数据包中的定义如下所示(bit):
LLID(2bits), link layer ID,对LL PDU进行分类:LL data PDU和LL control PDU。
LL Data PDU有两种:
Header中的LLID=01b时,Continuation fragment of an L2CAP message, or an Empty PDU。
这种类型的PDU,要么是一个未传输完成L2CAP message(长度超过255,被拆包,此时不是第一个),要么是一个空包(Header中的Length为0)。
Header中的LLID=10b时,Start of an L2CAP message or a complete L2CAP message with no fragmentation。这种类型的PDU,要么是L2CAP message的第一个包,要么是不需要拆包的完整的L2CAP message,
无论哪种情况,Header中的Length均不能为0。
LL control PDU:
Header中的LLID=11b时,表示这个数据包是用于控制、管理LL连接的LL control PDU。LL Control PDU是在Link layer层直接进行交互的,也就是说他们不会经过后面的L2CAP层LL control PDU的payload的格式如下:
Opcode指示控制&管理packet的类型,包括:
LL_CONNECTION_UPDATE_REQ,连接参数的更新;
LL_CHANNEL_MAP_REQ,Channel map的更新;
LL_TERMINATE_IND,连接即将被关闭的通知(可以通知被关闭的原因);
LL_ENC_REQ、LL_ENC_RSP、LL_START_ENC_REQ、LL_START_ENC_RSP,加密有关的请求;
等等,具体可参考“BLUETOOTH SPECIFICATION Version 4.2 [Vol 6, Part B]”。
NESN/SN,NESN和SN各占1bit。SN全称为sequence number,表示当前发送的packet编号。
NESN,next expected sequence number,用来告知对方下一个期待的packet的编号。
Link layer使用SN来告知对方这个packet是新数据包还是重传包,
用NESN来告诉对方你之前发我的包已经收到了(相当于ACK的作用),
我现在期待下一个新的数据包了,因此BLE没有专门的ACK包,它是通过NESN/SN来实现ACK和重传双重功能的。
MD(1bit),more data,用来指示对方我还有数据包要传,请继续打开射频窗口准备接收。
一个connection event包含多个数据包交互),用的就是MD来实现的。
以notify命令为例,设备(Server)notify第一个数据包并将MD置1,Client(比如手机)收到这个notify命令后,就知道Server还有数据包要传,此时手机可以继续发一个空包给设备,以让设备把第二个notify命令发过来,详情如下所示。
注:Master为手机(Client),Slave为设备(Server)。
Payload Length or Data Length,BT4.0/4.1定义如之前所示,这就是蓝牙4.0/4.1一个包只能传20个字节的根源。
BT4.2之后,Payload length 8 bits全部用来表示长度,这样的话,payload size最大可达251字节(255 – MIC size)
L2CAP length,2字节长度,表示后面information payload的长度,
information payload最大长度除了受这个L2CAP length字段约束,同时还受MTU的限制。MTU,Maximum Transmission Unit,是ATT层与L2CAP层可以交互的最大数据长度,
或者说是Client与Server可以交互的最大长度。
总结一下,蓝牙spec里面定义了2个长度字段:LL data length和L2CAP length,
同时ATT层还定义了一个MTU,以限制ATT PDU最大长度。
LL data length可以通过LL_LENGTH_REQ和LL_LENGTH_RSP来动态改变,
MTU size则可以通过后面要讲到的Exchange MTU Request和Exchange MTU Response来改变,
而L2CAP length无法动态改变,也就是说不能超过65535。
从数据格式了解ATT
Opcode总体分为6种大类型的:
-
Opcoderequest / response
-
indicate / confirm
-
command
-
notify
所有的ATT数据,都是属于这6种类型的子类request 和command从client 端发起,区别是request 必须要有response ,而command 不需要,单向indicate和notify从server 端发起,区别是indicate必须要有confirm 返回,而notify不需要,是单向的。
0x01 ERROR_RSP:
报错的opcode,handle,是什么错误(error code,1个byte)经常error rsp用于表示流程结束标志具体的error code。
0x02 EXCHANGE_MTU_REQ/ 0x03 EXCHANGE_MTU_RSP
request ,client向server发起交换mtu 的请求,request中携带自己支持的mtu值(2字节),server 端收到之后,会根据自己所支持的mtu值与接收到的比较,取较小的值,回复给client端。在没有收到exchange rsp 之前,双方用默认值ATT_MTU=23。ATT_MTU大小范围在23-512之间如果ATT 链接建立过程中没有MTU request ,双方使用默认值。
0x04 FIND_INFORMATION_REQ /0x05 FIND_INFORMATION_RSP
request,client 发给server。client 不知道server有定义哪些服务,告诉一个区间,返回server 定义的ATT类型,att支持的类型是有uuid 决定的,response 中 format 决定返回的信息需要告诉handle 和uuid 成组的信息。
0x06FIND_BY_TYPE_VALUE_REQ/0x07 FIND_BY_TYPE_VALUE_RSP
较于find information request,多一个uuid和value 限制条件start ,end 定义一个查找区间要查找什么类型,设置uuid过滤条件,比如primary serviceatt value,设置att value过滤条件,比如0x1801,定义GAP service值一般常用于discover service 开头,将范围设置为0-0xffff,返回GAP的primary service,过滤条件能够减少返回的数量。拿到对端的类型之后,才能通过恰当的方式去操作ATT,读取ATT携带的数据。
0x08 READ_BY_TYPE_REQ/ 0x09 READ_BY_TYPE_RSP
需要知道server段关于目标类型的ATT 数据分布情况。常见的UUID type 包括:include,characteristic,PNP ID等等,返回信息value是搜索类型的申明ATT信息,handle是指搜索类型对应的handle。所有定义的类型,第一条ATT就是申明。
0x0A READ_REQ / 0x0B READ_RSP
通过read by type获取到了操作方法及value对应的handle 值,下一步就是直接去获取handle对应的value,在读这个handle之前,必须要先获取到对端的类型,只有这样,返回的值才能够被正确解析SIG有规定部分类型的值 的数据格式,用户也可以自定义数据类型及其对应值的格式。
0x0C READ_BLOB_REQ / 0x0D READ_BLOB_RSP
如果ATT携带数据超过了MTU最大值,一次读取不完,这种情况下需要使用多次读取。如何判断数据超长第一次read response中,数据 size 等于(最大值-1),则马上改用 read blob req读取余下的所有值, 直到response中 数据size 小于最大值-1。读取的过程中,request 会传递offest=(最大值-1)的计数,保证数据完整性。每读取一次offset累加(最大值-1)因此read blob request一般都在第一次read request 之后用。
0x0e READ_MULTIPLE_REQ / 0x0f READ_MULTIPLE_RSP
如果遇到多个value size较小,多次读取显然不是一个明智的选择,read mutil request能够实现一次读取多个value值。
0x10 READ_BY_GROUP_TYPE_REQ / 0x11 READ_BY_GROUP_TYPE_RSPATT
group 是一个虚拟的概念,并没有指定的数据,明确表示具体的ATT属于哪一个service group。ATT数据结构中最大的单元是service。以service 为例, 每个service,以service declaration开头,以下一个service declaration结束,在这两个handle直接就属于同一个service group。
0x12 WRITE_REQ / 0x13 WRITE_RSP
用于向已知handle 写入值。如果写成功,会有write RSP返回。write 用于已经知道所操作handle 的数据类型。0x52 WRITE_CMD / 0xD2 SIGNED_WRITE_CMD用于client向 service写入数据,单向写入,没有反馈。
0x16 PREPARE_WRITE_REQ/ 0x17 PREPARE_WRITE_RSP
对于一些超长度的ATT value,可以分批写入req ,offset,value rsp 中携带已经完成写入的handle ,如果req 和rsp 中handle 和offset相同,则表明写入成功这个命令执行完必须搭配下一个命令,才能生效,server端仅仅只是cache这个value,还没有写入。
0x18 EXECUTE_WRITE_REQ / 0x19 EXECUTE_WRITE_RSP
搭配上一个命令使用,包含两种状态,生效或者取消。server接受到这个命令后,会根据状态,执行对应的操作。
0x1b HANDLE_VALUE_NTFserver主动发起,notify 可以在任何时候发出,不需要client 回复。
0x1d HANDLE_VALUE_IND / 0x1e HANDLE_VALUE_CFM
又一种主动从server 端发起的ATT类型,与notify最大的区别是,需要client回复comfirm,如果回复超时,ACL 对断开如果client 接收到indication,会回复confirm。0x23 MULTIPLE_HANDLE_VALUE_NTF一个notify 中可以包含多个handle value值。
总结:属性协议是比较简单的协议,客户端通过它可以发现并获取属性服务器上的属性。
巨人的肩膀
本文学习自以下文档:
[1] 详解BLE 空中包格式—兼BLE Link layer协议解析.iini.CSDN
[2] 低功耗蓝牙ATT/GATT/Profile/Service/Characteristic规格解读.iini.博客园
[3] 低功耗蓝牙开发权威指南–第一部分 综述 (第1-4章).sundaygeek.CSDN
[4] 低功耗蓝牙开发权威指南 .Robin Heydon