• 正文
  • 相关推荐
申请入驻 产业图谱

蓝牙5.0 BLE协议栈开发:GATT服务发现与通知机制

15小时前
117
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

蓝牙5.0 BLE(低功耗蓝牙)开发中,GATT(通用属性规范) 是应用层数据交互的绝对核心。很多开发者能连上设备,却在“找不到服务”或“收不到数据”上栽跟头。本文将深入服务发现流程与通知(Notify)机制这两大实战难点,助你打通BLE通信的“任督二脉”。

一、GATT服务发现:连接后的“地图加载”

BLE连接建立后,手机(Client)并不知道设备(Server)内部有什么能力。服务发现(Service Discovery) 就是Client主动去Server端“读取地图”的过程。这个过程必须严格按照ATT(属性协议)的顺序执行。

1. 发现流程状态机

在Android等平台,GATT操作是异步回调驱动的。一个标准的发现流程必须遵循以下状态迁移:

1.  连接成功:onConnectionStateChange回调状态变为STATE_CONNECTED。

2.  触发发现:立即调用gatt.discoverServices(),进入发现状态。

3.  发现完成:等待onServicesDiscovered回调,状态为GATT_SUCCESS。

4.  获取对象:只有在此之后,调用gatt.getService(UUID)才能拿到非空对象。

避坑指南:绝对不要在连接成功回调后立即进行读写操作,此时服务表尚未加载,必然失败。

2. 底层交互解析(ATT层)

服务发现在底层是通过一系列ATT指令完成的:

-   MTU交换:协商双方能处理的最大数据包长度。

•   发现主服务:Client发送ATT_READ_BY_GROUP_TYPE_REQ,遍历Server的属性表,找出所有UUID=0x2800(主服务声明)的条目。

-   发现特征:针对每个服务,发送ATT_READ_BY_TYPE_REQ,查找UUID=0x2803(特征声明),获取特征的属性(Properties)和句柄(Handle)。

二、通知机制(Notify/Indicate):服务端主动“推数据”

BLE通信默认是Client主动请求(Read/Write)。若要让Server(如传感器)主动上报数据,必须使用通知(Notify) 或指示(Indicate) 机制。

1. Notify vs Indicate:一字之差,天壤之别

两者都用于Server主动发送数据,但可靠性机制完全不同:

特性 Notify(通知) Indicate(指示)

确认机制 ❌ 无ACK(发完即忘) ✅ 有ACK(必须确认)

可靠性 可能丢包(适合传感器流) 可靠送达(适合关键指令)

速度 快,无等待开销 慢,需等待Client回复

适用场景 心率、温度等高频数据 固件升级确认、开关指令

2. 使能通知的“三步走”代码(Android示例)

通知机制是双向握手的:Client必须先告诉Server“我要订阅”,Server才能推送。这通过写入CCCD(客户端特征配置描述符) 实现。

// 1. 获取目标特征(Characteristic)

val service = gatt.getService(UUID_SERVICE)

val char = service.getCharacteristic(UUID_CHAR_NOTIFY)

// 2. 本地开启监听(告诉Android系统准备接收)

gatt.setCharacteristicNotification(char, true)

// 3. 关键步骤:写入CCCD描述符,告诉设备端“开启通知”

val cccd = char.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))

cccd.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE

gatt.writeDescriptor(cccd) // 这一步才是真正的“订阅”

关键点:

•   CCCD UUID:固定为00002902-0000-1000-8000-00805f9b34fb。

•   写入值:ENABLE_NOTIFICATION_VALUE(0x0001)开启通知;ENABLE_INDICATION_VALUE(0x0002)开启指示。

3. 服务端(嵌入式侧)发送逻辑

当Server端(如ESP32、nRF52)检测到CCCD被写入0x0001后,即可在数据更新时主动发送。

// 基于ESP-IDF的伪代码示例

// 检查客户端是否开启了通知

uint16_t cccd_value;

esp_ble_gatts_get_attr_value(cccd_handle, &cccd_value);

if (cccd_value & ESP_GATT_PERM_NOTIFY) {

   // 发送通知(无确认,高效)

   esp_ble_gatts_send_indicate(gatts_if, conn_id, char_handle, data_len, data, false);

   // 若为Indicate,最后一个参数传 true

}

三、量产级调试与排错

1. 服务发现失败(onServicesDiscovered返回错误)

•   原因:MTU协商失败或ATT超时。

•   对策:在连接后、发现服务前,先调用gatt.requestMtu(247)申请更大的MTU(需设备端支持),减少分包次数。

2. 收不到通知(Notify不触发)

•   检查CCCD:90%的问题出在这里。确认Client端writeDescriptor是否成功,且Server端正确解析了CCCD的值。

•   连接参数:通知频率受Connection Interval限制。若间隔太长(如1秒),数据会堆积。建议在Android端调用gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)请求更短的间隔。

3. 数据截断

•   默认限制:BLE默认ATT_MTU为23字节,有效载荷仅20字节。长数据需进行MTU协商(如扩展到247字节)或应用层分包。

四、结语

GATT服务发现是BLE通信的“地基”,而通知机制是实时数据流的“桥梁”。掌握这两者,意味着你已突破了BLE开发中最容易卡壳的关卡。记住核心口诀:先发现,后操作;开通知,先写CCCD。

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录