using System; using PepperDash.Core; using System.Threading; using PepperDash.Core.Logging; namespace PepperDash.Essentials.Core { /// /// Used for monitoring comms that are IBasicCommunication. Will send a poll string and provide an event when /// statuses change. /// Default monitoring uses TextReceived event on Client. /// public class GenericCommunicationMonitor : StatusMonitorBase { public IBasicCommunication Client { get; private set; } /// /// Will monitor Client.BytesReceived if set to true. Otherwise the default is to monitor Client.TextReceived /// public bool MonitorBytesReceived { get; private set; } /// /// Return true if the Client is ISocketStatus /// public bool IsSocket => Client is ISocketStatus; private readonly string PollString; private readonly Action PollAction; private readonly long PollTime; private Timer PollTimer; private SemaphoreSlim semaphore = new SemaphoreSlim(1, 1); /// /// GenericCommunicationMonitor constructor /// /// 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 /// /// Parent device /// Communications Client /// Time in MS for polling /// Warning time in MS. If a message is not received before this elapsed time the status will be Warning /// Error time in MS. If a message is not received before this elapsed time the status will be Error /// string to send for polling /// Poll time must be less than warning and error time public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, long warningTime, long errorTime, string pollString) : base(parent, warningTime, errorTime) { if (pollTime > warningTime || pollTime > errorTime) throw new ArgumentException("pollTime must be less than warning or errorTime"); Client = client; PollTime = pollTime; PollString = pollString; if (IsSocket) { (Client as ISocketStatus).ConnectionChange += Socket_ConnectionChange; } } /// /// 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 /// /// Parent device /// Communications Client /// Time in MS for polling /// Warning time in MS. If a message is not received before this elapsed time the status will be Warning /// Error time in MS. If a message is not received before this elapsed time the status will be Error /// string to send for polling /// Use bytesReceived event instead of textReceived when true public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, long warningTime, long errorTime, string pollString, bool monitorBytesReceived) : this(parent, client, pollTime, warningTime, errorTime, pollString) { MonitorBytesReceived = monitorBytesReceived; } /// /// 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 /// /// Parent device /// Communications Client /// Time in MS for polling /// Warning time in MS. If a message is not received before this elapsed time the status will be Warning /// Error time in MS. If a message is not received before this elapsed time the status will be Error /// Action to execute for polling /// Poll time must be less than warning and error time public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, long warningTime, long errorTime, Action pollAction) : base(parent, warningTime, errorTime) { if (pollTime > warningTime || pollTime > 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; PollTime = pollTime; PollAction = pollAction; if (IsSocket) { (Client as ISocketStatus).ConnectionChange += Socket_ConnectionChange; } } /// /// 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 /// /// Parent device /// Communications Client /// Time in MS for polling /// Warning time in MS. If a message is not received before this elapsed time the status will be Warning /// Error time in MS. If a message is not received before this elapsed time the status will be Error /// Action to execute for polling /// Use bytesReceived event instead of textReceived when true public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, long pollTime, long warningTime, long errorTime, Action pollAction, bool monitorBytesReceived) : this(parent, client, pollTime, warningTime, errorTime, pollAction) { MonitorBytesReceived = monitorBytesReceived; } /// /// GenericCommunicationMonitor constructor with a config object /// /// 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 /// /// Parent Device /// Communications Client /// Communication Monitor Config object public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, CommunicationMonitorConfig props) : this(parent, client, props.PollInterval, props.TimeToWarning, props.TimeToError, props.PollString) { if (IsSocket) { (Client as ISocketStatus).ConnectionChange += Socket_ConnectionChange; } } /// /// GenericCommunicationMonitor constructor with a config object 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 /// /// Parent Device /// Communications Client /// Communication Monitor Config object /// Use bytesReceived event instead of textReceived when true public GenericCommunicationMonitor(IKeyed parent, IBasicCommunication client, CommunicationMonitorConfig props, bool monitorBytesReceived) : this(parent, client, props.PollInterval, props.TimeToWarning, props.TimeToError, props.PollString) { MonitorBytesReceived = monitorBytesReceived; } /// /// Start the poll cycle /// public override void Start() { if (MonitorBytesReceived) { Client.BytesReceived -= Client_BytesReceived; Client.BytesReceived += Client_BytesReceived; } else { Client.TextReceived -= Client_TextReceived; Client.TextReceived += Client_TextReceived; } BeginPolling(); } private void Socket_ConnectionChange(object sender, GenericSocketStatusChageEventArgs e) { if (!e.Client.IsConnected) { // Immediately stop polling and notify that device is offline Stop(); Status = MonitorStatus.InError; ResetErrorTimers(); } else { // Start polling and set status to unknow and let poll result update the status to IsOk when a response is received Status = MonitorStatus.StatusUnknown; Start(); } } private void BeginPolling() { try { semaphore.Wait(); { if (PollTimer != null) { return; } PollTimer = new Timer(o => Poll(), null, 0, PollTime); } } finally { semaphore.Release(); } } /// /// Stop the poll cycle /// public override void Stop() { if(MonitorBytesReceived) { Client.BytesReceived -= Client_BytesReceived; } else { Client.TextReceived -= Client_TextReceived; } StopErrorTimers(); if (PollTimer == null) { return; } PollTimer.Dispose(); PollTimer = null; } private void Client_TextReceived(object sender, GenericCommMethodReceiveTextArgs e) { DataReceived(); } private void Client_BytesReceived(object sender, GenericCommMethodReceiveBytesArgs e) { DataReceived(); } private void DataReceived() { Status = MonitorStatus.IsOk; ResetErrorTimers(); } private void Poll() { StartErrorTimers(); if (Client.IsConnected) { //Debug.LogMessage(LogEventLevel.Verbose, this, "Polling"); if(PollAction != null) PollAction.Invoke(); else Client.SendText(PollString); } else { this.LogVerbose("Comm not connected"); } } } /// /// Communication Monitor Configuration from Essentials Configuration /// public class CommunicationMonitorConfig { public int PollInterval { get; set; } public int TimeToWarning { get; set; } public int TimeToError { get; set; } public string PollString { get; set; } /// /// Default constructor. Sets pollInterval to 30s, TimeToWarning to 120s, and TimeToError to 300s /// public CommunicationMonitorConfig() { PollInterval = 30000; TimeToWarning = 120000; TimeToError = 300000; PollString = ""; } } }