docs: document trajectory execution contracts

This commit is contained in:
梁薄云
2026-08-04 14:21:13 +08:00
parent fda84ce148
commit 4a7dd875a4
14 changed files with 149 additions and 18 deletions
@@ -6,8 +6,8 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary> /// <summary>
/// Latest-wins rolling coordinator around the pure one-shot planning service. /// 围绕纯单次 <see cref="IEmPlanningService"/> 的 latest-wins 滚动规划协调器。
/// It owns no localization, hardware, UI, or wall-clock source; callers supply each cycle's state and time. /// 它只使用调用方提供的状态快照和时钟值;不读取定位、硬件、UI 或系统时钟,并且只原子发布当前身份的完整成功轨迹。
/// </summary> /// </summary>
public sealed class EmPlanningCoordinator public sealed class EmPlanningCoordinator
{ {
@@ -22,12 +22,16 @@ public sealed class EmPlanningCoordinator
private DateTimeOffset? lastCycleStartedAtUtc; private DateTimeOffset? lastCycleStartedAtUtc;
private double replanPeriodSeconds = 0.20d; private double replanPeriodSeconds = 0.20d;
/// <summary>创建滚动规划协调器。</summary>
/// <param name="planningService">同步纯规划服务;每个周期在后台任务中调用其 <c>Plan</c> 方法。</param>
/// <param name="sink">可选周期完成观察器;观察器异常只追加诊断,不能回滚发布结果。</param>
public EmPlanningCoordinator(IEmPlanningService planningService, IEmPlanningCycleSink sink = null) public EmPlanningCoordinator(IEmPlanningService planningService, IEmPlanningCycleSink sink = null)
{ {
this.planningService = planningService ?? throw new ArgumentNullException(nameof(planningService)); this.planningService = planningService ?? throw new ArgumentNullException(nameof(planningService));
this.sink = sink; this.sink = sink;
} }
/// <summary>最后一条已原子发布的完整不可变轨迹;尚无成功周期或失败周期未替换旧轨迹时可为 <see langword="null"/>。</summary>
public EmTrajectory PublishedTrajectory public EmTrajectory PublishedTrajectory
{ {
get get
@@ -37,7 +41,9 @@ public sealed class EmPlanningCoordinator
} }
} }
/// <summary>Returns whether a caller-supplied time is due for another rolling cycle.</summary> /// <summary>判断调用方提供的时刻是否到达下一次滚动规划周期。</summary>
/// <param name="now">调用方冻结的当前时间;协调器不读取墙上时钟。</param>
/// <returns>尚未启动周期或距上次开始已达到配置周期时为 <see langword="true"/>;否则为 <see langword="false"/>。</returns>
public bool ShouldStartCycle(DateTimeOffset now) public bool ShouldStartCycle(DateTimeOffset now)
{ {
lock (publicationGate) lock (publicationGate)
@@ -47,6 +53,10 @@ public sealed class EmPlanningCoordinator
} }
} }
/// <summary>异步启动一个 latest-wins 滚动周期,并取消此前仍在执行的周期。</summary>
/// <param name="input">调用方冻结的规划请求、完整周期身份和 caller-clocked 时刻;其地图、路径和状态版本必须一致。</param>
/// <param name="cancellationToken">调用方取消令牌;会与本周期内部令牌链接。</param>
/// <returns>完成后给出不可变 <see cref="PlanningCycleResult"/>;只有版本和身份仍当前且结果成功完整时其 <c>Published</c> 为 <see langword="true"/>。</returns>
public Task<PlanningCycleResult> PlanLatestAsync(PlanningCycleInput input, CancellationToken cancellationToken) public Task<PlanningCycleResult> PlanLatestAsync(PlanningCycleInput input, CancellationToken cancellationToken)
{ {
if (input == null) if (input == null)
@@ -68,7 +78,10 @@ public sealed class EmPlanningCoordinator
return Task.Run(() => CompleteCycle(version, input, cycleCancellation)); return Task.Run(() => CompleteCycle(version, input, cycleCancellation));
} }
/// <summary>Builds a handoff decision from the coordinator's immutable published trajectory snapshot.</summary> /// <summary>从协调器当前已发布的不可变轨迹构建下一周期的安全交接决策。</summary>
/// <param name="input">下一周期的请求、状态快照和时刻。</param>
/// <param name="expectedDirection">下一周期期望的实际行驶方向。</param>
/// <returns>同段、同方向、年龄和追踪误差均安全时返回未来轨迹样本;否则返回调用方测量状态和拒绝原因。</returns>
public TrajectoryHandoffSelection SelectHandoff(PlanningCycleInput input, TravelDirection expectedDirection) public TrajectoryHandoffSelection SelectHandoff(PlanningCycleInput input, TravelDirection expectedDirection)
{ {
if (input == null) if (input == null)
@@ -81,6 +94,11 @@ public sealed class EmPlanningCoordinator
input.Now, input.Request.Configuration); input.Now, input.Request.Configuration);
} }
/// <summary>执行一次后台纯规划并在锁内比较版本和身份后决定是否发布。</summary>
/// <param name="version">启动时分配的单调递增周期版本。</param>
/// <param name="input">该周期的不可变请求、身份和 caller-clocked 时刻。</param>
/// <param name="cycleCancellation">本周期与调用方取消状态链接的令牌源。</param>
/// <returns>包含原始或 Superseded 规划结果、发布标记和观察器诊断的周期记录。</returns>
private PlanningCycleResult CompleteCycle(long version, PlanningCycleInput input, private PlanningCycleResult CompleteCycle(long version, PlanningCycleInput input,
CancellationTokenSource cycleCancellation) CancellationTokenSource cycleCancellation)
{ {
@@ -1,11 +1,18 @@
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>轨迹执行器的换向安全状态;所有离开 Following 的状态都禁止非零运动,直到方向已确认。</summary>
public enum GearSwitchState public enum GearSwitchState
{ {
/// <summary>正常跟随当前方向段的轨迹运动。</summary>
Following, Following,
/// <summary>目标方向不同,正在接近精确换向边界。</summary>
ApproachingGearSwitch, ApproachingGearSwitch,
/// <summary>位于换向边界并保持零速度,等待满足停稳 dwell。</summary>
HoldingZero, HoldingZero,
/// <summary>零速 dwell 已满足;本次更新发送一次方向变更请求。</summary>
RequestingDirectionChange, RequestingDirectionChange,
/// <summary>请求已发出,保持制动直到调用方确认实际方向改变。</summary>
AwaitingDirectionConfirmation, AwaitingDirectionConfirmation,
/// <summary>到达 Goal 或 RollingSafetyStop,永久保持零运动。</summary>
Completed, Completed,
} }
@@ -3,13 +3,16 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Pure caller-clocked state machine for a zero-speed direction change.</summary> /// <summary> caller-clocked 的零速换向状态机;只依据调用方时间、测量速度和方向确认推进状态。</summary>
public sealed class GearSwitchStateMachine public sealed class GearSwitchStateMachine
{ {
private readonly double stopSpeedToleranceMetersPerSecond; private readonly double stopSpeedToleranceMetersPerSecond;
private readonly TimeSpan zeroSpeedHoldDuration; private readonly TimeSpan zeroSpeedHoldDuration;
private DateTimeOffset? zeroSpeedSince; private DateTimeOffset? zeroSpeedSince;
/// <summary>创建换向状态机。</summary>
/// <param name="stopSpeedToleranceMetersPerSecond">判定已停稳的绝对带符号速度上限,单位 m/s。</param>
/// <param name="zeroSpeedHoldSeconds">发送方向请求前必须连续保持停稳的时长,单位 s。</param>
public GearSwitchStateMachine(double stopSpeedToleranceMetersPerSecond, double zeroSpeedHoldSeconds) public GearSwitchStateMachine(double stopSpeedToleranceMetersPerSecond, double zeroSpeedHoldSeconds)
{ {
if (!IsNonNegativeFinite(stopSpeedToleranceMetersPerSecond)) if (!IsNonNegativeFinite(stopSpeedToleranceMetersPerSecond))
@@ -21,8 +24,18 @@ public sealed class GearSwitchStateMachine
zeroSpeedHoldDuration = TimeSpan.FromSeconds(zeroSpeedHoldSeconds); zeroSpeedHoldDuration = TimeSpan.FromSeconds(zeroSpeedHoldSeconds);
} }
/// <summary>当前不可回退的换向状态;初始为 <see cref="GearSwitchState.Following"/>。</summary>
public GearSwitchState State { get; private set; } = GearSwitchState.Following; public GearSwitchState State { get; private set; } = GearSwitchState.Following;
/// <summary>基于调用方时刻、测量速度和边界语义推进一次换向状态。</summary>
/// <param name="now">调用方当前时刻,用于计算零速 dwell,不读取系统时钟。</param>
/// <param name="measuredSignedSpeed">测量带符号纵向速度,单位 m/s。</param>
/// <param name="desiredDirection">轨迹要求的实际行驶方向。</param>
/// <param name="currentDirection">调用方确认的当前实际方向。</param>
/// <param name="directionConfirmed">调用方是否已确认硬件方向切换完成。</param>
/// <param name="atGearSwitchBoundary">选中点是否为精确换向接近边界。</param>
/// <param name="atTerminal">选中点是否为 Goal 或 RollingSafetyStop 终端。</param>
/// <returns>包含停零、一次换向请求、是否允许轨迹运动和完成语义的不可变状态更新。</returns>
public GearSwitchStateUpdate Update(DateTimeOffset now, double measuredSignedSpeed, TravelDirection desiredDirection, public GearSwitchStateUpdate Update(DateTimeOffset now, double measuredSignedSpeed, TravelDirection desiredDirection,
TravelDirection currentDirection, bool directionConfirmed, bool atGearSwitchBoundary, bool atTerminal) TravelDirection currentDirection, bool directionConfirmed, bool atGearSwitchBoundary, bool atTerminal)
{ {
@@ -117,6 +130,7 @@ public sealed class GearSwitchStateMachine
} }
} }
/// <summary>一次换向状态机更新的不可变安全决策,供执行器映射为零速度/制动或轨迹跟随命令。</summary>
public sealed class GearSwitchStateUpdate public sealed class GearSwitchStateUpdate
{ {
internal GearSwitchStateUpdate(GearSwitchState state, bool holdZero, bool requestDirectionChange, internal GearSwitchStateUpdate(GearSwitchState state, bool holdZero, bool requestDirectionChange,
@@ -130,14 +144,17 @@ public sealed class GearSwitchStateUpdate
Reason = reason ?? string.Empty; Reason = reason ?? string.Empty;
} }
/// <summary>更新后的换向状态。</summary>
public GearSwitchState State { get; } public GearSwitchState State { get; }
public bool HoldZero { get; } public bool HoldZero { get; }
/// <summary>是否仅在本次更新请求一次方向变更。</summary>
public bool RequestDirectionChange { get; } public bool RequestDirectionChange { get; }
public bool AllowsTrajectoryMotion { get; } public bool AllowsTrajectoryMotion { get; }
/// <summary>是否因 Goal 或 RollingSafetyStop 完成轨迹并需要持续制动。</summary>
public bool IsTrajectoryComplete { get; } public bool IsTrajectoryComplete { get; }
public string Reason { get; } public string Reason { get; }
@@ -1,7 +1,9 @@
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Optional observer for completed rolling planning cycles.</summary> /// <summary>滚动规划周期完成后的可选观察器;实现不得依赖其回调来维持发布正确性。</summary>
public interface IEmPlanningCycleSink public interface IEmPlanningCycleSink
{ {
/// <summary>接收已经确定的周期完成记录。</summary>
/// <param name="result">不可变周期结果;观察器异常会被协调器隔离并仅追加诊断。</param>
void OnCycleCompleted(PlanningCycleResult result); void OnCycleCompleted(PlanningCycleResult result);
} }
@@ -1,7 +1,9 @@
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Execution-layer source of an immutable caller-owned vehicle-state snapshot.</summary> /// <summary>执行层的车辆状态快照来源;具体实现可在模块外读取定位或硬件,但核心只消费返回的不可变快照。</summary>
public interface IVehicleStateProvider public interface IVehicleStateProvider
{ {
/// <summary>捕获一次车辆位姿、带符号纵向速度、可选加速度、时刻和序列号快照。</summary>
/// <returns>不可变 <see cref="VehicleMotionState"/>;其位置单位为 m、航向为 rad、速度为 m/s。</returns>
VehicleMotionState Capture(); VehicleMotionState Capture();
} }
@@ -2,9 +2,15 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Immutable identity bound to one rolling planning cycle.</summary> /// <summary>绑定一次滚动周期的不可变版本身份;所有字段必须同时匹配,旧结果才可发布。</summary>
public sealed class PlanningCycleIdentity : IEquatable<PlanningCycleIdentity> public sealed class PlanningCycleIdentity : IEquatable<PlanningCycleIdentity>
{ {
/// <summary>创建完整周期身份。</summary>
/// <param name="mapSnapshotId">规划地图快照的非负版本 ID。</param>
/// <param name="referencePathId">平滑参考路径的非空版本 ID。</param>
/// <param name="vehicleStateSequenceId">调用方捕获的车辆状态非负序列号。</param>
/// <param name="previousTrajectoryId">上一条轨迹 ID;没有时使用空字符串。</param>
/// <param name="segmentIndex">当前前进或倒车方向段的非负索引。</param>
public PlanningCycleIdentity(long mapSnapshotId, string referencePathId, long vehicleStateSequenceId, public PlanningCycleIdentity(long mapSnapshotId, string referencePathId, long vehicleStateSequenceId,
string previousTrajectoryId, int segmentIndex) string previousTrajectoryId, int segmentIndex)
{ {
@@ -24,6 +30,7 @@ public sealed class PlanningCycleIdentity : IEquatable<PlanningCycleIdentity>
SegmentIndex = segmentIndex; SegmentIndex = segmentIndex;
} }
/// <summary>规划地图快照版本 ID。</summary>
public long MapSnapshotId { get; } public long MapSnapshotId { get; }
public string ReferencePathId { get; } public string ReferencePathId { get; }
@@ -34,6 +41,9 @@ public sealed class PlanningCycleIdentity : IEquatable<PlanningCycleIdentity>
public int SegmentIndex { get; } public int SegmentIndex { get; }
/// <summary>从已冻结的 EM 请求派生发布身份。</summary>
/// <param name="request">必须携带地图和车辆状态快照的 EM 请求。</param>
/// <returns>包含请求地图、路径、状态、上一轨迹和方向段字段的不可变身份。</returns>
public static PlanningCycleIdentity FromRequest(EmPlanningRequest request) public static PlanningCycleIdentity FromRequest(EmPlanningRequest request)
{ {
if (request == null) if (request == null)
@@ -2,9 +2,12 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Caller-captured request and clock value for one rolling cycle.</summary> /// <summary>一次滚动周期的调用方冻结请求、身份和时钟值;构造后不读取外部状态。</summary>
public sealed class PlanningCycleInput public sealed class PlanningCycleInput
{ {
/// <summary>从一次 EM 规划请求和调用方时刻创建周期输入。</summary>
/// <param name="request">已冻结的 EM 请求;用于派生地图、路径、状态、上一轨迹和方向段身份。</param>
/// <param name="now">调用方提供的当前 <see cref="DateTimeOffset"/>;用于周期调度,不由协调器替换。</param>
public PlanningCycleInput(EmPlanningRequest request, DateTimeOffset now) public PlanningCycleInput(EmPlanningRequest request, DateTimeOffset now)
{ {
Request = request ?? throw new ArgumentNullException(nameof(request)); Request = request ?? throw new ArgumentNullException(nameof(request));
@@ -13,12 +16,16 @@ public sealed class PlanningCycleInput
ReplanPeriodSeconds = ReadReplanPeriod(request.Configuration); ReplanPeriodSeconds = ReadReplanPeriod(request.Configuration);
} }
/// <summary>本周期消费的不可变 EM 规划请求快照。</summary>
public EmPlanningRequest Request { get; } public EmPlanningRequest Request { get; }
/// <summary>从 <see cref="Request"/> 派生的完整版本身份,用于拒绝过期发布。</summary>
public PlanningCycleIdentity Identity { get; } public PlanningCycleIdentity Identity { get; }
/// <summary>调用方冻结的周期开始时刻。</summary>
public DateTimeOffset Now { get; } public DateTimeOffset Now { get; }
/// <summary>从请求配置读取的重规划周期,单位 s;无效配置回退为 0.20 s。</summary>
public double ReplanPeriodSeconds { get; } public double ReplanPeriodSeconds { get; }
private static double ReadReplanPeriod(EmPlannerConfiguration configuration) private static double ReadReplanPeriod(EmPlannerConfiguration configuration)
@@ -2,9 +2,15 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Immutable completion record for one rolling planning cycle.</summary> /// <summary>一次滚动规划周期的不可变完成记录,区分规划结果与是否获准发布。</summary>
public sealed class PlanningCycleResult public sealed class PlanningCycleResult
{ {
/// <summary>创建周期完成记录。</summary>
/// <param name="version">协调器分配的正数单调周期版本。</param>
/// <param name="identity">本周期绑定的地图、路径、状态、上一轨迹和方向段身份。</param>
/// <param name="result">纯规划服务返回的结果,可能为 Superseded、取消或失败。</param>
/// <param name="published">仅当完整成功轨迹仍为当前身份时为 <see langword="true"/>。</param>
/// <param name="diagnostic">附加的周期或观察器诊断文本。</param>
public PlanningCycleResult(long version, PlanningCycleIdentity identity, EmPlanningResult result, bool published, public PlanningCycleResult(long version, PlanningCycleIdentity identity, EmPlanningResult result, bool published,
string diagnostic) string diagnostic)
{ {
@@ -17,14 +23,19 @@ public sealed class PlanningCycleResult
Diagnostic = diagnostic ?? string.Empty; Diagnostic = diagnostic ?? string.Empty;
} }
/// <summary>本周期的单调递增版本号。</summary>
public long Version { get; } public long Version { get; }
/// <summary>决定该结果是否仍可发布的完整不可变身份。</summary>
public PlanningCycleIdentity Identity { get; } public PlanningCycleIdentity Identity { get; }
/// <summary>纯 EM 规划结果;失败结果不携带可执行轨迹。</summary>
public EmPlanningResult Result { get; } public EmPlanningResult Result { get; }
/// <summary>该结果是否已原子替换协调器的已发布轨迹。</summary>
public bool Published { get; } public bool Published { get; }
/// <summary>周期处理或可选观察器产生的诊断文本。</summary>
public string Diagnostic { get; } public string Diagnostic { get; }
internal PlanningCycleResult WithDiagnostic(string diagnostic) internal PlanningCycleResult WithDiagnostic(string diagnostic)
@@ -2,9 +2,13 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Converts immutable execution state into a controller-neutral longitudinal and yaw command.</summary> /// <summary>将不可变执行状态转换为控制器中立的纵向速度和 yaw rate 命令,并在停稳/完成时强制零运动。</summary>
public sealed class TrajectoryControlAdapter public sealed class TrajectoryControlAdapter
{ {
/// <summary>依据选中轨迹点和换向状态创建安全控制命令。</summary>
/// <param name="trajectoryPoint">已验证轨迹中的选中点,提供带符号速度、yaw rate 和方向。</param>
/// <param name="executionState">不可变执行状态;停零、完成或不允许运动时优先于轨迹点运动字段。</param>
/// <returns>常规跟随时保留轨迹点 m/s 和 rad/s;保持或完成时返回零速度、零 yaw rate 和制动命令。</returns>
public TrajectoryControlCommand CreateCommand(EmTrajectoryPoint trajectoryPoint, public TrajectoryControlCommand CreateCommand(EmTrajectoryPoint trajectoryPoint,
TrajectoryExecutionState executionState) TrajectoryExecutionState executionState)
{ {
@@ -3,9 +3,19 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Immutable generic motion command derived from a validated trajectory execution state.</summary> /// <summary>
/// 从已验证轨迹执行状态导出的不可变、控制器中立运动命令。
/// 只表示带符号纵向速度和 yaw rate;不包含横向车体速度、蟹行、原地旋转、UI 或硬件字段。
/// </summary>
public sealed class TrajectoryControlCommand public sealed class TrajectoryControlCommand
{ {
/// <summary>创建控制器中立命令。</summary>
/// <param name="signedLongitudinalVelocity">车体纵向速度,单位 m/s;正数前进、负数倒车,保持制动时必须为 0。</param>
/// <param name="yawRate">车辆 yaw rate,单位 rad/s;保持制动或完成时必须为 0。</param>
/// <param name="direction">命令关联的实际行驶方向。</param>
/// <param name="requestDirectionChange">仅在零速 dwell 完成后的单个更新周期请求方向变更。</param>
/// <param name="holdBrake">为 <see langword="true"/> 时调用方必须保持零速度和零 yaw rate。</param>
/// <param name="isTrajectoryComplete">到达 Goal 或 RollingSafetyStop 后为 <see langword="true"/>。</param>
public TrajectoryControlCommand(double signedLongitudinalVelocity, double yawRate, TravelDirection direction, public TrajectoryControlCommand(double signedLongitudinalVelocity, double yawRate, TravelDirection direction,
bool requestDirectionChange, bool holdBrake, bool isTrajectoryComplete) bool requestDirectionChange, bool holdBrake, bool isTrajectoryComplete)
{ {
@@ -24,14 +34,18 @@ public sealed class TrajectoryControlCommand
IsTrajectoryComplete = isTrajectoryComplete; IsTrajectoryComplete = isTrajectoryComplete;
} }
/// <summary>权威带符号纵向速度,单位 m/s。</summary>
public double SignedLongitudinalVelocity { get; } public double SignedLongitudinalVelocity { get; }
/// <summary>逆时针为正的 yaw rate,单位 rad/s。</summary>
public double YawRate { get; } public double YawRate { get; }
public TravelDirection Direction { get; } public TravelDirection Direction { get; }
/// <summary>是否在本次更新请求一次方向切换。</summary>
public bool RequestDirectionChange { get; } public bool RequestDirectionChange { get; }
/// <summary>是否要求调用方保持制动,不允许轨迹运动。</summary>
public bool HoldBrake { get; } public bool HoldBrake { get; }
public bool IsTrajectoryComplete { get; } public bool IsTrajectoryComplete { get; }
@@ -2,7 +2,7 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Immutable trajectory-execution outcome without controller or hardware coupling.</summary> /// <summary>不耦合控制器或硬件的不可变轨迹执行状态,组合选中点和换向状态机的安全语义。</summary>
public sealed class TrajectoryExecutionState public sealed class TrajectoryExecutionState
{ {
internal TrajectoryExecutionState(EmTrajectoryPoint selectedPoint, GearSwitchStateUpdate gearSwitchUpdate) internal TrajectoryExecutionState(EmTrajectoryPoint selectedPoint, GearSwitchStateUpdate gearSwitchUpdate)
@@ -19,14 +19,17 @@ public sealed class TrajectoryExecutionState
Reason = gearSwitchUpdate.Reason; Reason = gearSwitchUpdate.Reason;
} }
/// <summary>按调用方时刻选中或插值得到的轨迹点;位置为 m、航向为 rad、速度为 m/s。</summary>
public EmTrajectoryPoint SelectedPoint { get; } public EmTrajectoryPoint SelectedPoint { get; }
public GearSwitchState GearSwitchState { get; } public GearSwitchState GearSwitchState { get; }
/// <summary>是否要求保持零速度和零 yaw rate。</summary>
public bool HoldZero { get; } public bool HoldZero { get; }
public bool RequestDirectionChange { get; } public bool RequestDirectionChange { get; }
/// <summary>状态机是否允许跟随选中点的非零轨迹运动。</summary>
public bool AllowsTrajectoryMotion { get; } public bool AllowsTrajectoryMotion { get; }
public bool IsTrajectoryComplete { get; } public bool IsTrajectoryComplete { get; }
@@ -3,18 +3,21 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Samples immutable EM trajectories and applies the pure zero-speed gear-switch state machine.</summary> /// <summary>按调用方时刻采样不可变 EM 轨迹,并应用纯零速换向状态机;从不在轨迹末点之后外推。</summary>
public sealed class TrajectoryExecutor public sealed class TrajectoryExecutor
{ {
private readonly TrajectorySampler sampler = new TrajectorySampler(); private readonly TrajectorySampler sampler = new TrajectorySampler();
private readonly TrajectoryControlAdapter controlAdapter = new TrajectoryControlAdapter(); private readonly TrajectoryControlAdapter controlAdapter = new TrajectoryControlAdapter();
private readonly GearSwitchStateMachine gearSwitchStateMachine; private readonly GearSwitchStateMachine gearSwitchStateMachine;
/// <summary>使用默认 EM 配置创建执行器,默认停车容差和零速 dwell 来自该配置。</summary>
public TrajectoryExecutor() public TrajectoryExecutor()
: this(EmPlannerConfiguration.CreateDefault()) : this(EmPlannerConfiguration.CreateDefault())
{ {
} }
/// <summary>使用指定 EM 配置创建执行器。</summary>
/// <param name="configuration">必须提供纵向停车速度容差(m/s)和零速 dwells)的配置快照。</param>
public TrajectoryExecutor(EmPlannerConfiguration configuration) public TrajectoryExecutor(EmPlannerConfiguration configuration)
{ {
if (configuration?.Longitudinal == null) if (configuration?.Longitudinal == null)
@@ -24,8 +27,17 @@ public sealed class TrajectoryExecutor
configuration.Longitudinal.ZeroSpeedHoldSeconds); configuration.Longitudinal.ZeroSpeedHoldSeconds);
} }
/// <summary>最近一次更新产生的不可变执行状态;尚未调用 <see cref="Update"/> 时为 <see langword="null"/>。</summary>
public TrajectoryExecutionState State { get; private set; } public TrajectoryExecutionState State { get; private set; }
/// <summary>按调用方时刻选择轨迹点并更新换向状态机。</summary>
/// <param name="now">调用方当前时刻;相对轨迹生效时间换算为采样秒数。</param>
/// <param name="measuredState">车辆测量状态快照;带符号纵向速度单位 m/s,用于判断是否已停稳。</param>
/// <param name="trajectory">已发布的完整不可变轨迹;开始前取首点、结束后取末点且不外推。</param>
/// <param name="desiredDirection">下一方向段要求的实际行驶方向。</param>
/// <param name="currentDirection">调用方确认的当前实际方向。</param>
/// <param name="directionConfirmed">硬件/调用方是否已确认方向变更完成。</param>
/// <returns>包含选中点、换向状态、停零、换向请求和完成标记的不可变执行状态。</returns>
public TrajectoryExecutionState Update(DateTimeOffset now, VehicleMotionState measuredState, EmTrajectory trajectory, public TrajectoryExecutionState Update(DateTimeOffset now, VehicleMotionState measuredState, EmTrajectory trajectory,
TravelDirection desiredDirection, TravelDirection currentDirection, bool directionConfirmed) TravelDirection desiredDirection, TravelDirection currentDirection, bool directionConfirmed)
{ {
@@ -45,7 +57,14 @@ public sealed class TrajectoryExecutor
return State; return State;
} }
/// <summary>Updates pure execution state and returns its controller-neutral motion command.</summary> /// <summary>更新纯执行状态并返回控制器中立命令。</summary>
/// <param name="now">调用方当前时刻。</param>
/// <param name="measuredState">车辆测量状态快照。</param>
/// <param name="trajectory">已发布完整轨迹。</param>
/// <param name="desiredDirection">目标实际行驶方向。</param>
/// <param name="currentDirection">已确认当前方向。</param>
/// <param name="directionConfirmed">是否已确认换向。</param>
/// <returns>常规跟随时输出轨迹 m/s 与 rad/s;停零、等待确认或完成时输出零运动和制动。</returns>
public TrajectoryControlCommand UpdateCommand(DateTimeOffset now, VehicleMotionState measuredState, public TrajectoryControlCommand UpdateCommand(DateTimeOffset now, VehicleMotionState measuredState,
EmTrajectory trajectory, TravelDirection desiredDirection, TravelDirection currentDirection, EmTrajectory trajectory, TravelDirection desiredDirection, TravelDirection currentDirection,
bool directionConfirmed) bool directionConfirmed)
@@ -3,12 +3,14 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>下一轮规划起点的来源;只允许安全未来样本或调用方测量状态二选一。</summary>
public enum TrajectoryHandoffSource public enum TrajectoryHandoffSource
{ {
MeasuredState, MeasuredState,
PreviousTrajectory, PreviousTrajectory,
} }
/// <summary>拒绝使用已发布轨迹进行交接的稳定原因;拒绝时必须回退到测量状态。</summary>
public enum TrajectoryHandoffRejectionReason public enum TrajectoryHandoffRejectionReason
{ {
None, None,
@@ -25,7 +27,7 @@ public enum TrajectoryHandoffRejectionReason
SampleUnavailable, SampleUnavailable,
} }
/// <summary>Immutable handoff decision for the next one-shot planning request.</summary> /// <summary>下一次单次规划的不可变交接决策;不会跨方向段、方向或终端边界复用旧轨迹。</summary>
public sealed class TrajectoryHandoffSelection public sealed class TrajectoryHandoffSelection
{ {
internal TrajectoryHandoffSelection(TrajectoryHandoffSource source, VehicleMotionState startState, internal TrajectoryHandoffSelection(TrajectoryHandoffSource source, VehicleMotionState startState,
@@ -38,6 +40,7 @@ public sealed class TrajectoryHandoffSelection
RejectionReason = rejectionReason; RejectionReason = rejectionReason;
} }
/// <summary>交接使用未来旧轨迹样本还是调用方测量状态。</summary>
public TrajectoryHandoffSource Source { get; } public TrajectoryHandoffSource Source { get; }
public VehicleMotionState StartState { get; } public VehicleMotionState StartState { get; }
@@ -49,12 +52,13 @@ public sealed class TrajectoryHandoffSelection
public TrajectoryHandoffRejectionReason RejectionReason { get; } public TrajectoryHandoffRejectionReason RejectionReason { get; }
} }
/// <summary>Selects a future same-segment seed or preserves the caller's measured state.</summary> /// <summary>选择安全的同段同方向未来轨迹种子;任一检查失败时保留调用方测量状态。</summary>
public sealed class TrajectoryHandoffSelector public sealed class TrajectoryHandoffSelector
{ {
private const double TimeEpsilonSeconds = 1e-9d; private const double TimeEpsilonSeconds = 1e-9d;
private readonly TrajectorySampler sampler; private readonly TrajectorySampler sampler;
/// <summary>使用默认同质区间采样器创建交接选择器。</summary>
public TrajectoryHandoffSelector() public TrajectoryHandoffSelector()
: this(new TrajectorySampler()) : this(new TrajectorySampler())
{ {
@@ -65,6 +69,14 @@ public sealed class TrajectoryHandoffSelector
this.sampler = sampler ?? throw new ArgumentNullException(nameof(sampler)); this.sampler = sampler ?? throw new ArgumentNullException(nameof(sampler));
} }
/// <summary>从已发布轨迹选择下一轮规划的未来交接状态。</summary>
/// <param name="previousTrajectory">最后一条完整已发布轨迹;为 <see langword="null"/> 时直接使用测量状态。</param>
/// <param name="measuredState">调用方当前车辆状态快照;位置 m、航向 rad、带符号速度 m/s。</param>
/// <param name="expectedSegmentIndex">下一轮请求期望的非负方向段索引。</param>
/// <param name="expectedDirection">下一轮请求期望的实际行驶方向。</param>
/// <param name="now">调用方当前时刻,用于检查旧轨迹年龄和 lookahead。</param>
/// <param name="configuration">包含最大状态年龄、lookahead 和追踪容差的配置快照。</param>
/// <returns>只有年龄、追踪、同段同方向、末点和边界检查均通过时返回未来轨迹样本;否则返回测量状态及拒绝原因。</returns>
public TrajectoryHandoffSelection Select(EmTrajectory previousTrajectory, VehicleMotionState measuredState, public TrajectoryHandoffSelection Select(EmTrajectory previousTrajectory, VehicleMotionState measuredState,
int expectedSegmentIndex, TravelDirection expectedDirection, DateTimeOffset now, int expectedSegmentIndex, TravelDirection expectedDirection, DateTimeOffset now,
EmPlannerConfiguration configuration) EmPlannerConfiguration configuration)
@@ -2,11 +2,16 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner; namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Interpolates an immutable trajectory only inside one homogeneous trajectory interval.</summary> /// <summary>仅在同一方向段、同一方向和同一边界类型的轨迹区间内插值不可变轨迹。</summary>
public sealed class TrajectorySampler public sealed class TrajectorySampler
{ {
private const double TimeEpsilonSeconds = 1e-9d; private const double TimeEpsilonSeconds = 1e-9d;
/// <summary>按轨迹起点后的时间采样一个同质区间内的轨迹点。</summary>
/// <param name="trajectory">完整不可变轨迹;其点序列必须按 <c>TimeFromStart</c> 严格递增。</param>
/// <param name="timeFromStart">相对轨迹起点的采样时刻,单位 s。</param>
/// <param name="sampledPoint">成功时为原始点或插值新点;失败时为 <see langword="null"/>。</param>
/// <returns>时刻位于轨迹范围内且不需跨方向段/方向/边界插值时为 <see langword="true"/>;区间外或跨边界时为 <see langword="false"/>。</returns>
public bool TrySample(EmTrajectory trajectory, double timeFromStart, out EmTrajectoryPoint sampledPoint) public bool TrySample(EmTrajectory trajectory, double timeFromStart, out EmTrajectoryPoint sampledPoint)
{ {
sampledPoint = null; sampledPoint = null;