Merge remote-tracking branch 'origin/release' into bugfiix/pdc-3

# Conflicts:
#	Pepperdash Core/Pepperdash Core/Properties/AssemblyInfo.cs
This commit is contained in:
Neil Dorin
2019-04-01 16:19:28 -06:00
10 changed files with 2858 additions and 742 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -18,9 +18,7 @@ using Crestron.SimplSharp.CrestronSockets;
namespace PepperDash.Core
{
#region GenericSocketStatusChangeEventArgs
public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client);
public class GenericSocketStatusChageEventArgs : EventArgs
{
public ISocketStatus Client { get; private set; }
@@ -32,44 +30,68 @@ namespace PepperDash.Core
Client = client;
}
}
#endregion
#region DynamicTCPServerStateChangedEventArgs
public delegate void DynamicTCPServerStateChangedEventDelegate(object server);
public class DynamicTCPServerStateChangedEventArgs : EventArgs
public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state);
public class GenericTcpServerStateChangedEventArgs : EventArgs
{
public bool Secure { get; private set; }
public object Server { get; private set; }
public ServerState State { get; private set; }
public DynamicTCPServerStateChangedEventArgs() { }
public GenericTcpServerStateChangedEventArgs() { }
public DynamicTCPServerStateChangedEventArgs(object server, bool secure)
public GenericTcpServerStateChangedEventArgs(ServerState state)
{
Secure = secure;
Server = server;
State = state;
}
}
#endregion
#region DynamicTCPSocketStatusChangeEventDelegate
public delegate void DynamicTCPSocketStatusChangeEventDelegate(object server);
public class DynamicTCPSocketStatusChangeEventArgs : EventArgs
public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus);
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
{
public bool Secure { get; private set; }
public object Server { get; private set; }
public object Socket { get; private set; }
public uint ReceivedFromClientIndex { get; private set; }
public SocketStatus ClientStatus { get; set; }
public GenericTcpServerSocketStatusChangeEventArgs() { }
public DynamicTCPSocketStatusChangeEventArgs() { }
public DynamicTCPSocketStatusChangeEventArgs(object server, bool secure)
public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
{
Secure = secure;
Server = server;
Socket = socket;
ClientStatus = clientStatus;
}
public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus)
{
Socket = socket;
ReceivedFromClientIndex = clientIndex;
ClientStatus = clientStatus;
}
}
#endregion
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
{
public uint ReceivedFromClientIndex { get; private set; }
public string Text { get; private set; }
public GenericTcpServerCommMethodReceiveTextArgs(string text)
{
Text = text;
}
public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex)
{
Text = text;
ReceivedFromClientIndex = clientIndex;
}
}
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
{
public bool IsReady;
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
{
IsReady = isReady;
}
}

View File

@@ -1,371 +0,0 @@
/*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
{
public class GenericSecureTcpIpClient : Device, ISocketStatus, IAutoReconnect
{
#region Events
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
#endregion
#region Properties & Variables
/// <summary>
/// Address of server
/// </summary>
public string Hostname { get; set; }
/// <summary>
/// Port on server
/// </summary>
public int Port { get; set; }
/// <summary>
/// S+ helper
/// </summary>
public ushort UPort
{
get { return Convert.ToUInt16(Port); }
set { Port = Convert.ToInt32(value); }
}
/// <summary>
/// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class
/// </summary>
public bool SharedKeyRequired { get; set; }
/// <summary>
/// S+ helper for requires shared key bool
/// </summary>
public ushort USharedKeyRequired
{
set
{
if (value == 1)
SharedKeyRequired = true;
else
SharedKeyRequired = false;
}
}
/// <summary>
/// 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
/// </summary>
public string SharedKey { get; set; }
/// <summary>
/// flag to show the client is waiting for the server to send the shared key
/// </summary>
private bool WaitingForSharedKeyResponse { get; set; }
/// <summary>
/// Defaults to 2000
/// </summary>
public int BufferSize { get; set; }
/// <summary>
/// Bool showing if socket is connected
/// </summary>
public bool IsConnected
{
get
{
return (Client != null) && (Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED);
}
}
/// <summary>
/// S+ helper for IsConnected
/// </summary>
public ushort UIsConnected
{
get { return (ushort)(IsConnected ? 1 : 0); }
}
/// <summary>
/// Client socket status Read only
/// </summary>
public SocketStatus ClientStatus
{
get
{
if (Client == null)
return SocketStatus.SOCKET_STATUS_NO_CONNECT;
return Client.ClientStatus;
}
}
/// <summary>
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
/// and IsConnected would be true when this == 2.
/// </summary>
public ushort UStatus
{
get { return (ushort)ClientStatus; }
}
/// <summary>
/// Status text shows the message associated with socket status
/// </summary>
public string ClientStatusText { get { return ClientStatus.ToString(); } }
/// <summary>
/// bool to track if auto reconnect should be set on the socket
/// </summary>
public bool AutoReconnect { get; set; }
/// <summary>
/// S+ helper for AutoReconnect
/// </summary>
public ushort UAutoReconnect
{
get { return (ushort)(AutoReconnect ? 1 : 0); }
set { AutoReconnect = value == 1; }
}
/// <summary>
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
/// </summary>
public int AutoReconnectIntervalMs { get; set; }
/// <summary>
/// Flag Set only when the disconnect method is called.
/// </summary>
bool DisconnectCalledByUser;
/// <summary>
/// Connected bool
/// </summary>
public bool Connected
{
get { return (Client != null) && (Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED); }
}
CTimer RetryTimer; //private Timer for auto reconnect
SecureTCPClient Client; //Secure Client Class
#endregion
#region Constructors
//Base class constructor
public GenericSecureTcpIpClient(string key, string address, int port, int bufferSize)
: base(key)
{
Hostname = address;
Port = port;
BufferSize = bufferSize;
AutoReconnectIntervalMs = 5000;
}
//base class constructor
public GenericSecureTcpIpClient()
: base("Uninitialized DynamicTcpClient")
{
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
BufferSize = 2000;
}
#endregion
#region Methods
/// <summary>
/// Just to help S+ set the key
/// </summary>
public void Initialize(string key)
{
Key = key;
}
/// <summary>
/// Handles closing this up when the program shuts down
/// </summary>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType == eProgramStatusEventType.Stopping)
{
if (Client != null)
{
Debug.Console(1, this, "Program stopping. Closing connection");
Client.DisconnectFromServer();
Client.Dispose();
}
}
}
/// <summary>
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
/// </summary>
public void Connect()
{
if (IsConnected)
return;
if (string.IsNullOrEmpty(Hostname))
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "DynamicTcpClient '{0}': No address set", Key);
ErrorLog.Warn(string.Format("DynamicTcpClient '{0}': No address set", Key));
return;
}
if (Port < 1 || Port > 65535)
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "DynamicTcpClient '{0}': Invalid port", Key);
ErrorLog.Warn(string.Format("DynamicTcpClient '{0}': Invalid port", Key));
return;
}
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "DynamicTcpClient '{0}': No Shared Key set", Key);
ErrorLog.Warn(string.Format("DynamicTcpClient '{0}': No Shared Key set", Key));
return;
}
Client = new SecureTCPClient(Hostname, Port, BufferSize);
Client.SocketStatusChange += SecureClient_SocketStatusChange;
DisconnectCalledByUser = false;
if (SharedKeyRequired)
WaitingForSharedKeyResponse = true;
Client.ConnectToServer();
}
/// <summary>
/// Disconnect client. Does not dispose.
/// </summary>
public void Disconnect()
{
DisconnectCalledByUser = true;
if(Client != null)
Client.DisconnectFromServer();
}
/// <summary>
/// callback after connection made
/// </summary>
/// <param name="o"></param>
void ConnectToServerCallback(object o)
{
Client.ConnectToServer();
if (Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
WaitAndTryReconnect();
}
/// <summary>
/// Called from Socket Status change if auto reconnect and socket disconnected (Not disconnected by user)
/// </summary>
void WaitAndTryReconnect()
{
Client.DisconnectFromServer();
Debug.Console(2, "Attempting reconnect, status={0}", Client.ClientStatus);
if (!DisconnectCalledByUser)
RetryTimer = new CTimer(ConnectToServerCallback, AutoReconnectIntervalMs);
}
/// <summary>
/// Receive callback
/// </summary>
/// <param name="client"></param>
/// <param name="numBytes"></param>
void SecureReceive(SecureTCPClient client, int numBytes)
{
if (numBytes > 0)
{
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
if (WaitingForSharedKeyResponse && SharedKeyRequired)
{
if (str != (SharedKey + "\n"))
{
WaitingForSharedKeyResponse = false;
client.DisconnectFromServer();
CrestronConsole.PrintLine("Client {0} was disconnected from server because the server did not respond with a matching shared key after connection", Key);
ErrorLog.Error("Client {0} was disconnected from server because the server did not respond with a matching shared key after connection", Key);
return;
}
else
{
WaitingForSharedKeyResponse = false;
CrestronConsole.PrintLine("Client {0} successfully connected to the server and received the Shared Key. Ready for communication", Key);
}
}
else
{
var bytesHandler = BytesReceived;
if (bytesHandler != null)
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
var textHandler = TextReceived;
if (textHandler != null)
{
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
}
}
}
client.ReceiveDataAsync(SecureReceive);
}
/// <summary>
/// General send method
/// </summary>
public void SendText(string text)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
Client.SendData(bytes, bytes.Length);
}
public void SendBytes(byte[] bytes)
{
Client.SendData(bytes, bytes.Length);
}
/// <summary>
/// SocketStatusChange Callback
/// </summary>
/// <param name="client"></param>
/// <param name="clientSocketStatus"></param>
void SecureClient_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus)
{
Debug.Console(2, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
if (client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED && !DisconnectCalledByUser && AutoReconnect)
WaitAndTryReconnect();
// Probably doesn't need to be a switch since all other cases were eliminated
switch (clientSocketStatus)
{
case SocketStatus.SOCKET_STATUS_CONNECTED:
client.ReceiveDataAsync(SecureReceive);
if(SharedKeyRequired)
SendText(SharedKey + "\n");
DisconnectCalledByUser = false;
break;
}
var handler = ConnectionChange;
if (handler != null)
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
}
#endregion
}
}

View File

@@ -0,0 +1,739 @@
/*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
{
public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
{
/// <summary>
/// Band aid delegate for choked server
/// </summary>
internal delegate void ConnectionHasHungCallbackDelegate();
#region Events
//public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived;
public event EventHandler<GenericTcpServerSocketStatusChangeEventArgs> ConnectionChange;
/// <summary>
/// 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.
/// </summary>
internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback;
/// <summary>
/// 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.
/// </summary>
public event EventHandler<GenericTcpServerClientReadyForcommunicationsEventArgs> ClientReadyForCommunications;
#endregion
#region Properties & Variables
/// <summary>
/// Address of server
/// </summary>
public string Hostname { get; set; }
/// <summary>
/// Port on server
/// </summary>
public int Port { get; set; }
/// <summary>
/// S+ helper
/// </summary>
public ushort UPort
{
get { return Convert.ToUInt16(Port); }
set { Port = Convert.ToInt32(value); }
}
/// <summary>
/// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class
/// </summary>
public bool SharedKeyRequired { get; set; }
/// <summary>
/// S+ helper for requires shared key bool
/// </summary>
public ushort USharedKeyRequired
{
set
{
if (value == 1)
SharedKeyRequired = true;
else
SharedKeyRequired = false;
}
}
/// <summary>
/// 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
/// </summary>
public string SharedKey { get; set; }
/// <summary>
/// flag to show the client is waiting for the server to send the shared key
/// </summary>
private bool WaitingForSharedKeyResponse { get; set; }
/// <summary>
/// Defaults to 2000
/// </summary>
public int BufferSize { get; set; }
/// <summary>
/// Semaphore on connect method
/// </summary>
bool IsTryingToConnect;
/// <summary>
/// Bool showing if socket is connected
/// </summary>
public bool IsConnected
{
get
{
if (Client != null)
return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED;
else
return false;
}
}
/// <summary>
/// S+ helper for IsConnected
/// </summary>
public ushort UIsConnected
{
get { return (ushort)(IsConnected ? 1 : 0); }
}
/// <summary>
/// Bool showing if socket is ready for communication after shared key exchange
/// </summary>
public bool IsReadyForCommunication { get; set; }
/// <summary>
/// S+ helper for IsReadyForCommunication
/// </summary>
public ushort UIsReadyForCommunication
{
get { return (ushort)(IsReadyForCommunication ? 1 : 0); }
}
/// <summary>
/// Client socket status Read only
/// </summary>
public SocketStatus ClientStatus
{
get
{
if (Client != null)
return Client.ClientStatus;
else
return SocketStatus.SOCKET_STATUS_NO_CONNECT;
}
}
/// <summary>
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
/// and IsConnected would be true when this == 2.
/// </summary>
public ushort UStatus
{
get { return (ushort)ClientStatus; }
}
/// <summary>
/// Status text shows the message associated with socket status
/// </summary>
public string ClientStatusText { get { return ClientStatus.ToString(); } }
/// <summary>
/// bool to track if auto reconnect should be set on the socket
/// </summary>
public bool AutoReconnect { get; set; }
/// <summary>
/// S+ helper for AutoReconnect
/// </summary>
public ushort UAutoReconnect
{
get { return (ushort)(AutoReconnect ? 1 : 0); }
set { AutoReconnect = value == 1; }
}
/// <summary>
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
/// </summary>
public int AutoReconnectIntervalMs { get; set; }
/// <summary>
/// Flag Set only when the disconnect method is called.
/// </summary>
bool DisconnectCalledByUser;
/// <summary>
/// private Timer for auto reconnect
/// </summary>
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;
/// <summary>
/// Used to force disconnection on a dead connect attempt
/// </summary>
CTimer ConnectFailTimer;
CTimer WaitForSharedKey;
private int ConnectionCount;
/// <summary>
/// Internal secure client
/// </summary>
SecureTCPClient Client;
bool ProgramIsStopping;
#endregion
#region Constructors
//Base class 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;
}
//base class constructor
public GenericSecureTcpIpClient_ForServer()
: base("Uninitialized DynamicTcpClient")
{
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
BufferSize = 2000;
}
#endregion
#region Methods
/// <summary>
/// Just to help S+ set the key
/// </summary>
public void Initialize(string key)
{
Key = key;
}
/// <summary>
/// Handles closing this up when the program shuts down
/// </summary>
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();
}
}
/// <summary>
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
/// </summary>
public void Connect()
{
ConnectionCount++;
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "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;
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(1, this, Debug.ErrorLogLevel.Notice, "Making Connection Count:{0}", ConnectionCount);
Client.ConnectToServerAsync(o =>
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
if (ConnectFailTimer != null)
{
ConnectFailTimer.Stop();
}
IsTryingToConnect = false;
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "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
{
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();
}
}
/// <summary>
///
/// </summary>
public void Disconnect()
{
Debug.Console(2, "Disconnect Called");
DisconnectCalledByUser = true;
if (IsConnected)
{
Client.DisconnectFromServer();
}
if (RetryTimer != null)
{
RetryTimer.Stop();
RetryTimer = null;
}
Cleanup();
}
/// <summary>
/// Internal call to close up client. ALWAYS use this when disconnecting.
/// </summary>
void Cleanup()
{
IsTryingToConnect = false;
if (Client != null)
{
//SecureClient.DisconnectFromServer();
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
Client.SocketStatusChange -= Client_SocketStatusChange;
Client.Dispose();
Client = null;
}
if (ConnectFailTimer != null)
{
ConnectFailTimer.Stop();
ConnectFailTimer.Dispose();
ConnectFailTimer = null;
}
}
/// <summary>ff
/// Called from Connect failure or Socket Status change if
/// auto reconnect and socket disconnected (Not disconnected by user)
/// </summary>
void CheckClosedAndTryReconnect()
{
if (Client != null)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Cleaning up remotely closed/failed connection.");
Cleanup();
}
if (!DisconnectCalledByUser && AutoReconnect)
{
var halfInterval = AutoReconnectIntervalMs / 2;
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Attempting reconnect in {0} ms, randomized", rndTime);
if (RetryTimer != null)
{
RetryTimer.Stop();
RetryTimer = null;
}
RetryTimer = new CTimer(o => Connect(), rndTime);
}
}
/// <summary>
/// Receive callback
/// </summary>
/// <param name="client"></param>
/// <param name="numBytes"></param>
void Receive(SecureTCPClient 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(1, this, "Server asking for shared key, sending");
SendText(SharedKey + "\n");
}
else if (SharedKeyRequired && str == "Shared Key Match")
{
StopWaitForSharedKeyTimer();
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Shared key confirmed. Ready for communication");
OnClientReadyForcommunications(true); // Successful key exchange
}
else if (SharedKeyRequired == false && IsReadyForCommunication == false)
{
OnClientReadyForcommunications(true); // Key not required
}
else
{
//var bytesHandler = BytesReceived;
//if (bytesHandler != null)
// bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
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, Debug.ErrorLogLevel.Notice, "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, Debug.ErrorLogLevel.Notice, "Stoping Heartbeat Send");
HeartbeatSendTimer.Stop();
HeartbeatSendTimer = null;
}
if (HeartbeatAckTimer != null)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "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(1, 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);
}
}
/// <summary>
///
/// </summary>
void StopWaitForSharedKeyTimer()
{
if (WaitForSharedKey != null)
{
WaitForSharedKey.Stop();
WaitForSharedKey = null;
}
}
/// <summary>
/// General send method
/// </summary>
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);
}
}
}
/// <summary>
///
/// </summary>
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);
}
}
}
/// <summary>
/// SocketStatusChange Callback
/// </summary>
/// <param name="client"></param>
/// <param name="clientSocketStatus"></param>
void Client_SocketStatusChange(SecureTCPClient client, SocketStatus clientSocketStatus)
{
if (ProgramIsStopping)
{
ProgramIsStopping = false;
return;
}
try
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
OnConnectionChange();
// The client could be null or disposed by this time...
if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{
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);
}
}
/// <summary>
/// Helper for ConnectionChange event
/// </summary>
void OnConnectionChange()
{
var handler = ConnectionChange;
if (handler != null)
ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus));
}
/// <summary>
/// Helper to fire ClientReadyForCommunications event
/// </summary>
void OnClientReadyForcommunications(bool isReady)
{
IsReadyForCommunication = isReady;
if (this.IsReadyForCommunication) { HeartbeatStart(); }
var handler = ClientReadyForCommunications;
if (handler != null)
handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication));
}
#endregion
}
}

View File

@@ -0,0 +1,739 @@
/*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
{
public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
{
/// <summary>
/// Band aid delegate for choked server
/// </summary>
internal delegate void ConnectionHasHungCallbackDelegate();
#region Events
//public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived;
public event EventHandler<GenericTcpServerSocketStatusChangeEventArgs> ConnectionChange;
/// <summary>
/// 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.
/// </summary>
internal ConnectionHasHungCallbackDelegate ConnectionHasHungCallback;
/// <summary>
/// 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.
/// </summary>
public event EventHandler<GenericTcpServerClientReadyForcommunicationsEventArgs> ClientReadyForCommunications;
#endregion
#region Properties & Variables
/// <summary>
/// Address of server
/// </summary>
public string Hostname { get; set; }
/// <summary>
/// Port on server
/// </summary>
public int Port { get; set; }
/// <summary>
/// S+ helper
/// </summary>
public ushort UPort
{
get { return Convert.ToUInt16(Port); }
set { Port = Convert.ToInt32(value); }
}
/// <summary>
/// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class
/// </summary>
public bool SharedKeyRequired { get; set; }
/// <summary>
/// S+ helper for requires shared key bool
/// </summary>
public ushort USharedKeyRequired
{
set
{
if (value == 1)
SharedKeyRequired = true;
else
SharedKeyRequired = false;
}
}
/// <summary>
/// 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
/// </summary>
public string SharedKey { get; set; }
/// <summary>
/// flag to show the client is waiting for the server to send the shared key
/// </summary>
private bool WaitingForSharedKeyResponse { get; set; }
/// <summary>
/// Defaults to 2000
/// </summary>
public int BufferSize { get; set; }
/// <summary>
/// Semaphore on connect method
/// </summary>
bool IsTryingToConnect;
/// <summary>
/// Bool showing if socket is connected
/// </summary>
public bool IsConnected
{
get
{
if (Client != null)
return Client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED;
else
return false;
}
}
/// <summary>
/// S+ helper for IsConnected
/// </summary>
public ushort UIsConnected
{
get { return (ushort)(IsConnected ? 1 : 0); }
}
/// <summary>
/// Bool showing if socket is ready for communication after shared key exchange
/// </summary>
public bool IsReadyForCommunication { get; set; }
/// <summary>
/// S+ helper for IsReadyForCommunication
/// </summary>
public ushort UIsReadyForCommunication
{
get { return (ushort)(IsReadyForCommunication ? 1 : 0); }
}
/// <summary>
/// Client socket status Read only
/// </summary>
public SocketStatus ClientStatus
{
get
{
if (Client != null)
return Client.ClientStatus;
else
return SocketStatus.SOCKET_STATUS_NO_CONNECT;
}
}
/// <summary>
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
/// and IsConnected would be true when this == 2.
/// </summary>
public ushort UStatus
{
get { return (ushort)ClientStatus; }
}
/// <summary>
/// Status text shows the message associated with socket status
/// </summary>
public string ClientStatusText { get { return ClientStatus.ToString(); } }
/// <summary>
/// bool to track if auto reconnect should be set on the socket
/// </summary>
public bool AutoReconnect { get; set; }
/// <summary>
/// S+ helper for AutoReconnect
/// </summary>
public ushort UAutoReconnect
{
get { return (ushort)(AutoReconnect ? 1 : 0); }
set { AutoReconnect = value == 1; }
}
/// <summary>
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
/// </summary>
public int AutoReconnectIntervalMs { get; set; }
/// <summary>
/// Flag Set only when the disconnect method is called.
/// </summary>
bool DisconnectCalledByUser;
/// <summary>
/// private Timer for auto reconnect
/// </summary>
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;
/// <summary>
/// Used to force disconnection on a dead connect attempt
/// </summary>
CTimer ConnectFailTimer;
CTimer WaitForSharedKey;
private int ConnectionCount;
/// <summary>
/// Internal secure client
/// </summary>
TCPClient Client;
bool ProgramIsStopping;
#endregion
#region Constructors
//Base class 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;
}
//base class constructor
public GenericTcpIpClient_ForServer()
: base("Uninitialized DynamicTcpClient")
{
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
BufferSize = 2000;
}
#endregion
#region Methods
/// <summary>
/// Just to help S+ set the key
/// </summary>
public void Initialize(string key)
{
Key = key;
}
/// <summary>
/// Handles closing this up when the program shuts down
/// </summary>
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();
}
}
/// <summary>
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
/// </summary>
public void Connect()
{
ConnectionCount++;
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "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;
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(1, this, Debug.ErrorLogLevel.Notice, "Making Connection Count:{0}", ConnectionCount);
Client.ConnectToServerAsync(o =>
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
if (ConnectFailTimer != null)
{
ConnectFailTimer.Stop();
}
IsTryingToConnect = false;
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "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
{
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();
}
}
/// <summary>
///
/// </summary>
public void Disconnect()
{
Debug.Console(2, "Disconnect Called");
DisconnectCalledByUser = true;
if (IsConnected)
{
Client.DisconnectFromServer();
}
if (RetryTimer != null)
{
RetryTimer.Stop();
RetryTimer = null;
}
Cleanup();
}
/// <summary>
/// Internal call to close up client. ALWAYS use this when disconnecting.
/// </summary>
void Cleanup()
{
IsTryingToConnect = false;
if (Client != null)
{
//SecureClient.DisconnectFromServer();
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
Client.SocketStatusChange -= Client_SocketStatusChange;
Client.Dispose();
Client = null;
}
if (ConnectFailTimer != null)
{
ConnectFailTimer.Stop();
ConnectFailTimer.Dispose();
ConnectFailTimer = null;
}
}
/// <summary>ff
/// Called from Connect failure or Socket Status change if
/// auto reconnect and socket disconnected (Not disconnected by user)
/// </summary>
void CheckClosedAndTryReconnect()
{
if (Client != null)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Cleaning up remotely closed/failed connection.");
Cleanup();
}
if (!DisconnectCalledByUser && AutoReconnect)
{
var halfInterval = AutoReconnectIntervalMs / 2;
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Attempting reconnect in {0} ms, randomized", rndTime);
if (RetryTimer != null)
{
RetryTimer.Stop();
RetryTimer = null;
}
RetryTimer = new CTimer(o => Connect(), rndTime);
}
}
/// <summary>
/// Receive callback
/// </summary>
/// <param name="client"></param>
/// <param name="numBytes"></param>
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(1, this, "Server asking for shared key, sending");
SendText(SharedKey + "\n");
}
else if (SharedKeyRequired && str == "Shared Key Match")
{
StopWaitForSharedKeyTimer();
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Shared key confirmed. Ready for communication");
OnClientReadyForcommunications(true); // Successful key exchange
}
else if (SharedKeyRequired == false && IsReadyForCommunication == false)
{
OnClientReadyForcommunications(true); // Key not required
}
else
{
//var bytesHandler = BytesReceived;
//if (bytesHandler != null)
// bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
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, Debug.ErrorLogLevel.Notice, "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, Debug.ErrorLogLevel.Notice, "Stoping Heartbeat Send");
HeartbeatSendTimer.Stop();
HeartbeatSendTimer = null;
}
if (HeartbeatAckTimer != null)
{
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "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(1, 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);
}
}
/// <summary>
///
/// </summary>
void StopWaitForSharedKeyTimer()
{
if (WaitForSharedKey != null)
{
WaitForSharedKey.Stop();
WaitForSharedKey = null;
}
}
/// <summary>
/// General send method
/// </summary>
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);
}
}
}
/// <summary>
///
/// </summary>
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);
}
}
}
/// <summary>
/// SocketStatusChange Callback
/// </summary>
/// <param name="client"></param>
/// <param name="clientSocketStatus"></param>
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
{
if (ProgramIsStopping)
{
ProgramIsStopping = false;
return;
}
try
{
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
OnConnectionChange();
// The client could be null or disposed by this time...
if (Client == null || Client.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{
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);
}
}
/// <summary>
/// Helper for ConnectionChange event
/// </summary>
void OnConnectionChange()
{
var handler = ConnectionChange;
if (handler != null)
ConnectionChange(this, new GenericTcpServerSocketStatusChangeEventArgs(this, Client.ClientStatus));
}
/// <summary>
/// Helper to fire ClientReadyForCommunications event
/// </summary>
void OnClientReadyForcommunications(bool isReady)
{
IsReadyForCommunication = isReady;
if (this.IsReadyForCommunication) { HeartbeatStart(); }
var handler = ClientReadyForCommunications;
if (handler != null)
handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication));
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Core
{
public class TcpServerConfigObject
{
public string Key { get; set; }
public bool Secure { get; set; }
public ushort MaxClients { get; set; }
public int Port { get; set; }
public bool SharedKeyRequired { get; set; }
public string SharedKey { get; set; }
public bool HeartbeatRequired { get; set; }
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
public string HeartbeatStringToMatch { get; set; }
public int BufferSize { get; set; }
}
}

View File

@@ -64,9 +64,10 @@
<ItemGroup>
<Compile Include="CommunicationExtras.cs" />
<Compile Include="Comm\ControlPropertiesConfig.cs" />
<Compile Include="Comm\GenericTcpIpClient_ForServer.cs" />
<Compile Include="Comm\GenericHttpSseClient.cs" />
<Compile Include="Comm\GenericSecureTcpIpServer.cs" />
<Compile Include="Comm\GenericSecureTcpIpClient.cs">
<Compile Include="Comm\GenericSecureTcpIpClient_ForServer.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Comm\eControlMethods.cs" />
@@ -75,6 +76,7 @@
<Compile Include="Comm\EventArgs.cs" />
<Compile Include="Comm\GenericSshClient.cs" />
<Compile Include="Comm\GenericUdpServer.cs" />
<Compile Include="Comm\TcpServerConfigObject.cs" />
<Compile Include="Config\PortalConfigReader.cs" />
<Compile Include="CoreInterfaces.cs" />
<Compile Include="JsonToSimpl\JsonToSimplPortalFileMaster.cs" />