Merged in bugfix/PDC-18_AddS+ConstructorToEventArgs (pull request #24)

PDC-18_AddS+ConstructorToEventArgs

Approved-by: Neil Dorin <ndorin@pepperdash.com>
This commit is contained in:
Jason Alborough
2019-08-30 18:38:33 +00:00
committed by Neil Dorin
26 changed files with 1503 additions and 840 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -23,12 +23,14 @@ namespace PepperDash.Core
{ {
public ISocketStatus Client { get; private set; } public ISocketStatus Client { get; private set; }
public GenericSocketStatusChageEventArgs() { }
public GenericSocketStatusChageEventArgs(ISocketStatus client) public GenericSocketStatusChageEventArgs(ISocketStatus client)
{ {
Client = client; Client = client;
} }
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericSocketStatusChageEventArgs() { }
} }
public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state); public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state);
@@ -36,12 +38,14 @@ namespace PepperDash.Core
{ {
public ServerState State { get; private set; } public ServerState State { get; private set; }
public GenericTcpServerStateChangedEventArgs() { }
public GenericTcpServerStateChangedEventArgs(ServerState state) public GenericTcpServerStateChangedEventArgs(ServerState state)
{ {
State = state; State = state;
} }
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericTcpServerStateChangedEventArgs() { }
} }
public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus); public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus);
@@ -50,8 +54,6 @@ namespace PepperDash.Core
public object Socket { get; private set; } public object Socket { get; private set; }
public uint ReceivedFromClientIndex { get; private set; } public uint ReceivedFromClientIndex { get; private set; }
public SocketStatus ClientStatus { get; set; } public SocketStatus ClientStatus { get; set; }
public GenericTcpServerSocketStatusChangeEventArgs() { }
public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus) public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
{ {
@@ -65,6 +67,10 @@ namespace PepperDash.Core
ReceivedFromClientIndex = clientIndex; ReceivedFromClientIndex = clientIndex;
ClientStatus = clientStatus; ClientStatus = clientStatus;
} }
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericTcpServerSocketStatusChangeEventArgs() { }
} }
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
@@ -82,6 +88,10 @@ namespace PepperDash.Core
Text = text; Text = text;
ReceivedFromClientIndex = clientIndex; ReceivedFromClientIndex = clientIndex;
} }
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericTcpServerCommMethodReceiveTextArgs() { }
} }
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
@@ -91,6 +101,29 @@ namespace PepperDash.Core
{ {
IsReady = isReady; IsReady = isReady;
} }
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericTcpServerClientReadyForcommunicationsEventArgs() { }
}
public class GenericUdpConnectedEventArgs : EventArgs
{
public ushort UConnected;
public bool Connected;
public GenericUdpConnectedEventArgs() { }
public GenericUdpConnectedEventArgs(ushort uconnected)
{
UConnected = uconnected;
}
public GenericUdpConnectedEventArgs(bool connected)
{
Connected = connected;
}
} }

View File

@@ -33,6 +33,15 @@ namespace PepperDash.Core
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived; public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived;
public event EventHandler AutoReconnectTriggered;
/// <summary>
/// Event for Receiving text. Once subscribed to this event the receive callback will start a thread that dequeues the messages and invokes the event on a new thread.
/// It is not recommended to use both the TextReceived event and the TextReceivedQueueInvoke event.
/// </summary>
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceivedQueueInvoke;
public event EventHandler<GenericTcpServerSocketStatusChangeEventArgs> ConnectionChange; public event EventHandler<GenericTcpServerSocketStatusChangeEventArgs> ConnectionChange;
@@ -209,8 +218,20 @@ namespace PepperDash.Core
get { return (ushort)(HeartbeatEnabled ? 1 : 0); } get { return (ushort)(HeartbeatEnabled ? 1 : 0); }
set { HeartbeatEnabled = value == 1; } set { HeartbeatEnabled = value == 1; }
} }
public string HeartbeatString = "heartbeat";
public int HeartbeatInterval = 50000; public string HeartbeatString { get; set; }
//public int HeartbeatInterval = 50000;
/// <summary>
/// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
/// </summary>
public int HeartbeatInterval { get; set; }
/// <summary>
/// Simpl+ Heartbeat Analog value in seconds
/// </summary>
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
CTimer HeartbeatSendTimer; CTimer HeartbeatSendTimer;
CTimer HeartbeatAckTimer; CTimer HeartbeatAckTimer;
/// <summary> /// <summary>
@@ -226,6 +247,24 @@ namespace PepperDash.Core
bool ProgramIsStopping; bool ProgramIsStopping;
/// <summary>
/// Queue lock
/// </summary>
CCriticalSection DequeueLock = new CCriticalSection();
/// <summary>
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
/// calling initialize.
/// </summary>
public int ReceiveQueueSize { get; set; }
/// <summary>
/// Queue to temporarily store received messages with the source IP and Port info. Defaults to size 20. Use constructor or set queue size property before
/// calling initialize.
/// </summary>
private CrestronQueue<GenericTcpServerCommMethodReceiveTextArgs> MessageQueue;
#endregion #endregion
#region Constructors #region Constructors
@@ -244,12 +283,24 @@ namespace PepperDash.Core
//base class constructor //base class constructor
public GenericSecureTcpIpClient_ForServer() public GenericSecureTcpIpClient_ForServer()
: base("Uninitialized DynamicTcpClient") : base("Uninitialized Secure Tcp Client For Server")
{ {
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000; AutoReconnectIntervalMs = 5000;
BufferSize = 2000; BufferSize = 2000;
} }
/// <summary>
/// Contstructor that sets all properties by calling the initialize method with a config object.
/// </summary>
/// <param name="serverConfigObject"></param>
public GenericSecureTcpIpClient_ForServer(string key, TcpClientConfigObject clientConfigObject)
: base(key)
{
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
Initialize(clientConfigObject);
}
#endregion #endregion
#region Methods #region Methods
@@ -262,6 +313,43 @@ namespace PepperDash.Core
Key = key; Key = key;
} }
/// <summary>
/// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client.
/// </summary>
/// <param name="clientConfigObject"></param>
public void Initialize(TcpClientConfigObject clientConfigObject)
{
try
{
if (clientConfigObject != null)
{
var TcpSshProperties = clientConfigObject.Control.TcpSshProperties;
Hostname = TcpSshProperties.Address;
AutoReconnect = TcpSshProperties.AutoReconnect;
AutoReconnectIntervalMs = TcpSshProperties.AutoReconnectIntervalMs > 1000 ?
TcpSshProperties.AutoReconnectIntervalMs : 5000;
SharedKey = clientConfigObject.SharedKey;
SharedKeyRequired = clientConfigObject.SharedKeyRequired;
HeartbeatEnabled = clientConfigObject.HeartbeatRequired;
HeartbeatRequiredIntervalInSeconds = clientConfigObject.HeartbeatRequiredIntervalInSeconds > 0 ?
clientConfigObject.HeartbeatRequiredIntervalInSeconds : (ushort)15;
HeartbeatString = string.IsNullOrEmpty(clientConfigObject.HeartbeatStringToMatch) ? "heartbeat" : clientConfigObject.HeartbeatStringToMatch;
Port = TcpSshProperties.Port;
BufferSize = TcpSshProperties.BufferSize > 2000 ? TcpSshProperties.BufferSize : 2000;
ReceiveQueueSize = clientConfigObject.ReceiveQueueSize > 20 ? clientConfigObject.ReceiveQueueSize : 20;
MessageQueue = new CrestronQueue<GenericTcpServerCommMethodReceiveTextArgs>(ReceiveQueueSize);
}
else
{
ErrorLog.Error("Could not initialize client with key: {0}", Key);
}
}
catch
{
ErrorLog.Error("Could not initialize client with key: {0}", Key);
}
}
/// <summary> /// <summary>
/// Handles closing this up when the program shuts down /// Handles closing this up when the program shuts down
/// </summary> /// </summary>
@@ -328,7 +416,8 @@ namespace PepperDash.Core
Client = new SecureTCPClient(Hostname, Port, BufferSize); Client = new SecureTCPClient(Hostname, Port, BufferSize);
Client.SocketStatusChange += Client_SocketStatusChange; Client.SocketStatusChange += Client_SocketStatusChange;
Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); if (HeartbeatEnabled)
Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5);
Client.AddressClientConnectedTo = Hostname; Client.AddressClientConnectedTo = Hostname;
Client.PortNumber = Port; Client.PortNumber = Port;
// SecureClient = c; // SecureClient = c;
@@ -381,6 +470,15 @@ namespace PepperDash.Core
//OnClientReadyForcommunications(false); // Should send false event //OnClientReadyForcommunications(false); // Should send false event
}, 15000); }, 15000);
} }
else
{
//CLient connected and shared key is not required so just raise the ready for communication event. if Shared key
//required this is called by the shared key being negotiated
if (IsReadyForCommunication == false)
{
OnClientReadyForcommunications(true); // Key not required
}
}
} }
else else
{ {
@@ -463,6 +561,8 @@ namespace PepperDash.Core
RetryTimer.Stop(); RetryTimer.Stop();
RetryTimer = null; RetryTimer = null;
} }
if(AutoReconnectTriggered != null)
AutoReconnectTriggered(this, new EventArgs());
RetryTimer = new CTimer(o => Connect(), rndTime); RetryTimer = new CTimer(o => Connect(), rndTime);
} }
} }
@@ -477,7 +577,7 @@ namespace PepperDash.Core
if (numBytes > 0) if (numBytes > 0)
{ {
string str = string.Empty; string str = string.Empty;
var handler = TextReceivedQueueInvoke;
try try
{ {
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
@@ -499,20 +599,18 @@ namespace PepperDash.Core
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Shared key confirmed. Ready for communication"); Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Shared key confirmed. Ready for communication");
OnClientReadyForcommunications(true); // Successful key exchange OnClientReadyForcommunications(true); // Successful key exchange
} }
else if (SharedKeyRequired == false && IsReadyForCommunication == false)
{
OnClientReadyForcommunications(true); // Key not required
}
else else
{ {
//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)
textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str)); textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str));
if (handler != null)
{
MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(str));
}
} }
} }
} }
@@ -520,9 +618,51 @@ namespace PepperDash.Core
{ {
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str); Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
} }
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
client.ReceiveDataAsync(Receive);
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
if (handler != null)
{
var gotLock = DequeueLock.TryEnter();
if (gotLock)
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
}
}
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
{
client.DisconnectFromServer();
}
}
/// <summary>
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
/// It will dequeue items as they are enqueued automatically.
/// </summary>
void DequeueEvent()
{
try
{
while (true)
{
// Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather.
var message = MessageQueue.Dequeue();
var handler = TextReceivedQueueInvoke;
if (handler != null)
{
handler(this, message);
}
}
}
catch (Exception e)
{
Debug.Console(0, "DequeueEvent error: {0}\r", e);
}
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
if (DequeueLock != null)
{
DequeueLock.Leave();
} }
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
client.ReceiveDataAsync(Receive);
} }
void HeartbeatStart() void HeartbeatStart()

View File

@@ -28,6 +28,12 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived; public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived;
/// <summary>
/// Event for Receiving text. Once subscribed to this event the receive callback will start a thread that dequeues the messages and invokes the event on a new thread.
/// It is not recommended to use both the TextReceived event and the TextReceivedQueueInvoke event.
/// </summary>
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceivedQueueInvoke;
/// <summary> /// <summary>
/// Event for client connection socket status change /// Event for client connection socket status change
/// </summary> /// </summary>
@@ -56,10 +62,26 @@ namespace PepperDash.Core
#region Properties/Variables #region Properties/Variables
/// <summary> /// <summary>
/// /// Server listen lock
/// </summary> /// </summary>
CCriticalSection ServerCCSection = new CCriticalSection(); CCriticalSection ServerCCSection = new CCriticalSection();
/// <summary>
/// Queue lock
/// </summary>
CCriticalSection DequeueLock = new CCriticalSection();
/// <summary>
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
/// calling initialize.
/// </summary>
public int ReceiveQueueSize { get; set; }
/// <summary>
/// Queue to temporarily store received messages with the source IP and Port info. Defaults to size 20. Use constructor or set queue size property before
/// calling initialize.
/// </summary>
private CrestronQueue<GenericTcpServerCommMethodReceiveTextArgs> MessageQueue;
/// <summary> /// <summary>
/// A bandaid client that monitors whether the server is reachable /// A bandaid client that monitors whether the server is reachable
@@ -145,7 +167,9 @@ namespace PepperDash.Core
{ {
get { return (ushort)(IsListening ? 1 : 0); } get { return (ushort)(IsListening ? 1 : 0); }
} }
/// <summary>
/// Max number of clients this server will allow for connection. Crestron max is 64. This number should be less than 65
/// </summary>
public ushort MaxClients { get; set; } // should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable public ushort MaxClients { get; set; } // should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable
/// <summary> /// <summary>
/// Number of clients currently connected. /// Number of clients currently connected.
@@ -269,7 +293,7 @@ namespace PepperDash.Core
/// constructor S+ Does not accept a key. Use initialze with key to set the debug key on this device. If using with + make sure to set all properties manually. /// constructor S+ Does not accept a key. Use initialze with key to set the debug key on this device. If using with + make sure to set all properties manually.
/// </summary> /// </summary>
public GenericSecureTcpIpServer() public GenericSecureTcpIpServer()
: base("Uninitialized Dynamic TCP Server") : base("Uninitialized Secure TCP Server")
{ {
HeartbeatRequiredIntervalInSeconds = 15; HeartbeatRequiredIntervalInSeconds = 15;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
@@ -282,7 +306,7 @@ namespace PepperDash.Core
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
public GenericSecureTcpIpServer(string key) public GenericSecureTcpIpServer(string key)
: base("Uninitialized Dynamic TCP Server") : base("Uninitialized Secure TCP Server")
{ {
HeartbeatRequiredIntervalInSeconds = 15; HeartbeatRequiredIntervalInSeconds = 15;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
@@ -292,11 +316,11 @@ namespace PepperDash.Core
} }
/// <summary> /// <summary>
/// Contstructor that sets all properties by calling the initialize method with a config object. /// Contstructor that sets all properties by calling the initialize method with a config object. This does set Queue size.
/// </summary> /// </summary>
/// <param name="serverConfigObject"></param> /// <param name="serverConfigObject"></param>
public GenericSecureTcpIpServer(TcpServerConfigObject serverConfigObject) public GenericSecureTcpIpServer(TcpServerConfigObject serverConfigObject)
: base("Uninitialized Dynamic TCP Server") : base("Uninitialized Secure TCP Server")
{ {
HeartbeatRequiredIntervalInSeconds = 15; HeartbeatRequiredIntervalInSeconds = 15;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
@@ -345,7 +369,8 @@ namespace PepperDash.Core
HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds; HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds;
HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch; HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch;
BufferSize = serverConfigObject.BufferSize; BufferSize = serverConfigObject.BufferSize;
ReceiveQueueSize = serverConfigObject.ReceiveQueueSize > 20 ? serverConfigObject.ReceiveQueueSize : 20;
MessageQueue = new CrestronQueue<GenericTcpServerCommMethodReceiveTextArgs>(ReceiveQueueSize);
} }
else else
{ {
@@ -384,13 +409,14 @@ namespace PepperDash.Core
if (SecureServer == null) if (SecureServer == null)
{ {
SecureServer = new SecureTCPServer(Port, MaxClients); SecureServer = new SecureTCPServer(Port, MaxClients);
SecureServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5); if(HeartbeatRequired)
SecureServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5);
SecureServer.HandshakeTimeout = 30; SecureServer.HandshakeTimeout = 30;
SecureServer.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SecureServer_SocketStatusChange); SecureServer.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SecureServer_SocketStatusChange);
} }
else else
{ {
KillServer(); //KillServer(); Remove this to be able to reactivate listener if it stops itself due to max clients without disconnecting connected clients.
SecureServer.PortNumber = Port; SecureServer.PortNumber = Port;
} }
ServerStopped = false; ServerStopped = false;
@@ -415,25 +441,21 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public void StopListening() public void StopListening()
{ {
try try
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Stopping Listener"); Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
if (SecureServer != null) if (SecureServer != null)
{ {
SecureServer.Stop(); SecureServer.Stop();
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", SecureServer.State); Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", SecureServer.State);
//SecureServer = null; OnServerStateChange(SecureServer.State);
} }
ServerStopped = true;
ServerStopped = true; }
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server Stopped"); catch (Exception ex)
{
OnServerStateChange(SecureServer.State); Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
} }
catch (Exception ex)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
}
} }
/// <summary> /// <summary>
@@ -445,11 +467,11 @@ namespace PepperDash.Core
try try
{ {
SecureServer.Disconnect(client); SecureServer.Disconnect(client);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client); Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client);
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex); Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex);
} }
} }
/// <summary> /// <summary>
@@ -469,17 +491,17 @@ namespace PepperDash.Core
try try
{ {
SecureServer.Disconnect(i); SecureServer.Disconnect(i);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i); Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i);
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex); Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex);
} }
} }
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", SecureServer.ServerSocketStatus); Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", SecureServer.ServerSocketStatus);
} }
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients"); Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients");
ConnectedClientsIndexes.Clear(); ConnectedClientsIndexes.Clear();
if (!ProgramIsStopping) if (!ProgramIsStopping)
@@ -510,7 +532,7 @@ namespace PepperDash.Core
{ {
SocketErrorCodes error = SecureServer.SendDataAsync(i, b, b.Length, (x, y, z) => { }); SocketErrorCodes error = SecureServer.SendDataAsync(i, b, b.Length, (x, y, z) => { });
if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING) if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING)
Debug.Console(0, error.ToString()); Debug.Console(2, error.ToString());
} }
} }
} }
@@ -541,7 +563,7 @@ namespace PepperDash.Core
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(0, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text); Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
} }
} }
@@ -669,13 +691,18 @@ namespace PepperDash.Core
} }
if (ClientReadyAfterKeyExchange.Contains(clientIndex)) if (ClientReadyAfterKeyExchange.Contains(clientIndex))
ClientReadyAfterKeyExchange.Remove(clientIndex); ClientReadyAfterKeyExchange.Remove(clientIndex);
if (WaitingForSharedKey.Contains(clientIndex))
WaitingForSharedKey.Remove(clientIndex);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex); Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex);
} }
onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); //Use a thread for this event so that the server state updates to listening while this event is processed. Listening must be added to the server state
//after every client connection so that the server can check and see if it is at max clients. Due to this the event fires and server listening enum bit flag
//is not set. Putting in a thread allows the state to update before this event processes so that the subscribers to this event get accurate isListening in the event.
CrestronInvoke.BeginInvoke(o => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)), null);
} }
#endregion #endregion
@@ -739,7 +766,7 @@ namespace PepperDash.Core
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex); Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex);
} }
//Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))", //Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))",
// server.State, // server.State,
@@ -767,6 +794,7 @@ namespace PepperDash.Core
if (numberOfBytesReceived > 0) if (numberOfBytesReceived > 0)
{ {
string received = "Nothing"; string received = "Nothing";
var handler = TextReceivedQueueInvoke;
try try
{ {
byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex); byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
@@ -781,31 +809,74 @@ namespace PepperDash.Core
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received); Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
mySecureTCPServer.SendData(clientIndex, b, b.Length); mySecureTCPServer.SendData(clientIndex, b, b.Length);
mySecureTCPServer.Disconnect(clientIndex); mySecureTCPServer.Disconnect(clientIndex);
WaitingForSharedKey.Remove(clientIndex);
return; return;
} }
if (mySecureTCPServer.NumberOfClientsConnected > 0)
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
WaitingForSharedKey.Remove(clientIndex); WaitingForSharedKey.Remove(clientIndex);
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match"); byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null); mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
OnServerClientReadyForCommunications(clientIndex); OnServerClientReadyForCommunications(clientIndex);
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex); Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
return;
} }
//var address = mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
//Debug.Console(1, this, "Secure Server Listening on Port: {0}, client IP: {1}, Client Index: {4}, NumberOfBytesReceived: {2}, Received: {3}\r\n", {
// mySecureTCPServer.PortNumber.ToString(), address , numberOfBytesReceived.ToString(), received, clientIndex.ToString());
if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
onTextReceived(received, clientIndex); onTextReceived(received, clientIndex);
if (handler != null)
{
MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(received, clientIndex));
}
}
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex); Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
}
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
if (handler != null)
{
var gotLock = DequeueLock.TryEnter();
if (gotLock)
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
} }
} }
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) else
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback); {
mySecureTCPServer.Disconnect(clientIndex);
}
}
/// <summary>
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
/// It will dequeue items as they are enqueued automatically.
/// </summary>
void DequeueEvent()
{
try
{
while (true)
{
// Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather.
var message = MessageQueue.Dequeue();
var handler = TextReceivedQueueInvoke;
if (handler != null)
{
handler(this, message);
}
}
}
catch (Exception e)
{
Debug.Console(2, "DequeueEvent error: {0}\r", e);
}
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
if (DequeueLock != null)
{
DequeueLock.Leave();
}
} }
#endregion #endregion
@@ -965,13 +1036,13 @@ namespace PepperDash.Core
StopMonitorClient(); StopMonitorClient();
if (MonitorClientFailureCount < MonitorClientMaxFailureCount) if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}", Debug.Console(2, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}",
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount); MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
StartMonitorClient(); StartMonitorClient();
} }
else else
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Error, Debug.Console(2, this, Debug.ErrorLogLevel.Error,
"\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************", "\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************",
MonitorClientMaxFailureCount); MonitorClientMaxFailureCount);

View File

@@ -174,8 +174,8 @@ namespace PepperDash.Core
if (Client != null) if (Client != null)
{ {
Debug.Console(1, this, "Program stopping. Closing connection"); Debug.Console(1, this, "Program stopping. Closing connection");
Client.Disconnect(); Disconnect();
Client.Dispose(); //Client.Dispose();
} }
} }
} }

View File

@@ -29,10 +29,27 @@ namespace PepperDash.Core
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange; //public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange; public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
private string _Hostname { get; set;}
/// <summary> /// <summary>
/// Address of server /// Address of server
/// </summary> /// </summary>
public string Hostname { get; set; } public string Hostname {
get
{
return _Hostname;
}
set
{
_Hostname = value;
if (Client != null)
{
Client.AddressClientConnectedTo = _Hostname;
}
}
}
/// <summary> /// <summary>
/// Port on server /// Port on server
@@ -233,10 +250,13 @@ namespace PepperDash.Core
if (Client == null) if (Client == null)
{ {
Client = new TCPClient(Hostname, Port, BufferSize); Client = new TCPClient(Hostname, Port, BufferSize);
Client.SocketStatusChange += Client_SocketStatusChange; Client.SocketStatusChange += Client_SocketStatusChange;
} }
DisconnectCalledByUser = false; DisconnectCalledByUser = false;
Client.ConnectToServerAsync(ConnectToServerCallback); // (null); Client.ConnectToServerAsync(ConnectToServerCallback); // (null);
} }
@@ -352,7 +372,6 @@ namespace PepperDash.Core
} }
} }
public class TcpSshPropertiesConfig public class TcpSshPropertiesConfig
{ {
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]

View File

@@ -328,7 +328,8 @@ namespace PepperDash.Core
Client = new TCPClient(Hostname, Port, BufferSize); Client = new TCPClient(Hostname, Port, BufferSize);
Client.SocketStatusChange += Client_SocketStatusChange; Client.SocketStatusChange += Client_SocketStatusChange;
Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); if(HeartbeatEnabled)
Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5);
Client.AddressClientConnectedTo = Hostname; Client.AddressClientConnectedTo = Hostname;
Client.PortNumber = Port; Client.PortNumber = Port;
// SecureClient = c; // SecureClient = c;
@@ -381,6 +382,15 @@ namespace PepperDash.Core
//OnClientReadyForcommunications(false); // Should send false event //OnClientReadyForcommunications(false); // Should send false event
}, 15000); }, 15000);
} }
else
{
//CLient connected and shared key is not required so just raise the ready for communication event. if Shared key
//required this is called by the shared key being negotiated
if (IsReadyForCommunication == false)
{
OnClientReadyForcommunications(true); // Key not required
}
}
} }
else else
{ {
@@ -485,7 +495,6 @@ namespace PepperDash.Core
Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str); Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str);
if (!string.IsNullOrEmpty(checkHeartbeat(str))) if (!string.IsNullOrEmpty(checkHeartbeat(str)))
{ {
if (SharedKeyRequired && str == "SharedKey:") if (SharedKeyRequired && str == "SharedKey:")
{ {
Debug.Console(1, this, "Server asking for shared key, sending"); Debug.Console(1, this, "Server asking for shared key, sending");
@@ -494,19 +503,11 @@ namespace PepperDash.Core
else if (SharedKeyRequired && str == "Shared Key Match") else if (SharedKeyRequired && str == "Shared Key Match")
{ {
StopWaitForSharedKeyTimer(); StopWaitForSharedKeyTimer();
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Shared key confirmed. Ready for communication"); Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Shared key confirmed. Ready for communication");
OnClientReadyForcommunications(true); // Successful key exchange OnClientReadyForcommunications(true); // Successful key exchange
} }
else if (SharedKeyRequired == false && IsReadyForCommunication == false)
{
OnClientReadyForcommunications(true); // Key not required
}
else else
{ {
//var bytesHandler = BytesReceived; //var bytesHandler = BytesReceived;
//if (bytesHandler != null) //if (bytesHandler != null)
// bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); // bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
@@ -698,6 +699,7 @@ namespace PepperDash.Core
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus)); Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
OnConnectionChange(); OnConnectionChange();
// The client could be null or disposed by this time... // The client could be null or disposed by this time...
if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{ {

View File

@@ -384,16 +384,20 @@ namespace PepperDash.Core
if (myTcpServer == null) if (myTcpServer == null)
{ {
myTcpServer = new TCPServer(Port, MaxClients); myTcpServer = new TCPServer(Port, MaxClients);
myTcpServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5); if(HeartbeatRequired)
myTcpServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5);
// myTcpServer.HandshakeTimeout = 30; // myTcpServer.HandshakeTimeout = 30;
myTcpServer.SocketStatusChange += new TCPServerSocketStatusChangeEventHandler(TcpServer_SocketStatusChange);
} }
else else
{ {
KillServer(); KillServer();
myTcpServer.PortNumber = Port; myTcpServer.PortNumber = Port;
} }
myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange;
myTcpServer.SocketStatusChange += TcpServer_SocketStatusChange;
ServerStopped = false; ServerStopped = false;
myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
OnServerStateChange(myTcpServer.State); OnServerStateChange(myTcpServer.State);
@@ -418,22 +422,18 @@ namespace PepperDash.Core
{ {
try try
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Stopping Listener"); Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
if (myTcpServer != null) if (myTcpServer != null)
{ {
myTcpServer.Stop(); myTcpServer.Stop();
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", myTcpServer.State); Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", myTcpServer.State);
//SecureServer = null; OnServerStateChange(myTcpServer.State);
} }
ServerStopped = true;
ServerStopped = true;
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server Stopped");
OnServerStateChange(myTcpServer.State);
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex); Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
} }
} }
@@ -446,11 +446,11 @@ namespace PepperDash.Core
try try
{ {
myTcpServer.Disconnect(client); myTcpServer.Disconnect(client);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client); Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client);
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex); Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex);
} }
} }
/// <summary> /// <summary>
@@ -470,17 +470,17 @@ namespace PepperDash.Core
try try
{ {
myTcpServer.Disconnect(i); myTcpServer.Disconnect(i);
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i); Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i);
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex); Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex);
} }
} }
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", myTcpServer.ServerSocketStatus); Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", myTcpServer.ServerSocketStatus);
} }
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients"); Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients");
ConnectedClientsIndexes.Clear(); ConnectedClientsIndexes.Clear();
if (!ProgramIsStopping) if (!ProgramIsStopping)
@@ -511,7 +511,7 @@ namespace PepperDash.Core
{ {
SocketErrorCodes error = myTcpServer.SendDataAsync(i, b, b.Length, (x, y, z) => { }); SocketErrorCodes error = myTcpServer.SendDataAsync(i, b, b.Length, (x, y, z) => { });
if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING) if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING)
Debug.Console(0, error.ToString()); Debug.Console(2, error.ToString());
} }
} }
} }
@@ -542,7 +542,7 @@ namespace PepperDash.Core
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(0, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text); Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
} }
} }
@@ -670,11 +670,13 @@ namespace PepperDash.Core
} }
if (ClientReadyAfterKeyExchange.Contains(clientIndex)) if (ClientReadyAfterKeyExchange.Contains(clientIndex))
ClientReadyAfterKeyExchange.Remove(clientIndex); ClientReadyAfterKeyExchange.Remove(clientIndex);
if (WaitingForSharedKey.Contains(clientIndex))
WaitingForSharedKey.Remove(clientIndex);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex); Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex);
} }
onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
} }
@@ -740,7 +742,7 @@ namespace PepperDash.Core
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex); Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex);
} }
//Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))", //Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))",
// server.State, // server.State,
@@ -763,50 +765,51 @@ namespace PepperDash.Core
/// <param name="mySecureTCPServer"></param> /// <param name="mySecureTCPServer"></param>
/// <param name="clientIndex"></param> /// <param name="clientIndex"></param>
/// <param name="numberOfBytesReceived"></param> /// <param name="numberOfBytesReceived"></param>
void TcpServerReceivedDataAsyncCallback(TCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived) void TcpServerReceivedDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesReceived)
{ {
if (numberOfBytesReceived > 0) if (numberOfBytesReceived > 0)
{ {
string received = "Nothing"; string received = "Nothing";
try try
{ {
byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex); byte[] bytes = myTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived); received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
if (WaitingForSharedKey.Contains(clientIndex)) if (WaitingForSharedKey.Contains(clientIndex))
{ {
received = received.Replace("\r", ""); received = received.Replace("\r", "");
received = received.Replace("\n", ""); received = received.Replace("\n", "");
if (received != SharedKey) if (received != SharedKey)
{ {
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting"); byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received); Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
mySecureTCPServer.SendData(clientIndex, b, b.Length); myTCPServer.SendData(clientIndex, b, b.Length);
mySecureTCPServer.Disconnect(clientIndex); myTCPServer.Disconnect(clientIndex);
WaitingForSharedKey.Remove(clientIndex); return;
return; }
}
if (mySecureTCPServer.NumberOfClientsConnected > 0) WaitingForSharedKey.Remove(clientIndex);
mySecureTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback); byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
WaitingForSharedKey.Remove(clientIndex); myTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match"); OnServerClientReadyForCommunications(clientIndex);
mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null); Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
OnServerClientReadyForCommunications(clientIndex); }
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
return; else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
} onTextReceived(received, clientIndex);
//var address = mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); }
//Debug.Console(1, this, "Secure Server Listening on Port: {0}, client IP: {1}, Client Index: {4}, NumberOfBytesReceived: {2}, Received: {3}\r\n", catch (Exception ex)
// mySecureTCPServer.PortNumber.ToString(), address , numberOfBytesReceived.ToString(), received, clientIndex.ToString()); {
if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received))) Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
onTextReceived(received, clientIndex); }
} if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
catch (Exception ex) myTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
{ }
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex); else
} {
} // If numberOfBytesReceived <= 0
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) myTCPServer.Disconnect();
mySecureTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback); }
} }
#endregion #endregion
@@ -966,13 +969,13 @@ namespace PepperDash.Core
StopMonitorClient(); StopMonitorClient();
if (MonitorClientFailureCount < MonitorClientMaxFailureCount) if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}", Debug.Console(2, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}",
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount); MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
StartMonitorClient(); StartMonitorClient();
} }
else else
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Error, Debug.Console(2, this, Debug.ErrorLogLevel.Error,
"\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************", "\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************",
MonitorClientMaxFailureCount); MonitorClientMaxFailureCount);

View File

@@ -1,231 +1,351 @@
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
{
public class GenericUdpServer : Device, IBasicCommunication namespace PepperDash.Core
{ {
/// <summary> public class GenericUdpServer : Device, IBasicCommunication
/// {
/// </summary> /// <summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived; ///
/// </summary>
/// <summary> public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
///
/// </summary> /// <summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived; ///
/// </summary>
/// <summary> public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
///
/// </summary> /// <summary>
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange; /// This event will fire when a message is dequeued that includes the source IP and Port info if needed to determine the source of the received data.
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange; /// </summary>
public event EventHandler<GenericUdpReceiveTextExtraArgs> DataRecievedExtra;
public SocketStatus ClientStatus
{ /// <summary>
get /// Queue to temporarily store received messages with the source IP and Port info
{ /// </summary>
return Server.ServerStatus; private CrestronQueue<GenericUdpReceiveTextExtraArgs> MessageQueue;
}
} /// <summary>
///
/// <summary> /// </summary>
/// Address of server //public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
/// </summary> public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
public string Hostname { get; set; }
public event EventHandler<GenericUdpConnectedEventArgs> UpdateConnectionStatus;
/// <summary>
/// Port on server public SocketStatus ClientStatus
/// </summary> {
public int Port { get; set; } get
{
/// <summary> return Server.ServerStatus;
/// Another damn S+ helper because S+ seems to treat large port nums as signed ints }
/// which screws up things }
/// </summary>
public ushort UPort public ushort UStatus
{ {
get { return Convert.ToUInt16(Port); } get { return (ushort)Server.ServerStatus; }
set { Port = Convert.ToInt32(value); } }
}
/// <summary> CCriticalSection DequeueLock;
/// Indicates that the UDP Server is enabled /// <summary>
/// </summary> /// Address of server
public bool IsConnected /// </summary>
{ public string Hostname { get; set; }
get;
private set; /// <summary>
} /// IP Address of the sender of the last recieved message
/// </summary>
/// <summary>
/// Defaults to 2000
/// </summary> /// <summary>
public int BufferSize { get; set; } /// Port on server
/// </summary>
public UDPServer Server { get; private set; } public int Port { get; set; }
public GenericUdpServer(string key, string address, int port, int buffefSize) /// <summary>
: base(key) /// Another damn S+ helper because S+ seems to treat large port nums as signed ints
{ /// which screws up things
Hostname = address; /// </summary>
Port = port; public ushort UPort
BufferSize = buffefSize; {
get { return Convert.ToUInt16(Port); }
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); set { Port = Convert.ToInt32(value); }
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler); }
}
/// <summary>
void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) /// Indicates that the UDP Server is enabled
{ /// </summary>
// Re-enable the server if the link comes back up and the status should be connected public bool IsConnected
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp {
&& IsConnected) get;
{ private set;
Connect(); }
}
} public ushort UIsConnected
{
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) get { return IsConnected ? (ushort)1 : (ushort)0; }
{ }
if (programEventType == eProgramStatusEventType.Stopping)
{ /// <summary>
Debug.Console(1, this, "Program stopping. Disabling Server"); /// Defaults to 2000
Disconnect(); /// </summary>
} public int BufferSize { get; set; }
}
public UDPServer Server { get; private set; }
/// <summary>
/// Enables the UDP Server /// <summary>
/// </summary> /// Constructor for S+. Make sure to set key, address, port, and buffersize using init method
public void Connect() /// </summary>
{ public GenericUdpServer()
if (Server == null) : base("Uninitialized Udp Server")
{ {
Server = new UDPServer(); BufferSize = 5000;
DequeueLock = new CCriticalSection();
} MessageQueue = new CrestronQueue<GenericUdpReceiveTextExtraArgs>();
if (string.IsNullOrEmpty(Hostname)) CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
{ CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key); }
return;
} public GenericUdpServer(string key, string address, int port, int buffefSize)
if (Port < 1 || Port > 65535) : base(key)
{ {
{ Hostname = address;
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key); Port = port;
return; BufferSize = buffefSize;
}
} DequeueLock = new CCriticalSection();
MessageQueue = new CrestronQueue<GenericUdpReceiveTextExtraArgs>();
var status = Server.EnableUDPServer(Hostname, Port);
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
Debug.Console(2, this, "SocketErrorCode: {0}", status); CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
if (status == SocketErrorCodes.SOCKET_OK) }
IsConnected = true;
public void Initialize(string key, string address, ushort port)
// Start receiving data {
Server.ReceiveDataAsync(Receive); Key = key;
} Hostname = address;
UPort = port;
/// <summary> }
/// Disabled the UDP Server
/// </summary> void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
public void Disconnect() {
{ // Re-enable the server if the link comes back up and the status should be connected
if(Server != null) if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp
Server.DisableUDPServer(); && IsConnected)
{
IsConnected = false; Connect();
} }
}
/// <summary> void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
/// Recursive method to receive data {
/// </summary> if (programEventType == eProgramStatusEventType.Stopping)
/// <param name="server"></param> {
/// <param name="numBytes"></param> Debug.Console(1, this, "Program stopping. Disabling Server");
void Receive(UDPServer server, int numBytes) Disconnect();
{ }
Debug.Console(2, this, "Received {0} bytes", numBytes); }
if (numBytes > 0) /// <summary>
{ /// Enables the UDP Server
var bytes = server.IncomingDataBuffer.Take(numBytes).ToArray(); /// </summary>
public void Connect()
Debug.Console(2, this, "Bytes: {0}", bytes.ToString()); {
var bytesHandler = BytesReceived; if (Server == null)
if (bytesHandler != null) {
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); Server = new UDPServer();
else }
Debug.Console(2, this, "bytesHandler is null");
var textHandler = TextReceived; if (string.IsNullOrEmpty(Hostname))
if (textHandler != null) {
{ Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key);
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); return;
Debug.Console(2, this, "RX: {0}", str); }
textHandler(this, new GenericCommMethodReceiveTextArgs(str)); if (Port < 1 || Port > 65535)
} {
else {
Debug.Console(2, this, "textHandler is null"); Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key);
} return;
server.ReceiveDataAsync(Receive); }
} }
/// <summary> var status = Server.EnableUDPServer(Hostname, Port);
/// General send method
/// </summary> Debug.Console(2, this, "SocketErrorCode: {0}", status);
/// <param name="text"></param> if (status == SocketErrorCodes.SOCKET_OK)
public void SendText(string text) IsConnected = true;
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text); var handler = UpdateConnectionStatus;
if (handler != null)
if (IsConnected && Server != null) handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
{
Debug.Console(2, this, "TX: {0}", text); // Start receiving data
Server.SendData(bytes, bytes.Length); Server.ReceiveDataAsync(Receive);
} }
}
/// <summary>
public void SendBytes(byte[] bytes) /// Disabled the UDP Server
{ /// </summary>
//if (Debug.Level == 2) public void Disconnect()
// Debug.Console(2, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); {
if (IsConnected && Server != null) if(Server != null)
Server.SendData(bytes, bytes.Length); Server.DisableUDPServer();
}
IsConnected = false;
var handler = UpdateConnectionStatus;
} if (handler != null)
handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
public class UdpServerPropertiesConfig }
{
[JsonProperty(Required = Required.Always)]
public string Address { get; set; } /// <summary>
/// Recursive method to receive data
[JsonProperty(Required = Required.Always)] /// </summary>
public int Port { get; set; } /// <param name="server"></param>
/// <param name="numBytes"></param>
/// <summary> void Receive(UDPServer server, int numBytes)
/// Defaults to 32768 {
/// </summary> Debug.Console(2, this, "Received {0} bytes", numBytes);
public int BufferSize { get; set; }
if (numBytes > 0)
public UdpServerPropertiesConfig() {
{ var sourceIp = Server.IPAddressLastMessageReceivedFrom;
BufferSize = 32768; var sourcePort = Server.IPPortLastMessageReceivedFrom;
} var bytes = server.IncomingDataBuffer.Take(numBytes).ToArray();
} var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
MessageQueue.TryToEnqueue(new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes));
Debug.Console(2, this, "Bytes: {0}", bytes.ToString());
var bytesHandler = BytesReceived;
if (bytesHandler != null)
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
else
Debug.Console(2, this, "bytesHandler is null");
var textHandler = TextReceived;
if (textHandler != null)
{
Debug.Console(2, this, "RX: {0}", str);
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
}
else
Debug.Console(2, this, "textHandler is null");
}
server.ReceiveDataAsync(Receive);
// Attempt to enter the CCritical Secion and if we can, start the dequeue thread
var gotLock = DequeueLock.TryEnter();
if (gotLock)
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
}
/// <summary>
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
/// It will dequeue items as they are enqueued automatically.
/// </summary>
void DequeueEvent()
{
try
{
while (true)
{
// Pull from Queue and fire an event. Block indefinitely until an item can be removed, similar to a Gather.
var message = MessageQueue.Dequeue();
var dataRecivedExtra = DataRecievedExtra;
if (dataRecivedExtra != null)
{
dataRecivedExtra(this, message);
}
}
}
catch (Exception e)
{
Debug.Console(0, "GenericUdpServer DequeueEvent error: {0}\r", e);
}
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
if (DequeueLock != null)
{
DequeueLock.Leave();
}
}
/// <summary>
/// General send method
/// </summary>
/// <param name="text"></param>
public void SendText(string text)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
if (IsConnected && Server != null)
{
Debug.Console(2, this, "TX: {0}", text);
Server.SendData(bytes, bytes.Length);
}
}
public void SendBytes(byte[] bytes)
{
//if (Debug.Level == 2)
// Debug.Console(2, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
if (IsConnected && Server != null)
Server.SendData(bytes, bytes.Length);
}
}
public class GenericUdpReceiveTextExtraArgs : EventArgs
{
public string Text { get; private set; }
public string IpAddress { get; private set; }
public int Port { get; private set; }
public byte[] Bytes { get; private set; }
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
{
Text = text;
IpAddress = ipAddress;
Port = port;
Bytes = bytes;
}
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericUdpReceiveTextExtraArgs() { }
}
public class UdpServerPropertiesConfig
{
[JsonProperty(Required = Required.Always)]
public string Address { get; set; }
[JsonProperty(Required = Required.Always)]
public int Port { get; set; }
/// <summary>
/// Defaults to 32768
/// </summary>
public int BufferSize { get; set; }
public UdpServerPropertiesConfig()
{
BufferSize = 32768;
}
}
} }

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
using Newtonsoft.Json;
namespace PepperDash.Core
{
/// <summary>
/// Client config object for TCP client with server that inherits from TcpSshPropertiesConfig and adds properties for shared key and heartbeat
/// </summary>
public class TcpClientConfigObject
{
/// <summary>
/// TcpSsh Properties
/// </summary>
public ControlPropertiesConfig Control { get; set; }
/// <summary>
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
/// </summary>
public bool Secure { get; set; }
/// <summary>
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
/// </summary>
public bool SharedKeyRequired { get; set; }
/// <summary>
/// The shared key that must match on the server and client
/// </summary>
public string SharedKey { get; set; }
/// <summary>
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
/// heartbeats do not raise received events.
/// </summary>
public bool HeartbeatRequired { get; set; }
/// <summary>
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
/// </summary>
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
/// <summary>
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
/// </summary>
public string HeartbeatStringToMatch { get; set; }
/// <summary>
/// Receive Queue size must be greater than 20 or defaults to 20
/// </summary>
public int ReceiveQueueSize { get; set; }
}
}

View File

@@ -6,17 +6,55 @@ using Crestron.SimplSharp;
namespace PepperDash.Core namespace PepperDash.Core
{ {
/// <summary>
/// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities
/// </summary>
public class TcpServerConfigObject public class TcpServerConfigObject
{ {
/// <summary>
/// Uique key
/// </summary>
public string Key { get; set; } public string Key { get; set; }
public bool Secure { get; set; } /// <summary>
/// Max Clients that the server will allow to connect.
/// </summary>
public ushort MaxClients { get; set; } public ushort MaxClients { get; set; }
/// <summary>
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
/// </summary>
public bool Secure { get; set; }
/// <summary>
/// Port for the server to listen on
/// </summary>
public int Port { get; set; } public int Port { get; set; }
/// <summary>
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
/// </summary>
public bool SharedKeyRequired { get; set; } public bool SharedKeyRequired { get; set; }
/// <summary>
/// The shared key that must match on the server and client
/// </summary>
public string SharedKey { get; set; } public string SharedKey { get; set; }
/// <summary>
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
/// heartbeats do not raise received events.
/// </summary>
public bool HeartbeatRequired { get; set; } public bool HeartbeatRequired { get; set; }
/// <summary>
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
/// </summary>
public ushort HeartbeatRequiredIntervalInSeconds { get; set; } public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
/// <summary>
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
/// </summary>
public string HeartbeatStringToMatch { get; set; } public string HeartbeatStringToMatch { get; set; }
/// <summary>
/// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000
/// </summary>
public int BufferSize { get; set; } public int BufferSize { get; set; }
/// <summary>
/// Receive Queue size must be greater than 20 or defaults to 20
/// </summary>
public int ReceiveQueueSize { get; set; }
} }
} }

View File

@@ -6,6 +6,9 @@ using Crestron.SimplSharp;
namespace PepperDash.Core namespace PepperDash.Core
{ {
/// <summary>
/// Crestron Control Methods for a comm object
/// </summary>
public enum eControlMethod public enum eControlMethod
{ {
None = 0, Com, IpId, IpidTcp, IR, Ssh, Tcpip, Telnet, Cresnet, Cec, Udp None = 0, Com, IpId, IpidTcp, IR, Ssh, Tcpip, Telnet, Cresnet, Cec, Udp

View File

@@ -67,7 +67,7 @@ namespace PepperDash.Core.Config
/// </summary> /// </summary>
/// <param name="doubleConfig"></param> /// <param name="doubleConfig"></param>
/// <returns></returns> /// <returns></returns>
static JObject MergeConfigs(JObject doubleConfig) public static JObject MergeConfigs(JObject doubleConfig)
{ {
var system = JObject.FromObject(doubleConfig["system"]); var system = JObject.FromObject(doubleConfig["system"]);
var template = JObject.FromObject(doubleConfig["template"]); var template = JObject.FromObject(doubleConfig["template"]);
@@ -114,28 +114,35 @@ namespace PepperDash.Core.Config
/// <summary> /// <summary>
/// Merges the contents of a base and a delta array, matching the entries on a top-level property /// Merges the contents of a base and a delta array, matching the entries on a top-level property
/// given by propertyName. Returns a merge of them. Items in the delta array that do not have /// given by propertyName. Returns a merge of them. Items in the delta array that do not have
/// a matched item in base array will not be merged. /// a matched item in base array will not be merged. Non keyed system items will replace the template items.
/// </summary> /// </summary>
static JArray MergeArraysOnTopLevelProperty(JArray a1, JArray a2, string propertyName, string path) static JArray MergeArraysOnTopLevelProperty(JArray a1, JArray a2, string propertyName, string path)
{ {
var result = new JArray(); var result = new JArray();
if (a2 == null) if (a2 == null || a2.Count == 0) // If the system array is null or empty, return the template array
result = a1; return a1;
else if (a1 != null) else if (a1 != null)
{ {
for (int i = 0; i < a1.Count(); i++) if (a2[0]["key"] == null) // If the first item in the system array has no key, overwrite the template array
{ { // with the system array
var a1Dev = a1[i]; return a2;
// Try to get a system device and if found, merge it onto template }
var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value<int>("uid") == tmplDev.Value<int>("uid")); else // The arrays are keyed, merge them by key
if (a2Match != null) {
{ for (int i = 0; i < a1.Count(); i++)
var mergedItem = Merge(a1Dev, a2Match, string.Format("{0}[{1}].", path, i));// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match)); {
result.Add(mergedItem); var a1Dev = a1[i];
} // Try to get a system device and if found, merge it onto template
else var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value<int>("uid") == tmplDev.Value<int>("uid"));
result.Add(a1Dev); if (a2Match != null)
} {
var mergedItem = Merge(a1Dev, a2Match, string.Format("{0}[{1}].", path, i));// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match));
result.Add(mergedItem);
}
else
result.Add(a1Dev);
}
}
} }
return result; return result;
} }

View File

@@ -6,13 +6,25 @@ using Crestron.SimplSharp;
namespace PepperDash.Core namespace PepperDash.Core
{ {
/// <summary>
/// Unique key interface to require a unique key for the class
/// </summary>
public interface IKeyed public interface IKeyed
{ {
/// <summary>
/// Unique Key
/// </summary>
string Key { get; } string Key { get; }
} }
/// <summary>
/// Named Keyed device interface. Forces the devie to have a Unique Key and a name.
/// </summary>
public interface IKeyName : IKeyed public interface IKeyName : IKeyed
{ {
/// <summary>
/// Isn't it obvious :)
/// </summary>
string Name { get; } string Name { get; }
} }
} }

View File

@@ -10,8 +10,17 @@ namespace PepperDash.Core
/// </summary> /// </summary>
public class Device : IKeyName public class Device : IKeyName
{ {
/// <summary>
/// Unique Key
/// </summary>
public string Key { get; protected set; } public string Key { get; protected set; }
public string Name { get; protected set; } /// <summary>
/// Name of the devie
/// </summary>
public string Name { get; protected set; }
/// <summary>
///
/// </summary>
public bool Enabled { get; protected set; } public bool Enabled { get; protected set; }
///// <summary> ///// <summary>

View File

@@ -6,6 +6,9 @@ using Crestron.SimplSharp;
namespace PepperDash.Core.JsonToSimpl namespace PepperDash.Core.JsonToSimpl
{ {
/// <summary>
/// Constants for Simpl modules
/// </summary>
public class JsonToSimplConstants public class JsonToSimplConstants
{ {
public const ushort JsonIsValidBoolChange = 2; public const ushort JsonIsValidBoolChange = 2;
@@ -13,6 +16,7 @@ namespace PepperDash.Core.JsonToSimpl
public const ushort BoolValueChange = 1; public const ushort BoolValueChange = 1;
public const ushort UshortValueChange = 101; public const ushort UshortValueChange = 101;
public const ushort StringValueChange = 201; public const ushort StringValueChange = 201;
public const ushort FullPathToArrayChange = 202;
} }
//**************************************************************************************************// //**************************************************************************************************//

View File

@@ -11,10 +11,19 @@ namespace PepperDash.Core.JsonToSimpl
public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase
{ {
public string SearchPropertyName { get; set; } public string SearchPropertyName { get; set; }
public string SearchPropertyValue { get; set; } public string SearchPropertyValue { get; set; }
int ArrayIndex; int ArrayIndex;
/// <summary>
/// For <2.4.1 array lookups
/// </summary>
/// <param name="file"></param>
/// <param name="key"></param>
/// <param name="pathPrefix"></param>
/// <param name="pathSuffix"></param>
/// <param name="searchPropertyName"></param>
/// <param name="searchPropertyValue"></param>
public void Initialize(string file, string key, string pathPrefix, string pathSuffix, public void Initialize(string file, string key, string pathPrefix, string pathSuffix,
string searchPropertyName, string searchPropertyValue) string searchPropertyName, string searchPropertyValue)
{ {
@@ -24,6 +33,28 @@ namespace PepperDash.Core.JsonToSimpl
} }
/// <summary>
/// For newer >=2.4.1 array lookups.
/// </summary>
/// <param name="file"></param>
/// <param name="key"></param>
/// <param name="pathPrefix"></param>
/// <param name="pathAppend"></param>
/// <param name="pathSuffix"></param>
/// <param name="searchPropertyName"></param>
/// <param name="searchPropertyValue"></param>
public void InitializeWithAppend(string file, string key, string pathPrefix, string pathAppend,
string pathSuffix, string searchPropertyName, string searchPropertyValue)
{
string pathPrefixWithAppend = (pathPrefix != null ? pathPrefix : "") + GetPathAppend(pathAppend);
base.Initialize(file, key, pathPrefixWithAppend, pathSuffix);
SearchPropertyName = searchPropertyName;
SearchPropertyValue = searchPropertyValue;
}
//PathPrefix+ArrayName+[x]+path+PathSuffix //PathPrefix+ArrayName+[x]+path+PathSuffix
/// <summary> /// <summary>
/// ///
@@ -32,9 +63,10 @@ namespace PepperDash.Core.JsonToSimpl
/// <returns></returns> /// <returns></returns>
protected override string GetFullPath(string path) protected override string GetFullPath(string path)
{ {
return string.Format("{0}[{1}].{2}{3}", return string.Format("{0}[{1}].{2}{3}",
PathPrefix == null ? "" : PathPrefix, PathPrefix == null ? "" : PathPrefix,
ArrayIndex, path, ArrayIndex,
path,
PathSuffix == null ? "" : PathSuffix); PathSuffix == null ? "" : PathSuffix);
} }
@@ -44,6 +76,30 @@ namespace PepperDash.Core.JsonToSimpl
base.ProcessAll(); base.ProcessAll();
} }
/// <summary>
/// Provides the path append for GetFullPath
/// </summary>
/// <returns></returns>
string GetPathAppend(string a)
{
if (string.IsNullOrEmpty(a))
{
return "";
}
if (a.StartsWith("."))
{
return a;
}
else
{
return "." + a;
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
bool FindInArray() bool FindInArray()
{ {
if (Master == null) if (Master == null)
@@ -51,41 +107,45 @@ namespace PepperDash.Core.JsonToSimpl
if (Master.JsonObject == null) if (Master.JsonObject == null)
throw new InvalidOperationException("Cannot do operations before master JSON has read"); throw new InvalidOperationException("Cannot do operations before master JSON has read");
if (PathPrefix == null) if (PathPrefix == null)
throw new InvalidOperationException("Cannot do operations before PathPrefix is set"); throw new InvalidOperationException("Cannot do operations before PathPrefix is set");
var token = Master.JsonObject.SelectToken(PathPrefix);
if (token is JArray) var token = Master.JsonObject.SelectToken(PathPrefix);
{ if (token is JArray)
var array = token as JArray; {
try var array = token as JArray;
{ try
var item = array.FirstOrDefault(o => {
{ var item = array.FirstOrDefault(o =>
var prop = o[SearchPropertyName]; {
return prop != null && prop.Value<string>() var prop = o[SearchPropertyName];
.Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase); return prop != null && prop.Value<string>()
}); .Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase);
if (item == null) });
{ if (item == null)
Debug.Console(1, "JSON Child[{0}] Array '{1}' '{2}={3}' not found: ", Key, {
PathPrefix, SearchPropertyName, SearchPropertyValue); Debug.Console(1, "JSON Child[{0}] Array '{1}' '{2}={3}' not found: ", Key,
this.LinkedToObject = false; PathPrefix, SearchPropertyName, SearchPropertyValue);
return false; this.LinkedToObject = false;
} return false;
}
this.LinkedToObject = true;
ArrayIndex = array.IndexOf(item); this.LinkedToObject = true;
Debug.Console(1, "JSON Child[{0}] Found array match at index {1}", Key, ArrayIndex); ArrayIndex = array.IndexOf(item);
return true; OnStringChange(string.Format("{0}[{1}]", PathPrefix, ArrayIndex), 0, JsonToSimplConstants.FullPathToArrayChange);
} Debug.Console(1, "JSON Child[{0}] Found array match at index {1}", Key, ArrayIndex);
catch (Exception e) return true;
{ }
Debug.Console(1, "JSON Child[{0}] Array '{1}' lookup error: '{2}={3}'\r{4}", Key, catch (Exception e)
PathPrefix, SearchPropertyName, SearchPropertyValue, e); {
} Debug.Console(1, "JSON Child[{0}] Array '{1}' lookup error: '{2}={3}'\r{4}", Key,
PathPrefix, SearchPropertyName, SearchPropertyValue, e);
}
}
else
{
Debug.Console(1, "JSON Child[{0}] Path '{1}' is not an array", Key, PathPrefix);
} }
else
Debug.Console(1, "JSON Child[{0}] Path '{1}' is not an array", Key, PathPrefix);
return false; return false;
} }

View File

@@ -28,7 +28,7 @@ namespace PepperDash.Core.JsonToSimpl
/// This will be prepended to all paths to allow path swapping or for more organized /// This will be prepended to all paths to allow path swapping or for more organized
/// sub-paths /// sub-paths
/// </summary> /// </summary>
public string PathPrefix { get; protected set; } public string PathPrefix { get; protected set; }
/// <summary> /// <summary>
/// This is added to the end of all paths /// This is added to the end of all paths
@@ -285,7 +285,7 @@ namespace PepperDash.Core.JsonToSimpl
protected virtual string GetFullPath(string path) protected virtual string GetFullPath(string path)
{ {
return (PathPrefix != null ? PathPrefix : "") + return (PathPrefix != null ? PathPrefix : "") +
path + (PathSuffix != null ? PathSuffix : ""); path + (PathSuffix != null ? PathSuffix : "");
} }
// Helpers for events // Helpers for events

View File

@@ -106,10 +106,10 @@ namespace PepperDash.Core.JsonToSimpl
/// <param name="child"></param> /// <param name="child"></param>
public void AddChild(JsonToSimplChildObjectBase child) public void AddChild(JsonToSimplChildObjectBase child)
{ {
if (Children.Contains(child)) { if (!Children.Contains(child))
Children.Remove(child); {
} Children.Add(child);
Children.Add(child); }
} }
/// <summary> /// <summary>

View File

@@ -1,393 +1,421 @@
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.CrestronLogger; using Crestron.SimplSharp.Reflection;
using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronLogger;
using Newtonsoft.Json; using Crestron.SimplSharp.CrestronIO;
using PepperDash.Core.DebugThings; using Newtonsoft.Json;
using PepperDash.Core.DebugThings;
namespace PepperDash.Core
{ namespace PepperDash.Core
public static class Debug {
{ public static class Debug
/// <summary> {
/// Describes the folder location where a given program stores it's debug level memory. By default, the /// <summary>
/// file written will be named appNdebug where N is 1-10. /// Describes the folder location where a given program stores it's debug level memory. By default, the
/// </summary> /// file written will be named appNdebug where N is 1-10.
public static string FilePathPrefix = @"\nvram\debug\"; /// </summary>
public static string FilePathPrefix = @"\nvram\debug\";
/// <summary>
/// The name of the file containing the current debug settings. /// <summary>
/// </summary> /// The name of the file containing the current debug settings.
public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber); /// </summary>
public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber);
public static int Level { get; private set; }
public static int Level { get; private set; }
static DebugContextCollection Contexts;
static DebugContextCollection Contexts;
static int SaveTimeoutMs = 30000;
static int SaveTimeoutMs = 30000;
static CTimer SaveTimer;
public static string PepperDashCoreVersion { get; private set; }
/// <summary>
/// When true, the IncludedExcludedKeys dict will contain keys to include. static CTimer SaveTimer;
/// When false (default), IncludedExcludedKeys will contain keys to exclude.
/// </summary> /// <summary>
static bool ExcludeAllMode; /// When true, the IncludedExcludedKeys dict will contain keys to include.
/// When false (default), IncludedExcludedKeys will contain keys to exclude.
//static bool ExcludeNoKeyMessages; /// </summary>
static bool ExcludeAllMode;
static Dictionary<string, object> IncludedExcludedKeys;
//static bool ExcludeNoKeyMessages;
static Debug()
{ static Dictionary<string, object> IncludedExcludedKeys;
IncludedExcludedKeys = new Dictionary<string, object>();
static Debug()
//CrestronDataStoreStatic.InitCrestronDataStore(); {
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro) // Get the assembly version and print it to console and the log
{ var version = Assembly.GetExecutingAssembly().GetName().Version;
// Add command to console
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug", PepperDashCoreVersion = string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision);
"appdebug:P [0-2]: Sets the application's console debug message level",
ConsoleAccessLevelEnum.AccessOperator); var msg = string.Format("[App {0}] Using PepperDash_Core v{1}", InitialParametersClass.ApplicationNumber, PepperDashCoreVersion);
CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
"appdebuglog:P [all] Use \"all\" for full log.", CrestronConsole.PrintLine(msg);
ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear", LogError(ErrorLogLevel.Notice, msg);
"appdebugclear:P Clears the current custom log",
ConsoleAccessLevelEnum.AccessOperator); IncludedExcludedKeys = new Dictionary<string, object>();
CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
"appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator); //CrestronDataStoreStatic.InitCrestronDataStore();
} if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
{
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; // Add command to console
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
LoadMemory(); "appdebug:P [0-2]: Sets the application's console debug message level",
Level = Contexts.GetOrCreateItem("DEFAULT").Level; ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
CrestronLogger.Initialize(2, LoggerModeEnum.RM); // Use RM instead of DEFAULT as not to double-up console messages. "appdebuglog:P [all] Use \"all\" for full log.",
} ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
/// <summary> "appdebugclear:P Clears the current custom log",
/// Used to save memory when shutting down ConsoleAccessLevelEnum.AccessOperator);
/// </summary> CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
/// <param name="programEventType"></param> "appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator);
static void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) }
{
if (programEventType == eProgramStatusEventType.Stopping) CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
{
if (SaveTimer != null) LoadMemory();
{ Level = Contexts.GetOrCreateItem("DEFAULT").Level;
SaveTimer.Stop();
SaveTimer = null; try
} {
Console(0, "Saving debug settings"); if (InitialParametersClass.NumberOfRemovableDrives > 0)
SaveMemory(); {
} CrestronConsole.PrintLine("{0} RM Drive(s) Present.", InitialParametersClass.NumberOfRemovableDrives);
} CrestronLogger.Initialize(2, LoggerModeEnum.DEFAULT); // Use RM instead of DEFAULT as not to double-up console messages.
}
/// <summary> else
/// Callback for console command CrestronConsole.PrintLine("No RM Drive(s) Present.");
/// </summary> }
/// <param name="levelString"></param> catch (Exception e)
public static void SetDebugFromConsole(string levelString) {
{
try CrestronConsole.PrintLine("Initializing of CrestronLogger failed: {0}", e);
{ }
if (string.IsNullOrEmpty(levelString.Trim())) }
{
CrestronConsole.PrintLine("AppDebug level = {0}", Level); /// <summary>
return; /// Used to save memory when shutting down
} /// </summary>
/// <param name="programEventType"></param>
SetDebugLevel(Convert.ToInt32(levelString)); static void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
} {
catch if (programEventType == eProgramStatusEventType.Stopping)
{ {
CrestronConsole.PrintLine("Usage: appdebug:P [0-2]"); if (SaveTimer != null)
} {
} SaveTimer.Stop();
SaveTimer = null;
public static void SetDebugFilterFromConsole(string items) }
{ Console(0, "Saving debug settings");
var str = items.Trim(); SaveMemory();
if (str == "?") }
{ }
CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " +
"+all: at beginning puts filter into 'default include' mode\r" + /// <summary>
" All keys that follow will be excluded from output.\r" + /// Callback for console command
"-all: at beginning puts filter into 'default excluse all' mode.\r" + /// </summary>
" All keys that follow will be the only keys that are shown\r" + /// <param name="levelString"></param>
"+nokey: Enables messages with no key (default)\r" + public static void SetDebugFromConsole(string levelString)
"-nokey: Disables messages with no key.\r" + {
"(nokey settings are independent of all other settings)"); try
return; {
} if (string.IsNullOrEmpty(levelString.Trim()))
var keys = Regex.Split(str, @"\s*"); {
foreach (var keyToken in keys) CrestronConsole.PrintLine("AppDebug level = {0}", Level);
{ return;
var lkey = keyToken.ToLower(); }
if (lkey == "+all")
{ SetDebugLevel(Convert.ToInt32(levelString));
IncludedExcludedKeys.Clear(); }
ExcludeAllMode = false; catch
} {
else if (lkey == "-all") CrestronConsole.PrintLine("Usage: appdebug:P [0-2]");
{ }
IncludedExcludedKeys.Clear(); }
ExcludeAllMode = true;
} public static void SetDebugFilterFromConsole(string items)
//else if (lkey == "+nokey") {
//{ var str = items.Trim();
// ExcludeNoKeyMessages = false; if (str == "?")
//} {
//else if (lkey == "-nokey") CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " +
//{ "+all: at beginning puts filter into 'default include' mode\r" +
// ExcludeNoKeyMessages = true; " All keys that follow will be excluded from output.\r" +
//} "-all: at beginning puts filter into 'default excluse all' mode.\r" +
else " All keys that follow will be the only keys that are shown\r" +
{ "+nokey: Enables messages with no key (default)\r" +
string key = null; ; "-nokey: Disables messages with no key.\r" +
if (lkey.StartsWith("-")) "(nokey settings are independent of all other settings)");
{ return;
key = lkey.Substring(1); }
// if in exclude all mode, we need to remove this from the inclusions var keys = Regex.Split(str, @"\s*");
if (ExcludeAllMode) foreach (var keyToken in keys)
{ {
if (IncludedExcludedKeys.ContainsKey(key)) var lkey = keyToken.ToLower();
IncludedExcludedKeys.Remove(key); if (lkey == "+all")
} {
// otherwise include all mode, add to the exclusions IncludedExcludedKeys.Clear();
else ExcludeAllMode = false;
{ }
IncludedExcludedKeys[key] = new object(); else if (lkey == "-all")
} {
} IncludedExcludedKeys.Clear();
else if (lkey.StartsWith("+")) ExcludeAllMode = true;
{ }
key = lkey.Substring(1); //else if (lkey == "+nokey")
// if in exclude all mode, we need to add this as inclusion //{
if (ExcludeAllMode) // ExcludeNoKeyMessages = false;
{ //}
//else if (lkey == "-nokey")
IncludedExcludedKeys[key] = new object(); //{
} // ExcludeNoKeyMessages = true;
// otherwise include all mode, remove this from exclusions //}
else else
{ {
if (IncludedExcludedKeys.ContainsKey(key)) string key = null; ;
IncludedExcludedKeys.Remove(key); if (lkey.StartsWith("-"))
} {
} key = lkey.Substring(1);
} // if in exclude all mode, we need to remove this from the inclusions
} if (ExcludeAllMode)
} {
if (IncludedExcludedKeys.ContainsKey(key))
IncludedExcludedKeys.Remove(key);
/// <summary> }
/// Sets the debug level // otherwise include all mode, add to the exclusions
/// </summary> else
/// <param name="level"> Valid values 0 (no debug), 1 (critical), 2 (all messages)</param> {
public static void SetDebugLevel(int level) IncludedExcludedKeys[key] = new object();
{ }
if (level <= 2) }
{ else if (lkey.StartsWith("+"))
Level = level; {
Contexts.GetOrCreateItem("DEFAULT").Level = level; key = lkey.Substring(1);
SaveMemoryOnTimeout(); // if in exclude all mode, we need to add this as inclusion
if (ExcludeAllMode)
CrestronConsole.PrintLine("[Application {0}], Debug level set to {1}", {
InitialParametersClass.ApplicationNumber, Level);
IncludedExcludedKeys[key] = new object();
//var err = CrestronDataStoreStatic.SetLocalUintValue("DebugLevel", level); }
//if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) // otherwise include all mode, remove this from exclusions
// CrestronConsole.PrintLine("Error saving console debug level setting: {0}", err); else
} {
} if (IncludedExcludedKeys.ContainsKey(key))
IncludedExcludedKeys.Remove(key);
/// <summary> }
/// }
/// </summary> }
public static void ShowDebugLog(string s) }
{ }
var loglist = CrestronLogger.PrintTheLog(s.ToLower() == "all");
foreach (var l in loglist)
CrestronConsole.ConsoleCommandResponse(l + CrestronEnvironment.NewLine); /// <summary>
} /// Sets the debug level
/// </summary>
/// <summary> /// <param name="level"> Valid values 0 (no debug), 1 (critical), 2 (all messages)</param>
/// Prints message to console if current debug level is equal to or higher than the level of this message. public static void SetDebugLevel(int level)
/// Uses CrestronConsole.PrintLine. {
/// </summary> if (level <= 2)
/// <param name="level"></param> {
/// <param name="format">Console format string</param> Level = level;
/// <param name="items">Object parameters</param> Contexts.GetOrCreateItem("DEFAULT").Level = level;
public static void Console(uint level, string format, params object[] items) SaveMemoryOnTimeout();
{
if (Level >= level) CrestronConsole.PrintLine("[Application {0}], Debug level set to {1}",
CrestronConsole.PrintLine("[{0}]App {1}:{2}", DateTime.Now.ToString("HH:mm:ss.fff"), InitialParametersClass.ApplicationNumber, InitialParametersClass.ApplicationNumber, Level);
string.Format(format, items));
} //var err = CrestronDataStoreStatic.SetLocalUintValue("DebugLevel", level);
//if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
/// <summary> // CrestronConsole.PrintLine("Error saving console debug level setting: {0}", err);
/// Logs to Console when at-level, and all messages to error log, including device key }
/// </summary> }
public static void Console(uint level, IKeyed dev, string format, params object[] items)
{ /// <summary>
if (Level >= level) ///
Console(level, "[{0}] {1}", dev.Key, string.Format(format, items)); /// </summary>
} public static void ShowDebugLog(string s)
{
public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel, var loglist = CrestronLogger.PrintTheLog(s.ToLower() == "all");
string format, params object[] items) foreach (var l in loglist)
{ CrestronConsole.ConsoleCommandResponse(l + CrestronEnvironment.NewLine);
var str = string.Format("[{0}] {1}", dev.Key, string.Format(format, items)); }
LogError(errorLogLevel, str);
if (Level >= level) /// <summary>
{ /// Prints message to console if current debug level is equal to or higher than the level of this message.
Console(level, str); /// Uses CrestronConsole.PrintLine.
} /// </summary>
} /// <param name="level"></param>
/// <param name="format">Console format string</param>
/// <summary> /// <param name="items">Object parameters</param>
/// Logs to Console when at-level, and all messages to error log public static void Console(uint level, string format, params object[] items)
/// </summary> {
public static void Console(uint level, ErrorLogLevel errorLogLevel, if (Level >= level)
string format, params object[] items) CrestronConsole.PrintLine("[{0}]App {1}:{2}", DateTime.Now.ToString("HH:mm:ss.fff"), InitialParametersClass.ApplicationNumber,
{ string.Format(format, items));
var str = string.Format(format, items); }
LogError(errorLogLevel, str);
if (Level >= level) /// <summary>
{ /// Logs to Console when at-level, and all messages to error log, including device key
Console(level, str); /// </summary>
} public static void Console(uint level, IKeyed dev, string format, params object[] items)
} {
if (Level >= level)
/// <summary> Console(level, "[{0}] {1}", dev.Key, string.Format(format, items));
/// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at }
/// or above the level provided, then the output will be written to both console and the log. Otherwise
/// it will only be written to the log. public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel,
/// </summary> string format, params object[] items)
/// <param name="level"></param> {
/// <param name="format"></param> var str = string.Format("[{0}] {1}", dev.Key, string.Format(format, items));
/// <param name="items"></param> LogError(errorLogLevel, str);
public static void ConsoleWithLog(uint level, string format, params object[] items) if (Level >= level)
{ {
var str = string.Format(format, items); Console(level, str);
if (Level >= level) }
CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber, str); }
CrestronLogger.WriteToLog(str, level);
} /// <summary>
/// Logs to Console when at-level, and all messages to error log
/// <summary> /// </summary>
/// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at public static void Console(uint level, ErrorLogLevel errorLogLevel,
/// or above the level provided, then the output will be written to both console and the log. Otherwise string format, params object[] items)
/// it will only be written to the log. {
/// </summary> var str = string.Format(format, items);
/// <param name="level"></param> LogError(errorLogLevel, str);
/// <param name="dev"></param> if (Level >= level)
/// <param name="format">String.format string</param> {
/// <param name="items">Parameters for substitution in the format string.</param> Console(level, str);
public static void ConsoleWithLog(uint level, IKeyed dev, string format, params object[] items) }
{ }
var str = string.Format(format, items);
if (Level >= level) /// <summary>
ConsoleWithLog(level, "[{0}] {1}", dev.Key, str); /// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at
} /// or above the level provided, then the output will be written to both console and the log. Otherwise
/// it will only be written to the log.
/// <summary> /// </summary>
/// Prints to log and error log /// <param name="level"></param>
/// </summary> /// <param name="format"></param>
/// <param name="errorLogLevel"></param> /// <param name="items"></param>
/// <param name="str"></param> public static void ConsoleWithLog(uint level, string format, params object[] items)
public static void LogError(ErrorLogLevel errorLogLevel, string str) {
{ var str = string.Format(format, items);
string msg = string.Format("App {0}:{1}", InitialParametersClass.ApplicationNumber, str); if (Level >= level)
switch (errorLogLevel) CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
{ CrestronLogger.WriteToLog(str, level);
case ErrorLogLevel.Error: }
ErrorLog.Error(msg);
break; /// <summary>
case ErrorLogLevel.Warning: /// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at
ErrorLog.Warn(msg); /// or above the level provided, then the output will be written to both console and the log. Otherwise
break; /// it will only be written to the log.
case ErrorLogLevel.Notice: /// </summary>
ErrorLog.Notice(msg); /// <param name="level"></param>
break; /// <param name="dev"></param>
} /// <param name="format">String.format string</param>
} /// <param name="items">Parameters for substitution in the format string.</param>
public static void ConsoleWithLog(uint level, IKeyed dev, string format, params object[] items)
/// <summary> {
/// Writes the memory object after timeout var str = string.Format(format, items);
/// </summary> if (Level >= level)
static void SaveMemoryOnTimeout() ConsoleWithLog(level, "[{0}] {1}", dev.Key, str);
{ }
if (SaveTimer == null)
SaveTimer = new CTimer(o => /// <summary>
{ /// Prints to log and error log
SaveTimer = null; /// </summary>
SaveMemory(); /// <param name="errorLogLevel"></param>
}, SaveTimeoutMs); /// <param name="str"></param>
else public static void LogError(ErrorLogLevel errorLogLevel, string str)
SaveTimer.Reset(SaveTimeoutMs); {
} string msg = string.Format("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
switch (errorLogLevel)
/// <summary> {
/// Writes the memory - use SaveMemoryOnTimeout case ErrorLogLevel.Error:
/// </summary> ErrorLog.Error(msg);
static void SaveMemory() break;
{ case ErrorLogLevel.Warning:
//var dir = @"\NVRAM\debug"; ErrorLog.Warn(msg);
//if (!Directory.Exists(dir)) break;
// Directory.Create(dir); case ErrorLogLevel.Notice:
ErrorLog.Notice(msg);
using (StreamWriter sw = new StreamWriter(GetMemoryFileName())) break;
{ }
var json = JsonConvert.SerializeObject(Contexts); }
sw.Write(json);
sw.Flush(); /// <summary>
} /// Writes the memory object after timeout
} /// </summary>
static void SaveMemoryOnTimeout()
/// <summary> {
/// if (SaveTimer == null)
/// </summary> SaveTimer = new CTimer(o =>
static void LoadMemory() {
{ SaveTimer = null;
var file = GetMemoryFileName(); SaveMemory();
if (File.Exists(file)) }, SaveTimeoutMs);
{ else
using (StreamReader sr = new StreamReader(file)) SaveTimer.Reset(SaveTimeoutMs);
{ }
var json = sr.ReadToEnd();
Contexts = JsonConvert.DeserializeObject<DebugContextCollection>(json); /// <summary>
/// Writes the memory - use SaveMemoryOnTimeout
if (Contexts != null) /// </summary>
{ static void SaveMemory()
Debug.Console(1, "Debug memory restored from file"); {
return; //var dir = @"\NVRAM\debug";
} //if (!Directory.Exists(dir))
} // Directory.Create(dir);
}
using (StreamWriter sw = new StreamWriter(GetMemoryFileName()))
Contexts = new DebugContextCollection(); {
} var json = JsonConvert.SerializeObject(Contexts);
sw.Write(json);
/// <summary> sw.Flush();
/// Helper to get the file path for this app's debug memory }
/// </summary> }
static string GetMemoryFileName()
{ /// <summary>
return string.Format(@"\NVRAM\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); ///
} /// </summary>
static void LoadMemory()
public enum ErrorLogLevel {
{ var file = GetMemoryFileName();
Error, Warning, Notice, None if (File.Exists(file))
} {
} using (StreamReader sr = new StreamReader(file))
{
var json = sr.ReadToEnd();
Contexts = JsonConvert.DeserializeObject<DebugContextCollection>(json);
if (Contexts != null)
{
Debug.Console(1, "Debug memory restored from file");
return;
}
}
}
Contexts = new DebugContextCollection();
}
/// <summary>
/// Helper to get the file path for this app's debug memory
/// </summary>
static string GetMemoryFileName()
{
return string.Format(@"\NVRAM\debugSettings\program{0}", InitialParametersClass.ApplicationNumber);
}
public enum ErrorLogLevel
{
Error, Warning, Notice, None
}
}
} }

View File

@@ -6,10 +6,14 @@ using Crestron.SimplSharp;
namespace PepperDash.Core namespace PepperDash.Core
{ {
/// <summary>
/// Not in use
/// </summary>
public static class NetworkComm public static class NetworkComm
{ {
/// <summary>
/// Not in use
/// </summary>
static NetworkComm() static NetworkComm()
{ {
} }

View File

@@ -31,6 +31,7 @@
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<NoStdLib>true</NoStdLib> <NoStdLib>true</NoStdLib>
<NoConfig>true</NoConfig> <NoConfig>true</NoConfig>
<DocumentationFile>bin\PepperDash_Core.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<AllowedReferenceRelatedFileExtensions>.allowedReferenceRelatedFileExtensions</AllowedReferenceRelatedFileExtensions> <AllowedReferenceRelatedFileExtensions>.allowedReferenceRelatedFileExtensions</AllowedReferenceRelatedFileExtensions>
@@ -42,6 +43,7 @@
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<NoStdLib>true</NoStdLib> <NoStdLib>true</NoStdLib>
<NoConfig>true</NoConfig> <NoConfig>true</NoConfig>
<DocumentationFile>bin\PepperDash_Core.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="mscorlib" /> <Reference Include="mscorlib" />
@@ -57,6 +59,10 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpNewtonsoft.dll</HintPath> <HintPath>..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpNewtonsoft.dll</HintPath>
</Reference> </Reference>
<Reference Include="SimplSharpReflectionInterface, Version=1.0.5583.25238, Culture=neutral, PublicKeyToken=1099c178b3b54c3b, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpReflectionInterface.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
@@ -76,6 +82,7 @@
<Compile Include="Comm\EventArgs.cs" /> <Compile Include="Comm\EventArgs.cs" />
<Compile Include="Comm\GenericSshClient.cs" /> <Compile Include="Comm\GenericSshClient.cs" />
<Compile Include="Comm\GenericUdpServer.cs" /> <Compile Include="Comm\GenericUdpServer.cs" />
<Compile Include="Comm\TcpClientConfigObject.cs" />
<Compile Include="Comm\TcpServerConfigObject.cs" /> <Compile Include="Comm\TcpServerConfigObject.cs" />
<Compile Include="Config\PortalConfigReader.cs" /> <Compile Include="Config\PortalConfigReader.cs" />
<Compile Include="CoreInterfaces.cs" /> <Compile Include="CoreInterfaces.cs" />

View File

@@ -4,4 +4,4 @@
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Pepperdash_Core")] [assembly: AssemblyProduct("Pepperdash_Core")]
[assembly: AssemblyCopyright("Copyright © PepperDash 2019")] [assembly: AssemblyCopyright("Copyright © PepperDash 2019")]
[assembly: AssemblyVersion("1.0.14.*")] [assembly: AssemblyVersion("1.0.23.*")]

View File

@@ -0,0 +1,35 @@
function Update-SourceVersion
{
Param ([string]$Version)
$NewVersion = AssemblyVersion(" + $Version + .*");
foreach ($o in $input)
{
Write-output $o.FullName
$TmpFile = $o.FullName + .tmp
get-content $o.FullName |
%{$_ -replace AssemblyVersion\("(\d+\.\d+\.\d+)\.\*"\), $NewVersion } > $TmpFile
move-item $TmpFile $o.FullName -force
}
}
function Update-AllAssemblyInfoFiles ( $version )
{
foreach ($file in AssemblyInfo.cs, AssemblyInfo.vb )
{
get-childitem -recurse |? {$_.Name -eq $file} | Update-SourceVersion $version ;
}
}
# validate arguments
$r= [System.Text.RegularExpressions.Regex]::Match($args[0], "^\d+\.\d+\.\d+$");
if ($r.Success)
{
Update-AllAssemblyInfoFiles $args[0];
}
else
{
echo ;
echo Error: Input version does not match x.y.z format!
echo ;
echo "Unable to apply version to AssemblyInfo.cs files";
}

17
Readme.md Normal file
View File

@@ -0,0 +1,17 @@
# Pepperdash Core
#### Workflow process
- Create a Jira issue for the feature/bugfix. If you know you're targeting this update to coincide with a new release of Core, specify that release (or create a new one) as the Fix Version for the Jira issue
- Branch from development using the syntax [feature/bugfix]/[pdc-x] (where x is the Jira issue number)
- Modify code to suit and test. Make commits to the branch as you work.
- Log a Pull Request on www.bitbucket.org and tag Heath and Neil as reviewers
#### Pull Request process
- Check out the branch for the PR and review.
- If necessary, merge the latest Development branch into the PR branch. Resolve any conflicts.
- Increment the appropriate Assembly version number to match the next release in Jira
- Build the project
- Copy PepperDash_Core.cpz and PepperDash_Core.dll from the bin folder to the CLZ Builds folder in the repo root. Commit.
- Merge the PR in Bitbucket