From f135029ad66b464c2d3c5863216b6e991d539681 Mon Sep 17 00:00:00 2001 From: Alex Johnson Date: Thu, 7 Jul 2022 10:51:37 -0400 Subject: [PATCH 1/5] Reduces error log messages from SSH client on basic disconnects --- .../Pepperdash Core/Comm/GenericSshClient.cs | 15 ++++++++++----- Pepperdash Core/Pepperdash Core/Logging/Debug.cs | 10 ++++++++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs index 2990ae0..a0093a7 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs @@ -66,6 +66,7 @@ namespace PepperDash.Core } private bool IsConnecting = false; + private bool DisconnectLogged = false; /// /// S+ helper for IsConnected @@ -251,24 +252,28 @@ namespace PepperDash.Core Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Connected"); ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; IsConnecting = false; + DisconnectLogged = false; return; // Success will not pass here } catch (SshConnectionException e) { var ie = e.InnerException; // The details are inside!! + var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; + if (ie is SocketException) - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "'{0}' CONNECTION failure: Cannot reach host, ({1})", Key, ie.Message); + Debug.Console(1, this, errorLogLevel, "'{0}' CONNECTION failure: Cannot reach host, ({1})", Key, ie.Message); else if (ie is System.Net.Sockets.SocketException) - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "'{0}' Connection failure: Cannot reach host '{1}' on port {2}, ({3})", + Debug.Console(1, this, errorLogLevel, "'{0}' Connection failure: Cannot reach host '{1}' on port {2}, ({3})", Key, Hostname, Port, ie.GetType()); else if (ie is SshAuthenticationException) { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Authentication failure for username '{0}', ({1})", + Debug.Console(1, this, errorLogLevel, "Authentication failure for username '{0}', ({1})", Username, ie.Message); } else - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error on connect:\r({0})", e); + Debug.Console(1, this, errorLogLevel, "Error on connect:\r({0})", e); + DisconnectLogged = true; ClientStatus = SocketStatus.SOCKET_STATUS_CONNECT_FAILED; HandleConnectionFailure(); } @@ -335,7 +340,7 @@ namespace PepperDash.Core { Connect(); }, AutoReconnectIntervalMs); - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Attempting connection in {0} seconds", + Debug.Console(1, this, "Attempting connection in {0} seconds", (float) (AutoReconnectIntervalMs/1000)); } else diff --git a/Pepperdash Core/Pepperdash Core/Logging/Debug.cs b/Pepperdash Core/Pepperdash Core/Logging/Debug.cs index a0e7ed8..211bc2d 100644 --- a/Pepperdash Core/Pepperdash Core/Logging/Debug.cs +++ b/Pepperdash Core/Pepperdash Core/Logging/Debug.cs @@ -389,7 +389,10 @@ namespace PepperDash.Core string format, params object[] items) { var str = string.Format("[{0}] {1}", dev.Key, string.Format(format, items)); - LogError(errorLogLevel, str); + if (errorLogLevel != ErrorLogLevel.None) + { + LogError(errorLogLevel, str); + } if (Level >= level) { Console(level, str); @@ -403,7 +406,10 @@ namespace PepperDash.Core string format, params object[] items) { var str = string.Format(format, items); - LogError(errorLogLevel, str); + if (errorLogLevel != ErrorLogLevel.None) + { + LogError(errorLogLevel, str); + } if (Level >= level) { Console(level, str); From b12e79804174880810c2aa9006e0d63574636b83 Mon Sep 17 00:00:00 2001 From: Alex Johnson Date: Wed, 24 Aug 2022 14:11:46 -0400 Subject: [PATCH 2/5] Reworks reconnect timer due to issues seen on 4 series where autoreconnect cycle breaks. --- .../Pepperdash Core/Comm/GenericSshClient.cs | 39 ++++++-------- .../Comm/GenericTcpIpClient.cs | 51 ++++++++++++------- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs index a0093a7..5f4c5c8 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs @@ -151,6 +151,11 @@ namespace PepperDash.Core Username = username; Password = password; AutoReconnectIntervalMs = 5000; + + ReconnectTimer = new CTimer(o => + { + Connect(); + }, Timeout.Infinite); } /// @@ -162,6 +167,11 @@ namespace PepperDash.Core StreamDebugging = new CommunicationStreamDebugging(SPlusKey); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); AutoReconnectIntervalMs = 5000; + + ReconnectTimer = new CTimer(o => + { + Connect(); + }, Timeout.Infinite); } /// @@ -203,11 +213,7 @@ namespace PepperDash.Core Debug.Console(1, this, "attempting connect"); // Cancel reconnect if running. - if (ReconnectTimer != null) - { - ReconnectTimer.Stop(); - ReconnectTimer = null; - } + ReconnectTimer.Stop(); // Don't try to connect if already if (IsConnected) @@ -297,11 +303,7 @@ namespace PepperDash.Core { ConnectEnabled = false; // Stop trying reconnects, if we are - if (ReconnectTimer != null) - { - ReconnectTimer.Stop(); - ReconnectTimer = null; - } + ReconnectTimer.Stop(); KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); } @@ -334,20 +336,9 @@ namespace PepperDash.Core if (AutoReconnect && ConnectEnabled) { Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - if (ReconnectTimer == null) - { - ReconnectTimer = new CTimer(o => - { - Connect(); - }, AutoReconnectIntervalMs); - Debug.Console(1, this, "Attempting connection in {0} seconds", - (float) (AutoReconnectIntervalMs/1000)); - } - else - { - Debug.Console(1, this, "{0} second reconnect cycle running", - (float) (AutoReconnectIntervalMs/1000)); - } + ReconnectTimer.Reset(AutoReconnectIntervalMs); + Debug.Console(1, this, "Attempting connection in {0} seconds", + (float) (AutoReconnectIntervalMs/1000)); } } diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs index 480e4d1..5f09f36 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs @@ -185,6 +185,16 @@ namespace PepperDash.Core BufferSize = bufferSize; AutoReconnectIntervalMs = 5000; + RetryTimer = new CTimer(o => + { + if (Client == null) + { + return; + } + + Client.ConnectToServerAsync(ConnectToServerCallback); + }, Timeout.Infinite); + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); } @@ -199,6 +209,16 @@ namespace PepperDash.Core CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); AutoReconnectIntervalMs = 5000; BufferSize = 2000; + + RetryTimer = new CTimer(o => + { + if (Client == null) + { + return; + } + + Client.ConnectToServerAsync(ConnectToServerCallback); + }, Timeout.Infinite); } /// @@ -211,6 +231,16 @@ namespace PepperDash.Core CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); AutoReconnectIntervalMs = 5000; BufferSize = 2000; + + RetryTimer = new CTimer(o => + { + if (Client == null) + { + return; + } + + Client.ConnectToServerAsync(ConnectToServerCallback); + }, Timeout.Infinite); } /// @@ -287,11 +317,7 @@ namespace PepperDash.Core DisconnectCalledByUser = true; // Stop trying reconnects, if we are - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } + RetryTimer.Stop(); if (Client != null) { @@ -337,15 +363,7 @@ namespace PepperDash.Core Debug.Console(1, this, "Attempting reconnect, status={0}", Client.ClientStatus); if (!DisconnectCalledByUser) - RetryTimer = new CTimer(o => - { - if (Client == null) - { - return; - } - - Client.ConnectToServerAsync(ConnectToServerCallback); - }, AutoReconnectIntervalMs); + RetryTimer.Reset(AutoReconnectIntervalMs); } } @@ -381,11 +399,8 @@ namespace PepperDash.Core textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } - - + } } - client.ReceiveDataAsync(Receive); } } From c722542b044b1f19a58543f38b4b1bddd170d2b2 Mon Sep 17 00:00:00 2001 From: Alex Johnson Date: Fri, 2 Sep 2022 14:50:15 -0400 Subject: [PATCH 3/5] Adds lock to ssh and tcp classes to prevent possible threading issues. --- .../Pepperdash Core/Comm/GenericSshClient.cs | 262 +++++++++--------- .../Comm/GenericTcpIpClient.cs | 139 +++++----- .../Pepperdash Core/Logging/Debug.cs | 4 - 3 files changed, 204 insertions(+), 201 deletions(-) diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs index 5f4c5c8..54ff6c9 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs @@ -14,6 +14,10 @@ namespace PepperDash.Core public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect { private const string SPlusKey = "Uninitialized SshClient"; + + /// + /// Enables debugging to console + /// public CommunicationStreamDebugging StreamDebugging { get; private set; } /// @@ -31,11 +35,6 @@ namespace PepperDash.Core /// public event EventHandler ConnectionChange; - /// - /// - /// - //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; - /// /// Address of server /// @@ -62,12 +61,9 @@ namespace PepperDash.Core public bool IsConnected { // returns false if no client or not connected - get { return ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } - private bool IsConnecting = false; - private bool DisconnectLogged = false; - /// /// S+ helper for IsConnected /// @@ -132,10 +128,10 @@ namespace PepperDash.Core CTimer ReconnectTimer; - //string PreviousHostname; - //int PreviousPort; - //string PreviousUsername; - //string PreviousPassword; + //Lock object to prevent simulatneous connect/disconnect operations + private readonly object connectLock = new object(); + + private bool DisconnectLogged = false; /// /// Typical constructor. @@ -154,7 +150,10 @@ namespace PepperDash.Core ReconnectTimer = new CTimer(o => { - Connect(); + if (ConnectEnabled) + { + Connect(); + } }, Timeout.Infinite); } @@ -170,7 +169,10 @@ namespace PepperDash.Core ReconnectTimer = new CTimer(o => { - Connect(); + if (ConnectEnabled) + { + Connect(); + } }, Timeout.Infinite); } @@ -202,110 +204,111 @@ namespace PepperDash.Core /// public void Connect() { - if (IsConnecting) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "Connection attempt in progress. Exiting Connect()"); - return; - } - - IsConnecting = true; - ConnectEnabled = true; - Debug.Console(1, this, "attempting connect"); - - // Cancel reconnect if running. - ReconnectTimer.Stop(); - - // Don't try to connect if already - if (IsConnected) - return; - // Don't go unless everything is here if (string.IsNullOrEmpty(Hostname) || Port < 1 || Port > 65535 || Username == null || Password == null) { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect failed. Check hostname, port, username and password are set or not null"); + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Connect failed. Check hostname, port, username and password are set or not null"); return; } - // Cleanup the old client if it already exists - if (Client != null) + ConnectEnabled = true; + lock (connectLock) { - Debug.Console(1, this, "Cleaning up disconnected client"); - Client.ErrorOccurred -= Client_ErrorOccurred; - KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); - } - - // This handles both password and keyboard-interactive (like on OS-X, 'nixes) - KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod(Username); - kauth.AuthenticationPrompt += new EventHandler(kauth_AuthenticationPrompt); - PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password); - - Debug.Console(1, this, "Creating new SshClient"); - ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); - Client = new SshClient(connectionInfo); - - Client.ErrorOccurred -= Client_ErrorOccurred; - Client.ErrorOccurred += Client_ErrorOccurred; - - //Attempt to connect - ClientStatus = SocketStatus.SOCKET_STATUS_WAITING; - try - { - Client.Connect(); - TheStream = Client.CreateShellStream("PDTShell", 100, 80, 100, 200, 65534); - TheStream.DataReceived += Stream_DataReceived; - //TheStream.ErrorOccurred += TheStream_ErrorOccurred; - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Connected"); - ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; - IsConnecting = false; - DisconnectLogged = false; - return; // Success will not pass here - } - catch (SshConnectionException e) - { - var ie = e.InnerException; // The details are inside!! - var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; - - if (ie is SocketException) - Debug.Console(1, this, errorLogLevel, "'{0}' CONNECTION failure: Cannot reach host, ({1})", Key, ie.Message); - else if (ie is System.Net.Sockets.SocketException) - Debug.Console(1, this, errorLogLevel, "'{0}' Connection failure: Cannot reach host '{1}' on port {2}, ({3})", - Key, Hostname, Port, ie.GetType()); - else if (ie is SshAuthenticationException) + if (IsConnected) { - Debug.Console(1, this, errorLogLevel, "Authentication failure for username '{0}', ({1})", - Username, ie.Message); + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "Connection already connected. Exiting Connect()"); } else - Debug.Console(1, this, errorLogLevel, "Error on connect:\r({0})", e); + { + Debug.Console(1, this, "Attempting connect"); - DisconnectLogged = true; - ClientStatus = SocketStatus.SOCKET_STATUS_CONNECT_FAILED; - HandleConnectionFailure(); - } - catch (Exception e) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Unhandled exception on connect:\r({0})", e); - ClientStatus = SocketStatus.SOCKET_STATUS_CONNECT_FAILED; - HandleConnectionFailure(); - } + // Cancel reconnect if running. + ReconnectTimer.Stop(); - ClientStatus = SocketStatus.SOCKET_STATUS_CONNECT_FAILED; - HandleConnectionFailure(); + // Cleanup the old client if it already exists + if (Client != null) + { + Debug.Console(1, this, "Cleaning up disconnected client"); + KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); + } + + // This handles both password and keyboard-interactive (like on OS-X, 'nixes) + KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod(Username); + kauth.AuthenticationPrompt += new EventHandler(kauth_AuthenticationPrompt); + PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password); + + Debug.Console(1, this, "Creating new SshClient"); + ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); + Client = new SshClient(connectionInfo); + + Client.ErrorOccurred -= Client_ErrorOccurred; + Client.ErrorOccurred += Client_ErrorOccurred; + + //Attempt to connect + ClientStatus = SocketStatus.SOCKET_STATUS_WAITING; + try + { + Client.Connect(); + TheStream = Client.CreateShellStream("PDTShell", 100, 80, 100, 200, 65534); + TheStream.DataReceived += Stream_DataReceived; + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Connected"); + ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; + DisconnectLogged = false; + } + catch (SshConnectionException e) + { + var ie = e.InnerException; // The details are inside!! + var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; + + if (ie is SocketException) + Debug.Console(1, this, errorLogLevel, "'{0}' CONNECTION failure: Cannot reach host, ({1})", Key, ie.Message); + else if (ie is System.Net.Sockets.SocketException) + Debug.Console(1, this, errorLogLevel, "'{0}' Connection failure: Cannot reach host '{1}' on port {2}, ({3})", + Key, Hostname, Port, ie.GetType()); + else if (ie is SshAuthenticationException) + { + Debug.Console(1, this, errorLogLevel, "Authentication failure for username '{0}', ({1})", + Username, ie.Message); + } + else + Debug.Console(1, this, errorLogLevel, "Error on connect:\r({0})", e); + + DisconnectLogged = true; + KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); + if (AutoReconnect) + { + Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); + ReconnectTimer.Reset(AutoReconnectIntervalMs); + } + } + catch (Exception e) + { + var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; + Debug.Console(1, this, errorLogLevel, "Unhandled exception on connect:\r({0})", e); + DisconnectLogged = true; + KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); + if (AutoReconnect) + { + Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); + ReconnectTimer.Reset(AutoReconnectIntervalMs); + } + } + } + } } - - /// /// Disconnect the clients and put away it's resources. /// public void Disconnect() { - ConnectEnabled = false; - // Stop trying reconnects, if we are - ReconnectTimer.Stop(); - - KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); + lock(connectLock) + { + // Stop trying reconnects, if we are + ReconnectTimer.Stop(); + KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); + } } /// @@ -317,31 +320,21 @@ namespace PepperDash.Core if (Client != null) { - IsConnecting = false; - Client.Disconnect(); - Client = null; - ClientStatus = status; - Debug.Console(1, this, "Disconnected"); + try + { + Client.Disconnect(); + Client.Dispose(); + Client = null; + ClientStatus = status; + Debug.Console(1, this, "Disconnected client"); + } + catch (Exception ex) + { + Debug.Console(0, this, "Exception killing client: {0}", ex.Message); + } } } - /// - /// Anything to do with reestablishing connection on failures - /// - void HandleConnectionFailure() - { - KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - - Debug.Console(1, this, "Client nulled due to connection failure. AutoReconnect: {0}, ConnectEnabled: {1}", AutoReconnect, ConnectEnabled); - if (AutoReconnect && ConnectEnabled) - { - Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Reset(AutoReconnectIntervalMs); - Debug.Console(1, this, "Attempting connection in {0} seconds", - (float) (AutoReconnectIntervalMs/1000)); - } - } - /// /// Kills the stream /// @@ -353,6 +346,7 @@ namespace PepperDash.Core TheStream.Close(); TheStream.Dispose(); TheStream = null; + Debug.Console(1, this, "Disconnected stream"); } } @@ -403,13 +397,23 @@ namespace PepperDash.Core /// void Client_ErrorOccurred(object sender, Crestron.SimplSharp.Ssh.Common.ExceptionEventArgs e) { - if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException) - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Disconnected by remote"); - else - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Unhandled SSH client error: {0}", e.Exception); + CrestronInvoke.BeginInvoke(o => + { + if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException) + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Disconnected by remote"); + else + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Unhandled SSH client error: {0}", e.Exception); - ClientStatus = SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY; - HandleConnectionFailure(); + lock (connectLock) + { + KillClient(SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY); + } + if (AutoReconnect && ConnectEnabled) + { + Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); + ReconnectTimer.Reset(AutoReconnectIntervalMs); + } + }); } /// @@ -451,8 +455,6 @@ namespace PepperDash.Core Debug.Console(0, "Stack Trace: {0}", ex.StackTrace); Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stream write failed. Disconnected, closing"); - ClientStatus = SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY; - HandleConnectionFailure(); } } @@ -480,8 +482,6 @@ namespace PepperDash.Core catch { Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stream write failed. Disconnected, closing"); - ClientStatus = SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY; - HandleConnectionFailure(); } } diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs index 5f09f36..0d5eb29 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs @@ -5,7 +5,6 @@ using System.Text; using System.Text.RegularExpressions; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; - using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -17,6 +16,10 @@ namespace PepperDash.Core public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect { private const string SplusKey = "Uninitialized TcpIpClient"; + + /// + /// Enables debugging to console + /// public CommunicationStreamDebugging StreamDebugging { get; private set; } /// @@ -167,6 +170,9 @@ namespace PepperDash.Core get { return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } + //Lock object to prevent simulatneous connect/disconnect operations + private readonly object connectLock = new object(); + CTimer RetryTimer; /// @@ -187,12 +193,7 @@ namespace PepperDash.Core RetryTimer = new CTimer(o => { - if (Client == null) - { - return; - } - - Client.ConnectToServerAsync(ConnectToServerCallback); + Reconnect(); }, Timeout.Infinite); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); @@ -212,12 +213,7 @@ namespace PepperDash.Core RetryTimer = new CTimer(o => { - if (Client == null) - { - return; - } - - Client.ConnectToServerAsync(ConnectToServerCallback); + Reconnect(); }, Timeout.Infinite); } @@ -234,12 +230,7 @@ namespace PepperDash.Core RetryTimer = new CTimer(o => { - if (Client == null) - { - return; - } - - Client.ConnectToServerAsync(ConnectToServerCallback); + Reconnect(); }, Timeout.Infinite); } @@ -259,7 +250,7 @@ namespace PepperDash.Core if (programEventType == eProgramStatusEventType.Stopping) { Debug.Console(1, this, "Program stopping. Closing connection"); - Disconnect(); + Deactivate(); } } @@ -269,6 +260,8 @@ namespace PepperDash.Core /// public override bool Deactivate() { + RetryTimer.Stop(); + RetryTimer.Dispose(); if (Client != null) { Client.SocketStatusChange -= this.Client_SocketStatusChange; @@ -282,9 +275,6 @@ namespace PepperDash.Core /// public void Connect() { - if (IsConnected) - DisconnectClient(); - if (string.IsNullOrEmpty(Hostname)) { Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key); @@ -298,32 +288,58 @@ namespace PepperDash.Core } } + lock(connectLock) + { + if (IsConnected) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "Connection already connected. Exiting Connect()"); + } + else + { + //Stop retry timer if running + RetryTimer.Stop(); + + Client = new TCPClient(Hostname, Port, BufferSize); + Client.SocketStatusChange -= Client_SocketStatusChange; + Client.SocketStatusChange += Client_SocketStatusChange; + DisconnectCalledByUser = false; + + Client.ConnectToServerAsync(ConnectToServerCallback); + } + } + } + + private void Reconnect() + { if (Client == null) { - Client = new TCPClient(Hostname, Port, BufferSize); - Client.SocketStatusChange -= Client_SocketStatusChange; - Client.SocketStatusChange += Client_SocketStatusChange; + return; } - DisconnectCalledByUser = false; - - Client.ConnectToServerAsync(ConnectToServerCallback); // (null); - } + lock (connectLock) + { + if (IsConnected || DisconnectCalledByUser == true) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "Reconnect no longer needed. Exiting Reconnect()"); + } + else + { + Client.ConnectToServerAsync(ConnectToServerCallback); + } + } + } /// /// Attempts to disconnect the client /// public void Disconnect() { - DisconnectCalledByUser = true; - - // Stop trying reconnects, if we are - RetryTimer.Stop(); - - if (Client != null) + lock (connectLock) { + DisconnectCalledByUser = true; + + // Stop trying reconnects, if we are + RetryTimer.Stop(); DisconnectClient(); - Client = null; - Debug.Console(1, this, "Disconnected"); } } @@ -335,7 +351,7 @@ namespace PepperDash.Core if (Client != null) { Debug.Console(1, this, "Disconnecting client"); - if(IsConnected) + if (IsConnected) Client.DisconnectFromServer(); } } @@ -347,7 +363,7 @@ namespace PepperDash.Core void ConnectToServerCallback(TCPClient c) { Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus); - if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED && AutoReconnect) + if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) WaitAndTryReconnect(); } @@ -356,16 +372,18 @@ namespace PepperDash.Core /// void WaitAndTryReconnect() { - DisconnectClient(); - - if (Client != null) + CrestronInvoke.BeginInvoke(o => { - Debug.Console(1, this, "Attempting reconnect, status={0}", Client.ClientStatus); - - if (!DisconnectCalledByUser) - RetryTimer.Reset(AutoReconnectIntervalMs); - } - + lock (connectLock) + { + if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && Client != null) + { + DisconnectClient(); + Debug.Console(1, this, "Attempting reconnect, status={0}", Client.ClientStatus); + RetryTimer.Reset(AutoReconnectIntervalMs); + } + } + }); } /// @@ -398,7 +416,6 @@ namespace PepperDash.Core Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length); textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } } client.ReceiveDataAsync(Receive); @@ -416,8 +433,6 @@ namespace PepperDash.Core Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text)); if(Client != null) Client.SendData(bytes, bytes.Length); - - } /// @@ -453,26 +468,18 @@ namespace PepperDash.Core void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) { Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); - if (client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED && !DisconnectCalledByUser && AutoReconnect) + if (client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) WaitAndTryReconnect(); - // Probably doesn't need to be a switch since all other cases were eliminated - switch (clientSocketStatus) - { - case SocketStatus.SOCKET_STATUS_CONNECTED: - Client.ReceiveDataAsync(Receive); - DisconnectCalledByUser = false; - break; - } + if(clientSocketStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + { + Client.ReceiveDataAsync(Receive); + DisconnectCalledByUser = false; + } var handler = ConnectionChange; if (handler != null) ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); - - // Relay the event - //var handler = SocketStatusChange; - //if (handler != null) - // SocketStatusChange(this); } } diff --git a/Pepperdash Core/Pepperdash Core/Logging/Debug.cs b/Pepperdash Core/Pepperdash Core/Logging/Debug.cs index 211bc2d..d15f711 100644 --- a/Pepperdash Core/Pepperdash Core/Logging/Debug.cs +++ b/Pepperdash Core/Pepperdash Core/Logging/Debug.cs @@ -537,10 +537,6 @@ namespace PepperDash.Core //check for file at old path if (!File.Exists(oldFilePath)) { - Console(0, ErrorLogLevel.Notice, - String.Format( - @"Debug settings file not found at \nvram\debugSettings\program{0}. Attempting to use file at \user\debugSettings\program{0}", - InitialParametersClass.ApplicationNumber)); return; } From 413955614913eea0de78131afefcac5deba06b13 Mon Sep 17 00:00:00 2001 From: Alex Johnson Date: Fri, 2 Sep 2022 15:00:57 -0400 Subject: [PATCH 4/5] Changes some debug levels to notify upon errors in console --- .../Pepperdash Core/Comm/GenericSshClient.cs | 8 ++--- .../Comm/GenericTcpIpClient.cs | 29 ++++++++++++------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs index 54ff6c9..f3d0d37 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs @@ -217,7 +217,7 @@ namespace PepperDash.Core { if (IsConnected) { - Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "Connection already connected. Exiting Connect()"); + Debug.Console(1, this, "Connection already connected. Exiting Connect()"); } else { @@ -272,7 +272,7 @@ namespace PepperDash.Core Username, ie.Message); } else - Debug.Console(1, this, errorLogLevel, "Error on connect:\r({0})", e); + Debug.Console(1, this, errorLogLevel, "Error on connect:\r({0})", ie.Message); DisconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); @@ -285,7 +285,7 @@ namespace PepperDash.Core catch (Exception e) { var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; - Debug.Console(1, this, errorLogLevel, "Unhandled exception on connect:\r({0})", e); + Debug.Console(1, this, errorLogLevel, "Unhandled exception on connect:\r({0})", e.Message); DisconnectLogged = true; KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); if (AutoReconnect) @@ -330,7 +330,7 @@ namespace PepperDash.Core } catch (Exception ex) { - Debug.Console(0, this, "Exception killing client: {0}", ex.Message); + Debug.Console(1, this, "Exception killing client: {0}", ex.Message); } } } diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs index 0d5eb29..a9198da 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs @@ -292,7 +292,7 @@ namespace PepperDash.Core { if (IsConnected) { - Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "Connection already connected. Exiting Connect()"); + Debug.Console(1, this, "Connection already connected. Exiting Connect()"); } else { @@ -319,10 +319,11 @@ namespace PepperDash.Core { if (IsConnected || DisconnectCalledByUser == true) { - Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "Reconnect no longer needed. Exiting Reconnect()"); + Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()"); } else { + Debug.Console(1, this, "Attempting reconnect now"); Client.ConnectToServerAsync(ConnectToServerCallback); } } @@ -362,9 +363,15 @@ namespace PepperDash.Core /// void ConnectToServerCallback(TCPClient c) { - Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus); - if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) - WaitAndTryReconnect(); + if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + { + Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus); + WaitAndTryReconnect(); + } + else + { + Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus); + } } /// @@ -467,14 +474,14 @@ namespace PepperDash.Core /// void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) { - Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); - if (client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) - WaitAndTryReconnect(); - - if(clientSocketStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) { + Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); + WaitAndTryReconnect(); + } + { + Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); Client.ReceiveDataAsync(Receive); - DisconnectCalledByUser = false; } var handler = ConnectionChange; From 9326703454ac69c870262aa8b4ca3e68796d30fb Mon Sep 17 00:00:00 2001 From: Alex Johnson Date: Wed, 7 Sep 2022 11:46:06 -0400 Subject: [PATCH 5/5] Changes lock to CCriticalSection --- .../Pepperdash Core/Comm/GenericSshClient.cs | 25 +++- .../Comm/GenericTcpIpClient.cs | 121 ++++++++++-------- 2 files changed, 90 insertions(+), 56 deletions(-) diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs index f3d0d37..0211205 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs @@ -129,7 +129,7 @@ namespace PepperDash.Core CTimer ReconnectTimer; //Lock object to prevent simulatneous connect/disconnect operations - private readonly object connectLock = new object(); + private CCriticalSection connectLock = new CCriticalSection(); private bool DisconnectLogged = false; @@ -163,7 +163,6 @@ namespace PepperDash.Core public GenericSshClient() : base(SPlusKey) { - StreamDebugging = new CommunicationStreamDebugging(SPlusKey); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); AutoReconnectIntervalMs = 5000; @@ -213,8 +212,10 @@ namespace PepperDash.Core } ConnectEnabled = true; - lock (connectLock) + + try { + connectLock.Enter(); if (IsConnected) { Debug.Console(1, this, "Connection already connected. Exiting Connect()"); @@ -296,6 +297,10 @@ namespace PepperDash.Core } } } + finally + { + connectLock.Leave(); + } } /// @@ -303,11 +308,16 @@ namespace PepperDash.Core /// public void Disconnect() { - lock(connectLock) + try { + connectLock.Enter(); // Stop trying reconnects, if we are ReconnectTimer.Stop(); KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); + } + finally + { + connectLock.Leave(); } } @@ -404,10 +414,15 @@ namespace PepperDash.Core else Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Unhandled SSH client error: {0}", e.Exception); - lock (connectLock) + try { + connectLock.Enter(); KillClient(SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY); } + finally + { + connectLock.Leave(); + } if (AutoReconnect && ConnectEnabled) { Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs index a9198da..6e57bed 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs @@ -6,7 +6,6 @@ using System.Text.RegularExpressions; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace PepperDash.Core { @@ -18,7 +17,7 @@ namespace PepperDash.Core private const string SplusKey = "Uninitialized TcpIpClient"; /// - /// Enables debugging to console + /// Stream debugging /// public CommunicationStreamDebugging StreamDebugging { get; private set; } @@ -39,23 +38,24 @@ namespace PepperDash.Core public event EventHandler ConnectionChange; - private string _Hostname { get; set;} + private string _hostname; + /// /// Address of server /// - public string Hostname { + public string Hostname + { get { - return _Hostname; + return _hostname; } set { - _Hostname = value; - if (Client != null) + _hostname = value; + if (_client != null) { - - Client.AddressClientConnectedTo = _Hostname; + _client.AddressClientConnectedTo = _hostname; } } } @@ -83,14 +83,14 @@ namespace PepperDash.Core /// /// The actual client class /// - public TCPClient Client { get; private set; } + private TCPClient _client; /// - /// True if connected to the server + /// Bool showing if socket is connected /// public bool IsConnected { - get { return Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } /// @@ -102,21 +102,19 @@ namespace PepperDash.Core } /// - /// Status of the socket + /// _client socket status Read only /// public SocketStatus ClientStatus { get { - if (Client == null) - return SocketStatus.SOCKET_STATUS_NO_CONNECT; - return Client.ClientStatus; + return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; } } /// /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event - /// and IsConnected with be true when this == 2. + /// and IsConnected would be true when this == 2. /// public ushort UStatus { @@ -124,7 +122,7 @@ namespace PepperDash.Core } /// - /// Status of the socket + /// Status text shows the message associated with socket status /// public string ClientStatusText { get { return ClientStatus.ToString(); } } @@ -140,7 +138,7 @@ namespace PepperDash.Core public string ConnectionFailure { get { return ClientStatus.ToString(); } } /// - /// If true, enables AutoConnect + /// bool to track if auto reconnect should be set on the socket /// public bool AutoReconnect { get; set; } @@ -152,13 +150,14 @@ namespace PepperDash.Core get { return (ushort)(AutoReconnect ? 1 : 0); } set { AutoReconnect = value == 1; } } + /// /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 /// public int AutoReconnectIntervalMs { get; set; } /// - /// Set only when the disconnect method is called. + /// Set only when the disconnect method is called /// bool DisconnectCalledByUser; @@ -167,13 +166,14 @@ namespace PepperDash.Core /// public bool Connected { - get { return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } //Lock object to prevent simulatneous connect/disconnect operations - private readonly object connectLock = new object(); + private CCriticalSection connectLock = new CCriticalSection(); - CTimer RetryTimer; + // private Timer for auto reconnect + private CTimer RetryTimer; /// /// Constructor @@ -186,18 +186,17 @@ namespace PepperDash.Core : base(key) { StreamDebugging = new CommunicationStreamDebugging(key); + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; Hostname = address; Port = port; BufferSize = bufferSize; - AutoReconnectIntervalMs = 5000; RetryTimer = new CTimer(o => { Reconnect(); }, Timeout.Infinite); - - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - } + } /// /// Constructor @@ -223,7 +222,6 @@ namespace PepperDash.Core public GenericTcpIpClient() : base(SplusKey) { - StreamDebugging = new CommunicationStreamDebugging(SplusKey); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); AutoReconnectIntervalMs = 5000; BufferSize = 2000; @@ -262,9 +260,9 @@ namespace PepperDash.Core { RetryTimer.Stop(); RetryTimer.Dispose(); - if (Client != null) + if (_client != null) { - Client.SocketStatusChange -= this.Client_SocketStatusChange; + _client.SocketStatusChange -= this.Client_SocketStatusChange; DisconnectClient(); } return true; @@ -288,8 +286,9 @@ namespace PepperDash.Core } } - lock(connectLock) + try { + connectLock.Enter(); if (IsConnected) { Debug.Console(1, this, "Connection already connected. Exiting Connect()"); @@ -298,25 +297,28 @@ namespace PepperDash.Core { //Stop retry timer if running RetryTimer.Stop(); - - Client = new TCPClient(Hostname, Port, BufferSize); - Client.SocketStatusChange -= Client_SocketStatusChange; - Client.SocketStatusChange += Client_SocketStatusChange; + _client = new TCPClient(Hostname, Port, BufferSize); + _client.SocketStatusChange -= Client_SocketStatusChange; + _client.SocketStatusChange += Client_SocketStatusChange; DisconnectCalledByUser = false; - - Client.ConnectToServerAsync(ConnectToServerCallback); + _client.ConnectToServerAsync(ConnectToServerCallback); } + } + finally + { + connectLock.Leave(); } } private void Reconnect() { - if (Client == null) + if (_client == null) { return; } - lock (connectLock) + try { + connectLock.Enter(); if (IsConnected || DisconnectCalledByUser == true) { Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()"); @@ -324,9 +326,13 @@ namespace PepperDash.Core else { Debug.Console(1, this, "Attempting reconnect now"); - Client.ConnectToServerAsync(ConnectToServerCallback); + _client.ConnectToServerAsync(ConnectToServerCallback); } } + finally + { + connectLock.Leave(); + } } /// @@ -334,13 +340,18 @@ namespace PepperDash.Core /// public void Disconnect() { - lock (connectLock) + try { + connectLock.Enter(); DisconnectCalledByUser = true; // Stop trying reconnects, if we are RetryTimer.Stop(); DisconnectClient(); + } + finally + { + connectLock.Leave(); } } @@ -349,11 +360,11 @@ namespace PepperDash.Core /// public void DisconnectClient() { - if (Client != null) + if (_client != null) { Debug.Console(1, this, "Disconnecting client"); if (IsConnected) - Client.DisconnectFromServer(); + _client.DisconnectFromServer(); } } @@ -381,15 +392,20 @@ namespace PepperDash.Core { CrestronInvoke.BeginInvoke(o => { - lock (connectLock) + try { - if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && Client != null) + connectLock.Enter(); + if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null) { DisconnectClient(); - Debug.Console(1, this, "Attempting reconnect, status={0}", Client.ClientStatus); + Debug.Console(1, this, "Attempting reconnect, status={0}", _client.ClientStatus); RetryTimer.Reset(AutoReconnectIntervalMs); } } + finally + { + connectLock.Leave(); + } }); } @@ -420,7 +436,9 @@ namespace PepperDash.Core var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); if (StreamDebugging.RxStreamDebuggingIsEnabled) + { Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length); + } textHandler(this, new GenericCommMethodReceiveTextArgs(str)); } @@ -438,8 +456,8 @@ namespace PepperDash.Core // Check debug level before processing byte array if (StreamDebugging.TxStreamDebuggingIsEnabled) Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text)); - if(Client != null) - Client.SendData(bytes, bytes.Length); + if (_client != null) + _client.SendData(bytes, bytes.Length); } /// @@ -463,8 +481,8 @@ namespace PepperDash.Core { if (StreamDebugging.TxStreamDebuggingIsEnabled) Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); - if(Client != null) - Client.SendData(bytes, bytes.Length); + if (_client != null) + _client.SendData(bytes, bytes.Length); } /// @@ -479,9 +497,10 @@ namespace PepperDash.Core Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); WaitAndTryReconnect(); } + else { Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); - Client.ReceiveDataAsync(Receive); + _client.ReceiveDataAsync(Receive); } var handler = ConnectionChange;