//******************************************************************************************************
// 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 ]
///
/// state flags.
///
[Flags]
internal enum CompactMeasurementStateFlags : byte
{
///
/// A data range flag was set.
///
DataRange = (byte)Bits.Bit00,
///
/// A data quality flag was set.
///
DataQuality = (byte)Bits.Bit01,
///
/// A time quality flag was set.
///
TimeQuality = (byte)Bits.Bit02,
///
/// A system flag was set.
///
SystemIssue = (byte)Bits.Bit03,
///
/// Calculated value bit was set.
///
CalculatedValue = (byte)Bits.Bit04,
///
/// Discarded value bit was set.
///
DiscardedValue = (byte)Bits.Bit05,
///
/// Compact measurement timestamp was serialized using base time offset when set.
///
BaseTimeOffset = (byte)Bits.Bit06,
///
/// Use odd time index (i.e., 1) when bit is set; even time index (i.e., 0) when bit is clear.
///
TimeIndex = (byte)Bits.Bit07,
///
/// No flags.
///
NoFlags = (byte)Bits.Nil
}
///
/// Defines static methods for mapping between compact and full measurement states.
///
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;
///
/// Maps to .
///
/// Flags to map.
/// mapped from .
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;
}
///
/// Maps to .
///
/// Flags to map.
/// mapped from .
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
///
/// Represents a that can be serialized with minimal size.
///
///
/// This measurement implementation is serialized through
/// 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.
///
public class CompactMeasurement : Measurement, ISupportBinaryImage
{
#region [ Members ]
// Constants
///
/// Fixed byte length of a .
///
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)
{
}
///
/// Creates a new .
///
/// Signal index cache used to serialize or deserialize runtime information.
/// Set to true to include time in serialized packet; otherwise false.
/// Base time offset array - set to null to use full fidelity measurement time.
/// Time index to use for base offset.
/// Flag that determines if millisecond resolution is in use for this serialization.
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)
{
}
///
/// Creates a new from an existing value.
///
/// Source value.
/// Signal index cache used to serialize or deserialize runtime information.
/// Set to true to include time in serialized packet; otherwise false.
/// Base time offset array - set to null to use full fidelity measurement time.
/// Time index to use for base offset.
/// Flag that determines if millisecond resolution is in use for this serialization.
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 ]
///
/// Gets flag that determines if time is serialized into measurement binary image.
///
public bool IncludeTime
{
get
{
return m_includeTime;
}
}
///
/// Gets the length of the .
///
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 ]
///
/// Initializes from the specified binary image.
///
/// Buffer containing binary image to parse.
/// 0-based starting index in the to start parsing.
/// Valid number of bytes within from .
/// The number of bytes used for initialization in the (i.e., the number of bytes parsed).
/// Not enough buffer available to deserialize measurement.
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
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 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);
}
///
/// Generates binary image of the and copies it into the given buffer, for bytes.
///
/// Buffer used to hold generated binary image of the source object.
/// 0-based starting index in the to start writing.
/// The number of bytes written to the .
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
///
///
/// Field: Bytes:
/// -------- -------
/// Flags 1
/// ID 2
/// Value 4
/// [Time] 2?
///
///
/// Constant Length = 7
/// Variable Length = 0, 2, 4 or 8 (i.e., total size is 7, 9, 11 or 15)
///
///
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
}
}