//******************************************************************************************************// IndependentActionAdapterManagerBase.cs - Gbtc//// Copyright © 2020, 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://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:// ----------------------------------------------------------------------------------------------------// 02/13/2020 - J. Ritchie Carroll// Generated original version of source code.////****************************************************************************************************** using System;using System.Collections.Generic;using System.Collections.ObjectModel;using System.ComponentModel;using System.Data;using System.Linq;using System.Reflection;using System.Text;using System.Threading;using GSF.Data;using GSF.Data.Model;using GSF.Diagnostics;using GSF.Reflection;using GSF.Threading;using GSF.TimeSeries.Data;using GSF.Units.EE;using MeasurementRecord = GSF.TimeSeries.Model.Measurement;using DeviceRecord = GSF.TimeSeries.Model.Device;using static GSF.TimeSeries.Adapters.IndependentAdapterManagerExtensions; namespace GSF.TimeSeries.Adapters{ /// <summary> /// Represents an adapter base class that provides functionality to manage and distribute measurements to a collection of action adapters. /// </summary> public abstract class IndependentActionAdapterManagerBase<TAdapter> : ActionAdapterCollection, IIndependentAdapterManager where TAdapter : IAdapter, new() { #region [ Members ] // Constants /// <summary> /// Defines the default value for the <see cref="FramesPerSecond"/>. /// </summary> public const int DefaultFramesPerSecond = 30; /// <summary> /// Defines the default value for the <see cref="LagTime"/>. /// </summary> public const double DefaultLagTime = 5.0D; /// <summary> /// Defines the default value for the <see cref="LeadTime"/>. /// </summary> public const double DefaultLeadTime = 5.0D; // Fields private readonly LongSynchronizedOperation m_parseConnectionString; private bool m_disposed; #endregion #region [ Constructors ] /// <summary> /// Creates a new <see cref="IndependentActionAdapterManagerBase{TAdapter}"/>. /// </summary> protected IndependentActionAdapterManagerBase() { this.HandleConstruct(); // Define a synchronized operation to manage parsing connection string operations sequentially m_parseConnectionString = new LongSynchronizedOperation(ParseConnectionString, ex => OnProcessException(MessageLevel.Warning, ex, "ParseConnectionString")) { IsBackground = true }; } #endregion #region [ Properties ] /// <summary> /// Gets or sets primary keys of input measurements for the <see cref="IndependentActionAdapterManagerBase{TAdapter}"/>. /// </summary> [ConnectionStringParameter] [Description("Defines primary keys of input measurements the 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")] [DefaultValue(null)] public override MeasurementKey[] InputMeasurementKeys { get => base.InputMeasurementKeys; set { base.InputMeasurementKeys = value; InputMeasurementKeyTypes = DataSource.GetSignalTypes(value, SourceMeasurementTable); } } /// <summary> /// Gets or sets output measurements that the <see cref="IndependentActionAdapterManagerBase{TAdapter}"/> will produce, if any. /// </summary> [ConnectionStringParameter] [Description("Defines primary keys of output measurements the 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")] [DefaultValue(null)] public override IMeasurement[] OutputMeasurements { get => base.OutputMeasurements; set { base.OutputMeasurements = value; OutputMeasurementTypes = DataSource.GetSignalTypes(value, SourceMeasurementTable); } } /// <summary> /// Gets or sets the number of frames per second applied to each adapter. /// </summary> /// <remarks> /// Valid frame rates for a <see cref="ConcentratorBase"/> are greater than 0 frames per second. /// </remarks> [ConnectionStringParameter] [Description("Defines the number of frames per second applied to each adapter.")] [DefaultValue(DefaultFramesPerSecond)] public virtual int FramesPerSecond { get; set; } = DefaultFramesPerSecond; /// <summary> /// Gets or sets the allowed past time deviation tolerance, in seconds (can be sub-second) applied to each adapter. /// </summary> /// <remarks> /// <para>Defines the time sensitivity to past measurement timestamps.</para> /// <para>The number of seconds allowed before assuming a measurement timestamp is too old.</para> /// <para>This becomes the amount of delay introduced by the concentrator to allow time for data to flow into the system.</para> /// </remarks> [ConnectionStringParameter] [Description("Defines the allowed past time deviation tolerance, in seconds (can be sub-second) applied to each adapter.")] [DefaultValue(DefaultLagTime)] public virtual double LagTime { get; set; } = DefaultLagTime; /// <summary> /// Gets or sets the allowed future time deviation tolerance, in seconds (can be sub-second) applied to each adapter. /// </summary> /// <remarks> /// <para>Defines the time sensitivity to future measurement timestamps.</para> /// <para>The number of seconds allowed before assuming a measurement timestamp is too advanced.</para> /// <para>This becomes the tolerated +/- accuracy of the local clock to real-time.</para> /// </remarks> [ConnectionStringParameter] [Description("Defines the allowed future time deviation tolerance, in seconds (can be sub-second) applied to each adapter.")] [DefaultValue(DefaultLeadTime)] public virtual double LeadTime { get; set; } = DefaultLeadTime; /// <summary> /// Gets or sets the wait timeout, in milliseconds, that system wait for system configuration reload to complete. /// </summary> [ConnectionStringParameter] [Description("Defines the wait timeout, in milliseconds, that system wait for system configuration reload to complete.")] [DefaultValue(DefaultConfigurationReloadWaitTimeout)] public int ConfigurationReloadWaitTimeout { get; set; } = DefaultConfigurationReloadWaitTimeout; /// <summary> /// Gets or sets the total number of attempts to wait for system configuration reloads when waiting for configuration updates to be available. /// </summary> [ConnectionStringParameter] [Description("Defines the total number of attempts to wait for system configuration reloads when waiting for configuration updates to be available.")] [DefaultValue(DefaultConfigurationReloadWaitAttempts)] public virtual int ConfigurationReloadWaitAttempts { get; set; } = DefaultConfigurationReloadWaitAttempts; /// <summary> /// Gets or sets the connection string used for database operations. Leave blank to use local configuration database defined in "systemSettings". /// </summary> [ConnectionStringParameter] [Description("Defines the connection string used for database operations. Leave blank to use local configuration database defined in \"systemSettings\".")] [DefaultValue(DefaultDatabaseConnectionString)] public virtual string DatabaseConnectionString { get; set; } /// <summary> /// Gets or sets the provider string used for database operations. Defaults to a SQL Server provider string. /// </summary> [ConnectionStringParameter] [Description("Defines the provider string used for database operations. Defaults to a SQL Server provider string.")] [DefaultValue(DefaultDatabaseProviderString)] public virtual string DatabaseProviderString { get; set; } /// <summary> /// Gets or sets template for output measurement point tag names. /// </summary> [ConnectionStringParameter] [Description("Defines template for output measurement point tag names, typically an expression like \"" + DefaultPointTagTemplate + "\" where \"{0}\" is substituted with this adapter name, a dash and then the PerAdapterOutputNames value for the current measurement. Note that \"{0}\" token is not required, property can be overridden to provide desired value.")] [DefaultValue(DefaultPointTagTemplate)] public virtual string PointTagTemplate { get; set; } = DefaultPointTagTemplate; /// <summary> /// Gets or sets template for output measurement alternate tag names. /// </summary> [ConnectionStringParameter] [Description("Defines template for output measurement alternate tag names, typically an expression where \"{0}\" is substituted with this adapter name, a dash and then the PerAdapterOutputNames value for the current measurement. Note that \"{0}\" token is not required, property can be overridden to provide desired value.")] [DefaultValue(DefaultAlternateTagTemplate)] public virtual string AlternateTagTemplate { get; set; } = DefaultAlternateTagTemplate; /// <summary> /// Gets or sets template for output measurement signal reference names. /// </summary> [ConnectionStringParameter] [Description("Defines template for output measurement signal reference names, typically an expression like \"" + DefaultSignalReferenceTemplate + "\" where \"{0}\" is substituted with this adapter name, a dash and then the PerAdapterOutputNames value for the current measurement. Note that \"{0}\" token is not required, property can be overridden to provide desired value.")] [DefaultValue(DefaultSignalReferenceTemplate)] public virtual string SignalReferenceTemplate { get; set; } = DefaultSignalReferenceTemplate; /// <summary> /// Gets or sets template for output measurement descriptions. /// </summary> [ConnectionStringParameter] [Description("Defines template for output measurement descriptions, typically an expression like \"" + DefaultDescriptionTemplate + "\".")] [DefaultValue(DefaultDescriptionTemplate)] public virtual string DescriptionTemplate { get; set; } = DefaultDescriptionTemplate; /// <summary> /// Gets or sets template for the parent device acronym used to group associated output measurements. /// </summary> [ConnectionStringParameter] [Description("Defines template for the parent device acronym used to group associated output measurements, typically an expression like \"" + DefaultParentDeviceAcronymTemplate + "\" where \"{0}\" is substituted with this adapter name. Set to blank value to create no parent device associated output measurements. Note that \"{0}\" token is not required, you can simply use a specific device acronym.")] [DefaultValue(DefaultParentDeviceAcronymTemplate)] public virtual string ParentDeviceAcronymTemplate { get; set; } = DefaultParentDeviceAcronymTemplate; /// <summary> /// Gets or sets default signal type to use for all output measurements when <see cref="SignalTypes"/> array is not defined. /// </summary> [ConnectionStringParameter] [Description("Defines the default signal type to use for all output measurements. Used when per output measurement SignalTypes array is not defined.")] [DefaultValue(typeof(SignalType), DefaultSignalType)] public virtual SignalType SignalType { get; set; } = (SignalType)Enum.Parse(typeof(SignalType), DefaultSignalType); /// <summary> /// Gets per adapter signal type for output measurements, used when each output needs to be a different type. /// </summary> public virtual SignalType[] SignalTypes { get; } = null; /// <summary> /// Gets any custom adapter settings to be added to each adapter connection string. Can be used to add /// settings that are custom per adapter. /// </summary> public virtual string CustomAdapterSettings { get; } = null; /// <summary> /// Gets or sets the target historian acronym for output measurements. /// </summary> [ConnectionStringParameter] [Description("Defines the target historian acronym for output measurements.")] [DefaultValue(DefaultTargetHistorianAcronym)] public virtual string TargetHistorianAcronym { get; set; } = DefaultTargetHistorianAcronym; /// <summary> /// Gets or sets the source measurement table to use for configuration. /// </summary> [ConnectionStringParameter] [Description("Defines the source measurement table to use for configuration.")] [DefaultValue(DefaultSourceMeasurementTable)] public virtual string SourceMeasurementTable { get; set; } = DefaultSourceMeasurementTable; /// <summary> /// Gets or sets <see cref="DataSet"/> based data source used to load each <see cref="IAdapter"/>. Updates /// to this property will cascade to all adapters in this <see cref="IndependentActionAdapterManagerBase{TAdapter}"/>. /// </summary> public override DataSet DataSource { get => base.DataSource; set { if (!this.DataSourceChanged(value)) return; base.DataSource = value; this.HandleUpdateDataSource(); } } /// <summary> /// Gets number of input measurement required by each adapter. /// </summary> public abstract int PerAdapterInputCount { get; } /// <summary> /// Gets or sets the index into the per adapter input measurements to use for target adapter name. /// </summary> [ConnectionStringParameter] [Description("Defines the index into the per adapter input measurements to use for target adapter name.")] public virtual int InputMeasurementIndexUsedForName { get; set; } /// <summary> /// Gets output measurement names to use for each adapter. /// </summary> public abstract ReadOnlyCollection<string> PerAdapterOutputNames { get; } /// <summary> /// Gets or sets flag that determines if the <see cref="IndependentActionAdapterManagerBase{TAdapter}"/> adapter /// <see cref="AdapterCollectionBase{T}.ConnectionString"/> should be automatically parsed every time the /// <see cref="DataSource"/> is updated without requiring adapter to be reinitialized. Defaults to <c>true</c> /// to allow child adapters to come and go based on updates to system configuration. /// </summary> /// <remarks> /// When this value is <c>true</c> (the default), implementors must call the <see cref="InitializeChildAdapterManagement"/> /// method an overridden <see cref="ParseConnectionString"/> method. /// </remarks> protected virtual bool AutoReparseConnectionString { get; set; } = true; /// <summary> /// Gets input measurement <see cref="SignalType"/>'s for each of the <see cref="ActionAdapterBase.InputMeasurementKeys"/>, if any. /// </summary> public virtual SignalType[] InputMeasurementKeyTypes { get; private set; } /// <summary> /// Gets output measurement <see cref="SignalType"/>'s for each of the <see cref="ActionAdapterBase.OutputMeasurements"/>, if any. /// </summary> public virtual SignalType[] OutputMeasurementTypes { get; private set; } /// <summary> /// Gets or sets current adapter ID counter. /// </summary> public uint AdapterIDCounter { get; set; } /// <summary> /// Gets adapter index currently being processed. /// </summary> public int CurrentAdapterIndex { get; protected set; } /// <summary> /// Gets adapter output index currently being processed. /// </summary> public int CurrentOutputIndex { get; protected set; } /// <summary> /// Gets associated device ID for <see cref="CurrentAdapterIndex"/>, if any, for measurement generation. If overridden to provide custom /// device ID, <see cref="ParentDeviceAcronymTemplate"/> should be set to <c>null</c> so no parent device is created. /// </summary> public virtual int CurrentDeviceID { get; } = 0; /// <summary> /// Returns the detailed status of the <see cref="IndependentActionAdapterManagerBase{TAdapter}"/>. /// </summary> public override string Status { get { StringBuilder status = new StringBuilder(); status.AppendLine($" Frames Per Second: {FramesPerSecond:N0}"); status.AppendLine($" Lag Time / Lead Time: {LagTime:N3} / {LeadTime:N3}"); status.Append(this.HandleStatus()); status.Append(base.Status); return status.ToString(); } } #endregion #region [ Methods ] /// <summary> /// Releases the unmanaged resources used by the <see cref="IndependentActionAdapterManagerBase{TAdapter}"/> object and optionally releases the managed resources. /// </summary> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> protected override void Dispose(bool disposing) { if (m_disposed) return; try { if (!disposing) return; this.HandleDispose(); } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } /// <summary> /// Initializes the <see cref="IndependentActionAdapterManagerBase{TAdapter}" />. /// </summary> public override void Initialize() => this.HandleInitialize(); /// <summary> /// Initializes management operations for child adapters based on inputs. Derived classes should override /// <see cref="ParseConnectionString"/> and call this method when <see cref="AutoReparseConnectionString"/> is /// <c>true</c> (the default). /// </summary> /// <param name="inputMeasurementKeys">Input measurement keys to use for initialization.</param> /// <remarks> /// When <see cref="AutoReparseConnectionString"/> is <c>true</c> (the default), implementors must override /// the <see cref="ParseConnectionString"/> method and at least call this method. /// </remarks> protected virtual void InitializeChildAdapterManagement(MeasurementKey[] inputMeasurementKeys) { try { // If no inputs are defined, skip setup if (inputMeasurementKeys.Length == 0) { OnStatusMessage(MessageLevel.Warning, "No inputs were configured. Cannot initialize child adapters."); return; } ReadOnlyCollection<string> perAdapterOutputNames = PerAdapterOutputNames; int inputsPerAdapter = PerAdapterInputCount; int outputsPerAdapter = perAdapterOutputNames?.Count ?? 0; if (perAdapterOutputNames is null || inputsPerAdapter <= 0 || outputsPerAdapter <= 0) { OnStatusMessage(MessageLevel.Warning, "No PerAdapterInputCount or PerAdapterOutputNames where defined. Cannot initialize child adapters."); return; } int nameIndex = InputMeasurementIndexUsedForName; int? currentDeviceID = null; // Create associated parent device for output measurements if parent device acronym template is defined if (!string.IsNullOrWhiteSpace(ParentDeviceAcronymTemplate)) { if (CurrentDeviceID > 0) OnStatusMessage(MessageLevel.Warning, $"WARNING: Creating a parent device for \"{Name}\" [{GetType().Name}] based on specified template \"{ParentDeviceAcronymTemplate}\", but overridden CurrentDeviceID property reports non-zero value: {CurrentDeviceID:N0}"); using (AdoDataConnection connection = GetConfiguredConnection()) { TableOperations<DeviceRecord> deviceTable = new TableOperations<DeviceRecord>(connection); string deviceAcronym = string.Format(ParentDeviceAcronymTemplate, Name); DeviceRecord device = deviceTable.QueryRecordWhere("Acronym = {0}", deviceAcronym) ?? deviceTable.NewRecord(); int protocolID = connection.ExecuteScalar<int?>("SELECT ID FROM Protocol WHERE Acronym = 'VirtualInput'") ?? 15; device.Acronym = deviceAcronym; device.Name = deviceAcronym; device.ProtocolID = protocolID; device.Enabled = true; deviceTable.AddNewOrUpdateRecord(device); currentDeviceID = deviceTable.QueryRecordWhere("Acronym = {0}", deviceAcronym)?.ID; } } HashSet<string> activeAdapterNames = new HashSet<string>(StringComparer.Ordinal); List<TAdapter> adapters = new List<TAdapter>(); HashSet<Guid> signalIDs = new HashSet<Guid>(); // Create settings dictionary for connection string to use with primary child adapters Dictionary<string, string> settings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (PropertyInfo property in GetType().GetProperties()) { if (property.AttributeExists<PropertyInfo, ConnectionStringParameterAttribute>()) settings[property.Name] = $"{property.GetValue(this)}"; } // Create child adapter for provided inputs to the parent bulk collection-based adapter for (int i = 0; i < inputMeasurementKeys.Length; i += inputsPerAdapter) { CurrentAdapterIndex = adapters.Count; Guid[] inputs = new Guid[inputsPerAdapter]; Guid[] outputs = new Guid[outputsPerAdapter]; // Adapter inputs are presumed to be grouped together for (int j = 0; j < inputsPerAdapter && i + j < inputMeasurementKeys.Length; j++) inputs[j] = inputMeasurementKeys[i + j].SignalID; string inputName = this.LookupPointTag(inputs[nameIndex], SourceMeasurementTable); string adapterName = $"{Name}!{inputName}"; string alternateTagPrefix = null; if (!string.IsNullOrWhiteSpace(AlternateTagTemplate)) { string deviceName = this.LookupDevice(inputs[nameIndex], SourceMeasurementTable); string phasorLabel = this.LookupPhasorLabel(inputs[nameIndex], SourceMeasurementTable); alternateTagPrefix = $"{deviceName}-{phasorLabel}"; } // Track active adapter names so that adapters that no longer have sources can be removed activeAdapterNames.Add(adapterName); // See if child adapter already exists if (this.FindAdapter(adapterName) != null) continue;Lines 1–500 of 664. Load more
//****************************************************************************************************** // IndependentActionAdapterManagerBase.cs - Gbtc // // Copyright © 2020, 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://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: // ---------------------------------------------------------------------------------------------------- // 02/13/2020 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Data; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using GSF.Data; using GSF.Data.Model; using GSF.Diagnostics; using GSF.Reflection; using GSF.Threading; using GSF.TimeSeries.Data; using GSF.Units.EE; using MeasurementRecord = GSF.TimeSeries.Model.Measurement; using DeviceRecord = GSF.TimeSeries.Model.Device; using static GSF.TimeSeries.Adapters.IndependentAdapterManagerExtensions; namespace GSF.TimeSeries.Adapters { /// <summary> /// Represents an adapter base class that provides functionality to manage and distribute measurements to a collection of action adapters. /// </summary> public abstract class IndependentActionAdapterManagerBase<TAdapter> : ActionAdapterCollection, IIndependentAdapterManager where TAdapter : IAdapter, new() { #region [ Members ] // Constants /// <summary> /// Defines the default value for the <see cref="FramesPerSecond"/>. /// </summary> public const int DefaultFramesPerSecond = 30; /// <summary> /// Defines the default value for the <see cref="LagTime"/>. /// </summary> public const double DefaultLagTime = 5.0D; /// <summary> /// Defines the default value for the <see cref="LeadTime"/>. /// </summary> public const double DefaultLeadTime = 5.0D; // Fields private readonly LongSynchronizedOperation m_parseConnectionString; private bool m_disposed; #endregion #region [ Constructors ] /// <summary> /// Creates a new <see cref="IndependentActionAdapterManagerBase{TAdapter}"/>. /// </summary> protected IndependentActionAdapterManagerBase() { this.HandleConstruct(); // Define a synchronized operation to manage parsing connection string operations sequentially m_parseConnectionString = new LongSynchronizedOperation(ParseConnectionString, ex => OnProcessException(MessageLevel.Warning, ex, "ParseConnectionString")) { IsBackground = true }; } #endregion #region [ Properties ] /// <summary> /// Gets or sets primary keys of input measurements for the <see cref="IndependentActionAdapterManagerBase{TAdapter}"/>. /// </summary> [ConnectionStringParameter] [Description("Defines primary keys of input measurements the 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")] [DefaultValue(null)] public override MeasurementKey[] InputMeasurementKeys { get => base.InputMeasurementKeys; set { base.InputMeasurementKeys = value; InputMeasurementKeyTypes = DataSource.GetSignalTypes(value, SourceMeasurementTable); } } /// <summary> /// Gets or sets output measurements that the <see cref="IndependentActionAdapterManagerBase{TAdapter}"/> will produce, if any. /// </summary> [ConnectionStringParameter] [Description("Defines primary keys of output measurements the 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")] [DefaultValue(null)] public override IMeasurement[] OutputMeasurements { get => base.OutputMeasurements; set { base.OutputMeasurements = value; OutputMeasurementTypes = DataSource.GetSignalTypes(value, SourceMeasurementTable); } } /// <summary> /// Gets or sets the number of frames per second applied to each adapter. /// </summary> /// <remarks> /// Valid frame rates for a <see cref="ConcentratorBase"/> are greater than 0 frames per second. /// </remarks> [ConnectionStringParameter] [Description("Defines the number of frames per second applied to each adapter.")] [DefaultValue(DefaultFramesPerSecond)] public virtual int FramesPerSecond { get; set; } = DefaultFramesPerSecond; /// <summary> /// Gets or sets the allowed past time deviation tolerance, in seconds (can be sub-second) applied to each adapter. /// </summary> /// <remarks> /// <para>Defines the time sensitivity to past measurement timestamps.</para> /// <para>The number of seconds allowed before assuming a measurement timestamp is too old.</para> /// <para>This becomes the amount of delay introduced by the concentrator to allow time for data to flow into the system.</para> /// </remarks> [ConnectionStringParameter] [Description("Defines the allowed past time deviation tolerance, in seconds (can be sub-second) applied to each adapter.")] [DefaultValue(DefaultLagTime)] public virtual double LagTime { get; set; } = DefaultLagTime; /// <summary> /// Gets or sets the allowed future time deviation tolerance, in seconds (can be sub-second) applied to each adapter. /// </summary> /// <remarks> /// <para>Defines the time sensitivity to future measurement timestamps.</para> /// <para>The number of seconds allowed before assuming a measurement timestamp is too advanced.</para> /// <para>This becomes the tolerated +/- accuracy of the local clock to real-time.</para> /// </remarks> [ConnectionStringParameter] [Description("Defines the allowed future time deviation tolerance, in seconds (can be sub-second) applied to each adapter.")] [DefaultValue(DefaultLeadTime)] public virtual double LeadTime { get; set; } = DefaultLeadTime; /// <summary> /// Gets or sets the wait timeout, in milliseconds, that system wait for system configuration reload to complete. /// </summary> [ConnectionStringParameter] [Description("Defines the wait timeout, in milliseconds, that system wait for system configuration reload to complete.")] [DefaultValue(DefaultConfigurationReloadWaitTimeout)] public int ConfigurationReloadWaitTimeout { get; set; } = DefaultConfigurationReloadWaitTimeout; /// <summary> /// Gets or sets the total number of attempts to wait for system configuration reloads when waiting for configuration updates to be available. /// </summary> [ConnectionStringParameter] [Description("Defines the total number of attempts to wait for system configuration reloads when waiting for configuration updates to be available.")] [DefaultValue(DefaultConfigurationReloadWaitAttempts)] public virtual int ConfigurationReloadWaitAttempts { get; set; } = DefaultConfigurationReloadWaitAttempts; /// <summary> /// Gets or sets the connection string used for database operations. Leave blank to use local configuration database defined in "systemSettings". /// </summary> [ConnectionStringParameter] [Description("Defines the connection string used for database operations. Leave blank to use local configuration database defined in \"systemSettings\".")] [DefaultValue(DefaultDatabaseConnectionString)] public virtual string DatabaseConnectionString { get; set; } /// <summary> /// Gets or sets the provider string used for database operations. Defaults to a SQL Server provider string. /// </summary> [ConnectionStringParameter] [Description("Defines the provider string used for database operations. Defaults to a SQL Server provider string.")] [DefaultValue(DefaultDatabaseProviderString)] public virtual string DatabaseProviderString { get; set; } /// <summary> /// Gets or sets template for output measurement point tag names. /// </summary> [ConnectionStringParameter] [Description("Defines template for output measurement point tag names, typically an expression like \"" + DefaultPointTagTemplate + "\" where \"{0}\" is substituted with this adapter name, a dash and then the PerAdapterOutputNames value for the current measurement. Note that \"{0}\" token is not required, property can be overridden to provide desired value.")] [DefaultValue(DefaultPointTagTemplate)] public virtual string PointTagTemplate { get; set; } = DefaultPointTagTemplate; /// <summary> /// Gets or sets template for output measurement alternate tag names. /// </summary> [ConnectionStringParameter] [Description("Defines template for output measurement alternate tag names, typically an expression where \"{0}\" is substituted with this adapter name, a dash and then the PerAdapterOutputNames value for the current measurement. Note that \"{0}\" token is not required, property can be overridden to provide desired value.")] [DefaultValue(DefaultAlternateTagTemplate)] public virtual string AlternateTagTemplate { get; set; } = DefaultAlternateTagTemplate; /// <summary> /// Gets or sets template for output measurement signal reference names. /// </summary> [ConnectionStringParameter] [Description("Defines template for output measurement signal reference names, typically an expression like \"" + DefaultSignalReferenceTemplate + "\" where \"{0}\" is substituted with this adapter name, a dash and then the PerAdapterOutputNames value for the current measurement. Note that \"{0}\" token is not required, property can be overridden to provide desired value.")] [DefaultValue(DefaultSignalReferenceTemplate)] public virtual string SignalReferenceTemplate { get; set; } = DefaultSignalReferenceTemplate; /// <summary> /// Gets or sets template for output measurement descriptions. /// </summary> [ConnectionStringParameter] [Description("Defines template for output measurement descriptions, typically an expression like \"" + DefaultDescriptionTemplate + "\".")] [DefaultValue(DefaultDescriptionTemplate)] public virtual string DescriptionTemplate { get; set; } = DefaultDescriptionTemplate; /// <summary> /// Gets or sets template for the parent device acronym used to group associated output measurements. /// </summary> [ConnectionStringParameter] [Description("Defines template for the parent device acronym used to group associated output measurements, typically an expression like \"" + DefaultParentDeviceAcronymTemplate + "\" where \"{0}\" is substituted with this adapter name. Set to blank value to create no parent device associated output measurements. Note that \"{0}\" token is not required, you can simply use a specific device acronym.")] [DefaultValue(DefaultParentDeviceAcronymTemplate)] public virtual string ParentDeviceAcronymTemplate { get; set; } = DefaultParentDeviceAcronymTemplate; /// <summary> /// Gets or sets default signal type to use for all output measurements when <see cref="SignalTypes"/> array is not defined. /// </summary> [ConnectionStringParameter] [Description("Defines the default signal type to use for all output measurements. Used when per output measurement SignalTypes array is not defined.")] [DefaultValue(typeof(SignalType), DefaultSignalType)] public virtual SignalType SignalType { get; set; } = (SignalType)Enum.Parse(typeof(SignalType), DefaultSignalType); /// <summary> /// Gets per adapter signal type for output measurements, used when each output needs to be a different type. /// </summary> public virtual SignalType[] SignalTypes { get; } = null; /// <summary> /// Gets any custom adapter settings to be added to each adapter connection string. Can be used to add /// settings that are custom per adapter. /// </summary> public virtual string CustomAdapterSettings { get; } = null; /// <summary> /// Gets or sets the target historian acronym for output measurements. /// </summary> [ConnectionStringParameter] [Description("Defines the target historian acronym for output measurements.")] [DefaultValue(DefaultTargetHistorianAcronym)] public virtual string TargetHistorianAcronym { get; set; } = DefaultTargetHistorianAcronym; /// <summary> /// Gets or sets the source measurement table to use for configuration. /// </summary> [ConnectionStringParameter] [Description("Defines the source measurement table to use for configuration.")] [DefaultValue(DefaultSourceMeasurementTable)] public virtual string SourceMeasurementTable { get; set; } = DefaultSourceMeasurementTable; /// <summary> /// Gets or sets <see cref="DataSet"/> based data source used to load each <see cref="IAdapter"/>. Updates /// to this property will cascade to all adapters in this <see cref="IndependentActionAdapterManagerBase{TAdapter}"/>. /// </summary> public override DataSet DataSource { get => base.DataSource; set { if (!this.DataSourceChanged(value)) return; base.DataSource = value; this.HandleUpdateDataSource(); } } /// <summary> /// Gets number of input measurement required by each adapter. /// </summary> public abstract int PerAdapterInputCount { get; } /// <summary> /// Gets or sets the index into the per adapter input measurements to use for target adapter name. /// </summary> [ConnectionStringParameter] [Description("Defines the index into the per adapter input measurements to use for target adapter name.")] public virtual int InputMeasurementIndexUsedForName { get; set; } /// <summary> /// Gets output measurement names to use for each adapter. /// </summary> public abstract ReadOnlyCollection<string> PerAdapterOutputNames { get; } /// <summary> /// Gets or sets flag that determines if the <see cref="IndependentActionAdapterManagerBase{TAdapter}"/> adapter /// <see cref="AdapterCollectionBase{T}.ConnectionString"/> should be automatically parsed every time the /// <see cref="DataSource"/> is updated without requiring adapter to be reinitialized. Defaults to <c>true</c> /// to allow child adapters to come and go based on updates to system configuration. /// </summary> /// <remarks> /// When this value is <c>true</c> (the default), implementors must call the <see cref="InitializeChildAdapterManagement"/> /// method an overridden <see cref="ParseConnectionString"/> method. /// </remarks> protected virtual bool AutoReparseConnectionString { get; set; } = true; /// <summary> /// Gets input measurement <see cref="SignalType"/>'s for each of the <see cref="ActionAdapterBase.InputMeasurementKeys"/>, if any. /// </summary> public virtual SignalType[] InputMeasurementKeyTypes { get; private set; } /// <summary> /// Gets output measurement <see cref="SignalType"/>'s for each of the <see cref="ActionAdapterBase.OutputMeasurements"/>, if any. /// </summary> public virtual SignalType[] OutputMeasurementTypes { get; private set; } /// <summary> /// Gets or sets current adapter ID counter. /// </summary> public uint AdapterIDCounter { get; set; } /// <summary> /// Gets adapter index currently being processed. /// </summary> public int CurrentAdapterIndex { get; protected set; } /// <summary> /// Gets adapter output index currently being processed. /// </summary> public int CurrentOutputIndex { get; protected set; } /// <summary> /// Gets associated device ID for <see cref="CurrentAdapterIndex"/>, if any, for measurement generation. If overridden to provide custom /// device ID, <see cref="ParentDeviceAcronymTemplate"/> should be set to <c>null</c> so no parent device is created. /// </summary> public virtual int CurrentDeviceID { get; } = 0; /// <summary> /// Returns the detailed status of the <see cref="IndependentActionAdapterManagerBase{TAdapter}"/>. /// </summary> public override string Status { get { StringBuilder status = new StringBuilder(); status.AppendLine($" Frames Per Second: {FramesPerSecond:N0}"); status.AppendLine($" Lag Time / Lead Time: {LagTime:N3} / {LeadTime:N3}"); status.Append(this.HandleStatus()); status.Append(base.Status); return status.ToString(); } } #endregion #region [ Methods ] /// <summary> /// Releases the unmanaged resources used by the <see cref="IndependentActionAdapterManagerBase{TAdapter}"/> object and optionally releases the managed resources. /// </summary> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> protected override void Dispose(bool disposing) { if (m_disposed) return; try { if (!disposing) return; this.HandleDispose(); } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } /// <summary> /// Initializes the <see cref="IndependentActionAdapterManagerBase{TAdapter}" />. /// </summary> public override void Initialize() => this.HandleInitialize(); /// <summary> /// Initializes management operations for child adapters based on inputs. Derived classes should override /// <see cref="ParseConnectionString"/> and call this method when <see cref="AutoReparseConnectionString"/> is /// <c>true</c> (the default). /// </summary> /// <param name="inputMeasurementKeys">Input measurement keys to use for initialization.</param> /// <remarks> /// When <see cref="AutoReparseConnectionString"/> is <c>true</c> (the default), implementors must override /// the <see cref="ParseConnectionString"/> method and at least call this method. /// </remarks> protected virtual void InitializeChildAdapterManagement(MeasurementKey[] inputMeasurementKeys) { try { // If no inputs are defined, skip setup if (inputMeasurementKeys.Length == 0) { OnStatusMessage(MessageLevel.Warning, "No inputs were configured. Cannot initialize child adapters."); return; } ReadOnlyCollection<string> perAdapterOutputNames = PerAdapterOutputNames; int inputsPerAdapter = PerAdapterInputCount; int outputsPerAdapter = perAdapterOutputNames?.Count ?? 0; if (perAdapterOutputNames is null || inputsPerAdapter <= 0 || outputsPerAdapter <= 0) { OnStatusMessage(MessageLevel.Warning, "No PerAdapterInputCount or PerAdapterOutputNames where defined. Cannot initialize child adapters."); return; } int nameIndex = InputMeasurementIndexUsedForName; int? currentDeviceID = null; // Create associated parent device for output measurements if parent device acronym template is defined if (!string.IsNullOrWhiteSpace(ParentDeviceAcronymTemplate)) { if (CurrentDeviceID > 0) OnStatusMessage(MessageLevel.Warning, $"WARNING: Creating a parent device for \"{Name}\" [{GetType().Name}] based on specified template \"{ParentDeviceAcronymTemplate}\", but overridden CurrentDeviceID property reports non-zero value: {CurrentDeviceID:N0}"); using (AdoDataConnection connection = GetConfiguredConnection()) { TableOperations<DeviceRecord> deviceTable = new TableOperations<DeviceRecord>(connection); string deviceAcronym = string.Format(ParentDeviceAcronymTemplate, Name); DeviceRecord device = deviceTable.QueryRecordWhere("Acronym = {0}", deviceAcronym) ?? deviceTable.NewRecord(); int protocolID = connection.ExecuteScalar<int?>("SELECT ID FROM Protocol WHERE Acronym = 'VirtualInput'") ?? 15; device.Acronym = deviceAcronym; device.Name = deviceAcronym; device.ProtocolID = protocolID; device.Enabled = true; deviceTable.AddNewOrUpdateRecord(device); currentDeviceID = deviceTable.QueryRecordWhere("Acronym = {0}", deviceAcronym)?.ID; } } HashSet<string> activeAdapterNames = new HashSet<string>(StringComparer.Ordinal); List<TAdapter> adapters = new List<TAdapter>(); HashSet<Guid> signalIDs = new HashSet<Guid>(); // Create settings dictionary for connection string to use with primary child adapters Dictionary<string, string> settings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (PropertyInfo property in GetType().GetProperties()) { if (property.AttributeExists<PropertyInfo, ConnectionStringParameterAttribute>()) settings[property.Name] = $"{property.GetValue(this)}"; } // Create child adapter for provided inputs to the parent bulk collection-based adapter for (int i = 0; i < inputMeasurementKeys.Length; i += inputsPerAdapter) { CurrentAdapterIndex = adapters.Count; Guid[] inputs = new Guid[inputsPerAdapter]; Guid[] outputs = new Guid[outputsPerAdapter]; // Adapter inputs are presumed to be grouped together for (int j = 0; j < inputsPerAdapter && i + j < inputMeasurementKeys.Length; j++) inputs[j] = inputMeasurementKeys[i + j].SignalID; string inputName = this.LookupPointTag(inputs[nameIndex], SourceMeasurementTable); string adapterName = $"{Name}!{inputName}"; string alternateTagPrefix = null; if (!string.IsNullOrWhiteSpace(AlternateTagTemplate)) { string deviceName = this.LookupDevice(inputs[nameIndex], SourceMeasurementTable); string phasorLabel = this.LookupPhasorLabel(inputs[nameIndex], SourceMeasurementTable); alternateTagPrefix = $"{deviceName}-{phasorLabel}"; } // Track active adapter names so that adapters that no longer have sources can be removed activeAdapterNames.Add(adapterName); // See if child adapter already exists if (this.FindAdapter(adapterName) != null) continue;