diff --git a/lib/ble/ble_service.dart b/lib/ble/ble_service.dart index 83e01c9..0d88698 100644 --- a/lib/ble/ble_service.dart +++ b/lib/ble/ble_service.dart @@ -7,6 +7,7 @@ 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_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'; @@ -281,12 +282,10 @@ class BleService { } /// 监听订阅值变化 - void _handleListenCharacteristicData(List data) { - AppLogger.highlight('✨✨✨ 📨 收到订阅数据:$data (长度: ${data.length}) ✨✨✨'); - + void _handleListenCharacteristicData(List data) async { // 解析数据 if (data.isNotEmpty) { - dynamic result = bleCommandManager.handleResponse(data); + dynamic result = await bleCommandManager.handleResponse(data); if (result != null) { // 触发命令响应等待器 @@ -418,6 +417,7 @@ class BleService { final int? commandId = switch (command) { BleCmdGetPublicKey() => BleCmdGetPublicKey.cmdId, BleCmdGetPrivateKey() => BleCmdGetPrivateKey.cmdId, + BleCmdReadLockStatus() => BleCmdReadLockStatus.cmdId, // 可在此添加更多命令类型 // BleCmdAnother() => BleCmdAnother.cmdId, _ => null, // 默认情况:无法识别的命令类型 diff --git a/lib/ble/command/base/base_ble_response_parser.dart b/lib/ble/command/base/base_ble_response_parser.dart index db3c7be..915b2a2 100644 --- a/lib/ble/command/base/base_ble_response_parser.dart +++ b/lib/ble/command/base/base_ble_response_parser.dart @@ -1,21 +1,22 @@ +import 'dart:convert'; + import 'package:starwork_flutter/base/app_logger.dart'; import 'package:starwork_flutter/ble/command/base/base_ble_command.dart'; /// ✨✨✨ 蓝牙命令应答解析基类 ✨✨✨ abstract class BaseBleResponseParser { - /// 命令ID - 子类必须定义 int get commandId; - + /// 命令名称 - 子类可以重写,用于日志显示 String get commandName => '未知命令(0x${commandId.toRadixString(16).padLeft(4, '0')})'; - + /// ✨✨✨ 解析命令应答数据的抽象方法 ✨✨✨ /// [parsedPacket] 已解析的数据包基本信息 /// [rawResponseData] 原始应答数据(仅数据块部分) /// 返回解析后的业务数据对象 dynamic parseResponse(ParsedPacket parsedPacket, List rawResponseData); - + /// ✨✨✨ 验证应答数据是否匹配当前命令 ✨✨✨ /// [parsedPacket] 已解析的数据包 bool isMatchingResponse(ParsedPacket parsedPacket) { @@ -24,30 +25,30 @@ abstract class BaseBleResponseParser { AppLogger.warn('⚠️ 数据包无效,不匹配任何命令'); return false; } - + // 检查是否为应答包 if (parsedPacket.packetType != BaseBleCommand.PACKET_TYPE_RESPONSE) { AppLogger.debug('📋 非应答包,类型: 0x${parsedPacket.packetType.toRadixString(16).padLeft(2, '0')}'); return false; } - + // 检查数据长度是否足够包含命令ID if (parsedPacket.data.length < 2) { AppLogger.warn('⚠️ 应答数据长度不足,无法包含命令ID: ${parsedPacket.data.length}字节'); return false; } - + // 提取命令ID (大端序) int responseCommandId = (parsedPacket.data[0] << 8) | parsedPacket.data[1]; - + // 检查命令ID是否匹配 bool isMatch = (responseCommandId == commandId); AppLogger.debug('🔍 命令ID匹配检查: 期望=0x${commandId.toRadixString(16).padLeft(4, '0')}, ' + '实际=0x${responseCommandId.toRadixString(16).padLeft(4, '0')}, 匹配=${isMatch ? '✅' : '❌'}'); - + return isMatch; } - + /// ✨✨✨ 完整的应答处理流程 ✨✨✨ /// [rawPacketData] 接收到的完整数据包 /// 返回解析后的业务数据,如果不匹配或解析失败则返回null @@ -57,27 +58,26 @@ abstract class BaseBleResponseParser { try { // 1. 解析数据包基本信息 ParsedPacket parsedPacket = BaseBleCommand.parsePacket(rawPacketData); - + // 2. 检查是否匹配当前命令 if (!isMatchingResponse(parsedPacket)) { AppLogger.debug('📝 应答不匹配${commandName},跳过处理'); return null; } - + AppLogger.info('🎯 ${commandName}应答匹配成功,开始解析业务数据'); - + // 3. 调用子类的具体解析逻辑 dynamic result = parseResponse(parsedPacket, parsedPacket.data); - + AppLogger.highlight('✨✨✨ ✅ ${commandName}应答处理完成 ✨✨✨'); return result; - } catch (e, stackTrace) { AppLogger.error('❌ ${commandName}应答处理异常', error: e, stackTrace: stackTrace); return null; } } - + /// ✨✨✨ 辅助方法 - 从数据中提取整数(大端序) ✨✨✨ /// [data] 数据字节数组 /// [offset] 起始偏移 @@ -86,14 +86,14 @@ abstract class BaseBleResponseParser { if (offset + length > data.length) { throw ArgumentError('数据长度不足: offset=$offset, length=$length, dataLength=${data.length}'); } - + int result = 0; for (int i = 0; i < length; i++) { result = (result << 8) | data[offset + i]; } return result; } - + /// ✨✨✨ 辅助方法 - 从数据中提取字节数组 ✨✨✨ /// [data] 源数据 /// [offset] 起始偏移 @@ -104,7 +104,7 @@ abstract class BaseBleResponseParser { } return data.sublist(offset, offset + length); } - + /// ✨✨✨ 辅助方法 - 从数据中提取字符串 ✨✨✨ /// [data] 数据字节数组 /// [offset] 起始偏移 @@ -112,7 +112,7 @@ abstract class BaseBleResponseParser { /// [removeNullTerminator] 是否移除空终止符 static String extractString(List data, int offset, int length, {bool removeNullTerminator = true}) { List stringBytes = extractBytes(data, offset, length); - + if (removeNullTerminator) { // 找到第一个0字节并截断 int nullIndex = stringBytes.indexOf(0); @@ -120,10 +120,10 @@ abstract class BaseBleResponseParser { stringBytes = stringBytes.sublist(0, nullIndex); } } - + return String.fromCharCodes(stringBytes); } - + /// ✨✨✨ 辅助方法 - 将字节数组转换为十六进制字符串 ✨✨✨ /// [data] 字节数组 /// [separator] 分隔符,默认为空格 @@ -131,61 +131,15 @@ abstract class BaseBleResponseParser { return data.map((b) => '0x${b.toRadixString(16).padLeft(2, '0')}').join(separator); } - /// ✨✨✨ 辅助方法 - 将十六进制字符串解析为字节数组 ✨✨✨ - /// 支持格式: - /// - "0x90 0x30 0x41 0x31" (带 0x 和空格) - /// - "90 30 41 31" (无 0x,用空格分隔) - /// - "90:30:41:31" (用冒号分隔) - /// - "0x90304131" (连续无分隔) - /// [hex] 十六进制字符串 - /// [separator] 分隔符(可选),如果为 null 则自动检测常见分隔符 - /// 返回 [List] 字节数组,解析失败返回空列表 - static List hexToBytes(String hex, {String? separator}) { - if (hex.isEmpty) return []; - - // 如果未指定分隔符,使用正则自动分割(支持空格、冒号、短横、逗号等) - List parts; - if (separator != null) { - parts = hex.split(separator); - } else { - // 移除所有空白和常见分隔符,然后每两个字符一组(适用于连续字符串) - // 或按分隔符切分 - final hasSeparators = RegExp(r'[\s,:;-]').hasMatch(hex); - if (hasSeparators) { - parts = hex.split(RegExp(r'[\s,:;-]+')); - } else { - // 无分隔符:每两个字符一组 - hex = hex.replaceAll('0x', '').replaceAll(' ', ''); - if (hex.length % 2 != 0) hex = '0$hex'; // 补齐偶数 - parts = []; - for (int i = 0; i < hex.length; i += 2) { - parts.add(hex.substring(i, i + 2)); - } + String utf8String(List codeUnits) { + codeUnits.reversed; + final List uniqueList = []; + for (int i = 0; i < codeUnits.length; i++) { + if (codeUnits[i] != 0) { + uniqueList.add(codeUnits[i]); } } - - final result = []; - final RegExp hexPattern = RegExp(r'^0x([0-9a-fA-F]{2})$|^([0-9a-fA-F]{2})$'); - - for (String part in parts) { - if (part.isEmpty) continue; - - final match = hexPattern.firstMatch(part.trim()); - if (match == null) { - print('Invalid hex part: $part'); - continue; - } - - // 提取十六进制数字(兼容 0xXX 和 XX) - final hexValue = match.group(1) ?? match.group(2); - try { - final byte = int.parse(hexValue!, radix: 16); - result.add(byte); - } catch (e) { - print('Failed to parse hex: $hexValue, error: $e'); - } - } - - return result; + uniqueList.reversed; + return utf8.decode(uniqueList).toString(); } -} \ No newline at end of file +} diff --git a/lib/ble/command/ble_command_manager.dart b/lib/ble/command/ble_command_manager.dart index d0861b4..5cbff88 100644 --- a/lib/ble/command/ble_command_manager.dart +++ b/lib/ble/command/ble_command_manager.dart @@ -6,7 +6,9 @@ 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_get_private_key_parser.dart'; import 'package:starwork_flutter/ble/command/response/ble_cmd_get_public_key_parser.dart'; +import 'package:starwork_flutter/common/constant/cache_keys.dart'; import 'package:starwork_flutter/common/sm4_encipher/sm4.dart'; +import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart'; /// ✨✨✨ 蓝牙命令管理器 - 统一管理所有命令解析器 ✨✨✨ class BleCommandManager { @@ -70,7 +72,7 @@ class BleCommandManager { /// ✨✨✨ 处理接收到的应答数据包 ✨✨✨ /// [rawPacketData] 完整的原始数据包 /// 返回解析后的业务数据,如果没有匹配的解析器则返回null - dynamic handleResponse(List rawPacketData) { + dynamic handleResponse(List rawPacketData) async { AppLogger.debug('📊 收到蓝牙数据 (${rawPacketData.length}字节): ${rawPacketData.map((b) => '0x${b.toRadixString(16).padLeft(2, '0')}').join(' ')}'); try { @@ -90,7 +92,7 @@ class BleCommandManager { } // ✨✨✨ 根据加密类型进行解密处理 ✨✨✨ - List processedData = _decryptDataIfNeeded(parsedPacket); + List processedData = await _decryptDataIfNeeded(parsedPacket); // ✨✨✨ 提取命令ID (大端序) ✨✨✨ int commandId = (processedData[0] << 8) | processedData[1]; @@ -133,7 +135,7 @@ class BleCommandManager { /// ✨✨✨ 根据加密类型解密数据 ✨✨✨ /// [parsedPacket] 已解析的数据包 /// 返回解密后的数据或原始数据(如果不需要解密) - List _decryptDataIfNeeded(ParsedPacket parsedPacket) { + Future> _decryptDataIfNeeded(ParsedPacket parsedPacket) async { // 如果是明文,直接返回原始数据 if (parsedPacket.encryptType == BaseBleCommand.ENCRYPT_TYPE_PLAIN) { AppLogger.debug('🔓 数据未加密,直接使用原始数据'); @@ -142,8 +144,7 @@ class BleCommandManager { switch (parsedPacket.encryptType) { case BaseBleCommand.ENCRYPT_TYPE_AES128: - return _decryptAES128(parsedPacket.data); - + return parsedPacket.data; case BaseBleCommand.ENCRYPT_TYPE_SM4_PRESET: var connectedDevice = BleService().connectedDevice; var platformName = connectedDevice?.platformName; @@ -154,29 +155,23 @@ class BleCommandManager { ); return decrypt; case BaseBleCommand.ENCRYPT_TYPE_SM4_DEVICE: - return _decryptSM4(parsedPacket.data); + var lockCommKey = await SharedPreferencesUtils.getString(CacheKeys.lockCommKey); + if (lockCommKey == null) { + AppLogger.warn('⚠️ 解密订阅数据时未找到私钥'); + return parsedPacket.data; + } + var decrypt = SM4.decrypt( + parsedPacket.data, + key: utf8.encode(lockCommKey), + mode: SM4CryptoMode.ECB, + ); + return decrypt; default: AppLogger.warn('⚠️ 未知的加密类型: ${parsedPacket.encryptType},使用原始数据'); return parsedPacket.data; } } - /// ✨✨✨ AES128解密 ✨✨✨ - /// [encryptedData] 加密数据 - /// 返回解密后的数据 - List _decryptAES128(List encryptedData) { - // TODO: 实现AES128解密逻辑 - - return encryptedData; - } - - /// ✨✨✨ SM4解密 ✨✨✨ - /// [encryptedData] 加密数据 - /// 返回解密后的数据 - List _decryptSM4(List encryptedData) { - return encryptedData; - } - /// ✨✨✨ 根据命令ID获取解析器 ✨✨✨ BaseBleResponseParser? getParser(int commandId) { return _parsers[commandId]; diff --git a/lib/ble/command/request/ble_cmd_add_admin.dart b/lib/ble/command/request/ble_cmd_add_admin.dart new file mode 100644 index 0000000..4c1c71e --- /dev/null +++ b/lib/ble/command/request/ble_cmd_add_admin.dart @@ -0,0 +1,175 @@ +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/response/ble_cmd_get_private_key_parser.dart'; +import 'package:starwork_flutter/common/sm4_encipher/sm4.dart'; + +/// ✨✨✨ 增加超级管理员命令类 - 继承BaseBleCommand ✨✨✨ +class BleCmdAddAdmin extends BaseBleCommand { + final String lockId; + final String keyId; + final String authUserID; + final String userID; + final List publicKey; + final int nowTime; + final int _encryptType; // 私有字段存储加密类型 + + /// 指令 ID: 0x3091 + static const int cmdId = 0x3091; + + /// 构造函数 + BleCmdAddAdmin({ + int encryptType = BaseBleCommand.ENCRYPT_TYPE_SM4_PRESET, + required this.lockId, + required this.keyId, + required this.authUserID, + required this.userID, + required this.nowTime, + required this.publicKey, + }) : _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'); + } + + /// ✨✨✨ 重写基类方法 - 返回当前命令的加密类型 ✨✨✨ + @override + int getEncryptType() { + return _encryptType; + } + + /// ✨✨✨ 实现BaseBleCommand的抽象方法 - 构建命令数据 ✨✨✨ + @override + List buildData() { + // final List buffer = []; + // + // // 1. 写入 CmdID (2 字节,大端序) + // buffer.add((cmdId >> 8) & 0xFF); // 高字节 + // buffer.add(cmdId & 0xFF); // 低字节 + // + // // 2. 处理 LockID:先转换实际字符,再检查是否需要填充到40字节 + // List lockIdBytes = List.from(lockId.codeUnits); // 创建可修改的副本 + // List keyIdBytes = List.from(keyId.codeUnits); // 创建可修改的副本 + // List 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 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 data = []; + List ebcData = []; + + // 指令类型 + final int type = cmdId; + final double typeDouble = type / 256; + final int type1 = typeDouble.toInt(); + final int type2 = type % 256; + data.add(type1); + data.add(type2); + + // 锁id + final int lockIDLength = utf8.encode(lockId!).length; + data.addAll(utf8.encode(lockId)); + data = getFixedLengthList(data, 40 - lockIDLength); + + //KeyID 40 + 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); + + //NowTime 4 + data.add((nowTime & 0xff000000) >> 24); + data.add((nowTime & 0xff0000) >> 16); + data.add((nowTime & 0xff00) >> 8); + data.add(nowTime & 0xff); + + final List authCodeData = []; + + //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(publicKey); + + // 把authUserID、KeyID、时间戳、公钥通过md5加密之后就是authCode + var authCode = md5.convert(authCodeData); + + data.add(authCode.bytes.length); + data.addAll(authCode.bytes); + + if ((data.length % 16) != 0) { + final int add = 16 - data.length % 16; + for (int i = 0; i < add; i++) { + data.add(0); + } + } + + // 拿到数据之后通过LockId进行SM4 ECB加密 key:544d485f633335373034383064613864 + ebcData = SM4.encrypt(data, key: utf8.encode(lockId), mode: SM4CryptoMode.ECB); + return ebcData; + } +} diff --git a/lib/ble/command/request/ble_cmd_get_private_key.dart b/lib/ble/command/request/ble_cmd_get_private_key.dart index 93431ed..8c72a0b 100644 --- a/lib/ble/command/request/ble_cmd_get_private_key.dart +++ b/lib/ble/command/request/ble_cmd_get_private_key.dart @@ -45,66 +45,6 @@ class BleCmdGetPrivateKey extends BaseBleCommand { /// ✨✨✨ 实现BaseBleCommand的抽象方法 - 构建命令数据 ✨✨✨ @override List buildData() { - // final List buffer = []; - // - // // 1. 写入 CmdID (2 字节,大端序) - // buffer.add((cmdId >> 8) & 0xFF); // 高字节 - // buffer.add(cmdId & 0xFF); // 低字节 - // - // // 2. 处理 LockID:先转换实际字符,再检查是否需要填充到40字节 - // List lockIdBytes = List.from(lockId.codeUnits); // 创建可修改的副本 - // List keyIdBytes = List.from(keyId.codeUnits); // 创建可修改的副本 - // List 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 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 data = []; List ebcData = []; diff --git a/lib/ble/command/request/ble_cmd_read_lock_status.dart b/lib/ble/command/request/ble_cmd_read_lock_status.dart new file mode 100644 index 0000000..75d87f7 --- /dev/null +++ b/lib/ble/command/request/ble_cmd_read_lock_status.dart @@ -0,0 +1,87 @@ +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/response/ble_cmd_get_private_key_parser.dart'; +import 'package:starwork_flutter/ble/command/response/ble_cmd_read_lock_status_parser.dart'; +import 'package:starwork_flutter/common/sm4_encipher/sm4.dart'; + +/// ✨✨✨ 读取锁状态命令类 - 继承BaseBleCommand ✨✨✨ +class BleCmdReadLockStatus extends BaseBleCommand { + final String lockId; + final String userId; + final int timeStamp; + final int localUnix; + final List privateKey; + final int _encryptType; // 私有字段存储加密类型 + + /// 指令 ID: 0x3040 + static const int cmdId = 0x3040; + + /// 构造函数 + BleCmdReadLockStatus({ + int encryptType = BaseBleCommand.ENCRYPT_TYPE_SM4_DEVICE, + required this.lockId, + required this.userId, + required this.timeStamp, + required this.localUnix, + required this.privateKey, + }) : _encryptType = encryptType { + AppLogger.debug('🔑 BleCmdReadLockStatus 创建: LockID="$lockId", 加密类型=$_encryptType'); + } + + /// ✨✨✨ 重写基类方法 - 返回当前命令的加密类型 ✨✨✨ + @override + int getEncryptType() { + return _encryptType; + } + + /// ✨✨✨ 实现BaseBleCommand的抽象方法 - 构建命令数据 ✨✨✨ + @override + List buildData() { + List data = []; + List ebcData = []; + + // 指令类型 + final int type = cmdId; + final double typeDouble = type / 256; + final int type1 = typeDouble.toInt(); + final int type2 = type % 256; + data.add(type1); + data.add(type2); + + // 锁id + final int lockIDLength = utf8.encode(lockId).length; + data.addAll(utf8.encode(lockId)); + data = getFixedLengthList(data, 40 - lockIDLength); + + //userId 40 + final int userIdLength = utf8.encode(userId).length; + data.addAll(utf8.encode(userId)); + data = getFixedLengthList(data, 40 - userIdLength); + + //timeStamp 4 + data.add((timeStamp & 0xff000000) >> 24); + data.add((timeStamp & 0xff0000) >> 16); + data.add((timeStamp & 0xff00) >> 8); + data.add(timeStamp & 0xff); + + //localUnix 4 + data.add((localUnix & 0xff000000) >> 24); + data.add((localUnix & 0xff0000) >> 16); + data.add((localUnix & 0xff00) >> 8); + data.add(localUnix & 0xff); + + if ((data.length % 16) != 0) { + final int add = 16 - data.length % 16; + for (int i = 0; i < add; i++) { + data.add(0); + } + } + + // 拿到数据之后通过私钥进行SM4 ECB加密 + ebcData = SM4.encrypt(data, key:privateKey, mode: SM4CryptoMode.ECB); + return ebcData; + } +} diff --git a/lib/ble/command/response/ble_cmd_read_lock_status_parser.dart b/lib/ble/command/response/ble_cmd_read_lock_status_parser.dart new file mode 100644 index 0000000..4b087b8 --- /dev/null +++ b/lib/ble/command/response/ble_cmd_read_lock_status_parser.dart @@ -0,0 +1,244 @@ +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_get_public_key.dart'; + +/// ✨✨✨ 读取锁状态命令应答数据结构 ✨✨✨ +class ReadLockStatusResponse { + final int commandId; + final int statusCode; + + final String vendor; // 厂商名称,例如 WDJT + final int product; // 锁设备类型:1=普通门锁,2=视频门锁,3=人脸识别门锁,4=挂锁 + final String model; // 产品型号,如 singlegrip2 + final String fwVersion; // 软件版本,如 1.0.0.230828 + final String hwVersion; // 硬件版本,如 1.0.0.230828 + final String serialNum0; // 厂商序列号(唯一,重置不变) + final String? serialNum1; // 成品商序列号(可选) + final String btDeviceName; // 蓝牙名称,如 TMH_c3570480da8d + final int electricQuantity; // 电池剩余电量 (%) + final int electricQuantityStandby; // 备用电池电量 + final int restoreCounter; // 重置次数 + final int restoreDate; // 重置时间(UNIX 时间戳,单位:秒) + final String icPartNo; // 主控芯片型号 + final int indate; // 有效时间(UNIX 时间戳) + final String mac; // 蓝牙 MAC 地址 + final int featureValueLength; // 特征值字符串长度 + + ReadLockStatusResponse({ + required this.commandId, + required this.statusCode, + required this.vendor, + required this.product, + required this.model, + required this.fwVersion, + required this.hwVersion, + required this.serialNum0, + required this.serialNum1, + required this.btDeviceName, + required this.electricQuantity, + required this.electricQuantityStandby, + required this.restoreCounter, + required this.restoreDate, + required this.icPartNo, + required this.indate, + required this.mac, + required this.featureValueLength, + }); + + /// 是否成功 + 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 'ReadLockStatusResponse(' + 'commandId: 0x${commandId.toRadixString(16).padLeft(4, '0')}, ' + 'status: $statusDescription, ' + 'vendor: $vendor, ' + 'product: $product, ' + 'model: $model, ' + 'fwVersion: $fwVersion, ' + 'hwVersion: $hwVersion, ' + 'serialNum0: $serialNum0, ' + 'serialNum1: $serialNum1, ' + 'btDeviceName: $btDeviceName, ' + 'electricQuantity: $electricQuantity%, ' + 'electricQuantityStandby: $electricQuantityStandby%, ' + 'restoreCounter: $restoreCounter, ' + 'restoreDate: $restoreDate (${DateTime.fromMillisecondsSinceEpoch(restoreDate * 1000, isUtc: true)}), ' + 'icPartNo: $icPartNo, ' + 'indate: $indate (${DateTime.fromMillisecondsSinceEpoch(indate * 1000, isUtc: true)}), ' + 'mac: $mac, ' + 'featureValueLength: $featureValueLength)'; + } +} + +/// ✨✨✨ 读取锁状态命令应答解析器 ✨✨✨ +class BleCmdReadLockStatusParser extends BaseBleResponseParser { + @override + int get commandId => BleCmdGetPublicKey.cmdId; // 0x3090 + + @override + String get commandName => '读取锁状态命令'; + + /// ✨✨✨ 解析获取读取锁状态的应答数据 ✨✨✨ + @override + ReadLockStatusResponse parseResponse(ParsedPacket parsedPacket, List 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. 提取状态码 + int statusCode = rawResponseData[offset++]; + + if (statusCode != 0) { + return ReadLockStatusResponse( + commandId: commandId, + statusCode: statusCode, + vendor: '', + product: 0, + model: '', + fwVersion: '', + hwVersion: '', + serialNum0: '', + serialNum1: '', + btDeviceName: '', + electricQuantity: 0, + electricQuantityStandby: 0, + restoreCounter: 0, + restoreDate: 0, + icPartNo: '', + indate: 0, + mac: '', + featureValueLength: 0, + ); + } + + // 3. 厂商名称 (20 bytes, UTF-8) + List vendorBytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 20); + String vendor = utf8String(vendorBytes).trim(); + offset += 20; + + // 4. 锁设备类型 (1 byte) + int product = rawResponseData[offset++]; + + // 5. 产品型号 (20 bytes, UTF-8) + List modelBytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 20); + String model = utf8String(modelBytes).trim(); + offset += 20; + + // 6. 软件版本 (20 bytes, UTF-8) + List fwVersionBytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 20); + String fwVersion = utf8String(fwVersionBytes).trim(); + offset += 20; + + // 7. 硬件版本 (20 bytes, UTF-8) + List hwVersionBytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 20); + String hwVersion = utf8String(hwVersionBytes).trim(); + offset += 20; + + // 8. 厂商序列号 (16 bytes, UTF-8) + List serialNum0Bytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 16); + String serialNum0 = utf8String(serialNum0Bytes).trim(); + offset += 16; + + // 9. 成品商序列号 (16 bytes, UTF-8, 可为空) + List serialNum1Bytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 16); + String serialNum1 = utf8String(serialNum1Bytes).trim(); + offset += 16; + + // 10. 蓝牙名称 (16 bytes, UTF-8) + List btDeviceNameBytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 16); + String btDeviceName = utf8String(btDeviceNameBytes).trim(); + offset += 16; + + // 11. 电池剩余电量 (1 byte) + int electricQuantity = rawResponseData[offset++]; + + // 12. 备用电池剩余电量 (1 byte) + int electricQuantityStandby = rawResponseData[offset++]; + + // 13. 重置次数 (2 bytes, big-endian) + int restoreCounter = BaseBleResponseParser.extractInt(rawResponseData, offset, 2); + offset += 2; + + // 14. 重置时间 (4 bytes, big-endian, 单位:秒) + int restoreDateSeconds = BaseBleResponseParser.extractInt(rawResponseData, offset, 4); + offset += 4; + + // 15. 主控芯片型号 (10 bytes, UTF-8) + List icPartNoBytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 10); + String icPartNo = utf8String(icPartNoBytes).trim(); + offset += 10; + + // 16. 有效时间 (4 bytes, big-endian, 单位:秒) + int indateSeconds = BaseBleResponseParser.extractInt(rawResponseData, offset, 4); + offset += 4; + + // 17. MAC 地址 (20 bytes, UTF-8) + List macBytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 20); + String mac = utf8String(macBytes).trim(); + offset += 20; + + // 18. 特征值长度 (1 byte) + int featureValueLength = rawResponseData[offset++]; + // ✅ 构造完整对象 + return ReadLockStatusResponse( + commandId: commandId, + statusCode: statusCode, + vendor: vendor, + product: product, + model: model, + fwVersion: fwVersion, + hwVersion: hwVersion, + serialNum0: serialNum0, + serialNum1: serialNum1, + btDeviceName: btDeviceName, + electricQuantity: electricQuantity, + electricQuantityStandby: electricQuantityStandby, + restoreCounter: restoreCounter, + restoreDate: restoreDateSeconds, + // 注意:UNIX 秒时间戳 + icPartNo: icPartNo, + indate: indateSeconds, + // 注意:UNIX 秒时间戳 + mac: mac, + featureValueLength: featureValueLength, + ); + } catch (e) { + AppLogger.error('❌ 解析获取公钥应答数据异常', error: e); + rethrow; + } + } + + /// ✨✨✨ 静态便捷方法 - 直接解析原始数据包 ✨✨✨ + static ReadLockStatusResponse? parseRawPacket(List rawPacketData) { + BleCmdReadLockStatusParser parser = BleCmdReadLockStatusParser(); + return parser.handleResponse(rawPacketData) as ReadLockStatusResponse?; + } +} diff --git a/lib/views/device/searchDevice/search_device_controller.dart b/lib/views/device/searchDevice/search_device_controller.dart index 648b3c7..6e17f7a 100644 --- a/lib/views/device/searchDevice/search_device_controller.dart +++ b/lib/views/device/searchDevice/search_device_controller.dart @@ -6,8 +6,10 @@ 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_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/model/scan_device_info.dart'; import 'package:starwork_flutter/common/constant/app_toast_messages.dart'; import 'package:starwork_flutter/common/constant/cache_keys.dart'; @@ -163,7 +165,10 @@ class SearchDeviceController extends BaseController { } } - // 连接设备 + /// 连接设备 + /// 1.获取锁公钥 + /// 2.获取锁私钥 + /// 3.注册管理员密码 void connectingDevices(ScanDeviceInfo device) async { try { // 停止搜索 @@ -182,14 +187,12 @@ class SearchDeviceController extends BaseController { ); if (publicKeyResponse != null && publicKeyResponse.isSuccess) { AppLogger.info('🎯 获取公钥成功: ${publicKeyResponse.publicKeyHex}'); - await SharedPreferencesUtils.saveIntList(CacheKeys.lockPublicKey, publicKeyResponse.publicKey); + await SharedPreferencesUtils.saveIntList(CacheKeys.lockPublicKey, publicKeyResponse.publicKey); BleCmdGetPrivateKey getPrivateKeyCmd = BleCmdGetPrivateKey( lockId: device.advName, keyId: '1', authUserID: '1', - nowTime: DateTime - .now() - .millisecondsSinceEpoch ~/ 1000, + nowTime: DateTime.now().millisecondsSinceEpoch ~/ 1000, publicKey: publicKeyResponse.publicKey, ); @@ -204,6 +207,25 @@ class SearchDeviceController extends BaseController { await SharedPreferencesUtils.saveIntList(CacheKeys.lockCommKey, privateKeyResponse.commKey); await SharedPreferencesUtils.saveIntList(CacheKeys.lockSignKey, privateKeyResponse.signKey); AppLogger.info('🎯 获取私钥成功: ${privateKeyResponse.toString()}'); + + //读取锁状态 + BleCmdReadLockStatus readLockStatusCmd = BleCmdReadLockStatus( + lockId: device.advName, + userId: '1', + timeStamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + localUnix: 0, + privateKey: privateKeyResponse.commKey, + ); + // 发送命令,允许自动连接和搜索设备 + ReadLockStatusResponse? readLockStatusResponse = await BleService().sendCommand( + command: readLockStatusCmd, + targetDeviceName: device.advName, + // 通过名称搜索设备 + autoConnectIfNeeded: true, + ); + if (readLockStatusResponse != null && readLockStatusResponse.isSuccess) { + AppLogger.highlight('readLockStatusResponse:${readLockStatusResponse}'); + } } } else { AppLogger.warn('⚠️ 命令发送完成,但未收到有效响应'); @@ -214,6 +236,7 @@ class SearchDeviceController extends BaseController { } } + /// 检查并申请权限 Future _checkAndRequestBlePermission() async { var locationPermission = await AppPermission.requestLocationPermission(); if (!locationPermission) {