0%

【蓝牙】BLE Profile概述

未完待续

前言

本文框架主要分为以下几部分:

  • BLE协议栈框架简述
  • BLE-GATT层相关概念简单介绍,包括profile、service等
  • 结合代码分析GATT层的概念
  • 思考与拓展

BLE协议栈框架

暂略

BLE-GATT层相关概念

GATT(Generic Attribute Profile)定义了一种用于数据交换的分层结构,包括Profile(通用属性配置文件)、Service(服务)、Characteristic(特征值)、Descriptor(描述符)等。

一个BLE设备可以有多个服务,每个服务可以包含多个特征值,每个特征值可以有零个或多个特征描述符。

例如,一个健康监测设备可能有一个心率服务,该服务有一个心率测量特征值,这个特征值可能有一个描述符来指示测量结果的格式。

特征声明是特征值的描述和定义。它包含了特征值的属性(如读、写、通知)、权限、以及可能的描述符等信息,定义如何访问和使用特征值。

Profile(配置文件)

Profile,即配置文件,定义了BLE设备的功能和数据交换规则。

包括关键概念有:服务、特征值、特征描述符、属性、配置文件角色、数据交换格式、安全和配对等。

Service(服务)

Service(服务)是一组特征值的组合,每个服务都有一个唯一的标识符UUID,使得客户端可以标准化识别服务。

服务可以包含多个特征值,特征值可以包含多个特征描述符(Descriptors)。

服务本身没有直接的访问权限,但它们包含的特征值可以具有不同的访问权限,如只读、可写、可通知等。


句柄: 服务句柄(Service Handle)是一个用于唯一标识服务的数字。每个服务、特征值(Characteristic)、以及特征描述符(Descriptor)在GATT数据库中都有一个唯一的句柄值。

  • 在GATT数据库,即profile_data中,句柄值通常会从1开始,然后依次增加。
  • 服务句柄通常与其他属性的句柄一起定义了一个服务的范围,即服务的第一个和最后一个属性句柄标识了服务的开始和结束。
  • 服务句柄与服务的UUID相关联,UUID用于定义服务的类型和功能,而句柄用于在GATT数据库中标识服务的位置。

主服务: 主服务是GATT数据库中的顶级服务,它作为GATT层次结构的起点,包含多个特征值(Characteristics)和次级服务(Secondary Services)。

  • 一个主服务可以包含多个次级服务,而次级服务也可以进一步包含其他次级服务或特征值。

  • 主服务可以被客户端设备发现,客户端设备通过扫描和查询来识别服务器端提供的服务

    在BLE设备配对和连接过程中,客户端设备首先发现主服务,然后才能进一步发现和访问服务内的特征值

  • 主服务在GATT数据库中定义了一个服务范围,由服务的起始句柄(Start Handle)和结束句柄(End Handle)标识

Characteristic(特征值)

每个特征值都有一个唯一的UUID(Universally Unique Identifier),用于在GATT数据库中标识不同的特征值。

特征值是客户端和服务器之间数据交换的基本单元。其表示BLE设备中的一个数据项,可以是温度传感器的读数、设备的电池电量、或者其它用户自定义的数据设置通道。

客户端可以读取服务器的特征值,或者在特征值上注册通知,以接收数据更新。

  • 其定义了一系列特征属性(Properties),这些属性描述了特征值的行为,例如是否可读、可写、可通知(Notify)、可指示(Indicate)等。

  • 特征值可以有零个或多个特征描述符(Descriptor),这些描述符提供了关于特征值的额外信息,例如用户友好的描述、配置参数等。

  • 特征值具有访问权限,定义了哪些操作(如读取、写入)是被允许的。这些权限可以用于实现安全措施,如认证和加密。

  • 特征值可以是动态的,其值随设备状态变化而变化;也可以是静态的,其值在设备使用期间保持不变。

UUID(唯一标识符)

蓝牙低功耗(BLE)的GATT(Generic Attribute Profile)框架中,服务(Service)、特征声明(Characteristic Declaration)、特征值(Characteristic Value)和特征描述符(Descriptor)都有各自的UUID(Universally Unique Identifier,通用唯一识别码)

BLE的服务和特征值都是通过UUID唯一标识的,并且可以通过UUID查找相关的服务和特征值

  • 唯一性:UUID提供了一个全球唯一的标识符,确保了不同设备或开发者定义的服务、特征值和描述符不会发生冲突。

  • 标准化:使用UUID有助于标准化,使得不同制造商生产的设备能够通过标准化的接口进行通信和交互。

等等

Client and Server

Server(服务器端): 比如蓝牙耳机

  • 提供服务,广播服务信息,被其它设备发现
  • 存储特征值数据,如设备参数、状态信息或者用户自定义的数据
  • 响应客户端的数据交互,或者发送通知/指示到客户端

Client(客户端): 比如手机

  • 发现服务,请求服务信息,连接到Server访问特征值
  • 根据实际应用,可以读取Server的特征值数据,或者写入数据到特征值
  • 也可以订阅特征值通知,当特征值数据发生变化时,会收到通知。

  • 一个通信会话包括一个 Server 和一个 Client
  • 设备可以同时充作为 Server 和 Client
  • BLE支持一对多通信模式,一个Server可以与多个Client建立通信

BLE通信流程

发现阶段:Client扫描周围环境中的Server,通过广播包发现服务。
连接阶段:Client发起连接请求,Server接受连接后,两者建立通信链路。
服务和特征值发现:Client通过读取操作获取Server上的服务列表,进一步发现服务内的特征值。
数据交互:Client根据需要读取或写入特征值,Server响应这些请求并提供数据或确认。
通知和指示:Server可以通过特征值向Client发送通知或指示,Client接收并处理这些更新。

GATT层概念小结

BLE定义了一系列的标准服务,其可以看作是特定应用场景下的profile,如Heart Rate Service、Battery Service等都是标准服务

标准服务和特征值:

标准服务和特征值的 UUID 通常为 16 位。
16位 UUID 通常用于表示标准的服务和特征值。这种 UUID 的格式为 0xXXXX,其中 XXXX 是一个四位的十六进制数。
16位 UUID 的完整形式为 0000XXXX-0000-1000-8000-00805F9B34FB。

例如,Battery Service 的 UUID 为 0x180F,其中包含的 Battery Level 特征值的 UUID 为 0x2A19。

自定义服务和特征值:

自定义服务和特征值的 UUID 可以为 16 位或 128 位。
例如,一个自定义的服务 UUID 可能是 128 位的,而该服务下的特征值也可以是 128 位的 UUID,或者其中一个为 16 位,另一个为 128 位。

128位 UUID 通常用于表示自定义的服务和特征值。这种 UUID 的格式为一组 32 位的十六进制数字,以连字号分为五段。
128位 UUID 的格式为 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX。

嵌入式BLE之业务应用层开发

以下为一项目范例BLE广播及连接示意

alt text

BLE的广播包设置及分析

0x02010609FF00008AE088E655540505DAF58F010201061409736F756E64636F72652053656C656374203253

LEN TYPE VALUE
2 0x01 0x06
9 OxFF 0x00008AE088E65554
5 0x05 0xDAF58F01
2 0x01 0x06
20 0x09 0x736F756E64636F72652053656C656374203253
  • LEN: 0x02 (2字节) TYPE: 0x01 (Flags) VALUE: 0x06
    这通常表示设备的发现模式,其中0x06通常表示设备支持 BLE 仅连接模式。

  • LEN: 0x09 (9字节) TYPE: 0xFF (Manufacturer Specific Data) VALUE: 0x00008AE088E65554
    这是制造商特定的数据,通常包含公司标识符和产品特定的信息

  • LEN: 0x05 (5字节)TYPE: 0x05 (Service Data)VALUE: 0xDAF58F01(低位在前)
    TYPE 0x05: 表示广播数据包的类型为 Service UUID;VALUE 0xDAF58F01: 表示一个服务的 UUID。

  • LEN: 0x02 (2字节) TYPE: 0x01 (Flags) VALUE: 0x06

    重复了,可能是设置错误?

  • LEN: 0x14 (20字节) TYPE: 0x09 (Local Name) VALUE: 0x736F756E64636F72652053656C656374203253

    设备的本地名称,解码为字符串soundcore Select 2S

Profile_data解析

profile_data[] 是一个 二进制格式的GATT(通用属性配置文件)描述数组,用于定义BLE设备的服务(Service)、特征(Characteristic)及权限。
每个条目对应一个属性(Attribute),按顺序存储以下信息:

属性句柄(Handle):2字节,唯一标识属性的地址。
属性类型(Type):2字节,表示属性类型(如服务、特征、描述符)。
属性值(Value):变长数据,具体内容由属性类型决定。

句柄递增规则: 主服务、特征声明、特征值、CCCD的句柄按顺序递增

字段逐字节解析(以第一个服务声明为例)

1
0x0a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x28, 0x00, 0x18
字节位置 值(十六进制) 含义
0 0x0a 条目总长度:10字节(后续9字节需+1,因长度本身占1字节)。
1-2 0x00 0x02 属性句柄:小端序,值为 0x0002。
3-4 0x00 0x28 属性类型:小端序,0x2800 表示 主服务声明(Primary Service)。
5-6 0x00 0x01 服务UUID长度/标志:此处为16位UUID,值为 0x0001(无关,通常忽略)。
7-8 0x00 0x18 服务UUID:小端序,0x1800 对应 GAP服务(通用访问)。

数据结构原理

  • 层级结构
    服务(Service) 包含多个 特征(Characteristic)。
    特征 包含值、权限及可选的 描述符(Descriptor)(如CCCD)。
  • 句柄分配
    句柄按顺序递增,确保唯一性。
    例如,服务声明句柄为 0x0002,其下特征值句柄为 0x0003,依此类推。
  • 动态值(DYNAMIC)
    若特征值标记为 DYNAMIC,实际数据由应用层通过回调函数实时提供,而非硬编码在配置中。
  • 小端序存储
    UUID、句柄等16位值均按小端序(低位在前)存储。例如 0x1800 存储为 0x00 0x18。

以下为一profile_data示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
static const uint8_t profile_data[] = {
//////////////////////////////////////////////////////
//
// 0x0001 PRIMARY_SERVICE 1800
//
//////////////////////////////////////////////////////
0x0a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x28, 0x00, 0x18,

/* CHARACTERISTIC, 2a00, READ | WRITE | DYNAMIC, */
// 0x0002 CHARACTERISTIC 2a00 READ | WRITE | DYNAMIC
0x0d, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x28, 0x0a, 0x03, 0x00, 0x00, 0x2a,
// 0x0003 VALUE 2a00 READ | WRITE | DYNAMIC
0x08, 0x00, 0x0a, 0x01, 0x03, 0x00, 0x00, 0x2a,

//////////////////////////////////////////////////////
//
// 0x0004 PRIMARY_SERVICE ae30
//
//////////////////////////////////////////////////////
0x0a, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x28, 0x30, 0xae,

/* CHARACTERISTIC, ae01, WRITE_WITHOUT_RESPONSE | DYNAMIC, */
// 0x0005 CHARACTERISTIC ae01 WRITE_WITHOUT_RESPONSE | DYNAMIC
0x0d, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x28, 0x04, 0x06, 0x00, 0x01, 0xae,
// 0x0006 VALUE ae01 WRITE_WITHOUT_RESPONSE | DYNAMIC
0x08, 0x00, 0x04, 0x01, 0x06, 0x00, 0x01, 0xae,

......

//////////////////////////////////////////////////////
//
// 0x0021 PRIMARY_SERVICE E49A25F8-F69A-11E8-8EB2-F2801F1B9FD1
//
//////////////////////////////////////////////////////
0x18, 0x00, 0x02, 0x00, 0x21, 0x00, 0x00, 0x28, 0xd1, 0x9f, 0x1b, 0x1f, 0x80, 0xf2, 0xb2, 0x8e, 0xe8, 0x11, 0x9a, 0xf6, 0xf8, 0x25, 0x9a, 0xe4,

/* CHARACTERISTIC, E49A25E0-F69A-11E8-8EB2-F2801F1B9FD1, WRITE_WITHOUT_RESPONSE | DYNAMIC | NOTIFY, */
// 0x0022 CHARACTERISTIC E49A25E0-F69A-11E8-8EB2-F2801F1B9FD1 WRITE_WITHOUT_RESPONSE | DYNAMIC | NOTIFY
0x1b, 0x00, 0x02, 0x00, 0x22, 0x00, 0x03, 0x28, 0x14, 0x23, 0x00, 0xd1, 0x9f, 0x1b, 0x1f, 0x80, 0xf2, 0xb2, 0x8e, 0xe8, 0x11, 0x9a, 0xf6, 0xe0, 0x25, 0x9a, 0xe4,
// 0x0023 VALUE E49A25E0-F69A-11E8-8EB2-F2801F1B9FD1 WRITE_WITHOUT_RESPONSE | DYNAMIC | NOTIFY
0x16, 0x00, 0x14, 0x03, 0x23, 0x00, 0xd1, 0x9f, 0x1b, 0x1f, 0x80, 0xf2, 0xb2, 0x8e, 0xe8, 0x11, 0x9a, 0xf6, 0xe0, 0x25, 0x9a, 0xe4,
// 0x0024 CLIENT_CHARACTERISTIC_CONFIGURATION
0x0a, 0x00, 0x0a, 0x01, 0x24, 0x00, 0x02, 0x29, 0x00, 0x00,

/* CHARACTERISTIC, E49A28E1-F69A-11E8-8EB2-F2801F1B9FD1, READ | NOTIFY, */
// 0x0025 CHARACTERISTIC E49A28E1-F69A-11E8-8EB2-F2801F1B9FD1 READ | NOTIFY
0x1b, 0x00, 0x02, 0x00, 0x25, 0x00, 0x03, 0x28, 0x12, 0x26, 0x00, 0xd1, 0x9f, 0x1b, 0x1f, 0x80, 0xf2, 0xb2, 0x8e, 0xe8, 0x11, 0x9a, 0xf6, 0xe1, 0x28, 0x9a, 0xe4,
// 0x0026 VALUE E49A28E1-F69A-11E8-8EB2-F2801F1B9FD1 READ | NOTIFY
0x16, 0x00, 0x12, 0x02, 0x26, 0x00, 0xd1, 0x9f, 0x1b, 0x1f, 0x80, 0xf2, 0xb2, 0x8e, 0xe8, 0x11, 0x9a, 0xf6, 0xe1, 0x28, 0x9a, 0xe4,
// 0x0027 CLIENT_CHARACTERISTIC_CONFIGURATION
0x0a, 0x00, 0x0a, 0x01, 0x27, 0x00, 0x02, 0x29, 0x00, 0x00,
}

服务类型与结构如下:

  1. 主服务声明(Primary Service)
    属性类型:0x2800(固定值)。
    属性值:服务UUID(16位或128位)。
    1
    2
    // 示例:声明UUID为0x1800的GAP服务
    0x0a, 0x00, [句柄], 0x00, 0x28, [UUID低字节], [UUID高字节]
  2. 特征声明(Characteristic Declaration)
    属性类型:0x2803(固定值)。
    属性值:包含特征权限、值句柄和特征UUID。
    1
    2
    3
    4
    5
    // 示例:声明一个可读、可通知的特征(UUID 0xFFC2)
    0x0d, 0x00, [句柄], 0x03, 0x28,
    0x12, // 权限:READ(0x02) + NOTIFY(0x10) = 0x12
    0x08, 0x00, // 特征值句柄:0x0008
    0xC2, 0xFF // 特征UUID:0xFFC2(小端序)
  3. 特征值(Characteristic Value)
    属性类型:特征UUID(如 0x2A00 表示设备名称)。
    属性值:实际数据或动态生成标志(DYNAMIC)。
    1
    2
    // 示例:特征值动态生成(由应用层提供)
    0x08, 0x00, [句柄], 0x01, 0x2A, 0x00, 0x00 // 0x2A00为设备名称特征
  4. 客户端特征配置描述符(CCCD)
    属性类型:0x2902(固定值)。
    属性值:配置通知(Notify)或指示(Indicate)的开关。
    1
    2
    // 示例:CCCD描述符,句柄为0x0009
    0x0a, 0x00, [句柄], 0x02, 0x29, 0x00, 0x00 // 0x2902为CCCD类型

初始化、服务及回调注册

根据业务需求,定义好profile_data后,调用ble_profile_init函数进行初始化,并注册服务及回调函数。

业务数据处理

当设备收到数据,会在相应回调中触发 case 相应的事件枚举。该枚举即对应相应的profile_data句柄。

att_server_init(multi_profile_data, att_read_callback, att_write_callback);

思考与拓展

服务句柄、特征值句柄、特征值值句柄、CCCD句柄?

在程序中通过case各种句柄事件,从而执行相应的操作

特征值句柄,用于读取元数据

特征值值句柄用于读写实际数据

CCCD句柄:特征描述符相关的标识,可选

客户端不与服务器端建立连接,可以读取特征值数据吗?

广播跟GATT层有什么联系?广播数据包必须包含哪些数据?必须与UUID关联?配置广播有什么标准要求吗?

扫描到的广播名称,即与0x09字段的绑定

广播数据包结构:
广播数据包包含了一个或多个字段,每个字段由长度、类型和数据组成。
类型字段定义了数据的含义,如设备名称、服务 UUID 等。

广播数据包可以是可连接的 (Connectable) 或不可连接的 (Non-connectable)。

参考站点