using System; using System.Collections.Generic; using System.Linq; using System.Threading; using LessokajiWeaverUtilities.Utilities; using Newtonsoft.Json; using SimpleCore; using SimpleCore.Library; using SimpleLite.RCS; using StandardScene.Signal.Logic; using StandardScene.Signal.Model; using StandardScene.Signal.Plc; using StandardScene.Signal.Ui; using StandardScene.Utils; namespace StandardScene.Signal { /// /// 第三方信号交互进程:PLC Worker(200ms)+ 扫车握手(1s)。 /// [MissionType(Name = "信号交互进程", editor = typeof(SignalInteractMission))] [I18N.DocumentTranslation(Name = "Signal Interact Mission", locale = "en")] public class SignalInteractMission : Mission { [FieldMember] public int TickMs = 1000; [FieldMember] public int PlcPollMs = 200; [FieldMember] public string PlcVersion = "S7_1500"; [FieldMember] public int PlcPort = 102; [FieldMember] public int PlcSlot = 1; [FieldMember] public string StationsPath = @"Config\Signal\stations.json"; [FieldMember] public string ReleasePointsPath = @"Config\Signal\release-points.json"; [FieldMember] public string HandshakePointsPath = @"Config\Signal\handshake-points.json"; [JsonIgnore] private bool _started; [JsonIgnore] private readonly object _workerLock = new object(); [JsonIgnore] private readonly List _workers = new List(); [JsonIgnore] private CarScanWorker _scan; [JsonIgnore] private int _statusTick; public class SignalInteractMissionStatus : MissionStatus { public int StationCount { get; set; } public int ConnectedCount { get; set; } public int ReleasePointCount { get; set; } public int HandshakePointCount { get; set; } public int ScanTickCount { get; set; } public string StationsFile { get; set; } public string ReleasePointsFile { get; set; } public string HandshakePointsFile { get; set; } public List Stations { get; set; } = new List(); } public override MissionStatus status { get; set; } = new SignalInteractMissionStatus(); public static Mission Create() => new SignalInteractMission(); [MethodMember(Name = "启动进程", Description = "加载配置,启动 PLC Worker 与扫车握手")] public override void Execute() { try { ReloadConfig(); _started = true; RestartWorkers(); RestartScan(); var st = (SignalInteractMissionStatus)status; Diagnosis.Post( $"信号交互进程已启动,机构={st.StationCount},握手点={st.HandshakePointCount},放行点={st.ReleasePointCount}", "Signal", true); } catch (Exception ex) { status.status = "启动失败"; Diagnosis.Post($"信号交互进程启动失败:{ExceptionFormatter.FormatEx(ex)}", "Signal", true); throw; } } [MethodMember(Name = "重新加载配置", Description = "重新读取 JSON;若进程已启动则重启 Worker/扫车")] public void ReloadConfig() { var stationsFile = SignalConfigStore.Resolve(StationsPath); var releaseFile = SignalConfigStore.Resolve(ReleasePointsPath); var handshakeFile = SignalConfigStore.Resolve(HandshakePointsPath); var stations = SignalConfigStore.Load(stationsFile); var points = SignalConfigStore.Load(releaseFile); var handshake = SignalConfigStore.Load(handshakeFile); var st = (SignalInteractMissionStatus)status; st.StationCount = stations.Count; st.ReleasePointCount = points.Count; st.HandshakePointCount = handshake.Count; st.StationsFile = stationsFile; st.ReleasePointsFile = releaseFile; st.HandshakePointsFile = handshakeFile; st.Stations = stations.Select(s => new PlcStationSnapshot { JgName = s.JgName }).ToList(); status.status = _started ? "运行中" : "配置已加载"; Diagnosis.Post( $"信号配置已加载:机构 {stations.Count};握手点 {handshake.Count} @ {handshakeFile};放行点 {points.Count} @ {releaseFile}", "Signal", true); if (_started) { RestartWorkers(); _scan?.ReplaceConfig(handshake, points); } } [MethodMember(Name = "停止进程")] public new void Stop() { _started = false; try { _scan?.Stop(); } catch { } _scan = null; StopWorkers(); status.status = "已停止"; Diagnosis.Post("信号交互进程已停止", "Signal", true); } [MethodMember(Name = "打开机构列表", Description = "按 PlcStationModel 增删改机构配置")] public void OpenStations() { try { var path = SignalConfigStore.Resolve(StationsPath); ModelListPanel.Open("PLC 机构配置", path, x => x?.JgName ?? ""); } catch (Exception ex) { CycleUiHelper.Alert("错误", $"打开机构列表失败:{ex.Message}"); } } [MethodMember(Name = "打开握手点列表", Description = "按站点配置 JGControl / PointControl / TimedCharging / RateFlowControl")] public void OpenHandshakePoints() { try { var path = SignalConfigStore.Resolve(HandshakePointsPath); ModelListPanel.Open( "握手点配置", path, x => x == null ? "" : $"{x.Sit} {x.SignalType}"); } catch (Exception ex) { CycleUiHelper.Alert("错误", $"打开握手点列表失败:{ex.Message}"); } } [MethodMember(Name = "打开放行点列表", Description = "按 ReleasePointModel 增删改放行点")] public void OpenReleasePoints() { try { var path = SignalConfigStore.Resolve(ReleasePointsPath); ModelListPanel.Open("放行点配置", path, x => x == null ? "" : x.Sit.ToString()); } catch (Exception ex) { CycleUiHelper.Alert("错误", $"打开放行点列表失败:{ex.Message}"); } } internal IReadOnlyList Runtimes { get { lock (_workerLock) return _workers.Select(w => w.Runtime).ToList(); } } private void RestartScan() { try { _scan?.Stop(); } catch { } var handshake = SignalConfigStore.Load(SignalConfigStore.Resolve(HandshakePointsPath)); var release = SignalConfigStore.Load(SignalConfigStore.Resolve(ReleasePointsPath)); _scan = new CarScanWorker(() => Runtimes, TickMs); _scan.ReplaceConfig(handshake, release); _scan.Start(); } private void RestartWorkers() { StopWorkers(); var stationsFile = SignalConfigStore.Resolve(StationsPath); var stations = SignalConfigStore.Load(stationsFile) .Where(s => !string.IsNullOrWhiteSpace(s.JgName)) .ToList(); var created = new List(); lock (_workerLock) { foreach (var station in stations) { var port = station.Port > 0 ? station.Port : PlcPort; var runtime = new PlcStationRuntime(station); var session = new SiemensPlcSession(PlcVersion, station.Ip, port, PlcSlot); var worker = new PlcStationWorker(runtime, session, PlcPollMs, RefreshStatus); _workers.Add(worker); created.Add(worker); } } foreach (var worker in created) worker.Start(); PushStatus(); } private void StopWorkers() { List copy; lock (_workerLock) { copy = _workers.ToList(); _workers.Clear(); } foreach (var w in copy) { try { w.Stop(); } catch { } } } private void RefreshStatus() { if (Interlocked.Increment(ref _statusTick) % 5 != 0) return; PushStatus(); } private void PushStatus() { List snaps; lock (_workerLock) snaps = _workers.Select(w => w.Runtime.ToSnapshot()).ToList(); var st = (SignalInteractMissionStatus)status; st.Stations = snaps; st.ConnectedCount = snaps.Count(s => s.IsConnected); st.StationCount = snaps.Count; st.ScanTickCount = _scan?.TickCount ?? 0; if (_started) status.status = $"运行中 {st.ConnectedCount}/{st.StationCount} 已连接 扫车{_scan?.TickCount ?? 0}"; } } }