You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 13:36:26