Refator: Refactor timer implementation across multiple classes to use System.Timers.Timer instead of CTimer for improved consistency and performance.

- Updated RelayControlledShade to utilize Timer for output pulsing.
- Refactored MockVC to replace CTimer with Timer for call status simulation.
- Modified VideoCodecBase to enhance documentation and improve feedback handling.
- Removed obsolete IHasCamerasMessenger and updated related classes to use IHasCamerasWithControls.
- Adjusted PressAndHoldHandler to implement Timer for button hold actions.
- Enhanced logging throughout MobileControl and RoomBridges for better debugging and information tracking.
- Cleaned up unnecessary comments and improved exception handling in various classes.
This commit is contained in:
Neil Dorin 2026-03-30 11:44:15 -06:00
parent b4d53dbe0e
commit 7076eafc21
56 changed files with 1343 additions and 2197 deletions

View file

@ -3,8 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using PepperDash.Core.Logging;
@ -183,7 +182,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
// private Timer for auto reconnect
private CTimer RetryTimer;
private Timer RetryTimer;
#endregion
@ -266,12 +265,12 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
/// </summary>
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
CTimer HeartbeatSendTimer;
CTimer HeartbeatAckTimer;
Timer HeartbeatSendTimer;
Timer HeartbeatAckTimer;
// Used to force disconnection on a dead connect attempt
CTimer ConnectFailTimer;
CTimer WaitForSharedKey;
Timer ConnectFailTimer;
Timer WaitForSharedKey;
private int ConnectionCount;
bool ProgramIsStopping;
@ -493,7 +492,8 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
ConnectFailTimer = new CTimer(o =>
ConnectFailTimer = new Timer(30000) { AutoReset = false };
ConnectFailTimer.Elapsed += (s, e) =>
{
this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
if (IsTryingToConnect)
@ -507,7 +507,8 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
//SecureClient.DisconnectFromServer();
//CheckClosedAndTryReconnect();
}
}, 30000);
};
ConnectFailTimer.Start();
this.LogVerbose("Making Connection Count:{0}", ConnectionCount);
_client.ConnectToServerAsync(o =>
@ -528,16 +529,16 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
if (SharedKeyRequired)
{
WaitingForSharedKeyResponse = true;
WaitForSharedKey = new CTimer(timer =>
WaitForSharedKey = new Timer(15000) { AutoReset = false };
WaitForSharedKey.Elapsed += (s, e) =>
{
this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
o.DisconnectFromServer();
//CheckClosedAndTryReconnect();
//OnClientReadyForcommunications(false); // Should send false event
}, 15000);
};
WaitForSharedKey.Start();
}
else
{
@ -637,7 +638,9 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
}
if (AutoReconnectTriggered != null)
AutoReconnectTriggered(this, new EventArgs());
RetryTimer = new CTimer(o => Connect(), rndTime);
RetryTimer = new Timer(rndTime) { AutoReset = false };
RetryTimer.Elapsed += (s, e) => Connect();
RetryTimer.Start();
}
}
@ -698,11 +701,11 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
if (handler != null)
{
if (Monitor.TryEnter(_dequeueLock))
Task.Run(() => DequeueEvent());
if (System.Threading.Monitor.TryEnter(_dequeueLock))
System.Threading.Tasks.Task.Run(() => 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
{
client.DisconnectFromServer();
}
@ -732,7 +735,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
this.LogError(e, "DequeueEvent error: {0}", e.Message);
}
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
Monitor.Exit(_dequeueLock);
System.Threading.Monitor.Exit(_dequeueLock);
}
void HeartbeatStart()
@ -743,11 +746,15 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
if (HeartbeatSendTimer == null)
{
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
HeartbeatSendTimer = new Timer(HeartbeatInterval) { AutoReset = true };
HeartbeatSendTimer.Elapsed += (s, e) => SendHeartbeat(null);
HeartbeatSendTimer.Start();
}
if (HeartbeatAckTimer == null)
{
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true };
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
HeartbeatAckTimer.Start();
}
}
@ -791,11 +798,15 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
{
if (HeartbeatAckTimer != null)
{
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
HeartbeatAckTimer.Stop();
HeartbeatAckTimer.Interval = HeartbeatInterval * 2;
HeartbeatAckTimer.Start();
}
else
{
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true };
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
HeartbeatAckTimer.Start();
}
this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString);
return remainingText;

View file

@ -17,6 +17,7 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using PepperDash.Core.Logging;
@ -223,7 +224,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
/// <summary>
/// private Timer for auto reconnect
/// </summary>
CTimer RetryTimer;
System.Timers.Timer RetryTimer;
/// <summary>
@ -255,13 +256,13 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
/// </summary>
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
CTimer HeartbeatSendTimer;
CTimer HeartbeatAckTimer;
System.Timers.Timer HeartbeatSendTimer;
System.Timers.Timer HeartbeatAckTimer;
/// <summary>
/// Used to force disconnection on a dead connect attempt
/// </summary>
CTimer ConnectFailTimer;
CTimer WaitForSharedKey;
System.Timers.Timer ConnectFailTimer;
System.Timers.Timer WaitForSharedKey;
private int ConnectionCount;
/// <summary>
/// Internal secure client
@ -457,7 +458,8 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
ConnectFailTimer = new CTimer(o =>
ConnectFailTimer = new System.Timers.Timer(30000) { AutoReset = false };
ConnectFailTimer.Elapsed += (s, e) =>
{
this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
if (IsTryingToConnect)
@ -471,7 +473,8 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
//SecureClient.DisconnectFromServer();
//CheckClosedAndTryReconnect();
}
}, 30000);
};
ConnectFailTimer.Start();
this.LogVerbose("Making Connection Count:{0}", ConnectionCount);
Client.ConnectToServerAsync(o =>
@ -492,16 +495,16 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
if (SharedKeyRequired)
{
WaitingForSharedKeyResponse = true;
WaitForSharedKey = new CTimer(timer =>
WaitForSharedKey = new System.Timers.Timer(15000) { AutoReset = false };
WaitForSharedKey.Elapsed += (s, e) =>
{
this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
o.DisconnectFromServer();
//CheckClosedAndTryReconnect();
//OnClientReadyForcommunications(false); // Should send false event
}, 15000);
};
WaitForSharedKey.Start();
}
else
{
@ -596,7 +599,9 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
}
if (AutoReconnectTriggered != null)
AutoReconnectTriggered(this, new EventArgs());
RetryTimer = new CTimer(o => Connect(), rndTime);
RetryTimer = new System.Timers.Timer(rndTime) { AutoReset = false };
RetryTimer.Elapsed += (s, e) => Connect();
RetryTimer.Start();
}
}
@ -702,11 +707,15 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
if (HeartbeatSendTimer == null)
{
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
HeartbeatSendTimer = new System.Timers.Timer(HeartbeatInterval) { AutoReset = true };
HeartbeatSendTimer.Elapsed += (s, e) => SendHeartbeat(null);
HeartbeatSendTimer.Start();
}
if (HeartbeatAckTimer == null)
{
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
HeartbeatAckTimer = new System.Timers.Timer(HeartbeatInterval * 2) { AutoReset = true };
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
HeartbeatAckTimer.Start();
}
}
@ -750,11 +759,15 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
{
if (HeartbeatAckTimer != null)
{
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
HeartbeatAckTimer.Stop();
HeartbeatAckTimer.Interval = HeartbeatInterval * 2;
HeartbeatAckTimer.Start();
}
else
{
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
HeartbeatAckTimer = new System.Timers.Timer(HeartbeatInterval * 2) { AutoReset = true };
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
HeartbeatAckTimer.Start();
}
this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString);
return remainingText;

View file

@ -2,8 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using PepperDash.Core.Logging;
@ -92,7 +91,7 @@ public class GenericSecureTcpIpServer : Device
/// <summary>
/// Timer to operate the bandaid monitor client in a loop.
/// </summary>
CTimer MonitorClientTimer;
Timer MonitorClientTimer;
/// <summary>
///
@ -259,7 +258,7 @@ public class GenericSecureTcpIpServer : Device
public string HeartbeatStringToMatch { get; set; }
//private timers for Heartbeats per client
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
Dictionary<uint, Timer> HeartbeatTimerDictionary = new Dictionary<uint, Timer>();
//flags to show the secure server is waiting for client at index to send the shared key
List<uint> WaitingForSharedKey = new List<uint>();
@ -592,11 +591,17 @@ public class GenericSecureTcpIpServer : Device
if (noDelimiter.Contains(HeartbeatStringToMatch))
{
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
{
HeartbeatTimerDictionary[clientIndex].Stop();
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
HeartbeatTimerDictionary[clientIndex].Start();
}
else
{
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
heartbeatTimer.Start();
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
}
this.LogDebug("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
// Return Heartbeat
@ -607,11 +612,17 @@ public class GenericSecureTcpIpServer : Device
else
{
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
{
HeartbeatTimerDictionary[clientIndex].Stop();
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
HeartbeatTimerDictionary[clientIndex].Start();
}
else
{
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
heartbeatTimer.Start();
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
}
this.LogInformation("Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
}
@ -665,7 +676,6 @@ public class GenericSecureTcpIpServer : Device
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
var discoResult = SecureServer.Disconnect(clientIndex);
//Debug.Console(1, this, "{0}", discoResult);
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
{
@ -694,8 +704,6 @@ public class GenericSecureTcpIpServer : Device
try
{
// Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.SecureServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.SecureServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
{
this.LogInformation("SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber);
@ -725,7 +733,7 @@ public class GenericSecureTcpIpServer : Device
//Use a thread for this event so that the server state updates to listening while this event is processed. Listening must be added to the server state
//after every client connection so that the server can check and see if it is at max clients. Due to this the event fires and server listening enum bit flag
//is not set. Putting in a thread allows the state to update before this event processes so that the subscribers to this event get accurate isListening in the event.
Task.Run(() => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)));
System.Threading.Tasks.Task.Run(() => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)));
}
#endregion
@ -770,7 +778,10 @@ public class GenericSecureTcpIpServer : Device
{
if (!HeartbeatTimerDictionary.ContainsKey(clientIndex))
{
HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs));
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
heartbeatTimer.Start();
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
}
}
@ -860,8 +871,8 @@ public class GenericSecureTcpIpServer : Device
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
if (handler != null)
{
if (Monitor.TryEnter(_dequeueLock))
Task.Run(() => DequeueEvent());
if (System.Threading.Monitor.TryEnter(_dequeueLock))
System.Threading.Tasks.Task.Run(() => DequeueEvent());
}
}
else
@ -894,7 +905,7 @@ public class GenericSecureTcpIpServer : Device
this.LogError(e, "DequeueEvent error");
}
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
Monitor.Exit(_dequeueLock);
System.Threading.Monitor.Exit(_dequeueLock);
}
#endregion
@ -991,7 +1002,9 @@ public class GenericSecureTcpIpServer : Device
{
return;
}
MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000);
MonitorClientTimer = new Timer(60000) { AutoReset = false };
MonitorClientTimer.Elapsed += (s, e) => RunMonitorClient();
MonitorClientTimer.Start();
}
/// <summary>

View file

@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using Org.BouncyCastle.Utilities;
using PepperDash.Core.Logging;
using Renci.SshNet;
using Renci.SshNet.Common;
@ -134,9 +131,9 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
ShellStream TheStream;
CTimer ReconnectTimer;
Timer ReconnectTimer;
private SemaphoreSlim connectLock = new SemaphoreSlim(1);
private System.Threading.SemaphoreSlim connectLock = new System.Threading.SemaphoreSlim(1);
private bool DisconnectLogged = false;
@ -155,13 +152,14 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
Password = password;
AutoReconnectIntervalMs = 5000;
ReconnectTimer = new CTimer(o =>
ReconnectTimer = new Timer { AutoReset = false, Enabled = false };
ReconnectTimer.Elapsed += (s, e) =>
{
if (ConnectEnabled)
{
Connect();
}
}, System.Threading.Timeout.Infinite);
};
}
/// <summary>
@ -173,13 +171,14 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
AutoReconnectIntervalMs = 5000;
ReconnectTimer = new CTimer(o =>
ReconnectTimer = new Timer { AutoReset = false, Enabled = false };
ReconnectTimer.Elapsed += (s, e) =>
{
if (ConnectEnabled)
{
Connect();
}
}, System.Threading.Timeout.Infinite);
};
}
/// <summary>
@ -265,7 +264,6 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
catch (SshConnectionException e)
{
var ie = e.InnerException; // The details are inside!!
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
if (ie is SocketException)
{
@ -289,7 +287,9 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
if (AutoReconnect)
{
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
ReconnectTimer.Reset(AutoReconnectIntervalMs);
ReconnectTimer.Stop();
ReconnectTimer.Interval = AutoReconnectIntervalMs;
ReconnectTimer.Start();
}
}
catch (SshOperationTimeoutException ex)
@ -301,19 +301,22 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
if (AutoReconnect)
{
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
ReconnectTimer.Reset(AutoReconnectIntervalMs);
ReconnectTimer.Stop();
ReconnectTimer.Interval = AutoReconnectIntervalMs;
ReconnectTimer.Start();
}
}
catch (Exception e)
{
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
this.LogException(e, "Unhandled exception on connect");
DisconnectLogged = true;
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
if (AutoReconnect)
{
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
ReconnectTimer.Reset(AutoReconnectIntervalMs);
ReconnectTimer.Stop();
ReconnectTimer.Interval = AutoReconnectIntervalMs;
ReconnectTimer.Start();
}
}
}
@ -434,7 +437,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
/// </summary>
void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
{
Task.Run(() =>
System.Threading.Tasks.Task.Run(() =>
{
if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
this.LogError("Disconnected by remote");
@ -452,7 +455,9 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
if (AutoReconnect && ConnectEnabled)
{
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
ReconnectTimer.Reset(AutoReconnectIntervalMs);
ReconnectTimer.Stop();
ReconnectTimer.Interval = AutoReconnectIntervalMs;
ReconnectTimer.Start();
}
});
}
@ -497,7 +502,8 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
ReconnectTimer.Reset();
ReconnectTimer.Stop();
ReconnectTimer.Start();
}
catch (Exception ex)
{
@ -531,7 +537,8 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
ReconnectTimer.Reset();
ReconnectTimer.Stop();
ReconnectTimer.Start();
}
catch (Exception ex)
{

View file

@ -11,10 +11,9 @@ PepperDash Technology Corporation reserves all rights under applicable laws.
------------------------------------ */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Timers;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using PepperDash.Core.Logging;
@ -210,7 +209,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
/// <summary>
/// private Timer for auto reconnect
/// </summary>
CTimer RetryTimer;
Timer RetryTimer;
/// <summary>
@ -237,13 +236,13 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
/// </summary>
public int HeartbeatInterval = 50000;
CTimer HeartbeatSendTimer;
CTimer HeartbeatAckTimer;
Timer HeartbeatSendTimer;
Timer HeartbeatAckTimer;
/// <summary>
/// Used to force disconnection on a dead connect attempt
/// </summary>
CTimer ConnectFailTimer;
CTimer WaitForSharedKey;
Timer ConnectFailTimer;
Timer WaitForSharedKey;
private int ConnectionCount;
/// <summary>
/// Internal secure client
@ -303,7 +302,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
{
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection");
this.LogInformation("Program stopping. Closing Client connection");
ProgramIsStopping = true;
Disconnect();
}
@ -316,17 +315,17 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
public void Connect()
{
ConnectionCount++;
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount);
this.LogDebug("Attempting connect Count:{0}", ConnectionCount);
if (IsConnected)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
this.LogInformation("Already connected. Ignoring.");
return;
}
if (IsTryingToConnect)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
this.LogInformation("Already trying to connect. Ignoring.");
return;
}
try
@ -339,17 +338,17 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
}
if (string.IsNullOrEmpty(Hostname))
{
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
this.LogWarning("DynamicTcpClient: No address set");
return;
}
if (Port < 1 || Port > 65535)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
this.LogWarning("DynamicTcpClient: Invalid port");
return;
}
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
this.LogWarning("DynamicTcpClient: No Shared Key set");
return;
}
@ -370,9 +369,10 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
ConnectFailTimer = new CTimer(o =>
ConnectFailTimer = new Timer(30000) { AutoReset = false };
ConnectFailTimer.Elapsed += (s, e) =>
{
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
if (IsTryingToConnect)
{
IsTryingToConnect = false;
@ -384,12 +384,13 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
//SecureClient.DisconnectFromServer();
//CheckClosedAndTryReconnect();
}
}, 30000);
};
ConnectFailTimer.Start();
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount);
this.LogDebug("Making Connection Count:{0}", ConnectionCount);
Client.ConnectToServerAsync(o =>
{
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
this.LogDebug("ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
if (ConnectFailTimer != null)
{
@ -399,22 +400,22 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
{
Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
this.LogVerbose("Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
o.ReceiveDataAsync(Receive);
if (SharedKeyRequired)
{
WaitingForSharedKeyResponse = true;
WaitForSharedKey = new CTimer(timer =>
WaitForSharedKey = new Timer(15000) { AutoReset = false };
WaitForSharedKey.Elapsed += (s, e) =>
{
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
o.DisconnectFromServer();
//CheckClosedAndTryReconnect();
//OnClientReadyForcommunications(false); // Should send false event
}, 15000);
};
WaitForSharedKey.Start();
}
else
{
@ -428,14 +429,15 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
}
else
{
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
this.LogWarning("Connect attempt failed {0}", o.ClientStatus);
CheckClosedAndTryReconnect();
}
});
}
catch (Exception ex)
{
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message);
this.LogException(ex, "Client connection exception: {0}", ex.Message);
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
IsTryingToConnect = false;
CheckClosedAndTryReconnect();
}
@ -472,7 +474,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
if (Client != null)
{
//SecureClient.DisconnectFromServer();
Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
this.LogVerbose("Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
Client.SocketStatusChange -= Client_SocketStatusChange;
Client.Dispose();
Client = null;
@ -494,20 +496,22 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
{
if (Client != null)
{
Debug.Console(2, this, "Cleaning up remotely closed/failed connection.");
this.LogVerbose("Cleaning up remotely closed/failed connection.");
Cleanup();
}
if (!DisconnectCalledByUser && AutoReconnect)
{
var halfInterval = AutoReconnectIntervalMs / 2;
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime);
this.LogVerbose("Attempting reconnect in {0} ms, randomized", rndTime);
if (RetryTimer != null)
{
RetryTimer.Stop();
RetryTimer = null;
}
RetryTimer = new CTimer(o => Connect(), rndTime);
RetryTimer = new Timer(rndTime) { AutoReset = false };
RetryTimer.Elapsed += (s, e) => Connect();
RetryTimer.Start();
}
}
@ -526,18 +530,18 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
{
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str);
this.LogVerbose("Client Received:\r--------\r{0}\r--------", str);
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
{
if (SharedKeyRequired && str == "SharedKey:")
{
Debug.Console(2, this, "Server asking for shared key, sending");
this.LogVerbose("Server asking for shared key, sending");
SendText(SharedKey + "\n");
}
else if (SharedKeyRequired && str == "Shared Key Match")
{
StopWaitForSharedKeyTimer();
Debug.Console(2, this, "Shared key confirmed. Ready for communication");
this.LogVerbose("Shared key confirmed. Ready for communication");
OnClientReadyForcommunications(true); // Successful key exchange
}
else
@ -553,7 +557,8 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
}
catch (Exception ex)
{
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
this.LogException(ex, "Error receiving data: {1}. Error: {0}", ex.Message, str);
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
}
}
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
@ -564,15 +569,19 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
{
if (HeartbeatEnabled)
{
Debug.Console(2, this, "Starting Heartbeat");
this.LogVerbose("Starting Heartbeat");
if (HeartbeatSendTimer == null)
{
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
HeartbeatSendTimer = new Timer(HeartbeatInterval) { AutoReset = true };
HeartbeatSendTimer.Elapsed += (s, e) => SendHeartbeat(null);
HeartbeatSendTimer.Start();
}
if (HeartbeatAckTimer == null)
{
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true };
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
HeartbeatAckTimer.Start();
}
}
@ -582,13 +591,13 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
if (HeartbeatSendTimer != null)
{
Debug.Console(2, this, "Stoping Heartbeat Send");
this.LogVerbose("Stoping Heartbeat Send");
HeartbeatSendTimer.Stop();
HeartbeatSendTimer = null;
}
if (HeartbeatAckTimer != null)
{
Debug.Console(2, this, "Stoping Heartbeat Ack");
this.LogVerbose("Stoping Heartbeat Ack");
HeartbeatAckTimer.Stop();
HeartbeatAckTimer = null;
}
@ -597,7 +606,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
void SendHeartbeat(object notused)
{
this.SendText(HeartbeatString);
Debug.Console(2, this, "Sending Heartbeat");
this.LogVerbose("Sending Heartbeat");
}
@ -616,13 +625,17 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
{
if (HeartbeatAckTimer != null)
{
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
HeartbeatAckTimer.Stop();
HeartbeatAckTimer.Interval = HeartbeatInterval * 2;
HeartbeatAckTimer.Start();
}
else
{
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true };
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
HeartbeatAckTimer.Start();
}
Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString);
return remainingText;
}
}
@ -630,7 +643,8 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
}
catch (Exception ex)
{
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
this.LogException(ex, "Error checking heartbeat: {0}", ex.Message);
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
}
return received;
}
@ -644,7 +658,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
if (IsConnected)
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
this.LogWarning("Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
SendText("Heartbeat not received by server, closing connection");
CheckClosedAndTryReconnect();
}
@ -652,7 +666,8 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
}
catch (Exception ex)
{
ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
this.LogException(ex, "Heartbeat timeout Error on Client: {0}, {1}", Key, ex.Message);
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
}
}
@ -685,14 +700,15 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
if (n <= 0)
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
this.LogWarning("[{0}] Sent zero bytes. Was there an error?", this.Key);
}
});
}
}
catch (Exception ex)
{
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
this.LogException(ex, "Error sending text: {1}. Error: {0}", ex.Message, text);
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
}
}
}
@ -711,7 +727,8 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
}
catch (Exception ex)
{
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
this.LogException(ex, "Error sending bytes. Error: {0}", ex.Message);
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
}
}
}
@ -730,7 +747,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
}
try
{
Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
this.LogVerbose("Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
OnConnectionChange();
@ -744,7 +761,8 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
}
catch (Exception ex)
{
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
this.LogException(ex, "Error in socket status change callback. Error: {0}", ex.Message);
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
}
}

View file

@ -13,7 +13,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Timers;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets;
using PepperDash.Core.Logging;
@ -80,7 +80,7 @@ public class GenericTcpIpServer : Device
/// <summary>
/// Timer to operate the bandaid monitor client in a loop.
/// </summary>
CTimer MonitorClientTimer;
Timer MonitorClientTimer;
/// <summary>
///
@ -250,7 +250,7 @@ public class GenericTcpIpServer : Device
public string HeartbeatStringToMatch { get; set; }
//private timers for Heartbeats per client
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
Dictionary<uint, Timer> HeartbeatTimerDictionary = new Dictionary<uint, Timer>();
//flags to show the secure server is waiting for client at index to send the shared key
List<uint> WaitingForSharedKey = new List<uint>();
@ -577,11 +577,17 @@ public class GenericTcpIpServer : Device
if (noDelimiter.Contains(HeartbeatStringToMatch))
{
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
{
HeartbeatTimerDictionary[clientIndex].Stop();
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
HeartbeatTimerDictionary[clientIndex].Start();
}
else
{
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
heartbeatTimer.Start();
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
}
this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
// Return Heartbeat
@ -592,11 +598,17 @@ public class GenericTcpIpServer : Device
else
{
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
{
HeartbeatTimerDictionary[clientIndex].Stop();
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
HeartbeatTimerDictionary[clientIndex].Start();
}
else
{
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
heartbeatTimer.Start();
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
}
this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
}
@ -650,7 +662,6 @@ public class GenericTcpIpServer : Device
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
var discoResult = myTcpServer.Disconnect(clientIndex);
//Debug.Console(1, this, "{0}", discoResult);
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
{
@ -661,7 +672,8 @@ public class GenericTcpIpServer : Device
}
catch (Exception ex)
{
ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key);
this.LogException(ex, "Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message);
this.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
}
}
@ -746,7 +758,10 @@ public class GenericTcpIpServer : Device
{
if (!HeartbeatTimerDictionary.ContainsKey(clientIndex))
{
HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs));
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
heartbeatTimer.Start();
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
}
}
@ -765,9 +780,9 @@ public class GenericTcpIpServer : Device
}
catch (Exception ex)
{
this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex);
this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex.Message);
this.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
}
//Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))",
// server.State,
// MaxClients,
// ServerStopped);
@ -929,7 +944,9 @@ public class GenericTcpIpServer : Device
{
return;
}
MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000);
MonitorClientTimer = new Timer(60000) { AutoReset = false };
MonitorClientTimer.Elapsed += (s, e) => RunMonitorClient();
MonitorClientTimer.Start();
}
/// <summary>

View file

@ -183,7 +183,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
if (programEventType != eProgramStatusEventType.Stopping)
return;
Debug.Console(1, this, "Program stopping. Disabling Server");
this.LogInformation("Program stopping. Disabling Server");
Disconnect();
}
@ -199,20 +199,20 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
if (string.IsNullOrEmpty(Hostname))
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key);
this.LogWarning("GenericUdpServer '{0}': No address set", Key);
return;
}
if (Port < 1 || Port > 65535)
{
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key);
this.LogWarning("GenericUdpServer '{0}': Invalid port", Key);
return;
}
}
var status = Server.EnableUDPServer(Hostname, Port);
Debug.Console(2, this, "SocketErrorCode: {0}", status);
this.LogVerbose("SocketErrorCode: {0}", status);
if (status == SocketErrorCodes.SOCKET_OK)
IsConnected = true;
@ -247,7 +247,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
/// <param name="numBytes"></param>
void Receive(UDPServer server, int numBytes)
{
Debug.Console(2, this, "Received {0} bytes", numBytes);
this.LogVerbose("Received {0} bytes", numBytes);
try
{
@ -263,13 +263,13 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
if (dataRecivedExtra != null)
dataRecivedExtra(this, new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes));
Debug.Console(2, this, "Bytes: {0}", bytes.ToString());
this.LogVerbose("Bytes: {0}", bytes.ToString());
var bytesHandler = BytesReceived;
if (bytesHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
@ -277,7 +277,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
if (textHandler != null)
{
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));
}
}
@ -302,7 +302,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
if (IsConnected && Server != null)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
this.LogVerbose("Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
Server.SendData(bytes, bytes.Length);
}
@ -315,7 +315,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
public void SendBytes(byte[] bytes)
{
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 (IsConnected && Server != null)
Server.SendData(bytes, bytes.Length);

View file

@ -155,7 +155,6 @@ namespace PepperDash.Core.Config;
else
merged.Add(global, template[global]);
//Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
return merged;
}

View file

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using PepperDash.Core.Logging;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
namespace PepperDash.Core.JsonToSimpl;
@ -91,7 +92,7 @@ namespace PepperDash.Core.JsonToSimpl;
if (Master != null)
Master.AddChild(this);
else
Debug.Console(1, "JSON Child [{0}] cannot link to master {1}", key, masterUniqueId);
this.LogWarning("JSON Child [{0}] cannot link to master {1}", key, masterUniqueId);
}
/// <summary>
@ -107,7 +108,7 @@ namespace PepperDash.Core.JsonToSimpl;
/// </summary>
public void SetBoolPath(ushort index, string path)
{
Debug.Console(1, "JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path);
this.LogDebug("JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path);
if (path == null || path.Trim() == string.Empty) return;
BoolPaths[index] = path;
}
@ -117,7 +118,7 @@ namespace PepperDash.Core.JsonToSimpl;
/// </summary>
public void SetUshortPath(ushort index, string path)
{
Debug.Console(1, "JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path);
this.LogDebug("JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path);
if (path == null || path.Trim() == string.Empty) return;
UshortPaths[index] = path;
}
@ -127,7 +128,7 @@ namespace PepperDash.Core.JsonToSimpl;
/// </summary>
public void SetStringPath(ushort index, string path)
{
Debug.Console(1, "JSON Child[{0}] SetStringPath {1}={2}", Key, index, path);
this.LogDebug("JSON Child[{0}] SetStringPath {1}={2}", Key, index, path);
if (path == null || path.Trim() == string.Empty) return;
StringPaths[index] = path;
}
@ -140,13 +141,13 @@ namespace PepperDash.Core.JsonToSimpl;
{
if (!LinkedToObject)
{
Debug.Console(1, this, "Not linked to object in file. Skipping");
this.LogDebug("Not linked to object in file. Skipping");
return;
}
if (SetAllPathsDelegate == null)
{
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring ProcessAll");
this.LogDebug("No SetAllPathsDelegate set. Ignoring ProcessAll");
return;
}
SetAllPathsDelegate();
@ -206,11 +207,11 @@ namespace PepperDash.Core.JsonToSimpl;
bool Process(string path, out string response)
{
path = GetFullPath(path);
Debug.Console(1, "JSON Child[{0}] Processing {1}", Key, path);
this.LogDebug("JSON Child[{0}] Processing {1}", Key, path);
response = "";
if (Master == null)
{
Debug.Console(1, "JSONChild[{0}] cannot process without Master attached", Key);
this.LogWarning("JSONChild[{0}] cannot process without Master attached", Key);
return false;
}
@ -233,7 +234,7 @@ namespace PepperDash.Core.JsonToSimpl;
response = (t.HasValues ? t.Children().Count() : 0).ToString();
else
response = (string)t;
Debug.Console(1, " ='{0}'", response);
this.LogDebug(" ='{0}'", response);
return true;
}
}
@ -259,13 +260,13 @@ namespace PepperDash.Core.JsonToSimpl;
{
if (!LinkedToObject)
{
Debug.Console(1, this, "Not linked to object in file. Skipping");
this.LogDebug("Not linked to object in file. Skipping");
return;
}
if (SetAllPathsDelegate == null)
{
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster");
this.LogDebug("No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster");
return;
}
SetAllPathsDelegate();
@ -327,7 +328,7 @@ namespace PepperDash.Core.JsonToSimpl;
var path = GetFullPath(keyPath);
try
{
Debug.Console(1, "JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave);
this.LogDebug("JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave);
//var token = Master.JsonObject.SelectToken(path);
//if (token != null) // The path exists in the file
@ -335,7 +336,7 @@ namespace PepperDash.Core.JsonToSimpl;
}
catch (Exception e)
{
Debug.Console(1, "JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e);
this.LogDebug("JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e);
}
}

View file

@ -7,6 +7,7 @@ using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using PepperDash.Core.Logging;
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
@ -129,7 +130,7 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
var fileName = Path.GetFileName(Filepath);
OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "Checking '{0}' for '{1}'", fileDirectory, fileName);
this.LogInformation("Checking '{0}' for '{1}'", fileDirectory, fileName);
if (Directory.Exists(fileDirectory))
{
@ -143,7 +144,7 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
var msg = string.Format("JSON file not found: {0}", Filepath);
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(msg);
ErrorLog.Error(msg);
this.LogError(msg);
return;
}
@ -152,18 +153,18 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
ActualFilePath = actualFile.FullName;
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "Actual JSON file is {0}", ActualFilePath);
this.LogInformation("Actual JSON file is {0}", ActualFilePath);
Filename = actualFile.Name;
OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange);
OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "JSON Filename is {0}", Filename);
this.LogInformation("JSON Filename is {0}", Filename);
FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator);
OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange);
OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "JSON File Path is {0}", FilePathName);
this.LogInformation("JSON File Path is {0}", FilePathName);
var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
@ -176,7 +177,7 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
else
{
OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "'{0}' not found", fileDirectory);
this.LogError("'{0}' not found", fileDirectory);
}
}
catch (Exception e)
@ -184,12 +185,12 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message);
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(msg);
ErrorLog.Error(msg);
this.LogException(e, "EvaluateFile Exception: {0}", e.Message);
var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace);
OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(stackTrace);
ErrorLog.Error(stackTrace);
this.LogVerbose("EvaluateFile: Stack Trace\r{0}", e.StackTrace);
}
}
@ -213,63 +214,31 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
// Make each child update their values into master object
foreach (var child in Children)
{
Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
this.LogInformation("Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
child.UpdateInputsForMaster();
}
if (UnsavedValues == null || UnsavedValues.Count == 0)
{
Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID);
this.LogInformation("Master [{0}] No updated values to save. Skipping", UniqueID);
return;
}
lock (FileLock)
{
Debug.Console(1, "Saving");
this.LogInformation("Saving");
foreach (var path in UnsavedValues.Keys)
{
var tokenToReplace = JsonObject.SelectToken(path);
if (tokenToReplace != null)
{// It's found
tokenToReplace.Replace(UnsavedValues[path]);
Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path);
this.LogInformation("JSON Master[{0}] Updating '{1}'", UniqueID, path);
}
else // No token. Let's make one
{
//http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net
Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
this.LogWarning("JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
// JContainer jpart = JsonObject;
// // walk down the path and find where it goes
//#warning Does not handle arrays.
// foreach (var part in path.Split('.'))
// {
// var openPos = part.IndexOf('[');
// if (openPos > -1)
// {
// openPos++; // move to number
// var closePos = part.IndexOf(']');
// var arrayName = part.Substring(0, openPos - 1); // get the name
// var index = Convert.ToInt32(part.Substring(openPos, closePos - openPos));
// // Check if the array itself exists and add the item if so
// if (jpart[arrayName] != null)
// {
// var arrayObj = jpart[arrayName] as JArray;
// var item = arrayObj[index];
// if (item == null)
// arrayObj.Add(new JObject());
// }
// Debug.Console(0, "IGNORING MISSING ARRAY VALUE FOR NOW");
// continue;
// }
// // Build the
// if (jpart[part] == null)
// jpart.Add(new JProperty(part, new JObject()));
// jpart = jpart[part] as JContainer;
// }
// jpart.Replace(UnsavedValues[path]);
}
}
using (StreamWriter sw = new StreamWriter(ActualFilePath))
@ -282,11 +251,13 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
catch (Exception e)
{
string err = string.Format("Error writing JSON file:\r{0}", e);
Debug.Console(0, err);
ErrorLog.Warn(err);
this.LogException(e, "Error writing JSON file: {0}", e.Message);
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
return;
}
}
}
}
}

View file

@ -3,6 +3,8 @@
using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using PepperDash.Core.Logging;
using Renci.SshNet.Messages;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
@ -74,7 +76,8 @@ namespace PepperDash.Core.JsonToSimpl;
}
catch (Exception e)
{
Debug.Console(0, this, "JSON parsing failed:\r{0}", e);
this.LogException(e, "JSON parsing failed:\r{0}", e.Message);
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
}
}
@ -89,36 +92,36 @@ namespace PepperDash.Core.JsonToSimpl;
// Make each child update their values into master object
foreach (var child in Children)
{
Debug.Console(1, this, "Master. checking child [{0}] for updates to save", child.Key);
this.LogDebug("Master. checking child [{0}] for updates to save", child.Key);
child.UpdateInputsForMaster();
}
if (UnsavedValues == null || UnsavedValues.Count == 0)
{
Debug.Console(1, this, "Master. No updated values to save. Skipping");
this.LogDebug("Master. No updated values to save. Skipping");
return;
}
lock (WriteLock)
{
Debug.Console(1, this, "Saving");
this.LogDebug("Saving");
foreach (var path in UnsavedValues.Keys)
{
var tokenToReplace = JsonObject.SelectToken(path);
if (tokenToReplace != null)
{// It's found
tokenToReplace.Replace(UnsavedValues[path]);
Debug.Console(1, this, "Master Updating '{0}'", path);
this.LogDebug("Master Updating '{0}'", path);
}
else // No token. Let's make one
{
Debug.Console(1, "Master Cannot write value onto missing property: '{0}'", path);
this.LogDebug("Master Cannot write value onto missing property: '{0}'", path);
}
}
}
if (SaveCallback != null)
SaveCallback(JsonObject.ToString());
else
Debug.Console(0, this, "WARNING: No save callback defined.");
this.LogDebug("WARNING: No save callback defined.");
}
}

View file

@ -10,6 +10,7 @@ using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
using JsonSerializationException = NewtonsoftJson::Newtonsoft.Json.JsonSerializationException;
using JsonTextReader = NewtonsoftJson::Newtonsoft.Json.JsonTextReader;
using PepperDash.Core.Logging;
namespace PepperDash.Core.JsonToSimpl;
@ -142,11 +143,10 @@ namespace PepperDash.Core.JsonToSimpl;
{
if (UnsavedValues.ContainsKey(path))
{
Debug.Console(0, "Master[{0}] WARNING - Attempt to add duplicate value for path '{1}'.\r Ingoring. Please ensure that path does not exist on multiple modules.", UniqueID, path);
this.LogWarning("Master[{0}] WARNING - Attempt to add duplicate value for path '{1}'.\r Ingoring. Please ensure that path does not exist on multiple modules.", UniqueID, path);
}
else
UnsavedValues.Add(path, value);
//Debug.Console(0, "Master[{0}] Unsaved size={1}", UniqueID, UnsavedValues.Count);
}
/// <summary>

View file

@ -8,6 +8,7 @@ using Crestron.SimplSharp.CrestronIO;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
using PepperDash.Core.Config;
using PepperDash.Core.Logging;
namespace PepperDash.Core.JsonToSimpl;
@ -61,7 +62,7 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
// If the portal file is xyz.json, then
// the file we want to check for first will be called xyz.local.json
var localFilepath = Path.ChangeExtension(PortalFilepath, "local.json");
Debug.Console(0, this, "Checking for local file {0}", localFilepath);
this.LogInformation("Checking for local file {0}", localFilepath);
var actualLocalFile = GetActualFileInfoFromPath(localFilepath);
if (actualLocalFile != null)
@ -73,7 +74,7 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
// and create the local.
else
{
Debug.Console(1, this, "Local JSON file not found {0}\rLoading portal JSON file", localFilepath);
this.LogInformation("Local JSON file not found {0}\rLoading portal JSON file", localFilepath);
var actualPortalFile = GetActualFileInfoFromPath(portalFilepath);
if (actualPortalFile != null)
{
@ -86,14 +87,13 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
else
{
var msg = string.Format("Portal JSON file not found: {0}", PortalFilepath);
Debug.Console(1, this, msg);
ErrorLog.Error(msg);
this.LogError(msg);
return;
}
}
// At this point we should have a local file. Do it.
Debug.Console(1, "Reading local JSON file {0}", ActualFilePath);
this.LogInformation("Reading local JSON file {0}", ActualFilePath);
string json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
@ -107,8 +107,7 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
catch (Exception e)
{
var msg = string.Format("JSON parsing failed:\r{0}", e);
CrestronConsole.PrintLine(msg);
ErrorLog.Error(msg);
this.LogError(msg);
return;
}
}
@ -149,30 +148,30 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
// Make each child update their values into master object
foreach (var child in Children)
{
Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
this.LogInformation("Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
child.UpdateInputsForMaster();
}
if (UnsavedValues == null || UnsavedValues.Count == 0)
{
Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID);
this.LogInformation("Master [{0}] No updated values to save. Skipping", UniqueID);
return;
}
lock (FileLock)
{
Debug.Console(1, "Saving");
this.LogInformation("Saving");
foreach (var path in UnsavedValues.Keys)
{
var tokenToReplace = JsonObject.SelectToken(path);
if (tokenToReplace != null)
{// It's found
tokenToReplace.Replace(UnsavedValues[path]);
Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path);
this.LogInformation("JSON Master[{0}] Updating '{1}'", UniqueID, path);
}
else // No token. Let's make one
{
//http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net
Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
this.LogWarning("JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
}
}
@ -186,8 +185,8 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
catch (Exception e)
{
string err = string.Format("Error writing JSON file:\r{0}", e);
Debug.Console(0, err);
ErrorLog.Warn(err);
this.LogException(e, "Error writing JSON file: {0}", e.Message);
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
return;
}
}

View file

@ -9,6 +9,9 @@ using System.Threading.Tasks;
namespace PepperDash.Core.Logging;
/// <summary>
/// Enriches log events with Crestron-specific context properties, such as the application name based on the device platform.
/// </summary>
public class CrestronEnricher : ILogEventEnricher
{
static readonly string _appName;
@ -27,6 +30,11 @@ public class CrestronEnricher : ILogEventEnricher
}
/// <summary>
/// Enriches the log event with Crestron-specific properties.
/// </summary>
/// <param name="logEvent"></param>
/// <param name="propertyFactory"></param>
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var property = propertyFactory.CreateProperty("App", _appName);

View file

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Timers;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronDataStore;
using Crestron.SimplSharp.CrestronIO;
@ -44,24 +45,32 @@ public static class Debug
private static ILogger _logger;
private static readonly LoggingLevelSwitch _consoleLoggingLevelSwitch;
private static readonly LoggingLevelSwitch consoleLoggingLevelSwitch;
private static readonly LoggingLevelSwitch _websocketLoggingLevelSwitch;
private static readonly LoggingLevelSwitch websocketLoggingLevelSwitch;
private static readonly LoggingLevelSwitch _errorLogLevelSwitch;
private static readonly LoggingLevelSwitch errorLogLevelSwitch;
private static readonly LoggingLevelSwitch _fileLevelSwitch;
private static readonly LoggingLevelSwitch fileLevelSwitch;
/// <summary>
/// The minimum log level for messages to be sent to the console sink
/// </summary>
public static LogEventLevel WebsocketMinimumLogLevel
{
get { return _websocketLoggingLevelSwitch.MinimumLevel; }
get { return websocketLoggingLevelSwitch.MinimumLevel; }
}
private static readonly DebugWebsocketSink _websocketSink;
private static readonly DebugWebsocketSink websocketSink;
/// <summary>
/// The DebugWebsocketSink instance used for sending log messages to connected websocket clients.
/// This is exposed publicly in case there is a need to call methods on the sink directly, such as SendMessageToClients.
/// For general logging purposes, use the LogMessage and LogError methods in this class which will send messages to all configured sinks including the websocket sink.
/// </summary>
public static DebugWebsocketSink WebsocketSink
{
get { return _websocketSink; }
get { return websocketSink; }
}
/// <summary>
@ -95,6 +104,9 @@ public static class Debug
private const int SaveTimeoutMs = 30000;
/// <summary>
/// Indicates whether the code is running on an appliance or not. Used to determine file paths and other appliance vs server differences
/// </summary>
public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance;
/// <summary>
@ -102,7 +114,12 @@ public static class Debug
/// </summary>
public static string PepperDashCoreVersion { get; private set; }
private static CTimer _saveTimer;
private static Timer _saveTimer;
private const int defaultConsoleDebugTimeoutMin = 120;
private static Timer consoleDebugTimer;
/// <summary>
/// When true, the IncludedExcludedKeys dict will contain keys to include.
@ -118,6 +135,10 @@ public static class Debug
private static LoggerConfiguration _loggerConfiguration;
/// <summary>
/// The default logger configuration used by the Debug class. Can be used as a base for creating custom logger configurations.
/// If changes are made to this configuration after initialization, call ResetLoggerConfiguration to have those changes reflected in the logger.
/// </summary>
public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration;
static Debug()
@ -126,6 +147,13 @@ public static class Debug
{
CrestronDataStoreStatic.InitCrestronDataStore();
consoleDebugTimer = new Timer(defaultConsoleDebugTimeoutMin * 60000) { AutoReset = false };
consoleDebugTimer.Elapsed += (s, e) =>
{
SetDebugLevel(LogEventLevel.Information);
CrestronConsole.ConsoleCommandResponse($"Console debug level reset to {LogEventLevel.Information} after timeout of {defaultConsoleDebugTimeoutMin} minutes");
};
var defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey);
var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey);
@ -134,15 +162,15 @@ public static class Debug
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey);
_consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
_websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
_errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
_fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
_websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ?
$@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" :
@ -158,14 +186,14 @@ public static class Debug
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.Enrich.With(new CrestronEnricher())
.WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLoggingLevelSwitch)
.WriteTo.Sink(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch)
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch)
.WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: consoleLoggingLevelSwitch)
.WriteTo.Sink(websocketSink, levelSwitch: websocketLoggingLevelSwitch)
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: errorLogLevelSwitch)
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
rollingInterval: RollingInterval.Day,
restrictedToMinimumLevel: LogEventLevel.Debug,
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
levelSwitch: _fileLevelSwitch
levelSwitch: fileLevelSwitch
);
// Instantiate the root logger
@ -214,9 +242,9 @@ public static class Debug
if (DoNotLoadConfigOnNextBoot)
CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber));
_consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
{
LogMessage(LogEventLevel.Information, "Console debug level set to {minimumLevel}", _consoleLoggingLevelSwitch.MinimumLevel);
LogMessage(LogEventLevel.Information, "Console debug level set to {minimumLevel}", consoleLoggingLevelSwitch.MinimumLevel);
};
}
catch (Exception ex)
@ -238,6 +266,12 @@ public static class Debug
return doNotLoad;
}
/// <summary>
/// Updates the LoggerConfiguration used by the Debug class.
/// This allows for changing logger settings such as sinks and output templates.
/// After calling this method, the new configuration will be used for all subsequent log messages.
/// </summary>
/// <param name="config"></param>
public static void UpdateLoggerConfiguration(LoggerConfiguration config)
{
_loggerConfiguration = config;
@ -245,6 +279,9 @@ public static class Debug
_logger = config.CreateLogger();
}
/// <summary>
/// Resets the LoggerConfiguration to the default configuration defined in this class.
/// </summary>
public static void ResetLoggerConfiguration()
{
_loggerConfiguration = _defaultLoggerConfiguration;
@ -332,7 +369,11 @@ public static class Debug
if (levelString.Trim() == "?")
{
CrestronConsole.ConsoleCommandResponse(
"Used to set the minimum level of debug messages to be printed to the console:\r\n" +
"[LogLevel] [TimeoutInMinutes]\r\n" +
"If TimeoutInMinutes is not provided, it will default to 120 minutes. If provided, the level will reset to Information after the timeout period elapses.\r\n" +
"LogLevel can be either a number from 0-5 or a log level name. If using a number, the mapping is as follows:\r\n" +
$"{_logLevels[0]} = 0\r\n" +
$"{_logLevels[1]} = 1\r\n" +
$"{_logLevels[2]} = 2\r\n" +
@ -344,10 +385,22 @@ public static class Debug
if (string.IsNullOrEmpty(levelString.Trim()))
{
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", _consoleLoggingLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", consoleLoggingLevelSwitch.MinimumLevel);
return;
}
// split on space to allow for potential future addition of timeout parameter without breaking existing command usage
var parts = Regex.Split(levelString.Trim(), @"\s+");
levelString = parts[0];
if (parts.Length > 1 && long.TryParse(parts[1], out var timeout))
{
timeout = Math.Max(timeout, 1); // enforce minimum timeout of 1 minute
consoleDebugTimer.Interval = timeout * 60000;
}
// first try to parse as int for backward compatibility with existing usage of numeric levels
if (int.TryParse(levelString, out var levelInt))
{
if (levelInt < 0 || levelInt > 5)
@ -377,7 +430,8 @@ public static class Debug
/// Sets the debug level
/// </summary>
/// <param name="level"> Valid values 0-5</param>
public static void SetDebugLevel(uint level)
/// <param name="timeout"> Timeout in minutes</param>
public static void SetDebugLevel(uint level, int timeout = defaultConsoleDebugTimeoutMin)
{
if (!_logLevels.TryGetValue(level, out var logLevel))
{
@ -385,18 +439,27 @@ public static class Debug
CrestronConsole.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}");
SetDebugLevel(logLevel);
SetDebugLevel(logLevel, timeout);
}
SetDebugLevel(logLevel);
SetDebugLevel(logLevel, timeout);
}
public static void SetDebugLevel(LogEventLevel level)
/// <summary>
/// Sets the debug level
/// </summary>
/// <param name="level"> The log level to set</param>
/// <param name="timeout"> Timeout in minutes</param>
public static void SetDebugLevel(LogEventLevel level, int timeout = defaultConsoleDebugTimeoutMin)
{
_consoleLoggingLevelSwitch.MinimumLevel = level;
consoleDebugTimer.Stop();
consoleDebugTimer.Interval = timeout * 60000;
consoleDebugTimer.Start();
consoleLoggingLevelSwitch.MinimumLevel = level;
CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}\r\n",
InitialParametersClass.ApplicationNumber, _consoleLoggingLevelSwitch.MinimumLevel);
InitialParametersClass.ApplicationNumber, consoleLoggingLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
@ -408,40 +471,52 @@ public static class Debug
CrestronConsole.PrintLine($"Error saving console debug level setting: {err}");
}
/// <summary>
/// Sets the debug level for the websocket sink
/// </summary>
/// <param name="level"></param>
public static void SetWebSocketMinimumDebugLevel(LogEventLevel level)
{
_websocketLoggingLevelSwitch.MinimumLevel = level;
websocketLoggingLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err);
LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", websocketLoggingLevelSwitch.MinimumLevel);
}
/// <summary>
/// Sets the minimum debug level for the error log sink
/// </summary>
/// <param name="level"></param>
public static void SetErrorLogMinimumDebugLevel(LogEventLevel level)
{
_errorLogLevelSwitch.MinimumLevel = level;
errorLogLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting: {error}", err);
LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", websocketLoggingLevelSwitch.MinimumLevel);
}
/// <summary>
/// Sets the minimum debug level for the file sink
/// </summary>
public static void SetFileMinimumDebugLevel(LogEventLevel level)
{
_errorLogLevelSwitch.MinimumLevel = level;
errorLogLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err);
LogMessage(LogEventLevel.Information, "File debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
LogMessage(LogEventLevel.Information, "File debug level set to {0}", websocketLoggingLevelSwitch.MinimumLevel);
}
/// <summary>
@ -626,21 +701,45 @@ public static class Debug
}
}
/// <summary>
/// Log a message with at the specified level.
/// </summary>
public static void LogMessage(LogEventLevel level, string message, params object[] args)
{
_logger.Write(level, message, args);
}
/// <summary>
/// Log a message with at the specified level and exception.
/// </summary>
/// <param name="level"></param>
/// <param name="ex"></param>
/// <param name="message"></param>
/// <param name="args"></param>
public static void LogMessage(LogEventLevel level, Exception ex, string message, params object[] args)
{
_logger.Write(level, ex, message, args);
}
/// <summary>
/// Log a message with at the specified level and device context.
/// </summary> <param name="level"></param>
/// <param name="keyed"></param>
/// <param name="message"></param>
/// <param name="args"></param>
public static void LogMessage(LogEventLevel level, IKeyed keyed, string message, params object[] args)
{
LogMessage(level, message, keyed, args);
}
/// <summary>
/// Log a message with at the specified level, exception, and device context.
/// </summary>
/// <param name="level"></param>
/// <param name="ex"></param>
/// <param name="device"></param>
/// <param name="message"></param>
/// <param name="args"></param>
public static void LogMessage(LogEventLevel level, Exception ex, IKeyed device, string message, params object[] args)
{
using (LogContext.PushProperty("Key", device?.Key))
@ -650,6 +749,13 @@ public static class Debug
}
#region Explicit methods for logging levels
/// <summary>
/// Log a message with Verbose level and device context.
/// </summary>
/// <param name="keyed"></param>
/// <param name="message"></param>
/// <param name="args"></param>
public static void LogVerbose(IKeyed keyed, string message, params object[] args)
{
using (LogContext.PushProperty("Key", keyed?.Key))
@ -658,6 +764,9 @@ public static class Debug
}
}
/// <summary>
/// Log a message with Verbose level, exception, and device context.
/// </summary>
public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args)
{
using (LogContext.PushProperty("Key", keyed?.Key))
@ -666,16 +775,25 @@ public static class Debug
}
}
/// <summary>
/// Log a message with Verbose level.
/// </summary>
public static void LogVerbose(string message, params object[] args)
{
_logger.Write(LogEventLevel.Verbose, message, args);
}
/// <summary>
/// Log a message with Verbose level and exception.
/// </summary>
public static void LogVerbose(Exception ex, string message, params object[] args)
{
_logger.Write(LogEventLevel.Verbose, ex, null, message, args);
}
/// <summary>
/// Log a message with Debug level and device context.
/// </summary>
public static void LogDebug(IKeyed keyed, string message, params object[] args)
{
using (LogContext.PushProperty("Key", keyed?.Key))
@ -684,6 +802,9 @@ public static class Debug
}
}
/// <summary>
/// Log a message with Debug level, exception, and device context.
/// </summary>
public static void LogDebug(Exception ex, IKeyed keyed, string message, params object[] args)
{
using (LogContext.PushProperty("Key", keyed?.Key))
@ -692,16 +813,25 @@ public static class Debug
}
}
/// <summary>
/// Log a message with Debug level.
/// </summary>
public static void LogDebug(string message, params object[] args)
{
_logger.Write(LogEventLevel.Debug, message, args);
}
/// <summary>
/// Log a message with Debug level and exception.
/// </summary>
public static void LogDebug(Exception ex, string message, params object[] args)
{
_logger.Write(LogEventLevel.Debug, ex, null, message, args);
}
/// <summary>
/// Log a message with Information level and device context.
/// </summary>
public static void LogInformation(IKeyed keyed, string message, params object[] args)
{
using (LogContext.PushProperty("Key", keyed?.Key))
@ -710,6 +840,9 @@ public static class Debug
}
}
/// <summary>
/// Log a message with Information level, exception, and device context.
/// </summary>
public static void LogInformation(Exception ex, IKeyed keyed, string message, params object[] args)
{
using (LogContext.PushProperty("Key", keyed?.Key))
@ -718,16 +851,25 @@ public static class Debug
}
}
/// <summary>
/// Log a message with Information level.
/// </summary>
public static void LogInformation(string message, params object[] args)
{
_logger.Write(LogEventLevel.Information, message, args);
}
/// <summary>
/// Log a message with Information level and exception.
/// </summary>
public static void LogInformation(Exception ex, string message, params object[] args)
{
_logger.Write(LogEventLevel.Information, ex, null, message, args);
}
/// <summary>
/// Log a message with Warning level and device context.
/// </summary>
public static void LogWarning(IKeyed keyed, string message, params object[] args)
{
using (LogContext.PushProperty("Key", keyed?.Key))
@ -736,6 +878,9 @@ public static class Debug
}
}
/// <summary>
/// Log a message with Warning level, exception, and device context.
/// </summary>
public static void LogWarning(Exception ex, IKeyed keyed, string message, params object[] args)
{
using (LogContext.PushProperty("Key", keyed?.Key))
@ -744,16 +889,25 @@ public static class Debug
}
}
/// <summary>
/// Log a message with Warning level.
/// </summary>
public static void LogWarning(string message, params object[] args)
{
_logger.Write(LogEventLevel.Warning, message, args);
}
/// <summary>
/// Log a message with Warning level and exception.
/// </summary>
public static void LogWarning(Exception ex, string message, params object[] args)
{
_logger.Write(LogEventLevel.Warning, ex, null, message, args);
}
/// <summary>
/// Log a message with Error level and device context.
/// </summary>
public static void LogError(IKeyed keyed, string message, params object[] args)
{
using (LogContext.PushProperty("Key", keyed?.Key))
@ -762,6 +916,9 @@ public static class Debug
}
}
/// <summary>
/// Log a message with Error level, exception, and device context.
/// </summary>
public static void LogError(Exception ex, IKeyed keyed, string message, params object[] args)
{
using (LogContext.PushProperty("Key", keyed?.Key))
@ -770,16 +927,25 @@ public static class Debug
}
}
/// <summary>
/// Log a message with Error level.
/// </summary>
public static void LogError(string message, params object[] args)
{
_logger.Write(LogEventLevel.Error, message, args);
}
/// <summary>
/// Log a message with Error level and exception.
/// </summary>
public static void LogError(Exception ex, string message, params object[] args)
{
_logger.Write(LogEventLevel.Error, ex, null, message, args);
}
/// <summary>
/// Log a message with Fatal level and device context.
/// </summary>
public static void LogFatal(IKeyed keyed, string message, params object[] args)
{
using (LogContext.PushProperty("Key", keyed?.Key))
@ -788,6 +954,9 @@ public static class Debug
}
}
/// <summary>
/// Log a message with Fatal level, exception, and device context.
/// </summary>
public static void LogFatal(Exception ex, IKeyed keyed, string message, params object[] args)
{
using (LogContext.PushProperty("Key", keyed?.Key))
@ -796,11 +965,19 @@ public static class Debug
}
}
/// <summary>
/// Log a message with Fatal level.
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void LogFatal(string message, params object[] args)
{
_logger.Write(LogEventLevel.Fatal, message, args);
}
/// <summary>
/// Log a message with Fatal level and exception.
/// </summary>
public static void LogFatal(Exception ex, string message, params object[] args)
{
_logger.Write(LogEventLevel.Fatal, ex, null, message, args);
@ -828,127 +1005,28 @@ public static class Debug
}
/// <summary>
/// Prints message to console if current debug level is equal to or higher than the level of this message.
/// Uses CrestronConsole.PrintLine.
/// </summary>
/// <param name="level"></param>
/// <param name="format">Console format string</param>
/// <param name="items">Object parameters</param>
[Obsolete("Use LogMessage methods. Will be removed in 2.2.0 and later versions")]
public static void Console(uint level, string format, params object[] items)
{
LogMessage(level, format, items);
//if (IsRunningOnAppliance)
//{
// CrestronConsole.PrintLine("[{0}]App {1} Lvl {2}:{3}", DateTime.Now.ToString("HH:mm:ss.fff"),
// InitialParametersClass.ApplicationNumber,
// level,
// string.Format(format, items));
//}
}
/// <summary>
/// Logs to Console when at-level, and all messages to error log, including device key
/// </summary>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
public static void Console(uint level, IKeyed dev, string format, params object[] items)
{
LogMessage(level, dev, format, items);
//if (Level >= level)
// Console(level, "[{0}] {1}", dev.Key, message);
}
/// <summary>
/// Prints message to console if current debug level is equal to or higher than the level of this message. Always sends message to Error Log.
/// Uses CrestronConsole.PrintLine.
/// </summary>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel,
string format, params object[] items)
{
LogMessage(level, dev, format, items);
}
/// <summary>
/// Logs to Console when at-level, and all messages to error log
/// </summary>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
public static void Console(uint level, ErrorLogLevel errorLogLevel,
string format, params object[] items)
{
LogMessage(level, format, items);
}
/// <summary>
/// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at
/// or above the level provided, then the output will be written to both console and the log. Otherwise
/// it will only be written to the log.
/// </summary>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
public static void ConsoleWithLog(uint level, string format, params object[] items)
{
LogMessage(level, format, items);
// var str = string.Format(format, items);
//if (Level >= level)
// CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
// CrestronLogger.WriteToLog(str, level);
}
/// <summary>
/// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at
/// or above the level provided, then the output will be written to both console and the log. Otherwise
/// it will only be written to the log.
/// </summary>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
public static void ConsoleWithLog(uint level, IKeyed dev, string format, params object[] items)
{
LogMessage(level, dev, format, items);
// var str = string.Format(format, items);
// CrestronLogger.WriteToLog(string.Format("[{0}] {1}", dev.Key, str), level);
}
/// <summary>
/// Prints to log and error log
/// </summary>
/// <param name="errorLogLevel"></param>
/// <param name="str"></param>
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
public static void LogError(ErrorLogLevel errorLogLevel, string str)
{
switch (errorLogLevel)
{
case ErrorLogLevel.Error:
LogMessage(LogEventLevel.Error, str);
break;
case ErrorLogLevel.Warning:
LogMessage(LogEventLevel.Warning, str);
break;
case ErrorLogLevel.Notice:
LogMessage(LogEventLevel.Information, str);
break;
}
}
/// <summary>
/// Writes the memory object after timeout
/// </summary>
static void SaveMemoryOnTimeout()
{
Console(0, "Saving debug settings");
LogInformation("Saving debug settings");
if (_saveTimer == null)
_saveTimer = new CTimer(o =>
{
_saveTimer = new Timer(SaveTimeoutMs) { AutoReset = false };
_saveTimer.Elapsed += (s, e) =>
{
_saveTimer = null;
SaveMemory();
}, SaveTimeoutMs);
};
_saveTimer.Start();
}
else
_saveTimer.Reset(SaveTimeoutMs);
{
_saveTimer.Stop();
_saveTimer.Interval = SaveTimeoutMs;
_saveTimer.Start();
}
}
/// <summary>
@ -964,7 +1042,7 @@ public static class Debug
{
var fileName = GetMemoryFileName();
LogMessage(LogEventLevel.Information, "Loading debug settings file from {fileName}", fileName);
LogInformation("Loading debug settings file from {fileName}", fileName);
using (var sw = new StreamWriter(fileName))
{
@ -1018,27 +1096,4 @@ public static class Debug
return string.Format("{0}{1}user{1}debugSettings{1}{2}.json", Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId);
}
/// <summary>
/// Error level to for message to be logged at
/// </summary>
public enum ErrorLogLevel
{
/// <summary>
/// Error
/// </summary>
Error,
/// <summary>
/// Warning
/// </summary>
Warning,
/// <summary>
/// Notice
/// </summary>
Notice,
/// <summary>
/// None
/// </summary>
None,
}
}

View file

@ -1,283 +0,0 @@
extern alias NewtonsoftJson;
using System;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
namespace PepperDash.Core;
/// <summary>
/// Represents a debugging context
/// </summary>
public class DebugContext
{
/// <summary>
/// Describes the folder location where a given program stores it's debug level memory. By default, the
/// file written will be named appNdebug where N is 1-10.
/// </summary>
public string Key { get; private set; }
///// <summary>
///// The name of the file containing the current debug settings.
///// </summary>
//string FileName = string.Format(@"\nvram\debug\app{0}Debug.json", InitialParametersClass.ApplicationNumber);
DebugContextSaveData SaveData;
int SaveTimeoutMs = 30000;
CTimer SaveTimer;
static List<DebugContext> Contexts = new List<DebugContext>();
/// <summary>
/// Creates or gets a debug context
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static DebugContext GetDebugContext(string key)
{
var context = Contexts.FirstOrDefault(c => c.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
if (context == null)
{
context = new DebugContext(key);
Contexts.Add(context);
}
return context;
}
/// <summary>
/// Do not use. For S+ access.
/// </summary>
public DebugContext() { }
DebugContext(string key)
{
Key = key;
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
{
// Add command to console
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
"appdebug:P [0-2]: Sets the application's console debug message level",
ConsoleAccessLevelEnum.AccessOperator);
}
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
LoadMemory();
}
/// <summary>
/// Used to save memory when shutting down
/// </summary>
/// <param name="programEventType"></param>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType == eProgramStatusEventType.Stopping)
{
if (SaveTimer != null)
{
SaveTimer.Stop();
SaveTimer = null;
}
Console(0, "Saving debug settings");
SaveMemory();
}
}
/// <summary>
/// Callback for console command
/// </summary>
/// <param name="levelString"></param>
public void SetDebugFromConsole(string levelString)
{
try
{
if (string.IsNullOrEmpty(levelString.Trim()))
{
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", SaveData.Level);
return;
}
SetDebugLevel(Convert.ToInt32(levelString));
}
catch
{
CrestronConsole.PrintLine("Usage: appdebug:P [0-2]");
}
}
/// <summary>
/// Sets the debug level
/// </summary>
/// <param name="level"> Valid values 0 (no debug), 1 (critical), 2 (all messages)</param>
public void SetDebugLevel(int level)
{
if (level <= 2)
{
SaveData.Level = level;
SaveMemoryOnTimeout();
CrestronConsole.PrintLine("[Application {0}], Debug level set to {1}",
InitialParametersClass.ApplicationNumber, SaveData.Level);
}
}
/// <summary>
/// Prints message to console if current debug level is equal to or higher than the level of this message.
/// Uses CrestronConsole.PrintLine.
/// </summary>
/// <param name="level"></param>
/// <param name="format">Console format string</param>
/// <param name="items">Object parameters</param>
public void Console(uint level, string format, params object[] items)
{
if (SaveData.Level >= level)
CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber,
string.Format(format, items));
}
/// <summary>
/// Appends a device Key to the beginning of a message
/// </summary>
public void Console(uint level, IKeyed dev, string format, params object[] items)
{
if (SaveData.Level >= level)
Console(level, "[{0}] {1}", dev.Key, string.Format(format, items));
}
/// <summary>
///
/// </summary>
/// <param name="level"></param>
/// <param name="dev"></param>
/// <param name="errorLogLevel"></param>
/// <param name="format"></param>
/// <param name="items"></param>
public void Console(uint level, IKeyed dev, Debug.ErrorLogLevel errorLogLevel,
string format, params object[] items)
{
if (SaveData.Level >= level)
{
var str = string.Format("[{0}] {1}", dev.Key, string.Format(format, items));
Console(level, str);
LogError(errorLogLevel, str);
}
}
/// <summary>
///
/// </summary>
/// <param name="level"></param>
/// <param name="errorLogLevel"></param>
/// <param name="format"></param>
/// <param name="items"></param>
public void Console(uint level, Debug.ErrorLogLevel errorLogLevel,
string format, params object[] items)
{
if (SaveData.Level >= level)
{
var str = string.Format(format, items);
Console(level, str);
LogError(errorLogLevel, str);
}
}
/// <summary>
///
/// </summary>
/// <param name="errorLogLevel"></param>
/// <param name="str"></param>
public void LogError(Debug.ErrorLogLevel errorLogLevel, string str)
{
string msg = string.Format("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
switch (errorLogLevel)
{
case Debug.ErrorLogLevel.Error:
ErrorLog.Error(msg);
break;
case Debug.ErrorLogLevel.Warning:
ErrorLog.Warn(msg);
break;
case Debug.ErrorLogLevel.Notice:
ErrorLog.Notice(msg);
break;
}
}
/// <summary>
/// Writes the memory object after timeout
/// </summary>
void SaveMemoryOnTimeout()
{
if (SaveTimer == null)
SaveTimer = new CTimer(o =>
{
SaveTimer = null;
SaveMemory();
}, SaveTimeoutMs);
else
SaveTimer.Reset(SaveTimeoutMs);
}
/// <summary>
/// Writes the memory - use SaveMemoryOnTimeout
/// </summary>
void SaveMemory()
{
using (StreamWriter sw = new StreamWriter(GetMemoryFileName()))
{
var json = JsonConvert.SerializeObject(SaveData);
sw.Write(json);
sw.Flush();
}
}
/// <summary>
///
/// </summary>
void LoadMemory()
{
var file = GetMemoryFileName();
if (File.Exists(file))
{
using (StreamReader sr = new StreamReader(file))
{
var data = JsonConvert.DeserializeObject<DebugContextSaveData>(sr.ReadToEnd());
if (data != null)
{
SaveData = data;
Debug.Console(1, "Debug memory restored from file");
return;
}
else
SaveData = new DebugContextSaveData();
}
}
}
/// <summary>
/// Helper to get the file path for this app's debug memory
/// </summary>
string GetMemoryFileName()
{
return string.Format(@"\NVRAM\debugSettings\program{0}-{1}", InitialParametersClass.ApplicationNumber, Key);
}
}
/// <summary>
///
/// </summary>
public class DebugContextSaveData
{
/// <summary>
///
/// </summary>
public int Level { get; set; }
}

View file

@ -105,7 +105,7 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
var hostName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0);
var domainName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, 0);
CrestronConsole.PrintLine(string.Format("DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress));
Debug.LogInformation("DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress);
var certificate = utility.CreateSelfSignedCertificate(string.Format("CN={0}.{1}", hostName, domainName), [string.Format("{0}.{1}", hostName, domainName), ipAddress], [KeyPurposeID.id_kp_serverAuth, KeyPurposeID.id_kp_clientAuth]);
@ -118,8 +118,8 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
}
catch (Exception ex)
{
//Debug.Console(0, "WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace);
CrestronConsole.PrintLine("WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace);
Debug.LogError(ex, "WSS CreateCert Failed: {0}", ex.Message);
Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
}
}
@ -149,7 +149,7 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
/// <param name="port">The port number on which the WebSocket server will listen. Must be a valid, non-negative port number.</param>
public void StartServerAndSetPort(int port)
{
Debug.Console(0, "Starting Websocket Server on port: {0}", port);
Debug.LogInformation("Starting Websocket Server on port: {0}", port);
Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword);
@ -163,7 +163,7 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
if (!string.IsNullOrWhiteSpace(certPath))
{
Debug.Console(0, "Assigning SSL Configuration");
Debug.LogInformation("Assigning SSL Configuration");
_httpsServer.SslConfiguration.ServerCertificate = new X509Certificate2(certPath, certPassword);
_httpsServer.SslConfiguration.ClientCertificateRequired = false;
@ -172,13 +172,13 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
//this is just to test, you might want to actually validate
_httpsServer.SslConfiguration.ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
Debug.Console(0, "HTTPS ClientCerticateValidation Callback triggered");
Debug.LogInformation("HTTPS ClientCerticateValidation Callback triggered");
return true;
};
}
Debug.Console(0, "Adding Debug Client Service");
Debug.LogInformation("Adding Debug Client Service");
_httpsServer.AddWebSocketService<DebugClient>(_path);
Debug.Console(0, "Assigning Log Info");
Debug.LogInformation("Assigning Log Info");
_httpsServer.Log.Level = LogLevel.Trace;
_httpsServer.Log.Output = (d, s) =>
{
@ -208,17 +208,17 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
level = 4;
break;
}
Debug.Console(level, "{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s);
Debug.LogInformation("{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s);
};
Debug.Console(0, "Starting");
Debug.LogInformation("Starting");
_httpsServer.Start();
Debug.Console(0, "Ready");
Debug.LogInformation("Ready");
}
catch (Exception ex)
{
Debug.Console(0, "WebSocket Failed to start {0}", ex.Message);
Debug.LogError(ex, "WebSocket Failed to start {0}", ex.Message);
Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
}
}
@ -229,7 +229,7 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
/// calling this method, the server will no longer accept or process incoming connections.</remarks>
public void StopServer()
{
Debug.Console(0, "Stopping Websocket Server");
Debug.LogInformation("Stopping Websocket Server");
_httpsServer?.Stop();
_httpsServer = null;
@ -291,20 +291,18 @@ public class DebugClient : WebSocketBehavior
/// <summary>
/// Initializes a new instance of the <see cref="DebugClient"/> class.
/// </summary>
/// <remarks>This constructor creates a new <see cref="DebugClient"/> instance and logs its
/// creation using the <see cref="Debug.Console(int, string)"/> method with a debug level of 0.</remarks>
public DebugClient()
{
Debug.Console(0, "DebugClient Created");
Debug.LogInformation("DebugClient Created");
}
/// <inheritdoc/>
protected override void OnOpen()
{
base.OnOpen();
var url = Context.WebSocket.Url;
Debug.Console(0, Debug.ErrorLogLevel.Notice, "New WebSocket Connection from: {0}", url);
Debug.LogInformation("New WebSocket Connection from: {0}", url);
_connectionTime = DateTime.Now;
}
@ -314,7 +312,7 @@ public class DebugClient : WebSocketBehavior
{
base.OnMessage(e);
Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data);
Debug.LogVerbose("WebSocket UiClient Message: {0}", e.Data);
}
/// <inheritdoc/>
@ -322,8 +320,7 @@ public class DebugClient : WebSocketBehavior
{
base.OnClose(e);
Debug.Console(0, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
Debug.LogDebug("WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
}
/// <inheritdoc/>
@ -331,6 +328,7 @@ public class DebugClient : WebSocketBehavior
{
base.OnError(e);
Debug.Console(2, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message);
Debug.LogError(e.Exception, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message);
Debug.LogVerbose("Stack Trace:\r{0}", e.Exception.StackTrace);
}
}

View file

@ -1,246 +1,252 @@
using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using System.Timers;
namespace PepperDash.Core.PasswordManagement;
/// <summary>
/// Allows passwords to be stored and managed
/// </summary>
public class PasswordManager
public class PasswordManager
{
/// <summary>
/// Public dictionary of known passwords
/// </summary>
public static Dictionary<uint, string> Passwords = new Dictionary<uint, string>();
/// <summary>
/// Private dictionary, used when passwords are updated
/// </summary>
private Dictionary<uint, string> _passwords = new Dictionary<uint, string>();
/// <summary>
/// Timer used to wait until password changes have stopped before updating the dictionary
/// </summary>
Timer PasswordTimer;
/// <summary>
/// Timer length
/// </summary>
public long PasswordTimerElapsedMs = 5000;
/// <summary>
/// Boolean event
/// </summary>
public event EventHandler<BoolChangeEventArgs> BoolChange;
/// <summary>
/// Ushort event
/// </summary>
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
/// <summary>
/// String event
/// </summary>
public event EventHandler<StringChangeEventArgs> StringChange;
/// <summary>
/// Event to notify clients of an updated password at the specified index (uint)
/// </summary>
public static event EventHandler<StringChangeEventArgs> PasswordChange;
/// <summary>
/// Constructor
/// </summary>
public PasswordManager()
{
/// <summary>
/// Public dictionary of known passwords
/// </summary>
public static Dictionary<uint, string> Passwords = new Dictionary<uint, string>();
/// <summary>
/// Private dictionary, used when passwords are updated
/// </summary>
private Dictionary<uint, string> _passwords = new Dictionary<uint, string>();
/// <summary>
/// Timer used to wait until password changes have stopped before updating the dictionary
/// </summary>
CTimer PasswordTimer;
/// <summary>
/// Timer length
/// </summary>
public long PasswordTimerElapsedMs = 5000;
}
/// <summary>
/// Boolean event
/// </summary>
public event EventHandler<BoolChangeEventArgs> BoolChange;
/// <summary>
/// Ushort event
/// </summary>
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
/// <summary>
/// String event
/// </summary>
public event EventHandler<StringChangeEventArgs> StringChange;
/// <summary>
/// Event to notify clients of an updated password at the specified index (uint)
/// </summary>
public static event EventHandler<StringChangeEventArgs> PasswordChange;
/// <summary>
/// Initialize password manager
/// </summary>
public void Initialize()
{
if (Passwords == null)
Passwords = new Dictionary<uint, string>();
/// <summary>
/// Constructor
/// </summary>
public PasswordManager()
if (_passwords == null)
_passwords = new Dictionary<uint, string>();
OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange);
}
/// <summary>
/// Updates password stored in the dictonary
/// </summary>
/// <param name="key"></param>
/// <param name="password"></param>
/// <summary>
/// UpdatePassword method
/// </summary>
public void UpdatePassword(ushort key, string password)
{
// validate the parameters
if (key > 0 && string.IsNullOrEmpty(password))
{
Debug.LogDebug("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password);
return;
}
/// <summary>
/// Initialize password manager
/// </summary>
public void Initialize()
try
{
if (Passwords == null)
Passwords = new Dictionary<uint, string>();
// if key exists, update the value
if (_passwords.ContainsKey(key))
_passwords[key] = password;
// else add the key & value
else
_passwords.Add(key, password);
if (_passwords == null)
_passwords = new Dictionary<uint, string>();
Debug.LogDebug("PasswordManager.UpdatePassword: _password[{0}] = {1}", key, _passwords[key]);
OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange);
}
/// <summary>
/// Updates password stored in the dictonary
/// </summary>
/// <param name="key"></param>
/// <param name="password"></param>
/// <summary>
/// UpdatePassword method
/// </summary>
public void UpdatePassword(ushort key, string password)
{
// validate the parameters
if (key > 0 && string.IsNullOrEmpty(password))
if (PasswordTimer == null)
{
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password));
return;
PasswordTimer = new Timer(PasswordTimerElapsedMs) { AutoReset = false };
PasswordTimer.Elapsed += (s, e) => PasswordTimerElapsed(s, e);
PasswordTimer.Start();
Debug.LogDebug("PasswordManager.UpdatePassword: Timer Started");
OnBoolChange(true, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
}
try
{
// if key exists, update the value
if(_passwords.ContainsKey(key))
_passwords[key] = password;
// else add the key & value
else
_passwords.Add(key, password);
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: _password[{0}] = {1}", key, _passwords[key]));
if (PasswordTimer == null)
{
PasswordTimer = new CTimer((o) => PasswordTimerElapsed(), PasswordTimerElapsedMs);
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Started"));
OnBoolChange(true, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
}
else
{
PasswordTimer.Reset(PasswordTimerElapsedMs);
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Reset"));
}
}
catch (Exception e)
{
var msg = string.Format("PasswordManager.UpdatePassword key-value[{0}, {1}] failed:\r{2}", key, password, e);
Debug.Console(1, msg);
}
}
/// <summary>
/// CTimer callback function
/// </summary>
private void PasswordTimerElapsed()
{
try
else
{
PasswordTimer.Stop();
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: CTimer Stopped"));
OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
foreach (var pw in _passwords)
PasswordTimer.Interval = PasswordTimerElapsedMs;
PasswordTimer.Start();
Debug.LogDebug("PasswordManager.UpdatePassword: Timer Reset");
}
}
catch (Exception e)
{
var msg = string.Format("PasswordManager.UpdatePassword key-value[{0}, {1}] failed:\r{2}", key, password, e);
Debug.LogError(e, msg);
Debug.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
}
}
/// <summary>
/// Timer callback function
/// </summary>
private void PasswordTimerElapsed(object sender, ElapsedEventArgs e)
{
try
{
PasswordTimer.Stop();
Debug.LogDebug("PasswordManager.PasswordTimerElapsed: Timer Stopped");
OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
foreach (var pw in _passwords)
{
// if key exists, continue
if (Passwords.ContainsKey(pw.Key))
{
// if key exists, continue
if (Passwords.ContainsKey(pw.Key))
Debug.LogDebug("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value);
if (Passwords[pw.Key] != _passwords[pw.Key])
{
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value));
if (Passwords[pw.Key] != _passwords[pw.Key])
{
Passwords[pw.Key] = _passwords[pw.Key];
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: Updated Password[{0} = {1}", pw.Key, Passwords[pw.Key]));
OnPasswordChange(Passwords[pw.Key], (ushort)pw.Key, PasswordManagementConstants.StringValueChange);
}
}
// else add the key & value
else
{
Passwords.Add(pw.Key, pw.Value);
Passwords[pw.Key] = _passwords[pw.Key];
Debug.LogDebug("PasswordManager.PasswordTimerElapsed: Updated Password[{0} = {1}", pw.Key, Passwords[pw.Key]);
OnPasswordChange(Passwords[pw.Key], (ushort)pw.Key, PasswordManagementConstants.StringValueChange);
}
}
OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
}
catch (Exception e)
{
var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", e);
Debug.Console(1, msg);
// else add the key & value
else
{
Passwords.Add(pw.Key, pw.Value);
}
}
OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
}
/// <summary>
/// Method to change the default timer value, (default 5000ms/5s)
/// </summary>
/// <param name="time"></param>
/// <summary>
/// PasswordTimerMs method
/// </summary>
public void PasswordTimerMs(ushort time)
catch (Exception ex)
{
PasswordTimerElapsedMs = Convert.ToInt64(time);
var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", ex.Message);
Debug.LogError(ex, msg);
Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
}
}
/// <summary>
/// Helper method for debugging to see what passwords are in the lists
/// </summary>
public void ListPasswords()
{
Debug.Console(0, "PasswordManager.ListPasswords:\r");
foreach (var pw in Passwords)
Debug.Console(0, "Passwords[{0}]: {1}\r", pw.Key, pw.Value);
Debug.Console(0, "\n");
foreach (var pw in _passwords)
Debug.Console(0, "_passwords[{0}]: {1}\r", pw.Key, pw.Value);
}
/// <summary>
/// Method to change the default timer value, (default 5000ms/5s)
/// </summary>
/// <param name="time"></param>
/// <summary>
/// PasswordTimerMs method
/// </summary>
public void PasswordTimerMs(ushort time)
{
PasswordTimerElapsedMs = Convert.ToInt64(time);
}
/// <summary>
/// Protected boolean change event handler
/// </summary>
/// <param name="state"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnBoolChange(bool state, ushort index, ushort type)
{
var handler = BoolChange;
if (handler != null)
{
var args = new BoolChangeEventArgs(state, type);
args.Index = index;
BoolChange(this, args);
}
}
/// <summary>
/// Helper method for debugging to see what passwords are in the lists
/// </summary>
public void ListPasswords()
{
Debug.LogInformation("PasswordManager.ListPasswords:\r");
foreach (var pw in Passwords)
Debug.LogInformation("Passwords[{0}]: {1}\r", pw.Key, pw.Value);
Debug.LogInformation("\n");
foreach (var pw in _passwords)
Debug.LogInformation("_passwords[{0}]: {1}\r", pw.Key, pw.Value);
}
/// <summary>
/// Protected ushort change event handler
/// </summary>
/// <param name="value"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnUshrtChange(ushort value, ushort index, ushort type)
/// <summary>
/// Protected boolean change event handler
/// </summary>
/// <param name="state"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnBoolChange(bool state, ushort index, ushort type)
{
var handler = BoolChange;
if (handler != null)
{
var handler = UshrtChange;
if (handler != null)
{
var args = new UshrtChangeEventArgs(value, type);
args.Index = index;
UshrtChange(this, args);
}
var args = new BoolChangeEventArgs(state, type);
args.Index = index;
BoolChange(this, args);
}
}
/// <summary>
/// Protected string change event handler
/// </summary>
/// <param name="value"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnStringChange(string value, ushort index, ushort type)
/// <summary>
/// Protected ushort change event handler
/// </summary>
/// <param name="value"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnUshrtChange(ushort value, ushort index, ushort type)
{
var handler = UshrtChange;
if (handler != null)
{
var handler = StringChange;
if (handler != null)
{
var args = new StringChangeEventArgs(value, type);
args.Index = index;
StringChange(this, args);
}
var args = new UshrtChangeEventArgs(value, type);
args.Index = index;
UshrtChange(this, args);
}
}
/// <summary>
/// Protected password change event handler
/// </summary>
/// <param name="value"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnPasswordChange(string value, ushort index, ushort type)
/// <summary>
/// Protected string change event handler
/// </summary>
/// <param name="value"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnStringChange(string value, ushort index, ushort type)
{
var handler = StringChange;
if (handler != null)
{
var handler = PasswordChange;
if (handler != null)
{
var args = new StringChangeEventArgs(value, type);
args.Index = index;
PasswordChange(this, args);
}
var args = new StringChangeEventArgs(value, type);
args.Index = index;
StringChange(this, args);
}
}
}
/// <summary>
/// Protected password change event handler
/// </summary>
/// <param name="value"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnPasswordChange(string value, ushort index, ushort type)
{
var handler = PasswordChange;
if (handler != null)
{
var args = new StringChangeEventArgs(value, type);
args.Index = index;
PasswordChange(this, args);
}
}
}