GenericSshClient changes for S+ module

This commit is contained in:
Heath Volmer
2016-09-22 21:57:21 -06:00
parent 0c72e68cc0
commit 8aa9f130cd
9 changed files with 785 additions and 708 deletions

View File

@@ -1,337 +1,404 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.Ssh; using Crestron.SimplSharp.Ssh;
using Crestron.SimplSharp.Ssh.Common; using Crestron.SimplSharp.Ssh.Common;
namespace PepperDash.Core namespace PepperDash.Core
{ {
public class ConnectionChangeEventArgs : EventArgs public class ConnectionChangeEventArgs : EventArgs
{ {
public bool IsConnected { get; private set; } public bool IsConnected { get; private set; }
public GenericSshClient Client { get; private set; }
public ushort Status { get { return Client.UStatus; } } public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } }
// S+ Constructor public GenericSshClient Client { get; private set; }
public ConnectionChangeEventArgs() { } public ushort Status { get { return Client.UStatus; } }
public ConnectionChangeEventArgs(bool isConnected, GenericSshClient client) // S+ Constructor
{ public ConnectionChangeEventArgs() { }
IsConnected = isConnected;
Client = client; public ConnectionChangeEventArgs(bool isConnected, GenericSshClient client)
} {
} IsConnected = isConnected;
Client = client;
//***************************************************************************************************** }
//***************************************************************************************************** }
public class GenericSshClient : Device, IBasicCommunication, IAutoReconnect //*****************************************************************************************************
{ //*****************************************************************************************************
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived; public class GenericSshClient : Device, IBasicCommunication, IAutoReconnect
{
public event EventHandler<ConnectionChangeEventArgs> ConnectionChange; /// <summary>
//public event EventHandler<DataReceiveEventArgs> DataReceive; /// Event that fires when data is received. Delivers args with byte array
/// </summary>
public string Hostname { get; set; } public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Port on server /// <summary>
/// </summary> /// Event that fires when data is received. Delivered as text.
public int Port { get; set; } /// </summary>
public string Username { get; set; } public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
public string Password { get; set; }
/// <summary>
public bool IsConnected /// Event when the connection status changes.
{ /// </summary>
// returns false if no client or not connected public event EventHandler<ConnectionChangeEventArgs> ConnectionChange;
get { return (Client != null ? Client.IsConnected : false); }
set
{ public string Hostname { get; set; }
if (value) /// <summary>
UStatus = 2; /// Port on server
OnConnectionChange(); /// </summary>
} public int Port { get; set; }
} public string Username { get; set; }
/// <summary> public string Password { get; set; }
/// Contains the familiar Simpl analog status values
/// </summary> public bool IsConnected
public ushort UStatus { get; private set; } {
// returns false if no client or not connected
/// <summary> get { return UStatus == 2; }
/// Determines whether client will attempt reconnection on failure. Default is true }
/// </summary> /// <summary>
public bool AutoReconnect { get; set; } /// Contains the familiar Simpl analog status values
/// </summary>
/// <summary> public ushort UStatus
/// Millisecond value, determines the timeout period in between reconnect attempts {
/// </summary> get { return _UStatus; }
public int AutoReconnectIntervalMs { get; set; } private set
{
SshClient Client; if (_UStatus == value)
ShellStream TheStream; return;
CTimer ReconnectTimer; _UStatus = value;
//bool ReconnectTimerRunning; OnConnectionChange();
}
public GenericSshClient(string key, string hostname, int port, string username, string password) :
base(key) }
{ ushort _UStatus;
AutoReconnectIntervalMs = 5000;
AutoReconnect = true; /// <summary>
Hostname = hostname; /// Determines whether client will attempt reconnection on failure. Default is true
Port = port; /// </summary>
Username = username; public bool AutoReconnect { get; set; }
Password = password;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); /// <summary>
} /// S+ helper for AutoReconnect
/// </summary>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) public ushort UAutoReconnect
{ {
if (programEventType == eProgramStatusEventType.Stopping) get { return (ushort)(AutoReconnect ? 1 : 0); }
{ set { AutoReconnect = value == 1; }
if (Client != null) }
{
Debug.Console(2, this, "Closing connection"); /// <summary>
Client.Disconnect(); /// Millisecond value, determines the timeout period in between reconnect attempts.
Client.Dispose(); /// Set to 5000 by default
Debug.Console(2, this, "Connection closed"); /// </summary>
} public int AutoReconnectIntervalMs { get; set; }
}
} SshClient Client;
ShellStream TheStream;
/// <summary> CTimer ReconnectTimer;
/// Connect to the server, using the provided properties.
/// </summary> /// <summary>
public void Connect() /// Typical constructor.
{ /// </summary>
Debug.Console(1, this, "attempting connect, IsConnected={0}", Client != null ? Client.IsConnected : false); public GenericSshClient(string key, string hostname, int port, string username, string password) :
base(key)
//ReconnectTimerRunning = false; {
if (ReconnectTimer != null) CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
{ Key = key;
ReconnectTimer.Stop(); Hostname = hostname;
ReconnectTimer = null; Port = port;
} Username = username;
Password = password;
if (IsConnected) AutoReconnectIntervalMs = 5000;
return; }
if (Hostname != null && Hostname != string.Empty && Port > 0 && /// <summary>
Username != null && Password != null) /// S+ Constructor - Must set all properties before calling Connect
{ /// </summary>
public GenericSshClient()
UStatus = 1; : base("Uninitialized SshClient")
IsConnected = false; {
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
// This handles both password and keyboard-interactive (like on OS-X, 'nixes) AutoReconnectIntervalMs = 5000;
KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod(Username); }
kauth.AuthenticationPrompt += new EventHandler<AuthenticationPromptEventArgs>(kauth_AuthenticationPrompt);
PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password); /// <summary>
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); /// Just to help S+ set the key
// always spin up new client in case parameters have changed /// </summary>
if (Client != null) public void Initialize(string key)
{ {
Client.Disconnect(); Key = key;
Client = null; }
}
Client = new SshClient(connectionInfo); /// <summary>
/// Handles closing this up when the program shuts down
Client.ErrorOccurred += Client_ErrorOccurred; /// </summary>
try void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{ {
Client.Connect(); if (programEventType == eProgramStatusEventType.Stopping)
if (Client.IsConnected) {
{ if (Client != null)
Client.KeepAliveInterval = TimeSpan.FromSeconds(2); {
Client.SendKeepAlive(); Debug.Console(2, this, "Program stopping. Closing connection");
IsConnected = true; Client.Disconnect();
Debug.Console(1, this, "Connected"); Client.Dispose();
TheStream = Client.CreateShellStream("PDTShell", 100, 80, 100, 200, 65534); }
TheStream.DataReceived += Stream_DataReceived; }
} }
return;
} /// <summary>
catch (SshConnectionException e) /// Connect to the server, using the provided properties.
{ /// </summary>
var ie = e.InnerException; // The details are inside!! public void Connect()
string msg; {
if (ie is SocketException) Debug.Console(1, this, "attempting connect, IsConnected={0}", Client != null ? Client.IsConnected : false);
msg = string.Format("'{0}' CONNECTION failure: Cannot reach host, ({1})", Key, ie.GetType());
else if (ie is System.Net.Sockets.SocketException) // Cancel reconnect if running.
msg = string.Format("'{0}' Connection failure: Cannot reach host '{1}' on port {2}, ({3})", if (ReconnectTimer != null)
Key, Hostname, Port, ie.GetType()); {
else if (ie is SshAuthenticationException) ReconnectTimer.Stop();
{ ReconnectTimer = null;
msg = string.Format("'{0}' Authentication failure for username '{1}', ({2})", }
Username, Key, ie.GetType());
Debug.Console(0, this, "Authentication failure for username '{0}', ({1})", // Don't try to connect if already
Username, ie.GetType()); if (IsConnected)
} return;
else
Debug.Console(0, this, "Error on connect:\r({0})", e); // Don't go unless everything is here
} if (string.IsNullOrEmpty(Hostname) || Port < 1 || Port > 65535
catch (Exception e) || Username == null || Password == null)
{ {
Debug.Console(0, this, "Unhandled exception on connect:\r({0})", e); Debug.Console(0, this, "Connect failed. Check hostname, port, username and password are set or not null");
} return;
} }
else
{ //You can do it!
Debug.Console(0, this, "Connect failed. Check hostname, port, username and password are set or not null"); UStatus = 1;
} //IsConnected = false;
// Sucess will not make it this far // This handles both password and keyboard-interactive (like on OS-X, 'nixes)
UStatus = 3; KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod(Username);
IsConnected = false; kauth.AuthenticationPrompt += new EventHandler<AuthenticationPromptEventArgs>(kauth_AuthenticationPrompt);
HandleConnectionFailure(); PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password);
} ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
/// <summary> // always spin up new client in case parameters have changed
/// Disconnect the clients and put away it's resources. // **** MAY WANT TO CHANGE THIS BECAUSE OF SOCKET LEAKS ****
/// </summary> if (Client != null)
public void Disconnect() {
{ Client.Disconnect();
// Stop trying reconnects, if we are Client = null;
if (ReconnectTimer != null) }
{ Client = new SshClient(connectionInfo);
ReconnectTimer.Stop();
ReconnectTimer = null; Client.ErrorOccurred += Client_ErrorOccurred;
} try
// Otherwise just close up {
if (Client != null) // && Client.IsConnected) <-- Doesn't always report properly... Client.Connect();
{ if (Client.IsConnected)
Debug.Console(1, this, "Disconnecting"); {
Client.Disconnect(); Client.KeepAliveInterval = TimeSpan.FromSeconds(2);
Cleanup(); Client.SendKeepAlive();
UStatus = 5; TheStream = Client.CreateShellStream("PDTShell", 100, 80, 100, 200, 65534);
IsConnected = false; TheStream.DataReceived += Stream_DataReceived;
} Debug.Console(1, this, "Connected");
} UStatus = 2;
//IsConnected = true;
/// <summary> }
/// Anything to do with reestablishing connection on failures return;
/// </summary> }
void HandleConnectionFailure() catch (SshConnectionException e)
{ {
Debug.Console(2, this, "Checking autoreconnect: {0}, {1}ms", var ie = e.InnerException; // The details are inside!!
AutoReconnect, AutoReconnectIntervalMs); if (ie is SocketException)
if (AutoReconnect) Debug.Console(0, this, "'{0}' CONNECTION failure: Cannot reach host, ({1})", Key, ie.GetType());
{ else if (ie is System.Net.Sockets.SocketException)
if (ReconnectTimer == null)// || !ReconnectTimerRunning) Debug.Console(0, this, "'{0}' Connection failure: Cannot reach host '{1}' on port {2}, ({3})",
{ Key, Hostname, Port, ie.GetType());
ReconnectTimer = new CTimer(o => else if (ie is SshAuthenticationException)
{ {
Connect(); Debug.Console(0, this, "Authentication failure for username '{0}', ({1})",
ReconnectTimer = null; Username, ie.GetType());
}, AutoReconnectIntervalMs); }
Debug.Console(1, this, "Attempting connection in {0} seconds", else
(float)(AutoReconnectIntervalMs / 1000)); Debug.Console(0, this, "Error on connect:\r({0})", e);
} }
else catch (Exception e)
{ {
Debug.Console(2, this, "{0} second reconnect cycle running", Debug.Console(0, this, "Unhandled exception on connect:\r({0})", e);
(float)(AutoReconnectIntervalMs / 1000)); }
}
}
} // Sucess will not make it this far
UStatus = 3;
void Cleanup() //IsConnected = false;
{ HandleConnectionFailure();
Debug.Console(2, this, "cleaning up resources"); }
Client = null;
} /// <summary>
/// Disconnect the clients and put away it's resources.
/// <summary> /// </summary>
/// Handles the keyboard interactive authentication, should it be required. public void Disconnect()
/// </summary> {
void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) // Stop trying reconnects, if we are
{ if (ReconnectTimer != null)
foreach (AuthenticationPrompt prompt in e.Prompts) {
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) ReconnectTimer.Stop();
prompt.Response = Password; ReconnectTimer = null;
} }
DiscoAndCleanup();
/// <summary> UStatus = 5;
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing. //IsConnected = false;
/// </summary> }
void Stream_DataReceived(object sender, Crestron.SimplSharp.Ssh.Common.ShellDataEventArgs e)
{ /// <summary>
var bytes = e.Data; ///
if (bytes.Length > 0) /// </summary>
{ void DiscoAndCleanup()
var bytesHandler = BytesReceived; {
if (bytesHandler != null) if (Client != null)
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); {
var textHandler = TextReceived; Client.ErrorOccurred -= Client_ErrorOccurred;
if (textHandler != null) TheStream.DataReceived -= Stream_DataReceived;
{ Debug.Console(2, this, "Cleaning up disconnected client");
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); Client.Disconnect();
textHandler(this, new GenericCommMethodReceiveTextArgs(str)); Client.Dispose();
} Client = null;
} }
} }
/// <summary> /// <summary>
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange /// Anything to do with reestablishing connection on failures
/// event /// </summary>
/// </summary> void HandleConnectionFailure()
void Client_ErrorOccurred(object sender, Crestron.SimplSharp.Ssh.Common.ExceptionEventArgs e) {
{ DiscoAndCleanup();
if (!(e.Exception is SshConnectionException))
{ Debug.Console(2, this, "Checking autoreconnect: {0}, {1}ms",
Debug.Console(0, this, "SSH client error: {0}", e.Exception); AutoReconnect, AutoReconnectIntervalMs);
UStatus = 4; if (AutoReconnect)
} {
Client.Disconnect(); if (ReconnectTimer == null)// || !ReconnectTimerRunning)
Client = null; {
Debug.Console(1, this, "Disconnected by remote"); ReconnectTimer = new CTimer(o =>
IsConnected = false; {
HandleConnectionFailure(); Connect();
} ReconnectTimer = null;
}, AutoReconnectIntervalMs);
/// <summary> Debug.Console(1, this, "Attempting connection in {0} seconds",
/// Helper for ConnectionChange event (float)(AutoReconnectIntervalMs / 1000));
/// </summary> }
void OnConnectionChange() else
{ {
if(ConnectionChange != null) Debug.Console(2, this, "{0} second reconnect cycle running",
ConnectionChange(this, new ConnectionChangeEventArgs(IsConnected, this)); (float)(AutoReconnectIntervalMs / 1000));
} }
}
#region IBasicCommunication Members }
public void SendText(string text) /// <summary>
{ /// Handles the keyboard interactive authentication, should it be required.
try /// </summary>
{ void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
TheStream.Write(text); {
TheStream.Flush(); foreach (AuthenticationPrompt prompt in e.Prompts)
} if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
catch prompt.Response = Password;
{ }
Debug.Console(1, this, "Stream write failed. Disconnected, closing");
UStatus = 4; /// <summary>
IsConnected = false; /// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
HandleConnectionFailure(); /// </summary>
} void Stream_DataReceived(object sender, Crestron.SimplSharp.Ssh.Common.ShellDataEventArgs e)
} {
var bytes = e.Data;
public void SendBytes(byte[] bytes) if (bytes.Length > 0)
{ {
try var bytesHandler = BytesReceived;
{ if (bytesHandler != null)
TheStream.Write(bytes, 0, bytes.Length); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
TheStream.Flush(); var textHandler = TextReceived;
} if (textHandler != null)
catch {
{ var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
Debug.Console(1, this, "Stream write failed. Disconnected, closing"); textHandler(this, new GenericCommMethodReceiveTextArgs(str));
UStatus = 4; }
IsConnected = false; }
HandleConnectionFailure(); }
}
} /// <summary>
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
#endregion /// event
} /// </summary>
} 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();
}
/// <summary>
/// Helper for ConnectionChange event
/// </summary>
void OnConnectionChange()
{
if(ConnectionChange != null)
ConnectionChange(this, new ConnectionChangeEventArgs(IsConnected, this));
}
#region IBasicCommunication Members
/// <summary>
///
/// </summary>
/// <param name="text"></param>
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
}
}

View File

@@ -1,282 +1,282 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets; using Crestron.SimplSharp.CrestronSockets;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace PepperDash.Core namespace PepperDash.Core
{ {
public class GenericTcpIpClient : Device, IBasicCommunication, IAutoReconnect public class GenericTcpIpClient : Device, IBasicCommunication, IAutoReconnect
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived; public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived; public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public TCPClient Client { get; private set; } public TCPClient Client { get; private set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public bool IsConnected { get { return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } } public bool IsConnected { get { return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public SocketStatus ClientStatus { get { return Client.ClientStatus; } } public SocketStatus ClientStatus { get { return Client.ClientStatus; } }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string ClientStatusText { get { return Client.ClientStatus.ToString(); } } public string ClientStatusText { get { return Client.ClientStatus.ToString(); } }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort UClientStatus { get { return (ushort)Client.ClientStatus; } } public ushort UClientStatus { get { return (ushort)Client.ClientStatus; } }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string ConnectionFailure { get { return Client.ClientStatus.ToString(); } } public string ConnectionFailure { get { return Client.ClientStatus.ToString(); } }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public bool AutoReconnect { get; set; } public bool AutoReconnect { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int AutoReconnectIntervalMs { get; set; } public int AutoReconnectIntervalMs { get; set; }
/// <summary> /// <summary>
/// Set only when the disconnect method is called. /// Set only when the disconnect method is called.
/// </summary> /// </summary>
bool DisconnectCalledByUser; bool DisconnectCalledByUser;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public bool Connected public bool Connected
{ {
get { return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } get { return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
} }
CTimer RetryTimer; CTimer RetryTimer;
public GenericTcpIpClient(string key, string address, int port, int bufferSize) public GenericTcpIpClient(string key, string address, int port, int bufferSize)
: base(key) : base(key)
{ {
Client = new TCPClient(address, port, bufferSize); Client = new TCPClient(address, port, bufferSize);
} Client.SocketStatusChange += new TCPClientSocketStatusChangeEventHandler(Client_SocketStatusChange);
}
public override bool CustomActivate()
{ //public override bool CustomActivate()
Client.SocketStatusChange += new TCPClientSocketStatusChangeEventHandler(Client_SocketStatusChange); //{
return true; // return true;
} //}
public override bool Deactivate() public override bool Deactivate()
{ {
Client.SocketStatusChange -= this.Client_SocketStatusChange; Client.SocketStatusChange -= this.Client_SocketStatusChange;
return true; return true;
} }
public void Connect() public void Connect()
{ {
Client.ConnectToServerAsync(null); Client.ConnectToServerAsync(null);
DisconnectCalledByUser = false; DisconnectCalledByUser = false;
} }
public void Disconnnect() public void Disconnnect()
{ {
DisconnectCalledByUser = true; DisconnectCalledByUser = true;
Client.DisconnectFromServer(); Client.DisconnectFromServer();
} }
void ConnectToServerCallback(TCPClient c) void ConnectToServerCallback(TCPClient c)
{ {
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
WaitAndTryReconnect(); WaitAndTryReconnect();
} }
void WaitAndTryReconnect() void WaitAndTryReconnect()
{ {
Client.DisconnectFromServer(); Client.DisconnectFromServer();
Debug.Console(2, "Attempting reconnect, status={0}", Client.ClientStatus); Debug.Console(2, "Attempting reconnect, status={0}", Client.ClientStatus);
RetryTimer = new CTimer(o => { Client.ConnectToServerAsync(ConnectToServerCallback); }, 1000); RetryTimer = new CTimer(o => { Client.ConnectToServerAsync(ConnectToServerCallback); }, 1000);
} }
void Receive(TCPClient client, int numBytes) void Receive(TCPClient client, int numBytes)
{ {
if (numBytes > 0) if (numBytes > 0)
{ {
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
var bytesHandler = BytesReceived; var bytesHandler = BytesReceived;
if (bytesHandler != null) if (bytesHandler != null)
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
var textHandler = TextReceived; var textHandler = TextReceived;
if (textHandler != null) if (textHandler != null)
{ {
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
textHandler(this, new GenericCommMethodReceiveTextArgs(str)); textHandler(this, new GenericCommMethodReceiveTextArgs(str));
} }
} }
Client.ReceiveDataAsync(Receive); Client.ReceiveDataAsync(Receive);
} }
/// <summary> /// <summary>
/// General send method /// General send method
/// </summary> /// </summary>
public void SendText(string text) public void SendText(string text)
{ {
var bytes = Encoding.GetEncoding(28591).GetBytes(text); var bytes = Encoding.GetEncoding(28591).GetBytes(text);
// Check debug level before processing byte array // Check debug level before processing byte array
if (Debug.Level == 2) if (Debug.Level == 2)
Debug.Console(2, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); Debug.Console(2, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
Client.SendData(bytes, bytes.Length); Client.SendData(bytes, bytes.Length);
} }
/// <summary> /// <summary>
/// This is useful from console and...? /// This is useful from console and...?
/// </summary> /// </summary>
public void SendEscapedText(string text) public void SendEscapedText(string text)
{ {
var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s =>
{ {
var hex = s.Groups[1].Value; var hex = s.Groups[1].Value;
return ((char)Convert.ToByte(hex, 16)).ToString(); return ((char)Convert.ToByte(hex, 16)).ToString();
}); });
SendText(unescapedText); SendText(unescapedText);
} }
public void SendBytes(byte[] bytes) public void SendBytes(byte[] bytes)
{ {
if (Debug.Level == 2) if (Debug.Level == 2)
Debug.Console(2, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); Debug.Console(2, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
Client.SendData(bytes, bytes.Length); Client.SendData(bytes, bytes.Length);
} }
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
{ {
Debug.Console(2, this, "Socket status change {0} ({1})", clientSocketStatus, UClientStatus); Debug.Console(2, this, "Socket status change {0} ({1})", clientSocketStatus, UClientStatus);
if (client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED && !DisconnectCalledByUser) if (client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED && !DisconnectCalledByUser)
WaitAndTryReconnect(); WaitAndTryReconnect();
switch (clientSocketStatus) switch (clientSocketStatus)
{ {
case SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY: case SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY:
break; break;
case SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY: case SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY:
break; break;
case SocketStatus.SOCKET_STATUS_CONNECTED: case SocketStatus.SOCKET_STATUS_CONNECTED:
Client.ReceiveDataAsync(Receive); Client.ReceiveDataAsync(Receive);
DisconnectCalledByUser = false; DisconnectCalledByUser = false;
break; break;
case SocketStatus.SOCKET_STATUS_CONNECT_FAILED: case SocketStatus.SOCKET_STATUS_CONNECT_FAILED:
break; break;
case SocketStatus.SOCKET_STATUS_DNS_FAILED: case SocketStatus.SOCKET_STATUS_DNS_FAILED:
break; break;
case SocketStatus.SOCKET_STATUS_DNS_LOOKUP: case SocketStatus.SOCKET_STATUS_DNS_LOOKUP:
break; break;
case SocketStatus.SOCKET_STATUS_DNS_RESOLVED: case SocketStatus.SOCKET_STATUS_DNS_RESOLVED:
break; break;
case SocketStatus.SOCKET_STATUS_LINK_LOST: case SocketStatus.SOCKET_STATUS_LINK_LOST:
break; break;
case SocketStatus.SOCKET_STATUS_NO_CONNECT: case SocketStatus.SOCKET_STATUS_NO_CONNECT:
break; break;
case SocketStatus.SOCKET_STATUS_SOCKET_NOT_EXIST: case SocketStatus.SOCKET_STATUS_SOCKET_NOT_EXIST:
break; break;
case SocketStatus.SOCKET_STATUS_WAITING: case SocketStatus.SOCKET_STATUS_WAITING:
break; break;
default: default:
break; break;
} }
} }
} }
public class TcpSshPropertiesConfig public class TcpSshPropertiesConfig
{ {
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public string Address { get; set; } public string Address { get; set; }
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public int Port { get; set; } public int Port { get; set; }
public string Username { get; set; } public string Username { get; set; }
public string Password { get; set; } public string Password { get; set; }
/// <summary> /// <summary>
/// Defaults to 32768 /// Defaults to 32768
/// </summary> /// </summary>
public int BufferSize { get; set; } public int BufferSize { get; set; }
/// <summary> /// <summary>
/// Defaults to true /// Defaults to true
/// </summary> /// </summary>
public bool AutoReconnect { get; set; } public bool AutoReconnect { get; set; }
/// <summary> /// <summary>
/// Defaults to 5000ms /// Defaults to 5000ms
/// </summary> /// </summary>
public int AutoReconnectIntervalMs { get; set; } public int AutoReconnectIntervalMs { get; set; }
public TcpSshPropertiesConfig() public TcpSshPropertiesConfig()
{ {
BufferSize = 32768; BufferSize = 32768;
AutoReconnect = true; AutoReconnect = true;
AutoReconnectIntervalMs = 5000; AutoReconnectIntervalMs = 5000;
} }
} }
//public class TcpIpConfig //public class TcpIpConfig
//{ //{
// [JsonProperty(Required = Required.Always)] // [JsonProperty(Required = Required.Always)]
// public string Address { get; set; } // public string Address { get; set; }
// [JsonProperty(Required = Required.Always)] // [JsonProperty(Required = Required.Always)]
// public int Port { get; set; } // public int Port { get; set; }
// /// <summary> // /// <summary>
// /// Defaults to 32768 // /// Defaults to 32768
// /// </summary> // /// </summary>
// public int BufferSize { get; set; } // public int BufferSize { get; set; }
// /// <summary> // /// <summary>
// /// Defaults to true // /// Defaults to true
// /// </summary> // /// </summary>
// public bool AutoReconnect { get; set; } // public bool AutoReconnect { get; set; }
// /// <summary> // /// <summary>
// /// Defaults to 5000ms // /// Defaults to 5000ms
// /// </summary> // /// </summary>
// public int AutoReconnectIntervalMs { get; set; } // public int AutoReconnectIntervalMs { get; set; }
// public TcpIpConfig() // public TcpIpConfig()
// { // {
// BufferSize = 32768; // BufferSize = 32768;
// AutoReconnect = true; // AutoReconnect = true;
// AutoReconnectIntervalMs = 5000; // AutoReconnectIntervalMs = 5000;
// } // }
//} //}
} }

View File

@@ -1,87 +1,97 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace PepperDash.Core namespace PepperDash.Core
{ {
/// <summary> /// <summary>
/// Represents a device that uses basic connection /// Represents a device that uses basic connection
/// </summary> /// </summary>
public interface IBasicCommunication : IKeyed public interface IBasicCommunication : IKeyed
{ {
event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived; event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived; event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
bool IsConnected { get; } bool IsConnected { get; }
void SendText(string text); void SendText(string text);
void SendBytes(byte[] bytes); void SendBytes(byte[] bytes);
void Connect(); void Connect();
} }
public interface IAutoReconnect public interface IAutoReconnect
{ {
bool AutoReconnect { get; set; } bool AutoReconnect { get; set; }
int AutoReconnectIntervalMs { get; set; } int AutoReconnectIntervalMs { get; set; }
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public enum eGenericCommMethodStatusChangeType public enum eGenericCommMethodStatusChangeType
{ {
Connected, Disconnected Connected, Disconnected
} }
/// <summary> /// <summary>
/// This delegate defines handler for IBasicCommunication status changes /// This delegate defines handler for IBasicCommunication status changes
/// </summary> /// </summary>
/// <param name="comm">Device firing the status change</param> /// <param name="comm">Device firing the status change</param>
/// <param name="status"></param> /// <param name="status"></param>
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status); public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public class GenericCommMethodReceiveBytesArgs : EventArgs public class GenericCommMethodReceiveBytesArgs : EventArgs
{ {
public byte[] Bytes { get; private set; } public byte[] Bytes { get; private set; }
public GenericCommMethodReceiveBytesArgs(byte[] bytes) public GenericCommMethodReceiveBytesArgs(byte[] bytes)
{ {
Bytes = bytes; Bytes = bytes;
} }
}
/// <summary>
/// <summary> /// Stupid S+ Constructor
/// /// </summary>
/// </summary> public GenericCommMethodReceiveBytesArgs() { }
public class GenericCommMethodReceiveTextArgs : EventArgs }
{
public string Text { get; private set; } /// <summary>
public GenericCommMethodReceiveTextArgs(string text) ///
{ /// </summary>
Text = text; public class GenericCommMethodReceiveTextArgs : EventArgs
} {
} public string Text { get; private set; }
public GenericCommMethodReceiveTextArgs(string text)
/// <summary> {
/// Text = text;
/// </summary> }
public class ComTextHelper
{ /// <summary>
public static string GetEscapedText(byte[] bytes) /// Stupid S+ Constructor
{ /// </summary>
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); public GenericCommMethodReceiveTextArgs() { }
} }
public static string GetEscapedText(string text) /// <summary>
{ ///
var bytes = Encoding.GetEncoding(28591).GetBytes(text); /// </summary>
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); 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());
}
}
} }

View File

@@ -85,7 +85,7 @@
<Programmer /> <Programmer />
<ArchiveFilename>C:\Users\hvolm\Desktop\working\pepperdash-simplsharp-core\Pepperdash Core\Pepperdash Core\bin\PepperDash_Core.clz</ArchiveFilename> <ArchiveFilename>C:\Users\hvolm\Desktop\working\pepperdash-simplsharp-core\Pepperdash Core\Pepperdash Core\bin\PepperDash_Core.clz</ArchiveFilename>
<MinFirmwareVersion>1.007.0017</MinFirmwareVersion> <MinFirmwareVersion>1.007.0017</MinFirmwareVersion>
<CompiledOn>9/20/2016 2:09:33 PM</CompiledOn> <CompiledOn>9/22/2016 9:42:40 PM</CompiledOn>
<AdditionalInfo /> <AdditionalInfo />
<EmbedSourceArchive>False</EmbedSourceArchive> <EmbedSourceArchive>False</EmbedSourceArchive>
<CopyTo /> <CopyTo />

View File

@@ -10,7 +10,7 @@
<ArchiveName /> <ArchiveName />
</RequiredInfo> </RequiredInfo>
<OptionalInfo> <OptionalInfo>
<CompiledOn>9/20/2016 2:09:33 PM</CompiledOn> <CompiledOn>9/22/2016 9:42:40 PM</CompiledOn>
<CompilerRev>1.0.0.23686</CompilerRev> <CompilerRev>1.0.0.37279</CompilerRev>
</OptionalInfo> </OptionalInfo>
</ProgramInfo> </ProgramInfo>

View File

@@ -1,4 +1,4 @@
MainAssembly=PepperDash_Core.dll:d21b9348a9ff127c20006e8d8b9a166c MainAssembly=PepperDash_Core.dll:022f5296f4c24102c3cb64cd3a10fa41
MainAssemblyMinFirmwareVersion=1.007.0017 MainAssemblyMinFirmwareVersion=1.007.0017
ü ü
DependencySource=Newtonsoft.Json.Compact.dll:ea996aa2ec65aa1878e7c9d09e37a896 DependencySource=Newtonsoft.Json.Compact.dll:ea996aa2ec65aa1878e7c9d09e37a896