403 lines
15 KiB
Dart
403 lines
15 KiB
Dart
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<int> 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<String, String> 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;
|
||
}
|
||
}
|
||
}
|