/// /// Copyright (c) 2021 Sensus Slovensko a.s. /// using System; using System.Globalization; using System.Text; namespace TBF.Rig.RegisterReaders.S640Stream { public class DatastreamFrame { public const int FrameLength = 49; /// Including CR,LF public static double VolumeScaleFactor = 1000.0; /// Raw volume is in ml public static double TimeScaleFactor = 1000.0; /// Raw time is in ms /// /// Stored values /// public FrameFlags Flags; public DateTime DateTime; /// From PC public int Counter; public float RefFlow; /// [m3/h] char[] data = new char[FrameLength - 2]; public Int32 VolumeRaw; public Int64 VolumeRawExt; public Int64 Timestamp; public Int64 TimestampExt; /// /// Calculated values /// public double Volume() { return (double)VolumeRawExt / VolumeScaleFactor; } public double TimestampDbl() { return (double)TimestampExt / TimeScaleFactor; } 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: /// /// AAAAAAAAAAAA[tab]BBBBBBBBBB[tab]CCCCCCCCC[tab]DDDDDDDDDD[tab]EE[cr][lf] (49 bytes) /// /// Data Comment Type Calculate to decimal /// ---------------------------------------------------------------- /// AAAAAAAAAAAA PCB number Decimal /// BBBBBBBBBB Radio address Decimal /// CCCCCCCCC Meter reading Decimal Volume in ml /// DDDDDDDDDD Timestamp Decimal Time in ms /// EE Checksum Hexadecimal /// ---------------------------------------------------------------- /// /// Example: /// 540210730770 4291524429 000250215 0433411626 4F /// 540210730770 4291524429 000250215 0433411876 56 /// ... /// /// A complete byte array data /// true = telegram OK, false = telegram NOK public bool UpdateFromString(string frame, ref Int64 volumeRawExtLast, ref Int64 timestampExtLast, int counter) { DateTime = DateTime.Now; Counter = counter; RefFlow = (float)Sequences.ProcessData.RefFlow.Val; if (frame == null || frame.Length < FrameLength || frame[FrameLength - 2] != '\r' || frame[FrameLength - 1] != '\n' || frame[12] != '\t' || frame[23] != '\t' || frame[33] != '\t' || frame[44] != '\t') { Flags = FrameFlags.InvalidFrame; return false; } /// Copy data into the fixed size buffer int sum = 0; for (int i = 0; i < FrameLength - 4; i++) { data[i] = frame[i]; sum += (int)frame[i]; } data[FrameLength - 4] = frame[FrameLength - 4]; data[FrameLength - 3] = frame[FrameLength - 3]; byte checkSum; bool chksumOk = byte.TryParse(frame.Substring(FrameLength - 4, 2), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out checkSum) && checkSum == (sum & 0xFF); bool volumeOk = Int32.TryParse(frame.Substring(24, 9), out VolumeRaw); volumeRawExtLast = VolumeRawExt = VolumeRaw; bool timeOk = Int64.TryParse(frame.Substring(34, 10), out Timestamp); timestampExtLast = TimestampExt = Timestamp; bool allOk = volumeOk && timeOk && chksumOk; Flags = allOk ? FrameFlags.OK : FrameFlags.InvalidFrame; return allOk; } /// /// Alternative to UpdateFromString(...) when data are flushed /// public bool UpdateFromStringDummy(string frame, ref Int64 volumeRawExtLast, ref Int64 timestampExtLast, out Int64 pcbNumber, out Int64 radioAddress) { pcbNumber = 0; radioAddress = 0; if (frame == null || frame.Length < FrameLength || frame[FrameLength - 2] != '\r' || frame[FrameLength - 1] != '\n' || frame[12] != '\t' || frame[23] != '\t' || frame[33] != '\t' || frame[44] != '\t') { Flags = FrameFlags.InvalidFrame; return false; } /// Copy data into the fixed size buffer int sum = 0; for (int i = 0; i < FrameLength - 4; i++) sum += (int)frame[i]; byte checkSum; bool chksumOk = byte.TryParse(frame.Substring(FrameLength - 4, 2), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out checkSum) && checkSum == (sum & 0xFF); bool pcbNumberOk = Int64.TryParse(frame.Substring(0, 12), out pcbNumber); bool radioAddressOk = Int64.TryParse(frame.Substring(13, 10), out radioAddress); bool volumeOk = Int32.TryParse(frame.Substring(24, 9), out VolumeRaw); volumeRawExtLast = VolumeRawExt = VolumeRaw; bool timeOk = Int64.TryParse(frame.Substring(34, 10), out Timestamp); timestampExtLast = TimestampExt = Timestamp; return chksumOk && pcbNumberOk && radioAddressOk && volumeOk && timeOk; } public void SetFlags(FrameFlags flags) { this.Flags = flags; } public override string ToString() { 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(FrameLength - 2); for (int i = 0; i < FrameLength - 2; 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(), TimestampDbl(), 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]; } } } }