1079 lines
42 KiB
Dart
1079 lines
42 KiB
Dart
import 'dart:async';
|
||
|
||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||
import 'package:starwork_flutter/base/app_logger.dart';
|
||
import 'package:starwork_flutter/ble/ble_config.dart';
|
||
import 'package:starwork_flutter/ble/command/base/base_ble_command.dart';
|
||
import 'package:starwork_flutter/ble/command/ble_command_manager.dart';
|
||
import 'package:starwork_flutter/ble/command/request/ble_cmd_add_admin.dart';
|
||
import 'package:starwork_flutter/ble/command/request/ble_cmd_get_private_key.dart';
|
||
import 'package:starwork_flutter/ble/command/request/ble_cmd_get_public_key.dart';
|
||
import 'package:starwork_flutter/ble/command/request/ble_cmd_read_lock_status.dart';
|
||
import 'package:starwork_flutter/ble/command/response/ble_cmd_get_private_key_parser.dart';
|
||
import 'package:starwork_flutter/ble/command/response/ble_cmd_get_public_key_parser.dart';
|
||
import 'package:starwork_flutter/ble/model/scan_device_info.dart';
|
||
import 'package:starwork_flutter/common/constant/device_type.dart';
|
||
|
||
/// ✨✨✨ 待发送命令类 ✨✨✨
|
||
class _PendingCommand<T> {
|
||
final BaseBleCommand<T> command;
|
||
final String targetDeviceId;
|
||
final String? targetDeviceName;
|
||
final Duration timeout;
|
||
final Completer<T?> completer;
|
||
final DateTime createdAt;
|
||
final int commandId; // 添加命令ID字段
|
||
|
||
_PendingCommand({
|
||
required this.command,
|
||
required this.targetDeviceId,
|
||
this.targetDeviceName,
|
||
required this.timeout,
|
||
required this.completer,
|
||
required this.commandId, // 添加命令ID参数
|
||
}) : createdAt = DateTime.now();
|
||
}
|
||
|
||
class BleService {
|
||
// 私有构造函数
|
||
BleService._() {
|
||
// ✅ 这里就是单例初始化的地方
|
||
// 只会执行一次(第一次获取实例时)
|
||
_initialize();
|
||
}
|
||
|
||
// 静态实例
|
||
static final BleService _instance = BleService._();
|
||
|
||
// 工厂构造函数,提供全局访问点
|
||
factory BleService() => _instance;
|
||
|
||
/// 用于存储最新的私钥,避免在解密时需要异步读取SharedPreferences
|
||
static List<int>? _cachedPrivateKey;
|
||
|
||
/// 获取缓存的私钥
|
||
static List<int>? get cachedPrivateKey => _cachedPrivateKey;
|
||
|
||
/// 设置缓存的私钥
|
||
static void setCachedPrivateKey(List<int> privateKey) {
|
||
_cachedPrivateKey = List.from(privateKey);
|
||
}
|
||
|
||
/// 用来存储搜索到的设备,并用于去重和过滤
|
||
final Map<String, ScanResult> _discoveredDevices = {};
|
||
|
||
// 使用命令管理器处理数据包
|
||
final BleCommandManager bleCommandManager = BleCommandManager();
|
||
|
||
/// mtu大小
|
||
int _mtu = 23;
|
||
|
||
/// 用来监听蓝牙适配器状态的订阅流
|
||
StreamSubscription<BluetoothAdapterState>? _adapterStateSubscription;
|
||
|
||
/// 用来监听搜索到的设备的订阅流
|
||
StreamSubscription<List<ScanResult>>? _scanResultSubscription;
|
||
|
||
/// 用来监听连接设备时的连接状态
|
||
StreamSubscription<BluetoothConnectionState>? _connectionStateSubscription;
|
||
|
||
/// 用来监听mtu的变化
|
||
StreamSubscription<int>? _mtuChangeSubscription;
|
||
|
||
/// 用来监听订阅服务的数据
|
||
StreamSubscription<List<int>>? _characteristicDataSubscription;
|
||
|
||
/// 当前连接的设备
|
||
BluetoothDevice? _connectedDevice;
|
||
|
||
/// 当前的写入特征值
|
||
BluetoothCharacteristic? _writeCharacteristic;
|
||
|
||
/// 当前的订阅特征值
|
||
BluetoothCharacteristic? _subscriptionCharacteristic;
|
||
|
||
/// ✨✨✨ 命令响应等待器映射 ✨✨✨
|
||
final Map<String, Completer<dynamic>> _commandResponseWaiters = {};
|
||
|
||
/// ✨✨✨ 命令超时定时器映射 ✨✨✨
|
||
final Map<String, Timer> _commandTimeouts = {};
|
||
|
||
/// ✨✨✨ 命令ID到命令键的映射 ✨✨✨
|
||
final Map<int, String> _commandIdToKeyMap = {};
|
||
|
||
/// 待发送命令队列 (用于自动连接后发送)
|
||
final List<_PendingCommand<dynamic>> _pendingCommands = [];
|
||
|
||
/// 搜索状态监控定时器
|
||
Timer? _scanningMonitorTimer;
|
||
|
||
// 内部维护的蓝牙状态
|
||
BluetoothAdapterState _bluetoothAdapterState = BluetoothAdapterState.unknown;
|
||
|
||
// 内部维护的设备连接状态
|
||
BluetoothConnectionState _bluetoothConnectionState = BluetoothConnectionState.disconnected;
|
||
|
||
/// 提供外部获取蓝牙适配器方法
|
||
BluetoothAdapterState get bluetoothAdapterState => _bluetoothAdapterState;
|
||
|
||
/// 搜索状态
|
||
bool get isScanningNow => FlutterBluePlus.isScanningNow;
|
||
|
||
/// 获取当前连接的设备
|
||
BluetoothDevice? get connectedDevice => _connectedDevice;
|
||
|
||
/// 初始化服务时执行的
|
||
Future<void> _initialize() async {
|
||
AppLogger.highlight('🚀 BleService 正在初始化...');
|
||
|
||
/// 监听蓝牙适配器状态
|
||
_adapterStateSubscription = FlutterBluePlus.adapterState.listen((BluetoothAdapterState state) {
|
||
_bluetoothAdapterState = state;
|
||
AppLogger.highlight('蓝牙适配器状态发送变化:${state}');
|
||
});
|
||
|
||
AppLogger.highlight('✅ BleService 初始化完成');
|
||
}
|
||
|
||
/// 开启蓝牙搜索
|
||
void enableBluetoothSearch({
|
||
DeviceType deviceType = DeviceType.all,
|
||
Duration searchTime = const Duration(seconds: 15),
|
||
required void Function(ScanDeviceInfo device) onDeviceFound,
|
||
}) async {
|
||
// 检查蓝牙适配器状态
|
||
AppLogger.highlight('🔍 当前蓝牙适配器状态: $_bluetoothAdapterState');
|
||
|
||
// 如果状态是unknown,等待一下状态更新
|
||
if (_bluetoothAdapterState == BluetoothAdapterState.unknown) {
|
||
AppLogger.highlight('⏳ 等待蓝牙适配器状态更新...');
|
||
await Future.delayed(const Duration(milliseconds: 500));
|
||
AppLogger.highlight('🔍 等待后蓝牙适配器状态: $_bluetoothAdapterState');
|
||
}
|
||
|
||
if (_bluetoothConnectionState == BluetoothConnectionState.connected) {
|
||
// 搜索时断开已有连接
|
||
List<BluetoothDevice> devs = FlutterBluePlus.connectedDevices;
|
||
for (var d in devs) {
|
||
d.disconnect();
|
||
}
|
||
}
|
||
|
||
if (_bluetoothAdapterState == BluetoothAdapterState.on) {
|
||
AppLogger.highlight('🚀 开始启动蓝牙扫描,搜索时长: ${searchTime.inSeconds}秒');
|
||
|
||
try {
|
||
FlutterBluePlus.startScan(timeout: searchTime);
|
||
|
||
/// 取消旧的订阅,防止重复
|
||
_scanResultSubscription?.cancel();
|
||
_discoveredDevices.clear();
|
||
|
||
/// 监听搜索到的设备
|
||
_scanResultSubscription = FlutterBluePlus.onScanResults.listen(
|
||
(List<ScanResult> results) {
|
||
for (var result in results) {
|
||
var device = result.device;
|
||
final deviceId = device.remoteId.toString();
|
||
final platformName = device.platformName;
|
||
var serviceUuids = result.advertisementData.serviceUuids;
|
||
|
||
// ✅ 只有新设备才回调
|
||
if (!_discoveredDevices.containsKey(deviceId) && platformName.isNotEmpty) {
|
||
_discoveredDevices[deviceId] = result;
|
||
bool pairStatus = false;
|
||
bool hasNewEvent = false;
|
||
for (var uuid in serviceUuids) {
|
||
String uuidStr = uuid.toString().replaceAll('-', '');
|
||
if (uuidStr.length == 8) {
|
||
var pairStatusStr = uuidStr.substring(4, 6);
|
||
var hasNewEventStr = uuidStr.substring(6, 8);
|
||
pairStatus = pairStatusStr == '01';
|
||
hasNewEvent = hasNewEventStr == '01';
|
||
var scanDeviceInfo = ScanDeviceInfo(
|
||
isBinding: pairStatus,
|
||
advName: device.platformName,
|
||
rawDeviceInfo: result,
|
||
hasNewEvent: hasNewEvent,
|
||
);
|
||
onDeviceFound.call(scanDeviceInfo);
|
||
} else if (uuidStr.length == 32) {
|
||
var pairStatusStr = uuidStr.substring(26, 28);
|
||
pairStatus = pairStatusStr == '00'; // 第4、5位(索引3和4)
|
||
int statusValue = int.parse(pairStatusStr, radix: 16);
|
||
// 提取 byte0(配对状态:第1位)
|
||
int byte0 = (statusValue >> 0) & 0x01; // 取最低位
|
||
// 提取 byte1(事件状态:第2位)
|
||
int byte1 = (statusValue >> 1) & 0x01; // 取次低位
|
||
// 判断是否未配对
|
||
pairStatus = (byte0 == 1);
|
||
// 判断是否有新事件
|
||
hasNewEvent = (byte1 == 1);
|
||
var scanDeviceInfo = ScanDeviceInfo(
|
||
isBinding: pairStatus,
|
||
advName: device.platformName,
|
||
rawDeviceInfo: result,
|
||
hasNewEvent: hasNewEvent,
|
||
);
|
||
onDeviceFound.call(scanDeviceInfo);
|
||
}
|
||
}
|
||
} else {
|
||
// 可选:更新 RSSI
|
||
_discoveredDevices[deviceId] = result;
|
||
}
|
||
}
|
||
},
|
||
onError: (e) => AppLogger.error('搜索设备时遇到错误:' + e),
|
||
);
|
||
|
||
AppLogger.highlight('✅ 蓝牙搜索已成功启动');
|
||
|
||
// 监听搜索状态变化
|
||
_monitorScanningState();
|
||
} catch (e, stackTrace) {
|
||
AppLogger.error('启动蓝牙搜索失败', error: e, stackTrace: stackTrace);
|
||
}
|
||
} else {
|
||
AppLogger.error('❌ 蓝牙适配器未开启,当前状态: $_bluetoothAdapterState');
|
||
}
|
||
}
|
||
|
||
/// 监控搜索状态变化
|
||
void _monitorScanningState() {
|
||
// 取消之前的定时器
|
||
_scanningMonitorTimer?.cancel();
|
||
|
||
_scanningMonitorTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||
bool currentlyScanning = FlutterBluePlus.isScanningNow;
|
||
|
||
if (!currentlyScanning) {
|
||
AppLogger.highlight('🔍 搜索已停止 (监控器检测)');
|
||
timer.cancel();
|
||
_scanningMonitorTimer = null;
|
||
}
|
||
});
|
||
}
|
||
|
||
/// 停止扫描
|
||
void stopBluetoothSearch() async {
|
||
var isScanningNow = FlutterBluePlus.isScanningNow;
|
||
if (isScanningNow) {
|
||
FlutterBluePlus.stopScan();
|
||
}
|
||
|
||
// 取消搜索状态监控定时器
|
||
_scanningMonitorTimer?.cancel();
|
||
_scanningMonitorTimer = null;
|
||
|
||
/// 清空搜索到的设备
|
||
_discoveredDevices.clear();
|
||
|
||
AppLogger.highlight('🛑 蓝牙搜索已停止');
|
||
}
|
||
|
||
void seedData({
|
||
required BaseBleCommand command,
|
||
}) async {
|
||
// 搜索时断开已有连接
|
||
List<BluetoothDevice> devs = FlutterBluePlus.connectedDevices;
|
||
for (var d in devs) {
|
||
d.disconnect();
|
||
}
|
||
}
|
||
|
||
/// 监听连接设备时的状态
|
||
void _handleListenBluetoothConnectionState(BluetoothConnectionState state) {
|
||
_bluetoothConnectionState = state;
|
||
}
|
||
|
||
/// 监听mtu变化
|
||
void _handleListenMtuChange(int mtu) {
|
||
_mtu = mtu;
|
||
// MTU包含了协议开销,需要减去包头和包尾的长度
|
||
// 包头: 4字节固定值 + 1字节包类型 + 2字节序号 + 1字节标识 + 4字节长度 = 12字节
|
||
// 包尾: 2字节CRC = 2字节
|
||
// 蓝牙协议还有额外开销,实际可用数据长度需要进一步减少
|
||
// 根据错误信息,MTU为185时,实际最大数据长度应该是182字节
|
||
// 所以我们保守计算,减去更多字节以确保兼容性
|
||
BaseBleCommand.MAX_PACKET_DATA_SIZE = mtu - 18;
|
||
AppLogger.info('📏 MTU更新为: $mtu, 最大数据长度设置为: ${BaseBleCommand.MAX_PACKET_DATA_SIZE}');
|
||
}
|
||
|
||
/// 用于存储分包接收的数据
|
||
final Map<int, List<int>> _pendingPackets = {};
|
||
|
||
/// 监听订阅值变化
|
||
void _handleListenCharacteristicData(List<int> data) {
|
||
AppLogger.highlight('✨✨✨ 📨 收到订阅数据:$data (长度: ${data.length}) ✨✨✨');
|
||
|
||
// 解析数据
|
||
if (data.isNotEmpty) {
|
||
// ✨✨✨ 处理分包数据重组 ✨✨✨
|
||
List<int>? completePacket = _reassemblePacket(data);
|
||
|
||
if (completePacket != null) {
|
||
// 如果有完整数据包,进行处理
|
||
dynamic result = bleCommandManager.handleResponse(completePacket);
|
||
|
||
if (result != null) {
|
||
// 触发命令响应等待器
|
||
_triggerCommandResponseWaiters(result);
|
||
} else {
|
||
AppLogger.warn('⚠️ 数据包解析失败或不匹配任何命令');
|
||
}
|
||
}
|
||
} else {
|
||
AppLogger.warn('✨✨✨ ⚠️ 收到空数据 ✨✨✨');
|
||
}
|
||
}
|
||
|
||
/// ✨✨✨ 重组分包数据 ✨✨✨
|
||
/// [data] 接收到的数据片段
|
||
/// 返回完整的数据包,如果数据不完整则返回null
|
||
List<int>? _reassemblePacket(List<int> data) {
|
||
try {
|
||
// 1. 检查是否是包头开始的数据
|
||
if (data.length >= 4 &&
|
||
data[0] == BaseBleCommand.PACKET_HEADER[0] &&
|
||
data[1] == BaseBleCommand.PACKET_HEADER[1] &&
|
||
data[2] == BaseBleCommand.PACKET_HEADER[2] &&
|
||
data[3] == BaseBleCommand.PACKET_HEADER[3]) {
|
||
// 这是一个新包的开始
|
||
// 首先检查是否已有未完成的包,如果有则丢弃(超时处理)
|
||
if (_pendingPackets.isNotEmpty) {
|
||
AppLogger.warn('⚠️ 检测到新包开始,丢弃未完成的包');
|
||
_pendingPackets.clear();
|
||
}
|
||
|
||
// 解析包头信息以获取预期长度
|
||
if (data.length >= 12) {
|
||
// 解析包序号 (大端序)
|
||
int packetSequence = (data[5] << 8) | data[6];
|
||
|
||
// 解析数据长度 (大端序: 高16位加密后长度 + 低16位原始长度)
|
||
int combinedLength = (data[8] << 24) | (data[9] << 16) | (data[10] << 8) | data[11];
|
||
int encryptedDataLength = (combinedLength >> 16) & 0xFFFF;
|
||
int originalDataLength = combinedLength & 0xFFFF;
|
||
|
||
// 根据加密类型确定实际数据长度
|
||
int encryptType = data[7] & 0x0F;
|
||
int actualDataLength = (encryptType == BaseBleCommand.ENCRYPT_TYPE_PLAIN) ? originalDataLength : encryptedDataLength;
|
||
|
||
// 计算完整包的预期长度
|
||
int expectedTotalLength = 12 + actualDataLength + 2; // 包头到数据长度(12) + 数据块 + CRC(2)
|
||
|
||
AppLogger.debug('📦 新包开始: 序号=$packetSequence, 预期长度=$expectedTotalLength, 实际长度=${data.length}');
|
||
|
||
if (data.length == expectedTotalLength) {
|
||
// 数据完整,直接返回
|
||
AppLogger.debug('✅ 数据包完整,无需重组');
|
||
return data;
|
||
} else if (data.length < expectedTotalLength) {
|
||
// 数据不完整,存储到待处理队列
|
||
AppLogger.debug('📥 数据包不完整,开始重组: 已接收${data.length}/$expectedTotalLength字节');
|
||
_pendingPackets[packetSequence] = List.from(data);
|
||
return null;
|
||
} else {
|
||
// 数据长度超过预期,可能存在错误
|
||
AppLogger.warn('⚠️ 数据包长度异常: 实际${data.length}字节 > 预期$expectedTotalLength字节');
|
||
return data;
|
||
}
|
||
} else {
|
||
// 包头信息不完整
|
||
AppLogger.warn('⚠️ 包头信息不完整,无法解析包长度');
|
||
return data;
|
||
}
|
||
} else {
|
||
// 这是数据片段,尝试追加到现有未完成的包
|
||
if (_pendingPackets.isNotEmpty) {
|
||
// 获取第一个(也是唯一一个)未完成的包
|
||
int packetSequence = _pendingPackets.keys.first;
|
||
List<int> pendingData = _pendingPackets[packetSequence]!;
|
||
|
||
// 追加数据
|
||
pendingData.addAll(data);
|
||
_pendingPackets[packetSequence] = pendingData;
|
||
|
||
AppLogger.debug('📥 追加数据片段: 当前长度${pendingData.length}字节');
|
||
|
||
// 尝试解析包头信息以检查是否完整
|
||
if (pendingData.length >= 12) {
|
||
// 解析数据长度
|
||
int combinedLength = (pendingData[8] << 24) | (pendingData[9] << 16) | (pendingData[10] << 8) | pendingData[11];
|
||
int encryptedDataLength = (combinedLength >> 16) & 0xFFFF;
|
||
int originalDataLength = combinedLength & 0xFFFF;
|
||
|
||
// 根据加密类型确定实际数据长度
|
||
int encryptType = pendingData[7] & 0x0F;
|
||
int actualDataLength = (encryptType == BaseBleCommand.ENCRYPT_TYPE_PLAIN) ? originalDataLength : encryptedDataLength;
|
||
|
||
// 计算完整包的预期长度
|
||
int expectedTotalLength = 12 + actualDataLength + 2; // 包头到数据长度(12) + 数据块 + CRC(2)
|
||
|
||
if (pendingData.length == expectedTotalLength) {
|
||
// 数据完整,返回完整包并清理待处理队列
|
||
AppLogger.debug('✅ 数据包重组完成: 序号=$packetSequence, 长度=$expectedTotalLength字节');
|
||
List<int> completePacket = _pendingPackets.remove(packetSequence)!;
|
||
return completePacket;
|
||
} else if (pendingData.length > expectedTotalLength) {
|
||
// 数据长度超过预期,可能存在错误
|
||
AppLogger.warn('⚠️ 重组后数据包长度异常: 实际${pendingData.length}字节 > 预期$expectedTotalLength字节');
|
||
_pendingPackets.clear();
|
||
return pendingData;
|
||
}
|
||
// 如果长度还不够,继续等待更多数据
|
||
}
|
||
// 如果包头信息还不完整,继续等待更多数据
|
||
return null;
|
||
} else {
|
||
// 没有未完成的包,但收到的数据不是包头开始的数据
|
||
AppLogger.warn('⚠️ 收到无法关联的数据片段');
|
||
return data;
|
||
}
|
||
}
|
||
} catch (e, stackTrace) {
|
||
AppLogger.error('❌ 数据包重组异常', error: e, stackTrace: stackTrace);
|
||
_pendingPackets.clear(); // 清理异常状态
|
||
return data; // 返回原始数据让后续处理尝试解析
|
||
}
|
||
}
|
||
|
||
/// ✨✨✨ 触发命令响应等待器 ✨✨✨
|
||
void _triggerCommandResponseWaiters(dynamic response) {
|
||
// 遍历所有等待中的命令,找到匹配的进行响应
|
||
List<String> completedKeys = [];
|
||
int commandId = response.commandId;
|
||
String? commandKey = _commandIdToKeyMap[commandId];
|
||
|
||
if (commandKey != null) {
|
||
// 精确匹配到特定命令
|
||
Completer? completer = _commandResponseWaiters[commandKey];
|
||
if (completer != null && !completer.isCompleted) {
|
||
AppLogger.debug('🔔 精确匹配命令响应: 命令ID=0x${commandId.toRadixString(16).padLeft(4, '0')}, 键=$commandKey');
|
||
completer.complete(response);
|
||
completedKeys.add(commandKey);
|
||
|
||
// 清理已完成的等待器
|
||
_cleanupCommandWaiter(commandKey);
|
||
_commandIdToKeyMap.remove(commandId);
|
||
return;
|
||
} else {
|
||
// 如果找不到对应的completer或者已经完成,从映射中移除
|
||
_commandIdToKeyMap.remove(commandId);
|
||
}
|
||
}
|
||
|
||
// 如果没有精确匹配,在重发场景中,我们应该完成所有匹配的等待器
|
||
// 而不是只完成最近的一个
|
||
if (_commandResponseWaiters.isNotEmpty) {
|
||
// 查找所有与该命令ID相关的等待器
|
||
List<String> matchingKeys = [];
|
||
_commandIdToKeyMap.forEach((id, key) {
|
||
if (id == commandId) {
|
||
matchingKeys.add(key);
|
||
}
|
||
});
|
||
|
||
// 完成所有匹配的等待器
|
||
for (String key in matchingKeys) {
|
||
Completer? completer = _commandResponseWaiters[key];
|
||
if (completer != null && !completer.isCompleted) {
|
||
AppLogger.debug('🔔 触发命令响应(精确匹配模式): $key');
|
||
completer.complete(response);
|
||
completedKeys.add(key);
|
||
// 清理已完成的等待器
|
||
_cleanupCommandWaiter(key);
|
||
}
|
||
}
|
||
|
||
// 从命令ID映射中移除已处理的键
|
||
_commandIdToKeyMap.removeWhere((id, key) => id == commandId && completedKeys.contains(key));
|
||
|
||
// 如果找到了匹配的等待器,直接返回
|
||
if (matchingKeys.isNotEmpty) {
|
||
return;
|
||
}
|
||
|
||
// 如果仍然没有找到匹配的等待器,回退到原来的逻辑(完成所有未完成的等待器)
|
||
// 但在重发场景中,我们只完成最近的等待器
|
||
// 获取最近添加的等待器(基于key的创建时间)
|
||
String mostRecentKey = _commandResponseWaiters.keys.last;
|
||
Completer? completer = _commandResponseWaiters[mostRecentKey];
|
||
if (completer != null && !completer.isCompleted) {
|
||
AppLogger.debug('🔔 触发命令响应(最近模式): $mostRecentKey');
|
||
completer.complete(response);
|
||
completedKeys.add(mostRecentKey);
|
||
}
|
||
}
|
||
|
||
// 清理已完成的等待器
|
||
for (String key in completedKeys) {
|
||
_cleanupCommandWaiter(key);
|
||
}
|
||
}
|
||
|
||
/// ✨✨✨ 发送蓝牙命令 ✨✨✨
|
||
Future<T?> sendCommand<T>({
|
||
required BaseBleCommand<T> command,
|
||
String? targetDeviceId,
|
||
String? targetDeviceName,
|
||
Duration timeout = const Duration(seconds: 10),
|
||
bool autoConnectIfNeeded = true,
|
||
Duration searchTimeoutIfNeeded = const Duration(seconds: 15),
|
||
int maxRetries = 3, // 最大重试次数
|
||
int retryCount = 0, // 当前重试次数
|
||
String? existingCommandKey, // 用于重发时传递已有的命令键
|
||
}) async {
|
||
AppLogger.highlight('✨✨✨ 🚀 开始发送蓝牙命令: ${command.runtimeType} (重试次数: $retryCount) ✨✨✨');
|
||
|
||
try {
|
||
// 1. 检查连接状态
|
||
bool isConnected = await _ensureDeviceConnected(
|
||
targetDeviceId: targetDeviceId,
|
||
targetDeviceName: targetDeviceName,
|
||
autoConnect: autoConnectIfNeeded,
|
||
searchTimeout: searchTimeoutIfNeeded,
|
||
);
|
||
|
||
if (!isConnected) {
|
||
AppLogger.error('❌ 设备未连接,无法发送命令');
|
||
throw Exception('设备未连接,无法发送命令');
|
||
}
|
||
|
||
// 1.1 检查设备是否仍然连接(在重发时特别重要)
|
||
if (_connectedDevice != null) {
|
||
try {
|
||
// 检查设备连接状态
|
||
BluetoothConnectionState currentState = await _connectedDevice!.connectionState.first;
|
||
if (currentState != BluetoothConnectionState.connected) {
|
||
AppLogger.warn('⚠️ 设备连接状态异常,当前状态: $currentState');
|
||
// 尝试重新连接
|
||
isConnected = await _ensureDeviceConnected(
|
||
targetDeviceId: targetDeviceId,
|
||
targetDeviceName: targetDeviceName,
|
||
autoConnect: true, // 强制重新连接
|
||
searchTimeout: searchTimeoutIfNeeded,
|
||
);
|
||
|
||
if (!isConnected) {
|
||
AppLogger.error('❌ 设备重新连接失败,无法发送命令');
|
||
throw Exception('设备重新连接失败,无法发送命令');
|
||
}
|
||
}
|
||
} catch (e) {
|
||
AppLogger.warn('⚠️ 检查设备连接状态时出错: $e');
|
||
// 出错时也尝试重新连接
|
||
isConnected = await _ensureDeviceConnected(
|
||
targetDeviceId: targetDeviceId,
|
||
targetDeviceName: targetDeviceName,
|
||
autoConnect: true, // 强制重新连接
|
||
searchTimeout: searchTimeoutIfNeeded,
|
||
);
|
||
|
||
if (!isConnected) {
|
||
AppLogger.error('❌ 设备重新连接失败,无法发送命令');
|
||
throw Exception('设备重新连接失败,无法发送命令');
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2. 检查写入特征值
|
||
if (_writeCharacteristic == null) {
|
||
AppLogger.error('❌ 写入特征值未初始化');
|
||
// 尝试重新设置设备连接
|
||
if (_connectedDevice != null) {
|
||
AppLogger.info('🔧 尝试重新设置设备连接...');
|
||
isConnected = await _setupDeviceConnection(_connectedDevice!);
|
||
if (!isConnected || _writeCharacteristic == null) {
|
||
AppLogger.error('❌ 重新设置设备连接失败');
|
||
throw Exception('写入特征值未初始化');
|
||
}
|
||
} else {
|
||
throw Exception('写入特征值未初始化');
|
||
}
|
||
}
|
||
|
||
// 3. 构建命令数据包
|
||
List<List<int>> packets = command.build();
|
||
AppLogger.info('📦 命令数据包数量: ${packets.length}');
|
||
|
||
// 4. 设置命令响应等待器
|
||
String commandKey = existingCommandKey ?? _generateCommandKey(command);
|
||
Completer<T?> responseCompleter = Completer<T?>();
|
||
_commandResponseWaiters[commandKey] = responseCompleter;
|
||
|
||
// 5. 如果命令有cmdId静态字段,将其与命令键关联(仅在非重发时)
|
||
if (existingCommandKey == null) {
|
||
_registerCommandId(command, commandKey);
|
||
} else if (command is BleCmdAddAdmin) {
|
||
// 对于重发的命令,我们也需要更新命令ID映射
|
||
_registerCommandId(command, commandKey);
|
||
}
|
||
|
||
// 6. 设置超时定时器
|
||
Timer timeoutTimer = Timer(timeout, () {
|
||
if (!responseCompleter.isCompleted) {
|
||
AppLogger.warn('⏰ 命令响应超时: $commandKey');
|
||
_cleanupCommandWaiter(commandKey);
|
||
responseCompleter.completeError(TimeoutException('命令响应超时', timeout));
|
||
}
|
||
});
|
||
_commandTimeouts[commandKey] = timeoutTimer;
|
||
|
||
// 7. 发送数据包
|
||
for (int i = 0; i < packets.length; i++) {
|
||
List<int> packet = packets[i];
|
||
AppLogger.debug('📤 发送第${i + 1}个数据包,数据包: (${packet.toString()},长度:${packet.length}字节)');
|
||
|
||
// 检查设备是否仍然连接
|
||
if (_connectedDevice != null) {
|
||
BluetoothConnectionState currentState = await _connectedDevice!.connectionState.first;
|
||
if (currentState != BluetoothConnectionState.connected) {
|
||
AppLogger.error('❌ 设备在发送数据包时断开连接');
|
||
throw Exception('设备在发送数据包时断开连接');
|
||
}
|
||
}
|
||
|
||
await _writeCharacteristic!.write(packet, withoutResponse: false);
|
||
|
||
// 在多包发送时稍微延迟
|
||
// if (i < packets.length - 1) {
|
||
// await Future.delayed(const Duration(milliseconds: 50));
|
||
// }
|
||
}
|
||
|
||
AppLogger.info('✅ 所有数据包发送完成,等待应答...');
|
||
|
||
// 8. 等待响应
|
||
T? response = await responseCompleter.future;
|
||
|
||
// 9. 检查是否需要重发(通用重发机制)
|
||
// 只对实现了RetryableBleCommand接口的命令进行重发检查
|
||
if (retryCount < maxRetries && command is RetryableBleCommand<T>) {
|
||
// 明确转换类型以避免静态分析错误
|
||
final retryableCommand = command as RetryableBleCommand<T>;
|
||
if (retryableCommand.shouldRetry(response)) {
|
||
AppLogger.info('🔄 命令${command.runtimeType}需要重发,准备创建重发命令 (重试次数: ${retryCount + 1}/${maxRetries})');
|
||
|
||
try {
|
||
// 创建重发命令
|
||
BaseBleCommand<T> retryCommand = retryableCommand.createRetryCommand(response);
|
||
|
||
// 重新发送命令,传递现有的命令键以确保响应正确处理
|
||
AppLogger.info('🔄 使用重发机制重新发送命令');
|
||
return await sendCommand<T>(
|
||
command: retryCommand,
|
||
targetDeviceId: targetDeviceId,
|
||
targetDeviceName: targetDeviceName,
|
||
timeout: timeout,
|
||
autoConnectIfNeeded: true,
|
||
// 重发时也需要自动连接
|
||
maxRetries: maxRetries,
|
||
retryCount: retryCount + 1,
|
||
// 增加重试次数
|
||
existingCommandKey: commandKey, // 传递现有的命令键
|
||
);
|
||
} catch (retryError, retryStackTrace) {
|
||
AppLogger.error('❌ 创建或发送重发命令失败', error: retryError, stackTrace: retryStackTrace);
|
||
// 如果创建重发命令失败,返回原始响应
|
||
AppLogger.highlight('✨✨✨ ✅ 命令发送完成,收到应答: $response ✨✨✨');
|
||
return response;
|
||
}
|
||
}
|
||
}
|
||
|
||
AppLogger.highlight('✨✨✨ ✅ 命令发送成功,收到应答: $response ✨✨✨');
|
||
return response;
|
||
} catch (e, stackTrace) {
|
||
AppLogger.error('❌ 发送蓝牙命令失败', error: e, stackTrace: stackTrace);
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
/// 将命令对象映射到 commandKey,特定命令需手动注册 cmdId
|
||
void _registerCommandId(dynamic command, String commandKey) {
|
||
try {
|
||
final int? commandId = switch (command) {
|
||
BleCmdGetPublicKey() => BleCmdGetPublicKey.cmdId,
|
||
BleCmdGetPrivateKey() => BleCmdGetPrivateKey.cmdId,
|
||
BleCmdReadLockStatus() => BleCmdReadLockStatus.cmdId,
|
||
BleCmdAddAdmin() => BleCmdAddAdmin.cmdId,
|
||
// 可在此添加更多命令类型
|
||
// BleCmdAnother() => BleCmdAnother.cmdId,
|
||
_ => null, // 默认情况:无法识别的命令类型
|
||
};
|
||
|
||
if (commandId != null) {
|
||
// 检查是否已存在该命令ID的映射
|
||
if (_commandIdToKeyMap.containsKey(commandId)) {
|
||
// 如果已存在,我们需要更新映射而不是覆盖
|
||
// 这在重发场景中很重要,确保最新的命令键能正确映射
|
||
AppLogger.debug(
|
||
'🔄 更新命令ID映射: 0x${commandId.toRadixString(16).padLeft(4, '0')} -> $commandKey (原键: ${_commandIdToKeyMap[commandId]})',
|
||
);
|
||
} else {
|
||
AppLogger.debug(
|
||
'📝 命令ID映射: 0x${commandId.toRadixString(16).padLeft(4, '0')} -> $commandKey',
|
||
);
|
||
}
|
||
_commandIdToKeyMap[commandId] = commandKey;
|
||
} else {
|
||
AppLogger.debug('❓ 未知命令类型,无法注册 commandId: $commandKey');
|
||
}
|
||
} catch (e) {
|
||
AppLogger.warn('⚠️ 无法获取命令ID: $e');
|
||
}
|
||
}
|
||
|
||
void cancel() {
|
||
/// 销毁蓝牙适配器监听
|
||
_adapterStateSubscription?.cancel();
|
||
_scanResultSubscription?.cancel();
|
||
_connectionStateSubscription?.cancel();
|
||
_mtuChangeSubscription?.cancel();
|
||
_characteristicDataSubscription?.cancel();
|
||
|
||
/// 取消搜索状态监控定时器
|
||
_scanningMonitorTimer?.cancel();
|
||
_scanningMonitorTimer = null;
|
||
|
||
/// 清理命令等待器
|
||
for (String key in _commandResponseWaiters.keys) {
|
||
_cleanupCommandWaiter(key);
|
||
}
|
||
_commandResponseWaiters.clear();
|
||
_commandTimeouts.clear();
|
||
_commandIdToKeyMap.clear(); // 清空命令ID映射
|
||
|
||
/// 清理待发送命令
|
||
for (_PendingCommand<dynamic> pendingCmd in _pendingCommands) {
|
||
if (!pendingCmd.completer.isCompleted) {
|
||
pendingCmd.completer.completeError(Exception('服务已关闭'));
|
||
}
|
||
}
|
||
_pendingCommands.clear();
|
||
|
||
/// 重置连接状态
|
||
_bluetoothAdapterState = BluetoothAdapterState.unknown;
|
||
_connectedDevice = null;
|
||
_writeCharacteristic = null;
|
||
_subscriptionCharacteristic = null;
|
||
|
||
AppLogger.highlight('🗑️ BleService 资源已清理');
|
||
}
|
||
|
||
/// ✨✨✨ 确保设备已连接 ✨✨✨
|
||
Future<bool> _ensureDeviceConnected({
|
||
String? targetDeviceId,
|
||
String? targetDeviceName,
|
||
bool autoConnect = true,
|
||
Duration searchTimeout = const Duration(seconds: 15),
|
||
}) async {
|
||
AppLogger.debug('🔍 检查设备连接状态...');
|
||
|
||
// 1. 检查当前连接状态
|
||
if (_connectedDevice != null && _bluetoothConnectionState == BluetoothConnectionState.connected) {
|
||
AppLogger.debug('📱 当前已连接设备: ${_connectedDevice!.platformName} (${_connectedDevice!.remoteId})');
|
||
|
||
// 如果指定了目标设备ID,检查是否匹配
|
||
if (targetDeviceId != null && _connectedDevice!.remoteId.toString() != targetDeviceId) {
|
||
AppLogger.info('🔄 当前连接的设备与目标不匹配,需要切换连接');
|
||
AppLogger.info('📱 当前设备: ${_connectedDevice!.remoteId},目标设备: $targetDeviceId');
|
||
await _connectedDevice!.disconnect();
|
||
_connectedDevice = null;
|
||
_writeCharacteristic = null;
|
||
_subscriptionCharacteristic = null;
|
||
} else {
|
||
AppLogger.info('✅ 设备已连接,无需重新连接');
|
||
return true;
|
||
}
|
||
} else if (_connectedDevice != null) {
|
||
AppLogger.warn('⚠️ 设备对象存在但连接状态异常: $_bluetoothConnectionState');
|
||
// 清理异常状态
|
||
_connectedDevice = null;
|
||
_writeCharacteristic = null;
|
||
_subscriptionCharacteristic = null;
|
||
}
|
||
|
||
// 2. 如果不允许自动连接,直接返回失败
|
||
if (!autoConnect) {
|
||
AppLogger.warn('⚠️ 设备未连接且不允许自动连接');
|
||
return false;
|
||
}
|
||
|
||
// 3. 尝试查找已连接的设备
|
||
List<BluetoothDevice> connectedDevices = FlutterBluePlus.connectedDevices;
|
||
AppLogger.debug('🔍 当前已连接设备数量: ${connectedDevices.length}');
|
||
|
||
if (targetDeviceId != null) {
|
||
for (BluetoothDevice device in connectedDevices) {
|
||
AppLogger.debug('📱 检查已连接设备: ${device.platformName} (${device.remoteId})');
|
||
if (device.remoteId.toString() == targetDeviceId) {
|
||
AppLogger.info('🔗 找到已连接的目标设备: ${device.platformName}');
|
||
bool isConnected = await _setupDeviceConnection(device);
|
||
if (isConnected) {
|
||
return true;
|
||
} else {
|
||
AppLogger.warn('⚠️ 已连接设备设置失败,继续搜索...');
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 4. 需要搜索并连接设备
|
||
AppLogger.info('🔍 开始搜索目标设备...');
|
||
BluetoothDevice? foundDevice = await _searchForTargetDevice(
|
||
targetDeviceId: targetDeviceId,
|
||
targetDeviceName: targetDeviceName,
|
||
searchTimeout: searchTimeout,
|
||
);
|
||
|
||
if (foundDevice == null) {
|
||
AppLogger.error('❌ 未找到目标设备');
|
||
return false;
|
||
}
|
||
|
||
// 5. 连接设备
|
||
AppLogger.info('🔗 开始连接设备: ${foundDevice.platformName}');
|
||
return await _connectToDevice(foundDevice);
|
||
}
|
||
|
||
/// ✨✨✨ 搜索目标设备 ✨✨✨
|
||
Future<BluetoothDevice?> _searchForTargetDevice({
|
||
String? targetDeviceId,
|
||
String? targetDeviceName,
|
||
Duration searchTimeout = const Duration(seconds: 15),
|
||
}) async {
|
||
Completer<BluetoothDevice?> searchCompleter = Completer<BluetoothDevice?>();
|
||
|
||
// 启动搜索
|
||
enableBluetoothSearch(
|
||
searchTime: searchTimeout,
|
||
onDeviceFound: (ScanDeviceInfo deviceInfo) {
|
||
BluetoothDevice device = deviceInfo.rawDeviceInfo.device;
|
||
String deviceId = device.remoteId.toString();
|
||
String deviceName = device.platformName;
|
||
|
||
AppLogger.debug('🔍 发现设备: $deviceName ($deviceId)');
|
||
|
||
// 检查是否匹配目标设备
|
||
bool isTargetDevice = false;
|
||
|
||
if (targetDeviceId != null && deviceId == targetDeviceId) {
|
||
isTargetDevice = true;
|
||
AppLogger.info('🎯 通过ID匹配到目标设备: $deviceName');
|
||
} else if (targetDeviceName != null && deviceName.contains(targetDeviceName)) {
|
||
isTargetDevice = true;
|
||
AppLogger.info('🎯 通过名称匹配到目标设备: $deviceName');
|
||
}
|
||
|
||
if (isTargetDevice && !searchCompleter.isCompleted) {
|
||
searchCompleter.complete(device);
|
||
}
|
||
},
|
||
);
|
||
|
||
// 设置超时
|
||
Timer(searchTimeout, () {
|
||
if (!searchCompleter.isCompleted) {
|
||
searchCompleter.complete(null);
|
||
}
|
||
});
|
||
|
||
return await searchCompleter.future;
|
||
}
|
||
|
||
/// ✨✨✨ 连接到设备 ✨✨✨
|
||
Future<bool> _connectToDevice(BluetoothDevice device) async {
|
||
try {
|
||
AppLogger.info('🔗 正在连接设备: ${device.platformName}');
|
||
|
||
// 尝试连接设备,最多重试3次
|
||
int maxRetries = 3;
|
||
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
||
try {
|
||
AppLogger.info('🔗 尝试连接设备 (第$attempt/$maxRetries次)');
|
||
await device.connect(timeout: const Duration(seconds: 10));
|
||
|
||
// 连接成功后设置设备连接
|
||
bool isConnected = await _setupDeviceConnection(device);
|
||
if (isConnected) {
|
||
return true;
|
||
} else {
|
||
AppLogger.warn('⚠️ 设备连接设置失败 (第$attempt/$maxRetries次)');
|
||
if (attempt < maxRetries) {
|
||
AppLogger.info('⏳ 等待2秒后重试...');
|
||
await Future.delayed(const Duration(seconds: 2));
|
||
}
|
||
}
|
||
} catch (e) {
|
||
AppLogger.warn('⚠️ 连接设备失败 (第$attempt/$maxRetries次): $e');
|
||
if (attempt < maxRetries) {
|
||
AppLogger.info('⏳ 等待2秒后重试...');
|
||
await Future.delayed(const Duration(seconds: 2));
|
||
} else {
|
||
rethrow; // 最后一次尝试失败,重新抛出异常
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
} catch (e) {
|
||
AppLogger.error('❌ 连接设备失败: ${device.platformName}', error: e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// ✨✨✨ 设置设备连接 ✨✨✨
|
||
Future<bool> _setupDeviceConnection(BluetoothDevice device) async {
|
||
try {
|
||
AppLogger.info('🔧 设置设备连接: ${device.platformName}');
|
||
|
||
// 更新内部状态
|
||
_connectedDevice = device;
|
||
|
||
// 监听连接状态
|
||
_connectionStateSubscription?.cancel();
|
||
_connectionStateSubscription = device.connectionState.listen(_handleListenBluetoothConnectionState);
|
||
|
||
// 监听MTU变化
|
||
_mtuChangeSubscription?.cancel();
|
||
_mtuChangeSubscription = device.mtu.listen(_handleListenMtuChange);
|
||
|
||
// 发现服务
|
||
List<BluetoothService> services = await device.discoverServices();
|
||
AppLogger.info('🔍 发现服务数量: ${services.length}');
|
||
|
||
// 查找目标服务
|
||
BluetoothService? targetService;
|
||
for (BluetoothService service in services) {
|
||
AppLogger.debug('📱 发现服务: ${service.uuid}');
|
||
if (service.uuid == BleConfig.serviceId) {
|
||
targetService = service;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (targetService == null) {
|
||
AppLogger.error('❌ 未找到目标服务: ${BleConfig.serviceId}');
|
||
// 打印所有发现的服务以便调试
|
||
for (BluetoothService service in services) {
|
||
AppLogger.debug('📱 可用服务: ${service.uuid}');
|
||
}
|
||
throw Exception('未找到目标服务: ${BleConfig.serviceId}');
|
||
}
|
||
|
||
AppLogger.info('✅ 找到目标服务: ${targetService.uuid}');
|
||
|
||
// 查找特征值
|
||
BluetoothCharacteristic? writeChar;
|
||
BluetoothCharacteristic? subscribeChar;
|
||
|
||
AppLogger.debug('🔍 目标服务中的特征值数量: ${targetService.characteristics.length}');
|
||
for (BluetoothCharacteristic characteristic in targetService.characteristics) {
|
||
AppLogger.debug('📱 发现特征值: ${characteristic.uuid}');
|
||
if (characteristic.uuid == BleConfig.characteristicIdWrite) {
|
||
writeChar = characteristic;
|
||
AppLogger.info('✅ 找到写入特征值: ${characteristic.uuid}');
|
||
} else if (characteristic.uuid == BleConfig.characteristicIdSubscription) {
|
||
subscribeChar = characteristic;
|
||
AppLogger.info('✅ 找到订阅特征值: ${characteristic.uuid}');
|
||
}
|
||
}
|
||
|
||
// 如果没有找到特征值,打印所有可用的特征值
|
||
if (writeChar == null || subscribeChar == null) {
|
||
AppLogger.warn('⚠️ 未找到所需的特征值,当前服务中的所有特征值:');
|
||
for (BluetoothCharacteristic characteristic in targetService.characteristics) {
|
||
AppLogger.warn(' 📱 特征值: ${characteristic.uuid}, 属性: ${characteristic.properties}');
|
||
}
|
||
|
||
// 尝试使用找到的特征值
|
||
if (writeChar == null && subscribeChar != null) {
|
||
writeChar = subscribeChar;
|
||
AppLogger.warn('⚠️ 未找到写入特征值,使用订阅特征值作为写入特征值');
|
||
} else if (subscribeChar == null && writeChar != null) {
|
||
subscribeChar = writeChar;
|
||
AppLogger.warn('⚠️ 未找到订阅特征值,使用写入特征值作为订阅特征值');
|
||
}
|
||
|
||
// 如果仍然没有找到所需的特征值,抛出异常
|
||
if (writeChar == null || subscribeChar == null) {
|
||
throw Exception('未找到所需的特征值');
|
||
}
|
||
}
|
||
|
||
// 设置特征值
|
||
_writeCharacteristic = writeChar;
|
||
_subscriptionCharacteristic = subscribeChar;
|
||
|
||
// 订阅通知
|
||
try {
|
||
await subscribeChar.setNotifyValue(true);
|
||
AppLogger.info('✅ 已订阅通知');
|
||
} catch (e) {
|
||
AppLogger.warn('⚠️ 订阅通知失败,尝试继续连接: $e');
|
||
// 即使订阅失败,也继续连接过程
|
||
}
|
||
|
||
// 监听数据
|
||
_characteristicDataSubscription?.cancel();
|
||
_characteristicDataSubscription = subscribeChar.onValueReceived.listen(_handleListenCharacteristicData);
|
||
|
||
AppLogger.highlight('✨✨✨ ✅ 设备连接设置完成 ✨✨✨');
|
||
|
||
// 处理待发送的命令
|
||
await _processPendingCommands();
|
||
|
||
return true;
|
||
} catch (e, stackTrace) {
|
||
AppLogger.error('❌ 设置设备连接失败', error: e, stackTrace: stackTrace);
|
||
_connectedDevice = null;
|
||
_writeCharacteristic = null;
|
||
_subscriptionCharacteristic = null;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// ✨✨✨ 处理待发送的命令 ✨✨✨
|
||
Future<void> _processPendingCommands() async {
|
||
if (_pendingCommands.isEmpty) return;
|
||
|
||
AppLogger.info('📦 处理待发送命令: ${_pendingCommands.length}个');
|
||
|
||
List<_PendingCommand<dynamic>> commandsToProcess = List.from(_pendingCommands);
|
||
_pendingCommands.clear();
|
||
|
||
for (_PendingCommand<dynamic> pendingCmd in commandsToProcess) {
|
||
try {
|
||
AppLogger.info('🚀 发送待处理命令: ${pendingCmd.commandId}');
|
||
dynamic result = await sendCommand(
|
||
command: pendingCmd.command,
|
||
targetDeviceId: pendingCmd.targetDeviceId,
|
||
targetDeviceName: pendingCmd.targetDeviceName,
|
||
timeout: pendingCmd.timeout,
|
||
);
|
||
pendingCmd.completer.complete(result);
|
||
} catch (e) {
|
||
pendingCmd.completer.completeError(e);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// ✨✨✨ 生成命令唯一标识 ✨✨✨
|
||
String _generateCommandKey<T>(BaseBleCommand<T> command) {
|
||
return '${command.runtimeType}_${DateTime.now().millisecondsSinceEpoch}';
|
||
}
|
||
|
||
/// ✨✨✨ 清理命令等待器 ✨✨✨
|
||
void _cleanupCommandWaiter(String commandKey) {
|
||
// 从命令ID映射中移除
|
||
_commandIdToKeyMap.removeWhere((commandId, key) => key == commandKey);
|
||
|
||
_commandResponseWaiters.remove(commandKey);
|
||
Timer? timer = _commandTimeouts.remove(commandKey);
|
||
timer?.cancel();
|
||
}
|
||
}
|