目录
nimble 蓝牙开发概述
BLE 简介
蓝牙技术是短距离的无线通信技术,工作在 2.4 GHz 的 ISM 频段上。
按照蓝牙规范的划分,存在两种蓝牙:
-
经典蓝牙(BR/EDR),传输数据量大,功率消耗高;
-
低功耗蓝牙(BLE)。
为了发扬蓝牙在短距离物联方面的优势,SIG (蓝牙兴趣小组)在蓝牙规范 v4.0 中引入了 BLE,定义了 BLE 的架构,以及协议框架。
本文介绍 nimble 蓝牙协议栈的应用开发方法,nimble 只用于 BLE 开发。
BLE 基础知识
蓝牙设备地址
蓝牙地址为 48 位,6 字节。
在蓝牙规范中,同时存在两种类型的地址:
-
公共地址,由 IEEE 分配,并保证地址的唯一性;
-
随机地址,自定义的地址。
这两种地址在设备中可以同时存在,数据传输时,任一选择使用哪种地址。
BLE 广播类型
在 BLE 中存在广播报文,且广播报文常用如下4种类型:
- 可连接、可扫描、不定向广播
- 可连接、不可扫描、定向广播
- 不可连接、可扫描、不定向广播
- 不可连接、不可扫描、不定向广播
其中,名词解释如下:
名词 | 解释 |
---|---|
可连接 | 表示接收该广播的设备,可以向发送该广播的设备发送连接请求 |
可扫描 | 表示接收该广播的设备,可以向发送该广播的设备发送扫描请求 |
定向 | 表示在广播帧种指定了该广播的目的蓝牙地址,其他非目的地址的蓝牙设备不会接收到该广播 |
标准广播数据
蓝牙规范 5.2 的 Vol 3, Part C,第 11节中,定义了标准的、符合规范的 BLE 广播数据,其数据结构 是由一个一个的 AD Structure 组合而成的,而 AD Structure 的结构是由1字节的Length、一字节的 AD Type、n字节的AD Data组成。
其中:
位域 描述
Length 1 字节 ,包含了 AD Type 位域和 AD Data 位域的长度,不包含 Length 本身的字节长度
AD Type 1 字节,指示当前 AD Structure 中保存的数据类型
AD Data n 字节,具体数据格式由 AD Type 决定,用于保存应用层设置的数据,n + 1 = Length
关于 AD Type 的数据格式定义在 CSS(Core Specification Supplement) 规范的 Part A,Section 1 中。
AD Type 的标识值代表的类型定义在 Assigned Numbers 。
扫描响应也包含广播数据。
BLE 工作概述
BLE 常见的操作
- BLE 设备发送广播。如:物联网设备向外广播数据,表示自己存在;
- 接收 BLE 广播,如:手机可以扫描周围的 BLE 设备发送的广播数据;
- BLE 设备之间建立连接,并实现点对点通信。如手机实现蓝牙灯控。
BLE 常见的工作流程
环境:一台物联网设备(BLE 温度传感器), 一台智能手机/电脑;
要求:手机/电脑需要去扫描这台物联网设备,并与他建立连接实现数据通信;
工作流程:
- 首先,BLE 温度传感器发送广播数据,向外展示自己的存在;
- 其次,手机/电脑扫描到广播,并与 BLE 温度传感器建立点对点连接;
- 接着,手机/电脑向 BLE 温度传感器发送数据请求;
- 最后,BLE 温度传感器使用温度数据,来响应手机/电脑的数据请求。
BLE 使用的协议规范
总结 BLE 工作流程可知,BLE 设备工作时,会处于两种不同的状态:
- 广播 / 扫描状态(无连接);
- 连接状态。
对于不同的状态,蓝牙规范定义了不同的操作协议:
BLE设备状态 | 设备操作规范 | 规范概述 |
---|---|---|
广播 / 扫描状态(无连接) | GAP(Generic Access Profile) | GAP定义了 BLE 设备发送广播,扫描广播,建立连接的模式和流程 |
连接状态 | ATT / GATT(ATTribute protocol/ Generic ATTribute profile) | ATT 协议定义了属性的概念,属性用于保存数据;GATT 是在 ATT 协议上进一步抽象而来的规范,定义了已链接设备之间的数据存在形式,与数据交换方法 |
GAP
GAP 定义归纳了一系列的角色、模式、流程等概念,用户需要首先理解这些概念,然后根据自己的开发需求,按照 GAP 规范去配置使用 BLE,从而实现BLE 设备的广播。如,用户如果需要开发一个收发 BLE 广播的应用程序,那么就需要设置 GAP 定义的相关模式,从而实现广播效果。
BLE 工作时,会存在4 种常见的操作,GAP 将这 4 种常见的操作定义为如下 4 种工作角色:
应用角色 | 应用特性 |
---|---|
Broadcaster | 用于发送不可连接广播,并对 Observer 发送的扫描请求做出响应,不能与 Observer 建立连接 |
Observer | 接收 Broadcaster 发送的广播,可以选择向 Broadcaster 发送扫描请求,并接收扫描响应 |
Peripheral | 用于发送可连接广播,并根据接收到连接请求与 Central 建立连接 |
Central | 接收可连接广播,并向 Peripheral 发送连接请求,并建立连接 |
GAP 工作模式
除了不同的 GAP 角色时, GAP 还定义如下不同的 GAP 模式:
GAP模式 | 说明 |
---|---|
广播模式 | 发送不可连接广播,并可以扫描广播请求 |
可发现模式 | 在Flag类型的 AD Structure 中位置可发现标志位 |
可连接模式 | 发送可连接广播,并可以接收连接请求 |
GAP 工作流程
与 GAP 模式相对应,GAP 也定义如下 GAP 工作流程:
GAP流程 | 说明 |
---|---|
观察流程 | 接收不可连接广播,并可以发送扫描请求 |
发现流程 | 接收任何类型的广播,并可以发送扫描请求 |
连接流程 | 可连接模式下的设备建立连接 |
GAP 工作角色、模式、流程关系
上文介绍了 GAP 的工作角色、模式、流程,当 GAP 处于不同的角色时,其工作模式和流程都可能不一样,下表详细的介绍了他们之间的关系:
Broadcaster | Observer | Peripheral | Central | |
---|---|---|---|---|
广播模式 | M | E | O | E |
可发现模式 | O | E | O | E |
可连接模式 | E | E | M | E |
观察流程 | E | M | E | O |
发现流程 | E | O | E | O |
连接流程 | E | E | E | M |
说明:
M:强制支持(Mandatory)
O:可选支持(Optional)
E:不支持(Exclude)
从上图可以看出:
Broadcaster / Peripheral 不支持任一的 GAP 流程;
Observer / Central 不支持任一的 GAP 模式。
即:GAP 流程用于扫描广播,GAP 模式用于发送广播。
通过 GAP 建立连接后的 master / salve
当建立连接后,两个 BLE 设备都处于连接状态,此时 peripheral 做为连接状态下的从机, Central 做为连接状态下的主机。
无连接状态 | 连接状态 |
---|---|
Peripheral | Slave |
Central | Master |
ATT
已连接的 BLE 设备使用 ATT / GATT 规范来进行应用数据交换。
ATT 定义了角色、属性的概念,属性用来来保存数据。
ATT角色
ATT角色 | 角色描述 |
---|---|
ATT服务器 | 服务器可以定义一系列的属性,供客户端访问 |
ATT客户端 | 客户端可以使用ATT协议来发现、读、写服务器定义的属性 |
在典型的手机 - BLE 设备物联网应用中:
在ATT层协议框架内,拥有一组属性的设备称为服务端(Server),读写该属性值的设备称为客户端(Client)。
- BLE 设备做为 ATT 服务器;
- 手机做为 ATT 客户端。
属性
属性逻辑结构 | 对应 | 说明 |
---|---|---|
属性句柄 (Attribute Handle) | 0x0000-0xFFFF | 属性句柄由属性服务器分配; |
属性类型(Attribute Type) | UUID | 属性类型由用户定义或更高层规范指定; |
属性值(Attribute Value) | 0-N 字节 | 属性值由用户定义或更高层规范指定,用来保存应用数据; |
属性权限(Attribute Permissions) | 读/写,notify/indicate | 属性权限由用户定义或更高层规范指定。 |
属性逻辑结构总结:
属性就好比一个一个的柜子,这个柜子由 ATT 服务器定义而来,每个柜子都存在一个编号,就是属性句柄,用来唯一的指定 ATT 服务器上的一个属性;ATT 服务器对每个柜子都做出了分类,即属性类型 UUID,同一种类型 UUID 的柜子,可以多次出现;属性值即是柜子中真正存储的东西;属性权限是对柜子的使用说明,比如读/写,就相当与从柜子中拿取/存储东西。
属性及其相关概念总结:
属性类型 属性类型由UUID定义,16位的规范或128位的自定义UUID,客户端通过UUID来辨别属性类型。
属性句柄 属性句柄是ATT服务器分配给它自己定义的属性的16位非0值,每一个属性句柄都唯一代表了一个属性,客户端使用属性句柄访问属性值。
属性组 属性组由一个特定的组属性定义,组属性位于组属性的开始处,组属性由更高层规范指定,客户端可以使用ATT协议来请求属性组第一个和最后一个属性句柄。
属性值 属性值是固定或可变长度的字节数组。
属性权限 属性权限由更高层规范指定,或自定义,表示属性允许访问的方式,比如读/写权限。
控制点属性 该属性不能被客户端读,只能别客户端写,或者只能被服务器以 notify 或 indicate 的形式发送
协议方法 协议方法用于对属性执行发现、读、写、notify、indicate 操作,等同于ATT协议帧
ATT_MTU交换 MTU(Maximum Transmission Unit)定义为客户端和服务器之间传输数据包的最大字节大小
属性访问方法 - ATT 协议帧
属性访问方法也就是 ATT 协议帧,在蓝牙规范中被称作 ATT PDU(protocol data unit).
ATT PDU 被 ATT 客户端用来发现、读、写属性,或者被 ATT 服务器用来发送属性的 notication、indication。
ATT PDU 的类型有如下 6 种:
ATT PDU 类型 | 方向 | 触发响应 |
---|---|---|
Command | Client -> Server | – |
Request | Client -> Server | Response |
Response | Server -> Client | – |
Notification | Server -> Client | – |
Indication | Server -> Client | Confirmation |
Confirmation | Client -> Server | – |
属性PDU格式如下:
字段 | Opcode | Parameter | Authentication Signature |
---|---|---|---|
长度 | 1字节 | 0 – (ATT_MTU-X) octets | 0 or 12 octets |
Parameter字段中包含了参数,其长度为0至ATT_MTU-x,如果认证签名位为1,则此处x等于13,否则等于1。
只有写命令才需要认证签名,其他命令不需要。此外,如果链路已经进行加密,则属性PDU中也无需额外添加认证签名。
Opcode位域分布:
位域 | 描述 | |
---|---|---|
bit 7 | Authentication Signature Flag | 该位置 1,表示在 ATT PDU 中存在 Authentication signature如果该位为1,表示该PDU的最后一个字段中包含12字节的认证签名。 |
bit 6 | Command Flag | 该位置1,表示当前 ATT PDU 是否为一个 Command |
bits[5:0] | method | 该位用来确定 ATT PDU 的类型和数据格式。 |
GATT
GATT 是为了给应用程序或其他配置文件使用,以便于 ATT 客户端可以跟 ATT 服务器通信。
GATT 定义了使用 ATT 协议 PDU 的框架 ,这个框架定义了数据交换流程,也定义了应用数据交换格式: 服务(service)和特征(characteristics)。
通过 GATT 我们可以发现服务,并读/写或配置对端设备的特征。
GATT角色
与 ATT 相同,GATT 也存在两种角色:
GATT角色 | 角色描述 |
---|---|
GATT服务器 | 定义服务和特征的BLE设备。 |
GATT客户端 | 发送数据请求来访问服务与特征的BLE设备。 |
GATT 角色是不固定的,只有当启动相应流程时,GATT 角色才被确定,流程结束时 GATT 角色释放。
其中:
GATT 客户端发送 commands 和 requests 给服务器,并且能接收来自服务器的 response,indications 和 notifications;
GATT 服务器接收来自客户端的 commands 和 requests 并且发送response,indication 和 notification 给客户端。
典型的 GATT 应用,电脑读温度传感器。
- 电脑做为 GATT 客户端;
- 温度传感器 BLE 设备做为 GATT 服务器;
- 电脑可以启动流程来访问传感器定义的服务与特征。温度传感器定义一些服务与特征(characteristics) ,这些特征(characteristics) 做为温度服务的一部分,并允许电脑访问。
GATT 数据结构
GATT 配置文件指定了数据交换的结构。这个结构定义了基本的元素:服务(service)和特征(characteristics)
所有服务和特征都包含在属性中,属性是承载 GATT 数据的容器。
GATT 数据结构如下图所示:
对于 GATT 数据结构的说明:
最顶层是一个 profile,可以理解为一个应用程序,这个应用程序由 1 个或多个服务组成;
每一个服务由特征定义和服务引用组成;
特征包含一个特征值和特征值相关的其他信息;
服务和特征都以属性的形式被 GATT 服务器存储。
关于为什么还要抽象一层 GATT 数据结构的思考与总结:
- GATT 的数据结构(服务)是以一个一个的属性组成的,为什么要在定义了属性之后还要再抽象一层 GATT 呢?沿用之前将属性比作柜子的说法,假设存在这样一种情况:某种蓝牙应用,包含多种功能,如果每一种功能的实现都需要定义一个属性,那么如何保证 ATT 客户端精确的访问对应功能的属性呢?通过 ATT 协议 PDU 来发现属性吗,但也只能发现柜子的编号和柜子的种类,但是你如何确保功能和属性句柄、属性 UUID 之间的关系呢?除非你为每一个功能对应的属性定义一个标准的、通用的 UUID;但是,为每一个功能都定义一个标准的、通用的UUID,不仅繁琐,而且增加了蓝牙应用开发的难度,还限制了蓝牙应用开发的想象力。所以我们需要另外定义一套标准的功能声明结构,通过这个标准的功能声明结构,我们可以查找到 ATT 服务器上存在的功能,以及实现每一个功能对应的一个或多个属性,这就是 GATT 的服务(service)。
- GATT 服务(service)把实现一种功能的属性进行了整合,并可以使用 UUID 对这个功能进行命令。沿用之前将属性比作柜子的说法,GATT 服务(service)相当于一列火车,将实现一种功能的柜子(属性)都搬上了车。ATT 客户端只需查找 ATT 服务器上存在的火车即可发现定义的蓝牙功能及实现这个功能相关的柜子(属性)。
服务(service)
服务是一系列的数据以及完成一个实际功能的相关行为的集合。
在GATT 中,一个服务定义包含:
- 服务声明属性;
- 服务引用(可选);
- 至少一个的特征定义。
GATT 指定了两种服务类型:
GATT服务类型 | 描述 |
---|---|
主服务(primary service) | 主服务可供客户端访问服务,客户端可以使用主服务发现流程来查找主服务。 |
次级服务(secondary service) | 次级服务只能被主服务引用。 |
这两种服务都是属性组 ,即包含了一系列的属性。
在 GATT 中,服务通过 服务声明 定义,服务声明是一个组属性,服务声明属性的逻辑结构如下:
属性句柄 | 属性类型 | 属性值 | 属性权限 |
---|---|---|---|
0x0001-0xFFFF | 0x2800 - 主服务类型UUID/ 0x2801 - 次级服务类型UUID | 16位的标准服务UUID/128位的自定义服务UUID | 只读,无认证,无授权 |
其中:
-
服务声明属性的属性值为服务 UUID,服务 UUID 可以是 SIG 定义的规范服务(16 位),也可以是自定义服务(128 位)。
-
服务 UUID 用来表示整个服务的类型。
服务是一个属性组,服务的起始句柄是服务声明句柄,服务的结束句柄为下一个服务的服务声明的前一个句柄或 0xFFFF。
服务引用
服务引用是一种将 GATT 服务器上存在的一个服务引用到另一个服务定义中的方法。
为了引用其他服务,在服务定义的开始处必须使用引用定义。
引用定义使用 引用声明 来引用其他服务,引用声明是一个属性,属性逻辑结构如下:
属性句柄 | 属性类型 | 属性值 | 属性权限 |
---|---|---|---|
0x0001-0xFFFF | 0x2802 - 引用声明属性UUID | 被引用服务的服务声明句柄/被引用服务的服务定义结束句柄/被引用服务的服务UUID | 只读,无认证,无授权 |
其中:
- 被引用服务的服务声明句柄,表示服务属性组的起始句柄;
- 被引用服务的服务定义结束句柄,表示服务属性组的结束句柄;
- 被引用的服务 UUID 可以是 16 位的规范 UUID,也可以是 128 位的自定义 UUID;
- 服务声明句柄,服务定义结束句柄,服务 UUID 可以唯一的标识定义在 GATT 服务器上的一个服务。
特征(Characteristic)
每一个特征都被服务所包含,特征中包含了存储应用数据的属性,即特征值属性。
特征通过特征定义来指定,在 GATT 中,每一个特征定义包含:
- 特征声明属性;
- 特征值属性;
- 可选的特征描述符属性。
特征声明属性
特征定义从特征声明属性开始,特征声明属性的逻辑结构如下:
属性句柄 | 属性类型 | 属性值 | 属性权限 |
---|---|---|---|
0x0001-0xFFFF | 0x2803 - 引用声明属性UUID | 1字节,特征值属性访问特性/2字节,特征值属性句柄/16或128位特征值属性UUID | 只读,无认证,无授权 |
其中,特征值属性访问特性字节主要定义了特征值属性可用于哪些 ATT 访问方法,其各个位定义如下:
位域 | 权限 | 描述 |
---|---|---|
bit0 | Broadcast | 如果置位,允许客户端配置服务器特征配置描述符来控制服务器广播该特征值,置位后,特征定义中应该存在服务器特征配置描述符。 |
bit1 | Read | 如果置位,允许客户端使用读流程来读特征值属性的属性值。 |
bit2 | Write without Response | 如果置位,允许客户端使用写流程来写特征值属性的属性值,且服务器不需要发送响应。 |
bit3 | Write | 如果置位,允许客户端使用写流程来写特征值属性的属性值,服务器会发送一个响应。 |
bit4 | Notify | 如果置位,允许服务器发送特征值属性的 notification 给客户端,不需要客户端响应,且置位后客户端特征配置描述符必须存在。 |
bit5 | Indicate | 允许服务器发送特征值属性的 indication 给客户端,客户端会发送 confirmation 作为响应,且置位后客户端特征配置描述符必须存在。 |
bit6 | Authenticated Signed Write | 如果置位,允许特征值属性的认证写。 |
bit7 | Extended Properties | 额外的特征值属性访问权限,置位后,特征扩展描述必须存在。 |
特征值属性
特征值属性包含了特征的值,这是真正用于存储应用数据的属性。
属性句柄 | 属性类型 | 属性值 | 属性权限 |
---|---|---|---|
0x0001-0xFFFF | 16的规范UUID/128位的自定义UUID | 特征值,自定义长度字节数组 | 由更高层规范指定货自定义 |
特征描述符属性
特征描述符被用来包含特征值有关的信息,特征描述符也是一个属性。
GATT 定义了标准的特征描述符集,这些特征描述符可以被更高层使用;每一个特征描述符使用特征描述符 UUID 标识。
GATT 供定义了 6 种特征描述符,在这里只介绍一个常用的特征描述符:客户端特征配置描述符(CCCD)。
客户端特征配置声明(CCCD)是一个可选的特征描述符,它被客户端配置,使服务器能够以 notification 或 indication 的方式发送特征值给客户端。当特征声明属性的特征值访问特性的 notify 位域和 indicate 位域置 1 时,该特征描述符必须存在。
属性句柄 | 属性类型 | 属性值 | 属性权限 |
---|---|---|---|
0x0001-0xFFFF | 0x2892 - CCID属性UUID | 2字节,特征配置位 | 可读/可写 |
2 字节的特征配置位的位域组成如下:
位域 | 配置 | 描述 |
---|---|---|
bit0 | Notification | 客户端配置该位为1后,表示特征值会被服务器以 notification 的方式发送,只有在特征声明中的特征访问特性位域的 notify 位置1 时有效。 |
bit1 | Indication | 该位置位,表示特征值会被服务器以 indication 的方式发送,只有在特征声明的特征访问特性位域 indicate 位置1时有效。 |
bits[15:2] | RFU | RFU |
GATT 功能
GATT 定义了各种功能来完成服务发现与特征访问。GATT 共定义了 11 种功能,这些功能使用 ATT 协议来实现:
编号 | GATT功能 | 描述 |
---|---|---|
1 | 服务器配置 | 这个功能被客户端用来配置ATT属性协议的 ATT_MTU, 最大传输数据单元。 |
2 | 主服务发现 | 这个功能被客户端用来查找服务器上定义的主服务。 |
3 | 引用服务发现 | 这个功能被客户端用来查找服务定义中的引用服务。 |
4 | 特征定义发现 | 这个功能被客户端用来查找服务定义中的特征定义。 |
5 | 特征描述符发现 | 这个功能被客户端用来查找特征定义中的特征描述符声明。 |
6 | 读特征值 | 这个功能被客户端用来读特征的特征值。 |
7 | 写特征值 | 这个功能被客户端用来写特征的特征值。 |
8 | 发送特征值的 notification | 这个功能被服务器使用,向客户端发送一个特征值的 notification ,客户端不需要响应。 |
9 | 发送特征值的 indication | 这个功能被服务器使用,向客户端发送一个特征值的 indication,客户端不需要发送 confirmation 作为响应。 |
10 | 读特征描述符 | 客户端使用这个功能来读取指定的特征描述符属性的属性值。 |
11 | 写特征描述符 | 客户端使用这个功能来配置指定的特征描述符属性的属性值。 |
两个已连接的 BLE 设备,分别做为 GATT 服务器和 GATT 客户端,其中 GATT 服务器定义服务与特征,GATT 客户端使用上述 GATT 功能来实现 BLE 设备之间的数据交换。
GATT应用详述
应用目的:手机/电脑与温度传感器 BLE 设备建立连接,并通过 GATT 来读取温度数据。
操作步骤说明:
- 温度传感器和手机/电脑 通过 GAP 建立连接 ;
- 建立连接后,手机/电脑做为 master,温度传感器做为 slave;
- 温度传感器做为 GATT 服务器,定义温度服务(service)和温度特征(characteristic) ;
- 手机/电脑做为 GATT 客户端,使用 主服务发现功能 查找温度服务;
- 温度服务查找到之后,手机/电脑使用 引用服务发现功能 查找温度服务中是否引用了其他服务;
- 手机/电脑使用 特征定义发现功能 查找温度特征;
- 手机/电脑使用 特征描述符发现功能 查找温度特征是否存在特征描述符;
- 如果查找到温度特征,手机/电脑可以使用 **读特征值功能 ** 来读取温度特征属性值中保存的温度数据;
- 如果查找到 CCCD 特征描述符,手机/电脑可以使用 写特征描述符功能 来配置 GATT 服务器发送温度特征的 notification / indication,手机/电脑接收 notification / indication 获得温度数据。