From 214e1d215c6aac31e31acc0f054a7298ff301934 Mon Sep 17 00:00:00 2001 From: Joshua Gutenplan Date: Thu, 13 Jun 2019 19:17:09 -0700 Subject: [PATCH 1/8] Added the client config object, and added the receive queue and thread with event to the Secure TCP server and client. Will need to duplicate to the unsecure at some point after testing. --- .../GenericSecureTcpIpClient_ForServer.cs | 140 +++++++++++++++++- .../Comm/GenericSecureTcpIpServer.cs | 89 +++++++++-- .../Comm/TcpClientConfigObject.cs | 21 +++ .../Comm/TcpServerConfigObject.cs | 3 +- .../Pepperdash Core/PepperDash_Core.csproj | 1 + 5 files changed, 234 insertions(+), 20 deletions(-) create mode 100644 Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs index df61965..6551f40 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs @@ -17,6 +17,7 @@ using System.Text; using System.Text.RegularExpressions; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; +using PepperDash_Core.Comm; namespace PepperDash.Core { @@ -33,6 +34,13 @@ 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; + + public event EventHandler ConnectionChange; @@ -209,8 +217,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 +246,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 +282,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 +312,36 @@ namespace PepperDash.Core Key = key; } + public void Initialize(TcpClientConfigObject clientConfigObject) + { + try + { + if (clientConfigObject != null) + { + Hostname = clientConfigObject.Address; + AutoReconnect = clientConfigObject.AutoReconnect; + AutoReconnectIntervalMs = clientConfigObject.AutoReconnectIntervalMs > 1000 ? clientConfigObject.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 = clientConfigObject.Port; + BufferSize = clientConfigObject.BufferSize > 2000 ? clientConfigObject.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 +408,7 @@ namespace PepperDash.Core Client = new SecureTCPClient(Hostname, Port, BufferSize); Client.SocketStatusChange += Client_SocketStatusChange; - if(HeartbeatEnabled) + if (HeartbeatEnabled) Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); Client.AddressClientConnectedTo = Hostname; Client.PortNumber = Port; @@ -487,7 +567,7 @@ namespace PepperDash.Core if (numBytes > 0) { string str = string.Empty; - + var handler = TextReceivedQueueInvoke; try { var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); @@ -517,6 +597,10 @@ namespace PepperDash.Core var textHandler = TextReceived; if (textHandler != null) textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str)); + if (handler != null) + { + MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(str)); + } } } } @@ -524,9 +608,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, "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(); } - 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 5554764..13b3cef 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 @@ -269,7 +291,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 +304,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 +314,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 +367,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 { @@ -770,6 +793,7 @@ namespace PepperDash.Core if (numberOfBytesReceived > 0) { string received = "Nothing"; + var handler = TextReceivedQueueInvoke; try { byte[] bytes = mySecureTCPServer.GetIncomingDataBufferForSpecificClient(clientIndex); @@ -792,11 +816,16 @@ namespace PepperDash.Core 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); - + 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); + if (handler != null) + { + MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(received, clientIndex)); + } } - else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received))) - onTextReceived(received, clientIndex); } catch (Exception ex) { @@ -804,13 +833,49 @@ namespace PepperDash.Core } 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()); + } } else { - // If numberOfBytesReceived <= 0 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(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(); + } } #endregion diff --git a/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs b/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs new file mode 100644 index 0000000..c0c7f45 --- /dev/null +++ b/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using PepperDash.Core; +using Newtonsoft.Json; + +namespace PepperDash_Core.Comm +{ + public class TcpClientConfigObject : TcpSshPropertiesConfig + { + public bool Secure { get; set; } + public bool SharedKeyRequired { get; set; } + public string SharedKey { get; set; } + public bool HeartbeatRequired { get; set; } + public ushort HeartbeatRequiredIntervalInSeconds { get; set; } + public string HeartbeatStringToMatch { get; set; } + 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..af455f4 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/TcpServerConfigObject.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/TcpServerConfigObject.cs @@ -9,8 +9,8 @@ namespace PepperDash.Core public class TcpServerConfigObject { public string Key { get; set; } - public bool Secure { get; set; } public ushort MaxClients { get; set; } + public bool Secure { get; set; } public int Port { get; set; } public bool SharedKeyRequired { get; set; } public string SharedKey { get; set; } @@ -18,5 +18,6 @@ namespace PepperDash.Core public ushort HeartbeatRequiredIntervalInSeconds { get; set; } public string HeartbeatStringToMatch { get; set; } public int BufferSize { get; set; } + public int ReceiveQueueSize { get; set; } } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj index 8a033a2..c0a4f03 100644 --- a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj +++ b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj @@ -80,6 +80,7 @@ + From 8dc3cb967bca81eb2389278aaf59a391b726d462 Mon Sep 17 00:00:00 2001 From: Joshua Gutenplan Date: Thu, 13 Jun 2019 19:33:39 -0700 Subject: [PATCH 2/8] Added xml doc and some summaries to some of the warnings. There are still 321 warnings for xml doc, but I think its helpful to see what summaries are there in ref'd solutions. --- .../Comm/GenericSecureTcpIpServer.cs | 4 +- .../Comm/GenericTcpIpClient.cs | 1 - .../Comm/TcpClientConfigObject.cs | 26 +++++++++++++ .../Comm/TcpServerConfigObject.cs | 37 +++++++++++++++++++ .../Pepperdash Core/Comm/eControlMethods.cs | 3 ++ .../Pepperdash Core/CoreInterfaces.cs | 12 ++++++ Pepperdash Core/Pepperdash Core/Device.cs | 11 +++++- .../JsonToSimpl/EventArgs and Constants.cs | 3 ++ .../Network/DiscoveryThings.cs | 8 +++- .../Pepperdash Core/PepperDash_Core.csproj | 1 + 10 files changed, 101 insertions(+), 5 deletions(-) diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs index 13b3cef..cff8361 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs @@ -167,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. diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs index 65c428f..a313b95 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs @@ -352,7 +352,6 @@ namespace PepperDash.Core } } - public class TcpSshPropertiesConfig { [JsonProperty(Required = Required.Always)] diff --git a/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs b/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs index c0c7f45..3e34aa6 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs @@ -8,14 +8,40 @@ using Newtonsoft.Json; namespace PepperDash_Core.Comm { + /// + /// Client config object for TCP client with server that inherits from TcpSshPropertiesConfig and adds properties for shared key and heartbeat + /// public class TcpClientConfigObject : TcpSshPropertiesConfig { + /// + /// 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 af455f4..785851b 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/TcpServerConfigObject.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/TcpServerConfigObject.cs @@ -6,18 +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; } + /// + /// 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/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..d2e7e8e 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; 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 c0a4f03..0aeb953 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 From 45edcae03452f8a998b8d9f6a8273162b10141cc Mon Sep 17 00:00:00 2001 From: Joshua Gutenplan Date: Fri, 14 Jun 2019 09:21:12 -0700 Subject: [PATCH 3/8] fix namespace in client config object --- .../Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs | 1 - Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs index 6551f40..a24e0bf 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs @@ -17,7 +17,6 @@ using System.Text; using System.Text.RegularExpressions; using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronSockets; -using PepperDash_Core.Comm; namespace PepperDash.Core { diff --git a/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs b/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs index 3e34aa6..b496bd6 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs @@ -6,7 +6,7 @@ using Crestron.SimplSharp; using PepperDash.Core; using Newtonsoft.Json; -namespace PepperDash_Core.Comm +namespace PepperDash.Core { /// /// Client config object for TCP client with server that inherits from TcpSshPropertiesConfig and adds properties for shared key and heartbeat From b1e310245567d47fd208694bcb0db39a76d6c91f Mon Sep 17 00:00:00 2001 From: Joshua Gutenplan Date: Fri, 14 Jun 2019 14:00:29 -0700 Subject: [PATCH 4/8] Fix tcp client config object. --- .../GenericSecureTcpIpClient_ForServer.cs | 19 +++++++++++++------ .../Comm/TcpClientConfigObject.cs | 6 +++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs index a24e0bf..d8088a3 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs @@ -311,22 +311,29 @@ 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) { - Hostname = clientConfigObject.Address; - AutoReconnect = clientConfigObject.AutoReconnect; - AutoReconnectIntervalMs = clientConfigObject.AutoReconnectIntervalMs > 1000 ? clientConfigObject.AutoReconnectIntervalMs : 5000; + 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; + HeartbeatRequiredIntervalInSeconds = clientConfigObject.HeartbeatRequiredIntervalInSeconds > 0 ? + clientConfigObject.HeartbeatRequiredIntervalInSeconds : (ushort)15; HeartbeatString = string.IsNullOrEmpty(clientConfigObject.HeartbeatStringToMatch) ? "heartbeat" : clientConfigObject.HeartbeatStringToMatch; - Port = clientConfigObject.Port; - BufferSize = clientConfigObject.BufferSize > 2000 ? clientConfigObject.BufferSize : 2000; + Port = TcpSshProperties.Port; + BufferSize = TcpSshProperties.BufferSize > 2000 ? TcpSshProperties.BufferSize : 2000; ReceiveQueueSize = clientConfigObject.ReceiveQueueSize > 20 ? clientConfigObject.ReceiveQueueSize : 20; MessageQueue = new CrestronQueue(ReceiveQueueSize); } diff --git a/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs b/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs index b496bd6..a4fe8b2 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs @@ -11,8 +11,12 @@ 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 : TcpSshPropertiesConfig + 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 /// From 7782f8747d2d7390e2b4f9a9101262084f5d0108 Mon Sep 17 00:00:00 2001 From: Joshua Gutenplan Date: Fri, 14 Jun 2019 15:49:03 -0700 Subject: [PATCH 5/8] Fix debug statement for secure client & server to remove the udp ref --- .../Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs | 2 +- .../Pepperdash Core/Comm/GenericSecureTcpIpServer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs index d8088a3..334824a 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs @@ -652,7 +652,7 @@ namespace PepperDash.Core } catch (Exception e) { - Debug.Console(0, "GenericUdpServer DequeueEvent error: {0}\r", 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) diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs index cff8361..3f21ac3 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs @@ -871,7 +871,7 @@ namespace PepperDash.Core } catch (Exception e) { - Debug.Console(0, "GenericUdpServer DequeueEvent error: {0}\r", 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) From 2c5c4e39a4bedf42912dfee68884f09782eb9476 Mon Sep 17 00:00:00 2001 From: Joshua Gutenplan Date: Tue, 18 Jun 2019 16:54:29 -0700 Subject: [PATCH 6/8] updated client to add an event for the auto reconnect. Using this event with empty args to catch the client before the next attempt and change the port for a multi server environment. Also updated the server to not kill on a listen so that if the server stops listening due to max clients (does this automatically) it will be able to start listening on when it drops below max clients without disconnecting all connected clients. On server Also udpated the onConnectionChange to fire in its own thread so that the server can update its state and the state will be accurate in the event callbacks. See description in code. --- .../Comm/GenericSecureTcpIpClient_ForServer.cs | 4 ++++ .../Pepperdash Core/Comm/GenericSecureTcpIpServer.cs | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs index 334824a..c3882ca 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs @@ -33,6 +33,8 @@ 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. @@ -559,6 +561,8 @@ namespace PepperDash.Core RetryTimer.Stop(); RetryTimer = null; } + if(AutoReconnectTriggered != null) + AutoReconnectTriggered(this, new EventArgs()); RetryTimer = new CTimer(o => Connect(), rndTime); } } diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs index 3f21ac3..ea9c606 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs @@ -416,7 +416,7 @@ namespace PepperDash.Core } 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; @@ -703,7 +703,10 @@ namespace PepperDash.Core { Debug.Console(0, 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 From a9dbaf21c24cb989ce5f15cdc3e26c6eb8a13528 Mon Sep 17 00:00:00 2001 From: Joshua Gutenplan Date: Tue, 25 Jun 2019 18:19:17 -0700 Subject: [PATCH 7/8] Added a few hooks for S+ in udp class no logic change just additions for s+ --- .../Pepperdash Core/Comm/GenericUdpServer.cs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs index f819c17..b7ebb48 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs @@ -51,6 +51,11 @@ namespace PepperDash.Core } } + public ushort UStatus + { + get { return (ushort)Server.ServerStatus; } + } + CCriticalSection DequeueLock; /// @@ -87,6 +92,11 @@ namespace PepperDash.Core private set; } + public ushort UIsConnected + { + get { return IsConnected ? (ushort)1 : (ushort)0; } + } + /// /// Defaults to 2000 /// @@ -94,6 +104,20 @@ namespace PepperDash.Core 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) { @@ -108,6 +132,13 @@ namespace PepperDash.Core 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 @@ -135,7 +166,6 @@ namespace PepperDash.Core if (Server == null) { Server = new UDPServer(); - } if (string.IsNullOrEmpty(Hostname)) From f0ad3b3706d234f0bdcfe5a1ba69d4763c478a76 Mon Sep 17 00:00:00 2001 From: Joshua Gutenplan Date: Wed, 26 Jun 2019 19:36:19 -0700 Subject: [PATCH 8/8] Added event for connected to drive connected fb on module. Status change is there but it uses socket status which is not available in UDP. --- .../Pepperdash Core/Comm/EventArgs.cs | 18 ++++++++++++++++++ .../Pepperdash Core/Comm/GenericUdpServer.cs | 10 ++++++++++ 2 files changed, 28 insertions(+) diff --git a/Pepperdash Core/Pepperdash Core/Comm/EventArgs.cs b/Pepperdash Core/Pepperdash Core/Comm/EventArgs.cs index ed43c62..52ee207 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/EventArgs.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/EventArgs.cs @@ -93,6 +93,24 @@ namespace PepperDash.Core } } + public class GenericUdpConnectedEventArgs : EventArgs + { + public ushort UConnected; + public bool Connected; + + public GenericUdpConnectedEventArgs() { } + + public GenericUdpConnectedEventArgs(ushort uconnected) + { + UConnected = uconnected; + } + + public GenericUdpConnectedEventArgs(bool connected) + { + Connected = connected; + } + } + } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs index b7ebb48..011c17c 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs +++ b/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs @@ -43,6 +43,8 @@ namespace PepperDash.Core //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; public event EventHandler ConnectionChange; + public event EventHandler UpdateConnectionStatus; + public SocketStatus ClientStatus { get @@ -187,6 +189,10 @@ namespace PepperDash.Core if (status == SocketErrorCodes.SOCKET_OK) IsConnected = true; + var handler = UpdateConnectionStatus; + if (handler != null) + handler(this, new GenericUdpConnectedEventArgs(UIsConnected)); + // Start receiving data Server.ReceiveDataAsync(Receive); } @@ -200,6 +206,10 @@ namespace PepperDash.Core Server.DisableUDPServer(); IsConnected = false; + + var handler = UpdateConnectionStatus; + if (handler != null) + handler(this, new GenericUdpConnectedEventArgs(UIsConnected)); }