转自 http://www.sunyouqun.com/2017/04/understand-ble-5-stack-generic-attribute-profile-layer/
通用属性规范GATT(Generic Attribute Profile)将ATT层定义的属性打包成不同的属性实体,包括服务项、特征项和描述符,这些属性实体组合在一起组成规范,即GATT规范。GATT规范是服务项的集合,服务项是特征项的集合,特征项携带了属性参数和数据,描述符协助特征项描述特征值的形式和功能。
GATT层按照命令的传输方向将设备分成GATT客户端和GATT服务端。客户端发起命令,服务端发出数据。GATT规范定义了客户端设备发现服务端设备的服务项的方法,建立连接以后,客户端设备可以通过发现方法检索服务端设备的GATT服务项和特征项,进而发送命了或数据。
服务端向客户端发送数据以通知和指示的形式发送,客户端收到指示信息需要返回确认信息。
服务端可以向客户端发送通知和指示,客户端按需返回响应。
客户端:设备发起命令、请求并接受响应、通知和指示。
服务端:设备接收命令、 请求并发出响应、通知和指示。
设备可以同时属于客户端和服务端。
GATT角色与执行过程相关,它不与设备绑定。设备在执行一个过程时,根据发起命令或接收命令而决定它是服务端还是客户端,该过程结束后就释放GATT角色。
GATT角色不与链路层的主机和从机角色绑定。一个链路层的主机,通常担任GATT客户端角色,也可以担任GATT服务端角色。
属性实体PDU如下:
字段 | Attribute Handle | Attribute Type | Attribute Value | Attribute Permissions |
---|---|---|---|---|
长度 | 2 octets | 2 or 16 octets | variable | implementation specific |
一个属性包含四个字段:属性句柄、属性类型、属性值和属性权限。
属性句柄用于指定具体的属性。属性句柄有效范围为0x0000-0xFFFF,属性句柄按步进1的增序排列,但有时可能会出现空缺。
属性类型为2字节、4字节或16字节的UUID。 如果是4字节UUID,在封装成属性PDU时根据蓝牙基础UUID转换成16字节标准UUID。
属性值字段包含了属性的具体数据。
属性权限决定了属性是否可读或可写。
两个设备属性层之间根据属性协议传输数据,属性协议包括几种类型:命令、请求、响应、通知、指示和确认。
属性协议PDU如下:
字段 | Opcode | Attribute Parameters | Authentication Signature |
---|---|---|---|
长度 | 1 octet | variable | 12 octets |
操作码Opcode决定了该PDU的操作过程类型。另外,操作码中包含一个认证标志位。
属性参数中包含了命令或请求的参数,或响应的数据。
最后字段的认证签名为可选字段,仅用于带签名的写操作,当操作码的认证标志位为1,则需要认证签名字段,否则不需要改字段。
客户端与服务端建立连接后,执行发现过程,以获取服务端所携带的全部属性。属性缓存功能用于保存服务端设备的属性句柄,使下一次重新连接时无需执行发现过程。
一般情况下,服务端设备的属性不会改变,但是执行固件升级则可以改变设备的属性。
如果改变设备的属性,将从Service Changed characteristic发出一个指示PDU,告知客户端设备服务端设备的属性发生了改变。该指示PDU中包含了发生改变的属性句柄范围,客户端设备收到该指示,重新执行发现过程,获取更新后的服务端设备的属性句柄。
如果设备的属性确定不能发生改变,则无需增加Service Changed characteristic属性。
如果两端设备完成绑定,则属性缓存信息一直有效,直到收到了Service Changed characteristic发出的指示。如果在服务端的属性在断开后发生了改变,则服务端在下次重连时候发送指示给客户端设备重新缓存属性句柄。
GATT定义了三种属性分组:主要服务、次要服务和特征项。
一个属性分组包括声明和定义。
主要服务和次要服务可以使用“按组类型读取”请求获得,特征项不可以。
蓝牙协议中包含了许多种GATT规范,每个规范适配一种用户案例,比如FindMe规范适配查找物件的场景,心率传感器规范适配心率测量场景。
每个规范均中均有若干服务项和特征项,服务项和特征项都属于属性实体,它们携带了通信中传输的数据。
服务项分为主要服务和次要服务,主要服务可以引用(Include)另一个主要服务或次要服务,客户端设备可以通过“主要服务发现过程”获取主要服务信息。
特征项包括一个声明、配置、数据和描述符。描述符用于描述特征项的数据如何被访问和展示。
规范、服务项和特征项之间有明确的包含关系,一个GATT规范中可以包括多个服务项,一个服务项中可以包括多个特征项。
GATT的规范结构框图如下:
属性的类型由UUID表示,协议栈预留了一些16-bit的UUID来表示常用的属性类型。
服务项必须包含一个服务项声明,可选地包含多个其他服务项和特征项。所包含的其他服务项和特征项均是该服务项的一部分。
服务项的声明格式如下:
服务项可以是主要服务项(UUID=0x2800)或次要服务项(UUID=0x2801)。
主要服务项可以独立使用,次要服务项一定要被其他服务项包含引用。
协议栈文档中对次要服务项的使用场景解释有限,在绝大多数情况下均可以不使用次要服务项,仅使用主要服务。
包含现了一个引用机制,比如需要扩展一个现有的服务项,可以在新的服务项中引用该服务项。
假如服务项中包含了其他服务项,则需要加入包含的声明(UUID=0x2802)。
协议栈文档中对包含的使用场景解释有限,在绝大多数情况下均可以不使用包含功能。
特征项是GATT数据的载体。
特征项包括:特征项的声明(UUID=0x2803),特征值的声明,以及若干描述符。特征值也是一个属性,它的句柄和UUID在特征项的声明中给出。
特征项始于该特征项的声明,结束语下一个特征项的声明。
特征项的声明数据格式如下:
其中属性值字段包括了特征值功能特性(Characteristic Properties),特征值的属性句柄和特征值的UUID。
特征值功能特性如下表所示:
特征值功能 | 值 | 描述 |
---|---|---|
Broadcast | 0x01 | 允许广播该特征值 |
Read | 0x02 | 允许读该特征值 |
Write Without Response | 0x04 | 允许写该特征值,不需要Response |
Write | 0x08 | 允许写该特征值,需要Response |
Notify | 0x10 | 允许该特征值发送通知 |
Indicate | 0x20 | 允许该特征值发送指示 |
Authenticated Signed Writes | 0x40 | 允许带认证签名的写该特征值 |
Extended Properties | 0x80 | 扩展特性 |
其中,Broadcase(0x01)、Notify(0x10)和Indicate(0x20)要求该特征值具有服务端特征项配置描述符(CCCD)。
特征项的声明中属性字段的特征值UUID跟特征值的声明中的UUID一致。
特征值的声明中包含了特征值所携带的数据内容,其格式如下:
描述符也是一种属性,它是特征项的一部分,用以提供特征值的额外信息。协议栈定义了6种不同的描述符,如下:
属性类型 | UUID | 描述 |
---|---|---|
«Characteristic Extended Properties» | 0x2900 | 特征项的扩展描述符 |
«Characteristic User Description» | 0x2901 | 特征项的用户描述符 |
«Client Characteristic Configuration» | 0x2902 | 客户端特征项配置描述符 |
«Server Characteristic Configuration» | 0x2903 | 服务端特征项配置描述符 |
«Characteristic Format» | 0x2904 | 特征项数据格式描述符 |
«Characteristic Aggregate Format» | 0x2905 | 聚合特征项数据格式描述符 |
0x2900 扩展性描述符,用于Reliable Write和Writable Auxiliaries这两类写属性。
0x2901 用户描述符,用于给出该特征值的文字描述。
0x2902 客户端特征项配置描述符,简称为CCCD,客户端设备通过一个标志参数,设置该特征值能否发送通知和指示。如果该标志参数为0x0001,表示该特征值允许发送通知;如果该标志参数为0x0002,表示该特征值允许发送指示。如果该标志参数为0x0000,表示该特征值不能发送通知和指示。
每个特征项最多能包含一个CCCD,对于具有Broadcast、Notify和Indicate功能的特征项,必须拥有一个CCCD。在两个建立了绑定的设备中,断开连接不会丢失CCCD信息。
0x2903 服务端特征项配置描述符,服务端设备通过一个标志参数,设置该特征值是否在广播中发出。如果该标志参数为0x0001,则广播消息中应该包含该特征值;如果该标志参数为0x0000,则广播消息中不包含该特征值。
0x2904 特征值格式描述符,用于提供特征值的数据格式。可选的数据类型包括:Boolean、1/4字节、1/2字节、1字节、2字节、3字节、4字节、8字节、16字节、带符号整数、无符号整数、浮点数、字符串、结构体等。还可以指定数据的指数、单位、名字空间、描述信息等。
0x2905 聚合特征项格式描述符,专用于聚合特征项。所谓聚合特征值,是指多个特征值共同组合成一个数值,每个特征值仅是该聚合数值的一部分。
GATT规范实现了以下功能:
这些功能利用了“深入BLE协议栈 —— 属性协议”中的属性协议PDU一节中的多种读写属性PDU。
下面具体分析。
该功能呢包含一个子功能:交换两端设备的ATT_MTU。
客户端设备发送Exchange MTU Request,其中包含了该设备的ATT_MTU,服务端设备返回Exchange MTU Response,其中包含了该设备的ATT_MTU,取二者的较小值作为协商的ATT_MTU值。
该功能包含两个子功能:发现全部主要服务项,按UUID发现主要服务项。
发现全部主要服务项
该功能向服务端设备发送Read By Group Type Request,起始句柄为0x0001,结束句柄为0xFFFF,属性类型为0x2800(主要属性的UUID),查找全部符合条件的首要服务项。
服务端返回Read By Group Type Response,响应中包含多个属性信息组成的列表,单个属性信息包含三个参数:元素长度、属性组首尾句柄、属性的UUID。
因为该列表长度不能超过一个属性层的PDU长度,所以需要多次执行请求和响应,直到服务端设备返回“未找到属性项”或到达结束句柄,才能获取全部的主要服务项,如下图所示:
观察上图,客户端第一次发起请求,查找主要服务项,首末句柄分别是0x0001和0xFFFF。
服务端返回响应中包含三个元素,每个元素代表一个首要服务项。每个元素的长度为0x06。第一个首要服务项的属性句柄为0x0001,类型为UUID1,末尾句柄为0x000F。第二个服务项的属性句柄为0x0010,类型为UUID2,末尾句柄为0x0017。第三个服务项的属性句柄为0x0100,类型为UUID3,末尾句柄为0x01FF。
客户端发起第二次请求,起始句柄设为0x2000。
服务端返回响应中仍然包含三个元素,每个元素的长度为0x06。三个元素分别表示三个首要服务项,其UUID分别为UUID4、UUID5和UUID6,UUID6的末尾句柄为0x04FF。
客户端接着发起第三次请求,起始句柄设为0x0500。
服务端返回错误,错误原因是未找到属性。客户端根据该错误原因,判断已经获取服务端设备的全部主要服务项。
按UUID发现主要服务项
该功能向服务端设备发送Read By Group Type Request,起始句柄为0x0001,结束句柄为0xFFFF,属性类型为0x2800(主要属性的UUID),指定的UUID为xxxx,查找全部符合条件的主要服务项。
具体的操作步骤与“发现全部主要服务”一致。
通常具有指定UUID的服务项仅有一个。
该功能呢包含一个子功能:查找包含的服务项。
该功能向服务端设备发送Read By Type Request,起始句柄为0x0001,结束句柄为0xFFFF,属性类型为0x2802(包含的声明UUID),查找全部符合条件的被包含服务项。
服务端返回响应,包含了满足条件的服务项的句柄和属性值。
该功能包含两个子功能:发现服务项下的全部特征项,按UUID发现特征项。
发现服务项下的全部特征项
该功能向服务端设备发送Read By Type Request,设置已知的服务项首末句柄,属性类型为0x2803(特征项声明的UUID),查找全部符合条件的特征项。
服务端返回Read By Type Response,响应中包含多个“属性句柄 – 值”元素组成的列表。单个元素包含三个参数:元素长度、特征项声明的句柄和特征值参数。特征值参数包括特征值的功能特性、特征值句柄和UUID。
因为该列表长度不能超过一个属性层的PDU长度,所以需要多次执行请求和响应,直到服务端设备返回“未找到属性项”或到达结束句柄,才能获取全部的特征项,如下图所示:
观察上图,客户端第一次发起请求,查找特征项,首末句柄分别是0x0200和0x0214。
服务端返回响应中包含两个元素,每个元素代表一个特征项。每个元素的长度为0x07。第一个特征项的声明句柄为0x0203,特征值的功能特性为0x02,即具有Read功能,特征值的句柄为0x0204,特征值的UUID为UUID1。第二个特征项的声明句柄为0x0210,特征值的功能特性为0x02,即具有Read功能,特征值的句柄为0x0212,特征值的UUID为UUID2。
由于每个元素的长度为7,表明该两个特征值的UUID均是2字节UUID,如果是16字节UUID,则每个元素的长度应该为0x15。
按UUID发现特征项
该功能根据已知的特征项UUID和首末句柄范围,查找满足条件的 特征项。
具体与“发现服务项下的全部特征项”完全一致。
该功能包含一个子功能:发现全部描述符。
该功能向服务端设备发送Find Information Request,设置已知的特征项首末句柄,查找全部符合条件的描述符。
服务端返回Find Information Response,响应中包含多个“属性句柄 – 值”元素组成的列表。单个元素包含三个参数:UUID格式、特征值的句柄和描述符的UUID。如果UUID格式参数等于1,表示描述符的UUID为2字节UUID,如果等于2,表示描述符的UUID为16字节UUID。
因为该列表长度不能超过一个属性层的PDU长度,所以需要多次执行请求和响应,直到服务端设备返回“未找到属性项”或到达结束句柄,才能获取全部的描述符,如下图所示:
观察上图,服务端的响应数据第一个参数0x01表示UUID1和UUID2均为2字节UUID,第二个参数0x0205表示该描述符上级的特征值的句柄。
该功能包含四个子功能:读特征值,按UUID读特征值,读长包特征值,读多个特征值。
读特征值
客户端已知特征值句柄,向服务端发送Read Request读取该句柄的特征值。
服务端返回指定句柄的特征值。该特征值长度应小于等于(ATT_MTU-1),如果大于该限制,则仅返回前(ATT_MTU-1)个数据。
下图为一次读取过程:
按UUID读特征值
客户端已知特征值的UUID,不知道其句柄,向服务端发送Read By Type Request读取该特征值。
具体操作与“按UUID发现特征项”一致。
读长包特征值
客户端已知特征值的句柄,但是特征值的长度大于(ATT_MTU-1),向服务端发送Read Request以读取前(ATT_MTU-1)个字节,然后发送Read Blob Request并设置合适的偏移量,以读取随后的(ATT_MTU-1)个字节,重复执行Read Blob Request直到服务端的Read Blob Response内容小于(ATT_MTU-1),表明该特征值完全被读取。
具体步骤如下:
读多个特征值
客户端已知多个特征值的句柄,向服务端发送Read Multiple Request,参数为多个特征值句柄。
服务端返回Read Multiple Response,包含了多个指定的特征值数据。
该功能包含五个子功能:写命令,带签名的写命令,写请求,写长包请求,可靠的写请求。
写命令
客户端已知特征值句柄,向服务端发送Write Command,写入指定数据。
数据长度不能超过(ATT_MTU-3)字节,如果超过,仅写入前(ATT_MTU-3)个字节。
该命令无需服务端返回响应。
带签名的写命令
客户端已知特征值句柄,且链接没有经过认证,向服务端发送Write Command,并设置签名认证标志位,实现带签名的写命令。
数据长度不能超过(ATT_MTU-3-12)字节,其中12表示认证签名的长度,如果超过,仅写入前(ATT_MTU-3-12)个字节。
该命令无需服务端返回响应。
写请求
客户端已知特征值句柄,向服务端发送Write Request,写入指定数据。
数据长度不能超过(ATT_MTU-3)字节,如果超过,仅写入前(ATT_MTU-3)个字节。
该命令需要服务端返回响应Write Response。
写长包请求
客户端已知特征值句柄,但待写入数据长度过长,向服务端发送Prepare Write Request,设置适当的偏移量,将数据发送至服务端缓存起来,数据发送完毕后,项服务端发送Execute Write Request执行写请求。
待写数据总长度不受限制,但是分步发送数据每次数据长度不得超过(ATT_MTU-3)。
两种请求均需要对应的服务端响应。
一个写长包请求流程如下:
可靠的写请求
客户端已知特征值句柄,希望一次性写入多字节的数据,或者要求数据的每个字节都必须被安全写入服务端设备,向服务端发送Prepare Write Request,偏移量永远等于0,一次性只发送一个数据,带多字节数据缓存完毕,再发送Execute Write Request执行写请求。
具体的操作与“写长包请求”完全一致。
该功能包含一个子功能:通知。
服务端执行Handle Value Notification,参数为特征值句柄和通知数据,向客户端推送通知。
执行通知前,该特征值需要已经使能通知,并且将通知数据写入该特征值。
该命令无需客户端返回响应。
该功能包含一个子功能:指示。
服务端执行Handle Value Indication,参数为特征值句柄和指示数据,向客户端推送指示。
执行指示前,该特征值需要已经使能指示,并且将指示数据写入该特征值。
该命令需要客户端返回响应Handle Value Confirmation。
该功能包含四个子功能:读描述符,读长包描述符,写描述符,写长包描述符。
读描述符与读特征值一致。
读长包描述符与读长包特征值一致。
写描述符与写请求一致。
写长包描述符与写长包请求一致。
GATT使用的L2CAP固定信道传输属性数据。
GATT的PDU长度限制ATT_MTU默认大小为23,它与L2CAP层的MTU值保持一致。