diff --git a/Examples/Program.cs b/Examples/Program.cs index ba6ddef..b78de7f 100644 --- a/Examples/Program.cs +++ b/Examples/Program.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using System.Text.Json; using MewtocolNet; +using MewtocolNet.Responses; namespace Examples { class Program { @@ -16,6 +17,8 @@ namespace Examples { interf.AddRegister("Cooler Status",1204); interf.AddRegister(1101, 4); + interf.WithPoller(); + interf.RegisterChanged += (o) => { Console.WriteLine($"DT{o.MemoryAdress} {(o.Name != null ? $"({o.Name}) " : "")}changed to {o.GetValueString()}"); }; @@ -24,11 +27,16 @@ namespace Examples { (plcinf) => { Console.WriteLine("Connected to PLC:\n" + plcinf.ToString()); + + //read back a register value + var statusNum = (NRegister)interf.Registers[1204]; + Console.WriteLine($"Status num is: {statusNum.Value}"); + }, () => { Console.WriteLine("Failed connection"); } - ).AttachContinousReader(50); + ); }); diff --git a/MewtocolNet/Mewtocol/DynamicInterface.cs b/MewtocolNet/Mewtocol/DynamicInterface.cs index 088a1a6..2eb0b0f 100644 --- a/MewtocolNet/Mewtocol/DynamicInterface.cs +++ b/MewtocolNet/Mewtocol/DynamicInterface.cs @@ -7,49 +7,129 @@ using System.Threading.Tasks; using MewtocolNet.Responses; namespace MewtocolNet { + + /// + /// The PLC com interface class + /// public partial class MewtocolInterface { - public event Action RegisterChanged; - + internal event Action PolledCycle; internal CancellationTokenSource cTokenAutoUpdater; - protected internal bool isWriting = false; - - - public bool ContinousReaderRunning { get; set; } - public List Registers { get; set; } = new List(); - public List Contacts { get; set; } = new List(); + internal bool isWriting; + internal bool ContinousReaderRunning; + internal bool usePoller = false; + + #region Register Polling /// - /// Trys to connect to the PLC by the IP given in the constructor + /// Attaches a continous reader that reads back the Registers and Contacts /// - /// Gets called when a connection with a PLC was established - /// Gets called when an error or timeout during connection occurs - /// - public async Task ConnectAsync (Action OnConnected = null, Action OnFailed = null) { + internal void AttachPoller () { - 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 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 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 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 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 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 /// /// Adds a PLC memory register to the watchlist - /// The registers can be read back by attaching - /// - /// to the end of a method + /// The registers can be read back by attaching /// /// /// The type of the register translated from C# to IEC 61131-3 types @@ -68,17 +148,17 @@ namespace MewtocolNet { Type regType = typeof(T); if (regType == typeof(short)) { - Registers.Add(new NRegister(_address)); + Registers.Add(_address, new NRegister(_address)); } else if (regType == typeof(ushort)) { - Registers.Add(new NRegister(_address)); + Registers.Add(_address, new NRegister(_address)); } else if (regType == typeof(int)) { - Registers.Add(new NRegister(_address)); + Registers.Add(_address, new NRegister(_address)); } else if (regType == typeof(uint)) { - Registers.Add(new NRegister(_address)); + Registers.Add(_address, new NRegister(_address)); } else if (regType == typeof(float)) { - Registers.Add(new NRegister(_address)); + Registers.Add(_address, new NRegister(_address)); } else if (regType == typeof(string)) { - Registers.Add(new SRegister(_address, _length)); + Registers.Add(_address, new SRegister(_address, _length)); } else { throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" + $"Allowed are: short, ushort, int, uint, float and string"); @@ -88,9 +168,7 @@ namespace MewtocolNet { /// /// Adds a PLC memory register to the watchlist - /// The registers can be read back by attaching - /// - /// to the end of a method + /// The registers can be read back by attaching /// /// /// The type of the register translated from C# to IEC 61131-3 types @@ -110,17 +188,17 @@ namespace MewtocolNet { Type regType = typeof(T); if (regType == typeof(short)) { - Registers.Add(new NRegister(_address, _name)); + Registers.Add(_address, new NRegister(_address, _name)); } else if (regType == typeof(ushort)) { - Registers.Add(new NRegister(_address, _name)); + Registers.Add(_address, new NRegister(_address, _name)); } else if (regType == typeof(int)) { - Registers.Add(new NRegister(_address, _name)); + Registers.Add(_address, new NRegister(_address, _name)); } else if (regType == typeof(uint)) { - Registers.Add(new NRegister(_address, _name)); + Registers.Add(_address, new NRegister(_address, _name)); } else if (regType == typeof(float)) { - Registers.Add(new NRegister(_address, _name)); + Registers.Add(_address, new NRegister(_address, _name)); } else if (regType == typeof(string)) { - Registers.Add(new SRegister(_address, _length, _name)); + Registers.Add(_address, new SRegister(_address, _length, _name)); } else { throw new NotSupportedException($"The type {regType} is not allowed for Registers \n" + $"Allowed are: short, ushort, int, uint, float and string"); @@ -132,7 +210,13 @@ namespace MewtocolNet { internal void InvokeRegisterChanged (Register reg) { - RegisterChanged?.Invoke (reg); + RegisterChanged?.Invoke(reg); + + } + + internal void InvokePolledCycleDone () { + + PolledCycle?.Invoke(); } diff --git a/MewtocolNet/Mewtocol/MewtocolInterface.cs b/MewtocolNet/Mewtocol/MewtocolInterface.cs index bba1a42..e67349f 100644 --- a/MewtocolNet/Mewtocol/MewtocolInterface.cs +++ b/MewtocolNet/Mewtocol/MewtocolInterface.cs @@ -10,32 +10,140 @@ using MewtocolNet.Responses; namespace MewtocolNet { + /// + /// The PLC com interface class + /// public partial class MewtocolInterface { + /// + /// Gets triggered when the PLC connection was established + /// + public event Action Connected; + + /// + /// Gets triggered when a registered data register changes its value + /// + public event Action RegisterChanged; + /// /// Generic information about the connected PLC /// public PLCInfo PlcInfo {get;private set;} + + /// + /// The registered data registers of the PLC + /// + public Dictionary Registers { get; set; } = new(); + private CancellationTokenSource tokenSource; private string ip {get;set;} private int port {get;set;} - public int ConnectionTimeout {get;set;} = 2000; + private int stationNumber {get;set;} + private int pollingDelayMs {get;set;} + + /// + /// The current IP of the PLC connection + /// + public string IpAddress => ip; + /// + /// The current port of the PLC connection + /// + public int Port => port; + /// + /// The station number of the PLC + /// + public int StationNumber => stationNumber; + #region Initialization + /// /// Builds a new Interfacer for a PLC /// - /// - /// - public MewtocolInterface (string _ip, int _port = 9094) { + /// IP adress of the PLC + /// Port of the PLC + /// Station Number of the PLC + public MewtocolInterface (string _ip, int _port = 9094, int _station = 1) { + ip = _ip; port = _port; + stationNumber = _station; + + Connected += MewtocolInterface_Connected; + + void MewtocolInterface_Connected (PLCInfo obj) { + + if (usePoller) + AttachPoller(); + + } + } + #endregion + + #region Setup + + /// + /// Trys to connect to the PLC by the IP given in the constructor + /// + /// + /// Gets called when a connection with a PLC was established + /// + /// If is used it waits for the first data receive cycle to complete + /// + /// Gets called when an error or timeout during connection occurs + /// + public async Task ConnectAsync (Action 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; + + } + + /// + /// Attaches a poller to the interface that continously + /// polls the registered data registers and writes the values to them + /// + public MewtocolInterface WithPoller (int pollerDelayMs = 50) { + + pollingDelayMs = pollerDelayMs; + usePoller = true; + + return this; + + } #endregion + #region Low level command handling /// diff --git a/MewtocolNet/Mewtocol/MewtocolInterfaceExtensions.cs b/MewtocolNet/Mewtocol/MewtocolInterfaceExtensions.cs index 37c397a..f829cd4 100644 --- a/MewtocolNet/Mewtocol/MewtocolInterfaceExtensions.cs +++ b/MewtocolNet/Mewtocol/MewtocolInterfaceExtensions.cs @@ -10,111 +10,7 @@ namespace MewtocolNet { public static class MewtocolInterfaceExtensions { - /// - /// Attaches a continous reader that reads back the Registers and Contacts - /// - public static Task AttachContinousReader (this Task 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 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 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 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 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 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; - - } - + } }