267 lines
9.3 KiB
C#
267 lines
9.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using ClumsyCore.Interfaces;
|
|
using ClumsyCore.Pilot;
|
|
using CommonUsage.Chassis;
|
|
using MultiWheelC.Control.Execution;
|
|
using MultiWheelC.StateEstimation;
|
|
using MultiWheelC.Trajectory;
|
|
using MyParking.Shared;
|
|
|
|
namespace MultiWheelC
|
|
{
|
|
/// <summary>
|
|
/// 表示组合运动计划中由一种控制方式完整执行的单个动作段。
|
|
/// </summary>
|
|
public abstract class MotionPlanSegment
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 表示使用新版几何控制器跟踪一条连续二维轨迹的动作段。
|
|
/// </summary>
|
|
public sealed class TrackMotionPlanSegment : MotionPlanSegment
|
|
{
|
|
public TrackMotionPlanSegment(Trajectory2D trajectory)
|
|
{
|
|
Trajectory = trajectory ??
|
|
throw new ArgumentNullException(nameof(trajectory));
|
|
}
|
|
|
|
public Trajectory2D Trajectory { get; }
|
|
|
|
/// <summary>
|
|
/// 获取或设置本段轨迹独立的终点距离容差;为空时沿用轨迹动作默认值。
|
|
/// </summary>
|
|
public double? FinishDistanceMeters { get; set; }
|
|
|
|
/// <summary>
|
|
/// 获取或设置本段轨迹独立的停车速度容差;为空时沿用轨迹动作默认值。
|
|
/// </summary>
|
|
public double? FinishSpeedMetersPerSecond { get; set; }
|
|
|
|
/// <summary>
|
|
/// 获取或设置本段轨迹独立的终点航向容差;为空时沿用轨迹动作默认值。
|
|
/// </summary>
|
|
public double? FinishHeadingToleranceRadians { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 表示车辆停车后原地旋转到指定世界航向的动作段。
|
|
/// </summary>
|
|
public sealed class RotateInPlaceMotionPlanSegment
|
|
: MotionPlanSegment
|
|
{
|
|
public RotateInPlaceMotionPlanSegment(
|
|
double targetYawRadians)
|
|
{
|
|
if (double.IsNaN(targetYawRadians) ||
|
|
double.IsInfinity(targetYawRadians))
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(targetYawRadians),
|
|
"原地自转目标航向必须是有限值。");
|
|
}
|
|
|
|
TargetYawRadians =
|
|
AngleMath.NormalizeRadians(targetYawRadians);
|
|
}
|
|
|
|
public double TargetYawRadians { get; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 顺序执行连续轨迹和原地自转动作,并在动作段边界完成停车与控制器切换。
|
|
/// </summary>
|
|
public sealed class MotionPlanExecutor : MovementDefinition
|
|
{
|
|
/// <summary>
|
|
/// 获取或设置一次性提交并按顺序执行的组合运动计划。
|
|
/// </summary>
|
|
public IReadOnlyList<MotionPlanSegment> Segments;
|
|
|
|
/// <summary>
|
|
/// 获取或设置所有动作段共享的车辆状态源;为空时组合Detour位姿与电机反馈速度。
|
|
/// </summary>
|
|
public IVehicleStateProvider StateProvider;
|
|
|
|
/// <summary>
|
|
/// 获取或设置创建每段轨迹动作后应用参数的回调。
|
|
/// </summary>
|
|
public Action<TrajectoryTrackingMovement>
|
|
ConfigureTrackingMovement;
|
|
|
|
/// <summary>
|
|
/// 获取或设置创建每段原地自转动作后应用参数的回调。
|
|
/// </summary>
|
|
public Action<MultiWheelRotateInPlace>
|
|
ConfigureRotationMovement;
|
|
|
|
/// <summary>
|
|
/// 获取或设置动作段开始前的通知,参数依次为索引和动作段。
|
|
/// </summary>
|
|
public Action<int, MotionPlanSegment> SegmentStarted;
|
|
|
|
/// <summary>
|
|
/// 获取或设置轨迹段每个有效控制周期后的诊断通知。
|
|
/// </summary>
|
|
public Action<int, ParkingGeometricController>
|
|
TrackingCycleObserver;
|
|
|
|
/// <summary>
|
|
/// 获取或设置自转段角速度命令通知,角速度单位为rad/s。
|
|
/// </summary>
|
|
public Action<int, double> RotationCommandObserver;
|
|
|
|
/// <summary>
|
|
/// 按计划顺序执行各动作段,任一动作失败时停止后续动作。
|
|
/// </summary>
|
|
public override IEnumerable<bool> Get()
|
|
{
|
|
var segments = ValidateAndSnapshotSegments();
|
|
|
|
var chassis =
|
|
PilotDefinition.Chassis as MultiWheelChassis;
|
|
if (chassis == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"当前底盘不是MultiWheelChassis,无法执行组合运动计划。");
|
|
}
|
|
|
|
var stateProvider =
|
|
StateProvider ??
|
|
ParkingVehicleStateProviderFactory.Create(
|
|
chassis);
|
|
|
|
for (var index = 0;
|
|
index < segments.Count;
|
|
index++)
|
|
{
|
|
var segment = segments[index];
|
|
|
|
SegmentStarted?.Invoke(index, segment);
|
|
|
|
if (segment is TrackMotionPlanSegment track)
|
|
{
|
|
var movement =
|
|
new TrajectoryTrackingMovement
|
|
{
|
|
Trajectory = track.Trajectory,
|
|
StateProvider = stateProvider,
|
|
CycleObserver = controller =>
|
|
TrackingCycleObserver?.Invoke(
|
|
index,
|
|
controller)
|
|
};
|
|
ConfigureTrackingMovement?.Invoke(movement);
|
|
|
|
// 单段参数后应用,确保中间连接段可以覆盖组合动作的公共配置。
|
|
if (track.FinishDistanceMeters.HasValue)
|
|
{
|
|
movement.FinishDistanceMeters =
|
|
track.FinishDistanceMeters.Value;
|
|
}
|
|
|
|
if (track.FinishSpeedMetersPerSecond.HasValue)
|
|
{
|
|
movement.FinishSpeedMetersPerSecond =
|
|
track.FinishSpeedMetersPerSecond.Value;
|
|
}
|
|
|
|
if (track.FinishHeadingToleranceRadians.HasValue)
|
|
{
|
|
movement.FinishHeadingToleranceRadians =
|
|
track.FinishHeadingToleranceRadians.Value;
|
|
}
|
|
|
|
foreach (var keepRunning in movement.Get())
|
|
{
|
|
if (!keepRunning)
|
|
{
|
|
break;
|
|
}
|
|
|
|
yield return true;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (segment is RotateInPlaceMotionPlanSegment rotate)
|
|
{
|
|
var movement =
|
|
new MultiWheelRotateInPlace
|
|
{
|
|
AngleTarget =
|
|
(float)AngleMath.RadiansToDegrees(
|
|
rotate.TargetYawRadians),
|
|
StateProvider = stateProvider,
|
|
CommandAngularSpeedObserver =
|
|
commandDegreesPerSecond =>
|
|
RotationCommandObserver?.Invoke(
|
|
index,
|
|
AngleMath.DegreesToRadians(
|
|
commandDegreesPerSecond))
|
|
};
|
|
ConfigureRotationMovement?.Invoke(movement);
|
|
|
|
foreach (var keepRunning in movement.Get())
|
|
{
|
|
if (!keepRunning)
|
|
{
|
|
break;
|
|
}
|
|
|
|
yield return true;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
throw new NotSupportedException(
|
|
$"组合运动计划不支持动作段类型:{segment.GetType().FullName}。");
|
|
}
|
|
|
|
// 所有子动作均已完成后,才向外层DriveTask发送组合计划结束信号。
|
|
yield return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 在车辆动作开始前验证全部动作段,并创建本次执行使用的稳定快照。
|
|
/// </summary>
|
|
private IReadOnlyList<MotionPlanSegment>
|
|
ValidateAndSnapshotSegments()
|
|
{
|
|
if (Segments == null || Segments.Count == 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"组合运动计划至少需要包含一个动作段。");
|
|
}
|
|
|
|
var segments =
|
|
new MotionPlanSegment[Segments.Count];
|
|
|
|
for (var index = 0;
|
|
index < Segments.Count;
|
|
index++)
|
|
{
|
|
var segment = Segments[index] ??
|
|
throw new InvalidOperationException(
|
|
$"组合运动计划第{index}段为空。");
|
|
|
|
if (!(segment is TrackMotionPlanSegment) &&
|
|
!(segment is RotateInPlaceMotionPlanSegment))
|
|
{
|
|
throw new NotSupportedException(
|
|
"组合运动计划不支持动作段类型:" +
|
|
$"{segment.GetType().FullName}。");
|
|
}
|
|
|
|
segments[index] = segment;
|
|
}
|
|
|
|
return segments;
|
|
}
|
|
}
|
|
}
|