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

View file

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

View file

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

View file

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

View file

@ -11,10 +11,9 @@ PepperDash Technology Corporation reserves all rights under applicable laws.
------------------------------------ */ ------------------------------------ */
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Timers;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets; using Crestron.SimplSharp.CrestronSockets;
using PepperDash.Core.Logging; using PepperDash.Core.Logging;
@ -210,7 +209,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
/// <summary> /// <summary>
/// private Timer for auto reconnect /// private Timer for auto reconnect
/// </summary> /// </summary>
CTimer RetryTimer; Timer RetryTimer;
/// <summary> /// <summary>
@ -237,13 +236,13 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
/// </summary> /// </summary>
public int HeartbeatInterval = 50000; public int HeartbeatInterval = 50000;
CTimer HeartbeatSendTimer; Timer HeartbeatSendTimer;
CTimer HeartbeatAckTimer; Timer HeartbeatAckTimer;
/// <summary> /// <summary>
/// Used to force disconnection on a dead connect attempt /// Used to force disconnection on a dead connect attempt
/// </summary> /// </summary>
CTimer ConnectFailTimer; Timer ConnectFailTimer;
CTimer WaitForSharedKey; Timer WaitForSharedKey;
private int ConnectionCount; private int ConnectionCount;
/// <summary> /// <summary>
/// Internal secure client /// Internal secure client
@ -303,7 +302,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
{ {
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused) 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; ProgramIsStopping = true;
Disconnect(); Disconnect();
} }
@ -316,17 +315,17 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
public void Connect() public void Connect()
{ {
ConnectionCount++; ConnectionCount++;
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount); this.LogDebug("Attempting connect Count:{0}", ConnectionCount);
if (IsConnected) if (IsConnected)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring."); this.LogInformation("Already connected. Ignoring.");
return; return;
} }
if (IsTryingToConnect) if (IsTryingToConnect)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring."); this.LogInformation("Already trying to connect. Ignoring.");
return; return;
} }
try try
@ -339,17 +338,17 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
} }
if (string.IsNullOrEmpty(Hostname)) if (string.IsNullOrEmpty(Hostname))
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set"); this.LogWarning("DynamicTcpClient: No address set");
return; return;
} }
if (Port < 1 || Port > 65535) if (Port < 1 || Port > 65535)
{ {
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port"); this.LogWarning("DynamicTcpClient: Invalid port");
return; return;
} }
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired) 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; return;
} }
@ -370,9 +369,10 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff"); //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) if (IsTryingToConnect)
{ {
IsTryingToConnect = false; IsTryingToConnect = false;
@ -384,12 +384,13 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
//SecureClient.DisconnectFromServer(); //SecureClient.DisconnectFromServer();
//CheckClosedAndTryReconnect(); //CheckClosedAndTryReconnect();
} }
}, 30000); };
ConnectFailTimer.Start();
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount); this.LogDebug("Making Connection Count:{0}", ConnectionCount);
Client.ConnectToServerAsync(o => Client.ConnectToServerAsync(o =>
{ {
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount); this.LogDebug("ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
if (ConnectFailTimer != null) if (ConnectFailTimer != null)
{ {
@ -399,22 +400,22 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED) 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); o.ReceiveDataAsync(Receive);
if (SharedKeyRequired) if (SharedKeyRequired)
{ {
WaitingForSharedKeyResponse = true; 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, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup // This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
o.DisconnectFromServer(); o.DisconnectFromServer();
//CheckClosedAndTryReconnect(); //CheckClosedAndTryReconnect();
//OnClientReadyForcommunications(false); // Should send false event //OnClientReadyForcommunications(false); // Should send false event
}, 15000); };
WaitForSharedKey.Start();
} }
else else
{ {
@ -428,14 +429,15 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
} }
else else
{ {
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus); this.LogWarning("Connect attempt failed {0}", o.ClientStatus);
CheckClosedAndTryReconnect(); CheckClosedAndTryReconnect();
} }
}); });
} }
catch (Exception ex) 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; IsTryingToConnect = false;
CheckClosedAndTryReconnect(); CheckClosedAndTryReconnect();
} }
@ -472,7 +474,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
if (Client != null) if (Client != null)
{ {
//SecureClient.DisconnectFromServer(); //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.SocketStatusChange -= Client_SocketStatusChange;
Client.Dispose(); Client.Dispose();
Client = null; Client = null;
@ -494,20 +496,22 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
{ {
if (Client != null) if (Client != null)
{ {
Debug.Console(2, this, "Cleaning up remotely closed/failed connection."); this.LogVerbose("Cleaning up remotely closed/failed connection.");
Cleanup(); Cleanup();
} }
if (!DisconnectCalledByUser && AutoReconnect) if (!DisconnectCalledByUser && AutoReconnect)
{ {
var halfInterval = AutoReconnectIntervalMs / 2; var halfInterval = AutoReconnectIntervalMs / 2;
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs; 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) if (RetryTimer != null)
{ {
RetryTimer.Stop(); RetryTimer.Stop();
RetryTimer = null; 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(); var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length); 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 (!string.IsNullOrEmpty(checkHeartbeat(str)))
{ {
if (SharedKeyRequired && str == "SharedKey:") 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"); SendText(SharedKey + "\n");
} }
else if (SharedKeyRequired && str == "Shared Key Match") else if (SharedKeyRequired && str == "Shared Key Match")
{ {
StopWaitForSharedKeyTimer(); StopWaitForSharedKeyTimer();
Debug.Console(2, this, "Shared key confirmed. Ready for communication"); this.LogVerbose("Shared key confirmed. Ready for communication");
OnClientReadyForcommunications(true); // Successful key exchange OnClientReadyForcommunications(true); // Successful key exchange
} }
else else
@ -553,7 +557,8 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
} }
catch (Exception ex) 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) if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
@ -564,15 +569,19 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
{ {
if (HeartbeatEnabled) if (HeartbeatEnabled)
{ {
Debug.Console(2, this, "Starting Heartbeat"); this.LogVerbose("Starting Heartbeat");
if (HeartbeatSendTimer == null) 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) 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) if (HeartbeatSendTimer != null)
{ {
Debug.Console(2, this, "Stoping Heartbeat Send"); this.LogVerbose("Stoping Heartbeat Send");
HeartbeatSendTimer.Stop(); HeartbeatSendTimer.Stop();
HeartbeatSendTimer = null; HeartbeatSendTimer = null;
} }
if (HeartbeatAckTimer != null) if (HeartbeatAckTimer != null)
{ {
Debug.Console(2, this, "Stoping Heartbeat Ack"); this.LogVerbose("Stoping Heartbeat Ack");
HeartbeatAckTimer.Stop(); HeartbeatAckTimer.Stop();
HeartbeatAckTimer = null; HeartbeatAckTimer = null;
} }
@ -597,7 +606,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
void SendHeartbeat(object notused) void SendHeartbeat(object notused)
{ {
this.SendText(HeartbeatString); 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) if (HeartbeatAckTimer != null)
{ {
HeartbeatAckTimer.Reset(HeartbeatInterval * 2); HeartbeatAckTimer.Stop();
HeartbeatAckTimer.Interval = HeartbeatInterval * 2;
HeartbeatAckTimer.Start();
} }
else 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; return remainingText;
} }
} }
@ -630,7 +643,8 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message); this.LogException(ex, "Error checking heartbeat: {0}", ex.Message);
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
} }
return received; return received;
} }
@ -644,7 +658,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
if (IsConnected) 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"); SendText("Heartbeat not received by server, closing connection");
CheckClosedAndTryReconnect(); CheckClosedAndTryReconnect();
} }
@ -652,7 +666,8 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
} }
catch (Exception ex) 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????? // HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
if (n <= 0) 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) 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) 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 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(); OnConnectionChange();
@ -744,7 +761,8 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
} }
catch (Exception ex) 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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Timers;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets; using Crestron.SimplSharp.CrestronSockets;
using PepperDash.Core.Logging; using PepperDash.Core.Logging;
@ -80,7 +80,7 @@ public class GenericTcpIpServer : Device
/// <summary> /// <summary>
/// Timer to operate the bandaid monitor client in a loop. /// Timer to operate the bandaid monitor client in a loop.
/// </summary> /// </summary>
CTimer MonitorClientTimer; Timer MonitorClientTimer;
/// <summary> /// <summary>
/// ///
@ -250,7 +250,7 @@ public class GenericTcpIpServer : Device
public string HeartbeatStringToMatch { get; set; } public string HeartbeatStringToMatch { get; set; }
//private timers for Heartbeats per client //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 //flags to show the secure server is waiting for client at index to send the shared key
List<uint> WaitingForSharedKey = new List<uint>(); List<uint> WaitingForSharedKey = new List<uint>();
@ -577,11 +577,17 @@ public class GenericTcpIpServer : Device
if (noDelimiter.Contains(HeartbeatStringToMatch)) if (noDelimiter.Contains(HeartbeatStringToMatch))
{ {
if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); {
HeartbeatTimerDictionary[clientIndex].Stop();
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
HeartbeatTimerDictionary[clientIndex].Start();
}
else else
{ {
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
heartbeatTimer.Start();
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
} }
this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex); this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
// Return Heartbeat // Return Heartbeat
@ -592,11 +598,17 @@ public class GenericTcpIpServer : Device
else else
{ {
if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs); {
HeartbeatTimerDictionary[clientIndex].Stop();
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
HeartbeatTimerDictionary[clientIndex].Start();
}
else else
{ {
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs); var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer); heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
heartbeatTimer.Start();
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
} }
this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", received, clientIndex); 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); SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
var discoResult = myTcpServer.Disconnect(clientIndex); var discoResult = myTcpServer.Disconnect(clientIndex);
//Debug.Console(1, this, "{0}", discoResult);
if (HeartbeatTimerDictionary.ContainsKey(clientIndex)) if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
{ {
@ -661,7 +672,8 @@ public class GenericTcpIpServer : Device
} }
catch (Exception ex) 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)) 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) 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, // server.State,
// MaxClients, // MaxClients,
// ServerStopped); // ServerStopped);
@ -929,7 +944,9 @@ public class GenericTcpIpServer : Device
{ {
return; return;
} }
MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000); MonitorClientTimer = new Timer(60000) { AutoReset = false };
MonitorClientTimer.Elapsed += (s, e) => RunMonitorClient();
MonitorClientTimer.Start();
} }
/// <summary> /// <summary>

View file

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

View file

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

View file

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using PepperDash.Core.Logging;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue; using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
namespace PepperDash.Core.JsonToSimpl; namespace PepperDash.Core.JsonToSimpl;
@ -91,7 +92,7 @@ namespace PepperDash.Core.JsonToSimpl;
if (Master != null) if (Master != null)
Master.AddChild(this); Master.AddChild(this);
else 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> /// <summary>
@ -107,7 +108,7 @@ namespace PepperDash.Core.JsonToSimpl;
/// </summary> /// </summary>
public void SetBoolPath(ushort index, string path) 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; if (path == null || path.Trim() == string.Empty) return;
BoolPaths[index] = path; BoolPaths[index] = path;
} }
@ -117,7 +118,7 @@ namespace PepperDash.Core.JsonToSimpl;
/// </summary> /// </summary>
public void SetUshortPath(ushort index, string path) 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; if (path == null || path.Trim() == string.Empty) return;
UshortPaths[index] = path; UshortPaths[index] = path;
} }
@ -127,7 +128,7 @@ namespace PepperDash.Core.JsonToSimpl;
/// </summary> /// </summary>
public void SetStringPath(ushort index, string path) 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; if (path == null || path.Trim() == string.Empty) return;
StringPaths[index] = path; StringPaths[index] = path;
} }
@ -140,13 +141,13 @@ namespace PepperDash.Core.JsonToSimpl;
{ {
if (!LinkedToObject) if (!LinkedToObject)
{ {
Debug.Console(1, this, "Not linked to object in file. Skipping"); this.LogDebug("Not linked to object in file. Skipping");
return; return;
} }
if (SetAllPathsDelegate == null) if (SetAllPathsDelegate == null)
{ {
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring ProcessAll"); this.LogDebug("No SetAllPathsDelegate set. Ignoring ProcessAll");
return; return;
} }
SetAllPathsDelegate(); SetAllPathsDelegate();
@ -206,11 +207,11 @@ namespace PepperDash.Core.JsonToSimpl;
bool Process(string path, out string response) bool Process(string path, out string response)
{ {
path = GetFullPath(path); path = GetFullPath(path);
Debug.Console(1, "JSON Child[{0}] Processing {1}", Key, path); this.LogDebug("JSON Child[{0}] Processing {1}", Key, path);
response = ""; response = "";
if (Master == null) 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; return false;
} }
@ -233,7 +234,7 @@ namespace PepperDash.Core.JsonToSimpl;
response = (t.HasValues ? t.Children().Count() : 0).ToString(); response = (t.HasValues ? t.Children().Count() : 0).ToString();
else else
response = (string)t; response = (string)t;
Debug.Console(1, " ='{0}'", response); this.LogDebug(" ='{0}'", response);
return true; return true;
} }
} }
@ -259,13 +260,13 @@ namespace PepperDash.Core.JsonToSimpl;
{ {
if (!LinkedToObject) if (!LinkedToObject)
{ {
Debug.Console(1, this, "Not linked to object in file. Skipping"); this.LogDebug("Not linked to object in file. Skipping");
return; return;
} }
if (SetAllPathsDelegate == null) if (SetAllPathsDelegate == null)
{ {
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster"); this.LogDebug("No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster");
return; return;
} }
SetAllPathsDelegate(); SetAllPathsDelegate();
@ -327,7 +328,7 @@ namespace PepperDash.Core.JsonToSimpl;
var path = GetFullPath(keyPath); var path = GetFullPath(keyPath);
try 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); //var token = Master.JsonObject.SelectToken(path);
//if (token != null) // The path exists in the file //if (token != null) // The path exists in the file
@ -335,7 +336,7 @@ namespace PepperDash.Core.JsonToSimpl;
} }
catch (Exception e) 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 System.Text.RegularExpressions;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronIO;
using PepperDash.Core.Logging;
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting; using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject; using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue; using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
@ -129,7 +130,7 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
var fileName = Path.GetFileName(Filepath); var fileName = Path.GetFileName(Filepath);
OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange); 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)) if (Directory.Exists(fileDirectory))
{ {
@ -143,7 +144,7 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
var msg = string.Format("JSON file not found: {0}", Filepath); var msg = string.Format("JSON file not found: {0}", Filepath);
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange); OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(msg); CrestronConsole.PrintLine(msg);
ErrorLog.Error(msg); this.LogError(msg);
return; return;
} }
@ -152,18 +153,18 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
ActualFilePath = actualFile.FullName; ActualFilePath = actualFile.FullName;
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange); 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; Filename = actualFile.Name;
OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange); OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange);
OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange); 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); FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator);
OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange); OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange);
OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange); 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); var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
@ -176,7 +177,7 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
else else
{ {
OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange); 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) catch (Exception e)
@ -184,12 +185,12 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message); var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message);
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange); OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(msg); 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); var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace);
OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange); OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(stackTrace); 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 // Make each child update their values into master object
foreach (var child in Children) 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(); child.UpdateInputsForMaster();
} }
if (UnsavedValues == null || UnsavedValues.Count == 0) 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; return;
} }
lock (FileLock) lock (FileLock)
{ {
Debug.Console(1, "Saving"); this.LogInformation("Saving");
foreach (var path in UnsavedValues.Keys) foreach (var path in UnsavedValues.Keys)
{ {
var tokenToReplace = JsonObject.SelectToken(path); var tokenToReplace = JsonObject.SelectToken(path);
if (tokenToReplace != null) if (tokenToReplace != null)
{// It's found {// It's found
tokenToReplace.Replace(UnsavedValues[path]); 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 else // No token. Let's make one
{ {
//http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net //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)) using (StreamWriter sw = new StreamWriter(ActualFilePath))
@ -282,11 +251,13 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
catch (Exception e) catch (Exception e)
{ {
string err = string.Format("Error writing JSON file:\r{0}", e); string err = string.Format("Error writing JSON file:\r{0}", e);
Debug.Console(0, err); this.LogException(e, "Error writing JSON file: {0}", e.Message);
ErrorLog.Warn(err); this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
return; return;
} }
} }
} }
} }
} }

View file

@ -3,6 +3,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using PepperDash.Core.Logging;
using Renci.SshNet.Messages;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject; using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue; using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
@ -74,7 +76,8 @@ namespace PepperDash.Core.JsonToSimpl;
} }
catch (Exception e) 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 // Make each child update their values into master object
foreach (var child in Children) 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(); child.UpdateInputsForMaster();
} }
if (UnsavedValues == null || UnsavedValues.Count == 0) 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; return;
} }
lock (WriteLock) lock (WriteLock)
{ {
Debug.Console(1, this, "Saving"); this.LogDebug("Saving");
foreach (var path in UnsavedValues.Keys) foreach (var path in UnsavedValues.Keys)
{ {
var tokenToReplace = JsonObject.SelectToken(path); var tokenToReplace = JsonObject.SelectToken(path);
if (tokenToReplace != null) if (tokenToReplace != null)
{// It's found {// It's found
tokenToReplace.Replace(UnsavedValues[path]); 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 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) if (SaveCallback != null)
SaveCallback(JsonObject.ToString()); SaveCallback(JsonObject.ToString());
else 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 JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
using JsonSerializationException = NewtonsoftJson::Newtonsoft.Json.JsonSerializationException; using JsonSerializationException = NewtonsoftJson::Newtonsoft.Json.JsonSerializationException;
using JsonTextReader = NewtonsoftJson::Newtonsoft.Json.JsonTextReader; using JsonTextReader = NewtonsoftJson::Newtonsoft.Json.JsonTextReader;
using PepperDash.Core.Logging;
namespace PepperDash.Core.JsonToSimpl; namespace PepperDash.Core.JsonToSimpl;
@ -142,11 +143,10 @@ namespace PepperDash.Core.JsonToSimpl;
{ {
if (UnsavedValues.ContainsKey(path)) 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 else
UnsavedValues.Add(path, value); UnsavedValues.Add(path, value);
//Debug.Console(0, "Master[{0}] Unsaved size={1}", UniqueID, UnsavedValues.Count);
} }
/// <summary> /// <summary>

View file

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

View file

@ -9,6 +9,9 @@ using System.Threading.Tasks;
namespace PepperDash.Core.Logging; 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 public class CrestronEnricher : ILogEventEnricher
{ {
static readonly string _appName; 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) public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{ {
var property = propertyFactory.CreateProperty("App", _appName); var property = propertyFactory.CreateProperty("App", _appName);

View file

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Timers;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronDataStore; using Crestron.SimplSharp.CrestronDataStore;
using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronIO;
@ -44,24 +45,32 @@ public static class Debug
private static ILogger _logger; 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 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 public static DebugWebsocketSink WebsocketSink
{ {
get { return _websocketSink; } get { return websocketSink; }
} }
/// <summary> /// <summary>
@ -95,6 +104,9 @@ public static class Debug
private const int SaveTimeoutMs = 30000; 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; public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance;
/// <summary> /// <summary>
@ -102,7 +114,12 @@ public static class Debug
/// </summary> /// </summary>
public static string PepperDashCoreVersion { get; private set; } 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> /// <summary>
/// When true, the IncludedExcludedKeys dict will contain keys to include. /// When true, the IncludedExcludedKeys dict will contain keys to include.
@ -118,6 +135,10 @@ public static class Debug
private static LoggerConfiguration _loggerConfiguration; 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; public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration;
static Debug() static Debug()
@ -126,6 +147,13 @@ public static class Debug
{ {
CrestronDataStoreStatic.InitCrestronDataStore(); 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 defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey);
var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey); var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey);
@ -134,15 +162,15 @@ public static class Debug
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey); 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 ? var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ?
$@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" : $@"{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() .MinimumLevel.Verbose()
.Enrich.FromLogContext() .Enrich.FromLogContext()
.Enrich.With(new CrestronEnricher()) .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(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(websocketSink, levelSwitch: websocketLoggingLevelSwitch)
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch) .WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: errorLogLevelSwitch)
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath, .WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
rollingInterval: RollingInterval.Day, rollingInterval: RollingInterval.Day,
restrictedToMinimumLevel: LogEventLevel.Debug, restrictedToMinimumLevel: LogEventLevel.Debug,
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60, retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
levelSwitch: _fileLevelSwitch levelSwitch: fileLevelSwitch
); );
// Instantiate the root logger // Instantiate the root logger
@ -214,9 +242,9 @@ public static class Debug
if (DoNotLoadConfigOnNextBoot) 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)); 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) catch (Exception ex)
@ -238,6 +266,12 @@ public static class Debug
return doNotLoad; 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) public static void UpdateLoggerConfiguration(LoggerConfiguration config)
{ {
_loggerConfiguration = config; _loggerConfiguration = config;
@ -245,6 +279,9 @@ public static class Debug
_logger = config.CreateLogger(); _logger = config.CreateLogger();
} }
/// <summary>
/// Resets the LoggerConfiguration to the default configuration defined in this class.
/// </summary>
public static void ResetLoggerConfiguration() public static void ResetLoggerConfiguration()
{ {
_loggerConfiguration = _defaultLoggerConfiguration; _loggerConfiguration = _defaultLoggerConfiguration;
@ -332,7 +369,11 @@ public static class Debug
if (levelString.Trim() == "?") if (levelString.Trim() == "?")
{ {
CrestronConsole.ConsoleCommandResponse( CrestronConsole.ConsoleCommandResponse(
"Used to set the minimum level of debug messages to be printed to the console:\r\n" + "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[0]} = 0\r\n" +
$"{_logLevels[1]} = 1\r\n" + $"{_logLevels[1]} = 1\r\n" +
$"{_logLevels[2]} = 2\r\n" + $"{_logLevels[2]} = 2\r\n" +
@ -344,10 +385,22 @@ public static class Debug
if (string.IsNullOrEmpty(levelString.Trim())) if (string.IsNullOrEmpty(levelString.Trim()))
{ {
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", _consoleLoggingLevelSwitch.MinimumLevel); CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", consoleLoggingLevelSwitch.MinimumLevel);
return; 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 (int.TryParse(levelString, out var levelInt))
{ {
if (levelInt < 0 || levelInt > 5) if (levelInt < 0 || levelInt > 5)
@ -377,7 +430,8 @@ public static class Debug
/// Sets the debug level /// Sets the debug level
/// </summary> /// </summary>
/// <param name="level"> Valid values 0-5</param> /// <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)) if (!_logLevels.TryGetValue(level, out var logLevel))
{ {
@ -385,18 +439,27 @@ public static class Debug
CrestronConsole.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}"); 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", 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}"); CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
@ -408,40 +471,52 @@ public static class Debug
CrestronConsole.PrintLine($"Error saving console debug level setting: {err}"); 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) public static void SetWebSocketMinimumDebugLevel(LogEventLevel level)
{ {
_websocketLoggingLevelSwitch.MinimumLevel = level; websocketLoggingLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint)level); var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err); 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) public static void SetErrorLogMinimumDebugLevel(LogEventLevel level)
{ {
_errorLogLevelSwitch.MinimumLevel = level; errorLogLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level); var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting: {error}", err); 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) public static void SetFileMinimumDebugLevel(LogEventLevel level)
{ {
_errorLogLevelSwitch.MinimumLevel = level; errorLogLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level); var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err); 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> /// <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) public static void LogMessage(LogEventLevel level, string message, params object[] args)
{ {
_logger.Write(level, message, 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) public static void LogMessage(LogEventLevel level, Exception ex, string message, params object[] args)
{ {
_logger.Write(level, ex, message, 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) public static void LogMessage(LogEventLevel level, IKeyed keyed, string message, params object[] args)
{ {
LogMessage(level, message, keyed, 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) public static void LogMessage(LogEventLevel level, Exception ex, IKeyed device, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", device?.Key)) using (LogContext.PushProperty("Key", device?.Key))
@ -650,6 +749,13 @@ public static class Debug
} }
#region Explicit methods for logging levels #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) public static void LogVerbose(IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) 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) public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) 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) public static void LogVerbose(string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Verbose, message, 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) public static void LogVerbose(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Verbose, ex, null, message, 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) public static void LogDebug(IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) 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) public static void LogDebug(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) 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) public static void LogDebug(string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Debug, message, 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) public static void LogDebug(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Debug, ex, null, message, 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) public static void LogInformation(IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) 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) public static void LogInformation(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) 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) public static void LogInformation(string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Information, message, 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) public static void LogInformation(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Information, ex, null, message, 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) public static void LogWarning(IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) 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) public static void LogWarning(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) 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) public static void LogWarning(string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Warning, message, 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) public static void LogWarning(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Warning, ex, null, message, 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) public static void LogError(IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) 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) public static void LogError(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) 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) public static void LogError(string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Error, message, 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) public static void LogError(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Error, ex, null, message, 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) public static void LogFatal(IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) 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) public static void LogFatal(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) 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) public static void LogFatal(string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Fatal, message, 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) public static void LogFatal(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Fatal, ex, null, message, 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> /// <summary>
/// Writes the memory object after timeout /// Writes the memory object after timeout
/// </summary> /// </summary>
static void SaveMemoryOnTimeout() static void SaveMemoryOnTimeout()
{ {
Console(0, "Saving debug settings"); LogInformation("Saving debug settings");
if (_saveTimer == null) if (_saveTimer == null)
_saveTimer = new CTimer(o => {
_saveTimer = new Timer(SaveTimeoutMs) { AutoReset = false };
_saveTimer.Elapsed += (s, e) =>
{ {
_saveTimer = null; _saveTimer = null;
SaveMemory(); SaveMemory();
}, SaveTimeoutMs); };
_saveTimer.Start();
}
else else
_saveTimer.Reset(SaveTimeoutMs); {
_saveTimer.Stop();
_saveTimer.Interval = SaveTimeoutMs;
_saveTimer.Start();
}
} }
/// <summary> /// <summary>
@ -964,7 +1042,7 @@ public static class Debug
{ {
var fileName = GetMemoryFileName(); 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)) 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); 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 hostName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0);
var domainName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, 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]); 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) catch (Exception ex)
{ {
//Debug.Console(0, "WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace); Debug.LogError(ex, "WSS CreateCert Failed: {0}", ex.Message);
CrestronConsole.PrintLine("WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace); 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> /// <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) 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); Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword);
@ -163,7 +163,7 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
if (!string.IsNullOrWhiteSpace(certPath)) if (!string.IsNullOrWhiteSpace(certPath))
{ {
Debug.Console(0, "Assigning SSL Configuration"); Debug.LogInformation("Assigning SSL Configuration");
_httpsServer.SslConfiguration.ServerCertificate = new X509Certificate2(certPath, certPassword); _httpsServer.SslConfiguration.ServerCertificate = new X509Certificate2(certPath, certPassword);
_httpsServer.SslConfiguration.ClientCertificateRequired = false; _httpsServer.SslConfiguration.ClientCertificateRequired = false;
@ -172,13 +172,13 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
//this is just to test, you might want to actually validate //this is just to test, you might want to actually validate
_httpsServer.SslConfiguration.ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => _httpsServer.SslConfiguration.ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{ {
Debug.Console(0, "HTTPS ClientCerticateValidation Callback triggered"); Debug.LogInformation("HTTPS ClientCerticateValidation Callback triggered");
return true; return true;
}; };
} }
Debug.Console(0, "Adding Debug Client Service"); Debug.LogInformation("Adding Debug Client Service");
_httpsServer.AddWebSocketService<DebugClient>(_path); _httpsServer.AddWebSocketService<DebugClient>(_path);
Debug.Console(0, "Assigning Log Info"); Debug.LogInformation("Assigning Log Info");
_httpsServer.Log.Level = LogLevel.Trace; _httpsServer.Log.Level = LogLevel.Trace;
_httpsServer.Log.Output = (d, s) => _httpsServer.Log.Output = (d, s) =>
{ {
@ -208,17 +208,17 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
level = 4; level = 4;
break; break;
} }
Debug.LogInformation("{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s);
Debug.Console(level, "{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(); _httpsServer.Start();
Debug.Console(0, "Ready"); Debug.LogInformation("Ready");
} }
catch (Exception ex) 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> /// calling this method, the server will no longer accept or process incoming connections.</remarks>
public void StopServer() public void StopServer()
{ {
Debug.Console(0, "Stopping Websocket Server"); Debug.LogInformation("Stopping Websocket Server");
_httpsServer?.Stop(); _httpsServer?.Stop();
_httpsServer = null; _httpsServer = null;
@ -291,20 +291,18 @@ public class DebugClient : WebSocketBehavior
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DebugClient"/> class. /// Initializes a new instance of the <see cref="DebugClient"/> class.
/// </summary> /// </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() public DebugClient()
{ {
Debug.Console(0, "DebugClient Created"); Debug.LogInformation("DebugClient Created");
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnOpen() protected override void OnOpen()
{ {
base.OnOpen(); base.OnOpen();
var url = Context.WebSocket.Url; 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; _connectionTime = DateTime.Now;
} }
@ -314,7 +312,7 @@ public class DebugClient : WebSocketBehavior
{ {
base.OnMessage(e); base.OnMessage(e);
Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data); Debug.LogVerbose("WebSocket UiClient Message: {0}", e.Data);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -322,8 +320,7 @@ public class DebugClient : WebSocketBehavior
{ {
base.OnClose(e); 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/> /// <inheritdoc/>
@ -331,6 +328,7 @@ public class DebugClient : WebSocketBehavior
{ {
base.OnError(e); 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;
using System.Collections.Generic; using System.Collections.Generic;
using Crestron.SimplSharp; using System.Timers;
namespace PepperDash.Core.PasswordManagement; namespace PepperDash.Core.PasswordManagement;
/// <summary> /// <summary>
/// Allows passwords to be stored and managed /// Allows passwords to be stored and managed
/// </summary> /// </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> /// <summary>
/// Boolean event /// Initialize password manager
/// </summary> /// </summary>
public event EventHandler<BoolChangeEventArgs> BoolChange; public void Initialize()
/// <summary> {
/// Ushort event if (Passwords == null)
/// </summary> Passwords = new Dictionary<uint, string>();
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> if (_passwords == null)
/// Constructor _passwords = new Dictionary<uint, string>();
/// </summary>
public PasswordManager() 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> try
/// Initialize password manager
/// </summary>
public void Initialize()
{ {
if (Passwords == null) // if key exists, update the value
Passwords = new Dictionary<uint, string>(); if (_passwords.ContainsKey(key))
_passwords[key] = password;
// else add the key & value
else
_passwords.Add(key, password);
if (_passwords == null) Debug.LogDebug("PasswordManager.UpdatePassword: _password[{0}] = {1}", key, _passwords[key]);
_passwords = new Dictionary<uint, string>();
OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange); if (PasswordTimer == null)
}
/// <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.Console(1, string.Format("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password)); PasswordTimer = new Timer(PasswordTimerElapsedMs) { AutoReset = false };
return; PasswordTimer.Elapsed += (s, e) => PasswordTimerElapsed(s, e);
PasswordTimer.Start();
Debug.LogDebug("PasswordManager.UpdatePassword: Timer Started");
OnBoolChange(true, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
} }
else
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
{ {
PasswordTimer.Stop(); PasswordTimer.Stop();
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: CTimer Stopped")); PasswordTimer.Interval = PasswordTimerElapsedMs;
OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange); PasswordTimer.Start();
foreach (var pw in _passwords) 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 Debug.LogDebug("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value);
if (Passwords.ContainsKey(pw.Key)) if (Passwords[pw.Key] != _passwords[pw.Key])
{ {
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value)); Passwords[pw.Key] = _passwords[pw.Key];
if (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);
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);
} }
} }
OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange); // else add the key & value
} else
catch (Exception e) {
{ Passwords.Add(pw.Key, pw.Value);
var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", e); }
Debug.Console(1, msg);
} }
OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
} }
catch (Exception ex)
/// <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); var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", ex.Message);
Debug.LogError(ex, msg);
Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
} }
}
/// <summary> /// <summary>
/// Helper method for debugging to see what passwords are in the lists /// Method to change the default timer value, (default 5000ms/5s)
/// </summary> /// </summary>
public void ListPasswords() /// <param name="time"></param>
{ /// <summary>
Debug.Console(0, "PasswordManager.ListPasswords:\r"); /// PasswordTimerMs method
foreach (var pw in Passwords) /// </summary>
Debug.Console(0, "Passwords[{0}]: {1}\r", pw.Key, pw.Value); public void PasswordTimerMs(ushort time)
Debug.Console(0, "\n"); {
foreach (var pw in _passwords) PasswordTimerElapsedMs = Convert.ToInt64(time);
Debug.Console(0, "_passwords[{0}]: {1}\r", pw.Key, pw.Value); }
}
/// <summary> /// <summary>
/// Protected boolean change event handler /// Helper method for debugging to see what passwords are in the lists
/// </summary> /// </summary>
/// <param name="state"></param> public void ListPasswords()
/// <param name="index"></param> {
/// <param name="type"></param> Debug.LogInformation("PasswordManager.ListPasswords:\r");
protected void OnBoolChange(bool state, ushort index, ushort type) foreach (var pw in Passwords)
{ Debug.LogInformation("Passwords[{0}]: {1}\r", pw.Key, pw.Value);
var handler = BoolChange; Debug.LogInformation("\n");
if (handler != null) foreach (var pw in _passwords)
{ Debug.LogInformation("_passwords[{0}]: {1}\r", pw.Key, pw.Value);
var args = new BoolChangeEventArgs(state, type); }
args.Index = index;
BoolChange(this, args);
}
}
/// <summary> /// <summary>
/// Protected ushort change event handler /// Protected boolean change event handler
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="state"></param>
/// <param name="index"></param> /// <param name="index"></param>
/// <param name="type"></param> /// <param name="type"></param>
protected void OnUshrtChange(ushort value, ushort index, ushort type) protected void OnBoolChange(bool state, ushort index, ushort type)
{
var handler = BoolChange;
if (handler != null)
{ {
var handler = UshrtChange; var args = new BoolChangeEventArgs(state, type);
if (handler != null) args.Index = index;
{ BoolChange(this, args);
var args = new UshrtChangeEventArgs(value, type);
args.Index = index;
UshrtChange(this, args);
}
} }
}
/// <summary> /// <summary>
/// Protected string change event handler /// Protected ushort change event handler
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="value"></param>
/// <param name="index"></param> /// <param name="index"></param>
/// <param name="type"></param> /// <param name="type"></param>
protected void OnStringChange(string value, ushort index, ushort type) protected void OnUshrtChange(ushort value, ushort index, ushort type)
{
var handler = UshrtChange;
if (handler != null)
{ {
var handler = StringChange; var args = new UshrtChangeEventArgs(value, type);
if (handler != null) args.Index = index;
{ UshrtChange(this, args);
var args = new StringChangeEventArgs(value, type);
args.Index = index;
StringChange(this, args);
}
} }
}
/// <summary> /// <summary>
/// Protected password change event handler /// Protected string change event handler
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="value"></param>
/// <param name="index"></param> /// <param name="index"></param>
/// <param name="type"></param> /// <param name="type"></param>
protected void OnPasswordChange(string value, ushort index, ushort type) protected void OnStringChange(string value, ushort index, ushort type)
{
var handler = StringChange;
if (handler != null)
{ {
var handler = PasswordChange; var args = new StringChangeEventArgs(value, type);
if (handler != null) args.Index = index;
{ StringChange(this, args);
var args = new StringChangeEventArgs(value, type);
args.Index = index;
PasswordChange(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);
}
}
}

View file

@ -1,94 +0,0 @@
using System;
namespace PepperDash.Essentials.Core;
/// <summary>
/// Defines the eSourceListItemDestinationTypes enumeration, which represents the various destination types for source list items in a room control system.
/// This enumeration is marked as obsolete, indicating that it may be removed in future versions and should not be used in new development.
/// Each member of the enumeration corresponds to a specific type of display or audio output commonly found in room control systems,
/// such as default displays, program audio, codec content, and auxiliary displays.
/// </summary>
[Obsolete]
public enum eSourceListItemDestinationTypes
{
/// <summary>
/// Default display, used for the main video output in a room
/// </summary>
defaultDisplay,
/// <summary>
/// Left display
/// </summary>
leftDisplay,
/// <summary>
/// Right display
/// </summary>
rightDisplay,
/// <summary>
/// Center display
/// </summary>
centerDisplay,
/// <summary>
/// Program audio, used for the main audio output in a room
/// </summary>
programAudio,
/// <summary>
/// Codec content, used for sharing content to the far end in a video call
/// </summary>
codecContent,
/// <summary>
/// Front left display, used for rooms with multiple displays
/// </summary>
frontLeftDisplay,
/// <summary>
/// Front right display, used for rooms with multiple displays
/// </summary>
frontRightDisplay,
/// <summary>
/// Rear left display, used for rooms with multiple displays
/// </summary>
rearLeftDisplay,
/// <summary>
/// Rear right display, used for rooms with multiple displays
/// </summary>
rearRightDisplay,
/// <summary>
/// Auxiliary display 1, used for additional displays in a room
/// </summary>
auxDisplay1,
/// <summary>
/// Auxiliary display 2, used for additional displays in a room
/// </summary>
auxDisplay2,
/// <summary>
/// Auxiliary display 3, used for additional displays in a room
/// </summary>
auxDisplay3,
/// <summary>
/// Auxiliary display 4, used for additional displays in a room
/// </summary>
auxDisplay4,
/// <summary>
/// Auxiliary display 5, used for additional displays in a room
/// </summary>
auxDisplay5,
/// <summary>
/// Auxiliary display 6, used for additional displays in a room
/// </summary>
auxDisplay6,
/// <summary>
/// Auxiliary display 7, used for additional displays in a room
/// </summary>
auxDisplay7,
/// <summary>
/// Auxiliary display 8, used for additional displays in a room
/// </summary>
auxDisplay8,
/// <summary>
/// Auxiliary display 9, used for additional displays in a room
/// </summary>
auxDisplay9,
/// <summary>
/// Auxiliary display 10, used for additional displays in a room
/// </summary>
auxDisplay10,
}

View file

@ -1,4 +1,4 @@
using Crestron.SimplSharp; using System.Timers;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core
{ {
/// <summary> /// <summary>
@ -20,7 +20,7 @@ namespace PepperDash.Essentials.Core
/// Gets or sets the Feedback /// Gets or sets the Feedback
/// </summary> /// </summary>
public BoolFeedback Feedback { get; private set; } public BoolFeedback Feedback { get; private set; }
CTimer Timer; Timer Timer;
bool _BoolValue; bool _BoolValue;
@ -51,16 +51,22 @@ namespace PepperDash.Essentials.Core
{ {
_BoolValue = true; _BoolValue = true;
Feedback.FireUpdate(); Feedback.FireUpdate();
Timer = new CTimer(o => Timer = new Timer(TimeoutMs) { AutoReset = false };
Timer.Elapsed += (s, e) =>
{ {
_BoolValue = false; _BoolValue = false;
Feedback.FireUpdate(); Feedback.FireUpdate();
Timer = null; Timer = null;
}, TimeoutMs); };
Timer.Start();
} }
// Timer is running, if retrigger is set, reset it. // Timer is running, if retrigger is set, reset it.
else if (CanRetrigger) else if (CanRetrigger)
Timer.Reset(TimeoutMs); {
Timer.Stop();
Timer.Interval = TimeoutMs;
Timer.Start();
}
} }
/// <summary> /// <summary>
@ -69,7 +75,11 @@ namespace PepperDash.Essentials.Core
public void Cancel() public void Cancel()
{ {
if (Timer != null) if (Timer != null)
Timer.Reset(0); {
Timer.Stop();
Timer.Interval = 1;
Timer.Start();
}
} }
} }
} }

View file

@ -2,8 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using System.Timers;
using Crestron.SimplSharpPro;
namespace PepperDash.Essentials.Core; namespace PepperDash.Essentials.Core;
/// <summary> /// <summary>
@ -21,7 +20,7 @@ namespace PepperDash.Essentials.Core;
/// Gets the Feedback /// Gets the Feedback
/// </summary> /// </summary>
public BoolFeedback Feedback { get; private set; } public BoolFeedback Feedback { get; private set; }
CTimer Timer; Timer Timer;
/// <summary> /// <summary>
/// When set to true, will cause Feedback to go high, and cancel the timer. /// When set to true, will cause Feedback to go high, and cancel the timer.
@ -49,7 +48,11 @@ namespace PepperDash.Essentials.Core;
else else
{ {
if (Timer == null) if (Timer == null)
Timer = new CTimer(o => ClearFeedback(), TimeoutMs); {
Timer = new Timer(TimeoutMs) { AutoReset = false };
Timer.Elapsed += (s, e) => ClearFeedback();
Timer.Start();
}
} }
} }
} }

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Timers;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronIO;
@ -62,7 +63,7 @@ namespace PepperDash.Essentials.Core.Fusion
private Event _currentMeeting; private Event _currentMeeting;
private RoomSchedule _currentSchedule; private RoomSchedule _currentSchedule;
private CTimer _dailyTimeRequestTimer; private Timer _dailyTimeRequestTimer;
private StatusMonitorCollection _errorMessageRollUp; private StatusMonitorCollection _errorMessageRollUp;
private FusionRoomGuids _guids; private FusionRoomGuids _guids;
@ -70,9 +71,9 @@ namespace PepperDash.Essentials.Core.Fusion
private bool _isRegisteredForSchedulePushNotifications; private bool _isRegisteredForSchedulePushNotifications;
private Event _nextMeeting; private Event _nextMeeting;
private CTimer _pollTimer; private Timer _pollTimer;
private CTimer _pushNotificationTimer; private Timer _pushNotificationTimer;
private string _roomOccupancyRemoteString; private string _roomOccupancyRemoteString;
@ -729,15 +730,15 @@ namespace PepperDash.Essentials.Core.Fusion
RequestLocalDateTime(null); RequestLocalDateTime(null);
// Setup timer to request time daily // Setup timer to request time daily
if (_dailyTimeRequestTimer != null && !_dailyTimeRequestTimer.Disposed) if (_dailyTimeRequestTimer != null)
{ {
_dailyTimeRequestTimer.Stop(); _dailyTimeRequestTimer.Stop();
_dailyTimeRequestTimer.Dispose(); _dailyTimeRequestTimer.Dispose();
} }
_dailyTimeRequestTimer = new CTimer(RequestLocalDateTime, null, 86400000, 86400000); _dailyTimeRequestTimer = new Timer(86400000) { AutoReset = true };
_dailyTimeRequestTimer.Elapsed += (s, e) => RequestLocalDateTime(null);
_dailyTimeRequestTimer.Reset(86400000, 86400000); _dailyTimeRequestTimer.Start();
}); });
} }
@ -950,25 +951,25 @@ namespace PepperDash.Essentials.Core.Fusion
{ {
case 1: case 1:
_isRegisteredForSchedulePushNotifications = true; _isRegisteredForSchedulePushNotifications = true;
if (_pollTimer != null && !_pollTimer.Disposed) if (_pollTimer != null)
{ {
_pollTimer.Stop(); _pollTimer.Stop();
_pollTimer.Dispose(); _pollTimer.Dispose();
} }
_pushNotificationTimer = new CTimer(RequestFullRoomSchedule, null, _pushNotificationTimer = new Timer(PushNotificationTimeout) { AutoReset = true };
PushNotificationTimeout, PushNotificationTimeout); _pushNotificationTimer.Elapsed += (s, e) => RequestFullRoomSchedule(null);
_pushNotificationTimer.Reset(PushNotificationTimeout, PushNotificationTimeout); _pushNotificationTimer.Start();
break; break;
case 0: case 0:
_isRegisteredForSchedulePushNotifications = false; _isRegisteredForSchedulePushNotifications = false;
if (_pushNotificationTimer != null && !_pushNotificationTimer.Disposed) if (_pushNotificationTimer != null)
{ {
_pushNotificationTimer.Stop(); _pushNotificationTimer.Stop();
_pushNotificationTimer.Dispose(); _pushNotificationTimer.Dispose();
} }
_pollTimer = new CTimer(RequestFullRoomSchedule, null, SchedulePollInterval, _pollTimer = new Timer(SchedulePollInterval) { AutoReset = true };
SchedulePollInterval); _pollTimer.Elapsed += (s, e) => RequestFullRoomSchedule(null);
_pollTimer.Reset(SchedulePollInterval, SchedulePollInterval); _pollTimer.Start();
break; break;
} }
} }
@ -1121,7 +1122,10 @@ namespace PepperDash.Essentials.Core.Fusion
if (action.OuterXml.IndexOf("RequestSchedule", StringComparison.Ordinal) > -1) if (action.OuterXml.IndexOf("RequestSchedule", StringComparison.Ordinal) > -1)
{ {
_pushNotificationTimer.Reset(PushNotificationTimeout, PushNotificationTimeout); _pushNotificationTimer.Stop();
_pushNotificationTimer.Interval = PushNotificationTimeout;
_pushNotificationTimer.Start();
Debug.LogMessage(LogEventLevel.Verbose, this, "Received push notification for schedule change");
} }
} }
else // Not a push notification else // Not a push notification
@ -1177,7 +1181,9 @@ namespace PepperDash.Essentials.Core.Fusion
if (!_isRegisteredForSchedulePushNotifications) if (!_isRegisteredForSchedulePushNotifications)
{ {
_pollTimer.Reset(SchedulePollInterval, SchedulePollInterval); _pollTimer.Stop();
_pollTimer.Interval = SchedulePollInterval;
_pollTimer.Start();
} }
// Fire Schedule Change Event // Fire Schedule Change Event

View file

@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
namespace PepperDash.Essentials.Core.Interfaces;
/// <summary>
/// Defines the contract for ILogStrings
/// </summary>
public interface ILogStrings : IKeyed
{
/// <summary>
/// Defines a class that is capable of logging a string
/// </summary>
void SendToLog(IKeyed device, string logMessage);
}

View file

@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core;
namespace PepperDash.Essentials.Core.Interfaces;
/// <summary>
/// Defines the contract for ILogStringsWithLevel
/// </summary>
public interface ILogStringsWithLevel : IKeyed
{
/// <summary>
/// Defines a class that is capable of logging a string with an int level
/// </summary>
void SendToLog(IKeyed device, Debug.ErrorLogLevel level, string logMessage);
}

View file

@ -7,6 +7,7 @@ using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using System.ComponentModel; using System.ComponentModel;
using System.Timers;
using PepperDash.Core; using PepperDash.Core;
@ -84,10 +85,8 @@ namespace PepperDash.Essentials.Core
long WarningTime; long WarningTime;
long ErrorTime; long ErrorTime;
CTimer WarningTimer; Timer WarningTimer;
CTimer ErrorTimer; Timer ErrorTimer;
/// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
/// <param name="parent">parent device</param> /// <param name="parent">parent device</param>
@ -155,8 +154,18 @@ namespace PepperDash.Essentials.Core
/// </summary> /// </summary>
protected void StartErrorTimers() protected void StartErrorTimers()
{ {
if (WarningTimer == null) WarningTimer = new CTimer(o => { Status = MonitorStatus.InWarning; }, WarningTime); if (WarningTimer == null)
if (ErrorTimer == null) ErrorTimer = new CTimer(o => { Status = MonitorStatus.InError; }, ErrorTime); {
WarningTimer = new Timer(WarningTime) { AutoReset = false };
WarningTimer.Elapsed += (s, e) => { Status = MonitorStatus.InWarning; };
WarningTimer.Start();
}
if (ErrorTimer == null)
{
ErrorTimer = new Timer(ErrorTime) { AutoReset = false };
ErrorTimer.Elapsed += (s, e) => { Status = MonitorStatus.InError; };
ErrorTimer.Start();
}
} }
/// <summary> /// <summary>
@ -176,10 +185,17 @@ namespace PepperDash.Essentials.Core
protected void ResetErrorTimers() protected void ResetErrorTimers()
{ {
if (WarningTimer != null) if (WarningTimer != null)
WarningTimer.Reset(WarningTime, WarningTime); {
if (ErrorTimer != null) WarningTimer.Stop();
ErrorTimer.Reset(ErrorTime, ErrorTime); WarningTimer.Interval = WarningTime;
WarningTimer.Start();
}
if (ErrorTimer != null)
{
ErrorTimer.Stop();
ErrorTimer.Interval = ErrorTime;
ErrorTimer.Start();
}
} }
} }
} }

View file

@ -10,6 +10,7 @@ using Newtonsoft.Json.Converters;
using PepperDash.Essentials.Core.Bridges; using PepperDash.Essentials.Core.Bridges;
using Serilog.Events; using Serilog.Events;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Timers;
namespace PepperDash.Essentials.Core.Monitoring; namespace PepperDash.Essentials.Core.Monitoring;
@ -20,7 +21,7 @@ namespace PepperDash.Essentials.Core.Monitoring;
public class SystemMonitorController : EssentialsBridgeableDevice public class SystemMonitorController : EssentialsBridgeableDevice
{ {
private const long UptimePollTime = 300000; private const long UptimePollTime = 300000;
private CTimer _uptimePollTimer; private Timer _uptimePollTimer;
private string _uptime; private string _uptime;
private string _lastStart; private string _lastStart;
@ -147,7 +148,10 @@ public class SystemMonitorController : EssentialsBridgeableDevice
CreateEthernetStatusFeedbacks(); CreateEthernetStatusFeedbacks();
UpdateEthernetStatusFeeedbacks(); UpdateEthernetStatusFeeedbacks();
_uptimePollTimer = new CTimer(PollUptime, null, 0, UptimePollTime); _uptimePollTimer = new Timer(UptimePollTime) { AutoReset = true };
_uptimePollTimer.Elapsed += (s, e) => PollUptime(null);
_uptimePollTimer.Start();
PollUptime(null);
SystemMonitor.ProgramChange += SystemMonitor_ProgramChange; SystemMonitor.ProgramChange += SystemMonitor_ProgramChange;
SystemMonitor.TimeZoneInformation.TimeZoneChange += TimeZoneInformation_TimeZoneChange; SystemMonitor.TimeZoneInformation.TimeZoneChange += TimeZoneInformation_TimeZoneChange;

View file

@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using System.Timers;
using PepperDash.Core; using PepperDash.Core;
@ -15,15 +15,30 @@ namespace PepperDash.Essentials.Core;
/// </summary> /// </summary>
public class ActionIncrementer public class ActionIncrementer
{ {
/// <summary>
/// The amount to change the value by each increment
/// </summary>
public int ChangeAmount { get; set; } public int ChangeAmount { get; set; }
/// <summary>
/// The maximum value the incrementer can reach
/// </summary>
public int MaxValue { get; set; } public int MaxValue { get; set; }
/// <summary>
/// The minimum value the incrementer can reach
/// </summary>
public int MinValue { get; set; } public int MinValue { get; set; }
/// <summary>
/// The delay before the incrementer starts repeating
/// </summary>
public uint RepeatDelay { get; set; } public uint RepeatDelay { get; set; }
/// <summary>
/// The time interval between each repeat
/// </summary>
public uint RepeatTime { get; set; } public uint RepeatTime { get; set; }
Action<int> SetAction; Action<int> SetAction;
Func<int> GetFunc; Func<int> GetFunc;
CTimer Timer; Timer Timer;
/// <summary> /// <summary>
/// ///
@ -89,7 +104,20 @@ public class ActionIncrementer
if (atLimit) // Don't go past end if (atLimit) // Don't go past end
Stop(); Stop();
else if (Timer == null) // Only enter the timer if it's not already running else if (Timer == null) // Only enter the timer if it's not already running
Timer = new CTimer(o => { Go(change); }, null, RepeatDelay, RepeatTime); {
Timer = new Timer(RepeatDelay) { AutoReset = false };
Timer.Elapsed += (s, e) =>
{
Go(change);
if (Timer != null)
{
Timer.Interval = RepeatTime;
Timer.AutoReset = true;
Timer.Start();
}
};
Timer.Start();
}
} }
/// <summary> /// <summary>

View file

@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using System.Timers;
using Crestron.SimplSharpPro; using Crestron.SimplSharpPro;
using PepperDash.Core; using PepperDash.Core;
@ -16,14 +16,39 @@ namespace PepperDash.Essentials.Core;
public class UshortSigIncrementer public class UshortSigIncrementer
{ {
UShortInputSig TheSig; UShortInputSig TheSig;
/// <summary>
/// Amount to change the signal on each step
/// </summary>
public ushort ChangeAmount { get; set; } public ushort ChangeAmount { get; set; }
/// <summary>
/// Maximum value to ramp to
/// </summary>
public int MaxValue { get; set; } public int MaxValue { get; set; }
/// <summary>
/// Minimum value to ramp to
/// </summary>
public int MinValue { get; set; } public int MinValue { get; set; }
/// <summary>
/// The delay before the incrementer starts repeating
/// </summary>
public uint RepeatDelay { get; set; } public uint RepeatDelay { get; set; }
/// <summary>
/// The time interval between each repeat
/// </summary>
public uint RepeatTime { get; set; } public uint RepeatTime { get; set; }
bool SignedMode; bool SignedMode;
CTimer Timer; Timer Timer;
/// <summary>
/// Constructor
/// </summary>
/// <param name="sig"></param>
/// <param name="changeAmount"></param>
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
/// <param name="repeatDelay"></param>
/// <param name="repeatTime"></param>
public UshortSigIncrementer(UShortInputSig sig, ushort changeAmount, int minValue, int maxValue, uint repeatDelay, uint repeatTime) public UshortSigIncrementer(UShortInputSig sig, ushort changeAmount, int minValue, int maxValue, uint repeatDelay, uint repeatTime)
{ {
TheSig = sig; TheSig = sig;
@ -37,12 +62,19 @@ public class UshortSigIncrementer
Debug.LogMessage(LogEventLevel.Debug, "UshortSigIncrementer has signed values that exceed range of -32768, 32767"); Debug.LogMessage(LogEventLevel.Debug, "UshortSigIncrementer has signed values that exceed range of -32768, 32767");
} }
/// <summary>
/// Starts incrementing cycle
/// </summary>
public void StartUp() public void StartUp()
{ {
if (Timer != null) return; if (Timer != null) return;
Go(ChangeAmount); Go(ChangeAmount);
} }
/// <summary>
/// Starts decrementing cycle
/// </summary>
public void StartDown() public void StartDown()
{ {
if (Timer != null) return; if (Timer != null) return;
@ -64,7 +96,20 @@ public class UshortSigIncrementer
if (atLimit) // Don't go past end if (atLimit) // Don't go past end
Stop(); Stop();
else if (Timer == null) // Only enter the timer if it's not already running else if (Timer == null) // Only enter the timer if it's not already running
Timer = new CTimer(o => { Go(change); }, null, RepeatDelay, RepeatTime); {
Timer = new Timer(RepeatDelay) { AutoReset = false };
Timer.Elapsed += (s, e) =>
{
Go(change);
if (Timer != null)
{
Timer.Interval = RepeatTime;
Timer.AutoReset = true;
Timer.Start();
}
};
Timer.Start();
}
} }
bool CheckLevel(int levelIn, out int levelOut) bool CheckLevel(int levelIn, out int levelOut)
@ -85,6 +130,9 @@ public class UshortSigIncrementer
return IsAtLimit; return IsAtLimit;
} }
/// <summary>
/// Stops incrementing/decrementing cycle
/// </summary>
public void Stop() public void Stop()
{ {
if (Timer != null) if (Timer != null)
@ -92,6 +140,11 @@ public class UshortSigIncrementer
Timer = null; Timer = null;
} }
/// <summary>
/// Sets the value of the signal
/// </summary>
/// <param name="value"></param>
void SetValue(ushort value) void SetValue(ushort value)
{ {
//CrestronConsole.PrintLine("Increment level:{0} / {1}", value, (short)value); //CrestronConsole.PrintLine("Increment level:{0} / {1}", value, (short)value);

View file

@ -1,12 +1,13 @@
using Crestron.SimplSharp; using PepperDash.Core;
using PepperDash.Core;
using PepperDash.Core.Logging; using PepperDash.Core.Logging;
using Serilog.Events; using Serilog.Events;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Timer = System.Timers.Timer;
namespace PepperDash.Essentials.Core; namespace PepperDash.Essentials.Core;
@ -73,7 +74,7 @@ public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombiner
} }
} }
private CTimer _scenarioChangeDebounceTimer; private Timer _scenarioChangeDebounceTimer;
private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s
@ -204,18 +205,22 @@ public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombiner
if (_scenarioChangeDebounceTimer == null) if (_scenarioChangeDebounceTimer == null)
{ {
_scenarioChangeDebounceTimer = new CTimer((o) => DetermineRoomCombinationScenario(), time); _scenarioChangeDebounceTimer = new Timer(time) { AutoReset = false };
_scenarioChangeDebounceTimer.Elapsed += async (s, e) => await DetermineRoomCombinationScenario();
_scenarioChangeDebounceTimer.Start();
} }
else else
{ {
_scenarioChangeDebounceTimer.Reset(time); _scenarioChangeDebounceTimer.Stop();
_scenarioChangeDebounceTimer.Interval = time;
_scenarioChangeDebounceTimer.Start();
} }
} }
/// <summary> /// <summary>
/// Determines the current room combination scenario based on the state of the partition sensors /// Determines the current room combination scenario based on the state of the partition sensors
/// </summary> /// </summary>
private void DetermineRoomCombinationScenario() private async Task DetermineRoomCombinationScenario()
{ {
if (_scenarioChangeDebounceTimer != null) if (_scenarioChangeDebounceTimer != null)
{ {
@ -250,7 +255,7 @@ public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombiner
if (currentScenario != null) if (currentScenario != null)
{ {
this.LogInformation("Found combination Scenario {scenarioKey}", currentScenario.Key); this.LogInformation("Found combination Scenario {scenarioKey}", currentScenario.Key);
ChangeScenario(currentScenario); await ChangeScenario(currentScenario);
} }
} }

View file

@ -24,18 +24,12 @@ public class EssentialsNDisplayRoomPropertiesConfig : EssentialsConferenceRoomPr
[JsonProperty("defaultVideoBehavior")] [JsonProperty("defaultVideoBehavior")]
public string DefaultVideoBehavior { get; set; } public string DefaultVideoBehavior { get; set; }
/// <summary>
/// Gets or sets the Displays
/// </summary>
[JsonProperty("displays")]
public Dictionary<eSourceListItemDestinationTypes, DisplayItem> Displays { get; set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
public EssentialsNDisplayRoomPropertiesConfig() public EssentialsNDisplayRoomPropertiesConfig()
{ {
Displays = new Dictionary<eSourceListItemDestinationTypes, DisplayItem>();
} }
} }

View file

@ -1,30 +1,56 @@
using System; using System;
using System.Collections.Generic; using System.Timers;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using PepperDash.Core; using PepperDash.Core;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Essentials.Core; namespace PepperDash.Essentials.Core;
/// <summary>
/// A class that represents a countdown timer with feedbacks for time remaining, percent, and seconds
/// </summary>
public class SecondsCountdownTimer: IKeyed public class SecondsCountdownTimer: IKeyed
{ {
/// <summary>
/// Event triggered when the timer starts.
/// </summary>
public event EventHandler<EventArgs> HasStarted; public event EventHandler<EventArgs> HasStarted;
/// <summary>
/// Event triggered when the timer finishes.
/// </summary>
public event EventHandler<EventArgs> HasFinished; public event EventHandler<EventArgs> HasFinished;
/// <summary>
/// Event triggered when the timer is cancelled.
/// </summary>
public event EventHandler<EventArgs> WasCancelled; public event EventHandler<EventArgs> WasCancelled;
/// <inheritdoc />
public string Key { get; private set; } public string Key { get; private set; }
/// <summary>
/// Indicates whether the timer is currently running
/// </summary>
public BoolFeedback IsRunningFeedback { get; private set; } public BoolFeedback IsRunningFeedback { get; private set; }
bool _isRunning; bool _isRunning;
/// <summary>
/// Feedback for the percentage of time remaining
/// </summary>
public IntFeedback PercentFeedback { get; private set; } public IntFeedback PercentFeedback { get; private set; }
/// <summary>
/// Feedback for the time remaining in a string format
// </summary>
public StringFeedback TimeRemainingFeedback { get; private set; } public StringFeedback TimeRemainingFeedback { get; private set; }
/// <summary>
/// Feedback for the time remaining in seconds
/// </summary>
public IntFeedback SecondsRemainingFeedback { get; private set; } public IntFeedback SecondsRemainingFeedback { get; private set; }
/// <summary>
/// When true, the timer will count down immediately upon calling Start. When false, the timer will count up, and when Finish is called, it will stop counting and fire the HasFinished event.
/// </summary>
public bool CountsDown { get; set; } public bool CountsDown { get; set; }
/// <summary> /// <summary>
@ -32,10 +58,17 @@ public class SecondsCountdownTimer: IKeyed
/// </summary> /// </summary>
public int SecondsToCount { get; set; } public int SecondsToCount { get; set; }
/// <summary>
/// The time at which the timer was started. Used to calculate percent and time remaining. Will be DateTime.MinValue if the timer is not currently running.
/// </summary>
public DateTime StartTime { get; private set; } public DateTime StartTime { get; private set; }
/// <summary>
/// The time at which the timer will finish counting down. Used to calculate percent and time remaining. Will be DateTime.MinValue if the timer is not currently running.
/// </summary>
public DateTime FinishTime { get; private set; } public DateTime FinishTime { get; private set; }
private CTimer _secondTimer; private Timer _secondTimer;
/// <summary> /// <summary>
/// Constructor /// Constructor
@ -88,7 +121,10 @@ public class SecondsCountdownTimer: IKeyed
if (_secondTimer != null) if (_secondTimer != null)
_secondTimer.Stop(); _secondTimer.Stop();
_secondTimer = new CTimer(SecondElapsedTimerCallback, null, 0, 1000); _secondTimer = new Timer(1000) { AutoReset = true };
_secondTimer.Elapsed += (s, e) => SecondElapsedTimerCallback(null);
_secondTimer.Start();
SecondElapsedTimerCallback(null);
_isRunning = true; _isRunning = true;
IsRunningFeedback.FireUpdate(); IsRunningFeedback.FireUpdate();

View file

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Crestron.SimplSharp; using System.Timers;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -17,9 +17,14 @@ public class RetriggerableTimer : EssentialsDevice
{ {
private RetriggerableTimerPropertiesConfig _propertiesConfig; private RetriggerableTimerPropertiesConfig _propertiesConfig;
private CTimer _timer; private Timer _timer;
private long _timerIntervalMs; private long _timerIntervalMs;
/// <summary>
/// Constructor for RetriggerableTimer
/// </summary>
/// <param name="key"></param>
/// <param name="config"></param>
public RetriggerableTimer(string key, DeviceConfig config) public RetriggerableTimer(string key, DeviceConfig config)
: base(key, config.Name) : base(key, config.Name)
{ {
@ -32,6 +37,7 @@ public class RetriggerableTimer : EssentialsDevice
} }
} }
/// <inheritdoc />
public override bool CustomActivate() public override bool CustomActivate()
{ {
if (_propertiesConfig.StartTimerOnActivation) if (_propertiesConfig.StartTimerOnActivation)
@ -53,14 +59,26 @@ public class RetriggerableTimer : EssentialsDevice
_timer = null; _timer = null;
} }
/// <summary>
/// Starts the timer with the interval specified in config. When the timer elapses, it executes the action specified in config for the Elapsed event. If the timer is already running, it will reset and start again.
/// When the timer is stopped, it executes the action specified in config for the Stopped event.
/// </summary>
public void StartTimer() public void StartTimer()
{ {
CleanUpTimer(); CleanUpTimer();
Debug.LogMessage(LogEventLevel.Information, this, "Starting Timer"); Debug.LogMessage(LogEventLevel.Information, this, "Starting Timer");
_timer = new CTimer(TimerElapsedCallback, GetActionFromConfig(eRetriggerableTimerEvents.Elapsed), _timerIntervalMs, _timerIntervalMs); var action = GetActionFromConfig(eRetriggerableTimerEvents.Elapsed);
_timer = new Timer(_timerIntervalMs) { AutoReset = true };
_timer.Elapsed += (s, e) => TimerElapsedCallback(action);
_timer.Start();
} }
/// <summary>
/// Stops the timer. If the timer is stopped before it elapses, it will execute the action specified in config for the Stopped event. If the timer is not running, this method does nothing.
/// If the timer is running, it will stop the timer and execute the Stopped action from config. If the timer is not running, it will do nothing.
/// If the timer is running and the Stopped action is not specified in config, it will stop the timer and do nothing else. If the timer is running and the Stopped action is specified in config, it will stop the timer and execute the action. If the timer is not running, it will do nothing regardless
/// </summary>
public void StopTimer() public void StopTimer()
{ {
Debug.LogMessage(LogEventLevel.Information, this, "Stopping Timer"); Debug.LogMessage(LogEventLevel.Information, this, "Stopping Timer");
@ -81,7 +99,7 @@ public class RetriggerableTimer : EssentialsDevice
/// <summary> /// <summary>
/// Executes the Elapsed action from confing when the timer elapses /// Executes the Elapsed action from confing when the timer elapses
/// </summary> /// </summary>
/// <param name="o"></param> /// <param name="action">The action to execute when the timer elapses</param>
private void TimerElapsedCallback(object action) private void TimerElapsedCallback(object action)
{ {
Debug.LogMessage(LogEventLevel.Debug, this, "Timer Elapsed. Executing Action"); Debug.LogMessage(LogEventLevel.Debug, this, "Timer Elapsed. Executing Action");
@ -127,15 +145,29 @@ public class RetriggerableTimer : EssentialsDevice
/// </summary> /// </summary>
public class RetriggerableTimerPropertiesConfig public class RetriggerableTimerPropertiesConfig
{ {
/// <summary>
/// When true, the timer will start immediately upon activation. When false, the timer will not start until StartTimer is called.
/// </summary>
[JsonProperty("startTimerOnActivation")] [JsonProperty("startTimerOnActivation")]
public bool StartTimerOnActivation { get; set; } public bool StartTimerOnActivation { get; set; }
/// <summary>
/// The interval at which the timer elapses, in milliseconds. This is required and must be greater than 0. If this value is not set or is less than or equal to 0, the timer will not start and an error will be logged.
/// </summary>
[JsonProperty("timerIntervalMs")] [JsonProperty("timerIntervalMs")]
public long TimerIntervalMs { get; set; } public long TimerIntervalMs { get; set; }
/// <summary>
/// The actions to execute when timer events occur. The key is the type of event, and the value is the action to execute when that event occurs.
/// This is required and must contain at least an action for the Elapsed event.
/// If an action for the Stopped event is not included, then when the timer is stopped, it will simply stop without executing any action.
/// </summary>
[JsonProperty("events")] [JsonProperty("events")]
public Dictionary<eRetriggerableTimerEvents, DeviceActionWrapper> Events { get; set; } public Dictionary<eRetriggerableTimerEvents, DeviceActionWrapper> Events { get; set; }
/// <summary>
/// Constructor
/// </summary>
public RetriggerableTimerPropertiesConfig() public RetriggerableTimerPropertiesConfig()
{ {
Events = new Dictionary<eRetriggerableTimerEvents, DeviceActionWrapper>(); Events = new Dictionary<eRetriggerableTimerEvents, DeviceActionWrapper>();
@ -147,7 +179,14 @@ public class RetriggerableTimerPropertiesConfig
/// </summary> /// </summary>
public enum eRetriggerableTimerEvents public enum eRetriggerableTimerEvents
{ {
/// <summary>
/// Elapsed event state
/// </summary>
Elapsed, Elapsed,
/// <summary>
/// Stopped event state
/// </summary>
Stopped, Stopped,
} }
@ -156,11 +195,16 @@ public enum eRetriggerableTimerEvents
/// </summary> /// </summary>
public class RetriggerableTimerFactory : EssentialsDeviceFactory<RetriggerableTimer> public class RetriggerableTimerFactory : EssentialsDeviceFactory<RetriggerableTimer>
{ {
/// <summary>
/// Constructor for factory
///
/// </summary>
public RetriggerableTimerFactory() public RetriggerableTimerFactory()
{ {
TypeNames = new List<string>() { "retriggerabletimer" }; TypeNames = new List<string>() { "retriggerabletimer" };
} }
/// <inheritdoc />
public override EssentialsDevice BuildDevice(DeviceConfig dc) public override EssentialsDevice BuildDevice(DeviceConfig dc)
{ {
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new RetriggerableTimer Device"); Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new RetriggerableTimer Device");

View file

@ -1,10 +1,13 @@
using System; using System;
using System.Text; using System.Text;
using Crestron.SimplSharp; using System.Timers;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
namespace PepperDash.Essentials.Core.Touchpanels.Keyboards; namespace PepperDash.Essentials.Core.Touchpanels.Keyboards;
/// <summary>
/// Controller for the Habanero keyboard. This class handles all interaction with the keyboard, including showing/hiding, shift states, and key presses. It exposes a KeyPress event for single key presses, and an OutputFeedback string that contains the full text of what's been entered on the keyboard.
/// </summary>
public class HabaneroKeyboardController public class HabaneroKeyboardController
{ {
/// <summary> /// <summary>
@ -12,29 +15,57 @@ public class HabaneroKeyboardController
/// </summary> /// </summary>
public event EventHandler<KeyboardControllerPressEventArgs> KeyPress; public event EventHandler<KeyboardControllerPressEventArgs> KeyPress;
/// <summary>
/// The BasicTriList that the keyboard is connected to. This is used for all interaction with the keyboard, including showing/hiding, setting button text/visibility, and handling button presses.
/// </summary>
public BasicTriList TriList { get; private set; } public BasicTriList TriList { get; private set; }
/// <summary>
/// Feedback for the current text entered on the keyboard. This is updated whenever a key is pressed, and can be used to get the full string of what's been entered on the keyboard.
/// </summary>
public StringFeedback OutputFeedback { get; private set; } public StringFeedback OutputFeedback { get; private set; }
/// <summary>
/// Indicates whether the keyboard is currently visible. This is updated when Show and Hide are called, and can be used to determine the current visibility state of the keyboard.
/// </summary>
public bool IsVisible { get; private set; } public bool IsVisible { get; private set; }
/// <summary>
/// The string displayed on the ".com" button.
/// </summary>
public string DotComButtonString { get; set; } public string DotComButtonString { get; set; }
/// <summary>
/// The text displayed on the "Go" button.
/// </summary>
public string GoButtonText { get; set; } public string GoButtonText { get; set; }
/// <summary>
/// The text displayed on the secondary button.
/// </summary>
public string SecondaryButtonText { get; set; } public string SecondaryButtonText { get; set; }
/// <summary>
/// Indicates whether the "Go" button is visible.
/// </summary>
public bool GoButtonVisible { get; set; } public bool GoButtonVisible { get; set; }
/// <summary>
/// Indicates whether the secondary button is visible.
/// </summary>
public bool SecondaryButtonVisible { get; set; } public bool SecondaryButtonVisible { get; set; }
int ShiftMode = 0; int ShiftMode = 0;
StringBuilder Output; StringBuilder Output;
/// <summary>
/// An action that is run when the keyboard is hidden, either by calling the Hide method or by pressing the close button on the keyboard. This can be used to perform any necessary cleanup or state updates when the keyboard is dismissed.
/// </summary>
/// </summary>
public Action HideAction { get; set; } public Action HideAction { get; set; }
CTimer BackspaceTimer; Timer BackspaceTimer;
/// <summary> /// <summary>
/// ///
@ -88,8 +119,8 @@ public class HabaneroKeyboardController
TriList.SetSigTrueAction(2947, () => Press('.')); TriList.SetSigTrueAction(2947, () => Press('.'));
TriList.SetSigTrueAction(2948, () => Press('@')); TriList.SetSigTrueAction(2948, () => Press('@'));
TriList.SetSigTrueAction(2949, () => Press(' ')); TriList.SetSigTrueAction(2949, () => Press(' '));
TriList.SetSigHeldAction(2950, 500, StartBackspaceRepeat, StopBackspaceRepeat, Backspace); TriList.SetSigHeldAction(2950, 500, StartBackspaceRepeat, StopBackspaceRepeat, Backspace);
//TriList.SetSigTrueAction(2950, Backspace); //TriList.SetSigTrueAction(2950, Backspace);
TriList.SetSigTrueAction(2951, Shift); TriList.SetSigTrueAction(2951, Shift);
TriList.SetSigTrueAction(2952, NumShift); TriList.SetSigTrueAction(2952, NumShift);
TriList.SetSigTrueAction(2953, Clear); TriList.SetSigTrueAction(2953, Clear);
@ -117,7 +148,7 @@ public class HabaneroKeyboardController
TriList.ClearBoolSigAction(i); TriList.ClearBoolSigAction(i);
// run attached actions // run attached actions
if(HideAction != null) if (HideAction != null)
HideAction(); HideAction();
TriList.SetBool(KeyboardVisible, false); TriList.SetBool(KeyboardVisible, false);
@ -205,28 +236,31 @@ public class HabaneroKeyboardController
char Y(int i) { return new char[] { 'y', 'Y', '6', '^' }[i]; } char Y(int i) { return new char[] { 'y', 'Y', '6', '^' }[i]; }
char Z(int i) { return new char[] { 'z', 'Z', ',', ',' }[i]; } char Z(int i) { return new char[] { 'z', 'Z', ',', ',' }[i]; }
/// <summary> /// <summary>
/// Does what it says /// Does what it says
/// </summary> /// </summary>
void StartBackspaceRepeat() void StartBackspaceRepeat()
{ {
if (BackspaceTimer == null) if (BackspaceTimer == null)
{ {
BackspaceTimer = new CTimer(o => Backspace(), null, 0, 175); BackspaceTimer = new Timer(175) { AutoReset = true };
} BackspaceTimer.Elapsed += (s, e) => Backspace();
} BackspaceTimer.Start();
Backspace();
}
}
/// <summary> /// <summary>
/// Does what it says /// Does what it says
/// </summary> /// </summary>
void StopBackspaceRepeat() void StopBackspaceRepeat()
{ {
if (BackspaceTimer != null) if (BackspaceTimer != null)
{ {
BackspaceTimer.Stop(); BackspaceTimer.Stop();
BackspaceTimer = null; BackspaceTimer = null;
} }
} }
void Backspace() void Backspace()
{ {
@ -384,7 +418,7 @@ public class HabaneroKeyboardController
/// <summary> /// <summary>
/// 2904 /// 2904
/// </summary> /// </summary>
public const uint SecondaryButtonTextJoin = 2904; public const uint SecondaryButtonTextJoin = 2904;
/// <summary> /// <summary>
/// 2905 /// 2905
/// </summary> /// </summary>
@ -413,21 +447,58 @@ public class HabaneroKeyboardController
/// </summary> /// </summary>
public class KeyboardControllerPressEventArgs : EventArgs public class KeyboardControllerPressEventArgs : EventArgs
{ {
/// <summary>
/// The text of the key that was pressed. This will be null if a special key (backspace, clear, go, secondary) was pressed, in which case the SpecialKey property should be checked to determine which key was pressed.
/// </summary>
public string Text { get; private set; } public string Text { get; private set; }
/// <summary>
/// If a special key (backspace, clear, go, secondary) was pressed, this property indicates which key it was. If a regular key was pressed, this will be KeyboardSpecialKey.None, and the Text property should be checked for the value of the key press.
/// </summary>
public KeyboardSpecialKey SpecialKey { get; private set; } public KeyboardSpecialKey SpecialKey { get; private set; }
/// <summary>
/// Constructor for regular key presses
/// </summary>
public KeyboardControllerPressEventArgs(string text) public KeyboardControllerPressEventArgs(string text)
{ {
Text = text; Text = text;
} }
/// <summary>
/// Constructor for special key presses
/// </summary>
/// <param name="key"></param>
public KeyboardControllerPressEventArgs(KeyboardSpecialKey key) public KeyboardControllerPressEventArgs(KeyboardSpecialKey key)
{ {
SpecialKey = key; SpecialKey = key;
} }
} }
/// <summary>
/// An enum representing special keys on the keyboard that don't have text values, such as backspace, clear, go, and the secondary button. The None value is used for regular key presses that do have text values, and should not be used for special key presses.
/// </summary>
public enum KeyboardSpecialKey public enum KeyboardSpecialKey
{ {
None = 0, Backspace, Clear, GoButton, SecondaryButton /// <summary>
/// Indicates that a regular key with a text value was pressed, rather than a special key. When this value is set, the Text property of the KeyboardControllerPressEventArgs should be checked to get the value of the key press.
/// </summary>
None = 0,
/// <summary>
/// Indicates that the backspace key was pressed.
/// </summary>
Backspace,
/// <summary>
/// Indicates that the clear key was pressed.
/// </summary>
Clear,
/// <summary>
/// Indicates that the go button was pressed.
/// </summary>
GoButton,
/// <summary>
/// Indicates that the secondary button was pressed.
/// </summary>
SecondaryButton
} }

View file

@ -1,5 +1,5 @@
using System; using System;
using Crestron.SimplSharp; using System.Timers;
using Crestron.SimplSharpPro; using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
@ -84,7 +84,7 @@ namespace PepperDash.Essentials.Core;
/// <returns></returns> /// <returns></returns>
public static BoolOutputSig SetSigHeldAction(this BoolOutputSig sig, uint heldMs, Action heldAction, Action holdReleasedAction, Action releaseAction) public static BoolOutputSig SetSigHeldAction(this BoolOutputSig sig, uint heldMs, Action heldAction, Action holdReleasedAction, Action releaseAction)
{ {
CTimer heldTimer = null; Timer heldTimer = null;
bool wasHeld = false; bool wasHeld = false;
return sig.SetBoolSigAction(press => return sig.SetBoolSigAction(press =>
{ {
@ -92,7 +92,8 @@ namespace PepperDash.Essentials.Core;
{ {
wasHeld = false; wasHeld = false;
// Could insert a pressed action here // Could insert a pressed action here
heldTimer = new CTimer(o => heldTimer = new Timer(heldMs) { AutoReset = false };
heldTimer.Elapsed += (s, e) =>
{ {
// if still held and there's an action // if still held and there's an action
if (sig.BoolValue && heldAction != null) if (sig.BoolValue && heldAction != null)
@ -101,7 +102,8 @@ namespace PepperDash.Essentials.Core;
// Hold action here // Hold action here
heldAction(); heldAction();
} }
}, heldMs); };
heldTimer.Start();
} }
else if (!press && !wasHeld) // released, no hold else if (!press && !wasHeld) // released, no hold
{ {

View file

@ -1,715 +0,0 @@

using System;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Bridges;
using PepperDash.Essentials.Core.Config;
using Serilog.Events;
namespace PepperDash.Essentials.Devices.Common.Cameras;
/// <summary>
/// Camera driver for cameras that use the VISCA control protocol. This driver supports both IP and RS-232 control depending on the communication method passed in. It also supports pan/tilt speed control, presets, and focus control.
/// </summary>
public class CameraVisca : CameraBase, IHasCameraPtzControl, ICommunicationMonitor, IHasCameraPresets, IHasPowerControlWithFeedback, IBridgeAdvanced, IHasCameraFocusControl, IHasAutoFocusMode
{
private readonly CameraViscaPropertiesConfig PropertiesConfig;
/// <summary>
/// Gets or sets the Communication
/// </summary>
public IBasicCommunication Communication { get; private set; }
/// <summary>
/// Gets or sets the CommunicationMonitor
/// </summary>
public StatusMonitorBase CommunicationMonitor { get; private set; }
/// <summary>
/// Used to store the actions to parse inquiry responses as the inquiries are sent
/// </summary>
private readonly CrestronQueue<Action<byte[]>> InquiryResponseQueue;
/// <summary>
/// Camera ID (Default 1)
/// </summary>
public byte ID = 0x01;
public byte ResponseID;
/// <summary>
/// Slow speed value for pan movement
/// </summary>
public byte PanSpeedSlow = 0x10;
/// <summary>
/// Slow speed value for tilt movement
/// </summary>
public byte TiltSpeedSlow = 0x10;
public byte PanSpeedFast = 0x13;
public byte TiltSpeedFast = 0x13;
// private bool IsMoving;
private bool IsZooming;
bool _powerIsOn;
public bool PowerIsOn
{
get
{
return _powerIsOn;
}
private set
{
if (value != _powerIsOn)
{
_powerIsOn = value;
PowerIsOnFeedback.FireUpdate();
CameraIsOffFeedback.FireUpdate();
}
}
}
const byte ZoomInCmd = 0x02;
const byte ZoomOutCmd = 0x03;
const byte ZoomStopCmd = 0x00;
/// <summary>
/// Used to determine when to move the camera at a faster speed if a direction is held
/// </summary>
CTimer SpeedTimer;
// TODO: Implment speed timer for PTZ controls
long FastSpeedHoldTimeMs = 2000;
byte[] IncomingBuffer = new byte[] { };
public BoolFeedback PowerIsOnFeedback { get; private set; }
public CameraVisca(string key, string name, IBasicCommunication comm, CameraViscaPropertiesConfig props) :
base(key, name)
{
InquiryResponseQueue = new CrestronQueue<Action<byte[]>>(15);
Presets = props.Presets;
PropertiesConfig = props;
ID = (byte)(props.Id + 0x80);
ResponseID = (byte)((props.Id * 0x10) + 0x80);
SetupCameraSpeeds();
OutputPorts.Add(new RoutingOutputPort("videoOut", eRoutingSignalType.Video, eRoutingPortConnectionType.None, null, this, true));
// Default to all capabilties
Capabilities = eCameraCapabilities.Pan | eCameraCapabilities.Tilt | eCameraCapabilities.Zoom | eCameraCapabilities.Focus;
Communication = comm;
if (comm is ISocketStatus socket)
{
// This instance uses IP control
socket.ConnectionChange += new EventHandler<GenericSocketStatusChageEventArgs>(Socket_ConnectionChange);
}
else
{
// This instance uses RS-232 control
}
Communication.BytesReceived += new EventHandler<GenericCommMethodReceiveBytesArgs>(Communication_BytesReceived);
PowerIsOnFeedback = new BoolFeedback(() => { return PowerIsOn; });
CameraIsOffFeedback = new BoolFeedback(() => { return !PowerIsOn; });
if (props.CommunicationMonitorProperties != null)
{
CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, props.CommunicationMonitorProperties);
}
else
{
CommunicationMonitor = new GenericCommunicationMonitor(this, Communication, 20000, 120000, 300000, "\x81\x09\x04\x00\xFF");
}
DeviceManager.AddDevice(CommunicationMonitor);
}
/// <summary>
/// Sets up camera speed values based on config
/// </summary>
void SetupCameraSpeeds()
{
if (PropertiesConfig.FastSpeedHoldTimeMs > 0)
{
FastSpeedHoldTimeMs = PropertiesConfig.FastSpeedHoldTimeMs;
}
if (PropertiesConfig.PanSpeedSlow > 0)
{
PanSpeedSlow = (byte)PropertiesConfig.PanSpeedSlow;
}
if (PropertiesConfig.PanSpeedFast > 0)
{
PanSpeedFast = (byte)PropertiesConfig.PanSpeedFast;
}
if (PropertiesConfig.TiltSpeedSlow > 0)
{
TiltSpeedSlow = (byte)PropertiesConfig.TiltSpeedSlow;
}
if (PropertiesConfig.TiltSpeedFast > 0)
{
TiltSpeedFast = (byte)PropertiesConfig.TiltSpeedFast;
}
}
public override bool CustomActivate()
{
Communication.Connect();
CommunicationMonitor.StatusChange += (o, a) => { Debug.LogMessage(LogEventLevel.Verbose, this, "Communication monitor state: {0}", CommunicationMonitor.Status); };
CommunicationMonitor.Start();
CommunicationMonitor.StatusChange += (o, a) => { Debug.LogMessage(LogEventLevel.Verbose, this, "Communication monitor state: {0}", CommunicationMonitor.Status); };
CommunicationMonitor.Start();
CrestronConsole.AddNewConsoleCommand(s => Communication.Connect(), "con" + Key, "", ConsoleAccessLevelEnum.AccessOperator);
return true;
}
/// <summary>
/// LinkToApi method
/// </summary>
public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
{
LinkCameraToApi(this, trilist, joinStart, joinMapKey, bridge);
}
void Socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e)
{
Debug.LogMessage(LogEventLevel.Verbose, this, "Socket Status Change: {0}", e.Client.ClientStatus.ToString());
if (e.Client.IsConnected)
{
}
else
{
}
}
void SendBytes(byte[] b)
{
if (Debug.Level == 2) // This check is here to prevent following string format from building unnecessarily on level 0 or 1
Debug.LogMessage(LogEventLevel.Verbose, this, "Sending:{0}", ComTextHelper.GetEscapedText(b));
Communication.SendBytes(b);
}
void Communication_BytesReceived(object sender, GenericCommMethodReceiveBytesArgs e)
{
var newBytes = new byte[IncomingBuffer.Length + e.Bytes.Length];
try
{
// This is probably not thread-safe buffering
// Append the incoming bytes with whatever is in the buffer
IncomingBuffer.CopyTo(newBytes, 0);
e.Bytes.CopyTo(newBytes, IncomingBuffer.Length);
if (Debug.Level == 2) // This check is here to prevent following string format from building unnecessarily on level 0 or 1
Debug.LogMessage(LogEventLevel.Verbose, this, "Received:{0}", ComTextHelper.GetEscapedText(newBytes));
byte[] message = new byte[] { };
// Search for the delimiter 0xFF character
for (int i = 0; i < newBytes.Length; i++)
{
if (newBytes[i] == 0xFF)
{
// i will be the index of the delmiter character
message = newBytes.Take(i).ToArray();
// Skip over what we just took and save the rest for next time
newBytes = newBytes.Skip(i).ToArray();
}
}
if (message.Length > 0)
{
// Check for matching ID
if (message[0] != ResponseID)
{
return;
}
switch (message[1])
{
case 0x40:
{
// ACK received
Debug.LogMessage(LogEventLevel.Verbose, this, "ACK Received");
break;
}
case 0x50:
{
if (message[2] == 0xFF)
{
// Completion received
Debug.LogMessage(LogEventLevel.Verbose, this, "Completion Received");
}
else
{
// Inquiry response received. Dequeue the next response handler and invoke it
if (InquiryResponseQueue.Count > 0)
{
var inquiryAction = InquiryResponseQueue.Dequeue();
inquiryAction.Invoke(message.Skip(2).ToArray());
}
else
{
Debug.LogMessage(LogEventLevel.Verbose, this, "Response Queue is empty. Nothing to dequeue.");
}
}
break;
}
case 0x60:
{
// Error message
switch (message[2])
{
case 0x01:
{
// Message Length Error
Debug.LogMessage(LogEventLevel.Verbose, this, "Error from device: Message Length Error");
break;
}
case 0x02:
{
// Syntax Error
Debug.LogMessage(LogEventLevel.Verbose, this, "Error from device: Syntax Error");
break;
}
case 0x03:
{
// Command Buffer Full
Debug.LogMessage(LogEventLevel.Verbose, this, "Error from device: Command Buffer Full");
break;
}
case 0x04:
{
// Command Cancelled
Debug.LogMessage(LogEventLevel.Verbose, this, "Error from device: Command Cancelled");
break;
}
case 0x05:
{
// No Socket
Debug.LogMessage(LogEventLevel.Verbose, this, "Error from device: No Socket");
break;
}
case 0x41:
{
// Command not executable
Debug.LogMessage(LogEventLevel.Verbose, this, "Error from device: Command not executable");
break;
}
}
break;
}
}
if (message == new byte[] { ResponseID, 0x50, 0x02, 0xFF })
{
PowerIsOn = true;
}
else if (message == new byte[] { ResponseID, 0x50, 0x03, 0xFF })
{
PowerIsOn = false;
}
}
}
catch (Exception err)
{
Debug.LogMessage(LogEventLevel.Verbose, this, "Error parsing feedback: {0}", err);
}
finally
{
// Save whatever partial message is here
IncomingBuffer = newBytes;
}
}
/// <summary>
/// Sends a pan/tilt command. If the command is not for fastSpeed then it starts a timer to initiate fast speed.
/// </summary>
/// <param name="cmd"></param>
/// <param name="fastSpeed"></param>
private void SendPanTiltCommand(byte[] cmd, bool fastSpeedEnabled)
{
SendBytes(GetPanTiltCommand(cmd, fastSpeedEnabled));
if (!fastSpeedEnabled)
{
if (SpeedTimer != null)
{
StopSpeedTimer();
}
// Start the timer to send fast speed if still moving after FastSpeedHoldTime elapses
SpeedTimer = new CTimer((o) => SendPanTiltCommand(GetPanTiltCommand(cmd, true), true), FastSpeedHoldTimeMs);
}
}
private void StopSpeedTimer()
{
if (SpeedTimer != null)
{
SpeedTimer.Stop();
SpeedTimer.Dispose();
SpeedTimer = null;
}
}
/// <summary>
/// Generates the pan/tilt command with either slow or fast speed
/// </summary>
/// <param name="cmd"></param>
/// <param name="fastSpeed"></param>
/// <returns></returns>
private byte[] GetPanTiltCommand(byte[] cmd, bool fastSpeed)
{
byte panSpeed;
byte tiltSpeed;
if (!fastSpeed)
{
panSpeed = PanSpeedSlow;
tiltSpeed = TiltSpeedSlow;
}
else
{
panSpeed = PanSpeedFast;
tiltSpeed = TiltSpeedFast;
}
var temp = new byte[] { ID, 0x01, 0x06, 0x01, panSpeed, tiltSpeed };
int length = temp.Length + cmd.Length + 1;
byte[] sum = new byte[length];
temp.CopyTo(sum, 0);
cmd.CopyTo(sum, temp.Length);
sum[length - 1] = 0xFF;
return sum;
}
void SendPowerQuery()
{
SendBytes(new byte[] { ID, 0x09, 0x04, 0x00, 0xFF });
InquiryResponseQueue.Enqueue(HandlePowerResponse);
}
public void PowerOn()
{
SendBytes(new byte[] { ID, 0x01, 0x04, 0x00, 0x02, 0xFF });
SendPowerQuery();
}
void HandlePowerResponse(byte[] response)
{
switch (response[0])
{
case 0x02:
{
PowerIsOn = true;
break;
}
case 0x03:
{
PowerIsOn = false;
break;
}
}
}
public void PowerOff()
{
SendBytes(new byte[] { ID, 0x01, 0x04, 0x00, 0x03, 0xFF });
SendPowerQuery();
}
public void PowerToggle()
{
if (PowerIsOnFeedback.BoolValue)
PowerOff();
else
PowerOn();
}
public void PanLeft()
{
SendPanTiltCommand(new byte[] { 0x01, 0x03 }, false);
// IsMoving = true;
}
public void PanRight()
{
SendPanTiltCommand(new byte[] { 0x02, 0x03 }, false);
// IsMoving = true;
}
public void PanStop()
{
Stop();
}
public void TiltDown()
{
SendPanTiltCommand(new byte[] { 0x03, 0x02 }, false);
// IsMoving = true;
}
public void TiltUp()
{
SendPanTiltCommand(new byte[] { 0x03, 0x01 }, false);
// IsMoving = true;
}
public void TiltStop()
{
Stop();
}
private void SendZoomCommand(byte cmd)
{
SendBytes(new byte[] { ID, 0x01, 0x04, 0x07, cmd, 0xFF });
}
public void ZoomIn()
{
SendZoomCommand(ZoomInCmd);
IsZooming = true;
}
public void ZoomOut()
{
SendZoomCommand(ZoomOutCmd);
IsZooming = true;
}
public void ZoomStop()
{
Stop();
}
public void Stop()
{
if (IsZooming)
{
SendZoomCommand(ZoomStopCmd);
IsZooming = false;
}
else
{
StopSpeedTimer();
SendPanTiltCommand(new byte[] { 0x03, 0x03 }, false);
// IsMoving = false;
}
}
public void PositionHome()
{
SendBytes(new byte[] { ID, 0x01, 0x06, 0x02, PanSpeedFast, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF });
SendBytes(new byte[] { ID, 0x01, 0x04, 0x47, 0x00, 0x00, 0x00, 0x00, 0xFF });
}
public void RecallPreset(int presetNumber)
{
SendBytes(new byte[] { ID, 0x01, 0x04, 0x3F, 0x02, (byte)presetNumber, 0xFF });
}
public void SavePreset(int presetNumber)
{
SendBytes(new byte[] { ID, 0x01, 0x04, 0x3F, 0x01, (byte)presetNumber, 0xFF });
}
#region IHasCameraPresets Members
public event EventHandler<EventArgs> PresetsListHasChanged;
/// <summary>
/// Raises the PresetsListHasChanged event
/// </summary>
protected void OnPresetsListHasChanged()
{
var handler = PresetsListHasChanged;
if (handler == null)
return;
handler.Invoke(this, EventArgs.Empty);
}
public List<CameraPreset> Presets { get; private set; }
public void PresetSelect(int preset)
{
RecallPreset(preset);
}
public void PresetStore(int preset, string description)
{
SavePreset(preset);
}
#endregion
#region IHasCameraFocusControl Members
public void FocusNear()
{
SendBytes(new byte[] { ID, 0x01, 0x04, 0x08, 0x03, 0xFF });
}
public void FocusFar()
{
SendBytes(new byte[] { ID, 0x01, 0x04, 0x08, 0x02, 0xFF });
}
public void FocusStop()
{
SendBytes(new byte[] { ID, 0x01, 0x04, 0x08, 0x00, 0xFF });
}
public void TriggerAutoFocus()
{
SendBytes(new byte[] { ID, 0x01, 0x04, 0x18, 0x01, 0xFF });
SendAutoFocusQuery();
}
#endregion
#region IHasAutoFocus Members
public void SetFocusModeAuto()
{
SendBytes(new byte[] { ID, 0x01, 0x04, 0x38, 0x02, 0xFF });
SendAutoFocusQuery();
}
public void SetFocusModeManual()
{
SendBytes(new byte[] { ID, 0x01, 0x04, 0x38, 0x03, 0xFF });
SendAutoFocusQuery();
}
public void ToggleFocusMode()
{
SendBytes(new byte[] { ID, 0x01, 0x04, 0x38, 0x10, 0xFF });
SendAutoFocusQuery();
}
#endregion
void SendAutoFocusQuery()
{
SendBytes(new byte[] { ID, 0x09, 0x04, 0x38, 0xFF });
InquiryResponseQueue.Enqueue(HandleAutoFocusResponse);
}
void HandleAutoFocusResponse(byte[] response)
{
switch (response[0])
{
case 0x02:
{
// Auto Mode
PowerIsOn = true;
break;
}
case 0x03:
{
// Manual Mode
PowerIsOn = false;
break;
}
}
}
#region IHasCameraOff Members
public BoolFeedback CameraIsOffFeedback { get; private set; }
public void CameraOff()
{
PowerOff();
}
#endregion
}
public class CameraViscaFactory : EssentialsDeviceFactory<CameraVisca>
{
public CameraViscaFactory()
{
TypeNames = new List<string>() { "cameravisca" };
}
public override EssentialsDevice BuildDevice(DeviceConfig dc)
{
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new CameraVisca Device");
var comm = CommFactory.CreateCommForDevice(dc);
var props = Newtonsoft.Json.JsonConvert.DeserializeObject<Cameras.CameraViscaPropertiesConfig>(
dc.Properties.ToString());
return new Cameras.CameraVisca(dc.Key, dc.Name, comm, props);
}
}
public class CameraViscaPropertiesConfig : CameraPropertiesConfig
{
/// <summary>
/// Control ID of the camera (1-7)
/// </summary>
[JsonProperty("id")]
public uint Id { get; set; }
/// <summary>
/// Slow Pan speed (0-18)
/// </summary>
[JsonProperty("panSpeedSlow")]
public uint PanSpeedSlow { get; set; }
/// <summary>
/// Fast Pan speed (0-18)
/// </summary>
[JsonProperty("panSpeedFast")]
public uint PanSpeedFast { get; set; }
/// <summary>
/// Slow tilt speed (0-18)
/// </summary>
[JsonProperty("tiltSpeedSlow")]
public uint TiltSpeedSlow { get; set; }
/// <summary>
/// Fast tilt speed (0-18)
/// </summary>
[JsonProperty("tiltSpeedFast")]
public uint TiltSpeedFast { get; set; }
/// <summary>
/// Time a button must be held before fast speed is engaged (Milliseconds)
/// </summary>
[JsonProperty("fastSpeedHoldTimeMs")]
public uint FastSpeedHoldTimeMs { get; set; }
}

View file

@ -1,32 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Devices.Common.Cameras namespace PepperDash.Essentials.Devices.Common.Cameras
{ {
/// <summary>
/// Event arguments for the CameraSelected event
/// </summary>
[Obsolete("Use CameraSelectedEventArgs<T> instead. This class will be removed in a future version")]
public class CameraSelectedEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the SelectedCamera
/// </summary>
public CameraBase SelectedCamera { get; private set; }
/// <summary>
/// Constructor for CameraSelectedEventArgs
/// </summary>
/// <param name="camera"></param>
public CameraSelectedEventArgs(CameraBase camera)
{
SelectedCamera = camera;
}
}
/// <summary> /// <summary>
/// Event arguments for the CameraSelected event /// Event arguments for the CameraSelected event
/// </summary> /// </summary>

View file

@ -1,41 +0,0 @@
using Newtonsoft.Json;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using System;
using System.Collections.Generic;
namespace PepperDash.Essentials.Devices.Common.Cameras
{
/// <summary>
/// Interface for devices that have cameras
/// </summary>
[Obsolete("Use IHasCamerasWithControls instead. This interface will be removed in a future version")]
public interface IHasCameras : IKeyName
{
/// <summary>
/// Event that is raised when a camera is selected
/// </summary>
event EventHandler<CameraSelectedEventArgs> CameraSelected;
/// <summary>
/// List of cameras on the device. This should be a list of CameraBase objects
/// </summary>
List<CameraBase> Cameras { get; }
/// <summary>
/// The currently selected camera. This should be a CameraBase object
/// </summary>
CameraBase SelectedCamera { get; }
/// <summary>
/// Feedback that indicates the currently selected camera
/// </summary>
StringFeedback SelectedCameraFeedback { get; }
/// <summary>
/// Selects a camera from the list of available cameras based on the provided key.
/// </summary>
/// <param name="key">The unique identifier or name of the camera to select.</param>
void SelectCamera(string key);
}
}

View file

@ -8,7 +8,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
/// <summary> /// <summary>
/// Interface for devices that have cameras with controls /// Interface for devices that have cameras with controls
/// </summary> /// </summary>
public interface IHasCamerasWithControls : IKeyName, IKeyed public interface IHasCamerasWithControls : IKeyName
{ {
/// <summary> /// <summary>
/// List of cameras on the device. This should be a list of IHasCameraControls objects /// List of cameras on the device. This should be a list of IHasCameraControls objects

View file

@ -5,7 +5,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
/// <summary> /// <summary>
/// Defines the contract for IHasCodecCameras /// Defines the contract for IHasCodecCameras
/// </summary> /// </summary>
public interface IHasCodecCameras : IHasCameras, IHasFarEndCameraControl public interface IHasCodecCameras : IHasCamerasWithControls, IHasFarEndCameraControl
{ {
} }

View file

@ -2,7 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Crestron.SimplSharp; using System.Timers;
using PepperDash.Core; using PepperDash.Core;
using Serilog.Events; using Serilog.Events;
@ -56,16 +56,14 @@ namespace PepperDash.Essentials.Devices.Common.Codec
} }
} }
private readonly CTimer _scheduleChecker; private Timer _scheduleChecker;
/// <summary> /// <summary>
/// Initializes a new instance of the CodecScheduleAwareness class with default poll time /// Initializes a new instance of the CodecScheduleAwareness class with default poll time
/// </summary> /// </summary>
public CodecScheduleAwareness() public CodecScheduleAwareness()
{ {
Meetings = new List<Meeting>(); Init(1000);
_scheduleChecker = new CTimer(CheckSchedule, null, 1000, 1000);
} }
/// <summary> /// <summary>
@ -73,10 +71,17 @@ namespace PepperDash.Essentials.Devices.Common.Codec
/// </summary> /// </summary>
/// <param name="pollTime">The poll time in milliseconds for checking schedule changes</param> /// <param name="pollTime">The poll time in milliseconds for checking schedule changes</param>
public CodecScheduleAwareness(long pollTime) public CodecScheduleAwareness(long pollTime)
{
Init(pollTime);
}
private void Init(long pollTime)
{ {
Meetings = new List<Meeting>(); Meetings = new List<Meeting>();
_scheduleChecker = new CTimer(CheckSchedule, null, pollTime, pollTime); _scheduleChecker = new Timer(pollTime) { AutoReset = true };
_scheduleChecker.Elapsed += (s, e) => CheckSchedule(null);
_scheduleChecker.Start();
} }
/// <summary> /// <summary>

View file

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Crestron.SimplSharp; using System.Timers;
using Crestron.SimplSharpPro; using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using PepperDash.Core; using PepperDash.Core;
@ -207,22 +207,26 @@ public class BasicIrDisplay : DisplayBase, IBasicVolumeControls, IBridgeAdvanced
{ {
_IsWarmingUp = true; _IsWarmingUp = true;
IsWarmingUpFeedback.FireUpdate(); IsWarmingUpFeedback.FireUpdate();
new CTimer(o => var t = new Timer(10000) { AutoReset = false };
t.Elapsed += (s, e) =>
{ {
_IsWarmingUp = false; _IsWarmingUp = false;
IsWarmingUpFeedback.FireUpdate(); IsWarmingUpFeedback.FireUpdate();
}, 10000); };
t.Start();
} }
void StartCoolingTimer() void StartCoolingTimer()
{ {
_IsCoolingDown = true; _IsCoolingDown = true;
IsCoolingDownFeedback.FireUpdate(); IsCoolingDownFeedback.FireUpdate();
new CTimer(o => var t = new Timer(7000) { AutoReset = false };
t.Elapsed += (s, e) =>
{ {
_IsCoolingDown = false; _IsCoolingDown = false;
IsCoolingDownFeedback.FireUpdate(); IsCoolingDownFeedback.FireUpdate();
}, 7000); };
t.Start();
} }
#region IRoutingSink Members #region IRoutingSink Members

View file

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Crestron.SimplSharp; using System.Timers;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json; using Newtonsoft.Json;
using PepperDash.Core; using PepperDash.Core;
@ -103,12 +103,12 @@ public abstract class DisplayBase : EssentialsDevice, IDisplay, ICurrentSources,
/// <summary> /// <summary>
/// Timer used for managing display warmup timing. /// Timer used for managing display warmup timing.
/// </summary> /// </summary>
protected CTimer WarmupTimer; protected Timer WarmupTimer;
/// <summary> /// <summary>
/// Timer used for managing display cooldown timing. /// Timer used for managing display cooldown timing.
/// </summary> /// </summary>
protected CTimer CooldownTimer; protected Timer CooldownTimer;
#region IRoutingInputs Members #region IRoutingInputs Members

View file

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Crestron.SimplSharp; using System.Timers;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
@ -17,12 +17,14 @@ namespace PepperDash.Essentials.Devices.Common.Displays;
/// </summary> /// </summary>
public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeAdvanced, IHasInputs<string>, IRoutingSinkWithSwitchingWithInputPort, IHasPowerControlWithFeedback public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeAdvanced, IHasInputs<string>, IRoutingSinkWithSwitchingWithInputPort, IHasPowerControlWithFeedback
{ {
/// <inheritdoc />
public ISelectableItems<string> Inputs { get; private set; } public ISelectableItems<string> Inputs { get; private set; }
bool _PowerIsOn; bool _PowerIsOn;
bool _IsWarmingUp; bool _IsWarmingUp;
bool _IsCoolingDown; bool _IsCoolingDown;
/// <inheritdoc />
protected override Func<bool> PowerIsOnFeedbackFunc protected override Func<bool> PowerIsOnFeedbackFunc
{ {
get get
@ -33,6 +35,8 @@ public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeA
}; };
} }
} }
/// <inheritdoc />
protected override Func<bool> IsCoolingDownFeedbackFunc protected override Func<bool> IsCoolingDownFeedbackFunc
{ {
get get
@ -43,6 +47,8 @@ public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeA
}; };
} }
} }
/// <inheritdoc />
protected override Func<bool> IsWarmingUpFeedbackFunc protected override Func<bool> IsWarmingUpFeedbackFunc
{ {
get get
@ -53,13 +59,21 @@ public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeA
}; };
} }
} }
/// <inheritdoc />
protected override Func<string> CurrentInputFeedbackFunc { get { return () => Inputs.CurrentItem; } } protected override Func<string> CurrentInputFeedbackFunc { get { return () => Inputs.CurrentItem; } }
int VolumeHeldRepeatInterval = 200; int VolumeHeldRepeatInterval = 200;
ushort VolumeInterval = 655; ushort VolumeInterval = 655;
ushort _FakeVolumeLevel = 31768; ushort _FakeVolumeLevel = 31768;
bool _IsMuted; bool _IsMuted;
Timer _volumeUpTimer;
Timer _volumeDownTimer;
/// <summary>
/// Constructor for MockDisplay
/// </summary> <param name="key"></param>
/// <param name="name"></param>
public MockDisplay(string key, string name) public MockDisplay(string key, string name)
: base(key, name) : base(key, name)
{ {
@ -108,16 +122,19 @@ public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeA
_IsWarmingUp = true; _IsWarmingUp = true;
IsWarmingUpFeedback.InvokeFireUpdate(); IsWarmingUpFeedback.InvokeFireUpdate();
// Fake power-up cycle // Fake power-up cycle
WarmupTimer = new CTimer(o => WarmupTimer = new Timer(WarmupTime) { AutoReset = false };
WarmupTimer.Elapsed += (s, e) =>
{ {
_IsWarmingUp = false; _IsWarmingUp = false;
_PowerIsOn = true; _PowerIsOn = true;
IsWarmingUpFeedback.InvokeFireUpdate(); IsWarmingUpFeedback.InvokeFireUpdate();
PowerIsOnFeedback.InvokeFireUpdate(); PowerIsOnFeedback.InvokeFireUpdate();
}, WarmupTime); };
WarmupTimer.Start();
} }
} }
/// <inheritdoc />
public override void PowerOff() public override void PowerOff()
{ {
// If a display has unreliable-power off feedback, just override this and // If a display has unreliable-power off feedback, just override this and
@ -127,17 +144,20 @@ public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeA
_IsCoolingDown = true; _IsCoolingDown = true;
IsCoolingDownFeedback.InvokeFireUpdate(); IsCoolingDownFeedback.InvokeFireUpdate();
// Fake cool-down cycle // Fake cool-down cycle
CooldownTimer = new CTimer(o => CooldownTimer = new Timer(CooldownTime) { AutoReset = false };
CooldownTimer.Elapsed += (s, e) =>
{ {
Debug.LogMessage(LogEventLevel.Verbose, "Cooldown timer ending", this); Debug.LogMessage(LogEventLevel.Verbose, "Cooldown timer ending", this);
_IsCoolingDown = false; _IsCoolingDown = false;
IsCoolingDownFeedback.InvokeFireUpdate(); IsCoolingDownFeedback.InvokeFireUpdate();
_PowerIsOn = false; _PowerIsOn = false;
PowerIsOnFeedback.InvokeFireUpdate(); PowerIsOnFeedback.InvokeFireUpdate();
}, CooldownTime); };
CooldownTimer.Start();
} }
} }
/// <inheritdoc />
public override void PowerToggle() public override void PowerToggle()
{ {
if (PowerIsOnFeedback.BoolValue && !IsWarmingUpFeedback.BoolValue) if (PowerIsOnFeedback.BoolValue && !IsWarmingUpFeedback.BoolValue)
@ -146,6 +166,7 @@ public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeA
PowerOn(); PowerOn();
} }
/// <inheritdoc />
public override void ExecuteSwitch(object selector) public override void ExecuteSwitch(object selector)
{ {
try try
@ -184,6 +205,7 @@ public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeA
} }
} }
/// <inheritdoc />
public void SetInput(string selector) public void SetInput(string selector)
{ {
ISelectableItem currentInput = null; ISelectableItem currentInput = null;
@ -206,6 +228,7 @@ public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeA
#region IBasicVolumeWithFeedback Members #region IBasicVolumeWithFeedback Members
/// <inheritdoc />
public IntFeedback VolumeLevelFeedback { get; private set; } public IntFeedback VolumeLevelFeedback { get; private set; }
/// <summary> /// <summary>
@ -245,32 +268,44 @@ public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeA
#region IBasicVolumeControls Members #region IBasicVolumeControls Members
/// <inheritdoc />
public void VolumeUp(bool pressRelease) public void VolumeUp(bool pressRelease)
{ {
//while (pressRelease)
//{
Debug.LogMessage(LogEventLevel.Verbose, this, "Volume Down {0}", pressRelease);
if (pressRelease) if (pressRelease)
{ {
var newLevel = _FakeVolumeLevel + VolumeInterval; SetVolume((ushort)(_FakeVolumeLevel + VolumeInterval));
SetVolume((ushort)newLevel); if (_volumeUpTimer == null)
CrestronEnvironment.Sleep(VolumeHeldRepeatInterval); {
_volumeUpTimer = new Timer(VolumeHeldRepeatInterval) { AutoReset = true };
_volumeUpTimer.Elapsed += (s, e) => SetVolume((ushort)(_FakeVolumeLevel + VolumeInterval));
_volumeUpTimer.Start();
}
}
else
{
_volumeUpTimer?.Stop();
_volumeUpTimer = null;
} }
//}
} }
/// <inheritdoc />
public void VolumeDown(bool pressRelease) public void VolumeDown(bool pressRelease)
{ {
//while (pressRelease)
//{
Debug.LogMessage(LogEventLevel.Verbose, this, "Volume Up {0}", pressRelease);
if (pressRelease) if (pressRelease)
{ {
var newLevel = _FakeVolumeLevel - VolumeInterval; SetVolume((ushort)(_FakeVolumeLevel - VolumeInterval));
SetVolume((ushort)newLevel); if (_volumeDownTimer == null)
CrestronEnvironment.Sleep(VolumeHeldRepeatInterval); {
_volumeDownTimer = new Timer(VolumeHeldRepeatInterval) { AutoReset = true };
_volumeDownTimer.Elapsed += (s, e) => SetVolume((ushort)(_FakeVolumeLevel - VolumeInterval));
_volumeDownTimer.Start();
}
}
else
{
_volumeDownTimer?.Stop();
_volumeDownTimer = null;
} }
//}
} }
/// <summary> /// <summary>
@ -284,6 +319,7 @@ public class MockDisplay : TwoWayDisplayBase, IBasicVolumeWithFeedback, IBridgeA
#endregion #endregion
/// <inheritdoc />
public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge) public void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
{ {
LinkDisplayToApi(this, trilist, joinStart, joinMapKey, bridge); LinkDisplayToApi(this, trilist, joinStart, joinMapKey, bridge);
@ -303,6 +339,7 @@ public class MockDisplayFactory : EssentialsDeviceFactory<MockDisplay>
TypeNames = new List<string>() { "mockdisplay", "mockdisplay2" }; TypeNames = new List<string>() { "mockdisplay", "mockdisplay2" };
} }
/// <inheritdoc />
public override EssentialsDevice BuildDevice(DeviceConfig dc) public override EssentialsDevice BuildDevice(DeviceConfig dc)
{ {
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Mock Display Device"); Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new Mock Display Device");

View file

@ -1,5 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Crestron.SimplSharp; using System.Timers;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Essentials.Core; using PepperDash.Essentials.Core;
using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.Config;
@ -84,7 +84,9 @@ public class RelayControlledShade : ShadeBase, IShadesOpenCloseStop
void PulseOutput(ISwitchedOutput output, int pulseTime) void PulseOutput(ISwitchedOutput output, int pulseTime)
{ {
output.On(); output.On();
CTimer pulseTimer = new CTimer(new CTimerCallbackFunction((o) => output.Off()), pulseTime); var pulseTimer = new Timer(pulseTime) { AutoReset = false };
pulseTimer.Elapsed += (s, e) => output.Off();
pulseTimer.Start();
} }
/// <summary> /// <summary>

View file

@ -3,7 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Crestron.SimplSharp; using System.Timers;
using Crestron.SimplSharpPro.DeviceSupport; using Crestron.SimplSharpPro.DeviceSupport;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -170,12 +170,16 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
OnCallStatusChange(call); OnCallStatusChange(call);
//ActiveCallCountFeedback.FireUpdate(); //ActiveCallCountFeedback.FireUpdate();
// Simulate 2-second ring, then connecting, then connected // Simulate 2-second ring, then connecting, then connected
new CTimer(o => var dialTimer = new Timer(2000) { AutoReset = false };
dialTimer.Elapsed += (s, e) =>
{ {
call.Type = eCodecCallType.Video; call.Type = eCodecCallType.Video;
SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call); SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call);
new CTimer(oo => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call), 1000); var connectTimer = new Timer(1000) { AutoReset = false };
}, 2000); connectTimer.Elapsed += (ss, ee) => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call);
connectTimer.Start();
};
dialTimer.Start();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -188,12 +192,16 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
//ActiveCallCountFeedback.FireUpdate(); //ActiveCallCountFeedback.FireUpdate();
// Simulate 2-second ring, then connecting, then connected // Simulate 2-second ring, then connecting, then connected
new CTimer(o => var dialMeetingTimer = new Timer(2000) { AutoReset = false };
dialMeetingTimer.Elapsed += (s, e) =>
{ {
call.Type = eCodecCallType.Video; call.Type = eCodecCallType.Video;
SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call); SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call);
new CTimer(oo => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call), 1000); var connectTimer = new Timer(1000) { AutoReset = false };
}, 2000); connectTimer.Elapsed += (ss, ee) => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call);
connectTimer.Start();
};
dialMeetingTimer.Start();
} }
@ -226,7 +234,9 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
{ {
Debug.LogMessage(LogEventLevel.Debug, this, "AcceptCall"); Debug.LogMessage(LogEventLevel.Debug, this, "AcceptCall");
SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call); SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connecting, call);
new CTimer(o => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call), 1000); var acceptTimer = new Timer(1000) { AutoReset = false };
acceptTimer.Elapsed += (s, e) => SetNewCallStatusAndFireCallStatusChange(eCodecCallStatus.Connected, call);
acceptTimer.Start();
// should already be in active list // should already be in active list
} }
@ -624,7 +634,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
SupportsCameraOff = false; SupportsCameraOff = false;
Cameras = new List<CameraBase>(); Cameras = new List<IHasCameraControls>();
var internalCamera = new MockVCCamera(Key + "-camera1", "Near End", this); var internalCamera = new MockVCCamera(Key + "-camera1", "Near End", this);
@ -679,19 +689,19 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
/// <summary> /// <summary>
/// CameraSelected event. Fired when a camera is selected /// CameraSelected event. Fired when a camera is selected
/// </summary> /// </summary>
public event EventHandler<CameraSelectedEventArgs> CameraSelected; public event EventHandler<CameraSelectedEventArgs<IHasCameraControls>> CameraSelected;
/// <summary> /// <summary>
/// Gets the list of cameras associated with this codec /// Gets the list of cameras associated with this codec
/// </summary> /// </summary>
public List<CameraBase> Cameras { get; private set; } public List<IHasCameraControls> Cameras { get; private set; }
private CameraBase _selectedCamera; private IHasCameraControls _selectedCamera;
/// <summary> /// <summary>
/// Returns the selected camera /// Returns the selected camera
/// </summary> /// </summary>
public CameraBase SelectedCamera public IHasCameraControls SelectedCamera
{ {
get get
{ {
@ -702,7 +712,7 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
_selectedCamera = value; _selectedCamera = value;
SelectedCameraFeedback.FireUpdate(); SelectedCameraFeedback.FireUpdate();
ControllingFarEndCameraFeedback.FireUpdate(); ControllingFarEndCameraFeedback.FireUpdate();
CameraSelected?.Invoke(this, new CameraSelectedEventArgs(SelectedCamera)); CameraSelected?.Invoke(this, new CameraSelectedEventArgs<IHasCameraControls>(SelectedCamera));
} }
} }

View file

@ -24,10 +24,19 @@ using System.Threading.Tasks;
namespace PepperDash.Essentials.Devices.Common.VideoCodec; namespace PepperDash.Essentials.Devices.Common.VideoCodec;
/// <summary>
/// Base class for video codecs. Contains common properties, methods, and feedback for video codecs.
/// Also contains the logic to link commonly implemented interfaces to the API bridge.
/// </summary>
public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutputs, public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutputs,
IUsageTracking, IHasDialer, IHasContentSharing, ICodecAudio, iVideoCodecInfo, IBridgeAdvanced, IHasStandbyMode IUsageTracking, IHasDialer, IHasContentSharing, ICodecAudio, iVideoCodecInfo, IBridgeAdvanced, IHasStandbyMode
{ {
private const int XSigEncoding = 28591; private const int XSigEncoding = 28591;
/// <summary>
/// Maximum number of participants to display in the participant list.
/// This is used to limit the number of XSigs that need to be updated and processed on the control system when there are many participants in a call.
/// </summary>
protected const int MaxParticipants = 50; protected const int MaxParticipants = 50;
private readonly byte[] _clearBytes = XSigHelpers.ClearOutputs(); private readonly byte[] _clearBytes = XSigHelpers.ClearOutputs();
@ -35,6 +44,10 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
private readonly BasicTriList _directoryTrilist; private readonly BasicTriList _directoryTrilist;
private readonly VideoCodecControllerJoinMap _directoryJoinmap; private readonly VideoCodecControllerJoinMap _directoryJoinmap;
/// <summary>
/// Time format specifier for any time-related XSigs.
/// This should be set by the inheriting class based on the expected format of the codec (e.g. "hh:mm:ss" or "mm:ss")
/// </summary>
protected string _timeFormatSpecifier; protected string _timeFormatSpecifier;
/// <summary> /// <summary>
@ -56,7 +69,6 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
SharingSourceFeedback = new StringFeedback("sharingSource", SharingSourceFeedbackFunc); SharingSourceFeedback = new StringFeedback("sharingSource", SharingSourceFeedbackFunc);
SharingContentIsOnFeedback = new BoolFeedback("sharingContentIsOn", SharingContentIsOnFeedbackFunc); SharingContentIsOnFeedback = new BoolFeedback("sharingContentIsOn", SharingContentIsOnFeedbackFunc);
// TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set
MeetingsToDisplayFeedback = new IntFeedback(() => MeetingsToDisplay); MeetingsToDisplayFeedback = new IntFeedback(() => MeetingsToDisplay);
InputPorts = new RoutingPortCollection<RoutingInputPort>(); InputPorts = new RoutingPortCollection<RoutingInputPort>();
@ -110,7 +122,10 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
/// </summary> /// </summary>
public bool ShowSelfViewByDefault { get; protected set; } public bool ShowSelfViewByDefault { get; protected set; }
/// <inheritdoc />
public bool SupportsCameraOff { get; protected set; } public bool SupportsCameraOff { get; protected set; }
/// <inheritdoc />
public bool SupportsCameraAutoMode { get; protected set; } public bool SupportsCameraAutoMode { get; protected set; }
/// <summary> /// <summary>
@ -282,6 +297,8 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
/// Sends DTMF tones /// Sends DTMF tones
/// </summary> /// </summary>
public abstract void SendDtmf(string s); public abstract void SendDtmf(string s);
/// <inheritdoc />
public virtual void SendDtmf(string s, CodecActiveCallItem call) { } public virtual void SendDtmf(string s, CodecActiveCallItem call) { }
#endregion #endregion
@ -746,7 +763,6 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
{ {
if (!args.DeviceOnLine) return; if (!args.DeviceOnLine) return;
// TODO [ ] Issue #868
trilist.SetString(joinMap.CurrentParticipants.JoinNumber, "\xFC"); trilist.SetString(joinMap.CurrentParticipants.JoinNumber, "\xFC");
UpdateParticipantsXSig(codec, trilist, joinMap); UpdateParticipantsXSig(codec, trilist, joinMap);
}; };
@ -974,10 +990,8 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
{ {
if (!args.DeviceOnLine) return; if (!args.DeviceOnLine) return;
// TODO [ ] Issue #868
trilist.SetString(joinMap.Schedule.JoinNumber, "\xFC"); trilist.SetString(joinMap.Schedule.JoinNumber, "\xFC");
UpdateMeetingsList(codec, trilist, joinMap); UpdateMeetingsList(codec, trilist, joinMap);
// TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set
MeetingsToDisplayFeedback.LinkInputSig(trilist.UShortInput[joinMap.MeetingsToDisplay.JoinNumber]); MeetingsToDisplayFeedback.LinkInputSig(trilist.UShortInput[joinMap.MeetingsToDisplay.JoinNumber]);
}; };
} }
@ -1006,16 +1020,14 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
{ {
if (!args.DeviceOnLine) return; if (!args.DeviceOnLine) return;
// TODO [ ] Issue #868
trilist.SetString(joinMap.Schedule.JoinNumber, "\xFC"); trilist.SetString(joinMap.Schedule.JoinNumber, "\xFC");
UpdateMeetingsListXSig(_currentMeetings); UpdateMeetingsListXSig(_currentMeetings);
}; };
} }
// TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set
private int _meetingsToDisplay = 3; private int _meetingsToDisplay = 3;
// TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set
/// <inheritdoc />
protected int MeetingsToDisplay protected int MeetingsToDisplay
{ {
get { return _meetingsToDisplay; } get { return _meetingsToDisplay; }
@ -1026,12 +1038,13 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
} }
} }
// TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set
/// <inheritdoc />
public IntFeedback MeetingsToDisplayFeedback { get; set; } public IntFeedback MeetingsToDisplayFeedback { get; set; }
private string UpdateMeetingsListXSig(List<Meeting> meetings) private string UpdateMeetingsListXSig(List<Meeting> meetings)
{ {
// TODO [ ] hotfix/videocodecbase-max-meeting-xsig-set
//const int _meetingsToDisplay = 3; //const int _meetingsToDisplay = 3;
const int maxDigitals = 2; const int maxDigitals = 2;
const int maxStrings = 7; const int maxStrings = 7;
@ -1291,7 +1304,6 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
Debug.LogMessage(LogEventLevel.Verbose, this, "Creating XSIG token array with size {0}", maxMethods * offset); Debug.LogMessage(LogEventLevel.Verbose, this, "Creating XSIG token array with size {0}", maxMethods * offset);
// TODO: Add code to generate XSig data
foreach (var method in contact.ContactMethods) foreach (var method in contact.ContactMethods)
{ {
if (arrayIndex >= maxMethods * offset) if (arrayIndex >= maxMethods * offset)
@ -1498,7 +1510,6 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
{ {
if (!args.DeviceOnLine) return; if (!args.DeviceOnLine) return;
// TODO [ ] #983
Debug.LogMessage(LogEventLevel.Information, this, "LinkVideoCodecCallControlsToApi: device is {0}, IsInCall {1}", args.DeviceOnLine ? "online" : "offline", IsInCall); Debug.LogMessage(LogEventLevel.Information, this, "LinkVideoCodecCallControlsToApi: device is {0}, IsInCall {1}", args.DeviceOnLine ? "online" : "offline", IsInCall);
trilist.SetBool(joinMap.HookState.JoinNumber, IsInCall); trilist.SetBool(joinMap.HookState.JoinNumber, IsInCall);
trilist.SetString(joinMap.CurrentCallData.JoinNumber, "\xFC"); trilist.SetString(joinMap.CurrentCallData.JoinNumber, "\xFC");
@ -1531,12 +1542,10 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, call.Direction.ToString()); tokenArray[stringIndex + 2] = new XSigSerialToken(stringIndex + 3, call.Direction.ToString());
tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, call.Type.ToString()); tokenArray[stringIndex + 3] = new XSigSerialToken(stringIndex + 4, call.Type.ToString());
tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, call.Status.ToString()); tokenArray[stringIndex + 4] = new XSigSerialToken(stringIndex + 5, call.Status.ToString());
if (call.Duration != null)
{ // May need to verify correct string format here
// May need to verify correct string format here var dur = string.Format("{0:c}", call.Duration);
var dur = string.Format("{0:c}", call.Duration); tokenArray[stringIndex + 5] = new XSigSerialToken(stringIndex + 6, dur);
tokenArray[arrayIndex + 6] = new XSigSerialToken(stringIndex + 6, dur);
}
arrayIndex += offset; arrayIndex += offset;
stringIndex += maxStrings; stringIndex += maxStrings;
@ -1879,7 +1888,6 @@ public abstract class VideoCodecBase : ReconfigurableDevice, IRoutingInputsOutpu
{ {
if (!args.DeviceOnLine) return; if (!args.DeviceOnLine) return;
// TODO [ ] Issue #868
trilist.SetString(joinMap.CameraPresetNames.JoinNumber, "\xFC"); trilist.SetString(joinMap.CameraPresetNames.JoinNumber, "\xFC");
SetCameraPresetNames(presetCodec.NearEndPresets); SetCameraPresetNames(presetCodec.NearEndPresets);
}; };

View file

@ -1,100 +0,0 @@
using Newtonsoft.Json;
using PepperDash.Essentials.Devices.Common.Cameras;
using System;
using System.Collections.Generic;
namespace PepperDash.Essentials.AppServer.Messengers
{
/// <summary>
/// Messenger for devices that implement the IHasCameras interface.
/// </summary>
[Obsolete("Use IHasCamerasWithControlsMessenger instead. This class will be removed in a future version")]
public class IHasCamerasMessenger : MessengerBase
{
/// <summary>
/// Device being bridged that implements IHasCameras interface.
/// </summary>
public IHasCameras CameraController { get; private set; }
/// <summary>
/// Messenger for devices that implement IHasCameras interface.
/// </summary>
/// <param name="key"></param>
/// <param name="cameraController"></param>
/// <param name="messagePath"></param>
/// <exception cref="ArgumentNullException"></exception>
public IHasCamerasMessenger(string key, string messagePath, IHasCameras cameraController)
: base(key, messagePath, cameraController)
{
CameraController = cameraController ?? throw new ArgumentNullException("cameraController");
CameraController.CameraSelected += CameraController_CameraSelected;
}
private void CameraController_CameraSelected(object sender, CameraSelectedEventArgs e)
{
PostStatusMessage(new IHasCamerasStateMessage
{
SelectedCamera = e.SelectedCamera
});
}
/// <summary>
/// Registers the actions for this messenger.
/// </summary>
/// <exception cref="ArgumentException"></exception>
protected override void RegisterActions()
{
base.RegisterActions();
AddAction("/fullStatus", (id, context) => SendFullStatus(id));
AddAction("/cameraListStatus", (id, content) => SendFullStatus(id));
AddAction("/selectCamera", (id, content) =>
{
var cameraKey = content?.ToObject<string>();
if (!string.IsNullOrEmpty(cameraKey))
{
CameraController.SelectCamera(cameraKey);
}
else
{
throw new ArgumentException("Content must be a string representing the camera key");
}
});
}
private void SendFullStatus(string clientId)
{
var state = new IHasCamerasStateMessage
{
CameraList = CameraController.Cameras,
SelectedCamera = CameraController.SelectedCamera
};
PostStatusMessage(state, clientId);
}
}
/// <summary>
/// State message for devices that implement the IHasCameras interface.
/// </summary>
public class IHasCamerasStateMessage : DeviceStateMessageBase
{
/// <summary>
/// List of cameras available in the device.
/// </summary>
[JsonProperty("cameraList", NullValueHandling = NullValueHandling.Ignore)]
public List<CameraBase> CameraList { get; set; }
/// <summary>
/// The currently selected camera on the device.
/// </summary>
[JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)]
public CameraBase SelectedCamera { get; set; }
}
}

View file

@ -285,7 +285,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
try try
{ {
//Debug.Console(2, this, "*********************Setting DeviceStateMessageProperties on MobileControlResponseMessage");
deviceState.SetInterfaces(_deviceInterfaces); deviceState.SetInterfaces(_deviceInterfaces);
deviceState.Key = _device.Key; deviceState.Key = _device.Key;

View file

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Crestron.SimplSharp; using System.Timers;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Core; using PepperDash.Core;
@ -13,7 +13,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
private const long ButtonHeartbeatInterval = 1000; private const long ButtonHeartbeatInterval = 1000;
private static readonly Dictionary<string, CTimer> _pushedActions = new Dictionary<string, CTimer>(); private static readonly Dictionary<string, Timer> _pushedActions = new Dictionary<string, Timer>();
private static readonly Dictionary<string, Action<string, Action<bool>>> _pushedActionHandlers; private static readonly Dictionary<string, Action<string, Action<bool>>> _pushedActionHandlers;
@ -31,7 +31,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
Debug.LogDebug("Attempting to add timer for {deviceKey}", deviceKey); Debug.LogDebug("Attempting to add timer for {deviceKey}", deviceKey);
if (_pushedActions.TryGetValue(deviceKey, out CTimer cancelTimer)) if (_pushedActions.TryGetValue(deviceKey, out Timer cancelTimer))
{ {
Debug.LogDebug("Timer for {deviceKey} already exists", deviceKey); Debug.LogDebug("Timer for {deviceKey} already exists", deviceKey);
return; return;
@ -41,14 +41,16 @@ namespace PepperDash.Essentials.AppServer.Messengers
action(true); action(true);
cancelTimer = new CTimer(o => cancelTimer = new Timer(ButtonHeartbeatInterval) { AutoReset = false };
cancelTimer.Elapsed += (s, e) =>
{ {
Debug.LogDebug("Timer expired for {deviceKey}", deviceKey); Debug.LogDebug("Timer expired for {deviceKey}", deviceKey);
action(false); action(false);
_pushedActions.Remove(deviceKey); _pushedActions.Remove(deviceKey);
}, ButtonHeartbeatInterval); };
cancelTimer.Start();
_pushedActions.Add(deviceKey, cancelTimer); _pushedActions.Add(deviceKey, cancelTimer);
} }
@ -57,7 +59,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
Debug.LogDebug("Attempting to reset timer for {deviceKey}", deviceKey); Debug.LogDebug("Attempting to reset timer for {deviceKey}", deviceKey);
if (!_pushedActions.TryGetValue(deviceKey, out CTimer cancelTimer)) if (!_pushedActions.TryGetValue(deviceKey, out Timer cancelTimer))
{ {
Debug.LogDebug("Timer for {deviceKey} not found", deviceKey); Debug.LogDebug("Timer for {deviceKey} not found", deviceKey);
return; return;
@ -65,14 +67,16 @@ namespace PepperDash.Essentials.AppServer.Messengers
Debug.LogDebug("Resetting timer for {deviceKey} with due time {dueTime}", deviceKey, ButtonHeartbeatInterval); Debug.LogDebug("Resetting timer for {deviceKey} with due time {dueTime}", deviceKey, ButtonHeartbeatInterval);
cancelTimer.Reset(ButtonHeartbeatInterval); cancelTimer.Stop();
cancelTimer.Interval = ButtonHeartbeatInterval;
cancelTimer.Start();
} }
private static void StopTimer(string deviceKey, Action<bool> action) private static void StopTimer(string deviceKey, Action<bool> action)
{ {
Debug.LogDebug("Attempting to stop timer for {deviceKey}", deviceKey); Debug.LogDebug("Attempting to stop timer for {deviceKey}", deviceKey);
if (!_pushedActions.TryGetValue(deviceKey, out CTimer cancelTimer)) if (!_pushedActions.TryGetValue(deviceKey, out Timer cancelTimer))
{ {
Debug.LogDebug("Timer for {deviceKey} not found", deviceKey); Debug.LogDebug("Timer for {deviceKey} not found", deviceKey);
return; return;
@ -85,6 +89,11 @@ namespace PepperDash.Essentials.AppServer.Messengers
_pushedActions.Remove(deviceKey); _pushedActions.Remove(deviceKey);
} }
/// <summary>
/// Gets the handler for a given press and hold message type
/// </summary>
/// <param name="value">The press and hold message type.</param>
/// <returns>The handler for the specified message type.</returns>
public static Action<string, Action<bool>> GetPressAndHoldHandler(string value) public static Action<string, Action<bool>> GetPressAndHoldHandler(string value)
{ {
Debug.LogDebug("Getting press and hold handler for {value}", value); Debug.LogDebug("Getting press and hold handler for {value}", value);

View file

@ -33,7 +33,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
protected override void RegisterActions() protected override void RegisterActions()
{ {
Debug.Console(2, "********** Direct Route Messenger CustomRegisterWithAppServer **********"); this.LogDebug("Direct Route Messenger CustomRegisterWithAppServer **********");
//Audio source //Audio source
@ -81,7 +81,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
var b = content.ToObject<MobileControlSimpleContent<bool>>(); var b = content.ToObject<MobileControlSimpleContent<bool>>();
Debug.Console(1, "Current Sharing Mode: {2}\r\nadvanced sharing mode: {0} join number: {1}", b.Value, this.LogDebug("Current Sharing Mode: {2}\r\nadvanced sharing mode: {0} join number: {1}", b.Value,
JoinMap.AdvancedSharingModeOn.JoinNumber, JoinMap.AdvancedSharingModeOn.JoinNumber,
_eisc.BooleanOutput[JoinMap.AdvancedSharingModeOn.JoinNumber].BoolValue); _eisc.BooleanOutput[JoinMap.AdvancedSharingModeOn.JoinNumber].BoolValue);

View file

@ -255,10 +255,10 @@ namespace PepperDash.Essentials.AppServer.Messengers
_eisc.SetUshort(JoinMap.DirectorySelectRow.JoinNumber, u); _eisc.SetUshort(JoinMap.DirectorySelectRow.JoinNumber, u);
_eisc.PulseBool(JoinMap.DirectoryLineSelected.JoinNumber); _eisc.PulseBool(JoinMap.DirectoryLineSelected.JoinNumber);
} }
catch (Exception) catch (Exception e)
{ {
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, this.LogException(e,"directoryById request contains non-numeric ID incompatible with SIMPL bridge: {0}", e.Message);
"/directoryById request contains non-numeric ID incompatible with SIMPL bridge"); this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
} }
}); });
AddAction("/directorySelectContact", (id, content) => AddAction("/directorySelectContact", (id, content) =>
@ -270,9 +270,10 @@ namespace PepperDash.Essentials.AppServer.Messengers
_eisc.SetUshort(JoinMap.DirectorySelectRow.JoinNumber, u); _eisc.SetUshort(JoinMap.DirectorySelectRow.JoinNumber, u);
_eisc.PulseBool(JoinMap.DirectoryLineSelected.JoinNumber); _eisc.PulseBool(JoinMap.DirectoryLineSelected.JoinNumber);
} }
catch catch (Exception e)
{ {
Debug.Console(2, this, "Error parsing contact from {0} for path /directorySelectContact", s); this.LogException(e, "Error parsing contact from {0} for path /directorySelectContact", s.Value);
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
} }
}); });
AddAction("/directoryDialContact", AddAction("/directoryDialContact",

View file

@ -40,7 +40,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
if (e.ProgramInfo != null) if (e.ProgramInfo != null)
{ {
//Debug.Console(1, "Posting Status Message: {0}", e.ProgramInfo.ToString());
PostStatusMessage(JToken.FromObject(e.ProgramInfo) PostStatusMessage(JToken.FromObject(e.ProgramInfo)
); );
} }

View file

@ -431,7 +431,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
} }
private void CameraCodec_CameraSelected(object sender, CameraSelectedEventArgs e) private void CameraCodec_CameraSelected(object sender, CameraSelectedEventArgs<IHasCameraControls> e)
{ {
try try
{ {
@ -449,7 +449,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// </summary> /// </summary>
private void MapCameraActions() private void MapCameraActions()
{ {
if (Codec is IHasCameras cameraCodec && cameraCodec.SelectedCamera != null) if (Codec is IHasCamerasWithControls cameraCodec && cameraCodec.SelectedCamera != null)
{ {
RemoveAction("/cameraUp"); RemoveAction("/cameraUp");
RemoveAction("/cameraDown"); RemoveAction("/cameraDown");
@ -764,7 +764,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
status.ShowSelfViewByDefault = Codec.ShowSelfViewByDefault; status.ShowSelfViewByDefault = Codec.ShowSelfViewByDefault;
status.SupportsAdHocMeeting = Codec is IHasStartMeeting; status.SupportsAdHocMeeting = Codec is IHasStartMeeting;
status.HasRecents = Codec is IHasCallHistory; status.HasRecents = Codec is IHasCallHistory;
status.HasCameras = Codec is IHasCameras; status.HasCameras = Codec is IHasCamerasWithControls;
status.Presets = GetCurrentPresets(); status.Presets = GetCurrentPresets();
status.IsZoomRoom = codecType.GetInterface("IHasZoomRoomLayouts") != null; status.IsZoomRoom = codecType.GetInterface("IHasZoomRoomLayouts") != null;
status.ReceivingContent = Codec is IHasFarEndContentStatus && (Codec as IHasFarEndContentStatus).ReceivingContent.BoolValue; status.ReceivingContent = Codec is IHasFarEndContentStatus && (Codec as IHasFarEndContentStatus).ReceivingContent.BoolValue;
@ -899,12 +899,15 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
camera.Name = camerasCodec.SelectedCamera.Name; camera.Name = camerasCodec.SelectedCamera.Name;
camera.Capabilities = new CameraCapabilities() if(camerasCodec.SelectedCamera is IHasCameraPtzControl cameraControls)
{ {
CanPan = camerasCodec.SelectedCamera.CanPan, camera.Capabilities = new CameraCapabilities()
CanTilt = camerasCodec.SelectedCamera.CanTilt, {
CanZoom = camerasCodec.SelectedCamera.CanZoom, CanPan = cameraControls is IHasCameraPanControl,
CanFocus = camerasCodec.SelectedCamera.CanFocus, CanTilt = cameraControls is IHasCameraTiltControl,
CanZoom = cameraControls is IHasCameraZoomControl,
CanFocus = cameraControls is IHasCameraFocusControl,
};
}; };
} }
@ -1084,7 +1087,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// Gets or sets the Cameras /// Gets or sets the Cameras
/// </summary> /// </summary>
[JsonProperty("cameraList", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("cameraList", NullValueHandling = NullValueHandling.Ignore)]
public List<CameraBase> Cameras { get; set; } public List<IHasCameraControls> Cameras { get; set; }
/// <summary> /// <summary>

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Timers;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharp.Net.Http; using Crestron.SimplSharp.Net.Http;
@ -186,9 +187,9 @@ namespace PepperDash.Essentials
/// </summary> /// </summary>
public DateTime LastAckMessage => _lastAckMessage; public DateTime LastAckMessage => _lastAckMessage;
private CTimer _pingTimer; private Timer _pingTimer;
private CTimer _serverReconnectTimer; private Timer _serverReconnectTimer;
private LogLevel _wsLogLevel = LogLevel.Error; private LogLevel _wsLogLevel = LogLevel.Error;
/// <summary> /// <summary>
@ -769,21 +770,6 @@ namespace PepperDash.Essentials
messengerAdded = true; messengerAdded = true;
} }
if (device is IHasCurrentSourceInfoChange csiChange)
{
this.LogVerbose("Adding IHasCurrentSourceInfoMessenger for {deviceKey}", device.Key);
var messenger = new IHasCurrentSourceInfoMessenger(
$"{device.Key}-currentSource-{Key}",
$"/device/{device.Key}",
csiChange
);
AddDefaultDeviceMessenger(messenger);
messengerAdded = true;
}
if (device is ICurrentSources currentSources) if (device is ICurrentSources currentSources)
{ {
this.LogVerbose("Adding CurrentSourcesMessenger for {deviceKey}", device.Key); this.LogVerbose("Adding CurrentSourcesMessenger for {deviceKey}", device.Key);
@ -993,19 +979,6 @@ namespace PepperDash.Essentials
messengerAdded = true; messengerAdded = true;
} }
if (device is IHasCameras cameras)
{
this.LogVerbose("Adding IHasCamerasMessenger for {deviceKey}", device.Key
);
var messenger = new IHasCamerasMessenger(
$"{device.Key}-cameras-{Key}",
$"/device/{device.Key}",
cameras
);
AddDefaultDeviceMessenger(messenger);
messengerAdded = true;
}
if (device is IHasCamerasWithControls cameras2) if (device is IHasCamerasWithControls cameras2)
{ {
this.LogVerbose("Adding IHasCamerasWithControlsMessenger for {deviceKey}", device.Key this.LogVerbose("Adding IHasCamerasWithControlsMessenger for {deviceKey}", device.Key
@ -1343,7 +1316,7 @@ namespace PepperDash.Essentials
} }
} }
var simplMessengers = _messengers.OfType<IDelayedConfiguration>().ToList(); var simplMessengers = _messengers.Values.OfType<IDelayedConfiguration>().ToList();
if (simplMessengers.Count > 0) if (simplMessengers.Count > 0)
{ {
@ -1404,8 +1377,9 @@ namespace PepperDash.Essentials
_wsClient2.Log.Level = LogLevel.Trace; _wsClient2.Log.Level = LogLevel.Trace;
_wsClient2.SslConfiguration.EnabledSslProtocols = _wsClient2.SslConfiguration.EnabledSslProtocols =
System.Security.Authentication.SslProtocols.Tls11 // System.Security.Authentication.SslProtocols.Tls11
| System.Security.Authentication.SslProtocols.Tls12; System.Security.Authentication.SslProtocols.Tls12
| System.Security.Authentication.SslProtocols.Tls13;
_wsClient2.OnMessage += HandleMessage; _wsClient2.OnMessage += HandleMessage;
_wsClient2.OnOpen += HandleOpen; _wsClient2.OnOpen += HandleOpen;
@ -2091,13 +2065,17 @@ namespace PepperDash.Essentials
private void ResetPingTimer() private void ResetPingTimer()
{ {
// This tells us we're online with the API and getting pings // This tells us we're online with the API and getting pings
_pingTimer.Reset(PingInterval); _pingTimer.Stop();
_pingTimer.Interval = PingInterval;
_pingTimer.Start();
} }
private void StartPingTimer() private void StartPingTimer()
{ {
StopPingTimer(); StopPingTimer();
_pingTimer = new CTimer(PingTimerCallback, null, PingInterval); _pingTimer = new Timer(PingInterval) { AutoReset = false };
_pingTimer.Elapsed += (s, e) => PingTimerCallback(null);
_pingTimer.Start();
} }
private void StopPingTimer() private void StopPingTimer()
@ -2136,10 +2114,9 @@ namespace PepperDash.Essentials
private void StartServerReconnectTimer() private void StartServerReconnectTimer()
{ {
StopServerReconnectTimer(); StopServerReconnectTimer();
_serverReconnectTimer = new CTimer( _serverReconnectTimer = new Timer(ServerReconnectInterval) { AutoReset = false };
ReconnectToServerTimerCallback, _serverReconnectTimer.Elapsed += (s, e) => ReconnectToServerTimerCallback(null);
ServerReconnectInterval _serverReconnectTimer.Start();
);
this.LogDebug("Reconnect Timer Started."); this.LogDebug("Reconnect Timer Started.");
} }

View file

@ -87,7 +87,7 @@ namespace PepperDash.Essentials.Room.MobileControl
Eisc = new ThreeSeriesTcpIpEthernetIntersystemCommunications(ipId, "127.0.0.2", Global.ControlSystem); Eisc = new ThreeSeriesTcpIpEthernetIntersystemCommunications(ipId, "127.0.0.2", Global.ControlSystem);
var reg = Eisc.Register(); var reg = Eisc.Register();
if (reg != eDeviceRegistrationUnRegistrationResponse.Success) if (reg != eDeviceRegistrationUnRegistrationResponse.Success)
Debug.Console(0, this, "Cannot connect EISC at IPID {0}: \r{1}", ipId, reg); this.LogWarning("Cannot connect EISC at IPID {0}: \r{1}", ipId, reg);
JoinMap = new MobileControlSIMPLRoomJoinMap(1); JoinMap = new MobileControlSIMPLRoomJoinMap(1);
@ -109,7 +109,7 @@ namespace PepperDash.Essentials.Room.MobileControl
return; return;
} }
Debug.Console(1, this, "SIMPL EISC online={0}. Config is ready={1}. Use Essentials Config={2}", this.LogDebug("SIMPL EISC online={0}. Config is ready={1}. Use Essentials Config={2}",
a.DeviceOnLine, Eisc.BooleanOutput[JoinMap.ConfigIsReady.JoinNumber].BoolValue, a.DeviceOnLine, Eisc.BooleanOutput[JoinMap.ConfigIsReady.JoinNumber].BoolValue,
Eisc.BooleanOutput[JoinMap.ConfigIsLocal.JoinNumber].BoolValue); Eisc.BooleanOutput[JoinMap.ConfigIsLocal.JoinNumber].BoolValue);
@ -143,7 +143,7 @@ namespace PepperDash.Essentials.Room.MobileControl
/// </summary> /// </summary>
public override bool CustomActivate() public override bool CustomActivate()
{ {
Debug.Console(0, this, "Final activation. Setting up actions and feedbacks"); this.LogDebug("Final activation. Setting up actions and feedbacks");
//SetupFunctions(); //SetupFunctions();
//SetupFeedbacks(); //SetupFeedbacks();
@ -169,7 +169,6 @@ namespace PepperDash.Essentials.Room.MobileControl
_directRouteMessenger.JoinMap.PrintJoinMapInfo(); _directRouteMessenger.JoinMap.PrintJoinMapInfo();
// TODO: Update Source Bridge to use new JoinMap scheme
//_sourceBridge.JoinMap.PrintJoinMapInfo(); //_sourceBridge.JoinMap.PrintJoinMapInfo();
}, "printmobilebridge", "Prints MC-SIMPL bridge EISC data", ConsoleAccessLevelEnum.AccessOperator); }, "printmobilebridge", "Prints MC-SIMPL bridge EISC data", ConsoleAccessLevelEnum.AccessOperator);
@ -182,7 +181,7 @@ namespace PepperDash.Essentials.Room.MobileControl
SetupDeviceMessengers(); SetupDeviceMessengers();
Debug.Console(0, this, "******* ESSENTIALS CONFIG: \r{0}", this.LogInformation("******* ESSENTIALS CONFIG: \r{0}",
JsonConvert.SerializeObject(ConfigReader.ConfigObject, Formatting.Indented)); JsonConvert.SerializeObject(ConfigReader.ConfigObject, Formatting.Indented));
ConfigurationIsReady?.Invoke(this, new EventArgs()); ConfigurationIsReady?.Invoke(this, new EventArgs());
@ -497,7 +496,7 @@ namespace PepperDash.Essentials.Room.MobileControl
/// </summary> /// </summary>
private void LoadConfigValues() private void LoadConfigValues()
{ {
Debug.Console(1, this, "Loading configuration from SIMPL EISC bridge"); this.LogInformation("Loading configuration from SIMPL EISC bridge");
ConfigIsLoaded = false; ConfigIsLoaded = false;
var co = ConfigReader.ConfigObject; var co = ConfigReader.ConfigObject;
@ -519,12 +518,12 @@ namespace PepperDash.Essentials.Room.MobileControl
var rm = new DeviceConfig(); var rm = new DeviceConfig();
if (co.Rooms.Count == 0) if (co.Rooms.Count == 0)
{ {
Debug.Console(0, this, "Adding room to config"); this.LogInformation("Adding room to config");
co.Rooms.Add(rm); co.Rooms.Add(rm);
} }
else else
{ {
Debug.Console(0, this, "Replacing Room[0] in config"); this.LogInformation("Replacing Room[0] in config");
co.Rooms[0] = rm; co.Rooms[0] = rm;
} }
rm.Name = Eisc.StringOutput[JoinMap.ConfigRoomName.JoinNumber].StringValue; rm.Name = Eisc.StringOutput[JoinMap.ConfigRoomName.JoinNumber].StringValue;
@ -620,7 +619,7 @@ namespace PepperDash.Essentials.Room.MobileControl
var controllable = Eisc.BooleanOutput[JoinMap.SourceIsControllableJoinStart.JoinNumber + i].BoolValue; var controllable = Eisc.BooleanOutput[JoinMap.SourceIsControllableJoinStart.JoinNumber + i].BoolValue;
var audioSource = Eisc.BooleanOutput[JoinMap.SourceIsAudioSourceJoinStart.JoinNumber + i].BoolValue; var audioSource = Eisc.BooleanOutput[JoinMap.SourceIsAudioSourceJoinStart.JoinNumber + i].BoolValue;
Debug.Console(0, this, "Adding source {0} '{1}'", key, name); this.LogInformation("Adding source {0} '{1}'", key, name);
var sourceKey = Eisc.StringOutput[JoinMap.SourceControlDeviceKeyJoinStart.JoinNumber + i].StringValue; var sourceKey = Eisc.StringOutput[JoinMap.SourceControlDeviceKeyJoinStart.JoinNumber + i].StringValue;
@ -645,16 +644,16 @@ namespace PepperDash.Essentials.Room.MobileControl
// Look to see if this is a device that already exists in Essentials and get it // Look to see if this is a device that already exists in Essentials and get it
if (existingSourceDevice != null) if (existingSourceDevice != null)
{ {
Debug.Console(0, this, "Found device with key: {0} in Essentials.", key); this.LogInformation("Found device with key: {0} in Essentials.", key);
if (existingSourceDevice.Properties.Value<bool>(_syntheticDeviceKey)) if (existingSourceDevice.Properties.Value<bool>(_syntheticDeviceKey))
{ {
Debug.Console(0, this, "Updating previous device config with new values"); this.LogInformation("Updating previous device config with new values");
existingSourceDevice = syntheticDevice; existingSourceDevice = syntheticDevice;
} }
else else
{ {
Debug.Console(0, this, "Using existing Essentials device (non synthetic)"); this.LogInformation("Using existing Essentials device (non synthetic)");
} }
} }
else else
@ -759,7 +758,7 @@ namespace PepperDash.Essentials.Room.MobileControl
SetupDeviceMessengers(); SetupDeviceMessengers();
Debug.Console(0, this, "******* CONFIG FROM SIMPL: \r{0}", this.LogDebug("******* CONFIG FROM SIMPL: \r{0}",
JsonConvert.SerializeObject(ConfigReader.ConfigObject, Formatting.Indented)); JsonConvert.SerializeObject(ConfigReader.ConfigObject, Formatting.Indented));
ConfigurationIsReady?.Invoke(this, new EventArgs()); ConfigurationIsReady?.Invoke(this, new EventArgs());
@ -806,7 +805,7 @@ namespace PepperDash.Essentials.Room.MobileControl
continue; continue;
} }
Debug.Console(0, this, "Adding destination {0} - {1}", key, name); this.LogInformation("Adding destination {0} - {1}", key, name);
eRoutingSignalType parsedType; eRoutingSignalType parsedType;
try try
@ -815,7 +814,7 @@ namespace PepperDash.Essentials.Room.MobileControl
} }
catch catch
{ {
Debug.Console(0, this, "Error parsing destination type: {0}", routeType); this.LogInformation("Error parsing destination type: {0}", routeType);
parsedType = eRoutingSignalType.AudioVideo; parsedType = eRoutingSignalType.AudioVideo;
} }
@ -852,15 +851,15 @@ namespace PepperDash.Essentials.Room.MobileControl
if (existingDev != null) if (existingDev != null)
{ {
Debug.Console(0, this, "Found device with key: {0} in Essentials.", key); this.LogInformation("Found device with key: {0} in Essentials.", key);
if (existingDev.Properties.Value<bool>(_syntheticDeviceKey)) if (existingDev.Properties.Value<bool>(_syntheticDeviceKey))
{ {
Debug.Console(0, this, "Updating previous device config with new values"); this.LogInformation("Updating previous device config with new values");
} }
else else
{ {
Debug.Console(0, this, "Using existing Essentials device (non synthetic)"); this.LogInformation("Using existing Essentials device (non synthetic)");
} }
} }
else else
@ -901,7 +900,7 @@ namespace PepperDash.Essentials.Room.MobileControl
if (DeviceManager.GetDeviceForKey(messengerKey) != null) if (DeviceManager.GetDeviceForKey(messengerKey) != null)
{ {
Debug.Console(2, this, "Messenger with key: {0} already exists. Skipping...", messengerKey); this.LogDebug("Messenger with key: {0} already exists. Skipping...", messengerKey);
continue; continue;
} }
@ -909,7 +908,7 @@ namespace PepperDash.Essentials.Room.MobileControl
if (dev == null) if (dev == null)
{ {
Debug.Console(1, this, "Unable to find device config for key: '{0}'", props.DeviceKey); this.LogDebug("Unable to find device config for key: '{0}'", props.DeviceKey);
continue; continue;
} }
@ -918,13 +917,13 @@ namespace PepperDash.Essentials.Room.MobileControl
if (type.Equals("simplcameramessenger")) if (type.Equals("simplcameramessenger"))
{ {
Debug.Console(2, this, "Adding SIMPLCameraMessenger for: '{0}'", props.DeviceKey); this.LogDebug("Adding SIMPLCameraMessenger for: '{0}'", props.DeviceKey);
messenger = new SIMPLCameraMessenger(messengerKey, Eisc, "/device/" + props.DeviceKey, messenger = new SIMPLCameraMessenger(messengerKey, Eisc, "/device/" + props.DeviceKey,
props.JoinStart); props.JoinStart);
} }
else if (type.Equals("simplroutemessenger")) else if (type.Equals("simplroutemessenger"))
{ {
Debug.Console(2, this, "Adding SIMPLRouteMessenger for: '{0}'", props.DeviceKey); this.LogDebug("Adding SIMPLRouteMessenger for: '{0}'", props.DeviceKey);
messenger = new SIMPLRouteMessenger(messengerKey, Eisc, "/device/" + props.DeviceKey, messenger = new SIMPLRouteMessenger(messengerKey, Eisc, "/device/" + props.DeviceKey,
props.JoinStart); props.JoinStart);
} }
@ -937,7 +936,7 @@ namespace PepperDash.Essentials.Room.MobileControl
} }
else else
{ {
Debug.Console(2, this, "Unable to add messenger for device: '{0}' of type: '{1}'", this.LogDebug("Unable to add messenger for device: '{0}' of type: '{1}'",
props.DeviceKey, type); props.DeviceKey, type);
} }
} }
@ -950,7 +949,7 @@ namespace PepperDash.Essentials.Room.MobileControl
if (dev is CameraBase) if (dev is CameraBase)
{ {
var camDevice = dev as CameraBase; var camDevice = dev as CameraBase;
Debug.Console(1, this, "Adding CameraBaseMessenger for device: {0}", dev.Key); this.LogDebug("Adding CameraBaseMessenger for device: {0}", dev.Key);
var cameraMessenger = new CameraBaseMessenger(device.Key + "-" + Key, camDevice, var cameraMessenger = new CameraBaseMessenger(device.Key + "-" + Key, camDevice,
"/device/" + device.Key); "/device/" + device.Key);
DeviceMessengers.Add(device.Key, cameraMessenger); DeviceMessengers.Add(device.Key, cameraMessenger);
@ -964,7 +963,8 @@ namespace PepperDash.Essentials.Room.MobileControl
} }
catch (Exception e) catch (Exception e)
{ {
Debug.Console(2, this, "Error Setting up Device Managers: {0}", e); this.LogException(e,"Error Setting up Device Managers: {0}", e.Message);
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
} }
} }
@ -977,7 +977,7 @@ namespace PepperDash.Essentials.Room.MobileControl
{ {
var count = Eisc.UShortOutput[JoinMap.NumberOfAuxFaders.JoinNumber].UShortValue; var count = Eisc.UShortOutput[JoinMap.NumberOfAuxFaders.JoinNumber].UShortValue;
Debug.Console(1, this, "The Fader Count is : {0}", count); this.LogDebug("The Fader Count is : {0}", count);
// build volumes object, serialize and put in content of method below // build volumes object, serialize and put in content of method below
@ -1013,8 +1013,6 @@ namespace PepperDash.Essentials.Room.MobileControl
NumberOfAuxFaders = Eisc.UShortInput[JoinMap.NumberOfAuxFaders.JoinNumber].UShortValue NumberOfAuxFaders = Eisc.UShortInput[JoinMap.NumberOfAuxFaders.JoinNumber].UShortValue
}; };
// TODO: Add property to status message to indicate if advanced sharing is supported and if users can change share mode
PostStatus(new PostStatus(new
{ {
activityMode = GetActivityMode(), activityMode = GetActivityMode(),
@ -1082,7 +1080,7 @@ namespace PepperDash.Essentials.Room.MobileControl
private void EISC_SigChange(object currentDevice, SigEventArgs args) private void EISC_SigChange(object currentDevice, SigEventArgs args)
{ {
if (Debug.Level >= 1) if (Debug.Level >= 1)
Debug.Console(1, this, "SIMPL EISC change: {0} {1}={2}", args.Sig.Type, args.Sig.Number, this.LogVerbose("SIMPL EISC change: {0} {1}={2}", args.Sig.Type, args.Sig.Number,
args.Sig.StringValue); args.Sig.StringValue);
var uo = args.Sig.UserObject; var uo = args.Sig.UserObject;
if (uo != null) if (uo != null)
@ -1122,12 +1120,12 @@ namespace PepperDash.Essentials.Room.MobileControl
protected override void UserCodeChange() protected override void UserCodeChange()
{ {
Debug.Console(1, this, "Server user code changed: {0}", UserCode); this.LogDebug("Server user code changed: {0}", UserCode);
var qrUrl = string.Format("{0}/api/rooms/{1}/{3}/qr?x={2}", AppServerController.Host, AppServerController.SystemUuid, new Random().Next(), "room1"); var qrUrl = string.Format("{0}/api/rooms/{1}/{3}/qr?x={2}", AppServerController.Host, AppServerController.SystemUuid, new Random().Next(), "room1");
QrCodeUrl = qrUrl; QrCodeUrl = qrUrl;
Debug.Console(1, this, "Server user code changed: {0} - {1}", UserCode, qrUrl); this.LogDebug("Server user code changed: {0} - {1}", UserCode, qrUrl);
OnUserCodeChanged(); OnUserCodeChanged();

View file

@ -1329,7 +1329,6 @@ namespace PepperDash.Essentials.WebSocketServer
var suffix = filePath.Substring(_userAppBaseHref.Length, filePath.Length - _userAppBaseHref.Length); var suffix = filePath.Substring(_userAppBaseHref.Length, filePath.Length - _userAppBaseHref.Length);
if (suffix != "/") if (suffix != "/")
{ {
//Debug.Console(2, this, "Suffix: {0}", suffix);
filePath = filePath.Replace(suffix, ""); filePath = filePath.Replace(suffix, "");
} }
} }