分区相关
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user