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