Files
StandardSence/StandardScene.Signal/Plc/PlcStationWorker.cs
T

156 lines
5.1 KiB
C#

using System;
using System.Threading;
using SimpleCore.Library;
using StandardScene.Signal.Model;
namespace StandardScene.Signal.Plc
{
/// <summary>
/// 单机构 200ms 循环:心跳、读允许进出、回读写值、按需写入、自动离站。
/// 对齐反编译 <c>PLCManager.ExcuteThreadMethod</c>。
/// </summary>
public sealed class PlcStationWorker : IDisposable
{
private readonly SiemensPlcSession _session;
private readonly int _pollMs;
private readonly Action _onTick;
private CancellationTokenSource _cts;
private Thread _thread;
private DateTime _lastFailLog = DateTime.MinValue;
public PlcStationWorker(PlcStationRuntime runtime, SiemensPlcSession session, int pollMs, Action onTick = null)
{
Runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_session = session ?? throw new ArgumentNullException(nameof(session));
_pollMs = Math.Max(50, pollMs);
_onTick = onTick;
}
public PlcStationRuntime Runtime { get; }
public void Start()
{
if (_thread != null && _thread.IsAlive) return;
_cts = new CancellationTokenSource();
_thread = new Thread(Loop)
{
IsBackground = true,
Name = $"PlcStation:{Runtime.Config.JgName}"
};
_thread.Start();
Diagnosis.Log($"{Runtime.Config.JgName} Worker 已启动 → {Runtime.Config.Ip}:{Runtime.Config.Port}", "PLC", true);
}
public void Stop()
{
try { _cts?.Cancel(); }
catch { }
try { _thread?.Join(2000); }
catch { }
_thread = null;
_session.Close();
Runtime.IsConnected = false;
Diagnosis.Log($"{Runtime.Config.JgName} Worker 已停止", "PLC", true);
}
public void Dispose() => Stop();
private void Loop()
{
var token = _cts.Token;
while (!token.IsCancellationRequested)
{
try
{
lock (Runtime.Sync)
Tick();
}
catch (Exception ex)
{
Runtime.LastError = ex.Message;
Runtime.IsConnected = false;
LogFail($"{Runtime.Config.JgName} 周期异常 {ex.Message}");
}
try { _onTick?.Invoke(); }
catch { }
try { Thread.Sleep(_pollMs); }
catch (ThreadInterruptedException) { break; }
}
}
internal void Tick()
{
var cfg = Runtime.Config;
Runtime.Heart = !Runtime.Heart;
if (!string.IsNullOrWhiteSpace(cfg.HeartDb))
{
if (_session.TryReadBool(cfg.HeartDb, out var bit))
{
_session.TryWriteBool(cfg.HeartDb, !bit);
Thread.Sleep(20);
}
else
{
MarkDisconnected("心跳读写失败");
return;
}
}
if (!string.IsNullOrWhiteSpace(cfg.ReadDb))
{
if (_session.TryReadByte(cfg.ReadDb, out var rb))
Runtime.ReadSignal = PlcStationLogic.ConvertRead(rb);
else
{
MarkDisconnected("读地址失败");
return;
}
}
if (!string.IsNullOrWhiteSpace(cfg.WriteDb))
{
if (_session.TryReadByte(cfg.WriteDb, out var wb))
{
Runtime.ReadWriteSignal = PlcStationLogic.ConvertWrite(wb);
if (Runtime.ReadWriteSignal == Runtime.WriteSignal)
Runtime.WriteSuccess = true;
}
else
{
MarkDisconnected("写地址回读失败");
return;
}
if (Runtime.WriteSignal != Runtime.ReadWriteSignal && !Runtime.WriteSuccess)
{
Diagnosis.Log(
$"AGV→机构 {cfg.JgName} 写入【{Runtime.WriteSignal}】请求进入{Runtime.RequestInAgvNo} 到达{Runtime.InAgvNo}",
"JGControl", true);
_session.TryWriteByte(cfg.WriteDb, (byte)Runtime.WriteSignal);
}
}
Runtime.IsConnected = true;
Runtime.LastError = "";
PlcStationLogic.AutoLeave(Runtime);
}
private void MarkDisconnected(string reason)
{
Runtime.IsConnected = false;
Runtime.LastError = reason;
LogFail($"{Runtime.Config.JgName} {reason}");
}
private void LogFail(string msg)
{
if ((DateTime.Now - _lastFailLog).TotalSeconds < 5) return;
_lastFailLog = DateTime.Now;
Diagnosis.Log(msg, "PLC", true);
}
}
}