先进的属性概念
在这一骗中,我们重点介绍一些不太常用但是很重要的一些属性概念。
属性缓存
之前我们已经讨论过属性句柄如何让一个客户端单独解析在服务器上的所有可以用的属性。发现在服务器上的所有可以使用你的属性并且获取每一个属性的内容会是一件很花时间的事情。我们想如何让一个客户端避免每次都要执行这个过程。
服务器端会维护一个属性的状态集,这个属性的状态集的结构基本上是不会变化的。但是对于服务器来说,确实没有严格的限制。服务器可以自由的变更它的属性,更可以彻底的替换掉整个属性集。所以我们需要一些规则来限制,这样的话,客户端就可以基于上次发现的服务继续工作,而不用担心属性的变化。
一般来说,蓝牙规格推荐客户端可以缓存感兴趣的属性的句柄。但是不会存储属性的值,因为这些值是经常变化的,存储这些值是没有什么意义的。
蓝牙规格提供了服务改变特性Service Changed,来让服务器告诉客户端它的属性结构是否发生变化。这是一个可选项,所以它扮演了一个警告的选项,用来警告客户端属性的内容可能已经发生了变化。
客户端可以观察下面的条件来确定已经缓存过的发现的结果是否可以被继续使用。
- No Service Changed characteristic present on the server
客户端可以自由的缓存所有的句柄,并且没有什么限制。服务器保证属性结构不会变化
- Service Changed characteristic present on the server
客户端需要订阅服务器的更新,通过写入CCCD。如果服务器端发生了什么变化,服务器端会警告客户端。如果属性变化了,客户端需要重新执行服务发现的过程。
在广播包中的GATT属性数据
虽然GATT主要是依赖于建立起来的连接,它也可能在广播包中包含一些属性的信息。告诉周围的观察者或者中心设备有哪些服务属性可以使用。
在下图中,表示的是能够广播服务数据,一个GATT服务器必须包含2个不同的部分。
Service Data 部分可以对应完整的或者部分的特性的内容,或者是描述符的内容。这里取决于规格是如何定义的。
特性
GATT的特性,可以让基于GATT的数据交换能够执行。他们都是基于ATT提供的操作来实现的。
MTU交换
使用2个包来让ATT节点之间知道彼此的最大传输单元MTU。
这个过程可以在任何时候被执行,比如MTU有变化的时候。当然,当传输到L2CAP层的时候,L2CAP层会进行分割这些大包为很小的链路层包并且在传输过去之后,再重新进行整合为一个大包。
服务和特性发现
当第一次连接到GATT服务器端的时候,客户端是不知道属性的情况的。因此,需要进行一系列的交互知道服务器端上属性的情况。
对于主要服务发现的过程,GATT提供了2种方法
- Discover all primary services发现所有的主要服务
客户端要获取一份所有的主要服务的列表。客户端需要指定一个句柄的范围来查找
- Discover primary service by service UUID通过服务的UUID发现所有的主要服务
当客户端知道需要查找哪些服务的时候,客户端可以简单的查找特定的服务。
当客户端查找到了服务以后,它可以执行关系发现relationship discovery的过程。
- Find included services发现包含的服务
- Discover all characteristics of a service 发现一个服务的所有特性
- Discover characteristics by UUID通过UUID查找特性
- Discover all characteristic descriptor查找所有的特性描述符
读取特性和描述符
为了获取当前特性的值或者描述符,客户端有几个选择
- Read characteristic value or descriptor
通过句柄的方式来读取特征值或者描述符
- Read long characteristic value or descriptor
如果内容太长,可以通过设置偏移量,结合句柄的方式来读取。使用多个请求响应对的方式来读取。
- Read characteristic value using characteristic UUID
通过UUID的方式来读取
- Read multiple characteristic values
通过一个特性集合的句柄来读取多个特征值
写入特性和描述符
为了写入当前特性的值或者描述符,客户端有几个选择
- Write characteristic value or descriptor
通过提供句柄和要写入的内容,可以介入特征值或者描述符
- Write long characteristic value or descriptor
类似于读取,这里客户端可以准备一个写的队列,包含了偏移量和要写入的内容,然后可以自动写入服务器中
- Write without response
使用Write Command包来写入内容。服务器收到以后,会静默处理。客户端不会知道是否写入,唯一的方法就是重新读取这个值来确认是否写入成功。
- Reliable writes
类似于读取,当客户端想要队列写操作的时候,它会生成一个最终的包,并且执行他们。