Files
ParkingRobot/MultiWheelC/Fleet/FleetCoordinator.cs
T

514 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using MultiWheelC.Trajectory;
using MyParking.Shared;
// 负责运动中:每周期计算每辆车的速度命令
namespace MultiWheelC.Fleet
{
// 主车单周期车队协调结果,不表示通信或成员底盘执行结果。
public enum FleetCoordinationCycleResult
{
Inactive = 0,
WaitingForState = 1,
CommandGenerated = 2,
Completed = 3,
Faulted = 4
}
// 保存本周期的状态估计、车队命令和成员基础命令。
public sealed class FleetCoordinationCycleOutput
{
internal FleetCoordinationCycleOutput(
FleetState? state,
IReadOnlyList<FleetMemberLayoutError> memberErrors,
FleetMotionCommand fleetCommand,
IReadOnlyList<FleetMemberCommand> baseMemberCommands,
IReadOnlyList<FleetMemberCommand> memberCommands,
double speedScale,
string reason)
{
NumericGuard.EnsureFiniteNonNegative(
speedScale,
nameof(speedScale));
if (speedScale > 1.0)
{
throw new ArgumentOutOfRangeException(
nameof(speedScale),
"车队统一速度比例不能大于1。");
}
State = state;
MemberErrors = memberErrors ??
throw new ArgumentNullException(
nameof(memberErrors));
FleetCommand = fleetCommand;
BaseMemberCommands = baseMemberCommands ??
throw new ArgumentNullException(
nameof(baseMemberCommands));
MemberCommands = memberCommands ??
throw new ArgumentNullException(
nameof(memberCommands));
SpeedScale = speedScale;
Reason = reason ?? string.Empty;
}
public FleetState? State { get; }
public IReadOnlyList<FleetMemberLayoutError> MemberErrors { get; }
public FleetMotionCommand FleetCommand { get; }
// 仅由车队刚体命令分解得到,尚未叠加成员相对布局纠偏。
public IReadOnlyList<FleetMemberCommand> BaseMemberCommands { get; }
// 已转换到成员当前车体系并叠加小范围相对布局纠偏的最终命令。
public IReadOnlyList<FleetMemberCommand> MemberCommands { get; }
// 因成员布局误差施加到Vx、Vy和Omega的统一比例,范围为[0,1]。
public double SpeedScale { get; }
public string Reason { get; }
}
// 在主车上串联车队状态估计、中心轨迹控制和成员命令分解。
public sealed class FleetCoordinator
{
private static readonly IReadOnlyList<FleetMemberLayoutError>
EmptyMemberErrors = Array.AsReadOnly(
Array.Empty<FleetMemberLayoutError>());
private static readonly IReadOnlyList<FleetMemberCommand>
EmptyMemberCommands = Array.AsReadOnly(
Array.Empty<FleetMemberCommand>());
private readonly FleetStateEstimator _stateEstimator;
private readonly FleetController _fleetController;
private readonly FleetMemberCommandCorrector
_memberCommandCorrector;
private readonly double _memberPositionErrorWarningMeters;
private readonly double _maximumMemberPositionErrorMeters;
private readonly double _memberYawErrorWarningRadians;
private readonly double _maximumMemberYawErrorRadians;
private FleetLayout _activeLayout;
private bool _isCompleted;
private bool _isFaulted;
public FleetCoordinator(
FleetStateEstimator stateEstimator,
FleetController fleetController,
FleetMemberCommandCorrector memberCommandCorrector,
double memberPositionErrorWarningMeters,
double maximumMemberPositionErrorMeters,
double memberYawErrorWarningRadians,
double maximumMemberYawErrorRadians)
{
_stateEstimator = stateEstimator ??
throw new ArgumentNullException(
nameof(stateEstimator));
_fleetController = fleetController ??
throw new ArgumentNullException(
nameof(fleetController));
_memberCommandCorrector =
memberCommandCorrector ??
throw new ArgumentNullException(
nameof(memberCommandCorrector));
NumericGuard.EnsureFiniteNonNegative(
memberPositionErrorWarningMeters,
nameof(memberPositionErrorWarningMeters));
NumericGuard.EnsureFinitePositive(
maximumMemberPositionErrorMeters,
nameof(maximumMemberPositionErrorMeters));
NumericGuard.EnsureFiniteNonNegative(
memberYawErrorWarningRadians,
nameof(memberYawErrorWarningRadians));
NumericGuard.EnsureFinitePositive(
maximumMemberYawErrorRadians,
nameof(maximumMemberYawErrorRadians));
if (memberPositionErrorWarningMeters >=
maximumMemberPositionErrorMeters)
{
throw new ArgumentOutOfRangeException(
nameof(memberPositionErrorWarningMeters),
"成员位置误差警告阈值必须小于停止阈值。");
}
if (memberYawErrorWarningRadians >=
maximumMemberYawErrorRadians)
{
throw new ArgumentOutOfRangeException(
nameof(memberYawErrorWarningRadians),
"成员航向误差警告阈值必须小于停止阈值。");
}
if (maximumMemberYawErrorRadians > Math.PI)
{
throw new ArgumentOutOfRangeException(
nameof(maximumMemberYawErrorRadians),
"成员航向误差上限不能大于π。");
}
_memberPositionErrorWarningMeters =
memberPositionErrorWarningMeters;
_maximumMemberPositionErrorMeters =
maximumMemberPositionErrorMeters;
_memberYawErrorWarningRadians =
memberYawErrorWarningRadians;
_maximumMemberYawErrorRadians =
maximumMemberYawErrorRadians;
LastFailureReason = string.Empty;
}
public FleetLayout ActiveLayout => _activeLayout;
public bool IsActive =>
_activeLayout != null &&
!_isCompleted &&
!_isFaulted &&
_fleetController.IsActive;
public bool IsCompleted => _isCompleted;
public bool IsFaulted => _isFaulted;
public string LastFailureReason { get; private set; }
// 运动前成员β准备必须与车队控制器采用同一个车队运动方向。
public double MotionDirectionInFleetRadians =>
_fleetController.MotionDirectionInFleetRadians;
public void Start(
FleetLayout layout,
Trajectory2D trajectory)
{
if (layout == null)
{
throw new ArgumentNullException(nameof(layout));
}
if (trajectory == null)
{
throw new ArgumentNullException(nameof(trajectory));
}
_fleetController.Start(trajectory);
_activeLayout = layout;
_isCompleted = false;
_isFaulted = false;
LastFailureReason = string.Empty;
}
public FleetCoordinationCycleResult ExecuteCycle(
IReadOnlyList<FleetMemberStateSample> memberStates,
double targetTimestampSeconds,
double deltaTimeSeconds,
out FleetCoordinationCycleOutput output)
{
if (memberStates == null)
{
throw new ArgumentNullException(nameof(memberStates));
}
NumericGuard.EnsureFiniteNonNegative(
targetTimestampSeconds,
nameof(targetTimestampSeconds));
NumericGuard.EnsureFinitePositive(
deltaTimeSeconds,
nameof(deltaTimeSeconds));
if (_activeLayout == null)
{
output = CreateOutput(
null,
EmptyMemberErrors,
EmptyMemberCommands,
"车队布局尚未激活。");
return FleetCoordinationCycleResult.Inactive;
}
if (_isFaulted)
{
output = CreateStopOutput(
null,
EmptyMemberErrors,
LastFailureReason);
return FleetCoordinationCycleResult.Faulted;
}
if (_isCompleted)
{
output = CreateStopOutput(
null,
EmptyMemberErrors,
string.Empty);
return FleetCoordinationCycleResult.Completed;
}
if (!_fleetController.IsActive)
{
output = CreateStopOutput(
null,
EmptyMemberErrors,
"车队中心控制器尚未启动或已经取消。");
return FleetCoordinationCycleResult.Inactive;
}
var estimate = _stateEstimator.Estimate(
_activeLayout,
memberStates,
targetTimestampSeconds);
if (!estimate.IsAvailable ||
!estimate.State.HasValue)
{
output = CreateStopOutput(
null,
estimate.MemberErrors,
estimate.UnavailableReason);
return FleetCoordinationCycleResult.WaitingForState;
}
var state = estimate.State.Value;
var speedScale = CalculateLayoutSpeedScale(
estimate.MemberErrors,
out var layoutFailureReason,
out var limitingReason);
if (layoutFailureReason != null)
{
return Fail(
state,
estimate.MemberErrors,
layoutFailureReason,
out output);
}
var controlResult =
_fleetController.ComputeCommand(
state,
deltaTimeSeconds,
out var fleetCommand);
if (controlResult ==
FleetControlCycleResult.CommandGenerated)
{
var scaledFleetCommand = ScaleFleetCommand(
fleetCommand,
speedScale);
var baseMemberCommands =
FleetKinematics.Decompose(
_activeLayout,
scaledFleetCommand);
var memberCommands =
_memberCommandCorrector.Correct(
_activeLayout,
baseMemberCommands,
estimate.MemberErrors,
applyRelativeCorrection:
speedScale >= 1.0);
output = new FleetCoordinationCycleOutput(
state,
estimate.MemberErrors,
scaledFleetCommand,
baseMemberCommands,
memberCommands,
speedScale,
limitingReason);
return FleetCoordinationCycleResult.CommandGenerated;
}
if (controlResult ==
FleetControlCycleResult.Completed)
{
_isCompleted = true;
output = CreateStopOutput(
state,
estimate.MemberErrors,
string.Empty);
return FleetCoordinationCycleResult.Completed;
}
if (controlResult ==
FleetControlCycleResult.Faulted)
{
var reason = string.IsNullOrWhiteSpace(
_fleetController.LastFailureReason)
? "车队中心轨迹控制失败。"
: _fleetController.LastFailureReason;
return Fail(
state,
estimate.MemberErrors,
reason,
out output,
cancelController: false);
}
output = CreateStopOutput(
state,
estimate.MemberErrors,
"车队中心控制器当前未生成命令。");
return FleetCoordinationCycleResult.Inactive;
}
public void Cancel()
{
_fleetController.Cancel();
_isCompleted = false;
_isFaulted = false;
LastFailureReason = string.Empty;
}
private double CalculateLayoutSpeedScale(
IReadOnlyList<FleetMemberLayoutError> memberErrors,
out string failureReason,
out string limitingReason)
{
var speedScale = 1.0;
failureReason = null;
limitingReason = string.Empty;
for (var index = 0;
index < memberErrors.Count;
index++)
{
var memberError = memberErrors[index];
var poseError =
memberError.ActualPoseInExpectedVehicleFrame;
var positionErrorMeters = Math.Sqrt(
poseError.XMeters * poseError.XMeters +
poseError.YMeters * poseError.YMeters);
var yawErrorRadians =
Math.Abs(poseError.YawRadians);
if (positionErrorMeters >=
_maximumMemberPositionErrorMeters)
{
failureReason =
$"车辆{memberError.VehicleId}相对布局位置误差" +
$"{positionErrorMeters:F3}m达到停止阈值。";
return 0.0;
}
if (yawErrorRadians >=
_maximumMemberYawErrorRadians)
{
failureReason =
$"车辆{memberError.VehicleId}相对布局航向误差" +
$"{AngleMath.RadiansToDegrees(yawErrorRadians):F2}°" +
"达到停止阈值。";
return 0.0;
}
var positionScale = CalculateScale(
positionErrorMeters,
_memberPositionErrorWarningMeters,
_maximumMemberPositionErrorMeters);
if (positionScale < speedScale)
{
speedScale = positionScale;
limitingReason =
$"车辆{memberError.VehicleId}相对布局位置误差" +
$"{positionErrorMeters:F3}m,车队统一速度比例" +
$"降至{speedScale:F3}。";
}
var yawScale = CalculateScale(
yawErrorRadians,
_memberYawErrorWarningRadians,
_maximumMemberYawErrorRadians);
if (yawScale < speedScale)
{
speedScale = yawScale;
limitingReason =
$"车辆{memberError.VehicleId}相对布局航向误差" +
$"{AngleMath.RadiansToDegrees(yawErrorRadians):F2}°," +
$"车队统一速度比例降至{speedScale:F3}。";
}
}
return speedScale;
}
private static double CalculateScale(
double errorMagnitude,
double warningThreshold,
double stopThreshold)
{
if (errorMagnitude <= warningThreshold)
{
return 1.0;
}
return (stopThreshold - errorMagnitude) /
(stopThreshold - warningThreshold);
}
private static FleetMotionCommand ScaleFleetCommand(
FleetMotionCommand command,
double speedScale)
{
var twist = command.TwistAtReferencePoint;
return new FleetMotionCommand(
command.ReferencePointInFleet,
new Twist2D(
twist.VxMetersPerSecond * speedScale,
twist.VyMetersPerSecond * speedScale,
twist.OmegaRadiansPerSecond * speedScale));
}
private FleetCoordinationCycleResult Fail(
FleetState state,
IReadOnlyList<FleetMemberLayoutError> memberErrors,
string reason,
out FleetCoordinationCycleOutput output,
bool cancelController = true)
{
if (cancelController)
{
_fleetController.Cancel();
}
_isFaulted = true;
_isCompleted = false;
LastFailureReason = reason ?? string.Empty;
output = CreateStopOutput(
state,
memberErrors,
LastFailureReason);
return FleetCoordinationCycleResult.Faulted;
}
private FleetCoordinationCycleOutput CreateStopOutput(
FleetState? state,
IReadOnlyList<FleetMemberLayoutError> memberErrors,
string reason)
{
var stopCommands = _activeLayout == null
? EmptyMemberCommands
: FleetKinematics.Decompose(
_activeLayout,
FleetMotionCommand.Stop());
return CreateOutput(
state,
memberErrors,
stopCommands,
reason);
}
private static FleetCoordinationCycleOutput CreateOutput(
FleetState? state,
IReadOnlyList<FleetMemberLayoutError> memberErrors,
IReadOnlyList<FleetMemberCommand> memberCommands,
string reason)
{
return new FleetCoordinationCycleOutput(
state,
memberErrors,
FleetMotionCommand.Stop(),
memberCommands,
memberCommands,
0.0,
reason);
}
}
}