tbf/TBF/Rig/RegisterReaders/SerialStream/DatastreamFrame.cs
2021-10-26 19:38:09 +02:00

291 lines
9.9 KiB
C#

///
/// Copyright (c) 2018 Sensus Slovensko a.s.
///
using System;
using System.Globalization;
using System.Text;
namespace TBF.Rig.RegisterReaders.SerialStream
{
public class DatastreamFrame
{
///
/// Strobed value
///
public static decimal TestStartTimestampDec;
///
/// Stored values
///
public FrameFlags Flags;
public DateTime DateTime; /// From PC
public float RefFlow; /// [m3/h]
public int Counter;
const int MaxDataLength = 512;
char[] data = new char[MaxDataLength];
int dataLength;
public byte CheckSum;
public UInt32 VolumeRaw;
public Int64 VolumeRawExt;
public UInt32 Timestamp;
public Int64 TimestampExt;
///
/// Calculated values
///
public double Volume(FrameFormat ff) { return (double)VolumeRawExt / ff.VolumeScaleFactor; }
public decimal TimestampDec(FrameFormat ff) { return (decimal)TimestampExt / (decimal)ff.TimeScaleFactor; }
public double VolumeDelta(FrameFormat ff, DatastreamFrame previous) { return (previous == null) ? 0 : Volume(ff) - previous.Volume(ff); }
public decimal TimeDelta(FrameFormat ff) { return TimestampDec(ff) - TestStartTimestampDec; }
public string Label()
{
if (Flags == FrameFlags.OK_TestStart) return "#### start test ####";
else if (Flags == FrameFlags.OK_TestEnd) return "#### end of test ####";
else return string.Empty;
}
static DatastreamFrame()
{
}
public DatastreamFrame()
{
}
/// <summary>
/// Parses optical telegram and returns OptoTelegramRaw object
/// </summary>
/// <description>
/// Create a configuration structure from a complete byte array
///
/// Telegram description:
///
/// AAAAAA[tab]BBBB[tab]CCCC[tab]DDDDDD[tab]EEEE[tab]FFFFFFFF[tab]GG[cr][lf] (42 bytes)
///
/// Data Comment Type Calculate to decimal
/// ----------------------------------------------------------------
/// AAAAAA EMF Int24 Value * 0.000000333
/// BBBB Magnetic field Int16 Value
/// CCCC Flow Int16 Value * 0.225 * Scalig factor
/// DDDDDD Volume Int24 Value / 16000 * Scaling factor
/// EEEE Impedance Int16 Value
/// FFFFFFFF Timestamp Uint32 Value / 8192
/// GG Checksum Byte
/// ----------------------------------------------------------------
///
/// Example:
/// FFFFFE 51EA 0000 65324E 0087 F6319DFF 86
/// FFDD3A 51F9 0000 65324E 0088 F631A60B 45
/// ...
/// </description>
/// <param name="data">A complete byte array data</param>
/// <returns>true = telegram OK, false = telegram NOK</returns>
public bool UpdateFromString(string frame, FrameFormat ff, int counter, ref Int64 volumeRawExtLast, ref Int64 timestampExtLast)
{
DateTime = DateTime.Now;
Counter = counter;
RefFlow = (float)Sequences.ProcessData.RefFlow.Val;
if ((frame == null)
|| (frame.Length < ff.FrameLength)
|| (frame[ff.FrameLength - 2] != '\r')
|| (frame[ff.FrameLength - 1] != '\n'))
{
Flags = FrameFlags.InvalidFrame;
return false;
}
/// Copy data into the fixed size buffer
dataLength = frame.Length - 2;
for (int i = 0; i < Math.Min(MaxDataLength, dataLength); i++) data[i] = frame[i];
bool f1 = true;
bool f2 = true;
bool f3 = true;
if (ff.VolumeFieldFormat == FieldFormat.HexadecimalWindow)
{
f1 = UInt32.TryParse(frame.Substring(ff.VolumeFieldStart, ff.VolumeFieldLength), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out VolumeRaw);
///
/// Cope with 'VolumeRaw' overflow
///
Int64 uncorrected = (Int64)(((UInt64)volumeRawExtLast & ff.VolumeWindowMask) | VolumeRaw);
if (Math.Abs(uncorrected - volumeRawExtLast) <= ff.VolumeWrapThreshold)
{
VolumeRawExt = uncorrected;
}
else if (Math.Abs(uncorrected + ff.VolumeWrapIncrement - volumeRawExtLast) <= ff.VolumeWrapThreshold)
{
VolumeRawExt = uncorrected + ff.VolumeWrapIncrement;
}
else if (Math.Abs(uncorrected - ff.VolumeWrapIncrement - volumeRawExtLast) <= ff.VolumeWrapThreshold)
{
VolumeRawExt = uncorrected - ff.VolumeWrapIncrement;
}
else
{
VolumeRawExt = uncorrected;
}
volumeRawExtLast = VolumeRawExt; /// Update referenced value from calling routine
}
if (ff.TimeFieldFormat == FieldFormat.HexadecimalWindow)
{
f2 = UInt32.TryParse(frame.Substring(ff.TimeFieldStart, ff.TimeFieldLength), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out Timestamp);
///
/// Cope with 'Timestamp' overflow
///
Int64 uncorrected = (Int64)(((UInt64)timestampExtLast & ff.TimeWindowMask) | Timestamp);
if (Math.Abs(uncorrected - timestampExtLast) <= ff.TimeWrapThreshold)
{
TimestampExt = uncorrected;
}
else if (Math.Abs(uncorrected + ff.TimeWrapIncrement - timestampExtLast) <= ff.TimeWrapThreshold)
{
TimestampExt = uncorrected + ff.TimeWrapIncrement;
}
else if (Math.Abs(uncorrected - ff.TimeWrapIncrement - timestampExtLast) <= ff.TimeWrapThreshold)
{
TimestampExt = uncorrected - ff.TimeWrapIncrement;
}
else
{
TimestampExt = uncorrected;
}
timestampExtLast = TimestampExt; /// Update referenced value from calling routine
}
else if (ff.TimeFieldFormat == FieldFormat.None)
{
TimestampExt = ff.RawTimeIncrementPerFrame * counter;
timestampExtLast = TimestampExt; /// Update referenced value from calling routine
}
f3 = byte.TryParse(frame.Substring(ff.FrameLength - 4, 2), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out CheckSum);
bool allOk = f1 && f2 && f3;
Flags = allOk ? FrameFlags.OK : FrameFlags.InvalidFrame;
return allOk;
}
/// <summary>
/// Alternative to UpdateFromString(...) when data are flushed
/// </summary>
public bool UpdateFromStringDummy(string frame, FrameFormat ff)
{
DateTime = DateTime.Now;
RefFlow = (float)Sequences.ProcessData.RefFlow.Val;
if ((frame == null)
|| (frame.Length < ff.FrameLength)
|| (frame[ff.FrameLength - 2] != '\r')
|| (frame[ff.FrameLength - 1] != '\n'))
{
Flags = FrameFlags.InvalidFrame;
return false;
}
dataLength = 0;
return true;
}
public void SetFlags(FrameFlags flags)
{
this.Flags = flags;
}
public string ToString(FrameFormat ff, DatastreamFrame previous)
{
if (Flags == FrameFlags.SyncError)
{
return "Sychronization error";
}
else if (Flags == FrameFlags.InvalidFrame)
{
return "Invalid telegram";
}
else /// if (flags == OptoTelegramFlags.OK || OptoTelegramFlags.OK_TestStart || OptoTelegramFlags.OK_TestEnd)
{
StringBuilder sb = new StringBuilder(MaxDataLength);
for (int i = 0; i < Math.Min(MaxDataLength, dataLength); i++) sb.Append(data[i]);
return string.Format("{0}:{1}:{2}.{3} #{4} : {5} Volume={6} Time={7} {8}",
DateTime.Hour.ToString("D2"),
DateTime.Minute.ToString("D2"),
DateTime.Second.ToString("D2"),
DateTime.Millisecond.ToString("D3"),
Counter.ToString("D6"),
sb.ToString(),
Volume(ff),
TimestampDec(ff),
Label());
}
}
/// <summary>
/// Filter RefFlow data in an array of OptoTelegramRaw objects by a FIR filter:
///
/// kSize = 5, kSize2 = 2
///
/// i k
/// ---------------------------------------------------------------------------
/// 0 -5 filtered[0] = data[0]
/// 1 -4 filtered[1] = data[1]
/// 2 -3 filtered[2] = data[0]*k[0] + ... + data[4]*k[4]
/// 3 -2 filtered[3] = data[1]*k[0] + ... + data[5]*k[4]
/// 4 -1 filtered[4] = data[2]*k[0] + ... + data[6]*k[4]
/// 5 0 data[0] = filtered[0], filtered[0] = data[3]*k[0] + ... + data[7]*k[4]
/// 6 1 data[1] = filtered[1], filtered[1] = data[4]*k[0] + ... + data[8]*k[4]
/// 7 ...
/// </summary>
/// <param name="optoData">array of OptoTelegramRaw objects</param>
/// <param name="optoDataCount">number of objects to process</param>
public static void FIRFilterFlow(DatastreamFrame[] optoData, int optoDataCount)
{
float[] kernel = new float[] { 0.1f, 0.2f, 0.4f, 0.2f, 0.1f };
int kSize = kernel.Length;
int kSize2 = kernel.Length / 2;
float[] filtered = new float[kSize];
for (int i = 0; i < optoDataCount; i++)
{
int k = i - kSize;
if (k >= 0) optoData[k].RefFlow = filtered[i % kSize];
if (i < kSize2 || i >= optoDataCount - kSize2)
{
filtered[i % kSize] = optoData[i].RefFlow;
}
else
{
float weoightedSum = 0;
for (int j = -kSize2; j <= kSize2; j++)
weoightedSum += optoData[i + j].RefFlow * kernel[j + kSize2];
filtered[i % kSize] = weoightedSum;
}
}
for (int k = optoDataCount - kSize; k < optoDataCount; k++)
{
if (k >= 0) optoData[k].RefFlow = filtered[k % kSize];
}
}
}
}