fix: 暂缓,改为sdk接入开发

This commit is contained in:
liyi 2025-09-09 15:42:31 +08:00
parent 730cb9c5bb
commit b21fd12ca7
10 changed files with 678 additions and 176 deletions

View File

@ -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
// : 2CRC = 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();

View File

@ -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);
}

View File

@ -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());

View File

@ -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. LockID40
// 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) {
// // 040
// int paddingNeeded = 40 - lockIdBytes.length;
// lockIdBytes.addAll(List.filled(paddingNeeded, 0));
// }
//
// // 40
// if (keyIdBytes.length < 40) {
// // 040
// int paddingNeeded = 40 - keyIdBytes.length;
// keyIdBytes.addAll(List.filled(paddingNeeded, 0));
// }
//
// // 40
// if (authUserIdBytes.length < 20) {
// // 040
// 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);
// authUserIDKeyIDmd5加密之后就是authCode
// authUserIDKeyIDtokenmd5加密之后就是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类型且状态码为6token
if (response is BleAddAdminResponse) {
return response.statusCode == 0x06; // token
}
return false;
}
///
@override
BaseBleCommand<BleAddAdminResponse> createRetryCommand(dynamic response) {
if (response is BleAddAdminResponse) {
// 0x06tokentoken字段是有效的使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');
}
}

View File

@ -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;
}
}

View 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++];
// 0x06tokentoken字段是有效的token
if (statusCode == 0x06) {
return BleAddAdminResponse(
commandId: commandId,
statusCode: statusCode,
token: token, // 0x06token
lockId: lockIdStr,
serialNo: '',
no: -1,
);
}
// 0token
if (statusCode == 0) {
return BleAddAdminResponse(
commandId: commandId,
statusCode: statusCode,
token: token, // 0token
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?;
}
}

View File

@ -286,7 +286,7 @@ class BleCmdReadLockStatusParser extends BaseBleResponseParser {
featureParaData: featureParaData,
);
} catch (e) {
AppLogger.error('❌ 解析获取公钥应答数据异常', error: e);
AppLogger.error('❌ 解析读取锁状态答数据异常', error: e);
rethrow;
}
}

View 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}';
}
}

View 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
}

View File

@ -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();
}
}