从零构建BLE应用:深入解析服务、特征与UUID的实战指南

张开发
2026/4/18 22:55:17 15 分钟阅读

分享文章

从零构建BLE应用:深入解析服务、特征与UUID的实战指南
1. 为什么BLE开发离不开服务、特征与UUID第一次接触BLE开发时我盯着那些Service、Characteristic和UUID也是一头雾水。直到做了一个温湿度传感器项目才明白这三者就像快递柜的取件系统服务是快递柜的某个区域比如生鲜区特征是具体的快递格口UUID则是每个格口的唯一编号。没有这套体系手机和蓝牙设备就像找不到快递的收件人。经典蓝牙和BLE最大的区别在于能耗设计。我做过对比测试同样传输温度数据经典蓝牙平均功耗15mABLE仅需0.01mA。这就是为什么智能手环能用小电池续航一周而传统蓝牙耳机每天都要充电。但BLE每次只能传20字节数据所以开发时要像发短信一样精简内容。2. 手把手设计温湿度传感器的蓝牙服务2.1 服务(Service)的规划逻辑去年给农场做环境监测系统时我把蓝牙服务分成三个模块设备信息服务0x180A包含固件版本、电量等固定信息环境数据服务自定义UUID处理实时温湿度数据配置服务自定义UUID接收报警阈值设置用Android Studio的Bluetooth LE Scanner工具扫描能看到设备广播的服务列表就像餐厅菜单。标准服务用SIG定义的16位UUID如0x180A自定义服务则需要生成完整的128位UUID。这里有个坑iOS对自定义UUID的缓存机制可能导致服务刷新不及时解决方法是在设备断开连接时手动清除缓存。2.2 特征(Characteristic)的属性配置特征才是真正的数据通道我的温湿度传感器用了这些配置// 温度读取特征只读通知 BLECharacteristic tempCharacteristic( 00002A6E-0000-1000-8000-00805F9B34FB, // 标准温度UUID BLERead | BLENotify, 4 // 32位浮点数占4字节 ); // 阈值设置特征可写 BLECharacteristic thresholdCharacteristic( C3F11001-0000-1000-8000-00805F9B34FB, // 自定义UUID BLEWrite, 2 // 16位整数足够 );特别注意属性权限的设置BLERead手机可以主动读取BLENotify设备主动推送温度变化时BLEWriteWithoutResponse快速写入不等待确认适合频繁配置BLEIndicate带确认的通知关键报警实测发现如果误将只读特征设为可写Android会直接报WRITE_NOT_PERMITTED错误而iOS可能静默失败。3. UUID的实战选用策略3.1 标准UUID与自定义UUID的抉择蓝牙技术联盟SIG已经定义了大量标准UUID比如电池服务0x180F心率测量0x2A37设备名称0x2A00在项目中应优先使用标准UUID有两个好处跨平台兼容性好客户端无需额外文档就能理解功能但当需要传输特殊数据时比如土壤酸碱度就得用自定义UUID。生成时建议遵循这个格式XXXXXXXX-0000-1000-8000-00805F9B34FB前4字节自定义后面固定。用在线工具生成时要注意有些工具会产生完全随机的UUID可能导致某些手机系统无法识别。3.2 UUID的存储与调用技巧在Arduino代码中我习惯用宏定义管理UUID#define ENV_SERVICE_UUID C3F11000-0000-1000-8000-00805F9B34FB #define TEMP_CHAR_UUID 00002A6E-0000-1000-8000-00805F9B34FB #define HUMIDITY_CHAR_UUID C3F11001-0000-1000-8000-00805F9B34FB BLEService envService(ENV_SERVICE_UUID);这样修改时只需调整一处。曾经因为手误写错一个字符调试了两天才发现UUID不匹配的问题。4. 完整实现流程与避坑指南4.1 设备端固件开发步骤以ESP32为例完整流程如下初始化BLE服务BLEDevice::init(EnvSensor); BLEServer *pServer BLEDevice::createServer(); BLEService *pService pServer-createService(ENV_SERVICE_UUID);添加特征并设置回调BLECharacteristic *pChar pService-createCharacteristic( TEMP_CHAR_UUID, BLERead | BLENotify ); pChar-setValue(0.0f); // 初始值 // 写入回调示例 class Callbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pChar) { uint8_t* data pChar-getData(); // 处理配置数据 } }; pChar-setCallbacks(new Callbacks());启动服务并广播pService-start(); BLEAdvertising *pAdvertising pServer-getAdvertising(); pAdvertising-start();常见问题排查手机搜不到设备检查广播间隔建议20-100ms连接频繁断开增大MTU大小ESP32默认23字节数据传输错误检查特征值的字节序4.2 手机端开发关键代码Android端的核心操作// 发现特征后设置通知 fun enableNotification(characteristic: BluetoothGattCharacteristic) { gatt?.setCharacteristicNotification(characteristic, true) val descriptor characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID) descriptor.value BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt?.writeDescriptor(descriptor) } // 接收通知数据 override fun onCharacteristicChanged( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic ) { when(characteristic.uuid) { TEMP_CHAR_UUID - { val temp characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_FLOAT, 0) updateUI(temp) } } }iOS端需要特别注意每次连接后需要重新发现服务写入数据要区分withResponse和withoutResponse通知需要手动订阅5. 进阶优化技巧当需要传输复杂数据时比如带时间戳的温湿度记录可以采用这些方案数据分包将长数据拆分成多个20字节包协议设计在数据前添加类型和长度标识错误校验添加简单的CRC校验曾经有个智能花盆项目因为土壤数据偶尔出错后来在特征值里加了1字节的校验码后问题解决。对于关键数据还可以启用BLE的加密功能通过配对绑定建立安全连接。

更多文章