Files
ParkingRobot/MultiWheelC/Fleet/FleetSafetySupervisor.cs
T

287 lines
8.8 KiB
C#

using System;
using System.Collections.Generic;
using MyParking.Shared;
namespace MultiWheelC.Fleet
{
// 主车已经接收并接受的一辆成员车安全状态。
public readonly struct FleetMemberSafetyStatus
{
public FleetMemberSafetyStatus(
int vehicleId,
long planId,
bool isStateAvailable,
bool isFaulted,
int failureCode,
double lastAcceptedReportTimeSeconds)
{
if (vehicleId <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(vehicleId),
"成员车编号必须大于零。");
}
if (planId < 0)
{
throw new ArgumentOutOfRangeException(
nameof(planId),
"任务编号不能为负数。");
}
NumericGuard.EnsureFiniteNonNegative(
lastAcceptedReportTimeSeconds,
nameof(lastAcceptedReportTimeSeconds));
VehicleId = vehicleId;
PlanId = planId;
IsStateAvailable = isStateAvailable;
IsFaulted = isFaulted;
FailureCode = failureCode;
LastAcceptedReportTimeSeconds =
lastAcceptedReportTimeSeconds;
}
public int VehicleId { get; }
public long PlanId { get; }
public bool IsStateAvailable { get; }
public bool IsFaulted { get; }
// 零表示成员车没有报告结构化故障。
public int FailureCode { get; }
// 使用主车本地单调时钟,不能直接填写从车上传的时间戳。
public double LastAcceptedReportTimeSeconds { get; }
}
// 一次安全检查的结果;ShouldStop可直接作为是否停车的判断标识。
public readonly struct FleetSafetyDecision
{
internal FleetSafetyDecision(
bool shouldStop,
int sourceVehicleId,
string reason)
{
ShouldStop = shouldStop;
SourceVehicleId = sourceVehicleId;
Reason = reason ?? string.Empty;
}
public bool ShouldStop { get; }
// 零表示原因属于整个车队,而不是某一辆成员车。
public int SourceVehicleId { get; }
public string Reason { get; }
}
// 检查成员通信和健康状态,并锁存需要整队停车的首个原因。
public sealed class FleetSafetySupervisor
{
private readonly double _communicationTimeoutSeconds;
private long _activePlanId;
private FleetSafetyDecision _latchedDecision;
public FleetSafetySupervisor(
double communicationTimeoutSeconds)
{
NumericGuard.EnsureFinitePositive(
communicationTimeoutSeconds,
nameof(communicationTimeoutSeconds));
_communicationTimeoutSeconds =
communicationTimeoutSeconds;
Reset();
}
public double CommunicationTimeoutSeconds =>
_communicationTimeoutSeconds;
public long ActivePlanId => _activePlanId;
public bool IsActive => _activePlanId > 0;
public bool IsStopLatched =>
_latchedDecision.ShouldStop;
public FleetSafetyDecision LastDecision =>
_latchedDecision;
// 开始一次新任务,同时清除上一任务留下的停车锁存。
public void Start(long planId)
{
if (planId <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(planId),
"活动任务编号必须大于零。");
}
_activePlanId = planId;
_latchedDecision = CreateContinueDecision();
}
// 返回ShouldStop;本类只负责判定,实际停车由后续运行入口执行。
public FleetSafetyDecision Evaluate(
FleetLayout layout,
IReadOnlyList<FleetMemberSafetyStatus> memberStatuses,
double currentTimeSeconds)
{
if (layout == null)
{
throw new ArgumentNullException(nameof(layout));
}
if (memberStatuses == null)
{
throw new ArgumentNullException(
nameof(memberStatuses));
}
NumericGuard.EnsureFiniteNonNegative(
currentTimeSeconds,
nameof(currentTimeSeconds));
if (!IsActive)
{
return new FleetSafetyDecision(
true,
0,
"车队安全监督器尚未启动活动任务。");
}
if (IsStopLatched)
{
return _latchedDecision;
}
var statusesByVehicleId =
new Dictionary<int, FleetMemberSafetyStatus>();
for (var index = 0;
index < memberStatuses.Count;
index++)
{
var status = memberStatuses[index];
// 非当前编队成员的状态不参与本次任务安全判定。
if (!layout.TryGetVehicle(
status.VehicleId,
out _))
{
continue;
}
if (statusesByVehicleId.ContainsKey(
status.VehicleId))
{
return LatchStop(
status.VehicleId,
$"成员车{status.VehicleId}存在重复状态报告。");
}
statusesByVehicleId.Add(
status.VehicleId,
status);
}
for (var index = 0;
index < layout.Vehicles.Count;
index++)
{
var vehicleId =
layout.Vehicles[index].VehicleId;
if (!statusesByVehicleId.TryGetValue(
vehicleId,
out var status))
{
return LatchStop(
vehicleId,
$"未收到成员车{vehicleId}的状态报告。");
}
if (status.PlanId != _activePlanId)
{
return LatchStop(
vehicleId,
$"成员车{vehicleId}报告的任务编号" +
$"{status.PlanId}与当前任务" +
$"{_activePlanId}不一致。");
}
if (status.LastAcceptedReportTimeSeconds >
currentTimeSeconds)
{
return LatchStop(
vehicleId,
$"成员车{vehicleId}的主车接收时间晚于当前时间。");
}
var reportAgeSeconds =
currentTimeSeconds -
status.LastAcceptedReportTimeSeconds;
if (reportAgeSeconds >
_communicationTimeoutSeconds)
{
return LatchStop(
vehicleId,
$"成员车{vehicleId}通信超时," +
$"最近有效报告距今" +
$"{reportAgeSeconds:F3}s。");
}
if (status.IsFaulted ||
status.FailureCode != 0)
{
return LatchStop(
vehicleId,
$"成员车{vehicleId}报告故障," +
$"故障码为{status.FailureCode}。");
}
if (!status.IsStateAvailable)
{
return LatchStop(
vehicleId,
$"成员车{vehicleId}状态不可用。");
}
}
_latchedDecision = CreateContinueDecision();
return _latchedDecision;
}
// 结束当前任务并清除锁存;未开始新任务前Evaluate仍会要求停车。
public void Reset()
{
_activePlanId = 0;
_latchedDecision = CreateContinueDecision();
}
private FleetSafetyDecision LatchStop(
int sourceVehicleId,
string reason)
{
_latchedDecision = new FleetSafetyDecision(
true,
sourceVehicleId,
reason);
return _latchedDecision;
}
private static FleetSafetyDecision
CreateContinueDecision()
{
return new FleetSafetyDecision(
false,
0,
string.Empty);
}
}
}