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),这些描述符提供了关于特征值的额外信息,例如用户友好的描述、配置参数等。

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

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

Properties(特征值的属性)

定义位置:在特征声明(Characteristic Declaration,UUID: 0x2803)中声明
作用:定义客户端(如手机)可以对特征值执行的操作类型(公开声明,客户端可见)。

具体属性值及含义: 1字节的位掩码(Bitmask),每个位表示一种支持的操作

属性位(十六进制) 名称 说明
0x01 Broadcast 允许通过广播发送特征值(需配合广播数据包配置)。
0x02 Read 允许客户端通过读请求(Read Request)主动读取特征值。
0x04 Write Without Response 允许客户端通过无响应写入(Write Without Response)发送数据,无需服务器确认。
0x08 Write 允许客户端通过写入请求(Write Request)修改特征值,需服务器响应确认。
0x10 Notify 允许服务器通过通知(Notification)主动推送数据,无需客户端确认。
0x20 Indicate 允许服务器通过指示(Indication)主动推送数据,需客户端确认。
0x40 Authenticated Signed Writes 写入操作需使用认证签名(增强安全性)。
0x80 Extended Properties 表示特征包含扩展属性描述符。

示例:
1、0x12(二进制 00010010):支持 Read 和 Write Without Response。
2、0x30(二进制 00110000):支持 Notify 和 Indicate。

指示(Indication):

  • 服务器主动推送 + 客户端强制确认。
  • 高可靠性,适用于关键数据。
  • 数据包类型:Handle Value Indication(0x1D);确认包类型:Handle Value Confirmation(0x1E)
  • 协议栈自动确认(无需应用层代码)

写入(Write):

  • 客户端主动发起,分带响应(可靠)和不带响应(快速)两种模式。
  • 带响应写入需处理服务器反馈,不带响应写入适合实时场景。
  • 带响应的写入 —— 数据包类型:Write Request(0x12);确认包类型:Write Response(0x13)
  • 不带响应的写入 —— 数据包类型:Write Command(0x52);确认包类型:无

实际应用示例:

  • 高频数据:优先使用 通知(Notify) 或 无响应写入,避免确认带来的延迟。
  • 关键配置:使用 指示 或 带响应写入,确保操作可靠

权限(Permissions)

作用:控制客户端对特征值的访问规则(服务器端安全配置,客户端不可见)。
定义位置:在特征值属性(Characteristic Value)和描述符(Descriptors)中设置。

具体权限类型

权限标志 说明
Readable 允许客户端读取特征值。
Writable 允许客户端通过 Write Request 写入特征值(需服务器确认)。
Write Without Response 允许客户端通过 Write Without Response 写入(无需确认)。
Encrypted Read 需加密连接(如LE Secure Connection)才能读取。
Encrypted Write 需加密连接才能写入。
Authenticated Read 需配对认证(如MITM保护)才能读取。
Authenticated Write 需配对认证才能写入。
Authorization Required 需应用层授权(如用户手动确认)。
No Access 完全禁止访问(仅服务器内部使用)。

属性(Properties)与权限(Permissions)两者共同决定客户端能否成功执行操作:
属性是“菜单”:客户端根据属性判断可以尝试哪些操作(如是否发送读请求)。
权限是“门禁”:服务器根据权限决定是否允许操作(如拒绝未加密的读请求)

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(服务器端)(Peripheral)(从机): 比如蓝牙耳机

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

Client(客户端)(Central)(主机): 比如手机

  • 发现服务,请求服务信息,连接到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。在0x2803特征声明后可见
    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)。

参考站点