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 { /// /// 1s 扫全场车,对齐反编译 DefaultPlugin.ExcuteThreadMethod + StarSignManage。 /// /// /// 每辆车每拍只看当前站点: /// /// 停稳:先按握手点类型分发 Handler,再执行放行点(放行点不读 PLC,可能把还在等允许进入的车开走,站点不要两边都配)。 /// 行驶中:仅 JGControl + RequestIn 调用 RequestInJudge,PLC 未允许进入则停车。 /// /// 握手点解析优先站点 fields(),没有再查 JSON。 /// public sealed class CarScanWorker : IDisposable { /// 取当前全部机构运行态;由 Mission 提供,内部会拷贝列表。 private readonly Func> _runtimes; /// 扫车间隔,下限 100ms,默认跟进程 TickMs=1000。 private readonly int _tickMs; /// 保护握手点 / 放行点字典,热替换配置时与扫车拍互斥。 private readonly object _configLock = new object(); /// 站点 → 握手点。同一站点多条配置只留第一条。 private Dictionary _handshake = new Dictionary(); /// 站点 → 放行点。同一站点多条配置只留第一条。 private Dictionary _release = new Dictionary(); /// 限流区域上次记日志的车号,避免同一区域每秒刷屏。 private readonly Dictionary _rateFlowLog = new Dictionary(); /// 车辆没有 GetChargeTime 时,用首次看到「充电中」的时刻估算秒数。 private readonly Dictionary _chargeStarted = new Dictionary(); /// 未知 SignalType 的日志节流(同站点 10s 一次)。 private readonly Dictionary _unknownTypeLog = new Dictionary(); private CancellationTokenSource _cts; private Thread _thread; private int _tickCount; public CarScanWorker(Func> runtimes, int tickMs) { _runtimes = runtimes ?? throw new ArgumentNullException(nameof(runtimes)); _tickMs = Math.Max(100, tickMs); } /// 已完成的扫车拍数,写入进程 status 供监视。 public int TickCount => _tickCount; /// /// 热替换握手点 / 放行点。过滤掉站点号无效或信号类型为空的握手点。 /// 不中断线程,下一拍生效。 /// public void ReplaceConfig(IEnumerable handshake, IEnumerable release) { lock (_configLock) { _handshake = (handshake ?? Array.Empty()) .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()) .Where(x => x != null && x.Sit > 0) .GroupBy(x => x.Sit) .ToDictionary(g => g.Key, g => g.First()); } } /// 启动后台扫车线程。重复 Start 若线程仍活则忽略。 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); } /// 取消令牌并最多等 2s 退出。不抛异常,供进程停止时调用。 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; } } } /// /// 一拍:拷贝配置字典 → 按机构名查找闭包 → 遍历全部车。 /// 查找机构时忽略大小写,与 JSON 里 JgName 对齐。 /// internal void ScanOnce() { Dictionary handshake; Dictionary release; lock (_configLock) { handshake = _handshake; release = _release; } PlcStationRuntime Find(string name) { if (string.IsNullOrWhiteSpace(name)) return null; var list = _runtimes() ?? Array.Empty(); return list.FirstOrDefault(x => string.Equals(x.Config.JgName, name, StringComparison.OrdinalIgnoreCase)); } 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); } } } /// /// 站点 fields 有 SignalType 则用地图点配置(老 SignalStop); /// 否则用 handshake-points.json。fields 存在但类型为空视为本站不握手。 /// private SignalStopSpec ResolveSpec(int sit, IReadOnlyDictionary 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; } /// /// 停稳分发。TimedCharging 优先:充电点不应再走机构逻辑。 /// 未识别的 SignalType 按站点节流打日志,避免刷屏。 /// private void DispatchStopped(Car car, SignalStopSpec spec, Func 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); } } }