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/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, // 默认情况:无法识别的命令类型
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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];
|
||||
|
||||
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的抽象方法 - 构建命令数据 ✨✨✨
|
||||
@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>[];
|
||||
|
||||
|
||||
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/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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user