fix: 增加获取设备公钥、私钥接口

This commit is contained in:
liyi 2025-09-08 18:04:14 +08:00
parent ac1e447eca
commit 067489b37b
8 changed files with 586 additions and 168 deletions

View File

@ -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<int> data) {
AppLogger.highlight('✨✨✨ 📨 收到订阅数据:$data (长度: ${data.length}) ✨✨✨');
void _handleListenCharacteristicData(List<int> 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, //

View File

@ -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<int> 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<int> data, int offset, int length, {bool removeNullTerminator = true}) {
List<int> 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<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));
}
String utf8String(List<int> codeUnits) {
codeUnits.reversed;
final List<int> uniqueList = <int>[];
for (int i = 0; i < codeUnits.length; i++) {
if (codeUnits[i] != 0) {
uniqueList.add(codeUnits[i]);
}
}
final result = <int>[];
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();
}
}
}

View File

@ -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<int> rawPacketData) {
dynamic handleResponse(List<int> 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<int> processedData = _decryptDataIfNeeded(parsedPacket);
List<int> processedData = await _decryptDataIfNeeded(parsedPacket);
// ID ()
int commandId = (processedData[0] << 8) | processedData[1];
@ -133,7 +135,7 @@ class BleCommandManager {
///
/// [parsedPacket]
///
List<int> _decryptDataIfNeeded(ParsedPacket parsedPacket) {
Future<List<int>> _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<int> _decryptAES128(List<int> encryptedData) {
// TODO: AES128解密逻辑
return encryptedData;
}
/// SM4解密
/// [encryptedData]
///
List<int> _decryptSM4(List<int> encryptedData) {
return encryptedData;
}
/// ID获取解析器
BaseBleResponseParser? getParser(int commandId) {
return _parsers[commandId];

View 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. 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>[];
//
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);
// authUserIDKeyIDmd5加密之后就是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;
}
}

View File

@ -45,66 +45,6 @@ class BleCmdGetPrivateKey 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>[];

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

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

View File

@ -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<ReadLockStatusResponse>(
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<bool> _checkAndRequestBlePermission() async {
var locationPermission = await AppPermission.requestLocationPermission();
if (!locationPermission) {