diff --git a/MedullaAdapter/build/Medulla/plugins/CommonUsage.dll b/MedullaAdapter/build/Medulla/plugins/CommonUsage.dll index 93900a2..82a8d1d 100644 Binary files a/MedullaAdapter/build/Medulla/plugins/CommonUsage.dll and b/MedullaAdapter/build/Medulla/plugins/CommonUsage.dll differ diff --git a/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.dll b/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.dll index 1713d12..b225c77 100644 Binary files a/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.dll and b/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.dll differ diff --git a/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.pdb b/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.pdb index 3ff5098..f755db3 100644 Binary files a/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.pdb and b/MedullaAdapter/build/Medulla/plugins/MedullaAdapter.pdb differ diff --git a/MultiWheelC/Control/Execution/ParkingGeometricController.cs b/MultiWheelC/Control/Execution/ParkingGeometricController.cs index ba123b3..b17c559 100644 --- a/MultiWheelC/Control/Execution/ParkingGeometricController.cs +++ b/MultiWheelC/Control/Execution/ParkingGeometricController.cs @@ -47,11 +47,11 @@ namespace MultiWheelC.Control.Execution ILongitudinalController longitudinalController, GcpCommandAllocator gcpAllocator, GcpCommandExecutor commandExecutor, - double finishDistanceMeters = 0.03, + double finishDistanceMeters = 0.04, double finishSpeedMetersPerSecond = 0.02, double finishHeadingToleranceRadians = 3.0 * Math.PI / 180.0, - double maximumDistanceToTrajectoryMeters = 0.50) + double maximumDistanceToTrajectoryMeters = 0.30) { _stateProvider = stateProvider ?? throw new ArgumentNullException( diff --git a/MultiWheelC/Control/Lateral/StanleyLateralController.cs b/MultiWheelC/Control/Lateral/StanleyLateralController.cs index 26c64e1..9c281a7 100644 --- a/MultiWheelC/Control/Lateral/StanleyLateralController.cs +++ b/MultiWheelC/Control/Lateral/StanleyLateralController.cs @@ -73,7 +73,7 @@ namespace MultiWheelC.Control.Lateral public double MinimumSpeedMetersPerSecond { get; } /// - /// 获取是否优先使用Detour估算的实际纵向速度计算横向修正。 + /// 获取是否优先使用当前状态源提供的实际纵向速度计算横向修正。 /// public bool UseActualSpeedForGain { get; } diff --git a/MultiWheelC/Experiments/CompositeMotionPlanTests.cs b/MultiWheelC/Experiments/CompositeMotionPlanTests.cs index d022c73..715b5e8 100644 --- a/MultiWheelC/Experiments/CompositeMotionPlanTests.cs +++ b/MultiWheelC/Experiments/CompositeMotionPlanTests.cs @@ -16,7 +16,7 @@ namespace MultiWheelC /// /// 测试平滑左转轨迹、停车原地左转90°和再次直行的组合运动执行过程。 /// - [MovementTest(name = "新版控制器:曲线-停车自转-直线组合测试")] + [MovementTest(name = "新版控制器:直线-圆弧-折线组合测试")] public sealed class CompositeStopTurnGoTest : MovementTest { private const float MillimetersPerMeter = 1000f; @@ -52,6 +52,13 @@ namespace MultiWheelC return; } + if (!TrajectoryExperimentInput + .TryReadLateralOffsetMeters( + out var lateralOffsetMeters)) + { + return; + } + if (!MovementTestPreparation.AreWheelsForward()) { return; @@ -65,19 +72,30 @@ namespace MultiWheelC return; } - var stateProvider = new DetourVehicleStateProvider(); - if (!stateProvider.TryGetState(out var initialState)) + var detourStateProvider = + new DetourVehicleStateProvider(); + if (!detourStateProvider.TryGetState( + out var initialState)) { Console.WriteLine( "无法读取组合运动起点位姿:" + - stateProvider.LastFailureReason); + detourStateProvider.LastFailureReason); return; } + var stateProvider = + new WheelFeedbackVehicleStateProvider( + detourStateProvider, + chassis); + + var planStartPose = + TrajectoryExperimentInput.OffsetPoseLaterally( + initialState.PoseInWorld, + lateralOffsetMeters); var firstTrajectory = TestTrajectoryFactory .CreateStraightSmoothLeftTurnStraight( - initialState.PoseInWorld, + planStartPose, StraightLengthMeters, TurnRadiusMeters, AngleMath.DegreesToRadians( @@ -133,7 +151,9 @@ namespace MultiWheelC _recorder = new TrackingExperimentRecorder( controllerName: "NewStanleyPidComposite", trajectoryName: - "SmoothTurnStopRotateStraight", + TrajectoryExperimentInput.BuildTrajectoryName( + "SmoothTurnStopRotateStraight", + lateralOffsetMeters), trialNumber: TrialNumber, referenceStart: ToMillimeterVector( firstTrajectory.StartPoint.PoseInWorld), @@ -145,7 +165,8 @@ namespace MultiWheelC referenceAccelerationMetersPerSecondSquared: (float)AccelerationMetersPerSecondSquared, referenceDecelerationMetersPerSecondSquared: - (float)DecelerationMetersPerSecondSquared); + (float)DecelerationMetersPerSecondSquared, + diagnosticChassis: chassis); _recorder.Start(); var controlPointRadiusMeters = @@ -157,6 +178,7 @@ namespace MultiWheelC StateProvider = stateProvider, ConfigureTrackingMovement = tracking => { + tracking.StanleyUsesActualSpeed = true; tracking.MaximumCommandSpeedMetersPerSecond = StraightMaximumSpeedMetersPerSecond; tracking @@ -166,6 +188,7 @@ namespace MultiWheelC SegmentStarted = (index, segment) => { _recorder?.ClearControlReference(); + _recorder?.ClearGcpCommand(); _recorder?.UpdateCommand(0f, 0f); Console.WriteLine( $"组合运动开始第{index + 1}段:" + @@ -174,7 +197,8 @@ namespace MultiWheelC TrackingCycleObserver = (index, controller) => RecordTrackingCycle( controller, - controlPointRadiusMeters), + controlPointRadiusMeters, + stateProvider), RotationCommandObserver = (index, omega) => _recorder?.UpdateCommand( 0f, @@ -276,7 +300,8 @@ namespace MultiWheelC /// private void RecordTrackingCycle( ParkingGeometricController controller, - double controlPointRadiusMeters) + double controlPointRadiusMeters, + WheelFeedbackVehicleStateProvider stateProvider) { if (controller.LastVehicleState.HasValue) { @@ -284,6 +309,21 @@ namespace MultiWheelC controller.LastVehicleState.Value); } + if (stateProvider.TryGetLatestVelocityDiagnostics( + out var detourBodyVx, + out var detourVelocityValid, + out var rawWheelBodyVx, + out var filteredWheelBodyVx, + out var wheelVelocityValid)) + { + _recorder?.UpdateVelocityDiagnostics( + detourBodyVx, + detourVelocityValid, + rawWheelBodyVx, + filteredWheelBodyVx, + wheelVelocityValid); + } + if (!controller.LastCommand.HasValue) { return; @@ -305,6 +345,9 @@ namespace MultiWheelC } var command = controller.LastCommand.Value; + _recorder?.UpdateGcpCommand( + command.FrontAngleRadians, + command.RearAngleRadians); var curvaturePerMeter = Math.Tan( command.FrontAngleRadians) / controlPointRadiusMeters; diff --git a/MultiWheelC/Experiments/NewControllerTrackingTests.cs b/MultiWheelC/Experiments/NewControllerTrackingTests.cs index d9269d3..ceab046 100644 --- a/MultiWheelC/Experiments/NewControllerTrackingTests.cs +++ b/MultiWheelC/Experiments/NewControllerTrackingTests.cs @@ -1,5 +1,6 @@ using System; using System.Drawing; +using System.Globalization; using System.Numerics; using System.Threading; using ClumsyCore; @@ -13,6 +14,82 @@ using MyParking.Shared; namespace MultiWheelC { + /// + /// 统一读取轨迹实验的有符号横向偏移,并将车体局部偏移转换到世界坐标系。 + /// + internal static class TrajectoryExperimentInput + { + private const double MaximumOffsetCentimeters = 30.0; + + /// + /// 从Clumsy输入框读取车体左正右负的横向偏移,单位转换为m。 + /// + public static bool TryReadLateralOffsetMeters( + out double lateralOffsetMeters) + { + lateralOffsetMeters = 0.0; + var input = UI.GetInput( + "输入轨迹横向偏移(cm,左正右负,范围-30~30):"); + var parsed = double.TryParse( + input, + NumberStyles.Float, + CultureInfo.CurrentCulture, + out var offsetCentimeters) || + double.TryParse( + input, + NumberStyles.Float, + CultureInfo.InvariantCulture, + out offsetCentimeters); + + if (!parsed || + double.IsNaN(offsetCentimeters) || + double.IsInfinity(offsetCentimeters) || + Math.Abs(offsetCentimeters) > + MaximumOffsetCentimeters) + { + Console.WriteLine( + "轨迹横向偏移必须是-30~30cm之间的有限数值,测试未启动。"); + return false; + } + + lateralOffsetMeters = + offsetCentimeters / 100.0; + return true; + } + + /// + /// 沿初始车体左方向平移参考轨迹起点,同时保持世界坐标航向不变。 + /// + public static Pose2D OffsetPoseLaterally( + Pose2D poseInWorld, + double lateralOffsetMeters) + { + var yawRadians = poseInWorld.YawRadians; + return new Pose2D( + poseInWorld.XMeters - + Math.Sin(yawRadians) * + lateralOffsetMeters, + poseInWorld.YMeters + + Math.Cos(yawRadians) * + lateralOffsetMeters, + yawRadians); + } + + /// + /// 生成带毫米偏移标识的实验轨迹名称。 + /// + public static string BuildTrajectoryName( + string baseName, + double lateralOffsetMeters) + { + return baseName + + "_Offset" + + (lateralOffsetMeters * 1000.0) + .ToString("+0;-0;0", CultureInfo.InvariantCulture) + + "mm"; + } + } + /// /// 从当前Detour位姿开始执行新版控制器4m直线跟踪并保存实验数据。 /// @@ -27,7 +104,7 @@ namespace MultiWheelC private DriveTask _task; private TrackingExperimentRecorder _recorder; - private DetourVehicleStateProvider _stateProvider; + private IVehicleStateProvider _stateProvider; /// /// 获取或设置本次测试编号,用于区分重复实验CSV。 @@ -37,7 +114,7 @@ namespace MultiWheelC /// /// 获取或设置4m直线的巡航参考速度,单位为m/s。 /// - public double CruiseSpeedMetersPerSecond = 0.30; + public double CruiseSpeedMetersPerSecond = 0.40; /// /// 获取或设置参考速度加速度,单位为m/s²。 @@ -66,6 +143,13 @@ namespace MultiWheelC return; } + if (!TrajectoryExperimentInput + .TryReadLateralOffsetMeters( + out var lateralOffsetMeters)) + { + return; + } + if (!MovementTestPreparation.AreWheelsForward()) { return; @@ -80,21 +164,30 @@ namespace MultiWheelC return; } - _stateProvider = + var detourStateProvider = new DetourVehicleStateProvider(); - if (!_stateProvider.TryGetState( + if (!detourStateProvider.TryGetState( out var initialState)) { Console.WriteLine( "无法读取有效Detour起点位姿:" + - _stateProvider.LastFailureReason); + detourStateProvider.LastFailureReason); _stateProvider = null; return; } + _stateProvider = + new WheelFeedbackVehicleStateProvider( + detourStateProvider, + chassis); + + var trajectoryStartPose = + TrajectoryExperimentInput.OffsetPoseLaterally( + initialState.PoseInWorld, + lateralOffsetMeters); var trajectory = TestTrajectoryFactory.CreateStraight4Meters( - initialState.PoseInWorld, + trajectoryStartPose, CruiseSpeedMetersPerSecond, AccelerationMetersPerSecondSquared, DecelerationMetersPerSecondSquared, @@ -109,7 +202,10 @@ namespace MultiWheelC var recorder = new TrackingExperimentRecorder( controllerName: "NewStanleyPid", - trajectoryName: "ProfiledStraight4m", + trajectoryName: + TrajectoryExperimentInput.BuildTrajectoryName( + "ProfiledStraight4m", + lateralOffsetMeters), trialNumber: TrialNumber, referenceStart: referenceStart, referenceEnd: referenceEnd, @@ -119,7 +215,8 @@ namespace MultiWheelC referenceAccelerationMetersPerSecondSquared: (float)AccelerationMetersPerSecondSquared, referenceDecelerationMetersPerSecondSquared: - (float)DecelerationMetersPerSecondSquared); + (float)DecelerationMetersPerSecondSquared, + diagnosticChassis: chassis); _recorder = recorder; var controlPointRadiusMeters = @@ -130,12 +227,15 @@ namespace MultiWheelC { Trajectory = trajectory, StateProvider = _stateProvider, + StanleyUsesActualSpeed = true, MaximumCommandSpeedMetersPerSecond = 0.50, CycleObserver = controller => RecordControlCycle( recorder, controller, - controlPointRadiusMeters) + controlPointRadiusMeters, + _stateProvider as + WheelFeedbackVehicleStateProvider) }; recorder.Start(); @@ -239,7 +339,8 @@ namespace MultiWheelC private static void RecordControlCycle( TrackingExperimentRecorder recorder, ParkingGeometricController controller, - double controlPointRadiusMeters) + double controlPointRadiusMeters, + WheelFeedbackVehicleStateProvider stateProvider) { if (controller.LastVehicleState.HasValue) { @@ -247,6 +348,10 @@ namespace MultiWheelC controller.LastVehicleState.Value); } + UpdateVelocityDiagnostics( + recorder, + stateProvider); + if (!controller.LastCommand.HasValue) { return; @@ -267,6 +372,9 @@ namespace MultiWheelC } var command = controller.LastCommand.Value; + recorder.UpdateGcpCommand( + command.FrontAngleRadians, + command.RearAngleRadians); var curvaturePerMeter = Math.Tan( command.FrontAngleRadians) / controlPointRadiusMeters; @@ -279,6 +387,32 @@ namespace MultiWheelC (float)angularSpeedRadiansPerSecond); } + /// + /// 将同一周期的Detour速度和轮速解算速度写入实验记录器。 + /// + private static void UpdateVelocityDiagnostics( + TrackingExperimentRecorder recorder, + WheelFeedbackVehicleStateProvider stateProvider) + { + if (stateProvider == null || + !stateProvider.TryGetLatestVelocityDiagnostics( + out var detourBodyVx, + out var detourVelocityValid, + out var rawWheelBodyVx, + out var filteredWheelBodyVx, + out var wheelVelocityValid)) + { + return; + } + + recorder.UpdateVelocityDiagnostics( + detourBodyVx, + detourVelocityValid, + rawWheelBodyVx, + filteredWheelBodyVx, + wheelVelocityValid); + } + /// /// 将Shared世界坐标系米制位姿转换为Clumsy绘图和旧记录器使用的毫米坐标。 /// @@ -293,6 +427,7 @@ namespace MultiWheelC poseInWorld.YMeters * MillimetersPerMeter)); } + } /// @@ -310,7 +445,7 @@ namespace MultiWheelC private DriveTask _task; private TrackingExperimentRecorder _recorder; - private DetourVehicleStateProvider _stateProvider; + private IVehicleStateProvider _stateProvider; /// /// 获取或设置本次测试编号,用于区分重复实验CSV。 @@ -369,6 +504,13 @@ namespace MultiWheelC return; } + if (!TrajectoryExperimentInput + .TryReadLateralOffsetMeters( + out var lateralOffsetMeters)) + { + return; + } + if (!MovementTestPreparation.AreWheelsForward()) { return; @@ -383,22 +525,31 @@ namespace MultiWheelC return; } - _stateProvider = + var detourStateProvider = new DetourVehicleStateProvider(); - if (!_stateProvider.TryGetState( + if (!detourStateProvider.TryGetState( out var initialState)) { Console.WriteLine( "无法读取有效Detour起点位姿:" + - _stateProvider.LastFailureReason); + detourStateProvider.LastFailureReason); _stateProvider = null; return; } + _stateProvider = + new WheelFeedbackVehicleStateProvider( + detourStateProvider, + chassis); + + var trajectoryStartPose = + TrajectoryExperimentInput.OffsetPoseLaterally( + initialState.PoseInWorld, + lateralOffsetMeters); var trajectory = TestTrajectoryFactory .CreateStraightLeftSemicircleStraight( - initialState.PoseInWorld, + trajectoryStartPose, StraightLengthMeters, TurnRadiusMeters, CurvatureTransitionLengthMeters, @@ -418,7 +569,9 @@ namespace MultiWheelC new TrackingExperimentRecorder( controllerName: "NewStanleyPid", trajectoryName: - "ProfiledStraightSmoothLeftTurnStraight", + TrajectoryExperimentInput.BuildTrajectoryName( + "ProfiledStraightSmoothLeftTurnStraight", + lateralOffsetMeters), trialNumber: TrialNumber, referenceStart: referenceStart, referenceEnd: referenceEnd, @@ -428,7 +581,8 @@ namespace MultiWheelC referenceAccelerationMetersPerSecondSquared: (float)AccelerationMetersPerSecondSquared, referenceDecelerationMetersPerSecondSquared: - (float)DecelerationMetersPerSecondSquared); + (float)DecelerationMetersPerSecondSquared, + diagnosticChassis: chassis); _recorder = recorder; var controlPointRadiusMeters = @@ -439,13 +593,16 @@ namespace MultiWheelC { Trajectory = trajectory, StateProvider = _stateProvider, + StanleyUsesActualSpeed = true, MaximumCommandSpeedMetersPerSecond = StraightMaximumSpeedMetersPerSecond, CycleObserver = controller => RecordControlCycle( recorder, controller, - controlPointRadiusMeters) + controlPointRadiusMeters, + _stateProvider as + WheelFeedbackVehicleStateProvider) }; recorder.Start(); @@ -549,7 +706,8 @@ namespace MultiWheelC private static void RecordControlCycle( TrackingExperimentRecorder recorder, ParkingGeometricController controller, - double controlPointRadiusMeters) + double controlPointRadiusMeters, + WheelFeedbackVehicleStateProvider stateProvider) { if (controller.LastVehicleState.HasValue) { @@ -557,6 +715,10 @@ namespace MultiWheelC controller.LastVehicleState.Value); } + UpdateVelocityDiagnostics( + recorder, + stateProvider); + if (!controller.LastCommand.HasValue) { return; @@ -577,6 +739,9 @@ namespace MultiWheelC } var command = controller.LastCommand.Value; + recorder.UpdateGcpCommand( + command.FrontAngleRadians, + command.RearAngleRadians); var curvaturePerMeter = Math.Tan( command.FrontAngleRadians) / controlPointRadiusMeters; @@ -589,6 +754,32 @@ namespace MultiWheelC (float)angularSpeedRadiansPerSecond); } + /// + /// 将同一周期的Detour速度和轮速解算速度写入实验记录器。 + /// + private static void UpdateVelocityDiagnostics( + TrackingExperimentRecorder recorder, + WheelFeedbackVehicleStateProvider stateProvider) + { + if (stateProvider == null || + !stateProvider.TryGetLatestVelocityDiagnostics( + out var detourBodyVx, + out var detourVelocityValid, + out var rawWheelBodyVx, + out var filteredWheelBodyVx, + out var wheelVelocityValid)) + { + return; + } + + recorder.UpdateVelocityDiagnostics( + detourBodyVx, + detourVelocityValid, + rawWheelBodyVx, + filteredWheelBodyVx, + wheelVelocityValid); + } + /// /// 将Shared世界坐标系米制位姿转换为Clumsy绘图和记录器使用的毫米坐标。 /// diff --git a/MultiWheelC/Experiments/TrackingExperimentRecorder.cs b/MultiWheelC/Experiments/TrackingExperimentRecorder.cs index a09ef46..1bbcc21 100644 --- a/MultiWheelC/Experiments/TrackingExperimentRecorder.cs +++ b/MultiWheelC/Experiments/TrackingExperimentRecorder.cs @@ -7,6 +7,7 @@ using System.IO; using System.Numerics; using System.Text; using System.Threading; +using CommonUsage.Chassis; using MyParking.Shared; using MultiWheelC.StateEstimation; @@ -47,6 +48,28 @@ namespace MultiWheelC public double ControlHeadingErrorRadians; public double ControlDistanceToTrajectoryMeters; public double ControlRemainingDistanceMeters; + + // 并列保存Detour速度与轮速解算速度,避免StateBodyVx的数据来源产生歧义。 + public bool HasVelocityDiagnostics; + public double DetourEstimatedBodyVxMetersPerSecond; + public bool DetourVelocityEstimateValid; + public double WheelFeedbackRawBodyVxMetersPerSecond; + public double WheelFeedbackFilteredBodyVxMetersPerSecond; + public bool WheelFeedbackVelocityEstimateValid; + + // 四舵轮机械角使用deg,前后虚拟GCP命令角使用rad。 + public bool HasSteeringDiagnostics; + public double TargetSteerLeftFrontDegrees; + public double TargetSteerLeftRearDegrees; + public double TargetSteerRightFrontDegrees; + public double TargetSteerRightRearDegrees; + public double ActualSteerLeftFrontDegrees; + public double ActualSteerLeftRearDegrees; + public double ActualSteerRightFrontDegrees; + public double ActualSteerRightRearDegrees; + public bool HasGcpCommand; + public double CommandFrontGcpAngleRadians; + public double CommandRearGcpAngleRadians; } // C层实验工具:统一采集并保存轨迹跟踪实验数据。 @@ -63,6 +86,7 @@ namespace MultiWheelC private readonly float _referenceAccelerationMetersPerSecondSquared; private readonly float _referenceDecelerationMetersPerSecondSquared; private readonly int _sampleIntervalMs; + private readonly MultiWheelChassis _diagnosticChassis; private readonly List _samples = new List(); @@ -97,6 +121,15 @@ namespace MultiWheelC private double _controlHeadingErrorRadians; private double _controlDistanceToTrajectoryMeters; private double _controlRemainingDistanceMeters; + private bool _hasVelocityDiagnostics; + private double _detourEstimatedBodyVxMetersPerSecond; + private bool _detourVelocityEstimateValid; + private double _wheelFeedbackRawBodyVxMetersPerSecond; + private double _wheelFeedbackFilteredBodyVxMetersPerSecond; + private bool _wheelFeedbackVelocityEstimateValid; + private bool _hasGcpCommand; + private double _commandFrontGcpAngleRadians; + private double _commandRearGcpAngleRadians; public TrackingExperimentRecorder( string controllerName, @@ -109,7 +142,8 @@ namespace MultiWheelC int sampleIntervalMs = 50, float referenceMotionFrameYawDegrees = 0f, float referenceAccelerationMetersPerSecondSquared = 0f, - float referenceDecelerationMetersPerSecondSquared = 0f) + float referenceDecelerationMetersPerSecondSquared = 0f, + MultiWheelChassis diagnosticChassis = null) { if (string.IsNullOrWhiteSpace(controllerName)) throw new ArgumentException( @@ -140,6 +174,7 @@ namespace MultiWheelC _referenceDecelerationMetersPerSecondSquared = referenceDecelerationMetersPerSecondSquared; _sampleIntervalMs = sampleIntervalMs; + _diagnosticChassis = diagnosticChassis; } // 保存成功后的CSV绝对路径;尚未保存时为空。 @@ -221,6 +256,60 @@ namespace MultiWheelC } } + /// + /// 保存同一控制周期的Detour纵向速度以及轮速解算的原始和滤波纵向速度。 + /// + public void UpdateVelocityDiagnostics( + double detourEstimatedBodyVxMetersPerSecond, + bool detourVelocityEstimateValid, + double wheelFeedbackRawBodyVxMetersPerSecond, + double wheelFeedbackFilteredBodyVxMetersPerSecond, + bool wheelFeedbackVelocityEstimateValid) + { + lock (_stateSyncRoot) + { + _detourEstimatedBodyVxMetersPerSecond = + detourEstimatedBodyVxMetersPerSecond; + _detourVelocityEstimateValid = + detourVelocityEstimateValid; + _wheelFeedbackRawBodyVxMetersPerSecond = + wheelFeedbackRawBodyVxMetersPerSecond; + _wheelFeedbackFilteredBodyVxMetersPerSecond = + wheelFeedbackFilteredBodyVxMetersPerSecond; + _wheelFeedbackVelocityEstimateValid = + wheelFeedbackVelocityEstimateValid; + _hasVelocityDiagnostics = true; + } + } + + /// + /// 保存经过GCP角速度限制后实际交给底盘的前后虚拟控制点转角。 + /// + public void UpdateGcpCommand( + double frontGcpAngleRadians, + double rearGcpAngleRadians) + { + lock (_stateSyncRoot) + { + _commandFrontGcpAngleRadians = + frontGcpAngleRadians; + _commandRearGcpAngleRadians = + rearGcpAngleRadians; + _hasGcpCommand = true; + } + } + + /// + /// 清除上一轨迹段的GCP命令,避免停车或原地自转阶段沿用旧角度。 + /// + public void ClearGcpCommand() + { + lock (_stateSyncRoot) + { + _hasGcpCommand = false; + } + } + /// /// 保存新版控制器本周期实际使用的轨迹投影、误差和参考速度。 /// @@ -331,6 +420,15 @@ namespace MultiWheelC double controlHeadingErrorRadians; double controlDistanceToTrajectoryMeters; double controlRemainingDistanceMeters; + bool hasVelocityDiagnostics; + double detourEstimatedBodyVxMetersPerSecond; + bool detourVelocityEstimateValid; + double wheelFeedbackRawBodyVxMetersPerSecond; + double wheelFeedbackFilteredBodyVxMetersPerSecond; + bool wheelFeedbackVelocityEstimateValid; + bool hasGcpCommand; + double commandFrontGcpAngleRadians; + double commandRearGcpAngleRadians; lock (_commandSyncRoot) { @@ -382,6 +480,23 @@ namespace MultiWheelC _controlDistanceToTrajectoryMeters; controlRemainingDistanceMeters = _controlRemainingDistanceMeters; + hasVelocityDiagnostics = + _hasVelocityDiagnostics; + detourEstimatedBodyVxMetersPerSecond = + _detourEstimatedBodyVxMetersPerSecond; + detourVelocityEstimateValid = + _detourVelocityEstimateValid; + wheelFeedbackRawBodyVxMetersPerSecond = + _wheelFeedbackRawBodyVxMetersPerSecond; + wheelFeedbackFilteredBodyVxMetersPerSecond = + _wheelFeedbackFilteredBodyVxMetersPerSecond; + wheelFeedbackVelocityEstimateValid = + _wheelFeedbackVelocityEstimateValid; + hasGcpCommand = _hasGcpCommand; + commandFrontGcpAngleRadians = + _commandFrontGcpAngleRadians; + commandRearGcpAngleRadians = + _commandRearGcpAngleRadians; } var sample = new TrackingSample @@ -411,9 +526,28 @@ namespace MultiWheelC ControlDistanceToTrajectoryMeters = controlDistanceToTrajectoryMeters, ControlRemainingDistanceMeters = - controlRemainingDistanceMeters + controlRemainingDistanceMeters, + HasVelocityDiagnostics = + hasVelocityDiagnostics, + DetourEstimatedBodyVxMetersPerSecond = + detourEstimatedBodyVxMetersPerSecond, + DetourVelocityEstimateValid = + detourVelocityEstimateValid, + WheelFeedbackRawBodyVxMetersPerSecond = + wheelFeedbackRawBodyVxMetersPerSecond, + WheelFeedbackFilteredBodyVxMetersPerSecond = + wheelFeedbackFilteredBodyVxMetersPerSecond, + WheelFeedbackVelocityEstimateValid = + wheelFeedbackVelocityEstimateValid, + HasGcpCommand = hasGcpCommand, + CommandFrontGcpAngleRadians = + commandFrontGcpAngleRadians, + CommandRearGcpAngleRadians = + commandRearGcpAngleRadians }; + CaptureSteeringDiagnostics(sample); + if (processedState.HasValue) { var state = processedState.Value; @@ -452,6 +586,90 @@ namespace MultiWheelC } } + /// + /// 按舵轮物理安装位置记录四轮目标角和实际反馈角。 + /// + private void CaptureSteeringDiagnostics( + TrackingSample sample) + { + if (_diagnosticChassis == null) + { + return; + } + +#pragma warning disable CS0612, CS0618 + var wheels = _diagnosticChassis.GetSteerWheels(); +#pragma warning restore CS0612, CS0618 + + var leftFront = FindWheel( + wheels, + requireFront: true, + requireLeft: true); + var leftRear = FindWheel( + wheels, + requireFront: false, + requireLeft: true); + var rightFront = FindWheel( + wheels, + requireFront: true, + requireLeft: false); + var rightRear = FindWheel( + wheels, + requireFront: false, + requireLeft: false); + + if (leftFront == null || + leftRear == null || + rightFront == null || + rightRear == null) + { + return; + } + + sample.HasSteeringDiagnostics = true; + sample.TargetSteerLeftFrontDegrees = + leftFront.GetSendAngle(); + sample.TargetSteerLeftRearDegrees = + leftRear.GetSendAngle(); + sample.TargetSteerRightFrontDegrees = + rightFront.GetSendAngle(); + sample.TargetSteerRightRearDegrees = + rightRear.GetSendAngle(); + sample.ActualSteerLeftFrontDegrees = + leftFront.ReadAngle(); + sample.ActualSteerLeftRearDegrees = + leftRear.ReadAngle(); + sample.ActualSteerRightFrontDegrees = + rightFront.ReadAngle(); + sample.ActualSteerRightRearDegrees = + rightRear.ReadAngle(); + } + + /// + /// 根据车体X向前、Y向左的物理坐标查找指定象限中的舵轮。 + /// + private static SteerWheel FindWheel( + IReadOnlyList wheels, + bool requireFront, + bool requireLeft) + { + foreach (var wheel in wheels) + { + var isFront = + wheel.PhysicalPosition.X >= 0f; + var isLeft = + wheel.PhysicalPosition.Y >= 0f; + + if (isFront == requireFront && + isLeft == requireLeft) + { + return wheel; + } + } + + return null; + } + // 将内存中的采样数据写入CSV。 private void SaveCsv() { @@ -523,7 +741,25 @@ namespace MultiWheelC "ControlLateralErrorMeters," + "ControlHeadingErrorRadians," + "ControlDistanceToTrajectoryMeters," + - "ControlRemainingDistanceMeters"); + "ControlRemainingDistanceMeters," + + "HasVelocityDiagnostics," + + "DetourEstimatedBodyVxMetersPerSecond," + + "DetourVelocityEstimateValid," + + "WheelFeedbackRawBodyVxMetersPerSecond," + + "WheelFeedbackFilteredBodyVxMetersPerSecond," + + "WheelFeedbackVelocityEstimateValid," + + "HasSteeringDiagnostics," + + "TargetSteerLeftFrontDegrees," + + "TargetSteerLeftRearDegrees," + + "TargetSteerRightFrontDegrees," + + "TargetSteerRightRearDegrees," + + "ActualSteerLeftFrontDegrees," + + "ActualSteerLeftRearDegrees," + + "ActualSteerRightFrontDegrees," + + "ActualSteerRightRearDegrees," + + "HasGcpCommand," + + "CommandFrontGcpAngleRadians," + + "CommandRearGcpAngleRadians"); foreach (var sample in snapshot) { @@ -610,7 +846,65 @@ namespace MultiWheelC sample.ControlDistanceToTrajectoryMeters), FormatOptional( sample.HasControlReference, - sample.ControlRemainingDistanceMeters))); + sample.ControlRemainingDistanceMeters), + sample.HasVelocityDiagnostics + ? "1" + : "0", + FormatOptional( + sample.HasVelocityDiagnostics, + sample.DetourEstimatedBodyVxMetersPerSecond), + sample.HasVelocityDiagnostics + ? sample.DetourVelocityEstimateValid + ? "1" + : "0" + : string.Empty, + FormatOptional( + sample.HasVelocityDiagnostics, + sample.WheelFeedbackRawBodyVxMetersPerSecond), + FormatOptional( + sample.HasVelocityDiagnostics, + sample.WheelFeedbackFilteredBodyVxMetersPerSecond), + sample.HasVelocityDiagnostics + ? sample.WheelFeedbackVelocityEstimateValid + ? "1" + : "0" + : string.Empty, + sample.HasSteeringDiagnostics + ? "1" + : "0", + FormatOptional( + sample.HasSteeringDiagnostics, + sample.TargetSteerLeftFrontDegrees), + FormatOptional( + sample.HasSteeringDiagnostics, + sample.TargetSteerLeftRearDegrees), + FormatOptional( + sample.HasSteeringDiagnostics, + sample.TargetSteerRightFrontDegrees), + FormatOptional( + sample.HasSteeringDiagnostics, + sample.TargetSteerRightRearDegrees), + FormatOptional( + sample.HasSteeringDiagnostics, + sample.ActualSteerLeftFrontDegrees), + FormatOptional( + sample.HasSteeringDiagnostics, + sample.ActualSteerLeftRearDegrees), + FormatOptional( + sample.HasSteeringDiagnostics, + sample.ActualSteerRightFrontDegrees), + FormatOptional( + sample.HasSteeringDiagnostics, + sample.ActualSteerRightRearDegrees), + sample.HasGcpCommand + ? "1" + : "0", + FormatOptional( + sample.HasGcpCommand, + sample.CommandFrontGcpAngleRadians), + FormatOptional( + sample.HasGcpCommand, + sample.CommandRearGcpAngleRadians))); } } } diff --git a/MultiWheelC/Movements/TrajectoryTrackingMovement.cs b/MultiWheelC/Movements/TrajectoryTrackingMovement.cs index 4ec7419..ad6f2cb 100644 --- a/MultiWheelC/Movements/TrajectoryTrackingMovement.cs +++ b/MultiWheelC/Movements/TrajectoryTrackingMovement.cs @@ -51,7 +51,7 @@ namespace MultiWheelC public double StanleyMinimumSpeedMetersPerSecond = 0.15; /// - /// 获取或设置Stanley是否优先使用Detour估算的实际速度。 + /// 获取或设置Stanley是否优先使用当前状态源提供的实际纵向速度。 /// public bool StanleyUsesActualSpeed = true; @@ -129,7 +129,7 @@ namespace MultiWheelC /// /// 车辆允许偏离参考轨迹的最大欧氏距离,单位为m。 /// - public double MaximumDistanceToTrajectoryMeters = 0.50; + public double MaximumDistanceToTrajectoryMeters = 0.30; /// /// 单次轨迹动作允许的最长执行时间,单位为s。 diff --git a/MultiWheelC/PilotConfig.cs b/MultiWheelC/PilotConfig.cs index cff2ed1..f1ae1c2 100644 --- a/MultiWheelC/PilotConfig.cs +++ b/MultiWheelC/PilotConfig.cs @@ -38,7 +38,7 @@ public class PilotConfig : MultiWheelPilotConfig #region 单车-临时 [FieldMember(desc = "原地旋转Kp")] - public float InPlaceRotateKp = 1.1f; + public float InPlaceRotateKp = 1.05f; [FieldMember(desc = "原地旋转Ki")] public float InPlaceRotateKi = 0f; diff --git a/MultiWheelC/build/Clumsy/CommonUsage.dll b/MultiWheelC/build/Clumsy/CommonUsage.dll index 93900a2..82a8d1d 100644 Binary files a/MultiWheelC/build/Clumsy/CommonUsage.dll and b/MultiWheelC/build/Clumsy/CommonUsage.dll differ diff --git a/MultiWheelC/build/Clumsy/MultiWheelC.dll b/MultiWheelC/build/Clumsy/MultiWheelC.dll index 5584b5b..962de2a 100644 Binary files a/MultiWheelC/build/Clumsy/MultiWheelC.dll and b/MultiWheelC/build/Clumsy/MultiWheelC.dll differ diff --git a/MultiWheelC/build/Clumsy/MultiWheelC.pdb b/MultiWheelC/build/Clumsy/MultiWheelC.pdb index d7fa53b..9369a82 100644 Binary files a/MultiWheelC/build/Clumsy/MultiWheelC.pdb and b/MultiWheelC/build/Clumsy/MultiWheelC.pdb differ diff --git a/data_process/新版前后角解耦控制器测试处理/4m直线0.3/记录.txt b/data_process/新版前后角解耦控制器测试处理/4m直线0.3/记录.txt deleted file mode 100644 index a94b318..0000000 --- a/data_process/新版前后角解耦控制器测试处理/4m直线0.3/记录.txt +++ /dev/null @@ -1,17 +0,0 @@ -第一次: -ok - -第二次: -: * (Exception):DriveTask failed, msg=车辆已在终点零速参考处停稳,但终点精度不满足要求:位置误差=0.031m,航向误差=0.03°。, stack: - at ClumsyCore.DriveTask.Wait() in D:\MDCS\Source\Core\Clumsy\ClumsyCore\DriveTask.cs:line 205 - at MultiWheelC.NewControllerStraight4mTest.Test() in D:\Users\Desktop\入职培训\停车机器人\MyParking\MultiWheelC\Experiments\NewControllerTrackingTests.cs:line 146 - at ClumsyLite.ClumsyLiteUI.<>c__DisplayClass19_1.b__21() in D:\MDCS\Source\Core\Clumsy\ClumsyLite\ClumsyLiteUI.cs:line 592 - - *p.InnerException * (InvalidOperationException):车辆已在终点零速参考处停稳,但终点精度不满足要求:位置误差=0.031m,航向误差=0.03°。, stack: - at MultiWheelC.TrajectoryTrackingMovement.Get()+MoveNext() in D:\Users\Desktop\入职培训\停车机器人\MyParking\MultiWheelC\Movements\TrajectoryTrackingMovement.cs:line 257 - at ClumsyCore.DriveTask.<>c__DisplayClass9_0.<.ctor>b__2() in D:\MDCS\Source\Core\Clumsy\ClumsyCore\DriveTask.cs:line 112 - - - -第三次: -ok \ No newline at end of file diff --git a/data_process/新版控制器轨迹测试处理/plot_new_controller_experiment.py b/data_process/新版控制器轨迹测试处理/plot_new_controller_experiment.py index 7f37601..c7a7fcb 100644 --- a/data_process/新版控制器轨迹测试处理/plot_new_controller_experiment.py +++ b/data_process/新版控制器轨迹测试处理/plot_new_controller_experiment.py @@ -1,4 +1,4 @@ -"""为新版4m直线控制器实验CSV生成轨迹、横向/航向误差和速度响应图。""" +"""为新版控制器实验CSV生成包含轨迹、误差、速度和转角的六子图总图。""" from __future__ import annotations @@ -293,13 +293,87 @@ def load_experiment(csv_path: Path) -> dict[str, object]: ) if np.any(has_control_reference): reference_speed[~has_control_reference] = np.nan - actual_speed = numeric_column(frame, "StateBodyVxMetersPerSecond") + state_body_vx = numeric_column(frame, "StateBodyVxMetersPerSecond") velocity_valid = ( numeric_column(frame, "StateVelocityEstimateValid", 0.0) > 0.5 ) - actual_speed[~velocity_valid] = np.nan + state_body_vx[~velocity_valid] = np.nan + has_velocity_diagnostics = ( + numeric_column(frame, "HasVelocityDiagnostics", 0.0) > 0.5 + ) + detour_speed = numeric_column( + frame, + "DetourEstimatedBodyVxMetersPerSecond", + ) + detour_speed_valid = ( + has_velocity_diagnostics + & ( + numeric_column( + frame, + "DetourVelocityEstimateValid", + 0.0, + ) + > 0.5 + ) + ) + detour_speed[~detour_speed_valid] = np.nan + wheel_raw_speed = numeric_column( + frame, + "WheelFeedbackRawBodyVxMetersPerSecond", + ) + wheel_filtered_speed = numeric_column( + frame, + "WheelFeedbackFilteredBodyVxMetersPerSecond", + ) + wheel_speed_valid = ( + has_velocity_diagnostics + & ( + numeric_column( + frame, + "WheelFeedbackVelocityEstimateValid", + 0.0, + ) + > 0.5 + ) + ) + wheel_raw_speed[~wheel_speed_valid] = np.nan + wheel_filtered_speed[~wheel_speed_valid] = np.nan + actual_speed = np.where( + np.isfinite(wheel_filtered_speed), + wheel_filtered_speed, + state_body_vx, + ) command_speed = numeric_column(frame, "CommandSpeed") + has_steering_diagnostics = ( + numeric_column(frame, "HasSteeringDiagnostics", 0.0) > 0.5 + ) + steering_angles_degrees = {} + for wheel_name in ( + "LeftFront", + "LeftRear", + "RightFront", + "RightRear", + ): + values = numeric_column( + frame, + f"ActualSteer{wheel_name}Degrees", + ) + values[~has_steering_diagnostics] = np.nan + steering_angles_degrees[wheel_name] = values + + has_gcp_command = ( + numeric_column(frame, "HasGcpCommand", 0.0) > 0.5 + ) + front_gcp_degrees = np.rad2deg( + numeric_column(frame, "CommandFrontGcpAngleRadians") + ) + rear_gcp_degrees = np.rad2deg( + numeric_column(frame, "CommandRearGcpAngleRadians") + ) + front_gcp_degrees[~has_gcp_command] = np.nan + rear_gcp_degrees[~has_gcp_command] = np.nan + return { "frame": frame, "time": time_seconds, @@ -316,7 +390,13 @@ def load_experiment(csv_path: Path) -> dict[str, object]: "heading_error": heading_error, "reference_speed": reference_speed, "actual_speed": actual_speed, + "detour_speed": detour_speed, + "wheel_raw_speed": wheel_raw_speed, + "wheel_filtered_speed": wheel_filtered_speed, "command_speed": command_speed, + "steering_angles_degrees": steering_angles_degrees, + "front_gcp_degrees": front_gcp_degrees, + "rear_gcp_degrees": rear_gcp_degrees, "controller_name": first_text( frame, "ControllerName", @@ -342,7 +422,7 @@ def save_figure( show: bool, ) -> None: """保存并关闭一张实验图。""" - fig.tight_layout() + fig.tight_layout(rect=(0.0, 0.0, 1.0, 0.97)) fig.savefig(destination, dpi=300, bbox_inches="tight") if show: plt.show() @@ -354,14 +434,16 @@ def plot_experiment( output_directory: Path, show: bool, ) -> list[Path]: - """为单份新版控制器CSV生成四类对比图。""" + """为单份新版控制器CSV生成一张包含六个子图的实验总图。""" data = load_experiment(csv_path) output_directory.mkdir(parents=True, exist_ok=True) title = f"{data['controller_name']} - {data['trajectory_name']}" - destinations: list[Path] = [] + fig, axes = plt.subplots(3, 2, figsize=(18.0, 16.0)) + fig.suptitle(title, fontsize=16) + # 1. 期望轨迹与实际轨迹。 + axis = axes[0, 0] valid_position = data["valid_position"] - fig, axis = plt.subplots(figsize=(9.0, 6.5)) valid_reference_position = data["valid_reference_position"] if np.count_nonzero(valid_reference_position) >= 2: axis.plot( @@ -390,47 +472,42 @@ def plot_experiment( axis.set_aspect("equal", adjustable="box") axis.set_xlabel("世界坐标X / m") axis.set_ylabel("世界坐标Y / m") - axis.set_title(f"期望轨迹与实际轨迹对比\n{title}") + axis.set_title("期望轨迹与实际轨迹对比") axis.grid(True, alpha=0.3) - axis.legend() - destination = output_directory / f"{csv_path.stem}_trajectory.png" - save_figure(fig, destination, show) - destinations.append(destination) + axis.legend(fontsize=8) + # 2. 横向误差。 lateral_mm = data["lateral_error"] * 1000.0 lateral_rmse_mm = finite_rmse(lateral_mm) - fig, axis = plt.subplots(figsize=(10.0, 5.5)) + axis = axes[0, 1] axis.plot(data["time"], lateral_mm, linewidth=1.5) axis.axhline(0.0, color="black", linewidth=0.8) axis.set_xlabel("时间 / s") axis.set_ylabel("横向误差 / mm") axis.set_title( - f"横向误差(轨迹在车辆左侧为正)\n{title},RMSE={lateral_rmse_mm:.2f}mm" + "横向误差(轨迹在车辆左侧为正)\n" + f"RMSE={lateral_rmse_mm:.2f}mm" ) axis.grid(True, alpha=0.3) - destination = output_directory / f"{csv_path.stem}_lateral_error.png" - save_figure(fig, destination, show) - destinations.append(destination) + # 3. 航向误差。 heading_degrees = np.rad2deg(data["heading_error"]) heading_rmse_degrees = finite_rmse(heading_degrees) - fig, axis = plt.subplots(figsize=(10.0, 5.5)) + axis = axes[1, 0] axis.plot(data["time"], heading_degrees, linewidth=1.5) axis.axhline(0.0, color="black", linewidth=0.8) axis.set_xlabel("时间 / s") axis.set_ylabel("航向角偏差 / °") axis.set_title( "航向角偏差:参考轨迹航向-实际车体航向(逆时针为正)\n" - f"{title},RMSE={heading_rmse_degrees:.3f}°" + f"RMSE={heading_rmse_degrees:.3f}°" ) axis.grid(True, alpha=0.3) - destination = output_directory / f"{csv_path.stem}_heading_error.png" - save_figure(fig, destination, show) - destinations.append(destination) + # 4. 参考、命令、Detour估计和轮速解算速度。 speed_error = data["actual_speed"] - data["reference_speed"] speed_rmse = finite_rmse(speed_error) - fig, axis = plt.subplots(figsize=(10.0, 5.8)) + axis = axes[1, 1] axis.plot( data["time"], data["reference_speed"], @@ -444,29 +521,118 @@ def plot_experiment( linewidth=1.3, label="纵向控制器下发速度", ) - axis.plot( - data["time"], - data["actual_speed"], - linewidth=1.5, - label="状态估计实际车体纵向速度", - ) + if np.any(np.isfinite(data["detour_speed"])): + axis.plot( + data["time"], + data["detour_speed"], + ":", + linewidth=1.2, + label="Detour估计Vx", + ) + if np.any(np.isfinite(data["wheel_filtered_speed"])): + axis.plot( + data["time"], + data["wheel_filtered_speed"], + linewidth=1.5, + label="轮速解算滤波Vx(控制使用)", + ) + else: + axis.plot( + data["time"], + data["actual_speed"], + linewidth=1.5, + label="控制器实际纵向速度", + ) axis.set_xlabel("时间 / s") axis.set_ylabel("速度 / (m/s)") - axis.set_title(f"参考速度与实际速度对比\n{title},RMSE={speed_rmse:.4f}m/s") + axis.set_title( + "参考速度、控制命令与观测速度\n" + f"轮速Vx相对参考速度RMSE={speed_rmse:.4f}m/s" + ) axis.grid(True, alpha=0.3) - axis.legend() - destination = output_directory / f"{csv_path.stem}_speed_response.png" + axis.legend(fontsize=8) + + # 5. 四个舵轮的实际机械转角。 + axis = axes[2, 0] + wheel_labels = { + "LeftFront": "左前轮", + "LeftRear": "左后轮", + "RightFront": "右前轮", + "RightRear": "右后轮", + } + steering_data_available = False + for wheel_name, wheel_label in wheel_labels.items(): + wheel_angles = data["steering_angles_degrees"][wheel_name] + if np.any(np.isfinite(wheel_angles)): + steering_data_available = True + axis.plot( + data["time"], + wheel_angles, + linewidth=1.2, + label=wheel_label, + ) + if steering_data_available: + axis.axhline(0.0, color="black", linewidth=0.8) + axis.legend(fontsize=8, ncol=2) + else: + axis.text( + 0.5, + 0.5, + "CSV不含四舵轮转角诊断数据", + ha="center", + va="center", + transform=axis.transAxes, + ) + axis.set_xlabel("时间 / s") + axis.set_ylabel("实际舵角 / °") + axis.set_title("四个舵轮实际反馈转角") + axis.grid(True, alpha=0.3) + + # 6. 经过角速度限制后实际发送的前、后虚拟GCP转角。 + axis = axes[2, 1] + gcp_data_available = ( + np.any(np.isfinite(data["front_gcp_degrees"])) + or np.any(np.isfinite(data["rear_gcp_degrees"])) + ) + if gcp_data_available: + axis.plot( + data["time"], + data["front_gcp_degrees"], + linewidth=1.4, + label="前GCP", + ) + axis.plot( + data["time"], + data["rear_gcp_degrees"], + linewidth=1.4, + label="后GCP", + ) + axis.axhline(0.0, color="black", linewidth=0.8) + axis.legend(fontsize=8) + else: + axis.text( + 0.5, + 0.5, + "CSV不含前后GCP转角数据", + ha="center", + va="center", + transform=axis.transAxes, + ) + axis.set_xlabel("时间 / s") + axis.set_ylabel("GCP命令角 / °") + axis.set_title("前后虚拟GCP实际发送转角") + axis.grid(True, alpha=0.3) + + destination = output_directory / f"{csv_path.stem}_summary_6plots.png" save_figure(fig, destination, show) - destinations.append(destination) print( f"{csv_path.name}: 横向RMSE={lateral_rmse_mm:.3f}mm, " f"航向RMSE={heading_rmse_degrees:.4f}°, " f"速度RMSE={speed_rmse:.5f}m/s" ) - for destination in destinations: - print(f"已生成:{destination}") - return destinations + print(f"已生成六子图总图:{destination}") + return [destination] def discover_csv_files(arguments: list[str]) -> list[Path]: @@ -487,7 +653,7 @@ def discover_csv_files(arguments: list[str]) -> list[Path]: def main() -> None: """解析命令行并批量处理新版控制器实验CSV。""" parser = argparse.ArgumentParser( - description="绘制新版控制器轨迹实验的四类对比图。" + description="绘制新版控制器轨迹实验的六子图总图。" ) parser.add_argument("csv", nargs="*", help="需要处理的CSV文件路径。") parser.add_argument( diff --git a/output/C/CommonUsage.dll b/output/C/CommonUsage.dll index 93900a2..82a8d1d 100644 Binary files a/output/C/CommonUsage.dll and b/output/C/CommonUsage.dll differ diff --git a/output/C/MultiWheelC.dll b/output/C/MultiWheelC.dll index 5584b5b..962de2a 100644 Binary files a/output/C/MultiWheelC.dll and b/output/C/MultiWheelC.dll differ diff --git a/output/C/MultiWheelC.pdb b/output/C/MultiWheelC.pdb index d7fa53b..9369a82 100644 Binary files a/output/C/MultiWheelC.pdb and b/output/C/MultiWheelC.pdb differ diff --git a/output/M/CommonUsage.dll b/output/M/CommonUsage.dll index 93900a2..82a8d1d 100644 Binary files a/output/M/CommonUsage.dll and b/output/M/CommonUsage.dll differ diff --git a/output/M/MedullaAdapter.dll b/output/M/MedullaAdapter.dll index 1713d12..b225c77 100644 Binary files a/output/M/MedullaAdapter.dll and b/output/M/MedullaAdapter.dll differ diff --git a/output/M/MedullaAdapter.pdb b/output/M/MedullaAdapter.pdb index 3ff5098..f755db3 100644 Binary files a/output/M/MedullaAdapter.pdb and b/output/M/MedullaAdapter.pdb differ diff --git a/ref/CommonUsage.dll b/ref/CommonUsage.dll index 93900a2..82a8d1d 100644 Binary files a/ref/CommonUsage.dll and b/ref/CommonUsage.dll differ