287 lines
8.8 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|