Restructure into DiffWheel/MultiWheel folders, add DiffWheelC and MultiWheelC with MovementTests, FleetRemote multi-vehicle manual sync, deploy templates, and documentation. Update dependency paths to D:\MDCS\Release and mirror motor feedback in MotorRoutine for simulation. Co-authored-by: Cursor <cursoragent@cursor.com>
184 lines
6.3 KiB
C#
184 lines
6.3 KiB
C#
using System;
|
||
using System.IO;
|
||
using CartActivator;
|
||
using CycleGUI;
|
||
using CycleGUI.API;
|
||
using Medulla.Types;
|
||
|
||
namespace MultiWheelM;
|
||
|
||
public partial class CartDefinition
|
||
{
|
||
private bool _fleetRemoteActive;
|
||
private DateTime _fleetDiagLastStick = DateTime.MinValue;
|
||
|
||
// 写盘诊断日志(Medulla 侧)。文件落在 Medulla 工作目录,便于复现后离线分析。
|
||
// 每次进程启动清空一次(_fleetDiagInit),同一次运行内追加,避免跨多次运行累积。
|
||
private static bool _fleetDiagInit;
|
||
private void FleetDiag(string msg)
|
||
{
|
||
try
|
||
{
|
||
const string path = "fleet_diag_medulla.log";
|
||
var line = $"{DateTime.Now:HH:mm:ss.fff} car{CarNum} {msg}{Environment.NewLine}";
|
||
if (!_fleetDiagInit)
|
||
{
|
||
_fleetDiagInit = true;
|
||
File.WriteAllText(path,
|
||
$"=== session start {DateTime.Now:yyyy-MM-dd HH:mm:ss} ==={Environment.NewLine}" + line);
|
||
}
|
||
else
|
||
File.AppendAllText(path, line);
|
||
}
|
||
catch
|
||
{
|
||
// 诊断日志失败不影响主流程
|
||
}
|
||
}
|
||
|
||
private void ZeroFleetManualFields()
|
||
{
|
||
MultiVehicleManualEnabled = false;
|
||
MultiVehicleManualMode = 0;
|
||
MultiVehicleManualVx = 0;
|
||
MultiVehicleManualVy = 0;
|
||
MultiVehicleManualVth = 0;
|
||
}
|
||
|
||
[IOObjectUtility]
|
||
public void FleetRemote()
|
||
{
|
||
if (_fleetRemoteActive)
|
||
{
|
||
GUI.GetPanelInstancingObject(this)?.BringToFront();
|
||
return;
|
||
}
|
||
|
||
_fleetRemoteActive = true;
|
||
var fleetOn = false;
|
||
// 速度比例 0~1,作用于摇杆输出的线速度与转向角。
|
||
var speedRatio = 1f;
|
||
UseGesture? manip = null;
|
||
|
||
void CleanupFleetRemote()
|
||
{
|
||
manip?.End();
|
||
manip = null;
|
||
fleetOn = false;
|
||
ZeroFleetManualFields();
|
||
_fleetRemoteActive = false;
|
||
}
|
||
|
||
manip = new UseGesture();
|
||
|
||
manip.AddWidget(new UseGesture.ToggleWidget
|
||
{
|
||
name = "fleet_enable",
|
||
text = "车队联动",
|
||
position = "12.5%+10px, 75%+10px",
|
||
size = "25%-10px, 12.5%-10px",
|
||
OnValue = b =>
|
||
{
|
||
fleetOn = b;
|
||
MultiVehicleManualEnabled = b;
|
||
MultiVehicleManualMode = 0;
|
||
if (!b)
|
||
ZeroFleetManualFields();
|
||
FleetDiag($"TOGGLE fleetOn={b} -> ManualEnabled={MultiVehicleManualEnabled}");
|
||
}
|
||
});
|
||
|
||
manip.AddWidget(new UseGesture.ThrottleWidget
|
||
{
|
||
name = "fleet_speed_ratio",
|
||
text = "速度比例",
|
||
position = "50%+10px, 75%+10px",
|
||
size = "50%-10px, 12.5%-10px",
|
||
bounceBack = false,
|
||
OnValue = (val, _) => speedRatio = Math.Clamp(val, 0, 1)
|
||
});
|
||
|
||
manip.AddWidget(new UseGesture.ButtonWidget
|
||
{
|
||
name = "fleet_stop",
|
||
text = "急停/归零",
|
||
position = "25%+10px, 87.5%+10px",
|
||
size = "25%-10px, 12.5%-10px",
|
||
OnPressed = _ =>
|
||
{
|
||
MultiVehicleManualVx = 0;
|
||
MultiVehicleManualVy = 0;
|
||
MultiVehicleManualVth = 0;
|
||
FleetDiag("BUTTON stop/zero");
|
||
}
|
||
});
|
||
|
||
manip.AddWidget(new UseGesture.StickWidget
|
||
{
|
||
name = "fleet_stick",
|
||
text = "速度摇杆",
|
||
position = "37.5%+10px, 25%+10px",
|
||
size = "37.5%-10px, 37.5%-10px",
|
||
bounceBack = true,
|
||
keyboard = "Up,Down,Left,Right",
|
||
joystick = "Axis0,Axis1",
|
||
OnValue = (pos, manipulating) =>
|
||
{
|
||
if (!fleetOn)
|
||
{
|
||
MultiVehicleManualVx = 0;
|
||
MultiVehicleManualVy = 0;
|
||
MultiVehicleManualVth = 0;
|
||
if ((DateTime.Now - _fleetDiagLastStick).TotalMilliseconds >= 200)
|
||
{
|
||
_fleetDiagLastStick = DateTime.Now;
|
||
FleetDiag($"STICK(ignored,fleetOff) pos=({pos.X:0.00},{pos.Y:0.00}) manip={manipulating}");
|
||
}
|
||
return;
|
||
}
|
||
|
||
// pos.Y(前后) → 车队线速度(m/s);pos.X(左右) → 车队转向角(deg)。松手 bounceBack 自动回零。
|
||
var ratio = Math.Clamp(speedRatio, 0, 1);
|
||
MultiVehicleManualVx = Math.Clamp(pos.Y, -1, 1) * MaxManualSpeed * ratio;
|
||
MultiVehicleManualVy = 0;
|
||
MultiVehicleManualVth = Math.Clamp(pos.X, -1, 1) * MaxManualAngularSpeed * ratio;
|
||
|
||
if ((DateTime.Now - _fleetDiagLastStick).TotalMilliseconds >= 200)
|
||
{
|
||
_fleetDiagLastStick = DateTime.Now;
|
||
FleetDiag($"STICK pos=({pos.X:0.00},{pos.Y:0.00}) manip={manipulating} ratio={ratio:0.00} " +
|
||
$"maxV={MaxManualSpeed:0.00} maxW={MaxManualAngularSpeed:0.0} " +
|
||
$"-> Vx={MultiVehicleManualVx:0.000} Vth={MultiVehicleManualVth:0.0} en={MultiVehicleManualEnabled}");
|
||
}
|
||
}
|
||
});
|
||
|
||
manip.ChangeState(new SetAppearance { drawGuizmo = false });
|
||
manip.Start();
|
||
|
||
GUI.PromptOrBringToFront(pb =>
|
||
{
|
||
if (pb.Closing())
|
||
{
|
||
CleanupFleetRemote();
|
||
pb.Panel.Exit();
|
||
return;
|
||
}
|
||
|
||
pb.Panel.TopMost(true)
|
||
.SetDefaultDocking(Panel.Docking.None)
|
||
.ShowTitle("车队联动遥控")
|
||
.InitSize(320, 160)
|
||
.InitPos(false, -32, 32, 1, 0, 1, 0);
|
||
|
||
pb.SeparatorText("状态");
|
||
pb.Label($"车队联动: {MultiVehicleManualEnabled}");
|
||
pb.Label($"速度比例: {speedRatio:0.00}");
|
||
pb.Label($"Vx={MultiVehicleManualVx:0.000} m/s, Vth={MultiVehicleManualVth:0.0} deg/s");
|
||
pb.Label($"优先级: {CartActivator.CartDefinition.currentPriority} ({CartActivator.CartDefinition.currentPriorityDesc})");
|
||
pb.Label("请勿同时打开「手动控制」面板");
|
||
pb.Panel.Repaint();
|
||
}, instancingObject: this);
|
||
}
|
||
}
|