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

352 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

///
/// 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());
}
}
}
}