Flutter Blue连接成功后如何正确读取Arduino发送的特征值
问题根因
- 核心问题出在Flutter端BLE实现逻辑,Arduino端代码不需要修改,此前经典蓝牙SPP能正常通信已经验证了发送端逻辑正确。
- 第一类错误是订阅了错误的BLE特征:BLE设备会暴露多组服务与特征,设备信息服务下的特征仅会返回设备名称、厂商版本这类系统广播信息,你之前拿到的
[45, 27, 8, 9]这类零散无意义数据,就是误订阅了系统特征导致的。Arduino发送的业务数据走单独的私有透传服务特征。 - 第二类错误是分包拼接逻辑错误:BLE单包默认最大有效载荷仅20字节,超过长度的数据会被拆成多个包分批推送,你原有代码每次收到新包就清空
finalData缓存,永远只能拿到分片内容,不可能拼出完整的目标字符串。 - flutter_blue插件本身不会自动拼接BLE分片、也不会自动区分数据帧,需要开发者基于自定义的数据格式做缓冲区处理。
正确实现步骤
- 第一步:连接设备后执行全量服务发现,遍历所有服务和特征,找到支持
notify/indicate属性的透传特征。通用Arduino BLE透传模块(HM-10、CC2541等)的透传服务UUID固定为0000ffe0-0000-1000-8000-00805f9b34fb,对应透传特征UUID为0000ffe1-0000-1000-8000-00805f9b34fb,注意避开设备信息服务(Device Information Service)下的所有特征。 - 第二步:开启通知前主动请求更大的MTU,减少分包数量,连接成功后直接调用
device.requestMtu(512)即可,iOS端该接口无实际副作用,不需要做平台判断。 - 第三步:维护全局固定的接收缓冲区,每次收到新的分片直接追加到缓冲区末尾,禁止每次收到包就清空缓存。
- 第四步:基于你Arduino端发送的数据格式(
[开头、]结尾)做帧完整性判断,只有凑齐完整一帧后再做UTF8转码和业务处理,处理完的帧从缓冲区中移除即可。
可运行参考代码
import 'dart:typed_data'; import 'package:flutter_blue/flutter_blue.dart'; import 'dart:convert'; // 全局接收缓冲区,不要在监听回调内重复初始化或清空 List<int> _receiveBuffer = []; // 透传服务与特征UUID,若通用值匹配失败,打印全量服务列表替换为实际值即可 final Guid uartServiceUuid = Guid("0000ffe0-0000-1000-8000-00805f9b34fb"); final Guid uartCharUuid = Guid("0000ffe1-0000-1000-8000-00805f9b34fb"); Future<void> initBleListener(BluetoothDevice connectedDevice) async { // 发现设备所有服务 List<BluetoothService> services = await connectedDevice.discoverServices(); BluetoothCharacteristic? targetUartChar; // 遍历匹配透传特征 for (BluetoothService service in services) { if (service.uuid == uartServiceUuid) { for (BluetoothCharacteristic char in service.characteristics) { if (char.uuid == uartCharUuid && char.properties.notify) { targetUartChar = char; break; } } } } if (targetUartChar == null) { print("未找到匹配的透传特征,请打印所有服务UUID核对"); return; } // 协商MTU,减少分包 await connectedDevice.requestMtu(512); // 开启特征通知 await targetUartChar.setNotifyValue(true); // 监听特征值返回 targetUartChar.value.listen((Uint8List dataChunk) { // 新分片直接追加到缓冲区 _receiveBuffer.addAll(dataChunk); // 循环处理缓冲区中的完整帧,兼容粘包场景 while (true) { // 查找帧头 [ 对应的ASCII码0x5B int frameStart = _receiveBuffer.indexOf(0x5B); if (frameStart == -1) { // 无有效帧头,清空无效垃圾数据 _receiveBuffer.clear(); break; } // 从帧头位置开始查找帧尾 ] 对应的ASCII码0x5D int frameEnd = _receiveBuffer.indexOf(0x5D, frameStart); if (frameEnd == -1) { // 帧尾未到,保留缓冲区数据等待下一分片,跳出循环 break; } // 取出完整帧内的有效数据 List<int> validPayload = _receiveBuffer.sublist(frameStart + 1, frameEnd); // 移除已处理的帧数据 _receiveBuffer.removeRange(0, frameEnd + 1); // 转码得到目标字符串,格式为你需要的111...234数值串 String arduinoData = utf8.decode(validPayload, allowMalformed: true); print("收到Arduino返回数据:$arduinoData"); // 此处写入你的后续业务逻辑即可 } }); }
排查注意事项
- 如果通用UUID无法匹配到透传特征,直接遍历打印所有服务下的特征UUID,筛选出属性支持notify、不属于设备信息服务的特征替换即可。
- Arduino端不需要修改任何发送逻辑,BLE协议层会自动完成长包拆分,不要手动在Arduino端加拆包延时,否则会增加拼接复杂度。
- 如果转码后出现乱码,先确认Arduino发送的是ASCII格式的字符串,如果是原始二进制数值,直接解析
validPayload字节数组即可,不需要走UTF8转码。 - 不要在监听回调内反复初始化或清空接收缓冲区,否则会丢失分片数据,永远拿不到完整帧。
内容的提问来源于stack exchange,提问作者devlade
相关产品推荐
相关产品推荐

