fix: FASS2 状态机与 UDP 枢纽审查项
等锁超时、全路径签名、无锁窗口不下发、Fault 终态可等待、Pass+机构不再短路、Alarm 不杀单、catch-up 停在未完成站;VehicleCode=0/重复车号拒绝注册,Hub 已运行时不抢监听口;FileLogger 空目录可写。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -80,6 +80,9 @@ namespace StandardScene.Magnetic.Tasking
|
||||
|
||||
public int ActionRetryIntervalMs { get; set; } = 1000;
|
||||
|
||||
/// <summary>交管等锁 / 管控等放行上限。0 表示不限制。</summary>
|
||||
public int TrafficWaitTimeoutMs { get; set; } = 180_000;
|
||||
|
||||
public bool StartBeforeMove { get; set; } = true;
|
||||
|
||||
public ushort HeadingAngle { get; set; }
|
||||
@@ -90,7 +93,8 @@ namespace StandardScene.Magnetic.Tasking
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return Context.Phase is Fass2TaskPhase.Idle or Fass2TaskPhase.Complete or Fass2TaskPhase.Cancelled;
|
||||
return Context.Phase is Fass2TaskPhase.Idle or Fass2TaskPhase.Complete
|
||||
or Fass2TaskPhase.Cancelled or Fass2TaskPhase.Fault;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,11 +331,6 @@ namespace StandardScene.Magnetic.Tasking
|
||||
|
||||
var result = new Fass2TaskTickResult { Phase = Context.Phase };
|
||||
|
||||
if (report?.Alarm != 0)
|
||||
{
|
||||
return Fault($"alarm=0x{report.Alarm:X}", result);
|
||||
}
|
||||
|
||||
if (report?.State == 3)
|
||||
{
|
||||
return Fault("vehicle emergency stop", result);
|
||||
@@ -432,14 +431,25 @@ namespace StandardScene.Magnetic.Tasking
|
||||
public async Task WaitForCompletionAsync(int timeoutMs, CancellationToken cancellationToken = default)
|
||||
{
|
||||
TaskCompletionSource<int> waiter;
|
||||
string failReason;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (Context.Phase is Fass2TaskPhase.Complete or Fass2TaskPhase.Cancelled or Fass2TaskPhase.Fault)
|
||||
if (Context.Phase == Fass2TaskPhase.Complete)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
waiter = _completionSource ?? ResetCompletionSource();
|
||||
waiter = _completionSource;
|
||||
failReason = Context.FaultReason;
|
||||
if (Context.Phase is not Fass2TaskPhase.Cancelled and not Fass2TaskPhase.Fault)
|
||||
{
|
||||
waiter = waiter ?? ResetCompletionSource();
|
||||
}
|
||||
}
|
||||
|
||||
if (waiter == null)
|
||||
{
|
||||
throw new InvalidOperationException(failReason ?? "task ended without completion source");
|
||||
}
|
||||
|
||||
await WaitWithTrafficPauseAsync(waiter, timeoutMs, cancellationToken);
|
||||
@@ -459,6 +469,7 @@ namespace StandardScene.Magnetic.Tasking
|
||||
var remainingMs = timeoutMs <= 0 ? -1 : timeoutMs;
|
||||
const int sliceMs = 500;
|
||||
var lastPauseLog = DateTime.MinValue;
|
||||
var pauseElapsedMs = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
@@ -500,6 +511,17 @@ namespace StandardScene.Magnetic.Tasking
|
||||
|
||||
if (waitingPaused)
|
||||
{
|
||||
if (TrafficWaitTimeoutMs > 0)
|
||||
{
|
||||
pauseElapsedMs += delayMs;
|
||||
if (pauseElapsedMs >= TrafficWaitTimeoutMs)
|
||||
{
|
||||
Cancel($"{pauseKind} timeout after {TrafficWaitTimeoutMs}ms");
|
||||
throw new TimeoutException(
|
||||
$"FASS2 {pauseKind} timeout after {TrafficWaitTimeoutMs}ms, phase={Context.Phase}, index={Context.CurrentIndex}, goal={Context.GoalSiteId}");
|
||||
}
|
||||
}
|
||||
|
||||
if ((DateTime.Now - lastPauseLog).TotalSeconds >= 5)
|
||||
{
|
||||
lastPauseLog = DateTime.Now;
|
||||
@@ -509,6 +531,8 @@ namespace StandardScene.Magnetic.Tasking
|
||||
continue;
|
||||
}
|
||||
|
||||
pauseElapsedMs = 0;
|
||||
|
||||
if (remainingMs < 0)
|
||||
{
|
||||
continue;
|
||||
@@ -552,7 +576,13 @@ namespace StandardScene.Magnetic.Tasking
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Fault($"traffic prepare failed: {ex.Message}", result);
|
||||
Context.Phase = Fass2TaskPhase.Moving;
|
||||
Context.WaitingForTraffic = true;
|
||||
result.Phase = Context.Phase;
|
||||
result.Message = $"traffic prepare wait: {ex.Message}";
|
||||
Log(result.Message);
|
||||
Persist();
|
||||
return result;
|
||||
}
|
||||
|
||||
if (StartBeforeMove && Context.CurrentIndex == 0)
|
||||
@@ -884,7 +914,7 @@ namespace StandardScene.Magnetic.Tasking
|
||||
}
|
||||
|
||||
var newSignature = Fass2SiteFieldReader.BuildFieldsSignature(
|
||||
Context.Plan.SiteIds, Context.CurrentIndex, SimpleLib.GetSite);
|
||||
Context.Plan.SiteIds, 0, SimpleLib.GetSite);
|
||||
if (string.Equals(newSignature, Context.FieldsSignature, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
@@ -1005,7 +1035,12 @@ namespace StandardScene.Magnetic.Tasking
|
||||
|
||||
lockedCount = Fass2ControlAreaGate.LimitWindowCount(
|
||||
nodes, start, lockedCount, Context.ControlReleasedIndex);
|
||||
lockedCount = Math.Max(1, Math.Min(lockedCount, wantWindow));
|
||||
lockedCount = Math.Max(0, Math.Min(lockedCount, wantWindow));
|
||||
if (lockedCount <= 0)
|
||||
{
|
||||
return Array.Empty<Fass2NodeMessage>();
|
||||
}
|
||||
|
||||
var window = new Fass2NodeMessage[lockedCount];
|
||||
for (var i = 0; i < lockedCount; i++)
|
||||
{
|
||||
@@ -1039,8 +1074,12 @@ namespace StandardScene.Magnetic.Tasking
|
||||
var catchTo = reportIndex;
|
||||
for (var i = Context.CurrentIndex; i < reportIndex; i++)
|
||||
{
|
||||
if (Fass2ControlAreaGate.IsControlStop(Context.Plan.Nodes[i].StartStop) &&
|
||||
Context.ControlReleasedIndex != i)
|
||||
var node = Context.Plan.Nodes[i];
|
||||
var unreleasedControl = Fass2ControlAreaGate.IsControlStop(node.StartStop) &&
|
||||
Context.ControlReleasedIndex != i;
|
||||
var actionStation = Fass2ActionResolver.RequiresActionWait(node) &&
|
||||
node.StartStop != Fass2TaskBuilder.StartStopControlStart;
|
||||
if (unreleasedControl || actionStation)
|
||||
{
|
||||
catchTo = i;
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user