tbf/TBF/Rig/TestMethods/iPerlCommunication/common/OptoTelegramRaw.cs

352 lines
12 KiB
C#
Raw Normal View History

2026-03-12 13:50:47 +00:00
///
/// Copyright (c) 2015-2021 Sensus Metering Systems
///
using System;
using System.Globalization;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer;
namespace TBF.Rig.TestMethods.iPerlCommunication.common
{
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 double VolumeRaw;
public double VolumeRawExt;
public Int16 Impedance;
public double Timestamp;
public double 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()
{
}
/// <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 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;
// }
// -------- TIMESTAMP (seconds) --------
// bbbbbbbb unsigned 32 bit ASIC time stamp in 8192 ticks per second rolls over after 2^32
private const double TS_TICKS_PER_SEC = 8192.0;
private const double TS_RANGE = 4294967296.0 / TS_TICKS_PER_SEC; // 2^32 / 8192 = 524288 sec
// -------- VOLUME (liters) --------
// vvvvvv is unsigned 24-bit, 1 tick = 1/4 ml = 0.00025 L
private const double VOL_LITERS_PER_TICK = 0.00025; // liters per tick
private const double VOL_RANGE = 16777216.0 * VOL_LITERS_PER_TICK; // 2^24 * 0.00025 = 4194.304 L
// -------- VOLUME (liters) --------
private const double GAL_TO_LITER = 3.785411784;
public void UpdateFromSmart(
DiagnosticLedState4Data data,
int counter,
float refFlow,
ref double volumeRawExtLast,
ref double timestampExtLast)
{
DateTime = DateTime.Now;
Counter = counter;
RefFlow = refFlow;
FlowRaw = data.RawFlow;
VolumeRaw = data.RawVolume;
// ---- TIMESTAMP RAW (seconds, modulo TS_RANGE) ----
// If upstream conversion ever produced negative values, normalize them.
double ts = data.AsicTimestamp; // already in seconds, but wraps every TS_RANGE
ts = ts % TS_RANGE;
if (ts < 0) ts += TS_RANGE;
Timestamp = ts;
// ---------- VOLUME UNWRAP ----------
double v = VolumeRaw;
if (double.IsNaN(volumeRawExtLast))
{
VolumeRawExt = volumeRawExtLast = v;
}
else
{
// nearest-lap unwrap
//double k = Math.Round(volumeRawExtLast - v) / VOL_RANGE);
if (v < volumeRawExtLast)
{
VolumeRawExt = volumeRawExtLast = v + VOL_RANGE;
}
else
{
VolumeRawExt = volumeRawExtLast = v;
}
}
// ---------- TIMESTAMP UNWRAP (seconds) ----------
if (double.IsNaN(timestampExtLast))
{
TimestampExt = timestampExtLast = ts;
}
else
{
// robust unwrap: choose the smallest jump across the modulo boundary
double lastMod = timestampExtLast % TS_RANGE;
if (lastMod < 0) lastMod += TS_RANGE;
double delta = ts - lastMod;
if (delta < -TS_RANGE / 2.0) delta += TS_RANGE;
else if (delta > TS_RANGE / 2.0) delta -= TS_RANGE;
TimestampExt = timestampExtLast = timestampExtLast + delta;
}
}
/// <summary>
/// Alternative to UpdateFromString(...) when data are flushed
/// </summary>
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());
}
}
}
}