mirror of
https://github.com/PepperDash/PepperDashCore.git
synced 2026-02-13 11:44:46 +00:00
fix: update solution filename and folder paths for workflow expectations.
This commit is contained in:
BIN
src/Comm/._GenericSshClient.cs
Normal file
BIN
src/Comm/._GenericSshClient.cs
Normal file
Binary file not shown.
BIN
src/Comm/._GenericTcpIpClient.cs
Normal file
BIN
src/Comm/._GenericTcpIpClient.cs
Normal file
Binary file not shown.
179
src/Comm/CommunicationGather.cs
Normal file
179
src/Comm/CommunicationGather.cs
Normal file
@@ -0,0 +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
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the string event handler for line events on the gather
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public delegate void LineReceivedHandler(string text);
|
||||
|
||||
/// <summary>
|
||||
/// Attaches to IBasicCommunication as a text gather
|
||||
/// </summary>
|
||||
public class CommunicationGather
|
||||
{
|
||||
/// <summary>
|
||||
/// Event that fires when a line is received from the IBasicCommunication source.
|
||||
/// The event merely contains the text, not an EventArgs type class.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> LineReceived;
|
||||
|
||||
/// <summary>
|
||||
/// The communication port that this gathers on
|
||||
/// </summary>
|
||||
public ICommunicationReceiver Port { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default false. If true, the delimiter will be included in the line output
|
||||
/// events
|
||||
/// </summary>
|
||||
public bool IncludeDelimiter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// For receive buffer
|
||||
/// </summary>
|
||||
StringBuilder ReceiveBuffer = new StringBuilder();
|
||||
|
||||
/// <summary>
|
||||
/// Delimiter, like it says!
|
||||
/// </summary>
|
||||
char Delimiter;
|
||||
|
||||
string[] StringDelimiters;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for using a char delimiter
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="delimiter"></param>
|
||||
public CommunicationGather(ICommunicationReceiver port, char delimiter)
|
||||
{
|
||||
Port = port;
|
||||
Delimiter = delimiter;
|
||||
port.TextReceived += new EventHandler<GenericCommMethodReceiveTextArgs>(Port_TextReceived);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for using a single string delimiter
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="delimiter"></param>
|
||||
public CommunicationGather(ICommunicationReceiver port, string delimiter)
|
||||
:this(port, new string[] { delimiter} )
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for using an array of string delimiters
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="delimiters"></param>
|
||||
public CommunicationGather(ICommunicationReceiver port, string[] delimiters)
|
||||
{
|
||||
Port = port;
|
||||
StringDelimiters = delimiters;
|
||||
port.TextReceived += Port_TextReceivedStringDelimiter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects this gather from the Port's TextReceived event. This will not fire LineReceived
|
||||
/// after the this call.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
Port.TextReceived -= Port_TextReceived;
|
||||
Port.TextReceived -= Port_TextReceivedStringDelimiter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for raw data coming from port
|
||||
/// </summary>
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deconstructor. Disconnects from port TextReceived events.
|
||||
/// </summary>
|
||||
~CommunicationGather()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
177
src/Comm/CommunicationStreamDebugging.cs
Normal file
177
src/Comm/CommunicationStreamDebugging.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using PepperDash.Core;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls the ability to disable/enable debugging of TX/RX data sent to/from a device with a built in timer to disable
|
||||
/// </summary>
|
||||
public class CommunicationStreamDebugging
|
||||
{
|
||||
/// <summary>
|
||||
/// Device Key that this instance configures
|
||||
/// </summary>
|
||||
public string ParentDeviceKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timer to disable automatically if not manually disabled
|
||||
/// </summary>
|
||||
private CTimer DebugExpiryPeriod;
|
||||
|
||||
/// <summary>
|
||||
/// The current debug setting
|
||||
/// </summary>
|
||||
public eStreamDebuggingSetting DebugSetting { get; private set; }
|
||||
|
||||
private uint _DebugTimeoutInMs;
|
||||
private const uint _DefaultDebugTimeoutMin = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout in Minutes
|
||||
/// </summary>
|
||||
public uint DebugTimeoutMinutes
|
||||
{
|
||||
get
|
||||
{
|
||||
return _DebugTimeoutInMs/60000;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that receive stream debugging is enabled
|
||||
/// </summary>
|
||||
public bool RxStreamDebuggingIsEnabled{ get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that transmit stream debugging is enabled
|
||||
/// </summary>
|
||||
public bool TxStreamDebuggingIsEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="parentDeviceKey"></param>
|
||||
public CommunicationStreamDebugging(string parentDeviceKey)
|
||||
{
|
||||
ParentDeviceKey = parentDeviceKey;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the debugging setting and if not setting to off, assumes the default of 30 mintues
|
||||
/// </summary>
|
||||
/// <param name="setting"></param>
|
||||
public void SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting setting)
|
||||
{
|
||||
if (setting == eStreamDebuggingSetting.Off)
|
||||
{
|
||||
DisableDebugging();
|
||||
return;
|
||||
}
|
||||
|
||||
SetDebuggingWithSpecificTimeout(setting, _DefaultDebugTimeoutMin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the debugging setting for the specified number of minutes
|
||||
/// </summary>
|
||||
/// <param name="setting"></param>
|
||||
/// <param name="minutes"></param>
|
||||
public void SetDebuggingWithSpecificTimeout(eStreamDebuggingSetting setting, uint minutes)
|
||||
{
|
||||
if (setting == eStreamDebuggingSetting.Off)
|
||||
{
|
||||
DisableDebugging();
|
||||
return;
|
||||
}
|
||||
|
||||
_DebugTimeoutInMs = minutes * 60000;
|
||||
|
||||
StopDebugTimer();
|
||||
|
||||
DebugExpiryPeriod = new CTimer((o) => DisableDebugging(), _DebugTimeoutInMs);
|
||||
|
||||
if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx)
|
||||
RxStreamDebuggingIsEnabled = true;
|
||||
|
||||
if ((setting & eStreamDebuggingSetting.Tx) == eStreamDebuggingSetting.Tx)
|
||||
TxStreamDebuggingIsEnabled = true;
|
||||
|
||||
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disabled debugging
|
||||
/// </summary>
|
||||
private void DisableDebugging()
|
||||
{
|
||||
StopDebugTimer();
|
||||
|
||||
Debug.SetDeviceDebugSettings(ParentDeviceKey, eStreamDebuggingSetting.Off);
|
||||
}
|
||||
|
||||
private void StopDebugTimer()
|
||||
{
|
||||
RxStreamDebuggingIsEnabled = false;
|
||||
TxStreamDebuggingIsEnabled = false;
|
||||
|
||||
if (DebugExpiryPeriod == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DebugExpiryPeriod.Stop();
|
||||
DebugExpiryPeriod.Dispose();
|
||||
DebugExpiryPeriod = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The available settings for stream debugging
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum eStreamDebuggingSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug off
|
||||
/// </summary>
|
||||
Off = 0,
|
||||
/// <summary>
|
||||
/// Debug received data
|
||||
/// </summary>
|
||||
Rx = 1,
|
||||
/// <summary>
|
||||
/// Debug transmitted data
|
||||
/// </summary>
|
||||
Tx = 2,
|
||||
/// <summary>
|
||||
/// Debug both received and transmitted data
|
||||
/// </summary>
|
||||
Both = Rx | Tx
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The available settings for stream debugging response types
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum eStreamDebuggingDataTypeSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug data in byte format
|
||||
/// </summary>
|
||||
Bytes = 0,
|
||||
/// <summary>
|
||||
/// Debug data in text format
|
||||
/// </summary>
|
||||
Text = 1,
|
||||
/// <summary>
|
||||
/// Debug data in both byte and text formats
|
||||
/// </summary>
|
||||
Both = Bytes | Text,
|
||||
}
|
||||
}
|
||||
86
src/Comm/ControlPropertiesConfig.cs
Normal file
86
src/Comm/ControlPropertiesConfig.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Config properties that indicate how to communicate with a device for control
|
||||
/// </summary>
|
||||
public class ControlPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The method of control
|
||||
/// </summary>
|
||||
public eControlMethod Method { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The key of the device that contains the control port
|
||||
/// </summary>
|
||||
public string ControlPortDevKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of the control port on the device specified by ControlPortDevKey
|
||||
/// </summary>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
||||
public uint ControlPortNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the control port on the device specified by ControlPortDevKey
|
||||
/// </summary>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
|
||||
public string ControlPortName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Properties for ethernet based communications
|
||||
/// </summary>
|
||||
public TcpSshPropertiesConfig TcpSshProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The filename and path for the IR file
|
||||
/// </summary>
|
||||
public string IrFile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The IpId of a Crestron device
|
||||
/// </summary>
|
||||
public string IpId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Readonly uint representation of the IpId
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint IpIdInt { get { return Convert.ToUInt32(IpId, 16); } }
|
||||
|
||||
/// <summary>
|
||||
/// Char indicating end of line
|
||||
/// </summary>
|
||||
public char EndOfLineChar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to Environment.NewLine;
|
||||
/// </summary>
|
||||
public string EndOfLineString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates
|
||||
/// </summary>
|
||||
public string DeviceReadyResponsePattern { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used when communcating to programs running in VC-4
|
||||
/// </summary>
|
||||
public string RoomId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public ControlPropertiesConfig()
|
||||
{
|
||||
EndOfLineString = CrestronEnvironment.NewLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
684
src/Comm/DynamicTCPServer.cs
Normal file
684
src/Comm/DynamicTCPServer.cs
Normal file
@@ -0,0 +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
|
||||
/// <summary>
|
||||
/// Event for Receiving text
|
||||
/// </summary>
|
||||
public event EventHandler<CopyCoreForSimplpGenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event for client connection socket status change
|
||||
/// </summary>
|
||||
public event EventHandler<DynamicTCPSocketStatusChangeEventArgs> ClientConnectionChange;
|
||||
|
||||
/// <summary>
|
||||
/// Event for Server State Change
|
||||
/// </summary>
|
||||
public event EventHandler<DynamicTCPServerStateChangedEventArgs> ServerStateChange;
|
||||
#endregion
|
||||
|
||||
#region Properties/Variables
|
||||
/// <summary>
|
||||
/// Secure or unsecure TCP server. Defaults to Unsecure or standard TCP server without SSL
|
||||
/// </summary>
|
||||
public bool Secure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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+
|
||||
/// </summary>
|
||||
public ushort USecure
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == 1)
|
||||
Secure = true;
|
||||
else if (value == 0)
|
||||
Secure = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Text representation of the Socket Status enum values for the server
|
||||
/// </summary>
|
||||
public string Status
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Secure ? SecureServer != null : UnsecureServer != null)
|
||||
return Secure ? SecureServer.State.ToString() : UnsecureServer.State.ToString();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bool showing if socket is connected
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Secure ? SecureServer != null : UnsecureServer != null) &&
|
||||
(Secure ? SecureServer.State == ServerState.SERVER_CONNECTED : UnsecureServer.State == ServerState.SERVER_CONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for IsConnected
|
||||
/// </summary>
|
||||
public ushort UIsConnected
|
||||
{
|
||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bool showing if socket is connected
|
||||
/// </summary>
|
||||
public bool IsListening
|
||||
{
|
||||
get { return (Secure ? SecureServer != null : UnsecureServer != null) &&
|
||||
(Secure ? SecureServer.State == ServerState.SERVER_LISTENING : UnsecureServer.State == ServerState.SERVER_LISTENING); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for IsConnected
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>
|
||||
/// Number of clients currently connected.
|
||||
/// </summary>
|
||||
public ushort NumberOfClientsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Secure ? SecureServer != null : UnsecureServer != null)
|
||||
return Secure ? (ushort)SecureServer.NumberOfClientsConnected : (ushort)UnsecureServer.NumberOfClientsConnected;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port Server should listen on
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for Port
|
||||
/// </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. Must be set the same in the client, and if true shared keys must be identical on server/client
|
||||
/// </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.
|
||||
/// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called
|
||||
/// </summary>
|
||||
public string SharedKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Heartbeat Required bool sets whether server disconnects client if heartbeat is not received
|
||||
/// </summary>
|
||||
public bool HeartbeatRequired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// S+ Helper for Heartbeat Required
|
||||
/// </summary>
|
||||
public ushort UHeartbeatRequired
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == 1)
|
||||
HeartbeatRequired = true;
|
||||
else
|
||||
HeartbeatRequired = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
|
||||
/// </summary>
|
||||
public int HeartbeatRequiredIntervalMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Simpl+ Heartbeat Analog value in seconds
|
||||
/// </summary>
|
||||
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } }
|
||||
|
||||
/// <summary>
|
||||
/// String to Match for heartbeat. If null or empty any string will reset heartbeat timer
|
||||
/// </summary>
|
||||
public string HeartbeatStringToMatch { get; set; }
|
||||
|
||||
//private timers for Heartbeats per client
|
||||
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
||||
|
||||
//flags to show the secure server is waiting for client at index to send the shared key
|
||||
List<uint> WaitingForSharedKey = new List<uint>();
|
||||
|
||||
//Store the connected client indexes
|
||||
List<uint> ConnectedClientsIndexes = new List<uint>();
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 2000
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Private flag to note that the server has stopped intentionally
|
||||
/// </summary>
|
||||
private bool ServerStopped { get; set; }
|
||||
|
||||
//Servers
|
||||
SecureTCPServer SecureServer;
|
||||
TCPServer UnsecureServer;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// constructor
|
||||
/// </summary>
|
||||
public DynamicTCPServer()
|
||||
: base("Uninitialized Dynamic TCP Server")
|
||||
{
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
BufferSize = 2000;
|
||||
Secure = false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods - Server Actions
|
||||
/// <summary>
|
||||
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
public void Initialize(string key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start listening on the specified port
|
||||
/// </summary>
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop Listeneing
|
||||
/// </summary>
|
||||
public void StopListening()
|
||||
{
|
||||
Debug.Console(2, "Stopping Listener");
|
||||
if (SecureServer != null)
|
||||
SecureServer.Stop();
|
||||
if (UnsecureServer != null)
|
||||
UnsecureServer.Stop();
|
||||
ServerStopped = true;
|
||||
onServerStateChange();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect All Clients
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast text from server to all connected clients
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not sure this is useful in library, maybe Pro??
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
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
|
||||
/// <summary>
|
||||
/// Secure Server Socket Status Changed Callback
|
||||
/// </summary>
|
||||
/// <param name="mySecureTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="serverSocketStatus"></param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TCP Server (Unsecure) Socket Status Change Callback
|
||||
/// </summary>
|
||||
/// <param name="mySecureTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="serverSocketStatus"></param>
|
||||
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
|
||||
/// <summary>
|
||||
/// Secure TCP Client Connected to Secure Server Callback
|
||||
/// </summary>
|
||||
/// <param name="mySecureTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsecure TCP Client Connected to Unsecure Server Callback
|
||||
/// </summary>
|
||||
/// <param name="myTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
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
|
||||
/// <summary>
|
||||
/// Secure Send Data Async Callback
|
||||
/// </summary>
|
||||
/// <param name="mySecureTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="numberOfBytesSent"></param>
|
||||
void SecureSendDataAsyncCallback(SecureTCPServer mySecureTCPServer, uint clientIndex, int numberOfBytesSent)
|
||||
{
|
||||
//Seems there is nothing to do here
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsecure Send Data Asyc Callback
|
||||
/// </summary>
|
||||
/// <param name="myTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="numberOfBytesSent"></param>
|
||||
void UnsecureSendDataAsyncCallback(TCPServer myTCPServer, uint clientIndex, int numberOfBytesSent)
|
||||
{
|
||||
//Seems there is nothing to do here
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Secure Received Data Async Callback
|
||||
/// </summary>
|
||||
/// <param name="mySecureTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="numberOfBytesReceived"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsecure Received Data Async Callback
|
||||
/// </summary>
|
||||
/// <param name="myTCPServer"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="numberOfBytesReceived"></param>
|
||||
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
|
||||
}
|
||||
}
|
||||
251
src/Comm/EventArgs.cs
Normal file
251
src/Comm/EventArgs.cs
Normal file
@@ -0,0 +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
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate for notifying of socket status changes
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client);
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs class for socket status changes
|
||||
/// </summary>
|
||||
public class GenericSocketStatusChageEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ISocketStatus Client { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
public GenericSocketStatusChageEventArgs(ISocketStatus client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericSocketStatusChageEventArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for notifying of TCP Server state changes
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state);
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs class for TCP Server state changes
|
||||
/// </summary>
|
||||
public class GenericTcpServerStateChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ServerState State { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
public GenericTcpServerStateChangedEventArgs(ServerState state)
|
||||
{
|
||||
State = state;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerStateChangedEventArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for TCP Server socket status changes
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="clientStatus"></param>
|
||||
public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus);
|
||||
/// <summary>
|
||||
/// EventArgs for TCP server socket status changes
|
||||
/// </summary>
|
||||
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public object Socket { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public uint ReceivedFromClientIndex { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
/// <param name="clientStatus"></param>
|
||||
public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
|
||||
{
|
||||
Socket = socket;
|
||||
ClientStatus = clientStatus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
/// <param name="clientStatus"></param>
|
||||
public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus)
|
||||
{
|
||||
Socket = socket;
|
||||
ReceivedFromClientIndex = clientIndex;
|
||||
ClientStatus = clientStatus;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerSocketStatusChangeEventArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs for TCP server com method receive text
|
||||
/// </summary>
|
||||
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public uint ReceivedFromClientIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ushort ReceivedFromClientIndexShort
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ushort)ReceivedFromClientIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Text { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public GenericTcpServerCommMethodReceiveTextArgs(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex)
|
||||
{
|
||||
Text = text;
|
||||
ReceivedFromClientIndex = clientIndex;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerCommMethodReceiveTextArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs for TCP server client ready for communication
|
||||
/// </summary>
|
||||
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool IsReady;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="isReady"></param>
|
||||
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
|
||||
{
|
||||
IsReady = isReady;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerClientReadyForcommunicationsEventArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs for UDP connected
|
||||
/// </summary>
|
||||
public class GenericUdpConnectedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ushort UConnected;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Connected;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public GenericUdpConnectedEventArgs() { }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="uconnected"></param>
|
||||
public GenericUdpConnectedEventArgs(ushort uconnected)
|
||||
{
|
||||
UConnected = uconnected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="connected"></param>
|
||||
public GenericUdpConnectedEventArgs(bool connected)
|
||||
{
|
||||
Connected = connected;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
104
src/Comm/FINISH CommStatic.cs
Normal file
104
src/Comm/FINISH CommStatic.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Background class that manages debug features for sockets
|
||||
/// </summary>
|
||||
public static class CommStatic
|
||||
{
|
||||
static List<ISocketStatus> Sockets = new List<ISocketStatus>();
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the backing class. Adds console commands for S#Pro programs
|
||||
/// </summary>
|
||||
static CommStatic()
|
||||
{
|
||||
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
|
||||
{
|
||||
CrestronConsole.AddNewConsoleCommand(SocketCommand, "socket", "socket commands: list, send, connect, disco",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
}
|
||||
}
|
||||
|
||||
static void SocketCommand(string s)
|
||||
{
|
||||
// 0 1 2
|
||||
//socket command number/key/all param
|
||||
//socket list
|
||||
//socket send 4 ver -v\n
|
||||
|
||||
if (string.IsNullOrEmpty(s))
|
||||
return;
|
||||
var tokens = s.Split(' ');
|
||||
if (tokens.Length == 0)
|
||||
return;
|
||||
var command = tokens[0].ToLower();
|
||||
if(command == "connect")
|
||||
{
|
||||
|
||||
}
|
||||
else if(command == "disco")
|
||||
{
|
||||
|
||||
}
|
||||
else if(command =="list")
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("{0} sockets", Sockets.Count);
|
||||
if(Sockets.Count == 0)
|
||||
return;
|
||||
// get the longest key name, for formatting
|
||||
var longestLength = Sockets.Aggregate("",
|
||||
(max, cur) => max.Length > cur.Key.Length ? max : cur.Key).Length;
|
||||
for(int i = 0; i < Sockets.Count; i++)
|
||||
{
|
||||
var sock = Sockets[i];
|
||||
CrestronConsole.ConsoleCommandResponse("{0} {1} {2} {3}",
|
||||
i, sock.Key, GetSocketType(sock), sock.ClientStatus);
|
||||
}
|
||||
}
|
||||
else if(command == "send")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper for socket list, to show types
|
||||
/// </summary>
|
||||
static string GetSocketType(ISocketStatus sock)
|
||||
{
|
||||
if (sock is GenericSshClient)
|
||||
return "SSH";
|
||||
else if (sock is GenericTcpIpClient)
|
||||
return "TCP-IP";
|
||||
else
|
||||
return "?";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
public static void AddSocket(ISocketStatus socket)
|
||||
{
|
||||
if(!Sockets.Contains(socket))
|
||||
Sockets.Add(socket);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
public static void RemoveSocket(ISocketStatus socket)
|
||||
{
|
||||
if (Sockets.Contains(socket))
|
||||
Sockets.Remove(socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
314
src/Comm/GenericHttpSseClient.cs
Normal file
314
src/Comm/GenericHttpSseClient.cs
Normal file
@@ -0,0 +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
|
||||
{
|
||||
/// <summary>
|
||||
/// Client for communicating with an HTTP Server Side Event pattern
|
||||
/// </summary>
|
||||
public class GenericHttpSseClient : ICommunicationReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// Notifies when bytes have been received
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
/// <summary>
|
||||
/// Notifies when text has been received
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates connection status
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for the instance
|
||||
/// </summary>
|
||||
public string Key
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name for the instance
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL of the server
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
|
||||
HttpClient Client;
|
||||
HttpClientRequest Request;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="name"></param>
|
||||
public GenericHttpSseClient(string key, string name)
|
||||
{
|
||||
Key = key;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the server. Requires Url to be set first.
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
InitiateConnection(Url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects from the server
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
CloseConnection(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiates connection to the server
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection to the server
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="error"></param>
|
||||
/// <param name="status"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the state of the request
|
||||
/// </summary>
|
||||
public class RequestState
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const int BUFFER_SIZE = 10000;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public byte[] BufferRead;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public HttpClient HttpClient;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public HttpClientRequest Request;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public HttpClientResponse Response;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Stream StreamResponse;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Done;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public RequestState()
|
||||
{
|
||||
BufferRead = new byte[BUFFER_SIZE];
|
||||
HttpClient = null;
|
||||
Request = null;
|
||||
Response = null;
|
||||
StreamResponse = null;
|
||||
Done = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waithandle for main thread.
|
||||
/// </summary>
|
||||
public class StreamAsyncTest
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public CEvent wait_for_response = new CEvent(true, false);
|
||||
}
|
||||
}
|
||||
954
src/Comm/GenericSecureTcpIpClient.cs
Normal file
954
src/Comm/GenericSecureTcpIpClient.cs
Normal file
@@ -0,0 +1,954 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to handle secure TCP/IP communications with a server
|
||||
/// </summary>
|
||||
public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
||||
{
|
||||
private const string SplusKey = "Uninitialized Secure Tcp _client";
|
||||
/// <summary>
|
||||
/// Stream debugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as a Byte array
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as text
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
#region GenericSecureTcpIpClient Events & Delegates
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
|
||||
/// <summary>
|
||||
/// Auto reconnect evant handler
|
||||
/// </summary>
|
||||
public event EventHandler AutoReconnectTriggered;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceivedQueueInvoke;
|
||||
|
||||
/// <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 GenricTcpIpClient properties
|
||||
|
||||
private string _hostname;
|
||||
|
||||
/// <summary>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname
|
||||
{
|
||||
get { return _hostname; }
|
||||
set
|
||||
{
|
||||
_hostname = value;
|
||||
if (_client != null)
|
||||
{
|
||||
_client.AddressClientConnectedTo = _hostname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Defaults to 2000
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal secure client
|
||||
/// </summary>
|
||||
private SecureTCPClient _client;
|
||||
|
||||
/// <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
|
||||
{
|
||||
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _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>
|
||||
/// Connection failure reason
|
||||
/// </summary>
|
||||
public string ConnectionFailure { 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>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Connected
|
||||
{
|
||||
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
|
||||
// private Timer for auto reconnect
|
||||
private CTimer RetryTimer;
|
||||
|
||||
#endregion
|
||||
|
||||
#region GenericSecureTcpIpClient properties
|
||||
|
||||
/// <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>
|
||||
/// Semaphore on connect method
|
||||
/// </summary>
|
||||
bool IsTryingToConnect;
|
||||
|
||||
/// <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>
|
||||
/// Bool Heartbeat Enabled flag
|
||||
/// </summary>
|
||||
public bool HeartbeatEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for Heartbeat Enabled
|
||||
/// </summary>
|
||||
public ushort UHeartbeatEnabled
|
||||
{
|
||||
get { return (ushort)(HeartbeatEnabled ? 1 : 0); }
|
||||
set { HeartbeatEnabled = value == 1; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Heartbeat String
|
||||
/// </summary>
|
||||
public string HeartbeatString { get; set; }
|
||||
//public int HeartbeatInterval = 50000;
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
|
||||
/// </summary>
|
||||
public int HeartbeatInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Simpl+ Heartbeat Analog value in seconds
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
bool ProgramIsStopping;
|
||||
|
||||
/// <summary>
|
||||
/// Queue lock
|
||||
/// </summary>
|
||||
CCriticalSection DequeueLock = new CCriticalSection();
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public int ReceiveQueueSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private CrestronQueue<GenericTcpServerCommMethodReceiveTextArgs> MessageQueue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="bufferSize"></param>
|
||||
public GenericSecureTcpIpClient(string key, string address, int port, int bufferSize)
|
||||
: base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
Hostname = address;
|
||||
Port = port;
|
||||
BufferSize = bufferSize;
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contstructor that sets all properties by calling the initialize method with a config object.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="clientConfigObject"></param>
|
||||
public GenericSecureTcpIpClient(string key, TcpClientConfigObject clientConfigObject)
|
||||
: base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
BufferSize = 2000;
|
||||
|
||||
Initialize(clientConfigObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor for S+
|
||||
/// </summary>
|
||||
public GenericSecureTcpIpClient()
|
||||
: base(SplusKey)
|
||||
{
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
BufferSize = 2000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Just to help S+ set the key
|
||||
/// </summary>
|
||||
public void Initialize(string key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client.
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
public void Initialize(TcpClientConfigObject config)
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
Debug.Console(0, this, "Could not initialize client with key: {0}", Key);
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
Hostname = config.Control.TcpSshProperties.Address;
|
||||
Port = config.Control.TcpSshProperties.Port > 0 && config.Control.TcpSshProperties.Port <= 65535
|
||||
? config.Control.TcpSshProperties.Port
|
||||
: 80;
|
||||
|
||||
AutoReconnect = config.Control.TcpSshProperties.AutoReconnect;
|
||||
AutoReconnectIntervalMs = config.Control.TcpSshProperties.AutoReconnectIntervalMs > 1000
|
||||
? config.Control.TcpSshProperties.AutoReconnectIntervalMs
|
||||
: 5000;
|
||||
|
||||
SharedKey = config.SharedKey;
|
||||
SharedKeyRequired = config.SharedKeyRequired;
|
||||
|
||||
HeartbeatEnabled = config.HeartbeatRequired;
|
||||
HeartbeatRequiredIntervalInSeconds = config.HeartbeatRequiredIntervalInSeconds > 0
|
||||
? config.HeartbeatRequiredIntervalInSeconds
|
||||
: (ushort)15;
|
||||
|
||||
|
||||
HeartbeatString = string.IsNullOrEmpty(config.HeartbeatStringToMatch)
|
||||
? "heartbeat"
|
||||
: config.HeartbeatStringToMatch;
|
||||
|
||||
BufferSize = config.Control.TcpSshProperties.BufferSize > 2000
|
||||
? config.Control.TcpSshProperties.BufferSize
|
||||
: 2000;
|
||||
|
||||
ReceiveQueueSize = config.ReceiveQueueSize > 20
|
||||
? config.ReceiveQueueSize
|
||||
: 20;
|
||||
|
||||
MessageQueue = new CrestronQueue<GenericTcpServerCommMethodReceiveTextArgs>(ReceiveQueueSize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Console(0, this, "Exception initializing client with key: {0}\rException: {1}", Key, ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <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>
|
||||
/// Deactivate the client
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool Deactivate()
|
||||
{
|
||||
if (_client != null)
|
||||
{
|
||||
_client.SocketStatusChange -= this.Client_SocketStatusChange;
|
||||
DisconnectClient();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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(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)
|
||||
{
|
||||
Disconnect();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
Debug.Console(2, "Disconnect Called");
|
||||
|
||||
DisconnectCalledByUser = true;
|
||||
|
||||
// stop trying reconnects, if we are
|
||||
if (RetryTimer != null)
|
||||
{
|
||||
RetryTimer.Stop();
|
||||
RetryTimer = null;
|
||||
}
|
||||
|
||||
if (_client != null)
|
||||
{
|
||||
DisconnectClient();
|
||||
Debug.Console(1, this, "Disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the actual disconnect business
|
||||
/// </summary>
|
||||
public void DisconnectClient()
|
||||
{
|
||||
if (_client == null) return;
|
||||
|
||||
Debug.Console(1, this, "Disconnecting client");
|
||||
if (IsConnected)
|
||||
_client.DisconnectFromServer();
|
||||
|
||||
// close up client. ALWAYS use this when disconnecting.
|
||||
IsTryingToConnect = false;
|
||||
|
||||
Debug.Console(2, this, "Disconnecting _client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
||||
_client.SocketStatusChange -= Client_SocketStatusChange;
|
||||
_client.Dispose();
|
||||
_client = null;
|
||||
|
||||
if (ConnectFailTimer == null) return;
|
||||
ConnectFailTimer.Stop();
|
||||
ConnectFailTimer.Dispose();
|
||||
ConnectFailTimer = null;
|
||||
}
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// 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(2, this, "Cleaning up remotely closed/failed connection.");
|
||||
Disconnect();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
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 GenericCommMethodReceiveTextArgs(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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper for ConnectionChange event
|
||||
/// </summary>
|
||||
void OnConnectionChange()
|
||||
{
|
||||
var handler = ConnectionChange;
|
||||
if (handler == null) return;
|
||||
|
||||
handler(this, new GenericSocketStatusChageEventArgs(this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to fire ClientReadyForCommunications event
|
||||
/// </summary>
|
||||
void OnClientReadyForcommunications(bool isReady)
|
||||
{
|
||||
IsReadyForCommunication = isReady;
|
||||
if (IsReadyForCommunication)
|
||||
HeartbeatStart();
|
||||
|
||||
var handler = ClientReadyForCommunications;
|
||||
if (handler == null) return;
|
||||
|
||||
handler(this, new GenericTcpServerClientReadyForcommunicationsEventArgs(IsReadyForCommunication));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
908
src/Comm/GenericSecureTcpIpClient_ForServer.cs
Normal file
908
src/Comm/GenericSecureTcpIpClient_ForServer.cs
Normal file
@@ -0,0 +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
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic secure TCP/IP client for server
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Notifies of text received
|
||||
/// </summary>
|
||||
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Notifies of auto reconnect sequence triggered
|
||||
/// </summary>
|
||||
public event EventHandler AutoReconnectTriggered;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceivedQueueInvoke;
|
||||
|
||||
/// <summary>
|
||||
/// Notifies of socket status change
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool HeartbeatEnabled { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ushort UHeartbeatEnabled
|
||||
{
|
||||
get { return (ushort)(HeartbeatEnabled ? 1 : 0); }
|
||||
set { HeartbeatEnabled = value == 1; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string HeartbeatString { get; set; }
|
||||
//public int HeartbeatInterval = 50000;
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
|
||||
/// </summary>
|
||||
public int HeartbeatInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Simpl+ Heartbeat Analog value in seconds
|
||||
/// </summary>
|
||||
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
|
||||
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Queue lock
|
||||
/// </summary>
|
||||
CCriticalSection DequeueLock = new CCriticalSection();
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public int ReceiveQueueSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private CrestronQueue<GenericTcpServerCommMethodReceiveTextArgs> MessageQueue;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="bufferSize"></param>
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for S+
|
||||
/// </summary>
|
||||
public GenericSecureTcpIpClient_ForServer()
|
||||
: base("Uninitialized Secure Tcp Client For Server")
|
||||
{
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
BufferSize = 2000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contstructor that sets all properties by calling the initialize method with a config object.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="clientConfigObject"></param>
|
||||
public GenericSecureTcpIpClient_ForServer(string key, TcpClientConfigObject clientConfigObject)
|
||||
: base(key)
|
||||
{
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
Initialize(clientConfigObject);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Just to help S+ set the key
|
||||
/// </summary>
|
||||
public void Initialize(string key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client.
|
||||
/// </summary>
|
||||
/// <param name="clientConfigObject"></param>
|
||||
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<GenericTcpServerCommMethodReceiveTextArgs>(ReceiveQueueSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorLog.Error("Could not initialize client with key: {0}", Key);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ErrorLog.Error("Could not initialize client with key: {0}", 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(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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <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(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
|
||||
}
|
||||
1085
src/Comm/GenericSecureTcpIpServer.cs
Normal file
1085
src/Comm/GenericSecureTcpIpServer.cs
Normal file
File diff suppressed because it is too large
Load Diff
610
src/Comm/GenericSshClient.cs
Normal file
610
src/Comm/GenericSshClient.cs
Normal file
@@ -0,0 +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
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
||||
{
|
||||
private const string SPlusKey = "Uninitialized SshClient";
|
||||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when data is received. Delivers args with byte array
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when data is received. Delivered as text.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event when the connection status changes.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
|
||||
///// <summary>
|
||||
/////
|
||||
///// </summary>
|
||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
||||
|
||||
/// <summary>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Port on server
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Username for server
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// And... Password for server. That was worth documenting!
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True when the server is connected - when status == 2.
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
// returns false if no client or not connected
|
||||
get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for IsConnected
|
||||
/// </summary>
|
||||
public ushort UIsConnected
|
||||
{
|
||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus
|
||||
{
|
||||
get { return _ClientStatus; }
|
||||
private set
|
||||
{
|
||||
if (_ClientStatus == value)
|
||||
return;
|
||||
_ClientStatus = value;
|
||||
OnConnectionChange();
|
||||
}
|
||||
}
|
||||
SocketStatus _ClientStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
||||
/// and IsConnected with be true when this == 2.
|
||||
/// </summary>
|
||||
public ushort UStatus
|
||||
{
|
||||
get { return (ushort)_ClientStatus; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether client will attempt reconnection on failure. Default is true
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Will be set and unset by connect and disconnect only
|
||||
/// </summary>
|
||||
public bool ConnectEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for AutoReconnect
|
||||
/// </summary>
|
||||
public ushort UAutoReconnect
|
||||
{
|
||||
get { return (ushort)(AutoReconnect ? 1 : 0); }
|
||||
set { AutoReconnect = value == 1; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Millisecond value, determines the timeout period in between reconnect attempts.
|
||||
/// Set to 5000 by default
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Typical constructor.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ Constructor - Must set all properties before calling Connect
|
||||
/// </summary>
|
||||
public GenericSshClient()
|
||||
: base(SPlusKey)
|
||||
{
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
|
||||
ReconnectTimer = new CTimer(o =>
|
||||
{
|
||||
if (ConnectEnabled)
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
}, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <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");
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect to the server, using the provided properties.
|
||||
/// </summary>
|
||||
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<AuthenticationPromptEventArgs>(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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect the clients and put away it's resources.
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
ConnectEnabled = false;
|
||||
// Stop trying reconnects, if we are
|
||||
if (ReconnectTimer != null)
|
||||
{
|
||||
ReconnectTimer.Stop();
|
||||
// ReconnectTimer = null;
|
||||
}
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills the stream, cleans up the client and sets it to null
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Anything to do with reestablishing connection on failures
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills the stream
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the keyboard interactive authentication, should it be required.
|
||||
/// </summary>
|
||||
void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
|
||||
{
|
||||
foreach (AuthenticationPrompt prompt in e.Prompts)
|
||||
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
|
||||
prompt.Response = Password;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
|
||||
/// event
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper for ConnectionChange event
|
||||
/// </summary>
|
||||
void OnConnectionChange()
|
||||
{
|
||||
if (ConnectionChange != null)
|
||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
||||
}
|
||||
|
||||
#region IBasicCommunication Members
|
||||
|
||||
/// <summary>
|
||||
/// Sends text to the server
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends Bytes to the server
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
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
|
||||
}
|
||||
|
||||
//*****************************************************************************************************
|
||||
//*****************************************************************************************************
|
||||
/// <summary>
|
||||
/// Fired when connection changes
|
||||
/// </summary>
|
||||
public class SshConnectionChangeEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Connection State
|
||||
/// </summary>
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Connection Status represented as a ushort
|
||||
/// </summary>
|
||||
public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } }
|
||||
|
||||
/// <summary>
|
||||
/// The client
|
||||
/// </summary>
|
||||
public GenericSshClient Client { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Socket Status as represented by
|
||||
/// </summary>
|
||||
public ushort Status { get { return Client.UStatus; } }
|
||||
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public SshConnectionChangeEventArgs() { }
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs class
|
||||
/// </summary>
|
||||
/// <param name="isConnected">Connection State</param>
|
||||
/// <param name="client">The Client</param>
|
||||
public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client)
|
||||
{
|
||||
IsConnected = isConnected;
|
||||
Client = client;
|
||||
}
|
||||
}
|
||||
}
|
||||
567
src/Comm/GenericTcpIpClient.cs
Normal file
567
src/Comm/GenericTcpIpClient.cs
Normal file
@@ -0,0 +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
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to handle basic TCP/IP communications with a server
|
||||
/// </summary>
|
||||
public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
||||
{
|
||||
private const string SplusKey = "Uninitialized TcpIpClient";
|
||||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as a Byte array
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as text
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
|
||||
|
||||
private string _hostname;
|
||||
|
||||
/// <summary>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hostname;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_hostname = value;
|
||||
if (_client != null)
|
||||
{
|
||||
_client.AddressClientConnectedTo = _hostname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port on server
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Another damn S+ helper because S+ seems to treat large port nums as signed ints
|
||||
/// which screws up things
|
||||
/// </summary>
|
||||
public ushort UPort
|
||||
{
|
||||
get { return Convert.ToUInt16(Port); }
|
||||
set { Port = Convert.ToInt32(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 2000
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The actual client class
|
||||
/// </summary>
|
||||
private TCPClient _client;
|
||||
|
||||
/// <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
|
||||
{
|
||||
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _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>
|
||||
/// Ushort representation of client status
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public ushort UClientStatus { get { return (ushort)ClientStatus; } }
|
||||
|
||||
/// <summary>
|
||||
/// Connection failure reason
|
||||
/// </summary>
|
||||
public string ConnectionFailure { 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>
|
||||
/// Set only when the disconnect method is called
|
||||
/// </summary>
|
||||
bool DisconnectCalledByUser;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key">unique string to differentiate between instances</param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="bufferSize"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor for S+
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
Debug.Console(1, this, "Program stopping. Closing connection");
|
||||
Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool Deactivate()
|
||||
{
|
||||
RetryTimer.Stop();
|
||||
RetryTimer.Dispose();
|
||||
if (_client != null)
|
||||
{
|
||||
_client.SocketStatusChange -= this.Client_SocketStatusChange;
|
||||
DisconnectClient();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to connect to the server
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to disconnect the client
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
DisconnectCalledByUser = true;
|
||||
|
||||
// Stop trying reconnects, if we are
|
||||
RetryTimer.Stop();
|
||||
DisconnectClient();
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the actual disconnect business
|
||||
/// </summary>
|
||||
public void DisconnectClient()
|
||||
{
|
||||
if (_client != null)
|
||||
{
|
||||
Debug.Console(1, this, "Disconnecting client");
|
||||
if (IsConnected)
|
||||
_client.DisconnectFromServer();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback method for connection attempt
|
||||
/// </summary>
|
||||
/// <param name="c"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects, waits and attemtps to connect again
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recieves incoming data
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="numBytes"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// General send method
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is useful from console and...?
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends Bytes to the server
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Socket Status Change Handler
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="clientSocketStatus"></param>
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration properties for TCP/SSH Connections
|
||||
/// </summary>
|
||||
public class TcpSshPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Address to connect to
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Port to connect to
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Username credential
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
/// <summary>
|
||||
/// Passord credential
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 32768
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to true
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 5000ms
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public TcpSshPropertiesConfig()
|
||||
{
|
||||
BufferSize = 32768;
|
||||
AutoReconnect = true;
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
Username = "";
|
||||
Password = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
774
src/Comm/GenericTcpIpClient_ForServer.cs
Normal file
774
src/Comm/GenericTcpIpClient_ForServer.cs
Normal file
@@ -0,0 +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
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic TCP/IP client for server
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Notifies of text received
|
||||
/// </summary>
|
||||
public event EventHandler<GenericTcpServerCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Notifies of socket status change
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool HeartbeatEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ushort UHeartbeatEnabled
|
||||
{
|
||||
get { return (ushort)(HeartbeatEnabled ? 1 : 0); }
|
||||
set { HeartbeatEnabled = value == 1; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string HeartbeatString = "heartbeat";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="bufferSize"></param>
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for S+
|
||||
/// </summary>
|
||||
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(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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <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(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
|
||||
}
|
||||
1012
src/Comm/GenericTcpIpServer.cs
Normal file
1012
src/Comm/GenericTcpIpServer.cs
Normal file
File diff suppressed because it is too large
Load Diff
401
src/Comm/GenericUdpServer.cs
Normal file
401
src/Comm/GenericUdpServer.cs
Normal file
@@ -0,0 +1,401 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic UDP Server device
|
||||
/// </summary>
|
||||
public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||
{
|
||||
private const string SplusKey = "Uninitialized Udp Server";
|
||||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericUdpReceiveTextExtraArgs> DataRecievedExtra;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public event EventHandler<GenericUdpConnectedEventArgs> UpdateConnectionStatus;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
return Server.ServerStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public ushort UStatus
|
||||
{
|
||||
get { return (ushort)Server.ServerStatus; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Port on server
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Another damn S+ helper because S+ seems to treat large port nums as signed ints
|
||||
/// which screws up things
|
||||
/// </summary>
|
||||
public ushort UPort
|
||||
{
|
||||
get { return Convert.ToUInt16(Port); }
|
||||
set { Port = Convert.ToInt32(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the UDP Server is enabled
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Numeric value indicating
|
||||
/// </summary>
|
||||
public ushort UIsConnected
|
||||
{
|
||||
get { return IsConnected ? (ushort)1 : (ushort)0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 2000
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The server
|
||||
/// </summary>
|
||||
public UDPServer Server { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for S+. Make sure to set key, address, port, and buffersize using init method
|
||||
/// </summary>
|
||||
public GenericUdpServer()
|
||||
: base(SplusKey)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
|
||||
BufferSize = 5000;
|
||||
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="buffefSize"></param>
|
||||
public GenericUdpServer(string key, string address, int port, int buffefSize)
|
||||
: base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
Hostname = address;
|
||||
Port = port;
|
||||
BufferSize = buffefSize;
|
||||
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call from S+ to initialize values
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
public void Initialize(string key, string address, ushort port)
|
||||
{
|
||||
Key = key;
|
||||
Hostname = address;
|
||||
UPort = port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ethernetEventArgs"></param>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="programEventType"></param>
|
||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
{
|
||||
if (programEventType != eProgramStatusEventType.Stopping)
|
||||
return;
|
||||
|
||||
Debug.Console(1, this, "Program stopping. Disabling Server");
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the UDP Server
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disabled the UDP Server
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
if(Server != null)
|
||||
Server.DisableUDPServer();
|
||||
|
||||
IsConnected = false;
|
||||
|
||||
var handler = UpdateConnectionStatus;
|
||||
if (handler != null)
|
||||
handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Recursive method to receive data
|
||||
/// </summary>
|
||||
/// <param name="server"></param>
|
||||
/// <param name="numBytes"></param>
|
||||
void Receive(UDPServer server, int numBytes)
|
||||
{
|
||||
Debug.Console(2, this, "Received {0} bytes", numBytes);
|
||||
|
||||
try
|
||||
{
|
||||
if (numBytes <= 0)
|
||||
return;
|
||||
|
||||
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);
|
||||
|
||||
var dataRecivedExtra = DataRecievedExtra;
|
||||
if (dataRecivedExtra != null)
|
||||
dataRecivedExtra(this, 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));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Console(0, "GenericUdpServer Receive error: {0}{1}", ex.Message, ex.StackTrace);
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.ReceiveDataAsync(Receive);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// General send method
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericUdpReceiveTextExtraArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Text { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string IpAddress { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int Port { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public byte[] Bytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="ipAddress"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="bytes"></param>
|
||||
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
|
||||
{
|
||||
Text = text;
|
||||
IpAddress = ipAddress;
|
||||
Port = port;
|
||||
Bytes = bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stupid S+ Constructor
|
||||
/// </summary>
|
||||
public GenericUdpReceiveTextExtraArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UdpServerPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 32768
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public UdpServerPropertiesConfig()
|
||||
{
|
||||
BufferSize = 32768;
|
||||
}
|
||||
}
|
||||
}
|
||||
351
src/Comm/QscCoreDoubleTcpIpClient.cs
Normal file
351
src/Comm/QscCoreDoubleTcpIpClient.cs
Normal file
@@ -0,0 +1,351 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows for two simultaneous TCP clients to connect to a redundant pair of QSC Core DSPs and manages
|
||||
/// </summary>
|
||||
public class QscCoreDoubleTcpIpClient : IKeyed
|
||||
{
|
||||
/// <summary>
|
||||
/// Key to uniquely identify the instance of the class
|
||||
/// </summary>
|
||||
public string Key { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fires when a bool value changes to notify the S+ module
|
||||
/// </summary>
|
||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
||||
/// <summary>
|
||||
/// Fires when a ushort value changes to notify the S+ module
|
||||
/// </summary>
|
||||
public event EventHandler<UshrtChangeEventArgs> UshortChange;
|
||||
/// <summary>
|
||||
/// Fires when a string value changes to notify the S+ module
|
||||
/// </summary>
|
||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
||||
|
||||
/// <summary>
|
||||
/// The client for the master DSP unit
|
||||
/// </summary>
|
||||
public GenericTcpIpClient MasterClient { get; private set; }
|
||||
/// <summary>
|
||||
/// The client for the slave DSP unit
|
||||
/// </summary>
|
||||
public GenericTcpIpClient SlaveClient { get; private set; }
|
||||
|
||||
string Username;
|
||||
string Password;
|
||||
string LineEnding;
|
||||
|
||||
CommunicationGather MasterGather;
|
||||
CommunicationGather SlaveGather;
|
||||
|
||||
bool IsPolling;
|
||||
int PollingIntervalSeconds;
|
||||
CTimer PollTimer;
|
||||
|
||||
bool SlaveIsActive;
|
||||
|
||||
/// <summary>
|
||||
/// Default constuctor for S+
|
||||
/// </summary>
|
||||
public QscCoreDoubleTcpIpClient()
|
||||
{
|
||||
MasterClient = new GenericTcpIpClient("temp-master");
|
||||
MasterClient.AutoReconnect = true;
|
||||
MasterClient.AutoReconnectIntervalMs = 2000;
|
||||
SlaveClient = new GenericTcpIpClient("temp-slave");
|
||||
SlaveClient.AutoReconnect = true;
|
||||
SlaveClient.AutoReconnectIntervalMs = 2000;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to both DSP units
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="masterAddress"></param>
|
||||
/// <param name="masterPort"></param>
|
||||
/// <param name="slaveAddress"></param>
|
||||
/// <param name="slavePort"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <param name="pollingIntervalSeconds"></param>
|
||||
/// <param name="lineEnding"></param>
|
||||
public void Connect(string key, string masterAddress, int masterPort,
|
||||
string slaveAddress, int slavePort, string username, string password,
|
||||
int pollingIntervalSeconds, string lineEnding)
|
||||
{
|
||||
Key = key;
|
||||
|
||||
PollingIntervalSeconds = pollingIntervalSeconds;
|
||||
Username = username;
|
||||
Password = password;
|
||||
LineEnding = lineEnding;
|
||||
|
||||
MasterClient.Initialize(key + "-master");
|
||||
SlaveClient.Initialize(key + "-slave");
|
||||
|
||||
MasterClient.Hostname = masterAddress;
|
||||
MasterClient.Port = masterPort;
|
||||
|
||||
if (MasterClient != null)
|
||||
{
|
||||
MasterClient.Disconnect();
|
||||
}
|
||||
|
||||
if (SlaveClient != null)
|
||||
{
|
||||
SlaveClient.Disconnect();
|
||||
}
|
||||
|
||||
if (MasterGather == null)
|
||||
{
|
||||
MasterGather = new CommunicationGather(MasterClient, lineEnding);
|
||||
MasterGather.IncludeDelimiter = true;
|
||||
}
|
||||
|
||||
MasterGather.LineReceived -= MasterGather_LineReceived;
|
||||
MasterGather.LineReceived += new EventHandler<GenericCommMethodReceiveTextArgs>(MasterGather_LineReceived);
|
||||
|
||||
MasterClient.ConnectionChange -= MasterClient_SocketStatusChange;
|
||||
MasterClient.ConnectionChange += MasterClient_SocketStatusChange;
|
||||
|
||||
SlaveClient.Hostname = slaveAddress;
|
||||
SlaveClient.Port = slavePort;
|
||||
|
||||
if (SlaveGather == null)
|
||||
{
|
||||
SlaveGather = new CommunicationGather(SlaveClient, lineEnding);
|
||||
SlaveGather.IncludeDelimiter = true;
|
||||
}
|
||||
|
||||
SlaveGather.LineReceived -= MasterGather_LineReceived;
|
||||
SlaveGather.LineReceived += new EventHandler<GenericCommMethodReceiveTextArgs>(SlaveGather_LineReceived);
|
||||
|
||||
SlaveClient.ConnectionChange -= SlaveClient_SocketStatusChange;
|
||||
SlaveClient.ConnectionChange += SlaveClient_SocketStatusChange;
|
||||
|
||||
MasterClient.Connect();
|
||||
SlaveClient.Connect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
if (MasterClient != null)
|
||||
{
|
||||
MasterGather.LineReceived -= MasterGather_LineReceived;
|
||||
MasterClient.Disconnect();
|
||||
}
|
||||
if (SlaveClient != null)
|
||||
{
|
||||
SlaveGather.LineReceived -= SlaveGather_LineReceived;
|
||||
SlaveClient.Disconnect();
|
||||
}
|
||||
if (PollTimer != null)
|
||||
{
|
||||
IsPolling = false;
|
||||
|
||||
PollTimer.Stop();
|
||||
PollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does not include line feed
|
||||
/// </summary>
|
||||
public void SendText(string s)
|
||||
{
|
||||
if (SlaveIsActive)
|
||||
{
|
||||
if (SlaveClient != null)
|
||||
{
|
||||
Debug.Console(2, this, "Sending to Slave: {0}", s);
|
||||
SlaveClient.SendText(s);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MasterClient != null)
|
||||
{
|
||||
Debug.Console(2, this, "Sending to Master: {0}", s);
|
||||
MasterClient.SendText(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MasterClient_SocketStatusChange(object sender, GenericSocketStatusChageEventArgs args)
|
||||
{
|
||||
OnUshortChange((ushort)args.Client.ClientStatus, MasterClientStatusId);
|
||||
|
||||
if (args.Client.IsConnected)
|
||||
{
|
||||
MasterGather.LineReceived += MasterGather_LineReceived;
|
||||
|
||||
StartPolling();
|
||||
}
|
||||
else
|
||||
MasterGather.LineReceived -= MasterGather_LineReceived;
|
||||
}
|
||||
|
||||
void SlaveClient_SocketStatusChange(object sender, GenericSocketStatusChageEventArgs args)
|
||||
{
|
||||
OnUshortChange((ushort)args.Client.ClientStatus, SlaveClientStatusId);
|
||||
|
||||
if (args.Client.IsConnected)
|
||||
{
|
||||
SlaveGather.LineReceived += SlaveGather_LineReceived;
|
||||
|
||||
StartPolling();
|
||||
}
|
||||
else
|
||||
SlaveGather.LineReceived -= SlaveGather_LineReceived;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void MasterGather_LineReceived(object sender, GenericCommMethodReceiveTextArgs e)
|
||||
{
|
||||
if (e.Text.Contains("login_required"))
|
||||
{
|
||||
MasterClient.SendText(string.Format("login {0} {1} \x0d\x0a", Username, Password));
|
||||
}
|
||||
else if (e.Text.Contains("login_success"))
|
||||
{
|
||||
// START THE POLLING, YO!
|
||||
}
|
||||
else if (e.Text.StartsWith("sr"))
|
||||
{
|
||||
// example response "sr "MyDesign" "NIEC2bxnVZ6a" 1 1"
|
||||
|
||||
var split = e.Text.Trim().Split(' ');
|
||||
if (split[split.Length - 1] == "1")
|
||||
{
|
||||
SlaveIsActive = false;
|
||||
OnBoolChange(false, SlaveIsActiveId);
|
||||
OnBoolChange(true, MasterIsActiveId);
|
||||
}
|
||||
}
|
||||
if (!SlaveIsActive)
|
||||
OnStringChange(e.Text, LineReceivedId);
|
||||
}
|
||||
|
||||
void SlaveGather_LineReceived(object sender, GenericCommMethodReceiveTextArgs e)
|
||||
{
|
||||
if (e.Text.Contains("login_required"))
|
||||
{
|
||||
SlaveClient.SendText(string.Format("login {0} {1} \x0d\x0a", Username, Password));
|
||||
}
|
||||
else if (e.Text.Contains("login_success"))
|
||||
{
|
||||
// START THE POLLING, YO!
|
||||
}
|
||||
else if (e.Text.StartsWith("sr"))
|
||||
{
|
||||
var split = e.Text.Trim().Split(' ');
|
||||
if (split[split.Length - 1] == "1")
|
||||
{
|
||||
SlaveIsActive = true;
|
||||
OnBoolChange(true, SlaveIsActiveId);
|
||||
OnBoolChange(false, MasterIsActiveId);
|
||||
}
|
||||
}
|
||||
if (SlaveIsActive)
|
||||
OnStringChange(e.Text, LineReceivedId);
|
||||
}
|
||||
|
||||
void StartPolling()
|
||||
{
|
||||
if (!IsPolling)
|
||||
{
|
||||
IsPolling = true;
|
||||
|
||||
Poll();
|
||||
if (PollTimer != null)
|
||||
PollTimer.Stop();
|
||||
|
||||
PollTimer = new CTimer(o => Poll(), null, PollingIntervalSeconds * 1000, PollingIntervalSeconds * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
void Poll()
|
||||
{
|
||||
if (MasterClient != null && MasterClient.IsConnected)
|
||||
{
|
||||
Debug.Console(2, this, "Polling Master.");
|
||||
MasterClient.SendText("sg\x0d\x0a");
|
||||
|
||||
}
|
||||
if (SlaveClient != null && SlaveClient.IsConnected)
|
||||
{
|
||||
Debug.Console(2, this, "Polling Slave.");
|
||||
SlaveClient.SendText("sg\x0d\x0a");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// login NAME PIN ---> login_success, login_failed
|
||||
|
||||
// status get
|
||||
// sg --> sr DESIGN_NAME DESIGN_ID IS_PRIMARY IS_ACTIVE
|
||||
|
||||
// CRLF
|
||||
|
||||
void OnBoolChange(bool state, ushort type)
|
||||
{
|
||||
var handler = BoolChange;
|
||||
if (handler != null)
|
||||
handler(this, new BoolChangeEventArgs(state, type));
|
||||
}
|
||||
|
||||
void OnUshortChange(ushort state, ushort type)
|
||||
{
|
||||
var handler = UshortChange;
|
||||
if (handler != null)
|
||||
handler(this, new UshrtChangeEventArgs(state, type));
|
||||
}
|
||||
|
||||
void OnStringChange(string value, ushort type)
|
||||
{
|
||||
var handler = StringChange;
|
||||
if (handler != null)
|
||||
handler(this, new StringChangeEventArgs(value, type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const ushort MasterIsActiveId = 3;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const ushort SlaveIsActiveId = 4;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const ushort MainModuleInitiailzeId = 5;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const ushort MasterClientStatusId = 101;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const ushort SlaveClientStatusId = 102;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public const ushort LineReceivedId = 201;
|
||||
}
|
||||
}
|
||||
59
src/Comm/TcpClientConfigObject.cs
Normal file
59
src/Comm/TcpClientConfigObject.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Client config object for TCP client with server that inherits from TcpSshPropertiesConfig and adds properties for shared key and heartbeat
|
||||
/// </summary>
|
||||
public class TcpClientConfigObject
|
||||
{
|
||||
/// <summary>
|
||||
/// TcpSsh Properties
|
||||
/// </summary>
|
||||
[JsonProperty("control")]
|
||||
public ControlPropertiesConfig Control { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
|
||||
/// </summary>
|
||||
[JsonProperty("secure")]
|
||||
public bool Secure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
|
||||
/// </summary>
|
||||
[JsonProperty("sharedKeyRequired")]
|
||||
public bool SharedKeyRequired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The shared key that must match on the server and client
|
||||
/// </summary>
|
||||
[JsonProperty("sharedKey")]
|
||||
public string SharedKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[JsonProperty("heartbeatRequired")]
|
||||
public bool HeartbeatRequired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
|
||||
/// </summary>
|
||||
[JsonProperty("heartbeatRequiredIntervalInSeconds")]
|
||||
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
|
||||
/// </summary>
|
||||
[JsonProperty("heartbeatStringToMatch")]
|
||||
public string HeartbeatStringToMatch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Receive Queue size must be greater than 20 or defaults to 20
|
||||
/// </summary>
|
||||
[JsonProperty("receiveQueueSize")]
|
||||
public int ReceiveQueueSize { get; set; }
|
||||
}
|
||||
}
|
||||
60
src/Comm/TcpServerConfigObject.cs
Normal file
60
src/Comm/TcpServerConfigObject.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities
|
||||
/// </summary>
|
||||
public class TcpServerConfigObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Uique key
|
||||
/// </summary>
|
||||
public string Key { get; set; }
|
||||
/// <summary>
|
||||
/// Max Clients that the server will allow to connect.
|
||||
/// </summary>
|
||||
public ushort MaxClients { get; set; }
|
||||
/// <summary>
|
||||
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
|
||||
/// </summary>
|
||||
public bool Secure { get; set; }
|
||||
/// <summary>
|
||||
/// Port for the server to listen on
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
/// <summary>
|
||||
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
|
||||
/// </summary>
|
||||
public bool SharedKeyRequired { get; set; }
|
||||
/// <summary>
|
||||
/// The shared key that must match on the server and client
|
||||
/// </summary>
|
||||
public string SharedKey { get; set; }
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool HeartbeatRequired { get; set; }
|
||||
/// <summary>
|
||||
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected
|
||||
/// </summary>
|
||||
public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
|
||||
/// <summary>
|
||||
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
|
||||
/// </summary>
|
||||
public string HeartbeatStringToMatch { get; set; }
|
||||
/// <summary>
|
||||
/// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
/// <summary>
|
||||
/// Receive Queue size must be greater than 20 or defaults to 20
|
||||
/// </summary>
|
||||
public int ReceiveQueueSize { get; set; }
|
||||
}
|
||||
}
|
||||
79
src/Comm/eControlMethods.cs
Normal file
79
src/Comm/eControlMethods.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Crestron Control Methods for a comm object
|
||||
/// </summary>
|
||||
public enum eControlMethod
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// RS232/422/485
|
||||
/// </summary>
|
||||
Com,
|
||||
/// <summary>
|
||||
/// Crestron IpId (most Crestron ethernet devices)
|
||||
/// </summary>
|
||||
IpId,
|
||||
/// <summary>
|
||||
/// Crestron IpIdTcp (HD-MD series, etc.)
|
||||
/// </summary>
|
||||
IpidTcp,
|
||||
/// <summary>
|
||||
/// Crestron IR control
|
||||
/// </summary>
|
||||
IR,
|
||||
/// <summary>
|
||||
/// SSH client
|
||||
/// </summary>
|
||||
Ssh,
|
||||
/// <summary>
|
||||
/// TCP/IP client
|
||||
/// </summary>
|
||||
Tcpip,
|
||||
/// <summary>
|
||||
/// Telnet
|
||||
/// </summary>
|
||||
Telnet,
|
||||
/// <summary>
|
||||
/// Crestnet device
|
||||
/// </summary>
|
||||
Cresnet,
|
||||
/// <summary>
|
||||
/// CEC Control, via a DM HDMI port
|
||||
/// </summary>
|
||||
Cec,
|
||||
/// <summary>
|
||||
/// UDP Server
|
||||
/// </summary>
|
||||
Udp,
|
||||
/// <summary>
|
||||
/// HTTP client
|
||||
/// </summary>
|
||||
Http,
|
||||
/// <summary>
|
||||
/// HTTPS client
|
||||
/// </summary>
|
||||
Https,
|
||||
/// <summary>
|
||||
/// Websocket client
|
||||
/// </summary>
|
||||
Ws,
|
||||
/// <summary>
|
||||
/// Secure Websocket client
|
||||
/// </summary>
|
||||
Wss,
|
||||
/// <summary>
|
||||
/// Secure TCP/IP
|
||||
/// </summary>
|
||||
SecureTcpIp
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user