新增1.0和2.0两种协议车型车型
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StandardScene.Fass2Simulator
|
||||
{
|
||||
/// <summary>
|
||||
/// 到站动作闭环:0xB1 登记期望 → 0xA1 或定时器补全字段 → 供调度 AtStation 判定完成。
|
||||
/// </summary>
|
||||
public sealed class Fass2SimActionEngine
|
||||
{
|
||||
private readonly Fass2SimVehicle _vehicle;
|
||||
private readonly Fass2SimConfig _config;
|
||||
private readonly Fass2SimNodeProfileStore _profiles;
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly List<PendingAction> _pendingActions = new List<PendingAction>();
|
||||
|
||||
private Fass2SimNodeMessage _expectedStation;
|
||||
|
||||
public Fass2SimActionEngine(Fass2SimVehicle vehicle, Fass2SimConfig config, Fass2SimNodeProfileStore profiles)
|
||||
{
|
||||
_vehicle = vehicle;
|
||||
_config = config;
|
||||
_profiles = profiles ?? new Fass2SimNodeProfileStore();
|
||||
}
|
||||
|
||||
public event Action StationActionsCompleted;
|
||||
|
||||
public void OnStationArrival(Fass2SimNodeMessage station)
|
||||
{
|
||||
if (station == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_pendingActions.Clear();
|
||||
_expectedStation = Clone(station);
|
||||
|
||||
ApplyArrivalFields(station);
|
||||
_vehicle.State = 2;
|
||||
|
||||
var pending = Fass2SimActionResolver.DescribePending(_expectedStation, _vehicle.CopySnapshotUnsafe());
|
||||
if (string.IsNullOrEmpty(pending))
|
||||
{
|
||||
Fass2SimLog.WriteLine($"[{Now()}] -> 站点 node={station.Node} 无待执行动作");
|
||||
NotifyStationCompleteIfReady();
|
||||
return;
|
||||
}
|
||||
|
||||
Fass2SimLog.WriteLine($"[{Now()}] -> 站点 node={station.Node} 待动作: {pending}");
|
||||
|
||||
if (_config.AutoCompleteStationActions)
|
||||
{
|
||||
SchedulePatch(Clone(station), 0, 0, ResolveActionDelayMs(station.Node));
|
||||
Fass2SimLog.WriteLine($"[{Now()}] -> AutoComplete 已排队,delay={ResolveActionDelayMs(station.Node)}ms");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnActionCommand(ulong actionId, Fass2SimNodeMessage patch)
|
||||
{
|
||||
if (patch == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (patch.Node != 0 && patch.Node != _vehicle.Node)
|
||||
{
|
||||
Fass2SimLog.WriteLine(
|
||||
$"[{Now()}] -> 0xA1 节点不匹配: patch={patch.Node}, current={_vehicle.Node},仍尝试应用");
|
||||
}
|
||||
|
||||
var delay = ResolveActionDelayMs(patch.Node != 0 ? patch.Node : _vehicle.Node);
|
||||
SchedulePatch(patch, actionId, 0, delay);
|
||||
Fass2SimLog.WriteLine(
|
||||
$"[{Now()}] -> 0xA1 actionId={actionId} 已排队,delay={delay}ms, pending={DescribePatch(patch)}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick(int deltaMs)
|
||||
{
|
||||
if (deltaMs <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<PendingAction> due = null;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
for (var i = _pendingActions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
_pendingActions[i].RemainingMs -= deltaMs;
|
||||
if (_pendingActions[i].RemainingMs <= 0)
|
||||
{
|
||||
due ??= new List<PendingAction>();
|
||||
due.Add(_pendingActions[i]);
|
||||
_pendingActions.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (due == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var action in due)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
ApplyPatch(action.Patch);
|
||||
if (action.ActionId > 0)
|
||||
{
|
||||
Fass2SimLog.WriteLine(
|
||||
$"[{Now()}] -> 动作完成 actionId={action.ActionId}, node={_vehicle.Node}, fields={DescribePatch(action.Patch)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Fass2SimLog.WriteLine(
|
||||
$"[{Now()}] -> 自动动作完成 node={_vehicle.Node}, fields={DescribePatch(action.Patch)}");
|
||||
}
|
||||
|
||||
var pending = Fass2SimActionResolver.DescribePending(_expectedStation, _vehicle.CopySnapshotUnsafe());
|
||||
if (!string.IsNullOrEmpty(pending))
|
||||
{
|
||||
Fass2SimLog.WriteLine($"[{Now()}] -> 仍有待动作: {pending}");
|
||||
}
|
||||
|
||||
NotifyStationCompleteIfReady();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsStationComplete()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return IsStationCompleteLocked();
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanLeaveStation()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return IsStationCompleteLocked();
|
||||
}
|
||||
}
|
||||
|
||||
public Fass2SimNodeMessage ExpectedStation
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _expectedStation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsStationCompleteLocked()
|
||||
{
|
||||
if (_pendingActions.Count > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_expectedStation == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Fass2SimActionResolver.IsStationActionComplete(
|
||||
_expectedStation,
|
||||
_vehicle.CopySnapshotUnsafe(),
|
||||
_vehicle.State);
|
||||
}
|
||||
|
||||
private void NotifyStationCompleteIfReady()
|
||||
{
|
||||
if (!IsStationCompleteLocked())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_expectedStation != null)
|
||||
{
|
||||
Fass2SimLog.WriteLine($"[{Now()}] -> 站点 node={_expectedStation.Node} 动作已全部完成");
|
||||
}
|
||||
|
||||
StationActionsCompleted?.Invoke();
|
||||
}
|
||||
|
||||
private void ApplyArrivalFields(Fass2SimNodeMessage station)
|
||||
{
|
||||
if (station.StartStop > 0)
|
||||
{
|
||||
_vehicle.StartStop = station.StartStop;
|
||||
}
|
||||
|
||||
if (station.Orientation > 0)
|
||||
{
|
||||
_vehicle.Orientation = station.Orientation;
|
||||
}
|
||||
|
||||
if (station.Direction > 0)
|
||||
{
|
||||
_vehicle.Direction = station.Direction;
|
||||
}
|
||||
|
||||
if (station.Byroad > 0)
|
||||
{
|
||||
_vehicle.Byroad = station.Byroad;
|
||||
}
|
||||
}
|
||||
|
||||
private void SchedulePatch(Fass2SimNodeMessage patch, ulong actionId, int source, int delayMs)
|
||||
{
|
||||
_pendingActions.Add(new PendingAction
|
||||
{
|
||||
Patch = Clone(patch),
|
||||
ActionId = actionId,
|
||||
RemainingMs = Math.Max(0, delayMs)
|
||||
});
|
||||
}
|
||||
|
||||
private void ApplyPatch(Fass2SimNodeMessage patch)
|
||||
{
|
||||
if (patch.Node != 0)
|
||||
{
|
||||
_vehicle.Node = patch.Node;
|
||||
}
|
||||
|
||||
if (patch.StartStop > 0)
|
||||
{
|
||||
_vehicle.StartStop = patch.StartStop;
|
||||
}
|
||||
if (patch.Speed > 0)
|
||||
{
|
||||
_vehicle.Speed = patch.Speed;
|
||||
}
|
||||
if (patch.Direction > 0)
|
||||
{
|
||||
_vehicle.Direction = patch.Direction;
|
||||
}
|
||||
if (patch.Orientation > 0)
|
||||
{
|
||||
_vehicle.Orientation = patch.Orientation;
|
||||
}
|
||||
if (patch.Byroad > 0)
|
||||
{
|
||||
_vehicle.Byroad = patch.Byroad;
|
||||
}
|
||||
if (patch.Obstacle > 0)
|
||||
{
|
||||
_vehicle.Obstacle = patch.Obstacle;
|
||||
}
|
||||
if (patch.Audio > 0)
|
||||
{
|
||||
_vehicle.Audio = patch.Audio;
|
||||
}
|
||||
if (patch.Light > 0)
|
||||
{
|
||||
_vehicle.Light = patch.Light;
|
||||
}
|
||||
if (patch.Charge > 0)
|
||||
{
|
||||
_vehicle.Charge = patch.Charge;
|
||||
}
|
||||
if (patch.Rest > 0)
|
||||
{
|
||||
_vehicle.Rest = patch.Rest;
|
||||
}
|
||||
if (patch.Lift > 0)
|
||||
{
|
||||
_vehicle.Lift = patch.Lift;
|
||||
}
|
||||
if (patch.Clamp > 0)
|
||||
{
|
||||
_vehicle.Clamp = patch.Clamp;
|
||||
}
|
||||
if (patch.Tray > 0)
|
||||
{
|
||||
_vehicle.Tray = patch.Tray;
|
||||
}
|
||||
if (patch.Roll > 0)
|
||||
{
|
||||
_vehicle.Roll = patch.Roll;
|
||||
}
|
||||
if (patch.Shutdown > 0)
|
||||
{
|
||||
_vehicle.Shutdown = patch.Shutdown;
|
||||
}
|
||||
}
|
||||
|
||||
private int ResolveActionDelayMs(ushort nodeId)
|
||||
{
|
||||
return _profiles.ResolveActionDelayMs(nodeId, _config.ActionDelayMs);
|
||||
}
|
||||
|
||||
private static Fass2SimNodeMessage Clone(Fass2SimNodeMessage source)
|
||||
{
|
||||
return new Fass2SimNodeMessage
|
||||
{
|
||||
Node = source.Node,
|
||||
Distance = source.Distance,
|
||||
StartStop = source.StartStop,
|
||||
Direction = source.Direction,
|
||||
Orientation = source.Orientation,
|
||||
Byroad = source.Byroad,
|
||||
Speed = source.Speed,
|
||||
Obstacle = source.Obstacle,
|
||||
Audio = source.Audio,
|
||||
Light = source.Light,
|
||||
Charge = source.Charge,
|
||||
Rest = source.Rest,
|
||||
Lift = source.Lift,
|
||||
Clamp = source.Clamp,
|
||||
Tray = source.Tray,
|
||||
Roll = source.Roll,
|
||||
Shutdown = source.Shutdown
|
||||
};
|
||||
}
|
||||
|
||||
private static string DescribePatch(Fass2SimNodeMessage patch)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
if (patch.StartStop > 0) parts.Add($"StartStop={patch.StartStop}");
|
||||
if (patch.Lift > 0) parts.Add($"Lift={patch.Lift}");
|
||||
if (patch.Charge > 0) parts.Add($"Charge={patch.Charge}");
|
||||
if (patch.Roll > 0) parts.Add($"Roll={patch.Roll}");
|
||||
if (patch.Speed > 0) parts.Add($"Speed={patch.Speed}");
|
||||
return parts.Count == 0 ? "(empty)" : string.Join(",", parts);
|
||||
}
|
||||
|
||||
private static string Now()
|
||||
{
|
||||
return DateTime.Now.ToString("HH:mm:ss.fff");
|
||||
}
|
||||
|
||||
private sealed class PendingAction
|
||||
{
|
||||
public Fass2SimNodeMessage Patch { get; set; }
|
||||
public ulong ActionId { get; set; }
|
||||
public int RemainingMs { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user