You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
154 lines
4.4 KiB
154 lines
4.4 KiB
import 'dart:io';
|
|
|
|
import 'package:device_jieli/device_jieli.dart';
|
|
|
|
import '../../core/utils/logger.dart';
|
|
|
|
/// 杰理设备广播包解析结果:从扫描到的 [JieliDevice.rawAdv] 中提取出
|
|
/// 服务端绑定接口需要的 PID / MAC / 设备授权码 (code)。
|
|
class JieliBindInfo {
|
|
/// 产品 ID(小端字节解析)
|
|
final int pid;
|
|
|
|
/// 设备真实 MAC(XOR 0x5A 解码后),格式 "AA:BB:CC:DD:EE:FF"
|
|
final String mac;
|
|
|
|
/// 设备授权码 (license / SN),hex 大写
|
|
final String code;
|
|
|
|
/// 解析得到的 PID 是否与扫描结果上的 BLE/EDR 地址一致
|
|
/// (仅用于日志,不强制要求一致)
|
|
final bool macMatchesAddress;
|
|
|
|
const JieliBindInfo({
|
|
required this.pid,
|
|
required this.mac,
|
|
required this.code,
|
|
required this.macMatchesAddress,
|
|
});
|
|
|
|
@override
|
|
String toString() =>
|
|
'JieliBindInfo(pid=0x${pid.toRadixString(16).toUpperCase()}, mac=$mac, code=$code, match=$macMatchesAddress)';
|
|
}
|
|
|
|
/// 杰理 BLE 广播解析。
|
|
///
|
|
/// 字节布局(与旧版一致,基于扫到的 raw scanRecord 字节):
|
|
/// Android:
|
|
/// - bytes[6..7] → PID
|
|
/// - bytes[9..14] → MAC(每字节 XOR 0x5A)
|
|
/// - bytes[15..24] → 授权码 code
|
|
/// iOS:
|
|
/// - bytes[4..5] → PID
|
|
/// - bytes[7..12] → MAC(每字节 XOR 0x5A)
|
|
/// - bytes[13..22] → 授权码 code
|
|
class JieliAdvParser {
|
|
static const String _tag = 'JieliAdvParser';
|
|
|
|
/// 用 [device.rawAdv] 直接解析。若 raw 数据不完整或 device 未携带广播包,返回 null。
|
|
static JieliBindInfo? parse(JieliDevice device) {
|
|
final raw = device.rawAdv;
|
|
if (raw == null || raw.isEmpty) {
|
|
Logger.w(_tag, '设备 ${device.address} 没有 rawAdv,无法解析绑定信息');
|
|
return null;
|
|
}
|
|
return parseHex(raw, deviceAddress: device.address);
|
|
}
|
|
|
|
/// 直接对 hex 字符串解析,便于测试。
|
|
static JieliBindInfo? parseHex(String hex, {String? deviceAddress}) {
|
|
final bytes = _hexToBytes(hex);
|
|
if (bytes == null) {
|
|
Logger.w(_tag, 'rawAdv 不是合法 hex: $hex');
|
|
return null;
|
|
}
|
|
|
|
final int pidStart;
|
|
final int pidEnd;
|
|
final int macStart;
|
|
final int macEnd;
|
|
final int codeStart;
|
|
final int codeEnd;
|
|
|
|
if (Platform.isIOS) {
|
|
pidStart = 4;
|
|
pidEnd = 5;
|
|
macStart = 7;
|
|
macEnd = 12;
|
|
codeStart = 13;
|
|
codeEnd = 22;
|
|
} else {
|
|
// Android 默认;其他平台沿用 Android 偏移。
|
|
pidStart = 6;
|
|
pidEnd = 7;
|
|
macStart = 9;
|
|
macEnd = 14;
|
|
codeStart = 15;
|
|
codeEnd = 24;
|
|
}
|
|
|
|
if (bytes.length <= codeEnd) {
|
|
Logger.w(_tag,
|
|
'rawAdv 长度不足 ${codeEnd + 1} 字节(实际 ${bytes.length}),跳过解析');
|
|
return null;
|
|
}
|
|
|
|
final pid = _bytesToIntBigEndian(bytes, pidStart, pidEnd);
|
|
final mac = _decodeMac(bytes, macStart, macEnd);
|
|
final code = _bytesToHexReversed(bytes, codeStart, codeEnd);
|
|
|
|
bool match = false;
|
|
if (deviceAddress != null && deviceAddress.isNotEmpty) {
|
|
final a = deviceAddress.replaceAll(':', '').toUpperCase();
|
|
final b = mac.replaceAll(':', '').toUpperCase();
|
|
match = a == b;
|
|
}
|
|
|
|
final info = JieliBindInfo(
|
|
pid: pid,
|
|
mac: mac,
|
|
code: code,
|
|
macMatchesAddress: match,
|
|
);
|
|
Logger.i(_tag, '解析广播: $info (deviceAddress=$deviceAddress)');
|
|
return info;
|
|
}
|
|
|
|
static List<int>? _hexToBytes(String hex) {
|
|
final s = hex.startsWith('0x') || hex.startsWith('0X') ? hex.substring(2) : hex;
|
|
if (s.length.isOdd) return null;
|
|
final out = <int>[];
|
|
for (int i = 0; i < s.length; i += 2) {
|
|
final v = int.tryParse(s.substring(i, i + 2), radix: 16);
|
|
if (v == null) return null;
|
|
out.add(v);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static int _bytesToIntBigEndian(List<int> bytes, int start, int end) {
|
|
int result = 0;
|
|
for (int i = start; i <= end; i++) {
|
|
result = (result << 8) | (bytes[i] & 0xFF);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static String _bytesToHexReversed(List<int> bytes, int start, int end) {
|
|
final slice = bytes.sublist(start, end + 1);
|
|
return slice
|
|
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
|
.join()
|
|
.toUpperCase();
|
|
}
|
|
|
|
static String _decodeMac(List<int> bytes, int start, int end) {
|
|
final out = <String>[];
|
|
for (int i = start; i <= end; i++) {
|
|
final decoded = bytes[i] ^ 0x5A;
|
|
out.add(decoded.toRadixString(16).padLeft(2, '0').toUpperCase());
|
|
}
|
|
return out.join(':');
|
|
}
|
|
}
|
|
|