分区相关

This commit is contained in:
2026-11-06 08:44:05 +08:00
parent 85c83ec143
commit 8eea42b75d
21 changed files with 3245 additions and 439 deletions

View File

@@ -34,38 +34,58 @@ class RemoteControlRepositoryImpl implements RemoteControlRepository {
}
@override
void sendControlMachineCmd(MachineControlStatusEntity status) {
// 1. 调用算法:将摇杆坐标 (x, y) 转换为线速度/角速度(物理量)
final speeds = _diffSteer.calculate(status.originX, status.originY);
final linear = speeds['linear']!;
final angular = speeds['angular']!;
bool sendControlMachineCmd(MachineControlStatusEntity status) {
// 🔥 整个“换算 + 编码 + 写 socket”都包在 try 里:本方法被 200ms 控制循环
// 和停止风暴(一次 30 帧)高频调用,任何一帧抛异常都会变成 Timer 回调
// 里的未捕获异常(每 200ms 刷一次),更糟的是会让 stopAllMovement 的
// 停止帧中途断掉——车就停不下来。失败一律返回 false,由调用方统计
// “一共出网了几帧”(控制循环里本来就不关心返回值)。
try {
// 1. 调用算法:将摇杆坐标 (x, y) 转换为线速度/角速度(物理量)
final speeds = _diffSteer.calculate(status.originX, status.originY);
final linear = speeds['linear']!;
final angular = speeds['angular']!;
// 2. 调用 Codec:生成协议要求的 20 字节 Payload(线速度/角速度 double 直传)
final payload = MachineProtocolCodec.encodeRemoteControlPayload(
linear: linear,
angular: angular,
lift: status.chassisLift,
mower: status.mowerSpeed,
ignition: status.ignitionStatus,
emergency: status.isEmergency ? 1 : 0,
);
// 2. 调用 Codec:生成协议要求的 20 字节 Payload(线速度/角速度 double 直传)
final payload = MachineProtocolCodec.encodeRemoteControlPayload(
linear: linear,
angular: angular,
lift: status.chassisLift,
mower: status.mowerSpeed,
ignition: status.ignitionStatus,
emergency: status.isEmergency ? 1 : 0,
);
// 联调日志:三段式打印 ①滑块/摇杆原始数据 ②乘完系数的发送值 ③包装后的整帧字节(联调结束可移除)
_logger.logWithLevel(
'🔗 [联调-0x00] 遥控发送明细\n'
'① 滑块/摇杆原始数据: x=${status.originX} y=${status.originY} '
'lift=${status.chassisLift} mower=${status.mowerSpeed} '
'ignition=${status.ignitionStatus} emergency=${status.isEmergency ? 1 : 0}\n'
'② 乘后发送值: linear=${linear.toStringAsFixed(3)} m/s, angular=${angular.toStringAsFixed(3)} rad/s\n'
'③ 包装后整帧(27字节): ab aa 00 | ${_hex(payload.sublist(0, 8))} | '
'${_hex(payload.sublist(8, 16))} | ${_hex(payload.sublist(16, 20))} | 00 00 | aa ab\n'
' 包头+cmd | linear@帧[3..10] | angular@帧[11..18] | '
'lift,mower,ignition,emergency@帧[19..22] | CRC | 包尾',
shouldLog: true,
);
// 联调日志:三段式打印 ①滑块/摇杆原始数据 ②乘完系数的发送值 ③包装后的整帧字节(联调结束可移除)
_logger.logWithLevel(
'🔗 [联调-0x00] 遥控发送明细\n'
'① 滑块/摇杆原始数据: x=${status.originX} y=${status.originY} '
'lift=${status.chassisLift} mower=${status.mowerSpeed} '
'ignition=${status.ignitionStatus} emergency=${status.isEmergency ? 1 : 0}\n'
'② 乘后发送值: linear=${linear.toStringAsFixed(3)} m/s, angular=${angular.toStringAsFixed(3)} rad/s\n'
'③ 包装后整帧(27字节): ab aa 00 | ${_hex(payload.sublist(0, 8))} | '
'${_hex(payload.sublist(8, 16))} | ${_hex(payload.sublist(16, 20))} | 00 00 | aa ab\n'
' 包头+cmd | linear@帧[3..10] | angular@帧[11..18] | '
'lift,mower,ignition,emergency@帧[19..22] | CRC | 包尾',
shouldLog: true,
);
// 3. 调用 TcpClient 发送指令
_tcpClient.sendRaw(MachineProtocolConstants.cmdRemoteControl, payload);
// 3. 调用 TcpClient 发送指令
// 🔥 把写入结果如实上抛:false 代表本帧没交给系统 socket(未连接/写入异常),
// 调用方(尤其 stopAllMovement 的停止风暴)需要知道“发出去了几个”。
return _tcpClient.sendRaw(
MachineProtocolConstants.cmdRemoteControl,
payload,
);
} catch (e, s) {
debugPrint('❌ [RemoteControl] 遥控指令构造/发送异常: $e');
_logger.logWithLevel(
'❌ [RemoteControl] 遥控指令构造/发送异常: $e\n$s',
level: 'ERROR',
shouldLog: true,
);
return false;
}
}
/// 字节转十六进制字符串,供联调对包使用
@@ -77,8 +97,25 @@ class RemoteControlRepositoryImpl implements RemoteControlRepository {
@override
void sendTcpPermissionRequest(String deviceName) {
_remoteTcp.sendSwitchControlRequest(deviceName);
_logger.logWithLevel('🔑 [RemoteControl] TCP已发送权限请求', shouldLog: true);
// 🔥 RemoteTcpDatasource.sendSwitchControlRequest 在 TCP 未连接时是 throw 的,
// 而本方法是在 requestControlPermissionS 的 result.fold(async 回调) 里调的:
// fold 不会 await 那个回调的 Future,异常从这条路径出去就是彻底的未捕获
// 异步异常(点“获取控制权”时断着 TCP 就会撞上)。抢占请求发不出去最坏
// 只是等不到 0x12 回包,不该崩,故就地吞掉并留 ERROR 痕迹。
try {
_remoteTcp.sendSwitchControlRequest(deviceName);
_logger.logWithLevel(
'🔑 [RemoteControl] TCP已发送权限请求',
shouldLog: true,
);
} catch (e) {
debugPrint('❌ [RemoteControl] TCP 权限请求发送失败: $e');
_logger.logWithLevel(
'❌ [RemoteControl] TCP 权限请求发送失败(未连接): $e',
level: 'ERROR',
shouldLog: true,
);
}
}
@override
@@ -177,9 +214,22 @@ class RemoteControlRepositoryImpl implements RemoteControlRepository {
required String deviceId,
required int type,
}) async {
return await _remoteHttp.releaseRemoteControlViaHttp(
deviceId: deviceId,
type: type,
);
// 🔥 datasource 内部只包住了 dio.post,前面的 _userStorage.getUser() 仍会抛;
// 而调用方多在页面 dispose 里 `.then()` 发就不管(没有 onError),抛出来
// 就是未捕获异常。释放失败最坏是服务端权限挂着等超时,不该崩,收敛成 false。
try {
return await _remoteHttp.releaseRemoteControlViaHttp(
deviceId: deviceId,
type: type,
);
} catch (e) {
debugPrint('❌ [RemoteControl] 释放控制权异常: $e');
_logger.logWithLevel(
'❌ [RemoteControl] 释放控制权异常: $e',
level: 'ERROR',
shouldLog: true,
);
return false;
}
}
}