/// <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);
        }