245 lines
11 KiB
C#
245 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using Xylem.Common.Hardware.WaterMeter.MagFlux.DataPackages.MeasurementRecords;
|
|
using Xylem.Common.Utils.Crc16Ccitt;
|
|
|
|
namespace Xylem.Common.Hardware.WaterMeter.MagFlux.Protocols.StreamingProtocol
|
|
{
|
|
/// <summary>
|
|
/// Data fields and definitions for GENESIS streaming protocol
|
|
/// </summary>
|
|
public class StreamingDecoder
|
|
{
|
|
private const Double MilliLitersToCmFactor = 1.0E-6;
|
|
private const Double CpuTimeToSecondsFactor = 1.0 / 0x10000;
|
|
private const Double CpuTimeOverflowS = 0x100000000 * CpuTimeToSecondsFactor;
|
|
private const Double LitersPerSecondToCmPerHourFactor = 3600.0 / 1000.0;
|
|
|
|
private const Double DefaultVolumeScaleRawPerMl = 1024.0;
|
|
private const Double DefaultVolumeFactorRawToCm = MilliLitersToCmFactor / DefaultVolumeScaleRawPerMl;
|
|
private const Double MaxGenesisAccuVolumeRaw = UInt32.MaxValue; //0x100000000; //2^32
|
|
private const Double DefaultAccuDutOverflowVolumeCm = MaxGenesisAccuVolumeRaw * DefaultVolumeFactorRawToCm;
|
|
|
|
private const Double DisplayMlSetupDutOverflowVolumeCm = 1000.0; //overflow of LCD if set to ml
|
|
private const Double TofToSecondsFactor38Bit = 1.0 / 0x4000000000; // 2^38
|
|
private const Double AmplitudeToVoltFactor = 1.0 / 0x400000 / 1000.0; // 2^22 100 0000 0000 0000 0000 0000b
|
|
private const Double PulseWidthToRelFactor = 1.0 / 0x100; // 2^8
|
|
|
|
// protocol 'l' MagFlux
|
|
private const Double MaxMagFluxVolumeRaw = UInt64.MaxValue;//0x10000000000000000; //2^64
|
|
private const Double DefaultMagFluxOverflowVolumeCm = MaxMagFluxVolumeRaw / MilliLitersToCmFactor;
|
|
/// <summary>
|
|
/// Default data for
|
|
/// </summary>
|
|
private readonly MagFluxRecord _dataMagFluxDefault = new MagFluxRecord
|
|
{
|
|
VolumeCm = 0.0,
|
|
ForwardVolumeCm = 0.0,
|
|
NegativeReverseVolumeCm = 0.0,
|
|
TimeS = 0.0,
|
|
FlowRateCmPh = 0.0,
|
|
StatusBits = 0,
|
|
UniqueId = "?",
|
|
SensorId = "?",
|
|
Crc = 0xFFFF,
|
|
OverflowVolumeCm = DefaultMagFluxOverflowVolumeCm,
|
|
OverflowTimeS = CpuTimeOverflowS,
|
|
IsValid = false
|
|
};
|
|
|
|
private MagFluxRecord _dataMagFluxRec;
|
|
private readonly Boolean _ignoreCorruptedData;
|
|
private String _sensorIdMask;
|
|
private String _uniqueIdMask;
|
|
|
|
/// <summary>
|
|
/// Constructor initializes all decoded members with default values
|
|
/// </summary>
|
|
public StreamingDecoder(Boolean ignoreCorruptedData = true)
|
|
{
|
|
_dataMagFluxRec = _dataMagFluxDefault;
|
|
_ignoreCorruptedData = ignoreCorruptedData;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Data of MagFlux
|
|
/// </summary>
|
|
public MagFluxRecord DataMagFlux
|
|
{
|
|
get; private set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set decoding mask for streaming protocol, wipes out all not matching records:
|
|
/// - SensorIdMask,
|
|
/// - UniqueIdMask
|
|
/// </summary>
|
|
/// <param name="sensorIdMask"></param>
|
|
/// <param name="uniqueIdMask"></param>
|
|
/// <returns>true if decoding was successful and data has been validated</returns>
|
|
/// <remarks date="2023-Mai-30" author="T.Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
/// <remarks date="2024-Aug-19" author="T.Wiedebusch">
|
|
/// - Changed from PcbId to SensorId.
|
|
/// </remarks>
|
|
public void SetDecodingMask(String sensorIdMask, String uniqueIdMask)
|
|
{
|
|
_sensorIdMask = sensorIdMask;
|
|
_uniqueIdMask = uniqueIdMask;
|
|
}
|
|
/// <summary>
|
|
/// Decoding the raw message
|
|
/// </summary>
|
|
/// <param name="rawMsg">message received as one line delimited with LF</param>
|
|
/// <returns>true if decoding was successful and data has been validated</returns>
|
|
/// <remarks date="2023-Mar-09" author="T.Wiedebusch">
|
|
/// - Modified using common CRC check before branching to the protocol specific decoder.
|
|
/// </remarks>
|
|
/// <remarks date="2023-May-25" author="T.Wiedebusch">
|
|
/// - Introduced reverse volume.
|
|
/// </remarks>
|
|
/// <remarks date="2024-Aug-19" author="T.Wiedebusch">
|
|
/// - Changed from PcbId to SensorId.
|
|
/// </remarks>
|
|
public Boolean DecodeMsg(String rawMsg)
|
|
{
|
|
var rawRecordIsValid = false;
|
|
try
|
|
{
|
|
//MagFlux test string:
|
|
//rawMsg = "@l 1bd911f 0 95b8d4 40cf9b5c 0 26004f5232500120373235 0 5863"
|
|
//Protocol version, DUT:Vol FWD [mL], DUT: Vol REV [ml], DUT:Time [s], DUT:Flow_LPS, DUT:StatusBitsHex,
|
|
//l, 29200671, 0, 149.7219849, 6.487714767, 0,
|
|
//DUT:UniqueId, DUT:SensorId, DUT:CRC
|
|
//4.59406E+25, 0, 22627 (0x5863)
|
|
|
|
// The received message is a string with a line delimiter.
|
|
rawMsg = rawMsg.Replace('\n', ' ');
|
|
|
|
// The raw message fields are the separated values from the received string with a blank as field separator
|
|
var rawMsgFields = rawMsg.Split(' ');
|
|
|
|
// The last element is the CRC, the CRC can be separated, calculated and validated before trying to decode the content
|
|
var rawRecordForCrc = "";
|
|
// get all fields excluding the CRC (length - 1)
|
|
for (var x = 0; x < rawMsgFields.Length - 1; x++)
|
|
{
|
|
rawRecordForCrc += rawMsgFields[x];
|
|
// add the field delimiter from raw data
|
|
rawRecordForCrc += " ";
|
|
}
|
|
|
|
// extract bytes of raw message for CRC calculation each character, CRC field is already removed
|
|
var byteArraySize = rawRecordForCrc.Length;
|
|
var byteArray = new Byte[byteArraySize];
|
|
for (var i = 0; i < byteArraySize; i++)
|
|
{
|
|
byteArray[i] = (Byte)rawRecordForCrc[i];
|
|
}
|
|
|
|
// calculate the CRC from the received data
|
|
var calculatedCrc = Crc16Ccitt.CalculateMsb1021(byteArray);
|
|
|
|
// extract received CRC
|
|
var receivedCrc = UInt16.Parse(rawMsgFields[rawMsgFields.Length - 1], NumberStyles.HexNumber);
|
|
|
|
// compare received with calculated CRC and remind valid decoding
|
|
rawRecordIsValid = calculatedCrc == receivedCrc;
|
|
|
|
switch (rawMsgFields[0])
|
|
{
|
|
case "@l":
|
|
//protocol @m starts with all contents of protocol @l and contains additional fields
|
|
//which needn't be decoded as those are used for DEBUG
|
|
case "@n":
|
|
if (rawRecordIsValid || !_ignoreCorruptedData)
|
|
{
|
|
DecodeProtocolL(ref _dataMagFluxRec, rawMsgFields);
|
|
if (string.Equals(_uniqueIdMask, _dataMagFluxRec.UniqueId) &&
|
|
string.Equals(_sensorIdMask, _dataMagFluxRec.SensorId))
|
|
{
|
|
DataMagFlux = _dataMagFluxRec;
|
|
_dataMagFluxRec.IsValid = rawRecordIsValid;
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// ignored
|
|
}
|
|
return rawRecordIsValid;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracting message from string fields for protocol 'l'
|
|
/// </summary>
|
|
/// <param name="dataMagFluxRec">reference to mag flux record</param>
|
|
/// <param name="fields">fields holding the separated values for decoding</param>
|
|
/// <remarks date="2023-Mar-09" author="T.Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
/// <remarks date="2023-Jun-06" author="T.Wiedebusch">
|
|
/// - Calculated sum volume from forward- minus reverse-volume.
|
|
/// </remarks>
|
|
/// <remarks date="2023-Jun-07" author="T.Wiedebusch">
|
|
/// - Reverse volume multiplied with -1.0 as the test bench expects a negative value for reverse volume.
|
|
/// </remarks>
|
|
/// <remarks date="2024-Oct-28" author="T.Wiedebusch">
|
|
/// - CRC is the last field of the data stream as new protocol "@n" is dynamic of size while the beginning
|
|
/// is identical to "@l".
|
|
/// </remarks>
|
|
private static void DecodeProtocolL(ref MagFluxRecord dataMagFluxRec, IList<String> fields)
|
|
{
|
|
// time stamp attachment
|
|
dataMagFluxRec.DecodedTime = DateTimeOffset.UtcNow;
|
|
|
|
// extract received CRC
|
|
dataMagFluxRec.Crc = UInt16.Parse(fields[fields.Count - 1], NumberStyles.HexNumber);
|
|
|
|
// build result values
|
|
dataMagFluxRec.ForwardVolumeCm = UInt64.Parse(fields[(Int32)ProtLsubString.FwdVolume],
|
|
NumberStyles.AllowHexSpecifier) * MilliLitersToCmFactor;
|
|
dataMagFluxRec.NegativeReverseVolumeCm = UInt64.Parse(fields[(Int32)ProtLsubString.RevVolume],
|
|
NumberStyles.AllowHexSpecifier) * MilliLitersToCmFactor * -1.0;
|
|
dataMagFluxRec.TimeS = UInt32.Parse(fields[(Int32)ProtLsubString.CpuTime],
|
|
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
|
|
dataMagFluxRec.VolumeCm = dataMagFluxRec.ForwardVolumeCm + dataMagFluxRec.NegativeReverseVolumeCm;
|
|
|
|
// parse the floating point value of the flow rate which is a string containing the hex value as 32 bit
|
|
var hex = UInt32.Parse(fields[(Int32)ProtLsubString.FlowRate], NumberStyles.AllowHexSpecifier);
|
|
var floatValues = BitConverter.GetBytes(hex);
|
|
dataMagFluxRec.FlowRateCmPh = BitConverter.ToSingle(floatValues, 0) *
|
|
LitersPerSecondToCmPerHourFactor;
|
|
|
|
// get status and identifiers
|
|
dataMagFluxRec.StatusBits = UInt32.Parse(fields[(Int32)ProtLsubString.StatusBits],
|
|
NumberStyles.AllowHexSpecifier);
|
|
|
|
// adding the hex identifier, as this is not part of the string
|
|
dataMagFluxRec.UniqueId = $"0x{fields[(Int32)ProtLsubString.UniqueId]}";
|
|
var decimalSensorId = UInt32.Parse(fields[(Int32)ProtLsubString.SensorId], NumberStyles.AllowHexSpecifier);
|
|
|
|
dataMagFluxRec.SensorId = decimalSensorId.ToString();
|
|
}
|
|
|
|
/// field position in protocol 'l' (small 'L')
|
|
private enum ProtLsubString
|
|
{
|
|
// Sorted to "@l" field positions.
|
|
ProtType,
|
|
FwdVolume,
|
|
RevVolume,
|
|
CpuTime,
|
|
FlowRate,
|
|
StatusBits,
|
|
UniqueId,
|
|
SensorId
|
|
}
|
|
}
|
|
}
|