// Make sure to expose any routing table messages private static void RoutingTables_StatusMessage(object sender, EventArgs <string> e) { if (sender is RoutingTables routingTables) { IIndependentAdapterManager instance = routingTables.ActionAdapters as IIndependentAdapterManager ?? routingTables.OutputAdapters as IIndependentAdapterManager; instance?.OnStatusMessage(MessageLevel.Info, e.Argument); } }
// Make sure to expose any routing table exceptions private static void RoutingTables_ProcessException(object sender, EventArgs <Exception> e) { if (sender is RoutingTables routingTables) { IIndependentAdapterManager instance = routingTables.ActionAdapters as IIndependentAdapterManager ?? routingTables.OutputAdapters as IIndependentAdapterManager; instance?.OnProcessException(MessageLevel.Warning, e.Argument); } }
public static void HandleRecalculateRoutingTables(this IIndependentAdapterManager instance) => instance.OnInputMeasurementKeysUpdated(); // Requests route recalculation by IonSession /// <summary> /// Queues a collection of measurements for processing to each <see cref="IAdapter"/> connected to this <see cref="IndependentAdapterManagerExtensions"/>. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> /// <param name="measurements">Measurements to queue for processing.</param> public static void HandleQueueMeasurementsForProcessing(this IIndependentAdapterManager instance, IEnumerable <IMeasurement> measurements) { if (instance.RoutingTables == null) { return; } // Pass measurements coming into parent collection adapter to routing tables for individual child adapter distribution IList <IMeasurement> measurementList = measurements as IList <IMeasurement> ?? measurements.ToList(); instance.RoutingTables.InjectMeasurements(instance, new EventArgs <ICollection <IMeasurement> >(measurementList)); }
/// <summary> /// Determines whether the data in the data source has actually changed when receiving a new data source. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> /// <param name="newDataSource">New data source to check.</param> /// <returns><c>true</c> if data source has changed; otherwise, <c>false</c>.</returns> public static bool DataSourceChanged(this IIndependentAdapterManager instance, DataSet newDataSource) { try { return(!DataSetEqualityComparer.Default.Equals(instance.DataSource, newDataSource)); } catch { // Function is for optimization, reason for failure is irrelevant return(true); } }
/// <summary> /// Validates that an even number of inputs are provided for specified <see cref="IIndependentAdapterManager.PerAdapterInputCount"/>. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> public static void HandleValidateEvenInputCount(this IIndependentAdapterManager instance) { int remainder = instance.InputMeasurementKeys.Length % instance.PerAdapterInputCount; if (remainder == 0) { return; } int adjustedCount = instance.InputMeasurementKeys.Length - remainder; instance.OnStatusMessage(MessageLevel.Warning, $"Uneven number of inputs provided, adjusting total number of inputs to {adjustedCount:N0}. Expected {instance.PerAdapterInputCount:N0} per adapter, received {instance.InputMeasurementKeys.Length:N0} total measurements, leaving {instance.PerAdapterInputCount - remainder:N0} needed."); instance.InputMeasurementKeys = instance.InputMeasurementKeys.Take(adjustedCount).ToArray(); }
/// <summary> /// Enumerates child adapters. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> public static void HandleEnumerateAdapters(this IIndependentAdapterManager instance) { StringBuilder enumeratedAdapters = new StringBuilder(); IAdapter[] adapters = instance.ToArray(); enumeratedAdapters.AppendLine($"{instance.Name} Indexed Adapter Enumeration - {adapters.Length:N0} Total:\r\n"); for (int i = 0; i < adapters.Length; i++) { enumeratedAdapters.AppendLine($"{i.ToString("N0").PadLeft(5)}: {adapters[i].Name}".TrimWithEllipsisMiddle(79)); } instance.OnStatusMessage(MessageLevel.Info, enumeratedAdapters.ToString()); }
/// <summary> /// Lookups up point tag name from provided <see cref="MeasurementKey"/>. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> /// <param name="signalID"><see cref="Guid"/> signal ID to lookup.</param> /// <param name="measurementTable">Measurement table name used for meta-data lookup.</param> /// <returns>Point tag name, if found; otherwise, string representation of provided signal ID.</returns> public static string LookupPointTag(this IIndependentAdapterManager instance, Guid signalID, string measurementTable = "ActiveMeasurements") { DataRow record = instance.DataSource.LookupMetadata(signalID, measurementTable); string pointTag = null; if (record != null) { pointTag = record["PointTag"].ToString(); } if (string.IsNullOrWhiteSpace(pointTag)) { pointTag = signalID.ToString(); } return(pointTag.ToUpper()); }
/// <summary> /// Gets measurement record, creating it if needed. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> /// <param name="currentDeviceID">Device ID associated with current adapter, or zero if none.</param> /// <param name="pointTag">Point tag of measurement.</param> /// <param name="alternateTag">Alternate tag of measurement.</param> /// <param name="signalReference">Signal reference of measurement.</param> /// <param name="description">Description of measurement.</param> /// <param name="signalType">Signal type of measurement.</param> /// <param name="targetHistorianAcronym">Acronym of target historian for measurement.</param> /// <returns>Measurement record.</returns> public static MeasurementRecord GetMeasurementRecord(this IIndependentAdapterManager instance, int currentDeviceID, string pointTag, string alternateTag, string signalReference, string description, SignalType signalType = SignalType.CALC, string targetHistorianAcronym = "PPA") { // Open database connection as defined in configuration file "systemSettings" category using (AdoDataConnection connection = instance.GetConfiguredConnection()) { TableOperations <DeviceRecord> deviceTable = new TableOperations <DeviceRecord>(connection); TableOperations <MeasurementRecord> measurementTable = new TableOperations <MeasurementRecord>(connection); TableOperations <HistorianRecord> historianTable = new TableOperations <HistorianRecord>(connection); TableOperations <SignalTypeRecord> signalTypeTable = new TableOperations <SignalTypeRecord>(connection); // Lookup target device ID int?deviceID = currentDeviceID > 0 ? currentDeviceID : deviceTable.QueryRecordWhere("Acronym = {0}", instance.Name)?.ID; // Lookup target historian ID int?historianID = historianTable.QueryRecordWhere("Acronym = {0}", targetHistorianAcronym)?.ID; // Lookup signal type ID int signalTypeID = signalTypeTable.QueryRecordWhere("Acronym = {0}", signalType.ToString())?.ID ?? 1; // Lookup measurement record by point tag, creating a new record if one does not exist MeasurementRecord measurement = measurementTable.QueryRecordWhere("SignalReference = {0}", signalReference) ?? measurementTable.NewRecord(); // Update record fields measurement.DeviceID = deviceID; measurement.HistorianID = historianID; measurement.PointTag = pointTag; measurement.AlternateTag = alternateTag; measurement.SignalReference = signalReference; measurement.SignalTypeID = signalTypeID; measurement.Description = description; // Save record updates measurementTable.AddNewOrUpdateRecord(measurement); // Re-query new records to get any database assigned information, e.g., unique Guid-based signal ID if (measurement.PointID == 0) { measurement = measurementTable.QueryRecordWhere("SignalReference = {0}", signalReference); } // Notify host system of configuration changes instance.OnConfigurationChanged(); return(measurement); } }
/// <summary> /// Disposes resources used by the <see cref="IIndependentAdapterManager"/> instance. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> public static void HandleDispose(this IIndependentAdapterManager instance) { if (instance.RoutingTables != null) { instance.InputMeasurementKeysUpdated -= Instance_InputMeasurementKeysUpdated; instance.RoutingTables.StatusMessage -= RoutingTables_StatusMessage; instance.RoutingTables.ProcessException -= RoutingTables_ProcessException; instance.RoutingTables.Dispose(); } if (instance.ConfigurationReloadedWaitHandle != null) { instance.ConfigurationReloadedWaitHandle.Set(); instance.ConfigurationReloadedWaitHandle.Dispose(); } }
/// <summary> /// Waits for <paramref name="signalIDs"/> to be loaded in system configuration. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> /// <param name="signalIDs">Signal IDs to wait for.</param> /// <param name="measurementTable">Measurement table name used for meta-data lookup.</param> public static void WaitForSignalsToLoad(this IIndependentAdapterManager instance, Guid[] signalIDs, string measurementTable = "ActiveMeasurements") { int attempts = 0; bool signalExists(Guid signalID) => instance.SignalIDExists(signalID, measurementTable); while (attempts++ < instance.ConfigurationReloadWaitAttempts) { instance.ConfigurationReloadedWaitHandle.Reset(); if (signalIDs.All(signalExists)) { break; } instance.ConfigurationReloadedWaitHandle.Wait(instance.ConfigurationReloadWaitTimeout); } }
/// <summary> /// Handles construction steps for a new <see cref="IIndependentAdapterManager"/> instance. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> public static void HandleConstruct(this IIndependentAdapterManager instance) { bool needsRouting = true; instance.OriginalDataMember = instance.DataMember; instance.DataMember = "[internal]"; instance.Name = $"{instance.GetType().Name} Collection"; // ReSharper disable SuspiciousTypeConversion.Global switch (instance) { case ActionAdapterCollection actionAdapterCollection: instance.RoutingTables = new RoutingTables { ActionAdapters = actionAdapterCollection }; break; case OutputAdapterCollection outputAdapterCollection: instance.RoutingTables = new RoutingTables { OutputAdapters = outputAdapterCollection }; break; case InputAdapterCollection _: needsRouting = false; break; default: throw new ArgumentOutOfRangeException(nameof(instance)); } // ReSharper restore SuspiciousTypeConversion.Global if (needsRouting) { instance.RoutingTables.StatusMessage += RoutingTables_StatusMessage; instance.RoutingTables.ProcessException += RoutingTables_ProcessException; // Make sure routes are recalculated any time measurements are updated instance.InputMeasurementKeysUpdated += Instance_InputMeasurementKeysUpdated; } instance.ConfigurationReloadedWaitHandle = new ManualResetEventSlim(); }
/// <summary> /// Parses connection string. Derived classes should override for custom connection string parsing. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> public static void HandleParseConnectionString(this IIndependentAdapterManager instance) { // Parse all properties marked with ConnectionStringParameterAttribute from provided ConnectionString value ConnectionStringParser parser = new ConnectionStringParser(); parser.ParseConnectionString(instance.ConnectionString, instance); // Parse input measurement keys like class was a typical adapter if (instance.Settings.TryGetValue(nameof(instance.InputMeasurementKeys), out string setting)) { instance.InputMeasurementKeys = AdapterBase.ParseInputMeasurementKeys(instance.DataSource, true, setting, instance.SourceMeasurementTable); } // Parse output measurement keys like class was a typical adapter if (instance.Settings.TryGetValue(nameof(instance.OutputMeasurements), out setting)) { instance.OutputMeasurements = AdapterBase.ParseOutputMeasurements(instance.DataSource, true, setting, instance.SourceMeasurementTable); } }
/// <summary> /// Initializes the <see cref="IndependentAdapterManagerExtensions" />. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> public static void HandleInitialize(this IIndependentAdapterManager instance) { // We don't call base class initialize since it tries to auto-load adapters from the defined // data member - instead, the multi-adapter class implementation manages its own adapters instance.Initialized = false; instance.ParseConnectionString(); if (instance.ConfigurationReloadWaitTimeout < 0) { instance.ConfigurationReloadWaitTimeout = 0; } if (instance.InputMeasurementIndexUsedForName < 0 || instance.InputMeasurementIndexUsedForName > instance.PerAdapterInputCount - 1) { instance.InputMeasurementIndexUsedForName = 0; } instance.Initialized = true; }
/// <summary> /// Lookups up associated phasor label from provided <paramref name="signalID"/>. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> /// <param name="signalID"><see cref="Guid"/> signal ID to lookup.</param> /// <param name="measurementTable">Measurement table name used for meta-data lookup.</param> /// <returns>Phasor label name, if found; otherwise, string representation associated point tag.</returns> public static string LookupPhasorLabel(this IIndependentAdapterManager instance, Guid signalID, string measurementTable = "ActiveMeasurements") { DataRow record = instance.DataSource.LookupMetadata(signalID, measurementTable); int phasorID = 0; if (record != null) { phasorID = record.ConvertNullableField <int>("PhasorID") ?? 0; } if (phasorID == 0) { return(instance.LookupPointTag(signalID, measurementTable)); } using (AdoDataConnection connection = instance.HandleGetConfiguredConnection()) { TableOperations <Phasor> phasorTable = new TableOperations <Phasor>(connection); Phasor phasorRecord = phasorTable.QueryRecordWhere("ID = {0}", phasorID); return(phasorRecord is null?instance.LookupPointTag(signalID, measurementTable) : phasorRecord.Label.Trim().ToUpper()); } }
/// <summary> /// Returns the detailed status for the <see cref="IIndependentAdapterManager"/> instance. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> public static string HandleStatus(this IIndependentAdapterManager instance) { const int MaxMeasurementsToShow = 10; StringBuilder status = new StringBuilder(); status.AppendFormat(" Point Tag Template: {0}", instance.PointTagTemplate); status.AppendLine(); status.AppendFormat(" Signal Reference Template: {0}", instance.SignalReferenceTemplate); status.AppendLine(); status.AppendFormat(" Output Signal Type: {0}", instance.SignalType); status.AppendLine(); status.AppendFormat(" Target Historian Acronym: {0}", instance.TargetHistorianAcronym); status.AppendLine(); status.AppendFormat(" Source Measurement Table: {0}", instance.SourceMeasurementTable); status.AppendLine(); status.AppendFormat(" Inputs per Adapter: {0:N0}", instance.PerAdapterInputCount); status.AppendLine(); status.AppendFormat(" Input Index Used for Name: {0:N0}", instance.InputMeasurementIndexUsedForName); status.AppendLine(); status.AppendFormat(" Output Names per Adapter: {0}", string.Join(", ", instance.PerAdapterOutputNames?.AsEnumerable() ?? new[] { "" })); status.AppendLine(); status.AppendFormat("Re-parse Connection String: {0}", instance.AutoReparseConnectionString); status.AppendLine(); status.AppendFormat(" Original Data Member: {0}", instance.OriginalDataMember); status.AppendLine(); status.AppendFormat(" Config Reload Timeout: {0:N0} ms", instance.ConfigurationReloadWaitAttempts); status.AppendLine(); status.AppendFormat(" Config Reload Attempts: {0:N0}", instance.ConfigurationReloadWaitTimeout); status.AppendLine(); status.AppendFormat("Database Connection String: {0}", string.IsNullOrWhiteSpace(instance.DatabaseConnectionString) ? "Using <systemSettings>" : instance.DatabaseConnectionString); status.AppendLine(); if (!string.IsNullOrWhiteSpace(instance.DatabaseConnectionString)) { status.AppendFormat(" Custom Database Provider: {0}", instance.DatabaseProviderString ?? ""); status.AppendLine(); } if (instance.OutputMeasurements != null && instance.OutputMeasurements.Length > instance.OutputMeasurements.Count(m => m.Key == MeasurementKey.Undefined)) { status.AppendFormat(" Output measurements: {0:N0} defined measurements", instance.OutputMeasurements.Length); status.AppendLine(); status.AppendLine(); for (int i = 0; i < Math.Min(instance.OutputMeasurements.Length, MaxMeasurementsToShow); i++) { status.Append(instance.OutputMeasurements[i].ToString().TruncateRight(40).PadLeft(40)); status.Append(" "); status.AppendLine(instance.OutputMeasurements[i].ID.ToString()); } if (instance.OutputMeasurements.Length > MaxMeasurementsToShow) { status.AppendLine("...".PadLeft(26)); } status.AppendLine(); } if (instance.InputMeasurementKeys != null && instance.InputMeasurementKeys.Length > instance.InputMeasurementKeys.Count(k => k == MeasurementKey.Undefined)) { status.AppendFormat(" Input measurements: {0:N0} defined measurements", instance.InputMeasurementKeys.Length); status.AppendLine(); status.AppendLine(); for (int i = 0; i < Math.Min(instance.InputMeasurementKeys.Length, MaxMeasurementsToShow); i++) { status.AppendLine(instance.InputMeasurementKeys[i].ToString().TruncateRight(25).CenterText(50)); } if (instance.InputMeasurementKeys.Length > MaxMeasurementsToShow) { status.AppendLine("...".CenterText(50)); } status.AppendLine(); } return(status.ToString()); }
/// <summary> /// Returns the detailed status for the <see cref="IIndependentAdapterManager"/> instance. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> public static string HandleStatus(this IIndependentAdapterManager instance) { const int MaxMeasurementsToShow = 10; StringBuilder status = new StringBuilder(); status.AppendLine($" Point Tag Template: {instance.PointTagTemplate}"); status.AppendLine($" Alternate Tag Template: {instance.AlternateTagTemplate}"); status.AppendLine($" Signal Reference Template: {instance.SignalReferenceTemplate}"); status.AppendLine($" Description Template: {instance.DescriptionTemplate}"); status.AppendLine($" Device Acronym Template: {instance.ParentDeviceAcronymTemplate}"); status.AppendLine($" Output Signal Type: {instance.SignalType}"); status.AppendLine($" Target Historian Acronym: {instance.TargetHistorianAcronym}"); status.AppendLine($" Source Measurement Table: {instance.SourceMeasurementTable}"); status.AppendLine($" Inputs per Adapter: {instance.PerAdapterInputCount:N0}"); status.AppendLine($" Input Index Used for Name: {instance.InputMeasurementIndexUsedForName:N0}"); status.AppendLine($" Output Names per Adapter: {instance.PerAdapterOutputNames.Count:N0}"); foreach (string outputName in instance.PerAdapterOutputNames) { status.AppendLine($" \"{outputName.TruncateRight(40)}\""); } status.AppendLine($"Re-parse Connection String: {instance.AutoReparseConnectionString}"); status.AppendLine($" Original Data Member: {instance.OriginalDataMember}"); status.AppendLine($" Config Reload Timeout: {instance.ConfigurationReloadWaitTimeout:N0} ms"); status.AppendLine($" Config Reload Attempts: {instance.ConfigurationReloadWaitAttempts:N0}"); status.AppendLine($"Database Connection String: {(string.IsNullOrWhiteSpace(instance.DatabaseConnectionString) ? "Using <systemSettings>" : instance.DatabaseConnectionString.TruncateRight(40))}"); if (!string.IsNullOrWhiteSpace(instance.DatabaseConnectionString)) { status.AppendLine($" Custom Database Provider: {instance.DatabaseProviderString ?? ""}"); } if (instance.OutputMeasurements != null && instance.OutputMeasurements.Length > instance.OutputMeasurements.Count(m => m.Key == MeasurementKey.Undefined)) { status.AppendLine($" Output measurements: {instance.OutputMeasurements.Length:N0} defined measurements"); status.AppendLine(); for (int i = 0; i < Math.Min(instance.OutputMeasurements.Length, MaxMeasurementsToShow); i++) { status.Append(instance.OutputMeasurements[i].ToString().TruncateRight(40).PadLeft(40)); status.Append(" "); status.AppendLine(instance.OutputMeasurements[i].ID.ToString()); } if (instance.OutputMeasurements.Length > MaxMeasurementsToShow) { status.AppendLine("...".PadLeft(26)); } status.AppendLine(); } if (instance.InputMeasurementKeys != null && instance.InputMeasurementKeys.Length > instance.InputMeasurementKeys.Count(k => k == MeasurementKey.Undefined)) { status.AppendLine($" Input measurements: {instance.InputMeasurementKeys.Length:N0} defined measurements"); status.AppendLine(); for (int i = 0; i < Math.Min(instance.InputMeasurementKeys.Length, MaxMeasurementsToShow); i++) { status.AppendLine(instance.InputMeasurementKeys[i].ToString().TruncateRight(25).CenterText(50)); } if (instance.InputMeasurementKeys.Length > MaxMeasurementsToShow) { status.AppendLine("...".CenterText(50)); } status.AppendLine(); } return(status.ToString()); }
/// <summary> /// Gets configured database connection. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> /// <returns>New ADO data connection based on configured settings.</returns> public static AdoDataConnection HandleGetConfiguredConnection(this IIndependentAdapterManager instance) => string.IsNullOrWhiteSpace(instance.DatabaseConnectionString) ? new AdoDataConnection("systemSettings") : new AdoDataConnection(instance.DatabaseConnectionString, instance.DatabaseProviderString);
/// <summary> /// Gets subscriber information for specified client connection. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> /// <param name="adapterIndex">Enumerated index for child adapter.</param> public static string HandleGetAdapterStatus(this IIndependentAdapterManager instance, int adapterIndex) => instance[adapterIndex].Status;
/// <summary> /// Finds child adapter with specified <paramref name="adapterName"/>. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> /// <param name="adapterName">Adapter name to find.</param> /// <returns><see cref="IAdapter"/> instance with <paramref name="adapterName"/>, if found; otherwise, <c>null</c>.</returns> public static IAdapter FindAdapter(this IIndependentAdapterManager instance, string adapterName) => instance.FirstOrDefault(adapter => adapterName.Equals(adapter.Name));
/// <summary> /// Recalculates routing tables. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> public static void HandleRecalculateRoutingTables(this IIndependentAdapterManager instance) => instance.OnInputMeasurementKeysUpdated(); // Requests route recalculation by IonSession
/// <summary> /// Determines if <paramref name="signalID"/> exists in local configuration. /// </summary> /// <param name="instance">Target <see cref="IIndependentAdapterManager"/> instance.</param> /// <param name="signalID">Signal ID to find.</param> /// <param name="measurementTable">Measurement table name used for meta-data lookup.</param> /// <returns><c>true</c>, if <paramref name="signalID"/> is found; otherwise, <c>false</c>.</returns> public static bool SignalIDExists(this IIndependentAdapterManager instance, Guid signalID, string measurementTable = "ActiveMeasurements") => instance.DataSource.LookupMetadata(signalID, measurementTable) != null;