using System; using System.Collections.Generic; using System.Linq; using System.Threading; using SimpleCore.Library; using StandardScene.Signal.Model; namespace StandardScene.Signal.Plc { /// /// 单机构 200ms 循环:心跳、按工位读 BOOL(或旧一字节)、回写变化位、自动离站。 /// public sealed class PlcStationWorker : IDisposable { private readonly SiemensPlcSession _session; private readonly int _pollMs; private readonly Action _onTick; private readonly PlcStationModel _station; private readonly List _docks; private CancellationTokenSource _cts; private Thread _thread; private DateTime _lastFailLog = DateTime.MinValue; public PlcStationWorker( PlcStationModel station, IEnumerable docks, SiemensPlcSession session, object sync, int pollMs, Action onTick = null) { _station = station ?? throw new ArgumentNullException(nameof(station)); _docks = (docks ?? Array.Empty()).Where(d => d != null).ToList(); _session = session ?? throw new ArgumentNullException(nameof(session)); Sync = sync ?? new object(); _pollMs = Math.Max(50, pollMs); _onTick = onTick; foreach (var d in _docks) d.Heart = false; } public object Sync { get; } public IReadOnlyList Docks => _docks; public PlcStationRuntime Runtime => _docks.Count > 0 ? _docks[0] : null; public void Start() { if (_thread != null && _thread.IsAlive) return; _cts = new CancellationTokenSource(); _thread = new Thread(Loop) { IsBackground = true, Name = $"PlcStation:{_station.JgName}" }; _thread.Start(); Diagnosis.Log($"{_station.JgName} Worker 已启动 → {_station.Ip}:{_station.Port} 工位{_docks.Count}", "PLC", true); } public void Stop() { try { _cts?.Cancel(); } catch { } try { _thread?.Join(2000); } catch { } _thread = null; _session.Close(); foreach (var d in _docks) d.IsConnected = false; Diagnosis.Log($"{_station.JgName} Worker 已停止", "PLC", true); } public void Dispose() => Stop(); private void Loop() { var token = _cts.Token; while (!token.IsCancellationRequested) { try { lock (Sync) Tick(); } catch (Exception ex) { foreach (var d in _docks) { d.LastError = ex.Message; d.IsConnected = false; } LogFail($"{_station.JgName} 周期异常 {ex.Message}"); } try { _onTick?.Invoke(); } catch { } try { Thread.Sleep(_pollMs); } catch (ThreadInterruptedException) { break; } } } internal void Tick() { var heart = !_docks.Select(d => d.Heart).FirstOrDefault(); foreach (var d in _docks) d.Heart = heart; var heartAddr = PlcConfigLoader.ResolveHeartAddress(_station); if (!string.IsNullOrWhiteSpace(heartAddr)) { if (_session.TryReadBool(heartAddr, out var bit)) { _session.TryWriteBool(heartAddr, !bit); Thread.Sleep(20); } else { MarkDisconnected("心跳读写失败"); return; } } var boolDocks = _docks.Where(d => !d.Dock.UseByteIo).ToList(); var byteDocks = _docks.Where(d => d.Dock.UseByteIo).ToList(); if (boolDocks.Count > 0 && !TickBoolDocks(boolDocks)) return; foreach (var rt in byteDocks) { if (!TickByteDock(rt)) return; } foreach (var rt in _docks) { rt.IsConnected = true; rt.LastError = ""; PlcStationLogic.AutoLeave(rt); } } private bool TickBoolDocks(List docks) { var db = PlcConfigLoader.ResolveDb(_station); var bytes = new List(); foreach (var rt in docks) CollectBytes(rt.Dock, bytes); if (bytes.Count == 0) return true; var origin = bytes.Min(); var last = bytes.Max(); var len = (ushort)(last - origin + 1); if (!_session.TryReadBytes(PlcAddress.Byte(db, origin), len, out var buffer) || buffer == null || buffer.Length < len) { MarkDisconnected("BOOL 块读失败"); return false; } var changedBytes = new HashSet(); foreach (var rt in docks) { var dock = rt.Dock; var allowIn = PlcAddress.GetBit(buffer, origin, dock.AllowInByte, dock.AllowInBit); var allowOut = PlcAddress.GetBit(buffer, origin, dock.AllowOutByte, dock.AllowOutBit); rt.ReadSignal = allowIn ? SignalReadValue.允许进入 : allowOut ? SignalReadValue.允许离开 : SignalReadValue.无状态; var readWrite = DecodeWrite(buffer, origin, dock); rt.ReadWriteSignal = readWrite; if (readWrite == rt.WriteSignal) rt.WriteSuccess = true; if (rt.WriteSignal != readWrite && !rt.WriteSuccess) { Diagnosis.Log( $"AGV→机构 {rt.DisplayName} 写入【{rt.WriteSignal}】请求进入{rt.RequestInAgvNo} 到达{rt.InAgvNo}", "JGControl", true); EncodeWrite(buffer, origin, dock, rt.WriteSignal, changedBytes); } } foreach (var abs in changedBytes) { var i = abs - origin; if (i < 0 || i >= buffer.Length) continue; if (!_session.TryWriteByte(PlcAddress.Byte(db, abs), buffer[i])) { MarkDisconnected("BOOL 写失败"); return false; } } return true; } private bool TickByteDock(PlcStationRuntime rt) { var cfg = rt.Dock; if (!string.IsNullOrWhiteSpace(cfg.ReadDb)) { if (_session.TryReadByte(cfg.ReadDb, out var rb)) rt.ReadSignal = PlcStationLogic.ConvertRead(rb); else { MarkDisconnected($"{rt.DisplayName} 读地址失败"); return false; } } if (string.IsNullOrWhiteSpace(cfg.WriteDb)) return true; if (!_session.TryReadByte(cfg.WriteDb, out var wb)) { MarkDisconnected($"{rt.DisplayName} 写地址回读失败"); return false; } rt.ReadWriteSignal = PlcStationLogic.ConvertWrite(wb); if (rt.ReadWriteSignal == rt.WriteSignal) rt.WriteSuccess = true; if (rt.WriteSignal != rt.ReadWriteSignal && !rt.WriteSuccess) { Diagnosis.Log( $"AGV→机构 {rt.DisplayName} 写入【{rt.WriteSignal}】请求进入{rt.RequestInAgvNo} 到达{rt.InAgvNo}", "JGControl", true); _session.TryWriteByte(cfg.WriteDb, (byte)rt.WriteSignal); } return true; } private static void CollectBytes(PlcStationDockModel dock, List bytes) { if (dock == null) return; bytes.Add(dock.AllowInByte); bytes.Add(dock.AllowOutByte); bytes.Add(dock.EnteringByte); bytes.Add(dock.ArrivedByte); bytes.Add(dock.LeavingByte); bytes.Add(dock.LeftByte); } private static SignalWriteValue DecodeWrite(byte[] buffer, int origin, PlcStationDockModel dock) { if (PlcAddress.GetBit(buffer, origin, dock.LeftByte, dock.LeftBit)) return SignalWriteValue.已离开; if (PlcAddress.GetBit(buffer, origin, dock.LeavingByte, dock.LeavingBit)) return SignalWriteValue.离开中; if (PlcAddress.GetBit(buffer, origin, dock.ArrivedByte, dock.ArrivedBit)) return SignalWriteValue.到位; if (PlcAddress.GetBit(buffer, origin, dock.EnteringByte, dock.EnteringBit)) return SignalWriteValue.进入中; return SignalWriteValue.无状态; } private static void EncodeWrite( byte[] buffer, int origin, PlcStationDockModel dock, SignalWriteValue value, HashSet changedBytes) { void Set(int abs, int bit, bool on) { var before = PlcAddress.GetBit(buffer, origin, abs, bit); if (before == on) return; PlcAddress.SetBit(buffer, origin, abs, bit, on); changedBytes.Add(abs); } Set(dock.EnteringByte, dock.EnteringBit, value == SignalWriteValue.进入中); Set(dock.ArrivedByte, dock.ArrivedBit, value == SignalWriteValue.到位); Set(dock.LeavingByte, dock.LeavingBit, value == SignalWriteValue.离开中); Set(dock.LeftByte, dock.LeftBit, value == SignalWriteValue.已离开); } private void MarkDisconnected(string reason) { foreach (var d in _docks) { d.IsConnected = false; d.LastError = reason; } LogFail($"{_station.JgName} {reason}"); } private void LogFail(string msg) { if ((DateTime.Now - _lastFailLog).TotalSeconds < 5) return; _lastFailLog = DateTime.Now; Diagnosis.Log(msg, "PLC", true); } } }