Merge pull request #1249 from PepperDash/generic-comm-monitor-issues

This commit is contained in:
Neil Dorin
2025-04-11 12:32:12 -06:00
committed by GitHub

View File

@@ -1,16 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharpPro;
using Crestron.SimplSharpPro.DeviceSupport;
using System.ComponentModel;
using PepperDash.Core; using PepperDash.Core;
using Serilog.Events; using System.Threading;
using PepperDash.Core.Logging;
namespace PepperDash.Essentials.Core namespace PepperDash.Essentials.Core
{ {
@@ -31,35 +22,32 @@ namespace PepperDash.Essentials.Core
/// <summary> /// <summary>
/// Return true if the Client is ISocketStatus /// Return true if the Client is ISocketStatus
/// </summary> /// </summary>
public bool IsSocket public bool IsSocket => Client is ISocketStatus;
{
get
{
return Client is ISocketStatus;
}
}
long PollTime; private readonly string PollString;
CTimer PollTimer; private readonly Action PollAction;
string PollString; private readonly long PollTime;
Action PollAction;
private Timer PollTimer;
/// <summary>
/// /// <summary>
/// </summary> /// GenericCommunicationMonitor constructor
/// <param name="client"></param> ///
/// <param name="pollTime">in MS, >= 5000</param> /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected
/// <param name="warningTime">in MS, >= 5000</param> /// </summary>
/// <param name="errorTime">in MS, >= 5000</param> /// <param name="parent">Parent device</param>
/// <param name="pollString">String to send to comm</param> /// <param name="client">Communications Client</param>
public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, /// <param name="pollTime">Time in MS for polling</param>
/// <param name="warningTime">Warning time in MS. If a message is not received before this elapsed time the status will be Warning</param>
/// <param name="errorTime">Error time in MS. If a message is not received before this elapsed time the status will be Error</param>
/// <param name="pollString">string to send for polling</param>
/// <exception cref="ArgumentException">Poll time must be less than warning and error time</exception>
public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime,
long warningTime, long errorTime, string pollString) : long warningTime, long errorTime, string pollString) :
base(parent, warningTime, errorTime) base(parent, warningTime, errorTime)
{ {
if (pollTime > warningTime || pollTime > errorTime) if (pollTime > warningTime || pollTime > errorTime)
throw new ArgumentException("pollTime must be less than warning or errorTime"); throw new ArgumentException("pollTime must be less than warning or errorTime");
//if (pollTime < 5000)
// throw new ArgumentException("pollTime cannot be less than 5000 ms");
Client = client; Client = client;
PollTime = pollTime; PollTime = pollTime;
@@ -67,26 +55,41 @@ namespace PepperDash.Essentials.Core
if (IsSocket) if (IsSocket)
{ {
(Client as ISocketStatus).ConnectionChange += new EventHandler<GenericSocketStatusChageEventArgs>(socket_ConnectionChange); (Client as ISocketStatus).ConnectionChange += Socket_ConnectionChange;
} }
} }
/// <summary>
/// GenericCommunicationMonitor constructor with a bool to specify whether to monitor BytesReceived
///
/// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected
/// </summary>
/// <param name="parent">Parent device</param>
/// <param name="client">Communications Client</param>
/// <param name="pollTime">Time in MS for polling</param>
/// <param name="warningTime">Warning time in MS. If a message is not received before this elapsed time the status will be Warning</param>
/// <param name="errorTime">Error time in MS. If a message is not received before this elapsed time the status will be Error</param>
/// <param name="pollString">string to send for polling</param>
/// <param name="monitorBytesReceived">Use bytesReceived event instead of textReceived when true</param>
public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime,
long warningTime, long errorTime, string pollString, bool monitorBytesReceived) : long warningTime, long errorTime, string pollString, bool monitorBytesReceived) :
this(parent, client, pollTime, warningTime, errorTime, pollString) this(parent, client, pollTime, warningTime, errorTime, pollString)
{ {
SetMonitorBytesReceived(monitorBytesReceived); MonitorBytesReceived = monitorBytesReceived;
} }
/// <summary> /// <summary>
/// Poll is a provided action instead of string /// GenericCommunicationMonitor constructor with a poll action instead of a poll string
///
/// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected
/// </summary> /// </summary>
/// <param name="parent"></param> /// <param name="parent">Parent device</param>
/// <param name="client"></param> /// <param name="client">Communications Client</param>
/// <param name="pollTime"></param> /// <param name="pollTime">Time in MS for polling</param>
/// <param name="warningTime"></param> /// <param name="warningTime">Warning time in MS. If a message is not received before this elapsed time the status will be Warning</param>
/// <param name="errorTime"></param> /// <param name="errorTime">Error time in MS. If a message is not received before this elapsed time the status will be Error</param>
/// <param name="pollBytes"></param> /// <param name="pollAction">Action to execute for polling</param>
/// <exception cref="ArgumentException">Poll time must be less than warning and error time</exception>
public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime,
long warningTime, long errorTime, Action pollAction) : long warningTime, long errorTime, Action pollAction) :
base(parent, warningTime, errorTime) base(parent, warningTime, errorTime)
@@ -102,51 +105,67 @@ namespace PepperDash.Essentials.Core
if (IsSocket) if (IsSocket)
{ {
(Client as ISocketStatus).ConnectionChange += new EventHandler<GenericSocketStatusChageEventArgs>(socket_ConnectionChange); (Client as ISocketStatus).ConnectionChange += Socket_ConnectionChange;
} }
} }
/// <summary>
/// GenericCommunicationMonitor constructor with a poll action instead of a poll string and a bool to specify whether to monitor BytesReceived
///
/// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected
/// </summary>
/// <param name="parent">Parent device</param>
/// <param name="client">Communications Client</param>
/// <param name="pollTime">Time in MS for polling</param>
/// <param name="warningTime">Warning time in MS. If a message is not received before this elapsed time the status will be Warning</param>
/// <param name="errorTime">Error time in MS. If a message is not received before this elapsed time the status will be Error</param>
/// <param name="pollAction">Action to execute for polling</param>
/// <param name="monitorBytesReceived">Use bytesReceived event instead of textReceived when true</param>
public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime,
long warningTime, long errorTime, Action pollAction, bool monitorBytesReceived) : long warningTime, long errorTime, Action pollAction, bool monitorBytesReceived) :
this(parent, client, pollTime, warningTime, errorTime, pollAction) this(parent, client, pollTime, warningTime, errorTime, pollAction)
{ {
SetMonitorBytesReceived(monitorBytesReceived); MonitorBytesReceived = monitorBytesReceived;
} }
/// <summary> /// <summary>
/// Build the monitor from a config object /// GenericCommunicationMonitor constructor with a config object
/// </summary> ///
public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, /// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected
/// </summary>
/// <param name="parent">Parent Device</param>
/// <param name="client">Communications Client</param>
/// <param name="props"><see cref="CommunicationMonitorConfig">Communication Monitor Config</see> object</param>
public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client,
CommunicationMonitorConfig props) : CommunicationMonitorConfig props) :
this(parent, client, props.PollInterval, props.TimeToWarning, props.TimeToError, props.PollString) this(parent, client, props.PollInterval, props.TimeToWarning, props.TimeToError, props.PollString)
{ {
if (IsSocket) if (IsSocket)
{ {
(Client as ISocketStatus).ConnectionChange += new EventHandler<GenericSocketStatusChageEventArgs>(socket_ConnectionChange); (Client as ISocketStatus).ConnectionChange += Socket_ConnectionChange;
} }
} }
/// <summary> /// <summary>
/// Builds the monitor from a config object and takes a bool to specify whether to monitor BytesReceived /// GenericCommunicationMonitor constructor with a config object and a bool to specify whether to monitor BytesReceived
/// Default is to monitor TextReceived ///
/// Note: If the client is a socket, the connection status will be monitored and the PollTimer will be started automatically when the client is connected
/// </summary> /// </summary>
/// <param name="parent"></param> /// <param name="parent">Parent Device</param>
/// <param name="client"></param> /// <param name="client">Communications Client</param>
/// <param name="props"></param> /// <param name="props"><see cref="CommunicationMonitorConfig">Communication Monitor Config</see> object</param>
/// <param name="monitorBytesReceived"></param> /// <param name="monitorBytesReceived">Use bytesReceived event instead of textReceived when true</param>
public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, CommunicationMonitorConfig props, bool monitorBytesReceived) : public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, CommunicationMonitorConfig props, bool monitorBytesReceived) :
this(parent, client, props.PollInterval, props.TimeToWarning, props.TimeToError, props.PollString) this(parent, client, props.PollInterval, props.TimeToWarning, props.TimeToError, props.PollString)
{
SetMonitorBytesReceived(monitorBytesReceived);
}
void SetMonitorBytesReceived(bool monitorBytesReceived)
{ {
MonitorBytesReceived = monitorBytesReceived; MonitorBytesReceived = monitorBytesReceived;
} }
/// <summary>
/// Start the poll cycle
/// </summary>
public override void Start() public override void Start()
{ {
if (MonitorBytesReceived) if (MonitorBytesReceived)
@@ -163,7 +182,7 @@ namespace PepperDash.Essentials.Core
BeginPolling(); BeginPolling();
} }
void socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) private void Socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e)
{ {
if (!e.Client.IsConnected) if (!e.Client.IsConnected)
{ {
@@ -176,58 +195,65 @@ namespace PepperDash.Essentials.Core
{ {
// Start polling and set status to unknow and let poll result update the status to IsOk when a response is received // Start polling and set status to unknow and let poll result update the status to IsOk when a response is received
Status = MonitorStatus.StatusUnknown; Status = MonitorStatus.StatusUnknown;
Start(); Start();
BeginPolling();
} }
} }
void BeginPolling() private void BeginPolling()
{ {
Poll(); lock (_pollTimerLock)
PollTimer = new CTimer(o => Poll(), null, PollTime, PollTime); {
if (PollTimer != null)
{
return;
}
PollTimer = new Timer(o => Poll(), null, 0, PollTime);
}
} }
/// <summary>
/// Stop the poll cycle
/// </summary>
public override void Stop() public override void Stop()
{ {
if(MonitorBytesReceived) if(MonitorBytesReceived)
{ {
Client.BytesReceived -= this.Client_BytesReceived; Client.BytesReceived -= Client_BytesReceived;
} }
else else
{ {
Client.TextReceived -= Client_TextReceived; Client.TextReceived -= Client_TextReceived;
} }
if (PollTimer != null) StopErrorTimers();
if (PollTimer == null)
{ {
PollTimer.Stop(); return;
PollTimer = null;
StopErrorTimers();
} }
PollTimer.Dispose();
PollTimer = null;
} }
void Client_TextReceived(object sender, GenericCommMethodReceiveTextArgs e) private void Client_TextReceived(object sender, GenericCommMethodReceiveTextArgs e)
{ {
DataReceived(); DataReceived();
} }
/// <summary> private void Client_BytesReceived(object sender, GenericCommMethodReceiveBytesArgs e)
/// Upon any receipt of data, set everything to ok!
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Client_BytesReceived(object sender, GenericCommMethodReceiveBytesArgs e)
{ {
DataReceived(); DataReceived();
} }
void DataReceived() private void DataReceived()
{ {
Status = MonitorStatus.IsOk; Status = MonitorStatus.IsOk;
ResetErrorTimers(); ResetErrorTimers();
} }
void Poll() private void Poll()
{ {
StartErrorTimers(); StartErrorTimers();
if (Client.IsConnected) if (Client.IsConnected)
@@ -240,12 +266,14 @@ namespace PepperDash.Essentials.Core
} }
else else
{ {
Debug.LogMessage(LogEventLevel.Verbose, this, "Comm not connected"); this.LogVerbose("Comm not connected");
} }
} }
} }
/// <summary>
/// Communication Monitor Configuration from Essentials Configuration
/// </summary>
public class CommunicationMonitorConfig public class CommunicationMonitorConfig
{ {
public int PollInterval { get; set; } public int PollInterval { get; set; }
@@ -253,6 +281,9 @@ namespace PepperDash.Essentials.Core
public int TimeToError { get; set; } public int TimeToError { get; set; }
public string PollString { get; set; } public string PollString { get; set; }
/// <summary>
/// Default constructor. Sets pollInterval to 30s, TimeToWarning to 120s, and TimeToError to 300s
/// </summary>
public CommunicationMonitorConfig() public CommunicationMonitorConfig()
{ {
PollInterval = 30000; PollInterval = 30000;