Changed poller structure and attachment method

This commit is contained in:
Felix Weiß
2022-06-16 11:26:03 +02:00
parent 3911bb9707
commit 6ddc52e0b2
4 changed files with 245 additions and 149 deletions

View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Text.Json; using System.Text.Json;
using MewtocolNet; using MewtocolNet;
using MewtocolNet.Responses;
namespace Examples { namespace Examples {
class Program { class Program {
@@ -16,6 +17,8 @@ namespace Examples {
interf.AddRegister<short>("Cooler Status",1204); interf.AddRegister<short>("Cooler Status",1204);
interf.AddRegister<string>(1101, 4); interf.AddRegister<string>(1101, 4);
interf.WithPoller();
interf.RegisterChanged += (o) => { interf.RegisterChanged += (o) => {
Console.WriteLine($"DT{o.MemoryAdress} {(o.Name != null ? $"({o.Name}) " : "")}changed to {o.GetValueString()}"); Console.WriteLine($"DT{o.MemoryAdress} {(o.Name != null ? $"({o.Name}) " : "")}changed to {o.GetValueString()}");
}; };
@@ -24,11 +27,16 @@ namespace Examples {
(plcinf) => { (plcinf) => {
Console.WriteLine("Connected to PLC:\n" + plcinf.ToString()); Console.WriteLine("Connected to PLC:\n" + plcinf.ToString());
//read back a register value
var statusNum = (NRegister<short>)interf.Registers[1204];
Console.WriteLine($"Status num is: {statusNum.Value}");
}, },
() => { () => {
Console.WriteLine("Failed connection"); Console.WriteLine("Failed connection");
} }
).AttachContinousReader(50); );
}); });

View File

@@ -7,49 +7,129 @@ using System.Threading.Tasks;
using MewtocolNet.Responses; using MewtocolNet.Responses;
namespace MewtocolNet { namespace MewtocolNet {
/// <summary>
/// The PLC com interface class
/// </summary>
public partial class MewtocolInterface { public partial class MewtocolInterface {
public event Action<Register> RegisterChanged; internal event Action PolledCycle;
internal CancellationTokenSource cTokenAutoUpdater; internal CancellationTokenSource cTokenAutoUpdater;
protected internal bool isWriting = false; internal bool isWriting;
internal bool ContinousReaderRunning;
internal bool usePoller = false;
public bool ContinousReaderRunning { get; set; }
public List<Register> Registers { get; set; } = new List<Register>(); #region Register Polling
public List<Contact> Contacts { get; set; } = new List<Contact>();
/// <summary> /// <summary>
/// Trys to connect to the PLC by the IP given in the constructor /// Attaches a continous reader that reads back the Registers and Contacts
/// </summary> /// </summary>
/// <param name="OnConnected">Gets called when a connection with a PLC was established</param> internal void AttachPoller () {
/// <param name="OnFailed">Gets called when an error or timeout during connection occurs</param>
/// <returns></returns>
public async Task<MewtocolInterface> ConnectAsync (Action<PLCInfo> OnConnected = null, Action OnFailed = null) {
var plcinf = await GetPLCInfoAsync(); if (ContinousReaderRunning) return;
if(plcinf is not null) { cTokenAutoUpdater = new CancellationTokenSource();
if(OnConnected != null) OnConnected(plcinf); Console.WriteLine("Attaching cont reader");
} else { Task.Factory.StartNew(async () => {
if(OnFailed != null) OnFailed(); var plcinf = await GetPLCInfoAsync();
if (plcinf == null) {
Console.WriteLine("PLC is not reachable");
throw new Exception("PLC is not reachable");
}
if (!plcinf.OperationMode.RunMode) {
Console.WriteLine("PLC is not running");
throw new Exception("PLC is not running");
}
} ContinousReaderRunning = true;
return this; while (true) {
//dont update when currently writing a var
if (isWriting) {
continue;
}
await Task.Delay(pollingDelayMs);
foreach (var registerPair in Registers) {
var reg = registerPair.Value;
if (reg is NRegister<short> shortReg) {
var lastVal = shortReg.Value;
var readout = (await ReadNumRegister(shortReg, stationNumber)).Register.Value;
if (lastVal != readout) {
shortReg.LastValue = readout;
InvokeRegisterChanged(shortReg);
shortReg.TriggerNotifyChange();
}
}
if (reg is NRegister<ushort> ushortReg) {
var lastVal = ushortReg.Value;
var readout = (await ReadNumRegister(ushortReg, stationNumber)).Register.Value;
if (lastVal != readout) {
ushortReg.LastValue = readout;
InvokeRegisterChanged(ushortReg);
ushortReg.TriggerNotifyChange();
}
}
if (reg is NRegister<int> intReg) {
var lastVal = intReg.Value;
var readout = (await ReadNumRegister(intReg, stationNumber)).Register.Value;
if (lastVal != readout) {
intReg.LastValue = readout;
InvokeRegisterChanged(intReg);
intReg.TriggerNotifyChange();
}
}
if (reg is NRegister<uint> uintReg) {
var lastVal = uintReg.Value;
var readout = (await ReadNumRegister(uintReg, stationNumber)).Register.Value;
if (lastVal != readout) {
uintReg.LastValue = readout;
InvokeRegisterChanged(uintReg);
uintReg.TriggerNotifyChange();
}
}
if (reg is NRegister<float> floatReg) {
var lastVal = floatReg.Value;
var readout = (await ReadNumRegister(floatReg, stationNumber)).Register.Value;
if (lastVal != readout) {
floatReg.LastValue = readout;
InvokeRegisterChanged(floatReg);
floatReg.TriggerNotifyChange();
}
} else if (reg is SRegister stringReg) {
var lastVal = stringReg.Value;
var readout = (await ReadStringRegister(stringReg, stationNumber)).Register.Value;
if (lastVal != readout) {
InvokeRegisterChanged(stringReg);
stringReg.TriggerNotifyChange();
}
}
}
//invoke cycle polled event
InvokePolledCycleDone();
}
}, cTokenAutoUpdater.Token);
} }
#endregion
#region Register Adding #region Register Adding
/// <summary> /// <summary>
/// Adds a PLC memory register to the watchlist <para/> /// Adds a PLC memory register to the watchlist <para/>
/// The registers can be read back by attaching <see cref="MewtocolInterfaceExtensions.AttachContinousReader(Task{MewtocolInterface}, int)"/> /// The registers can be read back by attaching <see cref="WithPoller"/>
/// <para/>
/// to the end of a <see cref="MewtocolInterface.ConnectAsync(Action{PLCInfo}, Action)"/> method
/// </summary> /// </summary>
/// <typeparam name="T"> /// <typeparam name="T">
/// The type of the register translated from C# to IEC 61131-3 types /// The type of the register translated from C# to IEC 61131-3 types
@@ -68,17 +148,17 @@ namespace MewtocolNet {
Type regType = typeof(T); Type regType = typeof(T);
if (regType == typeof(short)) { if (regType == typeof(short)) {
Registers.Add(new NRegister<short>(_address)); Registers.Add(_address, new NRegister<short>(_address));
} else if (regType == typeof(ushort)) { } else if (regType == typeof(ushort)) {
Registers.Add(new NRegister<ushort>(_address)); Registers.Add(_address, new NRegister<ushort>(_address));
} else if (regType == typeof(int)) { } else if (regType == typeof(int)) {
Registers.Add(new NRegister<int>(_address)); Registers.Add(_address, new NRegister<int>(_address));
} else if (regType == typeof(uint)) { } else if (regType == typeof(uint)) {
Registers.Add(new NRegister<uint>(_address)); Registers.Add(_address, new NRegister<uint>(_address));
} else if (regType == typeof(float)) { } else if (regType == typeof(float)) {
Registers.Add(new NRegister<float>(_address)); Registers.Add(_address, new NRegister<float>(_address));
} else if (regType == typeof(string)) { } else if (regType == typeof(string)) {
Registers.Add(new SRegister(_address, _length)); Registers.Add(_address, new SRegister(_address, _length));
} else { } else {
throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" + throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" +
$"Allowed are: short, ushort, int, uint, float and string"); $"Allowed are: short, ushort, int, uint, float and string");
@@ -88,9 +168,7 @@ namespace MewtocolNet {
/// <summary> /// <summary>
/// Adds a PLC memory register to the watchlist <para/> /// Adds a PLC memory register to the watchlist <para/>
/// The registers can be read back by attaching <see cref="MewtocolInterfaceExtensions.AttachContinousReader(Task{MewtocolInterface}, int)"/> /// The registers can be read back by attaching <see cref="WithPoller"/>
/// <para/>
/// to the end of a <see cref="MewtocolInterface.ConnectAsync(Action{PLCInfo}, Action)"/> method
/// </summary> /// </summary>
/// <typeparam name="T"> /// <typeparam name="T">
/// The type of the register translated from C# to IEC 61131-3 types /// The type of the register translated from C# to IEC 61131-3 types
@@ -110,17 +188,17 @@ namespace MewtocolNet {
Type regType = typeof(T); Type regType = typeof(T);
if (regType == typeof(short)) { if (regType == typeof(short)) {
Registers.Add(new NRegister<short>(_address, _name)); Registers.Add(_address, new NRegister<short>(_address, _name));
} else if (regType == typeof(ushort)) { } else if (regType == typeof(ushort)) {
Registers.Add(new NRegister<ushort>(_address, _name)); Registers.Add(_address, new NRegister<ushort>(_address, _name));
} else if (regType == typeof(int)) { } else if (regType == typeof(int)) {
Registers.Add(new NRegister<int>(_address, _name)); Registers.Add(_address, new NRegister<int>(_address, _name));
} else if (regType == typeof(uint)) { } else if (regType == typeof(uint)) {
Registers.Add(new NRegister<uint>(_address, _name)); Registers.Add(_address, new NRegister<uint>(_address, _name));
} else if (regType == typeof(float)) { } else if (regType == typeof(float)) {
Registers.Add(new NRegister<float>(_address, _name)); Registers.Add(_address, new NRegister<float>(_address, _name));
} else if (regType == typeof(string)) { } else if (regType == typeof(string)) {
Registers.Add(new SRegister(_address, _length, _name)); Registers.Add(_address, new SRegister(_address, _length, _name));
} else { } else {
throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" + throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" +
$"Allowed are: short, ushort, int, uint, float and string"); $"Allowed are: short, ushort, int, uint, float and string");
@@ -132,7 +210,13 @@ namespace MewtocolNet {
internal void InvokeRegisterChanged (Register reg) { internal void InvokeRegisterChanged (Register reg) {
RegisterChanged?.Invoke (reg); RegisterChanged?.Invoke(reg);
}
internal void InvokePolledCycleDone () {
PolledCycle?.Invoke();
} }

View File

@@ -10,32 +10,140 @@ using MewtocolNet.Responses;
namespace MewtocolNet { namespace MewtocolNet {
/// <summary>
/// The PLC com interface class
/// </summary>
public partial class MewtocolInterface { public partial class MewtocolInterface {
/// <summary>
/// Gets triggered when the PLC connection was established
/// </summary>
public event Action<PLCInfo> Connected;
/// <summary>
/// Gets triggered when a registered data register changes its value
/// </summary>
public event Action<Register> RegisterChanged;
/// <summary> /// <summary>
/// Generic information about the connected PLC /// Generic information about the connected PLC
/// </summary> /// </summary>
public PLCInfo PlcInfo {get;private set;} public PLCInfo PlcInfo {get;private set;}
/// <summary>
/// The registered data registers of the PLC
/// </summary>
public Dictionary<int, Register> Registers { get; set; } = new();
private CancellationTokenSource tokenSource; private CancellationTokenSource tokenSource;
private string ip {get;set;} private string ip {get;set;}
private int port {get;set;} private int port {get;set;}
public int ConnectionTimeout {get;set;} = 2000; private int stationNumber {get;set;}
private int pollingDelayMs {get;set;}
/// <summary>
/// The current IP of the PLC connection
/// </summary>
public string IpAddress => ip;
/// <summary>
/// The current port of the PLC connection
/// </summary>
public int Port => port;
/// <summary>
/// The station number of the PLC
/// </summary>
public int StationNumber => stationNumber;
#region Initialization #region Initialization
/// <summary> /// <summary>
/// Builds a new Interfacer for a PLC /// Builds a new Interfacer for a PLC
/// </summary> /// </summary>
/// <param name="_ip"></param> /// <param name="_ip">IP adress of the PLC</param>
/// <param name="_port"></param> /// <param name="_port">Port of the PLC</param>
public MewtocolInterface (string _ip, int _port = 9094) { /// <param name="_station">Station Number of the PLC</param>
public MewtocolInterface (string _ip, int _port = 9094, int _station = 1) {
ip = _ip; ip = _ip;
port = _port; port = _port;
stationNumber = _station;
Connected += MewtocolInterface_Connected;
void MewtocolInterface_Connected (PLCInfo obj) {
if (usePoller)
AttachPoller();
}
} }
#endregion
#region Setup
/// <summary>
/// Trys to connect to the PLC by the IP given in the constructor
/// </summary>
/// <param name="OnConnected">
/// Gets called when a connection with a PLC was established
/// <para/>
/// If <see cref="WithPoller"/> is used it waits for the first data receive cycle to complete
/// </param>
/// <param name="OnFailed">Gets called when an error or timeout during connection occurs</param>
/// <returns></returns>
public async Task<MewtocolInterface> ConnectAsync (Action<PLCInfo> OnConnected = null, Action OnFailed = null) {
var plcinf = await GetPLCInfoAsync();
if (plcinf is not null) {
Connected?.Invoke(plcinf);
if (OnConnected != null) {
if (!usePoller) {
OnConnected(plcinf);
return this;
}
PolledCycle += OnPollCycleDone;
void OnPollCycleDone () {
OnConnected(plcinf);
PolledCycle -= OnPollCycleDone;
}
}
} else {
if (OnFailed != null)
OnFailed();
}
return this;
}
/// <summary>
/// Attaches a poller to the interface that continously
/// polls the registered data registers and writes the values to them
/// </summary>
public MewtocolInterface WithPoller (int pollerDelayMs = 50) {
pollingDelayMs = pollerDelayMs;
usePoller = true;
return this;
}
#endregion #endregion
#region Low level command handling #region Low level command handling
/// <summary> /// <summary>

View File

@@ -10,111 +10,7 @@ namespace MewtocolNet {
public static class MewtocolInterfaceExtensions { public static class MewtocolInterfaceExtensions {
/// <summary>
/// Attaches a continous reader that reads back the Registers and Contacts
/// </summary>
public static Task AttachContinousReader (this Task<MewtocolInterface> interfaceTask, int _refreshTimeMS = 200) {
interfaceTask.Wait(-1);
var interf = interfaceTask.Result;
if (interf.ContinousReaderRunning)
return Task.CompletedTask;
interf.cTokenAutoUpdater = new CancellationTokenSource();
Console.WriteLine("Attaching cont reader");
Task.Factory.StartNew(async () => {
var plcinf = await interf.GetPLCInfoAsync();
if (plcinf == null) {
Console.WriteLine("PLC is not reachable");
throw new Exception("PLC is not reachable");
}
if (!plcinf.OperationMode.RunMode) {
Console.WriteLine("PLC is not running");
throw new Exception("PLC is not running");
}
interf.ContinousReaderRunning = true;
while (true) {
//dont update when currently writing a var
if (interf.isWriting) {
continue;
}
await Task.Delay(_refreshTimeMS);
foreach (var reg in interf.Registers) {
if (reg is NRegister<short> shortReg) {
var lastVal = shortReg.Value;
var readout = (await interf.ReadNumRegister(shortReg)).Register.Value;
if (lastVal != readout) {
shortReg.LastValue = readout;
interf.InvokeRegisterChanged(shortReg);
shortReg.TriggerNotifyChange();
}
}
if (reg is NRegister<ushort> ushortReg) {
var lastVal = ushortReg.Value;
var readout = (await interf.ReadNumRegister(ushortReg)).Register.Value;
if (lastVal != readout) {
ushortReg.LastValue = readout;
interf.InvokeRegisterChanged(ushortReg);
ushortReg.TriggerNotifyChange();
}
}
if (reg is NRegister<int> intReg) {
var lastVal = intReg.Value;
var readout = (await interf.ReadNumRegister(intReg)).Register.Value;
if (lastVal != readout) {
intReg.LastValue = readout;
interf.InvokeRegisterChanged(intReg);
intReg.TriggerNotifyChange();
}
}
if (reg is NRegister<uint> uintReg) {
var lastVal = uintReg.Value;
var readout = (await interf.ReadNumRegister(uintReg)).Register.Value;
if (lastVal != readout) {
uintReg.LastValue = readout;
interf.InvokeRegisterChanged(uintReg);
uintReg.TriggerNotifyChange();
}
}
if (reg is NRegister<float> floatReg) {
var lastVal = floatReg.Value;
var readout = (await interf.ReadNumRegister(floatReg)).Register.Value;
if (lastVal != readout) {
floatReg.LastValue = readout;
interf.InvokeRegisterChanged(floatReg);
floatReg.TriggerNotifyChange();
}
} else if (reg is SRegister stringReg) {
var lastVal = stringReg.Value;
var readout = (await interf.ReadStringRegister(stringReg)).Register.Value;
if (lastVal != readout) {
interf.InvokeRegisterChanged(stringReg);
stringReg.TriggerNotifyChange();
}
}
}
}
}, interf.cTokenAutoUpdater.Token);
return Task.CompletedTask;
}
} }
} }