1274 lines · 64.4 KB · C#
RawDownload
//******************************************************************************************************//  OutputAdapter.cs - Gbtc////  Copyright © 2010, 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://  ----------------------------------------------------------------------------------------------------//  03/14/2017 - J. Ritchie Carroll//       Generated original version of source code.////****************************************************************************************************** using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.IO;using System.Linq;using System.Net;using System.Net.NetworkInformation;using System.Text;using System.Threading;using System.Timers;using GSF;using GSF.Collections;using GSF.Data;using GSF.Diagnostics;using GSF.IO;using GSF.Threading;using GSF.TimeSeries;using GSF.TimeSeries.Adapters;using InStep.eDNA.EzDNAApiNet;using SerializationFormat = GSF.SerializationFormat;using Timer = System.Timers.Timer; namespace eDNAAdapters{    /// <summary>    /// Exports measurements to eDNA if the point tag or alternate tag corresponds to a eDNA point's tag name.    /// </summary>    [Description("eDNA: Archives measurements to an eDNA server")]    public class OutputAdapter : OutputAdapterBase    {        #region [ Members ]         // Nested Types        [Serializable]        private class Point        {            private static readonly RadixCodec Base36 = RadixCodec.Radix36;             /// <summary>            /// Base-36 encoded string of point ID value.            /// </summary>            public readonly string ID;             /// <summary>            /// Point data type, i.e., analog or digital.            /// </summary>            public readonly DataType Type;             public Point(uint id, DataType type)            {                // Encode ID value to base-36 string                ID = Base36.Encode(id);                Type = type;            }             /// <summary>            /// Point ID integer value decoded from base-36 string.            /// </summary>            public uint IDValue => Base36.Decode<uint>(ID);        }         // Constants        private const string DataCacheSuffix = "_eDNAData.cache";        private const string MetaCacheSuffix = "_eDNAMeta.cache";         // Fields                private uint m_dataConnection;                                      // eDNA server connection handle for data        private uint m_metaConnection;                                      // eDNA server connection handle for meta-data        private readonly Timer m_connectionMonitor;                         // eDNA server connection monitor, optional        private readonly ConcurrentDictionary<Guid, Point> m_pointMap;      // Persisted measurement signal ID to point map        private readonly LongSynchronizedOperation m_savePointMapCache;     // Save point map cache operation        private Dictionary<Guid, Ticks> m_lastArchiveTimes;                 // Cache of last point archive times        private DateTime m_lastMetadataRefresh;                             // Tracks time of last meta-data refresh        private long m_processedMeasurements;                               // Total number of measurements processed so far        private volatile bool m_refreshingMetadata;                         // Flag that determines if meta-data is currently refreshing        private double m_metadataRefreshProgress;                           // Current meta-data refresh progress        private Ticks m_lastPointMapFlushTime;                              // Last time of point map file flush        private bool m_disposed;                                            // Flag that determines if class is disposed         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new <see cref="OutputAdapter"/>        /// </summary>        public OutputAdapter()        {            m_dataConnection = uint.MaxValue;            m_metaConnection = uint.MaxValue;            m_connectionMonitor = new Timer            {                Enabled = false,                AutoReset = true            };            m_connectionMonitor.Elapsed += m_connectionMonitor_Elapsed;            m_pointMap = new ConcurrentDictionary<Guid, Point>();            m_savePointMapCache = new LongSynchronizedOperation(SavePointMapCache, ex => OnProcessException(MessageLevel.Warning, ex));            m_savePointMapCache.IsBackground = false;            m_lastMetadataRefresh = DateTime.MinValue;        }         #endregion         #region [ Properties ]         /// <summary>        /// Returns true to indicate that this <see cref="OutputAdapter"/> is sending measurements to a historian, i.e., eDNA.        /// </summary>        public override bool OutputIsForArchive => true;         /// <summary>        /// Returns false to indicate that this <see cref="OutputAdapter"/> will connect synchronously based on eDNA API operation.        /// </summary>        protected override bool UseAsyncConnect => false;         /// <summary>        /// Gets or sets the name of the eDNA primary server for the adapter's connection.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the name of the eDNA primary server for the adapter's connection.")]        public string PrimaryServer { get; set; }         /// <summary>        /// Gets or sets the eDNA primary port for the adapter's connection.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the eDNA primary port for the adapter's connection.")]        [DefaultValue(Default.PrimaryPort)]        public ushort PrimaryPort { get; set; } = Default.PrimaryPort;         /// <summary>        /// Gets or sets the name of the eDNA secondary server for the adapter's connection.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the name of the eDNA secondary server for the adapter's connection.")]        [DefaultValue(Default.SecondaryServer)]        public string SecondaryServer { get; set; } = Default.SecondaryServer;         /// <summary>        /// Gets or sets the eDNA secondary port for the adapter's connection.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the eDNA secondary port for the adapter's connection.")]        [DefaultValue(Default.SecondaryPort)]        public ushort SecondaryPort { get; set; } = Default.SecondaryPort;         /// <summary>        /// Gets or sets the eDNA site for the adapter's connection.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the eDNA site for the adapter's connection.")]        public string Site { get; set; }         /// <summary>        /// Gets or sets the eDNA service for the adapter's connection.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the eDNA service for the adapter's connection.")]        public string Service { get; set; }         /// <summary>        /// Gets or sets flag that determines if eDNA client API should acknowledge all process errors or protocol errors.        /// </summary>        [ConnectionStringParameter]        [Description("Defines flag that determines if eDNA client API should acknowledge all process errors or protocol errors.")]        [DefaultValue(Default.AcknowledgeDataPackets)]        public bool AcknowledgeDataPackets { get; set; } = Default.AcknowledgeDataPackets;         /// <summary>        /// Gets or sets flag that determines if eDNA client API should enable its memory queue.        /// </summary>        [ConnectionStringParameter]        [Description("Defines flag that determines if eDNA client API should enable its memory queue.")]        [DefaultValue(Default.EnableQueuing)]        public bool EnableQueuing { get; set; } = Default.EnableQueuing;         /// <summary>        /// Gets or sets flag that determines if eDNA client API should enable use of a local file system cache.        /// </summary>        [ConnectionStringParameter]        [Description("Defines flag that determines if eDNA client API should enable use of a local file system cache.")]        [DefaultValue(Default.EnableCaching)]        public bool EnableCaching { get; set; } = Default.EnableCaching;         /// <summary>        /// Gets or sets the filename to be used for point map cache.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the path and filename to be used for the eDNA client API cache. Leave blank for cache name to target host working directory and be same as adapter name.")]        [DefaultValue(Default.LocalCacheFileName)]        public string LocalCacheFileName { get; set; } = Default.LocalCacheFileName;         /// <summary>        /// Gets or sets flag that determines if eDNA client API cache should be cleared on startup.        /// </summary>        [ConnectionStringParameter]        [Description("Defines flag that determines if eDNA client API cache should be cleared on startup.")]        [DefaultValue(Default.ClearCacheOnStartup)]        public bool ClearCacheOnStartup { get; set; } = Default.ClearCacheOnStartup;         /// <summary>        /// Gets or sets whether or not this adapter should automatically manage meta-data for eDNA points.        /// </summary>        [ConnectionStringParameter]        [Description("Determines if this adapter should automatically manage meta-data for eDNA points (recommended).")]        [DefaultValue(Default.RunMetadataSync)]        public bool RunMetadataSync { get; set; } = Default.RunMetadataSync;         /// <summary>        /// Gets or sets whether or not this adapter should automatically manage meta-data for eDNA points.        /// </summary>        [ConnectionStringParameter]        [Description("Determines if this adapter should automatically create new tags when managing meta-data for eDNA points (recommended). Value will only be considered when RunMetadataSync is True.")]        [DefaultValue(Default.AutoCreateTags)]        public bool AutoCreateTags { get; set; } = Default.AutoCreateTags;         /// <summary>        /// Gets or sets whether or not this adapter should automatically manage meta-data for eDNA points.        /// </summary>        [ConnectionStringParameter]        [Description("Determines if this adapter should automatically update existing tags when managing meta-data for eDNA points (recommended). This will make the time-series library host application the master for maintaining eDNA tag meta-data (like the tag name); otherwise, when False, eDNA will be the master. Value will only be considered when RunMetadataSync is True.")]        [DefaultValue(Default.AutoUpdateTags)]        public bool AutoUpdateTags { get; set; } = Default.AutoUpdateTags;         /// <summary>        /// Gets or sets the number of tag name prefixes, e.g., "SOURCE!", applied by subscriptions to remove from eDNA tag names.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the number of tag name prefixes applied by subscriptions, e.g., \"SOURCE!\", to remove from eDNA tag names. Value will only be considered when RunMetadataSync is True.")]        [DefaultValue(Default.TagNamePrefixRemoveCount)]        public int TagNamePrefixRemoveCount { get; set; } = Default.TagNamePrefixRemoveCount;         /// <summary>        /// Gets or sets the filename to be used for point map cache.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the path and filename to be used for the point map cache file name. Leave blank for cache name to target host working directory and to be same as adapter name with a \"_PointMap.cache\" extension.")]        [DefaultValue(Default.PointMapCacheFileName)]        public string PointMapCacheFileName { get; set; } = Default.PointMapCacheFileName;         /// <summary>        /// Gets or sets the default string to represent the digital set state.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the default string to represent the digital set state.")]        [DefaultValue(Default.DigitalSetString)]        public string DigitalSetString { get; set; } = Default.DigitalSetString;         /// <summary>        /// Gets or sets the default string to represent the digital cleared state.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the default string to represent the digital cleared state.")]        [DefaultValue(Default.DigitalClearedString)]        public string DigitalClearedString { get; set; } = Default.DigitalClearedString;         /// <summary>        /// Gets or sets flag that determines if eDNA client API INI configuration file should be validated to exist before starting adapter. When set to True, adapter will fail to start when INI file does not exist.        /// </summary>        [ConnectionStringParameter]        [Description("Defines flag that determines if eDNA client API INI configuration file should be validated to exist before starting adapter. When set to True, adapter will fail to start when INI file does not exist.")]        [DefaultValue(Default.ValidateINIFileExists)]        public bool ValidateINIFileExists { get; set; } = Default.ValidateINIFileExists;         /// <summary>        /// Gets or sets the maximum time resolution, in seconds, for data points being archived, e.g., a value 1.0 would mean that data would be archived no more than once per second. A zero value indicates that all data should be archived.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the maximum time resolution, in seconds, for data points being archived, e.g., a value 1.0 would mean that data would be archived no more than once per second. A zero value indicates that all data should be archived.")]        [DefaultValue(Default.MaximumPointResolution)]        public double MaximumPointResolution { get; set; } = Default.MaximumPointResolution;         /// <summary>        /// Gets or sets the interval, in milliseconds, to monitor eDNA connection. A zero value will disable the monitor.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the interval, in milliseconds, to monitor an eDNA connection. A zero value will disable the monitor.")]        [DefaultValue(Default.ConnectionMonitoringInterval)]        public int ConnectionMonitoringInterval { get; set; } = Default.ConnectionMonitoringInterval;         /// <summary>        /// Gets or sets the timeout, in milliseconds, for writing data to eDNA connection. A value of -1 will wait indefinitely.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the timeout, in milliseconds, for writing data to eDNA connection. A value of -1 will wait indefinitely.")]        [DefaultValue(Default.WriteTimeout)]        public int WriteTimeout { get; set; } = Default.WriteTimeout;         /// <summary>        /// Gets or sets flag that determines if each bit of digital words are expanded to individual points. Set to False to treat words as analog values.        /// </summary>        [ConnectionStringParameter]        [Description("Defines flag that determines if each bit of digital words are expanded to individual points. Set to False to treat words as analog values.")]        [DefaultValue(Default.ExpandDigitalWordBits)]        public bool ExpandDigitalWordBits { get; set; } = Default.ExpandDigitalWordBits;         /// <summary>        /// Gets the detailed status of the eDNA output adapter.        /// </summary>        public override string Status        {            get            {                StringBuilder status = new StringBuilder();                 status.Append(base.Status);                status.AppendLine();                status.AppendFormat("       eDNA primary server: {0}:{1}", PrimaryServer ?? "[undefined]", PrimaryPort);                status.AppendLine();                 if (!string.IsNullOrWhiteSpace(SecondaryServer))                {                    status.AppendFormat("     eDNA secondary server: {0}:{1}", SecondaryServer, SecondaryPort);                    status.AppendLine();                }                 status.AppendFormat("                 eDNA site: {0}", Site);                status.AppendLine();                status.AppendFormat("              eDNA service: {0}", Service);                status.AppendLine();                status.AppendFormat("       Connected to server: {0}", m_dataConnection == uint.MaxValue ? "No" : $"Yes, handle = {m_dataConnection}");                status.AppendLine();                status.AppendFormat("       Monitoring interval: {0:N0}ms{1}", ConnectionMonitoringInterval, ConnectionMonitoringInterval <= 0 ? " - monitoring disabled" : "");                status.AppendLine();                status.AppendFormat("             Write timeout: {0}", WriteTimeout == Timeout.Infinite ? "No timeout defined" : $"{WriteTimeout:N0}ms");                status.AppendLine();                status.AppendFormat(" Expanding 16-bit digitals: {0}", ExpandDigitalWordBits);                status.AppendLine();                status.AppendFormat("  Maximum point resolution: {0:N3} seconds{1}", MaximumPointResolution, MaximumPointResolution <= 0.0D ? " - all data will be archived" : "");                status.AppendLine();                status.AppendFormat("Validating INI file exists: {0}", ValidateINIFileExists);                status.AppendLine();                status.AppendFormat("Acknowledging data packets: {0}", AcknowledgeDataPackets);                status.AppendLine();                status.AppendFormat("    Memory queuing enabled: {0}", EnableQueuing);                status.AppendLine();                status.AppendFormat("       Local cache enabled: {0}", EnableCaching);                status.AppendLine();                 if (EnableCaching)                {                    status.AppendFormat(" Clearing cache on startup: {0}", ClearCacheOnStartup);                    status.AppendLine();                    status.AppendFormat("          Local cache file: {0}", FilePath.TrimFileName($"{LocalCacheFileName}{DataCacheSuffix}", 51));                    status.AppendLine();                }                 status.AppendFormat("    Meta-data sync enabled: {0}", RunMetadataSync);                status.AppendLine();                 if (RunMetadataSync)                {                    status.AppendFormat("          Auto-create tags: {0}", AutoCreateTags);                    status.AppendLine();                    status.AppendFormat("          Auto-update tags: {0}", AutoUpdateTags);                    status.AppendLine();                    status.AppendFormat("    Tag prefixes to remove: {0:N0}", TagNamePrefixRemoveCount);                    status.AppendLine();                }                 status.AppendFormat("      Point map cache file: {0}", FilePath.TrimFileName(PointMapCacheFileName.ToNonNullString("[undefined]"), 51));                status.AppendLine();                status.AppendFormat("      Point map cache size: {0:N0} mappings", m_pointMap.Count);                status.AppendLine();                 if (RunMetadataSync)                {                    status.AppendFormat("      Meta-data connection: {0}", m_metaConnection == uint.MaxValue ? "Not connected" : $"Established, handle = {m_metaConnection}");                    status.AppendLine();                    status.AppendFormat("    Meta-data sync process: {0}, {1:0.00%} complete", m_refreshingMetadata ? "Active" : "Idle", m_metadataRefreshProgress);                    status.AppendLine();                }                 status.AppendFormat("           Points archived: {0:N0} - {1:#,##0}/second", Interlocked.Read(ref m_processedMeasurements), Interlocked.Read(ref m_processedMeasurements) / RunTime);                status.AppendLine();                 return status.ToString();            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Releases the unmanaged resources used by the <see cref="OutputAdapter"/> 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)            {                try                {                    if (disposing)                    {                        m_connectionMonitor.Elapsed -= m_connectionMonitor_Elapsed;                        m_connectionMonitor.Dispose();                    }                }                finally                {                    m_disposed = true;          // Prevent duplicate dispose.                    base.Dispose(disposing);    // Call base class Dispose().                }            }        }         /// <summary>        /// Returns a brief status of this <see cref="OutputAdapter"/>        /// </summary>        /// <param name="maxLength">Maximum number of characters in the status string</param>        /// <returns>Status</returns>        public override string GetShortStatus(int maxLength)        {            return $"Archived {m_processedMeasurements:N0} measurements to eDNA.".CenterText(maxLength);        }         /// <summary>        /// Initializes this <see cref="OutputAdapter"/>.        /// </summary>        public override void Initialize()        {            base.Initialize();             Dictionary<string, string> settings = Settings;            string setting;            ushort ushortVal;            int intVal;            double doubleVal;             if (settings.TryGetValue(nameof(PrimaryServer), out setting) && !string.IsNullOrWhiteSpace(setting))                PrimaryServer = setting;            else                throw new ArgumentException($"Cannot initialize adapter: required connection string parameter \"{nameof(PrimaryServer)}\" is not defined.");             if (settings.TryGetValue(nameof(PrimaryPort), out setting) && ushort.TryParse(setting, out ushortVal))                PrimaryPort = ushortVal;             if (settings.TryGetValue(nameof(SecondaryServer), out setting) && !string.IsNullOrWhiteSpace(setting))                SecondaryServer = setting;             if (settings.TryGetValue(nameof(SecondaryPort), out setting) && ushort.TryParse(setting, out ushortVal))                SecondaryPort = ushortVal;             if (settings.TryGetValue(nameof(Site), out setting) && !string.IsNullOrWhiteSpace(setting))                Site = setting;            else                throw new ArgumentException($"Cannot initialize adapter: required connection string parameter \"{nameof(Site)}\" is not defined.");             if (settings.TryGetValue(nameof(Service), out setting) && !string.IsNullOrWhiteSpace(setting))                Service = setting;            else                throw new ArgumentException($"Cannot initialize adapter: required connection string parameter \"{nameof(Service)}\" is not defined.");             if (settings.TryGetValue(nameof(AcknowledgeDataPackets), out setting) && !string.IsNullOrWhiteSpace(setting))                AcknowledgeDataPackets = setting.ParseBoolean();             if (settings.TryGetValue(nameof(EnableQueuing), out setting) && !string.IsNullOrWhiteSpace(setting))                EnableQueuing = setting.ParseBoolean();             if (settings.TryGetValue(nameof(EnableCaching), out setting) && !string.IsNullOrWhiteSpace(setting))                EnableCaching = setting.ParseBoolean();             if (!settings.TryGetValue(nameof(LocalCacheFileName), out setting) || string.IsNullOrWhiteSpace(setting))                setting = Name;             LocalCacheFileName = FilePath.GetAbsolutePath(setting);             if (settings.TryGetValue(nameof(ClearCacheOnStartup), out setting) && !string.IsNullOrWhiteSpace(setting))                ClearCacheOnStartup = setting.ParseBoolean();             if (settings.TryGetValue(nameof(RunMetadataSync), out setting) && !string.IsNullOrWhiteSpace(setting))                RunMetadataSync = setting.ParseBoolean();

Lines 1–500 of 1274.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t