/// /// 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() { } /// /// Parses optical telegram and returns OptoTelegramRaw object /// /// /// 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 /// ... /// /// A complete byte array data /// true = telegram OK, false = telegram NOK 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; } /// /// Alternative to UpdateFromString(...) when data are flushed /// 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()); } } /// /// 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 ... /// /// array of OptoTelegramRaw objects /// number of objects to process 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]; } } } }