using System; using System.Diagnostics; using System.Globalization; using System.Numerics; using System.Threading; using ClumsyCore; using ClumsyCore.Pilot; using CommonUsage.Chassis; using FundamentalLib; using MultiWheelC.StateEstimation; using MyParking.Shared; namespace MultiWheelC { /// /// 提供纵向开环辨识测试共用的舵轮准备、速度下发、采样和安全停车流程。 /// public abstract class LongitudinalIdentificationTestBase : MovementTest { private const double MaximumTargetSpeedMetersPerSecond = 0.70; private const double MinimumTargetSpeedMetersPerSecond = 0.02; private const float MillimetersPerMeter = 1000f; private DriveTask _preparationTask; private MultiWheelChassis _chassis; private MultiWheelChassisAdapter _adapter; private WheelFeedbackVehicleStateProvider _stateProvider; private TrackingExperimentRecorder _recorder; private int _testRunning; private int _stopRequested; /// /// 获取或设置本次重复实验编号。 /// public int TrialNumber = 1; /// /// 获取或设置速度阶跃前的静止记录时间,单位为s。 /// public double BaselineSeconds = 1.0; /// /// 获取或设置目标速度保持时间,单位为s。 /// public double CommandHoldSeconds = 5.0; /// /// 获取或设置停车后的继续记录时间,单位为s。 /// public double PostStopSeconds = 2.0; /// /// 获取或设置速度命令循环周期,单位为ms。 /// public int ControlIntervalMilliseconds = 50; /// /// 派生测试选择是否使用底盘DeAccPerSecond完成正常减速。 /// protected abstract bool UseConfiguredDeceleration { get; } /// /// 获取用于CSV文件名区分停车方式的标识。 /// protected abstract string StopModeName { get; } /// /// 读取目标速度,完成舵轮回正后执行纵向开环命令并保存CSV。 /// public override void Test() { if (Interlocked.CompareExchange( ref _testRunning, 1, 0) != 0) { Console.WriteLine( "纵向辨识测试已经在运行,请先停止当前测试。"); return; } Interlocked.Exchange(ref _stopRequested, 0); try { ValidateSettings(); if (!TryReadTargetSpeed( out var targetSpeedMetersPerSecond)) { return; } _chassis = PilotDefinition.Chassis as MultiWheelChassis; if (_chassis == null) { throw new InvalidOperationException( "当前底盘不是MultiWheelChassis,无法执行纵向辨识。"); } ValidateChassisAndTarget( targetSpeedMetersPerSecond); Console.WriteLine( "纵向辨识开始前请确认M层轮速诊断记录已经开启。"); Hedingben.ToastText( "请确认M层轮速诊断记录已开启。", "LongitudinalIdentification"); if (!MovementTestPreparation.AlignWheelsForward( ref _preparationTask)) { throw new InvalidOperationException( "四个舵轮未能稳定回正,纵向辨识已经取消。"); } ThrowIfStopRequested(); _adapter = new MultiWheelChassisAdapter( _chassis, PilotDefinition.Self.CarNum); _adapter.ResetToBodyFrame(); _adapter.StopImmediately(); _stateProvider = ParkingVehicleStateProviderFactory.Create( _chassis); if (!_stateProvider.TryGetState( out var initialState)) { throw new InvalidOperationException( "无法读取纵向辨识起点位姿:" + _stateProvider.LastFailureReason); } _recorder = CreateRecorder( targetSpeedMetersPerSecond, initialState.PoseInWorld); _recorder.Start(); Console.WriteLine( $"纵向辨识参数:目标速度={targetSpeedMetersPerSecond:F3}m/s," + $"AccPerSecond={_chassis.AccPerSecond:F3}m/s²," + $"DeAccPerSecond={_chassis.DeAccPerSecond:F3}m/s²," + $"保持={CommandHoldSeconds:F1}s,停车方式={StopModeName}。"); RunStationaryPhase( BaselineSeconds, "静止基线"); RunTargetSpeedPhase( targetSpeedMetersPerSecond); if (UseConfiguredDeceleration) { RunConfiguredDecelerationPhase( targetSpeedMetersPerSecond); } else { _adapter.StopImmediately(); RunStationaryPhase( PostStopSeconds, "立即停车后记录"); } Console.WriteLine( "纵向辨识测试完成。请同时保存对应的M层轮速诊断CSV。"); Hedingben.ToastText( "纵向辨识完成,请停止并保存M层轮速诊断记录。", "LongitudinalIdentification"); } catch (OperationCanceledException) { Console.WriteLine("纵向辨识已由用户停止。"); } catch (Exception exception) { Console.WriteLine( "纵向辨识失败:" + exception.Message); Hedingben.ToastText( "纵向辨识失败:" + exception.Message, "LongitudinalIdentification"); } finally { _adapter?.StopImmediately(); _chassis?.PredefinedDriveStop(); _recorder?.UpdateBodyCommand(0f, 0f, 0f); _recorder?.StopAndSave(); _preparationTask?.Stop(); _preparationTask = null; _recorder = null; _stateProvider = null; _adapter = null; _chassis = null; Interlocked.Exchange(ref _testRunning, 0); } } /// /// 请求停止当前辨识并立即清零底盘驱动速度。 /// public override void TestStop() { Interlocked.Exchange(ref _stopRequested, 1); _preparationTask?.Stop(); _adapter?.StopImmediately(); _chassis?.PredefinedDriveStop(); } /// /// 在固定周期内保持零命令并记录静止数据。 /// private void RunStationaryPhase( double durationSeconds, string phaseName) { _recorder.UpdateBodyCommand(0f, 0f, 0f); RunPeriodicPhase( durationSeconds, phaseName, _ => UpdateWheelDiagnostics()); } /// /// 通过标准车体速度适配器持续下发直线目标速度。 /// private void RunTargetSpeedPhase( double targetSpeedMetersPerSecond) { _recorder.UpdateBodyCommand( (float)targetSpeedMetersPerSecond, 0f, 0f); RunPeriodicPhase( CommandHoldSeconds, "目标速度保持", interval => { var accepted = _adapter.SendBodyTwist( new Twist2D( targetSpeedMetersPerSecond, 0.0, 0.0), interval); if (!accepted) { throw new InvalidOperationException( "底盘拒绝纵向速度命令:" + _adapter.LastFailureReason); } UpdateWheelDiagnostics(); }); } /// /// 重复发送零速SendMotion,使底盘内部DeAccPerSecond斜坡真实参与减速。 /// private void RunConfiguredDecelerationPhase( double targetSpeedMetersPerSecond) { _recorder.UpdateBodyCommand(0f, 0f, 0f); var rampDurationSeconds = Math.Abs(targetSpeedMetersPerSecond) / _chassis.DeAccPerSecond; var totalDurationSeconds = rampDurationSeconds + PostStopSeconds; RunPeriodicPhase( totalDurationSeconds, "配置化正常减速", interval => { // SendBodyTwist(Zero)会立即停车;辨识DeAccPerSecond时必须 // 直接保持SendMotion零目标,让底盘内部发送速度逐周期下降。 var accepted = _chassis.SendMotion( 0f, 0f, 0f, interval); if (!accepted) { throw new InvalidOperationException( "底盘拒绝正常减速命令:" + _chassis.LastMotionDecomposeFailureReason); } UpdateWheelDiagnostics(); }); } /// /// 按实际循环间隔运行一个阶段,并响应测试界面的停止请求。 /// private void RunPeriodicPhase( double durationSeconds, string phaseName, Action cycleAction) { Console.WriteLine( $"纵向辨识阶段:{phaseName},预计{durationSeconds:F2}s。"); var periodSeconds = ControlIntervalMilliseconds / 1000.0; var phaseClock = Stopwatch.StartNew(); var previousCycleSeconds = -periodSeconds; while (phaseClock.Elapsed.TotalSeconds < durationSeconds) { ThrowIfStopRequested(); var cycleStartSeconds = phaseClock.Elapsed.TotalSeconds; var deltaTimeSeconds = cycleStartSeconds - previousCycleSeconds; previousCycleSeconds = cycleStartSeconds; cycleAction( TimeSpan.FromSeconds( deltaTimeSeconds)); var elapsedMilliseconds = (phaseClock.Elapsed.TotalSeconds - cycleStartSeconds) * 1000.0; var remainingMilliseconds = ControlIntervalMilliseconds - elapsedMilliseconds; if (remainingMilliseconds > 1.0) { Thread.Sleep( (int)Math.Floor( remainingMilliseconds)); } } } /// /// 刷新轮组原始与滤波速度,供后台CSV记录器读取最新诊断值。 /// private void UpdateWheelDiagnostics() { _stateProvider.TryGetWheelTwist( out _, out _); } /// /// 创建复用现有字段格式的纵向辨识CSV记录器。 /// private TrackingExperimentRecorder CreateRecorder( double targetSpeedMetersPerSecond, Pose2D initialPoseInWorld) { var expectedTravelMeters = targetSpeedMetersPerSecond * CommandHoldSeconds; var referenceStart = new Vector2( (float)( initialPoseInWorld.XMeters * MillimetersPerMeter), (float)( initialPoseInWorld.YMeters * MillimetersPerMeter)); var referenceEnd = new Vector2( referenceStart.X + (float)( Math.Cos(initialPoseInWorld.YawRadians) * expectedTravelMeters * MillimetersPerMeter), referenceStart.Y + (float)( Math.Sin(initialPoseInWorld.YawRadians) * expectedTravelMeters * MillimetersPerMeter)); var speedMillimetersPerSecond = targetSpeedMetersPerSecond * MillimetersPerMeter; var trajectoryName = "LongitudinalStep_" + speedMillimetersPerSecond.ToString( "+0;-0;0", CultureInfo.InvariantCulture) + "mmps_" + StopModeName; return new TrackingExperimentRecorder( controllerName: "OpenLoopLongitudinalIdentification", trajectoryName: trajectoryName, trialNumber: TrialNumber, referenceStart: referenceStart, referenceEnd: referenceEnd, referenceSpeed: (float)targetSpeedMetersPerSecond, sampleIntervalMs: ControlIntervalMilliseconds, referenceMotionFrameYawDegrees: 0f, referenceAccelerationMetersPerSecondSquared: _chassis.AccPerSecond, referenceDecelerationMetersPerSecondSquared: _chassis.DeAccPerSecond, diagnosticChassis: _chassis, diagnosticStateProvider: _stateProvider); } /// /// 从测试界面读取带方向的纵向目标速度,正值前进、负值倒车。 /// private static bool TryReadTargetSpeed( out double targetSpeedMetersPerSecond) { targetSpeedMetersPerSecond = 0.0; var input = UI.GetInput( "输入纵向目标速度(m/s,正数前进、负数倒车," + $"范围-{MaximumTargetSpeedMetersPerSecond:F1}~" + $"{MaximumTargetSpeedMetersPerSecond:F1}且不能为0):"); var parsed = double.TryParse( input, NumberStyles.Float, CultureInfo.CurrentCulture, out targetSpeedMetersPerSecond) || double.TryParse( input, NumberStyles.Float, CultureInfo.InvariantCulture, out targetSpeedMetersPerSecond); if (!parsed || double.IsNaN(targetSpeedMetersPerSecond) || double.IsInfinity(targetSpeedMetersPerSecond) || Math.Abs(targetSpeedMetersPerSecond) < MinimumTargetSpeedMetersPerSecond || Math.Abs(targetSpeedMetersPerSecond) > MaximumTargetSpeedMetersPerSecond) { Console.WriteLine( "目标速度必须是绝对值位于" + $"{MinimumTargetSpeedMetersPerSecond:F2}~" + $"{MaximumTargetSpeedMetersPerSecond:F2}m/s之间的有限数值。"); return false; } return true; } /// /// 验证实验时间、循环周期和编号配置。 /// private void ValidateSettings() { NumericGuard.EnsureFiniteNonNegative( BaselineSeconds, nameof(BaselineSeconds)); NumericGuard.EnsureFinitePositive( CommandHoldSeconds, nameof(CommandHoldSeconds)); NumericGuard.EnsureFiniteNonNegative( PostStopSeconds, nameof(PostStopSeconds)); if (CommandHoldSeconds > 30.0 || BaselineSeconds > 10.0 || PostStopSeconds > 10.0) { throw new ArgumentOutOfRangeException( nameof(CommandHoldSeconds), "辨识阶段时间超出测试允许范围。"); } if (ControlIntervalMilliseconds < 20 || ControlIntervalMilliseconds > 200) { throw new ArgumentOutOfRangeException( nameof(ControlIntervalMilliseconds), "纵向辨识命令周期必须在20~200ms之间。"); } if (TrialNumber <= 0) { throw new ArgumentOutOfRangeException( nameof(TrialNumber), "测试编号必须大于零。"); } } /// /// 验证底盘运行时加载的速度、加速度和减速度配置。 /// private void ValidateChassisAndTarget( double targetSpeedMetersPerSecond) { NumericGuard.EnsureFinitePositive( _chassis.MaxSpeed, nameof(_chassis.MaxSpeed)); NumericGuard.EnsureFinitePositive( _chassis.AccPerSecond, nameof(_chassis.AccPerSecond)); NumericGuard.EnsureFinitePositive( _chassis.DeAccPerSecond, nameof(_chassis.DeAccPerSecond)); if (Math.Abs(targetSpeedMetersPerSecond) > _chassis.MaxSpeed) { throw new ArgumentOutOfRangeException( nameof(targetSpeedMetersPerSecond), "目标速度超过底盘当前MaxSpeed配置。"); } } /// /// 在收到停止请求时中断当前阶段并转入finally安全停车。 /// private void ThrowIfStopRequested() { if (Volatile.Read(ref _stopRequested) != 0) { throw new OperationCanceledException(); } } } /// /// 测量速度阶跃、加速与稳态,并在保持结束后立即清零驱动速度。 /// [MovementTest(name = "纵向辨识:速度阶跃与立即停车")] public sealed class LongitudinalStepImmediateStopTest : LongitudinalIdentificationTestBase { protected override bool UseConfiguredDeceleration => false; protected override string StopModeName => "ImmediateStop"; } /// /// 测量速度阶跃、稳态以及由底盘DeAccPerSecond形成的正常减速过程。 /// [MovementTest(name = "纵向辨识:速度阶跃与正常减速")] public sealed class LongitudinalStepConfiguredDecelerationTest : LongitudinalIdentificationTestBase { protected override bool UseConfiguredDeceleration => true; protected override string StopModeName => "ConfiguredDeceleration"; } }