689 lines · 30.4 KB · C#
RawDownload
//******************************************************************************************************//  CompactMeasurement.cs - Gbtc////  Copyright © 2012, 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://  ----------------------------------------------------------------------------------------------------//  08/23/2010 - J. Ritchie Carroll//       Generated original version of source code.//  05/15/2011 - J. Ritchie Carroll//       Added runtime size optimizations.//  06/07/2011 - J. Ritchie Carroll//       Implemented initialize issue fix as found and proposed by Luc Cezard.//  12/20/2012 - Starlynn Danyelle Gilliam//       Modified Header.////****************************************************************************************************** using System;using System.Collections.Generic;using System.Linq;using GSF.IO;using GSF.IO.Compression;using GSF.Parsing; namespace GSF.TimeSeries.Transport{    #region [ Enumerations ]     /// <summary>    /// <see cref="CompactMeasurement"/> state flags.    /// </summary>    [Flags]    internal enum CompactMeasurementStateFlags : byte    {        /// <summary>        /// A data range flag was set.        /// </summary>        DataRange = (byte)Bits.Bit00,        /// <summary>        /// A data quality flag was set.        /// </summary>        DataQuality = (byte)Bits.Bit01,        /// <summary>        /// A time quality flag was set.        /// </summary>        TimeQuality = (byte)Bits.Bit02,        /// <summary>        /// A system flag was set.        /// </summary>        SystemIssue = (byte)Bits.Bit03,        /// <summary>        /// Calculated value bit was set.        /// </summary>        CalculatedValue = (byte)Bits.Bit04,        /// <summary>        /// Discarded value bit was set.        /// </summary>        DiscardedValue = (byte)Bits.Bit05,        /// <summary>        /// Compact measurement timestamp was serialized using base time offset when set.        /// </summary>        BaseTimeOffset = (byte)Bits.Bit06,        /// <summary>        /// Use odd time index (i.e., 1) when bit is set; even time index (i.e., 0) when bit is clear.        /// </summary>        TimeIndex = (byte)Bits.Bit07,        /// <summary>        /// No flags.        /// </summary>        NoFlags = (byte)Bits.Nil    }     /// <summary>    /// Defines static methods for mapping between compact and full measurement states.    /// </summary>    internal static class MeasurementStateMappingExtensions    {        private const MeasurementStateFlags DataRangeMask = MeasurementStateFlags.OverRangeError | MeasurementStateFlags.UnderRangeError | MeasurementStateFlags.AlarmHigh | MeasurementStateFlags.AlarmLow | MeasurementStateFlags.WarningHigh | MeasurementStateFlags.WarningLow;        private const MeasurementStateFlags DataQualityMask = MeasurementStateFlags.BadData | MeasurementStateFlags.SuspectData | MeasurementStateFlags.FlatlineAlarm | MeasurementStateFlags.ComparisonAlarm | MeasurementStateFlags.ROCAlarm | MeasurementStateFlags.ReceivedAsBad | MeasurementStateFlags.CalculationError | MeasurementStateFlags.CalculationWarning | MeasurementStateFlags.ReservedQualityFlag;        private const MeasurementStateFlags TimeQualityMask = MeasurementStateFlags.BadTime | MeasurementStateFlags.SuspectTime | MeasurementStateFlags.LateTimeAlarm | MeasurementStateFlags.FutureTimeAlarm | MeasurementStateFlags.UpSampled | MeasurementStateFlags.DownSampled | MeasurementStateFlags.ReservedTimeFlag;        private const MeasurementStateFlags SystemIssueMask = MeasurementStateFlags.SystemError | MeasurementStateFlags.SystemWarning | MeasurementStateFlags.MeasurementError;        private const MeasurementStateFlags CalculatedValueMask = MeasurementStateFlags.CalculatedValue;        private const MeasurementStateFlags DiscardedValueMask = MeasurementStateFlags.DiscardedValue;         /// <summary>        /// Maps <see cref="MeasurementStateFlags"/> to <see cref="CompactMeasurementStateFlags"/>.        /// </summary>        /// <param name="stateFlags">Flags to map.</param>        /// <returns><see cref="CompactMeasurementStateFlags"/> mapped from <see cref="MeasurementStateFlags"/>.</returns>        public static CompactMeasurementStateFlags MapToCompactFlags(this MeasurementStateFlags stateFlags)        {            CompactMeasurementStateFlags mappedStateFlags = CompactMeasurementStateFlags.NoFlags;             if ((stateFlags & DataRangeMask) > 0)                mappedStateFlags |= CompactMeasurementStateFlags.DataRange;             if ((stateFlags & DataQualityMask) > 0)                mappedStateFlags |= CompactMeasurementStateFlags.DataQuality;             if ((stateFlags & TimeQualityMask) > 0)                mappedStateFlags |= CompactMeasurementStateFlags.TimeQuality;             if ((stateFlags & SystemIssueMask) > 0)                mappedStateFlags |= CompactMeasurementStateFlags.SystemIssue;             if ((stateFlags & CalculatedValueMask) > 0)                mappedStateFlags |= CompactMeasurementStateFlags.CalculatedValue;             if ((stateFlags & DiscardedValueMask) > 0)                mappedStateFlags |= CompactMeasurementStateFlags.DiscardedValue;             return mappedStateFlags;        }         /// <summary>        /// Maps <see cref="CompactMeasurementStateFlags"/> to <see cref="MeasurementStateFlags"/>.        /// </summary>        /// <param name="stateFlags">Flags to map.</param>        /// <returns><see cref="MeasurementStateFlags"/> mapped from <see cref="CompactMeasurementStateFlags"/>.</returns>        public static MeasurementStateFlags MapToFullFlags(this CompactMeasurementStateFlags stateFlags)        {            MeasurementStateFlags mappedStateFlags = MeasurementStateFlags.Normal;             if ((stateFlags & CompactMeasurementStateFlags.DataRange) > 0)                mappedStateFlags |= DataRangeMask;             if ((stateFlags & CompactMeasurementStateFlags.DataQuality) > 0)                mappedStateFlags |= DataQualityMask;             if ((stateFlags & CompactMeasurementStateFlags.TimeQuality) > 0)                mappedStateFlags |= TimeQualityMask;             if ((stateFlags & CompactMeasurementStateFlags.SystemIssue) > 0)                mappedStateFlags |= SystemIssueMask;             if ((stateFlags & CompactMeasurementStateFlags.CalculatedValue) > 0)                mappedStateFlags |= CalculatedValueMask;             if ((stateFlags & CompactMeasurementStateFlags.DiscardedValue) > 0)                mappedStateFlags |= DiscardedValueMask;             return mappedStateFlags;        }    }     #endregion     /// <summary>    /// Represents a <see cref="IMeasurement"/> that can be serialized with minimal size.    /// </summary>    /// <remarks>    /// This measurement implementation is serialized through <see cref="ISupportBinaryImage"/>    /// to allow complete control of binary format. Only critical measurements properties are    /// serialized and every attempt is made to optimize the binary image for purposes of size    /// reduction.    /// </remarks>    public class CompactMeasurement : Measurement, IBinaryMeasurement    {        #region [ Members ]         // Constants         /// <summary>        /// Fixed byte length of a <see cref="CompactMeasurement"/>.        /// </summary>        public const int FixedLength = 7;         // Members        private readonly SignalIndexCache m_signalIndexCache;        private readonly long[] m_baseTimeOffsets;        private int m_timeIndex;        private readonly bool m_useMillisecondResolution;        private bool m_usingBaseTimeOffset;        private readonly bool m_includeTime;         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new <see cref="CompactMeasurement"/>.        /// </summary>        /// <param name="signalIndexCache">Signal index cache used to serialize or deserialize runtime information.</param>        /// <param name="includeTime">Set to <c>true</c> to include time in serialized packet; otherwise <c>false</c>.</param>        /// <param name="baseTimeOffsets">Base time offset array - set to <c>null</c> to use full fidelity measurement time.</param>        /// <param name="timeIndex">Time index to use for base offset.</param>        /// <param name="useMillisecondResolution">Flag that determines if millisecond resolution is in use for this serialization.</param>        public CompactMeasurement(SignalIndexCache signalIndexCache, bool includeTime = true, long[] baseTimeOffsets = null, int timeIndex = 0, bool useMillisecondResolution = false)        {            m_signalIndexCache = signalIndexCache;            m_includeTime = includeTime;             // We keep a clone of the base time offsets, if provided, since array contents can change at any time            if ((object)baseTimeOffsets == null)                m_baseTimeOffsets = s_emptyBaseTimeOffsets;            else                m_baseTimeOffsets = new[] { baseTimeOffsets[0], baseTimeOffsets[1] };             m_timeIndex = timeIndex;            m_useMillisecondResolution = useMillisecondResolution;        }         /// <summary>        /// Creates a new <see cref="CompactMeasurement"/> from an existing <see cref="IMeasurement"/> value.        /// </summary>        /// <param name="measurement">Source <see cref="IMeasurement"/> value.</param>        /// <param name="signalIndexCache">Signal index cache used to serialize or deserialize runtime information.</param>        /// <param name="includeTime">Set to <c>true</c> to include time in serialized packet; otherwise <c>false</c>.</param>        /// <param name="baseTimeOffsets">Base time offset array - set to <c>null</c> to use full fidelity measurement time.</param>        /// <param name="timeIndex">Time index to use for base offset.</param>        /// <param name="useMillisecondResolution">Flag that determines if millisecond resolution is in use for this serialization.</param>        public CompactMeasurement(IMeasurement measurement, SignalIndexCache signalIndexCache, bool includeTime = true, long[] baseTimeOffsets = null, int timeIndex = 0, bool useMillisecondResolution = false)        {            Metadata = measurement.Metadata;            Value = measurement.Value;            Timestamp = measurement.Timestamp;            StateFlags = measurement.StateFlags;             m_signalIndexCache = signalIndexCache;            m_includeTime = includeTime;             // We keep a clone of the base time offsets, if provided, since array contents can change at any time            if ((object)baseTimeOffsets == null)                m_baseTimeOffsets = s_emptyBaseTimeOffsets;            else                m_baseTimeOffsets = new[] { baseTimeOffsets[0], baseTimeOffsets[1] };             m_timeIndex = timeIndex;            m_useMillisecondResolution = useMillisecondResolution;        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets flag that determines if time is serialized into measurement binary image.        /// </summary>        public bool IncludeTime        {            get            {                return m_includeTime;            }        }         /// <summary>        /// Gets the length of the <see cref="CompactMeasurement"/>.        /// </summary>        public int BinaryLength        {            get            {                int length = FixedLength;                 if (m_includeTime)                {                    long baseTimeOffset = m_baseTimeOffsets[m_timeIndex];                     if (baseTimeOffset > 0)                    {                        // See if timestamp will fit within space allowed for active base offset. We cache result so that post call                        // to binary length, result will speed other subsequent parsing operations by not having to reevaluate.                        long difference = (long)Timestamp - m_baseTimeOffsets[m_timeIndex];                         if (difference > 0)                            m_usingBaseTimeOffset = m_useMillisecondResolution ? difference / Ticks.PerMillisecond < ushort.MaxValue : difference < uint.MaxValue;                        else                            m_usingBaseTimeOffset = false;                         if (m_usingBaseTimeOffset)                        {                            if (m_useMillisecondResolution)                                length += 2;    // Use two bytes for millisecond resolution timestamp with valid offset                            else                                length += 4;    // Use four bytes for tick resolution timestamp with valid offset                        }                        else                        {                            // Use eight bytes for full fidelity time                            length += 8;                        }                    }                    else                    {                        // Use eight bytes for full fidelity time                        length += 8;                    }                }                 return length;            }        }         /// <summary>        /// Gets offset compressed millisecond-resolution 2-byte timestamp.        /// </summary>        public ushort TimestampC2        {            get            {                return (ushort)(Timestamp - m_baseTimeOffsets[m_timeIndex]).ToMilliseconds();            }        }         /// <summary>        /// Gets offset compressed tick-resolution 4-byte timestamp.        /// </summary>        public uint TimestampC4        {            get            {                return (uint)((long)Timestamp - m_baseTimeOffsets[m_timeIndex]);            }        }         /// <summary>        /// Gets or sets byte level compact state flags with encoded time index and base time offset bits.        /// </summary>        public byte CompactStateFlags        {            get            {                // Encode compact state flags                CompactMeasurementStateFlags flags = StateFlags.MapToCompactFlags();                 if (m_timeIndex != 0)                    flags |= CompactMeasurementStateFlags.TimeIndex;                 if (m_usingBaseTimeOffset)                    flags |= CompactMeasurementStateFlags.BaseTimeOffset;                 return (byte)flags;            }            set            {                // Decode compact state flags                CompactMeasurementStateFlags flags = (CompactMeasurementStateFlags)value;                StateFlags = flags.MapToFullFlags();                m_timeIndex = (flags & CompactMeasurementStateFlags.TimeIndex) > 0 ? 1 : 0;                m_usingBaseTimeOffset = (flags & CompactMeasurementStateFlags.BaseTimeOffset) > 0;            }        }         /// <summary>        /// Gets or sets the 2-byte run-time signal index for this measurement.        /// </summary>        public ushort RuntimeID        {            get            {                return m_signalIndexCache.GetSignalIndex(Key);            }            set            {                // Attempt to restore signal identification                MeasurementKey key;                 if (m_signalIndexCache.Reference.TryGetValue(value, out key))                {                    Metadata = key.Metadata;                }                else                    throw new InvalidOperationException("Failed to find associated signal identification for runtime ID " + value);            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Initializes <see cref="CompactMeasurement"/> from the specified binary image.        /// </summary>        /// <param name="buffer">Buffer containing binary image to parse.</param>        /// <param name="startIndex">0-based starting index in the <paramref name="buffer"/> to start parsing.</param>        /// <param name="length">Valid number of bytes within <paramref name="buffer"/> from <paramref name="startIndex"/>.</param>        /// <returns>The number of bytes used for initialization in the <paramref name="buffer"/> (i.e., the number of bytes parsed).</returns>        /// <exception cref="InvalidOperationException">Not enough buffer available to deserialize measurement.</exception>        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is null.</exception>        /// <exception cref="ArgumentOutOfRangeException">        /// <paramref name="startIndex"/> or <paramref name="length"/> is less than 0 -or-         /// <paramref name="startIndex"/> and <paramref name="length"/> will exceed <paramref name="buffer"/> length.        /// </exception>        public int ParseBinaryImage(byte[] buffer, int startIndex, int length)        {            buffer.ValidateParameters(startIndex, length);             if (length < 1)                throw new InvalidOperationException("Not enough buffer available to deserialize measurement.");             int index = startIndex;             // Decode state flags            CompactStateFlags = buffer[index++];             // Decode runtime ID            RuntimeID = BigEndian.ToUInt16(buffer, index);            index += 2;             // Decode value            Value = BigEndian.ToSingle(buffer, index);            index += 4;             if (m_includeTime)            {                if (m_usingBaseTimeOffset)                {                    long baseTimeOffset = m_baseTimeOffsets[m_timeIndex];                     if (m_useMillisecondResolution)                    {                        // Decode 2-byte millisecond offset timestamp                        if (baseTimeOffset > 0)                            Timestamp = baseTimeOffset + BigEndian.ToUInt16(buffer, index) * Ticks.PerMillisecond;                         index += 2;                    }                    else                    {                        // Decode 4-byte tick offset timestamp                        if (baseTimeOffset > 0)                            Timestamp = baseTimeOffset + BigEndian.ToUInt32(buffer, index);                         index += 4;                    }                }                else                {                    // Decode 8-byte full fidelity timestamp                    Timestamp = BigEndian.ToInt64(buffer, index);                    index += 8;                }            }             return index - startIndex;        }         /// <summary>        /// Generates binary image of the <see cref="CompactMeasurement"/> and copies it into the given buffer, for <see cref="ISupportBinaryImage.BinaryLength"/> bytes.        /// </summary>        /// <param name="buffer">Buffer used to hold generated binary image of the source object.</param>        /// <param name="startIndex">0-based starting index in the <paramref name="buffer"/> to start writing.</param>        /// <returns>The number of bytes written to the <paramref name="buffer"/>.</returns>        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is null.</exception>        /// <exception cref="ArgumentOutOfRangeException">        /// <paramref name="startIndex"/> or <see cref="ISupportBinaryImage.BinaryLength"/> is less than 0 -or-         /// <paramref name="startIndex"/> and <see cref="ISupportBinaryImage.BinaryLength"/> will exceed <paramref name="buffer"/> length.        /// </exception>        /// <remarks>        /// <para>        /// Field:     Bytes: <br/>        /// --------   -------<br/>        ///  Flags        1   <br/>        ///   ID          2   <br/>        ///  Value        4   <br/>        ///  [Time]       2?  <br/>        /// </para>        /// <para>        /// Constant Length = 7<br/>        /// Variable Length = 0, 2, 4 or 8 (i.e., total size is 7, 9, 11 or 15)        /// </para>        /// </remarks>        public int GenerateBinaryImage(byte[] buffer, int startIndex)        {            // Call to binary length property caches result of m_usingBaseTimeOffset            int length = BinaryLength;             buffer.ValidateParameters(startIndex, length);             // Added encoded compact state flags to beginning of buffer            buffer[startIndex++] = CompactStateFlags;             // Encode runtime ID            startIndex += BigEndian.CopyBytes(RuntimeID, buffer, startIndex);             // Encode adjusted value (accounts for adder and multiplier)            startIndex += BigEndian.CopyBytes((float)AdjustedValue, buffer, startIndex);             if (m_includeTime)            {                if (m_usingBaseTimeOffset)                {                    if (m_useMillisecondResolution)                    {                        // Encode 2-byte millisecond offset timestamp                        BigEndian.CopyBytes(TimestampC2, buffer, startIndex);                    }

Lines 1–500 of 689.

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