转载自:
https://blog.csdn.net/yueqian_scut/article/details/50752314
很多人都做过蓝牙开发,很多人也能够通过仿照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.单个属性定义
对于不同的属性,其length长度由value的类型来决定。例如服务的属性值对应的结构就是uint16_t;而特性声明的属性值对应的结构就是:
同样的,属性值pValue的数据结构由其对应的UUID来决定。
例如服务属性的pValue对应的结构是gattAttrType_t(包括长度2和具体的服务UUID)。
而特性声明的pValue对应的结构是一个字节的访问权限控制。特性数值的pValue对应的结构则是直接的变量定义,其可能是一个字节或者是字节数组,其长度由底层通过判断数组来决定。
2.电池电量服务定义
五、CC254X平台SDK属性结构定义
1.单个属性定义
同样的,属性值pValue的数据结构由其对应的UUID来决定。例如服务属性的pValue对应的结构是gattAttrType_t(包括长度2和具体的服务UUID)。而特性声明的pValue对应的结构是一个字节的访问权限控制。特性数值的pValue对应的结构则是直接的变量定义,其可能是一个字节或者是字节数组,其长度由底层通过判断数组来决定。
2.电池电量服务定义