diff --git a/CLZ Builds/PepperDash_Core.clz b/CLZ Builds/PepperDash_Core.clz
deleted file mode 100644
index ed8a694..0000000
Binary files a/CLZ Builds/PepperDash_Core.clz and /dev/null differ
diff --git a/CLZ Builds/PepperDash_Core.dll b/CLZ Builds/PepperDash_Core.dll
deleted file mode 100644
index 3e798d7..0000000
Binary files a/CLZ Builds/PepperDash_Core.dll and /dev/null differ
diff --git a/Pepperdash Core/Pepperdash Core/Comm/EventArgs.cs b/Pepperdash Core/Pepperdash Core/Comm/EventArgs.cs
index ed43c62..e9e9f1f 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/EventArgs.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/EventArgs.cs
@@ -23,12 +23,14 @@ namespace PepperDash.Core
{
public ISocketStatus Client { get; private set; }
- public GenericSocketStatusChageEventArgs() { }
-
public GenericSocketStatusChageEventArgs(ISocketStatus client)
{
Client = client;
}
+ ///
+ /// Stupid S+ Constructor
+ ///
+ public GenericSocketStatusChageEventArgs() { }
}
public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state);
@@ -36,12 +38,14 @@ namespace PepperDash.Core
{
public ServerState State { get; private set; }
- public GenericTcpServerStateChangedEventArgs() { }
-
public GenericTcpServerStateChangedEventArgs(ServerState state)
{
State = state;
}
+ ///
+ /// Stupid S+ Constructor
+ ///
+ public GenericTcpServerStateChangedEventArgs() { }
}
public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus);
@@ -50,8 +54,6 @@ namespace PepperDash.Core
public object Socket { get; private set; }
public uint ReceivedFromClientIndex { get; private set; }
public SocketStatus ClientStatus { get; set; }
-
- public GenericTcpServerSocketStatusChangeEventArgs() { }
public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
{
@@ -65,6 +67,10 @@ namespace PepperDash.Core
ReceivedFromClientIndex = clientIndex;
ClientStatus = clientStatus;
}
+ ///
+ /// Stupid S+ Constructor
+ ///
+ public GenericTcpServerSocketStatusChangeEventArgs() { }
}
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
@@ -82,6 +88,10 @@ namespace PepperDash.Core
Text = text;
ReceivedFromClientIndex = clientIndex;
}
+ ///
+ /// Stupid S+ Constructor
+ ///
+ public GenericTcpServerCommMethodReceiveTextArgs() { }
}
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
@@ -91,6 +101,29 @@ namespace PepperDash.Core
{
IsReady = isReady;
}
+ ///
+ /// Stupid S+ Constructor
+ ///
+ 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;
+ }
+
}
diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs
index 13e1c66..c3882ca 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs
@@ -33,6 +33,15 @@ namespace PepperDash.Core
public event EventHandler TextReceived;
+ public event EventHandler AutoReconnectTriggered;
+
+ ///
+ /// 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.
+ ///
+ public event EventHandler TextReceivedQueueInvoke;
+
+
public event EventHandler ConnectionChange;
@@ -209,8 +218,20 @@ namespace PepperDash.Core
get { return (ushort)(HeartbeatEnabled ? 1 : 0); }
set { HeartbeatEnabled = value == 1; }
}
- public string HeartbeatString = "heartbeat";
- public int HeartbeatInterval = 50000;
+
+ public string HeartbeatString { get; set; }
+ //public int HeartbeatInterval = 50000;
+
+ ///
+ /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
+ ///
+ public int HeartbeatInterval { get; set; }
+
+ ///
+ /// Simpl+ Heartbeat Analog value in seconds
+ ///
+ public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
+
CTimer HeartbeatSendTimer;
CTimer HeartbeatAckTimer;
///
@@ -226,6 +247,24 @@ namespace PepperDash.Core
bool ProgramIsStopping;
+ ///
+ /// Queue lock
+ ///
+ CCriticalSection DequeueLock = new CCriticalSection();
+
+ ///
+ /// 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.
+ ///
+ public int ReceiveQueueSize { get; set; }
+
+ ///
+ /// 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.
+ ///
+ private CrestronQueue MessageQueue;
+
+
#endregion
#region Constructors
@@ -244,12 +283,24 @@ namespace PepperDash.Core
//base class constructor
public GenericSecureTcpIpClient_ForServer()
- : base("Uninitialized DynamicTcpClient")
+ : base("Uninitialized Secure Tcp Client For Server")
{
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
BufferSize = 2000;
}
+
+ ///
+ /// Contstructor that sets all properties by calling the initialize method with a config object.
+ ///
+ ///
+ public GenericSecureTcpIpClient_ForServer(string key, TcpClientConfigObject clientConfigObject)
+ : base(key)
+ {
+ CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
+ Initialize(clientConfigObject);
+ }
+
#endregion
#region Methods
@@ -262,6 +313,43 @@ namespace PepperDash.Core
Key = key;
}
+ ///
+ /// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client.
+ ///
+ ///
+ 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(ReceiveQueueSize);
+ }
+ else
+ {
+ ErrorLog.Error("Could not initialize client with key: {0}", Key);
+ }
+ }
+ catch
+ {
+ ErrorLog.Error("Could not initialize client with key: {0}", Key);
+ }
+ }
+
///
/// Handles closing this up when the program shuts down
///
@@ -328,7 +416,8 @@ namespace PepperDash.Core
Client = new SecureTCPClient(Hostname, Port, BufferSize);
Client.SocketStatusChange += Client_SocketStatusChange;
- Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5);
+ if (HeartbeatEnabled)
+ Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5);
Client.AddressClientConnectedTo = Hostname;
Client.PortNumber = Port;
// SecureClient = c;
@@ -381,6 +470,15 @@ namespace PepperDash.Core
//OnClientReadyForcommunications(false); // Should send false event
}, 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
{
@@ -463,6 +561,8 @@ namespace PepperDash.Core
RetryTimer.Stop();
RetryTimer = null;
}
+ if(AutoReconnectTriggered != null)
+ AutoReconnectTriggered(this, new EventArgs());
RetryTimer = new CTimer(o => Connect(), rndTime);
}
}
@@ -477,7 +577,7 @@ namespace PepperDash.Core
if (numBytes > 0)
{
string str = string.Empty;
-
+ var handler = TextReceivedQueueInvoke;
try
{
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");
OnClientReadyForcommunications(true); // Successful key exchange
}
- else if (SharedKeyRequired == false && IsReadyForCommunication == false)
- {
- OnClientReadyForcommunications(true); // Key not required
- }
-
else
{
-
//var bytesHandler = BytesReceived;
//if (bytesHandler != null)
// bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
var textHandler = TextReceived;
if (textHandler != null)
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);
}
+ 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();
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ 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()
diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs
index 58f46c8..3eb325a 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs
@@ -28,6 +28,12 @@ namespace PepperDash.Core
///
public event EventHandler TextReceived;
+ ///
+ /// 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.
+ ///
+ public event EventHandler TextReceivedQueueInvoke;
+
///
/// Event for client connection socket status change
///
@@ -56,10 +62,26 @@ namespace PepperDash.Core
#region Properties/Variables
///
- ///
+ /// Server listen lock
///
CCriticalSection ServerCCSection = new CCriticalSection();
+ ///
+ /// Queue lock
+ ///
+ CCriticalSection DequeueLock = new CCriticalSection();
+
+ ///
+ /// 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.
+ ///
+ public int ReceiveQueueSize { get; set; }
+
+ ///
+ /// 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.
+ ///
+ private CrestronQueue MessageQueue;
///
/// A bandaid client that monitors whether the server is reachable
@@ -145,7 +167,9 @@ namespace PepperDash.Core
{
get { return (ushort)(IsListening ? 1 : 0); }
}
-
+ ///
+ /// Max number of clients this server will allow for connection. Crestron max is 64. This number should be less than 65
+ ///
public ushort MaxClients { get; set; } // should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable
///
/// 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.
///
public GenericSecureTcpIpServer()
- : base("Uninitialized Dynamic TCP Server")
+ : base("Uninitialized Secure TCP Server")
{
HeartbeatRequiredIntervalInSeconds = 15;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
@@ -282,7 +306,7 @@ namespace PepperDash.Core
///
///
public GenericSecureTcpIpServer(string key)
- : base("Uninitialized Dynamic TCP Server")
+ : base("Uninitialized Secure TCP Server")
{
HeartbeatRequiredIntervalInSeconds = 15;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
@@ -292,11 +316,11 @@ namespace PepperDash.Core
}
///
- /// 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.
///
///
public GenericSecureTcpIpServer(TcpServerConfigObject serverConfigObject)
- : base("Uninitialized Dynamic TCP Server")
+ : base("Uninitialized Secure TCP Server")
{
HeartbeatRequiredIntervalInSeconds = 15;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
@@ -345,7 +369,8 @@ namespace PepperDash.Core
HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds;
HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch;
BufferSize = serverConfigObject.BufferSize;
-
+ ReceiveQueueSize = serverConfigObject.ReceiveQueueSize > 20 ? serverConfigObject.ReceiveQueueSize : 20;
+ MessageQueue = new CrestronQueue(ReceiveQueueSize);
}
else
{
@@ -384,13 +409,14 @@ namespace PepperDash.Core
if (SecureServer == null)
{
SecureServer = new SecureTCPServer(Port, MaxClients);
- SecureServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5);
+ if(HeartbeatRequired)
+ SecureServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5);
SecureServer.HandshakeTimeout = 30;
SecureServer.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SecureServer_SocketStatusChange);
}
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;
}
ServerStopped = false;
@@ -415,25 +441,21 @@ namespace PepperDash.Core
///
public void StopListening()
{
- try
- {
- Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
- if (SecureServer != null)
- {
- SecureServer.Stop();
- Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", SecureServer.State);
- //SecureServer = null;
- }
-
- ServerStopped = true;
- Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server Stopped");
-
- OnServerStateChange(SecureServer.State);
- }
- catch (Exception ex)
- {
- Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
- }
+ try
+ {
+ Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
+ if (SecureServer != null)
+ {
+ SecureServer.Stop();
+ Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", SecureServer.State);
+ OnServerStateChange(SecureServer.State);
+ }
+ ServerStopped = true;
+ }
+ catch (Exception ex)
+ {
+ Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
+ }
}
///
@@ -445,11 +467,11 @@ namespace PepperDash.Core
try
{
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)
{
- 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);
}
}
///
@@ -469,17 +491,17 @@ namespace PepperDash.Core
try
{
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)
{
- 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();
if (!ProgramIsStopping)
@@ -510,7 +532,7 @@ namespace PepperDash.Core
{
SocketErrorCodes error = SecureServer.SendDataAsync(i, b, b.Length, (x, y, z) => { });
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)
{
- 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))
ClientReadyAfterKeyExchange.Remove(clientIndex);
+ if (WaitingForSharedKey.Contains(clientIndex))
+ WaitingForSharedKey.Remove(clientIndex);
}
}
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
@@ -739,7 +766,7 @@ namespace PepperDash.Core
}
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}))))))",
// server.State,
@@ -767,6 +794,7 @@ namespace PepperDash.Core
if (numberOfBytesReceived > 0)
{
string received = "Nothing";
+ var handler = TextReceivedQueueInvoke;
try
{
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);
mySecureTCPServer.SendData(clientIndex, b, b.Length);
mySecureTCPServer.Disconnect(clientIndex);
- WaitingForSharedKey.Remove(clientIndex);
+
return;
}
- if (mySecureTCPServer.NumberOfClientsConnected > 0)
- mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
+
WaitingForSharedKey.Remove(clientIndex);
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
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;
+ Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", 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",
- // mySecureTCPServer.PortNumber.ToString(), address , numberOfBytesReceived.ToString(), received, clientIndex.ToString());
- if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
+ else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
+ {
onTextReceived(received, clientIndex);
+ if (handler != null)
+ {
+ MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(received, clientIndex));
+ }
+ }
}
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)
- mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
+ else
+ {
+ mySecureTCPServer.Disconnect(clientIndex);
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ 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
@@ -965,13 +1036,13 @@ namespace PepperDash.Core
StopMonitorClient();
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);
StartMonitorClient();
}
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***************************",
MonitorClientMaxFailureCount);
diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs
index 3d1101a..6af5d7b 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs
@@ -174,8 +174,8 @@ namespace PepperDash.Core
if (Client != null)
{
Debug.Console(1, this, "Program stopping. Closing connection");
- Client.Disconnect();
- Client.Dispose();
+ Disconnect();
+ //Client.Dispose();
}
}
}
diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs
index 65c428f..525f32b 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs
@@ -29,10 +29,27 @@ namespace PepperDash.Core
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
public event EventHandler ConnectionChange;
+
+ private string _Hostname { get; set;}
///
/// Address of server
///
- public string Hostname { get; set; }
+ public string Hostname {
+ get
+ {
+ return _Hostname;
+ }
+
+ set
+ {
+ _Hostname = value;
+ if (Client != null)
+ {
+
+ Client.AddressClientConnectedTo = _Hostname;
+ }
+ }
+ }
///
/// Port on server
@@ -233,10 +250,13 @@ namespace PepperDash.Core
if (Client == null)
{
+
+
Client = new TCPClient(Hostname, Port, BufferSize);
Client.SocketStatusChange += Client_SocketStatusChange;
}
DisconnectCalledByUser = false;
+
Client.ConnectToServerAsync(ConnectToServerCallback); // (null);
}
@@ -352,7 +372,6 @@ namespace PepperDash.Core
}
}
-
public class TcpSshPropertiesConfig
{
[JsonProperty(Required = Required.Always)]
diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient_ForServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient_ForServer.cs
index 820f5bb..ec59259 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient_ForServer.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient_ForServer.cs
@@ -328,7 +328,8 @@ namespace PepperDash.Core
Client = new TCPClient(Hostname, Port, BufferSize);
Client.SocketStatusChange += Client_SocketStatusChange;
- Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5);
+ if(HeartbeatEnabled)
+ Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5);
Client.AddressClientConnectedTo = Hostname;
Client.PortNumber = Port;
// SecureClient = c;
@@ -381,6 +382,15 @@ namespace PepperDash.Core
//OnClientReadyForcommunications(false); // Should send false event
}, 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
{
@@ -485,7 +495,6 @@ namespace PepperDash.Core
Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str);
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
{
-
if (SharedKeyRequired && str == "SharedKey:")
{
Debug.Console(1, this, "Server asking for shared key, sending");
@@ -494,19 +503,11 @@ namespace PepperDash.Core
else if (SharedKeyRequired && str == "Shared Key Match")
{
StopWaitForSharedKeyTimer();
-
-
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Shared key confirmed. Ready for communication");
OnClientReadyForcommunications(true); // Successful key exchange
}
- else if (SharedKeyRequired == false && IsReadyForCommunication == false)
- {
- OnClientReadyForcommunications(true); // Key not required
- }
-
else
{
-
//var bytesHandler = BytesReceived;
//if (bytesHandler != null)
// 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));
OnConnectionChange();
+
// The client could be null or disposed by this time...
if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{
diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpServer.cs
index 2ef2845..443a69a 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpServer.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpServer.cs
@@ -384,16 +384,20 @@ namespace PepperDash.Core
if (myTcpServer == null)
{
myTcpServer = new TCPServer(Port, MaxClients);
- myTcpServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5);
+ if(HeartbeatRequired)
+ myTcpServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5);
// myTcpServer.HandshakeTimeout = 30;
- myTcpServer.SocketStatusChange += new TCPServerSocketStatusChangeEventHandler(TcpServer_SocketStatusChange);
}
else
{
KillServer();
myTcpServer.PortNumber = Port;
}
+
+ myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange;
+ myTcpServer.SocketStatusChange += TcpServer_SocketStatusChange;
+
ServerStopped = false;
myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
OnServerStateChange(myTcpServer.State);
@@ -418,22 +422,18 @@ namespace PepperDash.Core
{
try
{
- Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
+ Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
if (myTcpServer != null)
{
myTcpServer.Stop();
- Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", myTcpServer.State);
- //SecureServer = null;
+ Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", myTcpServer.State);
+ OnServerStateChange(myTcpServer.State);
}
-
- ServerStopped = true;
- Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Server Stopped");
-
- OnServerStateChange(myTcpServer.State);
+ ServerStopped = true;
}
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
{
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)
{
- 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);
}
}
///
@@ -470,17 +470,17 @@ namespace PepperDash.Core
try
{
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)
{
- 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();
if (!ProgramIsStopping)
@@ -511,7 +511,7 @@ namespace PepperDash.Core
{
SocketErrorCodes error = myTcpServer.SendDataAsync(i, b, b.Length, (x, y, z) => { });
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)
{
- 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))
ClientReadyAfterKeyExchange.Remove(clientIndex);
+ if (WaitingForSharedKey.Contains(clientIndex))
+ WaitingForSharedKey.Remove(clientIndex);
}
}
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));
}
@@ -740,7 +742,7 @@ namespace PepperDash.Core
}
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}))))))",
// server.State,
@@ -763,50 +765,51 @@ namespace PepperDash.Core
///
///
///
- void TcpServerReceivedDataAsyncCallback(TCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived)
+ void TcpServerReceivedDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesReceived)
{
- if (numberOfBytesReceived > 0)
- {
- string received = "Nothing";
- try
- {
- byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
- received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
- if (WaitingForSharedKey.Contains(clientIndex))
- {
- received = received.Replace("\r", "");
- received = received.Replace("\n", "");
- if (received != SharedKey)
- {
- 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);
- mySecureTCPServer.SendData(clientIndex, b, b.Length);
- mySecureTCPServer.Disconnect(clientIndex);
- WaitingForSharedKey.Remove(clientIndex);
- return;
- }
- if (mySecureTCPServer.NumberOfClientsConnected > 0)
- mySecureTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
- WaitingForSharedKey.Remove(clientIndex);
- byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
- mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
- 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;
- }
- //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",
- // mySecureTCPServer.PortNumber.ToString(), address , numberOfBytesReceived.ToString(), received, clientIndex.ToString());
- if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
- onTextReceived(received, clientIndex);
- }
- catch (Exception ex)
- {
- Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
- }
- }
- if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
- mySecureTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
+ if (numberOfBytesReceived > 0)
+ {
+ string received = "Nothing";
+ try
+ {
+ byte[] bytes = myTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex);
+ received = System.Text.Encoding.GetEncoding(28591).GetString(bytes, 0, numberOfBytesReceived);
+ if (WaitingForSharedKey.Contains(clientIndex))
+ {
+ received = received.Replace("\r", "");
+ received = received.Replace("\n", "");
+ if (received != SharedKey)
+ {
+ 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);
+ myTCPServer.SendData(clientIndex, b, b.Length);
+ myTCPServer.Disconnect(clientIndex);
+ return;
+ }
+
+ WaitingForSharedKey.Remove(clientIndex);
+ byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
+ myTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
+ OnServerClientReadyForCommunications(clientIndex);
+ Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
+ }
+
+ else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
+ onTextReceived(received, clientIndex);
+ }
+ catch (Exception ex)
+ {
+ Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
+ }
+ if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
+ myTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
+ }
+ else
+ {
+ // If numberOfBytesReceived <= 0
+ myTCPServer.Disconnect();
+ }
+
}
#endregion
@@ -966,13 +969,13 @@ namespace PepperDash.Core
StopMonitorClient();
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);
StartMonitorClient();
}
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***************************",
MonitorClientMaxFailureCount);
diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs
index a67a05f..011c17c 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs
@@ -1,231 +1,351 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
-
-using Crestron.SimplSharp;
-using Crestron.SimplSharp.CrestronSockets;
-
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-
-namespace PepperDash.Core
-{
- public class GenericUdpServer : Device, IBasicCommunication
- {
- ///
- ///
- ///
- public event EventHandler BytesReceived;
-
- ///
- ///
- ///
- public event EventHandler TextReceived;
-
- ///
- ///
- ///
- //public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
- public event EventHandler ConnectionChange;
-
- public SocketStatus ClientStatus
- {
- get
- {
- return Server.ServerStatus;
- }
- }
-
- ///
- /// Address of server
- ///
- public string Hostname { get; set; }
-
- ///
- /// Port on server
- ///
- public int Port { get; set; }
-
- ///
- /// Another damn S+ helper because S+ seems to treat large port nums as signed ints
- /// which screws up things
- ///
- public ushort UPort
- {
- get { return Convert.ToUInt16(Port); }
- set { Port = Convert.ToInt32(value); }
- }
-
- ///
- /// Indicates that the UDP Server is enabled
- ///
- public bool IsConnected
- {
- get;
- private set;
- }
-
- ///
- /// Defaults to 2000
- ///
- public int BufferSize { get; set; }
-
- public UDPServer Server { get; private set; }
-
- public GenericUdpServer(string key, string address, int port, int buffefSize)
- : base(key)
- {
- Hostname = address;
- Port = port;
- BufferSize = buffefSize;
-
- CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
- CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
- }
-
- void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
- {
- // Re-enable the server if the link comes back up and the status should be connected
- if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp
- && IsConnected)
- {
- Connect();
- }
- }
-
- void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
- {
- if (programEventType == eProgramStatusEventType.Stopping)
- {
- Debug.Console(1, this, "Program stopping. Disabling Server");
- Disconnect();
- }
- }
-
- ///
- /// Enables the UDP Server
- ///
- public void Connect()
- {
- if (Server == null)
- {
- Server = new UDPServer();
-
- }
-
- if (string.IsNullOrEmpty(Hostname))
- {
- Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key);
- return;
- }
- if (Port < 1 || Port > 65535)
- {
- {
- Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key);
- return;
- }
- }
-
- var status = Server.EnableUDPServer(Hostname, Port);
-
- Debug.Console(2, this, "SocketErrorCode: {0}", status);
- if (status == SocketErrorCodes.SOCKET_OK)
- IsConnected = true;
-
- // Start receiving data
- Server.ReceiveDataAsync(Receive);
- }
-
- ///
- /// Disabled the UDP Server
- ///
- public void Disconnect()
- {
- if(Server != null)
- Server.DisableUDPServer();
-
- IsConnected = false;
- }
-
-
- ///
- /// Recursive method to receive data
- ///
- ///
- ///
- void Receive(UDPServer server, int numBytes)
- {
- Debug.Console(2, this, "Received {0} bytes", numBytes);
-
- if (numBytes > 0)
- {
- var bytes = server.IncomingDataBuffer.Take(numBytes).ToArray();
-
- 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)
- {
- var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
- Debug.Console(2, this, "RX: {0}", str);
- textHandler(this, new GenericCommMethodReceiveTextArgs(str));
- }
- else
- Debug.Console(2, this, "textHandler is null");
- }
- server.ReceiveDataAsync(Receive);
- }
-
- ///
- /// General send method
- ///
- ///
- 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 UdpServerPropertiesConfig
- {
- [JsonProperty(Required = Required.Always)]
- public string Address { get; set; }
-
- [JsonProperty(Required = Required.Always)]
- public int Port { get; set; }
-
- ///
- /// Defaults to 32768
- ///
- public int BufferSize { get; set; }
-
- public UdpServerPropertiesConfig()
- {
- BufferSize = 32768;
- }
- }
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+using Crestron.SimplSharp;
+using Crestron.SimplSharp.CrestronSockets;
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+
+
+
+namespace PepperDash.Core
+{
+ public class GenericUdpServer : Device, IBasicCommunication
+ {
+ ///
+ ///
+ ///
+ public event EventHandler BytesReceived;
+
+ ///
+ ///
+ ///
+ public event EventHandler TextReceived;
+
+ ///
+ /// 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 DataRecievedExtra;
+
+ ///
+ /// Queue to temporarily store received messages with the source IP and Port info
+ ///
+ private CrestronQueue MessageQueue;
+
+ ///
+ ///
+ ///
+ //public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
+ public event EventHandler ConnectionChange;
+
+ public event EventHandler UpdateConnectionStatus;
+
+ public SocketStatus ClientStatus
+ {
+ get
+ {
+ return Server.ServerStatus;
+ }
+ }
+
+ public ushort UStatus
+ {
+ get { return (ushort)Server.ServerStatus; }
+ }
+
+
+ CCriticalSection DequeueLock;
+ ///
+ /// Address of server
+ ///
+ public string Hostname { get; set; }
+
+ ///
+ /// IP Address of the sender of the last recieved message
+ ///
+
+
+ ///
+ /// Port on server
+ ///
+ public int Port { get; set; }
+
+ ///
+ /// Another damn S+ helper because S+ seems to treat large port nums as signed ints
+ /// which screws up things
+ ///
+ public ushort UPort
+ {
+ get { return Convert.ToUInt16(Port); }
+ set { Port = Convert.ToInt32(value); }
+ }
+
+ ///
+ /// Indicates that the UDP Server is enabled
+ ///
+ public bool IsConnected
+ {
+ get;
+ private set;
+ }
+
+ public ushort UIsConnected
+ {
+ get { return IsConnected ? (ushort)1 : (ushort)0; }
+ }
+
+ ///
+ /// Defaults to 2000
+ ///
+ public int BufferSize { get; set; }
+
+ public UDPServer Server { get; private set; }
+
+ ///
+ /// Constructor for S+. Make sure to set key, address, port, and buffersize using init method
+ ///
+ public GenericUdpServer()
+ : base("Uninitialized Udp Server")
+ {
+ BufferSize = 5000;
+ DequeueLock = new CCriticalSection();
+ MessageQueue = new CrestronQueue();
+
+ CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
+ CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
+ }
+
+ public GenericUdpServer(string key, string address, int port, int buffefSize)
+ : base(key)
+ {
+ Hostname = address;
+ Port = port;
+ BufferSize = buffefSize;
+
+ DequeueLock = new CCriticalSection();
+ MessageQueue = new CrestronQueue();
+
+ CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
+ CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
+ }
+
+ public void Initialize(string key, string address, ushort port)
+ {
+ Key = key;
+ Hostname = address;
+ UPort = port;
+ }
+
+ void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
+ {
+ // Re-enable the server if the link comes back up and the status should be connected
+ if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp
+ && IsConnected)
+ {
+ Connect();
+ }
+ }
+
+ void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
+ {
+ if (programEventType == eProgramStatusEventType.Stopping)
+ {
+ Debug.Console(1, this, "Program stopping. Disabling Server");
+ Disconnect();
+ }
+ }
+
+ ///
+ /// Enables the UDP Server
+ ///
+ public void Connect()
+ {
+ if (Server == null)
+ {
+ Server = new UDPServer();
+ }
+
+ if (string.IsNullOrEmpty(Hostname))
+ {
+ Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key);
+ return;
+ }
+ if (Port < 1 || Port > 65535)
+ {
+ {
+ Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key);
+ return;
+ }
+ }
+
+ var status = Server.EnableUDPServer(Hostname, Port);
+
+ Debug.Console(2, this, "SocketErrorCode: {0}", status);
+ if (status == SocketErrorCodes.SOCKET_OK)
+ IsConnected = true;
+
+ var handler = UpdateConnectionStatus;
+ if (handler != null)
+ handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
+
+ // Start receiving data
+ Server.ReceiveDataAsync(Receive);
+ }
+
+ ///
+ /// Disabled the UDP Server
+ ///
+ public void Disconnect()
+ {
+ if(Server != null)
+ Server.DisableUDPServer();
+
+ IsConnected = false;
+
+ var handler = UpdateConnectionStatus;
+ if (handler != null)
+ handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
+ }
+
+
+ ///
+ /// Recursive method to receive data
+ ///
+ ///
+ ///
+ void Receive(UDPServer server, int numBytes)
+ {
+ Debug.Console(2, this, "Received {0} bytes", numBytes);
+
+ if (numBytes > 0)
+ {
+ var sourceIp = Server.IPAddressLastMessageReceivedFrom;
+ 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());
+ }
+
+ ///
+ /// 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.
+ ///
+ 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();
+ }
+ }
+
+ ///
+ /// General send method
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ /// Stupid S+ Constructor
+ ///
+ public GenericUdpReceiveTextExtraArgs() { }
+ }
+
+ public class UdpServerPropertiesConfig
+ {
+ [JsonProperty(Required = Required.Always)]
+ public string Address { get; set; }
+
+ [JsonProperty(Required = Required.Always)]
+ public int Port { get; set; }
+
+ ///
+ /// Defaults to 32768
+ ///
+ public int BufferSize { get; set; }
+
+ public UdpServerPropertiesConfig()
+ {
+ BufferSize = 32768;
+ }
+ }
}
\ No newline at end of file
diff --git a/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs b/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs
new file mode 100644
index 0000000..a4fe8b2
--- /dev/null
+++ b/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs
@@ -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
+{
+ ///
+ /// Client config object for TCP client with server that inherits from TcpSshPropertiesConfig and adds properties for shared key and heartbeat
+ ///
+ public class TcpClientConfigObject
+ {
+ ///
+ /// TcpSsh Properties
+ ///
+ public ControlPropertiesConfig Control { get; set; }
+ ///
+ /// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
+ ///
+ public bool Secure { get; set; }
+ ///
+ /// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
+ ///
+ public bool SharedKeyRequired { get; set; }
+
+ ///
+ /// The shared key that must match on the server and client
+ ///
+ public string SharedKey { get; set; }
+ ///
+ /// 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.
+ ///
+ public bool HeartbeatRequired { get; set; }
+ ///
+ /// The interval in seconds for the heartbeat from the client. If not received client is disconnected
+ ///
+ public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
+ ///
+ /// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
+ ///
+ public string HeartbeatStringToMatch { get; set; }
+ ///
+ /// Receive Queue size must be greater than 20 or defaults to 20
+ ///
+ public int ReceiveQueueSize { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Pepperdash Core/Pepperdash Core/Comm/TcpServerConfigObject.cs b/Pepperdash Core/Pepperdash Core/Comm/TcpServerConfigObject.cs
index c7c1998..785851b 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/TcpServerConfigObject.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/TcpServerConfigObject.cs
@@ -6,17 +6,55 @@ using Crestron.SimplSharp;
namespace PepperDash.Core
{
+ ///
+ /// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities
+ ///
public class TcpServerConfigObject
{
+ ///
+ /// Uique key
+ ///
public string Key { get; set; }
- public bool Secure { get; set; }
+ ///
+ /// Max Clients that the server will allow to connect.
+ ///
public ushort MaxClients { get; set; }
+ ///
+ /// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
+ ///
+ public bool Secure { get; set; }
+ ///
+ /// Port for the server to listen on
+ ///
public int Port { get; set; }
+ ///
+ /// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
+ ///
public bool SharedKeyRequired { get; set; }
+ ///
+ /// The shared key that must match on the server and client
+ ///
public string SharedKey { get; set; }
+ ///
+ /// 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.
+ ///
public bool HeartbeatRequired { get; set; }
+ ///
+ /// The interval in seconds for the heartbeat from the client. If not received client is disconnected
+ ///
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
+ ///
+ /// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
+ ///
public string HeartbeatStringToMatch { get; set; }
+ ///
+ /// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000
+ ///
public int BufferSize { get; set; }
+ ///
+ /// Receive Queue size must be greater than 20 or defaults to 20
+ ///
+ public int ReceiveQueueSize { get; set; }
}
}
\ No newline at end of file
diff --git a/Pepperdash Core/Pepperdash Core/Comm/eControlMethods.cs b/Pepperdash Core/Pepperdash Core/Comm/eControlMethods.cs
index d4a2028..5a7a6ea 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/eControlMethods.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/eControlMethods.cs
@@ -6,6 +6,9 @@ using Crestron.SimplSharp;
namespace PepperDash.Core
{
+ ///
+ /// Crestron Control Methods for a comm object
+ ///
public enum eControlMethod
{
None = 0, Com, IpId, IpidTcp, IR, Ssh, Tcpip, Telnet, Cresnet, Cec, Udp
diff --git a/Pepperdash Core/Pepperdash Core/Config/PortalConfigReader.cs b/Pepperdash Core/Pepperdash Core/Config/PortalConfigReader.cs
index 774f8e5..01dc0af 100644
--- a/Pepperdash Core/Pepperdash Core/Config/PortalConfigReader.cs
+++ b/Pepperdash Core/Pepperdash Core/Config/PortalConfigReader.cs
@@ -67,7 +67,7 @@ namespace PepperDash.Core.Config
///
///
///
- static JObject MergeConfigs(JObject doubleConfig)
+ public static JObject MergeConfigs(JObject doubleConfig)
{
var system = JObject.FromObject(doubleConfig["system"]);
var template = JObject.FromObject(doubleConfig["template"]);
@@ -114,28 +114,35 @@ namespace PepperDash.Core.Config
///
/// 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
- /// 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.
///
static JArray MergeArraysOnTopLevelProperty(JArray a1, JArray a2, string propertyName, string path)
{
var result = new JArray();
- if (a2 == null)
- result = a1;
+ if (a2 == null || a2.Count == 0) // If the system array is null or empty, return the template array
+ return a1;
else if (a1 != null)
{
- for (int i = 0; i < a1.Count(); i++)
- {
- var a1Dev = a1[i];
- // 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("uid") == tmplDev.Value("uid"));
- 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);
- }
+ if (a2[0]["key"] == null) // If the first item in the system array has no key, overwrite the template array
+ { // with the system array
+ return a2;
+ }
+ else // The arrays are keyed, merge them by key
+ {
+ for (int i = 0; i < a1.Count(); i++)
+ {
+ var a1Dev = a1[i];
+ // 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("uid") == tmplDev.Value("uid"));
+ 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;
}
diff --git a/Pepperdash Core/Pepperdash Core/CoreInterfaces.cs b/Pepperdash Core/Pepperdash Core/CoreInterfaces.cs
index 195d6e2..3a5df42 100644
--- a/Pepperdash Core/Pepperdash Core/CoreInterfaces.cs
+++ b/Pepperdash Core/Pepperdash Core/CoreInterfaces.cs
@@ -6,13 +6,25 @@ using Crestron.SimplSharp;
namespace PepperDash.Core
{
+ ///
+ /// Unique key interface to require a unique key for the class
+ ///
public interface IKeyed
{
+ ///
+ /// Unique Key
+ ///
string Key { get; }
}
+ ///
+ /// Named Keyed device interface. Forces the devie to have a Unique Key and a name.
+ ///
public interface IKeyName : IKeyed
{
+ ///
+ /// Isn't it obvious :)
+ ///
string Name { get; }
}
}
\ No newline at end of file
diff --git a/Pepperdash Core/Pepperdash Core/Device.cs b/Pepperdash Core/Pepperdash Core/Device.cs
index 7132f94..62dc6b1 100644
--- a/Pepperdash Core/Pepperdash Core/Device.cs
+++ b/Pepperdash Core/Pepperdash Core/Device.cs
@@ -10,8 +10,17 @@ namespace PepperDash.Core
///
public class Device : IKeyName
{
+ ///
+ /// Unique Key
+ ///
public string Key { get; protected set; }
- public string Name { get; protected set; }
+ ///
+ /// Name of the devie
+ ///
+ public string Name { get; protected set; }
+ ///
+ ///
+ ///
public bool Enabled { get; protected set; }
/////
diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/EventArgs and Constants.cs b/Pepperdash Core/Pepperdash Core/JsonToSimpl/EventArgs and Constants.cs
index e6e13a5..a94eac1 100644
--- a/Pepperdash Core/Pepperdash Core/JsonToSimpl/EventArgs and Constants.cs
+++ b/Pepperdash Core/Pepperdash Core/JsonToSimpl/EventArgs and Constants.cs
@@ -6,6 +6,9 @@ using Crestron.SimplSharp;
namespace PepperDash.Core.JsonToSimpl
{
+ ///
+ /// Constants for Simpl modules
+ ///
public class JsonToSimplConstants
{
public const ushort JsonIsValidBoolChange = 2;
@@ -13,6 +16,7 @@ namespace PepperDash.Core.JsonToSimpl
public const ushort BoolValueChange = 1;
public const ushort UshortValueChange = 101;
public const ushort StringValueChange = 201;
+ public const ushort FullPathToArrayChange = 202;
}
//**************************************************************************************************//
diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs b/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs
index 753dbc3..56ef04a 100644
--- a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs
+++ b/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs
@@ -11,10 +11,19 @@ namespace PepperDash.Core.JsonToSimpl
public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase
{
public string SearchPropertyName { get; set; }
- public string SearchPropertyValue { get; set; }
+ public string SearchPropertyValue { get; set; }
int ArrayIndex;
+ ///
+ /// For <2.4.1 array lookups
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public void Initialize(string file, string key, string pathPrefix, string pathSuffix,
string searchPropertyName, string searchPropertyValue)
{
@@ -24,6 +33,28 @@ namespace PepperDash.Core.JsonToSimpl
}
+ ///
+ /// For newer >=2.4.1 array lookups.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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
///
///
@@ -32,9 +63,10 @@ namespace PepperDash.Core.JsonToSimpl
///
protected override string GetFullPath(string path)
{
- return string.Format("{0}[{1}].{2}{3}",
- PathPrefix == null ? "" : PathPrefix,
- ArrayIndex, path,
+ return string.Format("{0}[{1}].{2}{3}",
+ PathPrefix == null ? "" : PathPrefix,
+ ArrayIndex,
+ path,
PathSuffix == null ? "" : PathSuffix);
}
@@ -44,6 +76,30 @@ namespace PepperDash.Core.JsonToSimpl
base.ProcessAll();
}
+ ///
+ /// Provides the path append for GetFullPath
+ ///
+ ///
+ string GetPathAppend(string a)
+ {
+ if (string.IsNullOrEmpty(a))
+ {
+ return "";
+ }
+ if (a.StartsWith("."))
+ {
+ return a;
+ }
+ else
+ {
+ return "." + a;
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
bool FindInArray()
{
if (Master == null)
@@ -51,41 +107,45 @@ namespace PepperDash.Core.JsonToSimpl
if (Master.JsonObject == null)
throw new InvalidOperationException("Cannot do operations before master JSON has read");
if (PathPrefix == null)
- throw new InvalidOperationException("Cannot do operations before PathPrefix is set");
-
- var token = Master.JsonObject.SelectToken(PathPrefix);
- if (token is JArray)
- {
- var array = token as JArray;
- try
- {
- var item = array.FirstOrDefault(o =>
- {
- var prop = o[SearchPropertyName];
- return prop != null && prop.Value()
- .Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase);
- });
- if (item == null)
- {
- Debug.Console(1, "JSON Child[{0}] Array '{1}' '{2}={3}' not found: ", Key,
- PathPrefix, SearchPropertyName, SearchPropertyValue);
- this.LinkedToObject = false;
- return false;
- }
-
- this.LinkedToObject = true;
- ArrayIndex = array.IndexOf(item);
- Debug.Console(1, "JSON Child[{0}] Found array match at index {1}", Key, ArrayIndex);
- return true;
- }
- catch (Exception e)
- {
- Debug.Console(1, "JSON Child[{0}] Array '{1}' lookup error: '{2}={3}'\r{4}", Key,
- PathPrefix, SearchPropertyName, SearchPropertyValue, e);
- }
+ throw new InvalidOperationException("Cannot do operations before PathPrefix is set");
+
+
+ var token = Master.JsonObject.SelectToken(PathPrefix);
+ if (token is JArray)
+ {
+ var array = token as JArray;
+ try
+ {
+ var item = array.FirstOrDefault(o =>
+ {
+ var prop = o[SearchPropertyName];
+ return prop != null && prop.Value()
+ .Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase);
+ });
+ if (item == null)
+ {
+ Debug.Console(1, "JSON Child[{0}] Array '{1}' '{2}={3}' not found: ", Key,
+ PathPrefix, SearchPropertyName, SearchPropertyValue);
+ this.LinkedToObject = false;
+ return false;
+ }
+
+ this.LinkedToObject = true;
+ ArrayIndex = array.IndexOf(item);
+ OnStringChange(string.Format("{0}[{1}]", PathPrefix, ArrayIndex), 0, JsonToSimplConstants.FullPathToArrayChange);
+ Debug.Console(1, "JSON Child[{0}] Found array match at index {1}", Key, ArrayIndex);
+ return true;
+ }
+ catch (Exception 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;
}
diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplChildObjectBase.cs b/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplChildObjectBase.cs
index cbed005..ec855f9 100644
--- a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplChildObjectBase.cs
+++ b/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplChildObjectBase.cs
@@ -28,7 +28,7 @@ namespace PepperDash.Core.JsonToSimpl
/// This will be prepended to all paths to allow path swapping or for more organized
/// sub-paths
///
- public string PathPrefix { get; protected set; }
+ public string PathPrefix { get; protected set; }
///
/// This is added to the end of all paths
@@ -285,7 +285,7 @@ namespace PepperDash.Core.JsonToSimpl
protected virtual string GetFullPath(string path)
{
return (PathPrefix != null ? PathPrefix : "") +
- path + (PathSuffix != null ? PathSuffix : "");
+ path + (PathSuffix != null ? PathSuffix : "");
}
// Helpers for events
diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplMaster.cs b/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplMaster.cs
index 4818711..bd48fa9 100644
--- a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplMaster.cs
+++ b/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplMaster.cs
@@ -106,10 +106,10 @@ namespace PepperDash.Core.JsonToSimpl
///
public void AddChild(JsonToSimplChildObjectBase child)
{
- if (Children.Contains(child)) {
- Children.Remove(child);
- }
- Children.Add(child);
+ if (!Children.Contains(child))
+ {
+ Children.Add(child);
+ }
}
///
diff --git a/Pepperdash Core/Pepperdash Core/Logging/Debug.cs b/Pepperdash Core/Pepperdash Core/Logging/Debug.cs
index ef7e927..14da95e 100644
--- a/Pepperdash Core/Pepperdash Core/Logging/Debug.cs
+++ b/Pepperdash Core/Pepperdash Core/Logging/Debug.cs
@@ -1,393 +1,421 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
-using Crestron.SimplSharp;
-using Crestron.SimplSharp.CrestronLogger;
-using Crestron.SimplSharp.CrestronIO;
-using Newtonsoft.Json;
-using PepperDash.Core.DebugThings;
-
-
-namespace PepperDash.Core
-{
- public static class Debug
- {
- ///
- /// Describes the folder location where a given program stores it's debug level memory. By default, the
- /// file written will be named appNdebug where N is 1-10.
- ///
- public static string FilePathPrefix = @"\nvram\debug\";
-
- ///
- /// The name of the file containing the current debug settings.
- ///
- public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber);
-
- public static int Level { get; private set; }
-
- static DebugContextCollection Contexts;
-
- static int SaveTimeoutMs = 30000;
-
- static CTimer SaveTimer;
-
- ///
- /// When true, the IncludedExcludedKeys dict will contain keys to include.
- /// When false (default), IncludedExcludedKeys will contain keys to exclude.
- ///
- static bool ExcludeAllMode;
-
- //static bool ExcludeNoKeyMessages;
-
- static Dictionary IncludedExcludedKeys;
-
- static Debug()
- {
- IncludedExcludedKeys = new Dictionary();
-
- //CrestronDataStoreStatic.InitCrestronDataStore();
- if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
- {
- // Add command to console
- CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
- "appdebug:P [0-2]: Sets the application's console debug message level",
- ConsoleAccessLevelEnum.AccessOperator);
- CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
- "appdebuglog:P [all] Use \"all\" for full log.",
- ConsoleAccessLevelEnum.AccessOperator);
- CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
- "appdebugclear:P Clears the current custom log",
- ConsoleAccessLevelEnum.AccessOperator);
- CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
- "appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator);
- }
-
- CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
-
- LoadMemory();
- Level = Contexts.GetOrCreateItem("DEFAULT").Level;
-
- CrestronLogger.Initialize(2, LoggerModeEnum.RM); // Use RM instead of DEFAULT as not to double-up console messages.
- }
-
- ///
- /// Used to save memory when shutting down
- ///
- ///
- static void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
- {
- if (programEventType == eProgramStatusEventType.Stopping)
- {
- if (SaveTimer != null)
- {
- SaveTimer.Stop();
- SaveTimer = null;
- }
- Console(0, "Saving debug settings");
- SaveMemory();
- }
- }
-
- ///
- /// Callback for console command
- ///
- ///
- public static void SetDebugFromConsole(string levelString)
- {
- try
- {
- if (string.IsNullOrEmpty(levelString.Trim()))
- {
- CrestronConsole.PrintLine("AppDebug level = {0}", Level);
- return;
- }
-
- SetDebugLevel(Convert.ToInt32(levelString));
- }
- catch
- {
- CrestronConsole.PrintLine("Usage: appdebug:P [0-2]");
- }
- }
-
- public static void SetDebugFilterFromConsole(string items)
- {
- var str = items.Trim();
- if (str == "?")
- {
- CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " +
- "+all: at beginning puts filter into 'default include' mode\r" +
- " All keys that follow will be excluded from output.\r" +
- "-all: at beginning puts filter into 'default excluse all' mode.\r" +
- " All keys that follow will be the only keys that are shown\r" +
- "+nokey: Enables messages with no key (default)\r" +
- "-nokey: Disables messages with no key.\r" +
- "(nokey settings are independent of all other settings)");
- return;
- }
- var keys = Regex.Split(str, @"\s*");
- foreach (var keyToken in keys)
- {
- var lkey = keyToken.ToLower();
- if (lkey == "+all")
- {
- IncludedExcludedKeys.Clear();
- ExcludeAllMode = false;
- }
- else if (lkey == "-all")
- {
- IncludedExcludedKeys.Clear();
- ExcludeAllMode = true;
- }
- //else if (lkey == "+nokey")
- //{
- // ExcludeNoKeyMessages = false;
- //}
- //else if (lkey == "-nokey")
- //{
- // ExcludeNoKeyMessages = true;
- //}
- else
- {
- string key = null; ;
- 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);
- }
- // otherwise include all mode, add to the exclusions
- else
- {
- IncludedExcludedKeys[key] = new object();
- }
- }
- else if (lkey.StartsWith("+"))
- {
- key = lkey.Substring(1);
- // if in exclude all mode, we need to add this as inclusion
- if (ExcludeAllMode)
- {
-
- IncludedExcludedKeys[key] = new object();
- }
- // otherwise include all mode, remove this from exclusions
- else
- {
- if (IncludedExcludedKeys.ContainsKey(key))
- IncludedExcludedKeys.Remove(key);
- }
- }
- }
- }
- }
-
-
- ///
- /// Sets the debug level
- ///
- /// Valid values 0 (no debug), 1 (critical), 2 (all messages)
- public static void SetDebugLevel(int level)
- {
- if (level <= 2)
- {
- Level = level;
- Contexts.GetOrCreateItem("DEFAULT").Level = level;
- SaveMemoryOnTimeout();
-
- CrestronConsole.PrintLine("[Application {0}], Debug level set to {1}",
- InitialParametersClass.ApplicationNumber, Level);
-
- //var err = CrestronDataStoreStatic.SetLocalUintValue("DebugLevel", level);
- //if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
- // CrestronConsole.PrintLine("Error saving console debug level setting: {0}", err);
- }
- }
-
- ///
- ///
- ///
- public static void ShowDebugLog(string s)
- {
- var loglist = CrestronLogger.PrintTheLog(s.ToLower() == "all");
- foreach (var l in loglist)
- CrestronConsole.ConsoleCommandResponse(l + CrestronEnvironment.NewLine);
- }
-
- ///
- /// Prints message to console if current debug level is equal to or higher than the level of this message.
- /// Uses CrestronConsole.PrintLine.
- ///
- ///
- /// Console format string
- /// Object parameters
- public static void Console(uint level, string format, params object[] items)
- {
- if (Level >= level)
- CrestronConsole.PrintLine("[{0}]App {1}:{2}", DateTime.Now.ToString("HH:mm:ss.fff"), InitialParametersClass.ApplicationNumber,
- string.Format(format, items));
- }
-
- ///
- /// Logs to Console when at-level, and all messages to error log, including device key
- ///
- public static void Console(uint level, IKeyed dev, string format, params object[] items)
- {
- if (Level >= level)
- Console(level, "[{0}] {1}", dev.Key, string.Format(format, items));
- }
-
- public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel,
- string format, params object[] items)
- {
- var str = string.Format("[{0}] {1}", dev.Key, string.Format(format, items));
- LogError(errorLogLevel, str);
- if (Level >= level)
- {
- Console(level, str);
- }
- }
-
- ///
- /// Logs to Console when at-level, and all messages to error log
- ///
- public static void Console(uint level, ErrorLogLevel errorLogLevel,
- string format, params object[] items)
- {
- var str = string.Format(format, items);
- LogError(errorLogLevel, str);
- if (Level >= level)
- {
- Console(level, 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.
- ///
- ///
- ///
- ///
- public static void ConsoleWithLog(uint level, string format, params object[] items)
- {
- var str = string.Format(format, items);
- if (Level >= level)
- CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
- CrestronLogger.WriteToLog(str, level);
- }
-
- ///
- /// 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.
- ///
- ///
- ///
- /// String.format string
- /// Parameters for substitution in the format string.
- public static void ConsoleWithLog(uint level, IKeyed dev, string format, params object[] items)
- {
- var str = string.Format(format, items);
- if (Level >= level)
- ConsoleWithLog(level, "[{0}] {1}", dev.Key, str);
- }
-
- ///
- /// Prints to log and error log
- ///
- ///
- ///
- public static void LogError(ErrorLogLevel errorLogLevel, string str)
- {
- string msg = string.Format("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
- switch (errorLogLevel)
- {
- case ErrorLogLevel.Error:
- ErrorLog.Error(msg);
- break;
- case ErrorLogLevel.Warning:
- ErrorLog.Warn(msg);
- break;
- case ErrorLogLevel.Notice:
- ErrorLog.Notice(msg);
- break;
- }
- }
-
- ///
- /// Writes the memory object after timeout
- ///
- static void SaveMemoryOnTimeout()
- {
- if (SaveTimer == null)
- SaveTimer = new CTimer(o =>
- {
- SaveTimer = null;
- SaveMemory();
- }, SaveTimeoutMs);
- else
- SaveTimer.Reset(SaveTimeoutMs);
- }
-
- ///
- /// Writes the memory - use SaveMemoryOnTimeout
- ///
- static void SaveMemory()
- {
- //var dir = @"\NVRAM\debug";
- //if (!Directory.Exists(dir))
- // Directory.Create(dir);
-
- using (StreamWriter sw = new StreamWriter(GetMemoryFileName()))
- {
- var json = JsonConvert.SerializeObject(Contexts);
- sw.Write(json);
- sw.Flush();
- }
- }
-
- ///
- ///
- ///
- static void LoadMemory()
- {
- var file = GetMemoryFileName();
- if (File.Exists(file))
- {
- using (StreamReader sr = new StreamReader(file))
- {
- var json = sr.ReadToEnd();
- Contexts = JsonConvert.DeserializeObject(json);
-
- if (Contexts != null)
- {
- Debug.Console(1, "Debug memory restored from file");
- return;
- }
- }
- }
-
- Contexts = new DebugContextCollection();
- }
-
- ///
- /// Helper to get the file path for this app's debug memory
- ///
- static string GetMemoryFileName()
- {
- return string.Format(@"\NVRAM\debugSettings\program{0}", InitialParametersClass.ApplicationNumber);
- }
-
- public enum ErrorLogLevel
- {
- Error, Warning, Notice, None
- }
- }
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using Crestron.SimplSharp;
+using Crestron.SimplSharp.Reflection;
+using Crestron.SimplSharp.CrestronLogger;
+using Crestron.SimplSharp.CrestronIO;
+using Newtonsoft.Json;
+using PepperDash.Core.DebugThings;
+
+
+namespace PepperDash.Core
+{
+ public static class Debug
+ {
+ ///
+ /// Describes the folder location where a given program stores it's debug level memory. By default, the
+ /// file written will be named appNdebug where N is 1-10.
+ ///
+ public static string FilePathPrefix = @"\nvram\debug\";
+
+ ///
+ /// The name of the file containing the current debug settings.
+ ///
+ public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber);
+
+ public static int Level { get; private set; }
+
+ static DebugContextCollection Contexts;
+
+ static int SaveTimeoutMs = 30000;
+
+ public static string PepperDashCoreVersion { get; private set; }
+
+ static CTimer SaveTimer;
+
+ ///
+ /// When true, the IncludedExcludedKeys dict will contain keys to include.
+ /// When false (default), IncludedExcludedKeys will contain keys to exclude.
+ ///
+ static bool ExcludeAllMode;
+
+ //static bool ExcludeNoKeyMessages;
+
+ static Dictionary IncludedExcludedKeys;
+
+ static Debug()
+ {
+ // Get the assembly version and print it to console and the log
+ var version = Assembly.GetExecutingAssembly().GetName().Version;
+
+ PepperDashCoreVersion = string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision);
+
+ var msg = string.Format("[App {0}] Using PepperDash_Core v{1}", InitialParametersClass.ApplicationNumber, PepperDashCoreVersion);
+
+ CrestronConsole.PrintLine(msg);
+
+ LogError(ErrorLogLevel.Notice, msg);
+
+ IncludedExcludedKeys = new Dictionary();
+
+ //CrestronDataStoreStatic.InitCrestronDataStore();
+ if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
+ {
+ // Add command to console
+ CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
+ "appdebug:P [0-2]: Sets the application's console debug message level",
+ ConsoleAccessLevelEnum.AccessOperator);
+ CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
+ "appdebuglog:P [all] Use \"all\" for full log.",
+ ConsoleAccessLevelEnum.AccessOperator);
+ CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
+ "appdebugclear:P Clears the current custom log",
+ ConsoleAccessLevelEnum.AccessOperator);
+ CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
+ "appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator);
+ }
+
+ CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
+
+ LoadMemory();
+ Level = Contexts.GetOrCreateItem("DEFAULT").Level;
+
+ try
+ {
+ if (InitialParametersClass.NumberOfRemovableDrives > 0)
+ {
+ 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.
+ }
+ else
+ CrestronConsole.PrintLine("No RM Drive(s) Present.");
+ }
+ catch (Exception e)
+ {
+
+ CrestronConsole.PrintLine("Initializing of CrestronLogger failed: {0}", e);
+ }
+ }
+
+ ///
+ /// Used to save memory when shutting down
+ ///
+ ///
+ static void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
+ {
+ if (programEventType == eProgramStatusEventType.Stopping)
+ {
+ if (SaveTimer != null)
+ {
+ SaveTimer.Stop();
+ SaveTimer = null;
+ }
+ Console(0, "Saving debug settings");
+ SaveMemory();
+ }
+ }
+
+ ///
+ /// Callback for console command
+ ///
+ ///
+ public static void SetDebugFromConsole(string levelString)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(levelString.Trim()))
+ {
+ CrestronConsole.PrintLine("AppDebug level = {0}", Level);
+ return;
+ }
+
+ SetDebugLevel(Convert.ToInt32(levelString));
+ }
+ catch
+ {
+ CrestronConsole.PrintLine("Usage: appdebug:P [0-2]");
+ }
+ }
+
+ public static void SetDebugFilterFromConsole(string items)
+ {
+ var str = items.Trim();
+ if (str == "?")
+ {
+ CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " +
+ "+all: at beginning puts filter into 'default include' mode\r" +
+ " All keys that follow will be excluded from output.\r" +
+ "-all: at beginning puts filter into 'default excluse all' mode.\r" +
+ " All keys that follow will be the only keys that are shown\r" +
+ "+nokey: Enables messages with no key (default)\r" +
+ "-nokey: Disables messages with no key.\r" +
+ "(nokey settings are independent of all other settings)");
+ return;
+ }
+ var keys = Regex.Split(str, @"\s*");
+ foreach (var keyToken in keys)
+ {
+ var lkey = keyToken.ToLower();
+ if (lkey == "+all")
+ {
+ IncludedExcludedKeys.Clear();
+ ExcludeAllMode = false;
+ }
+ else if (lkey == "-all")
+ {
+ IncludedExcludedKeys.Clear();
+ ExcludeAllMode = true;
+ }
+ //else if (lkey == "+nokey")
+ //{
+ // ExcludeNoKeyMessages = false;
+ //}
+ //else if (lkey == "-nokey")
+ //{
+ // ExcludeNoKeyMessages = true;
+ //}
+ else
+ {
+ string key = null; ;
+ 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);
+ }
+ // otherwise include all mode, add to the exclusions
+ else
+ {
+ IncludedExcludedKeys[key] = new object();
+ }
+ }
+ else if (lkey.StartsWith("+"))
+ {
+ key = lkey.Substring(1);
+ // if in exclude all mode, we need to add this as inclusion
+ if (ExcludeAllMode)
+ {
+
+ IncludedExcludedKeys[key] = new object();
+ }
+ // otherwise include all mode, remove this from exclusions
+ else
+ {
+ if (IncludedExcludedKeys.ContainsKey(key))
+ IncludedExcludedKeys.Remove(key);
+ }
+ }
+ }
+ }
+ }
+
+
+ ///
+ /// Sets the debug level
+ ///
+ /// Valid values 0 (no debug), 1 (critical), 2 (all messages)
+ public static void SetDebugLevel(int level)
+ {
+ if (level <= 2)
+ {
+ Level = level;
+ Contexts.GetOrCreateItem("DEFAULT").Level = level;
+ SaveMemoryOnTimeout();
+
+ CrestronConsole.PrintLine("[Application {0}], Debug level set to {1}",
+ InitialParametersClass.ApplicationNumber, Level);
+
+ //var err = CrestronDataStoreStatic.SetLocalUintValue("DebugLevel", level);
+ //if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
+ // CrestronConsole.PrintLine("Error saving console debug level setting: {0}", err);
+ }
+ }
+
+ ///
+ ///
+ ///
+ public static void ShowDebugLog(string s)
+ {
+ var loglist = CrestronLogger.PrintTheLog(s.ToLower() == "all");
+ foreach (var l in loglist)
+ CrestronConsole.ConsoleCommandResponse(l + CrestronEnvironment.NewLine);
+ }
+
+ ///
+ /// Prints message to console if current debug level is equal to or higher than the level of this message.
+ /// Uses CrestronConsole.PrintLine.
+ ///
+ ///
+ /// Console format string
+ /// Object parameters
+ public static void Console(uint level, string format, params object[] items)
+ {
+ if (Level >= level)
+ CrestronConsole.PrintLine("[{0}]App {1}:{2}", DateTime.Now.ToString("HH:mm:ss.fff"), InitialParametersClass.ApplicationNumber,
+ string.Format(format, items));
+ }
+
+ ///
+ /// Logs to Console when at-level, and all messages to error log, including device key
+ ///
+ public static void Console(uint level, IKeyed dev, string format, params object[] items)
+ {
+ if (Level >= level)
+ Console(level, "[{0}] {1}", dev.Key, string.Format(format, items));
+ }
+
+ public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel,
+ string format, params object[] items)
+ {
+ var str = string.Format("[{0}] {1}", dev.Key, string.Format(format, items));
+ LogError(errorLogLevel, str);
+ if (Level >= level)
+ {
+ Console(level, str);
+ }
+ }
+
+ ///
+ /// Logs to Console when at-level, and all messages to error log
+ ///
+ public static void Console(uint level, ErrorLogLevel errorLogLevel,
+ string format, params object[] items)
+ {
+ var str = string.Format(format, items);
+ LogError(errorLogLevel, str);
+ if (Level >= level)
+ {
+ Console(level, 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.
+ ///
+ ///
+ ///
+ ///
+ public static void ConsoleWithLog(uint level, string format, params object[] items)
+ {
+ var str = string.Format(format, items);
+ if (Level >= level)
+ CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
+ CrestronLogger.WriteToLog(str, level);
+ }
+
+ ///
+ /// 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.
+ ///
+ ///
+ ///
+ /// String.format string
+ /// Parameters for substitution in the format string.
+ public static void ConsoleWithLog(uint level, IKeyed dev, string format, params object[] items)
+ {
+ var str = string.Format(format, items);
+ if (Level >= level)
+ ConsoleWithLog(level, "[{0}] {1}", dev.Key, str);
+ }
+
+ ///
+ /// Prints to log and error log
+ ///
+ ///
+ ///
+ public static void LogError(ErrorLogLevel errorLogLevel, string str)
+ {
+ string msg = string.Format("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
+ switch (errorLogLevel)
+ {
+ case ErrorLogLevel.Error:
+ ErrorLog.Error(msg);
+ break;
+ case ErrorLogLevel.Warning:
+ ErrorLog.Warn(msg);
+ break;
+ case ErrorLogLevel.Notice:
+ ErrorLog.Notice(msg);
+ break;
+ }
+ }
+
+ ///
+ /// Writes the memory object after timeout
+ ///
+ static void SaveMemoryOnTimeout()
+ {
+ if (SaveTimer == null)
+ SaveTimer = new CTimer(o =>
+ {
+ SaveTimer = null;
+ SaveMemory();
+ }, SaveTimeoutMs);
+ else
+ SaveTimer.Reset(SaveTimeoutMs);
+ }
+
+ ///
+ /// Writes the memory - use SaveMemoryOnTimeout
+ ///
+ static void SaveMemory()
+ {
+ //var dir = @"\NVRAM\debug";
+ //if (!Directory.Exists(dir))
+ // Directory.Create(dir);
+
+ using (StreamWriter sw = new StreamWriter(GetMemoryFileName()))
+ {
+ var json = JsonConvert.SerializeObject(Contexts);
+ sw.Write(json);
+ sw.Flush();
+ }
+ }
+
+ ///
+ ///
+ ///
+ static void LoadMemory()
+ {
+ var file = GetMemoryFileName();
+ if (File.Exists(file))
+ {
+ using (StreamReader sr = new StreamReader(file))
+ {
+ var json = sr.ReadToEnd();
+ Contexts = JsonConvert.DeserializeObject(json);
+
+ if (Contexts != null)
+ {
+ Debug.Console(1, "Debug memory restored from file");
+ return;
+ }
+ }
+ }
+
+ Contexts = new DebugContextCollection();
+ }
+
+ ///
+ /// Helper to get the file path for this app's debug memory
+ ///
+ static string GetMemoryFileName()
+ {
+ return string.Format(@"\NVRAM\debugSettings\program{0}", InitialParametersClass.ApplicationNumber);
+ }
+
+ public enum ErrorLogLevel
+ {
+ Error, Warning, Notice, None
+ }
+ }
}
\ No newline at end of file
diff --git a/Pepperdash Core/Pepperdash Core/Network/DiscoveryThings.cs b/Pepperdash Core/Pepperdash Core/Network/DiscoveryThings.cs
index 72f2285..973c03a 100644
--- a/Pepperdash Core/Pepperdash Core/Network/DiscoveryThings.cs
+++ b/Pepperdash Core/Pepperdash Core/Network/DiscoveryThings.cs
@@ -6,10 +6,14 @@ using Crestron.SimplSharp;
namespace PepperDash.Core
{
-
+ ///
+ /// Not in use
+ ///
public static class NetworkComm
{
-
+ ///
+ /// Not in use
+ ///
static NetworkComm()
{
}
diff --git a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj
index 13a79a5..6c7960d 100644
--- a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj
+++ b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj
@@ -31,6 +31,7 @@
512
true
true
+ bin\PepperDash_Core.xml
.allowedReferenceRelatedFileExtensions
@@ -42,6 +43,7 @@
512
true
true
+ bin\PepperDash_Core.xml
@@ -57,6 +59,10 @@
False
..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpNewtonsoft.dll
+
+ False
+ ..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpReflectionInterface.dll
+
@@ -76,6 +82,7 @@
+
diff --git a/Pepperdash Core/Pepperdash Core/Properties/AssemblyInfo.cs b/Pepperdash Core/Pepperdash Core/Properties/AssemblyInfo.cs
index de52999..55b4cbb 100644
--- a/Pepperdash Core/Pepperdash Core/Properties/AssemblyInfo.cs
+++ b/Pepperdash Core/Pepperdash Core/Properties/AssemblyInfo.cs
@@ -4,4 +4,4 @@
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Pepperdash_Core")]
[assembly: AssemblyCopyright("Copyright © PepperDash 2019")]
-[assembly: AssemblyVersion("1.0.14.*")]
+[assembly: AssemblyVersion("1.0.23.*")]
diff --git a/Pepperdash Core/Pepperdash Core/Properties/UpdateAssemblyVersion.ps1 b/Pepperdash Core/Pepperdash Core/Properties/UpdateAssemblyVersion.ps1
new file mode 100644
index 0000000..46392b3
--- /dev/null
+++ b/Pepperdash Core/Pepperdash Core/Properties/UpdateAssemblyVersion.ps1
@@ -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";
+}
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..af824bf
--- /dev/null
+++ b/Readme.md
@@ -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