import 'dart:convert'; import 'dart:typed_data'; /// MC700设备配置实体类(与嵌入式协议规范一一对应) /// 所有偏移均为 payload 内偏移(ProtocolParser 已剥离帧头/命令/校验/帧尾) /// payload 共 171 字节,索引 0~170 class Mc700DeviceConfig { // ====== 字段定义(按嵌入式协议规范) ====== // --- 基础标识 --- late int uidSolidifiedFlag; // payload[0] UID固化标志 (0=未固化, 1=已固化) late String chipUid; // payload[1..45] 芯片UID (45字节) late List remoteChannelConfig; // payload[46..64] 遥控器通道配置 (19字节) // --- 字节68位域 (payload[65]) --- late int knifeMotorMode; // bit0-1 割刀电机模式 (0=纯电, 1=油电) late int walkMotorMode; // bit2-3 行走电机模式 (0=轮式, 1=履带) late bool leftWheelPolarity; // bit4 左轮极性 (false=高, true=低) late bool rightWheelPolarity; // bit5 右轮极性 (false=高, true=低) late bool channelSwap; // bit6 左右轮通道交换 late bool use4g; // bit7 联网目标 (false=WiFi, true=4G) // --- 速度限制 --- late int forwardSpeedLimit; // payload[66..67] 前进速度限制 (uint16 LE) late int turnSpeedLimit; // payload[68..69] 转向速度限制 (uint16 LE) // --- 字节73位域 (payload[70]) --- late bool knifeChannelPolarity; // bit0 割刀通道极性 late bool fanChannelPolarity; // bit1 风门通道极性 late bool throttleChannelPolarity; // bit2 油门通道极性 late int liftProtectTime; // bit3-6 底盘升降保护时间 (秒) late bool dualRtk; // bit7 RTK配置 (false=单天线, true=双天线) // --- 字节74位域 (payload[71]) --- late int knifeChannelConfig; // bit0-3 割刀通道配置 late int fanChannelConfig; // bit4-7 风门通道配置 // --- 字节75位域 (payload[72]) --- late int throttleChannelConfig; // bit0-3 油门通道配置 late int remoteType; // bit4-6 遥控器类型 (0=飞控, 1=自定义1) late bool relayBoard; // bit7 是否搭载继电器板 // --- 字节76位域 (payload[73]) --- late int chassisLiftChannel; // bit0-3 底盘升降通道配置 late int chassisChannel; // bit4-7 底盘通道配置 // --- 字节77位域 (payload[74]) --- late int armChannel; // bit0-3 机械臂通道配置 late int fuelPumpChannel; // bit4-7 燃油泵通道配置 // --- WiFi --- late String wifiName; // payload[75..94] WiFi名称 (20字节) late String wifiPassword; // payload[95..114] WiFi密码 (20字节) // --- 字节118位域 (payload[115]) --- late int batteryType; // bit0-1 电池类型 (0=铅酸, 1=锂电) late int walkDriveConfig; // bit2-3 行走驱动配置 // --- 尺寸参数 (float LE) --- late double gearRatio; // payload[116..119] 转速比 late double robotLength; // payload[120..123] 机器人长度 late double robotWidth; // payload[124..127] 机器人宽度 late double robotHeight; // payload[128..131] 机器人高度 late double knifeWidth; // payload[132..135] 割刀宽度 late double tireSize; // payload[136..139] 轮胎尺寸 // --- 增益 (float LE) --- late double leftForwardGain; // payload[140..143] 左轮前进增益 late double leftBackwardGain; // payload[144..147] 左轮后退增益 late double rightForwardGain; // payload[148..151] 右轮前进增益 late double rightBackwardGain; // payload[152..155] 右轮后退增益 // --- 固件版本 --- late String firmwareVersion; // payload[156..170] 固件版本 (15字节) Mc700DeviceConfig(); // ====== 从二进制数据解析(171字节 payload) ====== factory Mc700DeviceConfig.fromBytes(Uint8List data) { if (data.length < 171) { throw Exception('数据长度不足171字节,实际${data.length}字节'); } final bd = ByteData.sublistView(data); final obj = Mc700DeviceConfig(); // 1. UID固化标志 @0 obj.uidSolidifiedFlag = bd.getUint8(0); // 2. 芯片UID @1..45 (45字节) obj.chipUid = _readNullTerminatedString(data, 1, 45); // 3. 遥控器通道配置 @46..64 (19字节) obj.remoteChannelConfig = List.unmodifiable(data.sublist(46, 65)); // 4. 字节68位域 @65 final b68 = bd.getUint8(65); obj.knifeMotorMode = b68 & 0x03; obj.walkMotorMode = (b68 >> 2) & 0x03; obj.leftWheelPolarity = (b68 & 0x10) != 0; obj.rightWheelPolarity = (b68 & 0x20) != 0; obj.channelSwap = (b68 & 0x40) != 0; obj.use4g = (b68 & 0x80) != 0; // 5. 前进速度限制 @66..67 obj.forwardSpeedLimit = bd.getUint16(66, Endian.little); // 6. 转向速度限制 @68..69 obj.turnSpeedLimit = bd.getUint16(68, Endian.little); // 7. 字节73位域 @70 final b73 = bd.getUint8(70); obj.knifeChannelPolarity = (b73 & 0x01) != 0; obj.fanChannelPolarity = (b73 & 0x02) != 0; obj.throttleChannelPolarity = (b73 & 0x04) != 0; obj.liftProtectTime = (b73 >> 3) & 0x1F; obj.dualRtk = (b73 & 0x80) != 0; // 8. 字节74位域 @71 final b74 = bd.getUint8(71); obj.knifeChannelConfig = b74 & 0x0F; obj.fanChannelConfig = (b74 >> 4) & 0x0F; // 9. 字节75位域 @72 final b75 = bd.getUint8(72); obj.throttleChannelConfig = b75 & 0x0F; obj.remoteType = (b75 >> 4) & 0x07; obj.relayBoard = (b75 & 0x80) != 0; // 10. 字节76位域 @73 final b76 = bd.getUint8(73); obj.chassisLiftChannel = b76 & 0x0F; obj.chassisChannel = (b76 >> 4) & 0x0F; // 11. 字节77位域 @74 final b77 = bd.getUint8(74); obj.armChannel = b77 & 0x0F; obj.fuelPumpChannel = (b77 >> 4) & 0x0F; // 12. WiFi名称 @75..94 (20字节) obj.wifiName = _readNullTerminatedString(data, 75, 20); // 13. WiFi密码 @95..114 (20字节) obj.wifiPassword = _readNullTerminatedString(data, 95, 20); // 14. 字节118位域 @115 final b118 = bd.getUint8(115); obj.batteryType = b118 & 0x03; obj.walkDriveConfig = (b118 >> 2) & 0x03; // 15. 转速比 @116..119 obj.gearRatio = bd.getFloat32(116, Endian.little); // 16. 机器人长度 @120..123 obj.robotLength = bd.getFloat32(120, Endian.little); // 17. 机器人宽度 @124..127 obj.robotWidth = bd.getFloat32(124, Endian.little); // 18. 机器人高度 @128..131 obj.robotHeight = bd.getFloat32(128, Endian.little); // 19. 割刀宽度 @132..135 obj.knifeWidth = bd.getFloat32(132, Endian.little); // 20. 轮胎尺寸 @136..139 obj.tireSize = bd.getFloat32(136, Endian.little); // 21. 左轮前进增益 @140..143 obj.leftForwardGain = bd.getFloat32(140, Endian.little); // 22. 左轮后退增益 @144..147 obj.leftBackwardGain = bd.getFloat32(144, Endian.little); // 23. 右轮前进增益 @148..151 obj.rightForwardGain = bd.getFloat32(148, Endian.little); // 24. 右轮后退增益 @152..155 obj.rightBackwardGain = bd.getFloat32(152, Endian.little); // 25. 固件版本 @156..170 (15字节) obj.firmwareVersion = _readNullTerminatedString(data, 156, 15); return obj; } // ====== 转换回二进制字节(用于写配置下发) ====== /// [originalPayload] 是上次读取的 171 字节 payload,用于保留未修改字段 Uint8List toBytes(Uint8List originalPayload) { final config = Uint8List.fromList(originalPayload); final bd = ByteData.sublistView(config); // UID固化标志 @0(独立字段,不属于字节68) bd.setUint8(0, uidSolidifiedFlag & 0x01); // 字节68位域 @65 int b68 = 0; b68 |= (knifeMotorMode & 0x03); b68 |= (walkMotorMode & 0x03) << 2; if (leftWheelPolarity) b68 |= 0x10; if (rightWheelPolarity) b68 |= 0x20; if (channelSwap) b68 |= 0x40; if (use4g) b68 |= 0x80; bd.setUint8(65, b68); // 速度限制 bd.setUint16(66, forwardSpeedLimit, Endian.little); bd.setUint16(68, turnSpeedLimit, Endian.little); // 字节73位域 @70 int b73 = (liftProtectTime & 0x1F) << 3; if (knifeChannelPolarity) b73 |= 0x01; if (fanChannelPolarity) b73 |= 0x02; if (throttleChannelPolarity) b73 |= 0x04; if (dualRtk) b73 |= 0x80; bd.setUint8(70, b73); // 字节74位域 @71 int b74 = knifeChannelConfig & 0x0F; b74 |= (fanChannelConfig & 0x0F) << 4; bd.setUint8(71, b74); // 字节75位域 @72 int b75 = throttleChannelConfig & 0x0F; b75 |= (remoteType & 0x07) << 4; if (relayBoard) b75 |= 0x80; bd.setUint8(72, b75); // 字节76位域 @73 int b76 = chassisLiftChannel & 0x0F; b76 |= (chassisChannel & 0x0F) << 4; bd.setUint8(73, b76); // 字节77位域 @74 int b77 = armChannel & 0x0F; b77 |= (fuelPumpChannel & 0x0F) << 4; bd.setUint8(74, b77); // WiFi _writeNullTerminatedString(config, 75, wifiName, 20); _writeNullTerminatedString(config, 95, wifiPassword, 20); // 字节118位域 @115 int b118 = batteryType & 0x03; b118 |= (walkDriveConfig & 0x03) << 2; bd.setUint8(115, b118); // 尺寸参数 bd.setFloat32(116, gearRatio, Endian.little); bd.setFloat32(120, robotLength, Endian.little); bd.setFloat32(124, robotWidth, Endian.little); bd.setFloat32(128, robotHeight, Endian.little); bd.setFloat32(132, knifeWidth, Endian.little); bd.setFloat32(136, tireSize, Endian.little); // 增益 bd.setFloat32(140, leftForwardGain, Endian.little); bd.setFloat32(144, leftBackwardGain, Endian.little); bd.setFloat32(148, rightForwardGain, Endian.little); bd.setFloat32(152, rightBackwardGain, Endian.little); // 固件版本 _writeNullTerminatedString(config, 156, firmwareVersion, 15); return config; } /// 获取所有字段的 label→value 映射 Map toFieldMap() { return { 'UID固化标志': '$uidSolidifiedFlag', '芯片UID': chipUid, '割刀电机模式': knifeMotorMode == 0 ? '纯电' : '油电($knifeMotorMode)', '行走电机模式': walkMotorMode == 0 ? '轮式' : '履带($walkMotorMode)', '左轮极性': leftWheelPolarity ? '低' : '高', '右轮极性': rightWheelPolarity ? '低' : '高', '通道交换': channelSwap ? '是' : '否', '联网目标': use4g ? '4G' : 'WiFi', '前进速度限制': '$forwardSpeedLimit', '转向速度限制': '$turnSpeedLimit', '割刀通道极性': knifeChannelPolarity ? '低' : '高', '风门通道极性': fanChannelPolarity ? '低' : '高', '油门通道极性': throttleChannelPolarity ? '低' : '高', '升降保护时间': '$liftProtectTime 秒', 'RTK配置': dualRtk ? '双天线' : '单天线', '割刀通道配置': '$knifeChannelConfig', '风门通道配置': '$fanChannelConfig', '油门通道配置': '$throttleChannelConfig', '遥控器类型': '$remoteType', '搭载继电器板': relayBoard ? '是' : '否', '底盘升降通道': '$chassisLiftChannel', '底盘通道': '$chassisChannel', '机械臂通道': '$armChannel', '燃油泵通道': '$fuelPumpChannel', 'WiFi名称': wifiName, 'WiFi密码': wifiPassword, '电池类型': batteryType == 0 ? '铅酸' : '锂电', '行走驱动': walkDriveConfig.toString(), '转速比': gearRatio.toStringAsFixed(2), '机器人长度': robotLength.toStringAsFixed(2), '机器人宽度': robotWidth.toStringAsFixed(2), '机器人高度': robotHeight.toStringAsFixed(2), '割刀宽度': knifeWidth.toStringAsFixed(2), '轮胎尺寸': tireSize.toStringAsFixed(2), '左轮前进增益': leftForwardGain.toStringAsFixed(2), '左轮后退增益': leftBackwardGain.toStringAsFixed(2), '右轮前进增益': rightForwardGain.toStringAsFixed(2), '右轮后退增益': rightBackwardGain.toStringAsFixed(2), '固件版本': firmwareVersion, }; } /// 根据字段标签设置新值(返回 null 表示成功,返回错误原因字符串) String? setField(String label, String value) { final intVal = int.tryParse(value); final doubleVal = double.tryParse(value); switch (label) { case 'UID固化标志': if (intVal == null || intVal < 0 || intVal > 1) return '值范围: 0~1'; uidSolidifiedFlag = intVal; return null; case '前进速度限制': if (intVal == null || intVal < 0 || intVal > 65535) return '值范围: 0~65535'; forwardSpeedLimit = intVal; return null; case '转向速度限制': if (intVal == null || intVal < 0 || intVal > 65535) return '值范围: 0~65535'; turnSpeedLimit = intVal; return null; case 'WiFi名称': if (value.length > 20) return '最多20字符'; wifiName = value; return null; case 'WiFi密码': if (value.length > 20) return '最多20字符'; wifiPassword = value; return null; case '转速比': if (doubleVal == null) return '请输入有效数字'; gearRatio = doubleVal; return null; case '机器人长度': if (doubleVal == null) return '请输入有效数字'; robotLength = doubleVal; return null; case '机器人宽度': if (doubleVal == null) return '请输入有效数字'; robotWidth = doubleVal; return null; case '机器人高度': if (doubleVal == null) return '请输入有效数字'; robotHeight = doubleVal; return null; case '割刀宽度': if (doubleVal == null) return '请输入有效数字'; knifeWidth = doubleVal; return null; case '轮胎尺寸': if (doubleVal == null) return '请输入有效数字'; tireSize = doubleVal; return null; case '左轮前进增益': if (doubleVal == null) return '请输入有效数字'; leftForwardGain = doubleVal; return null; case '左轮后退增益': if (doubleVal == null) return '请输入有效数字'; leftBackwardGain = doubleVal; return null; case '右轮前进增益': if (doubleVal == null) return '请输入有效数字'; rightForwardGain = doubleVal; return null; case '右轮后退增益': if (doubleVal == null) return '请输入有效数字'; rightBackwardGain = doubleVal; return null; case '固件版本': if (value.length > 15) return '最多15字符'; firmwareVersion = value; return null; default: return '未知字段: $label'; } } // 工具:读取0结尾的ASCII字符串 static String _readNullTerminatedString( Uint8List data, int offset, int maxLen, ) { final end = data.indexOf(0, offset); final realEnd = (end == -1 || end > offset + maxLen) ? offset + maxLen : end; return ascii.decode(data.sublist(offset, realEnd)); } // 工具:写入ASCII字符串,不足补0 static void _writeNullTerminatedString( Uint8List data, int offset, String str, int maxLen, ) { final bytes = ascii.encode(str); for (int i = 0; i < maxLen; i++) { data[offset + i] = i < bytes.length ? bytes[i] : 0x00; } } }