Files
flutterApp/lib/core/bluetooth/mc700_device_config.dart
2026-08-07 08:49:29 +08:00

403 lines
15 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}
}