这里只介绍常用的一些概念,一些极少用到的概念为避免混淆这里不做介绍。
简单的来说,BLE的服务端属性数据库可以就看做是一张表。表中包含很多个条目。每一条条目就称为属性条目。
属性条目 |
属性条目 |
属性条目 |
。。。。。。 |
。。。。。。。 |
服务端,比如一个简单的防丢器。当连接后作为服务端时,其中可能包含有好几个服务,像是 设备信息服务 电池服务 警告服务等, 这些服务创建后都是存在数据库中,都是以一条条属性条目的形式存在于数据库中,那如何区分这些服务?
区分的方式就是一条为服务声明的属性条目就是一个服务的开始其后直到下一个服务声明这之间的属性条目都属于这个服务
设备信息服务声明(也是一条属性条目) |
属性条目 |
属性条目 |
属性条目 |
属性条目 |
电池服务声明(本质也是一条属性条目) |
属性条目 |
属性条目 |
属性条目 |
另一个服务的声明 |
。。。。。。 |
所以搞懂属性条目的定义就基本明白属性数据库了。也就明白的所谓的服务。因为属性数据库的本质就是一个个属性条目,而服务就是由几条相互关联的属性条目组成。 至于到底一个服务包含哪几条属性就是简单的从一个服务声明属性条目到下一个服务声明属性条目之前都是属于这个服务的属性条目。
具体的属性条目组织如下:
属性句柄 | 属性类型(UUID) | 属性值 |
属性句柄:
你可以把它当做一个地址来理解。 Ble的通信都是通过 特性值(特性值也是一种属性条目,包括句柄,UUID,和属性值,本质上数据传输是通过其中的属性值)来传输的。那么当手机 发一串数据过来时,设备怎么知道是发到那个特性值里面的?就是通过这个属性句柄来标示的,也是就手机写过来的数据你可以看成这样( 句柄:数据 )
PS: 一般在开发时,这个值通常是协议栈自动生成的。 比如 你调用一个创建特性的API时,是不需要指定属性句柄这个值的,这个值协议栈内部会自动在每次创建一个属性时赋予一个值。通常就是简单的随着你添加的特征值其属性句柄递增赋值。(这里 所说的特性下面解释 )
比如一个属性数据库如下:
0x0001 | 属性类型(UUID) | 属性值 |
0x0002 | 属性类型(UUID) | 属性值 |
0x0003 | 属性类型 | 属性值 |
。。。。。。。 |
属性类型:
可以简单理解成表示 这个属性条目中的属性值数据表示什么意思。
比如属性类型(UUID)为0x2800表示的是 首要服务的意思。 那么这个属性条目的 属性值 就是服务的UUID
比如如下一个如下的几条属性条目
0x0001 | 0x2800(UUID) | 0xfff1(属性值) |
0x0002 | 属性类型(UUID) | 属性值 |
0x0003 | 属性条目 | 属性值 |
0x0004 | 0x2800 | 0xfff2 |
0x0005 | 属性类型 | 属性值 |
0x0006 | 属性类型 | 属性值 |
第一个属性条目的 属性句柄为0x0001, 属性类型即UUID为0x2800表示这是个主要服务,那么根据之前说该条目以下,到下一个声明为主要服务的属性条目之间的属性条目都是属于这个服务的, 属性值为0xfff1表示这个主要服务的UUID,可以理解为区分不同的服务。
同样从0x0004开始 这个属性条目的UUID也是0x2800也表示为一个主服务的声明,即这是另一个服务的开始,之下都是属于这个服务的属性条目。 并且该服务的属性值为0xfff2即标识改服务的UUID为0xff2.
即对于 属性类型(UUID)为0x2800的属性条目来说,它代表的是一个主服务声明,它的属性值也是一个UUID,用来标示这个主服务。
属性值:
属性值的含义由属性类型(UUID)决定。 比如上面说的 主服务声明的 属性条目。
属性类型为0x2800则标识这个属性条目代表服务声明,那么它的属性值 就是这个服务的UUID。 (关于UUID不理解的可以将他简单的看做是区分标识符,比如上面的属性列表例子中有两个服务, 在应用层上区分这两个服务就是通过 0xfff1,0xff2这两个不同的UUID来区分的。)
属性条目中 不同的属性类型 其属性值的解释含义是不同的。 像是现在提到的 0x2800的属性类型,那么他的属性值就是表示一个主服务的UUID,这整个属性条目就是一个服务声明了。
后面还会提到 比如0x2803这个属性类型,那么他后面跟随的属性值数据就是解释成特性声明。
所以理解一点。 属性值的具体含义由属性类型决定
BLE的应用本质就是用来传输数据,而数据的传输最终利用就是属性条目中的属性值。通常我们所说的创建一个特性值,创建一个通道,本质上都是说的是 属性条目中的这个属性值,因为数据的最底层传输传输就是通过这个属性值来进行。
即特性值指的是整个属性条目,它包括句柄,UUID,和属性值。
因为服务器中的数据库本质上都是一条条属性条目,但是每个属性条目都具有自己的意义,像是前面说的 用来声明一个服务的属性条目,就称为服务声明。
那么这里说的 本质上用来传输数据的属性值也是属于某个属性条目的。所以就称该属性条目整体为特性值,其句柄和UUID都是用来标示该属性条目,而其属性值就是用来真实传输的。
抽象来说BLE的传输如下图所示
后续我们都将手机做为 客户端,而 ble硬件设备作为服务端来讨论。
PS:客户端和服务端由GATT层定义,一个设备是服务端还是客户端并不是固定的,因为服务端客户端的定义是针对一个具体的过程,而不是针对特定的设备。
具体的 某一过程 发起请求和命令的为客户端。
某一过程中 发起通知和指示的为服务端
所以GATT层的客户端,服务端是针对具体的一个过程的,这个过程结束后,角色就被释放了。
具有写性质的通道 就是 手机可以通过该特性值写数据给设备
具有读特性的通道在第二个图中用了一个虚线来表示,读是手机主动发起的,然后设备才会将数据再返回给手机
BLE采用的是 客户端-服务器的结构。该结构中通常只有客户端去读写服务器,服务器不会主动发数据给客户端,但是BLE的应用中比如一个防丢器,防丢器作为服务器,手机作为客户端,防丢器需要告诉手机自己的电量,一种方式是手机定时周期性的读,另一种更有效的方式是 防丢器自己电量发生改变的时候才主动发送改变的电量给手机。 所以为了应用于种类场景,BLE中定义了 notfiy和indication这两种由服务端主动发送数据给客户端通信方式
PS:notify和indication的区别在于,notify只是将你要发的数据发送给手机,没有确认机制,不会保证数据发送是否到达。而indication的方式在手机收到数据时会主动回一个ack回来。即有确认机制,只有收到这个ack你才能继续发送下一个数据。这保证了数据的正确到达,也起到了流控的作用。
上面说过 本质上数据的传输实际上通过 名为 特性值 的属性条目中的那个属性值来进行的。你可以将属性值其想像成一个数组,当手机发向某个特性值写送数据过来时,设备协议找收到数据会对写到特性值对应的那个数组上。 而设备想向手机发数据时先写到对应的那个数据buff中,然后协议栈再提取数组数据将其发送出去
下面再将上面的手机读写设备端数据,设备notify数据给手机的情形具体如何利用属性值来传输用图形解释下:
手机写数据给设备的情景:
当手机要写数据给设备时,是通过某个特性值来实现的。写过来的底层数据类似于(句柄 : 数据) 这种形式, 句柄表示是写那个特性值的。当设备端收到这个数据时,由句柄判断是写哪个特性值的,然后设备端的协议栈将收到的数据写到对应的那个特性值的内存buff出,然后再通知上层有数据写过来了,上层也是通过句柄,然后再找到对应的buff,将数据最终提取出来供上层处理。
手机读设备上的某个特性值数据的情形:
手机首先会发读取请求 读取请求中应该包含这个要读的特性的句柄,当设备端收到这个读请求后,设备中的ble协议栈会根据 句柄找到那个特性值,然后找到其对应的buff。最终提取其数据再发给手机
![](https://i-blog.csdnimg.cn/blog_migrate/18f88c4e2e281b187c60fe2a13bd1253.png)
设备使用notify 来主动发送数据给手机的情形:
(indication方式手机收到数据后会回一个确认)
首先 设备端的服务器中应该有某个特性值存在notify性质,并且手机端使能这个Notify功能后,设备端就可以通过该特性值使用Notify将数据发送给手机了。
设备先组织要发送给手机的数据,然后调用协议栈发送api,协议栈会将收到的数据写入该特性值对应的buff出,然后在下个连接事件到来时通过notify将数据发送给手机。当然发送的真实数据肯定也是形如(句柄:数据)这种形式的。这样手机端可以识别出事通过那个特性值传过来的数据,比如可能设备端定有两个特性值都具有notify功能,这类似于两个传输通道,一个通道用来发送命令,一个通道用来发送数据。
![](https://i-blog.csdnimg.cn/blog_migrate/0aa3673b0ba031cd68a463b350294e0a.png)
上面针对读写和Notify如何反应到特性值中的属性值上只是举了一个相对具体的内部实现的例子,真正的实现各个厂家的SDK内部可能都不一样,但是原理上都是类似的,比如无论的手机端写数据给设备,还是设备通过notify将数据发送给手机,数据在底层上的实现肯定都是(句柄:数据)这种形式