diff --git a/Examples/Program.cs b/Examples/Program.cs index a25cbd5..c54cd40 100644 --- a/Examples/Program.cs +++ b/Examples/Program.cs @@ -4,75 +4,126 @@ using MewtocolNet; using MewtocolNet.Logging; using MewtocolNet.Registers; -namespace Examples { +namespace Examples; - class Program { +class Program { - static void Main(string[] args) { + static void Main(string[] args) { - Task.Factory.StartNew(async () => { + Console.WriteLine("Enter your scenario number:\n" + + "1 = Permanent connection\n" + + "2 = Dispose connection"); - //attaching the logger - Logger.LogLevel = LogLevel.Verbose; - Logger.OnNewLogMessage((date, msg) => { - Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}"); - }); - - //setting up a new PLC interface and register collection - MewtocolInterface interf = new MewtocolInterface("10.237.191.3"); - TestRegisters registers = new TestRegisters(); + var line = Console.ReadLine(); - //attaching the register collection and an automatic poller - interf.WithRegisterCollection(registers).WithPoller(); + if(line == "1") { + Scenario1(); + } - await interf.ConnectAsync( - (plcinf) => { + if (line == "2") { + Scenario2(); + } + + Console.ReadLine(); + } + + static void Scenario1 () { + + Task.Factory.StartNew(async () => { + + //attaching the logger + Logger.LogLevel = LogLevel.Critical; + Logger.OnNewLogMessage((date, msg) => { + Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}"); + }); + + //setting up a new PLC interface and register collection + MewtocolInterface interf = new MewtocolInterface("10.237.191.3"); + TestRegisters registers = new TestRegisters(); + + //attaching the register collection and an automatic poller + interf.WithRegisterCollection(registers).WithPoller(); + + await interf.ConnectAsync( + (plcinf) => { //reading a value from the register collection - Console.WriteLine($"BitValue is: {registers.BitValue}"); - Console.WriteLine($"TestEnum is: {registers.TestEnum}"); + Console.WriteLine($"BitValue is: {registers.BitValue}"); + Console.WriteLine($"TestEnum is: {registers.TestEnum}"); //writing a value to the registers - Task.Factory.StartNew(async () => { + Task.Factory.StartNew(async () => { //set plc to run mode if not already - await interf.SetOperationMode(OPMode.Run); + await interf.SetOperationMode(OPMode.Run); - await Task.Delay(2000); + await Task.Delay(2000); - await interf.SetRegisterAsync(nameof(registers.TestInt32), 100); - - _ = Task.Factory.StartNew(async () => { - while(true) { - for (int i = 0; i < 5; i++) { - var bytes = await interf.ReadByteRange(1020, 20); - await interf.SetRegisterAsync(nameof(registers.TestBool1), !registers.TestBool1); - await interf.SetRegisterAsync(nameof(registers.TestInt32), registers.TestInt32 + 100); - await Task.Delay(1333); - } - await Task.Delay(10000); - } - }); + await interf.SetRegisterAsync(nameof(registers.TestInt32), 100); //adds 10 each time the plc connects to the PLCs INT regíster - //interf.SetRegister(nameof(registers.TestInt16), (short)(registers.TestInt16 + 10)); + interf.SetRegister(nameof(registers.TestInt16), (short)(registers.TestInt16 + 10)); //adds 1 each time the plc connects to the PLCs DINT regíster - //interf.SetRegister(nameof(registers.TestInt32), (registers.TestInt32 + 1)); + interf.SetRegister(nameof(registers.TestInt32), (registers.TestInt32 + 1)); //adds 11.11 each time the plc connects to the PLCs REAL regíster - //interf.SetRegister(nameof(registers.TestFloat32), (float)(registers.TestFloat32 + 11.11)); + interf.SetRegister(nameof(registers.TestFloat32), (float)(registers.TestFloat32 + 11.11)); //writes 'Hello' to the PLCs string register - //interf.SetRegister(nameof(registers.TestString2), "Hello"); + interf.SetRegister(nameof(registers.TestString2), "Hello"); //set the current second to the PLCs TIME register - //interf.SetRegister(nameof(registers.TestTime), TimeSpan.FromSeconds(DateTime.Now.Second)); + interf.SetRegister(nameof(registers.TestTime), TimeSpan.FromSeconds(DateTime.Now.Second)); - }); + }); - } - ); + } + ); + + }); - }); - - Console.ReadLine(); - } } + + static void Scenario2 () { + + Logger.LogLevel = LogLevel.Critical; + Logger.OnNewLogMessage((date, msg) => { + Console.WriteLine($"{date.ToString("HH:mm:ss")} {msg}"); + }); + + Task.Factory.StartNew(async () => { + + using(var interf = new MewtocolInterface("10.237.191.3")) { + + await interf.ConnectAsync(); + + if(interf.IsConnected) { + + var plcInf = await interf.GetPLCInfoAsync(); + Console.WriteLine(plcInf); + + } + + interf.Disconnect(); + + } + + + using (var interf = new MewtocolInterface("10.237.191.3")) { + + await interf.ConnectAsync(); + + if (interf.IsConnected) { + + var plcInf = await interf.GetPLCInfoAsync(); + Console.WriteLine(plcInf); + + } + + interf.Disconnect(); + + } + + + }); + + } + } diff --git a/MewtocolNet/Mewtocol/DynamicInterface.cs b/MewtocolNet/Mewtocol/DynamicInterface.cs index d96cdcd..7ce2c8a 100644 --- a/MewtocolNet/Mewtocol/DynamicInterface.cs +++ b/MewtocolNet/Mewtocol/DynamicInterface.cs @@ -15,7 +15,6 @@ namespace MewtocolNet { public partial class MewtocolInterface { internal event Action PolledCycle; - internal CancellationTokenSource cTokenAutoUpdater; internal bool ContinousReaderRunning; internal bool usePoller = false; @@ -24,7 +23,6 @@ namespace MewtocolNet { internal void KillPoller () { ContinousReaderRunning = false; - cTokenAutoUpdater?.Cancel(); } @@ -38,21 +36,26 @@ namespace MewtocolNet { Task.Factory.StartNew(async () => { - cTokenAutoUpdater = new CancellationTokenSource(); - Logger.Log("Poller is attaching", LogLevel.Info, this); int it = 0; + ContinousReaderRunning = true; - while (it < Registers.Count + 1) { + while (ContinousReaderRunning) { - if (it >= Registers.Count) { + if (it >= Registers.Count + 1) { it = 0; //invoke cycle polled event InvokePolledCycleDone(); continue; } + if (it >= Registers.Count) { + await GetPLCInfoAsync(); + it++; + continue; + } + var reg = Registers[it]; if (reg is NRegister shortReg) { diff --git a/MewtocolNet/Mewtocol/MewtocolInterface.cs b/MewtocolNet/Mewtocol/MewtocolInterface.cs index f7784b7..815140c 100644 --- a/MewtocolNet/Mewtocol/MewtocolInterface.cs +++ b/MewtocolNet/Mewtocol/MewtocolInterface.cs @@ -21,7 +21,7 @@ namespace MewtocolNet { /// /// The PLC com interface class /// - public partial class MewtocolInterface : INotifyPropertyChanged { + public partial class MewtocolInterface : INotifyPropertyChanged, IDisposable { /// /// Gets triggered when the PLC connection was established @@ -43,6 +43,15 @@ namespace MewtocolNet { /// public event PropertyChangedEventHandler PropertyChanged; + private int connectTimeout = 1000; + /// + /// The initial connection timeout in milliseconds + /// + public int ConnectTimeout { + get { return connectTimeout; } + set { connectTimeout = value; } + } + private bool isConnected; /// /// The current connection state of the interface @@ -55,6 +64,16 @@ namespace MewtocolNet { } } + private bool disposed; + /// + /// True if the current interface was disposed + /// + public bool Disposed { + get { return disposed; } + private set { disposed = value; } + } + + private PLCInfo plcInfo; /// /// Generic information about the connected PLC @@ -75,6 +94,7 @@ namespace MewtocolNet { private string ip; private int port; private int stationNumber; + private int cycleTimeMs = 25; /// /// The current IP of the PLC connection @@ -89,7 +109,6 @@ namespace MewtocolNet { /// public int StationNumber => stationNumber; - private int cycleTimeMs = 25; /// /// The duration of the last message cycle /// @@ -135,7 +154,6 @@ namespace MewtocolNet { RegisterChanged += (o) => { string address = $"{o.GetRegisterString()}{o.MemoryAdress}".PadRight(5, (char)32); - ; Logger.Log($"{address} " + $"{(o.Name != null ? $"({o.Name}) " : "")}" + @@ -200,14 +218,14 @@ namespace MewtocolNet { } /// - /// Closes all permanent polling + /// Closes the connection all cyclic polling /// public void Disconnect () { if (!IsConnected) return; - OnMajorSocketException(); + OnMajorSocketExceptionWhileConnected(); } @@ -225,6 +243,70 @@ namespace MewtocolNet { #endregion + #region TCP connection state handling + + private async Task ConnectTCP () { + + if (!IPAddress.TryParse(ip, out var targetIP)) { + throw new ArgumentException("The IP adress of the PLC was no valid format"); + } + + try { + + client = new TcpClient() { + ReceiveBufferSize = RecBufferSize, + NoDelay = false, + ExclusiveAddressUse = true, + }; + + var result = client.BeginConnect(targetIP, port, null, null); + var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(ConnectTimeout)); + + if(!success) { + OnMajorSocketExceptionWhileConnecting(); + return; + } + + stream = client.GetStream(); + stream.ReadTimeout = 1000; + + Console.WriteLine($"Connected {client.Connected}"); + await Task.CompletedTask; + + } catch (SocketException) { + + OnMajorSocketExceptionWhileConnecting(); + + } + + } + + private void OnMajorSocketExceptionWhileConnecting () { + + Logger.Log("The PLC connection timed out", LogLevel.Error, this); + CycleTimeMs = 0; + IsConnected = false; + KillPoller(); + + } + + private void OnMajorSocketExceptionWhileConnected () { + + if (IsConnected) { + + Logger.Log("The PLC connection was closed", LogLevel.Error, this); + CycleTimeMs = 0; + IsConnected = false; + Disconnected?.Invoke(); + KillPoller(); + client.Close(); + + } + + } + + #endregion + #region Register Collection /// @@ -547,25 +629,6 @@ namespace MewtocolNet { #region Low level command handling - private async Task ConnectTCP () { - - var targetIP = IPAddress.Parse(ip); - - client = new TcpClient() { - ReceiveBufferSize = RecBufferSize, - NoDelay = false, - ExclusiveAddressUse = true - }; - - await client.ConnectAsync(targetIP, port); - - stream = client.GetStream(); - stream.ReadTimeout = 1000; - - Console.WriteLine($"Connected {client.Connected}"); - - } - /// /// Calculates checksum and sends a command to the PLC then awaits results /// @@ -668,12 +731,12 @@ namespace MewtocolNet { Logger.Log($"Critical IO exception on receive", LogLevel.Critical, this); return null; } catch (SocketException) { - OnMajorSocketException(); + OnMajorSocketExceptionWhileConnected(); return null; } if(!string.IsNullOrEmpty(response.ToString())) { - Logger.Log($"<-- IN MSG (TXT): {response} ({response.Length} bytes)", LogLevel.Critical, this); + Logger.Log($"<-- IN MSG: {response}", LogLevel.Critical, this); return response.ToString(); } else { return null; @@ -681,17 +744,22 @@ namespace MewtocolNet { } - private void OnMajorSocketException () { + #endregion - if (IsConnected) { + #region Disposing - Logger.Log("The PLC connection was closed", LogLevel.Error, this); - CycleTimeMs = 0; - IsConnected = false; - Disconnected?.Invoke(); - KillPoller(); + /// + /// Disposes the current interface and clears all its members + /// + public void Dispose () { - } + if (Disposed) return; + + Disconnect(); + + GC.SuppressFinalize(this); + + Disposed = true; }