Ejemplo n.º 1
0
        /// <summary>
        /// Publish <see cref="IFrame"/> of time-aligned collection of <see cref="IMeasurement"/> values that arrived within the
        /// concentrator's defined <see cref="ConcentratorBase.LagTime"/>.
        /// </summary>
        /// <param name="frame"><see cref="IFrame"/> of measurements with the same timestamp that arrived within <see cref="ConcentratorBase.LagTime"/> that are ready for processing.</param>
        /// <param name="index">Index of <see cref="IFrame"/> within a second ranging from zero to <c><see cref="ConcentratorBase.FramesPerSecond"/> - 1</c>.</param>
        protected override void PublishFrame(IFrame frame, int index)
        {
            IMeasurement bestMeasurement = GetBestMeasurement(frame);

            if ((object)bestMeasurement == null)
            {
                return;
            }

            IMeasurement[] newMeasurements = OutputMeasurements
                                             .Select(measurement => Measurement.Clone(measurement))
                                             .ToArray();

            newMeasurements[0].Timestamp  = frame.Timestamp;
            newMeasurements[0].StateFlags = bestMeasurement.StateFlags;
            newMeasurements[0].Value      = bestMeasurement.Value;

            if (newMeasurements.Length > 1)
            {
                newMeasurements[1].Timestamp  = frame.Timestamp;
                newMeasurements[1].StateFlags = bestMeasurement.StateFlags;
                newMeasurements[1].Value      = Array.IndexOf(InputMeasurementKeys, bestMeasurement.Key) + 1;
            }

            OnNewMeasurements(newMeasurements);

            m_lastSelectedMeasurement = bestMeasurement.Key;
        }
        /// <summary>
        /// Initializes this <see cref="HadoopDataLoader"/>.
        /// </summary>
        public override void Initialize()
        {
            Dictionary <string, string> settings = Settings;

            // Handle misspelled property so previously configured adapters will still apply proper value
            if (settings.TryGetValue("UpdateIntervall", out string setting) && int.TryParse(setting, out _))
            {
                settings["UpdateInterval"] = setting;
            }

            new ConnectionStringParser <ConnectionStringParameterAttribute>().ParseConnectionString(ConnectionString, this);

            base.Initialize();

            //Generate Query
            m_query = $"SELECT {ValueField} AS V, {TimeStampField} AS T";

            if (!string.IsNullOrEmpty(SubSecondField))
            {
                m_query = m_query + $", {SubSecondField} AS Ticks";
            }

            m_query = m_query + $" FROM {TableName} WHERE {TimeStampField} > '{{0}}' AND {TagQuery}";

            if (!string.IsNullOrEmpty(OrderField))
            {
                m_query = $" ORDER BY {OrderField}";
            }


            //Create Mapping Dictionary
            List <string> pointTags = OutputMeasurements.Select(item => item.Metadata.TagName).ToList();

            m_queryParameter = new Dictionary <Guid, List <string> >();

            // Read Mapping File
            using (StreamReader reader = new StreamReader(FilePath.GetAbsolutePath(MappingFile)))
            {
                string line;

                while ((line = reader.ReadLine()) != null)
                {
                    List <string> entries  = line.Split(',').Select(item => item.Trim()).ToList();
                    string        pointTag = entries[0];

                    int index = pointTags.FindIndex(item => item == pointTag);
                    if (index > -1)
                    {
                        m_queryParameter.Add(OutputMeasurements[index].Key.SignalID, entries.Skip(1).ToList());
                    }
                }
            }

            //start Timer
            m_timer           = new Timer();
            m_timer.Interval  = UpdateInterval;
            m_timer.AutoReset = true;
            m_timer.Elapsed  += m_timer_Elapsed;
        }
Ejemplo n.º 3
0
        public override void Initialize()
        {
            ProcessingInterval = 0;

            OnStatusMessage(MessageLevel.Info, $"Initialising OPC for device {Name}");
            base.Initialize();

            // If more data is added to the connection string the it will have to be parsed here (Look at ModbusPoller.cs for example line 456: https://github.com/GridProtectionAlliance/gsf/blob/master/Source/Libraries/Adapters/ModbusAdapters/ModbusPoller.cs)

            // Statistics
            StatisticsEngine.Register(this, "OPCUA", "OPC");

            OutputMeasurements = ParseOutputMeasurements(DataSource, false, $"FILTER ActiveMeasurements WHERE Device = '{Name}'");
            DataTable measurements = DataSource.Tables["ActiveMeasurements"];

            items = OutputMeasurements.ToDictionary(measurement =>
            {
                DataRow[] records = measurements.Select($"ID = '{measurement.Key}'");
                var reference     = records[0]["SignalReference"].ToNonNullString();
                OnStatusMessage(MessageLevel.Info, $"Linking OPC item {reference} with tag {measurement.TagName}");
                return(reference);
            });


            // OPC Init
            ApplicationInstance application = new ApplicationInstance
            {
                ApplicationName   = "OPC UA Client",
                ApplicationType   = Opc.Ua.ApplicationType.Client,
                ConfigSectionName = "Opc.Ua.Client"
            };
            ApplicationConfiguration config = application.LoadApplicationConfiguration(false).GetAwaiter().GetResult();
            bool haveAppCertificate         = application.CheckApplicationInstanceCertificate(false, 0).GetAwaiter().GetResult();

            if (!haveAppCertificate)
            {
                throw new Exception("Application instance certificate invalid!");
            }

            if (haveAppCertificate)
            {
                config.ApplicationUri = Utils.GetApplicationUriFromCertificate(config.SecurityConfiguration.ApplicationCertificate.Certificate);
                if (config.SecurityConfiguration.AutoAcceptUntrustedCertificates)
                {
                    autoAccept = true;
                }
                config.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_CertificateValidation);
            }
            else
            {
                OnStatusMessage(MessageLevel.Info, $"Missing application certificate, using unsecure connection.");
            }

            m_configuration = config;
            OnStatusMessage(MessageLevel.Info, $"OPC Initialised ({ConnectionString})");
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Connects to the configured PI server.
        /// </summary>
        protected override void AttemptConnection()
        {
            m_connection = new PIConnection
            {
                ServerName     = this.ServerName,
                UserName       = this.UserName,
                Password       = this.Password,
                ConnectTimeout = this.ConnectTimeout
            };

            m_connection.Disconnected += m_connection_Disconnected;
            m_connection.Open();

            m_dataPipe = new PIDataPipe(AFDataPipeType.Snapshot);
            //m_dataPipe.Subscribe(m_dataUpdateObserver);

            if (AutoStart && (object)OutputMeasurements != null && OutputMeasurements.Any())
            {
                SubscribeToPointUpdates(this.OutputMeasurementKeys());
            }
        }
Ejemplo n.º 5
0
        private void Timer_Elapsed(object sender, ElapsedEventArgs elapsedEventArgs)
        {
            long   now             = DateTime.UtcNow.Ticks;
            long   nextPublication = GetNextPublicationTime(m_lastPublication);
            double delta;

            while (nextPublication < now)
            {
                delta = Ticks.ToSeconds(nextPublication - m_lastPublication);

                for (int i = 0; i < OutputMeasurements.Length; i++)
                {
                    Advance(i, delta);
                }

                OnNewMeasurements(OutputMeasurements
                                  .Select((measurement, index) => Measurement.Clone(measurement, GetWrappedValue(index), m_includeTime ? nextPublication : 0L))
                                  .ToList <IMeasurement>());

                m_lastPublication = nextPublication;
                nextPublication   = GetNextPublicationTime(m_lastPublication);
            }
        }
Ejemplo n.º 6
0
        private void PollingOperation()
        {
            if ((object)m_modbusConnection == null)
            {
                return;
            }

            try
            {
                Ticks timestamp = DateTime.UtcNow.Ticks;
                Dictionary <MeasurementKey, Measurement> measurements = OutputMeasurements.Select(measurement => Measurement.Clone(measurement, timestamp)).ToDictionary(measurement => measurement.Key, measurement => measurement);
                Group[] groups = m_sequences.SelectMany(sequence => sequence.Groups).ToArray();
                int     measurementsReceived = 0;

                int overallTasksCompleted = 0;
                int overallTasksCount     = m_sequences.Count + 1;
                OnProgressUpdated(this, new ProgressUpdate(ProgressState.Processing, true, "Starting polling operation...", overallTasksCompleted, overallTasksCount));
                OnStatusMessage(MessageLevel.Info, $"Executing poll operation {m_pollOperations + 1}.");

                // Handle read/write operations for sequence groups
                try
                {
                    foreach (Sequence sequence in m_sequences)
                    {
                        foreach (Group group in sequence.Groups)
                        {
                            OnProgressUpdated(this, new ProgressUpdate(ProgressState.Processing, false, $"Executing poll for {sequence.Type.ToString().ToLowerInvariant()} sequence group \"{group.Type}@{group.StartAddress}\"...", 0, group.PointCount));

                            switch (group.Type)
                            {
                            case RecordType.DI:
                                group.DataValues = ReadDiscreteInputs(group.StartAddress, group.PointCount);
                                break;

                            case RecordType.CO:
                                if (sequence.Type == SequenceType.Read)
                                {
                                    group.DataValues = ReadCoils(group.StartAddress, group.PointCount);
                                }
                                else
                                {
                                    WriteCoils(group.StartAddress, group.DataValues);
                                }
                                break;

                            case RecordType.IR:
                                group.DataValues = ReadInputRegisters(group.StartAddress, group.PointCount);
                                break;

                            case RecordType.HR:
                                if (sequence.Type == SequenceType.Read)
                                {
                                    group.DataValues = ReadHoldingRegisters(group.StartAddress, group.PointCount);
                                }
                                else
                                {
                                    WriteHoldingRegisters(group.StartAddress, group.DataValues);
                                }
                                break;
                            }

                            OnProgressUpdated(this, new ProgressUpdate(ProgressState.Processing, false, $"Completed poll for {sequence.Type.ToString().ToLowerInvariant()} sequence group \"{group.Type}@{group.StartAddress}\".", group.PointCount, group.PointCount));
                            Thread.Sleep(m_interSequenceGroupPollDelay);
                        }

                        OnProgressUpdated(this, new ProgressUpdate(ProgressState.Processing, true, null, ++overallTasksCompleted, overallTasksCount));
                    }
                }
                catch
                {
                    m_deviceErrors++;
                    throw;
                }

                OnProgressUpdated(this, new ProgressUpdate(ProgressState.Processing, false, "Processing derived values...", 0, m_derivedValues.Count));

                // Calculate derived values
                foreach (KeyValuePair <MeasurementKey, DerivedValue> item in m_derivedValues)
                {
                    DerivedValue derivedValue = item.Value;
                    ushort[]     dataValues   = derivedValue.GetDataValues(groups);
                    Measurement  measurement;

                    if (measurements.TryGetValue(item.Key, out measurement))
                    {
                        // TODO: Properly interpret measurement types after GSF data type transport update
                        switch (derivedValue.Type)
                        {
                        case DerivedType.String:
                            if (derivedValue.AddressRecords.Count > 0)
                            {
                                m_derivedStrings[item.Key] = DeriveString(dataValues);
                                measurementsReceived++;
                            }
                            else
                            {
                                OnStatusMessage(MessageLevel.Warning, $"No address records defined for derived String value \"{item.Key}\".");
                            }
                            break;

                        case DerivedType.Single:
                            if (derivedValue.AddressRecords.Count > 1)
                            {
                                measurement.Value = DeriveSingle(dataValues[0], dataValues[1]);
                                measurementsReceived++;
                            }
                            else
                            {
                                OnStatusMessage(MessageLevel.Warning, $"{derivedValue.AddressRecords.Count} address records defined for derived Single value \"{item.Key}\", expected 2.");
                            }
                            break;

                        case DerivedType.Double:
                            if (derivedValue.AddressRecords.Count > 3)
                            {
                                measurement.Value = DeriveDouble(dataValues[0], dataValues[1], dataValues[2], dataValues[3]);
                                measurementsReceived++;
                            }
                            else
                            {
                                OnStatusMessage(MessageLevel.Warning, $"{derivedValue.AddressRecords.Count} address records defined for derived Double value \"{item.Key}\", expected 4.");
                            }
                            break;

                        case DerivedType.UInt16:
                            if (derivedValue.AddressRecords.Count > 0)
                            {
                                measurement.Value = dataValues[0];
                                measurementsReceived++;
                            }
                            else
                            {
                                OnStatusMessage(MessageLevel.Warning, $"No address records defined for UInt16 value \"{item.Key}\".");
                            }
                            break;

                        case DerivedType.Int32:
                            if (derivedValue.AddressRecords.Count > 1)
                            {
                                measurement.Value = DeriveInt32(dataValues[0], dataValues[1]);
                                measurementsReceived++;
                            }
                            else
                            {
                                OnStatusMessage(MessageLevel.Warning, $"{derivedValue.AddressRecords.Count} address records defined for derived Int32 value \"{item.Key}\", expected 2.");
                            }
                            break;

                        case DerivedType.UInt32:
                            if (derivedValue.AddressRecords.Count > 1)
                            {
                                measurement.Value = DeriveUInt32(dataValues[0], dataValues[1]);
                                measurementsReceived++;
                            }
                            else
                            {
                                OnStatusMessage(MessageLevel.Warning, $"{derivedValue.AddressRecords.Count} address records defined for derived UInt32 value \"{item.Key}\", expected 2.");
                            }
                            break;

                        case DerivedType.Int64:
                            if (derivedValue.AddressRecords.Count > 3)
                            {
                                measurement.Value = DeriveInt64(dataValues[0], dataValues[1], dataValues[2], dataValues[3]);
                                measurementsReceived++;
                            }
                            else
                            {
                                OnStatusMessage(MessageLevel.Warning, $"{derivedValue.AddressRecords.Count} address records defined for derived Int64 value \"{item.Key}\", expected 4.");
                            }
                            break;

                        case DerivedType.UInt64:
                            if (derivedValue.AddressRecords.Count > 3)
                            {
                                measurement.Value = DeriveUInt64(dataValues[0], dataValues[1], dataValues[2], dataValues[3]);
                                measurementsReceived++;
                            }
                            else
                            {
                                OnStatusMessage(MessageLevel.Warning, $"{derivedValue.AddressRecords.Count} address records defined for derived UInt64 value \"{item.Key}\", expected 4.");
                            }
                            break;
                        }
                    }

                    if (measurementsReceived % 20 == 0)
                    {
                        OnProgressUpdated(this, new ProgressUpdate(ProgressState.Processing, false, null, measurementsReceived, m_derivedValues.Count));
                    }
                }

                OnProgressUpdated(this, new ProgressUpdate(ProgressState.Processing, false, "Completed derived value processing.", m_derivedValues.Count, m_derivedValues.Count));
                OnProgressUpdated(this, new ProgressUpdate(ProgressState.Succeeded, true, "Polling operation complete.", overallTasksCount, overallTasksCount));

                OnNewMeasurements(measurements.Values.ToArray());

                m_measurementsReceived += measurementsReceived;
                m_pollOperations++;
            }
            catch (Exception ex)
            {
                OnProgressUpdated(this, new ProgressUpdate(ProgressState.Failed, true, $"Failed during poll operation: {ex.Message}", 0, 1));

                // Restart connection cycle when an exception occurs
                Start();
                throw;
            }
            finally
            {
                m_measurementsExpected += OutputMeasurements.Length;
            }
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Initializes <see cref="ModbusPoller" />.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            ConnectionStringParser <ConnectionStringParameterAttribute> parser = new ConnectionStringParser <ConnectionStringParameterAttribute>();

            parser.ParseConnectionString(ConnectionString, this);

            // Register downloader with the statistics engine
            StatisticsEngine.Register(this, "Modbus", "MOD");
            StatisticsEngine.Register(m_deviceProxy, Name, "Device", "PMU");

            // Attach to output measurements for Modbus device
            OutputMeasurements = ParseOutputMeasurements(DataSource, false, $"FILTER ActiveMeasurements WHERE Device = '{Name}'");

            // Parse derived value expressions from defined signal reference fields
            m_derivedValues = OutputMeasurements.Select(measurement => measurement.Key).ToDictionary(key => key, key =>
            {
                DataTable measurements    = DataSource.Tables["ActiveMeasurements"];
                DerivedValue derivedValue = null;
                DataRow[] records         = measurements.Select($"ID = '{key}'");

                if (records.Length > 0)
                {
                    derivedValue = ParseDerivedValue(records[0]["SignalReference"].ToNonNullString());
                }

                return(derivedValue);
            });

            m_sequences = new List <Sequence>();

            Dictionary <string, string> settings = Settings;
            string setting;
            int    sequenceCount = 0;

            if (settings.TryGetValue("sequenceCount", out setting))
            {
                int.TryParse(setting, out sequenceCount);
            }

            for (int i = 0; i < sequenceCount; i++)
            {
                if (settings.TryGetValue($"sequence{i}", out setting))
                {
                    Dictionary <string, string> sequenceSettings = setting.ParseKeyValuePairs();
                    SequenceType sequenceType = SequenceType.Read;

                    if (sequenceSettings.TryGetValue("sequenceType", out setting))
                    {
                        Enum.TryParse(setting, true, out sequenceType);
                    }

                    Sequence sequence = new Sequence(sequenceType);
                    int      groupCount;

                    if (sequenceSettings.TryGetValue("groupCount", out setting) && int.TryParse(setting, out groupCount))
                    {
                        for (int j = 0; j < groupCount; j++)
                        {
                            Group group = new Group();

                            if (sequenceSettings.TryGetValue($"groupType{j}", out setting))
                            {
                                Enum.TryParse(setting, true, out group.Type);
                            }

                            if (sequenceSettings.TryGetValue($"groupStartAddress{j}", out setting))
                            {
                                ushort.TryParse(setting, out group.StartAddress);
                            }

                            if (sequenceSettings.TryGetValue($"groupPointCount{j}", out setting))
                            {
                                ushort.TryParse(setting, out group.PointCount);
                            }

                            if (group.StartAddress > 0 && group.PointCount > 0)
                            {
                                // Load any defined write sequence values
                                if (sequence.Type == SequenceType.Write)
                                {
                                    group.DataValues = new ushort[group.PointCount];

                                    for (int k = 0; k < group.PointCount; k++)
                                    {
                                        if (sequenceSettings.TryGetValue($"group{j}DataValue{k}", out setting))
                                        {
                                            ushort.TryParse(setting, out group.DataValues[k]);
                                        }
                                    }
                                }

                                sequence.Groups.Add(group);
                            }
                        }
                    }

                    if (sequence.Groups.Count > 0)
                    {
                        m_sequences.Add(sequence);
                    }
                }
            }

            if (m_sequences.Count == 0)
            {
                throw new InvalidOperationException("No sequences defined, cannot start Modbus polling.");
            }

            // Define synchronized polling operation
            m_pollingOperation = new ShortSynchronizedOperation(PollingOperation, exception => OnProcessException(MessageLevel.Warning, exception));

            // Define polling timer
            m_pollingTimer           = new Timer(m_pollingRate);
            m_pollingTimer.AutoReset = true;
            m_pollingTimer.Elapsed  += m_pollingTimer_Elapsed;
        }
Ejemplo n.º 8
0
        // Kick start read process for historian
        private void StartDataReader(object state)
        {
            MeasurementKey[] requestedKeys = SupportsTemporalProcessing ? RequestedOutputMeasurementKeys : OutputMeasurements.MeasurementKeys().ToArray();

            if (Enabled && (object)m_archiveReader != null && (object)requestedKeys != null && requestedKeys.Length > 0)
            {
                m_historianIDs    = requestedKeys.Select(key => unchecked ((int)key.ID)).ToArray();
                m_publicationTime = 0;

                // Start data read from historian
                lock (m_readTimer)
                {
                    m_startTime = base.StartTimeConstraint <TimeTag.MinValue?TimeTag.MinValue : base.StartTimeConstraint> TimeTag.MaxValue ? TimeTag.MaxValue : new TimeTag(base.StartTimeConstraint);
                    m_stopTime  = base.StopTimeConstraint <TimeTag.MinValue?TimeTag.MinValue : base.StopTimeConstraint> TimeTag.MaxValue ? TimeTag.MaxValue : new TimeTag(base.StopTimeConstraint);

                    m_dataReader        = m_archiveReader.ReadData(m_historianIDs, m_startTime, m_stopTime).GetEnumerator();
                    m_readTimer.Enabled = m_dataReader.MoveNext();

                    if (m_readTimer.Enabled)
                    {
                        OnStatusMessage(MessageLevel.Info, "Starting historical data read...");
                    }
                    else
                    {
                        OnStatusMessage(MessageLevel.Info, "No historical data was available to read for given timeframe.");
                        OnProcessingComplete();
                    }
                }
            }
            else
            {
                m_readTimer.Enabled = false;
                OnStatusMessage(MessageLevel.Info, "No measurement keys have been requested for reading, historian reader is idle.");
                OnProcessingComplete();
            }
        }
Ejemplo n.º 9
0
        /// <summary>
        /// Initializes <see cref="TimeSeriesProducer"/>.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            Dictionary <string, string> settings = Settings;
            string setting;
            int    intValue;
            double doubleValue;

            // Parse required settings
            if (!settings.TryGetValue(nameof(Servers), out setting) || string.IsNullOrWhiteSpace(setting))
            {
                throw new ArgumentException($"Required \"{nameof(Servers)}\" setting is missing.");
            }

            Servers   = setting.Trim();
            m_servers = Servers.Split(',').Select(uri => new Uri(uri)).ToArray();

            // Parse optional settings
            if (settings.TryGetValue(nameof(Topic), out setting) && !string.IsNullOrWhiteSpace(setting))
            {
                Topic = setting.Trim();
            }
            else
            {
                Topic = TimeSeriesProducer.DefaultTopic;
            }

            if (settings.TryGetValue(nameof(Partitions), out setting) && int.TryParse(setting, out intValue))
            {
                Partitions = intValue;
            }
            else
            {
                Partitions = TimeSeriesProducer.DefaultPartitions;
            }

            if (settings.TryGetValue(nameof(TrackConsumerOffset), out setting))
            {
                TrackConsumerOffset = setting.ParseBoolean();
            }
            else
            {
                TrackConsumerOffset = DefaultTrackConsumerIndex;
            }

            if (!settings.TryGetValue(nameof(ConsumerOffsetFileName), out setting) || string.IsNullOrWhiteSpace(setting))
            {
                setting = Name + ".offset";
            }

            ConsumerOffsetFileName = FilePath.GetAbsolutePath(setting);

            if (settings.TryGetValue(nameof(ConsumerOffsetCacheInterval), out setting) && double.TryParse(setting, out doubleValue))
            {
                ConsumerOffsetCacheInterval = doubleValue;
            }
            else
            {
                ConsumerOffsetCacheInterval = DefaultConsumerOffsetCacheInterval;
            }

            if (settings.TryGetValue(nameof(ReadDelay), out setting) && int.TryParse(setting, out intValue))
            {
                ReadDelay = intValue;
            }
            else
            {
                ReadDelay = DefaultReadDelay;
            }

            if (settings.TryGetValue(nameof(CacheMetadataLocally), out setting))
            {
                CacheMetadataLocally = setting.ParseBoolean();
            }
            else
            {
                CacheMetadataLocally = TimeSeriesProducer.DefaultCacheMetadataLocally;
            }

            if (CacheMetadataLocally)
            {
                m_cacheMetadataLocally = new LongSynchronizedOperation(() => TimeSeriesMetadata.CacheLocally(m_metadata, MetadataTopic, OnStatusMessage))
                {
                    IsBackground = true
                }
            }
            ;

            if ((object)OutputMeasurements != null && OutputMeasurements.Length > 0)
            {
                m_outputMeasurementKeys = new HashSet <MeasurementKey>(OutputMeasurements.Select(m => m.Key));
            }
        }
Ejemplo n.º 10
0
        // Kick start read process for historian
        private void StartDataReader(object state)
        {
            try
            {
                if (SupportsTemporalProcessing)
                {
                    if ((object)RequestedOutputMeasurementKeys != null)
                    {
                        OnStatusMessage(MessageLevel.Info, $"Replaying for requested output keys: {RequestedOutputMeasurementKeys.Length:N0} defined measurements");
                    }
                    else
                    {
                        OnStatusMessage(MessageLevel.Warning, "No measurements have been requested for playback - make sure \"; connectOnDemand=true\" is defined in the connection string for the reader.");
                    }
                }

                MeasurementKey[] requestedKeys = SupportsTemporalProcessing ? RequestedOutputMeasurementKeys : OutputMeasurements.MeasurementKeys().ToArray();

                if (Enabled && (object)m_connection != null && (object)requestedKeys != null && requestedKeys.Length > 0)
                {
                    HashSet <string> tagList = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

                    var query = from row in DataSource.Tables["ActiveMeasurements"].AsEnumerable()
                                from key in requestedKeys
                                where row["ID"].ToString() == key.ToString()
                                select new
                    {
                        Key          = key,
                        AlternateTag = row["AlternateTag"].ToString(),
                        PointTag     = row["PointTag"].ToString()
                    };

                    string tagName;
                    m_points = new PIPointList();
                    PIPoint point;

                    foreach (var result in query)
                    {
                        tagName = result.PointTag;

                        if (!string.IsNullOrWhiteSpace(result.AlternateTag))
                        {
                            tagName = result.AlternateTag;
                        }

                        if (tagList.Add(tagName) && PIPoint.TryFindPIPoint(m_connection.Server, tagName, out point))
                        {
                            m_tagKeyMap[point.ID] = result.Key;
                            m_points.Add(point);
                        }
                    }

                    m_publicationTime = 0;

                    // Start data read from historian
                    lock (m_readTimer)
                    {
                        m_startTime = base.StartTimeConstraint <DateTime.MinValue?DateTime.MinValue : base.StartTimeConstraint> DateTime.MaxValue ? DateTime.MaxValue : base.StartTimeConstraint;
                        m_stopTime  = base.StopTimeConstraint <DateTime.MinValue?DateTime.MinValue : base.StopTimeConstraint> DateTime.MaxValue ? DateTime.MaxValue : base.StopTimeConstraint;

                        m_dataReader = ReadData(m_startTime, m_stopTime).GetEnumerator();

                        m_readTimer.Enabled = m_dataReader.MoveNext();

                        if (m_readTimer.Enabled)
                        {
                            OnStatusMessage(MessageLevel.Info, "Starting historical data read...");
                        }
                        else
                        {
                            OnStatusMessage(MessageLevel.Info, "No historical data was available to read for given time frame.");
                            OnProcessingComplete();
                        }
                    }
                }
                else
                {
                    m_readTimer.Enabled = false;
                    OnStatusMessage(MessageLevel.Info, "No measurement keys have been requested for reading, historian reader is idle.");
                    OnProcessingComplete();
                }
            }
            catch (Exception ex)
            {
                OnProcessException(MessageLevel.Warning, new InvalidOperationException($"Could not start historical data read due to exception: {ex.Message}", ex));
            }
        }