using System; using System.Collections.Generic; using System.Threading; using MultiWheelC.TrajectoryPlanning.CoarsePath; using MultiWheelC.TrajectoryPlanning.CoarsePath.Facade; using MultiWheelC.TrajectoryPlanning.EMPlanner; using MultiWheelC.TrajectoryPlanning.Mapping; using MultiWheelC.TrajectoryPlanning.TrajectoryObservation; namespace TrajectoryOutputDemo; /// 一次 Demo 运行的结果;成功时同时提供原始 EM 轨迹、控制只读序列和已完成的 CSV 路径。 public sealed class TrajectoryOutputDemoResult { private TrajectoryOutputDemoResult(bool succeeded, string diagnostic, EmTrajectory? trajectory, ControlTrajectorySequence? controlTrajectory, string? csvPath) { Succeeded = succeeded; Diagnostic = diagnostic ?? string.Empty; Trajectory = trajectory; ControlTrajectory = controlTrajectory; CsvPath = csvPath; } public bool Succeeded { get; } public string Diagnostic { get; } public EmTrajectory? Trajectory { get; } public ControlTrajectorySequence? ControlTrajectory { get; } public string? CsvPath { get; } internal static TrajectoryOutputDemoResult Failure(string diagnostic) => new(false, diagnostic, null, null, null); internal static TrajectoryOutputDemoResult Success(EmTrajectory trajectory, ControlTrajectorySequence sequence, string csvPath) => new(true, string.Empty, trajectory, sequence, csvPath); } /// /// 编排一次真实轨迹输出:粗路径、Local G2 平滑、真实 OSQP EM 规划、控制序列投影和 CSV 导出。 /// 它只处理冻结的演示输入,绝不读取硬件状态或发送控制命令。 /// public sealed class TrajectoryOutputDemoRunner { /// 在配置定义的演示场景运行一次完整规划;失败时不导出任何轨迹文件。 public TrajectoryOutputDemoResult Run(TrajectoryOutputDemoConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); CoarsePathPlanningJob job = CreateCoarseJob(configuration); TrajectoryObservationBootstrapResult bootstrap = new TrajectoryObservationBootstrapper().Bootstrap(job, CancellationToken.None); if (!bootstrap.Succeeded) return TrajectoryOutputDemoResult.Failure("粗路径或平滑阶段失败:" + bootstrap.FailureReason); if (bootstrap.Segments.Count == 0) return TrajectoryOutputDemoResult.Failure("平滑路径没有可供 EM 规划的方向段。"); DateTimeOffset now = DateTimeOffset.UtcNow; DirectionSegmentView segment = bootstrap.Segments[0]; var state = new VehicleMotionState(configuration.Start, configuration.InitialSignedSpeedMetersPerSecond, 0d, now, 1L); var request = new EmPlanningRequest(bootstrap.SmoothedPath, bootstrap.Map, bootstrap.Vehicle, state, EmPlannerConfiguration.CreateDefault(), segment.SegmentIndex, null, now, now, "trajectory-output-demo-" + now.ToUnixTimeMilliseconds(), "trajectory-output-demo-reference", string.Empty, EmMotionModel.NonholonomicForwardReverse, EmPlanningScope.FullDirectionSegment); EmPlanningResult result = new EmPlanningService(new OsqpNativeSolver()).Plan(request, CancellationToken.None); bool accepted = (result.Status == EmPlanningStatus.Success || result.Status == EmPlanningStatus.SuccessWithFallback) && result.Trajectory != null && result.Trajectory.Points.Count > 0; if (!accepted) return TrajectoryOutputDemoResult.Failure("EM 规划失败:" + result.Status + ";" + result.FailureReason); EmTrajectory trajectory = result.Trajectory!; ControlTrajectorySequence sequence = new ControlModuleTrajectoryAdapter().Create(trajectory); string csvPath = new TrajectorySequenceExporter().Export(sequence, configuration.CsvOutputPath); return TrajectoryOutputDemoResult.Success(trajectory, sequence, csvPath); } private static CoarsePathPlanningJob CreateCoarseJob(TrajectoryOutputDemoConfiguration configuration) { return new CoarsePathPlanningJob { MapRequest = new PlanningMapRequest { Bounds = configuration.MapBounds, ResolutionMm = configuration.MapResolutionMillimeters, ObstacleSources = Array.Empty(), AllowExplicitEmptyMap = true, }, Start = configuration.Start, Goal = configuration.Goal, Vehicle = new VehicleParameters { LengthMeters = configuration.VehicleLengthMeters, WidthMeters = configuration.VehicleWidthMeters, SafetyMarginMeters = configuration.SafetyMarginMeters, MaximumCurvaturePerMeter = configuration.MaximumCurvaturePerMeter, }, Configuration = new HybridAStarConfiguration(), StartDirection = TravelDirection.Forward, GoalDirection = GoalDirectionConstraint.Forward, }; } }