新增1.0和2.0两种协议车型车型

This commit is contained in:
ykkokluo
2026-07-17 13:36:01 +08:00
parent a0dc1e6cd0
commit a28bc68e23
62 changed files with 12638 additions and 203 deletions
@@ -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; }
}
}
}