diff --git a/Pepperdash Core/Pepperdash Core/Comm/._GenericSshClient.cs b/Pepperdash Core/Pepperdash Core/Comm/._GenericSshClient.cs
new file mode 100644
index 0000000..583c583
Binary files /dev/null and b/Pepperdash Core/Pepperdash Core/Comm/._GenericSshClient.cs differ
diff --git a/Pepperdash Core/Pepperdash Core/Comm/._GenericTcpIpClient.cs b/Pepperdash Core/Pepperdash Core/Comm/._GenericTcpIpClient.cs
new file mode 100644
index 0000000..b65487e
Binary files /dev/null and b/Pepperdash Core/Pepperdash Core/Comm/._GenericTcpIpClient.cs differ
diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs
index 742ea03..f9a6ef6 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs
@@ -65,12 +65,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
///
@@ -135,10 +132,10 @@ namespace PepperDash.Core
CTimer ReconnectTimer;
- //string PreviousHostname;
- //int PreviousPort;
- //string PreviousUsername;
- //string PreviousPassword;
+ //Lock object to prevent simulatneous connect/disconnect operations
+ private CCriticalSection connectLock = new CCriticalSection();
+
+ private bool DisconnectLogged = false;
///
/// Typical constructor.
@@ -154,6 +151,14 @@ namespace PepperDash.Core
Username = username;
Password = password;
AutoReconnectIntervalMs = 5000;
+
+ ReconnectTimer = new CTimer(o =>
+ {
+ if (ConnectEnabled)
+ {
+ Connect();
+ }
+ }, Timeout.Infinite);
}
///
@@ -162,9 +167,16 @@ namespace PepperDash.Core
public GenericSshClient()
: base(SPlusKey)
{
- StreamDebugging = new CommunicationStreamDebugging(SPlusKey);
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
+
+ ReconnectTimer = new CTimer(o =>
+ {
+ if (ConnectEnabled)
+ {
+ Connect();
+ }
+ }, Timeout.Infinite);
}
///
@@ -195,104 +207,106 @@ 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.
- 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(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)
- {
- Debug.Console(1, this, "Cleaning up disconnected client");
- Client.ErrorOccurred -= Client_ErrorOccurred;
- KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
- }
+ ConnectEnabled = true;
- // 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)
+ connectLock.Enter();
+ if (IsConnected)
{
- Debug.Console(1, this, errorLogLevel, "Authentication failure for username '{0}', ({1})",
- Username, ie.Message);
+ Debug.Console(1, this, "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();
+ // Cancel reconnect if running.
+ ReconnectTimer.Stop();
+
+ // 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})", ie.Message);
+
+ 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.Message);
+ 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)
+ finally
{
- Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Unhandled exception on connect:\r({0})", e);
- ClientStatus = SocketStatus.SOCKET_STATUS_CONNECT_FAILED;
- HandleConnectionFailure();
+ connectLock.Leave();
}
-
- ClientStatus = SocketStatus.SOCKET_STATUS_CONNECT_FAILED;
- HandleConnectionFailure();
}
-
-
///
/// Disconnect the clients and put away it's resources.
///
@@ -317,8 +331,7 @@ namespace PepperDash.Core
KillStream();
if (Client != null)
- {
- IsConnecting = false;
+ {
Client.Disconnect();
Client = null;
ClientStatus = status;
@@ -365,6 +378,7 @@ namespace PepperDash.Core
TheStream.Close();
TheStream.Dispose();
TheStream = null;
+ Debug.Console(1, this, "Disconnected stream");
}
}
@@ -415,13 +429,28 @@ 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();
+ 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);
+ ReconnectTimer.Reset(AutoReconnectIntervalMs);
+ }
+ });
}
///
@@ -463,8 +492,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();
}
}
@@ -492,8 +519,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 16013a3..8cfaad2 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs
@@ -5,9 +5,7 @@ using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
-
using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
namespace PepperDash.Core
{
@@ -39,23 +37,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 +82,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 +101,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 +121,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 +137,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 +149,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,10 +165,14 @@ namespace PepperDash.Core
///
public bool Connected
{
- get { return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
+ get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
}
- CTimer RetryTimer;
+ //Lock object to prevent simulatneous connect/disconnect operations
+ private CCriticalSection connectLock = new CCriticalSection();
+
+ // private Timer for auto reconnect
+ private CTimer RetryTimer;
///
/// Constructor
@@ -183,13 +185,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;
- CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
- }
+ RetryTimer = new CTimer(o =>
+ {
+ Reconnect();
+ }, Timeout.Infinite);
+ }
///
/// Constructor
@@ -202,6 +208,11 @@ namespace PepperDash.Core
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
BufferSize = 2000;
+
+ RetryTimer = new CTimer(o =>
+ {
+ Reconnect();
+ }, Timeout.Infinite);
}
///
@@ -210,10 +221,14 @@ namespace PepperDash.Core
public GenericTcpIpClient()
: base(SplusKey)
{
- StreamDebugging = new CommunicationStreamDebugging(SplusKey);
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
BufferSize = 2000;
+
+ RetryTimer = new CTimer(o =>
+ {
+ Reconnect();
+ }, Timeout.Infinite);
}
///
@@ -232,7 +247,7 @@ namespace PepperDash.Core
if (programEventType == eProgramStatusEventType.Stopping)
{
Debug.Console(1, this, "Program stopping. Closing connection");
- Disconnect();
+ Deactivate();
}
}
@@ -242,9 +257,11 @@ namespace PepperDash.Core
///
public override bool Deactivate()
{
- if (Client != null)
+ RetryTimer.Stop();
+ RetryTimer.Dispose();
+ if (_client != null)
{
- Client.SocketStatusChange -= this.Client_SocketStatusChange;
+ _client.SocketStatusChange -= this.Client_SocketStatusChange;
DisconnectClient();
}
return true;
@@ -255,9 +272,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);
@@ -271,36 +285,72 @@ namespace PepperDash.Core
}
}
- if (Client == null)
+ try
{
- Client = new TCPClient(Hostname, Port, BufferSize);
- Client.SocketStatusChange -= Client_SocketStatusChange;
- Client.SocketStatusChange += Client_SocketStatusChange;
+ connectLock.Enter();
+ if (IsConnected)
+ {
+ Debug.Console(1, this, "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);
+ }
+ }
+ finally
+ {
+ connectLock.Leave();
}
- DisconnectCalledByUser = false;
-
- Client.ConnectToServerAsync(ConnectToServerCallback); // (null);
}
+ private void Reconnect()
+ {
+ if (_client == null)
+ {
+ return;
+ }
+ try
+ {
+ connectLock.Enter();
+ if (IsConnected || DisconnectCalledByUser == true)
+ {
+ Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()");
+ }
+ else
+ {
+ Debug.Console(1, this, "Attempting reconnect now");
+ _client.ConnectToServerAsync(ConnectToServerCallback);
+ }
+ }
+ finally
+ {
+ connectLock.Leave();
+ }
+ }
+
///
/// Attempts to disconnect the client
///
public void Disconnect()
{
- DisconnectCalledByUser = true;
-
- // Stop trying reconnects, if we are
- if (RetryTimer != null)
+ try
{
+ connectLock.Enter();
+ DisconnectCalledByUser = true;
+
+ // Stop trying reconnects, if we are
RetryTimer.Stop();
- RetryTimer = null;
- }
-
- if (Client != null)
- {
DisconnectClient();
- Client = null;
- Debug.Console(1, this, "Disconnected");
+ }
+ finally
+ {
+ connectLock.Leave();
}
}
@@ -309,11 +359,11 @@ namespace PepperDash.Core
///
public void DisconnectClient()
{
- if (Client != null)
+ if (_client != null)
{
Debug.Console(1, this, "Disconnecting client");
- if(IsConnected)
- Client.DisconnectFromServer();
+ if (IsConnected)
+ _client.DisconnectFromServer();
}
}
@@ -323,9 +373,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 && AutoReconnect)
- 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);
+ }
}
///
@@ -333,24 +389,23 @@ 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 = new CTimer(o =>
+ try
+ {
+ connectLock.Enter();
+ if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null)
{
- if (Client == null)
- {
- return;
- }
-
- Client.ConnectToServerAsync(ConnectToServerCallback);
- }, AutoReconnectIntervalMs);
- }
-
+ DisconnectClient();
+ Debug.Console(1, this, "Attempting reconnect, status={0}", _client.ClientStatus);
+ RetryTimer.Reset(AutoReconnectIntervalMs);
+ }
+ }
+ finally
+ {
+ connectLock.Leave();
+ }
+ });
}
///
@@ -380,15 +435,13 @@ 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));
-
- }
-
-
+ }
}
-
client.ReceiveDataAsync(Receive);
}
}
@@ -402,10 +455,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);
}
///
@@ -429,8 +480,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);
}
///
@@ -440,27 +491,20 @@ 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)
- 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)
+ {
+ 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);
+ }
var handler = ConnectionChange;
if (handler != null)
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
-
- // Relay the event
- //var handler = SocketStatusChange;
- //if (handler != null)
- // SocketStatusChange(this);
}
}
@@ -519,4 +563,4 @@ namespace PepperDash.Core
}
-}
\ No newline at end of file
+}
diff --git a/Pepperdash Core/Pepperdash Core/Logging/Debug.cs b/Pepperdash Core/Pepperdash Core/Logging/Debug.cs
index 23af35c..1a84968 100644
--- a/Pepperdash Core/Pepperdash Core/Logging/Debug.cs
+++ b/Pepperdash Core/Pepperdash Core/Logging/Debug.cs
@@ -572,6 +572,7 @@ namespace PepperDash.Core
String.Format(
@"Debug settings file migration not necessary. Using file at \user\debugSettings\program{0}",
InitialParametersClass.ApplicationNumber));
+
return;
}
diff --git a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj
index 86ae71d..ec00542 100644
--- a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj
+++ b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj
@@ -7,7 +7,7 @@
{87E29B4C-569B-4368-A4ED-984AC1440C96}
Library
Properties
- PepperDash_Core
+ PepperDash.Core
PepperDash_Core
{0B4745B0-194B-4BB6-8E21-E9057CA92500};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
WindowsCE
@@ -51,6 +51,10 @@
False
..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpCustomAttributesInterface.dll
+
+ False
+ ..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpCWSHelperInterface.dll
+
False
..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpHelperInterface.dll
@@ -89,6 +93,9 @@
+
+
+
diff --git a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj.DotSettings b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj.DotSettings
new file mode 100644
index 0000000..8e644b9
--- /dev/null
+++ b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj.DotSettings
@@ -0,0 +1,3 @@
+
+ True
+ False
\ No newline at end of file
diff --git a/Pepperdash Core/Pepperdash Core/Web/RequestHandlers/DefaultRequestHandler.cs b/Pepperdash Core/Pepperdash Core/Web/RequestHandlers/DefaultRequestHandler.cs
new file mode 100644
index 0000000..93cfae0
--- /dev/null
+++ b/Pepperdash Core/Pepperdash Core/Web/RequestHandlers/DefaultRequestHandler.cs
@@ -0,0 +1,17 @@
+using Crestron.SimplSharp.WebScripting;
+
+namespace PepperDash.Core.Web.RequestHandlers
+{
+ ///
+ /// Web API default request handler
+ ///
+ public class DefaultRequestHandler : WebApiBaseRequestHandler
+ {
+ ///
+ /// Constructor
+ ///
+ public DefaultRequestHandler()
+ : base(true)
+ { }
+ }
+}
\ No newline at end of file
diff --git a/Pepperdash Core/Pepperdash Core/Web/RequestHandlers/WebApiBaseRequestHandler.cs b/Pepperdash Core/Pepperdash Core/Web/RequestHandlers/WebApiBaseRequestHandler.cs
new file mode 100644
index 0000000..a73abd1
--- /dev/null
+++ b/Pepperdash Core/Pepperdash Core/Web/RequestHandlers/WebApiBaseRequestHandler.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Collections.Generic;
+using Crestron.SimplSharp.WebScripting;
+
+namespace PepperDash.Core.Web.RequestHandlers
+{
+ ///
+ /// CWS Base Handler, implements IHttpCwsHandler
+ ///
+ public abstract class WebApiBaseRequestHandler : IHttpCwsHandler
+ {
+ private readonly Dictionary> _handlers;
+ protected readonly bool EnableCors;
+
+ ///
+ /// Constructor
+ ///
+ protected WebApiBaseRequestHandler(bool enableCors)
+ {
+ EnableCors = enableCors;
+
+ _handlers = new Dictionary>
+ {
+ {"CONNECT", HandleConnect},
+ {"DELETE", HandleDelete},
+ {"GET", HandleGet},
+ {"HEAD", HandleHead},
+ {"OPTIONS", HandleOptions},
+ {"PATCH", HandlePatch},
+ {"POST", HandlePost},
+ {"PUT", HandlePut},
+ {"TRACE", HandleTrace}
+ };
+ }
+
+ ///
+ /// Constructor
+ ///
+ protected WebApiBaseRequestHandler()
+ : this(false)
+ {
+ }
+
+ ///
+ /// Handles CONNECT method requests
+ ///
+ ///
+ protected virtual void HandleConnect(HttpCwsContext context)
+ {
+ context.Response.StatusCode = 501;
+ context.Response.StatusDescription = "Not Implemented";
+ context.Response.End();
+ }
+
+ ///
+ /// Handles DELETE method requests
+ ///
+ ///
+ protected virtual void HandleDelete(HttpCwsContext context)
+ {
+ context.Response.StatusCode = 501;
+ context.Response.StatusDescription = "Not Implemented";
+ context.Response.End();
+ }
+
+ ///
+ /// Handles GET method requests
+ ///
+ ///
+ protected virtual void HandleGet(HttpCwsContext context)
+ {
+ context.Response.StatusCode = 501;
+ context.Response.StatusDescription = "Not Implemented";
+ context.Response.End();
+ }
+
+ ///
+ /// Handles HEAD method requests
+ ///
+ ///
+ protected virtual void HandleHead(HttpCwsContext context)
+ {
+ context.Response.StatusCode = 501;
+ context.Response.StatusDescription = "Not Implemented";
+ context.Response.End();
+ }
+
+ ///
+ /// Handles OPTIONS method requests
+ ///
+ ///
+ protected virtual void HandleOptions(HttpCwsContext context)
+ {
+ context.Response.StatusCode = 501;
+ context.Response.StatusDescription = "Not Implemented";
+ context.Response.End();
+ }
+
+ ///
+ /// Handles PATCH method requests
+ ///
+ ///
+ protected virtual void HandlePatch(HttpCwsContext context)
+ {
+ context.Response.StatusCode = 501;
+ context.Response.StatusDescription = "Not Implemented";
+ context.Response.End();
+ }
+
+ ///
+ /// Handles POST method requests
+ ///
+ ///
+ protected virtual void HandlePost(HttpCwsContext context)
+ {
+ context.Response.StatusCode = 501;
+ context.Response.StatusDescription = "Not Implemented";
+ context.Response.End();
+ }
+
+ ///
+ /// Handles PUT method requests
+ ///
+ ///
+ protected virtual void HandlePut(HttpCwsContext context)
+ {
+ context.Response.StatusCode = 501;
+ context.Response.StatusDescription = "Not Implemented";
+ context.Response.End();
+ }
+
+ ///
+ /// Handles TRACE method requests
+ ///
+ ///
+ protected virtual void HandleTrace(HttpCwsContext context)
+ {
+ context.Response.StatusCode = 501;
+ context.Response.StatusDescription = "Not Implemented";
+ context.Response.End();
+ }
+
+ ///
+ /// Process request
+ ///
+ ///
+ public void ProcessRequest(HttpCwsContext context)
+ {
+ Action handler;
+
+ if (!_handlers.TryGetValue(context.Request.HttpMethod, out handler))
+ {
+ return;
+ }
+
+ if (EnableCors)
+ {
+ context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
+ context.Response.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
+ }
+
+ handler(context);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Pepperdash Core/Pepperdash Core/Web/WebApiServer.cs b/Pepperdash Core/Pepperdash Core/Web/WebApiServer.cs
new file mode 100644
index 0000000..f2f8464
--- /dev/null
+++ b/Pepperdash Core/Pepperdash Core/Web/WebApiServer.cs
@@ -0,0 +1,284 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Crestron.SimplSharp;
+using Crestron.SimplSharp.WebScripting;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using PepperDash.Core.Web.RequestHandlers;
+
+namespace PepperDash.Core.Web
+{
+ ///
+ /// Web API server
+ ///
+ public class WebApiServer : IKeyName
+ {
+ private const string SplusKey = "Uninitialized Web API Server";
+ private const string DefaultName = "Web API Server";
+ private const string DefaultBasePath = "/api";
+
+ private const uint DebugTrace = 0;
+ private const uint DebugInfo = 1;
+ private const uint DebugVerbose = 2;
+
+ private readonly CCriticalSection _serverLock = new CCriticalSection();
+ private HttpCwsServer _server;
+
+ ///
+ /// Web API server key
+ ///
+ public string Key { get; private set; }
+
+ ///
+ /// Web API server name
+ ///
+ public string Name { get; private set; }
+
+ ///
+ /// CWS base path, will default to "/api" if not set via initialize method
+ ///
+ public string BasePath { get; private set; }
+
+ ///
+ /// Indicates CWS is registered with base path
+ ///
+ public bool IsRegistered { get; private set; }
+
+ ///
+ /// Http request handler
+ ///
+ //public IHttpCwsHandler HttpRequestHandler
+ //{
+ // get { return _server.HttpRequestHandler; }
+ // set
+ // {
+ // if (_server == null) return;
+ // _server.HttpRequestHandler = value;
+ // }
+ //}
+
+ ///
+ /// Received request event handler
+ ///
+ //public event EventHandler ReceivedRequestEvent
+ //{
+ // add { _server.ReceivedRequestEvent += new HttpCwsRequestEventHandler(value); }
+ // remove { _server.ReceivedRequestEvent -= new HttpCwsRequestEventHandler(value); }
+ //}
+
+ ///
+ /// Constructor for S+. Make sure to set necessary properties using init method
+ ///
+ public WebApiServer()
+ : this(SplusKey, DefaultName, null)
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ ///
+ public WebApiServer(string key, string basePath)
+ : this(key, DefaultName, basePath)
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ ///
+ ///
+ public WebApiServer(string key, string name, string basePath)
+ {
+ Key = key;
+ Name = string.IsNullOrEmpty(name) ? DefaultName : name;
+ BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
+
+ if (_server == null) _server = new HttpCwsServer(BasePath);
+
+ _server.setProcessName(Key);
+ _server.HttpRequestHandler = new DefaultRequestHandler();
+
+ CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
+ CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler;
+ }
+
+ ///
+ /// Program status event handler
+ ///
+ ///
+ void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
+ {
+ if (programEventType != eProgramStatusEventType.Stopping) return;
+
+ Debug.Console(DebugInfo, this, "Program stopping. stopping server");
+
+ Stop();
+ }
+
+ ///
+ /// Ethernet event handler
+ ///
+ ///
+ void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
+ {
+ // Re-enable the server if the link comes back up and the status should be connected
+ if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered)
+ {
+ Debug.Console(DebugInfo, this, "Ethernet link up. Server is alreedy registered.");
+ return;
+ }
+
+ Debug.Console(DebugInfo, this, "Ethernet link up. Starting server");
+
+ Start();
+ }
+
+ ///
+ /// Initializes CWS class
+ ///
+ public void Initialize(string key, string basePath)
+ {
+ Key = key;
+ BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
+ }
+
+ ///
+ /// Adds a route to CWS
+ ///
+ public void AddRoute(HttpCwsRoute route)
+ {
+ if (route == null)
+ {
+ Debug.Console(DebugInfo, this, "Failed to add route, route parameter is null");
+ return;
+ }
+
+ _server.Routes.Add(route);
+
+ }
+
+ ///
+ /// Removes a route from CWS
+ ///
+ ///
+ public void RemoveRoute(HttpCwsRoute route)
+ {
+ if (route == null)
+ {
+ Debug.Console(DebugInfo, this, "Failed to remote route, orute parameter is null");
+ return;
+ }
+
+ _server.Routes.Remove(route);
+ }
+
+ ///
+ /// Returns a list of the current routes
+ ///
+ public HttpCwsRouteCollection GetRouteCollection()
+ {
+ return _server.Routes;
+ }
+
+ ///
+ /// Starts CWS instance
+ ///
+ public void Start()
+ {
+ try
+ {
+ _serverLock.Enter();
+
+ if (_server == null)
+ {
+ Debug.Console(DebugInfo, this, "Server is null, unable to start");
+ return;
+ }
+
+ if (IsRegistered)
+ {
+ Debug.Console(DebugInfo, this, "Server has already been started");
+ return;
+ }
+
+ IsRegistered = _server.Register();
+
+ Debug.Console(DebugInfo, this, "Starting server, registration {0}", IsRegistered ? "was successful" : "failed");
+ }
+ catch (Exception ex)
+ {
+ Debug.Console(DebugInfo, this, "Start Exception Message: {0}", ex.Message);
+ Debug.Console(DebugVerbose, this, "Start Exception StackTrace: {0}", ex.StackTrace);
+ if (ex.InnerException != null)
+ Debug.Console(DebugVerbose, this, "Start Exception InnerException: {0}", ex.InnerException);
+ }
+ finally
+ {
+ _serverLock.Leave();
+ }
+ }
+
+ ///
+ /// Stop CWS instance
+ ///
+ public void Stop()
+ {
+ try
+ {
+ _serverLock.Enter();
+
+ if (_server == null)
+ {
+ Debug.Console(DebugInfo, this, "Server is null or has already been stopped");
+ return;
+ }
+
+ IsRegistered = _server.Unregister() == false;
+
+ Debug.Console(DebugInfo, this, "Stopping server, unregistration {0}", IsRegistered ? "failed" : "was successful");
+
+ _server.Dispose();
+ _server = null;
+ }
+ catch (Exception ex)
+ {
+ Debug.Console(DebugInfo, this, "Server Stop Exception Message: {0}", ex.Message);
+ Debug.Console(DebugVerbose, this, "Server Stop Exception StackTrace: {0}", ex.StackTrace);
+ if (ex.InnerException != null)
+ Debug.Console(DebugVerbose, this, "Server Stop Exception InnerException: {0}", ex.InnerException);
+ }
+ finally
+ {
+ _serverLock.Leave();
+ }
+ }
+
+ ///
+ /// Received request handler
+ ///
+ ///
+ /// This is here for development and testing
+ ///
+ ///
+ ///
+ public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args)
+ {
+ try
+ {
+ var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
+ Debug.Console(DebugVerbose, this, "RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
+ }
+ catch (Exception ex)
+ {
+ Debug.Console(DebugInfo, this, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message);
+ Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace);
+ if (ex.InnerException != null)
+ Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception InnerException: {0}", ex.InnerException);
+ }
+ }
+ }
+}
\ No newline at end of file