fix: 增加获取设备公钥、私钥接口
This commit is contained in:
parent
ac1e447eca
commit
067489b37b
@ -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/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_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_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_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_get_public_key_parser.dart';
|
||||||
import 'package:starwork_flutter/ble/model/scan_device_info.dart';
|
import 'package:starwork_flutter/ble/model/scan_device_info.dart';
|
||||||
@ -281,12 +282,10 @@ class BleService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 监听订阅值变化
|
/// 监听订阅值变化
|
||||||
void _handleListenCharacteristicData(List<int> data) {
|
void _handleListenCharacteristicData(List<int> data) async {
|
||||||
AppLogger.highlight('✨✨✨ 📨 收到订阅数据:$data (长度: ${data.length}) ✨✨✨');
|
|
||||||
|
|
||||||
// 解析数据
|
// 解析数据
|
||||||
if (data.isNotEmpty) {
|
if (data.isNotEmpty) {
|
||||||
dynamic result = bleCommandManager.handleResponse(data);
|
dynamic result = await bleCommandManager.handleResponse(data);
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
// 触发命令响应等待器
|
// 触发命令响应等待器
|
||||||
@ -418,6 +417,7 @@ class BleService {
|
|||||||
final int? commandId = switch (command) {
|
final int? commandId = switch (command) {
|
||||||
BleCmdGetPublicKey() => BleCmdGetPublicKey.cmdId,
|
BleCmdGetPublicKey() => BleCmdGetPublicKey.cmdId,
|
||||||
BleCmdGetPrivateKey() => BleCmdGetPrivateKey.cmdId,
|
BleCmdGetPrivateKey() => BleCmdGetPrivateKey.cmdId,
|
||||||
|
BleCmdReadLockStatus() => BleCmdReadLockStatus.cmdId,
|
||||||
// 可在此添加更多命令类型
|
// 可在此添加更多命令类型
|
||||||
// BleCmdAnother() => BleCmdAnother.cmdId,
|
// BleCmdAnother() => BleCmdAnother.cmdId,
|
||||||
_ => null, // 默认情况:无法识别的命令类型
|
_ => null, // 默认情况:无法识别的命令类型
|
||||||
|
|||||||
@ -1,21 +1,22 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:starwork_flutter/base/app_logger.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/base_ble_command.dart';
|
||||||
|
|
||||||
/// ✨✨✨ 蓝牙命令应答解析基类 ✨✨✨
|
/// ✨✨✨ 蓝牙命令应答解析基类 ✨✨✨
|
||||||
abstract class BaseBleResponseParser {
|
abstract class BaseBleResponseParser {
|
||||||
|
|
||||||
/// 命令ID - 子类必须定义
|
/// 命令ID - 子类必须定义
|
||||||
int get commandId;
|
int get commandId;
|
||||||
|
|
||||||
/// 命令名称 - 子类可以重写,用于日志显示
|
/// 命令名称 - 子类可以重写,用于日志显示
|
||||||
String get commandName => '未知命令(0x${commandId.toRadixString(16).padLeft(4, '0')})';
|
String get commandName => '未知命令(0x${commandId.toRadixString(16).padLeft(4, '0')})';
|
||||||
|
|
||||||
/// ✨✨✨ 解析命令应答数据的抽象方法 ✨✨✨
|
/// ✨✨✨ 解析命令应答数据的抽象方法 ✨✨✨
|
||||||
/// [parsedPacket] 已解析的数据包基本信息
|
/// [parsedPacket] 已解析的数据包基本信息
|
||||||
/// [rawResponseData] 原始应答数据(仅数据块部分)
|
/// [rawResponseData] 原始应答数据(仅数据块部分)
|
||||||
/// 返回解析后的业务数据对象
|
/// 返回解析后的业务数据对象
|
||||||
dynamic parseResponse(ParsedPacket parsedPacket, List<int> rawResponseData);
|
dynamic parseResponse(ParsedPacket parsedPacket, List<int> rawResponseData);
|
||||||
|
|
||||||
/// ✨✨✨ 验证应答数据是否匹配当前命令 ✨✨✨
|
/// ✨✨✨ 验证应答数据是否匹配当前命令 ✨✨✨
|
||||||
/// [parsedPacket] 已解析的数据包
|
/// [parsedPacket] 已解析的数据包
|
||||||
bool isMatchingResponse(ParsedPacket parsedPacket) {
|
bool isMatchingResponse(ParsedPacket parsedPacket) {
|
||||||
@ -24,30 +25,30 @@ abstract class BaseBleResponseParser {
|
|||||||
AppLogger.warn('⚠️ 数据包无效,不匹配任何命令');
|
AppLogger.warn('⚠️ 数据包无效,不匹配任何命令');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否为应答包
|
// 检查是否为应答包
|
||||||
if (parsedPacket.packetType != BaseBleCommand.PACKET_TYPE_RESPONSE) {
|
if (parsedPacket.packetType != BaseBleCommand.PACKET_TYPE_RESPONSE) {
|
||||||
AppLogger.debug('📋 非应答包,类型: 0x${parsedPacket.packetType.toRadixString(16).padLeft(2, '0')}');
|
AppLogger.debug('📋 非应答包,类型: 0x${parsedPacket.packetType.toRadixString(16).padLeft(2, '0')}');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查数据长度是否足够包含命令ID
|
// 检查数据长度是否足够包含命令ID
|
||||||
if (parsedPacket.data.length < 2) {
|
if (parsedPacket.data.length < 2) {
|
||||||
AppLogger.warn('⚠️ 应答数据长度不足,无法包含命令ID: ${parsedPacket.data.length}字节');
|
AppLogger.warn('⚠️ 应答数据长度不足,无法包含命令ID: ${parsedPacket.data.length}字节');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取命令ID (大端序)
|
// 提取命令ID (大端序)
|
||||||
int responseCommandId = (parsedPacket.data[0] << 8) | parsedPacket.data[1];
|
int responseCommandId = (parsedPacket.data[0] << 8) | parsedPacket.data[1];
|
||||||
|
|
||||||
// 检查命令ID是否匹配
|
// 检查命令ID是否匹配
|
||||||
bool isMatch = (responseCommandId == commandId);
|
bool isMatch = (responseCommandId == commandId);
|
||||||
AppLogger.debug('🔍 命令ID匹配检查: 期望=0x${commandId.toRadixString(16).padLeft(4, '0')}, ' +
|
AppLogger.debug('🔍 命令ID匹配检查: 期望=0x${commandId.toRadixString(16).padLeft(4, '0')}, ' +
|
||||||
'实际=0x${responseCommandId.toRadixString(16).padLeft(4, '0')}, 匹配=${isMatch ? '✅' : '❌'}');
|
'实际=0x${responseCommandId.toRadixString(16).padLeft(4, '0')}, 匹配=${isMatch ? '✅' : '❌'}');
|
||||||
|
|
||||||
return isMatch;
|
return isMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ✨✨✨ 完整的应答处理流程 ✨✨✨
|
/// ✨✨✨ 完整的应答处理流程 ✨✨✨
|
||||||
/// [rawPacketData] 接收到的完整数据包
|
/// [rawPacketData] 接收到的完整数据包
|
||||||
/// 返回解析后的业务数据,如果不匹配或解析失败则返回null
|
/// 返回解析后的业务数据,如果不匹配或解析失败则返回null
|
||||||
@ -57,27 +58,26 @@ abstract class BaseBleResponseParser {
|
|||||||
try {
|
try {
|
||||||
// 1. 解析数据包基本信息
|
// 1. 解析数据包基本信息
|
||||||
ParsedPacket parsedPacket = BaseBleCommand.parsePacket(rawPacketData);
|
ParsedPacket parsedPacket = BaseBleCommand.parsePacket(rawPacketData);
|
||||||
|
|
||||||
// 2. 检查是否匹配当前命令
|
// 2. 检查是否匹配当前命令
|
||||||
if (!isMatchingResponse(parsedPacket)) {
|
if (!isMatchingResponse(parsedPacket)) {
|
||||||
AppLogger.debug('📝 应答不匹配${commandName},跳过处理');
|
AppLogger.debug('📝 应答不匹配${commandName},跳过处理');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppLogger.info('🎯 ${commandName}应答匹配成功,开始解析业务数据');
|
AppLogger.info('🎯 ${commandName}应答匹配成功,开始解析业务数据');
|
||||||
|
|
||||||
// 3. 调用子类的具体解析逻辑
|
// 3. 调用子类的具体解析逻辑
|
||||||
dynamic result = parseResponse(parsedPacket, parsedPacket.data);
|
dynamic result = parseResponse(parsedPacket, parsedPacket.data);
|
||||||
|
|
||||||
AppLogger.highlight('✨✨✨ ✅ ${commandName}应答处理完成 ✨✨✨');
|
AppLogger.highlight('✨✨✨ ✅ ${commandName}应答处理完成 ✨✨✨');
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
AppLogger.error('❌ ${commandName}应答处理异常', error: e, stackTrace: stackTrace);
|
AppLogger.error('❌ ${commandName}应答处理异常', error: e, stackTrace: stackTrace);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ✨✨✨ 辅助方法 - 从数据中提取整数(大端序) ✨✨✨
|
/// ✨✨✨ 辅助方法 - 从数据中提取整数(大端序) ✨✨✨
|
||||||
/// [data] 数据字节数组
|
/// [data] 数据字节数组
|
||||||
/// [offset] 起始偏移
|
/// [offset] 起始偏移
|
||||||
@ -86,14 +86,14 @@ abstract class BaseBleResponseParser {
|
|||||||
if (offset + length > data.length) {
|
if (offset + length > data.length) {
|
||||||
throw ArgumentError('数据长度不足: offset=$offset, length=$length, dataLength=${data.length}');
|
throw ArgumentError('数据长度不足: offset=$offset, length=$length, dataLength=${data.length}');
|
||||||
}
|
}
|
||||||
|
|
||||||
int result = 0;
|
int result = 0;
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
result = (result << 8) | data[offset + i];
|
result = (result << 8) | data[offset + i];
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ✨✨✨ 辅助方法 - 从数据中提取字节数组 ✨✨✨
|
/// ✨✨✨ 辅助方法 - 从数据中提取字节数组 ✨✨✨
|
||||||
/// [data] 源数据
|
/// [data] 源数据
|
||||||
/// [offset] 起始偏移
|
/// [offset] 起始偏移
|
||||||
@ -104,7 +104,7 @@ abstract class BaseBleResponseParser {
|
|||||||
}
|
}
|
||||||
return data.sublist(offset, offset + length);
|
return data.sublist(offset, offset + length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ✨✨✨ 辅助方法 - 从数据中提取字符串 ✨✨✨
|
/// ✨✨✨ 辅助方法 - 从数据中提取字符串 ✨✨✨
|
||||||
/// [data] 数据字节数组
|
/// [data] 数据字节数组
|
||||||
/// [offset] 起始偏移
|
/// [offset] 起始偏移
|
||||||
@ -112,7 +112,7 @@ abstract class BaseBleResponseParser {
|
|||||||
/// [removeNullTerminator] 是否移除空终止符
|
/// [removeNullTerminator] 是否移除空终止符
|
||||||
static String extractString(List<int> data, int offset, int length, {bool removeNullTerminator = true}) {
|
static String extractString(List<int> data, int offset, int length, {bool removeNullTerminator = true}) {
|
||||||
List<int> stringBytes = extractBytes(data, offset, length);
|
List<int> stringBytes = extractBytes(data, offset, length);
|
||||||
|
|
||||||
if (removeNullTerminator) {
|
if (removeNullTerminator) {
|
||||||
// 找到第一个0字节并截断
|
// 找到第一个0字节并截断
|
||||||
int nullIndex = stringBytes.indexOf(0);
|
int nullIndex = stringBytes.indexOf(0);
|
||||||
@ -120,10 +120,10 @@ abstract class BaseBleResponseParser {
|
|||||||
stringBytes = stringBytes.sublist(0, nullIndex);
|
stringBytes = stringBytes.sublist(0, nullIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return String.fromCharCodes(stringBytes);
|
return String.fromCharCodes(stringBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ✨✨✨ 辅助方法 - 将字节数组转换为十六进制字符串 ✨✨✨
|
/// ✨✨✨ 辅助方法 - 将字节数组转换为十六进制字符串 ✨✨✨
|
||||||
/// [data] 字节数组
|
/// [data] 字节数组
|
||||||
/// [separator] 分隔符,默认为空格
|
/// [separator] 分隔符,默认为空格
|
||||||
@ -131,61 +131,15 @@ abstract class BaseBleResponseParser {
|
|||||||
return data.map((b) => '0x${b.toRadixString(16).padLeft(2, '0')}').join(separator);
|
return data.map((b) => '0x${b.toRadixString(16).padLeft(2, '0')}').join(separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ✨✨✨ 辅助方法 - 将十六进制字符串解析为字节数组 ✨✨✨
|
String utf8String(List<int> codeUnits) {
|
||||||
/// 支持格式:
|
codeUnits.reversed;
|
||||||
/// - "0x90 0x30 0x41 0x31" (带 0x 和空格)
|
final List<int> uniqueList = <int>[];
|
||||||
/// - "90 30 41 31" (无 0x,用空格分隔)
|
for (int i = 0; i < codeUnits.length; i++) {
|
||||||
/// - "90:30:41:31" (用冒号分隔)
|
if (codeUnits[i] != 0) {
|
||||||
/// - "0x90304131" (连续无分隔)
|
uniqueList.add(codeUnits[i]);
|
||||||
/// [hex] 十六进制字符串
|
|
||||||
/// [separator] 分隔符(可选),如果为 null 则自动检测常见分隔符
|
|
||||||
/// 返回 [List<int>] 字节数组,解析失败返回空列表
|
|
||||||
static List<int> hexToBytes(String hex, {String? separator}) {
|
|
||||||
if (hex.isEmpty) return [];
|
|
||||||
|
|
||||||
// 如果未指定分隔符,使用正则自动分割(支持空格、冒号、短横、逗号等)
|
|
||||||
List<String> 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
uniqueList.reversed;
|
||||||
final result = <int>[];
|
return utf8.decode(uniqueList).toString();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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/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_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_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/sm4_encipher/sm4.dart';
|
||||||
|
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
|
||||||
|
|
||||||
/// ✨✨✨ 蓝牙命令管理器 - 统一管理所有命令解析器 ✨✨✨
|
/// ✨✨✨ 蓝牙命令管理器 - 统一管理所有命令解析器 ✨✨✨
|
||||||
class BleCommandManager {
|
class BleCommandManager {
|
||||||
@ -70,7 +72,7 @@ class BleCommandManager {
|
|||||||
/// ✨✨✨ 处理接收到的应答数据包 ✨✨✨
|
/// ✨✨✨ 处理接收到的应答数据包 ✨✨✨
|
||||||
/// [rawPacketData] 完整的原始数据包
|
/// [rawPacketData] 完整的原始数据包
|
||||||
/// 返回解析后的业务数据,如果没有匹配的解析器则返回null
|
/// 返回解析后的业务数据,如果没有匹配的解析器则返回null
|
||||||
dynamic handleResponse(List<int> rawPacketData) {
|
dynamic handleResponse(List<int> rawPacketData) async {
|
||||||
AppLogger.debug('📊 收到蓝牙数据 (${rawPacketData.length}字节): ${rawPacketData.map((b) => '0x${b.toRadixString(16).padLeft(2, '0')}').join(' ')}');
|
AppLogger.debug('📊 收到蓝牙数据 (${rawPacketData.length}字节): ${rawPacketData.map((b) => '0x${b.toRadixString(16).padLeft(2, '0')}').join(' ')}');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -90,7 +92,7 @@ class BleCommandManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ✨✨✨ 根据加密类型进行解密处理 ✨✨✨
|
// ✨✨✨ 根据加密类型进行解密处理 ✨✨✨
|
||||||
List<int> processedData = _decryptDataIfNeeded(parsedPacket);
|
List<int> processedData = await _decryptDataIfNeeded(parsedPacket);
|
||||||
|
|
||||||
// ✨✨✨ 提取命令ID (大端序) ✨✨✨
|
// ✨✨✨ 提取命令ID (大端序) ✨✨✨
|
||||||
int commandId = (processedData[0] << 8) | processedData[1];
|
int commandId = (processedData[0] << 8) | processedData[1];
|
||||||
@ -133,7 +135,7 @@ class BleCommandManager {
|
|||||||
/// ✨✨✨ 根据加密类型解密数据 ✨✨✨
|
/// ✨✨✨ 根据加密类型解密数据 ✨✨✨
|
||||||
/// [parsedPacket] 已解析的数据包
|
/// [parsedPacket] 已解析的数据包
|
||||||
/// 返回解密后的数据或原始数据(如果不需要解密)
|
/// 返回解密后的数据或原始数据(如果不需要解密)
|
||||||
List<int> _decryptDataIfNeeded(ParsedPacket parsedPacket) {
|
Future<List<int>> _decryptDataIfNeeded(ParsedPacket parsedPacket) async {
|
||||||
// 如果是明文,直接返回原始数据
|
// 如果是明文,直接返回原始数据
|
||||||
if (parsedPacket.encryptType == BaseBleCommand.ENCRYPT_TYPE_PLAIN) {
|
if (parsedPacket.encryptType == BaseBleCommand.ENCRYPT_TYPE_PLAIN) {
|
||||||
AppLogger.debug('🔓 数据未加密,直接使用原始数据');
|
AppLogger.debug('🔓 数据未加密,直接使用原始数据');
|
||||||
@ -142,8 +144,7 @@ class BleCommandManager {
|
|||||||
|
|
||||||
switch (parsedPacket.encryptType) {
|
switch (parsedPacket.encryptType) {
|
||||||
case BaseBleCommand.ENCRYPT_TYPE_AES128:
|
case BaseBleCommand.ENCRYPT_TYPE_AES128:
|
||||||
return _decryptAES128(parsedPacket.data);
|
return parsedPacket.data;
|
||||||
|
|
||||||
case BaseBleCommand.ENCRYPT_TYPE_SM4_PRESET:
|
case BaseBleCommand.ENCRYPT_TYPE_SM4_PRESET:
|
||||||
var connectedDevice = BleService().connectedDevice;
|
var connectedDevice = BleService().connectedDevice;
|
||||||
var platformName = connectedDevice?.platformName;
|
var platformName = connectedDevice?.platformName;
|
||||||
@ -154,29 +155,23 @@ class BleCommandManager {
|
|||||||
);
|
);
|
||||||
return decrypt;
|
return decrypt;
|
||||||
case BaseBleCommand.ENCRYPT_TYPE_SM4_DEVICE:
|
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:
|
default:
|
||||||
AppLogger.warn('⚠️ 未知的加密类型: ${parsedPacket.encryptType},使用原始数据');
|
AppLogger.warn('⚠️ 未知的加密类型: ${parsedPacket.encryptType},使用原始数据');
|
||||||
return parsedPacket.data;
|
return parsedPacket.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ✨✨✨ AES128解密 ✨✨✨
|
|
||||||
/// [encryptedData] 加密数据
|
|
||||||
/// 返回解密后的数据
|
|
||||||
List<int> _decryptAES128(List<int> encryptedData) {
|
|
||||||
// TODO: 实现AES128解密逻辑
|
|
||||||
|
|
||||||
return encryptedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ✨✨✨ SM4解密 ✨✨✨
|
|
||||||
/// [encryptedData] 加密数据
|
|
||||||
/// 返回解密后的数据
|
|
||||||
List<int> _decryptSM4(List<int> encryptedData) {
|
|
||||||
return encryptedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ✨✨✨ 根据命令ID获取解析器 ✨✨✨
|
/// ✨✨✨ 根据命令ID获取解析器 ✨✨✨
|
||||||
BaseBleResponseParser? getParser(int commandId) {
|
BaseBleResponseParser? getParser(int commandId) {
|
||||||
return _parsers[commandId];
|
return _parsers[commandId];
|
||||||
|
|||||||
175
lib/ble/command/request/ble_cmd_add_admin.dart
Normal file
175
lib/ble/command/request/ble_cmd_add_admin.dart
Normal file
@ -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<GetPrivateKeyResponse> {
|
||||||
|
final String lockId;
|
||||||
|
final String keyId;
|
||||||
|
final String authUserID;
|
||||||
|
final String userID;
|
||||||
|
final List<int> 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<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>[];
|
||||||
|
|
||||||
|
// 指令类型
|
||||||
|
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<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(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -45,66 +45,6 @@ class BleCmdGetPrivateKey extends BaseBleCommand<GetPrivateKeyResponse> {
|
|||||||
/// ✨✨✨ 实现BaseBleCommand的抽象方法 - 构建命令数据 ✨✨✨
|
/// ✨✨✨ 实现BaseBleCommand的抽象方法 - 构建命令数据 ✨✨✨
|
||||||
@override
|
@override
|
||||||
List<int> buildData() {
|
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> data = <int>[];
|
||||||
List<int> ebcData = <int>[];
|
List<int> ebcData = <int>[];
|
||||||
|
|
||||||
|
|||||||
87
lib/ble/command/request/ble_cmd_read_lock_status.dart
Normal file
87
lib/ble/command/request/ble_cmd_read_lock_status.dart
Normal file
@ -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<ReadLockStatusResponse> {
|
||||||
|
final String lockId;
|
||||||
|
final String userId;
|
||||||
|
final int timeStamp;
|
||||||
|
final int localUnix;
|
||||||
|
final List<int> 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<int> buildData() {
|
||||||
|
List<int> data = <int>[];
|
||||||
|
List<int> ebcData = <int>[];
|
||||||
|
|
||||||
|
// 指令类型
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
244
lib/ble/command/response/ble_cmd_read_lock_status_parser.dart
Normal file
244
lib/ble/command/response/ble_cmd_read_lock_status_parser.dart
Normal file
@ -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<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. 提取状态码
|
||||||
|
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<int> 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<int> modelBytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 20);
|
||||||
|
String model = utf8String(modelBytes).trim();
|
||||||
|
offset += 20;
|
||||||
|
|
||||||
|
// 6. 软件版本 (20 bytes, UTF-8)
|
||||||
|
List<int> fwVersionBytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 20);
|
||||||
|
String fwVersion = utf8String(fwVersionBytes).trim();
|
||||||
|
offset += 20;
|
||||||
|
|
||||||
|
// 7. 硬件版本 (20 bytes, UTF-8)
|
||||||
|
List<int> hwVersionBytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 20);
|
||||||
|
String hwVersion = utf8String(hwVersionBytes).trim();
|
||||||
|
offset += 20;
|
||||||
|
|
||||||
|
// 8. 厂商序列号 (16 bytes, UTF-8)
|
||||||
|
List<int> serialNum0Bytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 16);
|
||||||
|
String serialNum0 = utf8String(serialNum0Bytes).trim();
|
||||||
|
offset += 16;
|
||||||
|
|
||||||
|
// 9. 成品商序列号 (16 bytes, UTF-8, 可为空)
|
||||||
|
List<int> serialNum1Bytes = BaseBleResponseParser.extractBytes(rawResponseData, offset, 16);
|
||||||
|
String serialNum1 = utf8String(serialNum1Bytes).trim();
|
||||||
|
offset += 16;
|
||||||
|
|
||||||
|
// 10. 蓝牙名称 (16 bytes, UTF-8)
|
||||||
|
List<int> 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<int> 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<int> 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<int> rawPacketData) {
|
||||||
|
BleCmdReadLockStatusParser parser = BleCmdReadLockStatusParser();
|
||||||
|
return parser.handleResponse(rawPacketData) as ReadLockStatusResponse?;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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/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_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_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_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_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/ble/model/scan_device_info.dart';
|
||||||
import 'package:starwork_flutter/common/constant/app_toast_messages.dart';
|
import 'package:starwork_flutter/common/constant/app_toast_messages.dart';
|
||||||
import 'package:starwork_flutter/common/constant/cache_keys.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 {
|
void connectingDevices(ScanDeviceInfo device) async {
|
||||||
try {
|
try {
|
||||||
// 停止搜索
|
// 停止搜索
|
||||||
@ -182,14 +187,12 @@ class SearchDeviceController extends BaseController {
|
|||||||
);
|
);
|
||||||
if (publicKeyResponse != null && publicKeyResponse.isSuccess) {
|
if (publicKeyResponse != null && publicKeyResponse.isSuccess) {
|
||||||
AppLogger.info('🎯 获取公钥成功: ${publicKeyResponse.publicKeyHex}');
|
AppLogger.info('🎯 获取公钥成功: ${publicKeyResponse.publicKeyHex}');
|
||||||
await SharedPreferencesUtils.saveIntList(CacheKeys.lockPublicKey, publicKeyResponse.publicKey);
|
await SharedPreferencesUtils.saveIntList(CacheKeys.lockPublicKey, publicKeyResponse.publicKey);
|
||||||
BleCmdGetPrivateKey getPrivateKeyCmd = BleCmdGetPrivateKey(
|
BleCmdGetPrivateKey getPrivateKeyCmd = BleCmdGetPrivateKey(
|
||||||
lockId: device.advName,
|
lockId: device.advName,
|
||||||
keyId: '1',
|
keyId: '1',
|
||||||
authUserID: '1',
|
authUserID: '1',
|
||||||
nowTime: DateTime
|
nowTime: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||||
.now()
|
|
||||||
.millisecondsSinceEpoch ~/ 1000,
|
|
||||||
publicKey: publicKeyResponse.publicKey,
|
publicKey: publicKeyResponse.publicKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -204,6 +207,25 @@ class SearchDeviceController extends BaseController {
|
|||||||
await SharedPreferencesUtils.saveIntList(CacheKeys.lockCommKey, privateKeyResponse.commKey);
|
await SharedPreferencesUtils.saveIntList(CacheKeys.lockCommKey, privateKeyResponse.commKey);
|
||||||
await SharedPreferencesUtils.saveIntList(CacheKeys.lockSignKey, privateKeyResponse.signKey);
|
await SharedPreferencesUtils.saveIntList(CacheKeys.lockSignKey, privateKeyResponse.signKey);
|
||||||
AppLogger.info('🎯 获取私钥成功: ${privateKeyResponse.toString()}');
|
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<ReadLockStatusResponse>(
|
||||||
|
command: readLockStatusCmd,
|
||||||
|
targetDeviceName: device.advName,
|
||||||
|
// 通过名称搜索设备
|
||||||
|
autoConnectIfNeeded: true,
|
||||||
|
);
|
||||||
|
if (readLockStatusResponse != null && readLockStatusResponse.isSuccess) {
|
||||||
|
AppLogger.highlight('readLockStatusResponse:${readLockStatusResponse}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AppLogger.warn('⚠️ 命令发送完成,但未收到有效响应');
|
AppLogger.warn('⚠️ 命令发送完成,但未收到有效响应');
|
||||||
@ -214,6 +236,7 @@ class SearchDeviceController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 检查并申请权限
|
||||||
Future<bool> _checkAndRequestBlePermission() async {
|
Future<bool> _checkAndRequestBlePermission() async {
|
||||||
var locationPermission = await AppPermission.requestLocationPermission();
|
var locationPermission = await AppPermission.requestLocationPermission();
|
||||||
if (!locationPermission) {
|
if (!locationPermission) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user