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 { /// /// Data fields and definitions for GENESIS streaming protocol /// 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; /// /// Default data for /// 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; /// /// Constructor initializes all decoded members with default values /// public StreamingDecoder(Boolean ignoreCorruptedData = true) { _dataMagFluxRec = _dataMagFluxDefault; _ignoreCorruptedData = ignoreCorruptedData; } /// /// Data of MagFlux /// public MagFluxRecord DataMagFlux { get; private set; } /// /// Set decoding mask for streaming protocol, wipes out all not matching records: /// - SensorIdMask, /// - UniqueIdMask /// /// /// /// true if decoding was successful and data has been validated /// /// - Initial. /// /// /// - Changed from PcbId to SensorId. /// public void SetDecodingMask(String sensorIdMask, String uniqueIdMask) { _sensorIdMask = sensorIdMask; _uniqueIdMask = uniqueIdMask; } /// /// Decoding the raw message /// /// message received as one line delimited with LF /// true if decoding was successful and data has been validated /// /// - Modified using common CRC check before branching to the protocol specific decoder. /// /// /// - Introduced reverse volume. /// /// /// - Changed from PcbId to SensorId. /// 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; } /// /// Extracting message from string fields for protocol 'l' /// /// reference to mag flux record /// fields holding the separated values for decoding /// /// - Initial. /// /// /// - Calculated sum volume from forward- minus reverse-volume. /// /// /// - Reverse volume multiplied with -1.0 as the test bench expects a negative value for reverse volume. /// /// /// - CRC is the last field of the data stream as new protocol "@n" is dynamic of size while the beginning /// is identical to "@l". /// private static void DecodeProtocolL(ref MagFluxRecord dataMagFluxRec, IList 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 } } }