291 lines
9.9 KiB
C#
291 lines
9.9 KiB
C#
///
|
|
/// Copyright (c) 2018 Sensus Slovensko a.s.
|
|
///
|
|
using System;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
|
|
namespace TBF.BenchControl.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];
|
|
}
|
|
}
|
|
}
|
|
}
|