/// <summary> /// Extract frame measurements and add expose them via the <see cref="IInputAdapter.NewMeasurements"/> event. /// </summary> /// <param name="frame">Phasor data frame to extract measurements from.</param> protected void ExtractFrameMeasurements(IDataFrame frame) { const int AngleIndex = (int)CompositePhasorValue.Angle; const int MagnitudeIndex = (int)CompositePhasorValue.Magnitude; const int FrequencyIndex = (int)CompositeFrequencyValue.Frequency; const int DfDtIndex = (int)CompositeFrequencyValue.DfDt; List<IMeasurement> mappedMeasurements = new List<IMeasurement>(m_lastMeasurementMappedCount); List<IMeasurement> deviceMappedMeasurements = new List<IMeasurement>(); DeviceStatisticsHelper<ConfigurationCell> statisticsHelper; ConfigurationCell definedDevice; PhasorValueCollection phasors; AnalogValueCollection analogs; DigitalValueCollection digitals; IMeasurement[] measurements; long timestamp; int x, count; // Adjust time to UTC based on source time zone if (!m_timezone.Equals(TimeZoneInfo.Utc)) frame.Timestamp = TimeZoneInfo.ConvertTimeToUtc(frame.Timestamp, m_timezone); // We also allow "fine tuning" of time for fickle GPS clocks... if (m_timeAdjustmentTicks.Value != 0) frame.Timestamp += m_timeAdjustmentTicks; // Get adjusted timestamp of this frame timestamp = frame.Timestamp; // Track latest reporting time for mapper if (timestamp > m_lastReportTime.Value) m_lastReportTime = timestamp; else m_outOfOrderFrames++; // Track latency statistics against system time - in order for these statistics // to be useful, the local clock must be fairly accurate long latency = frame.CreatedTimestamp.Value - timestamp; // Throw out latencies that exceed one hour as invalid if (Math.Abs(latency) <= Time.SecondsPerHour * Ticks.PerSecond) { if (m_minimumLatency > latency || m_minimumLatency == 0) m_minimumLatency = latency; if (m_maximumLatency < latency || m_maximumLatency == 0) m_maximumLatency = latency; m_totalLatency += latency; m_latencyFrames++; if (m_lifetimeMinimumLatency > latency || m_lifetimeMinimumLatency == 0) m_lifetimeMinimumLatency = latency; if (m_lifetimeMaximumLatency < latency || m_lifetimeMaximumLatency == 0) m_lifetimeMaximumLatency = latency; m_lifetimeTotalLatency += latency; m_lifetimeLatencyFrames++; } // Map quality flags (QF) from device frame, if any MapMeasurementAttributes(mappedMeasurements, m_qualityFlagsMetadata, frame.GetQualityFlagsMeasurement()); // Loop through each parsed device in the data frame foreach (IDataCell parsedDevice in frame.Cells) { try { // Lookup device by its label (if needed), then by its ID code if (((object)m_labelDefinedDevices != null && m_labelDefinedDevices.TryGetValue(parsedDevice.StationName.ToNonNullString(), out statisticsHelper)) || m_definedDevices.TryGetValue(parsedDevice.IDCode, out statisticsHelper)) { definedDevice = statisticsHelper.Device; // Track latest reporting time for this device if (timestamp > definedDevice.LastReportTime.Value) definedDevice.LastReportTime = timestamp; // Track quality statistics for this device definedDevice.TotalFrames++; if (!parsedDevice.DataIsValid) definedDevice.DataQualityErrors++; if (!parsedDevice.SynchronizationIsValid) definedDevice.TimeQualityErrors++; if (parsedDevice.DeviceError) definedDevice.DeviceErrors++; // Map status flags (SF) from device data cell itself MapMeasurementAttributes(mappedMeasurements, definedDevice.GetMetadata(m_definedMeasurements, SignalKind.Status), parsedDevice.GetStatusFlagsMeasurement()); // Map phase angles (PAn) and magnitudes (PMn) phasors = parsedDevice.PhasorValues; count = phasors.Count; for (x = 0; x < count; x++) { // Get composite phasor measurements measurements = phasors[x].Measurements; // Map angle MapMeasurementAttributes(deviceMappedMeasurements, definedDevice.GetMetadata(m_definedMeasurements, SignalKind.Angle, x, count), measurements[AngleIndex]); // Map magnitude MapMeasurementAttributes(deviceMappedMeasurements, definedDevice.GetMetadata(m_definedMeasurements, SignalKind.Magnitude, x, count), measurements[MagnitudeIndex]); } // Map frequency (FQ) and dF/dt (DF) measurements = parsedDevice.FrequencyValue.Measurements; // Map frequency MapMeasurementAttributes(deviceMappedMeasurements, definedDevice.GetMetadata(m_definedMeasurements, SignalKind.Frequency), measurements[FrequencyIndex]); // Map dF/dt MapMeasurementAttributes(deviceMappedMeasurements, definedDevice.GetMetadata(m_definedMeasurements, SignalKind.DfDt), measurements[DfDtIndex]); // Map analog values (AVn) analogs = parsedDevice.AnalogValues; count = analogs.Count; // Map analog values for (x = 0; x < count; x++) MapMeasurementAttributes(deviceMappedMeasurements, definedDevice.GetMetadata(m_definedMeasurements, SignalKind.Analog, x, count), analogs[x].Measurements[0]); // Map digital values (DVn) digitals = parsedDevice.DigitalValues; count = digitals.Count; // Map digital values for (x = 0; x < count; x++) MapMeasurementAttributes(deviceMappedMeasurements, definedDevice.GetMetadata(m_definedMeasurements, SignalKind.Digital, x, count), digitals[x].Measurements[0]); // Track measurement count statistics for this device if (m_countOnlyMappedMeasurements) statisticsHelper.AddToMeasurementsReceived(CountMappedMeasurements(parsedDevice, deviceMappedMeasurements)); else statisticsHelper.AddToMeasurementsReceived(CountParsedMeasurements(parsedDevice)); mappedMeasurements.AddRange(deviceMappedMeasurements); deviceMappedMeasurements.Clear(); } else { // Encountered an undefined device, track frame counts long frameCount; if (m_undefinedDevices.TryGetValue(parsedDevice.StationName, out frameCount)) { frameCount++; m_undefinedDevices[parsedDevice.StationName] = frameCount; } else { m_undefinedDevices.TryAdd(parsedDevice.StationName, 1); OnStatusMessage(MessageLevel.Warning, $"Encountered an undefined device \"{parsedDevice.StationName}\"..."); } } } catch (Exception ex) { OnProcessException(MessageLevel.Warning, new InvalidOperationException($"Exception encountered while mapping \"{Name}\" data frame cell \"{parsedDevice.StationName.ToNonNullString("[undefined]")}\" elements to measurements: {ex.Message}", ex)); } } m_lastMeasurementMappedCount = mappedMeasurements.Count; // Provide real-time measurements where needed OnNewMeasurements(mappedMeasurements); int measurementCount = mappedMeasurements.Count; m_lifetimeMeasurements += measurementCount; UpdateMeasurementsPerSecond(measurementCount); }