diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs index 9b2c36f..e5e40d1 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs @@ -1,337 +1,404 @@ -using System; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.Ssh; -using Crestron.SimplSharp.Ssh.Common; - -namespace PepperDash.Core -{ - public class ConnectionChangeEventArgs : EventArgs - { - public bool IsConnected { get; private set; } - public GenericSshClient Client { get; private set; } - public ushort Status { get { return Client.UStatus; } } - - // S+ Constructor - public ConnectionChangeEventArgs() { } - - public ConnectionChangeEventArgs(bool isConnected, GenericSshClient client) - { - IsConnected = isConnected; - Client = client; - } - } - - //***************************************************************************************************** - //***************************************************************************************************** - - public class GenericSshClient : Device, IBasicCommunication, IAutoReconnect - { - public event EventHandler BytesReceived; - public event EventHandler TextReceived; - - public event EventHandler ConnectionChange; - //public event EventHandler DataReceive; - - public string Hostname { get; set; } - /// - /// Port on server - /// - public int Port { get; set; } - public string Username { get; set; } - public string Password { get; set; } - - public bool IsConnected - { - // returns false if no client or not connected - get { return (Client != null ? Client.IsConnected : false); } - set - { - if (value) - UStatus = 2; - OnConnectionChange(); - } - } - /// - /// Contains the familiar Simpl analog status values - /// - public ushort UStatus { get; private set; } - - /// - /// Determines whether client will attempt reconnection on failure. Default is true - /// - public bool AutoReconnect { get; set; } - - /// - /// Millisecond value, determines the timeout period in between reconnect attempts - /// - public int AutoReconnectIntervalMs { get; set; } - - SshClient Client; - ShellStream TheStream; - CTimer ReconnectTimer; - //bool ReconnectTimerRunning; - - public GenericSshClient(string key, string hostname, int port, string username, string password) : - base(key) - { - AutoReconnectIntervalMs = 5000; - AutoReconnect = true; - Hostname = hostname; - Port = port; - Username = username; - Password = password; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - } - - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping) - { - if (Client != null) - { - Debug.Console(2, this, "Closing connection"); - Client.Disconnect(); - Client.Dispose(); - Debug.Console(2, this, "Connection closed"); - } - } - } - - /// - /// Connect to the server, using the provided properties. - /// - public void Connect() - { - Debug.Console(1, this, "attempting connect, IsConnected={0}", Client != null ? Client.IsConnected : false); - - //ReconnectTimerRunning = false; - if (ReconnectTimer != null) - { - ReconnectTimer.Stop(); - ReconnectTimer = null; - } - - if (IsConnected) - return; - - if (Hostname != null && Hostname != string.Empty && Port > 0 && - Username != null && Password != null) - { - - UStatus = 1; - IsConnected = false; - - // 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); - ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); - // always spin up new client in case parameters have changed - if (Client != null) - { - Client.Disconnect(); - Client = null; - } - Client = new SshClient(connectionInfo); - - Client.ErrorOccurred += Client_ErrorOccurred; - try - { - Client.Connect(); - if (Client.IsConnected) - { - Client.KeepAliveInterval = TimeSpan.FromSeconds(2); - Client.SendKeepAlive(); - IsConnected = true; - Debug.Console(1, this, "Connected"); - TheStream = Client.CreateShellStream("PDTShell", 100, 80, 100, 200, 65534); - TheStream.DataReceived += Stream_DataReceived; - } - return; - } - catch (SshConnectionException e) - { - var ie = e.InnerException; // The details are inside!! - string msg; - if (ie is SocketException) - msg = string.Format("'{0}' CONNECTION failure: Cannot reach host, ({1})", Key, ie.GetType()); - else if (ie is System.Net.Sockets.SocketException) - msg = string.Format("'{0}' Connection failure: Cannot reach host '{1}' on port {2}, ({3})", - Key, Hostname, Port, ie.GetType()); - else if (ie is SshAuthenticationException) - { - msg = string.Format("'{0}' Authentication failure for username '{1}', ({2})", - Username, Key, ie.GetType()); - Debug.Console(0, this, "Authentication failure for username '{0}', ({1})", - Username, ie.GetType()); - } - else - Debug.Console(0, this, "Error on connect:\r({0})", e); - } - catch (Exception e) - { - Debug.Console(0, this, "Unhandled exception on connect:\r({0})", e); - } - } - else - { - Debug.Console(0, this, "Connect failed. Check hostname, port, username and password are set or not null"); - } - - // Sucess will not make it this far - UStatus = 3; - IsConnected = false; - HandleConnectionFailure(); - } - - /// - /// Disconnect the clients and put away it's resources. - /// - public void Disconnect() - { - // Stop trying reconnects, if we are - if (ReconnectTimer != null) - { - ReconnectTimer.Stop(); - ReconnectTimer = null; - } - // Otherwise just close up - if (Client != null) // && Client.IsConnected) <-- Doesn't always report properly... - { - Debug.Console(1, this, "Disconnecting"); - Client.Disconnect(); - Cleanup(); - UStatus = 5; - IsConnected = false; - } - } - - /// - /// Anything to do with reestablishing connection on failures - /// - void HandleConnectionFailure() - { - Debug.Console(2, this, "Checking autoreconnect: {0}, {1}ms", - AutoReconnect, AutoReconnectIntervalMs); - if (AutoReconnect) - { - if (ReconnectTimer == null)// || !ReconnectTimerRunning) - { - ReconnectTimer = new CTimer(o => - { - Connect(); - ReconnectTimer = null; - }, AutoReconnectIntervalMs); - Debug.Console(1, this, "Attempting connection in {0} seconds", - (float)(AutoReconnectIntervalMs / 1000)); - } - else - { - Debug.Console(2, this, "{0} second reconnect cycle running", - (float)(AutoReconnectIntervalMs / 1000)); - } - } - } - - void Cleanup() - { - Debug.Console(2, this, "cleaning up resources"); - Client = null; - } - - /// - /// Handles the keyboard interactive authentication, should it be required. - /// - void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) - { - foreach (AuthenticationPrompt prompt in e.Prompts) - if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) - prompt.Response = Password; - } - - /// - /// Handler for data receive on ShellStream. Passes data across to queue for line parsing. - /// - void Stream_DataReceived(object sender, Crestron.SimplSharp.Ssh.Common.ShellDataEventArgs e) - { - var bytes = e.Data; - if (bytes.Length > 0) - { - var bytesHandler = BytesReceived; - if (bytesHandler != null) - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - var textHandler = TextReceived; - if (textHandler != null) - { - var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } - } - } - - /// - /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange - /// event - /// - void Client_ErrorOccurred(object sender, Crestron.SimplSharp.Ssh.Common.ExceptionEventArgs e) - { - if (!(e.Exception is SshConnectionException)) - { - Debug.Console(0, this, "SSH client error: {0}", e.Exception); - UStatus = 4; - } - Client.Disconnect(); - Client = null; - Debug.Console(1, this, "Disconnected by remote"); - IsConnected = false; - HandleConnectionFailure(); - } - - /// - /// Helper for ConnectionChange event - /// - void OnConnectionChange() - { - if(ConnectionChange != null) - ConnectionChange(this, new ConnectionChangeEventArgs(IsConnected, this)); - } - - #region IBasicCommunication Members - - public void SendText(string text) - { - try - { - TheStream.Write(text); - TheStream.Flush(); - } - catch - { - Debug.Console(1, this, "Stream write failed. Disconnected, closing"); - UStatus = 4; - IsConnected = false; - HandleConnectionFailure(); - } - } - - public void SendBytes(byte[] bytes) - { - try - { - TheStream.Write(bytes, 0, bytes.Length); - TheStream.Flush(); - } - catch - { - Debug.Console(1, this, "Stream write failed. Disconnected, closing"); - UStatus = 4; - IsConnected = false; - HandleConnectionFailure(); - } - } - - #endregion - } -} +using System; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.Ssh; +using Crestron.SimplSharp.Ssh.Common; + +namespace PepperDash.Core +{ + public class ConnectionChangeEventArgs : EventArgs + { + public bool IsConnected { get; private set; } + + public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } } + + public GenericSshClient Client { get; private set; } + public ushort Status { get { return Client.UStatus; } } + + // S+ Constructor + public ConnectionChangeEventArgs() { } + + public ConnectionChangeEventArgs(bool isConnected, GenericSshClient client) + { + IsConnected = isConnected; + Client = client; + } + } + + //***************************************************************************************************** + //***************************************************************************************************** + + public class GenericSshClient : Device, IBasicCommunication, IAutoReconnect + { + /// + /// Event that fires when data is received. Delivers args with byte array + /// + public event EventHandler BytesReceived; + + /// + /// Event that fires when data is received. Delivered as text. + /// + public event EventHandler TextReceived; + + /// + /// Event when the connection status changes. + /// + public event EventHandler ConnectionChange; + + + public string Hostname { get; set; } + /// + /// Port on server + /// + public int Port { get; set; } + public string Username { get; set; } + public string Password { get; set; } + + public bool IsConnected + { + // returns false if no client or not connected + get { return UStatus == 2; } + } + /// + /// Contains the familiar Simpl analog status values + /// + public ushort UStatus + { + get { return _UStatus; } + private set + { + if (_UStatus == value) + return; + _UStatus = value; + OnConnectionChange(); + } + + } + ushort _UStatus; + + /// + /// Determines whether client will attempt reconnection on failure. Default is true + /// + public bool AutoReconnect { get; set; } + + /// + /// S+ helper for AutoReconnect + /// + public ushort UAutoReconnect + { + get { return (ushort)(AutoReconnect ? 1 : 0); } + set { AutoReconnect = value == 1; } + } + + /// + /// Millisecond value, determines the timeout period in between reconnect attempts. + /// Set to 5000 by default + /// + public int AutoReconnectIntervalMs { get; set; } + + SshClient Client; + ShellStream TheStream; + CTimer ReconnectTimer; + + /// + /// Typical constructor. + /// + public GenericSshClient(string key, string hostname, int port, string username, string password) : + base(key) + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + Key = key; + Hostname = hostname; + Port = port; + Username = username; + Password = password; + AutoReconnectIntervalMs = 5000; + } + + /// + /// S+ Constructor - Must set all properties before calling Connect + /// + public GenericSshClient() + : base("Uninitialized SshClient") + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + } + + /// + /// Just to help S+ set the key + /// + public void Initialize(string key) + { + Key = key; + } + + /// + /// Handles closing this up when the program shuts down + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + if (Client != null) + { + Debug.Console(2, this, "Program stopping. Closing connection"); + Client.Disconnect(); + Client.Dispose(); + } + } + } + + /// + /// Connect to the server, using the provided properties. + /// + public void Connect() + { + Debug.Console(1, this, "attempting connect, IsConnected={0}", Client != null ? Client.IsConnected : false); + + // Cancel reconnect if running. + if (ReconnectTimer != null) + { + ReconnectTimer.Stop(); + ReconnectTimer = null; + } + + // 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(0, this, "Connect failed. Check hostname, port, username and password are set or not null"); + return; + } + + //You can do it! + UStatus = 1; + //IsConnected = false; + + // 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); + ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); + + // always spin up new client in case parameters have changed + // **** MAY WANT TO CHANGE THIS BECAUSE OF SOCKET LEAKS **** + if (Client != null) + { + Client.Disconnect(); + Client = null; + } + Client = new SshClient(connectionInfo); + + Client.ErrorOccurred += Client_ErrorOccurred; + try + { + Client.Connect(); + if (Client.IsConnected) + { + Client.KeepAliveInterval = TimeSpan.FromSeconds(2); + Client.SendKeepAlive(); + TheStream = Client.CreateShellStream("PDTShell", 100, 80, 100, 200, 65534); + TheStream.DataReceived += Stream_DataReceived; + Debug.Console(1, this, "Connected"); + UStatus = 2; + //IsConnected = true; + } + return; + } + catch (SshConnectionException e) + { + var ie = e.InnerException; // The details are inside!! + if (ie is SocketException) + Debug.Console(0, this, "'{0}' CONNECTION failure: Cannot reach host, ({1})", Key, ie.GetType()); + else if (ie is System.Net.Sockets.SocketException) + Debug.Console(0, this, "'{0}' Connection failure: Cannot reach host '{1}' on port {2}, ({3})", + Key, Hostname, Port, ie.GetType()); + else if (ie is SshAuthenticationException) + { + Debug.Console(0, this, "Authentication failure for username '{0}', ({1})", + Username, ie.GetType()); + } + else + Debug.Console(0, this, "Error on connect:\r({0})", e); + } + catch (Exception e) + { + Debug.Console(0, this, "Unhandled exception on connect:\r({0})", e); + } + + + // Sucess will not make it this far + UStatus = 3; + //IsConnected = false; + HandleConnectionFailure(); + } + + /// + /// Disconnect the clients and put away it's resources. + /// + public void Disconnect() + { + // Stop trying reconnects, if we are + if (ReconnectTimer != null) + { + ReconnectTimer.Stop(); + ReconnectTimer = null; + } + DiscoAndCleanup(); + UStatus = 5; + //IsConnected = false; + } + + /// + /// + /// + void DiscoAndCleanup() + { + if (Client != null) + { + Client.ErrorOccurred -= Client_ErrorOccurred; + TheStream.DataReceived -= Stream_DataReceived; + Debug.Console(2, this, "Cleaning up disconnected client"); + Client.Disconnect(); + Client.Dispose(); + Client = null; + } + } + + /// + /// Anything to do with reestablishing connection on failures + /// + void HandleConnectionFailure() + { + DiscoAndCleanup(); + + Debug.Console(2, this, "Checking autoreconnect: {0}, {1}ms", + AutoReconnect, AutoReconnectIntervalMs); + if (AutoReconnect) + { + if (ReconnectTimer == null)// || !ReconnectTimerRunning) + { + ReconnectTimer = new CTimer(o => + { + Connect(); + ReconnectTimer = null; + }, AutoReconnectIntervalMs); + Debug.Console(1, this, "Attempting connection in {0} seconds", + (float)(AutoReconnectIntervalMs / 1000)); + } + else + { + Debug.Console(2, this, "{0} second reconnect cycle running", + (float)(AutoReconnectIntervalMs / 1000)); + } + } + } + + /// + /// Handles the keyboard interactive authentication, should it be required. + /// + void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) + { + foreach (AuthenticationPrompt prompt in e.Prompts) + if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) + prompt.Response = Password; + } + + /// + /// Handler for data receive on ShellStream. Passes data across to queue for line parsing. + /// + void Stream_DataReceived(object sender, Crestron.SimplSharp.Ssh.Common.ShellDataEventArgs e) + { + var bytes = e.Data; + if (bytes.Length > 0) + { + var bytesHandler = BytesReceived; + if (bytesHandler != null) + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + var textHandler = TextReceived; + if (textHandler != null) + { + var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + textHandler(this, new GenericCommMethodReceiveTextArgs(str)); + } + } + } + + /// + /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange + /// event + /// + void Client_ErrorOccurred(object sender, Crestron.SimplSharp.Ssh.Common.ExceptionEventArgs e) + { + Debug.Console(0, this, "SSH client error: {0}", e.Exception); + if (!(e.Exception is SshConnectionException)) + { + Debug.Console(1, this, "Disconnected by remote"); + } + if (Client != null) + { + Client.Disconnect(); + Client.Dispose(); + Client = null; + } + UStatus = 4; + //IsConnected = false; + HandleConnectionFailure(); + } + + /// + /// Helper for ConnectionChange event + /// + void OnConnectionChange() + { + if(ConnectionChange != null) + ConnectionChange(this, new ConnectionChangeEventArgs(IsConnected, this)); + } + + #region IBasicCommunication Members + + /// + /// + /// + /// + public void SendText(string text) + { + try + { + TheStream.Write(text); + TheStream.Flush(); + } + catch + { + Debug.Console(1, this, "Stream write failed. Disconnected, closing"); + UStatus = 4; + //IsConnected = false; + HandleConnectionFailure(); + } + } + + public void SendBytes(byte[] bytes) + { + try + { + TheStream.Write(bytes, 0, bytes.Length); + TheStream.Flush(); + } + catch + { + Debug.Console(1, this, "Stream write failed. Disconnected, closing"); + UStatus = 4; + //IsConnected = false; + HandleConnectionFailure(); + } + } + + #endregion + } +} diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs index d5c618f..59fb3f3 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs @@ -1,282 +1,282 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronSockets; - -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace PepperDash.Core -{ - public class GenericTcpIpClient : Device, IBasicCommunication, IAutoReconnect - { - /// - /// - /// - public event EventHandler BytesReceived; - - /// - /// - /// - public event EventHandler TextReceived; - - /// - /// - /// - public TCPClient Client { get; private set; } - - /// - /// - /// - public bool IsConnected { get { return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } - - /// - /// - /// - public SocketStatus ClientStatus { get { return Client.ClientStatus; } } - - /// - /// - /// - public string ClientStatusText { get { return Client.ClientStatus.ToString(); } } - - /// - /// - /// - public ushort UClientStatus { get { return (ushort)Client.ClientStatus; } } - - /// - /// - /// - public string ConnectionFailure { get { return Client.ClientStatus.ToString(); } } - - /// - /// - /// - public bool AutoReconnect { get; set; } - - /// - /// - /// - public int AutoReconnectIntervalMs { get; set; } - - /// - /// Set only when the disconnect method is called. - /// - bool DisconnectCalledByUser; - - /// - /// - /// - public bool Connected - { - get { return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } - } - - CTimer RetryTimer; - - public GenericTcpIpClient(string key, string address, int port, int bufferSize) - : base(key) - { - Client = new TCPClient(address, port, bufferSize); - } - - public override bool CustomActivate() - { - Client.SocketStatusChange += new TCPClientSocketStatusChangeEventHandler(Client_SocketStatusChange); - return true; - } - - public override bool Deactivate() - { - Client.SocketStatusChange -= this.Client_SocketStatusChange; - return true; - } - - public void Connect() - { - Client.ConnectToServerAsync(null); - DisconnectCalledByUser = false; - } - - public void Disconnnect() - { - DisconnectCalledByUser = true; - Client.DisconnectFromServer(); - } - - void ConnectToServerCallback(TCPClient c) - { - if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) - WaitAndTryReconnect(); - } - - void WaitAndTryReconnect() - { - Client.DisconnectFromServer(); - Debug.Console(2, "Attempting reconnect, status={0}", Client.ClientStatus); - RetryTimer = new CTimer(o => { Client.ConnectToServerAsync(ConnectToServerCallback); }, 1000); - } - - void Receive(TCPClient client, int numBytes) - { - if (numBytes > 0) - { - var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); - var bytesHandler = BytesReceived; - if (bytesHandler != null) - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - var textHandler = TextReceived; - if (textHandler != null) - { - var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } - } - Client.ReceiveDataAsync(Receive); - } - - /// - /// General send method - /// - public void SendText(string text) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - // Check debug level before processing byte array - if (Debug.Level == 2) - Debug.Console(2, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); - Client.SendData(bytes, bytes.Length); - } - - /// - /// This is useful from console and...? - /// - public void SendEscapedText(string text) - { - var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => - { - var hex = s.Groups[1].Value; - return ((char)Convert.ToByte(hex, 16)).ToString(); - }); - SendText(unescapedText); - - } - - public void SendBytes(byte[] bytes) - { - if (Debug.Level == 2) - Debug.Console(2, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); - Client.SendData(bytes, bytes.Length); - } - - - void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) - { - - Debug.Console(2, this, "Socket status change {0} ({1})", clientSocketStatus, UClientStatus); - if (client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED && !DisconnectCalledByUser) - WaitAndTryReconnect(); - - switch (clientSocketStatus) - { - case SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY: - break; - case SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY: - break; - case SocketStatus.SOCKET_STATUS_CONNECTED: - Client.ReceiveDataAsync(Receive); - DisconnectCalledByUser = false; - break; - case SocketStatus.SOCKET_STATUS_CONNECT_FAILED: - break; - case SocketStatus.SOCKET_STATUS_DNS_FAILED: - break; - case SocketStatus.SOCKET_STATUS_DNS_LOOKUP: - break; - case SocketStatus.SOCKET_STATUS_DNS_RESOLVED: - break; - case SocketStatus.SOCKET_STATUS_LINK_LOST: - break; - case SocketStatus.SOCKET_STATUS_NO_CONNECT: - break; - case SocketStatus.SOCKET_STATUS_SOCKET_NOT_EXIST: - break; - case SocketStatus.SOCKET_STATUS_WAITING: - break; - default: - break; - } - } - } - - - public class TcpSshPropertiesConfig - { - [JsonProperty(Required = Required.Always)] - public string Address { get; set; } - - [JsonProperty(Required = Required.Always)] - public int Port { get; set; } - - public string Username { get; set; } - public string Password { get; set; } - - /// - /// Defaults to 32768 - /// - public int BufferSize { get; set; } - - /// - /// Defaults to true - /// - public bool AutoReconnect { get; set; } - - /// - /// Defaults to 5000ms - /// - public int AutoReconnectIntervalMs { get; set; } - - public TcpSshPropertiesConfig() - { - BufferSize = 32768; - AutoReconnect = true; - AutoReconnectIntervalMs = 5000; - } - - } - - //public class TcpIpConfig - //{ - // [JsonProperty(Required = Required.Always)] - // public string Address { get; set; } - - // [JsonProperty(Required = Required.Always)] - // public int Port { get; set; } - - // /// - // /// Defaults to 32768 - // /// - // public int BufferSize { get; set; } - - // /// - // /// Defaults to true - // /// - // public bool AutoReconnect { get; set; } - - // /// - // /// Defaults to 5000ms - // /// - // public int AutoReconnectIntervalMs { get; set; } - - // public TcpIpConfig() - // { - // BufferSize = 32768; - // AutoReconnect = true; - // AutoReconnectIntervalMs = 5000; - // } - //} - +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronSockets; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PepperDash.Core +{ + public class GenericTcpIpClient : Device, IBasicCommunication, IAutoReconnect + { + /// + /// + /// + public event EventHandler BytesReceived; + + /// + /// + /// + public event EventHandler TextReceived; + + /// + /// + /// + public TCPClient Client { get; private set; } + + /// + /// + /// + public bool IsConnected { get { return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } + + /// + /// + /// + public SocketStatus ClientStatus { get { return Client.ClientStatus; } } + + /// + /// + /// + public string ClientStatusText { get { return Client.ClientStatus.ToString(); } } + + /// + /// + /// + public ushort UClientStatus { get { return (ushort)Client.ClientStatus; } } + + /// + /// + /// + public string ConnectionFailure { get { return Client.ClientStatus.ToString(); } } + + /// + /// + /// + public bool AutoReconnect { get; set; } + + /// + /// + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// Set only when the disconnect method is called. + /// + bool DisconnectCalledByUser; + + /// + /// + /// + public bool Connected + { + get { return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + } + + CTimer RetryTimer; + + public GenericTcpIpClient(string key, string address, int port, int bufferSize) + : base(key) + { + Client = new TCPClient(address, port, bufferSize); + Client.SocketStatusChange += new TCPClientSocketStatusChangeEventHandler(Client_SocketStatusChange); + } + + //public override bool CustomActivate() + //{ + // return true; + //} + + public override bool Deactivate() + { + Client.SocketStatusChange -= this.Client_SocketStatusChange; + return true; + } + + public void Connect() + { + Client.ConnectToServerAsync(null); + DisconnectCalledByUser = false; + } + + public void Disconnnect() + { + DisconnectCalledByUser = true; + Client.DisconnectFromServer(); + } + + void ConnectToServerCallback(TCPClient c) + { + if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + WaitAndTryReconnect(); + } + + void WaitAndTryReconnect() + { + Client.DisconnectFromServer(); + Debug.Console(2, "Attempting reconnect, status={0}", Client.ClientStatus); + RetryTimer = new CTimer(o => { Client.ConnectToServerAsync(ConnectToServerCallback); }, 1000); + } + + void Receive(TCPClient client, int numBytes) + { + if (numBytes > 0) + { + var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); + var bytesHandler = BytesReceived; + if (bytesHandler != null) + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + var textHandler = TextReceived; + if (textHandler != null) + { + var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + textHandler(this, new GenericCommMethodReceiveTextArgs(str)); + } + } + Client.ReceiveDataAsync(Receive); + } + + /// + /// General send method + /// + public void SendText(string text) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + // Check debug level before processing byte array + if (Debug.Level == 2) + Debug.Console(2, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + Client.SendData(bytes, bytes.Length); + } + + /// + /// This is useful from console and...? + /// + public void SendEscapedText(string text) + { + var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => + { + var hex = s.Groups[1].Value; + return ((char)Convert.ToByte(hex, 16)).ToString(); + }); + SendText(unescapedText); + + } + + public void SendBytes(byte[] bytes) + { + if (Debug.Level == 2) + Debug.Console(2, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + Client.SendData(bytes, bytes.Length); + } + + + void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) + { + + Debug.Console(2, this, "Socket status change {0} ({1})", clientSocketStatus, UClientStatus); + if (client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED && !DisconnectCalledByUser) + WaitAndTryReconnect(); + + switch (clientSocketStatus) + { + case SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY: + break; + case SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY: + break; + case SocketStatus.SOCKET_STATUS_CONNECTED: + Client.ReceiveDataAsync(Receive); + DisconnectCalledByUser = false; + break; + case SocketStatus.SOCKET_STATUS_CONNECT_FAILED: + break; + case SocketStatus.SOCKET_STATUS_DNS_FAILED: + break; + case SocketStatus.SOCKET_STATUS_DNS_LOOKUP: + break; + case SocketStatus.SOCKET_STATUS_DNS_RESOLVED: + break; + case SocketStatus.SOCKET_STATUS_LINK_LOST: + break; + case SocketStatus.SOCKET_STATUS_NO_CONNECT: + break; + case SocketStatus.SOCKET_STATUS_SOCKET_NOT_EXIST: + break; + case SocketStatus.SOCKET_STATUS_WAITING: + break; + default: + break; + } + } + } + + + public class TcpSshPropertiesConfig + { + [JsonProperty(Required = Required.Always)] + public string Address { get; set; } + + [JsonProperty(Required = Required.Always)] + public int Port { get; set; } + + public string Username { get; set; } + public string Password { get; set; } + + /// + /// Defaults to 32768 + /// + public int BufferSize { get; set; } + + /// + /// Defaults to true + /// + public bool AutoReconnect { get; set; } + + /// + /// Defaults to 5000ms + /// + public int AutoReconnectIntervalMs { get; set; } + + public TcpSshPropertiesConfig() + { + BufferSize = 32768; + AutoReconnect = true; + AutoReconnectIntervalMs = 5000; + } + + } + + //public class TcpIpConfig + //{ + // [JsonProperty(Required = Required.Always)] + // public string Address { get; set; } + + // [JsonProperty(Required = Required.Always)] + // public int Port { get; set; } + + // /// + // /// Defaults to 32768 + // /// + // public int BufferSize { get; set; } + + // /// + // /// Defaults to true + // /// + // public bool AutoReconnect { get; set; } + + // /// + // /// Defaults to 5000ms + // /// + // public int AutoReconnectIntervalMs { get; set; } + + // public TcpIpConfig() + // { + // BufferSize = 32768; + // AutoReconnect = true; + // AutoReconnectIntervalMs = 5000; + // } + //} + } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/CommunicationExtras.cs b/Pepperdash Core/Pepperdash Core/CommunicationExtras.cs index fc50f0f..910d277 100644 --- a/Pepperdash Core/Pepperdash Core/CommunicationExtras.cs +++ b/Pepperdash Core/Pepperdash Core/CommunicationExtras.cs @@ -1,87 +1,97 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace PepperDash.Core -{ - /// - /// Represents a device that uses basic connection - /// - public interface IBasicCommunication : IKeyed - { - event EventHandler BytesReceived; - event EventHandler TextReceived; - - bool IsConnected { get; } - void SendText(string text); - void SendBytes(byte[] bytes); - void Connect(); - } - - public interface IAutoReconnect - { - bool AutoReconnect { get; set; } - int AutoReconnectIntervalMs { get; set; } - } - - /// - /// - /// - public enum eGenericCommMethodStatusChangeType - { - Connected, Disconnected - } - - /// - /// This delegate defines handler for IBasicCommunication status changes - /// - /// Device firing the status change - /// - public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status); - - /// - /// - /// - public class GenericCommMethodReceiveBytesArgs : EventArgs - { - public byte[] Bytes { get; private set; } - public GenericCommMethodReceiveBytesArgs(byte[] bytes) - { - Bytes = bytes; - } - } - - /// - /// - /// - public class GenericCommMethodReceiveTextArgs : EventArgs - { - public string Text { get; private set; } - public GenericCommMethodReceiveTextArgs(string text) - { - Text = text; - } - } - - /// - /// - /// - public class ComTextHelper - { - public static string GetEscapedText(byte[] bytes) - { - return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); - } - - public static string GetEscapedText(string text) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PepperDash.Core +{ + /// + /// Represents a device that uses basic connection + /// + public interface IBasicCommunication : IKeyed + { + event EventHandler BytesReceived; + event EventHandler TextReceived; + + bool IsConnected { get; } + void SendText(string text); + void SendBytes(byte[] bytes); + void Connect(); + } + + public interface IAutoReconnect + { + bool AutoReconnect { get; set; } + int AutoReconnectIntervalMs { get; set; } + } + + /// + /// + /// + public enum eGenericCommMethodStatusChangeType + { + Connected, Disconnected + } + + /// + /// This delegate defines handler for IBasicCommunication status changes + /// + /// Device firing the status change + /// + public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status); + + /// + /// + /// + public class GenericCommMethodReceiveBytesArgs : EventArgs + { + public byte[] Bytes { get; private set; } + public GenericCommMethodReceiveBytesArgs(byte[] bytes) + { + Bytes = bytes; + } + + /// + /// Stupid S+ Constructor + /// + public GenericCommMethodReceiveBytesArgs() { } + } + + /// + /// + /// + public class GenericCommMethodReceiveTextArgs : EventArgs + { + public string Text { get; private set; } + public GenericCommMethodReceiveTextArgs(string text) + { + Text = text; + } + + /// + /// Stupid S+ Constructor + /// + public GenericCommMethodReceiveTextArgs() { } + } + + /// + /// + /// + public class ComTextHelper + { + public static string GetEscapedText(byte[] bytes) + { + return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); + } + + public static string GetEscapedText(string text) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj index b3f1dba..71d09a4 100644 --- a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj +++ b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj @@ -85,7 +85,7 @@ C:\Users\hvolm\Desktop\working\pepperdash-simplsharp-core\Pepperdash Core\Pepperdash Core\bin\PepperDash_Core.clz 1.007.0017 - 9/20/2016 2:09:33 PM + 9/22/2016 9:42:40 PM False diff --git a/Pepperdash Core/Pepperdash Core/bin/PepperDash_Core.clz b/Pepperdash Core/Pepperdash Core/bin/PepperDash_Core.clz index 31f2338..2f42194 100644 Binary files a/Pepperdash Core/Pepperdash Core/bin/PepperDash_Core.clz and b/Pepperdash Core/Pepperdash Core/bin/PepperDash_Core.clz differ diff --git a/Pepperdash Core/Pepperdash Core/bin/PepperDash_Core.config b/Pepperdash Core/Pepperdash Core/bin/PepperDash_Core.config index 4936f83..7b07906 100644 --- a/Pepperdash Core/Pepperdash Core/bin/PepperDash_Core.config +++ b/Pepperdash Core/Pepperdash Core/bin/PepperDash_Core.config @@ -10,7 +10,7 @@ - 9/20/2016 2:09:33 PM - 1.0.0.23686 + 9/22/2016 9:42:40 PM + 1.0.0.37279 \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/bin/PepperDash_Core.dll b/Pepperdash Core/Pepperdash Core/bin/PepperDash_Core.dll index 41a5609..3216370 100644 Binary files a/Pepperdash Core/Pepperdash Core/bin/PepperDash_Core.dll and b/Pepperdash Core/Pepperdash Core/bin/PepperDash_Core.dll differ diff --git a/Pepperdash Core/Pepperdash Core/bin/manifest.info b/Pepperdash Core/Pepperdash Core/bin/manifest.info index 99454ee..523e5f6 100644 --- a/Pepperdash Core/Pepperdash Core/bin/manifest.info +++ b/Pepperdash Core/Pepperdash Core/bin/manifest.info @@ -1,4 +1,4 @@ -MainAssembly=PepperDash_Core.dll:d21b9348a9ff127c20006e8d8b9a166c +MainAssembly=PepperDash_Core.dll:022f5296f4c24102c3cb64cd3a10fa41 MainAssemblyMinFirmwareVersion=1.007.0017 ü DependencySource=Newtonsoft.Json.Compact.dll:ea996aa2ec65aa1878e7c9d09e37a896 diff --git a/Pepperdash Core/Pepperdash Core/bin/manifest.ser b/Pepperdash Core/Pepperdash Core/bin/manifest.ser index 05d9635..245249e 100644 Binary files a/Pepperdash Core/Pepperdash Core/bin/manifest.ser and b/Pepperdash Core/Pepperdash Core/bin/manifest.ser differ