import 'dart:convert'; import 'dart:developer' as developer; import 'dart:typed_data'; import '../protocol/machine_protocol_constants.dart'; import 'mc700_device_config.dart'; /// BLE 协议解析出的单个字段 class BleField { final String label; final String value; const BleField({required this.label, required this.value}); } /// BLE 协议解析结果 class BleDecodedResult { final List fields; final String rawHex; /// 0x05 读配置时携带的配置实体(可修改后回写) final Mc700DeviceConfig? configEntity; const BleDecodedResult({ required this.fields, required this.rawHex, this.configEntity, }); bool get isEmpty => fields.isEmpty; } /// BLE 协议解析器 /// 与 TCP net_message_dispatcher 使用相同的协议格式:AB AA cmd [payload] ... AA AB /// payload 通常为 UTF-8 逗号分隔文本,部分指令为二进制 class BleProtocolDecoder { /// 入口:按命令类型分发解析 static BleDecodedResult decode(int command, Uint8List payload) { final hex = _bytesToHex(payload); try { switch (command) { case MachineProtocolConstants.cmdStatusInfo: return _decodeStatusInfo(payload, hex); case MachineProtocolConstants.cmdRemoteControl: return _decodeRemoteControl(payload, hex); case MachineProtocolConstants.cmdGetId: return _decodeGetId(payload, hex); case MachineProtocolConstants.cmdGetAuth: return _decodeGetAuth(payload, hex); case MachineProtocolConstants.cmdReadConfig: return _decodeReadConfig(payload, hex); case MachineProtocolConstants.cmdWriteConfig: return _decodeWriteConfig(payload, hex); case MachineProtocolConstants.cmdHeartbeat: return _decodeHeartbeat(payload, hex); case MachineProtocolConstants.cmdPathPlanning: return _decodePathPlanning(payload, hex); case MachineProtocolConstants.cmdObstacleAvoid: return _decodeObstacleAvoid(payload, hex); default: return _decodeGeneric(command, payload, hex); } } catch (e) { return BleDecodedResult( fields: [const BleField(label: '解析错误', value: '异常')], rawHex: hex, ); } } // ─── 0x02 状态信息(与 TCP RunningStatusEntity.fromFields 一致,24字段逗号分隔) ─── static BleDecodedResult _decodeStatusInfo(Uint8List data, String hex) { final fields = []; try { final text = utf8.decode(data); if (text.contains(',')) { final parts = text.split(','); return BleDecodedResult(fields: _parseStatusFields(parts), rawHex: hex); } } catch (e) { try { final text = latin1.decode(data); if (text.contains(',')) { final parts = text.split(','); return BleDecodedResult( fields: _parseStatusFields(parts), rawHex: hex, ); } } catch (_) {} } if (data.isNotEmpty) { final status = data[0]; fields.add( BleField( label: '状态码', value: '0x${status.toRadixString(16).toUpperCase().padLeft(2, '0')}', ), ); } if (data.length >= 2) { final mode = data[1]; const modes = {0x00: '待机', 0x01: '遥控', 0x02: '自动', 0x03: '急停'}; fields.add( BleField( label: '控制模式', value: modes[mode] ?? '未知(0x${mode.toRadixString(16)})', ), ); } if (data.length >= 3) fields.add(BleField(label: '电量', value: '${data[2]}%')); if (data.length >= 4) { final fault = data[3]; fields.add( BleField( label: '故障状态', value: fault == 0 ? '无故障' : '故障码: 0x${fault.toRadixString(16).toUpperCase().padLeft(2, '0')}', ), ); } if (data.length >= 6) { final speed = (data[5] << 8) | data[4]; fields.add(BleField(label: '速度', value: '$speed')); } if (data.length > 8) { try { final text = String.fromCharCodes(data.sublist(8)); if (text.isNotEmpty && !text.contains('\x00')) { fields.add(BleField(label: '附加', value: text)); } } catch (_) {} } return BleDecodedResult(fields: fields, rawHex: hex); } /// 解析逗号分隔的 24 字段状态数据(与 RunningStatusEntity.fromFields 完全一致) static List _parseStatusFields(List parts) { while (parts.length < 24) { parts.add(''); } String controlModeText(String v) { return switch (v) { '0' => '待机', '1' => '遥控', '2' => '自动', '3' => '急停', _ => v.isNotEmpty ? v : '--', }; } String qualText(String v) { final q = int.tryParse(v) ?? -1; return switch (q) { 0 => '无效', 1 => '单点定位', 2 => '差分定位', 4 => '固定解', 5 => '浮点解', _ => v.isNotEmpty ? v : '--', }; } String headingText(String v) { final h = int.tryParse(v) ?? -1; return switch (h) { 0 => '未初始化', 1 => '已初始化', _ => v.isNotEmpty ? v : '--', }; } String obstacleText(String v) { final o = int.tryParse(v) ?? -1; return switch (o) { 0 => '无障碍', 1 => '有障碍', _ => v.isNotEmpty ? v : '--', }; } return [ BleField(label: '电压', value: '${_tryParseDouble(parts[0])} V'), BleField(label: '左目标速度', value: _tryParseDouble(parts[1])), BleField(label: '右目标速度', value: _tryParseDouble(parts[2])), BleField(label: '左实测速度', value: _tryParseDouble(parts[3])), BleField(label: '右实测速度', value: _tryParseDouble(parts[4])), BleField(label: '左电流', value: '${_tryParseDouble(parts[5])} A'), BleField(label: '右电流', value: '${_tryParseDouble(parts[6])} A'), BleField(label: '左电机温度', value: '${_tryParseDouble(parts[7])} ℃'), BleField(label: '右电机温度', value: '${_tryParseDouble(parts[8])} ℃'), BleField(label: '芯片温度', value: '${_tryParseDouble(parts[9])} ℃'), BleField(label: '偏航角', value: '${_tryParseDouble(parts[10])} °'), BleField(label: '俯仰角', value: '${_tryParseDouble(parts[11])} °'), BleField(label: '翻滚角', value: '${_tryParseDouble(parts[12])} °'), BleField(label: '卫星数量', value: parts[13].isNotEmpty ? parts[13] : '--'), BleField(label: '定位质量', value: qualText(parts[14])), BleField(label: '航向状态', value: headingText(parts[15])), BleField(label: '纬度', value: parts[16].isNotEmpty ? parts[16] : '--'), BleField(label: '经度', value: parts[17].isNotEmpty ? parts[17] : '--'), BleField(label: '时间戳', value: parts[18].isNotEmpty ? parts[18] : '--'), BleField( label: '割刀速度', value: parts[19].isNotEmpty ? '${parts[19]} rpm' : '--', ), BleField(label: '控制模式', value: controlModeText(parts[20])), BleField( label: '电量', value: parts[21].isNotEmpty ? '${parts[21]}%' : '--', ), BleField(label: '工作面积', value: parts[22].isNotEmpty ? parts[22] : '--'), BleField(label: '障碍物', value: obstacleText(parts[23])), ]; } static String _tryParseDouble(String s) { if (s.isEmpty) return '--'; final v = double.tryParse(s); if (v == null) return s; if (v == v.roundToDouble() && v.abs() < 10000) return v.toInt().toString(); return v.toStringAsFixed(2); } // ─── 0x00 远程遥控 ─── static BleDecodedResult _decodeRemoteControl(Uint8List data, String hex) { final fields = []; try { final text = utf8.decode(data, allowMalformed: true); if (text.contains(',')) { final parts = text.split(','); while (parts.length < 5) parts.add(''); fields.add( BleField(label: '左右速度', value: parts[0].isNotEmpty ? parts[0] : '--'), ); fields.add( BleField(label: '前后速度', value: parts[1].isNotEmpty ? parts[1] : '--'), ); final modeStr = switch (parts[2]) { '0' => '停止', '1' => '遥控', '2' => '自动', _ => parts[2].isNotEmpty ? parts[2] : '--', }; fields.add(BleField(label: '控制模式', value: modeStr)); if (parts.length > 3 && parts[3].isNotEmpty) fields.add(BleField(label: '参数4', value: parts[3])); if (parts.length > 4 && parts[4].isNotEmpty) fields.add(BleField(label: '参数5', value: parts[4])); return BleDecodedResult(fields: fields, rawHex: hex); } } catch (_) {} if (data.length >= 5) { fields.add(BleField(label: '左右速度', value: '${(data[1] << 8) | data[0]}')); fields.add(BleField(label: '前后速度', value: '${(data[3] << 8) | data[2]}')); const modes = {0: '停止', 1: '遥控', 2: '自动'}; fields.add( BleField(label: '控制模式', value: modes[data[4]] ?? '未知(${data[4]})'), ); } return BleDecodedResult(fields: fields, rawHex: hex); } // ─── 0x03 查询ID ─── static BleDecodedResult _decodeGetId(Uint8List data, String hex) { final fields = []; try { final text = utf8.decode(data, allowMalformed: true); if (text.replaceAll('\x00', '').trim().isNotEmpty) { fields.add( BleField(label: '设备ID', value: text.replaceAll('\x00', '').trim()), ); return BleDecodedResult(fields: fields, rawHex: hex); } } catch (_) {} try { final ascii = String.fromCharCodes(data); if (RegExp(r'^[\x20-\x7E]+$').hasMatch(ascii)) { fields.add(BleField(label: '设备ID', value: ascii)); } else { fields.add(BleField(label: '设备ID(Hex)', value: hex)); } } catch (_) { fields.add(BleField(label: '设备ID(Hex)', value: hex)); } return BleDecodedResult(fields: fields, rawHex: hex); } // ─── 0x04 获取授权 ─── static BleDecodedResult _decodeGetAuth(Uint8List data, String hex) { final fields = []; try { final text = utf8.decode(data, allowMalformed: true); final trimmed = text.replaceAll('\x00', '').trim(); if (trimmed == '1' || trimmed.toLowerCase() == 'true') { fields.add(const BleField(label: '授权状态', value: '已授权')); } else if (trimmed == '0' || trimmed.toLowerCase() == 'false') { fields.add(const BleField(label: '授权状态', value: '未授权')); } else if (trimmed.isNotEmpty) { fields.add(BleField(label: '授权状态', value: trimmed)); } if (fields.isNotEmpty) return BleDecodedResult(fields: fields, rawHex: hex); } catch (_) {} if (data.isNotEmpty) { if (data[0] == 0x01) { fields.add(const BleField(label: '授权状态', value: '已授权')); } else if (data[0] == 0x00) { fields.add(const BleField(label: '授权状态', value: '未授权')); } else { fields.add( BleField(label: '授权状态(Hex)', value: '0x${data[0].toRadixString(16)}'), ); } } return BleDecodedResult(fields: fields, rawHex: hex); } // ─── 0x05 读配置(按嵌入式协议规范精确解析,171字节) ─── static BleDecodedResult _decodeReadConfig(Uint8List data, String hex) { final fields = []; if (data.isEmpty) { fields.add(const BleField(label: '状态', value: '空数据')); return BleDecodedResult(fields: fields, rawHex: hex); } final bd = ByteData.sublistView(data); final len = data.length; // 1. UID固化标志 @payload[0] (帧字节3) if (len >= 1) { fields.add( BleField( label: 'UID固化标志', value: data[0] == 1 ? '已固化' : '未固化(${data[0]})', ), ); } // 2. 芯片UID @payload[1..45] (帧字节4-48, 45字节) if (len >= 46) { fields.add( BleField(label: '芯片UID', value: _readNullTermString(data, 1, 45)), ); } // 3. 遥控器通道配置 @payload[46..64] (帧字节49-67, 19字节) if (len >= 65) { fields.add( BleField(label: '遥控器通道配置', value: _bytesToHex(data.sublist(46, 65))), ); } // 4. 字节68位域 @payload[65] (帧字节68) if (len >= 66) { final b = data[65]; fields.add( BleField( label: '割刀电机模式', value: (b & 0x03) == 0 ? '纯电' : '油电(${b & 0x03})', ), ); fields.add( BleField( label: '行走电机模式', value: ((b >> 2) & 0x03) == 0 ? '轮式' : '履带(${(b >> 2) & 0x03})', ), ); fields.add( BleField(label: '左轮电机极性', value: (b & 0x10) == 0 ? '高极性' : '低极性(反取)'), ); fields.add( BleField(label: '右轮电机极性', value: (b & 0x20) == 0 ? '高极性' : '低极性(反取)'), ); fields.add( BleField(label: '左右轮通道交换', value: (b & 0x40) == 0 ? '不交换' : '通道互换'), ); fields.add( BleField(label: '联网目标', value: (b & 0x80) == 0 ? 'WiFi' : '4G'), ); } // 5. 前进速度限制 @payload[66..67] (帧字节69-70, uint16 LE) if (len >= 68) { fields.add( BleField(label: '前进速度限制', value: '${bd.getUint16(66, Endian.little)}'), ); } // 6. 转向速度限制 @payload[68..69] (帧字节71-72, uint16 LE) if (len >= 70) { fields.add( BleField(label: '转向速度限制', value: '${bd.getUint16(68, Endian.little)}'), ); } // 7. 字节73位域 @payload[70] (帧字节73) if (len >= 71) { final b = data[70]; fields.add( BleField(label: '割刀通道极性', value: (b & 0x01) == 0 ? '高极性' : '低极性(反取)'), ); fields.add( BleField(label: '风门通道极性', value: (b & 0x02) == 0 ? '高极性' : '低极性(反取)'), ); fields.add( BleField(label: '油门通道极性', value: (b & 0x04) == 0 ? '高极性' : '低极性(反取)'), ); fields.add(BleField(label: '底盘升降保护时间', value: '${(b >> 3) & 0x1F} 秒')); fields.add( BleField(label: 'RTK配置', value: (b & 0x80) == 0 ? '单天线' : '双天线'), ); } // 8. 字节74位域 @payload[71] (帧字节74) if (len >= 72) { final b = data[71]; fields.add(BleField(label: '割刀通道配置', value: '${b & 0x0F}')); fields.add(BleField(label: '风门通道配置', value: '${(b >> 4) & 0x0F}')); } // 9. 字节75位域 @payload[72] (帧字节75) if (len >= 73) { final b = data[72]; fields.add(BleField(label: '油门通道配置', value: '${b & 0x0F}')); fields.add( BleField( label: '遥控器类型', value: (b >> 4) & 0x07 == 0 ? '飞控' : '自定义遥控器(${(b >> 4) & 0x07})', ), ); fields.add( BleField(label: '是否搭载继电器板', value: (b & 0x80) == 0 ? '未搭载' : '搭载'), ); } // 10. 字节76位域 @payload[73] (帧字节76) if (len >= 74) { final b = data[73]; fields.add(BleField(label: '底盘升降通道配置', value: '${b & 0x0F}')); fields.add(BleField(label: '底盘通道配置', value: '${(b >> 4) & 0x0F}')); } // 11. 字节77位域 @payload[74] (帧字节77) if (len >= 75) { final b = data[74]; fields.add(BleField(label: '机械臂通道配置', value: '${b & 0x0F}')); fields.add(BleField(label: '燃油泵通道配置', value: '${(b >> 4) & 0x0F}')); } // 12. WiFi名称 @payload[75..94] (帧字节78-97, 20字节) if (len >= 95) { fields.add( BleField(label: 'WiFi名称', value: _readNullTermString(data, 75, 20)), ); } // 13. WiFi密码 @payload[95..114] (帧字节98-117, 20字节) if (len >= 115) { fields.add( BleField(label: 'WiFi密码', value: _readNullTermString(data, 95, 20)), ); } // 14. 字节118位域 @payload[115] (帧字节118) if (len >= 116) { final b = data[115]; fields.add( BleField( label: '电池类型', value: (b & 0x03) == 0 ? '铅酸' : '锂电(${b & 0x03})', ), ); fields.add( BleField( label: '行走驱动配置', value: switch ((b >> 2) & 0x03) { 0 => '风德控', 1 => '山东神澜BLDC', 2 => '山东神澜FOC', _ => '未知(${(b >> 2) & 0x03})', }, ), ); } // 15. 转速比 @payload[116..119] (帧字节119-122, float LE) if (len >= 120) { final v = bd.getFloat32(116, Endian.little); fields.add(BleField(label: '转速比', value: v.toStringAsFixed(2))); } // 16. 机器人长度 @payload[120..123] (帧字节123-126, float LE) if (len >= 124) { final v = bd.getFloat32(120, Endian.little); fields.add(BleField(label: '机器人长度', value: v.toStringAsFixed(2))); } // 17. 机器人宽度 @payload[124..127] (帧字节127-130, float LE) if (len >= 128) { final v = bd.getFloat32(124, Endian.little); fields.add(BleField(label: '机器人宽度', value: v.toStringAsFixed(2))); } // 18. 机器人高度 @payload[128..131] (帧字节131-134, float LE) if (len >= 132) { final v = bd.getFloat32(128, Endian.little); fields.add(BleField(label: '机器人高度', value: v.toStringAsFixed(2))); } // 19. 割刀宽度 @payload[132..135] (帧字节135-138, float LE) if (len >= 136) { final v = bd.getFloat32(132, Endian.little); fields.add(BleField(label: '割刀宽度', value: v.toStringAsFixed(2))); } // 20. 轮胎尺寸 @payload[136..139] (帧字节139-142, float LE) if (len >= 140) { final v = bd.getFloat32(136, Endian.little); fields.add(BleField(label: '轮胎尺寸', value: v.toStringAsFixed(2))); } // 21. 左轮前进增益 @payload[140..143] (帧字节143-146, float LE) if (len >= 144) { final v = bd.getFloat32(140, Endian.little); fields.add(BleField(label: '左轮前进增益', value: v.toStringAsFixed(2))); } // 22. 左轮后退增益 @payload[144..147] (帧字节147-150, float LE) if (len >= 148) { final v = bd.getFloat32(144, Endian.little); fields.add(BleField(label: '左轮后退增益', value: v.toStringAsFixed(2))); } // 23. 右轮前进增益 @payload[148..151] (帧字节151-154, float LE) if (len >= 152) { final v = bd.getFloat32(148, Endian.little); fields.add(BleField(label: '右轮前进增益', value: v.toStringAsFixed(2))); } // 24. 右轮后退增益 @payload[152..155] (帧字节155-158, float LE) if (len >= 156) { final v = bd.getFloat32(152, Endian.little); fields.add(BleField(label: '右轮后退增益', value: v.toStringAsFixed(2))); } // 25. 固件版本 @payload[156..170] (帧字节159-173, 15字节) if (len >= 171) { fields.add( BleField(label: '固件版本', value: _readNullTermString(data, 156, 15)), ); } // 构建配置实体(用于后续编辑和回写) Mc700DeviceConfig? configEntity; if (data.length >= 171) { try { configEntity = Mc700DeviceConfig.fromBytes(data); } catch (e) { developer.log('[BLE] 配置实体解析失败: $e', name: 'BleProtocolDecoder'); } } return BleDecodedResult( fields: fields, rawHex: hex, configEntity: configEntity, ); } /// 读取 null 终止字符串(最多读取 [maxLen] 字节) static String _readNullTermString(Uint8List data, int offset, int maxLen) { final buffer = StringBuffer(); for (int i = 0; i < maxLen && offset + i < data.length; i++) { final b = data[offset + i]; if (b == 0x00) break; if (b >= 0x20 && b <= 0x7E) { buffer.writeCharCode(b); } else { buffer.writeCharCode(0x2E); } } return buffer.toString(); } // ─── 0x06 写配置 ─── static BleDecodedResult _decodeWriteConfig(Uint8List data, String hex) { final fields = []; if (data.isNotEmpty) { final ok = data[0]; fields.add( BleField( label: '写入结果', value: ok == 0x01 ? '成功' : ok == 0x00 ? '失败' : '0x${ok.toRadixString(16)}', ), ); } return BleDecodedResult(fields: fields, rawHex: hex); } // ─── 0xFF 心跳包 ─── static BleDecodedResult _decodeHeartbeat(Uint8List data, String hex) { final fields = []; if (data.isEmpty) { fields.add(const BleField(label: '心跳', value: '正常')); } else if (data.length >= 2) { final interval = (data[1] << 8) | data[0]; fields.add(BleField(label: '心跳间隔', value: '${interval}ms')); } else { fields.add(BleField(label: '心跳数据', value: hex)); } return BleDecodedResult(fields: fields, rawHex: hex); } // ─── 0x01 路径规划 ─── static BleDecodedResult _decodePathPlanning(Uint8List data, String hex) { final fields = []; try { final text = utf8.decode(data, allowMalformed: true); if (text.contains(',')) { final parts = text.split(','); if (parts.length >= 2) { fields.add(BleField(label: '点编号', value: parts[0])); fields.add( BleField( label: '状态', value: switch (parts[1]) { '1' => '已到达', '2' => '收到指令', _ => parts[1], }, ), ); } if (parts.length >= 4) { fields.add(BleField(label: '纬度', value: parts[2])); fields.add(BleField(label: '经度', value: parts[3])); } return BleDecodedResult(fields: fields, rawHex: hex); } } catch (_) {} if (data.length >= 3) { fields.add(BleField(label: '点编号', value: '${data[0]}')); final status = data.length > 1 ? data[1] : -1; fields.add( BleField( label: '状态', value: status == 0x01 ? '已到达' : status == 0x02 ? '收到指令' : '0x${status.toRadixString(16)}', ), ); } return BleDecodedResult(fields: fields, rawHex: hex); } // ─── 0x07 避障 ─── static BleDecodedResult _decodeObstacleAvoid(Uint8List data, String hex) { final fields = []; if (data.isNotEmpty) { final flag = data[0]; fields.add( BleField( label: '避障状态', value: flag == 0x01 ? '触发' : flag == 0x00 ? '正常' : '0x${flag.toRadixString(16)}', ), ); } if (data.length >= 3) { fields.add(BleField(label: '距离', value: '${(data[2] << 8) | data[1]}mm')); } return BleDecodedResult(fields: fields, rawHex: hex); } // ─── 通用/未知指令 ─── static BleDecodedResult _decodeGeneric( int command, Uint8List data, String hex, ) { final fields = []; try { final text = utf8.decode(data, allowMalformed: true); final trimmed = text.replaceAll('\x00', '').trim(); if (trimmed.isNotEmpty) { fields.add( BleField( label: '文本内容', value: trimmed.length > 50 ? '${trimmed.substring(0, 50)}...' : trimmed, ), ); } } catch (_) {} if (fields.isEmpty) { fields.add(BleField(label: '数据长度', value: '${data.length}B')); } return BleDecodedResult(fields: fields, rawHex: hex); } // ─── 工具方法 ─── static String _bytesToHex(List bytes) { if (bytes.isEmpty) return ''; return bytes .map((b) => b.toRadixString(16).toUpperCase().padLeft(2, '0')) .join(' '); } }