/// /// Copyright (c) 2015-2021 Sensus Metering Systems /// using System; using System.Globalization; namespace Common.Iperl { public enum OptoTelegramFlags : byte { OK = 0, OK_TestStart, OK_TestEnd, InvalidTelegram, /// Wrong telegram format of checksum error SyncError, } public class OptoTelegramRaw { public static readonly int Length = 42; private static CultureInfo culture; /// /// Strobed value /// public static decimal TestStartTimestampDec; /// /// Stored values /// public OptoTelegramFlags Flags; public DateTime DateTime; /// From PC public float RefFlow; /// [m3/h] public int Counter; public Int32 EmfRaw; /// Signed EMF from iPerl opto data public Int16 MagneticFieldRaw; public Int16 FlowRaw; public UInt32 VolumeRaw; public Int64 VolumeRawExt; public Int16 Impedance; public UInt32 Timestamp; public Int64 TimestampExt; public byte CheckSum; /// /// Calculated values /// public double EMF() { return 0.000000333 * (double)EmfRaw; } public double MagneticField() { return (double)MagneticFieldRaw; } public double Flow(double scalingFactor) { return 0.225 * scalingFactor * (double)FlowRaw; } public double Volume(double scalingFactor) { return 0.0000625 * scalingFactor * (double)VolumeRawExt; } public Int32 FlipTime() { return Impedance; } public decimal TimestampDec() { return (decimal)TimestampExt / (decimal)8192; } public double VolumeDelta(double scalingFactor, OptoTelegramRaw previous) { return (previous == null) ? 0 : Volume(scalingFactor) - previous.Volume(scalingFactor); } public decimal TimeDelta() { return TimestampDec() - TestStartTimestampDec; } public string Label() { if (Flags == OptoTelegramFlags.OK_TestStart) return "#### start test ####"; else if (Flags == OptoTelegramFlags.OK_TestEnd) return "#### end of test ####"; else return string.Empty; } static OptoTelegramRaw() { culture = CultureInfo.CreateSpecificCulture("DE"); /// This is to use comma as decimal number separator } public OptoTelegramRaw() { } /// /// 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 telegram, int counter, float refFlow, ref Int64 volumeRawExtLast, ref Int64 timestampExtLast, bool isLog = false) { DateTime = DateTime.Now; Counter = counter; RefFlow = refFlow; if ((telegram == null) || (telegram.Length < Length) || (telegram[6] != '\t') || (telegram[11] != '\t') || (telegram[16] != '\t') || (telegram[23] != '\t') || (telegram[28] != '\t') || (telegram[37] != '\t') || (!isLog && (telegram[40] != '\r' || telegram[41] != '\n'))) { Flags = OptoTelegramFlags.InvalidTelegram; return false; } UInt32 uEmfRaw; bool f1 = UInt32.TryParse(telegram.Substring(0, 6), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out uEmfRaw); EmfRaw = (uEmfRaw > 0x7FFFFF) ? ((int)uEmfRaw - 0x1000000) : (int)uEmfRaw; bool f2 = Int16.TryParse(telegram.Substring(7, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out MagneticFieldRaw); bool f3 = Int16.TryParse(telegram.Substring(12, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out FlowRaw); bool f4 = UInt32.TryParse(telegram.Substring(17, 6), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out VolumeRaw); bool f5 = Int16.TryParse(telegram.Substring(24, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out Impedance); bool f6 = UInt32.TryParse(telegram.Substring(29, 8), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out Timestamp); bool f7 = byte.TryParse(telegram.Substring(38, 2), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out CheckSum); byte calculatedCheckSum = 0; for (int i = 0; i < Length - 4; i++) { calculatedCheckSum += (byte)telegram[i]; } bool allOk = f1 && f2 && f3 && f4 && f5 && f6 && f7 && (calculatedCheckSum == CheckSum); if (allOk) { /// /// Cope with 'VolumeRaw' overflow /// Int64 uncorrected = (Int64)(((UInt64)volumeRawExtLast & 0xFFFFFFFFFF000000UL) | VolumeRaw); if (Math.Abs(uncorrected - volumeRawExtLast) <= 0x800000L) { VolumeRawExt = volumeRawExtLast = uncorrected; } else if (Math.Abs(uncorrected + 0x1000000L - volumeRawExtLast) <= 0x800000L) { VolumeRawExt = volumeRawExtLast = uncorrected + 0x1000000L; } else if (Math.Abs(uncorrected - 0x1000000L - volumeRawExtLast) <= 0x800000L) { VolumeRawExt = volumeRawExtLast = uncorrected - 0x1000000L; } else { VolumeRawExt = volumeRawExtLast = uncorrected; } /// /// Cope with 'Timestamp' overflow /// uncorrected = (Int64)(((UInt64)timestampExtLast & 0xFFFFFFFF00000000UL) | Timestamp); if (Math.Abs(uncorrected - timestampExtLast) <= 0x80000000L) { TimestampExt = timestampExtLast = uncorrected; } else if (Math.Abs(uncorrected + 0x100000000L - timestampExtLast) <= 0x80000000L) { TimestampExt = timestampExtLast = uncorrected + 0x100000000L; } else if (Math.Abs(uncorrected - 0x100000000L - timestampExtLast) <= 0x80000000L) { TimestampExt = timestampExtLast = uncorrected - 0x100000000L; } else { TimestampExt = timestampExtLast = uncorrected; } } Flags = allOk ? OptoTelegramFlags.OK : OptoTelegramFlags.InvalidTelegram; return allOk; } /// /// Alternative to UpdateFromString(...) when data are flushed /// public bool UpdateFromStringDummy(string telegram) { DateTime = DateTime.Now; RefFlow = 0; if ((telegram == null) || (telegram.Length < Length) || (telegram[6] != '\t') || (telegram[11] != '\t') || (telegram[16] != '\t') || (telegram[23] != '\t') || (telegram[28] != '\t') || (telegram[37] != '\t') || (telegram[40] != '\r') || (telegram[41] != '\n')) { Flags = OptoTelegramFlags.InvalidTelegram; return false; } bool f7 = byte.TryParse(telegram.Substring(38, 2), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out CheckSum); byte calculatedCheckSum = 0; for (int i = 0; i < Length - 4; i++) { calculatedCheckSum += (byte)telegram[i]; } bool allOk = f7 && (calculatedCheckSum == CheckSum); Flags = allOk ? OptoTelegramFlags.OK : OptoTelegramFlags.InvalidTelegram; return allOk; } public void SetFlags(OptoTelegramFlags flags) { this.Flags = flags; } public string ToString(double scalingFactor, OptoTelegramRaw previous) { if (Flags == OptoTelegramFlags.SyncError) { return "Sychronization error"; } else if (Flags == OptoTelegramFlags.InvalidTelegram) { return "Invalid telegram"; } else /// if (flags == OptoTelegramFlags.OK / OptoTelegramFlags.OK_TestStart / OptoTelegramFlags.OK_TestEnd) { return string.Format("{0}:{1}:{2}.{3}\t{4} :\t{5}\t{6}\t{7}\t{8}\t{9}\t{10}\t{11}\t{12}\t{13}\t{14}\t{15}\t{16}\t{17}\t{18}\t{19}\t{20}\t{21}\t{22}", DateTime.Hour.ToString("D2"), DateTime.Minute.ToString("D2"), DateTime.Second.ToString("D2"), DateTime.Millisecond.ToString("D4"), Counter, (EmfRaw & 0x00FFFFFF).ToString("X6"), MagneticFieldRaw.ToString("X4"), FlowRaw.ToString("X4"), VolumeRaw.ToString("X6"), Impedance.ToString("X4"), Timestamp.ToString("X8"), CheckSum.ToString("X2"), EMF().ToString("F4", culture), MagneticField().ToString("F0", culture), Flow(scalingFactor).ToString("F2", culture), Volume(scalingFactor).ToString("F4", culture), FlipTime().ToString("F0", culture), TimestampDec().ToString("F4", culture), (RefFlow * 1000).ToString("F2", culture), VolumeDelta(scalingFactor, previous).ToString("F4", culture), TimeDelta().ToString("F3", culture), scalingFactor.ToString("F1", culture), Label()); } } } }