489 lines · 21.3 KB · C#
RawDownload
//******************************************************************************************************//  CompactMeasurement.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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php////  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 bug fix as found and proposed by Luc Cezard.////****************************************************************************************************** using GSF.Parsing;using System; 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.CalcuatedValue;        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, ISupportBinaryImage    {        #region [ Members ]         // Constants         /// <summary>        /// Fixed byte length of a <see cref="CompactMeasurement"/>.        /// </summary>        public const int FixedLength = 7;         // Members        private SignalIndexCache m_signalIndexCache;        private bool m_includeTime;        private long[] m_baseTimeOffsets;        private int m_timeIndex;        private bool m_useMillisecondResolution;        private bool m_usingBaseTimeOffset;         #endregion         #region [ Constructors ]          public CompactMeasurement(SignalIndexCache signalIndexCache)            : this(signalIndexCache, true, null, 0, false)        {                    }                /// <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, long[] baseTimeOffsets, int timeIndex, bool useMillisecondResolution)        {            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 (baseTimeOffsets == null)                m_baseTimeOffsets = s_emptyBaseTimeOffsets;            else                m_baseTimeOffsets = new long[] { baseTimeOffsets[0], baseTimeOffsets[1] };             m_timeIndex = timeIndex;            m_useMillisecondResolution = useMillisecondResolution;        }          public CompactMeasurement(IMeasurement measurement, SignalIndexCache signalIndexCache)            : this(measurement, signalIndexCache, true, null, 0 , false)        {                    }                /// <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, long[] baseTimeOffsets, int timeIndex, bool useMillisecondResolution)        {            ID = measurement.ID;            Key = measurement.Key;            Value = measurement.Value;            Adder = measurement.Adder;            Multiplier = measurement.Multiplier;            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 (baseTimeOffsets == null)                m_baseTimeOffsets = s_emptyBaseTimeOffsets;            else                m_baseTimeOffsets = new long[] { 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;            }        }         #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.");             // Decode flags            CompactMeasurementStateFlags flags = (CompactMeasurementStateFlags)buffer[startIndex];            StateFlags = flags.MapToFullFlags();            m_timeIndex = (flags & CompactMeasurementStateFlags.TimeIndex) > 0 ? 1 : 0;            m_usingBaseTimeOffset = (flags & CompactMeasurementStateFlags.BaseTimeOffset) > 0;             int index = startIndex + 1;             // Decode runtime ID            ushort id = EndianOrder.BigEndian.ToUInt16(buffer, index);            index += 2;             // Restore signal identification            Tuple<Guid, string, uint> tuple;             if (m_signalIndexCache.Reference.TryGetValue(id, out tuple))            {                ID = tuple.Item1;                Key = new MeasurementKey(tuple.Item1, tuple.Item3, tuple.Item2);            }            else                throw new InvalidOperationException("Failed to find associated signal identification for runtime ID " + id);             // Decode value            Value = EndianOrder.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 + EndianOrder.BigEndian.ToUInt16(buffer, index) * Ticks.PerMillisecond;                         index += 2;                    }                    else                    {                        // Decode 4-byte tick offset timestamp                        if (baseTimeOffset > 0)                            Timestamp = baseTimeOffset + EndianOrder.BigEndian.ToUInt32(buffer, index);                         index += 4;                    }                }                else                {                    // Decode 8-byte full fidelity timestamp                    Timestamp = EndianOrder.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);             // Encode compact state flags            CompactMeasurementStateFlags flags = StateFlags.MapToCompactFlags();             if (m_timeIndex != 0)                flags |= CompactMeasurementStateFlags.TimeIndex;             if (m_usingBaseTimeOffset)                flags |= CompactMeasurementStateFlags.BaseTimeOffset;             // Added flags to beginning of buffer            buffer[startIndex++] = (byte)flags;             // Encode runtime ID            EndianOrder.BigEndian.CopyBytes(m_signalIndexCache.GetSignalIndex(ID), buffer, startIndex);             startIndex += 2;             // Encode adjusted value (accounts for adder and multipler)            EndianOrder.BigEndian.CopyBytes((float)AdjustedValue, buffer, startIndex);            startIndex += 4;             if (m_includeTime)            {                if (m_usingBaseTimeOffset)                {                    if (m_useMillisecondResolution)                    {                        // Encode 2-byte millisecond offset timestamp                        EndianOrder.BigEndian.CopyBytes((ushort)(Timestamp - m_baseTimeOffsets[m_timeIndex]).ToMilliseconds(), buffer, startIndex);                    }                    else                    {                        // Encode 4-byte ticks offset timestamp                        EndianOrder.BigEndian.CopyBytes((uint)((long)Timestamp - m_baseTimeOffsets[m_timeIndex]), buffer, startIndex);                    }                }                else                {                    // Encode 8-byte full fidelity timestamp                    EndianOrder.BigEndian.CopyBytes((long)Timestamp, buffer, startIndex);                }            }             return length;        }         #endregion         #region [ Static ]         // Static Fields        private static readonly long[] s_emptyBaseTimeOffsets = new long[] { 0, 0 };         #endregion     }}

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