352 lines
12 KiB
C#
352 lines
12 KiB
C#
|
|
///
|
|||
|
|
/// 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());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|