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

使用Flutter Blue Plus读取ESP32特征时触发PlatformException问题

Flutter Blue Plus读取ESP32蓝牙特征触发PlatformException异常排查与解决

问题现象

使用Flutter Blue Plus库从ESP32读取蓝牙特征时,触发PlatformException,异常信息如下:

PlatformException(read_characteristic_error,unknown reason,may occur if readCharacteristic was called before last read finished, null, null)

用户提供的读取特征代码:

Future<void> readData async{
  BluetoothService? DataService =
  BLEComm().getService(DeviceServiceUUIDs.Service);
  if (null != DataService) {
  await Future.delayed(Duration(milliseconds:50),() async{
  BluetoothCharacteristic? DurationCharacteristic =
  BLEComm().getCharacteristic(
  DeviceServiceUUIDs.duration,
  DataService);
  List<int>? DurationResponse = await BLEComm()
      .readCharacteristic(DurationCharacteristic);
  String Duration = String.fromCharCodes(DurationResponse!);
  var Duration_Data = double.parse(Duration);
  });

依赖的底层BLEComm实现代码:

/// Get a specific service from the list of services.
BluetoothService? getService(String uuid) {
  for (BluetoothService? service in _deviceServices) {
    if (service?.uuid.toString() == uuid) {
      return service;
    }
  }
  return null;
}

/// Get Characteristic object from a service
BluetoothCharacteristic? getCharacteristic(
    String uuid, BluetoothService bleService) {
  var characteristics = bleService.characteristics;
  for (BluetoothCharacteristic characteristic in characteristics) {
    if (characteristic.uuid.toString() == uuid) {
      return characteristic;
    }
  }
  return null;
}

/// read value to a characteristic
/// Throws Exceptions on Non readable characteristic and general exceptions.
Future<List<int>?> readCharacteristic(
    BluetoothCharacteristic? characteristic) async {
  List<int>? value;
  try {
    if (null != characteristic && !characteristic.properties.read) {
      throw ("characteristic ${characteristic.uuid.toString()} is not readable");
    }
    value = await characteristic?.read();
  } catch (ex) {
    throw Exception(ex);
  }
  return value;
}

排查分析

从异常提示readCharacteristic was called before last read finished可以确定,核心问题是重复发起特征读取请求,前一次读取操作还未完成就触发了新的请求。结合代码细节,具体问题点包括:

  • BLEComm实例重复创建:每次调用BLEComm()都会生成新实例,无法统一管理蓝牙操作状态,容易引发并发读取冲突。
  • 无并发控制机制:没有状态标记或锁来限制同一时间仅执行一次读取操作,若readData被多次调用会直接触发重复请求。
  • Future.delayed无效:用固定延迟规避时序问题不可靠,无法精准确保前一次读取操作完成。
  • 空值处理不严谨:DurationResponse!强制解包会掩盖读取失败的错误,同时可能触发空指针异常。

解决方法

1. 改造BLEComm为单例模式

确保全局只有一个BLEComm实例,统一管理蓝牙操作状态:

class BLEComm {
  static final BLEComm _instance = BLEComm._internal();
  factory BLEComm() => _instance;
  BLEComm._internal();

  // 原有getService、getCharacteristic方法保持不变
}

2. 添加读取操作的并发控制

在BLEComm中加入状态标记,避免同一时间执行多个读取操作:

class BLEComm {
  static final BLEComm _instance = BLEComm._internal();
  factory BLEComm() => _instance;
  BLEComm._internal();

  bool _isReading = false; // 读取状态标记

  Future<List<int>?> readCharacteristic(
      BluetoothCharacteristic? characteristic) async {
    if (_isReading) {
      throw Exception("前一次读取操作尚未完成");
    }
    List<int>? value;
    try {
      _isReading = true;
      if (null != characteristic && !characteristic.properties.read) {
        throw ("characteristic ${characteristic.uuid.toString()} 不支持读取");
      }
      value = await characteristic?.read();
    } catch (ex) {
      throw Exception(ex);
    } finally {
      _isReading = false; // 无论成败都重置状态
    }
    return value;
  }

  // 原有getService、getCharacteristic方法保持不变
}

3. 优化readData方法逻辑

移除无效的延迟,完善空值和异常处理:

Future<void> readData() async {
  final bleComm = BLEComm();
  BluetoothService? dataService = bleComm.getService(DeviceServiceUUIDs.Service);
  if (dataService == null) {
    print("未找到目标服务");
    return;
  }

  BluetoothCharacteristic? durationCharacteristic = bleComm.getCharacteristic(
    DeviceServiceUUIDs.duration,
    dataService,
  );
  if (durationCharacteristic == null) {
    print("未找到目标特征");
    return;
  }

  try {
    List<int>? durationResponse = await bleComm.readCharacteristic(durationCharacteristic);
    if (durationResponse == null || durationResponse.isEmpty) {
      print("读取结果为空");
      return;
    }
    String durationStr = String.fromCharCodes(durationResponse);
    double durationData = double.parse(durationStr);
    print("读取到的时长数据:$durationData");
    // 此处添加数据处理逻辑
  } catch (e) {
    print("读取特征失败:$e");
  }
}

4. 检查ESP32端蓝牙逻辑

确认ESP32的BLE特征读取处理代码无阻塞,确保每次读取请求都能及时返回响应,避免因硬件端未完成处理导致客户端重复发起请求。

内容的提问来源于stack exchange,提问作者Nns_ninteyFIve

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 15:43:27