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 memberErrors, FleetMotionCommand fleetCommand, IReadOnlyList baseMemberCommands, IReadOnlyList 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 MemberErrors { get; } public FleetMotionCommand FleetCommand { get; } // 仅由车队刚体命令分解得到,尚未叠加成员相对布局纠偏。 public IReadOnlyList BaseMemberCommands { get; } // 已转换到成员当前车体系并叠加小范围相对布局纠偏的最终命令。 public IReadOnlyList MemberCommands { get; } // 因成员布局误差施加到Vx、Vy和Omega的统一比例,范围为[0,1]。 public double SpeedScale { get; } public string Reason { get; } } // 在主车上串联车队状态估计、中心轨迹控制和成员命令分解。 public sealed class FleetCoordinator { private static readonly IReadOnlyList EmptyMemberErrors = Array.AsReadOnly( Array.Empty()); private static readonly IReadOnlyList EmptyMemberCommands = Array.AsReadOnly( Array.Empty()); 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 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 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 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 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 memberErrors, IReadOnlyList memberCommands, string reason) { return new FleetCoordinationCycleOutput( state, memberErrors, FleetMotionCommand.Stop(), memberCommands, memberCommands, 0.0, reason); } } }