博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
蓝牙低功耗profile:ATT和GATT(转载)
阅读量:5276 次
发布时间:2019-06-14

本文共 8730 字,大约阅读时间需要 29 分钟。

原文:https://epx.com.br/artigos/bluetooth_gatt.php

蓝牙4.0版本推出了低功耗规范,引入了两个核心协议:ATT(Attribute Protocol)和GATT(Generic Attribute Protocol).这两个协议主要目标是BLE,但是也可以运行在传统蓝牙上(BR/EDR).

Overview

ATT是wire applicationprotocol(怎么翻译?连接协议?),GATT基于ATT协议。所有的BLE profile一定基于GATT。也就是所有的BLE服务都使用ATT作为应用协议。

锁定BLE使用这两个协议的好处是:

1, 开发和实现新的BLEprofile更加容易,因为不需要从头实现wire protocol。

2, ATT针对BLE 设备进行了特别的优化:使用尽可能少的字节,因此可能在存储中使用定长结构来生成PDU。

3, ATT/GATT的简单意味着固件可能提供某种程度的协议支持,省去了微处理器软件的麻烦。

4, 对于软件实现的协议栈来说,ATT/GATT在协议栈里实现,省去了应用的麻烦。

5, 即使有的场景下,ATT/GATT不够理想。也可以在L2CAP连接上实现平行于ATTchannel的协议。

ATT: Attribute Protocol

ATT协议的唯一基础是属性。每个属性由三个元素构成:

1,一个16bit handle;

2,一个UUID来定义属性的类型;

3,确定长度的属性值

在ATT中,属性值可以是任意长度的byte数组。属性值的实际意义依赖于UUID,而且ATT并不会检查属性值长度是否与给定的UUID定义一致。

Handle是用来唯一识别属性的数字,因为在一个BLE 设备中可能存在多个属性具有相同的UUID。

ATT协议本身没有定义任何UUID。这部分工作留给了GATT和上层协议。

ATT server存储属性。ATT client什么也不存储,它使用ATT协议来读写server端的属性。

和属性相关的还有读写权限。读写权限存在属性值里,由高层协议确定。ATT本身不会关心,也不会试图解释属性值来确定权限。这部分工作也留给了GATT和上层协议。

ATT有一些良好的特征,比如通过UUID来搜索属性,通过handle区间范围来获取所有区间内的属性,因此client不需要提前获得handle的值,也不需要高层协议硬编码这些值。

但是在特定的设备上handle的取值最好保持不变,这样的话client能够缓冲信息。在第一个discovery以后,client能够使用缓冲信息,这样能够减少传输的包数量,也能够节约能量。如果服务端的属性布局已经发生了变换,高层协议应该能够”暗示”client,比如固件升级。

大多数情况下ATT协议都是纯C/S架构,client发起请求,server响应。但是服务端也有通知的能力,在服务端属性发生变化时,server能够通知client,这样避免了client不停的poll。

ATT协议不会显式发送属性值的长度,只能从PDU长度里面获得。因此client最好能够知道某种UUID类型所代表的属性的精确结构。

不发送属性值长度,是为了减少发送的字节,因为LE的MTU只有23bytes。

23bytes的MTU对于较长的属性值来说是个麻烦。因此不得采用“read long”,”write long“这样的操作。

ATT是如此通用,意味着高层协议有太多工作要做。过度的自由也会带来问题,比如:如果一个设备提供多个服务怎么办?对每一个设备只有一个ATT handle空间,多个服务不得不共享同一份空间。

幸运地是,我们还有GATT,它为我们提供了属性用法,并解除了这些限制。

GATT:Generic Attribute Profile

GATT是所有LE顶层协议的基础。它定义了怎么把一堆ATT属性分组成为有意义的服务。

GATT services

GATT service的基础是UUID值为0x2800的属性。所有跟在这个属性后面的属性都属于这个属性定义的服务,直到另一个0x2800属性出现。

比如说,一个设备里面的三个属性布局如下:

 

 

每一个属性不知道它自己属于哪个服务,GATT需要根据0x2800属性作为标记来识别出哪个属性属于哪个服务。

按照这个定义,handle值就有意义了。在上面的例子中,属于service B的属性handle必须位于0x0151和0x02ff之间。

UUID 0x2800定义了primary

服务,也可以使用0x2801来定义secondary
服务。Secondary
服务表示包含于primary
服务。

然后我们怎么能知道一个服务是温度计,智能钥匙或者GPS?答案是通过读取属性值。服务属值包含了一个UUID,通过这个UUID区分服务。

因此,每个属性定义事实上包含了两个UUID,0x2800或者0x2801作为属性UUID,另外一个属性值里面存储的UUID。后面这个UUID是服务ID。

举个栗子:

 

在图中, thermometer service的UUID是0x1816。

是不是有点晕啊?两个UUID定义一个服务?这是GATT/ATT分层方式导致的后果。UUID

0x2800被GATT用来寻找服务定义边界。一旦找到了边界,属性值,也就是第二个UUID用来指定服务。这样client能够找到所有的服务而不需要知道服务的具体定义。

GATT service characteristics

每一个服务有几个特征。特征存储了有用的值以及权限。

比如,一个温度计可能有只读的温度特征,也可能有可读写的时间戳。

 

 

每一个服务可能有几个特征,这些特征也是通过路碑属性来发现的。

主特征的UUID是0x2803,然后主特征的属性值用来定义特征。比如图中 0x2803用来找到特征,0x2A2B用来找到特征包含的信息。

每一个特征至少包含两个属性,主属性0x2803和真正的值属性。主属性知道属性值的handle和UUID。这能够进行一定程度的交叉检测。

特征值的真正格式是由UUID决定的。因此,如果客户端知道如何解释UUID为 0x2A08的特征值,就能够从包含这个特征任何服务里面读取日期和时间。当然如果客户端不知道如何解释这个UUID的话,也可以选择忽略。

Characteristic descriptors

   除了特征值,我们也可以为每个特征增加更多的属性。在GATT语法里,这个额外的属性成为描述符。

         举个栗子,我们也许需要指定温度的计量单位。

 

GATT知道handle 0x0104是特征0x0101的描述符,因为:

1, 他不是特征的值,因为特征值的handle应该是0x0102

2, 他的handle落在了0x0103-0x010f之间,因此也不属于下一个特征。

 

描述符值的意义依赖于属性UUID。例子中,描述符的UUID是0x2A1F,客户端如果不能识别这个UUId,他可以选择忽略。这样可以实现向下兼容。

每个服务可能定义自己的描述符,但是GATT已经定义了能够覆盖大多数情况的标准描述符,比如:

数值格式和表示;

人类可读的描述;

合理范围扩展属性等等。其中特别重要的描述符是client characteristic configuration。

Client Characteristic Configurationdescriptor

Client Characteristic Configurationdescriptor的UUID是0x2902,具有一个16bit的可读写值,作为一个bitmap来使用。

这个属性被server用来存储和代表每个已经绑定的client的独立实例,每个client只能看到它自己的拷贝。

前两个bit被GATT用来定义通知和暗示。其他bit暂时未使用。

通过设置CCC,client能够让server在特征发生改变时得到通知。比如包含了CCC的属性布局如下:

 

Servicediscovery in Low Energy

因为GATT中所有的服务细节通过ATT来描述,所以不需要像BR/EDR那样设置专门的服务发现协议。ATT负责一切:发现服务,查找特征,读写值等等。

GATT andvanilla Bluetooth

GATT也可以工作在传统蓝牙上面,但是规范规定传统蓝牙仍然使用SDP发送服务,即使通过GATT来进行实际数据交换。

这样的好处是在双模设备上不用设置标识来识别LE-only服务。如果一个服务只能通过GATT发现,就是LE-only。如果能够通过GATT和SDP发现,就是双模。

         如果一个profile通过GATT来进行数据交换,并且是双模的,它必须首先发布SDP record。然后这个服务通过SDP来发现,然后通过GATT来查找特征。

         当然,现在没有双模的profile。以前的profile是BR/EDR only,并且没有适配到GATT;LE-only只有LE。

         如果想要测试GATT而没有LE硬件,可以修改蓝牙协议栈来使BR/EDR可以进行GATT discovery。这是规范不运行的,但是开发者可以。

Notificationsversus connections

        通知和暗示使得server可以发送消息给client。这样客户端不需要pollserver来获取新的数据。

另外,典型的GATT server是“小的“外设,像非常需要节能的传感器之类。因此,外设的LE 设备不能发起连接。那么通知怎么发送呢?

在BLE协议栈,如果server有数据发送,它就进入广播模式,并且发送一些信号。每个profile定义了广播时长和频率。时长和频率应该根据使用场景进行了节能和及时性的权衡。

处于中心模式的设备随时处于监听模式。当它监听到广播后,如果发现广播设备是认识的(配对过或者白名单中的),就会向外设发起连接。

连接建立以后,GATT通信能够进行,通知得以发送。所以典型的序列是:1,server发送广播 2,client连接 3server通知

如果没有更多的数据发送,server和client就会超时断开。最佳超时时间依赖于用例;如果服务不会频繁发送通知并且没有实时性要求的话,可以立马断开。因为BLE重连是非常快的。

典型的GATT server是外设设备,但是不是必须的。也可以外设做client,center做server。在这种场景下,client想要读写数据的时候,需要先进入广播模式。

蓝牙低功耗profile:ATT和GATT

蓝牙4.0版本推出了低功耗规范,引入了两个核心协议:ATT(Attribute Protocol)和GATT(Generic Attribute Protocol).这两个协议主要目标是BLE,但是也可以运行在传统蓝牙上(BR/EDR).

Overview

ATT是wire applicationprotocol(怎么翻译?连接协议?),GATT基于ATT协议。所有的BLE profile一定基于GATT。也就是所有的BLE服务都使用ATT作为应用协议。

锁定BLE使用这两个协议的好处是:

1, 开发和实现新的BLEprofile更加容易,因为不需要从头实现wire protocol。

2, ATT针对BLE 设备进行了特别的优化:使用尽可能少的字节,因此可能在存储中使用定长结构来生成PDU。

3, ATT/GATT的简单意味着固件可能提供某种程度的协议支持,省去了微处理器软件的麻烦。

4, 对于软件实现的协议栈来说,ATT/GATT在协议栈里实现,省去了应用的麻烦。

5, 即使有的场景下,ATT/GATT不够理想。也可以在L2CAP连接上实现平行于ATTchannel的协议。

ATT: Attribute Protocol

ATT协议的唯一基础是属性。每个属性由三个元素构成:

1,一个16bit handle;

2,一个UUID来定义属性的类型;

3,确定长度的属性值

在ATT中,属性值可以是任意长度的byte数组。属性值的实际意义依赖于UUID,而且ATT并不会检查属性值长度是否与给定的UUID定义一致。

Handle是用来唯一识别属性的数字,因为在一个BLE 设备中可能存在多个属性具有相同的UUID。

ATT协议本身没有定义任何UUID。这部分工作留给了GATT和上层协议。

ATT server存储属性。ATT client什么也不存储,它使用ATT协议来读写server端的属性。

和属性相关的还有读写权限。读写权限存在属性值里,由高层协议确定。ATT本身不会关心,也不会试图解释属性值来确定权限。这部分工作也留给了GATT和上层协议。

ATT有一些良好的特征,比如通过UUID来搜索属性,通过handle区间范围来获取所有区间内的属性,因此client不需要提前获得handle的值,也不需要高层协议硬编码这些值。

但是在特定的设备上handle的取值最好保持不变,这样的话client能够缓冲信息。在第一个discovery以后,client能够使用缓冲信息,这样能够减少传输的包数量,也能够节约能量。如果服务端的属性布局已经发生了变换,高层协议应该能够”暗示”client,比如固件升级。

大多数情况下ATT协议都是纯C/S架构,client发起请求,server响应。但是服务端也有通知的能力,在服务端属性发生变化时,server能够通知client,这样避免了client不停的poll。

ATT协议不会显式发送属性值的长度,只能从PDU长度里面获得。因此client最好能够知道某种UUID类型所代表的属性的精确结构。

不发送属性值长度,是为了减少发送的字节,因为LE的MTU只有23bytes。

23bytes的MTU对于较长的属性值来说是个麻烦。因此不得采用“read long”,”write long“这样的操作。

ATT是如此通用,意味着高层协议有太多工作要做。过度的自由也会带来问题,比如:如果一个设备提供多个服务怎么办?对每一个设备只有一个ATT handle空间,多个服务不得不共享同一份空间。

幸运地是,我们还有GATT,它为我们提供了属性用法,并解除了这些限制。

GATT:Generic Attribute Profile

GATT是所有LE顶层协议的基础。它定义了怎么把一堆ATT属性分组成为有意义的服务。

GATT services

GATT service的基础是UUID值为0x2800的属性。所有跟在这个属性后面的属性都属于这个属性定义的服务,直到另一个0x2800属性出现。

比如说,一个设备里面的三个属性布局如下:

 

 

每一个属性不知道它自己属于哪个服务,GATT需要根据0x2800属性作为标记来识别出哪个属性属于哪个服务。

按照这个定义,handle值就有意义了。在上面的例子中,属于service B的属性handle必须位于0x0151和0x02ff之间。

UUID 0x2800定义了primary

服务,也可以使用0x2801来定义secondary
服务。Secondary
服务表示包含于primary
服务。

然后我们怎么能知道一个服务是温度计,智能钥匙或者GPS?答案是通过读取属性值。服务属值包含了一个UUID,通过这个UUID区分服务。

因此,每个属性定义事实上包含了两个UUID,0x2800或者0x2801作为属性UUID,另外一个属性值里面存储的UUID。后面这个UUID是服务ID。

举个栗子:

 

在图中, thermometer service的UUID是0x1816。

是不是有点晕啊?两个UUID定义一个服务?这是GATT/ATT分层方式导致的后果。UUID

0x2800被GATT用来寻找服务定义边界。一旦找到了边界,属性值,也就是第二个UUID用来指定服务。这样client能够找到所有的服务而不需要知道服务的具体定义。

GATT service characteristics

每一个服务有几个特征。特征存储了有用的值以及权限。

比如,一个温度计可能有只读的温度特征,也可能有可读写的时间戳。

 

 

每一个服务可能有几个特征,这些特征也是通过路碑属性来发现的。

主特征的UUID是0x2803,然后主特征的属性值用来定义特征。比如图中 0x2803用来找到特征,0x2A2B用来找到特征包含的信息。

每一个特征至少包含两个属性,主属性0x2803和真正的值属性。主属性知道属性值的handle和UUID。这能够进行一定程度的交叉检测。

特征值的真正格式是由UUID决定的。因此,如果客户端知道如何解释UUID为 0x2A08的特征值,就能够从包含这个特征任何服务里面读取日期和时间。当然如果客户端不知道如何解释这个UUID的话,也可以选择忽略。

Characteristic descriptors

   除了特征值,我们也可以为每个特征增加更多的属性。在GATT语法里,这个额外的属性成为描述符。

         举个栗子,我们也许需要指定温度的计量单位。

 

GATT知道handle 0x0104是特征0x0101的描述符,因为:

1, 他不是特征的值,因为特征值的handle应该是0x0102

2, 他的handle落在了0x0103-0x010f之间,因此也不属于下一个特征。

 

描述符值的意义依赖于属性UUID。例子中,描述符的UUID是0x2A1F,客户端如果不能识别这个UUId,他可以选择忽略。这样可以实现向下兼容。

每个服务可能定义自己的描述符,但是GATT已经定义了能够覆盖大多数情况的标准描述符,比如:

数值格式和表示;

人类可读的描述;

合理范围扩展属性等等。其中特别重要的描述符是client characteristic configuration。

Client Characteristic Configurationdescriptor

Client Characteristic Configurationdescriptor的UUID是0x2902,具有一个16bit的可读写值,作为一个bitmap来使用。

这个属性被server用来存储和代表每个已经绑定的client的独立实例,每个client只能看到它自己的拷贝。

前两个bit被GATT用来定义通知和暗示。其他bit暂时未使用。

通过设置CCC,client能够让server在特征发生改变时得到通知。比如包含了CCC的属性布局如下:

 

Servicediscovery in Low Energy

因为GATT中所有的服务细节通过ATT来描述,所以不需要像BR/EDR那样设置专门的服务发现协议。ATT负责一切:发现服务,查找特征,读写值等等。

GATT andvanilla Bluetooth

GATT也可以工作在传统蓝牙上面,但是规范规定传统蓝牙仍然使用SDP发送服务,即使通过GATT来进行实际数据交换。

这样的好处是在双模设备上不用设置标识来识别LE-only服务。如果一个服务只能通过GATT发现,就是LE-only。如果能够通过GATT和SDP发现,就是双模。

         如果一个profile通过GATT来进行数据交换,并且是双模的,它必须首先发布SDP record。然后这个服务通过SDP来发现,然后通过GATT来查找特征。

         当然,现在没有双模的profile。以前的profile是BR/EDR only,并且没有适配到GATT;LE-only只有LE。

         如果想要测试GATT而没有LE硬件,可以修改蓝牙协议栈来使BR/EDR可以进行GATT discovery。这是规范不运行的,但是开发者可以。

Notificationsversus connections

        通知和暗示使得server可以发送消息给client。这样客户端不需要pollserver来获取新的数据。

另外,典型的GATT server是“小的“外设,像非常需要节能的传感器之类。因此,外设的LE 设备不能发起连接。那么通知怎么发送呢?

在BLE协议栈,如果server有数据发送,它就进入广播模式,并且发送一些信号。每个profile定义了广播时长和频率。时长和频率应该根据使用场景进行了节能和及时性的权衡。

处于中心模式的设备随时处于监听模式。当它监听到广播后,如果发现广播设备是认识的(配对过或者白名单中的),就会向外设发起连接。

连接建立以后,GATT通信能够进行,通知得以发送。所以典型的序列是:1,server发送广播 2,client连接 3server通知

如果没有更多的数据发送,server和client就会超时断开。最佳超时时间依赖于用例;如果服务不会频繁发送通知并且没有实时性要求的话,可以立马断开。因为BLE重连是非常快的。

典型的GATT server是外设设备,但是不是必须的。也可以外设做client,center做server。在这种场景下,client想要读写数据的时候,需要先进入广播模式。

转载于:https://www.cnblogs.com/fengzhouincoding/p/5909422.html

你可能感兴趣的文章
推荐一款UI设计软件Balsamiq Mockups
查看>>
DRF的版本控制,认证,权限和频率限制
查看>>
Linux crontab 命令格式与详细例子
查看>>
百度地图Api进阶教程-地图鼠标左右键操作实例和鼠标样式6.html
查看>>
游标使用
查看>>
LLBL Gen Pro 设计器使用指南
查看>>
SetCapture() & ReleaseCapture() 捕获窗口外的【松开左键事件】: WM_LBUTTONUP
查看>>
PLSQL Developer使用技巧
查看>>
Android 设置界面的圆角选项
查看>>
百度地图api服务端根据经纬度得到地址
查看>>
使用yum更新时不升级Linux内核的方法
查看>>
sqlserver计算时间差DATEDIFF 函数
查看>>
51nod1307(暴力树剖/二分&dfs/并查集)
查看>>
用户体验分析: 以 “南通市图书馆微信公众号” 为例
查看>>
linux的管道 |和grep命令以及一些其他命令(diff,echo,cat,date,time,wc,which,whereis,gzip,zcat,unzip,sort)...
查看>>
Nginx和PHP-FPM的启动、重启、停止脚本分享
查看>>
cookie 和session 的区别详解
查看>>
Vuex-状态管理模式
查看>>
浮点数运算的精度问题:以js语言为例
查看>>
数据挖掘领域十大经典算法
查看>>