using System; using System.Collections.Generic; using MultiWheelC.StateEstimation; using MultiWheelC.Trajectory; using MyParking.Shared; namespace MultiWheelC.Fleet { /// 表示本机车队任务运行入口的生命周期阶段。 public enum FleetRuntimeState { Idle = 0, Preparing = 1, Ready = 2, Active = 3, Completed = 4, Faulted = 5 } /// 在主车或从车上串联固定布局滚动车队任务。 public sealed class FleetRuntime { private const int RuntimeFailureCode = 1; private const int FaultStopReasonCode = 1; private readonly IFleetTransport _transport; private readonly FleetMemberAgent _localAgent; private readonly IVehicleStateProvider _localStateProvider; private readonly FleetPreparationCoordinator _preparationCoordinator; private readonly FleetCoordinator _fleetCoordinator; private readonly FleetSafetySupervisor _safetySupervisor; private readonly double _commandValidForSeconds; private readonly double _preparationTimeoutSeconds; private readonly Dictionary _reports = new Dictionary(); private readonly Dictionary _lastReportSequences = new Dictionary(); private FleetLayout _activeLayout; private VehicleState? _lastLocalState; private double? _planStartTimeSeconds; private long _nextCommandSequence; private long _nextReportSequence; private long _lastReceivedCommandSequence; private long _lastAppliedCommandSequence; /// 创建本车运行入口;主车必须额外提供三个主车侧组件。 public FleetRuntime( int selfVehicleId, int leaderVehicleId, IFleetTransport transport, FleetMemberAgent localAgent, IVehicleStateProvider localStateProvider, FleetPreparationCoordinator preparationCoordinator = null, FleetCoordinator fleetCoordinator = null, FleetSafetySupervisor safetySupervisor = null, double commandValidForSeconds = 0.5, double preparationTimeoutSeconds = 10.0) { if (selfVehicleId <= 0) { throw new ArgumentOutOfRangeException(nameof(selfVehicleId)); } if (leaderVehicleId <= 0) { throw new ArgumentOutOfRangeException(nameof(leaderVehicleId)); } _transport = transport ?? throw new ArgumentNullException(nameof(transport)); _localAgent = localAgent ?? throw new ArgumentNullException(nameof(localAgent)); _localStateProvider = localStateProvider ?? throw new ArgumentNullException(nameof(localStateProvider)); NumericGuard.EnsureFinitePositive( commandValidForSeconds, nameof(commandValidForSeconds)); NumericGuard.EnsureFinitePositive( preparationTimeoutSeconds, nameof(preparationTimeoutSeconds)); if (_localAgent.VehicleId != selfVehicleId) { throw new ArgumentException( "本车执行器车号与运行入口车号不一致。", nameof(localAgent)); } SelfVehicleId = selfVehicleId; LeaderVehicleId = leaderVehicleId; _preparationCoordinator = preparationCoordinator; _fleetCoordinator = fleetCoordinator; _safetySupervisor = safetySupervisor; _commandValidForSeconds = commandValidForSeconds; _preparationTimeoutSeconds = preparationTimeoutSeconds; if (IsLeader && (_preparationCoordinator == null || _fleetCoordinator == null || _safetySupervisor == null)) { throw new ArgumentException( "主车必须提供准备协调器、车队协调器和安全监督器。"); } State = FleetRuntimeState.Idle; LastFailureReason = string.Empty; } public int SelfVehicleId { get; } public int LeaderVehicleId { get; } public bool IsLeader => SelfVehicleId == LeaderVehicleId; public FleetRuntimeState State { get; private set; } public long CurrentPlanId { get; private set; } public long LastAppliedCommandSequence => _lastAppliedCommandSequence; public string LastFailureReason { get; private set; } public FleetCoordinationCycleOutput LastCoordinationOutput { get; private set; } /// 由主车启动一次使用当前控制器固定β的滚动轨迹任务。 public bool StartRollingPlan( long planId, FleetLayout layout, Trajectory2D trajectory) { EnsureLeader(); if (planId <= 0) { throw new ArgumentOutOfRangeException(nameof(planId)); } if (layout == null) { throw new ArgumentNullException(nameof(layout)); } if (trajectory == null) { throw new ArgumentNullException(nameof(trajectory)); } if (!layout.TryGetVehicle(SelfVehicleId, out _)) { throw new ArgumentException( "主车不在当前车队布局中。", nameof(layout)); } if (State == FleetRuntimeState.Preparing || State == FleetRuntimeState.Active) { LastFailureReason = "当前车队任务尚未结束。"; return false; } ResetLeaderForNewPlan(); CurrentPlanId = planId; _activeLayout = layout; State = FleetRuntimeState.Preparing; try { _fleetCoordinator.Start(layout, trajectory); _preparationCoordinator.StartRollingPreparation( planId, layout, _fleetCoordinator.MotionDirectionInFleetRadians); _safetySupervisor.Start(planId); foreach (var target in _preparationCoordinator.Targets) { var command = NewCommand( target.VehicleId, FleetCommandKind.PrepareRolling, target.MotionDirectionInBodyRadians, Twist2D.Zero); if (target.VehicleId == SelfVehicleId) { if (!_localAgent.BeginRollingPreparation( planId, target.MotionDirectionInBodyRadians)) { return FaultLeader( LocalFailure("主车舵轮准备失败。")); } AcceptLocalCommand(command.SequenceNumber); } else { _transport.SendCommand(command); } } return true; } catch (Exception exception) { return FaultLeader( "启动车队任务失败:" + exception.Message); } } /// 执行一个本机调度周期,并返回更新后的运行阶段。 public FleetRuntimeState Update( double currentTimeSeconds, double deltaTimeSeconds) { NumericGuard.EnsureFiniteNonNegative( currentTimeSeconds, nameof(currentTimeSeconds)); NumericGuard.EnsureFinitePositive( deltaTimeSeconds, nameof(deltaTimeSeconds)); return IsLeader ? UpdateLeader(currentTimeSeconds, deltaTimeSeconds) : UpdateMember(currentTimeSeconds, deltaTimeSeconds); } /// 正常取消当前任务;主车同时向其余成员广播停止。 public void Stop(string reason = "") { if (IsLeader && CurrentPlanId > 0) { BroadcastStop(reasonCode: 0); _preparationCoordinator.Cancel(); _fleetCoordinator.Cancel(); _safetySupervisor.Reset(); } _localAgent.Stop(); _activeLayout = null; CurrentPlanId = FleetProtocol.NoActivePlanId; State = FleetRuntimeState.Idle; _planStartTimeSeconds = null; LastCoordinationOutput = null; LastFailureReason = reason ?? string.Empty; } private FleetRuntimeState UpdateLeader( double currentTimeSeconds, double deltaTimeSeconds) { if (State != FleetRuntimeState.Preparing && State != FleetRuntimeState.Active) { return State; } _planStartTimeSeconds ??= currentTimeSeconds; if (!ReceiveReports(currentTimeSeconds)) { return State; } if (!TickLocalAgent(currentTimeSeconds, deltaTimeSeconds)) { FaultLeader(LocalFailure("主车本地执行失败。")); return State; } if (!TryReadLocalState(out var localState)) { FaultLeader("主车本地状态不可用。" ); return State; } StoreLocalReport(localState.Value, currentTimeSeconds); if (State == FleetRuntimeState.Preparing) { UpdatePreparationBarrier(); if (_preparationCoordinator.State == FleetPreparationCoordinatorState.Faulted) { FaultLeader(_preparationCoordinator.LastFailureReason); return State; } if (currentTimeSeconds - _planStartTimeSeconds.Value > _preparationTimeoutSeconds) { FaultLeader("车队舵轮准备超时,未能全员Ready。" ); return State; } } if (!CheckLeaderSafety(currentTimeSeconds)) { return State; } if (State == FleetRuntimeState.Preparing && !ActivateFleet(currentTimeSeconds)) { return State; } if (State == FleetRuntimeState.Active) { RunCoordination(currentTimeSeconds, deltaTimeSeconds); } return State; } private FleetRuntimeState UpdateMember( double currentTimeSeconds, double deltaTimeSeconds) { ReceiveCommands(currentTimeSeconds); if (!TickLocalAgent(currentTimeSeconds, deltaTimeSeconds)) { if (State != FleetRuntimeState.Faulted) { FaultMember(LocalFailure("成员车本地执行失败。")); } } var hasState = TryReadLocalState(out var localState); if (!hasState && CurrentPlanId > 0 && State != FleetRuntimeState.Faulted) { FaultMember("成员车本地状态不可用。" ); } SendLocalReport(localState, hasState, currentTimeSeconds); return State; } private bool TickLocalAgent( double currentTimeSeconds, double deltaTimeSeconds) { if (State == FleetRuntimeState.Faulted) { return false; } if (_localAgent.State == FleetMemberAgentState.Preparing) { _localAgent.UpdatePreparation(deltaTimeSeconds); } if (_localAgent.State == FleetMemberAgentState.Active && !_localAgent.UpdateCommandWatchdog(currentTimeSeconds)) { return false; } if (_localAgent.State == FleetMemberAgentState.Faulted) { return false; } if (!IsLeader) { State = ToRuntimeState(_localAgent.State); } return true; } private bool TryReadLocalState(out VehicleState? state) { try { if (_localStateProvider.TryGetState(out var value)) { _lastLocalState = value; state = value; return true; } } catch (Exception exception) { LastFailureReason = "读取本车状态失败:" + exception.Message; } state = null; return false; } private bool ReceiveReports(double currentTimeSeconds) { try { while (_transport.TryReceiveReport(out var report)) { if (report.VehicleId == SelfVehicleId || _activeLayout == null || !_activeLayout.TryGetVehicle(report.VehicleId, out _) || report.PlanId != CurrentPlanId) { continue; } ValidateReport(report); if (_lastReportSequences.TryGetValue( report.VehicleId, out var sequence) && report.SequenceNumber <= sequence) { continue; } _lastReportSequences[report.VehicleId] = report.SequenceNumber; _reports[report.VehicleId] = new AcceptedReport(report, currentTimeSeconds); } return true; } catch (Exception exception) { return FaultLeader( "接收成员报告失败:" + exception.Message); } } private void StoreLocalReport( VehicleState state, double currentTimeSeconds) { _reports[SelfVehicleId] = new AcceptedReport( CreateReport(state, isStateAvailable: true), currentTimeSeconds); } private void UpdatePreparationBarrier() { foreach (var accepted in _reports.Values) { var report = accepted.Report; _preparationCoordinator.ReportMemberStatus( new FleetMemberPreparationStatus( CurrentPlanId, report.VehicleId, ToAgentState(report.State), report.State == FleetMemberState.Faulted ? $"成员车故障码{report.FailureCode}。" : string.Empty)); } } private bool CheckLeaderSafety(double currentTimeSeconds) { var statuses = new List(); foreach (var accepted in _reports.Values) { var report = accepted.Report; statuses.Add(new FleetMemberSafetyStatus( report.VehicleId, report.PlanId, report.IsStateAvailable, report.State == FleetMemberState.Faulted, report.FailureCode, accepted.ReceivedTimeSeconds)); } var graceExpired = currentTimeSeconds - _planStartTimeSeconds.Value > _safetySupervisor.CommunicationTimeoutSeconds; if (statuses.Count != _activeLayout.VehicleCount && !graceExpired) { return true; } var decision = _safetySupervisor.Evaluate( _activeLayout, statuses, currentTimeSeconds); return !decision.ShouldStop || FaultLeader(decision.Reason); } private bool ActivateFleet(double currentTimeSeconds) { if (_preparationCoordinator.State != FleetPreparationCoordinatorState.ReadyToActivate) { return false; } if (!_preparationCoordinator.TryAuthorizeActivation( CurrentPlanId)) { return FaultLeader("无法授权全队激活。" ); } var command = NewCommand( FleetProtocol.BroadcastVehicleId, FleetCommandKind.Activate, 0.0, Twist2D.Zero); if (!_localAgent.Activate( CurrentPlanId, currentTimeSeconds, command.ValidForSeconds)) { return FaultLeader(LocalFailure("主车激活失败。")); } AcceptLocalCommand(command.SequenceNumber); try { _transport.SendCommand(command); State = FleetRuntimeState.Active; return true; } catch (Exception exception) { return FaultLeader( "广播激活命令失败:" + exception.Message); } } private void RunCoordination( double currentTimeSeconds, double deltaTimeSeconds) { var result = _fleetCoordinator.ExecuteCycle( BuildMemberStates(), currentTimeSeconds, deltaTimeSeconds, out var output); LastCoordinationOutput = output; if (result == FleetCoordinationCycleResult.CommandGenerated || result == FleetCoordinationCycleResult.WaitingForState) { Dispatch(output.MemberCommands, currentTimeSeconds); return; } if (result == FleetCoordinationCycleResult.Completed) { BroadcastStop(reasonCode: 0); _localAgent.Stop(); _preparationCoordinator.Cancel(); _safetySupervisor.Reset(); State = FleetRuntimeState.Completed; return; } var reason = string.IsNullOrWhiteSpace(output.Reason) ? _fleetCoordinator.LastFailureReason : output.Reason; FaultLeader(string.IsNullOrWhiteSpace(reason) ? "车队协调器未生成可执行命令。" : reason); } private bool Dispatch( IReadOnlyList commands, double currentTimeSeconds) { if (commands == null || commands.Count != _activeLayout.VehicleCount) { return FaultLeader("成员命令数量与布局不一致。" ); } try { foreach (var memberCommand in commands) { var command = NewCommand( memberCommand.VehicleId, FleetCommandKind.Motion, 0.0, memberCommand.TwistInVehicleBody); if (memberCommand.VehicleId == SelfVehicleId) { if (!_localAgent.Execute( CurrentPlanId, memberCommand, currentTimeSeconds, command.ValidForSeconds)) { return FaultLeader( LocalFailure("主车执行速度命令失败。")); } AcceptLocalCommand(command.SequenceNumber); } else { _transport.SendCommand(command); } } return true; } catch (Exception exception) { return FaultLeader( "分发成员命令失败:" + exception.Message); } } private void ReceiveCommands(double currentTimeSeconds) { try { while (_transport.TryReceiveCommand(out var command)) { ApplyCommand(command, currentTimeSeconds); } } catch (Exception exception) { FaultMember( "接收或执行主车命令失败:" + exception.Message); } } private void ApplyCommand( FleetCommand command, double currentTimeSeconds) { if (command.TargetVehicleId != FleetProtocol.BroadcastVehicleId && command.TargetVehicleId != SelfVehicleId) { return; } ValidateCommand(command); var isPreparation = command.Kind == FleetCommandKind.PrepareRolling || command.Kind == FleetCommandKind.PrepareSpin; if (isPreparation && command.PlanId != CurrentPlanId) { if (State == FleetRuntimeState.Preparing || State == FleetRuntimeState.Ready || State == FleetRuntimeState.Active) { return; } CurrentPlanId = command.PlanId; _lastReceivedCommandSequence = 0; _lastAppliedCommandSequence = 0; LastFailureReason = string.Empty; } if (command.PlanId != CurrentPlanId || command.SequenceNumber <= _lastReceivedCommandSequence) { return; } _lastReceivedCommandSequence = command.SequenceNumber; bool accepted; switch (command.Kind) { case FleetCommandKind.PrepareRolling: accepted = _localAgent.BeginRollingPreparation( command.PlanId, command.MotionDirectionInBodyRadians); break; case FleetCommandKind.PrepareSpin: accepted = _localAgent.BeginSpinPreparation(command.PlanId); break; case FleetCommandKind.Activate: accepted = _localAgent.Activate( command.PlanId, currentTimeSeconds, command.ValidForSeconds); break; case FleetCommandKind.Motion: accepted = _localAgent.Execute( command.PlanId, new FleetMemberCommand( SelfVehicleId, command.TwistInVehicleBody), currentTimeSeconds, command.ValidForSeconds); break; case FleetCommandKind.Stop: _localAgent.Stop(); _lastAppliedCommandSequence = command.SequenceNumber; CurrentPlanId = FleetProtocol.NoActivePlanId; State = FleetRuntimeState.Idle; LastFailureReason = string.Empty; return; default: accepted = false; break; } if (!accepted) { FaultMember(LocalFailure( $"成员车拒绝{command.Kind}命令。")); return; } _lastAppliedCommandSequence = command.SequenceNumber; State = ToRuntimeState(_localAgent.State); } private void SendLocalReport( VehicleState? state, bool isStateAvailable, double currentTimeSeconds) { try { var value = state ?? _lastLocalState ?? new VehicleState( currentTimeSeconds, Pose2D.Identity, Twist2D.Zero, hasValidVelocityEstimate: false); _transport.SendReport( CreateReport(value, isStateAvailable)); } catch (Exception exception) { FaultMember( "发送成员报告失败:" + exception.Message); } } private FleetMemberReport CreateReport( VehicleState state, bool isStateAvailable) { return new FleetMemberReport( SelfVehicleId, CurrentPlanId, NextReportSequence(), state.SampleTimestampSeconds, state.PoseInWorld, state.TwistInWorld, isStateAvailable, isStateAvailable && state.HasValidVelocityEstimate, ToProtocolState(), _lastAppliedCommandSequence, State == FleetRuntimeState.Faulted ? RuntimeFailureCode : 0); } private List BuildMemberStates() { var states = new List( _activeLayout.VehicleCount); foreach (var vehicle in _activeLayout.Vehicles) { var report = _reports[vehicle.VehicleId].Report; states.Add(new FleetMemberStateSample( report.VehicleId, report.SampleTimestampSeconds, report.PoseInCommonWorld, report.TwistAtVehicleOriginInCommonWorld, report.IsStateAvailable, report.HasValidVelocityEstimate)); } return states; } private FleetCommand NewCommand( int targetVehicleId, FleetCommandKind kind, double motionDirectionInBodyRadians, Twist2D twist, int reasonCode = 0) { return new FleetCommand( CurrentPlanId, NextCommandSequence(), targetVehicleId, kind, motionDirectionInBodyRadians, twist, _commandValidForSeconds, reasonCode); } private bool FaultLeader(string reason) { var preservedReason = string.IsNullOrWhiteSpace(reason) ? "车队运行入口发生未说明故障。" : reason; if (CurrentPlanId > 0) { BroadcastStop(FaultStopReasonCode); } _localAgent.Stop(); State = FleetRuntimeState.Faulted; LastFailureReason = preservedReason; return false; } private void FaultMember(string reason) { var preservedReason = string.IsNullOrWhiteSpace(reason) ? "成员车运行入口发生未说明故障。" : reason; _localAgent.Stop(); State = FleetRuntimeState.Faulted; LastFailureReason = preservedReason; } private void BroadcastStop(int reasonCode) { try { _transport.SendCommand(NewCommand( FleetProtocol.BroadcastVehicleId, FleetCommandKind.Stop, 0.0, Twist2D.Zero, reasonCode)); } catch { // 本车仍立即停车,失联成员由各自命令看门狗兜底。 } } private void ResetLeaderForNewPlan() { _localAgent.Stop(); _preparationCoordinator.Cancel(); _fleetCoordinator.Cancel(); _safetySupervisor.Reset(); _reports.Clear(); _lastReportSequences.Clear(); _activeLayout = null; _lastLocalState = null; _planStartTimeSeconds = null; _lastReceivedCommandSequence = 0; _lastAppliedCommandSequence = 0; LastCoordinationOutput = null; LastFailureReason = string.Empty; } private void AcceptLocalCommand(long sequenceNumber) { _lastReceivedCommandSequence = sequenceNumber; _lastAppliedCommandSequence = sequenceNumber; } private long NextCommandSequence() { if (++_nextCommandSequence <= 0) { throw new InvalidOperationException("主车命令序号耗尽。" ); } return _nextCommandSequence; } private long NextReportSequence() { if (++_nextReportSequence <= 0) { throw new InvalidOperationException("成员报告序号耗尽。" ); } return _nextReportSequence; } private string LocalFailure(string fallback) { return string.IsNullOrWhiteSpace(_localAgent.LastFailureReason) ? fallback : _localAgent.LastFailureReason; } private void EnsureLeader() { if (!IsLeader) { throw new InvalidOperationException( "只有主车可以启动完整车队任务。"); } } private FleetMemberState ToProtocolState() { if (State == FleetRuntimeState.Faulted) { return FleetMemberState.Faulted; } return _localAgent.State switch { FleetMemberAgentState.Idle => FleetMemberState.Idle, FleetMemberAgentState.Preparing => FleetMemberState.Preparing, FleetMemberAgentState.Ready => FleetMemberState.Ready, FleetMemberAgentState.Active => FleetMemberState.Active, FleetMemberAgentState.Faulted => FleetMemberState.Faulted, _ => throw new InvalidOperationException("本车状态无效。") }; } private static FleetRuntimeState ToRuntimeState( FleetMemberAgentState state) { return state switch { FleetMemberAgentState.Idle => FleetRuntimeState.Idle, FleetMemberAgentState.Preparing => FleetRuntimeState.Preparing, FleetMemberAgentState.Ready => FleetRuntimeState.Ready, FleetMemberAgentState.Active => FleetRuntimeState.Active, FleetMemberAgentState.Faulted => FleetRuntimeState.Faulted, _ => throw new ArgumentOutOfRangeException(nameof(state)) }; } private static FleetMemberAgentState ToAgentState( FleetMemberState state) { return state switch { FleetMemberState.Idle => FleetMemberAgentState.Idle, FleetMemberState.Preparing => FleetMemberAgentState.Preparing, FleetMemberState.Ready => FleetMemberAgentState.Ready, FleetMemberState.Active => FleetMemberAgentState.Active, FleetMemberState.Faulted => FleetMemberAgentState.Faulted, _ => throw new ArgumentOutOfRangeException(nameof(state)) }; } private static void ValidateCommand(FleetCommand command) { if (command.PlanId <= 0 || command.SequenceNumber <= 0) { throw new ArgumentException("车队命令任务号或序号无效。" ); } if (!Enum.IsDefined(typeof(FleetCommandKind), command.Kind)) { throw new ArgumentException("车队命令类型无效。" ); } NumericGuard.EnsureFinite( command.MotionDirectionInBodyRadians, nameof(command)); NumericGuard.EnsureFinite( command.TwistInVehicleBody, nameof(command)); NumericGuard.EnsureFinitePositive( command.ValidForSeconds, nameof(command)); } private static void ValidateReport(FleetMemberReport report) { if (report.VehicleId <= 0 || report.PlanId <= 0 || report.SequenceNumber <= 0 || report.LastAppliedCommandSequence < 0 || !Enum.IsDefined(typeof(FleetMemberState), report.State)) { throw new ArgumentException("成员状态报告字段无效。" ); } NumericGuard.EnsureFiniteNonNegative( report.SampleTimestampSeconds, nameof(report)); NumericGuard.EnsureFinite( report.PoseInCommonWorld, nameof(report)); NumericGuard.EnsureFinite( report.TwistAtVehicleOriginInCommonWorld, nameof(report)); } private readonly struct AcceptedReport { public AcceptedReport( FleetMemberReport report, double receivedTimeSeconds) { Report = report; ReceivedTimeSeconds = receivedTimeSeconds; } public FleetMemberReport Report { get; } public double ReceivedTimeSeconds { get; } } } }