feat: 发布 EM 轨迹规划首个版本

This commit is contained in:
2026-08-11 20:35:59 +08:00
parent 569de5f13c
commit 1903e71fc1
522 changed files with 4188 additions and 119188 deletions
@@ -2,8 +2,14 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// 验证规划契约中必须为有限数的标量输入。
/// </summary>
internal static class ContractNumeric
{
/// <summary>
/// 拒绝 NaN 或无穷数值。<paramref name="value"/> 的单位由调用方语境决定;<paramref name="parameterName"/> 原样传给异常构造器,方法本身不验证它。
/// </summary>
public static void RequireFinite(double value, string parameterName)
{
if (double.IsNaN(value) || double.IsInfinity(value))
@@ -1,10 +1,28 @@
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// 表示 EM 规划中参考段终端或滚动窗口终端的边界类型。
/// </summary>
public enum EmBoundaryType
{
/// <summary>
/// 非边界采样点;不携带终端或换挡边界语义。
/// </summary>
None,
/// <summary>
/// 滚动窗口因安全约束形成的停车终点,而非方向段的真实终点。
/// </summary>
RollingSafetyStop,
/// <summary>
/// 到达换挡位置前的真实停车边界。
/// </summary>
GearSwitchApproach,
/// <summary>
/// 换挡后新方向段开始时的离开边界。
/// </summary>
GearSwitchDeparture,
/// <summary>
/// 整条参考路径或当前任务目标的真实终点边界。
/// </summary>
Goal,
}
@@ -1,8 +1,20 @@
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// 指定纵向规划面对当前窗口末端时采用的速度终端语义。
/// </summary>
public enum EmLongitudinalMode
{
/// <summary>
/// 普通滚动窗口末端;保持连续行驶,不要求在本窗口内停止。
/// </summary>
RollingContinuation,
/// <summary>
/// 将真实停车边界纳入窗口,但在可达性不足时仅以可停车方式接近。
/// </summary>
ApproachStopBoundary,
/// <summary>
/// 要求在当前窗口内到达真实边界并保持零速。
/// </summary>
ExactStopAtBoundary,
}
@@ -1,8 +1,20 @@
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// 请求允许的车辆运动模型;服务依据此值选择或拒绝相应规划分支。
/// </summary>
public enum EmMotionModel
{
/// <summary>
/// 常规非完整约束车辆,可前进或倒车但不能侧移。
/// </summary>
NonholonomicForwardReverse,
/// <summary>
/// 蟹行平移模型;当前 EM 管线不支持时返回明确状态。
/// </summary>
CrabTranslation,
/// <summary>
/// 原地旋转模型;当前 EM 管线不支持时返回明确状态。
/// </summary>
InPlaceRotation,
}
@@ -1,12 +1,136 @@
using System;
using System.Diagnostics;
using System.Threading;
using MultiWheelC.TrajectoryPlanning.CoarsePath;
using MultiWheelC.TrajectoryPlanning.Mapping;
using MultiWheelC.TrajectoryPlanning.PathSmoothing;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
internal interface IEmPlanningPublicationAuthorization
{
TimeSpan Remaining { get; }
bool IsExpired { get; }
EmPlanningPublicationDecision TryPublish(CancellationToken requestCallerCancellationToken,
CancellationToken coordinatorCallerCancellationToken, Action publish);
}
internal enum EmPlanningPublicationDecision
{
Published = 0,
CallerCancelled = 1,
DeadlineExpired = 2,
}
internal sealed class EmPlanningRequestPublicationAuthorization : IEmPlanningPublicationAuthorization
{
private readonly object gate = new object();
private readonly TimeSpan? initialRemaining;
private readonly Stopwatch stopwatch;
private readonly CancellationToken deadlineToken;
private bool callerCancelled;
private bool deadlineExpired;
public EmPlanningRequestPublicationAuthorization(TimeSpan? remaining, CancellationToken deadlineToken)
{
initialRemaining = remaining;
this.deadlineToken = deadlineToken;
stopwatch = remaining.HasValue ? Stopwatch.StartNew() : null;
deadlineExpired = remaining.HasValue && remaining.Value <= TimeSpan.Zero ||
deadlineToken.IsCancellationRequested;
}
public TimeSpan Remaining
{
get
{
lock (gate)
{
ObserveDeadlineInsideGate();
return ComputeRemainingInsideGate();
}
}
}
public bool IsExpired
{
get
{
lock (gate)
{
ObserveDeadlineInsideGate();
return deadlineExpired;
}
}
}
public EmPlanningPublicationDecision TryPublish(CancellationToken requestCallerCancellationToken,
CancellationToken coordinatorCallerCancellationToken, Action publish)
{
if (publish == null)
throw new ArgumentNullException(nameof(publish));
using CancellationTokenRegistration requestCallerRegistration =
requestCallerCancellationToken.Register(ObserveCallerCancellation);
using CancellationTokenRegistration coordinatorCallerRegistration =
coordinatorCallerCancellationToken.Register(ObserveCallerCancellation);
using CancellationTokenRegistration deadlineRegistration =
deadlineToken.Register(ObserveDeadlineCancellation);
lock (gate)
{
if (requestCallerCancellationToken.IsCancellationRequested ||
coordinatorCallerCancellationToken.IsCancellationRequested)
{
callerCancelled = true;
}
ObserveDeadlineInsideGate();
if (callerCancelled)
return EmPlanningPublicationDecision.CallerCancelled;
if (deadlineExpired)
return EmPlanningPublicationDecision.DeadlineExpired;
publish();
return EmPlanningPublicationDecision.Published;
}
}
private void ObserveCallerCancellation()
{
lock (gate)
callerCancelled = true;
}
private void ObserveDeadlineCancellation()
{
lock (gate)
deadlineExpired = true;
}
private void ObserveDeadlineInsideGate()
{
if (deadlineToken.IsCancellationRequested || ComputeRemainingInsideGate() <= TimeSpan.Zero)
deadlineExpired = true;
}
private TimeSpan ComputeRemainingInsideGate()
{
if (!initialRemaining.HasValue)
return deadlineExpired ? TimeSpan.Zero : TimeSpan.MaxValue;
TimeSpan remaining = initialRemaining.Value - stopwatch.Elapsed;
return remaining > TimeSpan.Zero && !deadlineExpired ? remaining : TimeSpan.Zero;
}
}
/// <summary>
/// EM 单次规划的只读输入引用快照,绑定平滑参考路径、地图、车辆状态、目标方向段和输出身份。
/// 坐标使用 m,航向使用 rad,时间使用 UTC;调用方必须在进入服务前保证这些输入属于同一业务版本。
/// </summary>
public sealed class EmPlanningRequest
{
/// <summary>
/// 保存一次规划所需的引用快照和发布身份。构造器只赋值而不校验、复制或取得对象所有权;调用方仍拥有传入引用,服务随后负责验证并复制配置。
/// </summary>
public EmPlanningRequest(
PathSmoothingResult referencePath,
PlanningGridMap map,
@@ -21,7 +145,10 @@ public sealed class EmPlanningRequest
string referencePathId,
string previousTrajectoryId,
EmMotionModel motionModel,
EmPlanningScope planningScope)
EmPlanningScope planningScope,
TimeSpan? cycleDeadlineRemaining = null,
CancellationToken callerCancellationToken = default,
CancellationToken cycleDeadlineToken = default)
{
ReferencePath = referencePath;
Map = map;
@@ -37,25 +164,166 @@ public sealed class EmPlanningRequest
PreviousTrajectoryId = previousTrajectoryId;
MotionModel = motionModel;
PlanningScope = planningScope;
CycleDeadlineRemaining = cycleDeadlineRemaining;
CallerCancellationToken = callerCancellationToken;
CycleDeadlineToken = cycleDeadlineToken;
PublicationAuthorization = new EmPlanningRequestPublicationAuthorization(
cycleDeadlineRemaining, cycleDeadlineToken);
}
internal EmPlanningRequest(
PathSmoothingResult referencePath,
PlanningGridMap map,
VehicleParameters vehicle,
VehicleMotionState vehicleState,
EmPlannerConfiguration configuration,
int segmentIndex,
EmTrajectory previousTrajectory,
DateTimeOffset requestedAtUtc,
DateTimeOffset effectiveAtUtc,
string outputTrajectoryId,
string referencePathId,
string previousTrajectoryId,
EmMotionModel motionModel,
EmPlanningScope planningScope,
TimeSpan? cycleDeadlineRemaining,
CancellationToken callerCancellationToken,
CancellationToken cycleDeadlineToken,
IEmPlanningPublicationAuthorization publicationAuthorization)
: this(referencePath, map, vehicle, vehicleState, configuration, segmentIndex, previousTrajectory,
requestedAtUtc, effectiveAtUtc, outputTrajectoryId, referencePathId, previousTrajectoryId,
motionModel, planningScope, cycleDeadlineRemaining, callerCancellationToken, cycleDeadlineToken)
{
PublicationAuthorization = publicationAuthorization ??
new EmPlanningRequestPublicationAuthorization(cycleDeadlineRemaining, cycleDeadlineToken);
}
/// <summary>
/// 平滑后的参考路径结果引用;服务要求其为可消费的成功结果,且调用方负责保持其与其他输入版本一致。
/// </summary>
public PathSmoothingResult ReferencePath { get; }
/// <summary>
/// 规划使用的栅格地图快照引用;地图坐标单位和快照身份由地图契约定义,构造器不检查 null 或就绪状态。
/// </summary>
public PlanningGridMap Map { get; }
/// <summary>
/// 车辆几何与运动限制引用;调用方拥有该对象,服务在请求验证后使用其约束。
/// </summary>
public VehicleParameters Vehicle { get; }
/// <summary>
/// 一次性捕获的车辆运动状态;其位置为世界坐标 m、航向为 rad,时效和数值有效性由服务验证。
/// </summary>
public VehicleMotionState VehicleState { get; }
/// <summary>
/// 本次规划的配置引用;请求不深拷贝它,验证通过后服务取得配置副本供本次规划使用。
/// </summary>
public EmPlannerConfiguration Configuration { get; }
/// <summary>
/// 参考路径中待规划的方向段索引;必须由服务验证为有效的非负范围。
/// </summary>
public int SegmentIndex { get; }
/// <summary>
/// 可选的上一条已发布轨迹引用,用于衔接或诊断;构造器允许为 null,调用方保有其所有权。
/// </summary>
public EmTrajectory PreviousTrajectory { get; }
/// <summary>
/// 本次规划请求发起的 UTC 时刻;用于车辆状态时效判断和生成轨迹元数据,构造器不验证其时间关系。
/// </summary>
public DateTimeOffset RequestedAtUtc { get; }
/// <summary>
/// 成功发布轨迹开始生效的 UTC 时刻;由下游消费者依据其调度语义使用。
/// </summary>
public DateTimeOffset EffectiveAtUtc { get; }
/// <summary>
/// 成功轨迹的发布标识;构造器不校验,轨迹元数据构造时要求为非空白字符串。
/// </summary>
public string OutputTrajectoryId { get; }
/// <summary>
/// 输入参考路径的业务标识,用于成功轨迹的来源追踪;构造器不校验其 null 或空白值。
/// </summary>
public string ReferencePathId { get; }
/// <summary>
/// 上一条轨迹的业务标识;允许为 null,成功轨迹元数据会将 null 规范化为空字符串。
/// </summary>
public string PreviousTrajectoryId { get; }
/// <summary>
/// 请求的车辆运动模型;不支持或无效的枚举值由服务转换为失败状态。
/// </summary>
public EmMotionModel MotionModel { get; }
/// <summary>
/// 本次请求覆盖滚动窗口或整个方向段的范围;服务负责验证其枚举值和资源约束。
/// </summary>
public EmPlanningScope PlanningScope { get; }
/// <summary>Optional remaining wall-clock budget for the complete outer planning cycle.</summary>
public TimeSpan? CycleDeadlineRemaining { get; }
/// <summary>Dynamic caller-cancellation origin used to preserve terminal-status precedence.</summary>
public CancellationToken CallerCancellationToken { get; }
/// <summary>Dynamic shared-cycle deadline origin checked again at atomic publication.</summary>
public CancellationToken CycleDeadlineToken { get; }
internal IEmPlanningPublicationAuthorization PublicationAuthorization { get; }
internal bool IsCycleDeadlineExpired()
{
try
{
return PublicationAuthorization.IsExpired;
}
catch
{
return true;
}
}
internal TimeSpan? DynamicCycleDeadlineRemaining()
{
try
{
TimeSpan remaining = PublicationAuthorization.Remaining;
return remaining > TimeSpan.Zero ? remaining : TimeSpan.Zero;
}
catch
{
return TimeSpan.Zero;
}
}
internal EmPlanningPublicationDecision TryAuthorizePublication(
CancellationToken coordinatorCallerCancellationToken, Action publish)
{
if (publish == null)
throw new ArgumentNullException(nameof(publish));
try
{
return PublicationAuthorization.TryPublish(CallerCancellationToken,
coordinatorCallerCancellationToken, publish);
}
catch
{
return EmPlanningPublicationDecision.DeadlineExpired;
}
}
}
// The request owns this DTO contract; Task 2 adds its request-bound settings.
/// <summary>
/// 为请求契约保留的 <see cref="EmPlannerConfiguration"/> 部分声明;实际配置成员由其他同名分部提供。
/// </summary>
public sealed partial class EmPlannerConfiguration
{
}
@@ -2,8 +2,14 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// 一次 EM 规划的不可变结果。只有成功状态才携带可发布的完整轨迹;其余状态仅提供诊断,不能作为部分执行轨迹消费。
/// </summary>
public sealed class EmPlanningResult
{
/// <summary>
/// 创建规划结果并强制成功状态与轨迹发布的一致性:<see cref="EmPlanningStatus.Success"/> 和 <see cref="EmPlanningStatus.SuccessWithFallback"/> 必须提供非 null 轨迹,其他状态必须提供 null 轨迹;null 失败原因规范化为空字符串。
/// </summary>
public EmPlanningResult(EmPlanningStatus status, EmTrajectory trajectory, string failureReason)
{
if (!Enum.IsDefined(typeof(EmPlanningStatus), status))
@@ -20,9 +26,18 @@ public sealed class EmPlanningResult
FailureReason = failureReason ?? string.Empty;
}
/// <summary>
/// 本次规划的最终状态;消费者必须先判断其是否为成功状态,再访问可发布轨迹。
/// </summary>
public EmPlanningStatus Status { get; }
/// <summary>
/// 仅在成功或成功降级状态下存在的完整可发布轨迹;失败、取消和无效输入结果始终为 null,消费者不得把失败结果当作部分轨迹执行。
/// </summary>
public EmTrajectory Trajectory { get; }
/// <summary>
/// 面向诊断的失败或降级原因;null 输入已规范化为空字符串,不替代 <see cref="Status"/> 的机器可读状态。
/// </summary>
public string FailureReason { get; }
}
@@ -1,7 +1,16 @@
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// 定义一次 EM 规划覆盖当前滚动窗口还是完整方向段。
/// </summary>
public enum EmPlanningScope
{
/// <summary>
/// 仅覆盖有限的滚动规划窗口;段末之外保留给后续重规划。
/// </summary>
RollingHorizon,
/// <summary>
/// 覆盖选定方向段直至真实停车边界,并受完整段资源上限保护。
/// </summary>
FullDirectionSegment,
}
@@ -1,26 +1,96 @@
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// 表示 EM 规划请求的最终处理状态;仅两个成功状态允许发布轨迹,其余状态要求消费者保留现有安全策略并读取诊断。
/// </summary>
public enum EmPlanningStatus
{
/// <summary>
/// 所有规划和验证阶段成功,结果可按其生效时间发布。
/// </summary>
Success,
/// <summary>
/// 已发布经过验证的降级或回退轨迹;消费者仍可使用轨迹,并应记录诊断原因。
/// </summary>
SuccessWithFallback,
/// <summary>
/// 请求对象或其成员未满足服务输入要求。
/// </summary>
InvalidInput,
/// <summary>
/// 请求的车辆运动模型未被当前 EM 管线支持。
/// </summary>
UnsupportedMotionMode,
/// <summary>
/// 车辆状态采集时刻相对于请求时刻过旧。
/// </summary>
StaleVehicleState,
/// <summary>
/// 车辆状态的行驶方向与选定参考路径方向段不一致。
/// </summary>
StateDirectionMismatch,
/// <summary>
/// 参考路径结果、方向段或其状态不能用于规划。
/// </summary>
InvalidReferencePath,
/// <summary>
/// 无法将车辆状态投影到选定参考路径或方向段。
/// </summary>
ProjectionFailed,
/// <summary>
/// 地图和车辆约束下未能构造可行的行驶走廊。
/// </summary>
CorridorInfeasible,
/// <summary>
/// 横向优化未得到满足约束的候选解。
/// </summary>
LateralInfeasible,
/// <summary>
/// 纵向优化未得到满足时空与动力学约束的候选解。
/// </summary>
LongitudinalInfeasible,
/// <summary>
/// 当前速度、距离或限制不足以在所需边界前完成停车。
/// </summary>
StoppingDistanceInsufficient,
/// <summary>
/// 所需二次规划求解器不可用。
/// </summary>
SolverUnavailable,
/// <summary>
/// 求解在共享时间预算或迭代限制内未完成。
/// </summary>
SolverTimedOut,
/// <summary>
/// 调用方在可发布结果生成前取消了请求。
/// </summary>
Cancelled,
/// <summary>
/// 候选轨迹未通过最终世界空间、动力学或终端语义验证。
/// </summary>
ValidationFailed,
/// <summary>
/// 请求在处理期间被更新版本的规划工作替代。
/// </summary>
Superseded,
/// <summary>
/// 候选轨迹相对于当前状态未形成足够的有效进展。
/// </summary>
NoProgress,
/// <summary>
/// 轨迹末端位姿未满足要求的真实终端位姿。
/// </summary>
TerminalPoseMismatch,
/// <summary>
/// 完整方向段规划超过配置的时间、采样或求解资源上限。
/// </summary>
FullSegmentResourceLimitExceeded,
/// <summary>
/// 未被其他状态细分的规划失败。
/// </summary>
Failed,
/// <summary>
/// The outer planning cycle exhausted its shared bootstrap-to-publication deadline.
/// </summary>
CycleDeadlineExpired,
}
@@ -1,8 +1,20 @@
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// 表示已发布轨迹末端所对应的规划终端语义。
/// </summary>
public enum EmTerminalType
{
/// <summary>
/// 因滚动窗口安全约束形成的停车终端,不代表方向段完成。
/// </summary>
RollingSafetyStop,
/// <summary>
/// 用于在方向改变前后执行换挡的终端。
/// </summary>
GearSwitch,
/// <summary>
/// 最终任务目标的终端。
/// </summary>
Goal,
}
@@ -4,8 +4,14 @@ using System.Collections.ObjectModel;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// 供成功规划结果发布的时间参数化轨迹;本类型只施加结构约束,世界空间复核由上游服务完成,点序列、元数据和终端语义在发布后保持不可变。
/// </summary>
public sealed class EmTrajectory
{
/// <summary>
/// 创建可发布的轨迹并复制点列表容器。元数据和每个点对象按引用保存、调用方仍拥有它们;传入列表可随后修改而不影响 <see cref="Points"/>,且 null 或空列表会抛出异常。
/// </summary>
public EmTrajectory(EmTrajectoryMetadata metadata, IReadOnlyList<EmTrajectoryPoint> points)
{
if (metadata == null)
@@ -27,7 +33,13 @@ public sealed class EmTrajectory
Points = new ReadOnlyCollection<EmTrajectoryPoint>(copy);
}
/// <summary>
/// 轨迹的不可变发布元数据引用;构造时必须非 null,未在本类中深拷贝。
/// </summary>
public EmTrajectoryMetadata Metadata { get; }
/// <summary>
/// 按调用方提供顺序保存的只读点列表;列表容器为构造时复制的快照,至少包含一个非 null 点,时间单调性由上游验证保证而非本构造器检查。
/// </summary>
public IReadOnlyList<EmTrajectoryPoint> Points { get; }
}
@@ -3,8 +3,14 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// 轨迹发布身份、来源版本、有效时间、方向段和终端边界的不可变元数据。
/// </summary>
public sealed class EmTrajectoryMetadata
{
/// <summary>
/// 创建轨迹的发布和来源元数据。验证标识、序列号、索引和枚举值;<paramref name="previousTrajectoryId"/> 可为 null 并会规范化为空字符串,两个 UTC 时刻的先后关系不在此处验证。
/// </summary>
public EmTrajectoryMetadata(
string trajectoryId,
DateTimeOffset generatedAtUtc,
@@ -52,16 +58,63 @@ public sealed class EmTrajectoryMetadata
PlanningScope = planningScope;
}
/// <summary>
/// 非空白的本次发布轨迹标识,由消费者用于去重、替换和追踪。
/// </summary>
public string TrajectoryId { get; }
/// <summary>
/// 生成此元数据的 UTC 时刻;值原样保存,构造器不与生效时刻比较。
/// </summary>
public DateTimeOffset GeneratedAtUtc { get; }
/// <summary>
/// 轨迹计划开始生效的 UTC 时刻;执行消费者负责按其调度策略解释。
/// </summary>
public DateTimeOffset EffectiveAtUtc { get; }
/// <summary>
/// 地图快照的非负版本标识;用于确认轨迹依赖的环境版本。
/// </summary>
public long MapSnapshotId { get; }
/// <summary>
/// 非空白的输入参考路径标识;用于关联轨迹的几何来源。
/// </summary>
public string ReferencePathId { get; }
/// <summary>
/// 车辆状态的非负序列版本;用于判断轨迹是否基于当前状态快照。
/// </summary>
public long VehicleStateSequenceId { get; }
/// <summary>
/// 前一轨迹的可选标识;null 输入已规范化为空字符串,空字符串表示没有可关联的前轨迹标识。
/// </summary>
public string PreviousTrajectoryId { get; }
/// <summary>
/// 参考路径中此轨迹所属的非负方向段索引。
/// </summary>
public int SegmentIndex { get; }
/// <summary>
/// 所属方向段的已验证行驶方向。
/// </summary>
public TravelDirection Direction { get; }
/// <summary>
/// 轨迹最后一个终端的已验证业务类型。
/// </summary>
public EmTerminalType TerminalType { get; }
/// <summary>
/// 生成轨迹时采用的已验证纵向终端速度语义。
/// </summary>
public EmLongitudinalMode LongitudinalMode { get; }
/// <summary>
/// 生成轨迹时采用的已验证规划覆盖范围。
/// </summary>
public EmPlanningScope PlanningScope { get; }
}
@@ -3,8 +3,14 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// 轨迹中的单个时间样本。世界位置单位 m、航向单位 rad、速度单位 m/s、曲率单位 1/m。
/// </summary>
public sealed class EmTrajectoryPoint
{
/// <summary>
/// 创建一个轨迹时间样本。所有浮点输入必须有限;时间、段内弧长和路径弧长必须非负,段索引和枚举值必须有效。派生的绝对速度、世界速度分量和偏航角速度由有符号纵向速度、航向和曲率计算。
/// </summary>
public EmTrajectoryPoint(
double x,
double y,
@@ -62,23 +68,88 @@ public sealed class EmTrajectoryPoint
LongitudinalJerk = longitudinalJerk;
}
/// <summary>
/// 世界坐标系 X 位置,单位 m。
/// </summary>
public double X { get; }
/// <summary>
/// 世界坐标系 Y 位置,单位 m。
/// </summary>
public double Y { get; }
/// <summary>
/// 车辆在世界坐标系中的航向,单位 rad;构造器仅要求有限,不归一化角度。
/// </summary>
public double Yaw { get; }
/// <summary>
/// 沿车辆前向轴的有符号纵向速度,单位 m/s;符号表示前进或倒车。
/// </summary>
public double SignedLongitudinalVelocity { get; }
/// <summary>
/// 有符号纵向速度的绝对值,单位 m/s。
/// </summary>
public double Speed { get; }
/// <summary>
/// 由有符号纵向速度和航向导出的世界坐标 X 速度分量,单位 m/s。
/// </summary>
public double VelocityX { get; }
/// <summary>
/// 由有符号纵向速度和航向导出的世界坐标 Y 速度分量,单位 m/s。
/// </summary>
public double VelocityY { get; }
/// <summary>
/// 由有符号纵向速度乘车辆曲率导出的偏航角速度,单位 rad/s。
/// </summary>
public double YawRate { get; }
/// <summary>
/// 从本条轨迹开始执行起累计的非负时间,单位 s。
/// </summary>
public double TimeFromStart { get; }
/// <summary>
/// 车辆路径曲率,单位 1/m;符号约定由上游几何计算定义。
/// </summary>
public double VehicleCurvature { get; }
/// <summary>
/// 此点所属的非负方向段索引。
/// </summary>
public int SegmentIndex { get; }
/// <summary>
/// 从该方向段开始沿参考路径累计的非负弧长,单位 m。
/// </summary>
public double SegmentLocalS { get; }
/// <summary>
/// 从整条参考路径开始累计的非负弧长,单位 m。
/// </summary>
public double PathS { get; }
/// <summary>
/// 此点的已验证行驶方向。
/// </summary>
public TravelDirection Direction { get; }
/// <summary>
/// 此点的已验证边界语义;普通内部点使用 <see cref="EmBoundaryType.None"/>。
/// </summary>
public EmBoundaryType BoundaryType { get; }
/// <summary>
/// 沿车辆前向轴的纵向加速度,单位 m/s²;仅供同程序集的轨迹验证和执行逻辑读取。
/// </summary>
internal double LongitudinalAcceleration { get; }
/// <summary>
/// 沿车辆前向轴的纵向加加速度,单位 m/s³;仅供同程序集的轨迹验证和执行逻辑读取。
/// </summary>
internal double LongitudinalJerk { get; }
}
@@ -3,8 +3,14 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// 调用方一次性捕获的车辆运动状态;EMPlanner 只消费此快照,不直接读取定位、轮速或系统时钟。
/// </summary>
public sealed class VehicleMotionState
{
/// <summary>
/// 保存调用方捕获的车辆状态。构造器不校验 null、数值有限性、UTC 时效或序列号;服务在接受请求前负责验证,传入 <paramref name="pose"/> 的所有权仍归调用方。
/// </summary>
public VehicleMotionState(
Pose2D pose,
double signedLongitudinalSpeedMetersPerSecond,
@@ -19,13 +25,28 @@ public sealed class VehicleMotionState
SequenceId = sequenceId;
}
/// <summary>
/// 车辆世界位姿引用,其中位置单位为 m、航向单位为 rad;构造器允许 null,但服务要求有效位姿。
/// </summary>
public Pose2D Pose { get; }
/// <summary>
/// 沿车辆前向轴的有符号纵向速度,单位 m/s;正负方向约定由上游状态生产者负责,服务要求其为有限数。
/// </summary>
public double SignedLongitudinalSpeedMetersPerSecond { get; }
/// <summary>
/// 可选的沿车辆前向轴纵向加速度,单位 m/s²;null 表示采集方未提供该量,非 null 值必须由服务验证为有限数。
/// </summary>
public double? LongitudinalAccelerationMetersPerSecondSquared { get; }
/// <summary>
/// 采集此状态的 UTC 时刻;服务用它相对请求发起时刻判断状态是否过期。
/// </summary>
public DateTimeOffset CapturedAtUtc { get; }
/// <summary>
/// 调用方提供的状态快照序列版本;服务要求其非负,成功轨迹将其写入元数据供消费者关联。
/// </summary>
public long SequenceId { get; }
}