//****************************************************************************************************** // ActionAdapterBase.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 09/02/2010 - J. Ritchie Carroll // Generated original version of source code. // 02/08/2011 - J. Ritchie Carroll // Added invokable ExamineQueueState method to analyze real-time concentrator frame queue state. // 12/20/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading; using GSF.Annotations; using GSF.Diagnostics; namespace GSF.TimeSeries.Adapters { /// /// Represents the base class for action adapters. /// /// /// This base class acts as a measurement concentrator which time aligns all incoming measurements for proper processing. /// Derived classes are expected to override to handle time aligned measurements /// and call for any new measurements that may get created. /// [SuppressMessage("SonarQube.Methods", "S1206", Justification = "Class life-cycle is controlled through library.")] public abstract class ActionAdapterBase : ConcentratorBase, IActionAdapter { #region [ Members ] // Events /// /// Provides status messages to consumer. /// /// /// is new status message. /// public event EventHandler> StatusMessage; /// /// Event is raised when are updated. /// public event EventHandler InputMeasurementKeysUpdated; /// /// Event is raised when are updated. /// public event EventHandler OutputMeasurementsUpdated; /// /// Event is raised when adapter is aware of a configuration change. /// public event EventHandler ConfigurationChanged; /// /// Provides new measurements from action adapter. /// /// /// is a collection of new measurements for host to process. /// public event EventHandler>> NewMeasurements; // Fields private string m_name; private uint m_id; private string m_connectionString; private Dictionary m_settings; private DataSet m_dataSource; private int m_initializationTimeout; private bool m_autoStart; private bool m_respectInputDemands; private bool m_respectOutputDemands; private MeasurementKey[] m_inputMeasurementKeys; private IMeasurement[] m_outputMeasurements; private MeasurementKey[] m_requestedInputMeasurementKeys; private MeasurementKey[] m_requestedOutputMeasurementKeys; private List m_inputSourceIDs; private List m_outputSourceIDs; private int m_minimumMeasurementsToUse; private DateTime m_startTimeConstraint; private DateTime m_stopTimeConstraint; private int m_hashCode; private bool m_initialized; private bool m_disposed; #endregion #region [ Constructors ] /// /// Creates a new . /// protected ActionAdapterBase() { m_name = this.GetType().Name; Log.InitialStackMessages = Log.InitialStackMessages.Union("AdapterName", m_name); m_settings = new Dictionary(); m_startTimeConstraint = DateTime.MinValue; m_stopTimeConstraint = DateTime.MaxValue; GenHashCode(); // Set incoming measurements to none by default m_inputMeasurementKeys = new MeasurementKey[0]; // For most implementations millisecond resolution will be sufficient base.TimeResolution = Ticks.PerMillisecond; } #endregion #region [ Properties ] /// /// Gets name of the action adapter. /// public virtual string Name { get { return m_name; } set { m_name = value; Log.InitialStackMessages = Log.InitialStackMessages.Union("AdapterName", m_name); GenHashCode(); } } /// /// Gets or sets numeric ID associated with this action adapter. /// public virtual uint ID { get { return m_id; } set { m_id = value; GenHashCode(); } } /// /// Gets or sets key/value pair connection information specific to action adapter. /// /// /// /// Example connection string using manually defined measurements:
/// framesPerSecond=30; lagTime=1.0; leadTime=0.5; minimumMeasurementsToUse=-1; /// useLocalClockAsRealTime=true; allowSortsByArrival=false;
/// inputMeasurementKeys={P1:1245;P1:1247;P2:1335};
/// outputMeasurements={P3:1345,60.0,1.0;P3:1346;P3:1347}
/// When defined manually, elements in key:
/// * inputMeasurementKeys are defined as "ArchiveSource:PointID"
/// * outputMeasurements are defined as "ArchiveSource:PointID,Adder,Multiplier", the adder and multiplier are optional /// defaulting to 0.0 and 1.0 respectively. ///
///
/// /// Example connection string using measurements defined in a table:
/// framesPerSecond=30; lagTime=1.0; leadTime=0.5; minimumMeasurementsToUse=-1; /// useLocalClockAsRealTime=true; allowSortsByArrival=false;
/// inputMeasurementKeys={FILTER ActiveMeasurements WHERE Company='GSF' AND SignalType='FREQ' ORDER BY ID};
/// outputMeasurements={FILTER ActiveMeasurements WHERE SignalType IN ('IPHA','VPHA') AND Phase='+'}
///
/// Basic filtering syntax is as follows:
///
/// {FILTER <TableName> WHERE <Expression> [ORDER BY <SortField>]}
///
/// Source tables are expected to have at least the following fields:
/// /// /// Name /// Type /// Description. /// /// /// ID /// NVARCHAR /// Measurement key formatted as: ArchiveSource:PointID. /// /// /// PointTag /// NVARCHAR /// Point tag of measurement. /// /// /// Adder /// FLOAT /// Adder to apply to value, if any (default to 0.0). /// /// /// Multiplier /// FLOAT /// Multiplier to apply to value, if any (default to 1.0). /// /// ///
/// /// Note that framesPerSecond, lagTime and leadTime are required parameters, all other parameters are optional. /// ///
public virtual string ConnectionString { get { return m_connectionString; } set { m_connectionString = value; // Pre-parse settings upon connection string assignment if (string.IsNullOrWhiteSpace(m_connectionString)) m_settings = new Dictionary(); else m_settings = m_connectionString.ParseKeyValuePairs(); } } /// /// Gets connection info for adapter, if any. /// /// /// For example, this could return IP or host name of source connection. /// public virtual string ConnectionInfo => null; /// /// Gets or sets based data source available to this . /// public virtual DataSet DataSource { get { return m_dataSource; } set { m_dataSource = value; } } /// /// Gets or sets maximum time system will wait during for initialization. /// /// /// Set to to wait indefinitely. /// public virtual int InitializationTimeout { get { return m_initializationTimeout; } set { m_initializationTimeout = value; } } /// /// Gets or sets flag indicating if adapter should automatically start or otherwise connect on demand. /// public virtual bool AutoStart { get { return m_autoStart; } set { m_autoStart = value; } } /// /// Gets or sets flag indicating if action adapter should respect auto-start requests based on input demands. /// /// /// Action adapters are in the curious position of being able to both consume and produce points, as such the user needs to be able to control how their /// adapter will behave concerning routing demands when the adapter is setup to connect on demand. In the case of respecting auto-start input demands, /// as an example, this would be false for an action adapter that calculated measurement, but true for an action adapter used to archive inputs. /// public virtual bool RespectInputDemands { get { return m_respectInputDemands; } set { m_respectInputDemands = value; } } /// /// Gets or sets flag indicating if action adapter should respect auto-start requests based on output demands. /// /// /// Action adapters are in the curious position of being able to both consume and produce points, as such the user needs to be able to control how their /// adapter will behave concerning routing demands when the adapter is setup to connect on demand. In the case of respecting auto-start output demands, /// as an example, this would be true for an action adapter that calculated measurement, but false for an action adapter used to archive inputs. /// public virtual bool RespectOutputDemands { get { return m_respectOutputDemands; } set { m_respectOutputDemands = value; } } /// /// Gets or sets the number of frames per second. /// /// /// Valid frame rates for a are greater than 0 frames per second. /// [ConnectionStringParameter, Description("Defines the number of frames per second expected by the adapter.")] public new int FramesPerSecond { get { return base.FramesPerSecond; } set { base.FramesPerSecond = value; } } /// /// Gets or sets the allowed past time deviation tolerance, in seconds (can be sub-second). /// /// /// Defines the time sensitivity to past measurement timestamps. /// The number of seconds allowed before assuming a measurement timestamp is too old. /// This becomes the amount of delay introduced by the concentrator to allow time for data to flow into the system. /// /// LagTime must be greater than zero, but it can be less than one. [ConnectionStringParameter, Description("Defines the allowed past time deviation tolerance, in seconds (can be sub-second).")] public new double LagTime { get { return base.LagTime; } set { base.LagTime = value; } } /// /// Gets or sets the allowed future time deviation tolerance, in seconds (can be sub-second). /// /// /// Defines the time sensitivity to future measurement timestamps. /// The number of seconds allowed before assuming a measurement timestamp is too advanced. /// This becomes the tolerated +/- accuracy of the local clock to real-time. /// /// LeadTime must be greater than zero, but it can be less than one. [ConnectionStringParameter, Description("Defines the allowed future time deviation tolerance, in seconds (can be sub-second).")] public new double LeadTime { get { return base.LeadTime; } set { base.LeadTime = value; } } /// /// Gets or sets primary keys of input measurements the action adapter expects. /// /// /// If your adapter needs to receive all measurements, you must explicitly set InputMeasurementKeys to null. /// [ConnectionStringParameter, DefaultValue(null), Description("Defines primary keys of input measurements the action adapter expects; can be one of a filter expression, measurement key, point tag or Guid."), CustomConfigurationEditor("GSF.TimeSeries.UI.WPF.dll", "GSF.TimeSeries.UI.Editors.MeasurementEditor")] public virtual MeasurementKey[] InputMeasurementKeys { get { return m_inputMeasurementKeys; } set { if (m_inputMeasurementKeys == value) return; if ((object)m_inputMeasurementKeys != null && (object)value != null) { if (new HashSet(m_inputMeasurementKeys).SetEquals(value)) return; } m_inputMeasurementKeys = value; // The input measurements typically define the "expected measurements" of the action adapter, so // we use the number of these items to define the expected measurement count ExpectedMeasurements = (object)m_inputMeasurementKeys != null ? m_inputMeasurementKeys.Length : 0; OnInputMeasurementKeysUpdated(); } } /// /// Gets or sets output measurements that the action adapter will produce, if any. /// [ConnectionStringParameter, DefaultValue(null), Description("Defines primary keys of output measurements the action adapter expects; can be one of a filter expression, measurement key, point tag or Guid."), CustomConfigurationEditor("GSF.TimeSeries.UI.WPF.dll", "GSF.TimeSeries.UI.Editors.MeasurementEditor")] public virtual IMeasurement[] OutputMeasurements { get { return m_outputMeasurements; } set { m_outputMeasurements = value; OnOutputMeasurementsUpdated(); } } /// /// Gets or sets values used to filter input measurement keys. /// /// /// This allows an adapter to associate itself with entire collections of measurements based on the source of the measurement keys. /// Set to null apply no filter. /// public virtual string[] InputSourceIDs { get { if ((object)m_inputSourceIDs == null) return null; return m_inputSourceIDs.ToArray(); } set { if ((object)value == null) { m_inputSourceIDs = null; } else { m_inputSourceIDs = new List(value); m_inputSourceIDs.Sort(); } // Filter measurements to list of specified source IDs AdapterBase.LoadInputSourceIDs(this); } } /// /// Gets or sets values used to filter output measurements. /// /// /// This allows an adapter to associate itself with entire collections of measurements based on the source of the measurement keys. /// Set to null apply no filter. /// public virtual string[] OutputSourceIDs { get { if ((object)m_outputSourceIDs == null) return null; return m_outputSourceIDs.ToArray(); } set { if ((object)value == null) { m_outputSourceIDs = null; } else { m_outputSourceIDs = new List(value); m_outputSourceIDs.Sort(); } // Filter measurements to list of specified source IDs AdapterBase.LoadOutputSourceIDs(this); } } /// /// Gets or sets input measurement keys that are requested by other adapters based on what adapter says it can provide. /// public virtual MeasurementKey[] RequestedInputMeasurementKeys { get { return m_requestedInputMeasurementKeys; } set { m_requestedInputMeasurementKeys = value; } } /// /// Gets or sets output measurement keys that are requested by other adapters based on what adapter says it can provide. /// public virtual MeasurementKey[] RequestedOutputMeasurementKeys { get { return m_requestedOutputMeasurementKeys; } set { m_requestedOutputMeasurementKeys = value; } } /// /// Gets the flag indicating if this adapter supports temporal processing. /// public abstract bool SupportsTemporalProcessing { get; } /// /// Gets the start time temporal processing constraint defined by call to . /// /// /// This value will be when start time constraint is not set - meaning the adapter /// is processing data in real-time. /// public virtual DateTime StartTimeConstraint => m_startTimeConstraint; /// /// Gets the stop time temporal processing constraint defined by call to . /// /// /// This value will be when stop time constraint is not set - meaning the adapter /// is processing data in real-time. /// public virtual DateTime StopTimeConstraint => m_stopTimeConstraint; /// /// Gets or sets minimum number of input measurements required for adapter. Set to -1 to require all. /// public virtual int MinimumMeasurementsToUse { get { // Default to all measurements if minimum is not specified if (m_minimumMeasurementsToUse < 1 && (object)InputMeasurementKeys != null) return InputMeasurementKeys.Length; return m_minimumMeasurementsToUse; } set { m_minimumMeasurementsToUse = value; } } /// /// Gets or sets flag indicating if the action adapter has been initialized successfully. /// public virtual bool Initialized { get { return m_initialized; } set { m_initialized = value; } } /// /// Gets settings parsed when was assigned. /// public Dictionary Settings => m_settings; /// /// Returns the detailed status of the action adapter. /// /// /// Derived classes should extend status with implementation specific information. /// public override string Status { get { const int MaxMeasurementsToShow = 10; StringBuilder status = new StringBuilder(); DataSet dataSource = this.DataSource; status.AppendFormat(" Data source defined: {0}", (object)dataSource != null); status.AppendLine(); if ((object)dataSource != null) { status.AppendFormat(" Referenced data source: {0}, {1:N0} tables", dataSource.DataSetName, dataSource.Tables.Count); status.AppendLine(); } status.AppendFormat(" Initialization timeout: {0}", InitializationTimeout < 0 ? "Infinite" : InitializationTimeout + " milliseconds"); status.AppendLine(); status.AppendFormat(" Adapter initialized: {0}", Initialized); status.AppendLine(); status.AppendFormat(" Operational state: {0}", Enabled ? "Running" : "Stopped"); status.AppendLine(); status.AppendFormat(" Connect on demand: {0}", !AutoStart); status.AppendLine(); status.AppendFormat(" Respecting input demands: {0}", RespectInputDemands); status.AppendLine(); status.AppendFormat(" Respecting output demands: {0}", RespectOutputDemands); status.AppendLine(); status.AppendFormat(" Processed measurements: {0}", ProcessedMeasurements); status.AppendLine(); status.AppendFormat(" Total adapter run time: {0}", RunTime.ToString(3)); status.AppendLine(); status.AppendFormat(" Temporal processing: {0}", SupportsTemporalProcessing ? "Supported" : "Unsupported"); status.AppendLine(); if (SupportsTemporalProcessing) { status.AppendFormat(" Start time constraint: {0}", StartTimeConstraint == DateTime.MinValue ? "Unspecified" : StartTimeConstraint.ToString("yyyy-MM-dd HH:mm:ss.fff")); status.AppendLine(); status.AppendFormat(" Stop time constraint: {0}", StopTimeConstraint == DateTime.MaxValue ? "Unspecified" : StopTimeConstraint.ToString("yyyy-MM-dd HH:mm:ss.fff")); status.AppendLine(); status.AppendFormat(" Processing interval: {0}", ProcessingInterval < 0 ? "Default" : (ProcessingInterval == 0 ? "As fast as possible" : ProcessingInterval.ToString("N0") + " milliseconds")); status.AppendLine(); } status.AppendFormat(" Adapter ID: {0}", ID); status.AppendLine(); Dictionary keyValuePairs = Settings; char[] keyChars; string value; status.AppendFormat(" Connection string: {0:N0} key/value pairs", keyValuePairs.Count); // 1 2 3 4 5 6 7 // 123456789012345678901234567890123456789012345678901234567890123456789012345678 // Key = Value // 1 2 3 4 5 // 12345678901234567890123456789012345678901234567890 status.AppendLine(); status.AppendLine(); foreach (KeyValuePair item in keyValuePairs) { keyChars = item.Key.Trim().ToCharArray(); keyChars[0] = char.ToUpper(keyChars[0]); value = item.Value.Trim(); if (value.Length > 50) value = value.TruncateRight(47) + "..."; status.AppendFormat("{0} = {1}", new string(keyChars).TruncateRight(25).PadLeft(25), value.PadRight(50)); status.AppendLine(); } status.AppendLine(); if ((object)OutputMeasurements != null && OutputMeasurements.Length > OutputMeasurements.Count(m => m.Key == MeasurementKey.Undefined)) { status.AppendFormat(" Output measurements: {0:N0} defined measurements", OutputMeasurements.Length); status.AppendLine(); status.AppendLine(); for (int i = 0; i < Math.Min(OutputMeasurements.Length, MaxMeasurementsToShow); i++) { status.Append(OutputMeasurements[i].ToString().TruncateRight(40).PadLeft(40)); status.Append(" "); status.AppendLine(OutputMeasurements[i].ID.ToString()); } if (OutputMeasurements.Length > MaxMeasurementsToShow) status.AppendLine("...".PadLeft(26)); status.AppendLine(); } if ((object)InputMeasurementKeys != null && InputMeasurementKeys.Length > InputMeasurementKeys.Count(k => k == MeasurementKey.Undefined)) { status.AppendFormat(" Input measurements: {0:N0} defined measurements", InputMeasurementKeys.Length); status.AppendLine(); status.AppendLine(); for (int i = 0; i < Math.Min(InputMeasurementKeys.Length, MaxMeasurementsToShow); i++) { status.AppendLine(InputMeasurementKeys[i].ToString().TruncateRight(25).CenterText(50)); } if (InputMeasurementKeys.Length > MaxMeasurementsToShow) status.AppendLine("...".CenterText(50)); status.AppendLine(); } if ((object)RequestedInputMeasurementKeys != null && RequestedInputMeasurementKeys.Length > 0) { status.AppendFormat(" Requested input keys: {0:N0} defined measurements", RequestedInputMeasurementKeys.Length); status.AppendLine(); status.AppendLine(); for (int i = 0; i < Math.Min(RequestedInputMeasurementKeys.Length, MaxMeasurementsToShow); i++) { status.AppendLine(RequestedInputMeasurementKeys[i].ToString().TruncateRight(25).CenterText(50)); } if (RequestedInputMeasurementKeys.Length > MaxMeasurementsToShow) status.AppendLine("...".CenterText(50)); status.AppendLine(); } if ((object)RequestedOutputMeasurementKeys != null && RequestedOutputMeasurementKeys.Length > 0) { status.AppendFormat(" Requested output keys: {0:N0} defined measurements", RequestedOutputMeasurementKeys.Length); status.AppendLine(); status.AppendLine(); for (int i = 0; i < Math.Min(RequestedOutputMeasurementKeys.Length, MaxMeasurementsToShow); i++) { status.AppendLine(RequestedOutputMeasurementKeys[i].ToString().TruncateRight(25).CenterText(50)); } if (RequestedOutputMeasurementKeys.Length > MaxMeasurementsToShow) status.AppendLine("...".CenterText(50)); status.AppendLine(); } status.AppendFormat(" Minimum measurements used: {0:N0}", MinimumMeasurementsToUse); status.AppendLine(); status.Append(base.Status); return status.ToString(); } } #endregion #region [ Methods ] /// /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (!m_disposed) { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } /// /// Initializes . /// public virtual void Initialize() { Initialized = false; Dictionary settings = Settings; const string errorMessage = "{0} is missing from Settings - Example: framesPerSecond=30; lagTime=3; leadTime=1"; string setting; // Load required parameters if (!settings.TryGetValue("framesPerSecond", out setting)) throw new ArgumentException(string.Format(errorMessage, "framesPerSecond")); base.FramesPerSecond = int.Parse(setting); if (!settings.TryGetValue("lagTime", out setting)) throw new ArgumentException(string.Format(errorMessage, "lagTime")); base.LagTime = double.Parse(setting); if (!settings.TryGetValue("leadTime", out setting)) throw new ArgumentException(string.Format(errorMessage, "leadTime")); base.LeadTime = double.Parse(setting); // Load optional parameters if (settings.TryGetValue("usePrecisionTimer", out setting)) UsePrecisionTimer = setting.ParseBoolean(); if (settings.TryGetValue("useLocalClockAsRealTime", out setting)) UseLocalClockAsRealTime = setting.ParseBoolean(); if (settings.TryGetValue("ignoreBadTimestamps", out setting)) IgnoreBadTimestamps = setting.ParseBoolean(); if (settings.TryGetValue("allowSortsByArrival", out setting)) AllowSortsByArrival = setting.ParseBoolean(); if (settings.TryGetValue("inputMeasurementKeys", out setting)) InputMeasurementKeys = AdapterBase.ParseInputMeasurementKeys(DataSource, true, setting); else InputMeasurementKeys = new MeasurementKey[0]; if (settings.TryGetValue("outputMeasurements", out setting)) OutputMeasurements = AdapterBase.ParseOutputMeasurements(DataSource, true, setting); if (settings.TryGetValue("minimumMeasurementsToUse", out setting)) MinimumMeasurementsToUse = int.Parse(setting); if (settings.TryGetValue("timeResolution", out setting)) TimeResolution = long.Parse(setting); if (settings.TryGetValue("roundToNearestTimestamp", out setting)) RoundToNearestTimestamp = setting.ParseBoolean(); if (settings.TryGetValue("allowPreemptivePublishing", out setting)) AllowPreemptivePublishing = setting.ParseBoolean(); if (settings.TryGetValue("performTimestampReasonabilityCheck", out setting)) PerformTimestampReasonabilityCheck = setting.ParseBoolean(); if (settings.TryGetValue("processByReceivedTimestamp", out setting)) ProcessByReceivedTimestamp = setting.ParseBoolean(); if (settings.TryGetValue("maximumPublicationTimeout", out setting)) MaximumPublicationTimeout = int.Parse(setting); if (settings.TryGetValue("downsamplingMethod", out setting)) { DownsamplingMethod method; if (Enum.TryParse(setting, true, out method)) { DownsamplingMethod = method; } else { OnStatusMessage(MessageLevel.Info, $"No down-sampling method labeled \"{setting}\" exists, \"LastReceived\" method was selected.", flags: MessageFlags.UsageIssue); DownsamplingMethod = DownsamplingMethod.LastReceived; } } if (settings.TryGetValue("inputSourceIDs", out setting)) InputSourceIDs = setting.Split(','); if (settings.TryGetValue("outputSourceIDs", out setting)) OutputSourceIDs = setting.Split(','); if (settings.TryGetValue("connectOnDemand", out setting)) AutoStart = !setting.ParseBoolean(); else AutoStart = true; if (settings.TryGetValue("respectInputDemands", out setting)) RespectInputDemands = setting.ParseBoolean(); else RespectInputDemands = false; if (settings.TryGetValue("respectOutputDemands", out setting)) RespectOutputDemands = setting.ParseBoolean(); else RespectOutputDemands = true; string startTime, stopTime, parameters; bool startTimeDefined = settings.TryGetValue("startTimeConstraint", out startTime); bool stopTimeDefined = settings.TryGetValue("stopTimeConstraint", out stopTime); if (startTimeDefined || stopTimeDefined) { settings.TryGetValue("timeConstraintParameters", out parameters); SetTemporalConstraint(startTime, stopTime, parameters); } int processingInterval; if (settings.TryGetValue("processingInterval", out setting) && !string.IsNullOrWhiteSpace(setting) && int.TryParse(setting, out processingInterval)) ProcessingInterval = processingInterval; } /// /// Gets a flag that indicates whether the object has been disposed. /// public bool IsDisposed => m_disposed; /// /// Starts the or restarts it if it is already running. /// [AdapterCommand("Starts the action adapter or restarts it if it is already running.", "Administrator", "Editor")] public override void Start() { // Make sure we are stopped (e.g., disconnected) before attempting to start (e.g., connect) if (Enabled) Stop(); base.Start(); } /// /// Stops the . /// [AdapterCommand("Stops the action adapter.", "Administrator", "Editor")] public override void Stop() { base.Stop(); } /// /// Examines the concentrator frame queue state of the . /// [AdapterCommand("Examines concentration frame queue state.", "Administrator", "Editor", "Viewer")] public void ExamineQueueState() { OnStatusMessage(MessageLevel.Info, QueueState); } /// /// Resets the statistics of the . /// [AdapterCommand("Resets the statistics of the action adapter.", "Administrator", "Editor")] public override void ResetStatistics() { base.ResetStatistics(); if (Enabled) OnStatusMessage(MessageLevel.Info, "Action adapter concentration statistics have been reset."); } /// /// Manually sets the initialized state of the . /// /// Desired initialized state. [AdapterCommand("Manually sets the initialized state of the action adapter.", "Administrator", "Editor")] public virtual void SetInitializedState(bool initialized) { this.Initialized = initialized; } /// /// Gets a short one-line status of this . /// /// Maximum number of available characters for display. /// A short one-line summary of the current status of this . public virtual string GetShortStatus(int maxLength) { int inputCount = 0, outputCount = 0; if ((object)InputMeasurementKeys != null) inputCount = InputMeasurementKeys.Length; if ((object)OutputMeasurements != null) outputCount = OutputMeasurements.Length; return $"Total input measurements: {inputCount}, total output measurements: {outputCount}".PadLeft(maxLength); } /// /// Queues a collection of measurements for processing. Measurements are automatically filtered to the defined . /// /// Collection of measurements to queue for processing. /// /// Measurements are filtered against the defined . /// public virtual void QueueMeasurementsForProcessing(IEnumerable measurements) { if (m_disposed) return; SortMeasurements(measurements); } /// /// Defines a temporal processing constraint for the adapter. /// /// Defines a relative or exact start time for the temporal constraint. /// Defines a relative or exact stop time for the temporal constraint. /// Defines any temporal parameters related to the constraint. /// /// /// This method defines a temporal processing constraint for an adapter, i.e., the start and stop time over which an /// adapter will process data. Actual implementation of the constraint will be adapter specific. Implementations /// should be able to dynamically handle multiple calls to this function with new constraints. Passing in null /// for the and should cancel the temporal constraint and /// return the adapter to standard / real-time operation. /// /// /// The and parameters can be specified in one of the /// following formats: /// /// /// Time Format /// Format Description /// /// /// 12-30-2000 23:59:59.033 /// Absolute date and time. /// /// /// * /// Evaluates to . /// /// /// *-20s /// Evaluates to 20 seconds before . /// /// /// *-10m /// Evaluates to 10 minutes before . /// /// /// *-1h /// Evaluates to 1 hour before . /// /// /// *-1d /// Evaluates to 1 day before . /// /// /// /// [AdapterCommand("Defines a temporal processing constraint for the adapter.", "Administrator", "Editor", "Viewer")] public virtual void SetTemporalConstraint(string startTime, string stopTime, string constraintParameters) { if (!string.IsNullOrWhiteSpace(startTime)) m_startTimeConstraint = AdapterBase.ParseTimeTag(startTime); else m_startTimeConstraint = DateTime.MinValue; if (!string.IsNullOrWhiteSpace(stopTime)) m_stopTimeConstraint = AdapterBase.ParseTimeTag(stopTime); else m_stopTimeConstraint = DateTime.MaxValue; // When processing historical data, timestamps should not be evaluated for reasonability if (this.TemporalConstraintIsDefined()) { PerformTimestampReasonabilityCheck = false; LeadTime = double.MaxValue; } } /// /// Serves as a hash function for the current . /// /// A hash code for the current . // ReSharper disable once NonReadonlyMemberInGetHashCode public override int GetHashCode() => m_hashCode; /// /// Attempts to retrieve the minimum needed number of measurements from the frame (as specified by MinimumMeasurementsToUse) /// /// Source frame for the measurements /// Return array of measurements /// True if minimum needed number of measurements were returned in measurement array /// /// /// Remember this function will *only* return the minimum needed number of measurements, no more. If you want to use /// all available measurements in your adapter you should just use Frame.Measurements.Values directly. /// /// /// Note that the measurements array parameter will be created if the reference is null, otherwise if caller creates /// array it must be sized to MinimumMeasurementsToUse /// /// protected virtual bool TryGetMinimumNeededMeasurements(IFrame frame, ref IMeasurement[] measurements) { int index = 0, minNeeded = MinimumMeasurementsToUse; IDictionary frameMeasurements = frame.Measurements; MeasurementKey[] measurementKeys = InputMeasurementKeys; if ((object)measurements == null || measurements.Length < minNeeded) measurements = new IMeasurement[minNeeded]; if ((object)measurementKeys == null) { // No input measurements are defined, just get first set of measurements in this frame foreach (IMeasurement measurement in frameMeasurements.Values) { measurements[index++] = measurement; if (index == minNeeded) break; } } else { // Loop through all input measurements to see if they exist in this frame IMeasurement measurement; foreach (MeasurementKey key in measurementKeys) { if (frameMeasurements.TryGetValue(key, out measurement)) { measurements[index++] = measurement; if (index == minNeeded) break; } } } return index == minNeeded; } /// /// Raises the event. /// protected virtual void OnNewMeasurements(ICollection measurements) { try { NewMeasurements?.Invoke(this, new EventArgs>(measurements)); } catch (Exception ex) { // We protect our code from consumer thrown exceptions OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for NewMeasurements event: {ex.Message}", ex), "ConsumerEventException"); } } /// /// Raises the event. /// /// New status message. [Obsolete("Switch to using overload with MessageLevel parameter - this method may be removed from future builds.", false)] protected void OnStatusMessage(string status) { OnStatusMessage(MessageLevel.Info, status, "Unclassified Status"); } /// /// Raises the event with a formatted status message. /// /// Formatted status message. /// Arguments for . /// /// This overload combines string.Format and SendStatusMessage for convenience. /// [StringFormatMethod("formattedStatus"), Obsolete("Switch to using overload with MessageLevel parameter - this method may be removed from future builds.", false)] protected void OnStatusMessage(string formattedStatus, params object[] args) { OnStatusMessage(MessageLevel.Info, string.Format(formattedStatus, args), "Unclassified Status"); } /// /// Raises the event and sends this data to the . /// /// The to assign to this message /// New status message. /// A fixed string to classify this event; defaults to null. /// to use, if any; defaults to . /// /// should be a constant string value associated with what type of message is being /// generated. In general, there should only be a few dozen distinct event names per class. Exceeding this /// threshold will cause the EventName to be replaced with a general warning that a usage issue has occurred. /// protected virtual void OnStatusMessage(MessageLevel level, string status, string eventName = null, MessageFlags flags = MessageFlags.None) { try { Log.Publish(level, flags, eventName, status); using (Logger.SuppressLogMessages()) StatusMessage?.Invoke(this, new EventArgs(AdapterBase.GetStatusWithMessageLevelPrefix(status, level))); } catch (Exception ex) { // We protect our code from consumer thrown exceptions OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for StatusMessage event: {ex.Message}", ex), "ConsumerEventException"); } } /// /// Raises event. /// protected virtual void OnInputMeasurementKeysUpdated() { try { InputMeasurementKeysUpdated?.Invoke(this, EventArgs.Empty); } catch (Exception ex) { // We protect our code from consumer thrown exceptions OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for InputMeasurementKeysUpdated event: {ex.Message}", ex), "ConsumerEventException"); } } /// /// Raises event. /// protected virtual void OnOutputMeasurementsUpdated() { try { OutputMeasurementsUpdated?.Invoke(this, EventArgs.Empty); } catch (Exception ex) { // We protect our code from consumer thrown exceptions OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for OutputMeasurementsUpdated event: {ex.Message}", ex), "ConsumerEventException"); } } /// /// Raises event. /// protected virtual void OnConfigurationChanged() { try { ConfigurationChanged?.Invoke(this, EventArgs.Empty); } catch (Exception ex) { // We protect our code from consumer thrown exceptions OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for ConfigurationChanged event: {ex.Message}", ex), "ConsumerEventException"); } } private void GenHashCode() { // We cache hash code during construction or after element value change to speed usage m_hashCode = (Name + ID).GetHashCode(); } #endregion } }