mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-04-14 04:57:15 +00:00
refactor: Refactor DeviceManager and related classes to improve thread safety and performance
- Replaced CCriticalSection with lock statements in DeviceManager for better thread management. - Updated AddDevice and RemoveDevice methods to use Monitor for locking. - Enhanced event handling for device activation and registration. - Modified FileIO class to utilize Task for asynchronous file operations instead of CrestronInvoke. - Improved feedback mechanisms in FeedbackBase and SystemMonitorController using Task.Run. - Refactored GenericQueue to remove Crestron threading dependencies and utilize System.Threading. - Updated BlueJeansPc and VideoCodecBase classes to use Task for asynchronous operations. - Cleaned up unnecessary critical sections and improved code documentation across various files.
This commit is contained in:
parent
426ef4ad6b
commit
346a5e9e57
23 changed files with 998 additions and 912 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using System.Timers;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core;
|
||||||
|
|
@ -20,7 +20,7 @@ public class CommunicationStreamDebugging
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Timer to disable automatically if not manually disabled
|
/// Timer to disable automatically if not manually disabled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private CTimer DebugExpiryPeriod;
|
private Timer DebugExpiryPeriod;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current debug setting
|
/// The current debug setting
|
||||||
|
|
@ -93,7 +93,9 @@ public class CommunicationStreamDebugging
|
||||||
|
|
||||||
StopDebugTimer();
|
StopDebugTimer();
|
||||||
|
|
||||||
DebugExpiryPeriod = new CTimer((o) => DisableDebugging(), _DebugTimeoutInMs);
|
DebugExpiryPeriod = new Timer(_DebugTimeoutInMs) { AutoReset = false };
|
||||||
|
DebugExpiryPeriod.Elapsed += (s, e) => DisableDebugging();
|
||||||
|
DebugExpiryPeriod.Start();
|
||||||
|
|
||||||
if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx)
|
if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx)
|
||||||
RxStreamDebuggingIsEnabled = true;
|
RxStreamDebuggingIsEnabled = true;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
|
|
@ -277,7 +279,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue lock
|
/// Queue lock
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CCriticalSection DequeueLock = new CCriticalSection();
|
private readonly object _dequeueLock = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <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
|
/// 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
|
||||||
|
|
@ -696,9 +698,8 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||||
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
{
|
{
|
||||||
var gotLock = DequeueLock.TryEnter();
|
if (Monitor.TryEnter(_dequeueLock))
|
||||||
if (gotLock)
|
Task.Run(() => DequeueEvent());
|
||||||
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
|
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
|
||||||
|
|
@ -708,7 +709,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
|
/// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently.
|
||||||
/// It will dequeue items as they are enqueued automatically.
|
/// It will dequeue items as they are enqueued automatically.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void DequeueEvent()
|
void DequeueEvent()
|
||||||
|
|
@ -730,11 +731,8 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||||
{
|
{
|
||||||
this.LogError(e, "DequeueEvent error: {0}", e.Message);
|
this.LogError(e, "DequeueEvent error: {0}", e.Message);
|
||||||
}
|
}
|
||||||
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
|
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
|
||||||
if (DequeueLock != null)
|
Monitor.Exit(_dequeueLock);
|
||||||
{
|
|
||||||
DequeueLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeartbeatStart()
|
void HeartbeatStart()
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
|
|
@ -271,7 +273,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue lock
|
/// Queue lock
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CCriticalSection DequeueLock = new CCriticalSection();
|
private readonly object _dequeueLock = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <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
|
/// 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
|
||||||
|
|
@ -655,9 +657,8 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||||
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
{
|
{
|
||||||
var gotLock = DequeueLock.TryEnter();
|
if (Monitor.TryEnter(_dequeueLock))
|
||||||
if (gotLock)
|
Task.Run(() => DequeueEvent());
|
||||||
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
|
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
|
||||||
|
|
@ -667,7 +668,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
|
/// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently.
|
||||||
/// It will dequeue items as they are enqueued automatically.
|
/// It will dequeue items as they are enqueued automatically.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void DequeueEvent()
|
void DequeueEvent()
|
||||||
|
|
@ -689,11 +690,8 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||||
{
|
{
|
||||||
this.LogError("DequeueEvent error: {0}", e.Message, e);
|
this.LogError("DequeueEvent error: {0}", e.Message, 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.
|
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
|
||||||
if (DequeueLock != null)
|
Monitor.Exit(_dequeueLock);
|
||||||
{
|
|
||||||
DequeueLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeartbeatStart()
|
void HeartbeatStart()
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,9 @@
|
||||||
/*PepperDash Technology Corp.
|
using System;
|
||||||
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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
|
|
@ -69,12 +60,17 @@ public class GenericSecureTcpIpServer : Device
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server listen lock
|
/// Server listen lock
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CCriticalSection ServerCCSection = new CCriticalSection();
|
private readonly object _serverLock = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue lock
|
/// Queue lock
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CCriticalSection DequeueLock = new CCriticalSection();
|
private readonly object _dequeueLock = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcast lock
|
||||||
|
/// </summary>
|
||||||
|
private readonly object _broadcastLock = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <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
|
/// 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
|
||||||
|
|
@ -399,18 +395,19 @@ public class GenericSecureTcpIpServer : Device
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Listen()
|
public void Listen()
|
||||||
{
|
{
|
||||||
ServerCCSection.Enter();
|
lock (_serverLock)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Port < 1 || Port > 65535)
|
if (Port < 1 || Port > 65535)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key);
|
this.LogError("Server '{0}': Invalid port", Key);
|
||||||
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key);
|
this.LogError("Server '{0}': No Shared Key set", Key);
|
||||||
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -434,22 +431,21 @@ public class GenericSecureTcpIpServer : Device
|
||||||
SocketErrorCodes status = SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
SocketErrorCodes status = SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||||
if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING)
|
if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING)
|
||||||
{
|
{
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error starting WaitForConnectionAsync {0}", status);
|
this.LogError("Error starting WaitForConnectionAsync {0}", status);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ServerStopped = false;
|
ServerStopped = false;
|
||||||
}
|
}
|
||||||
OnServerStateChange(SecureServer.State);
|
OnServerStateChange(SecureServer.State);
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus);
|
this.LogInformation("Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus);
|
||||||
ServerCCSection.Leave();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ServerCCSection.Leave();
|
this.LogException(ex, "{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
|
||||||
ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
|
|
||||||
}
|
}
|
||||||
|
} // end lock
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -459,18 +455,18 @@ public class GenericSecureTcpIpServer : Device
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
|
this.LogVerbose("Stopping Listener");
|
||||||
if (SecureServer != null)
|
if (SecureServer != null)
|
||||||
{
|
{
|
||||||
SecureServer.Stop();
|
SecureServer.Stop();
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", SecureServer.State);
|
this.LogVerbose("Server State: {0}", SecureServer.State);
|
||||||
OnServerStateChange(SecureServer.State);
|
OnServerStateChange(SecureServer.State);
|
||||||
}
|
}
|
||||||
ServerStopped = true;
|
ServerStopped = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
|
this.LogException(ex, "Error stopping server. Error: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -483,11 +479,11 @@ public class GenericSecureTcpIpServer : Device
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SecureServer.Disconnect(client);
|
SecureServer.Disconnect(client);
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client);
|
this.LogVerbose("Disconnected client index: {0}", client);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex);
|
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", client, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -495,7 +491,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DisconnectAllClientsForShutdown()
|
public void DisconnectAllClientsForShutdown()
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients");
|
this.LogInformation("Disconnecting All Clients");
|
||||||
if (SecureServer != null)
|
if (SecureServer != null)
|
||||||
{
|
{
|
||||||
SecureServer.SocketStatusChange -= SecureServer_SocketStatusChange;
|
SecureServer.SocketStatusChange -= SecureServer_SocketStatusChange;
|
||||||
|
|
@ -507,17 +503,17 @@ public class GenericSecureTcpIpServer : Device
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SecureServer.Disconnect(i);
|
SecureServer.Disconnect(i);
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i);
|
this.LogInformation("Disconnected client index: {0}", i);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex);
|
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", i, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", SecureServer.ServerSocketStatus);
|
this.LogInformation("Server Status: {0}", SecureServer.ServerSocketStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients");
|
this.LogInformation("Disconnected All Clients");
|
||||||
ConnectedClientsIndexes.Clear();
|
ConnectedClientsIndexes.Clear();
|
||||||
|
|
||||||
if (!ProgramIsStopping)
|
if (!ProgramIsStopping)
|
||||||
|
|
@ -535,8 +531,8 @@ public class GenericSecureTcpIpServer : Device
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
public void BroadcastText(string text)
|
public void BroadcastText(string text)
|
||||||
{
|
{
|
||||||
CCriticalSection CCBroadcast = new CCriticalSection();
|
lock (_broadcastLock)
|
||||||
CCBroadcast.Enter();
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (ConnectedClientsIndexes.Count > 0)
|
if (ConnectedClientsIndexes.Count > 0)
|
||||||
|
|
@ -552,13 +548,12 @@ public class GenericSecureTcpIpServer : Device
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CCBroadcast.Leave();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
CCBroadcast.Leave();
|
this.LogException(ex, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
|
||||||
}
|
}
|
||||||
|
} // end lock
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -579,7 +574,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
this.LogException(ex, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -603,7 +598,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
}
|
}
|
||||||
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
this.LogDebug("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
||||||
// Return Heartbeat
|
// Return Heartbeat
|
||||||
SendTextToClient(HeartbeatStringToMatch, clientIndex);
|
SendTextToClient(HeartbeatStringToMatch, clientIndex);
|
||||||
return remainingText;
|
return remainingText;
|
||||||
|
|
@ -618,13 +613,13 @@ public class GenericSecureTcpIpServer : Device
|
||||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
}
|
}
|
||||||
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
this.LogInformation("Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
this.LogException(ex, "Error checking heartbeat: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
return received;
|
return received;
|
||||||
}
|
}
|
||||||
|
|
@ -636,11 +631,11 @@ public class GenericSecureTcpIpServer : Device
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public string GetClientIPAddress(uint clientIndex)
|
public string GetClientIPAddress(uint clientIndex)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex);
|
this.LogInformation("GetClientIPAddress Index: {0}", clientIndex);
|
||||||
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
||||||
{
|
{
|
||||||
var ipa = this.SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
var ipa = this.SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa);
|
this.LogInformation("GetClientIPAddress IPAddreess: {0}", ipa);
|
||||||
return ipa;
|
return ipa;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -663,7 +658,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
clientIndex = (uint)o;
|
clientIndex = (uint)o;
|
||||||
address = SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
address = SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
this.LogInformation("Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
||||||
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
|
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
|
||||||
|
|
||||||
if (SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
|
@ -703,7 +698,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
// Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.SecureServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.SecureServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
// Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.SecureServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.SecureServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber);
|
this.LogInformation("SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber);
|
||||||
|
|
||||||
if (ConnectedClientsIndexes.Contains(clientIndex))
|
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
ConnectedClientsIndexes.Remove(clientIndex);
|
ConnectedClientsIndexes.Remove(clientIndex);
|
||||||
|
|
@ -725,12 +720,12 @@ public class GenericSecureTcpIpServer : Device
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex);
|
this.LogException(ex, "Error in Socket Status Change Callback. Error: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
//Use a thread for this event so that the server state updates to listening while this event is processed. Listening must be added to the server state
|
//Use a thread for this event so that the server state updates to listening while this event is processed. Listening must be added to the server state
|
||||||
//after every client connection so that the server can check and see if it is at max clients. Due to this the event fires and server listening enum bit flag
|
//after every client connection so that the server can check and see if it is at max clients. Due to this the event fires and server listening enum bit flag
|
||||||
//is not set. Putting in a thread allows the state to update before this event processes so that the subscribers to this event get accurate isListening in the event.
|
//is not set. Putting in a thread allows the state to update before this event processes so that the subscribers to this event get accurate isListening in the event.
|
||||||
CrestronInvoke.BeginInvoke(o => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)), null);
|
Task.Run(() => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -745,7 +740,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
this.LogInformation("ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
||||||
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
|
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
|
||||||
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||||
if (clientIndex != 0)
|
if (clientIndex != 0)
|
||||||
|
|
@ -765,7 +760,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
}
|
}
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
|
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
|
||||||
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
this.LogInformation("Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -784,19 +779,19 @@ public class GenericSecureTcpIpServer : Device
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty.");
|
this.LogError("Client attempt faulty.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex);
|
this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rearm the listner
|
// Rearm the listner
|
||||||
SocketErrorCodes status = server.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
SocketErrorCodes status = server.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||||
if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING)
|
if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING)
|
||||||
{
|
{
|
||||||
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Socket status connect callback status {0}", status);
|
this.LogError("Socket status connect callback status {0}", status);
|
||||||
if (status == SocketErrorCodes.SOCKET_CONNECTION_IN_PROGRESS)
|
if (status == SocketErrorCodes.SOCKET_CONNECTION_IN_PROGRESS)
|
||||||
{
|
{
|
||||||
// There is an issue where on a failed negotiation we need to stop and start the server. This should still leave connected clients intact.
|
// There is an issue where on a failed negotiation we need to stop and start the server. This should still leave connected clients intact.
|
||||||
|
|
@ -833,7 +828,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
if (received != SharedKey)
|
if (received != SharedKey)
|
||||||
{
|
{
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
this.LogWarning("Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
||||||
mySecureTCPServer.SendData(clientIndex, b, b.Length);
|
mySecureTCPServer.SendData(clientIndex, b, b.Length);
|
||||||
mySecureTCPServer.Disconnect(clientIndex);
|
mySecureTCPServer.Disconnect(clientIndex);
|
||||||
|
|
||||||
|
|
@ -844,7 +839,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
|
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
|
||||||
mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
|
mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
|
||||||
OnServerClientReadyForCommunications(clientIndex);
|
OnServerClientReadyForCommunications(clientIndex);
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
this.LogInformation("Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
|
else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
|
||||||
{
|
{
|
||||||
|
|
@ -857,7 +852,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
|
this.LogException(ex, "Error Receiving data: {0}. Error: {1}", received, ex.Message);
|
||||||
}
|
}
|
||||||
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
|
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
|
||||||
|
|
@ -865,9 +860,8 @@ public class GenericSecureTcpIpServer : Device
|
||||||
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
{
|
{
|
||||||
var gotLock = DequeueLock.TryEnter();
|
if (Monitor.TryEnter(_dequeueLock))
|
||||||
if (gotLock)
|
Task.Run(() => DequeueEvent());
|
||||||
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -877,7 +871,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
|
/// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently.
|
||||||
/// It will dequeue items as they are enqueued automatically.
|
/// It will dequeue items as they are enqueued automatically.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void DequeueEvent()
|
void DequeueEvent()
|
||||||
|
|
@ -899,11 +893,8 @@ public class GenericSecureTcpIpServer : Device
|
||||||
{
|
{
|
||||||
this.LogError(e, "DequeueEvent error");
|
this.LogError(e, "DequeueEvent error");
|
||||||
}
|
}
|
||||||
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
|
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
|
||||||
if (DequeueLock != null)
|
Monitor.Exit(_dequeueLock);
|
||||||
{
|
|
||||||
DequeueLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -974,7 +965,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
if (MonitorClient != null)
|
if (MonitorClient != null)
|
||||||
MonitorClient.Disconnect();
|
MonitorClient.Disconnect();
|
||||||
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server");
|
this.LogInformation("Program stopping. Closing server");
|
||||||
KillServer();
|
KillServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1015,7 +1006,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
|
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
|
||||||
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
|
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
|
||||||
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check");
|
this.LogInformation("Starting monitor check");
|
||||||
|
|
||||||
MonitorClient.Connect();
|
MonitorClient.Connect();
|
||||||
// From here MonitorCLient either connects or hangs, MonitorClient will call back
|
// From here MonitorCLient either connects or hangs, MonitorClient will call back
|
||||||
|
|
@ -1042,7 +1033,7 @@ public class GenericSecureTcpIpServer : Device
|
||||||
{
|
{
|
||||||
if (args.IsReady)
|
if (args.IsReady)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s");
|
this.LogInformation("Monitor client connection success. Disconnecting in 2s");
|
||||||
MonitorClientTimer.Stop();
|
MonitorClientTimer.Stop();
|
||||||
MonitorClientTimer = null;
|
MonitorClientTimer = null;
|
||||||
MonitorClientFailureCount = 0;
|
MonitorClientFailureCount = 0;
|
||||||
|
|
@ -1063,13 +1054,13 @@ public class GenericSecureTcpIpServer : Device
|
||||||
StopMonitorClient();
|
StopMonitorClient();
|
||||||
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
|
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}",
|
this.LogWarning("Monitor client connection has hung {0} time{1}, maximum {2}",
|
||||||
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
|
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
|
||||||
StartMonitorClient();
|
StartMonitorClient();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error,
|
this.LogError(
|
||||||
"\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************",
|
"\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************",
|
||||||
MonitorClientMaxFailureCount);
|
MonitorClientMaxFailureCount);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using Org.BouncyCastle.Utilities;
|
using Org.BouncyCastle.Utilities;
|
||||||
|
|
@ -135,8 +136,6 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||||
|
|
||||||
CTimer ReconnectTimer;
|
CTimer ReconnectTimer;
|
||||||
|
|
||||||
//Lock object to prevent simulatneous connect/disconnect operations
|
|
||||||
//private CCriticalSection connectLock = new CCriticalSection();
|
|
||||||
private SemaphoreSlim connectLock = new SemaphoreSlim(1);
|
private SemaphoreSlim connectLock = new SemaphoreSlim(1);
|
||||||
|
|
||||||
private bool DisconnectLogged = false;
|
private bool DisconnectLogged = false;
|
||||||
|
|
@ -435,7 +434,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
|
void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
CrestronInvoke.BeginInvoke(o =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
|
if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
|
||||||
this.LogError("Disconnected by remote");
|
this.LogError("Disconnected by remote");
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,21 @@ using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using Timer = System.Timers.Timer;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||||
using Required = NewtonsoftJson::Newtonsoft.Json.Required;
|
using Required = NewtonsoftJson::Newtonsoft.Json.Required;
|
||||||
|
using PepperDash.Core.Logging;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A class to handle basic TCP/IP communications with a server
|
/// A class to handle basic TCP/IP communications with a server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
||||||
{
|
{
|
||||||
private const string SplusKey = "Uninitialized TcpIpClient";
|
private const string SplusKey = "Uninitialized TcpIpClient";
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -22,44 +26,44 @@ namespace PepperDash.Core;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fires when data is received from the server and returns it as a Byte array
|
/// Fires when data is received from the server and returns it as a Byte array
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fires when data is received from the server and returns it as text
|
/// Fires when data is received from the server and returns it as text
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
||||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||||
|
|
||||||
|
|
||||||
private string _hostname;
|
private string _hostname;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Address of server
|
/// Address of server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Hostname
|
public string Hostname
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _hostname;
|
return _hostname;
|
||||||
}
|
}
|
||||||
|
|
||||||
set
|
set
|
||||||
|
{
|
||||||
|
_hostname = value;
|
||||||
|
if (_client != null)
|
||||||
{
|
{
|
||||||
_hostname = value;
|
_client.AddressClientConnectedTo = _hostname;
|
||||||
if (_client != null)
|
|
||||||
{
|
|
||||||
_client.AddressClientConnectedTo = _hostname;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Port on server
|
/// Port on server
|
||||||
|
|
@ -81,19 +85,19 @@ namespace PepperDash.Core;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int BufferSize { get; set; }
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The actual client class
|
/// The actual client class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private TCPClient _client;
|
private TCPClient _client;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bool showing if socket is connected
|
/// Bool showing if socket is connected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsConnected
|
public bool IsConnected
|
||||||
{
|
{
|
||||||
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// S+ helper for IsConnected
|
/// S+ helper for IsConnected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -102,15 +106,15 @@ namespace PepperDash.Core;
|
||||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// _client socket status Read only
|
/// _client socket status Read only
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SocketStatus ClientStatus
|
public SocketStatus ClientStatus
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
|
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -122,26 +126,26 @@ namespace PepperDash.Core;
|
||||||
get { return (ushort)ClientStatus; }
|
get { return (ushort)ClientStatus; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Status text shows the message associated with socket status
|
/// Status text shows the message associated with socket status
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ushort representation of client status
|
/// Ushort representation of client status
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete]
|
[Obsolete]
|
||||||
public ushort UClientStatus { get { return (ushort)ClientStatus; } }
|
public ushort UClientStatus { get { return (ushort)ClientStatus; } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connection failure reason
|
/// Connection failure reason
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ConnectionFailure { get { return ClientStatus.ToString(); } }
|
public string ConnectionFailure { get { return ClientStatus.ToString(); } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the AutoReconnect
|
/// Gets or sets the AutoReconnect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AutoReconnect { get; set; }
|
public bool AutoReconnect { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// S+ helper for AutoReconnect
|
/// S+ helper for AutoReconnect
|
||||||
|
|
@ -152,29 +156,29 @@ namespace PepperDash.Core;
|
||||||
set { AutoReconnect = value == 1; }
|
set { AutoReconnect = value == 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int AutoReconnectIntervalMs { get; set; }
|
public int AutoReconnectIntervalMs { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set only when the disconnect method is called
|
/// Set only when the disconnect method is called
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool DisconnectCalledByUser;
|
bool DisconnectCalledByUser;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Connected
|
public bool Connected
|
||||||
{
|
{
|
||||||
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||||
}
|
}
|
||||||
|
|
||||||
//Lock object to prevent simulatneous connect/disconnect operations
|
//Lock object to prevent simulatneous connect/disconnect operations
|
||||||
private CCriticalSection connectLock = new CCriticalSection();
|
private readonly object _connectLock = new();
|
||||||
|
|
||||||
// private Timer for auto reconnect
|
// private Timer for auto reconnect
|
||||||
private CTimer RetryTimer;
|
private Timer RetryTimer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
|
|
@ -183,9 +187,9 @@ namespace PepperDash.Core;
|
||||||
/// <param name="address"></param>
|
/// <param name="address"></param>
|
||||||
/// <param name="port"></param>
|
/// <param name="port"></param>
|
||||||
/// <param name="bufferSize"></param>
|
/// <param name="bufferSize"></param>
|
||||||
public GenericTcpIpClient(string key, string address, int port, int bufferSize)
|
public GenericTcpIpClient(string key, string address, int port, int bufferSize)
|
||||||
: base(key)
|
: base(key)
|
||||||
{
|
{
|
||||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
AutoReconnectIntervalMs = 5000;
|
AutoReconnectIntervalMs = 5000;
|
||||||
|
|
@ -193,10 +197,7 @@ namespace PepperDash.Core;
|
||||||
Port = port;
|
Port = port;
|
||||||
BufferSize = bufferSize;
|
BufferSize = bufferSize;
|
||||||
|
|
||||||
RetryTimer = new CTimer(o =>
|
SetupRetryTimer();
|
||||||
{
|
|
||||||
Reconnect();
|
|
||||||
}, Timeout.Infinite);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -211,28 +212,30 @@ namespace PepperDash.Core;
|
||||||
AutoReconnectIntervalMs = 5000;
|
AutoReconnectIntervalMs = 5000;
|
||||||
BufferSize = 2000;
|
BufferSize = 2000;
|
||||||
|
|
||||||
RetryTimer = new CTimer(o =>
|
SetupRetryTimer();
|
||||||
{
|
|
||||||
Reconnect();
|
|
||||||
}, Timeout.Infinite);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default constructor for S+
|
/// Default constructor for S+
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GenericTcpIpClient()
|
public GenericTcpIpClient()
|
||||||
: base(SplusKey)
|
: base(SplusKey)
|
||||||
{
|
{
|
||||||
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
|
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
AutoReconnectIntervalMs = 5000;
|
AutoReconnectIntervalMs = 5000;
|
||||||
BufferSize = 2000;
|
BufferSize = 2000;
|
||||||
|
|
||||||
RetryTimer = new CTimer(o =>
|
SetupRetryTimer();
|
||||||
{
|
}
|
||||||
Reconnect();
|
|
||||||
}, Timeout.Infinite);
|
private void SetupRetryTimer()
|
||||||
}
|
{
|
||||||
|
RetryTimer = new Timer { AutoReset = false, Enabled = false };
|
||||||
|
RetryTimer.Elapsed += (s, e) => Reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Just to help S+ set the key
|
/// Just to help S+ set the key
|
||||||
|
|
@ -249,7 +252,7 @@ namespace PepperDash.Core;
|
||||||
{
|
{
|
||||||
if (programEventType == eProgramStatusEventType.Stopping)
|
if (programEventType == eProgramStatusEventType.Stopping)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Program stopping. Closing connection");
|
this.LogInformation("Program stopping. Closing connection");
|
||||||
Deactivate();
|
Deactivate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -258,42 +261,41 @@ namespace PepperDash.Core;
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public override bool Deactivate()
|
public override bool Deactivate()
|
||||||
{
|
{
|
||||||
RetryTimer.Stop();
|
RetryTimer.Stop();
|
||||||
RetryTimer.Dispose();
|
RetryTimer.Dispose();
|
||||||
if (_client != null)
|
if (_client != null)
|
||||||
{
|
{
|
||||||
_client.SocketStatusChange -= this.Client_SocketStatusChange;
|
_client.SocketStatusChange -= this.Client_SocketStatusChange;
|
||||||
DisconnectClient();
|
DisconnectClient();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to connect to the server
|
/// Attempts to connect to the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Connect()
|
public void Connect()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(Hostname))
|
if (string.IsNullOrEmpty(Hostname))
|
||||||
{
|
{
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key);
|
this.LogWarning("GenericTcpIpClient '{0}': No address set", Key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Port < 1 || Port > 65535)
|
if (Port < 1 || Port > 65535)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': Invalid port", Key);
|
this.LogWarning("GenericTcpIpClient '{0}': Invalid port", Key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
lock (_connectLock)
|
||||||
{
|
{
|
||||||
connectLock.Enter();
|
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Connection already connected. Exiting Connect()");
|
this.LogInformation("Connection already connected. Exiting Connect()");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -306,11 +308,7 @@ namespace PepperDash.Core;
|
||||||
_client.ConnectToServerAsync(ConnectToServerCallback);
|
_client.ConnectToServerAsync(ConnectToServerCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
}
|
||||||
{
|
|
||||||
connectLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Reconnect()
|
private void Reconnect()
|
||||||
{
|
{
|
||||||
|
|
@ -318,44 +316,34 @@ namespace PepperDash.Core;
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try
|
lock (_connectLock)
|
||||||
{
|
{
|
||||||
connectLock.Enter();
|
|
||||||
if (IsConnected || DisconnectCalledByUser == true)
|
if (IsConnected || DisconnectCalledByUser == true)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()");
|
this.LogInformation("Reconnect no longer needed. Exiting Reconnect()");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Attempting reconnect now");
|
this.LogInformation("Attempting reconnect now");
|
||||||
_client.ConnectToServerAsync(ConnectToServerCallback);
|
_client.ConnectToServerAsync(ConnectToServerCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
connectLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to disconnect the client
|
/// Attempts to disconnect the client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Disconnect()
|
public void Disconnect()
|
||||||
{
|
{
|
||||||
try
|
lock (_connectLock)
|
||||||
{
|
{
|
||||||
connectLock.Enter();
|
|
||||||
DisconnectCalledByUser = true;
|
DisconnectCalledByUser = true;
|
||||||
|
|
||||||
// Stop trying reconnects, if we are
|
// Stop trying reconnects, if we are
|
||||||
RetryTimer.Stop();
|
RetryTimer.Stop();
|
||||||
DisconnectClient();
|
DisconnectClient();
|
||||||
}
|
}
|
||||||
finally
|
}
|
||||||
{
|
|
||||||
connectLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Does the actual disconnect business
|
/// Does the actual disconnect business
|
||||||
|
|
@ -364,7 +352,7 @@ namespace PepperDash.Core;
|
||||||
{
|
{
|
||||||
if (_client != null)
|
if (_client != null)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Disconnecting client");
|
this.LogInformation("Disconnecting client");
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
_client.DisconnectFromServer();
|
_client.DisconnectFromServer();
|
||||||
}
|
}
|
||||||
|
|
@ -374,50 +362,47 @@ namespace PepperDash.Core;
|
||||||
/// Callback method for connection attempt
|
/// Callback method for connection attempt
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="c"></param>
|
/// <param name="c"></param>
|
||||||
void ConnectToServerCallback(TCPClient c)
|
void ConnectToServerCallback(TCPClient c)
|
||||||
{
|
{
|
||||||
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
{
|
{
|
||||||
Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus);
|
this.LogInformation("Server connection result: {0}", c.ClientStatus);
|
||||||
WaitAndTryReconnect();
|
WaitAndTryReconnect();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus);
|
this.LogInformation("Server connection result: {0}", c.ClientStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disconnects, waits and attemtps to connect again
|
/// Disconnects, waits and attemtps to connect again
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void WaitAndTryReconnect()
|
void WaitAndTryReconnect()
|
||||||
{
|
{
|
||||||
CrestronInvoke.BeginInvoke(o =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
try
|
lock (_connectLock)
|
||||||
{
|
{
|
||||||
connectLock.Enter();
|
|
||||||
if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null)
|
if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null)
|
||||||
{
|
{
|
||||||
DisconnectClient();
|
DisconnectClient();
|
||||||
Debug.Console(1, this, "Attempting reconnect, status={0}", _client.ClientStatus);
|
this.LogInformation("Attempting reconnect, status={0}", _client.ClientStatus);
|
||||||
RetryTimer.Reset(AutoReconnectIntervalMs);
|
RetryTimer.Stop();
|
||||||
|
RetryTimer.Interval = AutoReconnectIntervalMs;
|
||||||
|
RetryTimer.Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
connectLock.Leave();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recieves incoming data
|
/// Recieves incoming data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client"></param>
|
/// <param name="client"></param>
|
||||||
/// <param name="numBytes"></param>
|
/// <param name="numBytes"></param>
|
||||||
void Receive(TCPClient client, int numBytes)
|
void Receive(TCPClient client, int numBytes)
|
||||||
{
|
{
|
||||||
if (client != null)
|
if (client != null)
|
||||||
{
|
{
|
||||||
if (numBytes > 0)
|
if (numBytes > 0)
|
||||||
|
|
@ -428,7 +413,7 @@ namespace PepperDash.Core;
|
||||||
{
|
{
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||||
{
|
{
|
||||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||||
}
|
}
|
||||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||||
}
|
}
|
||||||
|
|
@ -439,135 +424,135 @@ namespace PepperDash.Core;
|
||||||
|
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||||
{
|
{
|
||||||
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
this.LogInformation("Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
client.ReceiveDataAsync(Receive);
|
client.ReceiveDataAsync(Receive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// General send method
|
/// General send method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendText(string text)
|
public void SendText(string text)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
// Check debug level before processing byte array
|
// Check debug level before processing byte array
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||||
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
this.LogInformation("Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||||
if (_client != null)
|
if (_client != null)
|
||||||
_client.SendData(bytes, bytes.Length);
|
_client.SendData(bytes, bytes.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SendEscapedText method
|
/// SendEscapedText method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendEscapedText(string text)
|
public void SendEscapedText(string text)
|
||||||
{
|
{
|
||||||
var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s =>
|
var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s =>
|
||||||
{
|
{
|
||||||
var hex = s.Groups[1].Value;
|
var hex = s.Groups[1].Value;
|
||||||
return ((char)Convert.ToByte(hex, 16)).ToString();
|
return ((char)Convert.ToByte(hex, 16)).ToString();
|
||||||
});
|
});
|
||||||
SendText(unescapedText);
|
SendText(unescapedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends Bytes to the server
|
/// Sends Bytes to the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bytes"></param>
|
/// <param name="bytes"></param>
|
||||||
public void SendBytes(byte[] bytes)
|
public void SendBytes(byte[] bytes)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||||
if (_client != null)
|
if (_client != null)
|
||||||
_client.SendData(bytes, bytes.Length);
|
_client.SendData(bytes, bytes.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Socket Status Change Handler
|
/// Socket Status Change Handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client"></param>
|
/// <param name="client"></param>
|
||||||
/// <param name="clientSocketStatus"></param>
|
/// <param name="clientSocketStatus"></param>
|
||||||
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
|
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
|
||||||
{
|
{
|
||||||
if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
{
|
{
|
||||||
Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
this.LogDebug("Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||||
WaitAndTryReconnect();
|
WaitAndTryReconnect();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
this.LogDebug("Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||||
_client.ReceiveDataAsync(Receive);
|
_client.ReceiveDataAsync(Receive);
|
||||||
}
|
}
|
||||||
|
|
||||||
var handler = ConnectionChange;
|
var handler = ConnectionChange;
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration properties for TCP/SSH Connections
|
/// Configuration properties for TCP/SSH Connections
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TcpSshPropertiesConfig
|
public class TcpSshPropertiesConfig
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Address to connect to
|
/// Address to connect to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonProperty(Required = Required.Always)]
|
||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Port to connect to
|
/// Port to connect to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty(Required = Required.Always)]
|
[JsonProperty(Required = Required.Always)]
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Username credential
|
/// Username credential
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Passord credential
|
/// Passord credential
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defaults to 32768
|
/// Defaults to 32768
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int BufferSize { get; set; }
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the AutoReconnect
|
/// Gets or sets the AutoReconnect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AutoReconnect { get; set; }
|
public bool AutoReconnect { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the AutoReconnectIntervalMs
|
/// Gets or sets the AutoReconnectIntervalMs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int AutoReconnectIntervalMs { get; set; }
|
public int AutoReconnectIntervalMs { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When true, turns off echo for the SSH session
|
/// When true, turns off echo for the SSH session
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("disableSshEcho")]
|
[JsonProperty("disableSshEcho")]
|
||||||
public bool DisableSshEcho { get; set; }
|
public bool DisableSshEcho { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default constructor
|
/// Default constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TcpSshPropertiesConfig()
|
public TcpSshPropertiesConfig()
|
||||||
{
|
{
|
||||||
BufferSize = 32768;
|
BufferSize = 32768;
|
||||||
AutoReconnect = true;
|
AutoReconnect = true;
|
||||||
AutoReconnectIntervalMs = 5000;
|
AutoReconnectIntervalMs = 5000;
|
||||||
Username = "";
|
Username = "";
|
||||||
Password = "";
|
Password = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
|
|
@ -61,9 +62,14 @@ public class GenericTcpIpServer : Device
|
||||||
#region Properties/Variables
|
#region Properties/Variables
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Server listen lock
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CCriticalSection ServerCCSection = new CCriticalSection();
|
object _serverLock = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcast lock
|
||||||
|
/// </summary>
|
||||||
|
private readonly object _broadcastLock = new();
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -365,12 +371,12 @@ public class GenericTcpIpServer : Device
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key);
|
this.LogError("Could not initialize server with key: {0}", serverConfigObject.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key);
|
this.LogError("Could not initialize server with key: {0}", serverConfigObject.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,19 +385,18 @@ public class GenericTcpIpServer : Device
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Listen()
|
public void Listen()
|
||||||
{
|
{
|
||||||
ServerCCSection.Enter();
|
lock (_serverLock)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Port < 1 || Port > 65535)
|
if (Port < 1 || Port > 65535)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key);
|
this.LogError("Server '{0}': Invalid port", Key);
|
||||||
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key);
|
this.LogError("Server '{0}': No Shared Key set", Key);
|
||||||
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (IsListening)
|
if (IsListening)
|
||||||
|
|
@ -417,18 +422,15 @@ public class GenericTcpIpServer : Device
|
||||||
ServerStopped = false;
|
ServerStopped = false;
|
||||||
myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
||||||
OnServerStateChange(myTcpServer.State);
|
OnServerStateChange(myTcpServer.State);
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus);
|
this.LogInformation("TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus);
|
||||||
|
|
||||||
// StartMonitorClient();
|
// StartMonitorClient();
|
||||||
|
|
||||||
|
|
||||||
ServerCCSection.Leave();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ServerCCSection.Leave();
|
this.LogException(ex, "Error with Dynamic Server: {0}", ex.Message);
|
||||||
ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
|
|
||||||
}
|
}
|
||||||
|
} // end lock
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -438,18 +440,18 @@ public class GenericTcpIpServer : Device
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
|
this.LogDebug("Stopping Listener");
|
||||||
if (myTcpServer != null)
|
if (myTcpServer != null)
|
||||||
{
|
{
|
||||||
myTcpServer.Stop();
|
myTcpServer.Stop();
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", myTcpServer.State);
|
this.LogDebug("Server State: {0}", myTcpServer.State);
|
||||||
OnServerStateChange(myTcpServer.State);
|
OnServerStateChange(myTcpServer.State);
|
||||||
}
|
}
|
||||||
ServerStopped = true;
|
ServerStopped = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
|
this.LogException(ex, "Error stopping server. Error: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -462,11 +464,11 @@ public class GenericTcpIpServer : Device
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
myTcpServer.Disconnect(client);
|
myTcpServer.Disconnect(client);
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client);
|
this.LogVerbose("Disconnected client index: {0}", client);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex);
|
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", client, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -474,7 +476,7 @@ public class GenericTcpIpServer : Device
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DisconnectAllClientsForShutdown()
|
public void DisconnectAllClientsForShutdown()
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients");
|
this.LogInformation("Disconnecting All Clients");
|
||||||
if (myTcpServer != null)
|
if (myTcpServer != null)
|
||||||
{
|
{
|
||||||
myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange;
|
myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange;
|
||||||
|
|
@ -486,17 +488,17 @@ public class GenericTcpIpServer : Device
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
myTcpServer.Disconnect(i);
|
myTcpServer.Disconnect(i);
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i);
|
this.LogVerbose("Disconnected client index: {0}", i);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex);
|
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", i, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", myTcpServer.ServerSocketStatus);
|
this.LogVerbose("Server Status: {0}", myTcpServer.ServerSocketStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients");
|
this.LogInformation("Disconnected All Clients");
|
||||||
ConnectedClientsIndexes.Clear();
|
ConnectedClientsIndexes.Clear();
|
||||||
|
|
||||||
if (!ProgramIsStopping)
|
if (!ProgramIsStopping)
|
||||||
|
|
@ -514,8 +516,8 @@ public class GenericTcpIpServer : Device
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
public void BroadcastText(string text)
|
public void BroadcastText(string text)
|
||||||
{
|
{
|
||||||
CCriticalSection CCBroadcast = new CCriticalSection();
|
lock (_broadcastLock)
|
||||||
CCBroadcast.Enter();
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (ConnectedClientsIndexes.Count > 0)
|
if (ConnectedClientsIndexes.Count > 0)
|
||||||
|
|
@ -531,13 +533,12 @@ public class GenericTcpIpServer : Device
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CCBroadcast.Leave();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
CCBroadcast.Leave();
|
this.LogException(ex, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
|
||||||
}
|
}
|
||||||
|
} // end lock
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -558,7 +559,7 @@ public class GenericTcpIpServer : Device
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
this.LogException(ex, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -582,7 +583,7 @@ public class GenericTcpIpServer : Device
|
||||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
}
|
}
|
||||||
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
||||||
// Return Heartbeat
|
// Return Heartbeat
|
||||||
SendTextToClient(HeartbeatStringToMatch, clientIndex);
|
SendTextToClient(HeartbeatStringToMatch, clientIndex);
|
||||||
return remainingText;
|
return remainingText;
|
||||||
|
|
@ -597,13 +598,13 @@ public class GenericTcpIpServer : Device
|
||||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
}
|
}
|
||||||
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
this.LogException(ex, "Error checking heartbeat: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
return received;
|
return received;
|
||||||
}
|
}
|
||||||
|
|
@ -615,11 +616,11 @@ public class GenericTcpIpServer : Device
|
||||||
/// <returns>IP address of the client</returns>
|
/// <returns>IP address of the client</returns>
|
||||||
public string GetClientIPAddress(uint clientIndex)
|
public string GetClientIPAddress(uint clientIndex)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex);
|
this.LogVerbose("GetClientIPAddress Index: {0}", clientIndex);
|
||||||
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
||||||
{
|
{
|
||||||
var ipa = this.myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
var ipa = this.myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa);
|
this.LogVerbose("GetClientIPAddress IPAddreess: {0}", ipa);
|
||||||
return ipa;
|
return ipa;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -642,7 +643,7 @@ public class GenericTcpIpServer : Device
|
||||||
clientIndex = (uint)o;
|
clientIndex = (uint)o;
|
||||||
address = myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
address = myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
this.LogWarning("Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
||||||
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
|
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
|
||||||
|
|
||||||
if (myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
|
|
@ -678,7 +679,7 @@ public class GenericTcpIpServer : Device
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
this.LogInformation("SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
{
|
{
|
||||||
if (ConnectedClientsIndexes.Contains(clientIndex))
|
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
|
|
@ -697,7 +698,7 @@ public class GenericTcpIpServer : Device
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex);
|
this.LogException(ex, "Error in Socket Status Change Callback. Error: {0}", ex);
|
||||||
}
|
}
|
||||||
onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||||
}
|
}
|
||||||
|
|
@ -714,7 +715,7 @@ public class GenericTcpIpServer : Device
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
this.LogDebug("ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
||||||
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
|
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
|
||||||
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||||
if (clientIndex != 0)
|
if (clientIndex != 0)
|
||||||
|
|
@ -734,10 +735,11 @@ public class GenericTcpIpServer : Device
|
||||||
}
|
}
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
|
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
|
||||||
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
this.LogDebug("Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
this.LogDebug("Client at index {0} is ready for communications", clientIndex);
|
||||||
OnServerClientReadyForCommunications(clientIndex);
|
OnServerClientReadyForCommunications(clientIndex);
|
||||||
}
|
}
|
||||||
if (HeartbeatRequired)
|
if (HeartbeatRequired)
|
||||||
|
|
@ -753,7 +755,7 @@ public class GenericTcpIpServer : Device
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty.");
|
this.LogError("Client attempt faulty.");
|
||||||
if (!ServerStopped)
|
if (!ServerStopped)
|
||||||
{
|
{
|
||||||
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
||||||
|
|
@ -763,7 +765,7 @@ public class GenericTcpIpServer : Device
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex);
|
this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex);
|
||||||
}
|
}
|
||||||
//Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))",
|
//Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))",
|
||||||
// server.State,
|
// server.State,
|
||||||
|
|
@ -771,7 +773,7 @@ public class GenericTcpIpServer : Device
|
||||||
// ServerStopped);
|
// ServerStopped);
|
||||||
if ((server.State & ServerState.SERVER_LISTENING) != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
if ((server.State & ServerState.SERVER_LISTENING) != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Waiting for next connection");
|
this.LogDebug("Waiting for next connection");
|
||||||
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -802,7 +804,7 @@ public class GenericTcpIpServer : Device
|
||||||
if (received != SharedKey)
|
if (received != SharedKey)
|
||||||
{
|
{
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
this.LogWarning("Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
||||||
myTCPServer.SendData(clientIndex, b, b.Length);
|
myTCPServer.SendData(clientIndex, b, b.Length);
|
||||||
myTCPServer.Disconnect(clientIndex);
|
myTCPServer.Disconnect(clientIndex);
|
||||||
return;
|
return;
|
||||||
|
|
@ -812,7 +814,7 @@ public class GenericTcpIpServer : Device
|
||||||
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
|
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
|
||||||
myTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
|
myTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
|
||||||
OnServerClientReadyForCommunications(clientIndex);
|
OnServerClientReadyForCommunications(clientIndex);
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
this.LogDebug("Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
|
else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
|
||||||
|
|
@ -820,7 +822,7 @@ public class GenericTcpIpServer : Device
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
|
this.LogException(ex, "Error Receiving data: {0}. Error: {1}", received, ex);
|
||||||
}
|
}
|
||||||
if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
myTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
|
myTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
|
||||||
|
|
@ -901,7 +903,7 @@ public class GenericTcpIpServer : Device
|
||||||
if (MonitorClient != null)
|
if (MonitorClient != null)
|
||||||
MonitorClient.Disconnect();
|
MonitorClient.Disconnect();
|
||||||
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server");
|
this.LogInformation("Program stopping. Closing server");
|
||||||
KillServer();
|
KillServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -942,7 +944,7 @@ public class GenericTcpIpServer : Device
|
||||||
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
|
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
|
||||||
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
|
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
|
||||||
|
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check");
|
this.LogDebug("Starting monitor check");
|
||||||
|
|
||||||
MonitorClient.Connect();
|
MonitorClient.Connect();
|
||||||
// From here MonitorCLient either connects or hangs, MonitorClient will call back
|
// From here MonitorCLient either connects or hangs, MonitorClient will call back
|
||||||
|
|
@ -969,7 +971,7 @@ public class GenericTcpIpServer : Device
|
||||||
{
|
{
|
||||||
if (args.IsReady)
|
if (args.IsReady)
|
||||||
{
|
{
|
||||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s");
|
this.LogInformation("Monitor client connection success. Disconnecting in 2s");
|
||||||
MonitorClientTimer.Stop();
|
MonitorClientTimer.Stop();
|
||||||
MonitorClientTimer = null;
|
MonitorClientTimer = null;
|
||||||
MonitorClientFailureCount = 0;
|
MonitorClientFailureCount = 0;
|
||||||
|
|
@ -990,13 +992,13 @@ public class GenericTcpIpServer : Device
|
||||||
StopMonitorClient();
|
StopMonitorClient();
|
||||||
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
|
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}",
|
this.LogWarning("Monitor client connection has hung {0} time{1}, maximum {2}",
|
||||||
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
|
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
|
||||||
StartMonitorClient();
|
StartMonitorClient();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error,
|
this.LogError(
|
||||||
"\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************",
|
"\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************",
|
||||||
MonitorClientMaxFailureCount);
|
MonitorClientMaxFailureCount);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
extern alias NewtonsoftJson;
|
extern alias NewtonsoftJson;
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||||
|
|
||||||
|
|
@ -9,62 +10,61 @@ namespace PepperDash.Core.Logging;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class to persist current Debug settings across program restarts
|
/// Class to persist current Debug settings across program restarts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DebugContextCollection
|
public class DebugContextCollection
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// To prevent threading issues with the DeviceDebugSettings collection
|
/// To prevent threading issues with the DeviceDebugSettings collection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly CCriticalSection _deviceDebugSettingsLock;
|
private readonly object _deviceDebugSettingsLock = new();
|
||||||
|
|
||||||
[JsonProperty("items")] private readonly Dictionary<string, DebugContextItem> _items;
|
[JsonProperty("items")]
|
||||||
|
private readonly Dictionary<string, DebugContextItem> _items = new Dictionary<string, DebugContextItem>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Collection of the debug settings for each device where the dictionary key is the device key
|
/// Collection of the debug settings for each device where the dictionary key is the device key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("deviceDebugSettings")]
|
[JsonProperty("deviceDebugSettings")]
|
||||||
private Dictionary<string, object> DeviceDebugSettings { get; set; }
|
private Dictionary<string, object> DeviceDebugSettings { get; set; } = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default constructor
|
/// Default constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DebugContextCollection()
|
public DebugContextCollection()
|
||||||
{
|
{
|
||||||
_deviceDebugSettingsLock = new CCriticalSection();
|
|
||||||
DeviceDebugSettings = new Dictionary<string, object>();
|
|
||||||
_items = new Dictionary<string, DebugContextItem>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
}
|
||||||
/// Sets the level of a given context item, and adds that item if it does not
|
|
||||||
/// exist
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="contextKey"></param>
|
|
||||||
/// <param name="level"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// SetLevel method
|
|
||||||
/// </summary>
|
|
||||||
public void SetLevel(string contextKey, int level)
|
|
||||||
{
|
|
||||||
if (level < 0 || level > 2)
|
|
||||||
return;
|
|
||||||
GetOrCreateItem(contextKey).Level = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a level or creates it if not existing
|
/// Sets the level of a given context item, and adds that item if it does not
|
||||||
/// </summary>
|
/// exist
|
||||||
/// <param name="contextKey"></param>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <param name="contextKey"></param>
|
||||||
/// <summary>
|
/// <param name="level"></param>
|
||||||
/// GetOrCreateItem method
|
/// <summary>
|
||||||
/// </summary>
|
/// SetLevel method
|
||||||
public DebugContextItem GetOrCreateItem(string contextKey)
|
/// </summary>
|
||||||
{
|
public void SetLevel(string contextKey, int level)
|
||||||
if (!_items.ContainsKey(contextKey))
|
{
|
||||||
_items[contextKey] = new DebugContextItem { Level = 0 };
|
if (level < 0 || level > 2)
|
||||||
return _items[contextKey];
|
return;
|
||||||
}
|
GetOrCreateItem(contextKey).Level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a level or creates it if not existing
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contextKey"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <summary>
|
||||||
|
/// GetOrCreateItem method
|
||||||
|
/// </summary>
|
||||||
|
public DebugContextItem GetOrCreateItem(string contextKey)
|
||||||
|
{
|
||||||
|
if (!_items.ContainsKey(contextKey))
|
||||||
|
_items[contextKey] = new DebugContextItem { Level = 0 };
|
||||||
|
return _items[contextKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -75,10 +75,8 @@ namespace PepperDash.Core.Logging;
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public void SetDebugSettingsForKey(string deviceKey, object settings)
|
public void SetDebugSettingsForKey(string deviceKey, object settings)
|
||||||
{
|
{
|
||||||
try
|
lock (_deviceDebugSettingsLock)
|
||||||
{
|
{
|
||||||
_deviceDebugSettingsLock.Enter();
|
|
||||||
|
|
||||||
if (DeviceDebugSettings.ContainsKey(deviceKey))
|
if (DeviceDebugSettings.ContainsKey(deviceKey))
|
||||||
{
|
{
|
||||||
DeviceDebugSettings[deviceKey] = settings;
|
DeviceDebugSettings[deviceKey] = settings;
|
||||||
|
|
@ -86,10 +84,6 @@ namespace PepperDash.Core.Logging;
|
||||||
else
|
else
|
||||||
DeviceDebugSettings.Add(deviceKey, settings);
|
DeviceDebugSettings.Add(deviceKey, settings);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
_deviceDebugSettingsLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -101,22 +95,22 @@ namespace PepperDash.Core.Logging;
|
||||||
{
|
{
|
||||||
return DeviceDebugSettings[deviceKey];
|
return DeviceDebugSettings[deviceKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains information about
|
/// Contains information about
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DebugContextItem
|
public class DebugContextItem
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The level of debug messages to print
|
/// The level of debug messages to print
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("level")]
|
[JsonProperty("level")]
|
||||||
public int Level { get; set; }
|
public int Level { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Property to tell the program not to intitialize when it boots, if desired
|
/// Property to tell the program not to intitialize when it boots, if desired
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("doNotLoadOnNextBoot")]
|
[JsonProperty("doNotLoadOnNextBoot")]
|
||||||
public bool DoNotLoadOnNextBoot { get; set; }
|
public bool DoNotLoadOnNextBoot { get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -3,287 +3,250 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.WebScripting;
|
using Crestron.SimplSharp.WebScripting;
|
||||||
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
||||||
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
|
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
|
||||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||||
using PepperDash.Core.Web.RequestHandlers;
|
using PepperDash.Core.Web.RequestHandlers;
|
||||||
|
using PepperDash.Core.Logging;
|
||||||
|
|
||||||
namespace PepperDash.Core.Web;
|
namespace PepperDash.Core.Web;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Web API server
|
||||||
|
/// </summary>
|
||||||
|
public class WebApiServer : IKeyName
|
||||||
|
{
|
||||||
|
private const string SplusKey = "Uninitialized Web API Server";
|
||||||
|
private const string DefaultName = "Web API Server";
|
||||||
|
private const string DefaultBasePath = "/api";
|
||||||
|
|
||||||
|
private readonly object _serverLock = new();
|
||||||
|
private HttpCwsServer _server;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Web API server
|
/// Gets or sets the Key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WebApiServer : IKeyName
|
public string Key { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Name
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the BasePath
|
||||||
|
/// </summary>
|
||||||
|
public string BasePath { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the IsRegistered
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRegistered { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for S+. Make sure to set necessary properties using init method
|
||||||
|
/// </summary>
|
||||||
|
public WebApiServer()
|
||||||
|
: this(SplusKey, DefaultName, null)
|
||||||
{
|
{
|
||||||
private const string SplusKey = "Uninitialized Web API Server";
|
}
|
||||||
private const string DefaultName = "Web API Server";
|
|
||||||
private const string DefaultBasePath = "/api";
|
|
||||||
|
|
||||||
private const uint DebugTrace = 0;
|
/// <summary>
|
||||||
private const uint DebugInfo = 1;
|
/// Constructor
|
||||||
private const uint DebugVerbose = 2;
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="basePath"></param>
|
||||||
|
public WebApiServer(string key, string basePath)
|
||||||
|
: this(key, DefaultName, basePath)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private readonly CCriticalSection _serverLock = new CCriticalSection();
|
/// <summary>
|
||||||
private HttpCwsServer _server;
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="basePath"></param>
|
||||||
|
public WebApiServer(string key, string name, string basePath)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
Name = string.IsNullOrEmpty(name) ? DefaultName : name;
|
||||||
|
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
|
||||||
|
|
||||||
/// <summary>
|
if (_server == null) _server = new HttpCwsServer(BasePath);
|
||||||
/// Gets or sets the Key
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
_server.setProcessName(Key);
|
||||||
/// Gets or sets the Name
|
_server.HttpRequestHandler = new DefaultRequestHandler();
|
||||||
/// </summary>
|
|
||||||
public string Name { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
|
||||||
/// Gets or sets the BasePath
|
CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler;
|
||||||
/// </summary>
|
}
|
||||||
public string BasePath { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the IsRegistered
|
/// Program status event handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsRegistered { get; private set; }
|
/// <param name="programEventType"></param>
|
||||||
|
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
|
{
|
||||||
|
if (programEventType != eProgramStatusEventType.Stopping) return;
|
||||||
|
|
||||||
/// <summary>
|
this.LogInformation("Program stopping. stopping server");
|
||||||
/// Http request handler
|
|
||||||
/// </summary>
|
|
||||||
//public IHttpCwsHandler HttpRequestHandler
|
|
||||||
//{
|
|
||||||
// get { return _server.HttpRequestHandler; }
|
|
||||||
// set
|
|
||||||
// {
|
|
||||||
// if (_server == null) return;
|
|
||||||
// _server.HttpRequestHandler = value;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// <summary>
|
Stop();
|
||||||
/// Received request event handler
|
}
|
||||||
/// </summary>
|
|
||||||
//public event EventHandler<HttpCwsRequestEventArgs> ReceivedRequestEvent
|
|
||||||
//{
|
|
||||||
// add { _server.ReceivedRequestEvent += new HttpCwsRequestEventHandler(value); }
|
|
||||||
// remove { _server.ReceivedRequestEvent -= new HttpCwsRequestEventHandler(value); }
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor for S+. Make sure to set necessary properties using init method
|
/// Ethernet event handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public WebApiServer()
|
/// <param name="ethernetEventArgs"></param>
|
||||||
: this(SplusKey, DefaultName, null)
|
void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
|
||||||
|
{
|
||||||
|
// Re-enable the server if the link comes back up and the status should be connected
|
||||||
|
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered)
|
||||||
{
|
{
|
||||||
|
this.LogInformation("Ethernet link up. Server is already registered.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
this.LogInformation("Ethernet link up. Starting server");
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
Start();
|
||||||
/// <param name="key"></param>
|
}
|
||||||
/// <param name="basePath"></param>
|
|
||||||
public WebApiServer(string key, string basePath)
|
/// <summary>
|
||||||
: this(key, DefaultName, basePath)
|
/// Initialize method
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize(string key, string basePath)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a route to CWS
|
||||||
|
/// </summary>
|
||||||
|
public void AddRoute(HttpCwsRoute route)
|
||||||
|
{
|
||||||
|
if (route == null)
|
||||||
{
|
{
|
||||||
|
this.LogWarning("Failed to add route, route parameter is null");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
_server.Routes.Add(route);
|
||||||
/// Constructor
|
|
||||||
/// </summary>
|
}
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="name"></param>
|
/// <summary>
|
||||||
/// <param name="basePath"></param>
|
/// Removes a route from CWS
|
||||||
public WebApiServer(string key, string name, string basePath)
|
/// </summary>
|
||||||
|
/// <param name="route"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// RemoveRoute method
|
||||||
|
/// </summary>
|
||||||
|
public void RemoveRoute(HttpCwsRoute route)
|
||||||
|
{
|
||||||
|
if (route == null)
|
||||||
{
|
{
|
||||||
Key = key;
|
this.LogWarning("Failed to remove route, route parameter is null");
|
||||||
Name = string.IsNullOrEmpty(name) ? DefaultName : name;
|
return;
|
||||||
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
|
|
||||||
|
|
||||||
if (_server == null) _server = new HttpCwsServer(BasePath);
|
|
||||||
|
|
||||||
_server.setProcessName(Key);
|
|
||||||
_server.HttpRequestHandler = new DefaultRequestHandler();
|
|
||||||
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
|
|
||||||
CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
_server.Routes.Remove(route);
|
||||||
/// Program status event handler
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="programEventType"></param>
|
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
|
||||||
{
|
|
||||||
if (programEventType != eProgramStatusEventType.Stopping) return;
|
|
||||||
|
|
||||||
Debug.Console(DebugInfo, this, "Program stopping. stopping server");
|
/// <summary>
|
||||||
|
/// GetRouteCollection method
|
||||||
|
/// </summary>
|
||||||
|
public HttpCwsRouteCollection GetRouteCollection()
|
||||||
|
{
|
||||||
|
return _server.Routes;
|
||||||
|
}
|
||||||
|
|
||||||
Stop();
|
/// <summary>
|
||||||
}
|
/// Starts CWS instance
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
public void Start()
|
||||||
/// Ethernet event handler
|
{
|
||||||
/// </summary>
|
lock (_serverLock)
|
||||||
/// <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 && IsRegistered)
|
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "Ethernet link up. Server is alreedy registered.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Console(DebugInfo, this, "Ethernet link up. Starting server");
|
|
||||||
|
|
||||||
Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize method
|
|
||||||
/// </summary>
|
|
||||||
public void Initialize(string key, string basePath)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a route to CWS
|
|
||||||
/// </summary>
|
|
||||||
public void AddRoute(HttpCwsRoute route)
|
|
||||||
{
|
|
||||||
if (route == null)
|
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "Failed to add route, route parameter is null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_server.Routes.Add(route);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes a route from CWS
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="route"></param>
|
|
||||||
/// <summary>
|
|
||||||
/// RemoveRoute method
|
|
||||||
/// </summary>
|
|
||||||
public void RemoveRoute(HttpCwsRoute route)
|
|
||||||
{
|
|
||||||
if (route == null)
|
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "Failed to remote route, orute parameter is null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_server.Routes.Remove(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// GetRouteCollection method
|
|
||||||
/// </summary>
|
|
||||||
public HttpCwsRouteCollection GetRouteCollection()
|
|
||||||
{
|
|
||||||
return _server.Routes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts CWS instance
|
|
||||||
/// </summary>
|
|
||||||
public void Start()
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_serverLock.Enter();
|
|
||||||
|
|
||||||
if (_server == null)
|
if (_server == null)
|
||||||
{
|
{
|
||||||
Debug.Console(DebugInfo, this, "Server is null, unable to start");
|
this.LogDebug("Server is null, unable to start");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsRegistered)
|
if (IsRegistered)
|
||||||
{
|
{
|
||||||
Debug.Console(DebugInfo, this, "Server has already been started");
|
this.LogDebug("Server has already been started");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsRegistered = _server.Register();
|
IsRegistered = _server.Register();
|
||||||
|
|
||||||
Debug.Console(DebugInfo, this, "Starting server, registration {0}", IsRegistered ? "was successful" : "failed");
|
this.LogDebug("Starting server, registration {0}", IsRegistered ? "was successful" : "failed");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(DebugInfo, this, "Start Exception Message: {0}", ex.Message);
|
this.LogException(ex, "Start Exception Message: {0}", ex.Message);
|
||||||
Debug.Console(DebugVerbose, this, "Start Exception StackTrace: {0}", ex.StackTrace);
|
this.LogVerbose("Start Exception StackTrace: {0}", ex.StackTrace);
|
||||||
if (ex.InnerException != null)
|
|
||||||
Debug.Console(DebugVerbose, this, "Start Exception InnerException: {0}", ex.InnerException);
|
|
||||||
}
|
}
|
||||||
finally
|
} // end lock
|
||||||
{
|
}
|
||||||
_serverLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stop method
|
/// Stop method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Stop()
|
public void Stop()
|
||||||
|
{
|
||||||
|
lock (_serverLock)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_serverLock.Enter();
|
|
||||||
|
|
||||||
if (_server == null)
|
if (_server == null)
|
||||||
{
|
{
|
||||||
Debug.Console(DebugInfo, this, "Server is null or has already been stopped");
|
this.LogDebug("Server is null or has already been stopped");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsRegistered = _server.Unregister() == false;
|
IsRegistered = _server.Unregister() == false;
|
||||||
|
|
||||||
Debug.Console(DebugInfo, this, "Stopping server, unregistration {0}", IsRegistered ? "failed" : "was successful");
|
this.LogDebug("Stopping server, unregistration {0}", IsRegistered ? "failed" : "was successful");
|
||||||
|
|
||||||
_server.Dispose();
|
_server.Dispose();
|
||||||
_server = null;
|
_server = null;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.Console(DebugInfo, this, "Server Stop Exception Message: {0}", ex.Message);
|
this.LogException(ex, "Server Stop Exception Message: {0}", ex.Message);
|
||||||
Debug.Console(DebugVerbose, this, "Server Stop Exception StackTrace: {0}", ex.StackTrace);
|
|
||||||
if (ex.InnerException != null)
|
|
||||||
Debug.Console(DebugVerbose, this, "Server Stop Exception InnerException: {0}", ex.InnerException);
|
|
||||||
}
|
}
|
||||||
finally
|
} // end lock
|
||||||
{
|
}
|
||||||
_serverLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Received request handler
|
/// Received request handler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is here for development and testing
|
/// This is here for development and testing
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="sender"></param>
|
/// <param name="sender"></param>
|
||||||
/// <param name="args"></param>
|
/// <param name="args"></param>
|
||||||
public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args)
|
public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
|
||||||
{
|
this.LogVerbose("RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
|
||||||
var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
|
|
||||||
Debug.Console(DebugVerbose, this, "RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Console(DebugInfo, this, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message);
|
|
||||||
Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace);
|
|
||||||
if (ex.InnerException != null)
|
|
||||||
Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception InnerException: {0}", ex.InnerException);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.LogException(ex, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message);
|
||||||
|
this.LogVerbose("ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Threading;
|
||||||
using Crestron.SimplSharp;
|
using Timer = System.Timers.Timer;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
@ -18,12 +17,27 @@ namespace PepperDash.Essentials.Core.Config;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ConfigWriter
|
public class ConfigWriter
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the subfolder where the config file will be written
|
||||||
|
/// </summary>
|
||||||
public const string LocalConfigFolder = "LocalConfig";
|
public const string LocalConfigFolder = "LocalConfig";
|
||||||
|
|
||||||
public const long WriteTimeout = 30000;
|
/// <summary>
|
||||||
|
/// The amount of time in milliseconds to wait after the last config update before writing the config file. This is to prevent multiple rapid updates from causing multiple file writes.
|
||||||
|
/// Default is 30 seconds.
|
||||||
|
/// </summary>
|
||||||
|
public const long WriteTimeoutInMs = 30000;
|
||||||
|
|
||||||
public static CTimer WriteTimer;
|
private static Timer WriteTimer;
|
||||||
static CCriticalSection fileLock = new CCriticalSection();
|
static readonly object _fileLock = new();
|
||||||
|
|
||||||
|
|
||||||
|
static ConfigWriter()
|
||||||
|
{
|
||||||
|
WriteTimer = new Timer(WriteTimeoutInMs);
|
||||||
|
WriteTimer.Elapsed += (s, e) => WriteConfigFile(null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the config properties of a device
|
/// Updates the config properties of a device
|
||||||
|
|
@ -53,6 +67,9 @@ public class ConfigWriter
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the config properties of a device
|
||||||
|
/// </summary>
|
||||||
public static bool UpdateDeviceConfig(DeviceConfig config)
|
public static bool UpdateDeviceConfig(DeviceConfig config)
|
||||||
{
|
{
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
@ -73,17 +90,20 @@ public class ConfigWriter
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the config properties of a room
|
||||||
|
/// </summary>
|
||||||
public static bool UpdateRoomConfig(DeviceConfig config)
|
public static bool UpdateRoomConfig(DeviceConfig config)
|
||||||
{
|
{
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
var roomConfigIndex = ConfigReader.ConfigObject.Rooms.FindIndex(d => d.Key.Equals(config.Key));
|
var roomConfigIndex = ConfigReader.ConfigObject.Rooms.FindIndex(d => d.Key.Equals(config.Key));
|
||||||
|
|
||||||
if (roomConfigIndex >= 0)
|
if (roomConfigIndex >= 0)
|
||||||
{
|
{
|
||||||
ConfigReader.ConfigObject.Rooms[roomConfigIndex] = config;
|
ConfigReader.ConfigObject.Rooms[roomConfigIndex] = config;
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "Updated room of device: '{0}'", config.Key);
|
Debug.LogMessage(LogEventLevel.Debug, "Updated config of room: '{0}'", config.Key);
|
||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
|
|
@ -98,10 +118,9 @@ public class ConfigWriter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static void ResetTimer()
|
static void ResetTimer()
|
||||||
{
|
{
|
||||||
if (WriteTimer == null)
|
WriteTimer.Stop();
|
||||||
WriteTimer = new CTimer(WriteConfigFile, WriteTimeout);
|
WriteTimer.Interval = WriteTimeoutInMs;
|
||||||
|
WriteTimer.Start();
|
||||||
WriteTimer.Reset(WriteTimeout);
|
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "Config File write timer has been reset.");
|
Debug.LogMessage(LogEventLevel.Debug, "Config File write timer has been reset.");
|
||||||
}
|
}
|
||||||
|
|
@ -120,10 +139,10 @@ public class ConfigWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes
|
/// Writes the specified configuration data to a file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filepath"></param>
|
/// <param name="filePath">The path of the file to write to.</param>
|
||||||
/// <param name="o"></param>
|
/// <param name="configData">The configuration data to write.</param>
|
||||||
public static void WriteFile(string filePath, string configData)
|
public static void WriteFile(string filePath, string configData)
|
||||||
{
|
{
|
||||||
if (WriteTimer != null)
|
if (WriteTimer != null)
|
||||||
|
|
@ -133,9 +152,11 @@ public class ConfigWriter
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Attempting to write config file: '{0}'", filePath);
|
Debug.LogMessage(LogEventLevel.Information, "Attempting to write config file: '{0}'", filePath);
|
||||||
|
|
||||||
|
var lockAcquired = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (fileLock.TryEnter())
|
lockAcquired = Monitor.TryEnter(_fileLock);
|
||||||
|
if (lockAcquired)
|
||||||
{
|
{
|
||||||
using (StreamWriter sw = new StreamWriter(filePath))
|
using (StreamWriter sw = new StreamWriter(filePath))
|
||||||
{
|
{
|
||||||
|
|
@ -154,11 +175,8 @@ public class ConfigWriter
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (fileLock != null && !fileLock.Disposed)
|
if (lockAcquired)
|
||||||
fileLock.Leave();
|
Monitor.Exit(_fileLock);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,11 +3,13 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using PepperDash.Essentials.Core;
|
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.DeviceInfo;
|
namespace PepperDash.Essentials.Core.DeviceInfo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper methods for network devices
|
||||||
|
/// </summary>
|
||||||
public static class NetworkDeviceHelpers
|
public static class NetworkDeviceHelpers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -24,7 +26,7 @@ public static class NetworkDeviceHelpers
|
||||||
private static readonly char NewLineSplitter = CrestronEnvironment.NewLine.ToCharArray().First();
|
private static readonly char NewLineSplitter = CrestronEnvironment.NewLine.ToCharArray().First();
|
||||||
private static readonly string NewLine = CrestronEnvironment.NewLine;
|
private static readonly string NewLine = CrestronEnvironment.NewLine;
|
||||||
|
|
||||||
private static readonly CCriticalSection Lock = new CCriticalSection();
|
private static readonly object _lock = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Last resolved ARP table - it is recommended to refresh the arp before using this.
|
/// Last resolved ARP table - it is recommended to refresh the arp before using this.
|
||||||
|
|
@ -37,46 +39,45 @@ public static class NetworkDeviceHelpers
|
||||||
public static void RefreshArp()
|
public static void RefreshArp()
|
||||||
{
|
{
|
||||||
var error = false;
|
var error = false;
|
||||||
try
|
lock (_lock)
|
||||||
{
|
{
|
||||||
Lock.Enter();
|
try
|
||||||
var consoleResponse = string.Empty;
|
|
||||||
if (!CrestronConsole.SendControlSystemCommand("showarptable", ref consoleResponse)) return;
|
|
||||||
if (string.IsNullOrEmpty(consoleResponse))
|
|
||||||
{
|
{
|
||||||
|
var consoleResponse = string.Empty;
|
||||||
|
if (!CrestronConsole.SendControlSystemCommand("showarptable", ref consoleResponse)) return;
|
||||||
|
if (string.IsNullOrEmpty(consoleResponse))
|
||||||
|
{
|
||||||
|
error = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ArpTable.Clear();
|
||||||
|
|
||||||
|
Debug.LogMessage(LogEventLevel.Verbose, "ConsoleResponse of 'showarptable' : {0}{1}", NewLine, consoleResponse);
|
||||||
|
|
||||||
|
var myLines =
|
||||||
|
consoleResponse.Split(NewLineSplitter)
|
||||||
|
.ToList()
|
||||||
|
.Where(o => (o.Contains(':') && !o.Contains("Type", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
.ToList();
|
||||||
|
foreach (var line in myLines)
|
||||||
|
{
|
||||||
|
var item = line;
|
||||||
|
var seperator = item.Contains('\t') ? '\t' : ' ';
|
||||||
|
var dataPoints = item.Split(seperator);
|
||||||
|
if (dataPoints == null || dataPoints.Length < 2) continue;
|
||||||
|
var ipAddress = SanitizeIpAddress(dataPoints.First().TrimAll());
|
||||||
|
var macAddress = dataPoints.Last();
|
||||||
|
ArpTable.Add(new ArpEntry(ipAddress, macAddress));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(LogEventLevel.Information, "Exception in \"RefreshArp\" : {0}", ex.Message);
|
||||||
error = true;
|
error = true;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
ArpTable.Clear();
|
} // end lock
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "ConsoleResponse of 'showarptable' : {0}{1}", NewLine, consoleResponse);
|
OnArpTableUpdated(new ArpTableEventArgs(ArpTable, error));
|
||||||
|
|
||||||
var myLines =
|
|
||||||
consoleResponse.Split(NewLineSplitter)
|
|
||||||
.ToList()
|
|
||||||
.Where(o => (o.Contains(':') && !o.Contains("Type", StringComparison.OrdinalIgnoreCase)))
|
|
||||||
.ToList();
|
|
||||||
foreach (var line in myLines)
|
|
||||||
{
|
|
||||||
var item = line;
|
|
||||||
var seperator = item.Contains('\t') ? '\t' : ' ';
|
|
||||||
var dataPoints = item.Split(seperator);
|
|
||||||
if (dataPoints == null || dataPoints.Length < 2) continue;
|
|
||||||
var ipAddress = SanitizeIpAddress(dataPoints.First().TrimAll());
|
|
||||||
var macAddress = dataPoints.Last();
|
|
||||||
ArpTable.Add(new ArpEntry(ipAddress, macAddress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Exception in \"RefreshArp\" : {0}", ex.Message);
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Lock.Leave();
|
|
||||||
OnArpTableUpdated(new ArpTableEventArgs(ArpTable, error));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -158,7 +159,14 @@ public static class NetworkDeviceHelpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ArpEntry
|
public class ArpEntry
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IP Address of the ARP entry
|
||||||
|
/// </summary>
|
||||||
public readonly IPAddress IpAddress;
|
public readonly IPAddress IpAddress;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MAC Address of the ARP entry
|
||||||
|
/// </summary>
|
||||||
public readonly string MacAddress;
|
public readonly string MacAddress;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharpPro;
|
using Crestron.SimplSharpPro;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
|
@ -10,13 +11,27 @@ using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core;
|
namespace PepperDash.Essentials.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manages devices in the system, including activation and console commands to interact with devices
|
||||||
|
/// </summary>
|
||||||
public static class DeviceManager
|
public static class DeviceManager
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised when all devices have been activated
|
||||||
|
/// </summary>
|
||||||
public static event EventHandler<EventArgs> AllDevicesActivated;
|
public static event EventHandler<EventArgs> AllDevicesActivated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised when all devices have been registered
|
||||||
|
/// </summary>
|
||||||
public static event EventHandler<EventArgs> AllDevicesRegistered;
|
public static event EventHandler<EventArgs> AllDevicesRegistered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised when all devices have been initialized
|
||||||
|
/// </summary>
|
||||||
public static event EventHandler<EventArgs> AllDevicesInitialized;
|
public static event EventHandler<EventArgs> AllDevicesInitialized;
|
||||||
|
|
||||||
private static readonly CCriticalSection DeviceCriticalSection = new CCriticalSection();
|
private static readonly object _deviceLock = new();
|
||||||
|
|
||||||
//public static List<Device> Devices { get { return _Devices; } }
|
//public static List<Device> Devices { get { return _Devices; } }
|
||||||
//static List<Device> _Devices = new List<Device>();
|
//static List<Device> _Devices = new List<Device>();
|
||||||
|
|
@ -28,7 +43,10 @@ public static class DeviceManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static List<IKeyed> AllDevices => [.. Devices.Values];
|
public static List<IKeyed> AllDevices => [.. Devices.Values];
|
||||||
|
|
||||||
public static bool AddDeviceEnabled;
|
/// <summary>
|
||||||
|
/// Flag to indicate whether adding devices is currently allowed. This is set to false once ActivateAll is called to prevent changes to the device list after activation.
|
||||||
|
/// </summary>
|
||||||
|
public static bool AddDeviceEnabled { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the control system by enabling device management and registering console commands.
|
/// Initializes the control system by enabling device management and registering console commands.
|
||||||
|
|
@ -65,11 +83,10 @@ public static class DeviceManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void ActivateAll()
|
public static void ActivateAll()
|
||||||
{
|
{
|
||||||
try
|
OnAllDevicesRegistered();
|
||||||
{
|
|
||||||
OnAllDevicesRegistered();
|
|
||||||
|
|
||||||
DeviceCriticalSection.Enter();
|
lock (_deviceLock)
|
||||||
|
{
|
||||||
AddDeviceEnabled = false;
|
AddDeviceEnabled = false;
|
||||||
// PreActivate all devices
|
// PreActivate all devices
|
||||||
Debug.LogMessage(LogEventLevel.Information, "****PreActivation starting...****");
|
Debug.LogMessage(LogEventLevel.Information, "****PreActivation starting...****");
|
||||||
|
|
@ -125,11 +142,7 @@ public static class DeviceManager
|
||||||
Debug.LogMessage(LogEventLevel.Information, "****PostActivation complete****");
|
Debug.LogMessage(LogEventLevel.Information, "****PostActivation complete****");
|
||||||
|
|
||||||
OnAllDevicesActivated();
|
OnAllDevicesActivated();
|
||||||
}
|
} // end lock
|
||||||
finally
|
|
||||||
{
|
|
||||||
DeviceCriticalSection.Leave();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DeviceManager_Initialized(object sender, EventArgs e)
|
private static void DeviceManager_Initialized(object sender, EventArgs e)
|
||||||
|
|
@ -176,18 +189,13 @@ public static class DeviceManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void DeactivateAll()
|
public static void DeactivateAll()
|
||||||
{
|
{
|
||||||
try
|
lock (_deviceLock)
|
||||||
{
|
{
|
||||||
DeviceCriticalSection.Enter();
|
|
||||||
foreach (var d in Devices.Values.OfType<Device>())
|
foreach (var d in Devices.Values.OfType<Device>())
|
||||||
{
|
{
|
||||||
d.Deactivate();
|
d.Deactivate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
DeviceCriticalSection.Leave();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//static void ListMethods(string devKey)
|
//static void ListMethods(string devKey)
|
||||||
|
|
@ -266,11 +274,16 @@ public static class DeviceManager
|
||||||
// Debug.LogMessage(LogEventLevel.Information, "Not yet implemented. Stay tuned");
|
// Debug.LogMessage(LogEventLevel.Information, "Not yet implemented. Stay tuned");
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a device to the manager
|
||||||
|
/// </summary>
|
||||||
public static void AddDevice(IKeyed newDev)
|
public static void AddDevice(IKeyed newDev)
|
||||||
{
|
{
|
||||||
|
var lockAcquired = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!DeviceCriticalSection.TryEnter())
|
lockAcquired = Monitor.TryEnter(_deviceLock);
|
||||||
|
if (!lockAcquired)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again");
|
Debug.LogMessage(LogEventLevel.Information, "Currently unable to add devices to Device Manager. Please try again");
|
||||||
return;
|
return;
|
||||||
|
|
@ -300,15 +313,22 @@ public static class DeviceManager
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeviceCriticalSection.Leave();
|
if (lockAcquired)
|
||||||
|
Monitor.Exit(_deviceLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a list of devices to the manager
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="devicesToAdd"></param>
|
||||||
public static void AddDevice(IEnumerable<IKeyed> devicesToAdd)
|
public static void AddDevice(IEnumerable<IKeyed> devicesToAdd)
|
||||||
{
|
{
|
||||||
|
var lockAcquired = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!DeviceCriticalSection.TryEnter())
|
lockAcquired = Monitor.TryEnter(_deviceLock);
|
||||||
|
if (!lockAcquired)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information,
|
Debug.LogMessage(LogEventLevel.Information,
|
||||||
"Currently unable to add devices to Device Manager. Please try again");
|
"Currently unable to add devices to Device Manager. Please try again");
|
||||||
|
|
@ -336,15 +356,19 @@ public static class DeviceManager
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeviceCriticalSection.Leave();
|
if (lockAcquired)
|
||||||
|
Monitor.Exit(_deviceLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a device from the manager
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newDev">The device to remove</param>
|
||||||
public static void RemoveDevice(IKeyed newDev)
|
public static void RemoveDevice(IKeyed newDev)
|
||||||
{
|
{
|
||||||
try
|
lock (_deviceLock)
|
||||||
{
|
{
|
||||||
DeviceCriticalSection.Enter();
|
|
||||||
if (newDev == null)
|
if (newDev == null)
|
||||||
return;
|
return;
|
||||||
if (Devices.ContainsKey(newDev.Key))
|
if (Devices.ContainsKey(newDev.Key))
|
||||||
|
|
@ -354,18 +378,22 @@ public static class DeviceManager
|
||||||
else
|
else
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key);
|
Debug.LogMessage(LogEventLevel.Information, "Device manager: Device '{0}' does not exist in manager. Cannot remove", newDev.Key);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
DeviceCriticalSection.Leave();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of all device keys currently in the manager
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A list of device keys</returns>
|
||||||
|
/// <remarks>This method provides a way to retrieve a list of all device keys currently registered in the Device Manager. It returns an enumerable collection of strings representing the keys of the devices, allowing for easy access and manipulation of the device list as needed.</remarks>
|
||||||
public static IEnumerable<string> GetDeviceKeys()
|
public static IEnumerable<string> GetDeviceKeys()
|
||||||
{
|
{
|
||||||
//return _Devices.Select(d => d.Key).ToList();
|
//return _Devices.Select(d => d.Key).ToList();
|
||||||
return Devices.Keys;
|
return Devices.Keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of all devices currently in the manager
|
||||||
|
/// </summary> <returns>A list of devices</returns>
|
||||||
public static IEnumerable<IKeyed> GetDevices()
|
public static IEnumerable<IKeyed> GetDevices()
|
||||||
{
|
{
|
||||||
//return _Devices.Select(d => d.Key).ToList();
|
//return _Devices.Select(d => d.Key).ToList();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
|
||||||
|
|
@ -77,11 +78,11 @@ namespace PepperDash.Essentials.Core
|
||||||
public abstract void FireUpdate();
|
public abstract void FireUpdate();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fires the update asynchronously within a CrestronInvoke
|
/// Fires the update asynchronously within a Task
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void InvokeFireUpdate()
|
public void InvokeFireUpdate()
|
||||||
{
|
{
|
||||||
CrestronInvoke.BeginInvoke(o => FireUpdate());
|
Task.Run(() => FireUpdate());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using Crestron.SimplSharpPro.CrestronThread;
|
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
|
|
@ -16,7 +15,7 @@ namespace PepperDash.Essentials.Core
|
||||||
public static class FileIO
|
public static class FileIO
|
||||||
{
|
{
|
||||||
|
|
||||||
static CCriticalSection fileLock = new CCriticalSection();
|
static readonly object _fileLock = new();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for GotFileEventHandler
|
/// Delegate for GotFileEventHandler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -103,9 +102,11 @@ namespace PepperDash.Essentials.Core
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ReadDataFromFile(FileInfo file)
|
public static string ReadDataFromFile(FileInfo file)
|
||||||
{
|
{
|
||||||
|
var lockAcquired = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (fileLock.TryEnter())
|
lockAcquired = Monitor.TryEnter(_fileLock);
|
||||||
|
if (lockAcquired)
|
||||||
{
|
{
|
||||||
DirectoryInfo dirInfo = new DirectoryInfo(file.DirectoryName);
|
DirectoryInfo dirInfo = new DirectoryInfo(file.DirectoryName);
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "FileIO Getting Data {0}", file.FullName);
|
Debug.LogMessage(LogEventLevel.Verbose, "FileIO Getting Data {0}", file.FullName);
|
||||||
|
|
@ -128,7 +129,6 @@ namespace PepperDash.Essentials.Core
|
||||||
Debug.LogMessage(LogEventLevel.Information, "FileIO Unable to enter FileLock");
|
Debug.LogMessage(LogEventLevel.Information, "FileIO Unable to enter FileLock");
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -137,9 +137,8 @@ namespace PepperDash.Essentials.Core
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (fileLock != null && !fileLock.Disposed)
|
if (lockAcquired)
|
||||||
fileLock.Leave();
|
Monitor.Exit(_fileLock);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,7 +165,7 @@ namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CrestronInvoke.BeginInvoke(o => _ReadDataFromFileASync(file));
|
Task.Run(() => _ReadDataFromFileASync(file));
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -177,9 +176,11 @@ namespace PepperDash.Essentials.Core
|
||||||
private static void _ReadDataFromFileASync(FileInfo file)
|
private static void _ReadDataFromFileASync(FileInfo file)
|
||||||
{
|
{
|
||||||
string data;
|
string data;
|
||||||
|
var lockAcquired = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (fileLock.TryEnter())
|
lockAcquired = Monitor.TryEnter(_fileLock);
|
||||||
|
if (lockAcquired)
|
||||||
{
|
{
|
||||||
DirectoryInfo dirInfo = new DirectoryInfo(file.Name);
|
DirectoryInfo dirInfo = new DirectoryInfo(file.Name);
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "FileIO Getting Data {0}", file.FullName);
|
Debug.LogMessage(LogEventLevel.Verbose, "FileIO Getting Data {0}", file.FullName);
|
||||||
|
|
@ -212,13 +213,9 @@ namespace PepperDash.Essentials.Core
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (fileLock != null && !fileLock.Disposed)
|
if (lockAcquired)
|
||||||
fileLock.Leave();
|
Monitor.Exit(_fileLock);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -228,35 +225,35 @@ namespace PepperDash.Essentials.Core
|
||||||
/// <param name="filePath"></param>
|
/// <param name="filePath"></param>
|
||||||
public static void WriteDataToFile(string data, string filePath)
|
public static void WriteDataToFile(string data, string filePath)
|
||||||
{
|
{
|
||||||
Thread _WriteFileThread;
|
var _WriteFileThread = new System.Threading.Thread(() => _WriteFileMethod(data, Global.FilePathPrefix + "/" + filePath))
|
||||||
_WriteFileThread = new Thread((O) => _WriteFileMethod(data, Global.FilePathPrefix + "/" + filePath), null, Thread.eThreadStartOptions.CreateSuspended);
|
{
|
||||||
_WriteFileThread.Priority = Thread.eThreadPriority.LowestPriority;
|
IsBackground = true,
|
||||||
|
Priority = ThreadPriority.Lowest
|
||||||
|
};
|
||||||
_WriteFileThread.Start();
|
_WriteFileThread.Start();
|
||||||
Debug.LogMessage(LogEventLevel.Information, "New WriteFile Thread");
|
Debug.LogMessage(LogEventLevel.Information, "New WriteFile Thread");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static object _WriteFileMethod(string data, string filePath)
|
static void _WriteFileMethod(string data, string filePath)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Attempting to write file: '{0}'", filePath);
|
Debug.LogMessage(LogEventLevel.Information, "Attempting to write file: '{0}'", filePath);
|
||||||
|
|
||||||
|
var lockAcquired = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (fileLock.TryEnter())
|
lockAcquired = Monitor.TryEnter(_fileLock);
|
||||||
|
if (lockAcquired)
|
||||||
{
|
{
|
||||||
|
|
||||||
using (StreamWriter sw = new StreamWriter(filePath))
|
using (StreamWriter sw = new StreamWriter(filePath))
|
||||||
{
|
{
|
||||||
sw.Write(data);
|
sw.Write(data);
|
||||||
sw.Flush();
|
sw.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, "FileIO Unable to enter FileLock");
|
Debug.LogMessage(LogEventLevel.Information, "FileIO Unable to enter FileLock");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -264,12 +261,9 @@ namespace PepperDash.Essentials.Core
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (fileLock != null && !fileLock.Disposed)
|
if (lockAcquired)
|
||||||
fileLock.Leave();
|
Monitor.Exit(_fileLock);
|
||||||
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Timers;
|
using Timer = System.Timers.Timer;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using Crestron.SimplSharp.CrestronXml;
|
using Crestron.SimplSharp.CrestronXml;
|
||||||
|
|
@ -78,6 +78,8 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||||
|
|
||||||
private bool _helpRequestSent;
|
private bool _helpRequestSent;
|
||||||
|
|
||||||
|
private readonly object _guidFileLock = new();
|
||||||
|
|
||||||
private eFusionHelpResponse _helpRequestStatus;
|
private eFusionHelpResponse _helpRequestStatus;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -390,44 +392,31 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileLock = new CCriticalSection();
|
lock (_guidFileLock)
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (fileLock.Disposed)
|
try
|
||||||
{
|
{
|
||||||
return;
|
Debug.LogMessage(LogEventLevel.Debug, this, "Writing GUIDs to file");
|
||||||
|
|
||||||
|
_guids = FusionOccSensor == null
|
||||||
|
? new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets)
|
||||||
|
: new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets, FusionOccSensor);
|
||||||
|
|
||||||
|
var json = JsonConvert.SerializeObject(_guids, Newtonsoft.Json.Formatting.Indented);
|
||||||
|
|
||||||
|
using (var sw = new StreamWriter(filePath))
|
||||||
|
{
|
||||||
|
sw.Write(json);
|
||||||
|
sw.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogMessage(LogEventLevel.Debug, this, "Guids successfully written to file '{0}'", filePath);
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
fileLock.Enter();
|
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "Writing GUIDs to file");
|
|
||||||
|
|
||||||
_guids = FusionOccSensor == null
|
|
||||||
? new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets)
|
|
||||||
: new FusionRoomGuids(Room.Name, _config.IpIdInt, RoomGuid, FusionStaticAssets, FusionOccSensor);
|
|
||||||
|
|
||||||
var json = JsonConvert.SerializeObject(_guids, Newtonsoft.Json.Formatting.Indented);
|
|
||||||
|
|
||||||
using (var sw = new StreamWriter(filePath))
|
|
||||||
{
|
{
|
||||||
sw.Write(json);
|
Debug.LogMessage(LogEventLevel.Information, this, "Error writing guid file: {0}", e);
|
||||||
sw.Flush();
|
|
||||||
}
|
}
|
||||||
|
} // end lock
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "Guids successfully written to file '{0}'", filePath);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Error writing guid file: {0}", e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (!fileLock.Disposed)
|
|
||||||
{
|
|
||||||
fileLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -442,50 +431,37 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileLock = new CCriticalSection();
|
lock (_guidFileLock)
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (fileLock.Disposed)
|
try
|
||||||
{
|
{
|
||||||
return;
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
var json = File.ReadToEnd(filePath, Encoding.ASCII);
|
||||||
|
|
||||||
|
_guids = JsonConvert.DeserializeObject<FusionRoomGuids>(json);
|
||||||
|
|
||||||
|
// _config.IpId = _guids.IpId;
|
||||||
|
|
||||||
|
FusionStaticAssets = _guids.StaticAssets;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogMessage(LogEventLevel.Information, this, "Fusion Guids successfully read from file: {0}",
|
||||||
|
filePath);
|
||||||
|
|
||||||
|
Debug.LogMessage(LogEventLevel.Debug, this, "\r\n********************\r\n\tRoom Name: {0}\r\n\tIPID: {1:X}\r\n\tRoomGuid: {2}\r\n*******************", Room.Name, _config.IpIdInt, RoomGuid);
|
||||||
|
|
||||||
|
foreach (var item in FusionStaticAssets)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(LogEventLevel.Debug, this, "\nAsset Name: {0}\nAsset No: {1}\n Guid: {2}", item.Value.Name,
|
||||||
|
item.Value.SlotNumber, item.Value.InstanceId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
fileLock.Enter();
|
|
||||||
|
|
||||||
if (File.Exists(filePath))
|
|
||||||
{
|
{
|
||||||
var json = File.ReadToEnd(filePath, Encoding.ASCII);
|
Debug.LogMessage(LogEventLevel.Information, this, "Error reading guid file: {0}", e);
|
||||||
|
|
||||||
_guids = JsonConvert.DeserializeObject<FusionRoomGuids>(json);
|
|
||||||
|
|
||||||
// _config.IpId = _guids.IpId;
|
|
||||||
|
|
||||||
FusionStaticAssets = _guids.StaticAssets;
|
|
||||||
}
|
}
|
||||||
|
} // end lock
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Fusion Guids successfully read from file: {0}",
|
|
||||||
filePath);
|
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "\r\n********************\r\n\tRoom Name: {0}\r\n\tIPID: {1:X}\r\n\tRoomGuid: {2}\r\n*******************", Room.Name, _config.IpIdInt, RoomGuid);
|
|
||||||
|
|
||||||
foreach (var item in FusionStaticAssets)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "\nAsset Name: {0}\nAsset No: {1}\n Guid: {2}", item.Value.Name,
|
|
||||||
item.Value.SlotNumber, item.Value.InstanceId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Error reading guid file: {0}", e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (!fileLock.Disposed)
|
|
||||||
{
|
|
||||||
fileLock.Leave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -1950,12 +1926,13 @@ namespace PepperDash.Essentials.Core.Fusion
|
||||||
HelpRequestStatusFeedback.FireUpdate();
|
HelpRequestStatusFeedback.FireUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTimedEvent(object source, ElapsedEventArgs e)
|
private void OnTimedEvent(object sender, System.Timers.ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
this.LogInformation("Help request timeout reached for room '{0}'. Cancelling help request.", Room.Name);
|
this.LogInformation("Help request timeout reached for room '{0}'. Cancelling help request.", Room.Name);
|
||||||
CancelHelpRequest();
|
CancelHelpRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void CancelHelpRequest()
|
public void CancelHelpRequest()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
using PepperDash.Essentials.Core.Bridges;
|
using PepperDash.Essentials.Core.Bridges;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Monitoring;
|
namespace PepperDash.Essentials.Core.Monitoring;
|
||||||
|
|
||||||
|
|
@ -272,7 +273,7 @@ public class SystemMonitorController : EssentialsBridgeableDevice
|
||||||
private void RefreshSystemMonitorData()
|
private void RefreshSystemMonitorData()
|
||||||
{
|
{
|
||||||
// this takes a while, launch a new thread
|
// this takes a while, launch a new thread
|
||||||
CrestronInvoke.BeginInvoke(UpdateFeedback);
|
Task.Run(() => UpdateFeedback(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFeedback(object o)
|
private void UpdateFeedback(object o)
|
||||||
|
|
@ -744,7 +745,7 @@ public class ProgramStatusFeedbacks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void GetProgramInfo()
|
public void GetProgramInfo()
|
||||||
{
|
{
|
||||||
CrestronInvoke.BeginInvoke(GetProgramInfo);
|
Task.Run(() => GetProgramInfo(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GetProgramInfo(object o)
|
private void GetProgramInfo(object o)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
|
||||||
//using SSMono.IO;
|
|
||||||
using PepperDash.Core.WebApi.Presets;
|
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Presets;
|
namespace PepperDash.Essentials.Core.Presets;
|
||||||
|
|
@ -19,11 +18,19 @@ namespace PepperDash.Essentials.Core.Presets;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DevicePresetsModel : Device
|
public class DevicePresetsModel : Device
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate for PresetRecalled event, which is fired when a preset is recalled. Provides the device and channel that was recalled.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="device"></param>
|
||||||
|
/// <param name="channel"></param>
|
||||||
public delegate void PresetRecalledCallback(ISetTopBoxNumericKeypad device, string channel);
|
public delegate void PresetRecalledCallback(ISetTopBoxNumericKeypad device, string channel);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate for PresetsSaved event, which is fired when presets are saved. Provides the list of presets that were saved.
|
||||||
|
/// </summary> <param name="presets"></param>
|
||||||
public delegate void PresetsSavedCallback(List<PresetChannel> presets);
|
public delegate void PresetsSavedCallback(List<PresetChannel> presets);
|
||||||
|
|
||||||
private readonly CCriticalSection _fileOps = new CCriticalSection();
|
private readonly object _fileOps = new();
|
||||||
private readonly bool _initSuccess;
|
private readonly bool _initSuccess;
|
||||||
|
|
||||||
private readonly ISetTopBoxNumericKeypad _setTopBox;
|
private readonly ISetTopBoxNumericKeypad _setTopBox;
|
||||||
|
|
@ -37,6 +44,12 @@ public class DevicePresetsModel : Device
|
||||||
private Action<bool> _enterFunction;
|
private Action<bool> _enterFunction;
|
||||||
private string _filePath;
|
private string _filePath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for DevicePresetsModel when a set top box device is included. If the set top box does not implement the required INumericKeypad interface, the model will still be created but dialing functionality will be disabled and a message will be logged.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="setTopBox"></param>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
public DevicePresetsModel(string key, ISetTopBoxNumericKeypad setTopBox, string fileName)
|
public DevicePresetsModel(string key, ISetTopBoxNumericKeypad setTopBox, string fileName)
|
||||||
: this(key, fileName)
|
: this(key, fileName)
|
||||||
{
|
{
|
||||||
|
|
@ -71,6 +84,11 @@ public class DevicePresetsModel : Device
|
||||||
_enterFunction = setTopBox.KeypadEnter;
|
_enterFunction = setTopBox.KeypadEnter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for DevicePresetsModel when only a file name is provided. Dialing functionality will be disabled.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
public DevicePresetsModel(string key, string fileName) : base(key)
|
public DevicePresetsModel(string key, string fileName) : base(key)
|
||||||
{
|
{
|
||||||
PulseTime = 150;
|
PulseTime = 150;
|
||||||
|
|
@ -88,27 +106,73 @@ public class DevicePresetsModel : Device
|
||||||
_initSuccess = true;
|
_initSuccess = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when a preset is recalled, providing the device and channel that was recalled
|
||||||
|
/// </summary>
|
||||||
public event PresetRecalledCallback PresetRecalled;
|
public event PresetRecalledCallback PresetRecalled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when presets are saved, providing the list of presets that were saved
|
||||||
|
/// </summary>
|
||||||
public event PresetsSavedCallback PresetsSaved;
|
public event PresetsSavedCallback PresetsSaved;
|
||||||
|
|
||||||
public int PulseTime { get; set; }
|
/// <summary>
|
||||||
public int DigitSpacingMs { get; set; }
|
/// Time in milliseconds to pulse the digit for when dialing a channel
|
||||||
|
/// </summary>
|
||||||
|
public int PulseTime { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Time in milliseconds to wait between pulsing digits when dialing a channel
|
||||||
|
/// </summary>
|
||||||
|
public int DigitSpacingMs { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the presets have finished loading from the file or not
|
||||||
|
/// </summary>
|
||||||
public bool PresetsAreLoaded { get; private set; }
|
public bool PresetsAreLoaded { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of presets to display
|
||||||
|
/// </summary>
|
||||||
public List<PresetChannel> PresetsList { get; private set; }
|
public List<PresetChannel> PresetsList { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of presets in the list
|
||||||
|
/// </summary>
|
||||||
public int Count
|
public int Count
|
||||||
{
|
{
|
||||||
get { return PresetsList != null ? PresetsList.Count : 0; }
|
get { return PresetsList != null ? PresetsList.Count : 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UseLocalImageStorage { get; set; }
|
/// <summary>
|
||||||
public string ImagesLocalHostPrefix { get; set; }
|
/// Indicates whether to use local image storage for preset images, which allows for more and larger images than the SIMPL+ zip file method
|
||||||
public string ImagesPathPrefix { get; set; }
|
/// </summary>
|
||||||
public string ListPathPrefix { get; set; }
|
public bool UseLocalImageStorage { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The prefix for the local host URL for preset images
|
||||||
|
/// </summary>
|
||||||
|
public string ImagesLocalHostPrefix { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The path prefix for preset images
|
||||||
|
/// </summary>
|
||||||
|
public string ImagesPathPrefix { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The path prefix for preset lists
|
||||||
|
/// </summary>
|
||||||
|
public string ListPathPrefix { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event fired when presets are loaded
|
||||||
|
/// </summary>
|
||||||
public event EventHandler PresetsLoaded;
|
public event EventHandler PresetsLoaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the file name for the presets list and loads the presets from that file. The file should be a JSON file in the format of the PresetsList class. If the file cannot be read, an empty list will be created and a message will be logged. This method is thread safe.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path to the presets file.</param>
|
||||||
public void SetFileName(string path)
|
public void SetFileName(string path)
|
||||||
{
|
{
|
||||||
_filePath = ListPathPrefix + path;
|
_filePath = ListPathPrefix + path;
|
||||||
|
|
@ -117,12 +181,13 @@ public class DevicePresetsModel : Device
|
||||||
LoadChannels();
|
LoadChannels();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the presets from the file specified by _filePath.
|
||||||
|
/// </summary>
|
||||||
public void LoadChannels()
|
public void LoadChannels()
|
||||||
{
|
{
|
||||||
try
|
lock (_fileOps)
|
||||||
{
|
{
|
||||||
_fileOps.Enter();
|
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Loading presets from {0}", _filePath);
|
Debug.LogMessage(LogEventLevel.Verbose, this, "Loading presets from {0}", _filePath);
|
||||||
PresetsAreLoaded = false;
|
PresetsAreLoaded = false;
|
||||||
try
|
try
|
||||||
|
|
@ -149,12 +214,12 @@ public class DevicePresetsModel : Device
|
||||||
handler(this, EventArgs.Empty);
|
handler(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
_fileOps.Leave();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dials a preset by its number in the list (starting at 1). If the preset number is out of range, nothing will happen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="presetNum">The number of the preset to dial, starting at 1</param>
|
||||||
public void Dial(int presetNum)
|
public void Dial(int presetNum)
|
||||||
{
|
{
|
||||||
if (presetNum <= PresetsList.Count)
|
if (presetNum <= PresetsList.Count)
|
||||||
|
|
@ -163,6 +228,10 @@ public class DevicePresetsModel : Device
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dials a preset by its channel number. If the channel number contains characters that are not 0-9 or '-', those characters will be ignored.
|
||||||
|
/// If the model was not initialized with a valid set top box device, dialing will be disabled and a message will be logged.
|
||||||
|
/// </summary> <param name="chanNum">The channel number to dial</param>
|
||||||
public void Dial(string chanNum)
|
public void Dial(string chanNum)
|
||||||
{
|
{
|
||||||
if (_dialIsRunning || !_initSuccess)
|
if (_dialIsRunning || !_initSuccess)
|
||||||
|
|
@ -176,7 +245,7 @@ public class DevicePresetsModel : Device
|
||||||
}
|
}
|
||||||
|
|
||||||
_dialIsRunning = true;
|
_dialIsRunning = true;
|
||||||
CrestronInvoke.BeginInvoke(o =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
foreach (var c in chanNum.ToCharArray())
|
foreach (var c in chanNum.ToCharArray())
|
||||||
{
|
{
|
||||||
|
|
@ -199,6 +268,11 @@ public class DevicePresetsModel : Device
|
||||||
OnPresetRecalled(_setTopBox, chanNum);
|
OnPresetRecalled(_setTopBox, chanNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dials a preset by its number in the list (starting at 1) using the provided set top box device. If the preset number is out of range, nothing will happen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="presetNum"></param>
|
||||||
|
/// <param name="setTopBox"></param>
|
||||||
public void Dial(int presetNum, ISetTopBoxNumericKeypad setTopBox)
|
public void Dial(int presetNum, ISetTopBoxNumericKeypad setTopBox)
|
||||||
{
|
{
|
||||||
if (presetNum <= PresetsList.Count)
|
if (presetNum <= PresetsList.Count)
|
||||||
|
|
@ -207,6 +281,13 @@ public class DevicePresetsModel : Device
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dials a preset by its channel number using the provided set top box device. If the channel number contains characters that are not 0-9 or '-', those characters will be ignored.
|
||||||
|
/// If the provided set top box device does not implement the required INumericKeypad interface, dialing will be disabled and a message will be logged.
|
||||||
|
/// If the model was not initialized with a valid set top box device, dialing will be disabled and a message will be logged.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chanNum"></param>
|
||||||
|
/// <param name="setTopBox"></param>
|
||||||
public void Dial(string chanNum, ISetTopBoxNumericKeypad setTopBox)
|
public void Dial(string chanNum, ISetTopBoxNumericKeypad setTopBox)
|
||||||
{
|
{
|
||||||
_dialFunctions = new Dictionary<char, Action<bool>>(10)
|
_dialFunctions = new Dictionary<char, Action<bool>>(10)
|
||||||
|
|
@ -243,6 +324,11 @@ public class DevicePresetsModel : Device
|
||||||
handler(setTopBox, channel);
|
handler(setTopBox, channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the preset at the given index with the provided preset information, then saves the updated presets list to the file. If the index is out of range, nothing will happen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the preset to update, starting at 0</param>
|
||||||
|
/// <param name="preset">The preset information to update</param>
|
||||||
public void UpdatePreset(int index, PresetChannel preset)
|
public void UpdatePreset(int index, PresetChannel preset)
|
||||||
{
|
{
|
||||||
if (index >= PresetsList.Count)
|
if (index >= PresetsList.Count)
|
||||||
|
|
@ -257,6 +343,10 @@ public class DevicePresetsModel : Device
|
||||||
OnPresetsSaved();
|
OnPresetsSaved();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the entire presets list with the provided list, then saves the updated presets list to the file. If the provided list is null, nothing will happen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="presets"></param>
|
||||||
public void UpdatePresets(List<PresetChannel> presets)
|
public void UpdatePresets(List<PresetChannel> presets)
|
||||||
{
|
{
|
||||||
PresetsList = presets;
|
PresetsList = presets;
|
||||||
|
|
@ -268,10 +358,9 @@ public class DevicePresetsModel : Device
|
||||||
|
|
||||||
private void SavePresets()
|
private void SavePresets()
|
||||||
{
|
{
|
||||||
try
|
lock (_fileOps)
|
||||||
{
|
{
|
||||||
_fileOps.Enter();
|
var pl = new PresetsList { Channels = PresetsList, Name = Name };
|
||||||
var pl = new PresetsList {Channels = PresetsList, Name = Name};
|
|
||||||
var json = JsonConvert.SerializeObject(pl, Formatting.Indented);
|
var json = JsonConvert.SerializeObject(pl, Formatting.Indented);
|
||||||
|
|
||||||
using (var file = File.Open(_filePath, FileMode.Truncate))
|
using (var file = File.Open(_filePath, FileMode.Truncate))
|
||||||
|
|
@ -279,11 +368,6 @@ public class DevicePresetsModel : Device
|
||||||
file.Write(json, Encoding.UTF8);
|
file.Write(json, Encoding.UTF8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
_fileOps.Leave();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPresetsSaved()
|
private void OnPresetsSaved()
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,35 @@ using System.Threading;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using Thread = Crestron.SimplSharpPro.CrestronThread.Thread;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Queues;
|
namespace PepperDash.Essentials.Core.Queues;
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: The capacity argument in the constructors should be removed. Now that this class uses System.Threading rather than the Crestron library, there is no longer a thread capacity limit.
|
||||||
|
// If a capacity limit is needed, it should be implemented by the caller by checking the QueueCount property before enqueuing items and deciding how to handle the situation when the queue is too full (e.g. drop messages, log warnings, etc.)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Threadsafe processing of queued items with pacing if required
|
/// Threadsafe processing of queued items with pacing if required
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericQueue : IQueue<IQueueMessage>
|
public class GenericQueue : IQueue<IQueueMessage>
|
||||||
{
|
{
|
||||||
private readonly string _key;
|
private readonly string _key;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the number of items currently in the queue. This is not threadsafe, so it should only be used for informational purposes and not for processing logic.
|
||||||
|
/// </summary>
|
||||||
protected readonly ConcurrentQueue<IQueueMessage> _queue;
|
protected readonly ConcurrentQueue<IQueueMessage> _queue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The thread that processes the queue items
|
||||||
|
/// </summary>
|
||||||
protected readonly Thread _worker;
|
protected readonly Thread _worker;
|
||||||
protected readonly CEvent _waitHandle = new CEvent();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
private bool _delayEnabled;
|
private bool _delayEnabled;
|
||||||
private int _delayTime;
|
private int _delayTime;
|
||||||
|
|
||||||
private const Thread.eThreadPriority _defaultPriority = Thread.eThreadPriority.MediumPriority;
|
private const ThreadPriority _defaultPriority = ThreadPriority.Normal;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the instance has been disposed.
|
/// If the instance has been disposed.
|
||||||
|
|
@ -96,7 +107,7 @@ public class GenericQueue : IQueue<IQueueMessage>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="pacing"></param>
|
/// <param name="pacing"></param>
|
||||||
/// <param name="priority"></param>
|
/// <param name="priority"></param>
|
||||||
public GenericQueue(string key, int pacing, Thread.eThreadPriority priority)
|
public GenericQueue(string key, int pacing, ThreadPriority priority)
|
||||||
: this(key, priority, 0, pacing)
|
: this(key, priority, 0, pacing)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +118,7 @@ public class GenericQueue : IQueue<IQueueMessage>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="priority"></param>
|
/// <param name="priority"></param>
|
||||||
/// <param name="capacity"></param>
|
/// <param name="capacity"></param>
|
||||||
public GenericQueue(string key, Thread.eThreadPriority priority, int capacity)
|
public GenericQueue(string key, ThreadPriority priority, int capacity)
|
||||||
: this(key, priority, capacity, 0)
|
: this(key, priority, capacity, 0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -119,7 +130,7 @@ public class GenericQueue : IQueue<IQueueMessage>
|
||||||
/// <param name="pacing"></param>
|
/// <param name="pacing"></param>
|
||||||
/// <param name="priority"></param>
|
/// <param name="priority"></param>
|
||||||
/// <param name="capacity"></param>
|
/// <param name="capacity"></param>
|
||||||
public GenericQueue(string key, int pacing, Thread.eThreadPriority priority, int capacity)
|
public GenericQueue(string key, int pacing, ThreadPriority priority, int capacity)
|
||||||
: this(key, priority, capacity, pacing)
|
: this(key, priority, capacity, pacing)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -131,21 +142,18 @@ public class GenericQueue : IQueue<IQueueMessage>
|
||||||
/// <param name="priority"></param>
|
/// <param name="priority"></param>
|
||||||
/// <param name="capacity"></param>
|
/// <param name="capacity"></param>
|
||||||
/// <param name="pacing"></param>
|
/// <param name="pacing"></param>
|
||||||
protected GenericQueue(string key, Thread.eThreadPriority priority, int capacity, int pacing)
|
protected GenericQueue(string key, ThreadPriority priority, int capacity, int pacing)
|
||||||
{
|
{
|
||||||
_key = key;
|
_key = key;
|
||||||
int cap = 25; // sets default
|
|
||||||
if (capacity > 0)
|
|
||||||
{
|
|
||||||
cap = capacity; // overrides default
|
|
||||||
}
|
|
||||||
|
|
||||||
_queue = new ConcurrentQueue<IQueueMessage>();
|
_queue = new ConcurrentQueue<IQueueMessage>();
|
||||||
_worker = new Thread(ProcessQueue, null, Thread.eThreadStartOptions.Running)
|
_worker = new Thread(ProcessQueue)
|
||||||
{
|
{
|
||||||
Priority = priority,
|
Priority = priority,
|
||||||
Name = _key
|
Name = _key,
|
||||||
|
IsBackground = true
|
||||||
};
|
};
|
||||||
|
_worker.Start();
|
||||||
|
|
||||||
SetDelayValues(pacing);
|
SetDelayValues(pacing);
|
||||||
}
|
}
|
||||||
|
|
@ -167,9 +175,8 @@ public class GenericQueue : IQueue<IQueueMessage>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Thread callback
|
/// Thread callback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obj">The action used to process dequeued items</param>
|
|
||||||
/// <returns>Null when the thread is exited</returns>
|
/// <returns>Null when the thread is exited</returns>
|
||||||
private object ProcessQueue(object obj)
|
private void ProcessQueue()
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
|
@ -186,7 +193,7 @@ public class GenericQueue : IQueue<IQueueMessage>
|
||||||
if (_delayEnabled)
|
if (_delayEnabled)
|
||||||
Thread.Sleep(_delayTime);
|
Thread.Sleep(_delayTime);
|
||||||
}
|
}
|
||||||
catch (ThreadAbortException)
|
catch (ThreadInterruptedException)
|
||||||
{
|
{
|
||||||
//swallowing this exception, as it should only happen on shut down
|
//swallowing this exception, as it should only happen on shut down
|
||||||
}
|
}
|
||||||
|
|
@ -202,12 +209,21 @@ public class GenericQueue : IQueue<IQueueMessage>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else _waitHandle.Wait();
|
else
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_queue.IsEmpty)
|
||||||
|
Monitor.Wait(_lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enqueues an item to be processed by the queue thread. If the queue has been disposed, the item will not be enqueued and a message will be logged.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
public void Enqueue(IQueueMessage item)
|
public void Enqueue(IQueueMessage item)
|
||||||
{
|
{
|
||||||
if (Disposed)
|
if (Disposed)
|
||||||
|
|
@ -217,7 +233,8 @@ public class GenericQueue : IQueue<IQueueMessage>
|
||||||
}
|
}
|
||||||
|
|
||||||
_queue.Enqueue(item);
|
_queue.Enqueue(item);
|
||||||
_waitHandle.Set();
|
lock (_lock)
|
||||||
|
Monitor.Pulse(_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -242,18 +259,17 @@ public class GenericQueue : IQueue<IQueueMessage>
|
||||||
|
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
using (_waitHandle)
|
Debug.LogMessage(LogEventLevel.Verbose, this, "Disposing...");
|
||||||
{
|
_queue.Enqueue(null);
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, this, "Disposing...");
|
lock (_lock)
|
||||||
_queue.Enqueue(null);
|
Monitor.Pulse(_lock);
|
||||||
_waitHandle.Set();
|
_worker.Join();
|
||||||
_worker.Join();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Disposed = true;
|
Disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finalizer in case Dispose is not called. This will clean up the thread, but any items still in the queue will not be processed and could potentially be lost.
|
||||||
~GenericQueue()
|
~GenericQueue()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Threading.Tasks;
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using PepperDash.Essentials.Core;
|
using PepperDash.Essentials.Core;
|
||||||
using PepperDash.Essentials.Core.Config;
|
using PepperDash.Essentials.Core.Config;
|
||||||
|
|
@ -10,19 +9,37 @@ using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Devices.Common.SoftCodec;
|
namespace PepperDash.Essentials.Devices.Common.SoftCodec;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class representing a BlueJeans soft codec running on an in-room PC.
|
||||||
|
/// </summary>
|
||||||
public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink
|
public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The input port for any video source.
|
||||||
|
/// </summary>
|
||||||
public RoutingInputPort AnyVideoIn { get; private set; }
|
public RoutingInputPort AnyVideoIn { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The currently active input port, which for this device is always AnyVideoIn
|
||||||
|
/// This is used by the routing system to determine where to route video sources when this device is a destination
|
||||||
|
/// </summary>
|
||||||
public RoutingInputPort CurrentInputPort => AnyVideoIn;
|
public RoutingInputPort CurrentInputPort => AnyVideoIn;
|
||||||
|
|
||||||
#region IRoutingInputs Members
|
#region IRoutingInputs Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Collection of the input ports for this device
|
||||||
|
/// </summary>
|
||||||
public RoutingPortCollection<RoutingInputPort> InputPorts { get; private set; }
|
public RoutingPortCollection<RoutingInputPort> InputPorts { get; private set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BlueJeansPc"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">The key for the device.</param>
|
||||||
|
/// <param name="name">The name of the device.</param>
|
||||||
public BlueJeansPc(string key, string name)
|
public BlueJeansPc(string key, string name)
|
||||||
: base(key, name)
|
: base(key, name)
|
||||||
{
|
{
|
||||||
|
|
@ -34,14 +51,25 @@ public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink
|
||||||
|
|
||||||
#region IRunRouteAction Members
|
#region IRunRouteAction Members
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs a route action for the specified route key and source list key. Optionally, a callback can be provided to be executed upon successful completion.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routeKey"></param>
|
||||||
|
/// <param name="sourceListKey"></param>
|
||||||
public void RunRouteAction(string routeKey, string sourceListKey)
|
public void RunRouteAction(string routeKey, string sourceListKey)
|
||||||
{
|
{
|
||||||
RunRouteAction(routeKey, sourceListKey, null);
|
RunRouteAction(routeKey, sourceListKey, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs a route action for the specified route key and source list key. Optionally, a callback can be provided to be executed upon successful completion.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routeKey"></param>
|
||||||
|
/// <param name="sourceListKey"></param>
|
||||||
|
/// <param name="successCallback"></param>
|
||||||
public void RunRouteAction(string routeKey, string sourceListKey, Action successCallback)
|
public void RunRouteAction(string routeKey, string sourceListKey, Action successCallback)
|
||||||
{
|
{
|
||||||
CrestronInvoke.BeginInvoke(o =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, this, "Run route action '{0}' on SourceList: {1}", routeKey, sourceListKey);
|
Debug.LogMessage(LogEventLevel.Debug, this, "Run route action '{0}' on SourceList: {1}", routeKey, sourceListKey);
|
||||||
|
|
||||||
|
|
@ -127,6 +155,7 @@ public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink
|
||||||
|
|
||||||
#region IHasCurrentSourceInfoChange Members
|
#region IHasCurrentSourceInfoChange Members
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string CurrentSourceInfoKey { get; set; }
|
public string CurrentSourceInfoKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -158,6 +187,7 @@ public class BlueJeansPc : InRoomPc, IRunRouteAction, IRoutingSink
|
||||||
}
|
}
|
||||||
SourceListItem _CurrentSourceInfo;
|
SourceListItem _CurrentSourceInfo;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public event SourceInfoChangeHandler CurrentSourceChange;
|
public event SourceInfoChangeHandler CurrentSourceChange;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ using PepperDash.Essentials.Devices.Common.VideoCodec.Interfaces;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using Feedback = PepperDash.Essentials.Core.Feedback;
|
using Feedback = PepperDash.Essentials.Core.Feedback;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Devices.Common.VideoCodec;
|
namespace PepperDash.Essentials.Devices.Common.VideoCodec;
|
||||||
|
|
||||||
|
|
@ -382,7 +383,7 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected void SetIsReady()
|
protected void SetIsReady()
|
||||||
{
|
{
|
||||||
CrestronInvoke.BeginInvoke((o) =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected VideoCodecBase Codec { get; private set; }
|
protected VideoCodecBase Codec { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -126,7 +124,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
//state.CurrentDirectory = PrefixDirectoryFolderItems(directory);
|
//state.CurrentDirectory = PrefixDirectoryFolderItems(directory);
|
||||||
state.CurrentDirectory = directory;
|
state.CurrentDirectory = directory;
|
||||||
|
|
||||||
CrestronInvoke.BeginInvoke((o) => PostStatusMessage(state));
|
Task.Run(() => PostStatusMessage(state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -162,7 +160,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called from base's RegisterWithAppServer method
|
/// Called from base's RegisterWithAppServer method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appServerController"></param>
|
|
||||||
protected override void RegisterActions()
|
protected override void RegisterActions()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -786,6 +783,10 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends the full status of the codec, including active calls, camera status, and directory info if applicable
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
protected virtual void SendFullStatus(string id = null)
|
protected virtual void SendFullStatus(string id = null)
|
||||||
{
|
{
|
||||||
if (!Codec.IsReady)
|
if (!Codec.IsReady)
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ namespace PepperDash.Essentials
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MobileControlWebsocketServer DirectServer => _directServer;
|
public MobileControlWebsocketServer DirectServer => _directServer;
|
||||||
|
|
||||||
private readonly CCriticalSection _wsCriticalSection = new CCriticalSection();
|
private readonly object _wsCriticalSection = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the SystemUrl
|
/// Gets or sets the SystemUrl
|
||||||
|
|
@ -206,14 +206,14 @@ namespace PepperDash.Essentials
|
||||||
//_receiveQueue = new ReceiveQueue(key, ParseStreamRx);
|
//_receiveQueue = new ReceiveQueue(key, ParseStreamRx);
|
||||||
_receiveQueue = new GenericQueue(
|
_receiveQueue = new GenericQueue(
|
||||||
key + "-rxqueue",
|
key + "-rxqueue",
|
||||||
Crestron.SimplSharpPro.CrestronThread.Thread.eThreadPriority.HighPriority,
|
System.Threading.ThreadPriority.Highest,
|
||||||
25
|
25
|
||||||
);
|
);
|
||||||
|
|
||||||
// The queue that will collect the outgoing messages in the order they are received
|
// The queue that will collect the outgoing messages in the order they are received
|
||||||
_transmitToServerQueue = new GenericQueue(
|
_transmitToServerQueue = new GenericQueue(
|
||||||
key + "-txqueue",
|
key + "-txqueue",
|
||||||
Crestron.SimplSharpPro.CrestronThread.Thread.eThreadPriority.HighPriority,
|
System.Threading.ThreadPriority.Highest,
|
||||||
25
|
25
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -228,7 +228,7 @@ namespace PepperDash.Essentials
|
||||||
|
|
||||||
_transmitToClientsQueue = new GenericQueue(
|
_transmitToClientsQueue = new GenericQueue(
|
||||||
key + "-clienttxqueue",
|
key + "-clienttxqueue",
|
||||||
Crestron.SimplSharpPro.CrestronThread.Thread.eThreadPriority.HighPriority,
|
System.Threading.ThreadPriority.Highest,
|
||||||
25
|
25
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1811,10 +1811,8 @@ namespace PepperDash.Essentials
|
||||||
|
|
||||||
private void ConnectWebsocketClient()
|
private void ConnectWebsocketClient()
|
||||||
{
|
{
|
||||||
try
|
lock (_wsCriticalSection)
|
||||||
{
|
{
|
||||||
_wsCriticalSection.Enter();
|
|
||||||
|
|
||||||
// set to 99999 to let things work on 4-Series
|
// set to 99999 to let things work on 4-Series
|
||||||
if (
|
if (
|
||||||
(CrestronEnvironment.ProgramCompatibility & eCrestronSeries.Series4)
|
(CrestronEnvironment.ProgramCompatibility & eCrestronSeries.Series4)
|
||||||
|
|
@ -1843,10 +1841,6 @@ namespace PepperDash.Essentials
|
||||||
|
|
||||||
TryConnect();
|
TryConnect();
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
_wsCriticalSection.Leave();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryConnect()
|
private void TryConnect()
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ using PepperDash.Essentials.Core.Routing;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Timeout = Crestron.SimplSharp.Timeout;
|
using Timeout = Crestron.SimplSharp.Timeout;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PepperDash.Essentials;
|
namespace PepperDash.Essentials;
|
||||||
|
|
||||||
|
|
@ -108,7 +109,7 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig
|
||||||
|
|
||||||
if (Debug.DoNotLoadConfigOnNextBoot)
|
if (Debug.DoNotLoadConfigOnNextBoot)
|
||||||
{
|
{
|
||||||
CrestronConsole.AddNewConsoleCommand(s => CrestronInvoke.BeginInvoke((o) => GoWithLoad()), "go", "Loads configuration file",
|
CrestronConsole.AddNewConsoleCommand(s => Task.Run(() => GoWithLoad()), "go", "Loads configuration file",
|
||||||
ConsoleAccessLevelEnum.AccessOperator);
|
ConsoleAccessLevelEnum.AccessOperator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue