feat: 增加团队管理页
This commit is contained in:
parent
c1a6830614
commit
7b57957e5b
@ -5,4 +5,6 @@ class ApiPath {
|
||||
static const String allTeamList = "/v1/team/teamListAll";
|
||||
static const String sceneList = "/v1/team/sceneList";
|
||||
static const String createTeam = "/v1/team/createTeam";
|
||||
static const String changeTeam = "/v1/team/changeTeam";
|
||||
static const String bindTeamStarCloudAccount = "/v1/team/bindStarCloudAccount";
|
||||
}
|
||||
|
||||
@ -18,11 +18,12 @@ class BaseApiService {
|
||||
dio.options.baseUrl = F.apiHost;
|
||||
dio.options.connectTimeout = const Duration(seconds: 30);
|
||||
dio.options.receiveTimeout = const Duration(seconds: 30);
|
||||
dio.options.headers['content-type'] = 'application/json';
|
||||
|
||||
// 添加拦截器
|
||||
dio.interceptors.add(InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
var token = SharedPreferencesUtils.getString(CacheKeys.token);
|
||||
AppLogger.info('token:${token}');
|
||||
if (token != null) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
class BindTeamStarCloudAccountRequest {
|
||||
String teamId;
|
||||
String teamNo;
|
||||
String username;
|
||||
String password;
|
||||
|
||||
BindTeamStarCloudAccountRequest({
|
||||
required this.teamId,
|
||||
required this.teamNo,
|
||||
required this.username,
|
||||
required this.password,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['teamId'] = teamId;
|
||||
data['teamNo'] = teamNo;
|
||||
data['username'] = username;
|
||||
data['password'] = password;
|
||||
return data;
|
||||
}
|
||||
|
||||
factory BindTeamStarCloudAccountRequest.fromJson(Map<String, dynamic> json) {
|
||||
return BindTeamStarCloudAccountRequest(
|
||||
teamId: json['teamId'] as String,
|
||||
teamNo: json['teamNo'] as String,
|
||||
username: json['username'] as String,
|
||||
password: json['password'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BindTeamStarCloudAccountRequest{teamId: $teamId, teamNo: $teamNo, username: $username, password: $password}';
|
||||
}
|
||||
}
|
||||
24
lib/api/model/team/request/change_current_team_request.dart
Normal file
24
lib/api/model/team/request/change_current_team_request.dart
Normal file
@ -0,0 +1,24 @@
|
||||
class ChangeCurrentTeamRequest {
|
||||
String teamNo;
|
||||
|
||||
ChangeCurrentTeamRequest({
|
||||
required this.teamNo,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"teamNo": teamNo,
|
||||
};
|
||||
}
|
||||
|
||||
factory ChangeCurrentTeamRequest.fromJson(Map<String, dynamic> json) {
|
||||
return ChangeCurrentTeamRequest(
|
||||
teamNo: json["teamNo"],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChangeCurrentTeamRequest{teamNo: $teamNo}';
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,8 @@ import 'package:get/get.dart';
|
||||
import 'package:starwork_flutter/api/api_path.dart';
|
||||
import 'package:starwork_flutter/api/api_response.dart';
|
||||
import 'package:starwork_flutter/api/base_api_service.dart';
|
||||
import 'package:starwork_flutter/api/model/team/request/bind_team_star_cloud_account_request.dart';
|
||||
import 'package:starwork_flutter/api/model/team/request/change_current_team_request.dart';
|
||||
import 'package:starwork_flutter/api/model/team/request/create_team_request.dart';
|
||||
import 'package:starwork_flutter/api/model/team/response/all_team_list_response.dart';
|
||||
import 'package:starwork_flutter/api/model/team/response/create_team_response.dart';
|
||||
@ -47,4 +49,30 @@ class TeamApiService {
|
||||
fromJson: (data) => SceneInfoResponseList.fromJson(data),
|
||||
);
|
||||
}
|
||||
|
||||
// 切换当前团队
|
||||
Future<ApiResponse<void>> requestChangeCurrentTeam({
|
||||
required ChangeCurrentTeamRequest request,
|
||||
}) {
|
||||
return _api.makeRequest(
|
||||
// 通过实例调用
|
||||
path: ApiPath.changeTeam,
|
||||
method: HttpConstant.post,
|
||||
data: request,
|
||||
fromJson: (data) {},
|
||||
);
|
||||
}
|
||||
|
||||
// 团队绑定星云账号
|
||||
Future<ApiResponse<SceneInfoResponseList>> requestBindTeamStarCloudAccount({
|
||||
required BindTeamStarCloudAccountRequest request,
|
||||
}) {
|
||||
return _api.makeRequest(
|
||||
// 通过实例调用
|
||||
path: ApiPath.bindTeamStarCloudAccount,
|
||||
method: HttpConstant.post,
|
||||
data: request,
|
||||
fromJson: (data) => SceneInfoResponseList.fromJson(data),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
|
||||
class BleConfig {
|
||||
|
||||
static Guid serviceId = Guid('fff0');
|
||||
|
||||
// 用来订阅的特征id
|
||||
static Guid characteristicIdSubscription = Guid('fff1');
|
||||
|
||||
// 用来写入的特征id
|
||||
static Guid characteristicIdWrite = Guid('fff2');
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,401 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:starwork_flutter/base/app_logger.dart';
|
||||
|
||||
/// ✨✨✨ 数据包解析结果类 ✨✨✨
|
||||
class ParsedPacket {
|
||||
final bool isValid;
|
||||
final int packetType;
|
||||
final int packetSequence;
|
||||
final int version;
|
||||
final int encryptType;
|
||||
final int encryptedDataLength;
|
||||
final int originalDataLength;
|
||||
final List<int> data;
|
||||
final int crc16;
|
||||
final String? errorMessage;
|
||||
|
||||
ParsedPacket({
|
||||
required this.isValid,
|
||||
required this.packetType,
|
||||
required this.packetSequence,
|
||||
required this.version,
|
||||
required this.encryptType,
|
||||
required this.encryptedDataLength,
|
||||
required this.originalDataLength,
|
||||
required this.data,
|
||||
required this.crc16,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (!isValid) {
|
||||
return 'ParsedPacket(invalid: $errorMessage)';
|
||||
}
|
||||
return 'ParsedPacket(type: 0x${packetType.toRadixString(16).padLeft(2, '0')}, ' +
|
||||
'seq: $packetSequence, version: ${version >> 4}, encrypt: $encryptType, ' +
|
||||
'dataLen: $originalDataLength, crc: 0x${crc16.toRadixString(16).padLeft(4, '0')})';
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 蓝牙命令基类 - 提供数据包组装功能 ✨✨✨
|
||||
abstract class BaseBleCommand<T> {
|
||||
/// 包头固定值: 0XEF01EE02
|
||||
static const List<int> PACKET_HEADER = [0xEF, 0x01, 0xEE, 0x02];
|
||||
|
||||
/// 包类型
|
||||
static const int PACKET_TYPE_REQUEST = 0x01; // 请求包
|
||||
static const int PACKET_TYPE_RESPONSE = 0x11; // 应答包
|
||||
|
||||
/// 包版本 (高4位)
|
||||
static const int PACKET_VERSION = 0x20; // 版本2.0
|
||||
|
||||
/// 加密类型 (低4位)
|
||||
static const int ENCRYPT_TYPE_PLAIN = 0x00; // 明文
|
||||
static const int ENCRYPT_TYPE_AES128 = 0x01; // AES128
|
||||
static const int ENCRYPT_TYPE_SM4_PRESET = 0x02; // SM4(事先约定密钥)
|
||||
static const int ENCRYPT_TYPE_SM4_DEVICE = 0x03; // SM4(设备指定密钥)
|
||||
|
||||
/// 最大单包数据长度 (可根据MTU调整)
|
||||
static int MAX_PACKET_DATA_SIZE = 100;
|
||||
|
||||
/// 全局包序号计数器
|
||||
static int _globalPacketSequence = 1;
|
||||
|
||||
/// ✨✨✨ 获取下一个包序号 ✨✨✨
|
||||
static int getNextPacketSequence() {
|
||||
int sequence = _globalPacketSequence;
|
||||
_globalPacketSequence++;
|
||||
if (_globalPacketSequence > 0xFFFF) {
|
||||
_globalPacketSequence = 1; // 2字节范围内循环
|
||||
}
|
||||
return sequence;
|
||||
}
|
||||
|
||||
/// ✨✨✨ 组装包头 ✨✨✨
|
||||
/// [packetType] 包类型 (0x01请求包, 0x11应答包)
|
||||
/// [packetSequence] 包序号 (2字节)
|
||||
/// [encryptType] 加密类型 (0:明文, 1:AES128, 2:SM4事先约定, 3:SM4设备指定)
|
||||
/// [dataLength] 数据长度 (高16位:加密后长度, 低16位:原始长度)
|
||||
static List<int> buildPacketHeader({
|
||||
required int packetType,
|
||||
required int packetSequence,
|
||||
int encryptType = ENCRYPT_TYPE_PLAIN,
|
||||
required int encryptedDataLength,
|
||||
required int originalDataLength,
|
||||
}) {
|
||||
List<int> header = [];
|
||||
|
||||
// 1. 包头 (4字节)
|
||||
header.addAll(PACKET_HEADER);
|
||||
|
||||
// 2. 包类型 (1字节)
|
||||
header.add(packetType);
|
||||
|
||||
// 3. 包序号 (2字节,大端序)
|
||||
header.add((packetSequence >> 8) & 0xFF); // 高字节
|
||||
header.add(packetSequence & 0xFF); // 低字节
|
||||
|
||||
// 4. 包标识 (1字节: 高4位版本 + 低4位加密类型)
|
||||
int packetFlag = (PACKET_VERSION & 0xF0) | (encryptType & 0x0F);
|
||||
header.add(packetFlag);
|
||||
|
||||
// 5. 数据长度 (4字节,大端序: 高16位加密后长度 + 低16位原始长度)
|
||||
int combinedLength = (encryptedDataLength << 16) | originalDataLength;
|
||||
header.add((combinedLength >> 24) & 0xFF); // 最高字节
|
||||
header.add((combinedLength >> 16) & 0xFF); // 次高字节
|
||||
header.add((combinedLength >> 8) & 0xFF); // 次低字节
|
||||
header.add(combinedLength & 0xFF); // 最低字节
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
/// ✨✨✨ 组装包尾 (CRC16校验) ✨✨✨
|
||||
/// [packetData] 完整的数据包(包头+数据块)
|
||||
static List<int> buildPacketTail(List<int> packetData) {
|
||||
int crc16 = calculateCRC16Kermit(packetData);
|
||||
|
||||
// CRC16校验位 (2字节,大端序)
|
||||
List<int> tail = [
|
||||
(crc16 >> 8) & 0xFF, // 高字节
|
||||
crc16 & 0xFF, // 低字节
|
||||
];
|
||||
|
||||
return tail;
|
||||
}
|
||||
|
||||
/// ✨✨✨ 计算CRC16-KERMIT校验 ✨✨✨
|
||||
static int calculateCRC16Kermit(List<int> data) {
|
||||
const int polynomial = 0x1021; // CRC16-KERMIT多项式
|
||||
const int initialValue = 0x0000;
|
||||
|
||||
int crc = initialValue;
|
||||
|
||||
for (int byte in data) {
|
||||
crc ^= (byte << 8);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((crc & 0x8000) != 0) {
|
||||
crc = (crc << 1) ^ polynomial;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
crc &= 0xFFFF; // 保持16位
|
||||
}
|
||||
}
|
||||
|
||||
// KERMIT CRC需要交换字节序
|
||||
int result = ((crc & 0xFF) << 8) | ((crc >> 8) & 0xFF);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// ✨✨✨ 分包处理 - 将大数据分割成多个包 ✨✨✨
|
||||
/// [originalData] 原始数据
|
||||
/// [encryptType] 加密类型
|
||||
/// 返回多个完整的数据包
|
||||
static List<List<int>> splitIntoPackets({
|
||||
required List<int> originalData,
|
||||
int encryptType = ENCRYPT_TYPE_PLAIN,
|
||||
}) {
|
||||
List<List<int>> packets = [];
|
||||
|
||||
// 计算需要分成多少包
|
||||
int totalPackets = (originalData.length / MAX_PACKET_DATA_SIZE).ceil();
|
||||
|
||||
for (int i = 0; i < totalPackets; i++) {
|
||||
int startIndex = i * MAX_PACKET_DATA_SIZE;
|
||||
int endIndex = (startIndex + MAX_PACKET_DATA_SIZE > originalData.length) ? originalData.length : startIndex + MAX_PACKET_DATA_SIZE;
|
||||
|
||||
// 获取当前包的数据
|
||||
List<int> currentPacketData = originalData.sublist(startIndex, endIndex);
|
||||
int originalDataLength = currentPacketData.length;
|
||||
|
||||
// ✨✨✨ 根据加密类型计算加密后数据长度 ✨✨✨
|
||||
int encryptedDataLength = originalDataLength; // 默认情况下加密后长度与原始长度相同
|
||||
if (encryptType != ENCRYPT_TYPE_PLAIN) {
|
||||
// TODO: 根据具体加密算法计算加密后长度
|
||||
// 例如AES128加密通常会填充到16字节的倍数
|
||||
switch (encryptType) {
|
||||
case ENCRYPT_TYPE_AES128:
|
||||
// AES128加密填充到16字节边界
|
||||
encryptedDataLength = ((originalDataLength + 15) ~/ 16) * 16;
|
||||
break;
|
||||
case ENCRYPT_TYPE_SM4_PRESET:
|
||||
case ENCRYPT_TYPE_SM4_DEVICE:
|
||||
// SM4加密填充到16字节边界
|
||||
encryptedDataLength = ((originalDataLength + 15) ~/ 16) * 16;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
// 获取分包序号
|
||||
int packetSequence = getNextPacketSequence();
|
||||
|
||||
// 组装包头
|
||||
List<int> header = buildPacketHeader(
|
||||
packetType: PACKET_TYPE_REQUEST,
|
||||
packetSequence: packetSequence,
|
||||
encryptType: encryptType,
|
||||
encryptedDataLength: originalData.length,
|
||||
originalDataLength: originalData.length,
|
||||
);
|
||||
// 组装完整包 (包头 + 数据)
|
||||
List<int> packetWithHeader = [];
|
||||
packetWithHeader.addAll(header);
|
||||
packetWithHeader.addAll(currentPacketData);
|
||||
|
||||
// 计算并添加包尾
|
||||
List<int> tail = buildPacketTail(packetWithHeader);
|
||||
|
||||
// 完整的数据包
|
||||
List<int> completePacket = [];
|
||||
completePacket.addAll(packetWithHeader);
|
||||
completePacket.addAll(tail);
|
||||
|
||||
packets.add(completePacket);
|
||||
} else {
|
||||
packets.add(currentPacketData);
|
||||
}
|
||||
}
|
||||
|
||||
return packets;
|
||||
}
|
||||
|
||||
/// ✨✨✨ 抽象方法 - 子类需要实现具体的数据构建逻辑 ✨✨✨
|
||||
List<int> buildData();
|
||||
|
||||
/// ✨✨✨ 获取当前命令的加密类型 - 子类可以重写此方法 ✨✨✨
|
||||
/// 默认返回明文类型,子类可以根据需要重写
|
||||
int getEncryptType() {
|
||||
return ENCRYPT_TYPE_PLAIN;
|
||||
}
|
||||
|
||||
/// ✨✨✨ 构建完整的命令包 ✨✨✨
|
||||
/// 使用子类定义的加密类型自动组装数据包
|
||||
List<List<int>> build() {
|
||||
// 获取子类实现的数据
|
||||
List<int> commandData = buildData();
|
||||
|
||||
// 使用子类定义的加密类型
|
||||
int encryptType = getEncryptType();
|
||||
|
||||
// 分包处理
|
||||
List<List<int>> packets = splitIntoPackets(
|
||||
originalData: commandData,
|
||||
encryptType: encryptType,
|
||||
);
|
||||
|
||||
return packets;
|
||||
}
|
||||
|
||||
/// ✨✨✨ 解析接收到的数据包 ✨✨✨
|
||||
/// [rawData] 接收到的原始数据包字节
|
||||
/// 返回解析结果,包含包的各个字段信息
|
||||
static ParsedPacket parsePacket(List<int> rawData) {
|
||||
try {
|
||||
// 1. 检查最小长度 (包头4 + 包类型1 + 包序号2 + 包标识1 + 数据长度4 + CRC2 = 14字节)
|
||||
if (rawData.length < 14) {
|
||||
return ParsedPacket(
|
||||
isValid: false,
|
||||
packetType: 0,
|
||||
packetSequence: 0,
|
||||
version: 0,
|
||||
encryptType: 0,
|
||||
encryptedDataLength: 0,
|
||||
originalDataLength: 0,
|
||||
data: [],
|
||||
crc16: 0,
|
||||
errorMessage: '数据包长度不足: ${rawData.length}字节 < 14字节',
|
||||
);
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
|
||||
// 2. 检查包头
|
||||
List<int> header = rawData.sublist(offset, offset + 4);
|
||||
offset += 4;
|
||||
if (!_isValidHeader(header)) {
|
||||
return ParsedPacket(
|
||||
isValid: false,
|
||||
packetType: 0,
|
||||
packetSequence: 0,
|
||||
version: 0,
|
||||
encryptType: 0,
|
||||
encryptedDataLength: 0,
|
||||
originalDataLength: 0,
|
||||
data: [],
|
||||
crc16: 0,
|
||||
errorMessage: '包头不正确: ${header.map((b) => '0x${b.toRadixString(16).padLeft(2, '0')}').join(' ')}',
|
||||
);
|
||||
}
|
||||
|
||||
// 3. 解析包类型
|
||||
int packetType = rawData[offset++];
|
||||
|
||||
// 4. 解析包序号 (大端序)
|
||||
int packetSequence = (rawData[offset] << 8) | rawData[offset + 1];
|
||||
offset += 2;
|
||||
|
||||
// 5. 解析包标识 (高4位版本 + 低4位加密类型)
|
||||
int packetFlag = rawData[offset++];
|
||||
int version = (packetFlag & 0xF0);
|
||||
int encryptType = (packetFlag & 0x0F);
|
||||
AppLogger.highlight('加密类型:${encryptType}');
|
||||
|
||||
// 6. 解析数据长度 (大端序: 高16位加密后长度 + 低16位原始长度)
|
||||
int combinedLength = (rawData[offset] << 24) | (rawData[offset + 1] << 16) | (rawData[offset + 2] << 8) | rawData[offset + 3];
|
||||
offset += 4;
|
||||
int encryptedDataLength = (combinedLength >> 16) & 0xFFFF;
|
||||
int originalDataLength = combinedLength & 0xFFFF;
|
||||
AppLogger.debug('📏 数据长度: 加密后=$encryptedDataLength, 原始=$originalDataLength');
|
||||
|
||||
// 7. 检查数据包完整性
|
||||
// ✨✨✨ 根据加密类型确定实际数据长度 ✨✨✨
|
||||
int actualDataLength = (encryptType == ENCRYPT_TYPE_PLAIN) ? originalDataLength : encryptedDataLength;
|
||||
int expectedTotalLength = 12 + actualDataLength + 2; // 包头到数据长度(12) + 数据块 + CRC(2)
|
||||
if (rawData.length != expectedTotalLength) {
|
||||
return ParsedPacket(
|
||||
isValid: false,
|
||||
packetType: packetType,
|
||||
packetSequence: packetSequence,
|
||||
version: version,
|
||||
encryptType: encryptType,
|
||||
encryptedDataLength: encryptedDataLength,
|
||||
originalDataLength: originalDataLength,
|
||||
data: [],
|
||||
crc16: 0,
|
||||
errorMessage: '数据包长度不匹配: 实际${rawData.length}字节 != 期望${expectedTotalLength}字节',
|
||||
);
|
||||
}
|
||||
|
||||
// 8. 提取数据块
|
||||
// ✨✨✨ 根据加密类型提取相应长度的数据 ✨✨✨
|
||||
List<int> data = rawData.sublist(offset, offset + actualDataLength);
|
||||
offset += actualDataLength;
|
||||
|
||||
// // 9. 提取CRC16校验位 (大端序)
|
||||
int crc16 = (rawData[offset] << 8) | rawData[offset + 1];
|
||||
|
||||
//
|
||||
// // 10. 验证CRC16校验
|
||||
// List<int> dataToCheck = rawData.sublist(0, rawData.length - 2); // 除去CRC的数据
|
||||
// int calculatedCRC = calculateCRC16Kermit(dataToCheck);
|
||||
// bool crcValid = (calculatedCRC == crc16);
|
||||
// AppLogger.debug('🔍 CRC16验证: 计算值=0x${calculatedCRC.toRadixString(16).padLeft(4, '0')}, 接收值=0x${crc16.toRadixString(16).padLeft(4, '0')}, ${crcValid ? '✅通过' : '❌失败'}');
|
||||
//
|
||||
// if (!crcValid) {
|
||||
// return ParsedPacket(
|
||||
// isValid: false,
|
||||
// packetType: packetType, packetSequence: packetSequence, version: version, encryptType: encryptType,
|
||||
// encryptedDataLength: encryptedDataLength, originalDataLength: originalDataLength, data: data,
|
||||
// crc16: crc16, errorMessage: 'CRC16校验失败: 计算值=0x${calculatedCRC.toRadixString(16).padLeft(4, '0')}, 接收值=0x${crc16.toRadixString(16).padLeft(4, '0')}',
|
||||
// );
|
||||
// }
|
||||
|
||||
return ParsedPacket(
|
||||
isValid: true,
|
||||
packetType: packetType,
|
||||
packetSequence: packetSequence,
|
||||
version: version,
|
||||
encryptType: encryptType,
|
||||
encryptedDataLength: encryptedDataLength,
|
||||
originalDataLength: originalDataLength,
|
||||
data: data,
|
||||
crc16: crc16,
|
||||
);
|
||||
} catch (e) {
|
||||
AppLogger.error('❌ 数据包解析异常', error: e);
|
||||
return ParsedPacket(
|
||||
isValid: false,
|
||||
packetType: 0,
|
||||
packetSequence: 0,
|
||||
version: 0,
|
||||
encryptType: 0,
|
||||
encryptedDataLength: 0,
|
||||
originalDataLength: 0,
|
||||
data: [],
|
||||
crc16: 0,
|
||||
errorMessage: '解析异常: $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 验证包头是否正确 ✨✨✨
|
||||
static bool _isValidHeader(List<int> header) {
|
||||
if (header.length != 4) return false;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (header[i] != PACKET_HEADER[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取固定长度的数组
|
||||
List<int> getFixedLengthList(List<int> data, int length) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
data.add(0);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@ -1,153 +0,0 @@
|
||||
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) {
|
||||
// 检查包是否有效
|
||||
if (!parsedPacket.isValid) {
|
||||
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
|
||||
dynamic handleResponse(List<int> rawPacketData) {
|
||||
AppLogger.highlight('✨✨✨ 🔄 开始处理${commandName}应答 ✨✨✨');
|
||||
|
||||
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] 起始偏移
|
||||
/// [length] 字节长度(1, 2, 4, 8)
|
||||
static int extractInt(List<int> data, int offset, int length) {
|
||||
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] 起始偏移
|
||||
/// [length] 提取长度
|
||||
static List<int> extractBytes(List<int> data, int offset, int length) {
|
||||
if (offset + length > data.length) {
|
||||
throw ArgumentError('数据长度不足: offset=$offset, length=$length, dataLength=${data.length}');
|
||||
}
|
||||
return data.sublist(offset, offset + length);
|
||||
}
|
||||
|
||||
/// ✨✨✨ 辅助方法 - 从数据中提取字符串 ✨✨✨
|
||||
/// [data] 数据字节数组
|
||||
/// [offset] 起始偏移
|
||||
/// [length] 字符串字节长度
|
||||
/// [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);
|
||||
if (nullIndex >= 0) {
|
||||
stringBytes = stringBytes.sublist(0, nullIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return String.fromCharCodes(stringBytes);
|
||||
}
|
||||
|
||||
/// ✨✨✨ 辅助方法 - 将字节数组转换为十六进制字符串 ✨✨✨
|
||||
/// [data] 字节数组
|
||||
/// [separator] 分隔符,默认为空格
|
||||
static String bytesToHex(List<int> data, {String separator = ' '}) {
|
||||
return data.map((b) => '0x${b.toRadixString(16).padLeft(2, '0')}').join(separator);
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
uniqueList.reversed;
|
||||
return utf8.decode(uniqueList).toString();
|
||||
}
|
||||
|
||||
String asciiString(List<int> codeUnits) {
|
||||
String result = '';
|
||||
for (final int value in codeUnits) {
|
||||
result += String.fromCharCode(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -1,270 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:starwork_flutter/base/app_logger.dart';
|
||||
import 'package:starwork_flutter/ble/ble_service.dart';
|
||||
import 'package:starwork_flutter/ble/command/base/base_ble_command.dart';
|
||||
import 'package:starwork_flutter/ble/command/base/base_ble_response_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/ble_cmd_add_admin_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/ble_cmd_get_private_key_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/ble_cmd_get_public_key_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/ble_cmd_read_lock_status_parser.dart';
|
||||
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 {
|
||||
// 私有构造函数
|
||||
BleCommandManager._() {
|
||||
// ✅ 这里就是单例初始化的地方
|
||||
// 只会执行一次(第一次获取实例时)
|
||||
_initialize();
|
||||
}
|
||||
|
||||
// 单例实例
|
||||
static final BleCommandManager _instance = BleCommandManager._();
|
||||
|
||||
// 工厂构造函数,提供全局访问点
|
||||
factory BleCommandManager() => _instance;
|
||||
|
||||
/// 所有已注册的命令解析器
|
||||
final Map<int, BaseBleResponseParser> _parsers = {};
|
||||
|
||||
/// ✨✨✨ 初始化命令管理器 ✨✨✨
|
||||
void _initialize() {
|
||||
AppLogger.highlight('✨✨✨ 🚀 初始化蓝牙命令管理器 ✨✨✨');
|
||||
|
||||
// 注册所有命令解析器
|
||||
_registerParsers();
|
||||
|
||||
AppLogger.info('📋 已注册命令解析器数量: ${_parsers.length}');
|
||||
_parsers.forEach((commandId, parser) {
|
||||
AppLogger.debug(' • 0x${commandId.toRadixString(16).padLeft(4, '0')}: ${parser.commandName}');
|
||||
});
|
||||
|
||||
AppLogger.highlight('✨✨✨ ✅ 蓝牙命令管理器初始化完成 ✨✨✨');
|
||||
}
|
||||
|
||||
/// ✨✨✨ 注册所有命令解析器 ✨✨✨
|
||||
void _registerParsers() {
|
||||
// 注册获取公钥命令解析器
|
||||
registerParser(BleCmdGetPublicKeyParser());
|
||||
registerParser(BleCmdGetPrivateKeyParser());
|
||||
registerParser(BleCmdReadLockStatusParser());
|
||||
registerParser(BleCmdAddAdminParser());
|
||||
|
||||
// TODO: 在这里注册其他命令解析器
|
||||
// registerParser(BleCmdSetPasswordParser());
|
||||
// registerParser(BleCmdUnlockParser());
|
||||
// 等等...
|
||||
}
|
||||
|
||||
/// ✨✨✨ 注册命令解析器 ✨✨✨
|
||||
void registerParser(BaseBleResponseParser parser) {
|
||||
_parsers[parser.commandId] = parser;
|
||||
AppLogger.debug('📝 注册命令解析器: 0x${parser.commandId.toRadixString(16).padLeft(4, '0')} - ${parser.commandName}');
|
||||
}
|
||||
|
||||
/// ✨✨✨ 移除命令解析器 ✨✨✨
|
||||
void unregisterParser(int commandId) {
|
||||
BaseBleResponseParser? removed = _parsers.remove(commandId);
|
||||
if (removed != null) {
|
||||
AppLogger.debug('🗑️ 移除命令解析器: 0x${commandId.toRadixString(16).padLeft(4, '0')} - ${removed.commandName}');
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 处理接收到的应答数据包 ✨✨✨
|
||||
/// [rawPacketData] 完整的原始数据包
|
||||
/// 返回解析后的业务数据,如果没有匹配的解析器则返回null
|
||||
dynamic handleResponse(List<int> rawPacketData) {
|
||||
AppLogger.debug('📊 收到蓝牙数据 (${rawPacketData.length}字节): ${rawPacketData.map((b) => '0x${b.toRadixString(16).padLeft(2, '0')}').join(' ')}');
|
||||
|
||||
try {
|
||||
// ✨✨✨ 首先解析数据包以获取命令ID ✨✨✨
|
||||
var parsedPacket = BaseBleCommand.parsePacket(rawPacketData);
|
||||
|
||||
// 检查数据包是否有效
|
||||
if (!parsedPacket.isValid) {
|
||||
AppLogger.warn('⚠️ 数据包无效: ${parsedPacket.errorMessage}');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查数据长度是否足够包含命令ID
|
||||
if (parsedPacket.data.length < 2) {
|
||||
AppLogger.warn('⚠️ 应答数据长度不足,无法包含命令ID: ${parsedPacket.data.length}字节');
|
||||
return null;
|
||||
}
|
||||
|
||||
// ✨✨✨ 根据加密类型进行解密处理 ✨✨✨
|
||||
List<int> processedData = _decryptDataIfNeededSync(parsedPacket);
|
||||
|
||||
// ✨✨✨ 提取命令ID (大端序) ✨✨✨
|
||||
int commandId = (processedData[0] << 8) | processedData[1];
|
||||
AppLogger.debug('🔍 提取命令ID: 0x${commandId.toRadixString(16).padLeft(4, '0')}');
|
||||
|
||||
// ✨✨✨ 直接通过命令ID查找对应的解析器 ✨✨✨
|
||||
BaseBleResponseParser? parser = _parsers[commandId];
|
||||
if (parser != null) {
|
||||
AppLogger.debug('🎯 找到匹配的解析器: ${parser.commandName}');
|
||||
|
||||
// 创建一个新的ParsedPacket对象,使用解密后的数据
|
||||
ParsedPacket decryptedPacket = ParsedPacket(
|
||||
isValid: parsedPacket.isValid,
|
||||
packetType: parsedPacket.packetType,
|
||||
packetSequence: parsedPacket.packetSequence,
|
||||
version: parsedPacket.version,
|
||||
encryptType: parsedPacket.encryptType,
|
||||
encryptedDataLength: parsedPacket.encryptedDataLength,
|
||||
originalDataLength: parsedPacket.originalDataLength,
|
||||
data: processedData,
|
||||
crc16: parsedPacket.crc16,
|
||||
errorMessage: parsedPacket.errorMessage,
|
||||
);
|
||||
|
||||
// 传递解密后的数据给解析器
|
||||
dynamic result = parser.parseResponse(decryptedPacket, processedData);
|
||||
return result;
|
||||
} else {
|
||||
// 没有找到匹配的解析器
|
||||
AppLogger.warn('⚠️ 未找到命令ID 0x${commandId.toRadixString(16).padLeft(4, '0')} 对应的解析器');
|
||||
_logUnknownPacket(rawPacketData);
|
||||
return null;
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
AppLogger.error('❌ 处理蓝牙应答异常', error: e, stackTrace: stackTrace);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 根据加密类型解密数据(同步版本) ✨✨✨
|
||||
/// [parsedPacket] 已解析的数据包
|
||||
/// 返回解密后的数据或原始数据(如果不需要解密)
|
||||
List<int> _decryptDataIfNeededSync(ParsedPacket parsedPacket) {
|
||||
// 如果是明文,直接返回原始数据
|
||||
if (parsedPacket.encryptType == BaseBleCommand.ENCRYPT_TYPE_PLAIN) {
|
||||
AppLogger.debug('🔓 数据未加密,直接使用原始数据');
|
||||
return parsedPacket.data;
|
||||
}
|
||||
|
||||
switch (parsedPacket.encryptType) {
|
||||
case BaseBleCommand.ENCRYPT_TYPE_AES128:
|
||||
return parsedPacket.data;
|
||||
case BaseBleCommand.ENCRYPT_TYPE_SM4_PRESET:
|
||||
var connectedDevice = BleService().connectedDevice;
|
||||
var platformName = connectedDevice?.platformName ?? '';
|
||||
if (platformName.isEmpty) {
|
||||
AppLogger.warn('⚠️ 解密数据时未找到设备名称');
|
||||
return parsedPacket.data;
|
||||
}
|
||||
var decrypt = SM4.decrypt(
|
||||
parsedPacket.data,
|
||||
key: utf8.encode(platformName),
|
||||
mode: SM4CryptoMode.ECB,
|
||||
);
|
||||
return decrypt;
|
||||
case BaseBleCommand.ENCRYPT_TYPE_SM4_DEVICE:
|
||||
// 使用缓存的私钥进行解密
|
||||
var cachedPrivateKey = BleService.cachedPrivateKey;
|
||||
if (cachedPrivateKey == null || cachedPrivateKey.isEmpty) {
|
||||
AppLogger.warn('⚠️ 解密数据时未找到缓存的私钥');
|
||||
return parsedPacket.data;
|
||||
}
|
||||
var decrypt = SM4.decrypt(
|
||||
parsedPacket.data,
|
||||
key: cachedPrivateKey,
|
||||
mode: SM4CryptoMode.ECB,
|
||||
);
|
||||
return decrypt;
|
||||
default:
|
||||
AppLogger.warn('⚠️ 未知的加密类型: ${parsedPacket.encryptType},使用原始数据');
|
||||
return parsedPacket.data;
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 根据加密类型解密数据 ✨✨✨
|
||||
/// [parsedPacket] 已解析的数据包
|
||||
/// 返回解密后的数据或原始数据(如果不需要解密)
|
||||
Future<List<int>> _decryptDataIfNeeded(ParsedPacket parsedPacket) async {
|
||||
// 如果是明文,直接返回原始数据
|
||||
if (parsedPacket.encryptType == BaseBleCommand.ENCRYPT_TYPE_PLAIN) {
|
||||
AppLogger.debug('🔓 数据未加密,直接使用原始数据');
|
||||
return parsedPacket.data;
|
||||
}
|
||||
|
||||
switch (parsedPacket.encryptType) {
|
||||
case BaseBleCommand.ENCRYPT_TYPE_AES128:
|
||||
return parsedPacket.data;
|
||||
case BaseBleCommand.ENCRYPT_TYPE_SM4_PRESET:
|
||||
var connectedDevice = BleService().connectedDevice;
|
||||
var platformName = connectedDevice?.platformName;
|
||||
var decrypt = SM4.decrypt(
|
||||
parsedPacket.data,
|
||||
key: utf8.encode(platformName!),
|
||||
mode: SM4CryptoMode.ECB,
|
||||
);
|
||||
return decrypt;
|
||||
case BaseBleCommand.ENCRYPT_TYPE_SM4_DEVICE:
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 根据命令ID获取解析器 ✨✨✨
|
||||
BaseBleResponseParser? getParser(int commandId) {
|
||||
return _parsers[commandId];
|
||||
}
|
||||
|
||||
/// ✨✨✨ 获取所有已注册的命令ID ✨✨✨
|
||||
List<int> getRegisteredCommandIds() {
|
||||
return _parsers.keys.toList();
|
||||
}
|
||||
|
||||
/// ✨✨✨ 获取所有已注册的解析器信息 ✨✨✨
|
||||
Map<int, String> getRegisteredParsersInfo() {
|
||||
return _parsers.map((commandId, parser) => MapEntry(commandId, parser.commandName));
|
||||
}
|
||||
|
||||
/// ✨✨✨ 记录未知数据包信息 ✨✨✨
|
||||
void _logUnknownPacket(List<int> rawPacketData) {
|
||||
try {
|
||||
// 尝试基本解析以获取更多信息
|
||||
var parsedPacket = BaseBleCommand.parsePacket(rawPacketData);
|
||||
|
||||
if (parsedPacket.isValid && parsedPacket.data.length >= 2) {
|
||||
int commandId = (parsedPacket.data[0] << 8) | parsedPacket.data[1];
|
||||
AppLogger.warn('🔍 未知命令应答: 命令ID=0x${commandId.toRadixString(16).padLeft(4, '0')}, ' +
|
||||
'包类型=0x${parsedPacket.packetType.toRadixString(16).padLeft(2, '0')}, ' +
|
||||
'数据长度=${parsedPacket.data.length}字节');
|
||||
} else {
|
||||
AppLogger.warn('🔍 无效或无法识别的数据包');
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.warn('🔍 无法解析的数据包: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 清除所有解析器 ✨✨✨
|
||||
void clear() {
|
||||
AppLogger.info('🧹 清除所有命令解析器');
|
||||
_parsers.clear();
|
||||
}
|
||||
|
||||
/// ✨✨✨ 重新初始化 ✨✨✨
|
||||
void reinitialize() {
|
||||
AppLogger.info('🔄 重新初始化命令管理器');
|
||||
clear();
|
||||
_initialize();
|
||||
}
|
||||
}
|
||||
@ -1,230 +0,0 @@
|
||||
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/model/ble_add_admin_response.dart';
|
||||
import 'package:starwork_flutter/ble/constant/lock_ble_constant.dart';
|
||||
import 'package:starwork_flutter/common/sm4_encipher/sm4.dart';
|
||||
|
||||
/// ✨✨✨ 增加超级管理员命令类 - 继承BaseBleCommand ✨✨✨
|
||||
class BleCmdAddAdmin extends BaseBleCommand<BleAddAdminResponse> {
|
||||
final String lockId;
|
||||
final String authUserId;
|
||||
final String keyId;
|
||||
final String userId;
|
||||
final int openMode;
|
||||
final int keyType;
|
||||
final int startDate;
|
||||
final int expireDate;
|
||||
final int useCountLimit;
|
||||
final int isRound;
|
||||
final int weekRound;
|
||||
final int startHour;
|
||||
final int startMin;
|
||||
final int endHour;
|
||||
final int endMin;
|
||||
final int role;
|
||||
final String password;
|
||||
final List<int> token;
|
||||
final List<int> publicKey;
|
||||
final List<int> privateKey;
|
||||
final int _encryptType;
|
||||
|
||||
/// 指令 ID: 0x3001
|
||||
static const int cmdId = 0x3001;
|
||||
|
||||
/// 构造函数
|
||||
BleCmdAddAdmin({
|
||||
int encryptType = BaseBleCommand.ENCRYPT_TYPE_SM4_DEVICE,
|
||||
required this.lockId,
|
||||
required this.authUserId,
|
||||
required this.keyId,
|
||||
required this.userId,
|
||||
this.openMode = 1, // 缺少定义,默认传1
|
||||
this.keyType = LockBleConstant.keyTypeTemporary,
|
||||
required this.startDate,
|
||||
required this.expireDate,
|
||||
this.useCountLimit = 0xFFFF,
|
||||
this.isRound = 0,
|
||||
this.weekRound = 0,
|
||||
this.startHour = 0,
|
||||
this.startMin = 0,
|
||||
this.endHour = 0,
|
||||
this.endMin = 0,
|
||||
this.role = 255,
|
||||
required this.password,
|
||||
required this.token,
|
||||
required this.publicKey,
|
||||
required this.privateKey,
|
||||
}) : _encryptType = encryptType {
|
||||
AppLogger.debug('🔑 BleCmdAddAdmin 创建: 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);
|
||||
|
||||
// lockId 40
|
||||
final int lockIDLength = utf8.encode(lockId).length;
|
||||
data.addAll(utf8.encode(lockId));
|
||||
data = getFixedLengthList(data, 40 - lockIDLength);
|
||||
|
||||
// authUserId 20
|
||||
final int authUserIdLength = utf8.encode(authUserId).length;
|
||||
data.addAll(utf8.encode(authUserId));
|
||||
data = getFixedLengthList(data, 20 - authUserIdLength);
|
||||
|
||||
//KeyID 40
|
||||
final int keyIDLength = utf8.encode(keyId).length;
|
||||
data.addAll(utf8.encode(keyId));
|
||||
data = getFixedLengthList(data, 40 - keyIDLength);
|
||||
|
||||
//userId 20
|
||||
final int userIdLength = utf8.encode(userId).length;
|
||||
data.addAll(utf8.encode(userId));
|
||||
data = getFixedLengthList(data, 20 - userIdLength);
|
||||
|
||||
// openModel 1
|
||||
data.add(openMode);
|
||||
|
||||
// keyType 1
|
||||
data.add(keyType);
|
||||
|
||||
int? d1, d2;
|
||||
if (role == LockBleConstant.roleSuperAdmin) {
|
||||
d1 = 0;
|
||||
d2 = 0xffffffff;
|
||||
} else {
|
||||
d1 = startDate;
|
||||
d2 = expireDate;
|
||||
}
|
||||
|
||||
// StartDate 4
|
||||
data.add((d1 & 0xff000000) >> 24);
|
||||
data.add((d1 & 0xff0000) >> 16);
|
||||
data.add((d1 & 0xff00) >> 8);
|
||||
data.add(d1 & 0xff);
|
||||
|
||||
// expireDate 4
|
||||
data.add((d2 & 0xff000000) >> 24);
|
||||
data.add((d2 & 0xff0000) >> 16);
|
||||
data.add((d2 & 0xff00) >> 8);
|
||||
data.add(d2 & 0xff);
|
||||
|
||||
//useCountLimit 2
|
||||
final double useCountLimitDouble = useCountLimit / 256;
|
||||
final int useCountLimit1 = useCountLimitDouble.toInt();
|
||||
final int useCountLimit2 = useCountLimit % 256;
|
||||
data.add(useCountLimit1);
|
||||
data.add(useCountLimit2);
|
||||
|
||||
data.add(isRound);
|
||||
data.add(weekRound);
|
||||
data.add(startHour);
|
||||
data.add(startMin);
|
||||
data.add(endHour);
|
||||
data.add(endMin);
|
||||
|
||||
// role 1
|
||||
data.add(role);
|
||||
|
||||
//password 20
|
||||
final int passwordLength = utf8.encode(password).length;
|
||||
data.addAll(utf8.encode(password));
|
||||
data = getFixedLengthList(data, 20 - passwordLength);
|
||||
|
||||
// token 长度4 首次请求 Token 填 0,如果锁需要鉴权 操作者身份,则会分配动态口令并在应答消息中返回,二次请求时带上。 当token失效或者第一次发送的时候token为0
|
||||
data.addAll(token);
|
||||
|
||||
final List<int> authCodeData = <int>[];
|
||||
authCodeData.addAll(utf8.encode(authUserId));
|
||||
authCodeData.addAll(utf8.encode(keyId));
|
||||
authCodeData.addAll(token);
|
||||
authCodeData.addAll(publicKey);
|
||||
|
||||
// 把authUserID、KeyID、token、公钥通过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);
|
||||
}
|
||||
}
|
||||
AppLogger.debug("完整明文数据: $data");
|
||||
|
||||
// 拿到数据之后通过privateKey进行SM4 ECB加密
|
||||
ebcData = SM4.encrypt(data, key: privateKey, mode: SM4CryptoMode.ECB);
|
||||
return ebcData;
|
||||
}
|
||||
|
||||
/// ✨✨✨ 检查是否需要重发 ✨✨✨
|
||||
@override
|
||||
bool shouldRetry(dynamic response) {
|
||||
// 检查响应是否为BleAddAdminResponse类型且状态码为6(需要token)
|
||||
if (response is BleAddAdminResponse) {
|
||||
return response.statusCode == 0x06; // 需要token
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// ✨✨✨ 创建重发命令实例 ✨✨✨
|
||||
@override
|
||||
BaseBleCommand<BleAddAdminResponse> createRetryCommand(dynamic response) {
|
||||
if (response is BleAddAdminResponse) {
|
||||
// 当状态码为0x06(需要token)时,响应中的token字段是有效的,应该使用这个token
|
||||
List<int> token = response.token;
|
||||
AppLogger.debug('🔐 获取到token用于重发: $token');
|
||||
|
||||
// 创建新的命令实例,携带token
|
||||
return BleCmdAddAdmin(
|
||||
encryptType: this.getEncryptType(),
|
||||
lockId: this.lockId,
|
||||
authUserId: this.authUserId,
|
||||
keyId: this.keyId,
|
||||
userId: this.userId,
|
||||
openMode: this.openMode,
|
||||
keyType: this.keyType,
|
||||
startDate: this.startDate,
|
||||
expireDate: this.expireDate,
|
||||
useCountLimit: this.useCountLimit,
|
||||
isRound: this.isRound,
|
||||
weekRound: this.weekRound,
|
||||
startHour: this.startHour,
|
||||
startMin: this.startMin,
|
||||
endHour: this.endHour,
|
||||
endMin: this.endMin,
|
||||
role: this.role,
|
||||
password: this.password,
|
||||
token: token,
|
||||
// 使用从响应中获取的有效token
|
||||
publicKey: this.publicKey,
|
||||
privateKey: this.privateKey,
|
||||
);
|
||||
}
|
||||
|
||||
// 如果无法创建重发命令,抛出异常
|
||||
throw Exception('无法为响应创建重发命令: $response');
|
||||
}
|
||||
}
|
||||
@ -1,113 +0,0 @@
|
||||
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 BleCmdGetPrivateKey extends BaseBleCommand<GetPrivateKeyResponse> {
|
||||
final String lockId;
|
||||
final String keyId;
|
||||
final String authUserID;
|
||||
final List<int> publicKey;
|
||||
final int nowTime;
|
||||
final int _encryptType; // 私有字段存储加密类型
|
||||
|
||||
/// 指令 ID: 0x3091
|
||||
static const int cmdId = 0x3091;
|
||||
|
||||
/// 构造函数
|
||||
BleCmdGetPrivateKey({
|
||||
int encryptType = BaseBleCommand.ENCRYPT_TYPE_SM4_PRESET,
|
||||
required this.lockId,
|
||||
required this.keyId,
|
||||
required this.authUserID,
|
||||
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('🔑 BleCmdGetPrivateKey 创建: 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);
|
||||
|
||||
//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;
|
||||
}
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
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_public_key_parser.dart';
|
||||
|
||||
/// ✨✨✨ 获取公钥命令类 - 继承BaseBleCommand ✨✨✨
|
||||
class BleCmdGetPublicKey extends BaseBleCommand<GetPublicKeyResponse> {
|
||||
final String lockId;
|
||||
final int _encryptType; // 私有字段存储加密类型
|
||||
|
||||
/// 指令 ID: 0x3090
|
||||
static const int cmdId = 0x3090;
|
||||
|
||||
/// 构造函数
|
||||
/// [lockId] 锁设备ID
|
||||
/// [encryptType] 加密类型,默认为明文
|
||||
BleCmdGetPublicKey({
|
||||
int encryptType = BaseBleCommand.ENCRYPT_TYPE_PLAIN,
|
||||
required this.lockId,
|
||||
}) : _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('🔑 BleCmdGetPublicKey 创建: 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); // 创建可修改的副本
|
||||
|
||||
// 检查是否需要填充到40字节
|
||||
if (lockIdBytes.length < 40) {
|
||||
// 用0填充到40字节
|
||||
int paddingNeeded = 40 - lockIdBytes.length;
|
||||
lockIdBytes.addAll(List.filled(paddingNeeded, 0));
|
||||
}
|
||||
|
||||
buffer.addAll(lockIdBytes);
|
||||
|
||||
if ((buffer.length % 16) != 0) {
|
||||
final int add = 16 - buffer.length % 16;
|
||||
for (int i = 0; i < add; i++) {
|
||||
buffer.add(0);
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,112 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:starwork_flutter/base/app_logger.dart';
|
||||
import 'package:starwork_flutter/ble/command/base/base_ble_command.dart';
|
||||
import 'package:starwork_flutter/ble/command/base/base_ble_response_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/request/ble_cmd_add_admin.dart';
|
||||
import 'package:starwork_flutter/ble/command/request/ble_cmd_get_public_key.dart';
|
||||
import 'package:starwork_flutter/ble/command/request/ble_cmd_read_lock_status.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/model/ble_add_admin_response.dart';
|
||||
|
||||
class BleCmdAddAdminParser extends BaseBleResponseParser {
|
||||
@override
|
||||
int get commandId => BleCmdAddAdmin.cmdId;
|
||||
|
||||
@override
|
||||
String get commandName => '增加超级管理员命令';
|
||||
|
||||
/// ✨✨✨ 解析获取增加超级管理员的应答数据 ✨✨✨
|
||||
@override
|
||||
BleAddAdminResponse parseResponse(ParsedPacket parsedPacket, List<int> rawResponseData) {
|
||||
try {
|
||||
if (rawResponseData.length < 3) {
|
||||
throw ArgumentError('应答数据长度不足: ${rawResponseData.length}字节 < 3字节');
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
|
||||
// 1. 提取命令ID (已在isMatchingResponse中验证过,这里记录日志)
|
||||
int responseCommandId = BaseBleResponseParser.extractInt(rawResponseData, offset, 2);
|
||||
offset += 2;
|
||||
|
||||
// 2. lockId
|
||||
List<int> lockId = BaseBleResponseParser.extractBytes(rawResponseData, offset, 40);
|
||||
String lockIdStr = _safeStringDecode(lockId).trim();
|
||||
offset += 40;
|
||||
|
||||
// 3. token
|
||||
List<int> token = BaseBleResponseParser.extractBytes(rawResponseData, offset, 4);
|
||||
offset += 4;
|
||||
|
||||
// 4. 提取状态码
|
||||
int statusCode = rawResponseData[offset++];
|
||||
|
||||
// 当状态码为0x06(需要token)时,token字段是有效的,应该返回这个token
|
||||
if (statusCode == 0x06) {
|
||||
return BleAddAdminResponse(
|
||||
commandId: commandId,
|
||||
statusCode: statusCode,
|
||||
token: token, // 状态码为0x06时返回有效的token
|
||||
lockId: lockIdStr,
|
||||
serialNo: '',
|
||||
no: -1,
|
||||
);
|
||||
}
|
||||
|
||||
// 当状态码为0时,表示成功,此时可能也有token(根据协议而定)
|
||||
if (statusCode == 0) {
|
||||
return BleAddAdminResponse(
|
||||
commandId: commandId,
|
||||
statusCode: statusCode,
|
||||
token: token, // 状态码为0时也返回token
|
||||
lockId: lockIdStr,
|
||||
serialNo: '',
|
||||
no: -1,
|
||||
);
|
||||
}
|
||||
|
||||
// 其他状态码情况
|
||||
return BleAddAdminResponse(
|
||||
commandId: commandId,
|
||||
statusCode: statusCode,
|
||||
token: [], // 其他状态码时返回空token
|
||||
lockId: lockIdStr,
|
||||
serialNo: '',
|
||||
no: -1,
|
||||
);
|
||||
} catch (e) {
|
||||
AppLogger.error('❌ 解析添加管理员应答数据异常', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 安全的字符串解码方法 ✨✨✨
|
||||
/// 处理可能包含非UTF-8字节的数据
|
||||
String _safeStringDecode(List<int> data) {
|
||||
// 1. 找到第一个 0x00(空字节)的位置,只取前面的有效部分
|
||||
int endIndex = 0;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
if (data[i] == 0) {
|
||||
break;
|
||||
}
|
||||
endIndex = i + 1;
|
||||
}
|
||||
|
||||
// 2. 截取有效字节
|
||||
List<int> validBytes = data.sublist(0, endIndex);
|
||||
|
||||
try {
|
||||
// 3. 使用 UTF-8 解码(允许畸形,但会替换为 )
|
||||
return utf8.decode(validBytes, allowMalformed: true).trim();
|
||||
} catch (e) {
|
||||
// 4. 极端情况:用字符码直接构造(基本不会走到这里)
|
||||
return String.fromCharCodes(validBytes).trim();
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 静态便捷方法 - 直接解析原始数据包 ✨✨✨
|
||||
static BleAddAdminResponse? parseRawPacket(List<int> rawPacketData) {
|
||||
BleCmdAddAdminParser parser = BleCmdAddAdminParser();
|
||||
return parser.handleResponse(rawPacketData) as BleAddAdminResponse?;
|
||||
}
|
||||
}
|
||||
@ -1,145 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
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_private_key.dart';
|
||||
import 'package:starwork_flutter/ble/command/request/ble_cmd_get_public_key.dart';
|
||||
|
||||
/// ✨✨✨ 获取私钥命令应答数据结构 ✨✨✨
|
||||
class GetPrivateKeyResponse {
|
||||
final int commandId;
|
||||
final int statusCode;
|
||||
final List<int> commKey;
|
||||
final String commKeyHex;
|
||||
final List<int> signKey;
|
||||
final String signKeyHex;
|
||||
final int randPassTick;
|
||||
|
||||
GetPrivateKeyResponse({
|
||||
required this.commandId,
|
||||
required this.statusCode,
|
||||
required this.commKey,
|
||||
required this.commKeyHex,
|
||||
required this.signKey,
|
||||
required this.signKeyHex,
|
||||
required this.randPassTick,
|
||||
});
|
||||
|
||||
/// 是否成功
|
||||
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 'GetPrivateKeyResponse{commandId: $commandId, statusCode: $statusCode, commKey: $commKey, commKeyHex: $commKeyHex, signKey: $signKey, signKeyHex: $signKeyHex, randPassTick: $randPassTick}';
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 获取私钥命令应答解析器 ✨✨✨
|
||||
class BleCmdGetPrivateKeyParser extends BaseBleResponseParser {
|
||||
@override
|
||||
int get commandId => BleCmdGetPrivateKey.cmdId;
|
||||
|
||||
@override
|
||||
String get commandName => '获取私钥命令';
|
||||
|
||||
/// ✨✨✨ 解析获取私钥命令的应答数据 ✨✨✨
|
||||
@override
|
||||
GetPrivateKeyResponse 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) {
|
||||
List<int> commKey = [];
|
||||
if (offset < rawResponseData.length) {
|
||||
commKey = BaseBleResponseParser.extractBytes(rawResponseData, offset, 16);
|
||||
}
|
||||
offset += 16;
|
||||
String commKeyHex = BaseBleResponseParser.bytesToHex(commKey, separator: '');
|
||||
|
||||
List<int> signKey = [];
|
||||
if (offset < rawResponseData.length) {
|
||||
signKey = BaseBleResponseParser.extractBytes(rawResponseData, offset, 16);
|
||||
}
|
||||
offset += 16;
|
||||
String signKeyHex = BaseBleResponseParser.bytesToHex(signKey, separator: '');
|
||||
|
||||
List<int> randPassTick = [];
|
||||
if (offset < rawResponseData.length) {
|
||||
randPassTick = BaseBleResponseParser.extractBytes(rawResponseData, offset, rawResponseData.length - offset);
|
||||
}
|
||||
|
||||
final buffer = Uint8List.fromList(randPassTick).buffer;
|
||||
final data = ByteData.view(buffer);
|
||||
|
||||
// 4. 创建应答对象
|
||||
GetPrivateKeyResponse response = GetPrivateKeyResponse(
|
||||
commandId: responseCommandId,
|
||||
statusCode: statusCode,
|
||||
commKey: commKey,
|
||||
commKeyHex: commKeyHex,
|
||||
signKey: signKey,
|
||||
signKeyHex: signKeyHex,
|
||||
randPassTick: data.getInt64(0, Endian.little),
|
||||
);
|
||||
|
||||
if (response.isSuccess) {
|
||||
AppLogger.highlight('✨✨✨ ✅ 获取私钥成功,私钥长度: ${commKey.length}字节 ✨✨✨');
|
||||
} else {
|
||||
AppLogger.warn('⚠️ 获取私钥失败: ${response.statusDescription}');
|
||||
}
|
||||
|
||||
return response;
|
||||
} else {
|
||||
AppLogger.warn('⚠️ 获取私钥失败: ${statusCode}');
|
||||
return GetPrivateKeyResponse(
|
||||
commandId: responseCommandId,
|
||||
statusCode: statusCode,
|
||||
commKey: [],
|
||||
commKeyHex: '',
|
||||
signKey: [],
|
||||
signKeyHex: '',
|
||||
randPassTick: -1,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.error('❌ 解析获取私钥应答数据异常', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 静态便捷方法 - 直接解析原始数据包 ✨✨✨
|
||||
static GetPrivateKeyResponse? parseRawPacket(List<int> rawPacketData) {
|
||||
BleCmdGetPrivateKeyParser parser = BleCmdGetPrivateKeyParser();
|
||||
return parser.handleResponse(rawPacketData) as GetPrivateKeyResponse?;
|
||||
}
|
||||
}
|
||||
@ -1,131 +0,0 @@
|
||||
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 GetPublicKeyResponse {
|
||||
final int commandId;
|
||||
final int statusCode;
|
||||
final List<int> publicKey;
|
||||
final String publicKeyHex;
|
||||
|
||||
GetPublicKeyResponse({
|
||||
required this.commandId,
|
||||
required this.statusCode,
|
||||
required this.publicKey,
|
||||
required this.publicKeyHex,
|
||||
});
|
||||
|
||||
/// 是否成功
|
||||
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 'GetPublicKeyResponse(commandId: 0x${commandId.toRadixString(16).padLeft(4, '0')}, ' +
|
||||
'status: $statusDescription, publicKeyLength: ${publicKey.length}, ' +
|
||||
'publicKey: ${publicKeyHex.substring(0, publicKeyHex.length > 32 ? 32 : publicKeyHex.length)}...)';
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 获取公钥命令应答解析器 ✨✨✨
|
||||
class BleCmdGetPublicKeyParser extends BaseBleResponseParser {
|
||||
@override
|
||||
int get commandId => BleCmdGetPublicKey.cmdId; // 0x3090
|
||||
|
||||
@override
|
||||
String get commandName => '获取公钥命令';
|
||||
|
||||
/// ✨✨✨ 解析获取公钥命令的应答数据 ✨✨✨
|
||||
@override
|
||||
GetPublicKeyResponse parseResponse(ParsedPacket parsedPacket, List<int> rawResponseData) {
|
||||
try {
|
||||
// 数据包格式分析:
|
||||
// 字节 0-1: 命令ID (0x3090, 大端序)
|
||||
// 字节 2: 状态码 (0x00=成功, 其他=失败)
|
||||
// 字节 3-N: 公钥数据 (长度可变)
|
||||
|
||||
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) {
|
||||
List<int> publicKey = [];
|
||||
if (offset < rawResponseData.length) {
|
||||
publicKey = BaseBleResponseParser.extractBytes(rawResponseData, offset, rawResponseData.length - offset);
|
||||
}
|
||||
|
||||
String publicKeyHex = BaseBleResponseParser.bytesToHex(publicKey, separator: '');
|
||||
|
||||
// 4. 创建应答对象
|
||||
GetPublicKeyResponse response = GetPublicKeyResponse(
|
||||
commandId: responseCommandId,
|
||||
statusCode: statusCode,
|
||||
publicKey: publicKey,
|
||||
publicKeyHex: publicKeyHex,
|
||||
);
|
||||
|
||||
return response;
|
||||
} else {
|
||||
return GetPublicKeyResponse(
|
||||
commandId: responseCommandId,
|
||||
statusCode: statusCode,
|
||||
publicKey: [],
|
||||
publicKeyHex: '',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.error('❌ 解析获取公钥应答数据异常', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取状态码描述
|
||||
String _getStatusDescription(int statusCode) {
|
||||
switch (statusCode) {
|
||||
case 0x00:
|
||||
return '成功';
|
||||
case 0x01:
|
||||
return '失败';
|
||||
case 0x02:
|
||||
return '参数错误';
|
||||
case 0x03:
|
||||
return '设备忙';
|
||||
default:
|
||||
return '未知状态';
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 静态便捷方法 - 直接解析原始数据包 ✨✨✨
|
||||
static GetPublicKeyResponse? parseRawPacket(List<int> rawPacketData) {
|
||||
BleCmdGetPublicKeyParser parser = BleCmdGetPublicKeyParser();
|
||||
return parser.handleResponse(rawPacketData) as GetPublicKeyResponse?;
|
||||
}
|
||||
}
|
||||
@ -1,299 +0,0 @@
|
||||
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';
|
||||
import 'package:starwork_flutter/ble/command/request/ble_cmd_read_lock_status.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; // 特征值字符串长度
|
||||
final List<int> featureValue; // 特征值
|
||||
final String featureValueHexStr; // 特征值16进制字符串
|
||||
final int featureEnValLength; // 启用功能长度
|
||||
final List<int> featureEnVal; // 启用功能
|
||||
final String featureEnValHexStr; // 启用功能16进制字符串
|
||||
final int featureParaTotal; // 支持的带参数特征值的总条目数
|
||||
final List<int> featureParaData; // 对应特征值的参数,从FeatureParaTotal开始,按照协议文档循环
|
||||
|
||||
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,
|
||||
required this.featureValue,
|
||||
required this.featureValueHexStr,
|
||||
required this.featureEnValLength,
|
||||
required this.featureEnVal,
|
||||
required this.featureEnValHexStr,
|
||||
required this.featureParaTotal,
|
||||
required this.featureParaData,
|
||||
});
|
||||
|
||||
/// 是否成功
|
||||
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,'
|
||||
'featureValueHexStr: $featureValueHexStr,'
|
||||
'featureEnValLength: $featureEnValLength,'
|
||||
'featureEnValHexStr: $featureEnValHexStr,'
|
||||
'featureParaTotal: $featureParaTotal,'
|
||||
'featureParaData: $featureParaData'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 读取锁状态命令应答解析器 ✨✨✨
|
||||
class BleCmdReadLockStatusParser extends BaseBleResponseParser {
|
||||
@override
|
||||
int get commandId => BleCmdReadLockStatus.cmdId;
|
||||
|
||||
@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,
|
||||
featureValue: [],
|
||||
featureEnValLength: 0,
|
||||
featureValueHexStr: '',
|
||||
featureEnVal: [],
|
||||
featureEnValHexStr: '',
|
||||
featureParaTotal: -1,
|
||||
featureParaData: [],
|
||||
);
|
||||
}
|
||||
|
||||
// 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++];
|
||||
|
||||
// 19. 锁特征值说明(本机能支持的功能)
|
||||
List<int> featureValue = BaseBleResponseParser.extractBytes(rawResponseData, offset, featureValueLength);
|
||||
String featureValueStr = asciiString(featureValue);
|
||||
offset += featureValueLength;
|
||||
|
||||
// 20. 启用功能长度
|
||||
int featureEnValLength = rawResponseData[offset++];
|
||||
|
||||
// 21. 启用功能说明(本机启用的功能)
|
||||
List<int> featureEnVal = BaseBleResponseParser.extractBytes(rawResponseData, offset, featureEnValLength);
|
||||
String featureEnValStr = asciiString(featureValue);
|
||||
offset += featureEnValLength;
|
||||
|
||||
// 22. 支持的带参数特征值的总条目数
|
||||
int featureParaTotal = rawResponseData[offset++];
|
||||
|
||||
// 23. 支持特征值的参数设置
|
||||
List<int> featureParaData = rawResponseData.sublist(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,
|
||||
featureValue: featureValue,
|
||||
featureValueHexStr: featureValueStr,
|
||||
featureEnValLength: featureEnValLength,
|
||||
featureEnVal: featureEnVal,
|
||||
featureEnValHexStr: featureEnValStr,
|
||||
featureParaTotal: featureParaTotal,
|
||||
featureParaData: featureParaData,
|
||||
);
|
||||
} catch (e) {
|
||||
AppLogger.error('❌ 解析读取锁状态答数据异常', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// ✨✨✨ 静态便捷方法 - 直接解析原始数据包 ✨✨✨
|
||||
static ReadLockStatusResponse? parseRawPacket(List<int> rawPacketData) {
|
||||
BleCmdReadLockStatusParser parser = BleCmdReadLockStatusParser();
|
||||
return parser.handleResponse(rawPacketData) as ReadLockStatusResponse?;
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
class BleAddAdminResponse {
|
||||
final int commandId;
|
||||
final int statusCode;
|
||||
final int no;
|
||||
final String serialNo;
|
||||
final String lockId;
|
||||
final List<int> token;
|
||||
|
||||
BleAddAdminResponse({
|
||||
required this.commandId,
|
||||
required this.statusCode,
|
||||
required this.token,
|
||||
required this.lockId,
|
||||
required this.serialNo,
|
||||
required this.no,
|
||||
});
|
||||
|
||||
/// 是否成功
|
||||
bool get isSuccess => statusCode == 0x00;
|
||||
|
||||
/// 状态描述
|
||||
String get statusDescription {
|
||||
switch (statusCode) {
|
||||
case 0x00:
|
||||
return '成功';
|
||||
case 0x01:
|
||||
return '失败';
|
||||
case 0x02:
|
||||
return '参数错误';
|
||||
case 0x03:
|
||||
return '设备忙';
|
||||
case 0x06:
|
||||
return '需要token';
|
||||
default:
|
||||
return '未知状态(0x${statusCode.toRadixString(16).padLeft(2, '0')})';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BleAddAdminResponse{commandId: $commandId, statusCode: $statusCode, no: $no, serialNo: $serialNo, lockId: $lockId, token: $token}';
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
class LockBleConstant {
|
||||
/// KeyType: 钥匙类型
|
||||
/// 0: 普通钥匙
|
||||
/// 1: 临时钥匙(到期自动删除)
|
||||
/// 2: 蓝牙遥控器
|
||||
/// 255: 测试钥匙(添加正式钥匙后自动失效)
|
||||
static const int keyTypeNormal = 0;
|
||||
static const int keyTypeTemporary = 1;
|
||||
static const int keyTypeBluetoothRemote = 2;
|
||||
static const int keyTypeTest = 255;
|
||||
|
||||
/// Role: 用户角色
|
||||
/// 0: 普通用户
|
||||
/// 1: 管理员
|
||||
/// 255 (0xFF): 超级管理员
|
||||
static const int roleNormalUser = 0;
|
||||
static const int roleAdmin = 1;
|
||||
static const int roleSuperAdmin = 255; // 0xFF
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
class BleConnectedException implements Exception{
|
||||
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
|
||||
class ScanDeviceInfo {
|
||||
//设备名字
|
||||
final String advName;
|
||||
|
||||
// 是否绑定
|
||||
final bool isBinding;
|
||||
// 是否有新事件
|
||||
final bool hasNewEvent;
|
||||
|
||||
// 原始设备信息
|
||||
final ScanResult rawDeviceInfo;
|
||||
|
||||
ScanDeviceInfo({
|
||||
required this.advName,
|
||||
this.isBinding = false, // 默认值为 false
|
||||
this.hasNewEvent = false, // 默认值为 false
|
||||
required this.rawDeviceInfo,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ScanDeviceInfo{advName: $advName, isBinding: $isBinding, hasNewEvent: $hasNewEvent,rawDeviceInfo:$rawDeviceInfo}';
|
||||
}
|
||||
}
|
||||
@ -9,17 +9,6 @@ import 'package:starcloud/sdk/starcloud.dart';
|
||||
import 'package:starwork_flutter/base/app_logger.dart';
|
||||
import 'package:starwork_flutter/base/app_permission.dart';
|
||||
import 'package:starwork_flutter/base/base_controller.dart';
|
||||
import 'package:starwork_flutter/ble/ble_service.dart';
|
||||
import 'package:starwork_flutter/ble/command/base/base_ble_response_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/request/ble_cmd_add_admin.dart';
|
||||
import 'package:starwork_flutter/ble/command/request/ble_cmd_get_private_key.dart';
|
||||
import 'package:starwork_flutter/ble/command/request/ble_cmd_get_public_key.dart';
|
||||
import 'package:starwork_flutter/ble/command/request/ble_cmd_read_lock_status.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/ble_cmd_get_private_key_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/ble_cmd_get_public_key_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/ble_cmd_read_lock_status_parser.dart';
|
||||
import 'package:starwork_flutter/ble/command/response/model/ble_add_admin_response.dart';
|
||||
import 'package:starwork_flutter/ble/model/scan_device_info.dart';
|
||||
import 'package:starwork_flutter/common/constant/app_toast_messages.dart';
|
||||
import 'package:starwork_flutter/common/constant/cache_keys.dart';
|
||||
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
|
||||
|
||||
@ -4,7 +4,6 @@ import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:starcloud/entity/star_cloud_scan_result.dart';
|
||||
import 'package:starwork_flutter/ble/model/scan_device_info.dart';
|
||||
import 'package:starwork_flutter/views/device/searchDevice/search_device_controller.dart';
|
||||
|
||||
class SearchDeviceView extends GetView<SearchDeviceController> {
|
||||
@ -173,7 +172,6 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
||||
}
|
||||
|
||||
_buildItem({required StarCloudScanResult device, required int index}) {
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
controller.connectingDevices(device);
|
||||
|
||||
@ -4,8 +4,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:starwork_flutter/api/model/team/request/change_current_team_request.dart';
|
||||
import 'package:starwork_flutter/api/model/team/response/team_info_response.dart';
|
||||
import 'package:starwork_flutter/api/service/team_api_service.dart';
|
||||
import 'package:starwork_flutter/base/app_logger.dart';
|
||||
import 'package:starwork_flutter/base/base_controller.dart';
|
||||
import 'package:starwork_flutter/common/constant/app_images.dart';
|
||||
import 'package:starwork_flutter/routes/app_routes.dart';
|
||||
@ -115,4 +117,21 @@ class MainController extends BaseController {
|
||||
selectedTeam.value = myTeamList.first;
|
||||
}
|
||||
}
|
||||
|
||||
/// 切换当前团队
|
||||
void requestChangeCurrentTeam(TeamInfoResponse teamInfo) async {
|
||||
var teamNo = teamInfo.teamNo;
|
||||
if (teamNo == null) {
|
||||
return;
|
||||
}
|
||||
var changeCurrentTeamResponse = await teamApi.requestChangeCurrentTeam(
|
||||
request: ChangeCurrentTeamRequest(
|
||||
teamNo: teamNo,
|
||||
),
|
||||
);
|
||||
if (changeCurrentTeamResponse.isSuccess) {
|
||||
AppLogger.highlight('切换当前团队成功');
|
||||
selectedTeam.value = teamInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,8 +38,8 @@ class MainView extends GetView<MainController> {
|
||||
teamList: controller.myTeamList,
|
||||
selectedTeam: controller.selectedTeam.value,
|
||||
onTeamSelected: (team) {
|
||||
controller.selectedTeam.value = team;
|
||||
AppLogger.highlight('message:${team}');
|
||||
controller.requestChangeCurrentTeam(team);
|
||||
|
||||
},
|
||||
onRefreshList: () {
|
||||
controller.requestAllTeamInfoList();
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:starcloud/sdk/entity/cloud_user_info.dart';
|
||||
import 'package:starcloud/sdk/starcloud.dart';
|
||||
import 'package:starwork_flutter/api/model/team/request/create_team_request.dart';
|
||||
import 'package:starwork_flutter/api/model/team/response/scene_info_response.dart';
|
||||
import 'package:starwork_flutter/api/service/team_api_service.dart';
|
||||
@ -28,19 +30,41 @@ class UseCaseSettingController extends BaseController {
|
||||
|
||||
/// 请求团队使用场景信息
|
||||
void requestAllSceneInfoList() async {
|
||||
showLoading();
|
||||
|
||||
var sceneList = await teamApi.requestAllSceneInfoList();
|
||||
if (sceneList.isSuccess) {
|
||||
// 使用场景列表
|
||||
useCases.value = sceneList.data?.list ?? [];
|
||||
useCases.refresh();
|
||||
}
|
||||
hideLoading();
|
||||
}
|
||||
|
||||
void requestCreateTeam() async {
|
||||
showLoading();
|
||||
var cacheStarCloudUserName = await SharedPreferencesUtils.getString(CacheKeys.starCloudUserName);
|
||||
var cacheStarCloudPassword = await SharedPreferencesUtils.getString(CacheKeys.starCloudPassword);
|
||||
var cacheStarCloudUid = await SharedPreferencesUtils.getString(CacheKeys.starCloudUid);
|
||||
if (cacheStarCloudUserName == null || cacheStarCloudPassword == null || cacheStarCloudUid == null) {
|
||||
await StarCloudSDK.instance.createCloudUser(
|
||||
onError: (err) {
|
||||
AppLogger.error('err:${err}');
|
||||
hideLoading();
|
||||
},
|
||||
onSuccess: (userInfo) {
|
||||
SharedPreferencesUtils.setString(CacheKeys.starCloudUserName, userInfo.username);
|
||||
SharedPreferencesUtils.setString(CacheKeys.starCloudPassword, userInfo.password);
|
||||
SharedPreferencesUtils.setString(CacheKeys.starCloudUid, userInfo.uid.toString());
|
||||
},
|
||||
);
|
||||
}
|
||||
StarCloudSDK.instance.setCloudAccounts(
|
||||
[
|
||||
CloudUserInfo(
|
||||
username: cacheStarCloudUserName!,
|
||||
password: cacheStarCloudPassword!,
|
||||
uid: int.parse(cacheStarCloudUid!),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
var createTeamResponse = await teamApi.requestCreateTeam(
|
||||
request: CreateTeamRequest(
|
||||
teamName: teamNameInputController.text,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user