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

307 lines
11 KiB
C#
Raw Normal View History

using System;
2026-08-24 14:55:35 +08:00
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using SimpleCore.Library;
using StandardScene.Signal.Model;
namespace StandardScene.Signal.Plc
{
/// <summary>
2026-08-24 14:55:35 +08:00
/// 单机构 200ms 循环:心跳、按工位读 BOOL(或旧一字节)、回写变化位、自动离站。
/// </summary>
public sealed class PlcStationWorker : IDisposable
{
private readonly SiemensPlcSession _session;
private readonly int _pollMs;
private readonly Action _onTick;
2026-08-24 14:55:35 +08:00
private readonly PlcStationModel _station;
private readonly List<PlcStationRuntime> _docks;
private CancellationTokenSource _cts;
private Thread _thread;
private DateTime _lastFailLog = DateTime.MinValue;
2026-08-24 14:55:35 +08:00
public PlcStationWorker(
PlcStationModel station,
IEnumerable<PlcStationRuntime> docks,
SiemensPlcSession session,
object sync,
int pollMs,
Action onTick = null)
{
2026-08-24 14:55:35 +08:00
_station = station ?? throw new ArgumentNullException(nameof(station));
_docks = (docks ?? Array.Empty<PlcStationRuntime>()).Where(d => d != null).ToList();
_session = session ?? throw new ArgumentNullException(nameof(session));
2026-08-24 14:55:35 +08:00
Sync = sync ?? new object();
_pollMs = Math.Max(50, pollMs);
_onTick = onTick;
2026-08-24 14:55:35 +08:00
foreach (var d in _docks)
d.Heart = false;
}
2026-08-24 14:55:35 +08:00
public object Sync { get; }
public IReadOnlyList<PlcStationRuntime> 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,
2026-08-24 14:55:35 +08:00
Name = $"PlcStation:{_station.JgName}"
};
_thread.Start();
2026-08-24 14:55:35 +08:00
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();
2026-08-24 14:55:35 +08:00
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
{
2026-08-24 14:55:35 +08:00
lock (Sync)
Tick();
}
catch (Exception ex)
{
2026-08-24 14:55:35 +08:00
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()
{
2026-08-24 14:55:35 +08:00
var heart = !_docks.Select(d => d.Heart).FirstOrDefault();
foreach (var d in _docks)
d.Heart = heart;
2026-08-24 14:55:35 +08:00
var heartAddr = PlcConfigLoader.ResolveHeartAddress(_station);
if (!string.IsNullOrWhiteSpace(heartAddr))
{
2026-08-24 14:55:35 +08:00
if (_session.TryReadBool(heartAddr, out var bit))
{
2026-08-24 14:55:35 +08:00
_session.TryWriteBool(heartAddr, !bit);
Thread.Sleep(20);
}
else
{
MarkDisconnected("心跳读写失败");
return;
}
}
2026-08-24 14:55:35 +08:00
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)
{
2026-08-24 14:55:35 +08:00
if (!TickByteDock(rt))
return;
}
2026-08-24 14:55:35 +08:00
foreach (var rt in _docks)
{
rt.IsConnected = true;
rt.LastError = "";
PlcStationLogic.AutoLeave(rt);
}
}
private bool TickBoolDocks(List<PlcStationRuntime> docks)
{
var db = PlcConfigLoader.ResolveDb(_station);
var bytes = new List<int>();
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<int>();
foreach (var rt in docks)
{
2026-08-24 14:55:35 +08:00
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)
{
2026-08-24 14:55:35 +08:00
Diagnosis.Log(
$"AGV→机构 {rt.DisplayName} 写入【{rt.WriteSignal}】请求进入{rt.RequestInAgvNo} 到达{rt.InAgvNo}",
"JGControl", true);
EncodeWrite(buffer, origin, dock, rt.WriteSignal, changedBytes);
}
2026-08-24 14:55:35 +08:00
}
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]))
{
2026-08-24 14:55:35 +08:00
MarkDisconnected("BOOL 写失败");
return false;
}
2026-08-24 14:55:35 +08:00
}
return true;
}
2026-08-24 14:55:35 +08:00
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
{
2026-08-24 14:55:35 +08:00
MarkDisconnected($"{rt.DisplayName} 读地址失败");
return false;
}
}
2026-08-24 14:55:35 +08:00
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<int> 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<int> 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)
{
2026-08-24 14:55:35 +08:00
foreach (var d in _docks)
{
d.IsConnected = false;
d.LastError = reason;
}
LogFail($"{_station.JgName} {reason}");
}
private void LogFail(string msg)
{
2026-08-24 14:55:35 +08:00
if ((DateTime.Now - _lastFailLog).TotalSeconds < 5)
return;
_lastFailLog = DateTime.Now;
Diagnosis.Log(msg, "PLC", true);
}
}
}