fix: 暂缓,改为sdk接入开发
This commit is contained in:
parent
730cb9c5bb
commit
b21fd12ca7
@ -5,6 +5,7 @@ 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';
|
||||
@ -289,7 +290,14 @@ class BleService {
|
||||
/// 监听mtu变化
|
||||
void _handleListenMtuChange(int mtu) {
|
||||
_mtu = mtu;
|
||||
BaseBleCommand.MAX_PACKET_DATA_SIZE = 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}');
|
||||
}
|
||||
|
||||
/// 用于存储分包接收的数据
|
||||
@ -303,11 +311,11 @@ class BleService {
|
||||
if (data.isNotEmpty) {
|
||||
// ✨✨✨ 处理分包数据重组 ✨✨✨
|
||||
List<int>? completePacket = _reassemblePacket(data);
|
||||
|
||||
|
||||
if (completePacket != null) {
|
||||
// 如果有完整数据包,进行处理
|
||||
dynamic result = bleCommandManager.handleResponse(completePacket);
|
||||
|
||||
|
||||
if (result != null) {
|
||||
// 触发命令响应等待器
|
||||
_triggerCommandResponseWaiters(result);
|
||||
@ -326,38 +334,37 @@ class BleService {
|
||||
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] &&
|
||||
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('✅ 数据包完整,无需重组');
|
||||
@ -383,27 +390,27 @@ class BleService {
|
||||
// 获取第一个(也是唯一一个)未完成的包
|
||||
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字节');
|
||||
@ -451,17 +458,54 @@ class BleService {
|
||||
_cleanupCommandWaiter(commandKey);
|
||||
_commandIdToKeyMap.remove(commandId);
|
||||
return;
|
||||
} else {
|
||||
// 如果找不到对应的completer或者已经完成,从映射中移除
|
||||
_commandIdToKeyMap.remove(commandId);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有精确匹配,回退到原来的逻辑(完成所有未完成的等待器)
|
||||
_commandResponseWaiters.forEach((key, completer) {
|
||||
if (!completer.isCompleted) {
|
||||
AppLogger.debug('🔔 触发命令响应(回退模式): $key');
|
||||
completer.complete(response);
|
||||
completedKeys.add(key);
|
||||
// 如果没有精确匹配,在重发场景中,我们应该完成所有匹配的等待器
|
||||
// 而不是只完成最近的一个
|
||||
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) {
|
||||
@ -477,8 +521,11 @@ class BleService {
|
||||
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} ✨✨✨');
|
||||
AppLogger.highlight('✨✨✨ 🚀 开始发送蓝牙命令: ${command.runtimeType} (重试次数: $retryCount) ✨✨✨');
|
||||
|
||||
try {
|
||||
// 1. 检查连接状态
|
||||
@ -490,12 +537,61 @@ class BleService {
|
||||
);
|
||||
|
||||
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) {
|
||||
throw Exception('写入特征值未初始化');
|
||||
AppLogger.error('❌ 写入特征值未初始化');
|
||||
// 尝试重新设置设备连接
|
||||
if (_connectedDevice != null) {
|
||||
AppLogger.info('🔧 尝试重新设置设备连接...');
|
||||
isConnected = await _setupDeviceConnection(_connectedDevice!);
|
||||
if (!isConnected || _writeCharacteristic == null) {
|
||||
AppLogger.error('❌ 重新设置设备连接失败');
|
||||
throw Exception('写入特征值未初始化');
|
||||
}
|
||||
} else {
|
||||
throw Exception('写入特征值未初始化');
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 构建命令数据包
|
||||
@ -503,16 +599,22 @@ class BleService {
|
||||
AppLogger.info('📦 命令数据包数量: ${packets.length}');
|
||||
|
||||
// 4. 设置命令响应等待器
|
||||
String commandKey = _generateCommandKey(command);
|
||||
String commandKey = existingCommandKey ?? _generateCommandKey(command);
|
||||
Completer<T?> responseCompleter = Completer<T?>();
|
||||
_commandResponseWaiters[commandKey] = responseCompleter;
|
||||
|
||||
// 5. 如果命令有cmdId静态字段,将其与命令键关联
|
||||
_registerCommandId(command, commandKey);
|
||||
// 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));
|
||||
}
|
||||
@ -524,6 +626,15 @@ class BleService {
|
||||
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);
|
||||
|
||||
// 在多包发送时稍微延迟
|
||||
@ -537,6 +648,41 @@ class BleService {
|
||||
// 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) {
|
||||
@ -552,16 +698,26 @@ class BleService {
|
||||
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;
|
||||
AppLogger.debug(
|
||||
'📝 命令ID映射: 0x${commandId.toRadixString(16).padLeft(4, '0')} -> $commandKey',
|
||||
);
|
||||
} else {
|
||||
AppLogger.debug('❓ 未知命令类型,无法注册 commandId: $commandKey');
|
||||
}
|
||||
@ -614,11 +770,16 @@ class BleService {
|
||||
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;
|
||||
@ -627,6 +788,12 @@ class BleService {
|
||||
AppLogger.info('✅ 设备已连接,无需重新连接');
|
||||
return true;
|
||||
}
|
||||
} else if (_connectedDevice != null) {
|
||||
AppLogger.warn('⚠️ 设备对象存在但连接状态异常: $_bluetoothConnectionState');
|
||||
// 清理异常状态
|
||||
_connectedDevice = null;
|
||||
_writeCharacteristic = null;
|
||||
_subscriptionCharacteristic = null;
|
||||
}
|
||||
|
||||
// 2. 如果不允许自动连接,直接返回失败
|
||||
@ -637,11 +804,19 @@ class BleService {
|
||||
|
||||
// 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('🔗 找到已连接的目标设备');
|
||||
return await _setupDeviceConnection(device);
|
||||
AppLogger.info('🔗 找到已连接的目标设备: ${device.platformName}');
|
||||
bool isConnected = await _setupDeviceConnection(device);
|
||||
if (isConnected) {
|
||||
return true;
|
||||
} else {
|
||||
AppLogger.warn('⚠️ 已连接设备设置失败,继续搜索...');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -714,10 +889,36 @@ class BleService {
|
||||
try {
|
||||
AppLogger.info('🔗 正在连接设备: ${device.platformName}');
|
||||
|
||||
await device.connect(timeout: const Duration(seconds: 10));
|
||||
// 尝试连接设备,最多重试3次
|
||||
int maxRetries = 3;
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
AppLogger.info('🔗 尝试连接设备 (第$attempt/$maxRetries次)');
|
||||
await device.connect(timeout: const Duration(seconds: 10));
|
||||
|
||||
// 设置连接设备
|
||||
return await _setupDeviceConnection(device);
|
||||
// 连接成功后设置设备连接
|
||||
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;
|
||||
@ -747,6 +948,7 @@ class BleService {
|
||||
// 查找目标服务
|
||||
BluetoothService? targetService;
|
||||
for (BluetoothService service in services) {
|
||||
AppLogger.debug('📱 发现服务: ${service.uuid}');
|
||||
if (service.uuid == BleConfig.serviceId) {
|
||||
targetService = service;
|
||||
break;
|
||||
@ -754,6 +956,11 @@ class BleService {
|
||||
}
|
||||
|
||||
if (targetService == null) {
|
||||
AppLogger.error('❌ 未找到目标服务: ${BleConfig.serviceId}');
|
||||
// 打印所有发现的服务以便调试
|
||||
for (BluetoothService service in services) {
|
||||
AppLogger.debug('📱 可用服务: ${service.uuid}');
|
||||
}
|
||||
throw Exception('未找到目标服务: ${BleConfig.serviceId}');
|
||||
}
|
||||
|
||||
@ -763,7 +970,9 @@ class BleService {
|
||||
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}');
|
||||
@ -773,8 +982,26 @@ class BleService {
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到特征值,打印所有可用的特征值
|
||||
if (writeChar == null || subscribeChar == null) {
|
||||
throw Exception('未找到所需的特征值');
|
||||
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('未找到所需的特征值');
|
||||
}
|
||||
}
|
||||
|
||||
// 设置特征值
|
||||
@ -782,8 +1009,13 @@ class BleService {
|
||||
_subscriptionCharacteristic = subscribeChar;
|
||||
|
||||
// 订阅通知
|
||||
await subscribeChar.setNotifyValue(true);
|
||||
AppLogger.info('✅ 已订阅通知');
|
||||
try {
|
||||
await subscribeChar.setNotifyValue(true);
|
||||
AppLogger.info('✅ 已订阅通知');
|
||||
} catch (e) {
|
||||
AppLogger.warn('⚠️ 订阅通知失败,尝试继续连接: $e');
|
||||
// 即使订阅失败,也继续连接过程
|
||||
}
|
||||
|
||||
// 监听数据
|
||||
_characteristicDataSubscription?.cancel();
|
||||
|
||||
@ -48,7 +48,7 @@ abstract class BaseBleCommand<T> {
|
||||
static const int PACKET_TYPE_RESPONSE = 0x11; // 应答包
|
||||
|
||||
/// 包版本 (高4位)
|
||||
static const int PACKET_VERSION = 0x10; // 版本1.0
|
||||
static const int PACKET_VERSION = 0x20; // 版本2.0
|
||||
|
||||
/// 加密类型 (低4位)
|
||||
static const int ENCRYPT_TYPE_PLAIN = 0x00; // 明文
|
||||
@ -187,32 +187,35 @@ abstract class BaseBleCommand<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取分包序号
|
||||
int packetSequence = getNextPacketSequence();
|
||||
if (i == 0) {
|
||||
// 获取分包序号
|
||||
int packetSequence = getNextPacketSequence();
|
||||
|
||||
// 组装包头
|
||||
List<int> header = buildPacketHeader(
|
||||
packetType: PACKET_TYPE_REQUEST,
|
||||
packetSequence: packetSequence,
|
||||
encryptType: encryptType,
|
||||
encryptedDataLength: encryptedDataLength,
|
||||
originalDataLength: originalDataLength,
|
||||
);
|
||||
// 组装包头
|
||||
List<int> header = buildPacketHeader(
|
||||
packetType: PACKET_TYPE_REQUEST,
|
||||
packetSequence: packetSequence,
|
||||
encryptType: encryptType,
|
||||
encryptedDataLength: originalData.length,
|
||||
originalDataLength: originalData.length,
|
||||
);
|
||||
// 组装完整包 (包头 + 数据)
|
||||
List<int> packetWithHeader = [];
|
||||
packetWithHeader.addAll(header);
|
||||
packetWithHeader.addAll(currentPacketData);
|
||||
|
||||
// 组装完整包 (包头 + 数据)
|
||||
List<int> packetWithHeader = [];
|
||||
packetWithHeader.addAll(header);
|
||||
packetWithHeader.addAll(currentPacketData);
|
||||
// 计算并添加包尾
|
||||
List<int> tail = buildPacketTail(packetWithHeader);
|
||||
|
||||
// 计算并添加包尾
|
||||
List<int> tail = buildPacketTail(packetWithHeader);
|
||||
// 完整的数据包
|
||||
List<int> completePacket = [];
|
||||
completePacket.addAll(packetWithHeader);
|
||||
completePacket.addAll(tail);
|
||||
|
||||
// 完整的数据包
|
||||
List<int> completePacket = [];
|
||||
completePacket.addAll(packetWithHeader);
|
||||
completePacket.addAll(tail);
|
||||
|
||||
packets.add(completePacket);
|
||||
packets.add(completePacket);
|
||||
} else {
|
||||
packets.add(currentPacketData);
|
||||
}
|
||||
}
|
||||
|
||||
return packets;
|
||||
@ -297,6 +300,7 @@ abstract class BaseBleCommand<T> {
|
||||
int packetFlag = rawData[offset++];
|
||||
int version = (packetFlag & 0xF0);
|
||||
int encryptType = (packetFlag & 0x0F);
|
||||
AppLogger.highlight('加密类型:${encryptType}');
|
||||
|
||||
// 6. 解析数据长度 (大端序: 高16位加密后长度 + 低16位原始长度)
|
||||
int combinedLength = (rawData[offset] << 24) | (rawData[offset + 1] << 16) | (rawData[offset + 2] << 8) | rawData[offset + 3];
|
||||
@ -348,7 +352,6 @@ abstract class BaseBleCommand<T> {
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
return ParsedPacket(
|
||||
isValid: true,
|
||||
packetType: packetType,
|
||||
@ -389,7 +392,7 @@ abstract class BaseBleCommand<T> {
|
||||
}
|
||||
|
||||
// 获取固定长度的数组
|
||||
List<int> getFixedLengthList(List<int> data, int length) {
|
||||
List<int> getFixedLengthList(List<int> data, int length) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
data.add(0);
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import 'package:starwork_flutter/base/app_logger.dart';
|
||||
import 'package:starwork_flutter/ble/ble_service.dart';
|
||||
import 'package:starwork_flutter/ble/command/base/base_ble_command.dart';
|
||||
import 'package:starwork_flutter/ble/command/base/base_ble_response_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/ble_cmd_add_admin_parser.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/command/response/ble_cmd_read_lock_status_parser.dart';
|
||||
@ -50,6 +51,7 @@ class BleCommandManager {
|
||||
registerParser(BleCmdGetPublicKeyParser());
|
||||
registerParser(BleCmdGetPrivateKeyParser());
|
||||
registerParser(BleCmdReadLockStatusParser());
|
||||
registerParser(BleCmdAddAdminParser());
|
||||
|
||||
// TODO: 在这里注册其他命令解析器
|
||||
// registerParser(BleCmdSetPasswordParser());
|
||||
|
||||
@ -3,38 +3,63 @@ import 'dart:convert';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:starwork_flutter/base/app_logger.dart';
|
||||
import 'package:starwork_flutter/ble/command/base/base_ble_command.dart';
|
||||
import 'package:starwork_flutter/ble/command/base/retryable_ble_command.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/ble_cmd_get_private_key_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/model/ble_add_admin_response.dart';
|
||||
import 'package:starwork_flutter/ble/constant/lock_ble_constant.dart';
|
||||
import 'package:starwork_flutter/common/sm4_encipher/sm4.dart';
|
||||
|
||||
/// ✨✨✨ 增加超级管理员命令类 - 继承BaseBleCommand ✨✨✨
|
||||
class BleCmdAddAdmin extends BaseBleCommand<GetPrivateKeyResponse> {
|
||||
class BleCmdAddAdmin extends BaseBleCommand<BleAddAdminResponse> implements RetryableBleCommand<BleAddAdminResponse> {
|
||||
final String lockId;
|
||||
final String authUserId;
|
||||
final String keyId;
|
||||
final String authUserID;
|
||||
final String userID;
|
||||
final String userId;
|
||||
final int openMode;
|
||||
final int keyType;
|
||||
final int startDate;
|
||||
final int expireDate;
|
||||
final int useCountLimit;
|
||||
final int isRound;
|
||||
final int weekRound;
|
||||
final int startHour;
|
||||
final int startMin;
|
||||
final int endHour;
|
||||
final int endMin;
|
||||
final int role;
|
||||
final String password;
|
||||
final List<int> token;
|
||||
final List<int> publicKey;
|
||||
final int nowTime;
|
||||
final int _encryptType; // 私有字段存储加密类型
|
||||
final List<int> privateKey;
|
||||
final int _encryptType;
|
||||
|
||||
/// 指令 ID: 0x3091
|
||||
static const int cmdId = 0x3091;
|
||||
/// 指令 ID: 0x3001
|
||||
static const int cmdId = 0x3001;
|
||||
|
||||
/// 构造函数
|
||||
BleCmdAddAdmin({
|
||||
int encryptType = BaseBleCommand.ENCRYPT_TYPE_SM4_PRESET,
|
||||
int encryptType = BaseBleCommand.ENCRYPT_TYPE_SM4_DEVICE,
|
||||
required this.lockId,
|
||||
required this.authUserId,
|
||||
required this.keyId,
|
||||
required this.authUserID,
|
||||
required this.userID,
|
||||
required this.nowTime,
|
||||
required this.userId,
|
||||
this.openMode = 1, // 缺少定义,默认传1
|
||||
this.keyType = LockBleConstant.keyTypeTemporary,
|
||||
required this.startDate,
|
||||
required this.expireDate,
|
||||
this.useCountLimit = 0xFFFF,
|
||||
this.isRound = 0,
|
||||
this.weekRound = 0,
|
||||
this.startHour = 0,
|
||||
this.startMin = 0,
|
||||
this.endHour = 0,
|
||||
this.endMin = 0,
|
||||
this.role = 255,
|
||||
required this.password,
|
||||
required this.token,
|
||||
required this.publicKey,
|
||||
required this.privateKey,
|
||||
}) : _encryptType = encryptType {
|
||||
if (lockId.isEmpty) {
|
||||
throw ArgumentError('LockID cannot be empty');
|
||||
}
|
||||
if (lockId.length > 40) {
|
||||
throw ArgumentError('LockID must be at most 40 characters long, got ${lockId.length}');
|
||||
}
|
||||
AppLogger.debug('🔑 BleCmdAddAdmin 创建: LockID="$lockId", 加密类型=$_encryptType');
|
||||
}
|
||||
|
||||
@ -47,66 +72,6 @@ class BleCmdAddAdmin extends BaseBleCommand<GetPrivateKeyResponse> {
|
||||
/// ✨✨✨ 实现BaseBleCommand的抽象方法 - 构建命令数据 ✨✨✨
|
||||
@override
|
||||
List<int> buildData() {
|
||||
// final List<int> buffer = [];
|
||||
//
|
||||
// // 1. 写入 CmdID (2 字节,大端序)
|
||||
// buffer.add((cmdId >> 8) & 0xFF); // 高字节
|
||||
// buffer.add(cmdId & 0xFF); // 低字节
|
||||
//
|
||||
// // 2. 处理 LockID:先转换实际字符,再检查是否需要填充到40字节
|
||||
// List<int> lockIdBytes = List.from(lockId.codeUnits); // 创建可修改的副本
|
||||
// List<int> keyIdBytes = List.from(keyId.codeUnits); // 创建可修改的副本
|
||||
// List<int> authUserIdBytes = List.from(authUserID.codeUnits); // 创建可修改的副本
|
||||
//
|
||||
// // 检查是否需要填充到40字节
|
||||
// if (lockIdBytes.length < 40) {
|
||||
// // 用0填充到40字节
|
||||
// int paddingNeeded = 40 - lockIdBytes.length;
|
||||
// lockIdBytes.addAll(List.filled(paddingNeeded, 0));
|
||||
// }
|
||||
//
|
||||
// // 检查是否需要填充到40字节
|
||||
// if (keyIdBytes.length < 40) {
|
||||
// // 用0填充到40字节
|
||||
// int paddingNeeded = 40 - keyIdBytes.length;
|
||||
// keyIdBytes.addAll(List.filled(paddingNeeded, 0));
|
||||
// }
|
||||
//
|
||||
// // 检查是否需要填充到40字节
|
||||
// if (authUserIdBytes.length < 20) {
|
||||
// // 用0填充到40字节
|
||||
// int paddingNeeded = 20 - authUserIdBytes.length;
|
||||
// authUserIdBytes.addAll(List.filled(paddingNeeded, 0));
|
||||
// }
|
||||
//
|
||||
// buffer.addAll(lockIdBytes);
|
||||
// buffer.addAll(keyIdBytes);
|
||||
// buffer.addAll(authUserIdBytes);
|
||||
// buffer.add((nowTime & 0xFF000000) >> 24);
|
||||
// buffer.add((nowTime & 0xFF0000) >> 16);
|
||||
// buffer.add((nowTime & 0xFF00) >> 8);
|
||||
// buffer.add((nowTime & 0xFF));
|
||||
//
|
||||
// List<int> countAuthCode = [];
|
||||
//
|
||||
// countAuthCode.addAll(keyIdBytes);
|
||||
// countAuthCode.addAll(authUserIdBytes);
|
||||
// countAuthCode.add((nowTime & 0xFF000000) >> 24);
|
||||
// countAuthCode.add((nowTime & 0xFF0000) >> 16);
|
||||
// countAuthCode.add((nowTime & 0xFF00) >> 8);
|
||||
// countAuthCode.add((nowTime & 0xFF));
|
||||
// countAuthCode.addAll(publicKey);
|
||||
//
|
||||
// var authCode = md5.convert(countAuthCode);
|
||||
//
|
||||
// buffer.add(authCode.bytes.length);
|
||||
// buffer.addAll(authCode.bytes);
|
||||
// var list = SM4.encrypt(buffer, key: utf8.encode(lockId), mode: SM4CryptoMode.ECB);
|
||||
// return list;
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
List<int> data = <int>[];
|
||||
List<int> ebcData = <int>[];
|
||||
|
||||
@ -118,44 +83,85 @@ class BleCmdAddAdmin extends BaseBleCommand<GetPrivateKeyResponse> {
|
||||
data.add(type1);
|
||||
data.add(type2);
|
||||
|
||||
// 锁id
|
||||
final int lockIDLength = utf8.encode(lockId!).length;
|
||||
// lockId 40
|
||||
final int lockIDLength = utf8.encode(lockId).length;
|
||||
data.addAll(utf8.encode(lockId));
|
||||
data = getFixedLengthList(data, 40 - lockIDLength);
|
||||
|
||||
// authUserId 20
|
||||
final int authUserIdLength = utf8.encode(authUserId).length;
|
||||
data.addAll(utf8.encode(authUserId));
|
||||
data = getFixedLengthList(data, 20 - authUserIdLength);
|
||||
|
||||
//KeyID 40
|
||||
final int keyIDLength = utf8.encode(keyId!).length;
|
||||
final int keyIDLength = utf8.encode(keyId).length;
|
||||
data.addAll(utf8.encode(keyId));
|
||||
data = getFixedLengthList(data, 40 - keyIDLength);
|
||||
|
||||
//authUserID 20
|
||||
final int authUserIDLength = utf8.encode(authUserID!).length;
|
||||
data.addAll(utf8.encode(authUserID));
|
||||
data = getFixedLengthList(data, 20 - authUserIDLength);
|
||||
//userId 20
|
||||
final int userIdLength = utf8.encode(userId).length;
|
||||
data.addAll(utf8.encode(userId));
|
||||
data = getFixedLengthList(data, 20 - userIdLength);
|
||||
|
||||
//NowTime 4
|
||||
data.add((nowTime & 0xff000000) >> 24);
|
||||
data.add((nowTime & 0xff0000) >> 16);
|
||||
data.add((nowTime & 0xff00) >> 8);
|
||||
data.add(nowTime & 0xff);
|
||||
// openModel 1
|
||||
data.add(openMode);
|
||||
|
||||
// keyType 1
|
||||
data.add(keyType);
|
||||
|
||||
int? d1, d2;
|
||||
if (role == LockBleConstant.roleSuperAdmin) {
|
||||
d1 = 0;
|
||||
d2 = 0xffffffff;
|
||||
} else {
|
||||
d1 = startDate;
|
||||
d2 = expireDate;
|
||||
}
|
||||
|
||||
// StartDate 4
|
||||
data.add((d1 & 0xff000000) >> 24);
|
||||
data.add((d1 & 0xff0000) >> 16);
|
||||
data.add((d1 & 0xff00) >> 8);
|
||||
data.add(d1 & 0xff);
|
||||
|
||||
// expireDate 4
|
||||
data.add((d2 & 0xff000000) >> 24);
|
||||
data.add((d2 & 0xff0000) >> 16);
|
||||
data.add((d2 & 0xff00) >> 8);
|
||||
data.add(d2 & 0xff);
|
||||
|
||||
//useCountLimit 2
|
||||
final double useCountLimitDouble = useCountLimit / 256;
|
||||
final int useCountLimit1 = useCountLimitDouble.toInt();
|
||||
final int useCountLimit2 = useCountLimit % 256;
|
||||
data.add(useCountLimit1);
|
||||
data.add(useCountLimit2);
|
||||
|
||||
data.add(isRound);
|
||||
data.add(weekRound);
|
||||
data.add(startHour);
|
||||
data.add(startMin);
|
||||
data.add(endHour);
|
||||
data.add(endMin);
|
||||
|
||||
// role 1
|
||||
data.add(role);
|
||||
|
||||
//password 20
|
||||
final int passwordLength = utf8.encode(password).length;
|
||||
data.addAll(utf8.encode(password));
|
||||
data = getFixedLengthList(data, 20 - passwordLength);
|
||||
|
||||
// token 长度4 首次请求 Token 填 0,如果锁需要鉴权 操作者身份,则会分配动态口令并在应答消息中返回,二次请求时带上。 当token失效或者第一次发送的时候token为0
|
||||
data.addAll(token);
|
||||
|
||||
final List<int> authCodeData = <int>[];
|
||||
|
||||
//authUserID
|
||||
authCodeData.addAll(utf8.encode(authUserID!));
|
||||
|
||||
//KeyID
|
||||
authCodeData.addAll(utf8.encode(keyId!));
|
||||
|
||||
//NowTime 4
|
||||
authCodeData.add((nowTime & 0xff000000) >> 24);
|
||||
authCodeData.add((nowTime & 0xff0000) >> 16);
|
||||
authCodeData.add((nowTime & 0xff00) >> 8);
|
||||
authCodeData.add(nowTime & 0xff);
|
||||
|
||||
authCodeData.addAll(utf8.encode(authUserId));
|
||||
authCodeData.addAll(utf8.encode(keyId));
|
||||
authCodeData.addAll(token);
|
||||
authCodeData.addAll(publicKey);
|
||||
|
||||
// 把authUserID、KeyID、时间戳、公钥通过md5加密之后就是authCode
|
||||
// 把authUserID、KeyID、token、公钥通过md5加密之后就是authCode
|
||||
var authCode = md5.convert(authCodeData);
|
||||
|
||||
data.add(authCode.bytes.length);
|
||||
@ -167,9 +173,58 @@ class BleCmdAddAdmin extends BaseBleCommand<GetPrivateKeyResponse> {
|
||||
data.add(0);
|
||||
}
|
||||
}
|
||||
AppLogger.debug("完整明文数据: $data");
|
||||
|
||||
// 拿到数据之后通过LockId进行SM4 ECB加密 key:544d485f633335373034383064613864
|
||||
ebcData = SM4.encrypt(data, key: utf8.encode(lockId), mode: SM4CryptoMode.ECB);
|
||||
// 拿到数据之后通过privateKey进行SM4 ECB加密
|
||||
ebcData = SM4.encrypt(data, key: privateKey, mode: SM4CryptoMode.ECB);
|
||||
return ebcData;
|
||||
}
|
||||
|
||||
/// ✨✨✨ 检查是否需要重发 ✨✨✨
|
||||
@override
|
||||
bool shouldRetry(dynamic response) {
|
||||
// 检查响应是否为BleAddAdminResponse类型且状态码为6(需要token)
|
||||
if (response is BleAddAdminResponse) {
|
||||
return response.statusCode == 0x06; // 需要token
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// ✨✨✨ 创建重发命令实例 ✨✨✨
|
||||
@override
|
||||
BaseBleCommand<BleAddAdminResponse> createRetryCommand(dynamic response) {
|
||||
if (response is BleAddAdminResponse) {
|
||||
// 当状态码为0x06(需要token)时,响应中的token字段是有效的,应该使用这个token
|
||||
List<int> token = response.token;
|
||||
AppLogger.debug('🔐 获取到token用于重发: $token');
|
||||
|
||||
// 创建新的命令实例,携带token
|
||||
return BleCmdAddAdmin(
|
||||
encryptType: this.getEncryptType(),
|
||||
lockId: this.lockId,
|
||||
authUserId: this.authUserId,
|
||||
keyId: this.keyId,
|
||||
userId: this.userId,
|
||||
openMode: this.openMode,
|
||||
keyType: this.keyType,
|
||||
startDate: this.startDate,
|
||||
expireDate: this.expireDate,
|
||||
useCountLimit: this.useCountLimit,
|
||||
isRound: this.isRound,
|
||||
weekRound: this.weekRound,
|
||||
startHour: this.startHour,
|
||||
startMin: this.startMin,
|
||||
endHour: this.endHour,
|
||||
endMin: this.endMin,
|
||||
role: this.role,
|
||||
password: this.password,
|
||||
token: token, // 使用从响应中获取的有效token
|
||||
publicKey: this.publicKey,
|
||||
privateKey: this.privateKey,
|
||||
);
|
||||
}
|
||||
|
||||
// 如果无法创建重发命令,抛出异常
|
||||
throw Exception('无法为响应创建重发命令: $response');
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +53,12 @@ class BleCmdGetPublicKey extends BaseBleCommand<GetPublicKeyResponse> {
|
||||
|
||||
buffer.addAll(lockIdBytes);
|
||||
|
||||
|
||||
if ((buffer.length % 16) != 0) {
|
||||
final int add = 16 - buffer.length % 16;
|
||||
for (int i = 0; i < add; i++) {
|
||||
buffer.add(0);
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
112
lib/ble/command/response/ble_cmd_add_admin_parser.dart
Normal file
112
lib/ble/command/response/ble_cmd_add_admin_parser.dart
Normal file
@ -0,0 +1,112 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:starwork_flutter/base/app_logger.dart';
|
||||
import 'package:starwork_flutter/ble/command/base/base_ble_command.dart';
|
||||
import 'package:starwork_flutter/ble/command/base/base_ble_response_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/request/ble_cmd_add_admin.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/model/ble_add_admin_response.dart';
|
||||
|
||||
class BleCmdAddAdminParser extends BaseBleResponseParser {
|
||||
@override
|
||||
int get commandId => BleCmdAddAdmin.cmdId;
|
||||
|
||||
@override
|
||||
String get commandName => '增加超级管理员命令';
|
||||
|
||||
/// ✨✨✨ 解析获取增加超级管理员的应答数据 ✨✨✨
|
||||
@override
|
||||
BleAddAdminResponse parseResponse(ParsedPacket parsedPacket, List<int> rawResponseData) {
|
||||
try {
|
||||
if (rawResponseData.length < 3) {
|
||||
throw ArgumentError('应答数据长度不足: ${rawResponseData.length}字节 < 3字节');
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
|
||||
// 1. 提取命令ID (已在isMatchingResponse中验证过,这里记录日志)
|
||||
int responseCommandId = BaseBleResponseParser.extractInt(rawResponseData, offset, 2);
|
||||
offset += 2;
|
||||
|
||||
// 2. lockId
|
||||
List<int> lockId = BaseBleResponseParser.extractBytes(rawResponseData, offset, 40);
|
||||
String lockIdStr = _safeStringDecode(lockId).trim();
|
||||
offset += 40;
|
||||
|
||||
// 3. token
|
||||
List<int> token = BaseBleResponseParser.extractBytes(rawResponseData, offset, 4);
|
||||
offset += 4;
|
||||
|
||||
// 4. 提取状态码
|
||||
int statusCode = rawResponseData[offset++];
|
||||
|
||||
// 当状态码为0x06(需要token)时,token字段是有效的,应该返回这个token
|
||||
if (statusCode == 0x06) {
|
||||
return BleAddAdminResponse(
|
||||
commandId: commandId,
|
||||
statusCode: statusCode,
|
||||
token: token, // 状态码为0x06时返回有效的token
|
||||
lockId: lockIdStr,
|
||||
serialNo: '',
|
||||
no: -1,
|
||||
);
|
||||
}
|
||||
|
||||
// 当状态码为0时,表示成功,此时可能也有token(根据协议而定)
|
||||
if (statusCode == 0) {
|
||||
return BleAddAdminResponse(
|
||||
commandId: commandId,
|
||||
statusCode: statusCode,
|
||||
token: token, // 状态码为0时也返回token
|
||||
lockId: lockIdStr,
|
||||
serialNo: '',
|
||||
no: -1,
|
||||
);
|
||||
}
|
||||
|
||||
// 其他状态码情况
|
||||
return BleAddAdminResponse(
|
||||
commandId: commandId,
|
||||
statusCode: statusCode,
|
||||
token: [], // 其他状态码时返回空token
|
||||
lockId: lockIdStr,
|
||||
serialNo: '',
|
||||
no: -1,
|
||||
);
|
||||
} catch (e) {
|
||||
AppLogger.error('❌ 解析添加管理员应答数据异常', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 安全的字符串解码方法 ✨✨✨
|
||||
/// 处理可能包含非UTF-8字节的数据
|
||||
String _safeStringDecode(List<int> data) {
|
||||
// 1. 找到第一个 0x00(空字节)的位置,只取前面的有效部分
|
||||
int endIndex = 0;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
if (data[i] == 0) {
|
||||
break;
|
||||
}
|
||||
endIndex = i + 1;
|
||||
}
|
||||
|
||||
// 2. 截取有效字节
|
||||
List<int> validBytes = data.sublist(0, endIndex);
|
||||
|
||||
try {
|
||||
// 3. 使用 UTF-8 解码(允许畸形,但会替换为 )
|
||||
return utf8.decode(validBytes, allowMalformed: true).trim();
|
||||
} catch (e) {
|
||||
// 4. 极端情况:用字符码直接构造(基本不会走到这里)
|
||||
return String.fromCharCodes(validBytes).trim();
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 静态便捷方法 - 直接解析原始数据包 ✨✨✨
|
||||
static BleAddAdminResponse? parseRawPacket(List<int> rawPacketData) {
|
||||
BleCmdAddAdminParser parser = BleCmdAddAdminParser();
|
||||
return parser.handleResponse(rawPacketData) as BleAddAdminResponse?;
|
||||
}
|
||||
}
|
||||
@ -286,7 +286,7 @@ class BleCmdReadLockStatusParser extends BaseBleResponseParser {
|
||||
featureParaData: featureParaData,
|
||||
);
|
||||
} catch (e) {
|
||||
AppLogger.error('❌ 解析获取公钥应答数据异常', error: e);
|
||||
AppLogger.error('❌ 解析读取锁状态答数据异常', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
43
lib/ble/command/response/model/ble_add_admin_response.dart
Normal file
43
lib/ble/command/response/model/ble_add_admin_response.dart
Normal file
@ -0,0 +1,43 @@
|
||||
class BleAddAdminResponse {
|
||||
final int commandId;
|
||||
final int statusCode;
|
||||
final int no;
|
||||
final String serialNo;
|
||||
final String lockId;
|
||||
final List<int> token;
|
||||
|
||||
BleAddAdminResponse({
|
||||
required this.commandId,
|
||||
required this.statusCode,
|
||||
required this.token,
|
||||
required this.lockId,
|
||||
required this.serialNo,
|
||||
required this.no,
|
||||
});
|
||||
|
||||
/// 是否成功
|
||||
bool get isSuccess => statusCode == 0x00;
|
||||
|
||||
/// 状态描述
|
||||
String get statusDescription {
|
||||
switch (statusCode) {
|
||||
case 0x00:
|
||||
return '成功';
|
||||
case 0x01:
|
||||
return '失败';
|
||||
case 0x02:
|
||||
return '参数错误';
|
||||
case 0x03:
|
||||
return '设备忙';
|
||||
case 0x06:
|
||||
return '需要token';
|
||||
default:
|
||||
return '未知状态(0x${statusCode.toRadixString(16).padLeft(2, '0')})';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BleAddAdminResponse{commandId: $commandId, statusCode: $statusCode, no: $no, serialNo: $serialNo, lockId: $lockId, token: $token}';
|
||||
}
|
||||
}
|
||||
19
lib/ble/constant/lock_ble_constant.dart
Normal file
19
lib/ble/constant/lock_ble_constant.dart
Normal file
@ -0,0 +1,19 @@
|
||||
class LockBleConstant {
|
||||
/// KeyType: 钥匙类型
|
||||
/// 0: 普通钥匙
|
||||
/// 1: 临时钥匙(到期自动删除)
|
||||
/// 2: 蓝牙遥控器
|
||||
/// 255: 测试钥匙(添加正式钥匙后自动失效)
|
||||
static const int keyTypeNormal = 0;
|
||||
static const int keyTypeTemporary = 1;
|
||||
static const int keyTypeBluetoothRemote = 2;
|
||||
static const int keyTypeTest = 255;
|
||||
|
||||
/// Role: 用户角色
|
||||
/// 0: 普通用户
|
||||
/// 1: 管理员
|
||||
/// 255 (0xFF): 超级管理员
|
||||
static const int roleNormalUser = 0;
|
||||
static const int roleAdmin = 1;
|
||||
static const int roleSuperAdmin = 255; // 0xFF
|
||||
}
|
||||
@ -1,15 +1,19 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:starwork_flutter/base/app_logger.dart';
|
||||
import 'package:starwork_flutter/base/app_permission.dart';
|
||||
import 'package:starwork_flutter/base/base_controller.dart';
|
||||
import 'package:starwork_flutter/ble/ble_service.dart';
|
||||
import 'package:starwork_flutter/ble/command/base/base_ble_response_parser.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/command/response/ble_cmd_read_lock_status_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/model/ble_add_admin_response.dart';
|
||||
import 'package:starwork_flutter/ble/model/scan_device_info.dart';
|
||||
import 'package:starwork_flutter/common/constant/app_toast_messages.dart';
|
||||
import 'package:starwork_flutter/common/constant/cache_keys.dart';
|
||||
@ -171,6 +175,7 @@ class SearchDeviceController extends BaseController {
|
||||
/// 3.注册管理员密码
|
||||
void connectingDevices(ScanDeviceInfo device) async {
|
||||
try {
|
||||
showLoading();
|
||||
// 停止搜索
|
||||
BleService().stopBluetoothSearch();
|
||||
isSearching.value = false;
|
||||
@ -207,7 +212,7 @@ class SearchDeviceController extends BaseController {
|
||||
await SharedPreferencesUtils.saveIntList(CacheKeys.lockCommKey, privateKeyResponse.commKey);
|
||||
await SharedPreferencesUtils.saveIntList(CacheKeys.lockSignKey, privateKeyResponse.signKey);
|
||||
AppLogger.info('🎯 获取私钥成功: ${privateKeyResponse.toString()}');
|
||||
|
||||
|
||||
// 更新缓存的私钥,以便在解密时使用
|
||||
BleService.setCachedPrivateKey(privateKeyResponse.commKey);
|
||||
|
||||
@ -228,6 +233,30 @@ class SearchDeviceController extends BaseController {
|
||||
);
|
||||
if (readLockStatusResponse != null && readLockStatusResponse.isSuccess) {
|
||||
AppLogger.highlight('readLockStatusResponse:${readLockStatusResponse}');
|
||||
final Random rng = Random();
|
||||
// 生成 100000 到 999999 之间的随机整数
|
||||
final int number = rng.nextInt(900000) + 100000;
|
||||
//读取锁状态
|
||||
BleCmdAddAdmin addAdminCmd = BleCmdAddAdmin(
|
||||
lockId: device.advName,
|
||||
userId: '13655',
|
||||
privateKey: privateKeyResponse.commKey,
|
||||
authUserId: '13655',
|
||||
keyId: '1',
|
||||
startDate: DateTime.now().millisecondsSinceEpoch,
|
||||
expireDate: 0x11223344,
|
||||
password: number.toString(),
|
||||
token: [0, 0, 0, 0],
|
||||
publicKey: publicKeyResponse.publicKey,
|
||||
);
|
||||
// 发送命令,允许自动连接和搜索设备
|
||||
BleAddAdminResponse? addAdminResponse = await BleService().sendCommand<BleAddAdminResponse>(
|
||||
command: addAdminCmd,
|
||||
targetDeviceName: device.advName,
|
||||
// 通过名称搜索设备
|
||||
autoConnectIfNeeded: true,
|
||||
);
|
||||
AppLogger.highlight('addAdminResponse:${addAdminResponse}');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -236,6 +265,8 @@ class SearchDeviceController extends BaseController {
|
||||
} catch (e, stackTrace) {
|
||||
AppLogger.error('连接设备失败', error: e, stackTrace: stackTrace);
|
||||
showToast('连接失败,请重试');
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user