diff --git a/Pepperdash Core/Pepperdash Core.slnold b/Pepperdash Core/Pepperdash Core.slnold deleted file mode 100644 index f56017b..0000000 --- a/Pepperdash Core/Pepperdash Core.slnold +++ /dev/null @@ -1,19 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual Studio 2008 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash_Core", "Pepperdash Core\PepperDash_Core.csproj", "{87E29B4C-569B-4368-A4ED-984AC1440C96}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {87E29B4C-569B-4368-A4ED-984AC1440C96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {87E29B4C-569B-4368-A4ED-984AC1440C96}.Debug|Any CPU.Build.0 = Debug|Any CPU - {87E29B4C-569B-4368-A4ED-984AC1440C96}.Release|Any CPU.ActiveCfg = Release|Any CPU - {87E29B4C-569B-4368-A4ED-984AC1440C96}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/Pepperdash Core/Pepperdash Core.suo.orig b/Pepperdash Core/Pepperdash Core.suo.orig deleted file mode 100644 index 6d73571..0000000 Binary files a/Pepperdash Core/Pepperdash Core.suo.orig and /dev/null differ diff --git a/Pepperdash Core/Pepperdash Core.sln b/Pepperdash_Core.3Series.sln similarity index 88% rename from Pepperdash Core/Pepperdash Core.sln rename to Pepperdash_Core.3Series.sln index 527f806..df6c1bc 100644 --- a/Pepperdash Core/Pepperdash Core.sln +++ b/Pepperdash_Core.3Series.sln @@ -1,19 +1,19 @@ -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual Studio 2008 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash_Core", "Pepperdash Core\PepperDash_Core.csproj", "{87E29B4C-569B-4368-A4ED-984AC1440C96}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {87E29B4C-569B-4368-A4ED-984AC1440C96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {87E29B4C-569B-4368-A4ED-984AC1440C96}.Debug|Any CPU.Build.0 = Debug|Any CPU - {87E29B4C-569B-4368-A4ED-984AC1440C96}.Release|Any CPU.ActiveCfg = Release|Any CPU - {87E29B4C-569B-4368-A4ED-984AC1440C96}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash_Core", "src\PepperDash_Core.csproj", "{87E29B4C-569B-4368-A4ED-984AC1440C96}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {87E29B4C-569B-4368-A4ED-984AC1440C96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87E29B4C-569B-4368-A4ED-984AC1440C96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87E29B4C-569B-4368-A4ED-984AC1440C96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87E29B4C-569B-4368-A4ED-984AC1440C96}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Pepperdash Core/Pepperdash Core/Comm/._GenericSshClient.cs b/src/Comm/._GenericSshClient.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Comm/._GenericSshClient.cs rename to src/Comm/._GenericSshClient.cs diff --git a/Pepperdash Core/Pepperdash Core/Comm/._GenericTcpIpClient.cs b/src/Comm/._GenericTcpIpClient.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Comm/._GenericTcpIpClient.cs rename to src/Comm/._GenericTcpIpClient.cs diff --git a/Pepperdash Core/Pepperdash Core/Comm/CommunicationGather.cs b/src/Comm/CommunicationGather.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/Comm/CommunicationGather.cs rename to src/Comm/CommunicationGather.cs index f71a496..9ffe826 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/CommunicationGather.cs +++ b/src/Comm/CommunicationGather.cs @@ -1,179 +1,179 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Crestron.SimplSharp; - -using PepperDash.Core; - - -namespace PepperDash.Core -{ - /// - /// Defines the string event handler for line events on the gather - /// - /// - public delegate void LineReceivedHandler(string text); - - /// - /// Attaches to IBasicCommunication as a text gather - /// - public class CommunicationGather - { - /// - /// Event that fires when a line is received from the IBasicCommunication source. - /// The event merely contains the text, not an EventArgs type class. - /// - public event EventHandler LineReceived; - - /// - /// The communication port that this gathers on - /// - public ICommunicationReceiver Port { get; private set; } - - /// - /// Default false. If true, the delimiter will be included in the line output - /// events - /// - public bool IncludeDelimiter { get; set; } - - /// - /// For receive buffer - /// - StringBuilder ReceiveBuffer = new StringBuilder(); - - /// - /// Delimiter, like it says! - /// - char Delimiter; - - string[] StringDelimiters; - - /// - /// Constructor for using a char delimiter - /// - /// - /// - public CommunicationGather(ICommunicationReceiver port, char delimiter) - { - Port = port; - Delimiter = delimiter; - port.TextReceived += new EventHandler(Port_TextReceived); - } - - /// - /// Constructor for using a single string delimiter - /// - /// - /// - public CommunicationGather(ICommunicationReceiver port, string delimiter) - :this(port, new string[] { delimiter} ) - { - } - - /// - /// Constructor for using an array of string delimiters - /// - /// - /// - public CommunicationGather(ICommunicationReceiver port, string[] delimiters) - { - Port = port; - StringDelimiters = delimiters; - port.TextReceived += Port_TextReceivedStringDelimiter; - } - - /// - /// Disconnects this gather from the Port's TextReceived event. This will not fire LineReceived - /// after the this call. - /// - public void Stop() - { - Port.TextReceived -= Port_TextReceived; - Port.TextReceived -= Port_TextReceivedStringDelimiter; - } - - /// - /// Handler for raw data coming from port - /// - void Port_TextReceived(object sender, GenericCommMethodReceiveTextArgs args) - { - var handler = LineReceived; - if (handler != null) - { - ReceiveBuffer.Append(args.Text); - var str = ReceiveBuffer.ToString(); - var lines = str.Split(Delimiter); - if (lines.Length > 0) - { - for (int i = 0; i < lines.Length - 1; i++) - { - string strToSend = null; - if (IncludeDelimiter) - strToSend = lines[i] + Delimiter; - else - strToSend = lines[i]; - handler(this, new GenericCommMethodReceiveTextArgs(strToSend)); - } - ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]); - } - } - } - - /// - /// - /// - /// - /// - void Port_TextReceivedStringDelimiter(object sender, GenericCommMethodReceiveTextArgs args) - { - var handler = LineReceived; - if (handler != null) - { - // Receive buffer should either be empty or not contain the delimiter - // If the line does not have a delimiter, append the - ReceiveBuffer.Append(args.Text); - var str = ReceiveBuffer.ToString(); - - // Case: Receiving DEVICE get version\x0d\0x0a+OK "value":"1234"\x0d\x0a - - // RX: DEV - // Split: (1) "DEV" - // RX: I - // Split: (1) "DEVI" - // RX: CE get version - // Split: (1) "DEVICE get version" - // RX: \x0d\x0a+OK "value":"1234"\x0d\x0a - // Split: (2) DEVICE get version, +OK "value":"1234" - - // Iterate the delimiters and fire an event for any matching delimiter - foreach (var delimiter in StringDelimiters) - { - var lines = Regex.Split(str, delimiter); - if (lines.Length == 1) - continue; - - for (int i = 0; i < lines.Length - 1; i++) - { - string strToSend = null; - if (IncludeDelimiter) - strToSend = lines[i] + delimiter; - else - strToSend = lines[i]; - handler(this, new GenericCommMethodReceiveTextArgs(strToSend, delimiter)); - } - ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]); - } - } - } - - /// - /// Deconstructor. Disconnects from port TextReceived events. - /// - ~CommunicationGather() - { - Stop(); - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Crestron.SimplSharp; + +using PepperDash.Core; + + +namespace PepperDash.Core +{ + /// + /// Defines the string event handler for line events on the gather + /// + /// + public delegate void LineReceivedHandler(string text); + + /// + /// Attaches to IBasicCommunication as a text gather + /// + public class CommunicationGather + { + /// + /// Event that fires when a line is received from the IBasicCommunication source. + /// The event merely contains the text, not an EventArgs type class. + /// + public event EventHandler LineReceived; + + /// + /// The communication port that this gathers on + /// + public ICommunicationReceiver Port { get; private set; } + + /// + /// Default false. If true, the delimiter will be included in the line output + /// events + /// + public bool IncludeDelimiter { get; set; } + + /// + /// For receive buffer + /// + StringBuilder ReceiveBuffer = new StringBuilder(); + + /// + /// Delimiter, like it says! + /// + char Delimiter; + + string[] StringDelimiters; + + /// + /// Constructor for using a char delimiter + /// + /// + /// + public CommunicationGather(ICommunicationReceiver port, char delimiter) + { + Port = port; + Delimiter = delimiter; + port.TextReceived += new EventHandler(Port_TextReceived); + } + + /// + /// Constructor for using a single string delimiter + /// + /// + /// + public CommunicationGather(ICommunicationReceiver port, string delimiter) + :this(port, new string[] { delimiter} ) + { + } + + /// + /// Constructor for using an array of string delimiters + /// + /// + /// + public CommunicationGather(ICommunicationReceiver port, string[] delimiters) + { + Port = port; + StringDelimiters = delimiters; + port.TextReceived += Port_TextReceivedStringDelimiter; + } + + /// + /// Disconnects this gather from the Port's TextReceived event. This will not fire LineReceived + /// after the this call. + /// + public void Stop() + { + Port.TextReceived -= Port_TextReceived; + Port.TextReceived -= Port_TextReceivedStringDelimiter; + } + + /// + /// Handler for raw data coming from port + /// + void Port_TextReceived(object sender, GenericCommMethodReceiveTextArgs args) + { + var handler = LineReceived; + if (handler != null) + { + ReceiveBuffer.Append(args.Text); + var str = ReceiveBuffer.ToString(); + var lines = str.Split(Delimiter); + if (lines.Length > 0) + { + for (int i = 0; i < lines.Length - 1; i++) + { + string strToSend = null; + if (IncludeDelimiter) + strToSend = lines[i] + Delimiter; + else + strToSend = lines[i]; + handler(this, new GenericCommMethodReceiveTextArgs(strToSend)); + } + ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]); + } + } + } + + /// + /// + /// + /// + /// + void Port_TextReceivedStringDelimiter(object sender, GenericCommMethodReceiveTextArgs args) + { + var handler = LineReceived; + if (handler != null) + { + // Receive buffer should either be empty or not contain the delimiter + // If the line does not have a delimiter, append the + ReceiveBuffer.Append(args.Text); + var str = ReceiveBuffer.ToString(); + + // Case: Receiving DEVICE get version\x0d\0x0a+OK "value":"1234"\x0d\x0a + + // RX: DEV + // Split: (1) "DEV" + // RX: I + // Split: (1) "DEVI" + // RX: CE get version + // Split: (1) "DEVICE get version" + // RX: \x0d\x0a+OK "value":"1234"\x0d\x0a + // Split: (2) DEVICE get version, +OK "value":"1234" + + // Iterate the delimiters and fire an event for any matching delimiter + foreach (var delimiter in StringDelimiters) + { + var lines = Regex.Split(str, delimiter); + if (lines.Length == 1) + continue; + + for (int i = 0; i < lines.Length - 1; i++) + { + string strToSend = null; + if (IncludeDelimiter) + strToSend = lines[i] + delimiter; + else + strToSend = lines[i]; + handler(this, new GenericCommMethodReceiveTextArgs(strToSend, delimiter)); + } + ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]); + } + } + } + + /// + /// Deconstructor. Disconnects from port TextReceived events. + /// + ~CommunicationGather() + { + Stop(); + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Comm/CommunicationStreamDebugging.cs b/src/Comm/CommunicationStreamDebugging.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Comm/CommunicationStreamDebugging.cs rename to src/Comm/CommunicationStreamDebugging.cs diff --git a/Pepperdash Core/Pepperdash Core/Comm/ControlPropertiesConfig.cs b/src/Comm/ControlPropertiesConfig.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Comm/ControlPropertiesConfig.cs rename to src/Comm/ControlPropertiesConfig.cs diff --git a/Pepperdash Core/Pepperdash Core/Comm/DynamicTCPServer.cs b/src/Comm/DynamicTCPServer.cs similarity index 97% rename from Pepperdash Core/Pepperdash Core/Comm/DynamicTCPServer.cs rename to src/Comm/DynamicTCPServer.cs index bad57f4..c74219d 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/DynamicTCPServer.cs +++ b/src/Comm/DynamicTCPServer.cs @@ -1,684 +1,684 @@ -/*PepperDash Technology Corp. -JAG -Copyright: 2017 ------------------------------------- -***Notice of Ownership and Copyright*** -The material in which this notice appears is the property of PepperDash Technology Corporation, -which claims copyright under the laws of the United States of America in the entire body of material -and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, -of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. -PepperDash Technology Corporation reserves all rights under applicable laws. ------------------------------------- */ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronSockets; -using PepperDash.Core; - -namespace DynamicTCP -{ - public class DynamicTCPServer : Device - { - #region Events - /// - /// Event for Receiving text - /// - public event EventHandler TextReceived; - - /// - /// Event for client connection socket status change - /// - public event EventHandler ClientConnectionChange; - - /// - /// Event for Server State Change - /// - public event EventHandler ServerStateChange; - #endregion - - #region Properties/Variables - /// - /// Secure or unsecure TCP server. Defaults to Unsecure or standard TCP server without SSL - /// - public bool Secure { get; set; } - - /// - /// S+ Helper for Secure bool. Parameter in SIMPL+ so there is no get, one way set from simpl+ Param to property in func main of SIMPL+ - /// - public ushort USecure - { - set - { - if (value == 1) - Secure = true; - else if (value == 0) - Secure = false; - } - } - - /// - /// Text representation of the Socket Status enum values for the server - /// - public string Status - { - get - { - if (Secure ? SecureServer != null : UnsecureServer != null) - return Secure ? SecureServer.State.ToString() : UnsecureServer.State.ToString(); - else - return ""; - } - - } - - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get - { - return (Secure ? SecureServer != null : UnsecureServer != null) && - (Secure ? SecureServer.State == ServerState.SERVER_CONNECTED : UnsecureServer.State == ServerState.SERVER_CONNECTED); - } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// Bool showing if socket is connected - /// - public bool IsListening - { - get { return (Secure ? SecureServer != null : UnsecureServer != null) && - (Secure ? SecureServer.State == ServerState.SERVER_LISTENING : UnsecureServer.State == ServerState.SERVER_LISTENING); } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsListening - { - get { return (ushort)(IsListening ? 1 : 0); } - } - - 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. - /// - public ushort NumberOfClientsConnected - { - get - { - if (Secure ? SecureServer != null : UnsecureServer != null) - return Secure ? (ushort)SecureServer.NumberOfClientsConnected : (ushort)UnsecureServer.NumberOfClientsConnected; - return 0; - } - } - - /// - /// Port Server should listen on - /// - public int Port { get; set; } - - /// - /// S+ helper for Port - /// - public ushort UPort - { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } - - /// - /// Bool to show whether the server requires a preshared key. Must be set the same in the client, and if true shared keys must be identical on server/client - /// - public bool SharedKeyRequired { get; set; } - - /// - /// S+ helper for requires shared key bool - /// - public ushort USharedKeyRequired - { - set - { - if (value == 1) - SharedKeyRequired = true; - else - SharedKeyRequired = false; - } - } - - /// - /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module. - /// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called - /// - public string SharedKey { get; set; } - - /// - /// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received - /// - public bool HeartbeatRequired { get; set; } - - /// - /// S+ Helper for Heartbeat Required - /// - public ushort UHeartbeatRequired - { - set - { - if (value == 1) - HeartbeatRequired = true; - else - HeartbeatRequired = false; - } - } - - /// - /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ - /// - public int HeartbeatRequiredIntervalMs { get; set; } - - /// - /// Simpl+ Heartbeat Analog value in seconds - /// - public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } } - - /// - /// String to Match for heartbeat. If null or empty any string will reset heartbeat timer - /// - public string HeartbeatStringToMatch { get; set; } - - //private timers for Heartbeats per client - Dictionary HeartbeatTimerDictionary = new Dictionary(); - - //flags to show the secure server is waiting for client at index to send the shared key - List WaitingForSharedKey = new List(); - - //Store the connected client indexes - List ConnectedClientsIndexes = new List(); - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// Private flag to note that the server has stopped intentionally - /// - private bool ServerStopped { get; set; } - - //Servers - SecureTCPServer SecureServer; - TCPServer UnsecureServer; - - #endregion - - #region Constructors - /// - /// constructor - /// - public DynamicTCPServer() - : base("Uninitialized Dynamic TCP Server") - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - Secure = false; - } - #endregion - - #region Methods - Server Actions - /// - /// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+ - /// - /// - public void Initialize(string key) - { - Key = key; - } - - /// - /// Start listening on the specified port - /// - public void Listen() - { - try - { - if (Port < 1 || Port > 65535) - { - Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericSecureTcpClient '{0}': Invalid port", Key); - ErrorLog.Warn(string.Format("GenericSecureTcpClient '{0}': Invalid port", Key)); - return; - } - if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) - { - Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericSecureTcpClient '{0}': No Shared Key set", Key); - ErrorLog.Warn(string.Format("GenericSecureTcpClient '{0}': No Shared Key set", Key)); - return; - } - if (IsListening) - return; - if (Secure) - { - SecureServer = new SecureTCPServer(Port, MaxClients); - SecureServer.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SecureServer_SocketStatusChange); - ServerStopped = false; - SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback); - onServerStateChange(); - Debug.Console(2, "Secure Server Status: {0}, Socket Status: {1}\r\n", SecureServer.State.ToString(), SecureServer.ServerSocketStatus); - } - else - { - UnsecureServer = new TCPServer(Port, MaxClients); - UnsecureServer.SocketStatusChange += new TCPServerSocketStatusChangeEventHandler(UnsecureServer_SocketStatusChange); - ServerStopped = false; - UnsecureServer.WaitForConnectionAsync(IPAddress.Any, UnsecureConnectCallback); - onServerStateChange(); - Debug.Console(2, "Unsecure Server Status: {0}, Socket Status: {1}\r\n", UnsecureServer.State.ToString(), UnsecureServer.ServerSocketStatus); - } - } - catch (Exception ex) - { - ErrorLog.Error("Error with Dynamic Server: {0}", ex.ToString()); - } - } - - /// - /// Stop Listeneing - /// - public void StopListening() - { - Debug.Console(2, "Stopping Listener"); - if (SecureServer != null) - SecureServer.Stop(); - if (UnsecureServer != null) - UnsecureServer.Stop(); - ServerStopped = true; - onServerStateChange(); - } - - /// - /// Disconnect All Clients - /// - public void DisconnectAllClients() - { - Debug.Console(2, "Disconnecting All Clients"); - if (SecureServer != null) - SecureServer.DisconnectAll(); - if (UnsecureServer != null) - UnsecureServer.DisconnectAll(); - onConnectionChange(); - onServerStateChange(); //State shows both listening and connected - } - - /// - /// Broadcast text from server to all connected clients - /// - /// - public void BroadcastText(string text) - { - if (ConnectedClientsIndexes.Count > 0) - { - byte[] b = Encoding.GetEncoding(28591).GetBytes(text); - if (Secure) - foreach (uint i in ConnectedClientsIndexes) - SecureServer.SendDataAsync(i, b, b.Length, SecureSendDataAsyncCallback); - else - foreach (uint i in ConnectedClientsIndexes) - UnsecureServer.SendDataAsync(i, b, b.Length, UnsecureSendDataAsyncCallback); - } - } - - /// - /// Not sure this is useful in library, maybe Pro?? - /// - /// - /// - public void SendTextToClient(string text, uint clientIndex) - { - byte[] b = Encoding.GetEncoding(28591).GetBytes(text); - if (Secure) - SecureServer.SendDataAsync(clientIndex, b, b.Length, SecureSendDataAsyncCallback); - else - UnsecureServer.SendDataAsync(clientIndex, b, b.Length, UnsecureSendDataAsyncCallback); - } - - //private method to check heartbeat requirements and start or reset timer - void checkHeartbeat(uint clientIndex, string received) - { - if (HeartbeatRequired) - { - if (!string.IsNullOrEmpty(HeartbeatStringToMatch)) - { - if (received == HeartbeatStringToMatch) - { - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) - HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); - else - { - CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); - HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); - } - } - } - else - { - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) - HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); - else - { - CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); - HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); - } - } - } - } - #endregion - - #region Methods - HeartbeatTimer Callback - - void HeartbeatTimer_CallbackFunction(object o) - { - uint clientIndex = (uint)o; - - string address = Secure ? SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex) : - UnsecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); - - ErrorLog.Error("Heartbeat not received for Client at IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE", address); - Debug.Console(2, "Heartbeat not received for Client at IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE", address); - - SendTextToClient("Heartbeat not received by server, closing connection", clientIndex); - - if (Secure) - SecureServer.Disconnect(clientIndex); - else - UnsecureServer.Disconnect(clientIndex); - HeartbeatTimerDictionary.Remove(clientIndex); - } - - #endregion - - #region Methods - Socket Status Changed Callbacks - /// - /// Secure Server Socket Status Changed Callback - /// - /// - /// - /// - void SecureServer_SocketStatusChange(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus) - { - Debug.Console(2, "Client at {0} ServerSocketStatus {1}", - server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), serverSocketStatus.ToString()); - if (server.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) - { - if (SharedKeyRequired && !WaitingForSharedKey.Contains(clientIndex)) - WaitingForSharedKey.Add(clientIndex); - if (!ConnectedClientsIndexes.Contains(clientIndex)) - ConnectedClientsIndexes.Add(clientIndex); - } - else - { - if (ConnectedClientsIndexes.Contains(clientIndex)) - ConnectedClientsIndexes.Remove(clientIndex); - if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) - HeartbeatTimerDictionary.Remove(clientIndex); - } - if(SecureServer.ServerSocketStatus.ToString() != Status) - onConnectionChange(); - } - - /// - /// TCP Server (Unsecure) Socket Status Change Callback - /// - /// - /// - /// - void UnsecureServer_SocketStatusChange(TCPServer server, uint clientIndex, SocketStatus serverSocketStatus) - { - Debug.Console(2, "Client at {0} ServerSocketStatus {1}", - server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), serverSocketStatus.ToString()); - if (server.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) - { - if (SharedKeyRequired && !WaitingForSharedKey.Contains(clientIndex)) - WaitingForSharedKey.Add(clientIndex); - if (!ConnectedClientsIndexes.Contains(clientIndex)) - ConnectedClientsIndexes.Add(clientIndex); - } - else - { - if (ConnectedClientsIndexes.Contains(clientIndex)) - ConnectedClientsIndexes.Remove(clientIndex); - if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) - HeartbeatTimerDictionary.Remove(clientIndex); - } - if (UnsecureServer.ServerSocketStatus.ToString() != Status) - onConnectionChange(); - } - #endregion - - #region Methods Connected Callbacks - /// - /// Secure TCP Client Connected to Secure Server Callback - /// - /// - /// - void SecureConnectCallback(SecureTCPServer mySecureTCPServer, uint clientIndex) - { - if (mySecureTCPServer.ClientConnected(clientIndex)) - { - if (SharedKeyRequired) - { - byte[] b = Encoding.GetEncoding(28591).GetBytes(SharedKey + "\n"); - mySecureTCPServer.SendDataAsync(clientIndex, b, b.Length, SecureSendDataAsyncCallback); - Debug.Console(2, "Sent Shared Key to client at {0}", mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); - } - if (HeartbeatRequired) - { - CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); - HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); - } - mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback); - if (mySecureTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped) - mySecureTCPServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback); - } - } - - /// - /// Unsecure TCP Client Connected to Unsecure Server Callback - /// - /// - /// - void UnsecureConnectCallback(TCPServer myTCPServer, uint clientIndex) - { - if (myTCPServer.ClientConnected(clientIndex)) - { - if (SharedKeyRequired) - { - byte[] b = Encoding.GetEncoding(28591).GetBytes(SharedKey + "\n"); - myTCPServer.SendDataAsync(clientIndex, b, b.Length, UnsecureSendDataAsyncCallback); - Debug.Console(2, "Sent Shared Key to client at {0}", myTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); - } - if (HeartbeatRequired) - { - CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); - HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); - } - myTCPServer.ReceiveDataAsync(clientIndex, UnsecureReceivedDataAsyncCallback); - if (myTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped) - myTCPServer.WaitForConnectionAsync(IPAddress.Any, UnsecureConnectCallback); - } - if (myTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped) - myTCPServer.WaitForConnectionAsync(IPAddress.Any, UnsecureConnectCallback); - } - #endregion - - #region Methods - Send/Receive Callbacks - /// - /// Secure Send Data Async Callback - /// - /// - /// - /// - void SecureSendDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesSent) - { - //Seems there is nothing to do here - } - - /// - /// Unsecure Send Data Asyc Callback - /// - /// - /// - /// - void UnsecureSendDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesSent) - { - //Seems there is nothing to do here - } - - /// - /// Secure Received Data Async Callback - /// - /// - /// - /// - void SecureReceivedDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived) - { - if (numberOfBytesReceived > 0) - { - string received = "Nothing"; - 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(2, "Client at index {0} Shared key did not match the server, disconnecting client", clientIndex); - ErrorLog.Error("Client at index {0} Shared key did not match the server, disconnecting client", clientIndex); - mySecureTCPServer.SendDataAsync(clientIndex, b, b.Length, null); - mySecureTCPServer.Disconnect(clientIndex); - } - if (mySecureTCPServer.NumberOfClientsConnected > 0) - mySecureTCPServer.ReceiveDataAsync(SecureReceivedDataAsyncCallback); - WaitingForSharedKey.Remove(clientIndex); - byte[] skResponse = Encoding.GetEncoding(28591).GetBytes("Shared Key Match, Connected and ready for communication"); - mySecureTCPServer.SendDataAsync(clientIndex, skResponse, skResponse.Length, null); - mySecureTCPServer.ReceiveDataAsync(SecureReceivedDataAsyncCallback); - } - else - { - mySecureTCPServer.ReceiveDataAsync(SecureReceivedDataAsyncCallback); - Debug.Console(2, "Secure Server Listening on Port: {0}, client IP: {1}, NumberOfBytesReceived: {2}, Received: {3}\r\n", - mySecureTCPServer.PortNumber, mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), numberOfBytesReceived, received); - onTextReceived(received); - } - checkHeartbeat(clientIndex, received); - } - if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) - mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback); - } - - /// - /// Unsecure Received Data Async Callback - /// - /// - /// - /// - void UnsecureReceivedDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesReceived) - { - if (numberOfBytesReceived > 0) - { - string received = "Nothing"; - 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(2, "Client at index {0} Shared key did not match the server, disconnecting client", clientIndex); - ErrorLog.Error("Client at index {0} Shared key did not match the server, disconnecting client", clientIndex); - myTCPServer.SendDataAsync(clientIndex, b, b.Length, null); - myTCPServer.Disconnect(clientIndex); - } - if (myTCPServer.NumberOfClientsConnected > 0) - myTCPServer.ReceiveDataAsync(UnsecureReceivedDataAsyncCallback); - WaitingForSharedKey.Remove(clientIndex); - byte[] skResponse = Encoding.GetEncoding(28591).GetBytes("Shared Key Match, Connected and ready for communication"); - myTCPServer.SendDataAsync(clientIndex, skResponse, skResponse.Length, null); - myTCPServer.ReceiveDataAsync(UnsecureReceivedDataAsyncCallback); - } - else - { - myTCPServer.ReceiveDataAsync(UnsecureReceivedDataAsyncCallback); - Debug.Console(2, "Secure Server Listening on Port: {0}, client IP: {1}, NumberOfBytesReceived: {2}, Received: {3}\r\n", - myTCPServer.PortNumber, myTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), numberOfBytesReceived, received); - onTextReceived(received); - } - checkHeartbeat(clientIndex, received); - } - if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) - myTCPServer.ReceiveDataAsync(clientIndex, UnsecureReceivedDataAsyncCallback); - } - #endregion - - #region Methods - EventHelpers/Callbacks - //Private Helper method to call the Connection Change Event - void onConnectionChange() - { - var handler = ClientConnectionChange; - if (handler != null) - { - if (Secure) - handler(this, new DynamicTCPSocketStatusChangeEventArgs(SecureServer, Secure)); - else - handler(this, new DynamicTCPSocketStatusChangeEventArgs(UnsecureServer, Secure)); - } - } - - //Private Helper Method to call the Text Received Event - void onTextReceived(string text) - { - var handler = TextReceived; - if (handler != null) - handler(this, new CopyCoreForSimplpGenericCommMethodReceiveTextArgs(text)); - } - - //Private Helper Method to call the Server State Change Event - void onServerStateChange() - { - var handler = ServerStateChange; - if(handler != null) - { - if(Secure) - handler(this, new DynamicTCPServerStateChangedEventArgs(SecureServer, Secure)); - else - handler(this, new DynamicTCPServerStateChangedEventArgs(UnsecureServer, Secure)); - } - } - - //Private Event Handler method to handle the closing of connections when the program stops - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping) - { - Debug.Console(1, this, "Program stopping. Closing server"); - DisconnectAllClients(); - StopListening(); - } - } - #endregion - } +/*PepperDash Technology Corp. +JAG +Copyright: 2017 +------------------------------------ +***Notice of Ownership and Copyright*** +The material in which this notice appears is the property of PepperDash Technology Corporation, +which claims copyright under the laws of the United States of America in the entire body of material +and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, +of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. +PepperDash Technology Corporation reserves all rights under applicable laws. +------------------------------------ */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronSockets; +using PepperDash.Core; + +namespace DynamicTCP +{ + public class DynamicTCPServer : Device + { + #region Events + /// + /// Event for Receiving text + /// + public event EventHandler TextReceived; + + /// + /// Event for client connection socket status change + /// + public event EventHandler ClientConnectionChange; + + /// + /// Event for Server State Change + /// + public event EventHandler ServerStateChange; + #endregion + + #region Properties/Variables + /// + /// Secure or unsecure TCP server. Defaults to Unsecure or standard TCP server without SSL + /// + public bool Secure { get; set; } + + /// + /// S+ Helper for Secure bool. Parameter in SIMPL+ so there is no get, one way set from simpl+ Param to property in func main of SIMPL+ + /// + public ushort USecure + { + set + { + if (value == 1) + Secure = true; + else if (value == 0) + Secure = false; + } + } + + /// + /// Text representation of the Socket Status enum values for the server + /// + public string Status + { + get + { + if (Secure ? SecureServer != null : UnsecureServer != null) + return Secure ? SecureServer.State.ToString() : UnsecureServer.State.ToString(); + else + return ""; + } + + } + + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get + { + return (Secure ? SecureServer != null : UnsecureServer != null) && + (Secure ? SecureServer.State == ServerState.SERVER_CONNECTED : UnsecureServer.State == ServerState.SERVER_CONNECTED); + } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// Bool showing if socket is connected + /// + public bool IsListening + { + get { return (Secure ? SecureServer != null : UnsecureServer != null) && + (Secure ? SecureServer.State == ServerState.SERVER_LISTENING : UnsecureServer.State == ServerState.SERVER_LISTENING); } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsListening + { + get { return (ushort)(IsListening ? 1 : 0); } + } + + 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. + /// + public ushort NumberOfClientsConnected + { + get + { + if (Secure ? SecureServer != null : UnsecureServer != null) + return Secure ? (ushort)SecureServer.NumberOfClientsConnected : (ushort)UnsecureServer.NumberOfClientsConnected; + return 0; + } + } + + /// + /// Port Server should listen on + /// + public int Port { get; set; } + + /// + /// S+ helper for Port + /// + public ushort UPort + { + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } + } + + /// + /// Bool to show whether the server requires a preshared key. Must be set the same in the client, and if true shared keys must be identical on server/client + /// + public bool SharedKeyRequired { get; set; } + + /// + /// S+ helper for requires shared key bool + /// + public ushort USharedKeyRequired + { + set + { + if (value == 1) + SharedKeyRequired = true; + else + SharedKeyRequired = false; + } + } + + /// + /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module. + /// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called + /// + public string SharedKey { get; set; } + + /// + /// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received + /// + public bool HeartbeatRequired { get; set; } + + /// + /// S+ Helper for Heartbeat Required + /// + public ushort UHeartbeatRequired + { + set + { + if (value == 1) + HeartbeatRequired = true; + else + HeartbeatRequired = false; + } + } + + /// + /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ + /// + public int HeartbeatRequiredIntervalMs { get; set; } + + /// + /// Simpl+ Heartbeat Analog value in seconds + /// + public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } } + + /// + /// String to Match for heartbeat. If null or empty any string will reset heartbeat timer + /// + public string HeartbeatStringToMatch { get; set; } + + //private timers for Heartbeats per client + Dictionary HeartbeatTimerDictionary = new Dictionary(); + + //flags to show the secure server is waiting for client at index to send the shared key + List WaitingForSharedKey = new List(); + + //Store the connected client indexes + List ConnectedClientsIndexes = new List(); + + /// + /// Defaults to 2000 + /// + public int BufferSize { get; set; } + + /// + /// Private flag to note that the server has stopped intentionally + /// + private bool ServerStopped { get; set; } + + //Servers + SecureTCPServer SecureServer; + TCPServer UnsecureServer; + + #endregion + + #region Constructors + /// + /// constructor + /// + public DynamicTCPServer() + : base("Uninitialized Dynamic TCP Server") + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + Secure = false; + } + #endregion + + #region Methods - Server Actions + /// + /// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+ + /// + /// + public void Initialize(string key) + { + Key = key; + } + + /// + /// Start listening on the specified port + /// + public void Listen() + { + try + { + if (Port < 1 || Port > 65535) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericSecureTcpClient '{0}': Invalid port", Key); + ErrorLog.Warn(string.Format("GenericSecureTcpClient '{0}': Invalid port", Key)); + return; + } + if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericSecureTcpClient '{0}': No Shared Key set", Key); + ErrorLog.Warn(string.Format("GenericSecureTcpClient '{0}': No Shared Key set", Key)); + return; + } + if (IsListening) + return; + if (Secure) + { + SecureServer = new SecureTCPServer(Port, MaxClients); + SecureServer.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SecureServer_SocketStatusChange); + ServerStopped = false; + SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback); + onServerStateChange(); + Debug.Console(2, "Secure Server Status: {0}, Socket Status: {1}\r\n", SecureServer.State.ToString(), SecureServer.ServerSocketStatus); + } + else + { + UnsecureServer = new TCPServer(Port, MaxClients); + UnsecureServer.SocketStatusChange += new TCPServerSocketStatusChangeEventHandler(UnsecureServer_SocketStatusChange); + ServerStopped = false; + UnsecureServer.WaitForConnectionAsync(IPAddress.Any, UnsecureConnectCallback); + onServerStateChange(); + Debug.Console(2, "Unsecure Server Status: {0}, Socket Status: {1}\r\n", UnsecureServer.State.ToString(), UnsecureServer.ServerSocketStatus); + } + } + catch (Exception ex) + { + ErrorLog.Error("Error with Dynamic Server: {0}", ex.ToString()); + } + } + + /// + /// Stop Listeneing + /// + public void StopListening() + { + Debug.Console(2, "Stopping Listener"); + if (SecureServer != null) + SecureServer.Stop(); + if (UnsecureServer != null) + UnsecureServer.Stop(); + ServerStopped = true; + onServerStateChange(); + } + + /// + /// Disconnect All Clients + /// + public void DisconnectAllClients() + { + Debug.Console(2, "Disconnecting All Clients"); + if (SecureServer != null) + SecureServer.DisconnectAll(); + if (UnsecureServer != null) + UnsecureServer.DisconnectAll(); + onConnectionChange(); + onServerStateChange(); //State shows both listening and connected + } + + /// + /// Broadcast text from server to all connected clients + /// + /// + public void BroadcastText(string text) + { + if (ConnectedClientsIndexes.Count > 0) + { + byte[] b = Encoding.GetEncoding(28591).GetBytes(text); + if (Secure) + foreach (uint i in ConnectedClientsIndexes) + SecureServer.SendDataAsync(i, b, b.Length, SecureSendDataAsyncCallback); + else + foreach (uint i in ConnectedClientsIndexes) + UnsecureServer.SendDataAsync(i, b, b.Length, UnsecureSendDataAsyncCallback); + } + } + + /// + /// Not sure this is useful in library, maybe Pro?? + /// + /// + /// + public void SendTextToClient(string text, uint clientIndex) + { + byte[] b = Encoding.GetEncoding(28591).GetBytes(text); + if (Secure) + SecureServer.SendDataAsync(clientIndex, b, b.Length, SecureSendDataAsyncCallback); + else + UnsecureServer.SendDataAsync(clientIndex, b, b.Length, UnsecureSendDataAsyncCallback); + } + + //private method to check heartbeat requirements and start or reset timer + void checkHeartbeat(uint clientIndex, string received) + { + if (HeartbeatRequired) + { + if (!string.IsNullOrEmpty(HeartbeatStringToMatch)) + { + if (received == HeartbeatStringToMatch) + { + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); + else + { + CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); + HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); + } + } + } + else + { + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); + else + { + CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); + HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); + } + } + } + } + #endregion + + #region Methods - HeartbeatTimer Callback + + void HeartbeatTimer_CallbackFunction(object o) + { + uint clientIndex = (uint)o; + + string address = Secure ? SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex) : + UnsecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); + + ErrorLog.Error("Heartbeat not received for Client at IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE", address); + Debug.Console(2, "Heartbeat not received for Client at IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE", address); + + SendTextToClient("Heartbeat not received by server, closing connection", clientIndex); + + if (Secure) + SecureServer.Disconnect(clientIndex); + else + UnsecureServer.Disconnect(clientIndex); + HeartbeatTimerDictionary.Remove(clientIndex); + } + + #endregion + + #region Methods - Socket Status Changed Callbacks + /// + /// Secure Server Socket Status Changed Callback + /// + /// + /// + /// + void SecureServer_SocketStatusChange(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus) + { + Debug.Console(2, "Client at {0} ServerSocketStatus {1}", + server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), serverSocketStatus.ToString()); + if (server.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) + { + if (SharedKeyRequired && !WaitingForSharedKey.Contains(clientIndex)) + WaitingForSharedKey.Add(clientIndex); + if (!ConnectedClientsIndexes.Contains(clientIndex)) + ConnectedClientsIndexes.Add(clientIndex); + } + else + { + if (ConnectedClientsIndexes.Contains(clientIndex)) + ConnectedClientsIndexes.Remove(clientIndex); + if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) + HeartbeatTimerDictionary.Remove(clientIndex); + } + if(SecureServer.ServerSocketStatus.ToString() != Status) + onConnectionChange(); + } + + /// + /// TCP Server (Unsecure) Socket Status Change Callback + /// + /// + /// + /// + void UnsecureServer_SocketStatusChange(TCPServer server, uint clientIndex, SocketStatus serverSocketStatus) + { + Debug.Console(2, "Client at {0} ServerSocketStatus {1}", + server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), serverSocketStatus.ToString()); + if (server.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) + { + if (SharedKeyRequired && !WaitingForSharedKey.Contains(clientIndex)) + WaitingForSharedKey.Add(clientIndex); + if (!ConnectedClientsIndexes.Contains(clientIndex)) + ConnectedClientsIndexes.Add(clientIndex); + } + else + { + if (ConnectedClientsIndexes.Contains(clientIndex)) + ConnectedClientsIndexes.Remove(clientIndex); + if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) + HeartbeatTimerDictionary.Remove(clientIndex); + } + if (UnsecureServer.ServerSocketStatus.ToString() != Status) + onConnectionChange(); + } + #endregion + + #region Methods Connected Callbacks + /// + /// Secure TCP Client Connected to Secure Server Callback + /// + /// + /// + void SecureConnectCallback(SecureTCPServer mySecureTCPServer, uint clientIndex) + { + if (mySecureTCPServer.ClientConnected(clientIndex)) + { + if (SharedKeyRequired) + { + byte[] b = Encoding.GetEncoding(28591).GetBytes(SharedKey + "\n"); + mySecureTCPServer.SendDataAsync(clientIndex, b, b.Length, SecureSendDataAsyncCallback); + Debug.Console(2, "Sent Shared Key to client at {0}", mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); + } + if (HeartbeatRequired) + { + CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); + HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); + } + mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback); + if (mySecureTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped) + mySecureTCPServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback); + } + } + + /// + /// Unsecure TCP Client Connected to Unsecure Server Callback + /// + /// + /// + void UnsecureConnectCallback(TCPServer myTCPServer, uint clientIndex) + { + if (myTCPServer.ClientConnected(clientIndex)) + { + if (SharedKeyRequired) + { + byte[] b = Encoding.GetEncoding(28591).GetBytes(SharedKey + "\n"); + myTCPServer.SendDataAsync(clientIndex, b, b.Length, UnsecureSendDataAsyncCallback); + Debug.Console(2, "Sent Shared Key to client at {0}", myTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); + } + if (HeartbeatRequired) + { + CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); + HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); + } + myTCPServer.ReceiveDataAsync(clientIndex, UnsecureReceivedDataAsyncCallback); + if (myTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped) + myTCPServer.WaitForConnectionAsync(IPAddress.Any, UnsecureConnectCallback); + } + if (myTCPServer.State != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped) + myTCPServer.WaitForConnectionAsync(IPAddress.Any, UnsecureConnectCallback); + } + #endregion + + #region Methods - Send/Receive Callbacks + /// + /// Secure Send Data Async Callback + /// + /// + /// + /// + void SecureSendDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesSent) + { + //Seems there is nothing to do here + } + + /// + /// Unsecure Send Data Asyc Callback + /// + /// + /// + /// + void UnsecureSendDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesSent) + { + //Seems there is nothing to do here + } + + /// + /// Secure Received Data Async Callback + /// + /// + /// + /// + void SecureReceivedDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived) + { + if (numberOfBytesReceived > 0) + { + string received = "Nothing"; + 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(2, "Client at index {0} Shared key did not match the server, disconnecting client", clientIndex); + ErrorLog.Error("Client at index {0} Shared key did not match the server, disconnecting client", clientIndex); + mySecureTCPServer.SendDataAsync(clientIndex, b, b.Length, null); + mySecureTCPServer.Disconnect(clientIndex); + } + if (mySecureTCPServer.NumberOfClientsConnected > 0) + mySecureTCPServer.ReceiveDataAsync(SecureReceivedDataAsyncCallback); + WaitingForSharedKey.Remove(clientIndex); + byte[] skResponse = Encoding.GetEncoding(28591).GetBytes("Shared Key Match, Connected and ready for communication"); + mySecureTCPServer.SendDataAsync(clientIndex, skResponse, skResponse.Length, null); + mySecureTCPServer.ReceiveDataAsync(SecureReceivedDataAsyncCallback); + } + else + { + mySecureTCPServer.ReceiveDataAsync(SecureReceivedDataAsyncCallback); + Debug.Console(2, "Secure Server Listening on Port: {0}, client IP: {1}, NumberOfBytesReceived: {2}, Received: {3}\r\n", + mySecureTCPServer.PortNumber, mySecureTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), numberOfBytesReceived, received); + onTextReceived(received); + } + checkHeartbeat(clientIndex, received); + } + if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) + mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback); + } + + /// + /// Unsecure Received Data Async Callback + /// + /// + /// + /// + void UnsecureReceivedDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesReceived) + { + if (numberOfBytesReceived > 0) + { + string received = "Nothing"; + 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(2, "Client at index {0} Shared key did not match the server, disconnecting client", clientIndex); + ErrorLog.Error("Client at index {0} Shared key did not match the server, disconnecting client", clientIndex); + myTCPServer.SendDataAsync(clientIndex, b, b.Length, null); + myTCPServer.Disconnect(clientIndex); + } + if (myTCPServer.NumberOfClientsConnected > 0) + myTCPServer.ReceiveDataAsync(UnsecureReceivedDataAsyncCallback); + WaitingForSharedKey.Remove(clientIndex); + byte[] skResponse = Encoding.GetEncoding(28591).GetBytes("Shared Key Match, Connected and ready for communication"); + myTCPServer.SendDataAsync(clientIndex, skResponse, skResponse.Length, null); + myTCPServer.ReceiveDataAsync(UnsecureReceivedDataAsyncCallback); + } + else + { + myTCPServer.ReceiveDataAsync(UnsecureReceivedDataAsyncCallback); + Debug.Console(2, "Secure Server Listening on Port: {0}, client IP: {1}, NumberOfBytesReceived: {2}, Received: {3}\r\n", + myTCPServer.PortNumber, myTCPServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), numberOfBytesReceived, received); + onTextReceived(received); + } + checkHeartbeat(clientIndex, received); + } + if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) + myTCPServer.ReceiveDataAsync(clientIndex, UnsecureReceivedDataAsyncCallback); + } + #endregion + + #region Methods - EventHelpers/Callbacks + //Private Helper method to call the Connection Change Event + void onConnectionChange() + { + var handler = ClientConnectionChange; + if (handler != null) + { + if (Secure) + handler(this, new DynamicTCPSocketStatusChangeEventArgs(SecureServer, Secure)); + else + handler(this, new DynamicTCPSocketStatusChangeEventArgs(UnsecureServer, Secure)); + } + } + + //Private Helper Method to call the Text Received Event + void onTextReceived(string text) + { + var handler = TextReceived; + if (handler != null) + handler(this, new CopyCoreForSimplpGenericCommMethodReceiveTextArgs(text)); + } + + //Private Helper Method to call the Server State Change Event + void onServerStateChange() + { + var handler = ServerStateChange; + if(handler != null) + { + if(Secure) + handler(this, new DynamicTCPServerStateChangedEventArgs(SecureServer, Secure)); + else + handler(this, new DynamicTCPServerStateChangedEventArgs(UnsecureServer, Secure)); + } + } + + //Private Event Handler method to handle the closing of connections when the program stops + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + Debug.Console(1, this, "Program stopping. Closing server"); + DisconnectAllClients(); + StopListening(); + } + } + #endregion + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Comm/EventArgs.cs b/src/Comm/EventArgs.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/Comm/EventArgs.cs rename to src/Comm/EventArgs.cs index 95dfa99..cf76d6b 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/EventArgs.cs +++ b/src/Comm/EventArgs.cs @@ -1,251 +1,251 @@ -/*PepperDash Technology Corp. -Copyright: 2017 ------------------------------------- -***Notice of Ownership and Copyright*** -The material in which this notice appears is the property of PepperDash Technology Corporation, -which claims copyright under the laws of the United States of America in the entire body of material -and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, -of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. -PepperDash Technology Corporation reserves all rights under applicable laws. ------------------------------------- */ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronSockets; - - -namespace PepperDash.Core -{ - /// - /// Delegate for notifying of socket status changes - /// - /// - public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client); - - /// - /// EventArgs class for socket status changes - /// - public class GenericSocketStatusChageEventArgs : EventArgs - { - /// - /// - /// - public ISocketStatus Client { get; private set; } - - /// - /// - /// - /// - public GenericSocketStatusChageEventArgs(ISocketStatus client) - { - Client = client; - } - /// - /// S+ Constructor - /// - public GenericSocketStatusChageEventArgs() { } - } - - /// - /// Delegate for notifying of TCP Server state changes - /// - /// - public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state); - - /// - /// EventArgs class for TCP Server state changes - /// - public class GenericTcpServerStateChangedEventArgs : EventArgs - { - /// - /// - /// - public ServerState State { get; private set; } - - /// - /// - /// - /// - public GenericTcpServerStateChangedEventArgs(ServerState state) - { - State = state; - } - /// - /// S+ Constructor - /// - public GenericTcpServerStateChangedEventArgs() { } - } - - /// - /// Delegate for TCP Server socket status changes - /// - /// - /// - /// - public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus); - /// - /// EventArgs for TCP server socket status changes - /// - public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs - { - /// - /// - /// - public object Socket { get; private set; } - /// - /// - /// - public uint ReceivedFromClientIndex { get; private set; } - /// - /// - /// - public SocketStatus ClientStatus { get; set; } - - /// - /// - /// - /// - /// - public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus) - { - Socket = socket; - ClientStatus = clientStatus; - } - - /// - /// - /// - /// - /// - /// - public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus) - { - Socket = socket; - ReceivedFromClientIndex = clientIndex; - ClientStatus = clientStatus; - } - /// - /// S+ Constructor - /// - public GenericTcpServerSocketStatusChangeEventArgs() { } - } - - /// - /// EventArgs for TCP server com method receive text - /// - public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs - { - /// - /// - /// - public uint ReceivedFromClientIndex { get; private set; } - - /// - /// - /// - public ushort ReceivedFromClientIndexShort - { - get - { - return (ushort)ReceivedFromClientIndex; - } - } - - /// - /// - /// - public string Text { get; private set; } - - /// - /// - /// - /// - public GenericTcpServerCommMethodReceiveTextArgs(string text) - { - Text = text; - } - - /// - /// - /// - /// - /// - public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex) - { - Text = text; - ReceivedFromClientIndex = clientIndex; - } - /// - /// S+ Constructor - /// - public GenericTcpServerCommMethodReceiveTextArgs() { } - } - - /// - /// EventArgs for TCP server client ready for communication - /// - public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs - { - /// - /// - /// - public bool IsReady; - - /// - /// - /// - /// - public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady) - { - IsReady = isReady; - } - /// - /// S+ Constructor - /// - public GenericTcpServerClientReadyForcommunicationsEventArgs() { } - } - - /// - /// EventArgs for UDP connected - /// - public class GenericUdpConnectedEventArgs : EventArgs - { - /// - /// - /// - public ushort UConnected; - /// - /// - /// - public bool Connected; - - /// - /// Constructor - /// - public GenericUdpConnectedEventArgs() { } - - /// - /// - /// - /// - public GenericUdpConnectedEventArgs(ushort uconnected) - { - UConnected = uconnected; - } - - /// - /// - /// - /// - public GenericUdpConnectedEventArgs(bool connected) - { - Connected = connected; - } - - } - - - +/*PepperDash Technology Corp. +Copyright: 2017 +------------------------------------ +***Notice of Ownership and Copyright*** +The material in which this notice appears is the property of PepperDash Technology Corporation, +which claims copyright under the laws of the United States of America in the entire body of material +and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, +of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. +PepperDash Technology Corporation reserves all rights under applicable laws. +------------------------------------ */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronSockets; + + +namespace PepperDash.Core +{ + /// + /// Delegate for notifying of socket status changes + /// + /// + public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client); + + /// + /// EventArgs class for socket status changes + /// + public class GenericSocketStatusChageEventArgs : EventArgs + { + /// + /// + /// + public ISocketStatus Client { get; private set; } + + /// + /// + /// + /// + public GenericSocketStatusChageEventArgs(ISocketStatus client) + { + Client = client; + } + /// + /// S+ Constructor + /// + public GenericSocketStatusChageEventArgs() { } + } + + /// + /// Delegate for notifying of TCP Server state changes + /// + /// + public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state); + + /// + /// EventArgs class for TCP Server state changes + /// + public class GenericTcpServerStateChangedEventArgs : EventArgs + { + /// + /// + /// + public ServerState State { get; private set; } + + /// + /// + /// + /// + public GenericTcpServerStateChangedEventArgs(ServerState state) + { + State = state; + } + /// + /// S+ Constructor + /// + public GenericTcpServerStateChangedEventArgs() { } + } + + /// + /// Delegate for TCP Server socket status changes + /// + /// + /// + /// + public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus); + /// + /// EventArgs for TCP server socket status changes + /// + public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs + { + /// + /// + /// + public object Socket { get; private set; } + /// + /// + /// + public uint ReceivedFromClientIndex { get; private set; } + /// + /// + /// + public SocketStatus ClientStatus { get; set; } + + /// + /// + /// + /// + /// + public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus) + { + Socket = socket; + ClientStatus = clientStatus; + } + + /// + /// + /// + /// + /// + /// + public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus) + { + Socket = socket; + ReceivedFromClientIndex = clientIndex; + ClientStatus = clientStatus; + } + /// + /// S+ Constructor + /// + public GenericTcpServerSocketStatusChangeEventArgs() { } + } + + /// + /// EventArgs for TCP server com method receive text + /// + public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs + { + /// + /// + /// + public uint ReceivedFromClientIndex { get; private set; } + + /// + /// + /// + public ushort ReceivedFromClientIndexShort + { + get + { + return (ushort)ReceivedFromClientIndex; + } + } + + /// + /// + /// + public string Text { get; private set; } + + /// + /// + /// + /// + public GenericTcpServerCommMethodReceiveTextArgs(string text) + { + Text = text; + } + + /// + /// + /// + /// + /// + public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex) + { + Text = text; + ReceivedFromClientIndex = clientIndex; + } + /// + /// S+ Constructor + /// + public GenericTcpServerCommMethodReceiveTextArgs() { } + } + + /// + /// EventArgs for TCP server client ready for communication + /// + public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs + { + /// + /// + /// + public bool IsReady; + + /// + /// + /// + /// + public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady) + { + IsReady = isReady; + } + /// + /// S+ Constructor + /// + public GenericTcpServerClientReadyForcommunicationsEventArgs() { } + } + + /// + /// EventArgs for UDP connected + /// + public class GenericUdpConnectedEventArgs : EventArgs + { + /// + /// + /// + public ushort UConnected; + /// + /// + /// + public bool Connected; + + /// + /// Constructor + /// + 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/FINISH CommStatic.cs b/src/Comm/FINISH CommStatic.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Comm/FINISH CommStatic.cs rename to src/Comm/FINISH CommStatic.cs diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericHttpSseClient.cs b/src/Comm/GenericHttpSseClient.cs similarity index 97% rename from Pepperdash Core/Pepperdash Core/Comm/GenericHttpSseClient.cs rename to src/Comm/GenericHttpSseClient.cs index 3b941c9..6992ef7 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericHttpSseClient.cs +++ b/src/Comm/GenericHttpSseClient.cs @@ -1,314 +1,314 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronIO; -using Crestron.SimplSharp.Net.Http; - -namespace PepperDash.Core -{ - /// - /// Client for communicating with an HTTP Server Side Event pattern - /// - public class GenericHttpSseClient : ICommunicationReceiver - { - /// - /// Notifies when bytes have been received - /// - public event EventHandler BytesReceived; - /// - /// Notifies when text has been received - /// - public event EventHandler TextReceived; - - /// - /// Indicates connection status - /// - public bool IsConnected - { - get; - private set; - } - - /// - /// Unique identifier for the instance - /// - public string Key - { - get; - private set; - } - - /// - /// Name for the instance - /// - public string Name - { - get; - private set; - } - - /// - /// URL of the server - /// - public string Url { get; set; } - - HttpClient Client; - HttpClientRequest Request; - - /// - /// Constructor - /// - /// - /// - public GenericHttpSseClient(string key, string name) - { - Key = key; - Name = name; - } - - /// - /// Connects to the server. Requires Url to be set first. - /// - public void Connect() - { - InitiateConnection(Url); - } - - /// - /// Disconnects from the server - /// - public void Disconnect() - { - CloseConnection(null); - } - - /// - /// Initiates connection to the server - /// - /// - public void InitiateConnection(string url) - { - CrestronInvoke.BeginInvoke(o => - { - try - { - if(string.IsNullOrEmpty(url)) - { - Debug.Console(0, this, "Error connecting to Server. No URL specified"); - return; - } - - Client = new HttpClient(); - Request = new HttpClientRequest(); - Client.Verbose = true; - Client.KeepAlive = true; - Request.Url.Parse(url); - Request.RequestType = RequestType.Get; - Request.Header.SetHeaderValue("Accept", "text/event-stream"); - - // In order to get a handle on the response stream, we have to get - // the request stream first. Boo - Client.BeginGetRequestStream(GetRequestStreamCallback, Request, null); - CrestronConsole.PrintLine("Request made!"); - } - catch (Exception e) - { - ErrorLog.Notice("Exception occured in AsyncWebPostHttps(): " + e.ToString()); - } - }); - } - - /// - /// Closes the connection to the server - /// - /// - public void CloseConnection(string s) - { - if (Client != null) - { - Client.Abort(); - IsConnected = false; - - Debug.Console(1, this, "Client Disconnected"); - } - } - - private void GetRequestStreamCallback(HttpClientRequest request, HTTP_CALLBACK_ERROR error, object status) - { - - try - { - // End the the async request operation and return the data stream - Stream requestStream = request.ThisClient.EndGetRequestStream(request, null); - // If this were something other than a GET we could write to the stream here - - // Closing makes the request happen - requestStream.Close(); - - // Get a handle on the response stream. - request.ThisClient.BeginGetResponseStream(GetResponseStreamCallback, request, status); - } - catch (Exception e) - { - ErrorLog.Notice("Exception occured in GetSecureRequestStreamCallback(): " + e.ToString()); - } - } - - /// - /// - /// - /// - /// - /// - private void GetResponseStreamCallback(HttpClientRequest request, HTTP_CALLBACK_ERROR error, object status) - { - try - { - // This closes up the GetResponseStream async - var response = request.ThisClient.EndGetResponseStream(request); - - response.DataConnection.OnBytesReceived += new EventHandler(DataConnection_OnBytesReceived); - - IsConnected = true; - - Debug.Console(1, this, "Client Disconnected"); - - Stream streamResponse = response.ContentStream; - // Object containing various states to be passed back to async callback below - RequestState asyncState = new RequestState(); - asyncState.Request = request; - asyncState.Response = response; - asyncState.StreamResponse = streamResponse; - asyncState.HttpClient = request.ThisClient; - - // This processes the ongoing data stream - Crestron.SimplSharp.CrestronIO.IAsyncResult asyncResult = null; - do - { - asyncResult = streamResponse.BeginRead(asyncState.BufferRead, 0, RequestState.BUFFER_SIZE, - new Crestron.SimplSharp.CrestronIO.AsyncCallback(ReadCallBack), asyncState); - } - while (asyncResult.CompletedSynchronously && !asyncState.Done); - - //Console.WriteLine("\r\nExit Response Callback\r\n"); - } - catch (Exception e) - { - ErrorLog.Notice("Exception occured in GetSecureRequestStreamCallback(): " + e.ToString()); - } - } - - void DataConnection_OnBytesReceived(object sender, EventArgs e) - { - Debug.Console(1, this, "DataConnection OnBytesReceived Fired"); - } - - private void ReadCallBack(Crestron.SimplSharp.CrestronIO.IAsyncResult asyncResult) - { - //we are getting back everything here, so cast the state from the call - RequestState requestState = asyncResult.AsyncState as RequestState; - Stream responseStream = requestState.StreamResponse; - - int read = responseStream.EndRead(asyncResult); - // Read the HTML page and then print it to the console. - if (read > 0) - { - var bytes = requestState.BufferRead; - - var bytesHandler = BytesReceived; - if (bytesHandler != null) - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - var textHandler = TextReceived; - if (textHandler != null) - { - var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } - - //requestState.RequestData.Append(Encoding.ASCII.GetString(requestState.BufferRead, 0, read)); - //CrestronConsole.PrintLine(requestState.RequestData.ToString()); - - //clear the byte array buffer used. - Array.Clear(requestState.BufferRead, 0, requestState.BufferRead.Length); - - if (asyncResult.CompletedSynchronously) - { - return; - } - - Crestron.SimplSharp.CrestronIO.IAsyncResult asynchronousResult; - do - { - asynchronousResult = responseStream.BeginRead(requestState.BufferRead, 0, RequestState.BUFFER_SIZE, - new Crestron.SimplSharp.CrestronIO.AsyncCallback(ReadCallBack), requestState); - } - while (asynchronousResult.CompletedSynchronously && !requestState.Done); - } - else - { - requestState.Done = true; - } - } - } - - /// - /// Stores the state of the request - /// - public class RequestState - { - /// - /// - /// - public const int BUFFER_SIZE = 10000; - /// - /// - /// - public byte[] BufferRead; - /// - /// - /// - public HttpClient HttpClient; - /// - /// - /// - public HttpClientRequest Request; - /// - /// - /// - public HttpClientResponse Response; - /// - /// - /// - public Stream StreamResponse; - /// - /// - /// - public bool Done; - - /// - /// Constructor - /// - public RequestState() - { - BufferRead = new byte[BUFFER_SIZE]; - HttpClient = null; - Request = null; - Response = null; - StreamResponse = null; - Done = false; - } - } - - /// - /// Waithandle for main thread. - /// - public class StreamAsyncTest - { - /// - /// - /// - public CEvent wait_for_response = new CEvent(true, false); - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronIO; +using Crestron.SimplSharp.Net.Http; + +namespace PepperDash.Core +{ + /// + /// Client for communicating with an HTTP Server Side Event pattern + /// + public class GenericHttpSseClient : ICommunicationReceiver + { + /// + /// Notifies when bytes have been received + /// + public event EventHandler BytesReceived; + /// + /// Notifies when text has been received + /// + public event EventHandler TextReceived; + + /// + /// Indicates connection status + /// + public bool IsConnected + { + get; + private set; + } + + /// + /// Unique identifier for the instance + /// + public string Key + { + get; + private set; + } + + /// + /// Name for the instance + /// + public string Name + { + get; + private set; + } + + /// + /// URL of the server + /// + public string Url { get; set; } + + HttpClient Client; + HttpClientRequest Request; + + /// + /// Constructor + /// + /// + /// + public GenericHttpSseClient(string key, string name) + { + Key = key; + Name = name; + } + + /// + /// Connects to the server. Requires Url to be set first. + /// + public void Connect() + { + InitiateConnection(Url); + } + + /// + /// Disconnects from the server + /// + public void Disconnect() + { + CloseConnection(null); + } + + /// + /// Initiates connection to the server + /// + /// + public void InitiateConnection(string url) + { + CrestronInvoke.BeginInvoke(o => + { + try + { + if(string.IsNullOrEmpty(url)) + { + Debug.Console(0, this, "Error connecting to Server. No URL specified"); + return; + } + + Client = new HttpClient(); + Request = new HttpClientRequest(); + Client.Verbose = true; + Client.KeepAlive = true; + Request.Url.Parse(url); + Request.RequestType = RequestType.Get; + Request.Header.SetHeaderValue("Accept", "text/event-stream"); + + // In order to get a handle on the response stream, we have to get + // the request stream first. Boo + Client.BeginGetRequestStream(GetRequestStreamCallback, Request, null); + CrestronConsole.PrintLine("Request made!"); + } + catch (Exception e) + { + ErrorLog.Notice("Exception occured in AsyncWebPostHttps(): " + e.ToString()); + } + }); + } + + /// + /// Closes the connection to the server + /// + /// + public void CloseConnection(string s) + { + if (Client != null) + { + Client.Abort(); + IsConnected = false; + + Debug.Console(1, this, "Client Disconnected"); + } + } + + private void GetRequestStreamCallback(HttpClientRequest request, HTTP_CALLBACK_ERROR error, object status) + { + + try + { + // End the the async request operation and return the data stream + Stream requestStream = request.ThisClient.EndGetRequestStream(request, null); + // If this were something other than a GET we could write to the stream here + + // Closing makes the request happen + requestStream.Close(); + + // Get a handle on the response stream. + request.ThisClient.BeginGetResponseStream(GetResponseStreamCallback, request, status); + } + catch (Exception e) + { + ErrorLog.Notice("Exception occured in GetSecureRequestStreamCallback(): " + e.ToString()); + } + } + + /// + /// + /// + /// + /// + /// + private void GetResponseStreamCallback(HttpClientRequest request, HTTP_CALLBACK_ERROR error, object status) + { + try + { + // This closes up the GetResponseStream async + var response = request.ThisClient.EndGetResponseStream(request); + + response.DataConnection.OnBytesReceived += new EventHandler(DataConnection_OnBytesReceived); + + IsConnected = true; + + Debug.Console(1, this, "Client Disconnected"); + + Stream streamResponse = response.ContentStream; + // Object containing various states to be passed back to async callback below + RequestState asyncState = new RequestState(); + asyncState.Request = request; + asyncState.Response = response; + asyncState.StreamResponse = streamResponse; + asyncState.HttpClient = request.ThisClient; + + // This processes the ongoing data stream + Crestron.SimplSharp.CrestronIO.IAsyncResult asyncResult = null; + do + { + asyncResult = streamResponse.BeginRead(asyncState.BufferRead, 0, RequestState.BUFFER_SIZE, + new Crestron.SimplSharp.CrestronIO.AsyncCallback(ReadCallBack), asyncState); + } + while (asyncResult.CompletedSynchronously && !asyncState.Done); + + //Console.WriteLine("\r\nExit Response Callback\r\n"); + } + catch (Exception e) + { + ErrorLog.Notice("Exception occured in GetSecureRequestStreamCallback(): " + e.ToString()); + } + } + + void DataConnection_OnBytesReceived(object sender, EventArgs e) + { + Debug.Console(1, this, "DataConnection OnBytesReceived Fired"); + } + + private void ReadCallBack(Crestron.SimplSharp.CrestronIO.IAsyncResult asyncResult) + { + //we are getting back everything here, so cast the state from the call + RequestState requestState = asyncResult.AsyncState as RequestState; + Stream responseStream = requestState.StreamResponse; + + int read = responseStream.EndRead(asyncResult); + // Read the HTML page and then print it to the console. + if (read > 0) + { + var bytes = requestState.BufferRead; + + var bytesHandler = BytesReceived; + if (bytesHandler != null) + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + var textHandler = TextReceived; + if (textHandler != null) + { + var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + textHandler(this, new GenericCommMethodReceiveTextArgs(str)); + } + + //requestState.RequestData.Append(Encoding.ASCII.GetString(requestState.BufferRead, 0, read)); + //CrestronConsole.PrintLine(requestState.RequestData.ToString()); + + //clear the byte array buffer used. + Array.Clear(requestState.BufferRead, 0, requestState.BufferRead.Length); + + if (asyncResult.CompletedSynchronously) + { + return; + } + + Crestron.SimplSharp.CrestronIO.IAsyncResult asynchronousResult; + do + { + asynchronousResult = responseStream.BeginRead(requestState.BufferRead, 0, RequestState.BUFFER_SIZE, + new Crestron.SimplSharp.CrestronIO.AsyncCallback(ReadCallBack), requestState); + } + while (asynchronousResult.CompletedSynchronously && !requestState.Done); + } + else + { + requestState.Done = true; + } + } + } + + /// + /// Stores the state of the request + /// + public class RequestState + { + /// + /// + /// + public const int BUFFER_SIZE = 10000; + /// + /// + /// + public byte[] BufferRead; + /// + /// + /// + public HttpClient HttpClient; + /// + /// + /// + public HttpClientRequest Request; + /// + /// + /// + public HttpClientResponse Response; + /// + /// + /// + public Stream StreamResponse; + /// + /// + /// + public bool Done; + + /// + /// Constructor + /// + public RequestState() + { + BufferRead = new byte[BUFFER_SIZE]; + HttpClient = null; + Request = null; + Response = null; + StreamResponse = null; + Done = false; + } + } + + /// + /// Waithandle for main thread. + /// + public class StreamAsyncTest + { + /// + /// + /// + public CEvent wait_for_response = new CEvent(true, false); + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient.cs b/src/Comm/GenericSecureTcpIpClient.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient.cs rename to src/Comm/GenericSecureTcpIpClient.cs diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs b/src/Comm/GenericSecureTcpIpClient_ForServer.cs similarity index 97% rename from Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs rename to src/Comm/GenericSecureTcpIpClient_ForServer.cs index 143b3cc..f99c16d 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpClient_ForServer.cs +++ b/src/Comm/GenericSecureTcpIpClient_ForServer.cs @@ -1,908 +1,908 @@ -/*PepperDash Technology Corp. -JAG -Copyright: 2017 ------------------------------------- -***Notice of Ownership and Copyright*** -The material in which this notice appears is the property of PepperDash Technology Corporation, -which claims copyright under the laws of the United States of America in the entire body of material -and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, -of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. -PepperDash Technology Corporation reserves all rights under applicable laws. ------------------------------------- */ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronSockets; - -namespace PepperDash.Core -{ - /// - /// Generic secure TCP/IP client for server - /// - public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect - { - /// - /// Band aid delegate for choked server - /// - internal delegate void ConnectionHasHungCallbackDelegate(); - - #region Events - - //public event EventHandler BytesReceived; - - /// - /// Notifies of text received - /// - public event EventHandler TextReceived; - - /// - /// Notifies of auto reconnect sequence triggered - /// - 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; - - /// - /// Notifies of socket status change - /// - public event EventHandler ConnectionChange; - - - /// - /// This is something of a band-aid callback. If the client times out during the connection process, because the server - /// is stuck, this will fire. It is intended to be used by the Server class monitor client, to help - /// keep a watch on the server and reset it if necessary. - /// - internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback; - - /// - /// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require - /// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect. - /// - public event EventHandler ClientReadyForCommunications; - - #endregion - - #region Properties & Variables - - /// - /// Address of server - /// - public string Hostname { get; set; } - - /// - /// Port on server - /// - public int Port { get; set; } - - /// - /// S+ helper - /// - public ushort UPort - { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } - - /// - /// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class - /// - public bool SharedKeyRequired { get; set; } - - /// - /// S+ helper for requires shared key bool - /// - public ushort USharedKeyRequired - { - set - { - if (value == 1) - SharedKeyRequired = true; - else - SharedKeyRequired = false; - } - } - - /// - /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module - /// - public string SharedKey { get; set; } - - /// - /// flag to show the client is waiting for the server to send the shared key - /// - private bool WaitingForSharedKeyResponse { get; set; } - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// Semaphore on connect method - /// - bool IsTryingToConnect; - - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get - { - if (Client != null) - return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; - else - return false; - } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// Bool showing if socket is ready for communication after shared key exchange - /// - public bool IsReadyForCommunication { get; set; } - - /// - /// S+ helper for IsReadyForCommunication - /// - public ushort UIsReadyForCommunication - { - get { return (ushort)(IsReadyForCommunication ? 1 : 0); } - } - - /// - /// Client socket status Read only - /// - public SocketStatus ClientStatus - { - get - { - if (Client != null) - return Client.ClientStatus; - else - return SocketStatus.SOCKET_STATUS_NO_CONNECT; - } - } - - /// - /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event - /// and IsConnected would be true when this == 2. - /// - public ushort UStatus - { - get { return (ushort)ClientStatus; } - } - - /// - /// Status text shows the message associated with socket status - /// - public string ClientStatusText { get { return ClientStatus.ToString(); } } - - /// - /// bool to track if auto reconnect should be set on the socket - /// - public bool AutoReconnect { get; set; } - - /// - /// S+ helper for AutoReconnect - /// - public ushort UAutoReconnect - { - get { return (ushort)(AutoReconnect ? 1 : 0); } - set { AutoReconnect = value == 1; } - } - /// - /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 - /// - public int AutoReconnectIntervalMs { get; set; } - - /// - /// Flag Set only when the disconnect method is called. - /// - bool DisconnectCalledByUser; - - /// - /// private Timer for auto reconnect - /// - CTimer RetryTimer; - - - /// - /// - /// - public bool HeartbeatEnabled { get; set; } - /// - /// - /// - public ushort UHeartbeatEnabled - { - get { return (ushort)(HeartbeatEnabled ? 1 : 0); } - set { HeartbeatEnabled = value == 1; } - } - - /// - /// - /// - 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; - /// - /// Used to force disconnection on a dead connect attempt - /// - CTimer ConnectFailTimer; - CTimer WaitForSharedKey; - private int ConnectionCount; - /// - /// Internal secure client - /// - SecureTCPClient Client; - - 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 - - /// - /// Constructor - /// - /// - /// - /// - /// - public GenericSecureTcpIpClient_ForServer(string key, string address, int port, int bufferSize) - : base(key) - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - Hostname = address; - Port = port; - BufferSize = bufferSize; - AutoReconnectIntervalMs = 5000; - - } - - /// - /// Constructor for S+ - /// - public GenericSecureTcpIpClient_ForServer() - : 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 - - /// - /// Just to help S+ set the key - /// - public void Initialize(string key) - { - 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 - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection"); - ProgramIsStopping = true; - Disconnect(); - } - - } - - /// - /// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name. - /// - public void Connect() - { - ConnectionCount++; - Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount); - - - if (IsConnected) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring."); - return; - } - if (IsTryingToConnect) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring."); - return; - } - try - { - IsTryingToConnect = true; - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } - if (string.IsNullOrEmpty(Hostname)) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set"); - return; - } - if (Port < 1 || Port > 65535) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port"); - return; - } - if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set"); - return; - } - - // clean up previous client - if (Client != null) - { - Cleanup(); - } - DisconnectCalledByUser = false; - - Client = new SecureTCPClient(Hostname, Port, BufferSize); - Client.SocketStatusChange += Client_SocketStatusChange; - if (HeartbeatEnabled) - Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); - Client.AddressClientConnectedTo = Hostname; - Client.PortNumber = Port; - // SecureClient = c; - - //var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff"); - - ConnectFailTimer = new CTimer(o => - { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount); - if (IsTryingToConnect) - { - IsTryingToConnect = false; - - //if (ConnectionHasHungCallback != null) - //{ - // ConnectionHasHungCallback(); - //} - //SecureClient.DisconnectFromServer(); - //CheckClosedAndTryReconnect(); - } - }, 30000); - - Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount); - Client.ConnectToServerAsync(o => - { - Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount); - - if (ConnectFailTimer != null) - { - ConnectFailTimer.Stop(); - } - IsTryingToConnect = false; - - if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - { - Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient); - o.ReceiveDataAsync(Receive); - - if (SharedKeyRequired) - { - WaitingForSharedKeyResponse = true; - WaitForSharedKey = new CTimer(timer => - { - - Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication); - // Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus); - // This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup - o.DisconnectFromServer(); - //CheckClosedAndTryReconnect(); - //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 - { - Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus); - CheckClosedAndTryReconnect(); - } - }); - } - catch (Exception ex) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message); - IsTryingToConnect = false; - CheckClosedAndTryReconnect(); - } - } - - /// - /// - /// - public void Disconnect() - { - Debug.Console(2, "Disconnect Called"); - - DisconnectCalledByUser = true; - if (IsConnected) - { - Client.DisconnectFromServer(); - - } - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } - Cleanup(); - } - - /// - /// Internal call to close up client. ALWAYS use this when disconnecting. - /// - void Cleanup() - { - IsTryingToConnect = false; - - if (Client != null) - { - //SecureClient.DisconnectFromServer(); - Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : ""); - Client.SocketStatusChange -= Client_SocketStatusChange; - Client.Dispose(); - Client = null; - } - if (ConnectFailTimer != null) - { - ConnectFailTimer.Stop(); - ConnectFailTimer.Dispose(); - ConnectFailTimer = null; - } - } - - - /// ff - /// Called from Connect failure or Socket Status change if - /// auto reconnect and socket disconnected (Not disconnected by user) - /// - void CheckClosedAndTryReconnect() - { - if (Client != null) - { - Debug.Console(2, this, "Cleaning up remotely closed/failed connection."); - Cleanup(); - } - if (!DisconnectCalledByUser && AutoReconnect) - { - var halfInterval = AutoReconnectIntervalMs / 2; - var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs; - Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime); - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } - if(AutoReconnectTriggered != null) - AutoReconnectTriggered(this, new EventArgs()); - RetryTimer = new CTimer(o => Connect(), rndTime); - } - } - - /// - /// Receive callback - /// - /// - /// - void Receive(SecureTCPClient client, int numBytes) - { - if (numBytes > 0) - { - string str = string.Empty; - var handler = TextReceivedQueueInvoke; - try - { - var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); - str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str); - if (!string.IsNullOrEmpty(checkHeartbeat(str))) - { - - if (SharedKeyRequired && str == "SharedKey:") - { - Debug.Console(2, this, "Server asking for shared key, sending"); - SendText(SharedKey + "\n"); - } - else if (SharedKeyRequired && str == "Shared Key Match") - { - StopWaitForSharedKeyTimer(); - - - Debug.Console(2, this, "Shared key confirmed. Ready for communication"); - OnClientReadyForcommunications(true); // Successful key exchange - } - 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)); - } - } - } - } - catch (Exception ex) - { - 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(); - } - } - - void HeartbeatStart() - { - if (HeartbeatEnabled) - { - Debug.Console(2, this, "Starting Heartbeat"); - if (HeartbeatSendTimer == null) - { - - HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval); - } - if (HeartbeatAckTimer == null) - { - HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); - } - } - - } - void HeartbeatStop() - { - - if (HeartbeatSendTimer != null) - { - Debug.Console(2, this, "Stoping Heartbeat Send"); - HeartbeatSendTimer.Stop(); - HeartbeatSendTimer = null; - } - if (HeartbeatAckTimer != null) - { - Debug.Console(2, this, "Stoping Heartbeat Ack"); - HeartbeatAckTimer.Stop(); - HeartbeatAckTimer = null; - } - - } - void SendHeartbeat(object notused) - { - this.SendText(HeartbeatString); - Debug.Console(2, this, "Sending Heartbeat"); - - } - - //private method to check heartbeat requirements and start or reset timer - string checkHeartbeat(string received) - { - try - { - if (HeartbeatEnabled) - { - if (!string.IsNullOrEmpty(HeartbeatString)) - { - var remainingText = received.Replace(HeartbeatString, ""); - var noDelimiter = received.Trim(new char[] { '\r', '\n' }); - if (noDelimiter.Contains(HeartbeatString)) - { - if (HeartbeatAckTimer != null) - { - HeartbeatAckTimer.Reset(HeartbeatInterval * 2); - } - else - { - HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); - } - Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString); - return remainingText; - } - } - } - } - catch (Exception ex) - { - Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); - } - return received; - } - - - - void HeartbeatAckTimerFail(object o) - { - try - { - - if (IsConnected) - { - Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE"); - SendText("Heartbeat not received by server, closing connection"); - CheckClosedAndTryReconnect(); - } - - } - catch (Exception ex) - { - ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex); - } - } - - /// - /// - /// - void StopWaitForSharedKeyTimer() - { - if (WaitForSharedKey != null) - { - WaitForSharedKey.Stop(); - WaitForSharedKey = null; - } - } - - /// - /// General send method - /// - public void SendText(string text) - { - if (!string.IsNullOrEmpty(text)) - { - try - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - { - Client.SendDataAsync(bytes, bytes.Length, (c, n) => - { - // HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING????? - if (n <= 0) - { - Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key); - } - }); - } - } - catch (Exception ex) - { - Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text); - } - } - } - - /// - /// - /// - public void SendBytes(byte[] bytes) - { - if (bytes.Length > 0) - { - try - { - if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - Client.SendData(bytes, bytes.Length); - } - catch (Exception ex) - { - Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message); - } - } - } - - /// - /// SocketStatusChange Callback - /// - /// - /// - void Client_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus) - { - if (ProgramIsStopping) - { - ProgramIsStopping = false; - return; - } - try - { - Debug.Console(2, this, "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) - { - HeartbeatStop(); - OnClientReadyForcommunications(false); // socket has gone low - CheckClosedAndTryReconnect(); - } - } - catch (Exception ex) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException); - } - } - - /// - /// Helper for ConnectionChange event - /// - void OnConnectionChange() - { - var handler = ConnectionChange; - if (handler != null) - ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus)); - } - - /// - /// Helper to fire ClientReadyForCommunications event - /// - void OnClientReadyForcommunications(bool isReady) - { - IsReadyForCommunication = isReady; - if (this.IsReadyForCommunication) { HeartbeatStart(); } - var handler = ClientReadyForCommunications; - if (handler != null) - handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication)); - } - #endregion - } - +/*PepperDash Technology Corp. +JAG +Copyright: 2017 +------------------------------------ +***Notice of Ownership and Copyright*** +The material in which this notice appears is the property of PepperDash Technology Corporation, +which claims copyright under the laws of the United States of America in the entire body of material +and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, +of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. +PepperDash Technology Corporation reserves all rights under applicable laws. +------------------------------------ */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronSockets; + +namespace PepperDash.Core +{ + /// + /// Generic secure TCP/IP client for server + /// + public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect + { + /// + /// Band aid delegate for choked server + /// + internal delegate void ConnectionHasHungCallbackDelegate(); + + #region Events + + //public event EventHandler BytesReceived; + + /// + /// Notifies of text received + /// + public event EventHandler TextReceived; + + /// + /// Notifies of auto reconnect sequence triggered + /// + 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; + + /// + /// Notifies of socket status change + /// + public event EventHandler ConnectionChange; + + + /// + /// This is something of a band-aid callback. If the client times out during the connection process, because the server + /// is stuck, this will fire. It is intended to be used by the Server class monitor client, to help + /// keep a watch on the server and reset it if necessary. + /// + internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback; + + /// + /// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require + /// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect. + /// + public event EventHandler ClientReadyForCommunications; + + #endregion + + #region Properties & Variables + + /// + /// Address of server + /// + public string Hostname { get; set; } + + /// + /// Port on server + /// + public int Port { get; set; } + + /// + /// S+ helper + /// + public ushort UPort + { + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } + } + + /// + /// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class + /// + public bool SharedKeyRequired { get; set; } + + /// + /// S+ helper for requires shared key bool + /// + public ushort USharedKeyRequired + { + set + { + if (value == 1) + SharedKeyRequired = true; + else + SharedKeyRequired = false; + } + } + + /// + /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module + /// + public string SharedKey { get; set; } + + /// + /// flag to show the client is waiting for the server to send the shared key + /// + private bool WaitingForSharedKeyResponse { get; set; } + + /// + /// Defaults to 2000 + /// + public int BufferSize { get; set; } + + /// + /// Semaphore on connect method + /// + bool IsTryingToConnect; + + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get + { + if (Client != null) + return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; + else + return false; + } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// Bool showing if socket is ready for communication after shared key exchange + /// + public bool IsReadyForCommunication { get; set; } + + /// + /// S+ helper for IsReadyForCommunication + /// + public ushort UIsReadyForCommunication + { + get { return (ushort)(IsReadyForCommunication ? 1 : 0); } + } + + /// + /// Client socket status Read only + /// + public SocketStatus ClientStatus + { + get + { + if (Client != null) + return Client.ClientStatus; + else + return SocketStatus.SOCKET_STATUS_NO_CONNECT; + } + } + + /// + /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event + /// and IsConnected would be true when this == 2. + /// + public ushort UStatus + { + get { return (ushort)ClientStatus; } + } + + /// + /// Status text shows the message associated with socket status + /// + public string ClientStatusText { get { return ClientStatus.ToString(); } } + + /// + /// bool to track if auto reconnect should be set on the socket + /// + public bool AutoReconnect { get; set; } + + /// + /// S+ helper for AutoReconnect + /// + public ushort UAutoReconnect + { + get { return (ushort)(AutoReconnect ? 1 : 0); } + set { AutoReconnect = value == 1; } + } + /// + /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// Flag Set only when the disconnect method is called. + /// + bool DisconnectCalledByUser; + + /// + /// private Timer for auto reconnect + /// + CTimer RetryTimer; + + + /// + /// + /// + public bool HeartbeatEnabled { get; set; } + /// + /// + /// + public ushort UHeartbeatEnabled + { + get { return (ushort)(HeartbeatEnabled ? 1 : 0); } + set { HeartbeatEnabled = value == 1; } + } + + /// + /// + /// + 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; + /// + /// Used to force disconnection on a dead connect attempt + /// + CTimer ConnectFailTimer; + CTimer WaitForSharedKey; + private int ConnectionCount; + /// + /// Internal secure client + /// + SecureTCPClient Client; + + 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 + + /// + /// Constructor + /// + /// + /// + /// + /// + public GenericSecureTcpIpClient_ForServer(string key, string address, int port, int bufferSize) + : base(key) + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + Hostname = address; + Port = port; + BufferSize = bufferSize; + AutoReconnectIntervalMs = 5000; + + } + + /// + /// Constructor for S+ + /// + public GenericSecureTcpIpClient_ForServer() + : 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 + + /// + /// Just to help S+ set the key + /// + public void Initialize(string key) + { + 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 + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection"); + ProgramIsStopping = true; + Disconnect(); + } + + } + + /// + /// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name. + /// + public void Connect() + { + ConnectionCount++; + Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount); + + + if (IsConnected) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring."); + return; + } + if (IsTryingToConnect) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring."); + return; + } + try + { + IsTryingToConnect = true; + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + if (string.IsNullOrEmpty(Hostname)) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set"); + return; + } + if (Port < 1 || Port > 65535) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port"); + return; + } + if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set"); + return; + } + + // clean up previous client + if (Client != null) + { + Cleanup(); + } + DisconnectCalledByUser = false; + + Client = new SecureTCPClient(Hostname, Port, BufferSize); + Client.SocketStatusChange += Client_SocketStatusChange; + if (HeartbeatEnabled) + Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); + Client.AddressClientConnectedTo = Hostname; + Client.PortNumber = Port; + // SecureClient = c; + + //var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff"); + + ConnectFailTimer = new CTimer(o => + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount); + if (IsTryingToConnect) + { + IsTryingToConnect = false; + + //if (ConnectionHasHungCallback != null) + //{ + // ConnectionHasHungCallback(); + //} + //SecureClient.DisconnectFromServer(); + //CheckClosedAndTryReconnect(); + } + }, 30000); + + Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount); + Client.ConnectToServerAsync(o => + { + Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount); + + if (ConnectFailTimer != null) + { + ConnectFailTimer.Stop(); + } + IsTryingToConnect = false; + + if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + { + Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient); + o.ReceiveDataAsync(Receive); + + if (SharedKeyRequired) + { + WaitingForSharedKeyResponse = true; + WaitForSharedKey = new CTimer(timer => + { + + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication); + // Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus); + // This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup + o.DisconnectFromServer(); + //CheckClosedAndTryReconnect(); + //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 + { + Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus); + CheckClosedAndTryReconnect(); + } + }); + } + catch (Exception ex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message); + IsTryingToConnect = false; + CheckClosedAndTryReconnect(); + } + } + + /// + /// + /// + public void Disconnect() + { + Debug.Console(2, "Disconnect Called"); + + DisconnectCalledByUser = true; + if (IsConnected) + { + Client.DisconnectFromServer(); + + } + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + Cleanup(); + } + + /// + /// Internal call to close up client. ALWAYS use this when disconnecting. + /// + void Cleanup() + { + IsTryingToConnect = false; + + if (Client != null) + { + //SecureClient.DisconnectFromServer(); + Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : ""); + Client.SocketStatusChange -= Client_SocketStatusChange; + Client.Dispose(); + Client = null; + } + if (ConnectFailTimer != null) + { + ConnectFailTimer.Stop(); + ConnectFailTimer.Dispose(); + ConnectFailTimer = null; + } + } + + + /// ff + /// Called from Connect failure or Socket Status change if + /// auto reconnect and socket disconnected (Not disconnected by user) + /// + void CheckClosedAndTryReconnect() + { + if (Client != null) + { + Debug.Console(2, this, "Cleaning up remotely closed/failed connection."); + Cleanup(); + } + if (!DisconnectCalledByUser && AutoReconnect) + { + var halfInterval = AutoReconnectIntervalMs / 2; + var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs; + Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime); + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + if(AutoReconnectTriggered != null) + AutoReconnectTriggered(this, new EventArgs()); + RetryTimer = new CTimer(o => Connect(), rndTime); + } + } + + /// + /// Receive callback + /// + /// + /// + void Receive(SecureTCPClient client, int numBytes) + { + if (numBytes > 0) + { + string str = string.Empty; + var handler = TextReceivedQueueInvoke; + try + { + var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); + str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str); + if (!string.IsNullOrEmpty(checkHeartbeat(str))) + { + + if (SharedKeyRequired && str == "SharedKey:") + { + Debug.Console(2, this, "Server asking for shared key, sending"); + SendText(SharedKey + "\n"); + } + else if (SharedKeyRequired && str == "Shared Key Match") + { + StopWaitForSharedKeyTimer(); + + + Debug.Console(2, this, "Shared key confirmed. Ready for communication"); + OnClientReadyForcommunications(true); // Successful key exchange + } + 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)); + } + } + } + } + catch (Exception ex) + { + 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(); + } + } + + void HeartbeatStart() + { + if (HeartbeatEnabled) + { + Debug.Console(2, this, "Starting Heartbeat"); + if (HeartbeatSendTimer == null) + { + + HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval); + } + if (HeartbeatAckTimer == null) + { + HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); + } + } + + } + void HeartbeatStop() + { + + if (HeartbeatSendTimer != null) + { + Debug.Console(2, this, "Stoping Heartbeat Send"); + HeartbeatSendTimer.Stop(); + HeartbeatSendTimer = null; + } + if (HeartbeatAckTimer != null) + { + Debug.Console(2, this, "Stoping Heartbeat Ack"); + HeartbeatAckTimer.Stop(); + HeartbeatAckTimer = null; + } + + } + void SendHeartbeat(object notused) + { + this.SendText(HeartbeatString); + Debug.Console(2, this, "Sending Heartbeat"); + + } + + //private method to check heartbeat requirements and start or reset timer + string checkHeartbeat(string received) + { + try + { + if (HeartbeatEnabled) + { + if (!string.IsNullOrEmpty(HeartbeatString)) + { + var remainingText = received.Replace(HeartbeatString, ""); + var noDelimiter = received.Trim(new char[] { '\r', '\n' }); + if (noDelimiter.Contains(HeartbeatString)) + { + if (HeartbeatAckTimer != null) + { + HeartbeatAckTimer.Reset(HeartbeatInterval * 2); + } + else + { + HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); + } + Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString); + return remainingText; + } + } + } + } + catch (Exception ex) + { + Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); + } + return received; + } + + + + void HeartbeatAckTimerFail(object o) + { + try + { + + if (IsConnected) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE"); + SendText("Heartbeat not received by server, closing connection"); + CheckClosedAndTryReconnect(); + } + + } + catch (Exception ex) + { + ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex); + } + } + + /// + /// + /// + void StopWaitForSharedKeyTimer() + { + if (WaitForSharedKey != null) + { + WaitForSharedKey.Stop(); + WaitForSharedKey = null; + } + } + + /// + /// General send method + /// + public void SendText(string text) + { + if (!string.IsNullOrEmpty(text)) + { + try + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + { + Client.SendDataAsync(bytes, bytes.Length, (c, n) => + { + // HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING????? + if (n <= 0) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key); + } + }); + } + } + catch (Exception ex) + { + Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text); + } + } + } + + /// + /// + /// + public void SendBytes(byte[] bytes) + { + if (bytes.Length > 0) + { + try + { + if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + Client.SendData(bytes, bytes.Length); + } + catch (Exception ex) + { + Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message); + } + } + } + + /// + /// SocketStatusChange Callback + /// + /// + /// + void Client_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus) + { + if (ProgramIsStopping) + { + ProgramIsStopping = false; + return; + } + try + { + Debug.Console(2, this, "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) + { + HeartbeatStop(); + OnClientReadyForcommunications(false); // socket has gone low + CheckClosedAndTryReconnect(); + } + } + catch (Exception ex) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException); + } + } + + /// + /// Helper for ConnectionChange event + /// + void OnConnectionChange() + { + var handler = ConnectionChange; + if (handler != null) + ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus)); + } + + /// + /// Helper to fire ClientReadyForCommunications event + /// + void OnClientReadyForcommunications(bool isReady) + { + IsReadyForCommunication = isReady; + if (this.IsReadyForCommunication) { HeartbeatStart(); } + var handler = ClientReadyForCommunications; + if (handler != null) + handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication)); + } + #endregion + } + } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs b/src/Comm/GenericSecureTcpIpServer.cs similarity index 97% rename from Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs rename to src/Comm/GenericSecureTcpIpServer.cs index d53a801..1fb9d05 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSecureTcpIpServer.cs +++ b/src/Comm/GenericSecureTcpIpServer.cs @@ -1,1085 +1,1085 @@ -/*PepperDash Technology Corp. -JAG -Copyright: 2017 ------------------------------------- -***Notice of Ownership and Copyright*** -The material in which this notice appears is the property of PepperDash Technology Corporation, -which claims copyright under the laws of the United States of America in the entire body of material -and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, -of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. -PepperDash Technology Corporation reserves all rights under applicable laws. ------------------------------------- */ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronSockets; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace PepperDash.Core -{ - /// - /// Generic secure TCP/IP server - /// - public class GenericSecureTcpIpServer : Device - { - #region Events - /// - /// Event for Receiving text - /// - 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 - /// - public event EventHandler ClientConnectionChange; - - /// - /// Event for Server State Change - /// - public event EventHandler ServerStateChange; - - /// - /// For a server with a pre shared key, this will fire after the communication is established and the key exchange is complete. If no shared key, this will fire - /// after connection is successful. Use this event to know when the client is ready for communication to avoid stepping on shared key. - /// - public event EventHandler ServerClientReadyForCommunications; - - /// - /// A band aid event to notify user that the server has choked. - /// - public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; } - - /// - /// - /// - public delegate void ServerHasChokedCallbackDelegate(); - - #endregion - - #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 - /// - GenericSecureTcpIpClient_ForServer MonitorClient; - - /// - /// Timer to operate the bandaid monitor client in a loop. - /// - CTimer MonitorClientTimer; - - /// - /// - /// - int MonitorClientFailureCount; - - /// - /// 3 by default - /// - public int MonitorClientMaxFailureCount { get; set; } - - /// - /// Text representation of the Socket Status enum values for the server - /// - public string Status - { - get - { - if (SecureServer != null) - return SecureServer.State.ToString(); - return ServerState.SERVER_NOT_LISTENING.ToString(); - - } - - } - - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get - { - if (SecureServer != null) - return (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED; - return false; - - //return (Secure ? SecureServer != null : UnsecureServer != null) && - //(Secure ? (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED : - // (UnsecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED); - } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// Bool showing if socket is connected - /// - public bool IsListening - { - get - { - if (SecureServer != null) - return (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING; - else - return false; - //return (Secure ? SecureServer != null : UnsecureServer != null) && - //(Secure ? (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING : - // (UnsecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING); - } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsListening - { - 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. - /// - public ushort NumberOfClientsConnected - { - get - { - if (SecureServer != null) - return (ushort)SecureServer.NumberOfClientsConnected; - return 0; - } - } - - /// - /// Port Server should listen on - /// - public int Port { get; set; } - - /// - /// S+ helper for Port - /// - public ushort UPort - { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } - - /// - /// Bool to show whether the server requires a preshared key. Must be set the same in the client, and if true shared keys must be identical on server/client - /// - public bool SharedKeyRequired { get; set; } - - /// - /// S+ helper for requires shared key bool - /// - public ushort USharedKeyRequired - { - set - { - if (value == 1) - SharedKeyRequired = true; - else - SharedKeyRequired = false; - } - } - - /// - /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module. - /// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called - /// - public string SharedKey { get; set; } - - /// - /// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received - /// - public bool HeartbeatRequired { get; set; } - - /// - /// S+ Helper for Heartbeat Required - /// - public ushort UHeartbeatRequired - { - set - { - if (value == 1) - HeartbeatRequired = true; - else - HeartbeatRequired = false; - } - } - - /// - /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ - /// - public int HeartbeatRequiredIntervalMs { get; set; } - - /// - /// Simpl+ Heartbeat Analog value in seconds - /// - public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } } - - /// - /// String to Match for heartbeat. If null or empty any string will reset heartbeat timer - /// - public string HeartbeatStringToMatch { get; set; } - - //private timers for Heartbeats per client - Dictionary HeartbeatTimerDictionary = new Dictionary(); - - //flags to show the secure server is waiting for client at index to send the shared key - List WaitingForSharedKey = new List(); - - List ClientReadyAfterKeyExchange = new List(); - - /// - /// The connected client indexes - /// - public List ConnectedClientsIndexes = new List(); - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// Private flag to note that the server has stopped intentionally - /// - private bool ServerStopped { get; set; } - - //Servers - SecureTCPServer SecureServer; - - /// - /// - /// - bool ProgramIsStopping; - - #endregion - - #region Constructors - /// - /// 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 Secure TCP Server") - { - HeartbeatRequiredIntervalInSeconds = 15; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - MonitorClientMaxFailureCount = 3; - } - - /// - /// constructor with debug key set at instantiation. Make sure to set all properties before listening. - /// - /// - public GenericSecureTcpIpServer(string key) - : base("Uninitialized Secure TCP Server") - { - HeartbeatRequiredIntervalInSeconds = 15; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - MonitorClientMaxFailureCount = 3; - Key = key; - } - - /// - /// 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 Secure TCP Server") - { - HeartbeatRequiredIntervalInSeconds = 15; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - MonitorClientMaxFailureCount = 3; - Initialize(serverConfigObject); - } - #endregion - - #region Methods - Server Actions - /// - /// Disconnects all clients and stops the server - /// - public void KillServer() - { - ServerStopped = true; - if (MonitorClient != null) - { - MonitorClient.Disconnect(); - } - DisconnectAllClientsForShutdown(); - StopListening(); - } - - /// - /// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+ - /// - /// - public void Initialize(string key) - { - Key = key; - } - - /// - /// Initialze the server - /// - /// - public void Initialize(TcpServerConfigObject serverConfigObject) - { - try - { - if (serverConfigObject != null || string.IsNullOrEmpty(serverConfigObject.Key)) - { - Key = serverConfigObject.Key; - MaxClients = serverConfigObject.MaxClients; - Port = serverConfigObject.Port; - SharedKeyRequired = serverConfigObject.SharedKeyRequired; - SharedKey = serverConfigObject.SharedKey; - HeartbeatRequired = serverConfigObject.HeartbeatRequired; - HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds; - HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch; - BufferSize = serverConfigObject.BufferSize; - ReceiveQueueSize = serverConfigObject.ReceiveQueueSize > 20 ? serverConfigObject.ReceiveQueueSize : 20; - MessageQueue = new CrestronQueue(ReceiveQueueSize); - } - else - { - ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); - } - } - catch - { - ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); - } - } - - /// - /// Start listening on the specified port - /// - public void Listen() - { - ServerCCSection.Enter(); - try - { - if (Port < 1 || Port > 65535) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key); - ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key)); - return; - } - if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key); - ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key)); - return; - } - - - if (SecureServer == null) - { - SecureServer = new SecureTCPServer(Port, MaxClients); - if (HeartbeatRequired) - SecureServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5); - SecureServer.HandshakeTimeout = 30; - SecureServer.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SecureServer_SocketStatusChange); - } - else - { - SecureServer.PortNumber = Port; - } - ServerStopped = false; - - // Start the listner - SocketErrorCodes status = SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback); - if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error starting WaitForConnectionAsync {0}", status); - } - else - { - ServerStopped = false; - } - OnServerStateChange(SecureServer.State); - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus); - ServerCCSection.Leave(); - - } - catch (Exception ex) - { - ServerCCSection.Leave(); - ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key); - } - } - - /// - /// Stop Listeneing - /// - public void StopListening() - { - 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); - } - } - - /// - /// Disconnects Client - /// - /// - public void DisconnectClient(uint client) - { - try - { - SecureServer.Disconnect(client); - Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client); - } - catch (Exception ex) - { - Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex); - } - } - /// - /// Disconnect All Clients - /// - public void DisconnectAllClientsForShutdown() - { - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients"); - if (SecureServer != null) - { - SecureServer.SocketStatusChange -= SecureServer_SocketStatusChange; - foreach (var index in ConnectedClientsIndexes.ToList()) // copy it here so that it iterates properly - { - var i = index; - if (!SecureServer.ClientConnected(index)) - continue; - try - { - SecureServer.Disconnect(i); - Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i); - } - catch (Exception ex) - { - Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex); - } - } - Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", SecureServer.ServerSocketStatus); - } - - Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients"); - ConnectedClientsIndexes.Clear(); - - if (!ProgramIsStopping) - { - OnConnectionChange(); - OnServerStateChange(SecureServer.State); //State shows both listening and connected - } - - // var o = new { }; - } - - /// - /// Broadcast text from server to all connected clients - /// - /// - public void BroadcastText(string text) - { - CCriticalSection CCBroadcast = new CCriticalSection(); - CCBroadcast.Enter(); - try - { - if (ConnectedClientsIndexes.Count > 0) - { - byte[] b = Encoding.GetEncoding(28591).GetBytes(text); - foreach (uint i in ConnectedClientsIndexes) - { - if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(i))) - { - SocketErrorCodes error = SecureServer.SendDataAsync(i, b, b.Length, (x, y, z) => { }); - if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING) - Debug.Console(2, error.ToString()); - } - } - } - CCBroadcast.Leave(); - } - catch (Exception ex) - { - CCBroadcast.Leave(); - Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message); - } - } - - /// - /// Not sure this is useful in library, maybe Pro?? - /// - /// - /// - public void SendTextToClient(string text, uint clientIndex) - { - try - { - byte[] b = Encoding.GetEncoding(28591).GetBytes(text); - if (SecureServer != null && SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) - { - if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) - SecureServer.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); - } - } - catch (Exception ex) - { - Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text); - } - } - - //private method to check heartbeat requirements and start or reset timer - string checkHeartbeat(uint clientIndex, string received) - { - try - { - if (HeartbeatRequired) - { - if (!string.IsNullOrEmpty(HeartbeatStringToMatch)) - { - var remainingText = received.Replace(HeartbeatStringToMatch, ""); - var noDelimiter = received.Trim(new char[] { '\r', '\n' }); - if (noDelimiter.Contains(HeartbeatStringToMatch)) - { - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) - HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); - else - { - CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); - HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); - } - Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex); - // Return Heartbeat - SendTextToClient(HeartbeatStringToMatch, clientIndex); - return remainingText; - } - } - else - { - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) - HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); - else - { - CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); - HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); - } - Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex); - } - } - } - catch (Exception ex) - { - Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); - } - return received; - } - - /// - /// Get the IP Address for the client at the specifed index - /// - /// - /// - public string GetClientIPAddress(uint clientIndex) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex); - if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) - { - var ipa = this.SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa); - return ipa; - - } - else - { - return ""; - } - } - - #endregion - - #region Methods - HeartbeatTimer Callback - - void HeartbeatTimer_CallbackFunction(object o) - { - uint clientIndex = 99999; - string address = string.Empty; - try - { - clientIndex = (uint)o; - address = SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); - - Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}", - address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex); - - if (SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) - SendTextToClient("Heartbeat not received by server, closing connection", clientIndex); - - var discoResult = SecureServer.Disconnect(clientIndex); - //Debug.Console(1, this, "{0}", discoResult); - - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) - { - HeartbeatTimerDictionary[clientIndex].Stop(); - HeartbeatTimerDictionary[clientIndex].Dispose(); - HeartbeatTimerDictionary.Remove(clientIndex); - } - } - catch (Exception ex) - { - ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key); - } - } - - #endregion - - #region Methods - Socket Status Changed Callbacks - /// - /// Secure Server Socket Status Changed Callback - /// - /// - /// - /// - void SecureServer_SocketStatusChange(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus) - { - try - { - - - // Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.SecureServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.SecureServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); - if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber); - - if (ConnectedClientsIndexes.Contains(clientIndex)) - ConnectedClientsIndexes.Remove(clientIndex); - if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) - { - HeartbeatTimerDictionary[clientIndex].Stop(); - HeartbeatTimerDictionary[clientIndex].Dispose(); - HeartbeatTimerDictionary.Remove(clientIndex); - } - if (ClientReadyAfterKeyExchange.Contains(clientIndex)) - ClientReadyAfterKeyExchange.Remove(clientIndex); - if (WaitingForSharedKey.Contains(clientIndex)) - WaitingForSharedKey.Remove(clientIndex); - if (SecureServer.MaxNumberOfClientSupported > SecureServer.NumberOfClientsConnected) - { - Listen(); - } - } - } - catch (Exception ex) - { - Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex); - } - //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 - - #region Methods Connected Callbacks - /// - /// Secure TCP Client Connected to Secure Server Callback - /// - /// - /// - void SecureConnectCallback(SecureTCPServer server, uint clientIndex) - { - try - { - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}", - server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), - clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); - if (clientIndex != 0) - { - if (server.ClientConnected(clientIndex)) - { - - if (!ConnectedClientsIndexes.Contains(clientIndex)) - { - ConnectedClientsIndexes.Add(clientIndex); - } - if (SharedKeyRequired) - { - if (!WaitingForSharedKey.Contains(clientIndex)) - { - WaitingForSharedKey.Add(clientIndex); - } - byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:"); - server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); - } - else - { - OnServerClientReadyForCommunications(clientIndex); - } - if (HeartbeatRequired) - { - if (!HeartbeatTimerDictionary.ContainsKey(clientIndex)) - { - HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs)); - } - } - - server.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback); - } - } - else - { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty."); - } - } - catch (Exception ex) - { - Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex); - } - - // Rearm the listner - SocketErrorCodes status = server.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback); - if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Socket status connect callback status {0}", status); - if (status == SocketErrorCodes.SOCKET_CONNECTION_IN_PROGRESS) - { - // There is an issue where on a failed negotiation we need to stop and start the server. This should still leave connected clients intact. - server.Stop(); - Listen(); - } - } - } - - #endregion - - #region Methods - Send/Receive Callbacks - /// - /// Secure Received Data Async Callback - /// - /// - /// - /// - void SecureReceivedDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived) - { - if (numberOfBytesReceived > 0) - { - - string received = "Nothing"; - var handler = TextReceivedQueueInvoke; - 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); - - return; - } - - 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); - } - else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received))) - { - onTextReceived(received, clientIndex); - if (handler != null) - { - MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(received, clientIndex)); - } - } - } - catch (Exception 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()); - } - } - 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 - - #region Methods - EventHelpers/Callbacks - - //Private Helper method to call the Connection Change Event - void onConnectionChange(uint clientIndex, SocketStatus clientStatus) - { - if (clientIndex != 0) //0 is error not valid client change - { - var handler = ClientConnectionChange; - if (handler != null) - { - handler(this, new GenericTcpServerSocketStatusChangeEventArgs(SecureServer, clientIndex, clientStatus)); - } - } - } - - //Private Helper method to call the Connection Change Event - void OnConnectionChange() - { - if (ProgramIsStopping) - { - return; - } - var handler = ClientConnectionChange; - if (handler != null) - { - handler(this, new GenericTcpServerSocketStatusChangeEventArgs()); - } - } - - //Private Helper Method to call the Text Received Event - void onTextReceived(string text, uint clientIndex) - { - var handler = TextReceived; - if (handler != null) - handler(this, new GenericTcpServerCommMethodReceiveTextArgs(text, clientIndex)); - } - - //Private Helper Method to call the Server State Change Event - void OnServerStateChange(ServerState state) - { - if (ProgramIsStopping) - { - return; - } - var handler = ServerStateChange; - if (handler != null) - { - handler(this, new GenericTcpServerStateChangedEventArgs(state)); - } - } - - /// - /// Private Event Handler method to handle the closing of connections when the program stops - /// - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping) - { - ProgramIsStopping = true; - // kill bandaid things - if (MonitorClientTimer != null) - MonitorClientTimer.Stop(); - if (MonitorClient != null) - MonitorClient.Disconnect(); - - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server"); - KillServer(); - } - } - - //Private event handler method to raise the event that the server is ready to send data after a successful client shared key negotiation - void OnServerClientReadyForCommunications(uint clientIndex) - { - ClientReadyAfterKeyExchange.Add(clientIndex); - var handler = ServerClientReadyForCommunications; - if (handler != null) - handler(this, new GenericTcpServerSocketStatusChangeEventArgs( - this, clientIndex, SecureServer.GetServerSocketStatusForSpecificClient(clientIndex))); - } - #endregion - - #region Monitor Client - /// - /// Starts the monitor client cycle. Timed wait, then call RunMonitorClient - /// - void StartMonitorClient() - { - if (MonitorClientTimer != null) - { - return; - } - MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000); - } - - /// - /// - /// - void RunMonitorClient() - { - MonitorClient = new GenericSecureTcpIpClient_ForServer(Key + "-MONITOR", "127.0.0.1", Port, 2000); - MonitorClient.SharedKeyRequired = this.SharedKeyRequired; - MonitorClient.SharedKey = this.SharedKey; - MonitorClient.ConnectionHasHungCallback = MonitorClientHasHungCallback; - //MonitorClient.ConnectionChange += MonitorClient_ConnectionChange; - MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm; - - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check"); - - MonitorClient.Connect(); - // From here MonitorCLient either connects or hangs, MonitorClient will call back - - } - - /// - /// - /// - void StopMonitorClient() - { - if (MonitorClient == null) - return; - - MonitorClient.ClientReadyForCommunications -= MonitorClient_IsReadyForComm; - MonitorClient.Disconnect(); - MonitorClient = null; - } - - /// - /// On monitor connect, restart the operation - /// - void MonitorClient_IsReadyForComm(object sender, GenericTcpServerClientReadyForcommunicationsEventArgs args) - { - if (args.IsReady) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s"); - MonitorClientTimer.Stop(); - MonitorClientTimer = null; - MonitorClientFailureCount = 0; - CrestronEnvironment.Sleep(2000); - StopMonitorClient(); - StartMonitorClient(); - } - } - - /// - /// If the client hangs, add to counter and maybe fire the choke event - /// - void MonitorClientHasHungCallback() - { - MonitorClientFailureCount++; - MonitorClientTimer.Stop(); - MonitorClientTimer = null; - StopMonitorClient(); - if (MonitorClientFailureCount < MonitorClientMaxFailureCount) - { - 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(2, this, Debug.ErrorLogLevel.Error, - "\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************", - MonitorClientMaxFailureCount); - - var handler = ServerHasChoked; - if (handler != null) - handler(); - // Some external thing is in charge here. Expected reset of program - } - } - #endregion - } +/*PepperDash Technology Corp. +JAG +Copyright: 2017 +------------------------------------ +***Notice of Ownership and Copyright*** +The material in which this notice appears is the property of PepperDash Technology Corporation, +which claims copyright under the laws of the United States of America in the entire body of material +and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, +of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. +PepperDash Technology Corporation reserves all rights under applicable laws. +------------------------------------ */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronSockets; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PepperDash.Core +{ + /// + /// Generic secure TCP/IP server + /// + public class GenericSecureTcpIpServer : Device + { + #region Events + /// + /// Event for Receiving text + /// + 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 + /// + public event EventHandler ClientConnectionChange; + + /// + /// Event for Server State Change + /// + public event EventHandler ServerStateChange; + + /// + /// For a server with a pre shared key, this will fire after the communication is established and the key exchange is complete. If no shared key, this will fire + /// after connection is successful. Use this event to know when the client is ready for communication to avoid stepping on shared key. + /// + public event EventHandler ServerClientReadyForCommunications; + + /// + /// A band aid event to notify user that the server has choked. + /// + public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; } + + /// + /// + /// + public delegate void ServerHasChokedCallbackDelegate(); + + #endregion + + #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 + /// + GenericSecureTcpIpClient_ForServer MonitorClient; + + /// + /// Timer to operate the bandaid monitor client in a loop. + /// + CTimer MonitorClientTimer; + + /// + /// + /// + int MonitorClientFailureCount; + + /// + /// 3 by default + /// + public int MonitorClientMaxFailureCount { get; set; } + + /// + /// Text representation of the Socket Status enum values for the server + /// + public string Status + { + get + { + if (SecureServer != null) + return SecureServer.State.ToString(); + return ServerState.SERVER_NOT_LISTENING.ToString(); + + } + + } + + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get + { + if (SecureServer != null) + return (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED; + return false; + + //return (Secure ? SecureServer != null : UnsecureServer != null) && + //(Secure ? (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED : + // (UnsecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED); + } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// Bool showing if socket is connected + /// + public bool IsListening + { + get + { + if (SecureServer != null) + return (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING; + else + return false; + //return (Secure ? SecureServer != null : UnsecureServer != null) && + //(Secure ? (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING : + // (UnsecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING); + } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsListening + { + 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. + /// + public ushort NumberOfClientsConnected + { + get + { + if (SecureServer != null) + return (ushort)SecureServer.NumberOfClientsConnected; + return 0; + } + } + + /// + /// Port Server should listen on + /// + public int Port { get; set; } + + /// + /// S+ helper for Port + /// + public ushort UPort + { + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } + } + + /// + /// Bool to show whether the server requires a preshared key. Must be set the same in the client, and if true shared keys must be identical on server/client + /// + public bool SharedKeyRequired { get; set; } + + /// + /// S+ helper for requires shared key bool + /// + public ushort USharedKeyRequired + { + set + { + if (value == 1) + SharedKeyRequired = true; + else + SharedKeyRequired = false; + } + } + + /// + /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module. + /// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called + /// + public string SharedKey { get; set; } + + /// + /// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received + /// + public bool HeartbeatRequired { get; set; } + + /// + /// S+ Helper for Heartbeat Required + /// + public ushort UHeartbeatRequired + { + set + { + if (value == 1) + HeartbeatRequired = true; + else + HeartbeatRequired = false; + } + } + + /// + /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ + /// + public int HeartbeatRequiredIntervalMs { get; set; } + + /// + /// Simpl+ Heartbeat Analog value in seconds + /// + public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } } + + /// + /// String to Match for heartbeat. If null or empty any string will reset heartbeat timer + /// + public string HeartbeatStringToMatch { get; set; } + + //private timers for Heartbeats per client + Dictionary HeartbeatTimerDictionary = new Dictionary(); + + //flags to show the secure server is waiting for client at index to send the shared key + List WaitingForSharedKey = new List(); + + List ClientReadyAfterKeyExchange = new List(); + + /// + /// The connected client indexes + /// + public List ConnectedClientsIndexes = new List(); + + /// + /// Defaults to 2000 + /// + public int BufferSize { get; set; } + + /// + /// Private flag to note that the server has stopped intentionally + /// + private bool ServerStopped { get; set; } + + //Servers + SecureTCPServer SecureServer; + + /// + /// + /// + bool ProgramIsStopping; + + #endregion + + #region Constructors + /// + /// 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 Secure TCP Server") + { + HeartbeatRequiredIntervalInSeconds = 15; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + MonitorClientMaxFailureCount = 3; + } + + /// + /// constructor with debug key set at instantiation. Make sure to set all properties before listening. + /// + /// + public GenericSecureTcpIpServer(string key) + : base("Uninitialized Secure TCP Server") + { + HeartbeatRequiredIntervalInSeconds = 15; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + MonitorClientMaxFailureCount = 3; + Key = key; + } + + /// + /// 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 Secure TCP Server") + { + HeartbeatRequiredIntervalInSeconds = 15; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + MonitorClientMaxFailureCount = 3; + Initialize(serverConfigObject); + } + #endregion + + #region Methods - Server Actions + /// + /// Disconnects all clients and stops the server + /// + public void KillServer() + { + ServerStopped = true; + if (MonitorClient != null) + { + MonitorClient.Disconnect(); + } + DisconnectAllClientsForShutdown(); + StopListening(); + } + + /// + /// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+ + /// + /// + public void Initialize(string key) + { + Key = key; + } + + /// + /// Initialze the server + /// + /// + public void Initialize(TcpServerConfigObject serverConfigObject) + { + try + { + if (serverConfigObject != null || string.IsNullOrEmpty(serverConfigObject.Key)) + { + Key = serverConfigObject.Key; + MaxClients = serverConfigObject.MaxClients; + Port = serverConfigObject.Port; + SharedKeyRequired = serverConfigObject.SharedKeyRequired; + SharedKey = serverConfigObject.SharedKey; + HeartbeatRequired = serverConfigObject.HeartbeatRequired; + HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds; + HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch; + BufferSize = serverConfigObject.BufferSize; + ReceiveQueueSize = serverConfigObject.ReceiveQueueSize > 20 ? serverConfigObject.ReceiveQueueSize : 20; + MessageQueue = new CrestronQueue(ReceiveQueueSize); + } + else + { + ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); + } + } + catch + { + ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); + } + } + + /// + /// Start listening on the specified port + /// + public void Listen() + { + ServerCCSection.Enter(); + try + { + if (Port < 1 || Port > 65535) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key); + ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key)); + return; + } + if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key); + ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key)); + return; + } + + + if (SecureServer == null) + { + SecureServer = new SecureTCPServer(Port, MaxClients); + if (HeartbeatRequired) + SecureServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5); + SecureServer.HandshakeTimeout = 30; + SecureServer.SocketStatusChange += new SecureTCPServerSocketStatusChangeEventHandler(SecureServer_SocketStatusChange); + } + else + { + SecureServer.PortNumber = Port; + } + ServerStopped = false; + + // Start the listner + SocketErrorCodes status = SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback); + if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error starting WaitForConnectionAsync {0}", status); + } + else + { + ServerStopped = false; + } + OnServerStateChange(SecureServer.State); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus); + ServerCCSection.Leave(); + + } + catch (Exception ex) + { + ServerCCSection.Leave(); + ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key); + } + } + + /// + /// Stop Listeneing + /// + public void StopListening() + { + 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); + } + } + + /// + /// Disconnects Client + /// + /// + public void DisconnectClient(uint client) + { + try + { + SecureServer.Disconnect(client); + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client); + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex); + } + } + /// + /// Disconnect All Clients + /// + public void DisconnectAllClientsForShutdown() + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients"); + if (SecureServer != null) + { + SecureServer.SocketStatusChange -= SecureServer_SocketStatusChange; + foreach (var index in ConnectedClientsIndexes.ToList()) // copy it here so that it iterates properly + { + var i = index; + if (!SecureServer.ClientConnected(index)) + continue; + try + { + SecureServer.Disconnect(i); + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i); + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex); + } + } + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", SecureServer.ServerSocketStatus); + } + + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients"); + ConnectedClientsIndexes.Clear(); + + if (!ProgramIsStopping) + { + OnConnectionChange(); + OnServerStateChange(SecureServer.State); //State shows both listening and connected + } + + // var o = new { }; + } + + /// + /// Broadcast text from server to all connected clients + /// + /// + public void BroadcastText(string text) + { + CCriticalSection CCBroadcast = new CCriticalSection(); + CCBroadcast.Enter(); + try + { + if (ConnectedClientsIndexes.Count > 0) + { + byte[] b = Encoding.GetEncoding(28591).GetBytes(text); + foreach (uint i in ConnectedClientsIndexes) + { + if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(i))) + { + SocketErrorCodes error = SecureServer.SendDataAsync(i, b, b.Length, (x, y, z) => { }); + if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING) + Debug.Console(2, error.ToString()); + } + } + } + CCBroadcast.Leave(); + } + catch (Exception ex) + { + CCBroadcast.Leave(); + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message); + } + } + + /// + /// Not sure this is useful in library, maybe Pro?? + /// + /// + /// + public void SendTextToClient(string text, uint clientIndex) + { + try + { + byte[] b = Encoding.GetEncoding(28591).GetBytes(text); + if (SecureServer != null && SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) + { + if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) + SecureServer.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); + } + } + catch (Exception ex) + { + Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text); + } + } + + //private method to check heartbeat requirements and start or reset timer + string checkHeartbeat(uint clientIndex, string received) + { + try + { + if (HeartbeatRequired) + { + if (!string.IsNullOrEmpty(HeartbeatStringToMatch)) + { + var remainingText = received.Replace(HeartbeatStringToMatch, ""); + var noDelimiter = received.Trim(new char[] { '\r', '\n' }); + if (noDelimiter.Contains(HeartbeatStringToMatch)) + { + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); + else + { + CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); + HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); + } + Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex); + // Return Heartbeat + SendTextToClient(HeartbeatStringToMatch, clientIndex); + return remainingText; + } + } + else + { + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); + else + { + CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); + HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); + } + Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex); + } + } + } + catch (Exception ex) + { + Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); + } + return received; + } + + /// + /// Get the IP Address for the client at the specifed index + /// + /// + /// + public string GetClientIPAddress(uint clientIndex) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex); + if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) + { + var ipa = this.SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa); + return ipa; + + } + else + { + return ""; + } + } + + #endregion + + #region Methods - HeartbeatTimer Callback + + void HeartbeatTimer_CallbackFunction(object o) + { + uint clientIndex = 99999; + string address = string.Empty; + try + { + clientIndex = (uint)o; + address = SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); + + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}", + address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex); + + if (SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) + SendTextToClient("Heartbeat not received by server, closing connection", clientIndex); + + var discoResult = SecureServer.Disconnect(clientIndex); + //Debug.Console(1, this, "{0}", discoResult); + + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + { + HeartbeatTimerDictionary[clientIndex].Stop(); + HeartbeatTimerDictionary[clientIndex].Dispose(); + HeartbeatTimerDictionary.Remove(clientIndex); + } + } + catch (Exception ex) + { + ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key); + } + } + + #endregion + + #region Methods - Socket Status Changed Callbacks + /// + /// Secure Server Socket Status Changed Callback + /// + /// + /// + /// + void SecureServer_SocketStatusChange(SecureTCPServer server, uint clientIndex, SocketStatus serverSocketStatus) + { + try + { + + + // Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.SecureServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.SecureServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); + if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber); + + if (ConnectedClientsIndexes.Contains(clientIndex)) + ConnectedClientsIndexes.Remove(clientIndex); + if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) + { + HeartbeatTimerDictionary[clientIndex].Stop(); + HeartbeatTimerDictionary[clientIndex].Dispose(); + HeartbeatTimerDictionary.Remove(clientIndex); + } + if (ClientReadyAfterKeyExchange.Contains(clientIndex)) + ClientReadyAfterKeyExchange.Remove(clientIndex); + if (WaitingForSharedKey.Contains(clientIndex)) + WaitingForSharedKey.Remove(clientIndex); + if (SecureServer.MaxNumberOfClientSupported > SecureServer.NumberOfClientsConnected) + { + Listen(); + } + } + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex); + } + //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 + + #region Methods Connected Callbacks + /// + /// Secure TCP Client Connected to Secure Server Callback + /// + /// + /// + void SecureConnectCallback(SecureTCPServer server, uint clientIndex) + { + try + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}", + server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), + clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); + if (clientIndex != 0) + { + if (server.ClientConnected(clientIndex)) + { + + if (!ConnectedClientsIndexes.Contains(clientIndex)) + { + ConnectedClientsIndexes.Add(clientIndex); + } + if (SharedKeyRequired) + { + if (!WaitingForSharedKey.Contains(clientIndex)) + { + WaitingForSharedKey.Add(clientIndex); + } + byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:"); + server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); + } + else + { + OnServerClientReadyForCommunications(clientIndex); + } + if (HeartbeatRequired) + { + if (!HeartbeatTimerDictionary.ContainsKey(clientIndex)) + { + HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs)); + } + } + + server.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback); + } + } + else + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty."); + } + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex); + } + + // Rearm the listner + SocketErrorCodes status = server.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback); + if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Socket status connect callback status {0}", status); + if (status == SocketErrorCodes.SOCKET_CONNECTION_IN_PROGRESS) + { + // There is an issue where on a failed negotiation we need to stop and start the server. This should still leave connected clients intact. + server.Stop(); + Listen(); + } + } + } + + #endregion + + #region Methods - Send/Receive Callbacks + /// + /// Secure Received Data Async Callback + /// + /// + /// + /// + void SecureReceivedDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesReceived) + { + if (numberOfBytesReceived > 0) + { + + string received = "Nothing"; + var handler = TextReceivedQueueInvoke; + 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); + + return; + } + + 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); + } + else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received))) + { + onTextReceived(received, clientIndex); + if (handler != null) + { + MessageQueue.TryToEnqueue(new GenericTcpServerCommMethodReceiveTextArgs(received, clientIndex)); + } + } + } + catch (Exception 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()); + } + } + 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 + + #region Methods - EventHelpers/Callbacks + + //Private Helper method to call the Connection Change Event + void onConnectionChange(uint clientIndex, SocketStatus clientStatus) + { + if (clientIndex != 0) //0 is error not valid client change + { + var handler = ClientConnectionChange; + if (handler != null) + { + handler(this, new GenericTcpServerSocketStatusChangeEventArgs(SecureServer, clientIndex, clientStatus)); + } + } + } + + //Private Helper method to call the Connection Change Event + void OnConnectionChange() + { + if (ProgramIsStopping) + { + return; + } + var handler = ClientConnectionChange; + if (handler != null) + { + handler(this, new GenericTcpServerSocketStatusChangeEventArgs()); + } + } + + //Private Helper Method to call the Text Received Event + void onTextReceived(string text, uint clientIndex) + { + var handler = TextReceived; + if (handler != null) + handler(this, new GenericTcpServerCommMethodReceiveTextArgs(text, clientIndex)); + } + + //Private Helper Method to call the Server State Change Event + void OnServerStateChange(ServerState state) + { + if (ProgramIsStopping) + { + return; + } + var handler = ServerStateChange; + if (handler != null) + { + handler(this, new GenericTcpServerStateChangedEventArgs(state)); + } + } + + /// + /// Private Event Handler method to handle the closing of connections when the program stops + /// + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + ProgramIsStopping = true; + // kill bandaid things + if (MonitorClientTimer != null) + MonitorClientTimer.Stop(); + if (MonitorClient != null) + MonitorClient.Disconnect(); + + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server"); + KillServer(); + } + } + + //Private event handler method to raise the event that the server is ready to send data after a successful client shared key negotiation + void OnServerClientReadyForCommunications(uint clientIndex) + { + ClientReadyAfterKeyExchange.Add(clientIndex); + var handler = ServerClientReadyForCommunications; + if (handler != null) + handler(this, new GenericTcpServerSocketStatusChangeEventArgs( + this, clientIndex, SecureServer.GetServerSocketStatusForSpecificClient(clientIndex))); + } + #endregion + + #region Monitor Client + /// + /// Starts the monitor client cycle. Timed wait, then call RunMonitorClient + /// + void StartMonitorClient() + { + if (MonitorClientTimer != null) + { + return; + } + MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000); + } + + /// + /// + /// + void RunMonitorClient() + { + MonitorClient = new GenericSecureTcpIpClient_ForServer(Key + "-MONITOR", "127.0.0.1", Port, 2000); + MonitorClient.SharedKeyRequired = this.SharedKeyRequired; + MonitorClient.SharedKey = this.SharedKey; + MonitorClient.ConnectionHasHungCallback = MonitorClientHasHungCallback; + //MonitorClient.ConnectionChange += MonitorClient_ConnectionChange; + MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm; + + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check"); + + MonitorClient.Connect(); + // From here MonitorCLient either connects or hangs, MonitorClient will call back + + } + + /// + /// + /// + void StopMonitorClient() + { + if (MonitorClient == null) + return; + + MonitorClient.ClientReadyForCommunications -= MonitorClient_IsReadyForComm; + MonitorClient.Disconnect(); + MonitorClient = null; + } + + /// + /// On monitor connect, restart the operation + /// + void MonitorClient_IsReadyForComm(object sender, GenericTcpServerClientReadyForcommunicationsEventArgs args) + { + if (args.IsReady) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s"); + MonitorClientTimer.Stop(); + MonitorClientTimer = null; + MonitorClientFailureCount = 0; + CrestronEnvironment.Sleep(2000); + StopMonitorClient(); + StartMonitorClient(); + } + } + + /// + /// If the client hangs, add to counter and maybe fire the choke event + /// + void MonitorClientHasHungCallback() + { + MonitorClientFailureCount++; + MonitorClientTimer.Stop(); + MonitorClientTimer = null; + StopMonitorClient(); + if (MonitorClientFailureCount < MonitorClientMaxFailureCount) + { + 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(2, this, Debug.ErrorLogLevel.Error, + "\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************", + MonitorClientMaxFailureCount); + + var handler = ServerHasChoked; + if (handler != null) + handler(); + // Some external thing is in charge here. Expected reset of program + } + } + #endregion + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs b/src/Comm/GenericSshClient.cs similarity index 97% rename from Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs rename to src/Comm/GenericSshClient.cs index e13c284..0d5113e 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericSshClient.cs +++ b/src/Comm/GenericSshClient.cs @@ -1,610 +1,610 @@ -using System; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronSockets; -using Crestron.SimplSharp.Ssh; -using Crestron.SimplSharp.Ssh.Common; - -namespace PepperDash.Core -{ - /// - /// - /// - public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect - { - private const string SPlusKey = "Uninitialized SshClient"; - /// - /// Object to enable stream debugging - /// - public CommunicationStreamDebugging StreamDebugging { get; private set; } - - /// - /// Event that fires when data is received. Delivers args with byte array - /// - public event EventHandler BytesReceived; - - /// - /// Event that fires when data is received. Delivered as text. - /// - public event EventHandler TextReceived; - - /// - /// Event when the connection status changes. - /// - public event EventHandler ConnectionChange; - - ///// - ///// - ///// - //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; - - /// - /// Address of server - /// - public string Hostname { get; set; } - - /// - /// Port on server - /// - public int Port { get; set; } - - /// - /// Username for server - /// - public string Username { get; set; } - - /// - /// And... Password for server. That was worth documenting! - /// - public string Password { get; set; } - - /// - /// True when the server is connected - when status == 2. - /// - public bool IsConnected - { - // returns false if no client or not connected - get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// - /// - public SocketStatus ClientStatus - { - get { return _ClientStatus; } - private set - { - if (_ClientStatus == value) - return; - _ClientStatus = value; - OnConnectionChange(); - } - } - SocketStatus _ClientStatus; - - /// - /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event - /// and IsConnected with be true when this == 2. - /// - public ushort UStatus - { - get { return (ushort)_ClientStatus; } - } - - /// - /// Determines whether client will attempt reconnection on failure. Default is true - /// - public bool AutoReconnect { get; set; } - - /// - /// Will be set and unset by connect and disconnect only - /// - public bool ConnectEnabled { get; private set; } - - /// - /// S+ helper for AutoReconnect - /// - public ushort UAutoReconnect - { - get { return (ushort)(AutoReconnect ? 1 : 0); } - set { AutoReconnect = value == 1; } - } - - /// - /// Millisecond value, determines the timeout period in between reconnect attempts. - /// Set to 5000 by default - /// - public int AutoReconnectIntervalMs { get; set; } - - SshClient Client; - - ShellStream TheStream; - - CTimer ReconnectTimer; - - //Lock object to prevent simulatneous connect/disconnect operations - private CCriticalSection connectLock = new CCriticalSection(); - - private bool DisconnectLogged = false; - - /// - /// Typical constructor. - /// - public GenericSshClient(string key, string hostname, int port, string username, string password) : - base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - Key = key; - Hostname = hostname; - Port = port; - Username = username; - Password = password; - AutoReconnectIntervalMs = 5000; - - ReconnectTimer = new CTimer(o => - { - if (ConnectEnabled) - { - Connect(); - } - }, Timeout.Infinite); - } - - /// - /// S+ Constructor - Must set all properties before calling Connect - /// - public GenericSshClient() - : base(SPlusKey) - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - - ReconnectTimer = new CTimer(o => - { - if (ConnectEnabled) - { - Connect(); - } - }, Timeout.Infinite); - } - - /// - /// Just to help S+ set the key - /// - public void Initialize(string key) - { - Key = key; - } - - /// - /// Handles closing this up when the program shuts down - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping) - { - if (Client != null) - { - Debug.Console(1, this, "Program stopping. Closing connection"); - Disconnect(); - } - } - } - - /// - /// Connect to the server, using the provided properties. - /// - public void Connect() - { - // Don't go unless everything is here - if (string.IsNullOrEmpty(Hostname) || Port < 1 || Port > 65535 - || Username == null || Password == null) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Connect failed. Check hostname, port, username and password are set or not null"); - return; - } - - ConnectEnabled = true; - - try - { - connectLock.Enter(); - if (IsConnected) - { - Debug.Console(1, this, "Connection already connected. Exiting Connect()"); - } - else - { - Debug.Console(1, this, "Attempting connect"); - - // Cancel reconnect if running. - if (ReconnectTimer != null) - { - ReconnectTimer.Stop(); - } - - // Cleanup the old client if it already exists - if (Client != null) - { - Debug.Console(1, this, "Cleaning up disconnected client"); - KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); - } - - // This handles both password and keyboard-interactive (like on OS-X, 'nixes) - KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod(Username); - kauth.AuthenticationPrompt += new EventHandler(kauth_AuthenticationPrompt); - PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password); - - Debug.Console(1, this, "Creating new SshClient"); - ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); - Client = new SshClient(connectionInfo); - Client.ErrorOccurred += Client_ErrorOccurred; - - //Attempt to connect - ClientStatus = SocketStatus.SOCKET_STATUS_WAITING; - try - { - Client.Connect(); - TheStream = Client.CreateShellStream("PDTShell", 100, 80, 100, 200, 65534); - TheStream.DataReceived += Stream_DataReceived; - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Connected"); - ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; - DisconnectLogged = false; - } - catch (SshConnectionException e) - { - var ie = e.InnerException; // The details are inside!! - var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; - - if (ie is SocketException) - Debug.Console(1, this, errorLogLevel, "'{0}' CONNECTION failure: Cannot reach host, ({1})", Key, ie.Message); - else if (ie is System.Net.Sockets.SocketException) - Debug.Console(1, this, errorLogLevel, "'{0}' Connection failure: Cannot reach host '{1}' on port {2}, ({3})", - Key, Hostname, Port, ie.GetType()); - else if (ie is SshAuthenticationException) - { - Debug.Console(1, this, errorLogLevel, "Authentication failure for username '{0}', ({1})", - Username, ie.Message); - } - else - Debug.Console(1, this, errorLogLevel, "Error on connect:\r({0})", ie.Message); - - DisconnectLogged = true; - KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect) - { - Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Reset(AutoReconnectIntervalMs); - } - } - catch (Exception e) - { - var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; - Debug.Console(1, this, errorLogLevel, "Unhandled exception on connect:\r({0})", e.Message); - DisconnectLogged = true; - KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - if (AutoReconnect) - { - Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Reset(AutoReconnectIntervalMs); - } - } - } - } - finally - { - connectLock.Leave(); - } - } - - /// - /// Disconnect the clients and put away it's resources. - /// - public void Disconnect() - { - ConnectEnabled = false; - // Stop trying reconnects, if we are - if (ReconnectTimer != null) - { - ReconnectTimer.Stop(); - // ReconnectTimer = null; - } - - KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); - } - - /// - /// Kills the stream, cleans up the client and sets it to null - /// - private void KillClient(SocketStatus status) - { - KillStream(); - - try - { - if (Client != null) - { - Client.ErrorOccurred -= Client_ErrorOccurred; - Client.Disconnect(); - Client.Dispose(); - Client = null; - ClientStatus = status; - Debug.Console(1, this, "Disconnected"); - } - } - catch (Exception ex) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception in Kill Client:{0}", ex); - } - } - - /// - /// Anything to do with reestablishing connection on failures - /// - void HandleConnectionFailure() - { - KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - - Debug.Console(1, this, "Client nulled due to connection failure. AutoReconnect: {0}, ConnectEnabled: {1}", AutoReconnect, ConnectEnabled); - if (AutoReconnect && ConnectEnabled) - { - Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - if (ReconnectTimer == null) - { - ReconnectTimer = new CTimer(o => - { - Connect(); - }, AutoReconnectIntervalMs); - Debug.Console(1, this, "Attempting connection in {0} seconds", - (float) (AutoReconnectIntervalMs/1000)); - } - else - { - Debug.Console(1, this, "{0} second reconnect cycle running", - (float) (AutoReconnectIntervalMs/1000)); - } - } - } - - /// - /// Kills the stream - /// - void KillStream() - { - try - { - if (TheStream != null) - { - TheStream.DataReceived -= Stream_DataReceived; - TheStream.Close(); - TheStream.Dispose(); - TheStream = null; - Debug.Console(1, this, "Disconnected stream"); - } - } - catch (Exception ex) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception in Kill Stream:{0}", ex); - } - } - - /// - /// Handles the keyboard interactive authentication, should it be required. - /// - void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) - { - foreach (AuthenticationPrompt prompt in e.Prompts) - if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) - prompt.Response = Password; - } - - /// - /// Handler for data receive on ShellStream. Passes data across to queue for line parsing. - /// - void Stream_DataReceived(object sender, Crestron.SimplSharp.Ssh.Common.ShellDataEventArgs e) - { - var bytes = e.Data; - if (bytes.Length > 0) - { - var bytesHandler = BytesReceived; - if (bytesHandler != null) - { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - { - Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); - } - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - } - - var textHandler = TextReceived; - if (textHandler != null) - { - var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - if (StreamDebugging.RxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Received: '{0}'", ComTextHelper.GetDebugText(str)); - - textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } - } - } - - - /// - /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange - /// event - /// - void Client_ErrorOccurred(object sender, Crestron.SimplSharp.Ssh.Common.ExceptionEventArgs e) - { - CrestronInvoke.BeginInvoke(o => - { - if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException) - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Disconnected by remote"); - else - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Unhandled SSH client error: {0}", e.Exception); - - try - { - connectLock.Enter(); - KillClient(SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY); - } - finally - { - connectLock.Leave(); - } - if (AutoReconnect && ConnectEnabled) - { - Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); - ReconnectTimer.Reset(AutoReconnectIntervalMs); - } - }); - } - - /// - /// Helper for ConnectionChange event - /// - void OnConnectionChange() - { - if (ConnectionChange != null) - ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); - } - - #region IBasicCommunication Members - - /// - /// Sends text to the server - /// - /// - public void SendText(string text) - { - try - { - if (Client != null && TheStream != null && IsConnected) - { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.Console(0, - this, - "Sending {0} characters of text: '{1}'", - text.Length, - ComTextHelper.GetDebugText(text)); - - TheStream.Write(text); - TheStream.Flush(); - } - else - { - Debug.Console(1, this, "Client is null or disconnected. Cannot Send Text"); - } - } - catch (ObjectDisposedException ex) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message); - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace); - - KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - ReconnectTimer.Reset(); - } - catch (Exception ex) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message); - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace); - - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stream write failed"); - } - } - - /// - /// Sends Bytes to the server - /// - /// - public void SendBytes(byte[] bytes) - { - try - { - if (Client != null && TheStream != null && IsConnected) - { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); - - TheStream.Write(bytes, 0, bytes.Length); - TheStream.Flush(); - } - else - { - Debug.Console(1, this, "Client is null or disconnected. Cannot Send Bytes"); - } - } - catch (ObjectDisposedException ex) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message); - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace); - - KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); - ReconnectTimer.Reset(); - } - catch (Exception ex) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message); - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace); - - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stream write failed"); - } - } - - #endregion - } - - //***************************************************************************************************** - //***************************************************************************************************** - /// - /// Fired when connection changes - /// - public class SshConnectionChangeEventArgs : EventArgs - { - /// - /// Connection State - /// - public bool IsConnected { get; private set; } - - /// - /// Connection Status represented as a ushort - /// - public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } } - - /// - /// The client - /// - public GenericSshClient Client { get; private set; } - - /// - /// Socket Status as represented by - /// - public ushort Status { get { return Client.UStatus; } } - - /// - /// S+ Constructor - /// - public SshConnectionChangeEventArgs() { } - - /// - /// EventArgs class - /// - /// Connection State - /// The Client - public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client) - { - IsConnected = isConnected; - Client = client; - } - } -} +using System; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronSockets; +using Crestron.SimplSharp.Ssh; +using Crestron.SimplSharp.Ssh.Common; + +namespace PepperDash.Core +{ + /// + /// + /// + public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect + { + private const string SPlusKey = "Uninitialized SshClient"; + /// + /// Object to enable stream debugging + /// + public CommunicationStreamDebugging StreamDebugging { get; private set; } + + /// + /// Event that fires when data is received. Delivers args with byte array + /// + public event EventHandler BytesReceived; + + /// + /// Event that fires when data is received. Delivered as text. + /// + public event EventHandler TextReceived; + + /// + /// Event when the connection status changes. + /// + public event EventHandler ConnectionChange; + + ///// + ///// + ///// + //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; + + /// + /// Address of server + /// + public string Hostname { get; set; } + + /// + /// Port on server + /// + public int Port { get; set; } + + /// + /// Username for server + /// + public string Username { get; set; } + + /// + /// And... Password for server. That was worth documenting! + /// + public string Password { get; set; } + + /// + /// True when the server is connected - when status == 2. + /// + public bool IsConnected + { + // returns false if no client or not connected + get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// + /// + public SocketStatus ClientStatus + { + get { return _ClientStatus; } + private set + { + if (_ClientStatus == value) + return; + _ClientStatus = value; + OnConnectionChange(); + } + } + SocketStatus _ClientStatus; + + /// + /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event + /// and IsConnected with be true when this == 2. + /// + public ushort UStatus + { + get { return (ushort)_ClientStatus; } + } + + /// + /// Determines whether client will attempt reconnection on failure. Default is true + /// + public bool AutoReconnect { get; set; } + + /// + /// Will be set and unset by connect and disconnect only + /// + public bool ConnectEnabled { get; private set; } + + /// + /// S+ helper for AutoReconnect + /// + public ushort UAutoReconnect + { + get { return (ushort)(AutoReconnect ? 1 : 0); } + set { AutoReconnect = value == 1; } + } + + /// + /// Millisecond value, determines the timeout period in between reconnect attempts. + /// Set to 5000 by default + /// + public int AutoReconnectIntervalMs { get; set; } + + SshClient Client; + + ShellStream TheStream; + + CTimer ReconnectTimer; + + //Lock object to prevent simulatneous connect/disconnect operations + private CCriticalSection connectLock = new CCriticalSection(); + + private bool DisconnectLogged = false; + + /// + /// Typical constructor. + /// + public GenericSshClient(string key, string hostname, int port, string username, string password) : + base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + Key = key; + Hostname = hostname; + Port = port; + Username = username; + Password = password; + AutoReconnectIntervalMs = 5000; + + ReconnectTimer = new CTimer(o => + { + if (ConnectEnabled) + { + Connect(); + } + }, Timeout.Infinite); + } + + /// + /// S+ Constructor - Must set all properties before calling Connect + /// + public GenericSshClient() + : base(SPlusKey) + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + + ReconnectTimer = new CTimer(o => + { + if (ConnectEnabled) + { + Connect(); + } + }, Timeout.Infinite); + } + + /// + /// Just to help S+ set the key + /// + public void Initialize(string key) + { + Key = key; + } + + /// + /// Handles closing this up when the program shuts down + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + if (Client != null) + { + Debug.Console(1, this, "Program stopping. Closing connection"); + Disconnect(); + } + } + } + + /// + /// Connect to the server, using the provided properties. + /// + public void Connect() + { + // Don't go unless everything is here + if (string.IsNullOrEmpty(Hostname) || Port < 1 || Port > 65535 + || Username == null || Password == null) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Connect failed. Check hostname, port, username and password are set or not null"); + return; + } + + ConnectEnabled = true; + + try + { + connectLock.Enter(); + if (IsConnected) + { + Debug.Console(1, this, "Connection already connected. Exiting Connect()"); + } + else + { + Debug.Console(1, this, "Attempting connect"); + + // Cancel reconnect if running. + if (ReconnectTimer != null) + { + ReconnectTimer.Stop(); + } + + // Cleanup the old client if it already exists + if (Client != null) + { + Debug.Console(1, this, "Cleaning up disconnected client"); + KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); + } + + // This handles both password and keyboard-interactive (like on OS-X, 'nixes) + KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod(Username); + kauth.AuthenticationPrompt += new EventHandler(kauth_AuthenticationPrompt); + PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(Username, Password); + + Debug.Console(1, this, "Creating new SshClient"); + ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth); + Client = new SshClient(connectionInfo); + Client.ErrorOccurred += Client_ErrorOccurred; + + //Attempt to connect + ClientStatus = SocketStatus.SOCKET_STATUS_WAITING; + try + { + Client.Connect(); + TheStream = Client.CreateShellStream("PDTShell", 100, 80, 100, 200, 65534); + TheStream.DataReceived += Stream_DataReceived; + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Connected"); + ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED; + DisconnectLogged = false; + } + catch (SshConnectionException e) + { + var ie = e.InnerException; // The details are inside!! + var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; + + if (ie is SocketException) + Debug.Console(1, this, errorLogLevel, "'{0}' CONNECTION failure: Cannot reach host, ({1})", Key, ie.Message); + else if (ie is System.Net.Sockets.SocketException) + Debug.Console(1, this, errorLogLevel, "'{0}' Connection failure: Cannot reach host '{1}' on port {2}, ({3})", + Key, Hostname, Port, ie.GetType()); + else if (ie is SshAuthenticationException) + { + Debug.Console(1, this, errorLogLevel, "Authentication failure for username '{0}', ({1})", + Username, ie.Message); + } + else + Debug.Console(1, this, errorLogLevel, "Error on connect:\r({0})", ie.Message); + + DisconnectLogged = true; + KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); + if (AutoReconnect) + { + Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); + ReconnectTimer.Reset(AutoReconnectIntervalMs); + } + } + catch (Exception e) + { + var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error; + Debug.Console(1, this, errorLogLevel, "Unhandled exception on connect:\r({0})", e.Message); + DisconnectLogged = true; + KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); + if (AutoReconnect) + { + Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); + ReconnectTimer.Reset(AutoReconnectIntervalMs); + } + } + } + } + finally + { + connectLock.Leave(); + } + } + + /// + /// Disconnect the clients and put away it's resources. + /// + public void Disconnect() + { + ConnectEnabled = false; + // Stop trying reconnects, if we are + if (ReconnectTimer != null) + { + ReconnectTimer.Stop(); + // ReconnectTimer = null; + } + + KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY); + } + + /// + /// Kills the stream, cleans up the client and sets it to null + /// + private void KillClient(SocketStatus status) + { + KillStream(); + + try + { + if (Client != null) + { + Client.ErrorOccurred -= Client_ErrorOccurred; + Client.Disconnect(); + Client.Dispose(); + Client = null; + ClientStatus = status; + Debug.Console(1, this, "Disconnected"); + } + } + catch (Exception ex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception in Kill Client:{0}", ex); + } + } + + /// + /// Anything to do with reestablishing connection on failures + /// + void HandleConnectionFailure() + { + KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); + + Debug.Console(1, this, "Client nulled due to connection failure. AutoReconnect: {0}, ConnectEnabled: {1}", AutoReconnect, ConnectEnabled); + if (AutoReconnect && ConnectEnabled) + { + Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); + if (ReconnectTimer == null) + { + ReconnectTimer = new CTimer(o => + { + Connect(); + }, AutoReconnectIntervalMs); + Debug.Console(1, this, "Attempting connection in {0} seconds", + (float) (AutoReconnectIntervalMs/1000)); + } + else + { + Debug.Console(1, this, "{0} second reconnect cycle running", + (float) (AutoReconnectIntervalMs/1000)); + } + } + } + + /// + /// Kills the stream + /// + void KillStream() + { + try + { + if (TheStream != null) + { + TheStream.DataReceived -= Stream_DataReceived; + TheStream.Close(); + TheStream.Dispose(); + TheStream = null; + Debug.Console(1, this, "Disconnected stream"); + } + } + catch (Exception ex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception in Kill Stream:{0}", ex); + } + } + + /// + /// Handles the keyboard interactive authentication, should it be required. + /// + void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e) + { + foreach (AuthenticationPrompt prompt in e.Prompts) + if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1) + prompt.Response = Password; + } + + /// + /// Handler for data receive on ShellStream. Passes data across to queue for line parsing. + /// + void Stream_DataReceived(object sender, Crestron.SimplSharp.Ssh.Common.ShellDataEventArgs e) + { + var bytes = e.Data; + if (bytes.Length > 0) + { + var bytesHandler = BytesReceived; + if (bytesHandler != null) + { + if (StreamDebugging.RxStreamDebuggingIsEnabled) + { + Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); + } + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + } + + var textHandler = TextReceived; + if (textHandler != null) + { + var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + if (StreamDebugging.RxStreamDebuggingIsEnabled) + Debug.Console(0, this, "Received: '{0}'", ComTextHelper.GetDebugText(str)); + + textHandler(this, new GenericCommMethodReceiveTextArgs(str)); + } + } + } + + + /// + /// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange + /// event + /// + void Client_ErrorOccurred(object sender, Crestron.SimplSharp.Ssh.Common.ExceptionEventArgs e) + { + CrestronInvoke.BeginInvoke(o => + { + if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException) + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Disconnected by remote"); + else + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Unhandled SSH client error: {0}", e.Exception); + + try + { + connectLock.Enter(); + KillClient(SocketStatus.SOCKET_STATUS_BROKEN_REMOTELY); + } + finally + { + connectLock.Leave(); + } + if (AutoReconnect && ConnectEnabled) + { + Debug.Console(1, this, "Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs); + ReconnectTimer.Reset(AutoReconnectIntervalMs); + } + }); + } + + /// + /// Helper for ConnectionChange event + /// + void OnConnectionChange() + { + if (ConnectionChange != null) + ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); + } + + #region IBasicCommunication Members + + /// + /// Sends text to the server + /// + /// + public void SendText(string text) + { + try + { + if (Client != null && TheStream != null && IsConnected) + { + if (StreamDebugging.TxStreamDebuggingIsEnabled) + Debug.Console(0, + this, + "Sending {0} characters of text: '{1}'", + text.Length, + ComTextHelper.GetDebugText(text)); + + TheStream.Write(text); + TheStream.Flush(); + } + else + { + Debug.Console(1, this, "Client is null or disconnected. Cannot Send Text"); + } + } + catch (ObjectDisposedException ex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace); + + KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); + ReconnectTimer.Reset(); + } + catch (Exception ex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace); + + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stream write failed"); + } + } + + /// + /// Sends Bytes to the server + /// + /// + public void SendBytes(byte[] bytes) + { + try + { + if (Client != null && TheStream != null && IsConnected) + { + if (StreamDebugging.TxStreamDebuggingIsEnabled) + Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + + TheStream.Write(bytes, 0, bytes.Length); + TheStream.Flush(); + } + else + { + Debug.Console(1, this, "Client is null or disconnected. Cannot Send Bytes"); + } + } + catch (ObjectDisposedException ex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace); + + KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED); + ReconnectTimer.Reset(); + } + catch (Exception ex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Exception: {0}", ex.Message); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Stack Trace: {0}", ex.StackTrace); + + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Stream write failed"); + } + } + + #endregion + } + + //***************************************************************************************************** + //***************************************************************************************************** + /// + /// Fired when connection changes + /// + public class SshConnectionChangeEventArgs : EventArgs + { + /// + /// Connection State + /// + public bool IsConnected { get; private set; } + + /// + /// Connection Status represented as a ushort + /// + public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } } + + /// + /// The client + /// + public GenericSshClient Client { get; private set; } + + /// + /// Socket Status as represented by + /// + public ushort Status { get { return Client.UStatus; } } + + /// + /// S+ Constructor + /// + public SshConnectionChangeEventArgs() { } + + /// + /// EventArgs class + /// + /// Connection State + /// The Client + public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client) + { + IsConnected = isConnected; + Client = client; + } + } +} diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs b/src/Comm/GenericTcpIpClient.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs rename to src/Comm/GenericTcpIpClient.cs index 024221f..9fa17c1 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient.cs +++ b/src/Comm/GenericTcpIpClient.cs @@ -1,567 +1,567 @@ -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; - -namespace PepperDash.Core -{ - /// - /// A class to handle basic TCP/IP communications with a server - /// - public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect - { - private const string SplusKey = "Uninitialized TcpIpClient"; - /// - /// Object to enable stream debugging - /// - public CommunicationStreamDebugging StreamDebugging { get; private set; } - - /// - /// Fires when data is received from the server and returns it as a Byte array - /// - public event EventHandler BytesReceived; - - /// - /// Fires when data is received from the server and returns it as text - /// - public event EventHandler TextReceived; - - /// - /// - /// - //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; - public event EventHandler ConnectionChange; - - - private string _hostname; - - /// - /// Address of server - /// - public string Hostname - { - get - { - return _hostname; - } - - set - { - _hostname = value; - if (_client != null) - { - _client.AddressClientConnectedTo = _hostname; - } - } - } - - /// - /// 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); } - } - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// The actual client class - /// - private TCPClient _client; - - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// _client socket status Read only - /// - public SocketStatus ClientStatus - { - get - { - return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; - } - } - - /// - /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event - /// and IsConnected would be true when this == 2. - /// - public ushort UStatus - { - get { return (ushort)ClientStatus; } - } - - /// - /// Status text shows the message associated with socket status - /// - public string ClientStatusText { get { return ClientStatus.ToString(); } } - - /// - /// Ushort representation of client status - /// - [Obsolete] - public ushort UClientStatus { get { return (ushort)ClientStatus; } } - - /// - /// Connection failure reason - /// - public string ConnectionFailure { get { return ClientStatus.ToString(); } } - - /// - /// bool to track if auto reconnect should be set on the socket - /// - public bool AutoReconnect { get; set; } - - /// - /// S+ helper for AutoReconnect - /// - public ushort UAutoReconnect - { - get { return (ushort)(AutoReconnect ? 1 : 0); } - set { AutoReconnect = value == 1; } - } - - /// - /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 - /// - public int AutoReconnectIntervalMs { get; set; } - - /// - /// Set only when the disconnect method is called - /// - bool DisconnectCalledByUser; - - /// - /// - /// - public bool Connected - { - get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } - } - - //Lock object to prevent simulatneous connect/disconnect operations - private CCriticalSection connectLock = new CCriticalSection(); - - // private Timer for auto reconnect - private CTimer RetryTimer; - - /// - /// Constructor - /// - /// unique string to differentiate between instances - /// - /// - /// - public GenericTcpIpClient(string key, string address, int port, int bufferSize) - : base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - Hostname = address; - Port = port; - BufferSize = bufferSize; - - RetryTimer = new CTimer(o => - { - Reconnect(); - }, Timeout.Infinite); - } - - /// - /// Constructor - /// - /// - public GenericTcpIpClient(string key) - : base(key) - { - StreamDebugging = new CommunicationStreamDebugging(key); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - BufferSize = 2000; - - RetryTimer = new CTimer(o => - { - Reconnect(); - }, Timeout.Infinite); - } - - /// - /// Default constructor for S+ - /// - public GenericTcpIpClient() - : base(SplusKey) - { - StreamDebugging = new CommunicationStreamDebugging(SplusKey); - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - BufferSize = 2000; - - RetryTimer = new CTimer(o => - { - Reconnect(); - }, Timeout.Infinite); - } - - /// - /// Just to help S+ set the key - /// - public void Initialize(string key) - { - Key = key; - } - - /// - /// Handles closing this up when the program shuts down - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping) - { - Debug.Console(1, this, "Program stopping. Closing connection"); - Deactivate(); - } - } - - /// - /// - /// - /// - public override bool Deactivate() - { - RetryTimer.Stop(); - RetryTimer.Dispose(); - if (_client != null) - { - _client.SocketStatusChange -= this.Client_SocketStatusChange; - DisconnectClient(); - } - return true; - } - - /// - /// Attempts to connect to the server - /// - public void Connect() - { - if (string.IsNullOrEmpty(Hostname)) - { - Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key); - return; - } - if (Port < 1 || Port > 65535) - { - { - Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': Invalid port", Key); - return; - } - } - - try - { - connectLock.Enter(); - if (IsConnected) - { - Debug.Console(1, this, "Connection already connected. Exiting Connect()"); - } - else - { - //Stop retry timer if running - RetryTimer.Stop(); - _client = new TCPClient(Hostname, Port, BufferSize); - _client.SocketStatusChange -= Client_SocketStatusChange; - _client.SocketStatusChange += Client_SocketStatusChange; - DisconnectCalledByUser = false; - _client.ConnectToServerAsync(ConnectToServerCallback); - } - } - finally - { - connectLock.Leave(); - } - } - - private void Reconnect() - { - if (_client == null) - { - return; - } - try - { - connectLock.Enter(); - if (IsConnected || DisconnectCalledByUser == true) - { - Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()"); - } - else - { - Debug.Console(1, this, "Attempting reconnect now"); - _client.ConnectToServerAsync(ConnectToServerCallback); - } - } - finally - { - connectLock.Leave(); - } - } - - /// - /// Attempts to disconnect the client - /// - public void Disconnect() - { - try - { - connectLock.Enter(); - DisconnectCalledByUser = true; - - // Stop trying reconnects, if we are - RetryTimer.Stop(); - DisconnectClient(); - } - finally - { - connectLock.Leave(); - } - } - - /// - /// Does the actual disconnect business - /// - public void DisconnectClient() - { - if (_client != null) - { - Debug.Console(1, this, "Disconnecting client"); - if (IsConnected) - _client.DisconnectFromServer(); - } - } - - /// - /// Callback method for connection attempt - /// - /// - void ConnectToServerCallback(TCPClient c) - { - if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) - { - Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus); - WaitAndTryReconnect(); - } - else - { - Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus); - } - } - - /// - /// Disconnects, waits and attemtps to connect again - /// - void WaitAndTryReconnect() - { - CrestronInvoke.BeginInvoke(o => - { - try - { - connectLock.Enter(); - if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null) - { - DisconnectClient(); - Debug.Console(1, this, "Attempting reconnect, status={0}", _client.ClientStatus); - RetryTimer.Reset(AutoReconnectIntervalMs); - } - } - finally - { - connectLock.Leave(); - } - }); - } - - /// - /// Recieves incoming data - /// - /// - /// - void Receive(TCPClient client, int numBytes) - { - if (client != null) - { - if (numBytes > 0) - { - var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); - var bytesHandler = BytesReceived; - if (bytesHandler != null) - { - if (StreamDebugging.RxStreamDebuggingIsEnabled) - { - Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); - } - bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - } - var textHandler = TextReceived; - if (textHandler != null) - { - var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - - if (StreamDebugging.RxStreamDebuggingIsEnabled) - { - Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length); - } - - textHandler(this, new GenericCommMethodReceiveTextArgs(str)); - } - } - client.ReceiveDataAsync(Receive); - } - } - - /// - /// General send method - /// - public void SendText(string text) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - // Check debug level before processing byte array - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text)); - if (_client != null) - _client.SendData(bytes, bytes.Length); - } - - /// - /// This is useful from console and...? - /// - public void SendEscapedText(string text) - { - var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => - { - var hex = s.Groups[1].Value; - return ((char)Convert.ToByte(hex, 16)).ToString(); - }); - SendText(unescapedText); - } - - /// - /// Sends Bytes to the server - /// - /// - public void SendBytes(byte[] bytes) - { - if (StreamDebugging.TxStreamDebuggingIsEnabled) - Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); - if (_client != null) - _client.SendData(bytes, bytes.Length); - } - - /// - /// Socket Status Change Handler - /// - /// - /// - void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) - { - if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) - { - Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); - WaitAndTryReconnect(); - } - else - { - Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); - _client.ReceiveDataAsync(Receive); - } - - var handler = ConnectionChange; - if (handler != null) - ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); - } - } - - /// - /// Configuration properties for TCP/SSH Connections - /// - public class TcpSshPropertiesConfig - { - /// - /// Address to connect to - /// - [JsonProperty(Required = Required.Always)] - public string Address { get; set; } - - /// - /// Port to connect to - /// - [JsonProperty(Required = Required.Always)] - public int Port { get; set; } - - /// - /// Username credential - /// - public string Username { get; set; } - /// - /// Passord credential - /// - public string Password { get; set; } - - /// - /// Defaults to 32768 - /// - public int BufferSize { get; set; } - - /// - /// Defaults to true - /// - public bool AutoReconnect { get; set; } - - /// - /// Defaults to 5000ms - /// - public int AutoReconnectIntervalMs { get; set; } - - /// - /// Default constructor - /// - public TcpSshPropertiesConfig() - { - BufferSize = 32768; - AutoReconnect = true; - AutoReconnectIntervalMs = 5000; - Username = ""; - Password = ""; - } - - } - +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; + +namespace PepperDash.Core +{ + /// + /// A class to handle basic TCP/IP communications with a server + /// + public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect + { + private const string SplusKey = "Uninitialized TcpIpClient"; + /// + /// Object to enable stream debugging + /// + public CommunicationStreamDebugging StreamDebugging { get; private set; } + + /// + /// Fires when data is received from the server and returns it as a Byte array + /// + public event EventHandler BytesReceived; + + /// + /// Fires when data is received from the server and returns it as text + /// + public event EventHandler TextReceived; + + /// + /// + /// + //public event GenericSocketStatusChangeEventDelegate SocketStatusChange; + public event EventHandler ConnectionChange; + + + private string _hostname; + + /// + /// Address of server + /// + public string Hostname + { + get + { + return _hostname; + } + + set + { + _hostname = value; + if (_client != null) + { + _client.AddressClientConnectedTo = _hostname; + } + } + } + + /// + /// 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); } + } + + /// + /// Defaults to 2000 + /// + public int BufferSize { get; set; } + + /// + /// The actual client class + /// + private TCPClient _client; + + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// _client socket status Read only + /// + public SocketStatus ClientStatus + { + get + { + return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus; + } + } + + /// + /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event + /// and IsConnected would be true when this == 2. + /// + public ushort UStatus + { + get { return (ushort)ClientStatus; } + } + + /// + /// Status text shows the message associated with socket status + /// + public string ClientStatusText { get { return ClientStatus.ToString(); } } + + /// + /// Ushort representation of client status + /// + [Obsolete] + public ushort UClientStatus { get { return (ushort)ClientStatus; } } + + /// + /// Connection failure reason + /// + public string ConnectionFailure { get { return ClientStatus.ToString(); } } + + /// + /// bool to track if auto reconnect should be set on the socket + /// + public bool AutoReconnect { get; set; } + + /// + /// S+ helper for AutoReconnect + /// + public ushort UAutoReconnect + { + get { return (ushort)(AutoReconnect ? 1 : 0); } + set { AutoReconnect = value == 1; } + } + + /// + /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// Set only when the disconnect method is called + /// + bool DisconnectCalledByUser; + + /// + /// + /// + public bool Connected + { + get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; } + } + + //Lock object to prevent simulatneous connect/disconnect operations + private CCriticalSection connectLock = new CCriticalSection(); + + // private Timer for auto reconnect + private CTimer RetryTimer; + + /// + /// Constructor + /// + /// unique string to differentiate between instances + /// + /// + /// + public GenericTcpIpClient(string key, string address, int port, int bufferSize) + : base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + Hostname = address; + Port = port; + BufferSize = bufferSize; + + RetryTimer = new CTimer(o => + { + Reconnect(); + }, Timeout.Infinite); + } + + /// + /// Constructor + /// + /// + public GenericTcpIpClient(string key) + : base(key) + { + StreamDebugging = new CommunicationStreamDebugging(key); + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + BufferSize = 2000; + + RetryTimer = new CTimer(o => + { + Reconnect(); + }, Timeout.Infinite); + } + + /// + /// Default constructor for S+ + /// + public GenericTcpIpClient() + : base(SplusKey) + { + StreamDebugging = new CommunicationStreamDebugging(SplusKey); + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + BufferSize = 2000; + + RetryTimer = new CTimer(o => + { + Reconnect(); + }, Timeout.Infinite); + } + + /// + /// Just to help S+ set the key + /// + public void Initialize(string key) + { + Key = key; + } + + /// + /// Handles closing this up when the program shuts down + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + Debug.Console(1, this, "Program stopping. Closing connection"); + Deactivate(); + } + } + + /// + /// + /// + /// + public override bool Deactivate() + { + RetryTimer.Stop(); + RetryTimer.Dispose(); + if (_client != null) + { + _client.SocketStatusChange -= this.Client_SocketStatusChange; + DisconnectClient(); + } + return true; + } + + /// + /// Attempts to connect to the server + /// + public void Connect() + { + if (string.IsNullOrEmpty(Hostname)) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key); + return; + } + if (Port < 1 || Port > 65535) + { + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': Invalid port", Key); + return; + } + } + + try + { + connectLock.Enter(); + if (IsConnected) + { + Debug.Console(1, this, "Connection already connected. Exiting Connect()"); + } + else + { + //Stop retry timer if running + RetryTimer.Stop(); + _client = new TCPClient(Hostname, Port, BufferSize); + _client.SocketStatusChange -= Client_SocketStatusChange; + _client.SocketStatusChange += Client_SocketStatusChange; + DisconnectCalledByUser = false; + _client.ConnectToServerAsync(ConnectToServerCallback); + } + } + finally + { + connectLock.Leave(); + } + } + + private void Reconnect() + { + if (_client == null) + { + return; + } + try + { + connectLock.Enter(); + if (IsConnected || DisconnectCalledByUser == true) + { + Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()"); + } + else + { + Debug.Console(1, this, "Attempting reconnect now"); + _client.ConnectToServerAsync(ConnectToServerCallback); + } + } + finally + { + connectLock.Leave(); + } + } + + /// + /// Attempts to disconnect the client + /// + public void Disconnect() + { + try + { + connectLock.Enter(); + DisconnectCalledByUser = true; + + // Stop trying reconnects, if we are + RetryTimer.Stop(); + DisconnectClient(); + } + finally + { + connectLock.Leave(); + } + } + + /// + /// Does the actual disconnect business + /// + public void DisconnectClient() + { + if (_client != null) + { + Debug.Console(1, this, "Disconnecting client"); + if (IsConnected) + _client.DisconnectFromServer(); + } + } + + /// + /// Callback method for connection attempt + /// + /// + void ConnectToServerCallback(TCPClient c) + { + if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + { + Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus); + WaitAndTryReconnect(); + } + else + { + Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus); + } + } + + /// + /// Disconnects, waits and attemtps to connect again + /// + void WaitAndTryReconnect() + { + CrestronInvoke.BeginInvoke(o => + { + try + { + connectLock.Enter(); + if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null) + { + DisconnectClient(); + Debug.Console(1, this, "Attempting reconnect, status={0}", _client.ClientStatus); + RetryTimer.Reset(AutoReconnectIntervalMs); + } + } + finally + { + connectLock.Leave(); + } + }); + } + + /// + /// Recieves incoming data + /// + /// + /// + void Receive(TCPClient client, int numBytes) + { + if (client != null) + { + if (numBytes > 0) + { + var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); + var bytesHandler = BytesReceived; + if (bytesHandler != null) + { + if (StreamDebugging.RxStreamDebuggingIsEnabled) + { + Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length); + } + bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + } + var textHandler = TextReceived; + if (textHandler != null) + { + var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + + if (StreamDebugging.RxStreamDebuggingIsEnabled) + { + Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length); + } + + textHandler(this, new GenericCommMethodReceiveTextArgs(str)); + } + } + client.ReceiveDataAsync(Receive); + } + } + + /// + /// General send method + /// + public void SendText(string text) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + // Check debug level before processing byte array + if (StreamDebugging.TxStreamDebuggingIsEnabled) + Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text)); + if (_client != null) + _client.SendData(bytes, bytes.Length); + } + + /// + /// This is useful from console and...? + /// + public void SendEscapedText(string text) + { + var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s => + { + var hex = s.Groups[1].Value; + return ((char)Convert.ToByte(hex, 16)).ToString(); + }); + SendText(unescapedText); + } + + /// + /// Sends Bytes to the server + /// + /// + public void SendBytes(byte[] bytes) + { + if (StreamDebugging.TxStreamDebuggingIsEnabled) + Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes)); + if (_client != null) + _client.SendData(bytes, bytes.Length); + } + + /// + /// Socket Status Change Handler + /// + /// + /// + void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) + { + if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + { + Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); + WaitAndTryReconnect(); + } + else + { + Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText); + _client.ReceiveDataAsync(Receive); + } + + var handler = ConnectionChange; + if (handler != null) + ConnectionChange(this, new GenericSocketStatusChageEventArgs(this)); + } + } + + /// + /// Configuration properties for TCP/SSH Connections + /// + public class TcpSshPropertiesConfig + { + /// + /// Address to connect to + /// + [JsonProperty(Required = Required.Always)] + public string Address { get; set; } + + /// + /// Port to connect to + /// + [JsonProperty(Required = Required.Always)] + public int Port { get; set; } + + /// + /// Username credential + /// + public string Username { get; set; } + /// + /// Passord credential + /// + public string Password { get; set; } + + /// + /// Defaults to 32768 + /// + public int BufferSize { get; set; } + + /// + /// Defaults to true + /// + public bool AutoReconnect { get; set; } + + /// + /// Defaults to 5000ms + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// Default constructor + /// + public TcpSshPropertiesConfig() + { + BufferSize = 32768; + AutoReconnect = true; + AutoReconnectIntervalMs = 5000; + Username = ""; + Password = ""; + } + + } + } diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient_ForServer.cs b/src/Comm/GenericTcpIpClient_ForServer.cs similarity index 97% rename from Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient_ForServer.cs rename to src/Comm/GenericTcpIpClient_ForServer.cs index 1e31c6f..df277f0 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpClient_ForServer.cs +++ b/src/Comm/GenericTcpIpClient_ForServer.cs @@ -1,774 +1,774 @@ -/*PepperDash Technology Corp. -JAG -Copyright: 2017 ------------------------------------- -***Notice of Ownership and Copyright*** -The material in which this notice appears is the property of PepperDash Technology Corporation, -which claims copyright under the laws of the United States of America in the entire body of material -and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, -of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. -PepperDash Technology Corporation reserves all rights under applicable laws. ------------------------------------- */ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronSockets; - -namespace PepperDash.Core -{ - /// - /// Generic TCP/IP client for server - /// - public class GenericTcpIpClient_ForServer : Device, IAutoReconnect - { - /// - /// Band aid delegate for choked server - /// - internal delegate void ConnectionHasHungCallbackDelegate(); - - #region Events - - //public event EventHandler BytesReceived; - - /// - /// Notifies of text received - /// - public event EventHandler TextReceived; - - /// - /// Notifies of socket status change - /// - public event EventHandler ConnectionChange; - - - /// - /// This is something of a band-aid callback. If the client times out during the connection process, because the server - /// is stuck, this will fire. It is intended to be used by the Server class monitor client, to help - /// keep a watch on the server and reset it if necessary. - /// - internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback; - - /// - /// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require - /// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect. - /// - public event EventHandler ClientReadyForCommunications; - - #endregion - - #region Properties & Variables - - /// - /// Address of server - /// - public string Hostname { get; set; } - - /// - /// Port on server - /// - public int Port { get; set; } - - /// - /// S+ helper - /// - public ushort UPort - { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } - - /// - /// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class - /// - public bool SharedKeyRequired { get; set; } - - /// - /// S+ helper for requires shared key bool - /// - public ushort USharedKeyRequired - { - set - { - if (value == 1) - SharedKeyRequired = true; - else - SharedKeyRequired = false; - } - } - - /// - /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module - /// - public string SharedKey { get; set; } - - /// - /// flag to show the client is waiting for the server to send the shared key - /// - private bool WaitingForSharedKeyResponse { get; set; } - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// Semaphore on connect method - /// - bool IsTryingToConnect; - - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get - { - if (Client != null) - return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; - else - return false; - } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// Bool showing if socket is ready for communication after shared key exchange - /// - public bool IsReadyForCommunication { get; set; } - - /// - /// S+ helper for IsReadyForCommunication - /// - public ushort UIsReadyForCommunication - { - get { return (ushort)(IsReadyForCommunication ? 1 : 0); } - } - - /// - /// Client socket status Read only - /// - public SocketStatus ClientStatus - { - get - { - if (Client != null) - return Client.ClientStatus; - else - return SocketStatus.SOCKET_STATUS_NO_CONNECT; - } - } - - /// - /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event - /// and IsConnected would be true when this == 2. - /// - public ushort UStatus - { - get { return (ushort)ClientStatus; } - } - - /// - /// Status text shows the message associated with socket status - /// - public string ClientStatusText { get { return ClientStatus.ToString(); } } - - /// - /// bool to track if auto reconnect should be set on the socket - /// - public bool AutoReconnect { get; set; } - - /// - /// S+ helper for AutoReconnect - /// - public ushort UAutoReconnect - { - get { return (ushort)(AutoReconnect ? 1 : 0); } - set { AutoReconnect = value == 1; } - } - /// - /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 - /// - public int AutoReconnectIntervalMs { get; set; } - - /// - /// Flag Set only when the disconnect method is called. - /// - bool DisconnectCalledByUser; - - /// - /// private Timer for auto reconnect - /// - CTimer RetryTimer; - - - /// - /// - /// - public bool HeartbeatEnabled { get; set; } - - /// - /// - /// - public ushort UHeartbeatEnabled - { - get { return (ushort)(HeartbeatEnabled ? 1 : 0); } - set { HeartbeatEnabled = value == 1; } - } - - /// - /// - /// - public string HeartbeatString = "heartbeat"; - - /// - /// - /// - public int HeartbeatInterval = 50000; - - CTimer HeartbeatSendTimer; - CTimer HeartbeatAckTimer; - /// - /// Used to force disconnection on a dead connect attempt - /// - CTimer ConnectFailTimer; - CTimer WaitForSharedKey; - private int ConnectionCount; - /// - /// Internal secure client - /// - TCPClient Client; - - bool ProgramIsStopping; - - #endregion - - #region Constructors - - /// - /// Constructor - /// - /// - /// - /// - /// - public GenericTcpIpClient_ForServer(string key, string address, int port, int bufferSize) - : base(key) - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - Hostname = address; - Port = port; - BufferSize = bufferSize; - AutoReconnectIntervalMs = 5000; - - } - - /// - /// Constructor for S+ - /// - public GenericTcpIpClient_ForServer() - : base("Uninitialized DynamicTcpClient") - { - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - AutoReconnectIntervalMs = 5000; - BufferSize = 2000; - } - #endregion - - #region Methods - - /// - /// Just to help S+ set the key - /// - public void Initialize(string key) - { - Key = key; - } - - /// - /// Handles closing this up when the program shuts down - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection"); - ProgramIsStopping = true; - Disconnect(); - } - - } - - /// - /// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name. - /// - public void Connect() - { - ConnectionCount++; - Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount); - - - if (IsConnected) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring."); - return; - } - if (IsTryingToConnect) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring."); - return; - } - try - { - IsTryingToConnect = true; - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } - if (string.IsNullOrEmpty(Hostname)) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set"); - return; - } - if (Port < 1 || Port > 65535) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port"); - return; - } - if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set"); - return; - } - - // clean up previous client - if (Client != null) - { - Cleanup(); - } - DisconnectCalledByUser = false; - - Client = new TCPClient(Hostname, Port, BufferSize); - Client.SocketStatusChange += Client_SocketStatusChange; - if(HeartbeatEnabled) - Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); - Client.AddressClientConnectedTo = Hostname; - Client.PortNumber = Port; - // SecureClient = c; - - //var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff"); - - ConnectFailTimer = new CTimer(o => - { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount); - if (IsTryingToConnect) - { - IsTryingToConnect = false; - - //if (ConnectionHasHungCallback != null) - //{ - // ConnectionHasHungCallback(); - //} - //SecureClient.DisconnectFromServer(); - //CheckClosedAndTryReconnect(); - } - }, 30000); - - Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount); - Client.ConnectToServerAsync(o => - { - Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount); - - if (ConnectFailTimer != null) - { - ConnectFailTimer.Stop(); - } - IsTryingToConnect = false; - - if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - { - Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient); - o.ReceiveDataAsync(Receive); - - if (SharedKeyRequired) - { - WaitingForSharedKeyResponse = true; - WaitForSharedKey = new CTimer(timer => - { - - Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication); - // Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus); - // This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup - o.DisconnectFromServer(); - //CheckClosedAndTryReconnect(); - //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 - { - Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus); - CheckClosedAndTryReconnect(); - } - }); - } - catch (Exception ex) - { - Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message); - IsTryingToConnect = false; - CheckClosedAndTryReconnect(); - } - } - - /// - /// - /// - public void Disconnect() - { - Debug.Console(2, "Disconnect Called"); - - DisconnectCalledByUser = true; - if (IsConnected) - { - Client.DisconnectFromServer(); - - } - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } - Cleanup(); - } - - /// - /// Internal call to close up client. ALWAYS use this when disconnecting. - /// - void Cleanup() - { - IsTryingToConnect = false; - - if (Client != null) - { - //SecureClient.DisconnectFromServer(); - Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : ""); - Client.SocketStatusChange -= Client_SocketStatusChange; - Client.Dispose(); - Client = null; - } - if (ConnectFailTimer != null) - { - ConnectFailTimer.Stop(); - ConnectFailTimer.Dispose(); - ConnectFailTimer = null; - } - } - - - /// ff - /// Called from Connect failure or Socket Status change if - /// auto reconnect and socket disconnected (Not disconnected by user) - /// - void CheckClosedAndTryReconnect() - { - if (Client != null) - { - Debug.Console(2, this, "Cleaning up remotely closed/failed connection."); - Cleanup(); - } - if (!DisconnectCalledByUser && AutoReconnect) - { - var halfInterval = AutoReconnectIntervalMs / 2; - var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs; - Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime); - if (RetryTimer != null) - { - RetryTimer.Stop(); - RetryTimer = null; - } - RetryTimer = new CTimer(o => Connect(), rndTime); - } - } - - /// - /// Receive callback - /// - /// - /// - void Receive(TCPClient client, int numBytes) - { - if (numBytes > 0) - { - string str = string.Empty; - - try - { - var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); - str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); - Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str); - if (!string.IsNullOrEmpty(checkHeartbeat(str))) - { - if (SharedKeyRequired && str == "SharedKey:") - { - Debug.Console(2, this, "Server asking for shared key, sending"); - SendText(SharedKey + "\n"); - } - else if (SharedKeyRequired && str == "Shared Key Match") - { - StopWaitForSharedKeyTimer(); - Debug.Console(2, this, "Shared key confirmed. Ready for communication"); - OnClientReadyForcommunications(true); // Successful key exchange - } - else - { - //var bytesHandler = BytesReceived; - //if (bytesHandler != null) - // bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); - var textHandler = TextReceived; - if (textHandler != null) - textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str)); - } - } - } - catch (Exception ex) - { - Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str); - } - } - if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - client.ReceiveDataAsync(Receive); - } - - void HeartbeatStart() - { - if (HeartbeatEnabled) - { - Debug.Console(2, this, "Starting Heartbeat"); - if (HeartbeatSendTimer == null) - { - - HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval); - } - if (HeartbeatAckTimer == null) - { - HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); - } - } - - } - void HeartbeatStop() - { - - if (HeartbeatSendTimer != null) - { - Debug.Console(2, this, "Stoping Heartbeat Send"); - HeartbeatSendTimer.Stop(); - HeartbeatSendTimer = null; - } - if (HeartbeatAckTimer != null) - { - Debug.Console(2, this, "Stoping Heartbeat Ack"); - HeartbeatAckTimer.Stop(); - HeartbeatAckTimer = null; - } - - } - void SendHeartbeat(object notused) - { - this.SendText(HeartbeatString); - Debug.Console(2, this, "Sending Heartbeat"); - - } - - //private method to check heartbeat requirements and start or reset timer - string checkHeartbeat(string received) - { - try - { - if (HeartbeatEnabled) - { - if (!string.IsNullOrEmpty(HeartbeatString)) - { - var remainingText = received.Replace(HeartbeatString, ""); - var noDelimiter = received.Trim(new char[] { '\r', '\n' }); - if (noDelimiter.Contains(HeartbeatString)) - { - if (HeartbeatAckTimer != null) - { - HeartbeatAckTimer.Reset(HeartbeatInterval * 2); - } - else - { - HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); - } - Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString); - return remainingText; - } - } - } - } - catch (Exception ex) - { - Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); - } - return received; - } - - - - void HeartbeatAckTimerFail(object o) - { - try - { - - if (IsConnected) - { - Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE"); - SendText("Heartbeat not received by server, closing connection"); - CheckClosedAndTryReconnect(); - } - - } - catch (Exception ex) - { - ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex); - } - } - - /// - /// - /// - void StopWaitForSharedKeyTimer() - { - if (WaitForSharedKey != null) - { - WaitForSharedKey.Stop(); - WaitForSharedKey = null; - } - } - - /// - /// General send method - /// - public void SendText(string text) - { - if (!string.IsNullOrEmpty(text)) - { - try - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - { - Client.SendDataAsync(bytes, bytes.Length, (c, n) => - { - // HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING????? - if (n <= 0) - { - Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key); - } - }); - } - } - catch (Exception ex) - { - Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text); - } - } - } - - /// - /// - /// - public void SendBytes(byte[] bytes) - { - if (bytes.Length > 0) - { - try - { - if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) - Client.SendData(bytes, bytes.Length); - } - catch (Exception ex) - { - Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message); - } - } - } - - /// - /// SocketStatusChange Callback - /// - /// - /// - void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) - { - if (ProgramIsStopping) - { - ProgramIsStopping = false; - return; - } - try - { - Debug.Console(2, this, "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) - { - HeartbeatStop(); - OnClientReadyForcommunications(false); // socket has gone low - CheckClosedAndTryReconnect(); - } - } - catch (Exception ex) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException); - } - } - - /// - /// Helper for ConnectionChange event - /// - void OnConnectionChange() - { - var handler = ConnectionChange; - if (handler != null) - ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus)); - } - - /// - /// Helper to fire ClientReadyForCommunications event - /// - void OnClientReadyForcommunications(bool isReady) - { - IsReadyForCommunication = isReady; - if (this.IsReadyForCommunication) { HeartbeatStart(); } - var handler = ClientReadyForCommunications; - if (handler != null) - handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication)); - } - #endregion - } - +/*PepperDash Technology Corp. +JAG +Copyright: 2017 +------------------------------------ +***Notice of Ownership and Copyright*** +The material in which this notice appears is the property of PepperDash Technology Corporation, +which claims copyright under the laws of the United States of America in the entire body of material +and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, +of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. +PepperDash Technology Corporation reserves all rights under applicable laws. +------------------------------------ */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronSockets; + +namespace PepperDash.Core +{ + /// + /// Generic TCP/IP client for server + /// + public class GenericTcpIpClient_ForServer : Device, IAutoReconnect + { + /// + /// Band aid delegate for choked server + /// + internal delegate void ConnectionHasHungCallbackDelegate(); + + #region Events + + //public event EventHandler BytesReceived; + + /// + /// Notifies of text received + /// + public event EventHandler TextReceived; + + /// + /// Notifies of socket status change + /// + public event EventHandler ConnectionChange; + + + /// + /// This is something of a band-aid callback. If the client times out during the connection process, because the server + /// is stuck, this will fire. It is intended to be used by the Server class monitor client, to help + /// keep a watch on the server and reset it if necessary. + /// + internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback; + + /// + /// For a client with a pre shared key, this will fire after the communication is established and the key exchange is complete. If you require + /// a key and subscribe to the socket change event and try to send data on a connection the data sent will interfere with the key exchange and disconnect. + /// + public event EventHandler ClientReadyForCommunications; + + #endregion + + #region Properties & Variables + + /// + /// Address of server + /// + public string Hostname { get; set; } + + /// + /// Port on server + /// + public int Port { get; set; } + + /// + /// S+ helper + /// + public ushort UPort + { + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } + } + + /// + /// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class + /// + public bool SharedKeyRequired { get; set; } + + /// + /// S+ helper for requires shared key bool + /// + public ushort USharedKeyRequired + { + set + { + if (value == 1) + SharedKeyRequired = true; + else + SharedKeyRequired = false; + } + } + + /// + /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module + /// + public string SharedKey { get; set; } + + /// + /// flag to show the client is waiting for the server to send the shared key + /// + private bool WaitingForSharedKeyResponse { get; set; } + + /// + /// Defaults to 2000 + /// + public int BufferSize { get; set; } + + /// + /// Semaphore on connect method + /// + bool IsTryingToConnect; + + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get + { + if (Client != null) + return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; + else + return false; + } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// Bool showing if socket is ready for communication after shared key exchange + /// + public bool IsReadyForCommunication { get; set; } + + /// + /// S+ helper for IsReadyForCommunication + /// + public ushort UIsReadyForCommunication + { + get { return (ushort)(IsReadyForCommunication ? 1 : 0); } + } + + /// + /// Client socket status Read only + /// + public SocketStatus ClientStatus + { + get + { + if (Client != null) + return Client.ClientStatus; + else + return SocketStatus.SOCKET_STATUS_NO_CONNECT; + } + } + + /// + /// Contains the familiar Simpl analog status values. This drives the ConnectionChange event + /// and IsConnected would be true when this == 2. + /// + public ushort UStatus + { + get { return (ushort)ClientStatus; } + } + + /// + /// Status text shows the message associated with socket status + /// + public string ClientStatusText { get { return ClientStatus.ToString(); } } + + /// + /// bool to track if auto reconnect should be set on the socket + /// + public bool AutoReconnect { get; set; } + + /// + /// S+ helper for AutoReconnect + /// + public ushort UAutoReconnect + { + get { return (ushort)(AutoReconnect ? 1 : 0); } + set { AutoReconnect = value == 1; } + } + /// + /// Milliseconds to wait before attempting to reconnect. Defaults to 5000 + /// + public int AutoReconnectIntervalMs { get; set; } + + /// + /// Flag Set only when the disconnect method is called. + /// + bool DisconnectCalledByUser; + + /// + /// private Timer for auto reconnect + /// + CTimer RetryTimer; + + + /// + /// + /// + public bool HeartbeatEnabled { get; set; } + + /// + /// + /// + public ushort UHeartbeatEnabled + { + get { return (ushort)(HeartbeatEnabled ? 1 : 0); } + set { HeartbeatEnabled = value == 1; } + } + + /// + /// + /// + public string HeartbeatString = "heartbeat"; + + /// + /// + /// + public int HeartbeatInterval = 50000; + + CTimer HeartbeatSendTimer; + CTimer HeartbeatAckTimer; + /// + /// Used to force disconnection on a dead connect attempt + /// + CTimer ConnectFailTimer; + CTimer WaitForSharedKey; + private int ConnectionCount; + /// + /// Internal secure client + /// + TCPClient Client; + + bool ProgramIsStopping; + + #endregion + + #region Constructors + + /// + /// Constructor + /// + /// + /// + /// + /// + public GenericTcpIpClient_ForServer(string key, string address, int port, int bufferSize) + : base(key) + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + Hostname = address; + Port = port; + BufferSize = bufferSize; + AutoReconnectIntervalMs = 5000; + + } + + /// + /// Constructor for S+ + /// + public GenericTcpIpClient_ForServer() + : base("Uninitialized DynamicTcpClient") + { + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + AutoReconnectIntervalMs = 5000; + BufferSize = 2000; + } + #endregion + + #region Methods + + /// + /// Just to help S+ set the key + /// + public void Initialize(string key) + { + Key = key; + } + + /// + /// Handles closing this up when the program shuts down + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection"); + ProgramIsStopping = true; + Disconnect(); + } + + } + + /// + /// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name. + /// + public void Connect() + { + ConnectionCount++; + Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount); + + + if (IsConnected) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring."); + return; + } + if (IsTryingToConnect) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring."); + return; + } + try + { + IsTryingToConnect = true; + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + if (string.IsNullOrEmpty(Hostname)) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set"); + return; + } + if (Port < 1 || Port > 65535) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port"); + return; + } + if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set"); + return; + } + + // clean up previous client + if (Client != null) + { + Cleanup(); + } + DisconnectCalledByUser = false; + + Client = new TCPClient(Hostname, Port, BufferSize); + Client.SocketStatusChange += Client_SocketStatusChange; + if(HeartbeatEnabled) + Client.SocketSendOrReceiveTimeOutInMs = (HeartbeatInterval * 5); + Client.AddressClientConnectedTo = Hostname; + Client.PortNumber = Port; + // SecureClient = c; + + //var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff"); + + ConnectFailTimer = new CTimer(o => + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount); + if (IsTryingToConnect) + { + IsTryingToConnect = false; + + //if (ConnectionHasHungCallback != null) + //{ + // ConnectionHasHungCallback(); + //} + //SecureClient.DisconnectFromServer(); + //CheckClosedAndTryReconnect(); + } + }, 30000); + + Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount); + Client.ConnectToServerAsync(o => + { + Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount); + + if (ConnectFailTimer != null) + { + ConnectFailTimer.Stop(); + } + IsTryingToConnect = false; + + if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + { + Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient); + o.ReceiveDataAsync(Receive); + + if (SharedKeyRequired) + { + WaitingForSharedKeyResponse = true; + WaitForSharedKey = new CTimer(timer => + { + + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication); + // Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus); + // This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup + o.DisconnectFromServer(); + //CheckClosedAndTryReconnect(); + //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 + { + Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus); + CheckClosedAndTryReconnect(); + } + }); + } + catch (Exception ex) + { + Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message); + IsTryingToConnect = false; + CheckClosedAndTryReconnect(); + } + } + + /// + /// + /// + public void Disconnect() + { + Debug.Console(2, "Disconnect Called"); + + DisconnectCalledByUser = true; + if (IsConnected) + { + Client.DisconnectFromServer(); + + } + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + Cleanup(); + } + + /// + /// Internal call to close up client. ALWAYS use this when disconnecting. + /// + void Cleanup() + { + IsTryingToConnect = false; + + if (Client != null) + { + //SecureClient.DisconnectFromServer(); + Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : ""); + Client.SocketStatusChange -= Client_SocketStatusChange; + Client.Dispose(); + Client = null; + } + if (ConnectFailTimer != null) + { + ConnectFailTimer.Stop(); + ConnectFailTimer.Dispose(); + ConnectFailTimer = null; + } + } + + + /// ff + /// Called from Connect failure or Socket Status change if + /// auto reconnect and socket disconnected (Not disconnected by user) + /// + void CheckClosedAndTryReconnect() + { + if (Client != null) + { + Debug.Console(2, this, "Cleaning up remotely closed/failed connection."); + Cleanup(); + } + if (!DisconnectCalledByUser && AutoReconnect) + { + var halfInterval = AutoReconnectIntervalMs / 2; + var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs; + Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime); + if (RetryTimer != null) + { + RetryTimer.Stop(); + RetryTimer = null; + } + RetryTimer = new CTimer(o => Connect(), rndTime); + } + } + + /// + /// Receive callback + /// + /// + /// + void Receive(TCPClient client, int numBytes) + { + if (numBytes > 0) + { + string str = string.Empty; + + try + { + var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray(); + str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); + Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str); + if (!string.IsNullOrEmpty(checkHeartbeat(str))) + { + if (SharedKeyRequired && str == "SharedKey:") + { + Debug.Console(2, this, "Server asking for shared key, sending"); + SendText(SharedKey + "\n"); + } + else if (SharedKeyRequired && str == "Shared Key Match") + { + StopWaitForSharedKeyTimer(); + Debug.Console(2, this, "Shared key confirmed. Ready for communication"); + OnClientReadyForcommunications(true); // Successful key exchange + } + else + { + //var bytesHandler = BytesReceived; + //if (bytesHandler != null) + // bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes)); + var textHandler = TextReceived; + if (textHandler != null) + textHandler(this, new GenericTcpServerCommMethodReceiveTextArgs(str)); + } + } + } + catch (Exception ex) + { + Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str); + } + } + if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + client.ReceiveDataAsync(Receive); + } + + void HeartbeatStart() + { + if (HeartbeatEnabled) + { + Debug.Console(2, this, "Starting Heartbeat"); + if (HeartbeatSendTimer == null) + { + + HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval); + } + if (HeartbeatAckTimer == null) + { + HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); + } + } + + } + void HeartbeatStop() + { + + if (HeartbeatSendTimer != null) + { + Debug.Console(2, this, "Stoping Heartbeat Send"); + HeartbeatSendTimer.Stop(); + HeartbeatSendTimer = null; + } + if (HeartbeatAckTimer != null) + { + Debug.Console(2, this, "Stoping Heartbeat Ack"); + HeartbeatAckTimer.Stop(); + HeartbeatAckTimer = null; + } + + } + void SendHeartbeat(object notused) + { + this.SendText(HeartbeatString); + Debug.Console(2, this, "Sending Heartbeat"); + + } + + //private method to check heartbeat requirements and start or reset timer + string checkHeartbeat(string received) + { + try + { + if (HeartbeatEnabled) + { + if (!string.IsNullOrEmpty(HeartbeatString)) + { + var remainingText = received.Replace(HeartbeatString, ""); + var noDelimiter = received.Trim(new char[] { '\r', '\n' }); + if (noDelimiter.Contains(HeartbeatString)) + { + if (HeartbeatAckTimer != null) + { + HeartbeatAckTimer.Reset(HeartbeatInterval * 2); + } + else + { + HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2)); + } + Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString); + return remainingText; + } + } + } + } + catch (Exception ex) + { + Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); + } + return received; + } + + + + void HeartbeatAckTimerFail(object o) + { + try + { + + if (IsConnected) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE"); + SendText("Heartbeat not received by server, closing connection"); + CheckClosedAndTryReconnect(); + } + + } + catch (Exception ex) + { + ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex); + } + } + + /// + /// + /// + void StopWaitForSharedKeyTimer() + { + if (WaitForSharedKey != null) + { + WaitForSharedKey.Stop(); + WaitForSharedKey = null; + } + } + + /// + /// General send method + /// + public void SendText(string text) + { + if (!string.IsNullOrEmpty(text)) + { + try + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + { + Client.SendDataAsync(bytes, bytes.Length, (c, n) => + { + // HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING????? + if (n <= 0) + { + Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key); + } + }); + } + } + catch (Exception ex) + { + Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text); + } + } + } + + /// + /// + /// + public void SendBytes(byte[] bytes) + { + if (bytes.Length > 0) + { + try + { + if (Client != null && Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) + Client.SendData(bytes, bytes.Length); + } + catch (Exception ex) + { + Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message); + } + } + } + + /// + /// SocketStatusChange Callback + /// + /// + /// + void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus) + { + if (ProgramIsStopping) + { + ProgramIsStopping = false; + return; + } + try + { + Debug.Console(2, this, "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) + { + HeartbeatStop(); + OnClientReadyForcommunications(false); // socket has gone low + CheckClosedAndTryReconnect(); + } + } + catch (Exception ex) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException); + } + } + + /// + /// Helper for ConnectionChange event + /// + void OnConnectionChange() + { + var handler = ConnectionChange; + if (handler != null) + ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus)); + } + + /// + /// Helper to fire ClientReadyForCommunications event + /// + void OnClientReadyForcommunications(bool isReady) + { + IsReadyForCommunication = isReady; + if (this.IsReadyForCommunication) { HeartbeatStart(); } + var handler = ClientReadyForCommunications; + if (handler != null) + handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication)); + } + #endregion + } + } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpServer.cs b/src/Comm/GenericTcpIpServer.cs similarity index 97% rename from Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpServer.cs rename to src/Comm/GenericTcpIpServer.cs index e3a00f2..7351657 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/GenericTcpIpServer.cs +++ b/src/Comm/GenericTcpIpServer.cs @@ -1,1012 +1,1012 @@ -/*PepperDash Technology Corp. -JAG -Copyright: 2017 ------------------------------------- -***Notice of Ownership and Copyright*** -The material in which this notice appears is the property of PepperDash Technology Corporation, -which claims copyright under the laws of the United States of America in the entire body of material -and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, -of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. -PepperDash Technology Corporation reserves all rights under applicable laws. ------------------------------------- */ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronSockets; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace PepperDash.Core -{ - /// - /// Generic TCP/IP server device - /// - public class GenericTcpIpServer : Device - { - #region Events - /// - /// Event for Receiving text - /// - public event EventHandler TextReceived; - - /// - /// Event for client connection socket status change - /// - public event EventHandler ClientConnectionChange; - - /// - /// Event for Server State Change - /// - public event EventHandler ServerStateChange; - - /// - /// For a server with a pre shared key, this will fire after the communication is established and the key exchange is complete. If no shared key, this will fire - /// after connection is successful. Use this event to know when the client is ready for communication to avoid stepping on shared key. - /// - public event EventHandler ServerClientReadyForCommunications; - - /// - /// A band aid event to notify user that the server has choked. - /// - public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; } - - /// - /// - /// - public delegate void ServerHasChokedCallbackDelegate(); - - #endregion - - #region Properties/Variables - - /// - /// - /// - CCriticalSection ServerCCSection = new CCriticalSection(); - - - /// - /// A bandaid client that monitors whether the server is reachable - /// - GenericTcpIpClient_ForServer MonitorClient; - - /// - /// Timer to operate the bandaid monitor client in a loop. - /// - CTimer MonitorClientTimer; - - /// - /// - /// - int MonitorClientFailureCount; - - /// - /// 3 by default - /// - public int MonitorClientMaxFailureCount { get; set; } - - /// - /// Text representation of the Socket Status enum values for the server - /// - public string Status - { - get - { - if (myTcpServer != null) - return myTcpServer.State.ToString(); - return ServerState.SERVER_NOT_LISTENING.ToString(); - - } - - } - - /// - /// Bool showing if socket is connected - /// - public bool IsConnected - { - get - { - if (myTcpServer != null) - return (myTcpServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED; - return false; - - //return (Secure ? SecureServer != null : UnsecureServer != null) && - //(Secure ? (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED : - // (UnsecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED); - } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsConnected - { - get { return (ushort)(IsConnected ? 1 : 0); } - } - - /// - /// Bool showing if socket is connected - /// - public bool IsListening - { - get - { - if (myTcpServer != null) - return (myTcpServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING; - else - return false; - //return (Secure ? SecureServer != null : UnsecureServer != null) && - //(Secure ? (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING : - // (UnsecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING); - } - } - - /// - /// S+ helper for IsConnected - /// - public ushort UIsListening - { - get { return (ushort)(IsListening ? 1 : 0); } - } - - /// - /// The maximum number of clients. - /// Should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable - /// - public ushort MaxClients { get; set; } - - /// - /// Number of clients currently connected. - /// - public ushort NumberOfClientsConnected - { - get - { - if (myTcpServer != null) - return (ushort)myTcpServer.NumberOfClientsConnected; - return 0; - } - } - - /// - /// Port Server should listen on - /// - public int Port { get; set; } - - /// - /// S+ helper for Port - /// - public ushort UPort - { - get { return Convert.ToUInt16(Port); } - set { Port = Convert.ToInt32(value); } - } - - /// - /// Bool to show whether the server requires a preshared key. Must be set the same in the client, and if true shared keys must be identical on server/client - /// - public bool SharedKeyRequired { get; set; } - - /// - /// S+ helper for requires shared key bool - /// - public ushort USharedKeyRequired - { - set - { - if (value == 1) - SharedKeyRequired = true; - else - SharedKeyRequired = false; - } - } - - /// - /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module. - /// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called - /// - public string SharedKey { get; set; } - - /// - /// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received - /// - public bool HeartbeatRequired { get; set; } - - /// - /// S+ Helper for Heartbeat Required - /// - public ushort UHeartbeatRequired - { - set - { - if (value == 1) - HeartbeatRequired = true; - else - HeartbeatRequired = false; - } - } - - /// - /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ - /// - public int HeartbeatRequiredIntervalMs { get; set; } - - /// - /// Simpl+ Heartbeat Analog value in seconds - /// - public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } } - - /// - /// String to Match for heartbeat. If null or empty any string will reset heartbeat timer - /// - public string HeartbeatStringToMatch { get; set; } - - //private timers for Heartbeats per client - Dictionary HeartbeatTimerDictionary = new Dictionary(); - - //flags to show the secure server is waiting for client at index to send the shared key - List WaitingForSharedKey = new List(); - - List ClientReadyAfterKeyExchange = new List(); - - /// - /// The connected client indexes - /// - public List ConnectedClientsIndexes = new List(); - - /// - /// Defaults to 2000 - /// - public int BufferSize { get; set; } - - /// - /// Private flag to note that the server has stopped intentionally - /// - private bool ServerStopped { get; set; } - - //Servers - TCPServer myTcpServer; - - /// - /// - /// - bool ProgramIsStopping; - - #endregion - - #region Constructors - /// - /// 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 GenericTcpIpServer() - : base("Uninitialized Dynamic TCP Server") - { - HeartbeatRequiredIntervalInSeconds = 15; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - MonitorClientMaxFailureCount = 3; - } - - /// - /// constructor with debug key set at instantiation. Make sure to set all properties before listening. - /// - /// - public GenericTcpIpServer(string key) - : base("Uninitialized Dynamic TCP Server") - { - HeartbeatRequiredIntervalInSeconds = 15; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - MonitorClientMaxFailureCount = 3; - Key = key; - } - - /// - /// Contstructor that sets all properties by calling the initialize method with a config object. - /// - /// - public GenericTcpIpServer(TcpServerConfigObject serverConfigObject) - : base("Uninitialized Dynamic TCP Server") - { - HeartbeatRequiredIntervalInSeconds = 15; - CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); - BufferSize = 2000; - MonitorClientMaxFailureCount = 3; - Initialize(serverConfigObject); - } - #endregion - - #region Methods - Server Actions - /// - /// Disconnects all clients and stops the server - /// - public void KillServer() - { - ServerStopped = true; - if (MonitorClient != null) - { - MonitorClient.Disconnect(); - } - DisconnectAllClientsForShutdown(); - StopListening(); - } - - /// - /// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+ - /// - /// - public void Initialize(string key) - { - Key = key; - } - - /// - /// Initialze with server configuration object - /// - /// - public void Initialize(TcpServerConfigObject serverConfigObject) - { - try - { - if (serverConfigObject != null || string.IsNullOrEmpty(serverConfigObject.Key)) - { - Key = serverConfigObject.Key; - MaxClients = serverConfigObject.MaxClients; - Port = serverConfigObject.Port; - SharedKeyRequired = serverConfigObject.SharedKeyRequired; - SharedKey = serverConfigObject.SharedKey; - HeartbeatRequired = serverConfigObject.HeartbeatRequired; - HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds; - HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch; - BufferSize = serverConfigObject.BufferSize; - - } - else - { - ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); - } - } - catch - { - ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); - } - } - - /// - /// Start listening on the specified port - /// - public void Listen() - { - ServerCCSection.Enter(); - try - { - if (Port < 1 || Port > 65535) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key); - ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key)); - return; - } - if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key); - ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key)); - return; - } - if (IsListening) - return; - - if (myTcpServer == null) - { - myTcpServer = new TCPServer(Port, MaxClients); - if(HeartbeatRequired) - myTcpServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5); - - // myTcpServer.HandshakeTimeout = 30; - } - else - { - KillServer(); - myTcpServer.PortNumber = Port; - } - - myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange; - myTcpServer.SocketStatusChange += TcpServer_SocketStatusChange; - - ServerStopped = false; - myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); - OnServerStateChange(myTcpServer.State); - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus); - - // StartMonitorClient(); - - - ServerCCSection.Leave(); - } - catch (Exception ex) - { - ServerCCSection.Leave(); - ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key); - } - } - - /// - /// Stop Listening - /// - public void StopListening() - { - try - { - Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener"); - if (myTcpServer != null) - { - myTcpServer.Stop(); - Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", myTcpServer.State); - OnServerStateChange(myTcpServer.State); - } - ServerStopped = true; - } - catch (Exception ex) - { - Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex); - } - } - - /// - /// Disconnects Client - /// - /// - public void DisconnectClient(uint client) - { - try - { - myTcpServer.Disconnect(client); - Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client); - } - catch (Exception ex) - { - Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex); - } - } - /// - /// Disconnect All Clients - /// - public void DisconnectAllClientsForShutdown() - { - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients"); - if (myTcpServer != null) - { - myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange; - foreach (var index in ConnectedClientsIndexes.ToList()) // copy it here so that it iterates properly - { - var i = index; - if (!myTcpServer.ClientConnected(index)) - continue; - try - { - myTcpServer.Disconnect(i); - Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i); - } - catch (Exception ex) - { - Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex); - } - } - Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", myTcpServer.ServerSocketStatus); - } - - Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients"); - ConnectedClientsIndexes.Clear(); - - if (!ProgramIsStopping) - { - OnConnectionChange(); - OnServerStateChange(myTcpServer.State); //State shows both listening and connected - } - - // var o = new { }; - } - - /// - /// Broadcast text from server to all connected clients - /// - /// - public void BroadcastText(string text) - { - CCriticalSection CCBroadcast = new CCriticalSection(); - CCBroadcast.Enter(); - try - { - if (ConnectedClientsIndexes.Count > 0) - { - byte[] b = Encoding.GetEncoding(28591).GetBytes(text); - foreach (uint i in ConnectedClientsIndexes) - { - if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(i))) - { - SocketErrorCodes error = myTcpServer.SendDataAsync(i, b, b.Length, (x, y, z) => { }); - if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING) - Debug.Console(2, error.ToString()); - } - } - } - CCBroadcast.Leave(); - } - catch (Exception ex) - { - CCBroadcast.Leave(); - Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message); - } - } - - /// - /// Not sure this is useful in library, maybe Pro?? - /// - /// - /// - public void SendTextToClient(string text, uint clientIndex) - { - try - { - byte[] b = Encoding.GetEncoding(28591).GetBytes(text); - if (myTcpServer != null && myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) - { - if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) - myTcpServer.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); - } - } - catch (Exception ex) - { - Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text); - } - } - - //private method to check heartbeat requirements and start or reset timer - string checkHeartbeat(uint clientIndex, string received) - { - try - { - if (HeartbeatRequired) - { - if (!string.IsNullOrEmpty(HeartbeatStringToMatch)) - { - var remainingText = received.Replace(HeartbeatStringToMatch, ""); - var noDelimiter = received.Trim(new char[] { '\r', '\n' }); - if (noDelimiter.Contains(HeartbeatStringToMatch)) - { - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) - HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); - else - { - CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); - HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); - } - Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex); - // Return Heartbeat - SendTextToClient(HeartbeatStringToMatch, clientIndex); - return remainingText; - } - } - else - { - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) - HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); - else - { - CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); - HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); - } - Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex); - } - } - } - catch (Exception ex) - { - Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); - } - return received; - } - - /// - /// Gets the IP address based on the client index - /// - /// - /// IP address of the client - public string GetClientIPAddress(uint clientIndex) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex); - if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) - { - var ipa = this.myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa); - return ipa; - - } - else - { - return ""; - } - } - - #endregion - - #region Methods - HeartbeatTimer Callback - - void HeartbeatTimer_CallbackFunction(object o) - { - uint clientIndex = 99999; - string address = string.Empty; - try - { - clientIndex = (uint)o; - address = myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); - - Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}", - address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex); - - if (myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) - SendTextToClient("Heartbeat not received by server, closing connection", clientIndex); - - var discoResult = myTcpServer.Disconnect(clientIndex); - //Debug.Console(1, this, "{0}", discoResult); - - if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) - { - HeartbeatTimerDictionary[clientIndex].Stop(); - HeartbeatTimerDictionary[clientIndex].Dispose(); - HeartbeatTimerDictionary.Remove(clientIndex); - } - } - catch (Exception ex) - { - ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key); - } - } - - #endregion - - #region Methods - Socket Status Changed Callbacks - /// - /// Secure Server Socket Status Changed Callback - /// - /// - /// - /// - void TcpServer_SocketStatusChange(TCPServer server, uint clientIndex, SocketStatus serverSocketStatus) - { - try - { - - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); - if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) - { - if (ConnectedClientsIndexes.Contains(clientIndex)) - ConnectedClientsIndexes.Remove(clientIndex); - if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) - { - HeartbeatTimerDictionary[clientIndex].Stop(); - HeartbeatTimerDictionary[clientIndex].Dispose(); - HeartbeatTimerDictionary.Remove(clientIndex); - } - if (ClientReadyAfterKeyExchange.Contains(clientIndex)) - ClientReadyAfterKeyExchange.Remove(clientIndex); - if (WaitingForSharedKey.Contains(clientIndex)) - WaitingForSharedKey.Remove(clientIndex); - } - } - catch (Exception ex) - { - Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex); - } - onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); - } - - #endregion - - #region Methods Connected Callbacks - /// - /// Secure TCP Client Connected to Secure Server Callback - /// - /// - /// - void TcpConnectCallback(TCPServer server, uint clientIndex) - { - try - { - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}", - server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), - clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); - if (clientIndex != 0) - { - if (server.ClientConnected(clientIndex)) - { - - if (!ConnectedClientsIndexes.Contains(clientIndex)) - { - ConnectedClientsIndexes.Add(clientIndex); - } - if (SharedKeyRequired) - { - if (!WaitingForSharedKey.Contains(clientIndex)) - { - WaitingForSharedKey.Add(clientIndex); - } - byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:"); - server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); - } - else - { - OnServerClientReadyForCommunications(clientIndex); - } - if (HeartbeatRequired) - { - if (!HeartbeatTimerDictionary.ContainsKey(clientIndex)) - { - HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs)); - } - } - - server.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback); - } - } - else - { - Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty."); - if (!ServerStopped) - { - server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); - return; - } - } - } - catch (Exception 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, - // MaxClients, - // ServerStopped); - if ((server.State & ServerState.SERVER_LISTENING) != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Waiting for next connection"); - server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); - - } - } - - #endregion - - #region Methods - Send/Receive Callbacks - /// - /// Secure Received Data Async Callback - /// - /// - /// - /// - void TcpServerReceivedDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesReceived) - { - 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 - - #region Methods - EventHelpers/Callbacks - - //Private Helper method to call the Connection Change Event - void onConnectionChange(uint clientIndex, SocketStatus clientStatus) - { - if (clientIndex != 0) //0 is error not valid client change - { - var handler = ClientConnectionChange; - if (handler != null) - { - handler(this, new GenericTcpServerSocketStatusChangeEventArgs(myTcpServer, clientIndex, clientStatus)); - } - } - } - - //Private Helper method to call the Connection Change Event - void OnConnectionChange() - { - if (ProgramIsStopping) - { - return; - } - var handler = ClientConnectionChange; - if (handler != null) - { - handler(this, new GenericTcpServerSocketStatusChangeEventArgs()); - } - } - - //Private Helper Method to call the Text Received Event - void onTextReceived(string text, uint clientIndex) - { - var handler = TextReceived; - if (handler != null) - handler(this, new GenericTcpServerCommMethodReceiveTextArgs(text, clientIndex)); - } - - //Private Helper Method to call the Server State Change Event - void OnServerStateChange(ServerState state) - { - if (ProgramIsStopping) - { - return; - } - var handler = ServerStateChange; - if (handler != null) - { - handler(this, new GenericTcpServerStateChangedEventArgs(state)); - } - } - - /// - /// Private Event Handler method to handle the closing of connections when the program stops - /// - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType == eProgramStatusEventType.Stopping) - { - ProgramIsStopping = true; - // kill bandaid things - if (MonitorClientTimer != null) - MonitorClientTimer.Stop(); - if (MonitorClient != null) - MonitorClient.Disconnect(); - - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server"); - KillServer(); - } - } - - //Private event handler method to raise the event that the server is ready to send data after a successful client shared key negotiation - void OnServerClientReadyForCommunications(uint clientIndex) - { - ClientReadyAfterKeyExchange.Add(clientIndex); - var handler = ServerClientReadyForCommunications; - if (handler != null) - handler(this, new GenericTcpServerSocketStatusChangeEventArgs( - this, clientIndex, myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex))); - } - #endregion - - #region Monitor Client - /// - /// Starts the monitor client cycle. Timed wait, then call RunMonitorClient - /// - void StartMonitorClient() - { - if (MonitorClientTimer != null) - { - return; - } - MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000); - } - - /// - /// - /// - void RunMonitorClient() - { - MonitorClient = new GenericTcpIpClient_ForServer(Key + "-MONITOR", "127.0.0.1", Port, 2000); - MonitorClient.SharedKeyRequired = this.SharedKeyRequired; - MonitorClient.SharedKey = this.SharedKey; - MonitorClient.ConnectionHasHungCallback = MonitorClientHasHungCallback; - //MonitorClient.ConnectionChange += MonitorClient_ConnectionChange; - MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm; - - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check"); - - MonitorClient.Connect(); - // From here MonitorCLient either connects or hangs, MonitorClient will call back - - } - - /// - /// - /// - void StopMonitorClient() - { - if (MonitorClient == null) - return; - - MonitorClient.ClientReadyForCommunications -= MonitorClient_IsReadyForComm; - MonitorClient.Disconnect(); - MonitorClient = null; - } - - /// - /// On monitor connect, restart the operation - /// - void MonitorClient_IsReadyForComm(object sender, GenericTcpServerClientReadyForcommunicationsEventArgs args) - { - if (args.IsReady) - { - Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s"); - MonitorClientTimer.Stop(); - MonitorClientTimer = null; - MonitorClientFailureCount = 0; - CrestronEnvironment.Sleep(2000); - StopMonitorClient(); - StartMonitorClient(); - } - } - - /// - /// If the client hangs, add to counter and maybe fire the choke event - /// - void MonitorClientHasHungCallback() - { - MonitorClientFailureCount++; - MonitorClientTimer.Stop(); - MonitorClientTimer = null; - StopMonitorClient(); - if (MonitorClientFailureCount < MonitorClientMaxFailureCount) - { - 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(2, this, Debug.ErrorLogLevel.Error, - "\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************", - MonitorClientMaxFailureCount); - - var handler = ServerHasChoked; - if (handler != null) - handler(); - // Some external thing is in charge here. Expected reset of program - } - } - #endregion - } +/*PepperDash Technology Corp. +JAG +Copyright: 2017 +------------------------------------ +***Notice of Ownership and Copyright*** +The material in which this notice appears is the property of PepperDash Technology Corporation, +which claims copyright under the laws of the United States of America in the entire body of material +and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part, +of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited. +PepperDash Technology Corporation reserves all rights under applicable laws. +------------------------------------ */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronSockets; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PepperDash.Core +{ + /// + /// Generic TCP/IP server device + /// + public class GenericTcpIpServer : Device + { + #region Events + /// + /// Event for Receiving text + /// + public event EventHandler TextReceived; + + /// + /// Event for client connection socket status change + /// + public event EventHandler ClientConnectionChange; + + /// + /// Event for Server State Change + /// + public event EventHandler ServerStateChange; + + /// + /// For a server with a pre shared key, this will fire after the communication is established and the key exchange is complete. If no shared key, this will fire + /// after connection is successful. Use this event to know when the client is ready for communication to avoid stepping on shared key. + /// + public event EventHandler ServerClientReadyForCommunications; + + /// + /// A band aid event to notify user that the server has choked. + /// + public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; } + + /// + /// + /// + public delegate void ServerHasChokedCallbackDelegate(); + + #endregion + + #region Properties/Variables + + /// + /// + /// + CCriticalSection ServerCCSection = new CCriticalSection(); + + + /// + /// A bandaid client that monitors whether the server is reachable + /// + GenericTcpIpClient_ForServer MonitorClient; + + /// + /// Timer to operate the bandaid monitor client in a loop. + /// + CTimer MonitorClientTimer; + + /// + /// + /// + int MonitorClientFailureCount; + + /// + /// 3 by default + /// + public int MonitorClientMaxFailureCount { get; set; } + + /// + /// Text representation of the Socket Status enum values for the server + /// + public string Status + { + get + { + if (myTcpServer != null) + return myTcpServer.State.ToString(); + return ServerState.SERVER_NOT_LISTENING.ToString(); + + } + + } + + /// + /// Bool showing if socket is connected + /// + public bool IsConnected + { + get + { + if (myTcpServer != null) + return (myTcpServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED; + return false; + + //return (Secure ? SecureServer != null : UnsecureServer != null) && + //(Secure ? (SecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED : + // (UnsecureServer.State & ServerState.SERVER_CONNECTED) == ServerState.SERVER_CONNECTED); + } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsConnected + { + get { return (ushort)(IsConnected ? 1 : 0); } + } + + /// + /// Bool showing if socket is connected + /// + public bool IsListening + { + get + { + if (myTcpServer != null) + return (myTcpServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING; + else + return false; + //return (Secure ? SecureServer != null : UnsecureServer != null) && + //(Secure ? (SecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING : + // (UnsecureServer.State & ServerState.SERVER_LISTENING) == ServerState.SERVER_LISTENING); + } + } + + /// + /// S+ helper for IsConnected + /// + public ushort UIsListening + { + get { return (ushort)(IsListening ? 1 : 0); } + } + + /// + /// The maximum number of clients. + /// Should be set by parameter in SIMPL+ in the MAIN method, Should not ever need to be configurable + /// + public ushort MaxClients { get; set; } + + /// + /// Number of clients currently connected. + /// + public ushort NumberOfClientsConnected + { + get + { + if (myTcpServer != null) + return (ushort)myTcpServer.NumberOfClientsConnected; + return 0; + } + } + + /// + /// Port Server should listen on + /// + public int Port { get; set; } + + /// + /// S+ helper for Port + /// + public ushort UPort + { + get { return Convert.ToUInt16(Port); } + set { Port = Convert.ToInt32(value); } + } + + /// + /// Bool to show whether the server requires a preshared key. Must be set the same in the client, and if true shared keys must be identical on server/client + /// + public bool SharedKeyRequired { get; set; } + + /// + /// S+ helper for requires shared key bool + /// + public ushort USharedKeyRequired + { + set + { + if (value == 1) + SharedKeyRequired = true; + else + SharedKeyRequired = false; + } + } + + /// + /// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module. + /// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called + /// + public string SharedKey { get; set; } + + /// + /// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received + /// + public bool HeartbeatRequired { get; set; } + + /// + /// S+ Helper for Heartbeat Required + /// + public ushort UHeartbeatRequired + { + set + { + if (value == 1) + HeartbeatRequired = true; + else + HeartbeatRequired = false; + } + } + + /// + /// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+ + /// + public int HeartbeatRequiredIntervalMs { get; set; } + + /// + /// Simpl+ Heartbeat Analog value in seconds + /// + public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } } + + /// + /// String to Match for heartbeat. If null or empty any string will reset heartbeat timer + /// + public string HeartbeatStringToMatch { get; set; } + + //private timers for Heartbeats per client + Dictionary HeartbeatTimerDictionary = new Dictionary(); + + //flags to show the secure server is waiting for client at index to send the shared key + List WaitingForSharedKey = new List(); + + List ClientReadyAfterKeyExchange = new List(); + + /// + /// The connected client indexes + /// + public List ConnectedClientsIndexes = new List(); + + /// + /// Defaults to 2000 + /// + public int BufferSize { get; set; } + + /// + /// Private flag to note that the server has stopped intentionally + /// + private bool ServerStopped { get; set; } + + //Servers + TCPServer myTcpServer; + + /// + /// + /// + bool ProgramIsStopping; + + #endregion + + #region Constructors + /// + /// 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 GenericTcpIpServer() + : base("Uninitialized Dynamic TCP Server") + { + HeartbeatRequiredIntervalInSeconds = 15; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + MonitorClientMaxFailureCount = 3; + } + + /// + /// constructor with debug key set at instantiation. Make sure to set all properties before listening. + /// + /// + public GenericTcpIpServer(string key) + : base("Uninitialized Dynamic TCP Server") + { + HeartbeatRequiredIntervalInSeconds = 15; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + MonitorClientMaxFailureCount = 3; + Key = key; + } + + /// + /// Contstructor that sets all properties by calling the initialize method with a config object. + /// + /// + public GenericTcpIpServer(TcpServerConfigObject serverConfigObject) + : base("Uninitialized Dynamic TCP Server") + { + HeartbeatRequiredIntervalInSeconds = 15; + CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler); + BufferSize = 2000; + MonitorClientMaxFailureCount = 3; + Initialize(serverConfigObject); + } + #endregion + + #region Methods - Server Actions + /// + /// Disconnects all clients and stops the server + /// + public void KillServer() + { + ServerStopped = true; + if (MonitorClient != null) + { + MonitorClient.Disconnect(); + } + DisconnectAllClientsForShutdown(); + StopListening(); + } + + /// + /// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+ + /// + /// + public void Initialize(string key) + { + Key = key; + } + + /// + /// Initialze with server configuration object + /// + /// + public void Initialize(TcpServerConfigObject serverConfigObject) + { + try + { + if (serverConfigObject != null || string.IsNullOrEmpty(serverConfigObject.Key)) + { + Key = serverConfigObject.Key; + MaxClients = serverConfigObject.MaxClients; + Port = serverConfigObject.Port; + SharedKeyRequired = serverConfigObject.SharedKeyRequired; + SharedKey = serverConfigObject.SharedKey; + HeartbeatRequired = serverConfigObject.HeartbeatRequired; + HeartbeatRequiredIntervalInSeconds = serverConfigObject.HeartbeatRequiredIntervalInSeconds; + HeartbeatStringToMatch = serverConfigObject.HeartbeatStringToMatch; + BufferSize = serverConfigObject.BufferSize; + + } + else + { + ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); + } + } + catch + { + ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key); + } + } + + /// + /// Start listening on the specified port + /// + public void Listen() + { + ServerCCSection.Enter(); + try + { + if (Port < 1 || Port > 65535) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key); + ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key)); + return; + } + if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key); + ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key)); + return; + } + if (IsListening) + return; + + if (myTcpServer == null) + { + myTcpServer = new TCPServer(Port, MaxClients); + if(HeartbeatRequired) + myTcpServer.SocketSendOrReceiveTimeOutInMs = (this.HeartbeatRequiredIntervalMs * 5); + + // myTcpServer.HandshakeTimeout = 30; + } + else + { + KillServer(); + myTcpServer.PortNumber = Port; + } + + myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange; + myTcpServer.SocketStatusChange += TcpServer_SocketStatusChange; + + ServerStopped = false; + myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); + OnServerStateChange(myTcpServer.State); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus); + + // StartMonitorClient(); + + + ServerCCSection.Leave(); + } + catch (Exception ex) + { + ServerCCSection.Leave(); + ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key); + } + } + + /// + /// Stop Listening + /// + public void StopListening() + { + try + { + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener"); + if (myTcpServer != null) + { + myTcpServer.Stop(); + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", myTcpServer.State); + OnServerStateChange(myTcpServer.State); + } + ServerStopped = true; + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex); + } + } + + /// + /// Disconnects Client + /// + /// + public void DisconnectClient(uint client) + { + try + { + myTcpServer.Disconnect(client); + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client); + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex); + } + } + /// + /// Disconnect All Clients + /// + public void DisconnectAllClientsForShutdown() + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients"); + if (myTcpServer != null) + { + myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange; + foreach (var index in ConnectedClientsIndexes.ToList()) // copy it here so that it iterates properly + { + var i = index; + if (!myTcpServer.ClientConnected(index)) + continue; + try + { + myTcpServer.Disconnect(i); + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i); + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex); + } + } + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", myTcpServer.ServerSocketStatus); + } + + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients"); + ConnectedClientsIndexes.Clear(); + + if (!ProgramIsStopping) + { + OnConnectionChange(); + OnServerStateChange(myTcpServer.State); //State shows both listening and connected + } + + // var o = new { }; + } + + /// + /// Broadcast text from server to all connected clients + /// + /// + public void BroadcastText(string text) + { + CCriticalSection CCBroadcast = new CCriticalSection(); + CCBroadcast.Enter(); + try + { + if (ConnectedClientsIndexes.Count > 0) + { + byte[] b = Encoding.GetEncoding(28591).GetBytes(text); + foreach (uint i in ConnectedClientsIndexes) + { + if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(i))) + { + SocketErrorCodes error = myTcpServer.SendDataAsync(i, b, b.Length, (x, y, z) => { }); + if (error != SocketErrorCodes.SOCKET_OK && error != SocketErrorCodes.SOCKET_OPERATION_PENDING) + Debug.Console(2, error.ToString()); + } + } + } + CCBroadcast.Leave(); + } + catch (Exception ex) + { + CCBroadcast.Leave(); + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message); + } + } + + /// + /// Not sure this is useful in library, maybe Pro?? + /// + /// + /// + public void SendTextToClient(string text, uint clientIndex) + { + try + { + byte[] b = Encoding.GetEncoding(28591).GetBytes(text); + if (myTcpServer != null && myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) + { + if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) + myTcpServer.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); + } + } + catch (Exception ex) + { + Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text); + } + } + + //private method to check heartbeat requirements and start or reset timer + string checkHeartbeat(uint clientIndex, string received) + { + try + { + if (HeartbeatRequired) + { + if (!string.IsNullOrEmpty(HeartbeatStringToMatch)) + { + var remainingText = received.Replace(HeartbeatStringToMatch, ""); + var noDelimiter = received.Trim(new char[] { '\r', '\n' }); + if (noDelimiter.Contains(HeartbeatStringToMatch)) + { + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); + else + { + CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); + HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); + } + Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex); + // Return Heartbeat + SendTextToClient(HeartbeatStringToMatch, clientIndex); + return remainingText; + } + } + else + { + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); + else + { + CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); + HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); + } + Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex); + } + } + } + catch (Exception ex) + { + Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); + } + return received; + } + + /// + /// Gets the IP address based on the client index + /// + /// + /// IP address of the client + public string GetClientIPAddress(uint clientIndex) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex); + if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex))) + { + var ipa = this.myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa); + return ipa; + + } + else + { + return ""; + } + } + + #endregion + + #region Methods - HeartbeatTimer Callback + + void HeartbeatTimer_CallbackFunction(object o) + { + uint clientIndex = 99999; + string address = string.Empty; + try + { + clientIndex = (uint)o; + address = myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex); + + Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}", + address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex); + + if (myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED) + SendTextToClient("Heartbeat not received by server, closing connection", clientIndex); + + var discoResult = myTcpServer.Disconnect(clientIndex); + //Debug.Console(1, this, "{0}", discoResult); + + if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) + { + HeartbeatTimerDictionary[clientIndex].Stop(); + HeartbeatTimerDictionary[clientIndex].Dispose(); + HeartbeatTimerDictionary.Remove(clientIndex); + } + } + catch (Exception ex) + { + ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key); + } + } + + #endregion + + #region Methods - Socket Status Changed Callbacks + /// + /// Secure Server Socket Status Changed Callback + /// + /// + /// + /// + void TcpServer_SocketStatusChange(TCPServer server, uint clientIndex, SocketStatus serverSocketStatus) + { + try + { + + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); + if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED) + { + if (ConnectedClientsIndexes.Contains(clientIndex)) + ConnectedClientsIndexes.Remove(clientIndex); + if (HeartbeatRequired && HeartbeatTimerDictionary.ContainsKey(clientIndex)) + { + HeartbeatTimerDictionary[clientIndex].Stop(); + HeartbeatTimerDictionary[clientIndex].Dispose(); + HeartbeatTimerDictionary.Remove(clientIndex); + } + if (ClientReadyAfterKeyExchange.Contains(clientIndex)) + ClientReadyAfterKeyExchange.Remove(clientIndex); + if (WaitingForSharedKey.Contains(clientIndex)) + WaitingForSharedKey.Remove(clientIndex); + } + } + catch (Exception ex) + { + Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex); + } + onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); + } + + #endregion + + #region Methods Connected Callbacks + /// + /// Secure TCP Client Connected to Secure Server Callback + /// + /// + /// + void TcpConnectCallback(TCPServer server, uint clientIndex) + { + try + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}", + server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex), + clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)); + if (clientIndex != 0) + { + if (server.ClientConnected(clientIndex)) + { + + if (!ConnectedClientsIndexes.Contains(clientIndex)) + { + ConnectedClientsIndexes.Add(clientIndex); + } + if (SharedKeyRequired) + { + if (!WaitingForSharedKey.Contains(clientIndex)) + { + WaitingForSharedKey.Add(clientIndex); + } + byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:"); + server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { }); + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex)); + } + else + { + OnServerClientReadyForCommunications(clientIndex); + } + if (HeartbeatRequired) + { + if (!HeartbeatTimerDictionary.ContainsKey(clientIndex)) + { + HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs)); + } + } + + server.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback); + } + } + else + { + Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty."); + if (!ServerStopped) + { + server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); + return; + } + } + } + catch (Exception 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, + // MaxClients, + // ServerStopped); + if ((server.State & ServerState.SERVER_LISTENING) != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Waiting for next connection"); + server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback); + + } + } + + #endregion + + #region Methods - Send/Receive Callbacks + /// + /// Secure Received Data Async Callback + /// + /// + /// + /// + void TcpServerReceivedDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesReceived) + { + 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 + + #region Methods - EventHelpers/Callbacks + + //Private Helper method to call the Connection Change Event + void onConnectionChange(uint clientIndex, SocketStatus clientStatus) + { + if (clientIndex != 0) //0 is error not valid client change + { + var handler = ClientConnectionChange; + if (handler != null) + { + handler(this, new GenericTcpServerSocketStatusChangeEventArgs(myTcpServer, clientIndex, clientStatus)); + } + } + } + + //Private Helper method to call the Connection Change Event + void OnConnectionChange() + { + if (ProgramIsStopping) + { + return; + } + var handler = ClientConnectionChange; + if (handler != null) + { + handler(this, new GenericTcpServerSocketStatusChangeEventArgs()); + } + } + + //Private Helper Method to call the Text Received Event + void onTextReceived(string text, uint clientIndex) + { + var handler = TextReceived; + if (handler != null) + handler(this, new GenericTcpServerCommMethodReceiveTextArgs(text, clientIndex)); + } + + //Private Helper Method to call the Server State Change Event + void OnServerStateChange(ServerState state) + { + if (ProgramIsStopping) + { + return; + } + var handler = ServerStateChange; + if (handler != null) + { + handler(this, new GenericTcpServerStateChangedEventArgs(state)); + } + } + + /// + /// Private Event Handler method to handle the closing of connections when the program stops + /// + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType == eProgramStatusEventType.Stopping) + { + ProgramIsStopping = true; + // kill bandaid things + if (MonitorClientTimer != null) + MonitorClientTimer.Stop(); + if (MonitorClient != null) + MonitorClient.Disconnect(); + + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server"); + KillServer(); + } + } + + //Private event handler method to raise the event that the server is ready to send data after a successful client shared key negotiation + void OnServerClientReadyForCommunications(uint clientIndex) + { + ClientReadyAfterKeyExchange.Add(clientIndex); + var handler = ServerClientReadyForCommunications; + if (handler != null) + handler(this, new GenericTcpServerSocketStatusChangeEventArgs( + this, clientIndex, myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex))); + } + #endregion + + #region Monitor Client + /// + /// Starts the monitor client cycle. Timed wait, then call RunMonitorClient + /// + void StartMonitorClient() + { + if (MonitorClientTimer != null) + { + return; + } + MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000); + } + + /// + /// + /// + void RunMonitorClient() + { + MonitorClient = new GenericTcpIpClient_ForServer(Key + "-MONITOR", "127.0.0.1", Port, 2000); + MonitorClient.SharedKeyRequired = this.SharedKeyRequired; + MonitorClient.SharedKey = this.SharedKey; + MonitorClient.ConnectionHasHungCallback = MonitorClientHasHungCallback; + //MonitorClient.ConnectionChange += MonitorClient_ConnectionChange; + MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm; + + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check"); + + MonitorClient.Connect(); + // From here MonitorCLient either connects or hangs, MonitorClient will call back + + } + + /// + /// + /// + void StopMonitorClient() + { + if (MonitorClient == null) + return; + + MonitorClient.ClientReadyForCommunications -= MonitorClient_IsReadyForComm; + MonitorClient.Disconnect(); + MonitorClient = null; + } + + /// + /// On monitor connect, restart the operation + /// + void MonitorClient_IsReadyForComm(object sender, GenericTcpServerClientReadyForcommunicationsEventArgs args) + { + if (args.IsReady) + { + Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s"); + MonitorClientTimer.Stop(); + MonitorClientTimer = null; + MonitorClientFailureCount = 0; + CrestronEnvironment.Sleep(2000); + StopMonitorClient(); + StartMonitorClient(); + } + } + + /// + /// If the client hangs, add to counter and maybe fire the choke event + /// + void MonitorClientHasHungCallback() + { + MonitorClientFailureCount++; + MonitorClientTimer.Stop(); + MonitorClientTimer = null; + StopMonitorClient(); + if (MonitorClientFailureCount < MonitorClientMaxFailureCount) + { + 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(2, this, Debug.ErrorLogLevel.Error, + "\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************", + MonitorClientMaxFailureCount); + + var handler = ServerHasChoked; + if (handler != null) + handler(); + // Some external thing is in charge here. Expected reset of program + } + } + #endregion + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs b/src/Comm/GenericUdpServer.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Comm/GenericUdpServer.cs rename to src/Comm/GenericUdpServer.cs diff --git a/Pepperdash Core/Pepperdash Core/Comm/QscCoreDoubleTcpIpClient.cs b/src/Comm/QscCoreDoubleTcpIpClient.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Comm/QscCoreDoubleTcpIpClient.cs rename to src/Comm/QscCoreDoubleTcpIpClient.cs diff --git a/Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs b/src/Comm/TcpClientConfigObject.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Comm/TcpClientConfigObject.cs rename to src/Comm/TcpClientConfigObject.cs diff --git a/Pepperdash Core/Pepperdash Core/Comm/TcpServerConfigObject.cs b/src/Comm/TcpServerConfigObject.cs similarity index 97% rename from Pepperdash Core/Pepperdash Core/Comm/TcpServerConfigObject.cs rename to src/Comm/TcpServerConfigObject.cs index 785851b..043cf58 100644 --- a/Pepperdash Core/Pepperdash Core/Comm/TcpServerConfigObject.cs +++ b/src/Comm/TcpServerConfigObject.cs @@ -1,60 +1,60 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -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; } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +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/src/Comm/eControlMethods.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Comm/eControlMethods.cs rename to src/Comm/eControlMethods.cs diff --git a/Pepperdash Core/Pepperdash Core/CommunicationExtras.cs b/src/CommunicationExtras.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/CommunicationExtras.cs rename to src/CommunicationExtras.cs index 353b14f..d588e47 100644 --- a/Pepperdash Core/Pepperdash Core/CommunicationExtras.cs +++ b/src/CommunicationExtras.cs @@ -1,242 +1,242 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronSockets; -using System.Text.RegularExpressions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace PepperDash.Core -{ - /// - /// An incoming communication stream - /// - public interface ICommunicationReceiver : IKeyed - { - /// - /// Notifies of bytes received - /// - event EventHandler BytesReceived; - /// - /// Notifies of text received - /// - event EventHandler TextReceived; - - /// - /// Indicates connection status - /// - bool IsConnected { get; } - /// - /// Connect to the device - /// - void Connect(); - /// - /// Disconnect from the device - /// - void Disconnect(); - } - - /// - /// Represents a device that uses basic connection - /// - public interface IBasicCommunication : ICommunicationReceiver - { - /// - /// Send text to the device - /// - /// - void SendText(string text); - - /// - /// Send bytes to the device - /// - /// - void SendBytes(byte[] bytes); - } - - /// - /// Represents a device that implements IBasicCommunication and IStreamDebugging - /// - public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, IStreamDebugging - { - - } - - /// - /// Represents a device with stream debugging capablities - /// - public interface IStreamDebugging - { - /// - /// Object to enable stream debugging - /// - CommunicationStreamDebugging StreamDebugging { get; } - } - - /// - /// For IBasicCommunication classes that have SocketStatus. GenericSshClient, - /// GenericTcpIpClient - /// - public interface ISocketStatus : IBasicCommunication - { - /// - /// Notifies of socket status changes - /// - event EventHandler ConnectionChange; - - /// - /// The current socket status of the client - /// - SocketStatus ClientStatus { get; } - } - - /// - /// Describes a device that implements ISocketStatus and IStreamDebugging - /// - public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugging - { - - } - - /// - /// Describes a device that can automatically attempt to reconnect - /// - public interface IAutoReconnect - { - /// - /// Enable automatic recconnect - /// - bool AutoReconnect { get; set; } - /// - /// Interval in ms to attempt automatic recconnections - /// - int AutoReconnectIntervalMs { get; set; } - } - - /// - /// - /// - public enum eGenericCommMethodStatusChangeType - { - /// - /// Connected - /// - Connected, - /// - /// Disconnected - /// - Disconnected - } - - /// - /// This delegate defines handler for IBasicCommunication status changes - /// - /// Device firing the status change - /// - public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status); - - /// - /// - /// - public class GenericCommMethodReceiveBytesArgs : EventArgs - { - /// - /// - /// - public byte[] Bytes { get; private set; } - - /// - /// - /// - /// - public GenericCommMethodReceiveBytesArgs(byte[] bytes) - { - Bytes = bytes; - } - - /// - /// S+ Constructor - /// - public GenericCommMethodReceiveBytesArgs() { } - } - - /// - /// - /// - public class GenericCommMethodReceiveTextArgs : EventArgs - { - /// - /// - /// - public string Text { get; private set; } - /// - /// - /// - public string Delimiter { get; private set; } - /// - /// - /// - /// - public GenericCommMethodReceiveTextArgs(string text) - { - Text = text; - } - - /// - /// - /// - /// - /// - public GenericCommMethodReceiveTextArgs(string text, string delimiter) - :this(text) - { - Delimiter = delimiter; - } - - /// - /// S+ Constructor - /// - public GenericCommMethodReceiveTextArgs() { } - } - - - - /// - /// - /// - public class ComTextHelper - { - /// - /// Gets escaped text for a byte array - /// - /// - /// - public static string GetEscapedText(byte[] bytes) - { - return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); - } - - /// - /// Gets escaped text for a string - /// - /// - /// - public static string GetEscapedText(string text) - { - var bytes = Encoding.GetEncoding(28591).GetBytes(text); - return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); - } - - /// - /// Gets debug text for a string - /// - /// - /// - public static string GetDebugText(string text) - { - return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value)); - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronSockets; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PepperDash.Core +{ + /// + /// An incoming communication stream + /// + public interface ICommunicationReceiver : IKeyed + { + /// + /// Notifies of bytes received + /// + event EventHandler BytesReceived; + /// + /// Notifies of text received + /// + event EventHandler TextReceived; + + /// + /// Indicates connection status + /// + bool IsConnected { get; } + /// + /// Connect to the device + /// + void Connect(); + /// + /// Disconnect from the device + /// + void Disconnect(); + } + + /// + /// Represents a device that uses basic connection + /// + public interface IBasicCommunication : ICommunicationReceiver + { + /// + /// Send text to the device + /// + /// + void SendText(string text); + + /// + /// Send bytes to the device + /// + /// + void SendBytes(byte[] bytes); + } + + /// + /// Represents a device that implements IBasicCommunication and IStreamDebugging + /// + public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, IStreamDebugging + { + + } + + /// + /// Represents a device with stream debugging capablities + /// + public interface IStreamDebugging + { + /// + /// Object to enable stream debugging + /// + CommunicationStreamDebugging StreamDebugging { get; } + } + + /// + /// For IBasicCommunication classes that have SocketStatus. GenericSshClient, + /// GenericTcpIpClient + /// + public interface ISocketStatus : IBasicCommunication + { + /// + /// Notifies of socket status changes + /// + event EventHandler ConnectionChange; + + /// + /// The current socket status of the client + /// + SocketStatus ClientStatus { get; } + } + + /// + /// Describes a device that implements ISocketStatus and IStreamDebugging + /// + public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugging + { + + } + + /// + /// Describes a device that can automatically attempt to reconnect + /// + public interface IAutoReconnect + { + /// + /// Enable automatic recconnect + /// + bool AutoReconnect { get; set; } + /// + /// Interval in ms to attempt automatic recconnections + /// + int AutoReconnectIntervalMs { get; set; } + } + + /// + /// + /// + public enum eGenericCommMethodStatusChangeType + { + /// + /// Connected + /// + Connected, + /// + /// Disconnected + /// + Disconnected + } + + /// + /// This delegate defines handler for IBasicCommunication status changes + /// + /// Device firing the status change + /// + public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status); + + /// + /// + /// + public class GenericCommMethodReceiveBytesArgs : EventArgs + { + /// + /// + /// + public byte[] Bytes { get; private set; } + + /// + /// + /// + /// + public GenericCommMethodReceiveBytesArgs(byte[] bytes) + { + Bytes = bytes; + } + + /// + /// S+ Constructor + /// + public GenericCommMethodReceiveBytesArgs() { } + } + + /// + /// + /// + public class GenericCommMethodReceiveTextArgs : EventArgs + { + /// + /// + /// + public string Text { get; private set; } + /// + /// + /// + public string Delimiter { get; private set; } + /// + /// + /// + /// + public GenericCommMethodReceiveTextArgs(string text) + { + Text = text; + } + + /// + /// + /// + /// + /// + public GenericCommMethodReceiveTextArgs(string text, string delimiter) + :this(text) + { + Delimiter = delimiter; + } + + /// + /// S+ Constructor + /// + public GenericCommMethodReceiveTextArgs() { } + } + + + + /// + /// + /// + public class ComTextHelper + { + /// + /// Gets escaped text for a byte array + /// + /// + /// + public static string GetEscapedText(byte[] bytes) + { + return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); + } + + /// + /// Gets escaped text for a string + /// + /// + /// + public static string GetEscapedText(string text) + { + var bytes = Encoding.GetEncoding(28591).GetBytes(text); + return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); + } + + /// + /// Gets debug text for a string + /// + /// + /// + public static string GetDebugText(string text) + { + return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value)); + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Config/PortalConfigReader.cs b/src/Config/PortalConfigReader.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Config/PortalConfigReader.cs rename to src/Config/PortalConfigReader.cs diff --git a/Pepperdash Core/Pepperdash Core/Conversion/Convert.cs b/src/Conversion/Convert.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Conversion/Convert.cs rename to src/Conversion/Convert.cs diff --git a/Pepperdash Core/Pepperdash Core/CoreInterfaces.cs b/src/CoreInterfaces.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/CoreInterfaces.cs rename to src/CoreInterfaces.cs diff --git a/Pepperdash Core/Pepperdash Core/Device.cs b/src/Device.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/Device.cs rename to src/Device.cs index c889dd3..55d4c15 100644 --- a/Pepperdash Core/Pepperdash Core/Device.cs +++ b/src/Device.cs @@ -1,162 +1,162 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace PepperDash.Core -{ - //********************************************************************************************************* - /// - /// The core event and status-bearing class that most if not all device and connectors can derive from. - /// - public class Device : IKeyName - { - /// - /// Unique Key - /// - public string Key { get; protected set; } - /// - /// Name of the devie - /// - public string Name { get; protected set; } - /// - /// - /// - public bool Enabled { get; protected set; } - - ///// - ///// A place to store reference to the original config object, if any. These values should - ///// NOT be used as properties on the device as they are all publicly-settable values. - ///// - //public DeviceConfig Config { get; private set; } - ///// - ///// Helper method to check if Config exists - ///// - //public bool HasConfig { get { return Config != null; } } - - List _PreActivationActions; - List _PostActivationActions; - - /// - /// - /// - public static Device DefaultDevice { get { return _DefaultDevice; } } - static Device _DefaultDevice = new Device("Default", "Default"); - - /// - /// Base constructor for all Devices. - /// - /// - public Device(string key) - { - Key = key; - if (key.Contains('.')) Debug.Console(0, this, "WARNING: Device name's should not include '.'"); - Name = ""; - - } - - /// - /// Constructor with key and name - /// - /// - /// - public Device(string key, string name) : this(key) - { - Name = name; - - } - - //public Device(DeviceConfig config) - // : this(config.Key, config.Name) - //{ - // Config = config; - //} - - /// - /// Adds a pre activation action - /// - /// - public void AddPreActivationAction(Action act) - { - if (_PreActivationActions == null) - _PreActivationActions = new List(); - _PreActivationActions.Add(act); - } - - /// - /// Adds a post activation action - /// - /// - public void AddPostActivationAction(Action act) - { - if (_PostActivationActions == null) - _PostActivationActions = new List(); - _PostActivationActions.Add(act); - } - - /// - /// Executes the preactivation actions - /// - public void PreActivate() - { - if (_PreActivationActions != null) - _PreActivationActions.ForEach(a => a.Invoke()); - } - - /// - /// Gets this device ready to be used in the system. Runs any added pre-activation items, and - /// all post-activation at end. Classes needing additional logic to - /// run should override CustomActivate() - /// - public bool Activate() - { - //if (_PreActivationActions != null) - // _PreActivationActions.ForEach(a => a.Invoke()); - var result = CustomActivate(); - //if(result && _PostActivationActions != null) - // _PostActivationActions.ForEach(a => a.Invoke()); - return result; - } - - /// - /// Executes the postactivation actions - /// - public void PostActivate() - { - if (_PostActivationActions != null) - _PostActivationActions.ForEach(a => a.Invoke()); - } - - /// - /// Called in between Pre and PostActivationActions when Activate() is called. - /// Override to provide addtitional setup when calling activation. Overriding classes - /// do not need to call base.CustomActivate() - /// - /// true if device activated successfully. - public virtual bool CustomActivate() { return true; } - - /// - /// Call to deactivate device - unlink events, etc. Overriding classes do not - /// need to call base.Deactivate() - /// - /// - public virtual bool Deactivate() { return true; } - - /// - /// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize() - /// - public virtual void Initialize() - { - } - - /// - /// Helper method to check object for bool value false and fire an Action method - /// - /// Should be of type bool, others will be ignored - /// Action to be run when o is false - public void OnFalse(object o, Action a) - { - if (o is bool && !(bool)o) a(); - } - - } +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PepperDash.Core +{ + //********************************************************************************************************* + /// + /// The core event and status-bearing class that most if not all device and connectors can derive from. + /// + public class Device : IKeyName + { + /// + /// Unique Key + /// + public string Key { get; protected set; } + /// + /// Name of the devie + /// + public string Name { get; protected set; } + /// + /// + /// + public bool Enabled { get; protected set; } + + ///// + ///// A place to store reference to the original config object, if any. These values should + ///// NOT be used as properties on the device as they are all publicly-settable values. + ///// + //public DeviceConfig Config { get; private set; } + ///// + ///// Helper method to check if Config exists + ///// + //public bool HasConfig { get { return Config != null; } } + + List _PreActivationActions; + List _PostActivationActions; + + /// + /// + /// + public static Device DefaultDevice { get { return _DefaultDevice; } } + static Device _DefaultDevice = new Device("Default", "Default"); + + /// + /// Base constructor for all Devices. + /// + /// + public Device(string key) + { + Key = key; + if (key.Contains('.')) Debug.Console(0, this, "WARNING: Device name's should not include '.'"); + Name = ""; + + } + + /// + /// Constructor with key and name + /// + /// + /// + public Device(string key, string name) : this(key) + { + Name = name; + + } + + //public Device(DeviceConfig config) + // : this(config.Key, config.Name) + //{ + // Config = config; + //} + + /// + /// Adds a pre activation action + /// + /// + public void AddPreActivationAction(Action act) + { + if (_PreActivationActions == null) + _PreActivationActions = new List(); + _PreActivationActions.Add(act); + } + + /// + /// Adds a post activation action + /// + /// + public void AddPostActivationAction(Action act) + { + if (_PostActivationActions == null) + _PostActivationActions = new List(); + _PostActivationActions.Add(act); + } + + /// + /// Executes the preactivation actions + /// + public void PreActivate() + { + if (_PreActivationActions != null) + _PreActivationActions.ForEach(a => a.Invoke()); + } + + /// + /// Gets this device ready to be used in the system. Runs any added pre-activation items, and + /// all post-activation at end. Classes needing additional logic to + /// run should override CustomActivate() + /// + public bool Activate() + { + //if (_PreActivationActions != null) + // _PreActivationActions.ForEach(a => a.Invoke()); + var result = CustomActivate(); + //if(result && _PostActivationActions != null) + // _PostActivationActions.ForEach(a => a.Invoke()); + return result; + } + + /// + /// Executes the postactivation actions + /// + public void PostActivate() + { + if (_PostActivationActions != null) + _PostActivationActions.ForEach(a => a.Invoke()); + } + + /// + /// Called in between Pre and PostActivationActions when Activate() is called. + /// Override to provide addtitional setup when calling activation. Overriding classes + /// do not need to call base.CustomActivate() + /// + /// true if device activated successfully. + public virtual bool CustomActivate() { return true; } + + /// + /// Call to deactivate device - unlink events, etc. Overriding classes do not + /// need to call base.Deactivate() + /// + /// + public virtual bool Deactivate() { return true; } + + /// + /// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize() + /// + public virtual void Initialize() + { + } + + /// + /// Helper method to check object for bool value false and fire an Action method + /// + /// Should be of type bool, others will be ignored + /// Action to be run when o is false + public void OnFalse(object o, Action a) + { + if (o is bool && !(bool)o) a(); + } + + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/EthernetHelper.cs b/src/EthernetHelper.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/EthernetHelper.cs rename to src/EthernetHelper.cs diff --git a/Pepperdash Core/Pepperdash Core/EventArgs.cs b/src/EventArgs.cs similarity index 95% rename from Pepperdash Core/Pepperdash Core/EventArgs.cs rename to src/EventArgs.cs index 6def673..29ef13a 100644 --- a/Pepperdash Core/Pepperdash Core/EventArgs.cs +++ b/src/EventArgs.cs @@ -1,172 +1,172 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core -{ - /// - /// Bool change event args - /// - public class BoolChangeEventArgs : EventArgs - { - /// - /// Boolean state property - /// - public bool State { get; set; } - - /// - /// Boolean ushort value property - /// - public ushort IntValue { get { return (ushort)(State ? 1 : 0); } } - - /// - /// Boolean change event args type - /// - public ushort Type { get; set; } - - /// - /// Boolean change event args index - /// - public ushort Index { get; set; } - - /// - /// Constructor - /// - public BoolChangeEventArgs() - { - - } - - /// - /// Constructor overload - /// - /// - /// - public BoolChangeEventArgs(bool state, ushort type) - { - State = state; - Type = type; - } - - /// - /// Constructor overload - /// - /// - /// - /// - public BoolChangeEventArgs(bool state, ushort type, ushort index) - { - State = state; - Type = type; - Index = index; - } - } - - /// - /// Ushort change event args - /// - public class UshrtChangeEventArgs : EventArgs - { - /// - /// Ushort change event args integer value - /// - public ushort IntValue { get; set; } - - /// - /// Ushort change event args type - /// - public ushort Type { get; set; } - - /// - /// Ushort change event args index - /// - public ushort Index { get; set; } - - /// - /// Constructor - /// - public UshrtChangeEventArgs() - { - - } - - /// - /// Constructor overload - /// - /// - /// - public UshrtChangeEventArgs(ushort intValue, ushort type) - { - IntValue = intValue; - Type = type; - } - - /// - /// Constructor overload - /// - /// - /// - /// - public UshrtChangeEventArgs(ushort intValue, ushort type, ushort index) - { - IntValue = intValue; - Type = type; - Index = index; - } - } - - /// - /// String change event args - /// - public class StringChangeEventArgs : EventArgs - { - /// - /// String change event args value - /// - public string StringValue { get; set; } - - /// - /// String change event args type - /// - public ushort Type { get; set; } - - /// - /// string change event args index - /// - public ushort Index { get; set; } - - /// - /// Constructor - /// - public StringChangeEventArgs() - { - - } - - /// - /// Constructor overload - /// - /// - /// - public StringChangeEventArgs(string stringValue, ushort type) - { - StringValue = stringValue; - Type = type; - } - - /// - /// Constructor overload - /// - /// - /// - /// - public StringChangeEventArgs(string stringValue, ushort type, ushort index) - { - StringValue = stringValue; - Type = type; - Index = index; - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core +{ + /// + /// Bool change event args + /// + public class BoolChangeEventArgs : EventArgs + { + /// + /// Boolean state property + /// + public bool State { get; set; } + + /// + /// Boolean ushort value property + /// + public ushort IntValue { get { return (ushort)(State ? 1 : 0); } } + + /// + /// Boolean change event args type + /// + public ushort Type { get; set; } + + /// + /// Boolean change event args index + /// + public ushort Index { get; set; } + + /// + /// Constructor + /// + public BoolChangeEventArgs() + { + + } + + /// + /// Constructor overload + /// + /// + /// + public BoolChangeEventArgs(bool state, ushort type) + { + State = state; + Type = type; + } + + /// + /// Constructor overload + /// + /// + /// + /// + public BoolChangeEventArgs(bool state, ushort type, ushort index) + { + State = state; + Type = type; + Index = index; + } + } + + /// + /// Ushort change event args + /// + public class UshrtChangeEventArgs : EventArgs + { + /// + /// Ushort change event args integer value + /// + public ushort IntValue { get; set; } + + /// + /// Ushort change event args type + /// + public ushort Type { get; set; } + + /// + /// Ushort change event args index + /// + public ushort Index { get; set; } + + /// + /// Constructor + /// + public UshrtChangeEventArgs() + { + + } + + /// + /// Constructor overload + /// + /// + /// + public UshrtChangeEventArgs(ushort intValue, ushort type) + { + IntValue = intValue; + Type = type; + } + + /// + /// Constructor overload + /// + /// + /// + /// + public UshrtChangeEventArgs(ushort intValue, ushort type, ushort index) + { + IntValue = intValue; + Type = type; + Index = index; + } + } + + /// + /// String change event args + /// + public class StringChangeEventArgs : EventArgs + { + /// + /// String change event args value + /// + public string StringValue { get; set; } + + /// + /// String change event args type + /// + public ushort Type { get; set; } + + /// + /// string change event args index + /// + public ushort Index { get; set; } + + /// + /// Constructor + /// + public StringChangeEventArgs() + { + + } + + /// + /// Constructor overload + /// + /// + /// + public StringChangeEventArgs(string stringValue, ushort type) + { + StringValue = stringValue; + Type = type; + } + + /// + /// Constructor overload + /// + /// + /// + /// + public StringChangeEventArgs(string stringValue, ushort type, ushort index) + { + StringValue = stringValue; + Type = type; + Index = index; + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/GenericRESTfulCommunications/Constants.cs b/src/GenericRESTfulCommunications/Constants.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/GenericRESTfulCommunications/Constants.cs rename to src/GenericRESTfulCommunications/Constants.cs index 9df43f0..1b78c33 100644 --- a/Pepperdash Core/Pepperdash Core/GenericRESTfulCommunications/Constants.cs +++ b/src/GenericRESTfulCommunications/Constants.cs @@ -1,39 +1,39 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core.GenericRESTfulCommunications -{ - /// - /// Constants - /// - public class GenericRESTfulConstants - { - /// - /// Generic boolean change - /// - public const ushort BoolValueChange = 1; - /// - /// Generic Ushort change - /// - public const ushort UshrtValueChange = 101; - /// - /// Response Code Ushort change - /// - public const ushort ResponseCodeChange = 102; - /// - /// Generic String chagne - /// - public const ushort StringValueChange = 201; - /// - /// Response string change - /// - public const ushort ResponseStringChange = 202; - /// - /// Error string change - /// - public const ushort ErrorStringChange = 203; - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.GenericRESTfulCommunications +{ + /// + /// Constants + /// + public class GenericRESTfulConstants + { + /// + /// Generic boolean change + /// + public const ushort BoolValueChange = 1; + /// + /// Generic Ushort change + /// + public const ushort UshrtValueChange = 101; + /// + /// Response Code Ushort change + /// + public const ushort ResponseCodeChange = 102; + /// + /// Generic String chagne + /// + public const ushort StringValueChange = 201; + /// + /// Response string change + /// + public const ushort ResponseStringChange = 202; + /// + /// Error string change + /// + public const ushort ErrorStringChange = 203; + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/GenericRESTfulCommunications/GenericRESTfulClient.cs b/src/GenericRESTfulCommunications/GenericRESTfulClient.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/GenericRESTfulCommunications/GenericRESTfulClient.cs rename to src/GenericRESTfulCommunications/GenericRESTfulClient.cs index c1db1aa..bd33e13 100644 --- a/Pepperdash Core/Pepperdash Core/GenericRESTfulCommunications/GenericRESTfulClient.cs +++ b/src/GenericRESTfulCommunications/GenericRESTfulClient.cs @@ -1,256 +1,256 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.Net.Http; -using Crestron.SimplSharp.Net.Https; - -namespace PepperDash.Core.GenericRESTfulCommunications -{ - /// - /// Generic RESTful communication class - /// - public class GenericRESTfulClient - { - /// - /// Boolean event handler - /// - public event EventHandler BoolChange; - /// - /// Ushort event handler - /// - public event EventHandler UshrtChange; - /// - /// String event handler - /// - public event EventHandler StringChange; - - /// - /// Constructor - /// - public GenericRESTfulClient() - { - - } - - /// - /// Generic RESTful submit request - /// - /// - /// - /// - /// - /// - /// - public void SubmitRequest(string url, ushort port, ushort requestType, string contentType, string username, string password) - { - if (url.StartsWith("https:", StringComparison.OrdinalIgnoreCase)) - { - SubmitRequestHttps(url, port, requestType, contentType, username, password); - } - else if (url.StartsWith("http:", StringComparison.OrdinalIgnoreCase)) - { - SubmitRequestHttp(url, port, requestType, contentType, username, password); - } - else - { - OnStringChange(string.Format("Invalid URL {0}", url), 0, GenericRESTfulConstants.ErrorStringChange); - } - } - - /// - /// Private HTTP submit request - /// - /// - /// - /// - /// - /// - /// - private void SubmitRequestHttp(string url, ushort port, ushort requestType, string contentType, string username, string password) - { - try - { - HttpClient client = new HttpClient(); - HttpClientRequest request = new HttpClientRequest(); - HttpClientResponse response; - - client.KeepAlive = false; - - if(port >= 1 || port <= 65535) - client.Port = port; - else - client.Port = 80; - - var authorization = ""; - if (!string.IsNullOrEmpty(username)) - authorization = EncodeBase64(username, password); - - if (!string.IsNullOrEmpty(authorization)) - request.Header.SetHeaderValue("Authorization", authorization); - - if (!string.IsNullOrEmpty(contentType)) - request.Header.ContentType = contentType; - - request.Url.Parse(url); - request.RequestType = (Crestron.SimplSharp.Net.Http.RequestType)requestType; - - response = client.Dispatch(request); - - CrestronConsole.PrintLine(string.Format("SubmitRequestHttp Response[{0}]: {1}", response.Code, response.ContentString.ToString())); - - if (!string.IsNullOrEmpty(response.ContentString.ToString())) - OnStringChange(response.ContentString.ToString(), 0, GenericRESTfulConstants.ResponseStringChange); - - if (response.Code > 0) - OnUshrtChange((ushort)response.Code, 0, GenericRESTfulConstants.ResponseCodeChange); - } - catch (Exception e) - { - //var msg = string.Format("SubmitRequestHttp({0}, {1}, {2}) failed:{3}", url, port, requestType, e.Message); - //CrestronConsole.PrintLine(msg); - //ErrorLog.Error(msg); - - CrestronConsole.PrintLine(e.Message); - OnStringChange(e.Message, 0, GenericRESTfulConstants.ErrorStringChange); - } - } - - /// - /// Private HTTPS submit request - /// - /// - /// - /// - /// - /// - /// - private void SubmitRequestHttps(string url, ushort port, ushort requestType, string contentType, string username, string password) - { - try - { - HttpsClient client = new HttpsClient(); - HttpsClientRequest request = new HttpsClientRequest(); - HttpsClientResponse response; - - client.KeepAlive = false; - client.HostVerification = false; - client.PeerVerification = false; - - var authorization = ""; - if (!string.IsNullOrEmpty(username)) - authorization = EncodeBase64(username, password); - - if (!string.IsNullOrEmpty(authorization)) - request.Header.SetHeaderValue("Authorization", authorization); - - if (!string.IsNullOrEmpty(contentType)) - request.Header.ContentType = contentType; - - request.Url.Parse(url); - request.RequestType = (Crestron.SimplSharp.Net.Https.RequestType)requestType; - - response = client.Dispatch(request); - - CrestronConsole.PrintLine(string.Format("SubmitRequestHttp Response[{0}]: {1}", response.Code, response.ContentString.ToString())); - - if(!string.IsNullOrEmpty(response.ContentString.ToString())) - OnStringChange(response.ContentString.ToString(), 0, GenericRESTfulConstants.ResponseStringChange); - - if(response.Code > 0) - OnUshrtChange((ushort)response.Code, 0, GenericRESTfulConstants.ResponseCodeChange); - - } - catch (Exception e) - { - //var msg = string.Format("SubmitRequestHttps({0}, {1}, {2}, {3}, {4}) failed:{5}", url, port, requestType, username, password, e.Message); - //CrestronConsole.PrintLine(msg); - //ErrorLog.Error(msg); - - CrestronConsole.PrintLine(e.Message); - OnStringChange(e.Message, 0, GenericRESTfulConstants.ErrorStringChange); - } - } - - /// - /// Private method to encode username and password to Base64 string - /// - /// - /// - /// authorization - private string EncodeBase64(string username, string password) - { - var authorization = ""; - - try - { - if (!string.IsNullOrEmpty(username)) - { - string base64String = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(string.Format("{0}:{1}", username, password))); - authorization = string.Format("Basic {0}", base64String); - } - } - catch (Exception e) - { - var msg = string.Format("EncodeBase64({0}, {1}) failed:\r{2}", username, password, e); - CrestronConsole.PrintLine(msg); - ErrorLog.Error(msg); - return "" ; - } - - return authorization; - } - - /// - /// Protected method to handle boolean change events - /// - /// - /// - /// - protected void OnBoolChange(bool state, ushort index, ushort type) - { - var handler = BoolChange; - if (handler != null) - { - var args = new BoolChangeEventArgs(state, type); - args.Index = index; - BoolChange(this, args); - } - } - - /// - /// Protected mehtod to handle ushort change events - /// - /// - /// - /// - protected void OnUshrtChange(ushort value, ushort index, ushort type) - { - var handler = UshrtChange; - if (handler != null) - { - var args = new UshrtChangeEventArgs(value, type); - args.Index = index; - UshrtChange(this, args); - } - } - - /// - /// Protected method to handle string change events - /// - /// - /// - /// - protected void OnStringChange(string value, ushort index, ushort type) - { - var handler = StringChange; - if (handler != null) - { - var args = new StringChangeEventArgs(value, type); - args.Index = index; - StringChange(this, args); - } - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.Net.Http; +using Crestron.SimplSharp.Net.Https; + +namespace PepperDash.Core.GenericRESTfulCommunications +{ + /// + /// Generic RESTful communication class + /// + public class GenericRESTfulClient + { + /// + /// Boolean event handler + /// + public event EventHandler BoolChange; + /// + /// Ushort event handler + /// + public event EventHandler UshrtChange; + /// + /// String event handler + /// + public event EventHandler StringChange; + + /// + /// Constructor + /// + public GenericRESTfulClient() + { + + } + + /// + /// Generic RESTful submit request + /// + /// + /// + /// + /// + /// + /// + public void SubmitRequest(string url, ushort port, ushort requestType, string contentType, string username, string password) + { + if (url.StartsWith("https:", StringComparison.OrdinalIgnoreCase)) + { + SubmitRequestHttps(url, port, requestType, contentType, username, password); + } + else if (url.StartsWith("http:", StringComparison.OrdinalIgnoreCase)) + { + SubmitRequestHttp(url, port, requestType, contentType, username, password); + } + else + { + OnStringChange(string.Format("Invalid URL {0}", url), 0, GenericRESTfulConstants.ErrorStringChange); + } + } + + /// + /// Private HTTP submit request + /// + /// + /// + /// + /// + /// + /// + private void SubmitRequestHttp(string url, ushort port, ushort requestType, string contentType, string username, string password) + { + try + { + HttpClient client = new HttpClient(); + HttpClientRequest request = new HttpClientRequest(); + HttpClientResponse response; + + client.KeepAlive = false; + + if(port >= 1 || port <= 65535) + client.Port = port; + else + client.Port = 80; + + var authorization = ""; + if (!string.IsNullOrEmpty(username)) + authorization = EncodeBase64(username, password); + + if (!string.IsNullOrEmpty(authorization)) + request.Header.SetHeaderValue("Authorization", authorization); + + if (!string.IsNullOrEmpty(contentType)) + request.Header.ContentType = contentType; + + request.Url.Parse(url); + request.RequestType = (Crestron.SimplSharp.Net.Http.RequestType)requestType; + + response = client.Dispatch(request); + + CrestronConsole.PrintLine(string.Format("SubmitRequestHttp Response[{0}]: {1}", response.Code, response.ContentString.ToString())); + + if (!string.IsNullOrEmpty(response.ContentString.ToString())) + OnStringChange(response.ContentString.ToString(), 0, GenericRESTfulConstants.ResponseStringChange); + + if (response.Code > 0) + OnUshrtChange((ushort)response.Code, 0, GenericRESTfulConstants.ResponseCodeChange); + } + catch (Exception e) + { + //var msg = string.Format("SubmitRequestHttp({0}, {1}, {2}) failed:{3}", url, port, requestType, e.Message); + //CrestronConsole.PrintLine(msg); + //ErrorLog.Error(msg); + + CrestronConsole.PrintLine(e.Message); + OnStringChange(e.Message, 0, GenericRESTfulConstants.ErrorStringChange); + } + } + + /// + /// Private HTTPS submit request + /// + /// + /// + /// + /// + /// + /// + private void SubmitRequestHttps(string url, ushort port, ushort requestType, string contentType, string username, string password) + { + try + { + HttpsClient client = new HttpsClient(); + HttpsClientRequest request = new HttpsClientRequest(); + HttpsClientResponse response; + + client.KeepAlive = false; + client.HostVerification = false; + client.PeerVerification = false; + + var authorization = ""; + if (!string.IsNullOrEmpty(username)) + authorization = EncodeBase64(username, password); + + if (!string.IsNullOrEmpty(authorization)) + request.Header.SetHeaderValue("Authorization", authorization); + + if (!string.IsNullOrEmpty(contentType)) + request.Header.ContentType = contentType; + + request.Url.Parse(url); + request.RequestType = (Crestron.SimplSharp.Net.Https.RequestType)requestType; + + response = client.Dispatch(request); + + CrestronConsole.PrintLine(string.Format("SubmitRequestHttp Response[{0}]: {1}", response.Code, response.ContentString.ToString())); + + if(!string.IsNullOrEmpty(response.ContentString.ToString())) + OnStringChange(response.ContentString.ToString(), 0, GenericRESTfulConstants.ResponseStringChange); + + if(response.Code > 0) + OnUshrtChange((ushort)response.Code, 0, GenericRESTfulConstants.ResponseCodeChange); + + } + catch (Exception e) + { + //var msg = string.Format("SubmitRequestHttps({0}, {1}, {2}, {3}, {4}) failed:{5}", url, port, requestType, username, password, e.Message); + //CrestronConsole.PrintLine(msg); + //ErrorLog.Error(msg); + + CrestronConsole.PrintLine(e.Message); + OnStringChange(e.Message, 0, GenericRESTfulConstants.ErrorStringChange); + } + } + + /// + /// Private method to encode username and password to Base64 string + /// + /// + /// + /// authorization + private string EncodeBase64(string username, string password) + { + var authorization = ""; + + try + { + if (!string.IsNullOrEmpty(username)) + { + string base64String = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(string.Format("{0}:{1}", username, password))); + authorization = string.Format("Basic {0}", base64String); + } + } + catch (Exception e) + { + var msg = string.Format("EncodeBase64({0}, {1}) failed:\r{2}", username, password, e); + CrestronConsole.PrintLine(msg); + ErrorLog.Error(msg); + return "" ; + } + + return authorization; + } + + /// + /// Protected method to handle boolean change events + /// + /// + /// + /// + protected void OnBoolChange(bool state, ushort index, ushort type) + { + var handler = BoolChange; + if (handler != null) + { + var args = new BoolChangeEventArgs(state, type); + args.Index = index; + BoolChange(this, args); + } + } + + /// + /// Protected mehtod to handle ushort change events + /// + /// + /// + /// + protected void OnUshrtChange(ushort value, ushort index, ushort type) + { + var handler = UshrtChange; + if (handler != null) + { + var args = new UshrtChangeEventArgs(value, type); + args.Index = index; + UshrtChange(this, args); + } + } + + /// + /// Protected method to handle string change events + /// + /// + /// + /// + protected void OnStringChange(string value, ushort index, ushort type) + { + var handler = StringChange; + if (handler != null) + { + var args = new StringChangeEventArgs(value, type); + args.Index = index; + StringChange(this, args); + } + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/JsonStandardObjects/EventArgs and Constants.cs b/src/JsonStandardObjects/EventArgs and Constants.cs similarity index 95% rename from Pepperdash Core/Pepperdash Core/JsonStandardObjects/EventArgs and Constants.cs rename to src/JsonStandardObjects/EventArgs and Constants.cs index 2bac226..ed02ccb 100644 --- a/Pepperdash Core/Pepperdash Core/JsonStandardObjects/EventArgs and Constants.cs +++ b/src/JsonStandardObjects/EventArgs and Constants.cs @@ -1,77 +1,77 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core.JsonStandardObjects -{ - /// - /// Constants for simpl modules - /// - public class JsonStandardDeviceConstants - { - /// - /// Json object evaluated constant - /// - public const ushort JsonObjectEvaluated = 2; - - /// - /// Json object changed constant - /// - public const ushort JsonObjectChanged = 104; - } - - /// - /// - /// - public class DeviceChangeEventArgs : EventArgs - { - /// - /// Device change event args object - /// - public DeviceConfig Device { get; set; } - - /// - /// Device change event args type - /// - public ushort Type { get; set; } - - /// - /// Device change event args index - /// - public ushort Index { get; set; } - - /// - /// Default constructor - /// - public DeviceChangeEventArgs() - { - - } - - /// - /// Constructor overload - /// - /// - /// - public DeviceChangeEventArgs(DeviceConfig device, ushort type) - { - Device = device; - Type = type; - } - - /// - /// Constructor overload - /// - /// - /// - /// - public DeviceChangeEventArgs(DeviceConfig device, ushort type, ushort index) - { - Device = device; - Type = type; - Index = index; - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.JsonStandardObjects +{ + /// + /// Constants for simpl modules + /// + public class JsonStandardDeviceConstants + { + /// + /// Json object evaluated constant + /// + public const ushort JsonObjectEvaluated = 2; + + /// + /// Json object changed constant + /// + public const ushort JsonObjectChanged = 104; + } + + /// + /// + /// + public class DeviceChangeEventArgs : EventArgs + { + /// + /// Device change event args object + /// + public DeviceConfig Device { get; set; } + + /// + /// Device change event args type + /// + public ushort Type { get; set; } + + /// + /// Device change event args index + /// + public ushort Index { get; set; } + + /// + /// Default constructor + /// + public DeviceChangeEventArgs() + { + + } + + /// + /// Constructor overload + /// + /// + /// + public DeviceChangeEventArgs(DeviceConfig device, ushort type) + { + Device = device; + Type = type; + } + + /// + /// Constructor overload + /// + /// + /// + /// + public DeviceChangeEventArgs(DeviceConfig device, ushort type, ushort index) + { + Device = device; + Type = type; + Index = index; + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/JsonStandardObjects/JsonToSimplDevice.cs b/src/JsonStandardObjects/JsonToSimplDevice.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/JsonStandardObjects/JsonToSimplDevice.cs rename to src/JsonStandardObjects/JsonToSimplDevice.cs index eef68f2..e4b1567 100644 --- a/Pepperdash Core/Pepperdash Core/JsonStandardObjects/JsonToSimplDevice.cs +++ b/src/JsonStandardObjects/JsonToSimplDevice.cs @@ -1,186 +1,186 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core.JsonToSimpl; - -namespace PepperDash.Core.JsonStandardObjects -{ - /// - /// Device class - /// - public class DeviceConfig - { - /// - /// JSON config key property - /// - public string key { get; set; } - /// - /// JSON config name property - /// - public string name { get; set; } - /// - /// JSON config type property - /// - public string type { get; set; } - /// - /// JSON config properties - /// - public PropertiesConfig properties { get; set; } - - /// - /// Bool change event handler - /// - public event EventHandler BoolChange; - /// - /// Ushort change event handler - /// - public event EventHandler UshrtChange; - /// - /// String change event handler - /// - public event EventHandler StringChange; - /// - /// Object change event handler - /// - public event EventHandler DeviceChange; - - /// - /// Constructor - /// - public DeviceConfig() - { - properties = new PropertiesConfig(); - } - - /// - /// Initialize method - /// - /// - /// - public void Initialize(string uniqueID, string deviceKey) - { - // S+ set EvaluateFb low - OnBoolChange(false, 0, JsonStandardDeviceConstants.JsonObjectEvaluated); - // validate parameters - if (string.IsNullOrEmpty(uniqueID) || string.IsNullOrEmpty(deviceKey)) - { - Debug.Console(1, "UniqueID ({0} or key ({1} is null or empty", uniqueID, deviceKey); - // S+ set EvaluteFb high - OnBoolChange(true, 0, JsonStandardDeviceConstants.JsonObjectEvaluated); - return; - } - - key = deviceKey; - - try - { - // get the file using the unique ID - JsonToSimplMaster jsonMaster = J2SGlobal.GetMasterByFile(uniqueID); - if (jsonMaster == null) - { - Debug.Console(1, "Could not find JSON file with uniqueID {0}", uniqueID); - return; - } - - // get the device configuration using the key - var devices = jsonMaster.JsonObject.ToObject().devices; - var device = devices.FirstOrDefault(d => d.key.Equals(key)); - if (device == null) - { - Debug.Console(1, "Could not find device with key {0}", key); - return; - } - OnObjectChange(device, 0, JsonStandardDeviceConstants.JsonObjectChanged); - - var index = devices.IndexOf(device); - OnStringChange(string.Format("devices[{0}]", index), 0, JsonToSimplConstants.FullPathToArrayChange); - } - catch (Exception e) - { - var msg = string.Format("Device {0} lookup failed:\r{1}", key, e); - CrestronConsole.PrintLine(msg); - ErrorLog.Error(msg); - } - finally - { - // S+ set EvaluteFb high - OnBoolChange(true, 0, JsonStandardDeviceConstants.JsonObjectEvaluated); - } - } - - #region EventHandler Helpers - - /// - /// BoolChange event handler helper - /// - /// - /// - /// - protected void OnBoolChange(bool state, ushort index, ushort type) - { - var handler = BoolChange; - if (handler != null) - { - var args = new BoolChangeEventArgs(state, type); - args.Index = index; - BoolChange(this, args); - } - } - - /// - /// UshrtChange event handler helper - /// - /// - /// - /// - protected void OnUshrtChange(ushort state, ushort index, ushort type) - { - var handler = UshrtChange; - if (handler != null) - { - var args = new UshrtChangeEventArgs(state, type); - args.Index = index; - UshrtChange(this, args); - } - } - - /// - /// StringChange event handler helper - /// - /// - /// - /// - protected void OnStringChange(string value, ushort index, ushort type) - { - var handler = StringChange; - if (handler != null) - { - var args = new StringChangeEventArgs(value, type); - args.Index = index; - StringChange(this, args); - } - } - - /// - /// ObjectChange event handler helper - /// - /// - /// - /// - protected void OnObjectChange(DeviceConfig device, ushort index, ushort type) - { - if (DeviceChange != null) - { - var args = new DeviceChangeEventArgs(device, type); - args.Index = index; - DeviceChange(this, args); - } - } - - #endregion EventHandler Helpers - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core.JsonToSimpl; + +namespace PepperDash.Core.JsonStandardObjects +{ + /// + /// Device class + /// + public class DeviceConfig + { + /// + /// JSON config key property + /// + public string key { get; set; } + /// + /// JSON config name property + /// + public string name { get; set; } + /// + /// JSON config type property + /// + public string type { get; set; } + /// + /// JSON config properties + /// + public PropertiesConfig properties { get; set; } + + /// + /// Bool change event handler + /// + public event EventHandler BoolChange; + /// + /// Ushort change event handler + /// + public event EventHandler UshrtChange; + /// + /// String change event handler + /// + public event EventHandler StringChange; + /// + /// Object change event handler + /// + public event EventHandler DeviceChange; + + /// + /// Constructor + /// + public DeviceConfig() + { + properties = new PropertiesConfig(); + } + + /// + /// Initialize method + /// + /// + /// + public void Initialize(string uniqueID, string deviceKey) + { + // S+ set EvaluateFb low + OnBoolChange(false, 0, JsonStandardDeviceConstants.JsonObjectEvaluated); + // validate parameters + if (string.IsNullOrEmpty(uniqueID) || string.IsNullOrEmpty(deviceKey)) + { + Debug.Console(1, "UniqueID ({0} or key ({1} is null or empty", uniqueID, deviceKey); + // S+ set EvaluteFb high + OnBoolChange(true, 0, JsonStandardDeviceConstants.JsonObjectEvaluated); + return; + } + + key = deviceKey; + + try + { + // get the file using the unique ID + JsonToSimplMaster jsonMaster = J2SGlobal.GetMasterByFile(uniqueID); + if (jsonMaster == null) + { + Debug.Console(1, "Could not find JSON file with uniqueID {0}", uniqueID); + return; + } + + // get the device configuration using the key + var devices = jsonMaster.JsonObject.ToObject().devices; + var device = devices.FirstOrDefault(d => d.key.Equals(key)); + if (device == null) + { + Debug.Console(1, "Could not find device with key {0}", key); + return; + } + OnObjectChange(device, 0, JsonStandardDeviceConstants.JsonObjectChanged); + + var index = devices.IndexOf(device); + OnStringChange(string.Format("devices[{0}]", index), 0, JsonToSimplConstants.FullPathToArrayChange); + } + catch (Exception e) + { + var msg = string.Format("Device {0} lookup failed:\r{1}", key, e); + CrestronConsole.PrintLine(msg); + ErrorLog.Error(msg); + } + finally + { + // S+ set EvaluteFb high + OnBoolChange(true, 0, JsonStandardDeviceConstants.JsonObjectEvaluated); + } + } + + #region EventHandler Helpers + + /// + /// BoolChange event handler helper + /// + /// + /// + /// + protected void OnBoolChange(bool state, ushort index, ushort type) + { + var handler = BoolChange; + if (handler != null) + { + var args = new BoolChangeEventArgs(state, type); + args.Index = index; + BoolChange(this, args); + } + } + + /// + /// UshrtChange event handler helper + /// + /// + /// + /// + protected void OnUshrtChange(ushort state, ushort index, ushort type) + { + var handler = UshrtChange; + if (handler != null) + { + var args = new UshrtChangeEventArgs(state, type); + args.Index = index; + UshrtChange(this, args); + } + } + + /// + /// StringChange event handler helper + /// + /// + /// + /// + protected void OnStringChange(string value, ushort index, ushort type) + { + var handler = StringChange; + if (handler != null) + { + var args = new StringChangeEventArgs(value, type); + args.Index = index; + StringChange(this, args); + } + } + + /// + /// ObjectChange event handler helper + /// + /// + /// + /// + protected void OnObjectChange(DeviceConfig device, ushort index, ushort type) + { + if (DeviceChange != null) + { + var args = new DeviceChangeEventArgs(device, type); + args.Index = index; + DeviceChange(this, args); + } + } + + #endregion EventHandler Helpers + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/JsonStandardObjects/JsonToSimplDeviceConfig.cs b/src/JsonStandardObjects/JsonToSimplDeviceConfig.cs similarity index 95% rename from Pepperdash Core/Pepperdash Core/JsonStandardObjects/JsonToSimplDeviceConfig.cs rename to src/JsonStandardObjects/JsonToSimplDeviceConfig.cs index 114609e..fa23d87 100644 --- a/Pepperdash Core/Pepperdash Core/JsonStandardObjects/JsonToSimplDeviceConfig.cs +++ b/src/JsonStandardObjects/JsonToSimplDeviceConfig.cs @@ -1,257 +1,257 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core.JsonStandardObjects -{ - /* - Convert JSON snippt to C#: http://json2csharp.com/# - - JSON Snippet: - { - "devices": [ - { - "key": "deviceKey", - "name": "deviceName", - "type": "deviceType", - "properties": { - "deviceId": 1, - "enabled": true, - "control": { - "method": "methodName", - "controlPortDevKey": "deviceControlPortDevKey", - "controlPortNumber": 1, - "comParams": { - "baudRate": 9600, - "dataBits": 8, - "stopBits": 1, - "parity": "None", - "protocol": "RS232", - "hardwareHandshake": "None", - "softwareHandshake": "None", - "pacing": 0 - }, - "tcpSshProperties": { - "address": "172.22.1.101", - "port": 23, - "username": "user01", - "password": "password01", - "autoReconnect": false, - "autoReconnectIntervalMs": 10000 - } - } - } - } - ] - } - */ - /// - /// Device communication parameter class - /// - public class ComParamsConfig - { - /// - /// - /// - public int baudRate { get; set; } - /// - /// - /// - public int dataBits { get; set; } - /// - /// - /// - public int stopBits { get; set; } - /// - /// - /// - public string parity { get; set; } - /// - /// - /// - public string protocol { get; set; } - /// - /// - /// - public string hardwareHandshake { get; set; } - /// - /// - /// - public string softwareHandshake { get; set; } - /// - /// - /// - public int pacing { get; set; } - - // convert properties for simpl - /// - /// - /// - public ushort simplBaudRate { get { return Convert.ToUInt16(baudRate); } } - /// - /// - /// - public ushort simplDataBits { get { return Convert.ToUInt16(dataBits); } } - /// - /// - /// - public ushort simplStopBits { get { return Convert.ToUInt16(stopBits); } } - /// - /// - /// - public ushort simplPacing { get { return Convert.ToUInt16(pacing); } } - - /// - /// Constructor - /// - public ComParamsConfig() - { - - } - } - - /// - /// Device TCP/SSH properties class - /// - public class TcpSshPropertiesConfig - { - /// - /// - /// - public string address { get; set; } - /// - /// - /// - public int port { get; set; } - /// - /// - /// - public string username { get; set; } - /// - /// - /// - public string password { get; set; } - /// - /// - /// - public bool autoReconnect { get; set; } - /// - /// - /// - public int autoReconnectIntervalMs { get; set; } - - // convert properties for simpl - /// - /// - /// - public ushort simplPort { get { return Convert.ToUInt16(port); } } - /// - /// - /// - public ushort simplAutoReconnect { get { return (ushort)(autoReconnect ? 1 : 0); } } - /// - /// - /// - public ushort simplAutoReconnectIntervalMs { get { return Convert.ToUInt16(autoReconnectIntervalMs); } } - - /// - /// Constructor - /// - public TcpSshPropertiesConfig() - { - - } - } - - /// - /// Device control class - /// - public class ControlConfig - { - /// - /// - /// - public string method { get; set; } - /// - /// - /// - public string controlPortDevKey { get; set; } - /// - /// - /// - public int controlPortNumber { get; set; } - /// - /// - /// - public ComParamsConfig comParams { get; set; } - /// - /// - /// - public TcpSshPropertiesConfig tcpSshProperties { get; set; } - - // convert properties for simpl - /// - /// - /// - public ushort simplControlPortNumber { get { return Convert.ToUInt16(controlPortNumber); } } - - /// - /// Constructor - /// - public ControlConfig() - { - comParams = new ComParamsConfig(); - tcpSshProperties = new TcpSshPropertiesConfig(); - } - } - - /// - /// Device properties class - /// - public class PropertiesConfig - { - /// - /// - /// - public int deviceId { get; set; } - /// - /// - /// - public bool enabled { get; set; } - /// - /// - /// - public ControlConfig control { get; set; } - - // convert properties for simpl - /// - /// - /// - public ushort simplDeviceId { get { return Convert.ToUInt16(deviceId); } } - /// - /// - /// - public ushort simplEnabled { get { return (ushort)(enabled ? 1 : 0); } } - - /// - /// Constructor - /// - public PropertiesConfig() - { - control = new ControlConfig(); - } - } - - /// - /// Root device class - /// - public class RootObject - { - /// - /// The collection of devices - /// - public List devices { get; set; } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.JsonStandardObjects +{ + /* + Convert JSON snippt to C#: http://json2csharp.com/# + + JSON Snippet: + { + "devices": [ + { + "key": "deviceKey", + "name": "deviceName", + "type": "deviceType", + "properties": { + "deviceId": 1, + "enabled": true, + "control": { + "method": "methodName", + "controlPortDevKey": "deviceControlPortDevKey", + "controlPortNumber": 1, + "comParams": { + "baudRate": 9600, + "dataBits": 8, + "stopBits": 1, + "parity": "None", + "protocol": "RS232", + "hardwareHandshake": "None", + "softwareHandshake": "None", + "pacing": 0 + }, + "tcpSshProperties": { + "address": "172.22.1.101", + "port": 23, + "username": "user01", + "password": "password01", + "autoReconnect": false, + "autoReconnectIntervalMs": 10000 + } + } + } + } + ] + } + */ + /// + /// Device communication parameter class + /// + public class ComParamsConfig + { + /// + /// + /// + public int baudRate { get; set; } + /// + /// + /// + public int dataBits { get; set; } + /// + /// + /// + public int stopBits { get; set; } + /// + /// + /// + public string parity { get; set; } + /// + /// + /// + public string protocol { get; set; } + /// + /// + /// + public string hardwareHandshake { get; set; } + /// + /// + /// + public string softwareHandshake { get; set; } + /// + /// + /// + public int pacing { get; set; } + + // convert properties for simpl + /// + /// + /// + public ushort simplBaudRate { get { return Convert.ToUInt16(baudRate); } } + /// + /// + /// + public ushort simplDataBits { get { return Convert.ToUInt16(dataBits); } } + /// + /// + /// + public ushort simplStopBits { get { return Convert.ToUInt16(stopBits); } } + /// + /// + /// + public ushort simplPacing { get { return Convert.ToUInt16(pacing); } } + + /// + /// Constructor + /// + public ComParamsConfig() + { + + } + } + + /// + /// Device TCP/SSH properties class + /// + public class TcpSshPropertiesConfig + { + /// + /// + /// + public string address { get; set; } + /// + /// + /// + public int port { get; set; } + /// + /// + /// + public string username { get; set; } + /// + /// + /// + public string password { get; set; } + /// + /// + /// + public bool autoReconnect { get; set; } + /// + /// + /// + public int autoReconnectIntervalMs { get; set; } + + // convert properties for simpl + /// + /// + /// + public ushort simplPort { get { return Convert.ToUInt16(port); } } + /// + /// + /// + public ushort simplAutoReconnect { get { return (ushort)(autoReconnect ? 1 : 0); } } + /// + /// + /// + public ushort simplAutoReconnectIntervalMs { get { return Convert.ToUInt16(autoReconnectIntervalMs); } } + + /// + /// Constructor + /// + public TcpSshPropertiesConfig() + { + + } + } + + /// + /// Device control class + /// + public class ControlConfig + { + /// + /// + /// + public string method { get; set; } + /// + /// + /// + public string controlPortDevKey { get; set; } + /// + /// + /// + public int controlPortNumber { get; set; } + /// + /// + /// + public ComParamsConfig comParams { get; set; } + /// + /// + /// + public TcpSshPropertiesConfig tcpSshProperties { get; set; } + + // convert properties for simpl + /// + /// + /// + public ushort simplControlPortNumber { get { return Convert.ToUInt16(controlPortNumber); } } + + /// + /// Constructor + /// + public ControlConfig() + { + comParams = new ComParamsConfig(); + tcpSshProperties = new TcpSshPropertiesConfig(); + } + } + + /// + /// Device properties class + /// + public class PropertiesConfig + { + /// + /// + /// + public int deviceId { get; set; } + /// + /// + /// + public bool enabled { get; set; } + /// + /// + /// + public ControlConfig control { get; set; } + + // convert properties for simpl + /// + /// + /// + public ushort simplDeviceId { get { return Convert.ToUInt16(deviceId); } } + /// + /// + /// + public ushort simplEnabled { get { return (ushort)(enabled ? 1 : 0); } } + + /// + /// Constructor + /// + public PropertiesConfig() + { + control = new ControlConfig(); + } + } + + /// + /// Root device class + /// + public class RootObject + { + /// + /// The collection of devices + /// + public List devices { get; set; } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/Constants.cs b/src/JsonToSimpl/Constants.cs similarity index 95% rename from Pepperdash Core/Pepperdash Core/JsonToSimpl/Constants.cs rename to src/JsonToSimpl/Constants.cs index 0bf9872..d87b50c 100644 --- a/Pepperdash Core/Pepperdash Core/JsonToSimpl/Constants.cs +++ b/src/JsonToSimpl/Constants.cs @@ -1,143 +1,143 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core.JsonToSimpl -{ - /// - /// Constants for Simpl modules - /// - public class JsonToSimplConstants - { - /// - /// - /// - public const ushort BoolValueChange = 1; - /// - /// - /// - public const ushort JsonIsValidBoolChange = 2; - - /// - /// Reports the if the device is 3-series compatible - /// - public const ushort ProgramCompatibility3SeriesChange = 3; - - /// - /// Reports the if the device is 4-series compatible - /// - public const ushort ProgramCompatibility4SeriesChange = 4; - - /// - /// Reports the device platform enum value - /// - public const ushort DevicePlatformValueChange = 5; - - /// - /// - /// - public const ushort UshortValueChange = 101; - - /// - /// - /// - public const ushort StringValueChange = 201; - /// - /// - /// - public const ushort FullPathToArrayChange = 202; - /// - /// - /// - public const ushort ActualFilePathChange = 203; - - /// - /// - /// - public const ushort FilenameResolvedChange = 204; - /// - /// - /// - public const ushort FilePathResolvedChange = 205; - - /// - /// Reports the root directory change - /// - public const ushort RootDirectoryChange = 206; - - /// - /// Reports the room ID change - /// - public const ushort RoomIdChange = 207; - - /// - /// Reports the room name change - /// - public const ushort RoomNameChange = 208; - } - - /// - /// S+ values delegate - /// - public delegate void SPlusValuesDelegate(); - - /// - /// S+ values wrapper - /// - public class SPlusValueWrapper - { - /// - /// - /// - public SPlusType ValueType { get; private set; } - /// - /// - /// - public ushort Index { get; private set; } - /// - /// - /// - public ushort BoolUShortValue { get; set; } - /// - /// - /// - public string StringValue { get; set; } - - /// - /// - /// - public SPlusValueWrapper() {} - - /// - /// - /// - /// - /// - public SPlusValueWrapper(SPlusType type, ushort index) - { - ValueType = type; - Index = index; - } - } - - /// - /// S+ types enum - /// - public enum SPlusType - { - /// - /// Digital - /// - Digital, - /// - /// Analog - /// - Analog, - /// - /// String - /// - String - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.JsonToSimpl +{ + /// + /// Constants for Simpl modules + /// + public class JsonToSimplConstants + { + /// + /// + /// + public const ushort BoolValueChange = 1; + /// + /// + /// + public const ushort JsonIsValidBoolChange = 2; + + /// + /// Reports the if the device is 3-series compatible + /// + public const ushort ProgramCompatibility3SeriesChange = 3; + + /// + /// Reports the if the device is 4-series compatible + /// + public const ushort ProgramCompatibility4SeriesChange = 4; + + /// + /// Reports the device platform enum value + /// + public const ushort DevicePlatformValueChange = 5; + + /// + /// + /// + public const ushort UshortValueChange = 101; + + /// + /// + /// + public const ushort StringValueChange = 201; + /// + /// + /// + public const ushort FullPathToArrayChange = 202; + /// + /// + /// + public const ushort ActualFilePathChange = 203; + + /// + /// + /// + public const ushort FilenameResolvedChange = 204; + /// + /// + /// + public const ushort FilePathResolvedChange = 205; + + /// + /// Reports the root directory change + /// + public const ushort RootDirectoryChange = 206; + + /// + /// Reports the room ID change + /// + public const ushort RoomIdChange = 207; + + /// + /// Reports the room name change + /// + public const ushort RoomNameChange = 208; + } + + /// + /// S+ values delegate + /// + public delegate void SPlusValuesDelegate(); + + /// + /// S+ values wrapper + /// + public class SPlusValueWrapper + { + /// + /// + /// + public SPlusType ValueType { get; private set; } + /// + /// + /// + public ushort Index { get; private set; } + /// + /// + /// + public ushort BoolUShortValue { get; set; } + /// + /// + /// + public string StringValue { get; set; } + + /// + /// + /// + public SPlusValueWrapper() {} + + /// + /// + /// + /// + /// + public SPlusValueWrapper(SPlusType type, ushort index) + { + ValueType = type; + Index = index; + } + } + + /// + /// S+ types enum + /// + public enum SPlusType + { + /// + /// Digital + /// + Digital, + /// + /// Analog + /// + Analog, + /// + /// String + /// + String + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/Global.cs b/src/JsonToSimpl/Global.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/JsonToSimpl/Global.cs rename to src/JsonToSimpl/Global.cs diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs b/src/JsonToSimpl/JsonToSimplArrayLookupChild.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs rename to src/JsonToSimpl/JsonToSimplArrayLookupChild.cs index 6535308..eb15448 100644 --- a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs +++ b/src/JsonToSimpl/JsonToSimplArrayLookupChild.cs @@ -1,165 +1,165 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace PepperDash.Core.JsonToSimpl -{ - /// - /// Used to interact with an array of values with the S+ modules - /// - public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase - { - /// - /// - /// - public string SearchPropertyName { get; set; } - /// - /// - /// - public string SearchPropertyValue { get; set; } - - int ArrayIndex; - - /// - /// For gt2.4.1 array lookups - /// - /// - /// - /// - /// - /// - /// - public void Initialize(string file, string key, string pathPrefix, string pathSuffix, - string searchPropertyName, string searchPropertyValue) - { - base.Initialize(file, key, pathPrefix, pathSuffix); - SearchPropertyName = searchPropertyName; - SearchPropertyValue = searchPropertyValue; - } - - - /// - /// 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 - /// - /// - /// - /// - /// - protected override string GetFullPath(string path) - { - return string.Format("{0}[{1}].{2}{3}", - PathPrefix == null ? "" : PathPrefix, - ArrayIndex, - path, - PathSuffix == null ? "" : PathSuffix); - } - - /// - /// Process all values - /// - public override void ProcessAll() - { - if (FindInArray()) - 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) - throw new InvalidOperationException("Cannot do operations before master is linked"); - 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); - 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); - } - - return false; - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PepperDash.Core.JsonToSimpl +{ + /// + /// Used to interact with an array of values with the S+ modules + /// + public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase + { + /// + /// + /// + public string SearchPropertyName { get; set; } + /// + /// + /// + public string SearchPropertyValue { get; set; } + + int ArrayIndex; + + /// + /// For gt2.4.1 array lookups + /// + /// + /// + /// + /// + /// + /// + public void Initialize(string file, string key, string pathPrefix, string pathSuffix, + string searchPropertyName, string searchPropertyValue) + { + base.Initialize(file, key, pathPrefix, pathSuffix); + SearchPropertyName = searchPropertyName; + SearchPropertyValue = searchPropertyValue; + } + + + /// + /// 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 + /// + /// + /// + /// + /// + protected override string GetFullPath(string path) + { + return string.Format("{0}[{1}].{2}{3}", + PathPrefix == null ? "" : PathPrefix, + ArrayIndex, + path, + PathSuffix == null ? "" : PathSuffix); + } + + /// + /// Process all values + /// + public override void ProcessAll() + { + if (FindInArray()) + 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) + throw new InvalidOperationException("Cannot do operations before master is linked"); + 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); + 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); + } + + return false; + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplChildObjectBase.cs b/src/JsonToSimpl/JsonToSimplChildObjectBase.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplChildObjectBase.cs rename to src/JsonToSimpl/JsonToSimplChildObjectBase.cs index e507105..e294fc8 100644 --- a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplChildObjectBase.cs +++ b/src/JsonToSimpl/JsonToSimplChildObjectBase.cs @@ -1,407 +1,407 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace PepperDash.Core.JsonToSimpl -{ - /// - /// Base class for JSON objects - /// - public abstract class JsonToSimplChildObjectBase : IKeyed - { - /// - /// Notifies of bool change - /// - public event EventHandler BoolChange; - /// - /// Notifies of ushort change - /// - public event EventHandler UShortChange; - /// - /// Notifies of string change - /// - public event EventHandler StringChange; - - /// - /// Delegate to get all values - /// - public SPlusValuesDelegate GetAllValuesDelegate { get; set; } - - /// - /// Use a callback to reduce task switch/threading - /// - public SPlusValuesDelegate SetAllPathsDelegate { get; set; } - - /// - /// Unique identifier for instance - /// - public string Key { get; protected set; } - - /// - /// This will be prepended to all paths to allow path swapping or for more organized - /// sub-paths - /// - public string PathPrefix { get; protected set; } - - /// - /// This is added to the end of all paths - /// - public string PathSuffix { get; protected set; } - - /// - /// Indicates if the instance is linked to an object - /// - public bool LinkedToObject { get; protected set; } - - /// - /// Reference to Master instance - /// - protected JsonToSimplMaster Master; - - /// - /// Paths to boolean values in JSON structure - /// - protected Dictionary BoolPaths = new Dictionary(); - /// - /// Paths to numeric values in JSON structure - /// - protected Dictionary UshortPaths = new Dictionary(); - /// - /// Paths to string values in JSON structure - /// - protected Dictionary StringPaths = new Dictionary(); - - /// - /// Call this before doing anything else - /// - /// - /// - /// - /// - public void Initialize(string masterUniqueId, string key, string pathPrefix, string pathSuffix) - { - Key = key; - PathPrefix = pathPrefix; - PathSuffix = pathSuffix; - - Master = J2SGlobal.GetMasterByFile(masterUniqueId); - if (Master != null) - Master.AddChild(this); - else - Debug.Console(1, "JSON Child [{0}] cannot link to master {1}", key, masterUniqueId); - } - - /// - /// Sets the path prefix for the object - /// - /// - public void SetPathPrefix(string pathPrefix) - { - PathPrefix = pathPrefix; - } - /// - /// Set the JPath to evaluate for a given bool out index. - /// - public void SetBoolPath(ushort index, string path) - { - Debug.Console(1, "JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path); - if (path == null || path.Trim() == string.Empty) return; - BoolPaths[index] = path; - } - - /// - /// Set the JPath for a ushort out index. - /// - public void SetUshortPath(ushort index, string path) - { - Debug.Console(1, "JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path); - if (path == null || path.Trim() == string.Empty) return; - UshortPaths[index] = path; - } - - /// - /// Set the JPath for a string output index. - /// - public void SetStringPath(ushort index, string path) - { - Debug.Console(1, "JSON Child[{0}] SetStringPath {1}={2}", Key, index, path); - if (path == null || path.Trim() == string.Empty) return; - StringPaths[index] = path; - } - - /// - /// Evalutates all outputs with defined paths. called by S+ when paths are ready to process - /// and by Master when file is read. - /// - public virtual void ProcessAll() - { - if (!LinkedToObject) - { - Debug.Console(1, this, "Not linked to object in file. Skipping"); - return; - } - - if (SetAllPathsDelegate == null) - { - Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring ProcessAll"); - return; - } - SetAllPathsDelegate(); - foreach (var kvp in BoolPaths) - ProcessBoolPath(kvp.Key); - foreach (var kvp in UshortPaths) - ProcessUshortPath(kvp.Key); - foreach (var kvp in StringPaths) - ProcessStringPath(kvp.Key); - } - - /// - /// Processes a bool property, converting to bool, firing off a BoolChange event - /// - void ProcessBoolPath(ushort index) - { - string response; - if (Process(BoolPaths[index], out response)) - OnBoolChange(response.Equals("true", StringComparison.OrdinalIgnoreCase), - index, JsonToSimplConstants.BoolValueChange); - else { } - // OnBoolChange(false, index, JsonToSimplConstants.BoolValueChange); - } - - // Processes the path to a ushort, converting to ushort if able, twos complement if necessary, firing off UshrtChange event - void ProcessUshortPath(ushort index) { - string response; - if (Process(UshortPaths[index], out response)) { - ushort val; - try { val = Convert.ToInt32(response) < 0 ? (ushort)(Convert.ToInt16(response) + 65536) : Convert.ToUInt16(response); } - catch { val = 0; } - - OnUShortChange(val, index, JsonToSimplConstants.UshortValueChange); - } - else { } - // OnUShortChange(0, index, JsonToSimplConstants.UshortValueChange); - } - - // Processes the path to a string property and fires of a StringChange event. - void ProcessStringPath(ushort index) - { - string response; - if (Process(StringPaths[index], out response)) - OnStringChange(response, index, JsonToSimplConstants.StringValueChange); - else { } - // OnStringChange("", index, JsonToSimplConstants.StringValueChange); - } - - /// - /// Processes the given path. - /// - /// JPath formatted path to the desired property - /// The string value of the property, or a default value if it - /// doesn't exist - /// This will return false in the case that EvaulateAllOnJsonChange - /// is false and the path does not evaluate to a property in the incoming JSON. - bool Process(string path, out string response) - { - path = GetFullPath(path); - Debug.Console(1, "JSON Child[{0}] Processing {1}", Key, path); - response = ""; - if (Master == null) - { - Debug.Console(1, "JSONChild[{0}] cannot process without Master attached", Key); - return false; - } - - if (Master.JsonObject != null && path != string.Empty) - { - bool isCount = false; - path = path.Trim(); - if (path.EndsWith(".Count")) - { - path = path.Remove(path.Length - 6, 6); - isCount = true; - } - try // Catch a strange cast error on a bad path - { - var t = Master.JsonObject.SelectToken(path); - if (t != null) - { - // return the count of children objects - if any - if (isCount) - response = (t.HasValues ? t.Children().Count() : 0).ToString(); - else - response = t.Value(); - Debug.Console(1, " ='{0}'", response); - return true; - } - } - catch - { - response = ""; - } - } - // If the path isn't found, return this to determine whether to pass out the non-value or not. - return false; - } - - - //************************************************************************************************ - // Save-related functions - - - /// - /// Called from Master to read inputs and update their values in master JObject - /// Callback should hit one of the following four methods - /// - public void UpdateInputsForMaster() - { - if (!LinkedToObject) - { - Debug.Console(1, this, "Not linked to object in file. Skipping"); - return; - } - - if (SetAllPathsDelegate == null) - { - Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster"); - return; - } - SetAllPathsDelegate(); - var del = GetAllValuesDelegate; - if (del != null) - GetAllValuesDelegate(); - } - - /// - /// - /// - /// - /// - public void USetBoolValue(ushort key, ushort theValue) - { - SetBoolValue(key, theValue == 1); - } - - /// - /// - /// - /// - /// - public void SetBoolValue(ushort key, bool theValue) - { - if (BoolPaths.ContainsKey(key)) - SetValueOnMaster(BoolPaths[key], new JValue(theValue)); - } - - /// - /// - /// - /// - /// - public void SetUShortValue(ushort key, ushort theValue) - { - if (UshortPaths.ContainsKey(key)) - SetValueOnMaster(UshortPaths[key], new JValue(theValue)); - } - - /// - /// - /// - /// - /// - public void SetStringValue(ushort key, string theValue) - { - if (StringPaths.ContainsKey(key)) - SetValueOnMaster(StringPaths[key], new JValue(theValue)); - } - - /// - /// - /// - /// - /// - public void SetValueOnMaster(string keyPath, JValue valueToSave) - { - var path = GetFullPath(keyPath); - try - { - Debug.Console(1, "JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave); - - //var token = Master.JsonObject.SelectToken(path); - //if (token != null) // The path exists in the file - Master.AddUnsavedValue(path, valueToSave); - } - catch (Exception e) - { - Debug.Console(1, "JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e); - } - } - - /// - /// Called during Process(...) to get the path to a given property. By default, - /// returns PathPrefix+path+PathSuffix. Override to change the way path is built. - /// - protected virtual string GetFullPath(string path) - { - return (PathPrefix != null ? PathPrefix : "") + - path + (PathSuffix != null ? PathSuffix : ""); - } - - // Helpers for events - //****************************************************************************************** - /// - /// Event helper - /// - /// - /// - /// - protected void OnBoolChange(bool state, ushort index, ushort type) - { - var handler = BoolChange; - if (handler != null) - { - var args = new BoolChangeEventArgs(state, type); - args.Index = index; - BoolChange(this, args); - } - } - - //****************************************************************************************** - /// - /// Event helper - /// - /// - /// - /// - protected void OnUShortChange(ushort state, ushort index, ushort type) - { - var handler = UShortChange; - if (handler != null) - { - var args = new UshrtChangeEventArgs(state, type); - args.Index = index; - UShortChange(this, args); - } - } - - /// - /// Event helper - /// - /// - /// - /// - protected void OnStringChange(string value, ushort index, ushort type) - { - var handler = StringChange; - if (handler != null) - { - var args = new StringChangeEventArgs(value, type); - args.Index = index; - StringChange(this, args); - } - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PepperDash.Core.JsonToSimpl +{ + /// + /// Base class for JSON objects + /// + public abstract class JsonToSimplChildObjectBase : IKeyed + { + /// + /// Notifies of bool change + /// + public event EventHandler BoolChange; + /// + /// Notifies of ushort change + /// + public event EventHandler UShortChange; + /// + /// Notifies of string change + /// + public event EventHandler StringChange; + + /// + /// Delegate to get all values + /// + public SPlusValuesDelegate GetAllValuesDelegate { get; set; } + + /// + /// Use a callback to reduce task switch/threading + /// + public SPlusValuesDelegate SetAllPathsDelegate { get; set; } + + /// + /// Unique identifier for instance + /// + public string Key { get; protected set; } + + /// + /// This will be prepended to all paths to allow path swapping or for more organized + /// sub-paths + /// + public string PathPrefix { get; protected set; } + + /// + /// This is added to the end of all paths + /// + public string PathSuffix { get; protected set; } + + /// + /// Indicates if the instance is linked to an object + /// + public bool LinkedToObject { get; protected set; } + + /// + /// Reference to Master instance + /// + protected JsonToSimplMaster Master; + + /// + /// Paths to boolean values in JSON structure + /// + protected Dictionary BoolPaths = new Dictionary(); + /// + /// Paths to numeric values in JSON structure + /// + protected Dictionary UshortPaths = new Dictionary(); + /// + /// Paths to string values in JSON structure + /// + protected Dictionary StringPaths = new Dictionary(); + + /// + /// Call this before doing anything else + /// + /// + /// + /// + /// + public void Initialize(string masterUniqueId, string key, string pathPrefix, string pathSuffix) + { + Key = key; + PathPrefix = pathPrefix; + PathSuffix = pathSuffix; + + Master = J2SGlobal.GetMasterByFile(masterUniqueId); + if (Master != null) + Master.AddChild(this); + else + Debug.Console(1, "JSON Child [{0}] cannot link to master {1}", key, masterUniqueId); + } + + /// + /// Sets the path prefix for the object + /// + /// + public void SetPathPrefix(string pathPrefix) + { + PathPrefix = pathPrefix; + } + /// + /// Set the JPath to evaluate for a given bool out index. + /// + public void SetBoolPath(ushort index, string path) + { + Debug.Console(1, "JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path); + if (path == null || path.Trim() == string.Empty) return; + BoolPaths[index] = path; + } + + /// + /// Set the JPath for a ushort out index. + /// + public void SetUshortPath(ushort index, string path) + { + Debug.Console(1, "JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path); + if (path == null || path.Trim() == string.Empty) return; + UshortPaths[index] = path; + } + + /// + /// Set the JPath for a string output index. + /// + public void SetStringPath(ushort index, string path) + { + Debug.Console(1, "JSON Child[{0}] SetStringPath {1}={2}", Key, index, path); + if (path == null || path.Trim() == string.Empty) return; + StringPaths[index] = path; + } + + /// + /// Evalutates all outputs with defined paths. called by S+ when paths are ready to process + /// and by Master when file is read. + /// + public virtual void ProcessAll() + { + if (!LinkedToObject) + { + Debug.Console(1, this, "Not linked to object in file. Skipping"); + return; + } + + if (SetAllPathsDelegate == null) + { + Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring ProcessAll"); + return; + } + SetAllPathsDelegate(); + foreach (var kvp in BoolPaths) + ProcessBoolPath(kvp.Key); + foreach (var kvp in UshortPaths) + ProcessUshortPath(kvp.Key); + foreach (var kvp in StringPaths) + ProcessStringPath(kvp.Key); + } + + /// + /// Processes a bool property, converting to bool, firing off a BoolChange event + /// + void ProcessBoolPath(ushort index) + { + string response; + if (Process(BoolPaths[index], out response)) + OnBoolChange(response.Equals("true", StringComparison.OrdinalIgnoreCase), + index, JsonToSimplConstants.BoolValueChange); + else { } + // OnBoolChange(false, index, JsonToSimplConstants.BoolValueChange); + } + + // Processes the path to a ushort, converting to ushort if able, twos complement if necessary, firing off UshrtChange event + void ProcessUshortPath(ushort index) { + string response; + if (Process(UshortPaths[index], out response)) { + ushort val; + try { val = Convert.ToInt32(response) < 0 ? (ushort)(Convert.ToInt16(response) + 65536) : Convert.ToUInt16(response); } + catch { val = 0; } + + OnUShortChange(val, index, JsonToSimplConstants.UshortValueChange); + } + else { } + // OnUShortChange(0, index, JsonToSimplConstants.UshortValueChange); + } + + // Processes the path to a string property and fires of a StringChange event. + void ProcessStringPath(ushort index) + { + string response; + if (Process(StringPaths[index], out response)) + OnStringChange(response, index, JsonToSimplConstants.StringValueChange); + else { } + // OnStringChange("", index, JsonToSimplConstants.StringValueChange); + } + + /// + /// Processes the given path. + /// + /// JPath formatted path to the desired property + /// The string value of the property, or a default value if it + /// doesn't exist + /// This will return false in the case that EvaulateAllOnJsonChange + /// is false and the path does not evaluate to a property in the incoming JSON. + bool Process(string path, out string response) + { + path = GetFullPath(path); + Debug.Console(1, "JSON Child[{0}] Processing {1}", Key, path); + response = ""; + if (Master == null) + { + Debug.Console(1, "JSONChild[{0}] cannot process without Master attached", Key); + return false; + } + + if (Master.JsonObject != null && path != string.Empty) + { + bool isCount = false; + path = path.Trim(); + if (path.EndsWith(".Count")) + { + path = path.Remove(path.Length - 6, 6); + isCount = true; + } + try // Catch a strange cast error on a bad path + { + var t = Master.JsonObject.SelectToken(path); + if (t != null) + { + // return the count of children objects - if any + if (isCount) + response = (t.HasValues ? t.Children().Count() : 0).ToString(); + else + response = t.Value(); + Debug.Console(1, " ='{0}'", response); + return true; + } + } + catch + { + response = ""; + } + } + // If the path isn't found, return this to determine whether to pass out the non-value or not. + return false; + } + + + //************************************************************************************************ + // Save-related functions + + + /// + /// Called from Master to read inputs and update their values in master JObject + /// Callback should hit one of the following four methods + /// + public void UpdateInputsForMaster() + { + if (!LinkedToObject) + { + Debug.Console(1, this, "Not linked to object in file. Skipping"); + return; + } + + if (SetAllPathsDelegate == null) + { + Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster"); + return; + } + SetAllPathsDelegate(); + var del = GetAllValuesDelegate; + if (del != null) + GetAllValuesDelegate(); + } + + /// + /// + /// + /// + /// + public void USetBoolValue(ushort key, ushort theValue) + { + SetBoolValue(key, theValue == 1); + } + + /// + /// + /// + /// + /// + public void SetBoolValue(ushort key, bool theValue) + { + if (BoolPaths.ContainsKey(key)) + SetValueOnMaster(BoolPaths[key], new JValue(theValue)); + } + + /// + /// + /// + /// + /// + public void SetUShortValue(ushort key, ushort theValue) + { + if (UshortPaths.ContainsKey(key)) + SetValueOnMaster(UshortPaths[key], new JValue(theValue)); + } + + /// + /// + /// + /// + /// + public void SetStringValue(ushort key, string theValue) + { + if (StringPaths.ContainsKey(key)) + SetValueOnMaster(StringPaths[key], new JValue(theValue)); + } + + /// + /// + /// + /// + /// + public void SetValueOnMaster(string keyPath, JValue valueToSave) + { + var path = GetFullPath(keyPath); + try + { + Debug.Console(1, "JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave); + + //var token = Master.JsonObject.SelectToken(path); + //if (token != null) // The path exists in the file + Master.AddUnsavedValue(path, valueToSave); + } + catch (Exception e) + { + Debug.Console(1, "JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e); + } + } + + /// + /// Called during Process(...) to get the path to a given property. By default, + /// returns PathPrefix+path+PathSuffix. Override to change the way path is built. + /// + protected virtual string GetFullPath(string path) + { + return (PathPrefix != null ? PathPrefix : "") + + path + (PathSuffix != null ? PathSuffix : ""); + } + + // Helpers for events + //****************************************************************************************** + /// + /// Event helper + /// + /// + /// + /// + protected void OnBoolChange(bool state, ushort index, ushort type) + { + var handler = BoolChange; + if (handler != null) + { + var args = new BoolChangeEventArgs(state, type); + args.Index = index; + BoolChange(this, args); + } + } + + //****************************************************************************************** + /// + /// Event helper + /// + /// + /// + /// + protected void OnUShortChange(ushort state, ushort index, ushort type) + { + var handler = UShortChange; + if (handler != null) + { + var args = new UshrtChangeEventArgs(state, type); + args.Index = index; + UShortChange(this, args); + } + } + + /// + /// Event helper + /// + /// + /// + /// + protected void OnStringChange(string value, ushort index, ushort type) + { + var handler = StringChange; + if (handler != null) + { + var args = new StringChangeEventArgs(value, type); + args.Index = index; + StringChange(this, args); + } + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplFileMaster.cs b/src/JsonToSimpl/JsonToSimplFileMaster.cs similarity index 97% rename from Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplFileMaster.cs rename to src/JsonToSimpl/JsonToSimplFileMaster.cs index c5f50d5..6ed8cd3 100644 --- a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplFileMaster.cs +++ b/src/JsonToSimpl/JsonToSimplFileMaster.cs @@ -1,291 +1,291 @@ -using System; -//using System.IO; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronIO; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace PepperDash.Core.JsonToSimpl -{ - /// - /// Represents a JSON file that can be read and written to - /// - public class JsonToSimplFileMaster : JsonToSimplMaster - { - /// - /// Sets the filepath as well as registers this with the Global.Masters list - /// - public string Filepath { get; private set; } - - /// - /// Filepath to the actual file that will be read (Portal or local) - /// - public string ActualFilePath { get; private set; } - - /// - /// - /// - public string Filename { get; private set; } - /// - /// - /// - public string FilePathName { get; private set; } - - /*****************************************************************************************/ - /** Privates **/ - - - // The JSON file in JObject form - // For gathering the incoming data - object StringBuilderLock = new object(); - // To prevent multiple same-file access - static object FileLock = new object(); - - /*****************************************************************************************/ - - /// - /// SIMPL+ default constructor. - /// - public JsonToSimplFileMaster() - { - } - - /// - /// Read, evaluate and udpate status - /// - public void EvaluateFile(string filepath) - { - try - { - OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange); - - var dirSeparator = Path.DirectorySeparatorChar; - var dirSeparatorAlt = Path.AltDirectorySeparatorChar; - - var series = CrestronEnvironment.ProgramCompatibility; - - var is3Series = (eCrestronSeries.Series3 == (series & eCrestronSeries.Series3)); - OnBoolChange(is3Series, 0, - JsonToSimplConstants.ProgramCompatibility3SeriesChange); - - var is4Series = (eCrestronSeries.Series4 == (series & eCrestronSeries.Series4)); - OnBoolChange(is4Series, 0, - JsonToSimplConstants.ProgramCompatibility4SeriesChange); - - var isServer = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server; - OnBoolChange(isServer, 0, - JsonToSimplConstants.DevicePlatformValueChange); - - // get the roomID - var roomId = Crestron.SimplSharp.InitialParametersClass.RoomId; - if (!string.IsNullOrEmpty(roomId)) - { - OnStringChange(roomId, 0, JsonToSimplConstants.RoomIdChange); - } - - // get the roomName - var roomName = Crestron.SimplSharp.InitialParametersClass.RoomName; - if (!string.IsNullOrEmpty(roomName)) - { - OnStringChange(roomName, 0, JsonToSimplConstants.RoomNameChange); - } - - var rootDirectory = Directory.GetApplicationRootDirectory(); - OnStringChange(rootDirectory, 0, JsonToSimplConstants.RootDirectoryChange); - - var splusPath = string.Empty; - if (Regex.IsMatch(filepath, @"user", RegexOptions.IgnoreCase)) - { - if (is4Series) - splusPath = Regex.Replace(filepath, "user", "user", RegexOptions.IgnoreCase); - else if (isServer) - splusPath = Regex.Replace(filepath, "user", "User", RegexOptions.IgnoreCase); - else - splusPath = filepath; - } - - filepath = splusPath.Replace(dirSeparatorAlt, dirSeparator); - - Filepath = string.Format("{1}{0}{2}", dirSeparator, rootDirectory, - filepath.TrimStart(dirSeparator, dirSeparatorAlt)); - - OnStringChange(string.Format("Attempting to evaluate {0}", Filepath), 0, JsonToSimplConstants.StringValueChange); - - if (string.IsNullOrEmpty(Filepath)) - { - OnStringChange(string.Format("Cannot evaluate file. JSON file path not set"), 0, JsonToSimplConstants.StringValueChange); - CrestronConsole.PrintLine("Cannot evaluate file. JSON file path not set"); - return; - } - - // get file directory and name to search - var fileDirectory = Path.GetDirectoryName(Filepath); - var fileName = Path.GetFileName(Filepath); - - OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange); - Debug.Console(1, "Checking '{0}' for '{1}'", fileDirectory, fileName); - - if (Directory.Exists(fileDirectory)) - { - // get the directory info - var directoryInfo = new DirectoryInfo(fileDirectory); - - // get the file to be read - var actualFile = directoryInfo.GetFiles(fileName).FirstOrDefault(); - if (actualFile == null) - { - var msg = string.Format("JSON file not found: {0}", Filepath); - OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange); - CrestronConsole.PrintLine(msg); - ErrorLog.Error(msg); - return; - } - - // \xSE\xR\PDT000-Template_Main_Config-Combined_DSP_v00.02.json - // \USER\PDT000-Template_Main_Config-Combined_DSP_v00.02.json - ActualFilePath = actualFile.FullName; - OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); - OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange); - Debug.Console(1, "Actual JSON file is {0}", ActualFilePath); - - Filename = actualFile.Name; - OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange); - OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange); - Debug.Console(1, "JSON Filename is {0}", Filename); - - - FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator); - OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange); - OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange); - Debug.Console(1, "JSON File Path is {0}", FilePathName); - - var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII); - - JsonObject = JObject.Parse(json); - foreach (var child in Children) - child.ProcessAll(); - - OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange); - } - else - { - OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange); - Debug.Console(1, "'{0}' not found", fileDirectory); - } - } - catch (Exception e) - { - var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message); - OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange); - CrestronConsole.PrintLine(msg); - ErrorLog.Error(msg); - - var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace); - OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange); - CrestronConsole.PrintLine(stackTrace); - ErrorLog.Error(stackTrace); - } - } - - - /// - /// Sets the debug level - /// - /// - public void setDebugLevel(int level) - { - Debug.SetDebugLevel(level); - } - - /// - /// Saves the values to the file - /// - public override void Save() - { - // this code is duplicated in the other masters!!!!!!!!!!!!! - UnsavedValues = new Dictionary(); - // Make each child update their values into master object - foreach (var child in Children) - { - Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key); - child.UpdateInputsForMaster(); - } - - if (UnsavedValues == null || UnsavedValues.Count == 0) - { - Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID); - return; - } - lock (FileLock) - { - Debug.Console(1, "Saving"); - foreach (var path in UnsavedValues.Keys) - { - var tokenToReplace = JsonObject.SelectToken(path); - if (tokenToReplace != null) - {// It's found - tokenToReplace.Replace(UnsavedValues[path]); - Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path); - } - else // No token. Let's make one - { - //http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net - Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path); - - // JContainer jpart = JsonObject; - // // walk down the path and find where it goes - //#warning Does not handle arrays. - // foreach (var part in path.Split('.')) - // { - - // var openPos = part.IndexOf('['); - // if (openPos > -1) - // { - // openPos++; // move to number - // var closePos = part.IndexOf(']'); - // var arrayName = part.Substring(0, openPos - 1); // get the name - // var index = Convert.ToInt32(part.Substring(openPos, closePos - openPos)); - - // // Check if the array itself exists and add the item if so - // if (jpart[arrayName] != null) - // { - // var arrayObj = jpart[arrayName] as JArray; - // var item = arrayObj[index]; - // if (item == null) - // arrayObj.Add(new JObject()); - // } - - // Debug.Console(0, "IGNORING MISSING ARRAY VALUE FOR NOW"); - // continue; - // } - // // Build the - // if (jpart[part] == null) - // jpart.Add(new JProperty(part, new JObject())); - // jpart = jpart[part] as JContainer; - // } - // jpart.Replace(UnsavedValues[path]); - } - } - using (StreamWriter sw = new StreamWriter(ActualFilePath)) - { - try - { - sw.Write(JsonObject.ToString()); - sw.Flush(); - } - catch (Exception e) - { - string err = string.Format("Error writing JSON file:\r{0}", e); - Debug.Console(0, err); - ErrorLog.Warn(err); - return; - } - } - } - } - } -} +using System; +//using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronIO; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PepperDash.Core.JsonToSimpl +{ + /// + /// Represents a JSON file that can be read and written to + /// + public class JsonToSimplFileMaster : JsonToSimplMaster + { + /// + /// Sets the filepath as well as registers this with the Global.Masters list + /// + public string Filepath { get; private set; } + + /// + /// Filepath to the actual file that will be read (Portal or local) + /// + public string ActualFilePath { get; private set; } + + /// + /// + /// + public string Filename { get; private set; } + /// + /// + /// + public string FilePathName { get; private set; } + + /*****************************************************************************************/ + /** Privates **/ + + + // The JSON file in JObject form + // For gathering the incoming data + object StringBuilderLock = new object(); + // To prevent multiple same-file access + static object FileLock = new object(); + + /*****************************************************************************************/ + + /// + /// SIMPL+ default constructor. + /// + public JsonToSimplFileMaster() + { + } + + /// + /// Read, evaluate and udpate status + /// + public void EvaluateFile(string filepath) + { + try + { + OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange); + + var dirSeparator = Path.DirectorySeparatorChar; + var dirSeparatorAlt = Path.AltDirectorySeparatorChar; + + var series = CrestronEnvironment.ProgramCompatibility; + + var is3Series = (eCrestronSeries.Series3 == (series & eCrestronSeries.Series3)); + OnBoolChange(is3Series, 0, + JsonToSimplConstants.ProgramCompatibility3SeriesChange); + + var is4Series = (eCrestronSeries.Series4 == (series & eCrestronSeries.Series4)); + OnBoolChange(is4Series, 0, + JsonToSimplConstants.ProgramCompatibility4SeriesChange); + + var isServer = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server; + OnBoolChange(isServer, 0, + JsonToSimplConstants.DevicePlatformValueChange); + + // get the roomID + var roomId = Crestron.SimplSharp.InitialParametersClass.RoomId; + if (!string.IsNullOrEmpty(roomId)) + { + OnStringChange(roomId, 0, JsonToSimplConstants.RoomIdChange); + } + + // get the roomName + var roomName = Crestron.SimplSharp.InitialParametersClass.RoomName; + if (!string.IsNullOrEmpty(roomName)) + { + OnStringChange(roomName, 0, JsonToSimplConstants.RoomNameChange); + } + + var rootDirectory = Directory.GetApplicationRootDirectory(); + OnStringChange(rootDirectory, 0, JsonToSimplConstants.RootDirectoryChange); + + var splusPath = string.Empty; + if (Regex.IsMatch(filepath, @"user", RegexOptions.IgnoreCase)) + { + if (is4Series) + splusPath = Regex.Replace(filepath, "user", "user", RegexOptions.IgnoreCase); + else if (isServer) + splusPath = Regex.Replace(filepath, "user", "User", RegexOptions.IgnoreCase); + else + splusPath = filepath; + } + + filepath = splusPath.Replace(dirSeparatorAlt, dirSeparator); + + Filepath = string.Format("{1}{0}{2}", dirSeparator, rootDirectory, + filepath.TrimStart(dirSeparator, dirSeparatorAlt)); + + OnStringChange(string.Format("Attempting to evaluate {0}", Filepath), 0, JsonToSimplConstants.StringValueChange); + + if (string.IsNullOrEmpty(Filepath)) + { + OnStringChange(string.Format("Cannot evaluate file. JSON file path not set"), 0, JsonToSimplConstants.StringValueChange); + CrestronConsole.PrintLine("Cannot evaluate file. JSON file path not set"); + return; + } + + // get file directory and name to search + var fileDirectory = Path.GetDirectoryName(Filepath); + var fileName = Path.GetFileName(Filepath); + + OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange); + Debug.Console(1, "Checking '{0}' for '{1}'", fileDirectory, fileName); + + if (Directory.Exists(fileDirectory)) + { + // get the directory info + var directoryInfo = new DirectoryInfo(fileDirectory); + + // get the file to be read + var actualFile = directoryInfo.GetFiles(fileName).FirstOrDefault(); + if (actualFile == null) + { + var msg = string.Format("JSON file not found: {0}", Filepath); + OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange); + CrestronConsole.PrintLine(msg); + ErrorLog.Error(msg); + return; + } + + // \xSE\xR\PDT000-Template_Main_Config-Combined_DSP_v00.02.json + // \USER\PDT000-Template_Main_Config-Combined_DSP_v00.02.json + ActualFilePath = actualFile.FullName; + OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); + OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange); + Debug.Console(1, "Actual JSON file is {0}", ActualFilePath); + + Filename = actualFile.Name; + OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange); + OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange); + Debug.Console(1, "JSON Filename is {0}", Filename); + + + FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator); + OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange); + OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange); + Debug.Console(1, "JSON File Path is {0}", FilePathName); + + var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII); + + JsonObject = JObject.Parse(json); + foreach (var child in Children) + child.ProcessAll(); + + OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange); + } + else + { + OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange); + Debug.Console(1, "'{0}' not found", fileDirectory); + } + } + catch (Exception e) + { + var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message); + OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange); + CrestronConsole.PrintLine(msg); + ErrorLog.Error(msg); + + var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace); + OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange); + CrestronConsole.PrintLine(stackTrace); + ErrorLog.Error(stackTrace); + } + } + + + /// + /// Sets the debug level + /// + /// + public void setDebugLevel(int level) + { + Debug.SetDebugLevel(level); + } + + /// + /// Saves the values to the file + /// + public override void Save() + { + // this code is duplicated in the other masters!!!!!!!!!!!!! + UnsavedValues = new Dictionary(); + // Make each child update their values into master object + foreach (var child in Children) + { + Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key); + child.UpdateInputsForMaster(); + } + + if (UnsavedValues == null || UnsavedValues.Count == 0) + { + Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID); + return; + } + lock (FileLock) + { + Debug.Console(1, "Saving"); + foreach (var path in UnsavedValues.Keys) + { + var tokenToReplace = JsonObject.SelectToken(path); + if (tokenToReplace != null) + {// It's found + tokenToReplace.Replace(UnsavedValues[path]); + Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path); + } + else // No token. Let's make one + { + //http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net + Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path); + + // JContainer jpart = JsonObject; + // // walk down the path and find where it goes + //#warning Does not handle arrays. + // foreach (var part in path.Split('.')) + // { + + // var openPos = part.IndexOf('['); + // if (openPos > -1) + // { + // openPos++; // move to number + // var closePos = part.IndexOf(']'); + // var arrayName = part.Substring(0, openPos - 1); // get the name + // var index = Convert.ToInt32(part.Substring(openPos, closePos - openPos)); + + // // Check if the array itself exists and add the item if so + // if (jpart[arrayName] != null) + // { + // var arrayObj = jpart[arrayName] as JArray; + // var item = arrayObj[index]; + // if (item == null) + // arrayObj.Add(new JObject()); + // } + + // Debug.Console(0, "IGNORING MISSING ARRAY VALUE FOR NOW"); + // continue; + // } + // // Build the + // if (jpart[part] == null) + // jpart.Add(new JProperty(part, new JObject())); + // jpart = jpart[part] as JContainer; + // } + // jpart.Replace(UnsavedValues[path]); + } + } + using (StreamWriter sw = new StreamWriter(ActualFilePath)) + { + try + { + sw.Write(JsonObject.ToString()); + sw.Flush(); + } + catch (Exception e) + { + string err = string.Format("Error writing JSON file:\r{0}", e); + Debug.Console(0, err); + ErrorLog.Warn(err); + return; + } + } + } + } + } +} diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplFixedPathObject.cs b/src/JsonToSimpl/JsonToSimplFixedPathObject.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplFixedPathObject.cs rename to src/JsonToSimpl/JsonToSimplFixedPathObject.cs diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplGenericMaster.cs b/src/JsonToSimpl/JsonToSimplGenericMaster.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplGenericMaster.cs rename to src/JsonToSimpl/JsonToSimplGenericMaster.cs diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplMaster.cs b/src/JsonToSimpl/JsonToSimplMaster.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplMaster.cs rename to src/JsonToSimpl/JsonToSimplMaster.cs diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplPortalFileMaster.cs b/src/JsonToSimpl/JsonToSimplPortalFileMaster.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplPortalFileMaster.cs rename to src/JsonToSimpl/JsonToSimplPortalFileMaster.cs index ca307e5..2a2b535 100644 --- a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplPortalFileMaster.cs +++ b/src/JsonToSimpl/JsonToSimplPortalFileMaster.cs @@ -1,195 +1,195 @@ -using System; -//using System.IO; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; -using Crestron.SimplSharp.CrestronIO; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -using PepperDash.Core.Config; - -namespace PepperDash.Core.JsonToSimpl -{ - /// - /// Portal File Master - /// - public class JsonToSimplPortalFileMaster : JsonToSimplMaster - { - /// - /// Sets the filepath as well as registers this with the Global.Masters list - /// - public string PortalFilepath { get; private set; } - - /// - /// File path of the actual file being read (Portal or local) - /// - public string ActualFilePath { get; private set; } - - /*****************************************************************************************/ - /** Privates **/ - - // To prevent multiple same-file access - object StringBuilderLock = new object(); - static object FileLock = new object(); - - /*****************************************************************************************/ - - /// - /// SIMPL+ default constructor. - /// - public JsonToSimplPortalFileMaster() - { - } - - /// - /// Read, evaluate and udpate status - /// - public void EvaluateFile(string portalFilepath) - { - PortalFilepath = portalFilepath; - - OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange); - if (string.IsNullOrEmpty(PortalFilepath)) - { - CrestronConsole.PrintLine("Cannot evaluate file. JSON file path not set"); - return; - } - - // Resolve possible wildcarded filename - - // If the portal file is xyz.json, then - // the file we want to check for first will be called xyz.local.json - var localFilepath = Path.ChangeExtension(PortalFilepath, "local.json"); - Debug.Console(0, this, "Checking for local file {0}", localFilepath); - var actualLocalFile = GetActualFileInfoFromPath(localFilepath); - - if (actualLocalFile != null) - { - ActualFilePath = actualLocalFile.FullName; - OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); - } - // If the local file does not exist, then read the portal file xyz.json - // and create the local. - else - { - Debug.Console(1, this, "Local JSON file not found {0}\rLoading portal JSON file", localFilepath); - var actualPortalFile = GetActualFileInfoFromPath(portalFilepath); - if (actualPortalFile != null) - { - var newLocalPath = Path.ChangeExtension(actualPortalFile.FullName, "local.json"); - // got the portal file, hand off to the merge / save method - PortalConfigReader.ReadAndMergeFileIfNecessary(actualPortalFile.FullName, newLocalPath); - ActualFilePath = newLocalPath; - OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); - } - else - { - var msg = string.Format("Portal JSON file not found: {0}", PortalFilepath); - Debug.Console(1, this, msg); - ErrorLog.Error(msg); - return; - } - } - - // At this point we should have a local file. Do it. - Debug.Console(1, "Reading local JSON file {0}", ActualFilePath); - - string json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII); - - try - { - JsonObject = JObject.Parse(json); - foreach (var child in Children) - child.ProcessAll(); - OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange); - } - catch (Exception e) - { - var msg = string.Format("JSON parsing failed:\r{0}", e); - CrestronConsole.PrintLine(msg); - ErrorLog.Error(msg); - return; - } - } - - /// - /// Returns the FileInfo object for a given path, with possible wildcards - /// - /// - /// - FileInfo GetActualFileInfoFromPath(string path) - { - var dir = Path.GetDirectoryName(path); - var localFilename = Path.GetFileName(path); - var directory = new DirectoryInfo(dir); - // search the directory for the file w/ wildcards - return directory.GetFiles(localFilename).FirstOrDefault(); - } - - /// - /// - /// - /// - public void setDebugLevel(int level) - { - Debug.SetDebugLevel(level); - } - - /// - /// - /// - public override void Save() - { - // this code is duplicated in the other masters!!!!!!!!!!!!! - UnsavedValues = new Dictionary(); - // Make each child update their values into master object - foreach (var child in Children) - { - Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key); - child.UpdateInputsForMaster(); - } - - if (UnsavedValues == null || UnsavedValues.Count == 0) - { - Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID); - return; - } - lock (FileLock) - { - Debug.Console(1, "Saving"); - foreach (var path in UnsavedValues.Keys) - { - var tokenToReplace = JsonObject.SelectToken(path); - if (tokenToReplace != null) - {// It's found - tokenToReplace.Replace(UnsavedValues[path]); - Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path); - } - else // No token. Let's make one - { - //http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net - Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path); - - } - } - using (StreamWriter sw = new StreamWriter(ActualFilePath)) - { - try - { - sw.Write(JsonObject.ToString()); - sw.Flush(); - } - catch (Exception e) - { - string err = string.Format("Error writing JSON file:\r{0}", e); - Debug.Console(0, err); - ErrorLog.Warn(err); - return; - } - } - } - } - } -} +using System; +//using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronIO; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using PepperDash.Core.Config; + +namespace PepperDash.Core.JsonToSimpl +{ + /// + /// Portal File Master + /// + public class JsonToSimplPortalFileMaster : JsonToSimplMaster + { + /// + /// Sets the filepath as well as registers this with the Global.Masters list + /// + public string PortalFilepath { get; private set; } + + /// + /// File path of the actual file being read (Portal or local) + /// + public string ActualFilePath { get; private set; } + + /*****************************************************************************************/ + /** Privates **/ + + // To prevent multiple same-file access + object StringBuilderLock = new object(); + static object FileLock = new object(); + + /*****************************************************************************************/ + + /// + /// SIMPL+ default constructor. + /// + public JsonToSimplPortalFileMaster() + { + } + + /// + /// Read, evaluate and udpate status + /// + public void EvaluateFile(string portalFilepath) + { + PortalFilepath = portalFilepath; + + OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange); + if (string.IsNullOrEmpty(PortalFilepath)) + { + CrestronConsole.PrintLine("Cannot evaluate file. JSON file path not set"); + return; + } + + // Resolve possible wildcarded filename + + // If the portal file is xyz.json, then + // the file we want to check for first will be called xyz.local.json + var localFilepath = Path.ChangeExtension(PortalFilepath, "local.json"); + Debug.Console(0, this, "Checking for local file {0}", localFilepath); + var actualLocalFile = GetActualFileInfoFromPath(localFilepath); + + if (actualLocalFile != null) + { + ActualFilePath = actualLocalFile.FullName; + OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); + } + // If the local file does not exist, then read the portal file xyz.json + // and create the local. + else + { + Debug.Console(1, this, "Local JSON file not found {0}\rLoading portal JSON file", localFilepath); + var actualPortalFile = GetActualFileInfoFromPath(portalFilepath); + if (actualPortalFile != null) + { + var newLocalPath = Path.ChangeExtension(actualPortalFile.FullName, "local.json"); + // got the portal file, hand off to the merge / save method + PortalConfigReader.ReadAndMergeFileIfNecessary(actualPortalFile.FullName, newLocalPath); + ActualFilePath = newLocalPath; + OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); + } + else + { + var msg = string.Format("Portal JSON file not found: {0}", PortalFilepath); + Debug.Console(1, this, msg); + ErrorLog.Error(msg); + return; + } + } + + // At this point we should have a local file. Do it. + Debug.Console(1, "Reading local JSON file {0}", ActualFilePath); + + string json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII); + + try + { + JsonObject = JObject.Parse(json); + foreach (var child in Children) + child.ProcessAll(); + OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange); + } + catch (Exception e) + { + var msg = string.Format("JSON parsing failed:\r{0}", e); + CrestronConsole.PrintLine(msg); + ErrorLog.Error(msg); + return; + } + } + + /// + /// Returns the FileInfo object for a given path, with possible wildcards + /// + /// + /// + FileInfo GetActualFileInfoFromPath(string path) + { + var dir = Path.GetDirectoryName(path); + var localFilename = Path.GetFileName(path); + var directory = new DirectoryInfo(dir); + // search the directory for the file w/ wildcards + return directory.GetFiles(localFilename).FirstOrDefault(); + } + + /// + /// + /// + /// + public void setDebugLevel(int level) + { + Debug.SetDebugLevel(level); + } + + /// + /// + /// + public override void Save() + { + // this code is duplicated in the other masters!!!!!!!!!!!!! + UnsavedValues = new Dictionary(); + // Make each child update their values into master object + foreach (var child in Children) + { + Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key); + child.UpdateInputsForMaster(); + } + + if (UnsavedValues == null || UnsavedValues.Count == 0) + { + Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID); + return; + } + lock (FileLock) + { + Debug.Console(1, "Saving"); + foreach (var path in UnsavedValues.Keys) + { + var tokenToReplace = JsonObject.SelectToken(path); + if (tokenToReplace != null) + {// It's found + tokenToReplace.Replace(UnsavedValues[path]); + Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path); + } + else // No token. Let's make one + { + //http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net + Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path); + + } + } + using (StreamWriter sw = new StreamWriter(ActualFilePath)) + { + try + { + sw.Write(JsonObject.ToString()); + sw.Flush(); + } + catch (Exception e) + { + string err = string.Format("Error writing JSON file:\r{0}", e); + Debug.Console(0, err); + ErrorLog.Warn(err); + return; + } + } + } + } + } +} diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/REMOVE JsonToSimplFixedPathObject.cs b/src/JsonToSimpl/REMOVE JsonToSimplFixedPathObject.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/JsonToSimpl/REMOVE JsonToSimplFixedPathObject.cs rename to src/JsonToSimpl/REMOVE JsonToSimplFixedPathObject.cs diff --git a/Pepperdash Core/Pepperdash Core/Logging/Debug.cs b/src/Logging/Debug.cs similarity index 97% rename from Pepperdash Core/Pepperdash Core/Logging/Debug.cs rename to src/Logging/Debug.cs index 1a84968..62bca73 100644 --- a/Pepperdash Core/Pepperdash Core/Logging/Debug.cs +++ b/src/Logging/Debug.cs @@ -1,625 +1,625 @@ -using System; -using System.Collections.Generic; -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 -{ - /// - /// Contains debug commands for use in various situations - /// - 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 OldFilePathPrefix = @"\nvram\debug\"; - - /// - /// Describes the new 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 NewFilePathPrefix = @"\user\debug\"; - - /// - /// The name of the file containing the current debug settings. - /// - public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber); - - /// - /// Debug level to set for a given program. - /// - public static int Level { get; private set; } - - /// - /// When this is true, the configuration file will NOT be loaded until triggered by either a console command or a signal - /// - public static bool DoNotLoadOnNextBoot { get; private set; } - - private static DebugContextCollection _contexts; - - private const int SaveTimeoutMs = 30000; - - /// - /// Version for the currently loaded PepperDashCore dll - /// - public static string PepperDashCoreVersion { get; private set; } - - private static CTimer _saveTimer; - - /// - /// When true, the IncludedExcludedKeys dict will contain keys to include. - /// When false (default), IncludedExcludedKeys will contain keys to exclude. - /// - private static bool _excludeAllMode; - - //static bool ExcludeNoKeyMessages; - - private static readonly Dictionary IncludedExcludedKeys; - - static Debug() - { - // Get the assembly version and print it to console and the log - GetVersion(); - - string msg = ""; - - if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) - { - msg = string.Format("[App {0}] Using PepperDash_Core v{1}", InitialParametersClass.ApplicationNumber, PepperDashCoreVersion); - } - else if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) - { - msg = string.Format("[Room {0}] Using PepperDash_Core v{1}", InitialParametersClass.RoomId, PepperDashCoreVersion); - } - - CrestronConsole.PrintLine(msg); - - LogError(ErrorLogLevel.Notice, msg); - - IncludedExcludedKeys = new Dictionary(); - - //CrestronDataStoreStatic.InitCrestronDataStore(); - if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro) - { - // Add command to console - CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot", - "donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator); - - 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(); - - var context = _contexts.GetOrCreateItem("DEFAULT"); - Level = context.Level; - DoNotLoadOnNextBoot = context.DoNotLoadOnNextBoot; - - if(DoNotLoadOnNextBoot) - CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber)); - - 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); - } - } - - private static void GetVersion() - { - var assembly = Assembly.GetExecutingAssembly(); - var ver = - assembly - .GetCustomAttributes(typeof (AssemblyInformationalVersionAttribute), false); - - if (ver != null && ver.Length > 0) - { - var verAttribute = ver[0] as AssemblyInformationalVersionAttribute; - - if (verAttribute != null) - { - PepperDashCoreVersion = verAttribute.InformationalVersion; - } - } - else - { - var version = assembly.GetName().Version; - PepperDashCoreVersion = string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, - version.Revision); - } - } - - /// - /// 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]"); - } - } - - /// - /// Callback for console command - /// - /// - public static void SetDoNotLoadOnNextBootFromConsole(string stateString) - { - try - { - if (string.IsNullOrEmpty(stateString.Trim())) - { - CrestronConsole.PrintLine("DoNotLoadOnNextBoot = {0}", DoNotLoadOnNextBoot); - return; - } - - SetDoNotLoadOnNextBoot(Boolean.Parse(stateString)); - } - catch - { - CrestronConsole.PrintLine("Usage: donotloadonnextboot:P [true/false]"); - } - } - - /// - /// Callback for console command - /// - /// - 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; - 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); - } - } - - /// - /// sets the settings for a device or creates a new entry - /// - /// - /// - /// - public static void SetDeviceDebugSettings(string deviceKey, object settings) - { - _contexts.SetDebugSettingsForKey(deviceKey, settings); - SaveMemoryOnTimeout(); - } - - /// - /// Gets the device settings for a device by key or returns null - /// - /// - /// - public static object GetDeviceDebugSettingsForKey(string deviceKey) - { - return _contexts.GetDebugSettingsForKey(deviceKey); - } - - /// - /// Sets the flag to prevent application starting on next boot - /// - /// - public static void SetDoNotLoadOnNextBoot(bool state) - { - DoNotLoadOnNextBoot = state; - _contexts.GetOrCreateItem("DEFAULT").DoNotLoadOnNextBoot = state; - SaveMemoryOnTimeout(); - - CrestronConsole.PrintLine("[Application {0}], Do Not Start on Next Boot set to {1}", - InitialParametersClass.ApplicationNumber, DoNotLoadOnNextBoot); - } - - /// - /// - /// - 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 (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) - { - var logString = string.Format("[level {0}] {1}", level, string.Format(format, items)); - - LogError(ErrorLogLevel.Notice, logString); - return; - } - - if(Level < level) - { - return; - } - - 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)); - } - - /// - /// Prints message to console if current debug level is equal to or higher than the level of this message. Always sends message to Error Log. - /// Uses CrestronConsole.PrintLine. - /// - 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)); - if (errorLogLevel != ErrorLogLevel.None) - { - 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); - if (errorLogLevel != ErrorLogLevel.None) - { - 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. - /// - 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) - { - - var msg = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? string.Format("App {0}:{1}", InitialParametersClass.ApplicationNumber, str) : string.Format("Room {0}:{1}", InitialParametersClass.RoomId, 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() - { - Console(0, "Saving debug settings"); - 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); - - var fileName = GetMemoryFileName(); - - Console(0, ErrorLogLevel.Notice, "Loading debug settings file from {0}", fileName); - - using (var sw = new StreamWriter(fileName)) - { - var json = JsonConvert.SerializeObject(_contexts); - sw.Write(json); - sw.Flush(); - } - } - - /// - /// - /// - static void LoadMemory() - { - var file = GetMemoryFileName(); - if (File.Exists(file)) - { - using (var sr = new StreamReader(file)) - { - var json = sr.ReadToEnd(); - _contexts = JsonConvert.DeserializeObject(json); - - if (_contexts != null) - { - 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() - { - if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) - { - CheckForMigration(); - return string.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); - } - - return string.Format("{0}{1}user{1}debugSettings{1}{2}.json",Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId); - } - - private static void CheckForMigration() - { - var oldFilePath = String.Format(@"\nvram\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); - var newFilePath = String.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); - - //check for file at old path - if (!File.Exists(oldFilePath)) - { - Console(0, ErrorLogLevel.Notice, - String.Format( - @"Debug settings file migration not necessary. Using file at \user\debugSettings\program{0}", - InitialParametersClass.ApplicationNumber)); - - return; - } - - //create the new directory if it doesn't already exist - if (!Directory.Exists(@"\user\debugSettings")) - { - Directory.CreateDirectory(@"\user\debugSettings"); - } - - Console(0, ErrorLogLevel.Notice, - String.Format( - @"File found at \nvram\debugSettings\program{0}. Migrating to \user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber)); - - //Copy file from old path to new path, then delete it. This will overwrite the existing file - File.Copy(oldFilePath, newFilePath, true); - File.Delete(oldFilePath); - - //Check if the old directory is empty, then delete it if it is - if (Directory.GetFiles(@"\nvram\debugSettings").Length > 0) - { - return; - } - - Directory.Delete(@"\nvram\debugSettings"); - } - - /// - /// Error level to for message to be logged at - /// - public enum ErrorLogLevel - { - /// - /// Error - /// - Error, - /// - /// Warning - /// - Warning, - /// - /// Notice - /// - Notice, - /// - /// None - /// - None, - } - } +using System; +using System.Collections.Generic; +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 +{ + /// + /// Contains debug commands for use in various situations + /// + 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 OldFilePathPrefix = @"\nvram\debug\"; + + /// + /// Describes the new 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 NewFilePathPrefix = @"\user\debug\"; + + /// + /// The name of the file containing the current debug settings. + /// + public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber); + + /// + /// Debug level to set for a given program. + /// + public static int Level { get; private set; } + + /// + /// When this is true, the configuration file will NOT be loaded until triggered by either a console command or a signal + /// + public static bool DoNotLoadOnNextBoot { get; private set; } + + private static DebugContextCollection _contexts; + + private const int SaveTimeoutMs = 30000; + + /// + /// Version for the currently loaded PepperDashCore dll + /// + public static string PepperDashCoreVersion { get; private set; } + + private static CTimer _saveTimer; + + /// + /// When true, the IncludedExcludedKeys dict will contain keys to include. + /// When false (default), IncludedExcludedKeys will contain keys to exclude. + /// + private static bool _excludeAllMode; + + //static bool ExcludeNoKeyMessages; + + private static readonly Dictionary IncludedExcludedKeys; + + static Debug() + { + // Get the assembly version and print it to console and the log + GetVersion(); + + string msg = ""; + + if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) + { + msg = string.Format("[App {0}] Using PepperDash_Core v{1}", InitialParametersClass.ApplicationNumber, PepperDashCoreVersion); + } + else if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) + { + msg = string.Format("[Room {0}] Using PepperDash_Core v{1}", InitialParametersClass.RoomId, PepperDashCoreVersion); + } + + CrestronConsole.PrintLine(msg); + + LogError(ErrorLogLevel.Notice, msg); + + IncludedExcludedKeys = new Dictionary(); + + //CrestronDataStoreStatic.InitCrestronDataStore(); + if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro) + { + // Add command to console + CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot", + "donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator); + + 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(); + + var context = _contexts.GetOrCreateItem("DEFAULT"); + Level = context.Level; + DoNotLoadOnNextBoot = context.DoNotLoadOnNextBoot; + + if(DoNotLoadOnNextBoot) + CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber)); + + 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); + } + } + + private static void GetVersion() + { + var assembly = Assembly.GetExecutingAssembly(); + var ver = + assembly + .GetCustomAttributes(typeof (AssemblyInformationalVersionAttribute), false); + + if (ver != null && ver.Length > 0) + { + var verAttribute = ver[0] as AssemblyInformationalVersionAttribute; + + if (verAttribute != null) + { + PepperDashCoreVersion = verAttribute.InformationalVersion; + } + } + else + { + var version = assembly.GetName().Version; + PepperDashCoreVersion = string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, + version.Revision); + } + } + + /// + /// 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]"); + } + } + + /// + /// Callback for console command + /// + /// + public static void SetDoNotLoadOnNextBootFromConsole(string stateString) + { + try + { + if (string.IsNullOrEmpty(stateString.Trim())) + { + CrestronConsole.PrintLine("DoNotLoadOnNextBoot = {0}", DoNotLoadOnNextBoot); + return; + } + + SetDoNotLoadOnNextBoot(Boolean.Parse(stateString)); + } + catch + { + CrestronConsole.PrintLine("Usage: donotloadonnextboot:P [true/false]"); + } + } + + /// + /// Callback for console command + /// + /// + 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; + 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); + } + } + + /// + /// sets the settings for a device or creates a new entry + /// + /// + /// + /// + public static void SetDeviceDebugSettings(string deviceKey, object settings) + { + _contexts.SetDebugSettingsForKey(deviceKey, settings); + SaveMemoryOnTimeout(); + } + + /// + /// Gets the device settings for a device by key or returns null + /// + /// + /// + public static object GetDeviceDebugSettingsForKey(string deviceKey) + { + return _contexts.GetDebugSettingsForKey(deviceKey); + } + + /// + /// Sets the flag to prevent application starting on next boot + /// + /// + public static void SetDoNotLoadOnNextBoot(bool state) + { + DoNotLoadOnNextBoot = state; + _contexts.GetOrCreateItem("DEFAULT").DoNotLoadOnNextBoot = state; + SaveMemoryOnTimeout(); + + CrestronConsole.PrintLine("[Application {0}], Do Not Start on Next Boot set to {1}", + InitialParametersClass.ApplicationNumber, DoNotLoadOnNextBoot); + } + + /// + /// + /// + 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 (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) + { + var logString = string.Format("[level {0}] {1}", level, string.Format(format, items)); + + LogError(ErrorLogLevel.Notice, logString); + return; + } + + if(Level < level) + { + return; + } + + 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)); + } + + /// + /// Prints message to console if current debug level is equal to or higher than the level of this message. Always sends message to Error Log. + /// Uses CrestronConsole.PrintLine. + /// + 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)); + if (errorLogLevel != ErrorLogLevel.None) + { + 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); + if (errorLogLevel != ErrorLogLevel.None) + { + 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. + /// + 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) + { + + var msg = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? string.Format("App {0}:{1}", InitialParametersClass.ApplicationNumber, str) : string.Format("Room {0}:{1}", InitialParametersClass.RoomId, 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() + { + Console(0, "Saving debug settings"); + 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); + + var fileName = GetMemoryFileName(); + + Console(0, ErrorLogLevel.Notice, "Loading debug settings file from {0}", fileName); + + using (var sw = new StreamWriter(fileName)) + { + var json = JsonConvert.SerializeObject(_contexts); + sw.Write(json); + sw.Flush(); + } + } + + /// + /// + /// + static void LoadMemory() + { + var file = GetMemoryFileName(); + if (File.Exists(file)) + { + using (var sr = new StreamReader(file)) + { + var json = sr.ReadToEnd(); + _contexts = JsonConvert.DeserializeObject(json); + + if (_contexts != null) + { + 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() + { + if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) + { + CheckForMigration(); + return string.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); + } + + return string.Format("{0}{1}user{1}debugSettings{1}{2}.json",Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId); + } + + private static void CheckForMigration() + { + var oldFilePath = String.Format(@"\nvram\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); + var newFilePath = String.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); + + //check for file at old path + if (!File.Exists(oldFilePath)) + { + Console(0, ErrorLogLevel.Notice, + String.Format( + @"Debug settings file migration not necessary. Using file at \user\debugSettings\program{0}", + InitialParametersClass.ApplicationNumber)); + + return; + } + + //create the new directory if it doesn't already exist + if (!Directory.Exists(@"\user\debugSettings")) + { + Directory.CreateDirectory(@"\user\debugSettings"); + } + + Console(0, ErrorLogLevel.Notice, + String.Format( + @"File found at \nvram\debugSettings\program{0}. Migrating to \user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber)); + + //Copy file from old path to new path, then delete it. This will overwrite the existing file + File.Copy(oldFilePath, newFilePath, true); + File.Delete(oldFilePath); + + //Check if the old directory is empty, then delete it if it is + if (Directory.GetFiles(@"\nvram\debugSettings").Length > 0) + { + return; + } + + Directory.Delete(@"\nvram\debugSettings"); + } + + /// + /// Error level to for message to be logged at + /// + public enum ErrorLogLevel + { + /// + /// Error + /// + Error, + /// + /// Warning + /// + Warning, + /// + /// Notice + /// + Notice, + /// + /// None + /// + None, + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Logging/DebugContext.cs b/src/Logging/DebugContext.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Logging/DebugContext.cs rename to src/Logging/DebugContext.cs diff --git a/Pepperdash Core/Pepperdash Core/Logging/DebugMemory.cs b/src/Logging/DebugMemory.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Logging/DebugMemory.cs rename to src/Logging/DebugMemory.cs diff --git a/Pepperdash Core/Pepperdash Core/Network/DiscoveryThings.cs b/src/Network/DiscoveryThings.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Network/DiscoveryThings.cs rename to src/Network/DiscoveryThings.cs diff --git a/Pepperdash Core/Pepperdash Core/PasswordManagement/Config.cs b/src/PasswordManagement/Config.cs similarity index 95% rename from Pepperdash Core/Pepperdash Core/PasswordManagement/Config.cs rename to src/PasswordManagement/Config.cs index 4d6b035..22aa488 100644 --- a/Pepperdash Core/Pepperdash Core/PasswordManagement/Config.cs +++ b/src/PasswordManagement/Config.cs @@ -1,26 +1,26 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core.PasswordManagement -{ - /// - /// JSON password configuration - /// - public class PasswordConfig - { - /// - /// Password object configured password - /// - public string password { get; set; } - /// - /// Constructor - /// - public PasswordConfig() - { - - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.PasswordManagement +{ + /// + /// JSON password configuration + /// + public class PasswordConfig + { + /// + /// Password object configured password + /// + public string password { get; set; } + /// + /// Constructor + /// + public PasswordConfig() + { + + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/PasswordManagement/Constants.cs b/src/PasswordManagement/Constants.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/PasswordManagement/Constants.cs rename to src/PasswordManagement/Constants.cs index 8ba89e0..65a1bf4 100644 --- a/Pepperdash Core/Pepperdash Core/PasswordManagement/Constants.cs +++ b/src/PasswordManagement/Constants.cs @@ -1,57 +1,57 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core.PasswordManagement -{ - /// - /// Constants - /// - public class PasswordManagementConstants - { - /// - /// Generic boolean value change constant - /// - public const ushort BoolValueChange = 1; - /// - /// Evaluated boolean change constant - /// - public const ushort PasswordInitializedChange = 2; - /// - /// Update busy change const - /// - public const ushort PasswordUpdateBusyChange = 3; - /// - /// Password is valid change constant - /// - public const ushort PasswordValidationChange = 4; - /// - /// Password LED change constant - /// - public const ushort PasswordLedFeedbackChange = 5; - - /// - /// Generic ushort value change constant - /// - public const ushort UshrtValueChange = 101; - /// - /// Password count - /// - public const ushort PasswordManagerCountChange = 102; - /// - /// Password selecte index change constant - /// - public const ushort PasswordSelectIndexChange = 103; - /// - /// Password length - /// - public const ushort PasswordLengthChange = 104; - - /// - /// Generic string value change constant - /// - public const ushort StringValueChange = 201; - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.PasswordManagement +{ + /// + /// Constants + /// + public class PasswordManagementConstants + { + /// + /// Generic boolean value change constant + /// + public const ushort BoolValueChange = 1; + /// + /// Evaluated boolean change constant + /// + public const ushort PasswordInitializedChange = 2; + /// + /// Update busy change const + /// + public const ushort PasswordUpdateBusyChange = 3; + /// + /// Password is valid change constant + /// + public const ushort PasswordValidationChange = 4; + /// + /// Password LED change constant + /// + public const ushort PasswordLedFeedbackChange = 5; + + /// + /// Generic ushort value change constant + /// + public const ushort UshrtValueChange = 101; + /// + /// Password count + /// + public const ushort PasswordManagerCountChange = 102; + /// + /// Password selecte index change constant + /// + public const ushort PasswordSelectIndexChange = 103; + /// + /// Password length + /// + public const ushort PasswordLengthChange = 104; + + /// + /// Generic string value change constant + /// + public const ushort StringValueChange = 201; + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/PasswordManagement/OLD-ARRAY-Config.cs b/src/PasswordManagement/OLD-ARRAY-Config.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/PasswordManagement/OLD-ARRAY-Config.cs rename to src/PasswordManagement/OLD-ARRAY-Config.cs index c49df19..8167c4f 100644 --- a/Pepperdash Core/Pepperdash Core/PasswordManagement/OLD-ARRAY-Config.cs +++ b/src/PasswordManagement/OLD-ARRAY-Config.cs @@ -1,149 +1,149 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core.PasswordManagement -{ - // Example JSON password array configuration object - //{ - // "global":{ - // "passwords":[ - // { - // "key": "Password01", - // "name": "Technician Password", - // "enabled": true, - // "password": "1988" - // } - // ] - // } - //} - - /// - /// JSON password array configuration object - /// - //public class PasswordConfig - //{ - // /// - // /// Key used to search for object in JSON array - // /// - // public string key { get; set; } - // /// - // /// Friendly name of password object - // /// - // public string name { get; set; } - // /// - // /// Password object enabled - // /// - // public bool enabled { get; set; } - // /// - // /// - // /// - // public ushort simplEnabled - // { - // get { return (ushort)(enabled ? 1 : 0); } - // set { enabled = Convert.ToBoolean(value); } - // } - // /// - // /// Password object configured password - // /// - // public string password { get; set; } - // /// - // /// Password type - // /// - // private int type { get; set; } - // /// - // /// Password Type for S+ - // /// - // public ushort simplType - // { - // get { return Convert.ToUInt16(type); } - // set { type = value; } - // } - // /// - // /// Password path - // /// **FUTURE** implementation of saving passwords recieved from Fusion or other external sources back to config - // /// - // public string path { get; set; } - // /// - // /// Constructor - // /// - // public PasswordConfig() - // { - // simplEnabled = 0; - // simplType = 0; - // } - //} - - // Example JSON password collections configuration object - //{ - // "global": { - // "passwords": { - // "1": { - // "name": "Technician Password", - // "password": "2468" - // }, - // "2": { - // "name": "System Password", - // "password": "123456" - // }, - // "3": { - // "name": "Master Password", - // "password": "abc123" - // }, - // "5": { - // "name": "Backdoor Password", - // "password": "1988" - // }, - // "10": { - // "name": "Backdoor Password", - // "password": "1988" - // } - // } - // } - //} - - /// - /// JSON password array configuration object - /// - public class PasswordConfig - { - /// - /// Password object configured password - /// - public string password { get; set; } - /// - /// Constructor - /// - public PasswordConfig() - { - - } - } - - /// - /// Global JSON object - /// - //public class GlobalConfig - //{ - // //public List passwords { get; set; } - // public Dictionary passwords { get; set; } - - // /// - // /// Constructor - // /// - // public GlobalConfig() - // { - - // } - //} - - /// - /// Root JSON object - /// - //public class RootObject - //{ - // public GlobalConfig global { get; set; } - //} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.PasswordManagement +{ + // Example JSON password array configuration object + //{ + // "global":{ + // "passwords":[ + // { + // "key": "Password01", + // "name": "Technician Password", + // "enabled": true, + // "password": "1988" + // } + // ] + // } + //} + + /// + /// JSON password array configuration object + /// + //public class PasswordConfig + //{ + // /// + // /// Key used to search for object in JSON array + // /// + // public string key { get; set; } + // /// + // /// Friendly name of password object + // /// + // public string name { get; set; } + // /// + // /// Password object enabled + // /// + // public bool enabled { get; set; } + // /// + // /// + // /// + // public ushort simplEnabled + // { + // get { return (ushort)(enabled ? 1 : 0); } + // set { enabled = Convert.ToBoolean(value); } + // } + // /// + // /// Password object configured password + // /// + // public string password { get; set; } + // /// + // /// Password type + // /// + // private int type { get; set; } + // /// + // /// Password Type for S+ + // /// + // public ushort simplType + // { + // get { return Convert.ToUInt16(type); } + // set { type = value; } + // } + // /// + // /// Password path + // /// **FUTURE** implementation of saving passwords recieved from Fusion or other external sources back to config + // /// + // public string path { get; set; } + // /// + // /// Constructor + // /// + // public PasswordConfig() + // { + // simplEnabled = 0; + // simplType = 0; + // } + //} + + // Example JSON password collections configuration object + //{ + // "global": { + // "passwords": { + // "1": { + // "name": "Technician Password", + // "password": "2468" + // }, + // "2": { + // "name": "System Password", + // "password": "123456" + // }, + // "3": { + // "name": "Master Password", + // "password": "abc123" + // }, + // "5": { + // "name": "Backdoor Password", + // "password": "1988" + // }, + // "10": { + // "name": "Backdoor Password", + // "password": "1988" + // } + // } + // } + //} + + /// + /// JSON password array configuration object + /// + public class PasswordConfig + { + /// + /// Password object configured password + /// + public string password { get; set; } + /// + /// Constructor + /// + public PasswordConfig() + { + + } + } + + /// + /// Global JSON object + /// + //public class GlobalConfig + //{ + // //public List passwords { get; set; } + // public Dictionary passwords { get; set; } + + // /// + // /// Constructor + // /// + // public GlobalConfig() + // { + + // } + //} + + /// + /// Root JSON object + /// + //public class RootObject + //{ + // public GlobalConfig global { get; set; } + //} } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/PasswordManagement/OLD-ARRAY-PasswordClient.cs b/src/PasswordManagement/OLD-ARRAY-PasswordClient.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/PasswordManagement/OLD-ARRAY-PasswordClient.cs rename to src/PasswordManagement/OLD-ARRAY-PasswordClient.cs index 634badf..540d935 100644 --- a/Pepperdash Core/Pepperdash Core/PasswordManagement/OLD-ARRAY-PasswordClient.cs +++ b/src/PasswordManagement/OLD-ARRAY-PasswordClient.cs @@ -1,207 +1,207 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core.PasswordManagement -{ - public class PasswordClient - { - /// - /// Password Client - /// - public PasswordConfig Client { get; set; } - /// - /// Used to build the password entered by the user - /// - public string PasswordToValidate { get; set; } - - /// - /// Boolean event - /// - public event EventHandler BoolChange; - /// - /// Ushort event - /// - public event EventHandler UshrtChange; - /// - /// String event - /// - public event EventHandler StringChange; - - /// - /// Constructor - /// - public PasswordClient() - { - - } - - /// - /// Initialize method - /// - /// - public void Initialize(string key) - { - OnBoolChange(false, 0, PasswordManagementConstants.BoolEvaluatedChange); - - Client = new PasswordConfig(); - PasswordToValidate = ""; - - // there has to be a better way to get the index of the current index of password - ushort i = 0; - foreach (var password in PasswordManager.Passwords) - { - i++; - OnUshrtChange((ushort)password.Key, (ushort)password.Key, PasswordManagementConstants.PasswordKey); - } - - OnBoolChange(true, 0, PasswordManagementConstants.BoolEvaluatedChange); - } - - /// - /// Retrieves password by key - /// - /// - //public void GetPasswordByKey(string key) - //{ - // if (string.IsNullOrEmpty(key)) - // { - // Debug.Console(1, "PassowrdClient.GetPasswordByKey failed:\rKey {0} is null or empty", key); - // return; - // } - - // PasswordConfig password = PasswordManager.Passwords.FirstOrDefault(p => p.key.Equals(key)); - // if (password == null) - // { - // OnUshrtChange(0, 0, PasswordManagementConstants.SelectedPasswordLength); - // return; - // } - - // Client = password; - // OnUshrtChange((ushort)Client.password.Length, 0, PasswordManagementConstants.SelectedPasswordLength); - // OnStringChange(Client.key, 0, PasswordManagementConstants.PasswordKeySelected); - //} - - /// - /// Retrieve password by index - /// - /// - public void GetPasswordByIndex(ushort key) - { - PasswordConfig pw = PasswordManager.Passwords[key]; - if (pw == null) - { - OnUshrtChange(0, 0, PasswordManagementConstants.SelectedPasswordLength); - return; - } - - Client = pw; - OnUshrtChange((ushort)Client.password.Length, 0, PasswordManagementConstants.SelectedPasswordLength); - OnUshrtChange(key, 0, PasswordManagementConstants.PasswordKeySelected); - } - - /// - /// Password validation method - /// - /// - public void ValidatePassword(string password) - { - if (string.IsNullOrEmpty(password)) - return; - - if (string.Equals(Client.password, password)) - { - OnBoolChange(true, 0, PasswordManagementConstants.PasswordIsValid); - } - else - { - OnBoolChange(true, 0, PasswordManagementConstants.PasswordIsInvalid); - } - - - OnBoolChange(false, 0, PasswordManagementConstants.PasswordIsValid); - OnBoolChange(false, 0, PasswordManagementConstants.PasswordIsInvalid); - - ClearPassword(); - } - - /// - /// Builds the user entered passwrod string, will attempt to validate the user entered - /// password against the selected password when the length of the 2 are equal - /// - /// - public void BuildPassword(string data) - { - PasswordToValidate = String.Concat(PasswordToValidate, data); - OnBoolChange(true, (ushort)PasswordToValidate.Length, PasswordManagementConstants.PasswordLedChange); - - if (PasswordToValidate.Length == Client.password.Length) - ValidatePassword(PasswordToValidate); - } - - /// - /// Clears the user entered password and resets the LEDs - /// - public void ClearPassword() - { - PasswordToValidate = ""; - OnBoolChange(true, (ushort)PasswordToValidate.Length, PasswordManagementConstants.PasswordLedChange); - - for(var i = 1; i <= Client.password.Length; i++) - OnBoolChange(false, (ushort)i, PasswordManagementConstants.PasswordLedChange); - } - - /// - /// Protected boolean change event handler - /// - /// - /// - /// - protected void OnBoolChange(bool state, ushort index, ushort type) - { - var handler = BoolChange; - if (handler != null) - { - var args = new BoolChangeEventArgs(state, type); - args.Index = index; - BoolChange(this, args); - } - } - - /// - /// Protected ushort change event handler - /// - /// - /// - /// - protected void OnUshrtChange(ushort value, ushort index, ushort type) - { - var handler = UshrtChange; - if (handler != null) - { - var args = new UshrtChangeEventArgs(value, type); - args.Index = index; - UshrtChange(this, args); - } - } - - /// - /// Protected string change event handler - /// - /// - /// - /// - protected void OnStringChange(string value, ushort index, ushort type) - { - var handler = StringChange; - if (handler != null) - { - var args = new StringChangeEventArgs(value, type); - args.Index = index; - StringChange(this, args); - } - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.PasswordManagement +{ + public class PasswordClient + { + /// + /// Password Client + /// + public PasswordConfig Client { get; set; } + /// + /// Used to build the password entered by the user + /// + public string PasswordToValidate { get; set; } + + /// + /// Boolean event + /// + public event EventHandler BoolChange; + /// + /// Ushort event + /// + public event EventHandler UshrtChange; + /// + /// String event + /// + public event EventHandler StringChange; + + /// + /// Constructor + /// + public PasswordClient() + { + + } + + /// + /// Initialize method + /// + /// + public void Initialize(string key) + { + OnBoolChange(false, 0, PasswordManagementConstants.BoolEvaluatedChange); + + Client = new PasswordConfig(); + PasswordToValidate = ""; + + // there has to be a better way to get the index of the current index of password + ushort i = 0; + foreach (var password in PasswordManager.Passwords) + { + i++; + OnUshrtChange((ushort)password.Key, (ushort)password.Key, PasswordManagementConstants.PasswordKey); + } + + OnBoolChange(true, 0, PasswordManagementConstants.BoolEvaluatedChange); + } + + /// + /// Retrieves password by key + /// + /// + //public void GetPasswordByKey(string key) + //{ + // if (string.IsNullOrEmpty(key)) + // { + // Debug.Console(1, "PassowrdClient.GetPasswordByKey failed:\rKey {0} is null or empty", key); + // return; + // } + + // PasswordConfig password = PasswordManager.Passwords.FirstOrDefault(p => p.key.Equals(key)); + // if (password == null) + // { + // OnUshrtChange(0, 0, PasswordManagementConstants.SelectedPasswordLength); + // return; + // } + + // Client = password; + // OnUshrtChange((ushort)Client.password.Length, 0, PasswordManagementConstants.SelectedPasswordLength); + // OnStringChange(Client.key, 0, PasswordManagementConstants.PasswordKeySelected); + //} + + /// + /// Retrieve password by index + /// + /// + public void GetPasswordByIndex(ushort key) + { + PasswordConfig pw = PasswordManager.Passwords[key]; + if (pw == null) + { + OnUshrtChange(0, 0, PasswordManagementConstants.SelectedPasswordLength); + return; + } + + Client = pw; + OnUshrtChange((ushort)Client.password.Length, 0, PasswordManagementConstants.SelectedPasswordLength); + OnUshrtChange(key, 0, PasswordManagementConstants.PasswordKeySelected); + } + + /// + /// Password validation method + /// + /// + public void ValidatePassword(string password) + { + if (string.IsNullOrEmpty(password)) + return; + + if (string.Equals(Client.password, password)) + { + OnBoolChange(true, 0, PasswordManagementConstants.PasswordIsValid); + } + else + { + OnBoolChange(true, 0, PasswordManagementConstants.PasswordIsInvalid); + } + + + OnBoolChange(false, 0, PasswordManagementConstants.PasswordIsValid); + OnBoolChange(false, 0, PasswordManagementConstants.PasswordIsInvalid); + + ClearPassword(); + } + + /// + /// Builds the user entered passwrod string, will attempt to validate the user entered + /// password against the selected password when the length of the 2 are equal + /// + /// + public void BuildPassword(string data) + { + PasswordToValidate = String.Concat(PasswordToValidate, data); + OnBoolChange(true, (ushort)PasswordToValidate.Length, PasswordManagementConstants.PasswordLedChange); + + if (PasswordToValidate.Length == Client.password.Length) + ValidatePassword(PasswordToValidate); + } + + /// + /// Clears the user entered password and resets the LEDs + /// + public void ClearPassword() + { + PasswordToValidate = ""; + OnBoolChange(true, (ushort)PasswordToValidate.Length, PasswordManagementConstants.PasswordLedChange); + + for(var i = 1; i <= Client.password.Length; i++) + OnBoolChange(false, (ushort)i, PasswordManagementConstants.PasswordLedChange); + } + + /// + /// Protected boolean change event handler + /// + /// + /// + /// + protected void OnBoolChange(bool state, ushort index, ushort type) + { + var handler = BoolChange; + if (handler != null) + { + var args = new BoolChangeEventArgs(state, type); + args.Index = index; + BoolChange(this, args); + } + } + + /// + /// Protected ushort change event handler + /// + /// + /// + /// + protected void OnUshrtChange(ushort value, ushort index, ushort type) + { + var handler = UshrtChange; + if (handler != null) + { + var args = new UshrtChangeEventArgs(value, type); + args.Index = index; + UshrtChange(this, args); + } + } + + /// + /// Protected string change event handler + /// + /// + /// + /// + protected void OnStringChange(string value, ushort index, ushort type) + { + var handler = StringChange; + if (handler != null) + { + var args = new StringChangeEventArgs(value, type); + args.Index = index; + StringChange(this, args); + } + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/PasswordManagement/OLD-ARRAY-PasswordManager.cs b/src/PasswordManagement/OLD-ARRAY-PasswordManager.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/PasswordManagement/OLD-ARRAY-PasswordManager.cs rename to src/PasswordManagement/OLD-ARRAY-PasswordManager.cs index b34db61..45b65e9 100644 --- a/Pepperdash Core/Pepperdash Core/PasswordManagement/OLD-ARRAY-PasswordManager.cs +++ b/src/PasswordManagement/OLD-ARRAY-PasswordManager.cs @@ -1,233 +1,233 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Crestron.SimplSharp; -using PepperDash.Core.JsonToSimpl; -using PepperDash.Core.JsonStandardObjects; - -namespace PepperDash.Core.PasswordManagement -{ - public class PasswordManager - { - /// - /// List of passwords configured - /// - public static Dictionary Passwords = new Dictionary(); - private Dictionary TempPasswords = new Dictionary(); - - CTimer UpdateTimer; - public long UpdateTimerElapsedMs = 5000; - - /// - /// Boolean event - /// - public event EventHandler BoolChange; - /// - /// Ushort event - /// - public event EventHandler UshrtChange; - /// - /// String event - /// - public event EventHandler StringChange; - - - /// - /// Constructor - /// - public PasswordManager() - { - Passwords.Clear(); - } - - /// - /// Initialize method - /// - /// - /// - //public void Initialize(string uniqueId, string key) - //{ - // OnBoolChange(false, 0, PasswordManagementConstants.BoolEvaluatedChange); - - // try - // { - // if(string.IsNullOrEmpty(uniqueId) || string.IsNullOrEmpty(key)) - // { - // Debug.Console(1, "PasswordManager.Initialize({0}, {1}) null or empty parameters", uniqueId, key); - // return; - // } - - // JsonToSimplMaster master = J2SGlobal.GetMasterByFile(uniqueId); - // if(master == null) - // { - // Debug.Console(1, "PassowrdManager.Initialize failed:\rCould not find JSON file with uniqueID {0}", uniqueId); - // return; - // } - - // var global = master.JsonObject.ToObject().global; - // var passwords = global.passwords; - // if(passwords == null) - // { - // Debug.Console(1, "PasswordManager.Initialize failed:\rCould not find password object"); - // return; - // } - - // foreach(var password in passwords) - // { - // if (password != null) - // { - // var index = passwords.IndexOf(password); - - // password.path = string.Format("global.passwords[{0}]", index); - // Debug.Console(1, "PasswordManager.Initialize: {0}, {1}, {2}, {3}, {4}, {5}", password.key, password.name, password.simplEnabled, password.simplType, password.password, password.path); - // //AddPassword(password); - - // OnStringChange(password.path, (ushort)index, PasswordManagementConstants.FullPathToPassword); - // OnStringChange(password.key, (ushort)index, PasswordManagementConstants.PasswordKey); - // } - // } - - // OnUshrtChange(Convert.ToUInt16(Passwords.Count), 0, PasswordManagementConstants.PasswordListCount); - // } - // catch(Exception e) - // { - // var msg = string.Format("PasswordManager.Initialize({0}, {1}) failed:\r{2}", uniqueId, key, e.Message); - // CrestronConsole.PrintLine(msg); - // ErrorLog.Error(msg); - // } - // finally - // { - // OnBoolChange(true, 0, PasswordManagementConstants.BoolEvaluatedChange); - // } - //} - - /// - /// Adds password to the list - /// - /// - //private void AddPassword(PasswordConfig password) - //{ - // if (password == null) - // return; - - // var item = Passwords.FirstOrDefault(i => i.key.Equals(password.key)); - // if (item != null) - // Passwords.Remove(item); - // Passwords.Add(password); - - // Passwords.Sort((x, y) => string.Compare(x.key, y.key)); - //} - - /// - /// Removes password from the list - /// - /// - //private void RemovePassword(PasswordConfig password) - //{ - // if (password == null) - // return; - - // var item = Passwords.FirstOrDefault(i => i.key.Equals(password.key)); - // if (item != null) - // Passwords.Remove(item); - //} - - /// - /// Updates password stored in the dictonary - /// - /// - /// - /// - public void UpdatePassword(ushort key, string password) - { - if (string.IsNullOrEmpty(password)) - return; - - var pw = TempPasswords[key]; - if (pw == null) - { - pw = new PasswordConfig(); - } - pw.password = password; - - if (UpdateTimer == null) - { - // (o) => SavePasswords removes the need to create a callback method that takes in an object - UpdateTimer = new CTimer((o) => StorePassword(), UpdateTimerElapsedMs); - } - else - { - UpdateTimer.Reset(); - } - } - - /// - /// Stores the updated passwords in TempPassword in the Passwords dictionary - /// - private void StorePassword() - { - UpdateTimer.Stop(); - - foreach (var tempPw in TempPasswords) - { - Passwords[tempPw.Key] = tempPw.Value; - } - - TempPasswords.Clear(); - } - - /// - /// Protected boolean change event handler - /// - /// - /// - /// - protected void OnBoolChange(bool state, ushort index, ushort type) - { - var handler = BoolChange; - if (handler != null) - { - var args = new BoolChangeEventArgs(state, type); - args.Index = index; - BoolChange(this, args); - } - } - - /// - /// Protected ushort change event handler - /// - /// - /// - /// - protected void OnUshrtChange(ushort value, ushort index, ushort type) - { - var handler = UshrtChange; - if (handler != null) - { - var args = new UshrtChangeEventArgs(value, type); - args.Index = index; - UshrtChange(this, args); - } - } - - /// - /// Protected string change event handler - /// - /// - /// - /// - protected void OnStringChange(string value, ushort index, ushort type) - { - var handler = StringChange; - if (handler != null) - { - var args = new StringChangeEventArgs(value, type); - args.Index = index; - StringChange(this, args); - } - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Crestron.SimplSharp; +using PepperDash.Core.JsonToSimpl; +using PepperDash.Core.JsonStandardObjects; + +namespace PepperDash.Core.PasswordManagement +{ + public class PasswordManager + { + /// + /// List of passwords configured + /// + public static Dictionary Passwords = new Dictionary(); + private Dictionary TempPasswords = new Dictionary(); + + CTimer UpdateTimer; + public long UpdateTimerElapsedMs = 5000; + + /// + /// Boolean event + /// + public event EventHandler BoolChange; + /// + /// Ushort event + /// + public event EventHandler UshrtChange; + /// + /// String event + /// + public event EventHandler StringChange; + + + /// + /// Constructor + /// + public PasswordManager() + { + Passwords.Clear(); + } + + /// + /// Initialize method + /// + /// + /// + //public void Initialize(string uniqueId, string key) + //{ + // OnBoolChange(false, 0, PasswordManagementConstants.BoolEvaluatedChange); + + // try + // { + // if(string.IsNullOrEmpty(uniqueId) || string.IsNullOrEmpty(key)) + // { + // Debug.Console(1, "PasswordManager.Initialize({0}, {1}) null or empty parameters", uniqueId, key); + // return; + // } + + // JsonToSimplMaster master = J2SGlobal.GetMasterByFile(uniqueId); + // if(master == null) + // { + // Debug.Console(1, "PassowrdManager.Initialize failed:\rCould not find JSON file with uniqueID {0}", uniqueId); + // return; + // } + + // var global = master.JsonObject.ToObject().global; + // var passwords = global.passwords; + // if(passwords == null) + // { + // Debug.Console(1, "PasswordManager.Initialize failed:\rCould not find password object"); + // return; + // } + + // foreach(var password in passwords) + // { + // if (password != null) + // { + // var index = passwords.IndexOf(password); + + // password.path = string.Format("global.passwords[{0}]", index); + // Debug.Console(1, "PasswordManager.Initialize: {0}, {1}, {2}, {3}, {4}, {5}", password.key, password.name, password.simplEnabled, password.simplType, password.password, password.path); + // //AddPassword(password); + + // OnStringChange(password.path, (ushort)index, PasswordManagementConstants.FullPathToPassword); + // OnStringChange(password.key, (ushort)index, PasswordManagementConstants.PasswordKey); + // } + // } + + // OnUshrtChange(Convert.ToUInt16(Passwords.Count), 0, PasswordManagementConstants.PasswordListCount); + // } + // catch(Exception e) + // { + // var msg = string.Format("PasswordManager.Initialize({0}, {1}) failed:\r{2}", uniqueId, key, e.Message); + // CrestronConsole.PrintLine(msg); + // ErrorLog.Error(msg); + // } + // finally + // { + // OnBoolChange(true, 0, PasswordManagementConstants.BoolEvaluatedChange); + // } + //} + + /// + /// Adds password to the list + /// + /// + //private void AddPassword(PasswordConfig password) + //{ + // if (password == null) + // return; + + // var item = Passwords.FirstOrDefault(i => i.key.Equals(password.key)); + // if (item != null) + // Passwords.Remove(item); + // Passwords.Add(password); + + // Passwords.Sort((x, y) => string.Compare(x.key, y.key)); + //} + + /// + /// Removes password from the list + /// + /// + //private void RemovePassword(PasswordConfig password) + //{ + // if (password == null) + // return; + + // var item = Passwords.FirstOrDefault(i => i.key.Equals(password.key)); + // if (item != null) + // Passwords.Remove(item); + //} + + /// + /// Updates password stored in the dictonary + /// + /// + /// + /// + public void UpdatePassword(ushort key, string password) + { + if (string.IsNullOrEmpty(password)) + return; + + var pw = TempPasswords[key]; + if (pw == null) + { + pw = new PasswordConfig(); + } + pw.password = password; + + if (UpdateTimer == null) + { + // (o) => SavePasswords removes the need to create a callback method that takes in an object + UpdateTimer = new CTimer((o) => StorePassword(), UpdateTimerElapsedMs); + } + else + { + UpdateTimer.Reset(); + } + } + + /// + /// Stores the updated passwords in TempPassword in the Passwords dictionary + /// + private void StorePassword() + { + UpdateTimer.Stop(); + + foreach (var tempPw in TempPasswords) + { + Passwords[tempPw.Key] = tempPw.Value; + } + + TempPasswords.Clear(); + } + + /// + /// Protected boolean change event handler + /// + /// + /// + /// + protected void OnBoolChange(bool state, ushort index, ushort type) + { + var handler = BoolChange; + if (handler != null) + { + var args = new BoolChangeEventArgs(state, type); + args.Index = index; + BoolChange(this, args); + } + } + + /// + /// Protected ushort change event handler + /// + /// + /// + /// + protected void OnUshrtChange(ushort value, ushort index, ushort type) + { + var handler = UshrtChange; + if (handler != null) + { + var args = new UshrtChangeEventArgs(value, type); + args.Index = index; + UshrtChange(this, args); + } + } + + /// + /// Protected string change event handler + /// + /// + /// + /// + protected void OnStringChange(string value, ushort index, ushort type) + { + var handler = StringChange; + if (handler != null) + { + var args = new StringChangeEventArgs(value, type); + args.Index = index; + StringChange(this, args); + } + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/PasswordManagement/PasswordClient.cs b/src/PasswordManagement/PasswordClient.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/PasswordManagement/PasswordClient.cs rename to src/PasswordManagement/PasswordClient.cs index f965dfa..2d66c52 100644 --- a/Pepperdash Core/Pepperdash Core/PasswordManagement/PasswordClient.cs +++ b/src/PasswordManagement/PasswordClient.cs @@ -1,202 +1,202 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core.PasswordManagement -{ - /// - /// A class to allow user interaction with the PasswordManager - /// - public class PasswordClient - { - /// - /// Password selected - /// - public string Password { get; set; } - /// - /// Password selected key - /// - public ushort Key { get; set; } - /// - /// Used to build the password entered by the user - /// - public string PasswordToValidate { get; set; } - - /// - /// Boolean event - /// - public event EventHandler BoolChange; - /// - /// Ushort event - /// - public event EventHandler UshrtChange; - /// - /// String event - /// - public event EventHandler StringChange; - - /// - /// Constructor - /// - public PasswordClient() - { - PasswordManager.PasswordChange += new EventHandler(PasswordManager_PasswordChange); - } - - /// - /// Initialize method - /// - public void Initialize() - { - OnBoolChange(false, 0, PasswordManagementConstants.PasswordInitializedChange); - - Password = ""; - PasswordToValidate = ""; - - OnUshrtChange((ushort)PasswordManager.Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange); - OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange); - } - - /// - /// Retrieve password by index - /// - /// - public void GetPasswordByIndex(ushort key) - { - OnUshrtChange((ushort)PasswordManager.Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange); - - Key = key; - - var pw = PasswordManager.Passwords[Key]; - if (pw == null) - { - OnUshrtChange(0, 0, PasswordManagementConstants.PasswordLengthChange); - return; - } - - Password = pw; - OnUshrtChange((ushort)Password.Length, 0, PasswordManagementConstants.PasswordLengthChange); - OnUshrtChange(key, 0, PasswordManagementConstants.PasswordSelectIndexChange); - } - - /// - /// Password validation method - /// - /// - public void ValidatePassword(string password) - { - if (string.IsNullOrEmpty(password)) - return; - - if (string.Equals(Password, password)) - OnBoolChange(true, 0, PasswordManagementConstants.PasswordValidationChange); - else - OnBoolChange(false, 0, PasswordManagementConstants.PasswordValidationChange); - - ClearPassword(); - } - - /// - /// Builds the user entered passwrod string, will attempt to validate the user entered - /// password against the selected password when the length of the 2 are equal - /// - /// - public void BuildPassword(string data) - { - PasswordToValidate = String.Concat(PasswordToValidate, data); - OnBoolChange(true, (ushort)PasswordToValidate.Length, PasswordManagementConstants.PasswordLedFeedbackChange); - - if (PasswordToValidate.Length == Password.Length) - ValidatePassword(PasswordToValidate); - } - - /// - /// Clears the user entered password and resets the LEDs - /// - public void ClearPassword() - { - PasswordToValidate = ""; - OnBoolChange(false, (ushort)PasswordToValidate.Length, PasswordManagementConstants.PasswordLedFeedbackChange); - } - - /// - /// Deletes the last character in the currently entered password field - /// - public void DeletePasswordCharacter() - { - ushort PasswordLengthBeforeDelete = (ushort)PasswordToValidate.Length; - PasswordToValidate = PasswordToValidate.Substring(0, PasswordToValidate.Length - 1); - OnBoolChange(false, (ushort)PasswordLengthBeforeDelete, PasswordManagementConstants.PasswordLedFeedbackChange); - // Verify if OnStringChange is needed to update the S+ wrapper with the entered PasswordToValidate - } - - /// - /// Protected boolean change event handler - /// - /// - /// - /// - protected void OnBoolChange(bool state, ushort index, ushort type) - { - var handler = BoolChange; - if (handler != null) - { - var args = new BoolChangeEventArgs(state, type); - args.Index = index; - BoolChange(this, args); - } - } - - /// - /// Protected ushort change event handler - /// - /// - /// - /// - protected void OnUshrtChange(ushort value, ushort index, ushort type) - { - var handler = UshrtChange; - if (handler != null) - { - var args = new UshrtChangeEventArgs(value, type); - args.Index = index; - UshrtChange(this, args); - } - } - - /// - /// Protected string change event handler - /// - /// - /// - /// - protected void OnStringChange(string value, ushort index, ushort type) - { - var handler = StringChange; - if (handler != null) - { - var args = new StringChangeEventArgs(value, type); - args.Index = index; - StringChange(this, args); - } - } - - /// - /// If password changes while selected change event will be notifed and update the client - /// - /// - /// - protected void PasswordManager_PasswordChange(object sender, StringChangeEventArgs args) - { - //throw new NotImplementedException(); - if (Key == args.Index) - { - //PasswordSelectedKey = args.Index; - //PasswordSelected = args.StringValue; - GetPasswordByIndex(args.Index); - } - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.PasswordManagement +{ + /// + /// A class to allow user interaction with the PasswordManager + /// + public class PasswordClient + { + /// + /// Password selected + /// + public string Password { get; set; } + /// + /// Password selected key + /// + public ushort Key { get; set; } + /// + /// Used to build the password entered by the user + /// + public string PasswordToValidate { get; set; } + + /// + /// Boolean event + /// + public event EventHandler BoolChange; + /// + /// Ushort event + /// + public event EventHandler UshrtChange; + /// + /// String event + /// + public event EventHandler StringChange; + + /// + /// Constructor + /// + public PasswordClient() + { + PasswordManager.PasswordChange += new EventHandler(PasswordManager_PasswordChange); + } + + /// + /// Initialize method + /// + public void Initialize() + { + OnBoolChange(false, 0, PasswordManagementConstants.PasswordInitializedChange); + + Password = ""; + PasswordToValidate = ""; + + OnUshrtChange((ushort)PasswordManager.Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange); + OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange); + } + + /// + /// Retrieve password by index + /// + /// + public void GetPasswordByIndex(ushort key) + { + OnUshrtChange((ushort)PasswordManager.Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange); + + Key = key; + + var pw = PasswordManager.Passwords[Key]; + if (pw == null) + { + OnUshrtChange(0, 0, PasswordManagementConstants.PasswordLengthChange); + return; + } + + Password = pw; + OnUshrtChange((ushort)Password.Length, 0, PasswordManagementConstants.PasswordLengthChange); + OnUshrtChange(key, 0, PasswordManagementConstants.PasswordSelectIndexChange); + } + + /// + /// Password validation method + /// + /// + public void ValidatePassword(string password) + { + if (string.IsNullOrEmpty(password)) + return; + + if (string.Equals(Password, password)) + OnBoolChange(true, 0, PasswordManagementConstants.PasswordValidationChange); + else + OnBoolChange(false, 0, PasswordManagementConstants.PasswordValidationChange); + + ClearPassword(); + } + + /// + /// Builds the user entered passwrod string, will attempt to validate the user entered + /// password against the selected password when the length of the 2 are equal + /// + /// + public void BuildPassword(string data) + { + PasswordToValidate = String.Concat(PasswordToValidate, data); + OnBoolChange(true, (ushort)PasswordToValidate.Length, PasswordManagementConstants.PasswordLedFeedbackChange); + + if (PasswordToValidate.Length == Password.Length) + ValidatePassword(PasswordToValidate); + } + + /// + /// Clears the user entered password and resets the LEDs + /// + public void ClearPassword() + { + PasswordToValidate = ""; + OnBoolChange(false, (ushort)PasswordToValidate.Length, PasswordManagementConstants.PasswordLedFeedbackChange); + } + + /// + /// Deletes the last character in the currently entered password field + /// + public void DeletePasswordCharacter() + { + ushort PasswordLengthBeforeDelete = (ushort)PasswordToValidate.Length; + PasswordToValidate = PasswordToValidate.Substring(0, PasswordToValidate.Length - 1); + OnBoolChange(false, (ushort)PasswordLengthBeforeDelete, PasswordManagementConstants.PasswordLedFeedbackChange); + // Verify if OnStringChange is needed to update the S+ wrapper with the entered PasswordToValidate + } + + /// + /// Protected boolean change event handler + /// + /// + /// + /// + protected void OnBoolChange(bool state, ushort index, ushort type) + { + var handler = BoolChange; + if (handler != null) + { + var args = new BoolChangeEventArgs(state, type); + args.Index = index; + BoolChange(this, args); + } + } + + /// + /// Protected ushort change event handler + /// + /// + /// + /// + protected void OnUshrtChange(ushort value, ushort index, ushort type) + { + var handler = UshrtChange; + if (handler != null) + { + var args = new UshrtChangeEventArgs(value, type); + args.Index = index; + UshrtChange(this, args); + } + } + + /// + /// Protected string change event handler + /// + /// + /// + /// + protected void OnStringChange(string value, ushort index, ushort type) + { + var handler = StringChange; + if (handler != null) + { + var args = new StringChangeEventArgs(value, type); + args.Index = index; + StringChange(this, args); + } + } + + /// + /// If password changes while selected change event will be notifed and update the client + /// + /// + /// + protected void PasswordManager_PasswordChange(object sender, StringChangeEventArgs args) + { + //throw new NotImplementedException(); + if (Key == args.Index) + { + //PasswordSelectedKey = args.Index; + //PasswordSelected = args.StringValue; + GetPasswordByIndex(args.Index); + } + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/PasswordManagement/PasswordManager.cs b/src/PasswordManagement/PasswordManager.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/PasswordManagement/PasswordManager.cs rename to src/PasswordManagement/PasswordManager.cs index c9bc06b..8c8f1b7 100644 --- a/Pepperdash Core/Pepperdash Core/PasswordManagement/PasswordManager.cs +++ b/src/PasswordManagement/PasswordManager.cs @@ -1,247 +1,247 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Crestron.SimplSharp; -using PepperDash.Core.JsonToSimpl; -using PepperDash.Core.JsonStandardObjects; - -namespace PepperDash.Core.PasswordManagement -{ - /// - /// Allows passwords to be stored and managed - /// - public class PasswordManager - { - /// - /// Public dictionary of known passwords - /// - public static Dictionary Passwords = new Dictionary(); - /// - /// Private dictionary, used when passwords are updated - /// - private Dictionary _passwords = new Dictionary(); - - /// - /// Timer used to wait until password changes have stopped before updating the dictionary - /// - CTimer PasswordTimer; - /// - /// Timer length - /// - public long PasswordTimerElapsedMs = 5000; - - /// - /// Boolean event - /// - public event EventHandler BoolChange; - /// - /// Ushort event - /// - public event EventHandler UshrtChange; - /// - /// String event - /// - public event EventHandler StringChange; - /// - /// Event to notify clients of an updated password at the specified index (uint) - /// - public static event EventHandler PasswordChange; - - /// - /// Constructor - /// - public PasswordManager() - { - - } - - /// - /// Initialize password manager - /// - public void Initialize() - { - if (Passwords == null) - Passwords = new Dictionary(); - - if (_passwords == null) - _passwords = new Dictionary(); - - OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange); - } - - /// - /// Updates password stored in the dictonary - /// - /// - /// - public void UpdatePassword(ushort key, string password) - { - // validate the parameters - if (key > 0 && string.IsNullOrEmpty(password)) - { - Debug.Console(1, string.Format("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password)); - return; - } - - try - { - // if key exists, update the value - if(_passwords.ContainsKey(key)) - _passwords[key] = password; - // else add the key & value - else - _passwords.Add(key, password); - - Debug.Console(1, string.Format("PasswordManager.UpdatePassword: _password[{0}] = {1}", key, _passwords[key])); - - if (PasswordTimer == null) - { - PasswordTimer = new CTimer((o) => PasswordTimerElapsed(), PasswordTimerElapsedMs); - Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Started")); - OnBoolChange(true, 0, PasswordManagementConstants.PasswordUpdateBusyChange); - } - else - { - PasswordTimer.Reset(PasswordTimerElapsedMs); - Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Reset")); - } - } - catch (Exception e) - { - var msg = string.Format("PasswordManager.UpdatePassword key-value[{0}, {1}] failed:\r{2}", key, password, e); - Debug.Console(1, msg); - } - } - - /// - /// CTimer callback function - /// - private void PasswordTimerElapsed() - { - try - { - PasswordTimer.Stop(); - Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: CTimer Stopped")); - OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange); - foreach (var pw in _passwords) - { - // if key exists, continue - if (Passwords.ContainsKey(pw.Key)) - { - Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value)); - if (Passwords[pw.Key] != _passwords[pw.Key]) - { - Passwords[pw.Key] = _passwords[pw.Key]; - Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: Updated Password[{0} = {1}", pw.Key, Passwords[pw.Key])); - OnPasswordChange(Passwords[pw.Key], (ushort)pw.Key, PasswordManagementConstants.StringValueChange); - } - } - // else add the key & value - else - { - Passwords.Add(pw.Key, pw.Value); - } - } - OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange); - } - catch (Exception e) - { - var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", e); - Debug.Console(1, msg); - } - } - - /// - /// Method to change the default timer value, (default 5000ms/5s) - /// - /// - public void PasswordTimerMs(ushort time) - { - PasswordTimerElapsedMs = Convert.ToInt64(time); - } - - /// - /// Helper method for debugging to see what passwords are in the lists - /// - public void ListPasswords() - { - Debug.Console(0, "PasswordManager.ListPasswords:\r"); - foreach (var pw in Passwords) - Debug.Console(0, "Passwords[{0}]: {1}\r", pw.Key, pw.Value); - Debug.Console(0, "\n"); - foreach (var pw in _passwords) - Debug.Console(0, "_passwords[{0}]: {1}\r", pw.Key, pw.Value); - } - - /// - /// Protected boolean change event handler - /// - /// - /// - /// - protected void OnBoolChange(bool state, ushort index, ushort type) - { - var handler = BoolChange; - if (handler != null) - { - var args = new BoolChangeEventArgs(state, type); - args.Index = index; - BoolChange(this, args); - } - } - - /// - /// Protected ushort change event handler - /// - /// - /// - /// - protected void OnUshrtChange(ushort value, ushort index, ushort type) - { - var handler = UshrtChange; - if (handler != null) - { - var args = new UshrtChangeEventArgs(value, type); - args.Index = index; - UshrtChange(this, args); - } - } - - /// - /// Protected string change event handler - /// - /// - /// - /// - protected void OnStringChange(string value, ushort index, ushort type) - { - var handler = StringChange; - if (handler != null) - { - var args = new StringChangeEventArgs(value, type); - args.Index = index; - StringChange(this, args); - } - } - - /// - /// Protected password change event handler - /// - /// - /// - /// - protected void OnPasswordChange(string value, ushort index, ushort type) - { - var handler = PasswordChange; - if (handler != null) - { - var args = new StringChangeEventArgs(value, type); - args.Index = index; - PasswordChange(this, args); - } - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Crestron.SimplSharp; +using PepperDash.Core.JsonToSimpl; +using PepperDash.Core.JsonStandardObjects; + +namespace PepperDash.Core.PasswordManagement +{ + /// + /// Allows passwords to be stored and managed + /// + public class PasswordManager + { + /// + /// Public dictionary of known passwords + /// + public static Dictionary Passwords = new Dictionary(); + /// + /// Private dictionary, used when passwords are updated + /// + private Dictionary _passwords = new Dictionary(); + + /// + /// Timer used to wait until password changes have stopped before updating the dictionary + /// + CTimer PasswordTimer; + /// + /// Timer length + /// + public long PasswordTimerElapsedMs = 5000; + + /// + /// Boolean event + /// + public event EventHandler BoolChange; + /// + /// Ushort event + /// + public event EventHandler UshrtChange; + /// + /// String event + /// + public event EventHandler StringChange; + /// + /// Event to notify clients of an updated password at the specified index (uint) + /// + public static event EventHandler PasswordChange; + + /// + /// Constructor + /// + public PasswordManager() + { + + } + + /// + /// Initialize password manager + /// + public void Initialize() + { + if (Passwords == null) + Passwords = new Dictionary(); + + if (_passwords == null) + _passwords = new Dictionary(); + + OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange); + } + + /// + /// Updates password stored in the dictonary + /// + /// + /// + public void UpdatePassword(ushort key, string password) + { + // validate the parameters + if (key > 0 && string.IsNullOrEmpty(password)) + { + Debug.Console(1, string.Format("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password)); + return; + } + + try + { + // if key exists, update the value + if(_passwords.ContainsKey(key)) + _passwords[key] = password; + // else add the key & value + else + _passwords.Add(key, password); + + Debug.Console(1, string.Format("PasswordManager.UpdatePassword: _password[{0}] = {1}", key, _passwords[key])); + + if (PasswordTimer == null) + { + PasswordTimer = new CTimer((o) => PasswordTimerElapsed(), PasswordTimerElapsedMs); + Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Started")); + OnBoolChange(true, 0, PasswordManagementConstants.PasswordUpdateBusyChange); + } + else + { + PasswordTimer.Reset(PasswordTimerElapsedMs); + Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Reset")); + } + } + catch (Exception e) + { + var msg = string.Format("PasswordManager.UpdatePassword key-value[{0}, {1}] failed:\r{2}", key, password, e); + Debug.Console(1, msg); + } + } + + /// + /// CTimer callback function + /// + private void PasswordTimerElapsed() + { + try + { + PasswordTimer.Stop(); + Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: CTimer Stopped")); + OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange); + foreach (var pw in _passwords) + { + // if key exists, continue + if (Passwords.ContainsKey(pw.Key)) + { + Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value)); + if (Passwords[pw.Key] != _passwords[pw.Key]) + { + Passwords[pw.Key] = _passwords[pw.Key]; + Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: Updated Password[{0} = {1}", pw.Key, Passwords[pw.Key])); + OnPasswordChange(Passwords[pw.Key], (ushort)pw.Key, PasswordManagementConstants.StringValueChange); + } + } + // else add the key & value + else + { + Passwords.Add(pw.Key, pw.Value); + } + } + OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange); + } + catch (Exception e) + { + var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", e); + Debug.Console(1, msg); + } + } + + /// + /// Method to change the default timer value, (default 5000ms/5s) + /// + /// + public void PasswordTimerMs(ushort time) + { + PasswordTimerElapsedMs = Convert.ToInt64(time); + } + + /// + /// Helper method for debugging to see what passwords are in the lists + /// + public void ListPasswords() + { + Debug.Console(0, "PasswordManager.ListPasswords:\r"); + foreach (var pw in Passwords) + Debug.Console(0, "Passwords[{0}]: {1}\r", pw.Key, pw.Value); + Debug.Console(0, "\n"); + foreach (var pw in _passwords) + Debug.Console(0, "_passwords[{0}]: {1}\r", pw.Key, pw.Value); + } + + /// + /// Protected boolean change event handler + /// + /// + /// + /// + protected void OnBoolChange(bool state, ushort index, ushort type) + { + var handler = BoolChange; + if (handler != null) + { + var args = new BoolChangeEventArgs(state, type); + args.Index = index; + BoolChange(this, args); + } + } + + /// + /// Protected ushort change event handler + /// + /// + /// + /// + protected void OnUshrtChange(ushort value, ushort index, ushort type) + { + var handler = UshrtChange; + if (handler != null) + { + var args = new UshrtChangeEventArgs(value, type); + args.Index = index; + UshrtChange(this, args); + } + } + + /// + /// Protected string change event handler + /// + /// + /// + /// + protected void OnStringChange(string value, ushort index, ushort type) + { + var handler = StringChange; + if (handler != null) + { + var args = new StringChangeEventArgs(value, type); + args.Index = index; + StringChange(this, args); + } + } + + /// + /// Protected password change event handler + /// + /// + /// + /// + protected void OnPasswordChange(string value, ushort index, ushort type) + { + var handler = PasswordChange; + if (handler != null) + { + var args = new StringChangeEventArgs(value, type); + args.Index = index; + PasswordChange(this, args); + } + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj b/src/PepperDash_Core.csproj similarity index 98% rename from Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj rename to src/PepperDash_Core.csproj index ec00542..372ad04 100644 --- a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj +++ b/src/PepperDash_Core.csproj @@ -1,156 +1,156 @@ - - - Release - AnyCPU - 9.0.30729 - 2.0 - {87E29B4C-569B-4368-A4ED-984AC1440C96} - Library - Properties - PepperDash.Core - PepperDash_Core - {0B4745B0-194B-4BB6-8E21-E9057CA92500};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - WindowsCE - E2BECB1F-8C8C-41ba-B736-9BE7D946A398 - 5.0 - SmartDeviceProject1 - v3.5 - Windows CE - - - - - .allowedReferenceRelatedFileExtensions - true - full - false - bin\ - DEBUG;TRACE; - prompt - 4 - 512 - true - true - bin\PepperDash_Core.xml - - - .allowedReferenceRelatedFileExtensions - none - true - bin\ - prompt - 4 - 512 - true - true - bin\PepperDash_Core.xml - - - - - False - ..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpCustomAttributesInterface.dll - - - False - ..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpCWSHelperInterface.dll - - - False - ..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpHelperInterface.dll - - - False - ..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpNewtonsoft.dll - - - False - ..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpReflectionInterface.dll - - - - - - - - - - - - - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - rem S# preparation will execute after these operations - del "$(TargetDir)PepperDash_Core.*" /q - - + + + Release + AnyCPU + 9.0.30729 + 2.0 + {87E29B4C-569B-4368-A4ED-984AC1440C96} + Library + Properties + PepperDash.Core + PepperDash_Core + {0B4745B0-194B-4BB6-8E21-E9057CA92500};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + WindowsCE + E2BECB1F-8C8C-41ba-B736-9BE7D946A398 + 5.0 + SmartDeviceProject1 + v3.5 + Windows CE + + + + + .allowedReferenceRelatedFileExtensions + true + full + false + bin\ + DEBUG;TRACE; + prompt + 4 + 512 + true + true + bin\PepperDash_Core.xml + + + .allowedReferenceRelatedFileExtensions + none + true + bin\ + prompt + 4 + 512 + true + true + bin\PepperDash_Core.xml + + + + + False + ..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpCustomAttributesInterface.dll + + + False + ..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpCWSHelperInterface.dll + + + False + ..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpHelperInterface.dll + + + False + ..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpNewtonsoft.dll + + + False + ..\..\..\..\..\..\..\..\ProgramData\Crestron\SDK\SimplSharpReflectionInterface.dll + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rem S# preparation will execute after these operations + del "$(TargetDir)PepperDash_Core.*" /q + + \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj.DotSettings b/src/PepperDash_Core.csproj.DotSettings similarity index 100% rename from Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj.DotSettings rename to src/PepperDash_Core.csproj.DotSettings diff --git a/Pepperdash Core/Pepperdash Core/PepperDash_Core.nuspec b/src/PepperDash_Core.nuspec similarity index 100% rename from Pepperdash Core/Pepperdash Core/PepperDash_Core.nuspec rename to src/PepperDash_Core.nuspec diff --git a/Pepperdash Core/Pepperdash Core/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/Properties/AssemblyInfo.cs rename to src/Properties/AssemblyInfo.cs diff --git a/Pepperdash Core/Pepperdash Core/Properties/AssemblyInfo.cs.orig b/src/Properties/AssemblyInfo.cs.orig similarity index 100% rename from Pepperdash Core/Pepperdash Core/Properties/AssemblyInfo.cs.orig rename to src/Properties/AssemblyInfo.cs.orig diff --git a/Pepperdash Core/Pepperdash Core/Properties/ControlSystem.cfg b/src/Properties/ControlSystem.cfg similarity index 97% rename from Pepperdash Core/Pepperdash Core/Properties/ControlSystem.cfg rename to src/Properties/ControlSystem.cfg index 276e200..f99176f 100644 --- a/Pepperdash Core/Pepperdash Core/Properties/ControlSystem.cfg +++ b/src/Properties/ControlSystem.cfg @@ -1,7 +1,7 @@ - - - MC3 SSH -
ssh 10.0.0.15
- Program01 - Internal Flash + + + MC3 SSH +
ssh 10.0.0.15
+ Program01 + Internal Flash
\ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Properties/UpdateAssemblyVersion.ps1 b/src/Properties/UpdateAssemblyVersion.ps1 similarity index 100% rename from Pepperdash Core/Pepperdash Core/Properties/UpdateAssemblyVersion.ps1 rename to src/Properties/UpdateAssemblyVersion.ps1 diff --git a/Pepperdash Core/Pepperdash Core/SystemInfo/EventArgs and Constants.cs b/src/SystemInfo/EventArgs and Constants.cs similarity index 95% rename from Pepperdash Core/Pepperdash Core/SystemInfo/EventArgs and Constants.cs rename to src/SystemInfo/EventArgs and Constants.cs index 25f8099..cc71e30 100644 --- a/Pepperdash Core/Pepperdash Core/SystemInfo/EventArgs and Constants.cs +++ b/src/SystemInfo/EventArgs and Constants.cs @@ -1,264 +1,264 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core.SystemInfo -{ - /// - /// Constants - /// - public class SystemInfoConstants - { - /// - /// - /// - public const ushort BoolValueChange = 1; - /// - /// - /// - public const ushort CompleteBoolChange = 2; - /// - /// - /// - public const ushort BusyBoolChange = 3; - - /// - /// - /// - public const ushort UshortValueChange = 101; - - /// - /// - /// - public const ushort StringValueChange = 201; - /// - /// - /// - public const ushort ConsoleResponseChange = 202; - /// - /// - /// - public const ushort ProcessorUptimeChange = 203; - /// - /// - /// - public const ushort ProgramUptimeChange = 204; - - /// - /// - /// - public const ushort ObjectChange = 301; - /// - /// - /// - public const ushort ProcessorConfigChange = 302; - /// - /// - /// - public const ushort EthernetConfigChange = 303; - /// - /// - /// - public const ushort ControlSubnetConfigChange = 304; - /// - /// - /// - public const ushort ProgramConfigChange = 305; - } - - /// - /// Processor Change Event Args Class - /// - public class ProcessorChangeEventArgs : EventArgs - { - /// - /// - /// - public ProcessorInfo Processor { get; set; } - /// - /// - /// - public ushort Type { get; set; } - /// - /// - /// - public ushort Index { get; set; } - - /// - /// Constructor - /// - public ProcessorChangeEventArgs() - { - - } - - /// - /// Constructor overload - /// - public ProcessorChangeEventArgs(ProcessorInfo processor, ushort type) - { - Processor = processor; - Type = type; - } - - /// - /// Constructor - /// - public ProcessorChangeEventArgs(ProcessorInfo processor, ushort type, ushort index) - { - Processor = processor; - Type = type; - Index = index; - } - } - - /// - /// Ethernet Change Event Args Class - /// - public class EthernetChangeEventArgs : EventArgs - { - /// - /// - /// - public EthernetInfo Adapter { get; set; } - /// - /// - /// - public ushort Type { get; set; } - /// - /// - /// - public ushort Index { get; set; } - - /// - /// Constructor - /// - public EthernetChangeEventArgs() - { - - } - - /// - /// Constructor overload - /// - /// - /// - public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type) - { - Adapter = ethernet; - Type = type; - } - - /// - /// Constructor overload - /// - /// - /// - /// - public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type, ushort index) - { - Adapter = ethernet; - Type = type; - Index = index; - } - } - - /// - /// Control Subnet Chage Event Args Class - /// - public class ControlSubnetChangeEventArgs : EventArgs - { - /// - /// - /// - public ControlSubnetInfo Adapter { get; set; } - /// - /// - /// - public ushort Type { get; set; } - /// - /// - /// - public ushort Index { get; set; } - - /// - /// Constructor - /// - public ControlSubnetChangeEventArgs() - { - - } - - /// - /// Constructor overload - /// - public ControlSubnetChangeEventArgs(ControlSubnetInfo controlSubnet, ushort type) - { - Adapter = controlSubnet; - Type = type; - } - - /// - /// Constructor overload - /// - public ControlSubnetChangeEventArgs(ControlSubnetInfo controlSubnet, ushort type, ushort index) - { - Adapter = controlSubnet; - Type = type; - Index = index; - } - } - - /// - /// Program Change Event Args Class - /// - public class ProgramChangeEventArgs : EventArgs - { - /// - /// - /// - public ProgramInfo Program { get; set; } - /// - /// - /// - public ushort Type { get; set; } - /// - /// - /// - public ushort Index { get; set; } - - /// - /// Constructor - /// - public ProgramChangeEventArgs() - { - - } - - /// - /// Constructor overload - /// - /// - /// - public ProgramChangeEventArgs(ProgramInfo program, ushort type) - { - Program = program; - Type = type; - } - - /// - /// Constructor overload - /// - /// - /// - /// - public ProgramChangeEventArgs(ProgramInfo program, ushort type, ushort index) - { - Program = program; - Type = type; - Index = index; - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.SystemInfo +{ + /// + /// Constants + /// + public class SystemInfoConstants + { + /// + /// + /// + public const ushort BoolValueChange = 1; + /// + /// + /// + public const ushort CompleteBoolChange = 2; + /// + /// + /// + public const ushort BusyBoolChange = 3; + + /// + /// + /// + public const ushort UshortValueChange = 101; + + /// + /// + /// + public const ushort StringValueChange = 201; + /// + /// + /// + public const ushort ConsoleResponseChange = 202; + /// + /// + /// + public const ushort ProcessorUptimeChange = 203; + /// + /// + /// + public const ushort ProgramUptimeChange = 204; + + /// + /// + /// + public const ushort ObjectChange = 301; + /// + /// + /// + public const ushort ProcessorConfigChange = 302; + /// + /// + /// + public const ushort EthernetConfigChange = 303; + /// + /// + /// + public const ushort ControlSubnetConfigChange = 304; + /// + /// + /// + public const ushort ProgramConfigChange = 305; + } + + /// + /// Processor Change Event Args Class + /// + public class ProcessorChangeEventArgs : EventArgs + { + /// + /// + /// + public ProcessorInfo Processor { get; set; } + /// + /// + /// + public ushort Type { get; set; } + /// + /// + /// + public ushort Index { get; set; } + + /// + /// Constructor + /// + public ProcessorChangeEventArgs() + { + + } + + /// + /// Constructor overload + /// + public ProcessorChangeEventArgs(ProcessorInfo processor, ushort type) + { + Processor = processor; + Type = type; + } + + /// + /// Constructor + /// + public ProcessorChangeEventArgs(ProcessorInfo processor, ushort type, ushort index) + { + Processor = processor; + Type = type; + Index = index; + } + } + + /// + /// Ethernet Change Event Args Class + /// + public class EthernetChangeEventArgs : EventArgs + { + /// + /// + /// + public EthernetInfo Adapter { get; set; } + /// + /// + /// + public ushort Type { get; set; } + /// + /// + /// + public ushort Index { get; set; } + + /// + /// Constructor + /// + public EthernetChangeEventArgs() + { + + } + + /// + /// Constructor overload + /// + /// + /// + public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type) + { + Adapter = ethernet; + Type = type; + } + + /// + /// Constructor overload + /// + /// + /// + /// + public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type, ushort index) + { + Adapter = ethernet; + Type = type; + Index = index; + } + } + + /// + /// Control Subnet Chage Event Args Class + /// + public class ControlSubnetChangeEventArgs : EventArgs + { + /// + /// + /// + public ControlSubnetInfo Adapter { get; set; } + /// + /// + /// + public ushort Type { get; set; } + /// + /// + /// + public ushort Index { get; set; } + + /// + /// Constructor + /// + public ControlSubnetChangeEventArgs() + { + + } + + /// + /// Constructor overload + /// + public ControlSubnetChangeEventArgs(ControlSubnetInfo controlSubnet, ushort type) + { + Adapter = controlSubnet; + Type = type; + } + + /// + /// Constructor overload + /// + public ControlSubnetChangeEventArgs(ControlSubnetInfo controlSubnet, ushort type, ushort index) + { + Adapter = controlSubnet; + Type = type; + Index = index; + } + } + + /// + /// Program Change Event Args Class + /// + public class ProgramChangeEventArgs : EventArgs + { + /// + /// + /// + public ProgramInfo Program { get; set; } + /// + /// + /// + public ushort Type { get; set; } + /// + /// + /// + public ushort Index { get; set; } + + /// + /// Constructor + /// + public ProgramChangeEventArgs() + { + + } + + /// + /// Constructor overload + /// + /// + /// + public ProgramChangeEventArgs(ProgramInfo program, ushort type) + { + Program = program; + Type = type; + } + + /// + /// Constructor overload + /// + /// + /// + /// + public ProgramChangeEventArgs(ProgramInfo program, ushort type, ushort index) + { + Program = program; + Type = type; + Index = index; + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/SystemInfo/SystemInfoConfig.cs b/src/SystemInfo/SystemInfoConfig.cs similarity index 95% rename from Pepperdash Core/Pepperdash Core/SystemInfo/SystemInfoConfig.cs rename to src/SystemInfo/SystemInfoConfig.cs index 7b33b47..8dc3aca 100644 --- a/Pepperdash Core/Pepperdash Core/SystemInfo/SystemInfoConfig.cs +++ b/src/SystemInfo/SystemInfoConfig.cs @@ -1,204 +1,204 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core.SystemInfo -{ - /// - /// Processor info class - /// - public class ProcessorInfo - { - /// - /// - /// - public string Model { get; set; } - /// - /// - /// - public string SerialNumber { get; set; } - /// - /// - /// - public string Firmware { get; set; } - /// - /// - /// - public string FirmwareDate { get; set; } - /// - /// - /// - public string OsVersion { get; set; } - /// - /// - /// - public string RuntimeEnvironment { get; set; } - /// - /// - /// - public string DevicePlatform { get; set; } - /// - /// - /// - public string ModuleDirectory { get; set; } - /// - /// - /// - public string LocalTimeZone { get; set; } - /// - /// - /// - public string ProgramIdTag { get; set; } - - /// - /// Constructor - /// - public ProcessorInfo() - { - - } - } - - /// - /// Ethernet info class - /// - public class EthernetInfo - { - /// - /// - /// - public ushort DhcpIsOn { get; set; } - /// - /// - /// - public string Hostname { get; set; } - /// - /// - /// - public string MacAddress { get; set; } - /// - /// - /// - public string IpAddress { get; set; } - /// - /// - /// - public string Subnet { get; set; } - /// - /// - /// - public string Gateway { get; set; } - /// - /// - /// - public string Dns1 { get; set; } - /// - /// - /// - public string Dns2 { get; set; } - /// - /// - /// - public string Dns3 { get; set; } - /// - /// - /// - public string Domain { get; set; } - - /// - /// Constructor - /// - public EthernetInfo() - { - - } - } - - /// - /// Control subnet info class - /// - public class ControlSubnetInfo - { - /// - /// - /// - public ushort Enabled { get; set; } - /// - /// - /// - public ushort IsInAutomaticMode { get; set; } - /// - /// - /// - public string MacAddress { get; set; } - /// - /// - /// - public string IpAddress { get; set; } - /// - /// - /// - public string Subnet { get; set; } - /// - /// - /// - public string RouterPrefix { get; set; } - - /// - /// Constructor - /// - public ControlSubnetInfo() - { - - } - } - - /// - /// Program info class - /// - public class ProgramInfo - { - /// - /// - /// - public string Name { get; set; } - /// - /// - /// - public string Header { get; set; } - /// - /// - /// - public string System { get; set; } - /// - /// - /// - public string ProgramIdTag { get; set; } - /// - /// - /// - public string CompileTime { get; set; } - /// - /// - /// - public string Database { get; set; } - /// - /// - /// - public string Environment { get; set; } - /// - /// - /// - public string Programmer { get; set; } - - /// - /// Constructor - /// - public ProgramInfo() - { - - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.SystemInfo +{ + /// + /// Processor info class + /// + public class ProcessorInfo + { + /// + /// + /// + public string Model { get; set; } + /// + /// + /// + public string SerialNumber { get; set; } + /// + /// + /// + public string Firmware { get; set; } + /// + /// + /// + public string FirmwareDate { get; set; } + /// + /// + /// + public string OsVersion { get; set; } + /// + /// + /// + public string RuntimeEnvironment { get; set; } + /// + /// + /// + public string DevicePlatform { get; set; } + /// + /// + /// + public string ModuleDirectory { get; set; } + /// + /// + /// + public string LocalTimeZone { get; set; } + /// + /// + /// + public string ProgramIdTag { get; set; } + + /// + /// Constructor + /// + public ProcessorInfo() + { + + } + } + + /// + /// Ethernet info class + /// + public class EthernetInfo + { + /// + /// + /// + public ushort DhcpIsOn { get; set; } + /// + /// + /// + public string Hostname { get; set; } + /// + /// + /// + public string MacAddress { get; set; } + /// + /// + /// + public string IpAddress { get; set; } + /// + /// + /// + public string Subnet { get; set; } + /// + /// + /// + public string Gateway { get; set; } + /// + /// + /// + public string Dns1 { get; set; } + /// + /// + /// + public string Dns2 { get; set; } + /// + /// + /// + public string Dns3 { get; set; } + /// + /// + /// + public string Domain { get; set; } + + /// + /// Constructor + /// + public EthernetInfo() + { + + } + } + + /// + /// Control subnet info class + /// + public class ControlSubnetInfo + { + /// + /// + /// + public ushort Enabled { get; set; } + /// + /// + /// + public ushort IsInAutomaticMode { get; set; } + /// + /// + /// + public string MacAddress { get; set; } + /// + /// + /// + public string IpAddress { get; set; } + /// + /// + /// + public string Subnet { get; set; } + /// + /// + /// + public string RouterPrefix { get; set; } + + /// + /// Constructor + /// + public ControlSubnetInfo() + { + + } + } + + /// + /// Program info class + /// + public class ProgramInfo + { + /// + /// + /// + public string Name { get; set; } + /// + /// + /// + public string Header { get; set; } + /// + /// + /// + public string System { get; set; } + /// + /// + /// + public string ProgramIdTag { get; set; } + /// + /// + /// + public string CompileTime { get; set; } + /// + /// + /// + public string Database { get; set; } + /// + /// + /// + public string Environment { get; set; } + /// + /// + /// + public string Programmer { get; set; } + + /// + /// Constructor + /// + public ProgramInfo() + { + + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/SystemInfo/SystemInfoToSimpl.cs b/src/SystemInfo/SystemInfoToSimpl.cs similarity index 97% rename from Pepperdash Core/Pepperdash Core/SystemInfo/SystemInfoToSimpl.cs rename to src/SystemInfo/SystemInfoToSimpl.cs index d4fe40a..6677b9e 100644 --- a/Pepperdash Core/Pepperdash Core/SystemInfo/SystemInfoToSimpl.cs +++ b/src/SystemInfo/SystemInfoToSimpl.cs @@ -1,462 +1,462 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Crestron.SimplSharp; - -namespace PepperDash.Core.SystemInfo -{ - /// - /// System Info class - /// - public class SystemInfoToSimpl - { - /// - /// Notifies of bool change - /// - public event EventHandler BoolChange; - /// - /// Notifies of string change - /// - public event EventHandler StringChange; - - /// - /// Notifies of processor change - /// - public event EventHandler ProcessorChange; - /// - /// Notifies of ethernet change - /// - public event EventHandler EthernetChange; - /// - /// Notifies of control subnet change - /// - public event EventHandler ControlSubnetChange; - /// - /// Notifies of program change - /// - public event EventHandler ProgramChange; - - /// - /// Constructor - /// - public SystemInfoToSimpl() - { - - } - - /// - /// Gets the current processor info - /// - public void GetProcessorInfo() - { - OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange); - - try - { - var processor = new ProcessorInfo(); - processor.Model = InitialParametersClass.ControllerPromptName; - processor.SerialNumber = CrestronEnvironment.SystemInfo.SerialNumber; - processor.ModuleDirectory = InitialParametersClass.ProgramDirectory.ToString(); - processor.ProgramIdTag = InitialParametersClass.ProgramIDTag; - processor.DevicePlatform = CrestronEnvironment.DevicePlatform.ToString(); - processor.OsVersion = CrestronEnvironment.OSVersion.Version.ToString(); - processor.RuntimeEnvironment = CrestronEnvironment.RuntimeEnvironment.ToString(); - processor.LocalTimeZone = CrestronEnvironment.GetTimeZone().Offset; - - // Does not return firmware version matching a "ver" command - // returns the "ver -v" 'CAB' version - // example return ver -v: - // RMC3 Cntrl Eng [v1.503.3568.25373 (Oct 09 2018), #4001E302] @E-00107f4420f0 - // Build: 14:05:46 Oct 09 2018 (3568.25373) - // Cab: 1.503.0070 - // Applications: 1.0.6855.21351 - // Updater: 1.4.24 - // Bootloader: 1.22.00 - // RMC3-SetupProgram: 1.003.0011 - // IOPVersion: FPGA [v09] slot:7 - // PUF: Unknown - //Firmware = CrestronEnvironment.OSVersion.Firmware; - //Firmware = InitialParametersClass.FirmwareVersion; - - // Use below logic to get actual firmware ver, not the 'CAB' returned by the above - // matches console return of a "ver" and on SystemInfo page - // example return ver: - // RMC3 Cntrl Eng [v1.503.3568.25373 (Oct 09 2018), #4001E302] @E-00107f4420f0 - var response = ""; - CrestronConsole.SendControlSystemCommand("ver", ref response); - processor.Firmware = ParseConsoleResponse(response, "Cntrl Eng", "[", "("); - processor.FirmwareDate = ParseConsoleResponse(response, "Cntrl Eng", "(", ")"); - - OnProcessorChange(processor, 0, SystemInfoConstants.ProcessorConfigChange); - } - catch (Exception e) - { - var msg = string.Format("GetProcessorInfo failed: {0}", e.Message); - CrestronConsole.PrintLine(msg); - //ErrorLog.Error(msg); - } - - OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange); - } - - /// - /// Gets the current ethernet info - /// - public void GetEthernetInfo() - { - OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange); - - var adapter = new EthernetInfo(); - - try - { - // get lan adapter id - var adapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter); - - // get lan adapter info - var dhcpState = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, adapterId); - if (!string.IsNullOrEmpty(dhcpState)) - adapter.DhcpIsOn = (ushort)(dhcpState.ToLower().Contains("on") ? 1 : 0); - - adapter.Hostname = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, adapterId); - adapter.MacAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, adapterId); - adapter.IpAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterId); - adapter.Subnet = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterId); - adapter.Gateway = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, adapterId); - adapter.Domain = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, adapterId); - - // returns comma seperate list of dns servers with trailing comma - // example return: "8.8.8.8 (DHCP),8.8.4.4 (DHCP)," - string dns = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER, adapterId); - if (dns.Contains(",")) - { - string[] dnsList = dns.Split(','); - for (var i = 0; i < dnsList.Length; i++) - { - if(i == 0) - adapter.Dns1 = !string.IsNullOrEmpty(dnsList[0]) ? dnsList[0] : "0.0.0.0"; - if(i == 1) - adapter.Dns2 = !string.IsNullOrEmpty(dnsList[1]) ? dnsList[1] : "0.0.0.0"; - if(i == 2) - adapter.Dns3 = !string.IsNullOrEmpty(dnsList[2]) ? dnsList[2] : "0.0.0.0"; - } - } - else - { - adapter.Dns1 = !string.IsNullOrEmpty(dns) ? dns : "0.0.0.0"; - adapter.Dns2 = "0.0.0.0"; - adapter.Dns3 = "0.0.0.0"; - } - - OnEthernetInfoChange(adapter, 0, SystemInfoConstants.EthernetConfigChange); - } - catch (Exception e) - { - var msg = string.Format("GetEthernetInfo failed: {0}", e.Message); - CrestronConsole.PrintLine(msg); - //ErrorLog.Error(msg); - } - - OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange); - } - - /// - /// Gets the current control subnet info - /// - public void GetControlSubnetInfo() - { - OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange); - - var adapter = new ControlSubnetInfo(); - - try - { - // get cs adapter id - var adapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter); - if (!adapterId.Equals(EthernetAdapterType.EthernetUnknownAdapter)) - { - adapter.Enabled = 1; - adapter.IsInAutomaticMode = (ushort)(CrestronEthernetHelper.IsControlSubnetInAutomaticMode ? 1 : 0); - adapter.MacAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, adapterId); - adapter.IpAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterId); - adapter.Subnet = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterId); - adapter.RouterPrefix = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CONTROL_SUBNET_ROUTER_PREFIX, adapterId); - } - } - catch (Exception e) - { - adapter.Enabled = 0; - adapter.IsInAutomaticMode = 0; - adapter.MacAddress = "NA"; - adapter.IpAddress = "NA"; - adapter.Subnet = "NA"; - adapter.RouterPrefix = "NA"; - - var msg = string.Format("GetControlSubnetInfo failed: {0}", e.Message); - CrestronConsole.PrintLine(msg); - //ErrorLog.Error(msg); - } - - OnControlSubnetInfoChange(adapter, 0, SystemInfoConstants.ControlSubnetConfigChange); - OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange); - } - - /// - /// Gets the program info by index - /// - /// - public void GetProgramInfoByIndex(ushort index) - { - if (index < 1 || index > 10) - return; - - OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange); - - var program = new ProgramInfo(); - - try - { - var response = ""; - CrestronConsole.SendControlSystemCommand(string.Format("progcomments:{0}", index), ref response); - - // no program loaded or running - if (response.Contains("Bad or Incomplete Command")) - { - program.Name = ""; - program.System = ""; - program.Programmer = ""; - program.CompileTime = ""; - program.Database = ""; - program.Environment = ""; - } - else - { - // SIMPL returns - program.Name = ParseConsoleResponse(response, "Program File", ":", "\x0D"); - program.System = ParseConsoleResponse(response, "System Name", ":", "\x0D"); - program.ProgramIdTag = ParseConsoleResponse(response, "Friendly Name", ":", "\x0D"); - program.Programmer = ParseConsoleResponse(response, "Programmer", ":", "\x0D"); - program.CompileTime = ParseConsoleResponse(response, "Compiled On", ":", "\x0D"); - program.Database = ParseConsoleResponse(response, "CrestronDB", ":", "\x0D"); - program.Environment = ParseConsoleResponse(response, "Source Env", ":", "\x0D"); - - // S# returns - if (program.System.Length == 0) - program.System = ParseConsoleResponse(response, "Application Name", ":", "\x0D"); - if (program.Database.Length == 0) - program.Database = ParseConsoleResponse(response, "PlugInVersion", ":", "\x0D"); - if (program.Environment.Length == 0) - program.Environment = ParseConsoleResponse(response, "Program Tool", ":", "\x0D"); - - } - - OnProgramChange(program, index, SystemInfoConstants.ProgramConfigChange); - } - catch (Exception e) - { - var msg = string.Format("GetProgramInfoByIndex failed: {0}", e.Message); - CrestronConsole.PrintLine(msg); - //ErrorLog.Error(msg); - } - - OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange); - } - - /// - /// Gets the processor uptime and passes it to S+ - /// - public void RefreshProcessorUptime() - { - try - { - string response = ""; - CrestronConsole.SendControlSystemCommand("uptime", ref response); - var uptime = ParseConsoleResponse(response, "running for", "running for", "\x0D"); - OnStringChange(uptime, 0, SystemInfoConstants.ProcessorUptimeChange); - } - catch (Exception e) - { - var msg = string.Format("RefreshProcessorUptime failed:\r{0}", e.Message); - CrestronConsole.PrintLine(msg); - //ErrorLog.Error(msg); - } - } - - /// - /// Gets the program uptime, by index, and passes it to S+ - /// - /// - public void RefreshProgramUptimeByIndex(int index) - { - try - { - string response = ""; - CrestronConsole.SendControlSystemCommand(string.Format("proguptime:{0}", index), ref response); - string uptime = ParseConsoleResponse(response, "running for", "running for", "\x0D"); - OnStringChange(uptime, (ushort)index, SystemInfoConstants.ProgramUptimeChange); - } - catch (Exception e) - { - var msg = string.Format("RefreshProgramUptimebyIndex({0}) failed:\r{1}", index, e.Message); - CrestronConsole.PrintLine(msg); - //ErrorLog.Error(msg); - } - } - - /// - /// Sends command to console, passes response back using string change event - /// - /// - public void SendConsoleCommand(string cmd) - { - if (string.IsNullOrEmpty(cmd)) - return; - - string response = ""; - CrestronConsole.SendControlSystemCommand(cmd, ref response); - if (!string.IsNullOrEmpty(response)) - { - if (response.EndsWith("\x0D\\x0A")) - response.Trim('\n'); - - OnStringChange(response, 0, SystemInfoConstants.ConsoleResponseChange); - } - } - - /// - /// private method to parse console messages - /// - /// - /// - /// - /// - /// - private string ParseConsoleResponse(string data, string line, string dataStart, string dataEnd) - { - var response = ""; - - if (string.IsNullOrEmpty(data) || string.IsNullOrEmpty(line) || string.IsNullOrEmpty(dataStart) || string.IsNullOrEmpty(dataEnd)) - return response; - - try - { - var linePos = data.IndexOf(line); - var startPos = data.IndexOf(dataStart, linePos) + dataStart.Length; - var endPos = data.IndexOf(dataEnd, startPos); - response = data.Substring(startPos, endPos - startPos).Trim(); - } - catch (Exception e) - { - var msg = string.Format("ParseConsoleResponse failed: {0}", e.Message); - CrestronConsole.PrintLine(msg); - //ErrorLog.Error(msg); - } - - return response; - } - - /// - /// Protected boolean change event handler - /// - /// - /// - /// - protected void OnBoolChange(bool state, ushort index, ushort type) - { - var handler = BoolChange; - if (handler != null) - { - var args = new BoolChangeEventArgs(state, type); - args.Index = index; - BoolChange(this, args); - } - } - - /// - /// Protected string change event handler - /// - /// - /// - /// - protected void OnStringChange(string value, ushort index, ushort type) - { - var handler = StringChange; - if (handler != null) - { - var args = new StringChangeEventArgs(value, type); - args.Index = index; - StringChange(this, args); - } - } - - /// - /// Protected processor config change event handler - /// - /// - /// - /// - protected void OnProcessorChange(ProcessorInfo processor, ushort index, ushort type) - { - var handler = ProcessorChange; - if (handler != null) - { - var args = new ProcessorChangeEventArgs(processor, type); - args.Index = index; - ProcessorChange(this, args); - } - } - - /// - /// Ethernet change event handler - /// - /// - /// - /// - protected void OnEthernetInfoChange(EthernetInfo ethernet, ushort index, ushort type) - { - var handler = EthernetChange; - if (handler != null) - { - var args = new EthernetChangeEventArgs(ethernet, type); - args.Index = index; - EthernetChange(this, args); - } - } - - /// - /// Control Subnet change event handler - /// - /// - /// - /// - protected void OnControlSubnetInfoChange(ControlSubnetInfo ethernet, ushort index, ushort type) - { - var handler = ControlSubnetChange; - if (handler != null) - { - var args = new ControlSubnetChangeEventArgs(ethernet, type); - args.Index = index; - ControlSubnetChange(this, args); - } - } - - /// - /// Program change event handler - /// - /// - /// - /// - protected void OnProgramChange(ProgramInfo program, ushort index, ushort type) - { - var handler = ProgramChange; - - if (handler != null) - { - var args = new ProgramChangeEventArgs(program, type); - args.Index = index; - ProgramChange(this, args); - } - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; + +namespace PepperDash.Core.SystemInfo +{ + /// + /// System Info class + /// + public class SystemInfoToSimpl + { + /// + /// Notifies of bool change + /// + public event EventHandler BoolChange; + /// + /// Notifies of string change + /// + public event EventHandler StringChange; + + /// + /// Notifies of processor change + /// + public event EventHandler ProcessorChange; + /// + /// Notifies of ethernet change + /// + public event EventHandler EthernetChange; + /// + /// Notifies of control subnet change + /// + public event EventHandler ControlSubnetChange; + /// + /// Notifies of program change + /// + public event EventHandler ProgramChange; + + /// + /// Constructor + /// + public SystemInfoToSimpl() + { + + } + + /// + /// Gets the current processor info + /// + public void GetProcessorInfo() + { + OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange); + + try + { + var processor = new ProcessorInfo(); + processor.Model = InitialParametersClass.ControllerPromptName; + processor.SerialNumber = CrestronEnvironment.SystemInfo.SerialNumber; + processor.ModuleDirectory = InitialParametersClass.ProgramDirectory.ToString(); + processor.ProgramIdTag = InitialParametersClass.ProgramIDTag; + processor.DevicePlatform = CrestronEnvironment.DevicePlatform.ToString(); + processor.OsVersion = CrestronEnvironment.OSVersion.Version.ToString(); + processor.RuntimeEnvironment = CrestronEnvironment.RuntimeEnvironment.ToString(); + processor.LocalTimeZone = CrestronEnvironment.GetTimeZone().Offset; + + // Does not return firmware version matching a "ver" command + // returns the "ver -v" 'CAB' version + // example return ver -v: + // RMC3 Cntrl Eng [v1.503.3568.25373 (Oct 09 2018), #4001E302] @E-00107f4420f0 + // Build: 14:05:46 Oct 09 2018 (3568.25373) + // Cab: 1.503.0070 + // Applications: 1.0.6855.21351 + // Updater: 1.4.24 + // Bootloader: 1.22.00 + // RMC3-SetupProgram: 1.003.0011 + // IOPVersion: FPGA [v09] slot:7 + // PUF: Unknown + //Firmware = CrestronEnvironment.OSVersion.Firmware; + //Firmware = InitialParametersClass.FirmwareVersion; + + // Use below logic to get actual firmware ver, not the 'CAB' returned by the above + // matches console return of a "ver" and on SystemInfo page + // example return ver: + // RMC3 Cntrl Eng [v1.503.3568.25373 (Oct 09 2018), #4001E302] @E-00107f4420f0 + var response = ""; + CrestronConsole.SendControlSystemCommand("ver", ref response); + processor.Firmware = ParseConsoleResponse(response, "Cntrl Eng", "[", "("); + processor.FirmwareDate = ParseConsoleResponse(response, "Cntrl Eng", "(", ")"); + + OnProcessorChange(processor, 0, SystemInfoConstants.ProcessorConfigChange); + } + catch (Exception e) + { + var msg = string.Format("GetProcessorInfo failed: {0}", e.Message); + CrestronConsole.PrintLine(msg); + //ErrorLog.Error(msg); + } + + OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange); + } + + /// + /// Gets the current ethernet info + /// + public void GetEthernetInfo() + { + OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange); + + var adapter = new EthernetInfo(); + + try + { + // get lan adapter id + var adapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter); + + // get lan adapter info + var dhcpState = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE, adapterId); + if (!string.IsNullOrEmpty(dhcpState)) + adapter.DhcpIsOn = (ushort)(dhcpState.ToLower().Contains("on") ? 1 : 0); + + adapter.Hostname = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, adapterId); + adapter.MacAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, adapterId); + adapter.IpAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterId); + adapter.Subnet = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterId); + adapter.Gateway = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, adapterId); + adapter.Domain = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, adapterId); + + // returns comma seperate list of dns servers with trailing comma + // example return: "8.8.8.8 (DHCP),8.8.4.4 (DHCP)," + string dns = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER, adapterId); + if (dns.Contains(",")) + { + string[] dnsList = dns.Split(','); + for (var i = 0; i < dnsList.Length; i++) + { + if(i == 0) + adapter.Dns1 = !string.IsNullOrEmpty(dnsList[0]) ? dnsList[0] : "0.0.0.0"; + if(i == 1) + adapter.Dns2 = !string.IsNullOrEmpty(dnsList[1]) ? dnsList[1] : "0.0.0.0"; + if(i == 2) + adapter.Dns3 = !string.IsNullOrEmpty(dnsList[2]) ? dnsList[2] : "0.0.0.0"; + } + } + else + { + adapter.Dns1 = !string.IsNullOrEmpty(dns) ? dns : "0.0.0.0"; + adapter.Dns2 = "0.0.0.0"; + adapter.Dns3 = "0.0.0.0"; + } + + OnEthernetInfoChange(adapter, 0, SystemInfoConstants.EthernetConfigChange); + } + catch (Exception e) + { + var msg = string.Format("GetEthernetInfo failed: {0}", e.Message); + CrestronConsole.PrintLine(msg); + //ErrorLog.Error(msg); + } + + OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange); + } + + /// + /// Gets the current control subnet info + /// + public void GetControlSubnetInfo() + { + OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange); + + var adapter = new ControlSubnetInfo(); + + try + { + // get cs adapter id + var adapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter); + if (!adapterId.Equals(EthernetAdapterType.EthernetUnknownAdapter)) + { + adapter.Enabled = 1; + adapter.IsInAutomaticMode = (ushort)(CrestronEthernetHelper.IsControlSubnetInAutomaticMode ? 1 : 0); + adapter.MacAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS, adapterId); + adapter.IpAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, adapterId); + adapter.Subnet = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, adapterId); + adapter.RouterPrefix = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CONTROL_SUBNET_ROUTER_PREFIX, adapterId); + } + } + catch (Exception e) + { + adapter.Enabled = 0; + adapter.IsInAutomaticMode = 0; + adapter.MacAddress = "NA"; + adapter.IpAddress = "NA"; + adapter.Subnet = "NA"; + adapter.RouterPrefix = "NA"; + + var msg = string.Format("GetControlSubnetInfo failed: {0}", e.Message); + CrestronConsole.PrintLine(msg); + //ErrorLog.Error(msg); + } + + OnControlSubnetInfoChange(adapter, 0, SystemInfoConstants.ControlSubnetConfigChange); + OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange); + } + + /// + /// Gets the program info by index + /// + /// + public void GetProgramInfoByIndex(ushort index) + { + if (index < 1 || index > 10) + return; + + OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange); + + var program = new ProgramInfo(); + + try + { + var response = ""; + CrestronConsole.SendControlSystemCommand(string.Format("progcomments:{0}", index), ref response); + + // no program loaded or running + if (response.Contains("Bad or Incomplete Command")) + { + program.Name = ""; + program.System = ""; + program.Programmer = ""; + program.CompileTime = ""; + program.Database = ""; + program.Environment = ""; + } + else + { + // SIMPL returns + program.Name = ParseConsoleResponse(response, "Program File", ":", "\x0D"); + program.System = ParseConsoleResponse(response, "System Name", ":", "\x0D"); + program.ProgramIdTag = ParseConsoleResponse(response, "Friendly Name", ":", "\x0D"); + program.Programmer = ParseConsoleResponse(response, "Programmer", ":", "\x0D"); + program.CompileTime = ParseConsoleResponse(response, "Compiled On", ":", "\x0D"); + program.Database = ParseConsoleResponse(response, "CrestronDB", ":", "\x0D"); + program.Environment = ParseConsoleResponse(response, "Source Env", ":", "\x0D"); + + // S# returns + if (program.System.Length == 0) + program.System = ParseConsoleResponse(response, "Application Name", ":", "\x0D"); + if (program.Database.Length == 0) + program.Database = ParseConsoleResponse(response, "PlugInVersion", ":", "\x0D"); + if (program.Environment.Length == 0) + program.Environment = ParseConsoleResponse(response, "Program Tool", ":", "\x0D"); + + } + + OnProgramChange(program, index, SystemInfoConstants.ProgramConfigChange); + } + catch (Exception e) + { + var msg = string.Format("GetProgramInfoByIndex failed: {0}", e.Message); + CrestronConsole.PrintLine(msg); + //ErrorLog.Error(msg); + } + + OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange); + } + + /// + /// Gets the processor uptime and passes it to S+ + /// + public void RefreshProcessorUptime() + { + try + { + string response = ""; + CrestronConsole.SendControlSystemCommand("uptime", ref response); + var uptime = ParseConsoleResponse(response, "running for", "running for", "\x0D"); + OnStringChange(uptime, 0, SystemInfoConstants.ProcessorUptimeChange); + } + catch (Exception e) + { + var msg = string.Format("RefreshProcessorUptime failed:\r{0}", e.Message); + CrestronConsole.PrintLine(msg); + //ErrorLog.Error(msg); + } + } + + /// + /// Gets the program uptime, by index, and passes it to S+ + /// + /// + public void RefreshProgramUptimeByIndex(int index) + { + try + { + string response = ""; + CrestronConsole.SendControlSystemCommand(string.Format("proguptime:{0}", index), ref response); + string uptime = ParseConsoleResponse(response, "running for", "running for", "\x0D"); + OnStringChange(uptime, (ushort)index, SystemInfoConstants.ProgramUptimeChange); + } + catch (Exception e) + { + var msg = string.Format("RefreshProgramUptimebyIndex({0}) failed:\r{1}", index, e.Message); + CrestronConsole.PrintLine(msg); + //ErrorLog.Error(msg); + } + } + + /// + /// Sends command to console, passes response back using string change event + /// + /// + public void SendConsoleCommand(string cmd) + { + if (string.IsNullOrEmpty(cmd)) + return; + + string response = ""; + CrestronConsole.SendControlSystemCommand(cmd, ref response); + if (!string.IsNullOrEmpty(response)) + { + if (response.EndsWith("\x0D\\x0A")) + response.Trim('\n'); + + OnStringChange(response, 0, SystemInfoConstants.ConsoleResponseChange); + } + } + + /// + /// private method to parse console messages + /// + /// + /// + /// + /// + /// + private string ParseConsoleResponse(string data, string line, string dataStart, string dataEnd) + { + var response = ""; + + if (string.IsNullOrEmpty(data) || string.IsNullOrEmpty(line) || string.IsNullOrEmpty(dataStart) || string.IsNullOrEmpty(dataEnd)) + return response; + + try + { + var linePos = data.IndexOf(line); + var startPos = data.IndexOf(dataStart, linePos) + dataStart.Length; + var endPos = data.IndexOf(dataEnd, startPos); + response = data.Substring(startPos, endPos - startPos).Trim(); + } + catch (Exception e) + { + var msg = string.Format("ParseConsoleResponse failed: {0}", e.Message); + CrestronConsole.PrintLine(msg); + //ErrorLog.Error(msg); + } + + return response; + } + + /// + /// Protected boolean change event handler + /// + /// + /// + /// + protected void OnBoolChange(bool state, ushort index, ushort type) + { + var handler = BoolChange; + if (handler != null) + { + var args = new BoolChangeEventArgs(state, type); + args.Index = index; + BoolChange(this, args); + } + } + + /// + /// Protected string change event handler + /// + /// + /// + /// + protected void OnStringChange(string value, ushort index, ushort type) + { + var handler = StringChange; + if (handler != null) + { + var args = new StringChangeEventArgs(value, type); + args.Index = index; + StringChange(this, args); + } + } + + /// + /// Protected processor config change event handler + /// + /// + /// + /// + protected void OnProcessorChange(ProcessorInfo processor, ushort index, ushort type) + { + var handler = ProcessorChange; + if (handler != null) + { + var args = new ProcessorChangeEventArgs(processor, type); + args.Index = index; + ProcessorChange(this, args); + } + } + + /// + /// Ethernet change event handler + /// + /// + /// + /// + protected void OnEthernetInfoChange(EthernetInfo ethernet, ushort index, ushort type) + { + var handler = EthernetChange; + if (handler != null) + { + var args = new EthernetChangeEventArgs(ethernet, type); + args.Index = index; + EthernetChange(this, args); + } + } + + /// + /// Control Subnet change event handler + /// + /// + /// + /// + protected void OnControlSubnetInfoChange(ControlSubnetInfo ethernet, ushort index, ushort type) + { + var handler = ControlSubnetChange; + if (handler != null) + { + var args = new ControlSubnetChangeEventArgs(ethernet, type); + args.Index = index; + ControlSubnetChange(this, args); + } + } + + /// + /// Program change event handler + /// + /// + /// + /// + protected void OnProgramChange(ProgramInfo program, ushort index, ushort type) + { + var handler = ProgramChange; + + if (handler != null) + { + var args = new ProgramChangeEventArgs(program, type); + args.Index = index; + ProgramChange(this, args); + } + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Web/RequestHandlers/DefaultRequestHandler.cs b/src/Web/RequestHandlers/DefaultRequestHandler.cs similarity index 95% rename from Pepperdash Core/Pepperdash Core/Web/RequestHandlers/DefaultRequestHandler.cs rename to src/Web/RequestHandlers/DefaultRequestHandler.cs index 93cfae0..ca19cf2 100644 --- a/Pepperdash Core/Pepperdash Core/Web/RequestHandlers/DefaultRequestHandler.cs +++ b/src/Web/RequestHandlers/DefaultRequestHandler.cs @@ -1,17 +1,17 @@ -using Crestron.SimplSharp.WebScripting; - -namespace PepperDash.Core.Web.RequestHandlers -{ - /// - /// Web API default request handler - /// - public class DefaultRequestHandler : WebApiBaseRequestHandler - { - /// - /// Constructor - /// - public DefaultRequestHandler() - : base(true) - { } - } +using Crestron.SimplSharp.WebScripting; + +namespace PepperDash.Core.Web.RequestHandlers +{ + /// + /// Web API default request handler + /// + public class DefaultRequestHandler : WebApiBaseRequestHandler + { + /// + /// Constructor + /// + public DefaultRequestHandler() + : base(true) + { } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Web/RequestHandlers/WebApiBaseRequestHandler.cs b/src/Web/RequestHandlers/WebApiBaseRequestHandler.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/Web/RequestHandlers/WebApiBaseRequestHandler.cs rename to src/Web/RequestHandlers/WebApiBaseRequestHandler.cs index a73abd1..99e4aa9 100644 --- a/Pepperdash Core/Pepperdash Core/Web/RequestHandlers/WebApiBaseRequestHandler.cs +++ b/src/Web/RequestHandlers/WebApiBaseRequestHandler.cs @@ -1,165 +1,165 @@ -using System; -using System.Collections.Generic; -using Crestron.SimplSharp.WebScripting; - -namespace PepperDash.Core.Web.RequestHandlers -{ - /// - /// CWS Base Handler, implements IHttpCwsHandler - /// - public abstract class WebApiBaseRequestHandler : IHttpCwsHandler - { - private readonly Dictionary> _handlers; - protected readonly bool EnableCors; - - /// - /// Constructor - /// - protected WebApiBaseRequestHandler(bool enableCors) - { - EnableCors = enableCors; - - _handlers = new Dictionary> - { - {"CONNECT", HandleConnect}, - {"DELETE", HandleDelete}, - {"GET", HandleGet}, - {"HEAD", HandleHead}, - {"OPTIONS", HandleOptions}, - {"PATCH", HandlePatch}, - {"POST", HandlePost}, - {"PUT", HandlePut}, - {"TRACE", HandleTrace} - }; - } - - /// - /// Constructor - /// - protected WebApiBaseRequestHandler() - : this(false) - { - } - - /// - /// Handles CONNECT method requests - /// - /// - protected virtual void HandleConnect(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles DELETE method requests - /// - /// - protected virtual void HandleDelete(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles GET method requests - /// - /// - protected virtual void HandleGet(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles HEAD method requests - /// - /// - protected virtual void HandleHead(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles OPTIONS method requests - /// - /// - protected virtual void HandleOptions(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles PATCH method requests - /// - /// - protected virtual void HandlePatch(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles POST method requests - /// - /// - protected virtual void HandlePost(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles PUT method requests - /// - /// - protected virtual void HandlePut(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Handles TRACE method requests - /// - /// - protected virtual void HandleTrace(HttpCwsContext context) - { - context.Response.StatusCode = 501; - context.Response.StatusDescription = "Not Implemented"; - context.Response.End(); - } - - /// - /// Process request - /// - /// - public void ProcessRequest(HttpCwsContext context) - { - Action handler; - - if (!_handlers.TryGetValue(context.Request.HttpMethod, out handler)) - { - return; - } - - if (EnableCors) - { - context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); - context.Response.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); - } - - handler(context); - } - } +using System; +using System.Collections.Generic; +using Crestron.SimplSharp.WebScripting; + +namespace PepperDash.Core.Web.RequestHandlers +{ + /// + /// CWS Base Handler, implements IHttpCwsHandler + /// + public abstract class WebApiBaseRequestHandler : IHttpCwsHandler + { + private readonly Dictionary> _handlers; + protected readonly bool EnableCors; + + /// + /// Constructor + /// + protected WebApiBaseRequestHandler(bool enableCors) + { + EnableCors = enableCors; + + _handlers = new Dictionary> + { + {"CONNECT", HandleConnect}, + {"DELETE", HandleDelete}, + {"GET", HandleGet}, + {"HEAD", HandleHead}, + {"OPTIONS", HandleOptions}, + {"PATCH", HandlePatch}, + {"POST", HandlePost}, + {"PUT", HandlePut}, + {"TRACE", HandleTrace} + }; + } + + /// + /// Constructor + /// + protected WebApiBaseRequestHandler() + : this(false) + { + } + + /// + /// Handles CONNECT method requests + /// + /// + protected virtual void HandleConnect(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles DELETE method requests + /// + /// + protected virtual void HandleDelete(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles GET method requests + /// + /// + protected virtual void HandleGet(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles HEAD method requests + /// + /// + protected virtual void HandleHead(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles OPTIONS method requests + /// + /// + protected virtual void HandleOptions(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PATCH method requests + /// + /// + protected virtual void HandlePatch(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles POST method requests + /// + /// + protected virtual void HandlePost(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles PUT method requests + /// + /// + protected virtual void HandlePut(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Handles TRACE method requests + /// + /// + protected virtual void HandleTrace(HttpCwsContext context) + { + context.Response.StatusCode = 501; + context.Response.StatusDescription = "Not Implemented"; + context.Response.End(); + } + + /// + /// Process request + /// + /// + public void ProcessRequest(HttpCwsContext context) + { + Action handler; + + if (!_handlers.TryGetValue(context.Request.HttpMethod, out handler)) + { + return; + } + + if (EnableCors) + { + context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); + context.Response.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); + } + + handler(context); + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/Web/WebApiServer.cs b/src/Web/WebApiServer.cs similarity index 96% rename from Pepperdash Core/Pepperdash Core/Web/WebApiServer.cs rename to src/Web/WebApiServer.cs index f2f8464..cf45b36 100644 --- a/Pepperdash Core/Pepperdash Core/Web/WebApiServer.cs +++ b/src/Web/WebApiServer.cs @@ -1,284 +1,284 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Crestron.SimplSharp; -using Crestron.SimplSharp.WebScripting; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PepperDash.Core.Web.RequestHandlers; - -namespace PepperDash.Core.Web -{ - /// - /// Web API server - /// - public class WebApiServer : IKeyName - { - private const string SplusKey = "Uninitialized Web API Server"; - private const string DefaultName = "Web API Server"; - private const string DefaultBasePath = "/api"; - - private const uint DebugTrace = 0; - private const uint DebugInfo = 1; - private const uint DebugVerbose = 2; - - private readonly CCriticalSection _serverLock = new CCriticalSection(); - private HttpCwsServer _server; - - /// - /// Web API server key - /// - public string Key { get; private set; } - - /// - /// Web API server name - /// - public string Name { get; private set; } - - /// - /// CWS base path, will default to "/api" if not set via initialize method - /// - public string BasePath { get; private set; } - - /// - /// Indicates CWS is registered with base path - /// - public bool IsRegistered { get; private set; } - - /// - /// Http request handler - /// - //public IHttpCwsHandler HttpRequestHandler - //{ - // get { return _server.HttpRequestHandler; } - // set - // { - // if (_server == null) return; - // _server.HttpRequestHandler = value; - // } - //} - - /// - /// Received request event handler - /// - //public event EventHandler ReceivedRequestEvent - //{ - // add { _server.ReceivedRequestEvent += new HttpCwsRequestEventHandler(value); } - // remove { _server.ReceivedRequestEvent -= new HttpCwsRequestEventHandler(value); } - //} - - /// - /// Constructor for S+. Make sure to set necessary properties using init method - /// - public WebApiServer() - : this(SplusKey, DefaultName, null) - { - } - - /// - /// Constructor - /// - /// - /// - public WebApiServer(string key, string basePath) - : this(key, DefaultName, basePath) - { - } - - /// - /// Constructor - /// - /// - /// - /// - public WebApiServer(string key, string name, string basePath) - { - Key = key; - Name = string.IsNullOrEmpty(name) ? DefaultName : name; - BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath; - - if (_server == null) _server = new HttpCwsServer(BasePath); - - _server.setProcessName(Key); - _server.HttpRequestHandler = new DefaultRequestHandler(); - - CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; - CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler; - } - - /// - /// Program status event handler - /// - /// - void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) - { - if (programEventType != eProgramStatusEventType.Stopping) return; - - Debug.Console(DebugInfo, this, "Program stopping. stopping server"); - - Stop(); - } - - /// - /// Ethernet event handler - /// - /// - void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) - { - // Re-enable the server if the link comes back up and the status should be connected - if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered) - { - Debug.Console(DebugInfo, this, "Ethernet link up. Server is alreedy registered."); - return; - } - - Debug.Console(DebugInfo, this, "Ethernet link up. Starting server"); - - Start(); - } - - /// - /// Initializes CWS class - /// - public void Initialize(string key, string basePath) - { - Key = key; - BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath; - } - - /// - /// Adds a route to CWS - /// - public void AddRoute(HttpCwsRoute route) - { - if (route == null) - { - Debug.Console(DebugInfo, this, "Failed to add route, route parameter is null"); - return; - } - - _server.Routes.Add(route); - - } - - /// - /// Removes a route from CWS - /// - /// - public void RemoveRoute(HttpCwsRoute route) - { - if (route == null) - { - Debug.Console(DebugInfo, this, "Failed to remote route, orute parameter is null"); - return; - } - - _server.Routes.Remove(route); - } - - /// - /// Returns a list of the current routes - /// - public HttpCwsRouteCollection GetRouteCollection() - { - return _server.Routes; - } - - /// - /// Starts CWS instance - /// - public void Start() - { - try - { - _serverLock.Enter(); - - if (_server == null) - { - Debug.Console(DebugInfo, this, "Server is null, unable to start"); - return; - } - - if (IsRegistered) - { - Debug.Console(DebugInfo, this, "Server has already been started"); - return; - } - - IsRegistered = _server.Register(); - - Debug.Console(DebugInfo, this, "Starting server, registration {0}", IsRegistered ? "was successful" : "failed"); - } - catch (Exception ex) - { - Debug.Console(DebugInfo, this, "Start Exception Message: {0}", ex.Message); - Debug.Console(DebugVerbose, this, "Start Exception StackTrace: {0}", ex.StackTrace); - if (ex.InnerException != null) - Debug.Console(DebugVerbose, this, "Start Exception InnerException: {0}", ex.InnerException); - } - finally - { - _serverLock.Leave(); - } - } - - /// - /// Stop CWS instance - /// - public void Stop() - { - try - { - _serverLock.Enter(); - - if (_server == null) - { - Debug.Console(DebugInfo, this, "Server is null or has already been stopped"); - return; - } - - IsRegistered = _server.Unregister() == false; - - Debug.Console(DebugInfo, this, "Stopping server, unregistration {0}", IsRegistered ? "failed" : "was successful"); - - _server.Dispose(); - _server = null; - } - catch (Exception ex) - { - Debug.Console(DebugInfo, this, "Server Stop Exception Message: {0}", ex.Message); - Debug.Console(DebugVerbose, this, "Server Stop Exception StackTrace: {0}", ex.StackTrace); - if (ex.InnerException != null) - Debug.Console(DebugVerbose, this, "Server Stop Exception InnerException: {0}", ex.InnerException); - } - finally - { - _serverLock.Leave(); - } - } - - /// - /// Received request handler - /// - /// - /// This is here for development and testing - /// - /// - /// - public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args) - { - try - { - var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented); - Debug.Console(DebugVerbose, this, "RecieveRequestEventHandler Context:\x0d\x0a{0}", j); - } - catch (Exception ex) - { - Debug.Console(DebugInfo, this, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message); - Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace); - if (ex.InnerException != null) - Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception InnerException: {0}", ex.InnerException); - } - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using Crestron.SimplSharp; +using Crestron.SimplSharp.WebScripting; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using PepperDash.Core.Web.RequestHandlers; + +namespace PepperDash.Core.Web +{ + /// + /// Web API server + /// + public class WebApiServer : IKeyName + { + private const string SplusKey = "Uninitialized Web API Server"; + private const string DefaultName = "Web API Server"; + private const string DefaultBasePath = "/api"; + + private const uint DebugTrace = 0; + private const uint DebugInfo = 1; + private const uint DebugVerbose = 2; + + private readonly CCriticalSection _serverLock = new CCriticalSection(); + private HttpCwsServer _server; + + /// + /// Web API server key + /// + public string Key { get; private set; } + + /// + /// Web API server name + /// + public string Name { get; private set; } + + /// + /// CWS base path, will default to "/api" if not set via initialize method + /// + public string BasePath { get; private set; } + + /// + /// Indicates CWS is registered with base path + /// + public bool IsRegistered { get; private set; } + + /// + /// Http request handler + /// + //public IHttpCwsHandler HttpRequestHandler + //{ + // get { return _server.HttpRequestHandler; } + // set + // { + // if (_server == null) return; + // _server.HttpRequestHandler = value; + // } + //} + + /// + /// Received request event handler + /// + //public event EventHandler ReceivedRequestEvent + //{ + // add { _server.ReceivedRequestEvent += new HttpCwsRequestEventHandler(value); } + // remove { _server.ReceivedRequestEvent -= new HttpCwsRequestEventHandler(value); } + //} + + /// + /// Constructor for S+. Make sure to set necessary properties using init method + /// + public WebApiServer() + : this(SplusKey, DefaultName, null) + { + } + + /// + /// Constructor + /// + /// + /// + public WebApiServer(string key, string basePath) + : this(key, DefaultName, basePath) + { + } + + /// + /// Constructor + /// + /// + /// + /// + public WebApiServer(string key, string name, string basePath) + { + Key = key; + Name = string.IsNullOrEmpty(name) ? DefaultName : name; + BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath; + + if (_server == null) _server = new HttpCwsServer(BasePath); + + _server.setProcessName(Key); + _server.HttpRequestHandler = new DefaultRequestHandler(); + + CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; + CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler; + } + + /// + /// Program status event handler + /// + /// + void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + if (programEventType != eProgramStatusEventType.Stopping) return; + + Debug.Console(DebugInfo, this, "Program stopping. stopping server"); + + Stop(); + } + + /// + /// Ethernet event handler + /// + /// + void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) + { + // Re-enable the server if the link comes back up and the status should be connected + if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered) + { + Debug.Console(DebugInfo, this, "Ethernet link up. Server is alreedy registered."); + return; + } + + Debug.Console(DebugInfo, this, "Ethernet link up. Starting server"); + + Start(); + } + + /// + /// Initializes CWS class + /// + public void Initialize(string key, string basePath) + { + Key = key; + BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath; + } + + /// + /// Adds a route to CWS + /// + public void AddRoute(HttpCwsRoute route) + { + if (route == null) + { + Debug.Console(DebugInfo, this, "Failed to add route, route parameter is null"); + return; + } + + _server.Routes.Add(route); + + } + + /// + /// Removes a route from CWS + /// + /// + public void RemoveRoute(HttpCwsRoute route) + { + if (route == null) + { + Debug.Console(DebugInfo, this, "Failed to remote route, orute parameter is null"); + return; + } + + _server.Routes.Remove(route); + } + + /// + /// Returns a list of the current routes + /// + public HttpCwsRouteCollection GetRouteCollection() + { + return _server.Routes; + } + + /// + /// Starts CWS instance + /// + public void Start() + { + try + { + _serverLock.Enter(); + + if (_server == null) + { + Debug.Console(DebugInfo, this, "Server is null, unable to start"); + return; + } + + if (IsRegistered) + { + Debug.Console(DebugInfo, this, "Server has already been started"); + return; + } + + IsRegistered = _server.Register(); + + Debug.Console(DebugInfo, this, "Starting server, registration {0}", IsRegistered ? "was successful" : "failed"); + } + catch (Exception ex) + { + Debug.Console(DebugInfo, this, "Start Exception Message: {0}", ex.Message); + Debug.Console(DebugVerbose, this, "Start Exception StackTrace: {0}", ex.StackTrace); + if (ex.InnerException != null) + Debug.Console(DebugVerbose, this, "Start Exception InnerException: {0}", ex.InnerException); + } + finally + { + _serverLock.Leave(); + } + } + + /// + /// Stop CWS instance + /// + public void Stop() + { + try + { + _serverLock.Enter(); + + if (_server == null) + { + Debug.Console(DebugInfo, this, "Server is null or has already been stopped"); + return; + } + + IsRegistered = _server.Unregister() == false; + + Debug.Console(DebugInfo, this, "Stopping server, unregistration {0}", IsRegistered ? "failed" : "was successful"); + + _server.Dispose(); + _server = null; + } + catch (Exception ex) + { + Debug.Console(DebugInfo, this, "Server Stop Exception Message: {0}", ex.Message); + Debug.Console(DebugVerbose, this, "Server Stop Exception StackTrace: {0}", ex.StackTrace); + if (ex.InnerException != null) + Debug.Console(DebugVerbose, this, "Server Stop Exception InnerException: {0}", ex.InnerException); + } + finally + { + _serverLock.Leave(); + } + } + + /// + /// Received request handler + /// + /// + /// This is here for development and testing + /// + /// + /// + public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args) + { + try + { + var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented); + Debug.Console(DebugVerbose, this, "RecieveRequestEventHandler Context:\x0d\x0a{0}", j); + } + catch (Exception ex) + { + Debug.Console(DebugInfo, this, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message); + Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace); + if (ex.InnerException != null) + Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception InnerException: {0}", ex.InnerException); + } + } + } } \ No newline at end of file diff --git a/Pepperdash Core/Pepperdash Core/WebApi/Presets/Preset.cs b/src/WebApi/Presets/Preset.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/WebApi/Presets/Preset.cs rename to src/WebApi/Presets/Preset.cs diff --git a/Pepperdash Core/Pepperdash Core/WebApi/Presets/User.cs b/src/WebApi/Presets/User.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/WebApi/Presets/User.cs rename to src/WebApi/Presets/User.cs diff --git a/Pepperdash Core/Pepperdash Core/WebApi/Presets/WebApiPasscodeClient.cs b/src/WebApi/Presets/WebApiPasscodeClient.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/WebApi/Presets/WebApiPasscodeClient.cs rename to src/WebApi/Presets/WebApiPasscodeClient.cs diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/Serialization/IXSigSerialization.cs b/src/XSigUtility/Serialization/IXSigSerialization.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/XSigUtility/Serialization/IXSigSerialization.cs rename to src/XSigUtility/Serialization/IXSigSerialization.cs diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/Serialization/XSigSerializationException.cs b/src/XSigUtility/Serialization/XSigSerializationException.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/XSigUtility/Serialization/XSigSerializationException.cs rename to src/XSigUtility/Serialization/XSigSerializationException.cs diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigAnalogToken.cs b/src/XSigUtility/Tokens/XSigAnalogToken.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigAnalogToken.cs rename to src/XSigUtility/Tokens/XSigAnalogToken.cs diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigDigitalToken.cs b/src/XSigUtility/Tokens/XSigDigitalToken.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigDigitalToken.cs rename to src/XSigUtility/Tokens/XSigDigitalToken.cs diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigSerialToken.cs b/src/XSigUtility/Tokens/XSigSerialToken.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigSerialToken.cs rename to src/XSigUtility/Tokens/XSigSerialToken.cs diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigToken.cs b/src/XSigUtility/Tokens/XSigToken.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigToken.cs rename to src/XSigUtility/Tokens/XSigToken.cs diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigTokenType.cs b/src/XSigUtility/Tokens/XSigTokenType.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/XSigUtility/Tokens/XSigTokenType.cs rename to src/XSigUtility/Tokens/XSigTokenType.cs diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/XSigHelpers.cs b/src/XSigUtility/XSigHelpers.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/XSigUtility/XSigHelpers.cs rename to src/XSigUtility/XSigHelpers.cs diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/XSigTokenStreamReader.cs b/src/XSigUtility/XSigTokenStreamReader.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/XSigUtility/XSigTokenStreamReader.cs rename to src/XSigUtility/XSigTokenStreamReader.cs diff --git a/Pepperdash Core/Pepperdash Core/XSigUtility/XSigTokenStreamWriter.cs b/src/XSigUtility/XSigTokenStreamWriter.cs similarity index 100% rename from Pepperdash Core/Pepperdash Core/XSigUtility/XSigTokenStreamWriter.cs rename to src/XSigUtility/XSigTokenStreamWriter.cs