关于蓝牙协议中GATT、特性、属性解释
在蓝牙的学习中,一上来就要面对一大堆的专业名词。首先就是服务是什么?特性是什么?属性是什么?刚开始的时候查了很多资料都还是没有搞懂。这次就结合代码,尽量用最通俗易懂的方式将这些东西解释并记录下来,也希望能帮助到别人。(如果有不对的,也希望有大神能指出来)
通用属性配置文件(Generic Attribute Profile)GATT
GATT主要定义了。服务器server和客户端client。而其他的服务、特性、属性都是在这个协议下的各种角色。他们的关系如下:
属性(Attribute)
属性由三部分组成,属性句柄、属性类型(UUID)、属性值。如下图:
属性句柄(2字节) | 属性类型(UUID)(2或6字节) | 属性值(0-512字节) |
属性句柄是属性在GATT表中的索引。一台设备可以有许多的属性,但是可能会有许多相同功能的属性,比如有许多测温属性,为了识别不同的属性,需要给每个属性加一个索引值。可以理解为内存地址。每一个属性的句柄都是唯一的。
这里已写属性为例,
这里可以看到这个事件属性中第一个字节就是属性句柄。那再看是如何调用的。
这个以点亮LED的例程为例,在进行属性指针申请后,将指针指向了底层上报上来的事件属性,然后进行指针的一个寻址。这里代码可以不用看懂,只要知道属性句柄是这个用法即可。一般在开发时,这个值通常是协议栈自动生成的。 比如 你调用一个创建特性的API时,是不需要指定属性句柄这个值的,这个值协议栈内部会自动在每次创建一个属性时赋予一个值。通常就是简单的随着你添加的特征值其属性句柄递增赋值。
那UUID是干嘛的呢?所有的属性都必须要有一个UUID值,UUID是全球唯一的128位的数据,用来识别不同的特性。蓝牙核心规范制定了两种不同的UUID,一种是基本的UUID,一种是代替基本UUID的16为UUID。我们协议栈的后面那里使用的就是代替基本UUID的16为UUID:先增加一个特定的基本UUID,在定义一个十六位的UUID。加载到基本UUID之上。
其实这个UUID主要是用来区别一个服务下的不同特性的。
还是以LED的例子。这个函数是添加特性的函数中,
这里可以看到LED的这个UUID是1525.那在手机上连接之后看一下。
看这个实际上是确定了这个特性的UUID。
属性权限
属性权限定义了与属性交互的规则。它定义了该条属性是否可读可写以及进行读写操作时需要进行哪种授权。这里需要强调的是属性权限只能用来控制对属性值的操作,它不用于属性句柄,属性类型以及属性权限本身。客户端可以通过遍历服务器上的属性表,就可以获悉服务器可以提供的服务,不需要读写属性值。
依旧以LED例子为例,这里可以看到配置了属性值(attr_char_value)。而读写属性直接赋值给了属性值。
属性值
属性值可以是任何东西。它可以是每分钟心率数值,可以是灯的开关状态,或者是像“hello world”之类的字符串。有时候它还可以包含某些信息,通过这些信息可以找到其它属性及其性质。
例如在上表中的服务声明属性(属性句柄0x000C)中,属性值是0x180D,这是蓝牙技术联盟定义的服务UUID,用以标识这是哪种服务。特性声明属性(属性句柄0x000D)的属性值包含了下一条属性即特性值声明属性的信息,包括属性句柄,属性类型,属性权限。最后,心率测量特性值属性(属性句柄0x000E)的属性值中包含了每分钟的心率数值。属性值的具体含义由属性类型决定。
描述符
任何在特性中的属性不是定义为属性值就是为描述符。描述符是一个额外的属性以提供更多特性的信息,它提供一个人类可识别的特性描述的实例。
有一个特别的描述符值得特别的提起,客户端特性描述符(Client Charactristic Configuration Descriptor,CCCD),这个描述符是给任何支持通知或指示功能的图形额外增加的。该描述符是一个2位的数值,分别用于设置通知与指示,但是不允许同时设置。在CCCD中写入“1”使能通知功能。写入“2”使能指示功能。写入“0”同时禁止通知和指示功能。
实际上这个CCCD在创建好之后默认都是关闭的,我们通过nRF Connect软件能更直观的看到。以电量采集为例。
这个是连接上后的状态。当点击“控件”开启notifi时
看软件的LOG,显示如下
可以看到notifi功能已经开启。这时候Battery Level 的Value部分就会自动变化。
属性符被配置到特性中。这里配置CCCD属性可读可写。
这里有个问题,为什么UUID是2902,在程序里也没有设置呀。查看Bluetooth core文档后发现。
这个是SIG已经定义好的,所有的CCCD的UUID都是2902.。
特性(Characteristics)
一个特性至少包含2个属性:一个属性用于声明,一个属性用于存放特性的值。所有通过GATT服务传输的数据必须映射成一系列的特性,可以把特性中的这些数据看成是一个个捆绑起来的数据,每个特性就是一个自我包容而独立的数据点。例如,如果几块数据总是一起变化,那么我们可以把它们集中在一个特性里。
而属性其实也可以称为属性条目,一个特性值就是一个属性条目。但是特性值 属性条目不是单独存在的, 它是属于特性 的一部分。特性由几条属性条目组成, 包括一条 特性声明属性条目, 一条特性值属性条目, 几条描述符属性条目。
如下所示例的一个特性组成,包括一个 声明,一个特性值,两个描述符。
描述符可以有很多,其中特殊的就是CCCD,客户端描述符。在上边有说过。那如果没有notifi的功能,那一个特性也就只有声明条目和属性值条目。
Nordic sdk中提供的API就是创建特性api, 根据我们填入的参数协议栈会自动创建 声明,特性值,以及描述符, 并且API会返回 特性值以及一些存在的描述符的句柄,如下图。
API的第一个参数 service_handle为 服务句柄,即添加的特性是添加到哪个服务中的,所以首先要创建在服务后才能再服务下面添加特性,sdk中调用创建服务API函数后,会返回被创建的服务的句柄,如下图
之后第二个参数p_char_md定义了 特性中的 特性值 支持哪些操作,读,些,notify等。以及所需要的描述符的读写权限等信息。
第三个参数p_attr_char_value 定义了 特性值 的UUID,长度等信息。
第四个参数p_handles 为协议栈返回的 创建的特性 中的一些句柄,包括特性值句柄,以及存在的描述符句柄。
这里有个问题,为什么每条属性已经有属性权限来控制属性值的读写了,还需要其他的属性性质来控制特性值的读写呢?它们不应该是一样的吗?这是一个值得一提的问题。事实上,控制特性值的属性性质只用在GATT和应用层,仅仅为客户端提供一些线索,客户端可以据此知道从特性值声明属性中能够获取哪些内容。控制属性的属性权限(ATT层)凌驾于特性值性质(GATT层)之上,最终决定对属性值的访问。你可能会问,为什么需要属性权限和性质两个部分呢?答案很简单,因为蓝牙核心规范是这样规定的。可能这个答案很令人困惑和失望,但是蓝牙核心规范决定我们如何设置这些特性,所以这里要提一下。
更多推荐
所有评论(0)