fix: multiple messages no longer sent

Due to how the `BeginPolling` method was written and being called, there
were situations where multiple PollTimers were created, causing there to
be multiple messages sent to the end point for each poll cycle.
This commit is contained in:
Andrew Welker
2025-04-11 12:13:26 -05:00
parent bf31bf9e93
commit 59baa74dd7

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,62 @@ 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(); if(PollTimer != null)
PollTimer = new CTimer(o => Poll(), null, PollTime, PollTime); {
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 +263,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 +278,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;