Files
StandardSence/StandardScene.Signal/Logic/CarScanWorker.cs
T

242 lines
9.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using SimpleCore.Library;
using SimpleLite.RCS.CarTypes;
using StandardScene.Signal.Model;
using StandardScene.Signal.Plc;
namespace StandardScene.Signal.Logic
{
/// <summary>
/// 1s 扫全场车,对齐反编译 <c>DefaultPlugin.ExcuteThreadMethod</c> + <c>StarSignManage</c>。
/// </summary>
/// <remarks>
/// 每辆车每拍只看当前站点:
/// <list type="number">
/// <item>停稳:先按握手点类型分发 Handler,再执行放行点(放行点不读 PLC,可能把还在等允许进入的车开走,站点不要两边都配)。</item>
/// <item>行驶中:仅 JGControl + RequestIn 调用 <c>RequestInJudge</c>PLC 未允许进入则停车。</item>
/// </list>
/// 握手点解析优先站点 fields<see cref="SignalCarAdapter.TryReadSignalStop"/>),没有再查 JSON。
/// </remarks>
public sealed class CarScanWorker : IDisposable
{
/// <summary>取当前全部机构运行态;由 Mission 提供,内部会拷贝列表。</summary>
private readonly Func<IReadOnlyList<PlcStationRuntime>> _runtimes;
/// <summary>扫车间隔,下限 100ms,默认跟进程 TickMs=1000。</summary>
private readonly int _tickMs;
/// <summary>保护握手点 / 放行点字典,热替换配置时与扫车拍互斥。</summary>
private readonly object _configLock = new object();
/// <summary>站点 → 握手点。同一站点多条配置只留第一条。</summary>
private Dictionary<int, HandshakePointModel> _handshake = new Dictionary<int, HandshakePointModel>();
/// <summary>站点 → 放行点。同一站点多条配置只留第一条。</summary>
private Dictionary<int, ReleasePointModel> _release = new Dictionary<int, ReleasePointModel>();
/// <summary>限流区域上次记日志的车号,避免同一区域每秒刷屏。</summary>
private readonly Dictionary<string, int> _rateFlowLog = new Dictionary<string, int>();
/// <summary>车辆没有 GetChargeTime 时,用首次看到「充电中」的时刻估算秒数。</summary>
private readonly Dictionary<int, DateTime> _chargeStarted = new Dictionary<int, DateTime>();
/// <summary>未知 SignalType 的日志节流(同站点 10s 一次)。</summary>
private readonly Dictionary<int, DateTime> _unknownTypeLog = new Dictionary<int, DateTime>();
private CancellationTokenSource _cts;
private Thread _thread;
private int _tickCount;
public CarScanWorker(Func<IReadOnlyList<PlcStationRuntime>> runtimes, int tickMs)
{
_runtimes = runtimes ?? throw new ArgumentNullException(nameof(runtimes));
_tickMs = Math.Max(100, tickMs);
}
/// <summary>已完成的扫车拍数,写入进程 status 供监视。</summary>
public int TickCount => _tickCount;
/// <summary>
/// 热替换握手点 / 放行点。过滤掉站点号无效或信号类型为空的握手点。
/// 不中断线程,下一拍生效。
/// </summary>
public void ReplaceConfig(IEnumerable<HandshakePointModel> handshake, IEnumerable<ReleasePointModel> release)
{
lock (_configLock)
{
_handshake = (handshake ?? Array.Empty<HandshakePointModel>())
.Where(x => x != null && x.Sit > 0 && !string.IsNullOrWhiteSpace(x.SignalType))
.GroupBy(x => x.Sit)
.ToDictionary(g => g.Key, g => g.First());
_release = (release ?? Array.Empty<ReleasePointModel>())
.Where(x => x != null && x.Sit > 0)
.GroupBy(x => x.Sit)
.ToDictionary(g => g.Key, g => g.First());
}
}
/// <summary>启动后台扫车线程。重复 Start 若线程仍活则忽略。</summary>
public void Start()
{
if (_thread != null && _thread.IsAlive) return;
_cts = new CancellationTokenSource();
_thread = new Thread(Loop)
{
IsBackground = true,
Name = "SignalScan"
};
_thread.Start();
Diagnosis.Log("扫车握手线程已启动", "Signal", true);
}
/// <summary>取消令牌并最多等 2s 退出。不抛异常,供进程停止时调用。</summary>
public void Stop()
{
try { _cts?.Cancel(); }
catch { }
try { _thread?.Join(2000); }
catch { }
_thread = null;
Diagnosis.Log("扫车握手线程已停止", "Signal", true);
}
public void Dispose() => Stop();
private void Loop()
{
var token = _cts.Token;
while (!token.IsCancellationRequested)
{
try
{
ScanOnce();
Interlocked.Increment(ref _tickCount);
}
catch (Exception ex)
{
Diagnosis.Log($"检测信号停止的AGV是否可以放行异常:{ex.Message}", "Signal", true);
}
try { Thread.Sleep(_tickMs); }
catch (ThreadInterruptedException) { break; }
}
}
/// <summary>
/// 一拍:拷贝配置字典 → 按机构名查找闭包 → 遍历全部车。
/// 查找机构时忽略大小写,与 JSON 里 JgName 对齐。
/// </summary>
internal void ScanOnce()
{
Dictionary<int, HandshakePointModel> handshake;
Dictionary<int, ReleasePointModel> release;
lock (_configLock)
{
handshake = _handshake;
release = _release;
}
PlcStationRuntime Find(string name, string dockId)
{
if (string.IsNullOrWhiteSpace(name))
return null;
var list = _runtimes() ?? Array.Empty<PlcStationRuntime>();
var ofStation = list.Where(x =>
string.Equals(x.JgName, name, StringComparison.OrdinalIgnoreCase)).ToList();
if (ofStation.Count == 0)
return null;
if (!string.IsNullOrWhiteSpace(dockId))
{
var hit = ofStation.FirstOrDefault(x =>
string.Equals(x.DockId, dockId.Trim(), StringComparison.OrdinalIgnoreCase));
if (hit != null)
return hit;
}
return ofStation.FirstOrDefault(x =>
string.Equals(x.DockId, PlcConfigLoader.DefaultDockId, StringComparison.OrdinalIgnoreCase))
?? ofStation[0];
}
foreach (var car in SignalCarAdapter.All())
{
if (car == null) continue;
var sit = SignalCarAdapter.PositionId(car);
var spec = ResolveSpec(sit, handshake);
if (SignalCarAdapter.IsStopped(car))
{
if (spec != null)
DispatchStopped(car, spec, Find);
ReleasePointHandler.OnStopped(car, sit, release);
}
else if (spec != null && spec.Is("JGControl"))
{
JgControlHandler.OnMoving(car, spec, Find);
}
}
}
/// <summary>
/// 站点 fields 有 SignalType 则用地图点配置(老 SignalStop);
/// 否则用 handshake-points.json。fields 存在但类型为空视为本站不握手。
/// </summary>
private SignalStopSpec ResolveSpec(int sit, IReadOnlyDictionary<int, HandshakePointModel> handshake)
{
if (sit <= 0) return null;
if (SignalCarAdapter.TryReadSignalStop(sit, out var type, out var parms))
{
if (string.IsNullOrWhiteSpace(type))
return null;
return SignalStopSpec.Parse(type, parms);
}
if (handshake != null && handshake.TryGetValue(sit, out var model))
return SignalStopSpec.From(model);
return null;
}
/// <summary>
/// 停稳分发。TimedCharging 优先:充电点不应再走机构逻辑。
/// 未识别的 SignalType 按站点节流打日志,避免刷屏。
/// </summary>
private void DispatchStopped(Car car, SignalStopSpec spec, Func<string, string, PlcStationRuntime> find)
{
if (spec.Is("TimedCharging"))
{
TimedChargeHandler.OnStopped(car, spec, _chargeStarted);
return;
}
if (spec.Is("JGControl"))
{
JgControlHandler.OnStopped(car, spec, find, _rateFlowLog);
return;
}
if (spec.Is("PointControl"))
{
PointControlHandler.OnStopped(car, spec, find);
return;
}
if (spec.Is("RateFlowControl"))
{
RateFlowHandler.OnStopped(car, spec, _rateFlowLog);
return;
}
var sit = SignalCarAdapter.PositionId(car);
if (_unknownTypeLog.TryGetValue(sit, out var last) && (DateTime.Now - last).TotalSeconds < 10)
return;
_unknownTypeLog[sit] = DateTime.Now;
Diagnosis.Log($"信号类型【{spec.SignalType}】未找到匹配的业务处理模块", "Signal", true);
}
}
}