MagFlux: - Initial streaming protocol with Crc16Ccitt.ModbudRtuLsbA001 implementation
This commit is contained in:
@@ -49,6 +49,7 @@
|
|||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=METROLOGYASST/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=METROLOGYASST/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mettbl/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=mettbl/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Milli/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Milli/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Modbus/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PCBID/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PCBID/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=perc/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=perc/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Preadjustment/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Preadjustment/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|||||||
@@ -221,6 +221,7 @@
|
|||||||
<Compile Include="EventArguments\RegisterUpdateEventArgs.cs" />
|
<Compile Include="EventArguments\RegisterUpdateEventArgs.cs" />
|
||||||
<Compile Include="EventArguments\RequestResponseDataEventArgs.cs" />
|
<Compile Include="EventArguments\RequestResponseDataEventArgs.cs" />
|
||||||
<Compile Include="MeasurementRecords\CalibrationRecord.cs" />
|
<Compile Include="MeasurementRecords\CalibrationRecord.cs" />
|
||||||
|
<Compile Include="MeasurementRecords\MagFluxRecord.cs" />
|
||||||
<Compile Include="MeasurementRecords\FlowTestRecord.cs" />
|
<Compile Include="MeasurementRecords\FlowTestRecord.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="RadioConfigurationParams.cs" />
|
<Compile Include="RadioConfigurationParams.cs" />
|
||||||
|
|||||||
+1
-127
@@ -1,130 +1,4 @@
|
|||||||
//using System;
|
using System;
|
||||||
//using System.Text;
|
|
||||||
//using Xylem.Common.CommonCore.Consts;
|
|
||||||
//using Xylem.Common.Metrology.Measurements;
|
|
||||||
|
|
||||||
//namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords
|
|
||||||
//{
|
|
||||||
// /// <inheritdoc />
|
|
||||||
// /// <summary>
|
|
||||||
// /// struct to hold Led record for protocol f (contains measurement record)
|
|
||||||
// /// </summary>
|
|
||||||
// public class FlowTestRecord : IMeasurementRecord
|
|
||||||
// {
|
|
||||||
// /// <summary>
|
|
||||||
// /// actual volume in cubic meters
|
|
||||||
// /// </summary>
|
|
||||||
// public Double DisplayVolumeQm;
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// Overflow volume at Display
|
|
||||||
// /// </summary>
|
|
||||||
// public Double DisplayDutOverflowVolumeCm;
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// time converted to seconds
|
|
||||||
// /// </summary>
|
|
||||||
// public Double TimeS;
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// Overflow time
|
|
||||||
// /// </summary>
|
|
||||||
// public Double OverflowTimeS;
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// CRC16 CCITT
|
|
||||||
// /// </summary>
|
|
||||||
// public UInt16 Crc;
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// record is valid
|
|
||||||
// /// </summary>
|
|
||||||
// public Boolean IsValid;
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// Mark package as start, end, intermediate or not sync
|
|
||||||
// /// </summary>
|
|
||||||
// public SyncMarkRecord SyncMarkRecord;
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// time when package was decoded
|
|
||||||
// /// </summary>
|
|
||||||
// public DateTimeOffset DecodedTime;
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// time when package was received
|
|
||||||
// /// </summary>
|
|
||||||
// public DateTimeOffset ReceivedTime;
|
|
||||||
|
|
||||||
// public Double VolumeCm { get; set; }
|
|
||||||
// public Double OverflowVolumeCm { get; set; }
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// Get result as string
|
|
||||||
// /// </summary>
|
|
||||||
// /// <returns></returns>
|
|
||||||
// public override String ToString()
|
|
||||||
// {
|
|
||||||
// var sb = new StringBuilder();
|
|
||||||
// sb = sb.Append($"DisplayVolumeQm={DisplayVolumeQm}").Append(",")
|
|
||||||
// .Append($"DisplayDutOverflowVolumeCm={DisplayDutOverflowVolumeCm}").Append(",")
|
|
||||||
// .Append($"TimeS={TimeS}").Append(",")
|
|
||||||
// .Append($"OverflowTimeS={OverflowTimeS}").Append(",")
|
|
||||||
// .Append($"CRC={Crc}").Append(",")
|
|
||||||
// .Append($"IsValid={IsValid}").Append(",")
|
|
||||||
// .Append($"ReceivedTimeUtc={ReceivedTime}").Append(",")
|
|
||||||
// .Append($"DecodedTimeUtc={DecodedTime}").Append(",")
|
|
||||||
// .Append($"SyncMarkRecord={SyncMarkRecord}");
|
|
||||||
// return sb.ToString();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <inheritdoc />
|
|
||||||
// public Double GetVolumeCm()
|
|
||||||
// {
|
|
||||||
// return DisplayVolumeQm;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <inheritdoc />
|
|
||||||
// public Double GetTimeS()
|
|
||||||
// {
|
|
||||||
// return TimeS;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <inheritdoc />
|
|
||||||
// public SyncMarkRecord GetDataSyncMark()
|
|
||||||
// {
|
|
||||||
// return SyncMarkRecord;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <inheritdoc />
|
|
||||||
// public Int32 GetChannel()
|
|
||||||
// {
|
|
||||||
// //channel 0 is for the entire device without a physical channel assignment
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <inheritdoc />
|
|
||||||
// public Double GetOverflowVolumeCm()
|
|
||||||
// {
|
|
||||||
// return DisplayDutOverflowVolumeCm;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <inheritdoc />
|
|
||||||
// public Double GetOverflowTimeS()
|
|
||||||
// {
|
|
||||||
// return OverflowTimeS;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <inheritdoc />
|
|
||||||
// public Object Clone()
|
|
||||||
// {
|
|
||||||
// return MemberwiseClone();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Xylem.Common.Metrology.Measurements;
|
using Xylem.Common.Metrology.Measurements;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using Xylem.Common.Metrology.Measurements;
|
||||||
|
|
||||||
|
namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// struct to hold Led record for protocol L (contains measurement record)
|
||||||
|
/// </summary>
|
||||||
|
public class MagFluxRecord : MeasurementRecord
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flow rate in cubic meters per hour
|
||||||
|
/// </summary>
|
||||||
|
public Double FlowRateCmPh;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sensor Id of MagFlux
|
||||||
|
/// </summary>
|
||||||
|
public String SensorId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serial number of MagFlux
|
||||||
|
/// </summary>
|
||||||
|
public String UniqueId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Status of sensor and electronics
|
||||||
|
/// </summary>
|
||||||
|
public UInt32 StatusBits;
|
||||||
|
|
||||||
|
private const String StringDelimiter = "; \n";
|
||||||
|
/// <summary>
|
||||||
|
/// Get result as string
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override String ToString()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb = sb.Append($"Volume [m³]: {VolumeCm}").Append(StringDelimiter)
|
||||||
|
.Append($"CPU Time [s]: {TimeS}").Append(StringDelimiter)
|
||||||
|
.Append($"FlowRate [m³/h]: {FlowRateCmPh}").Append(StringDelimiter)
|
||||||
|
.Append($"Status: {StatusBits:X8}").Append(StringDelimiter)
|
||||||
|
.Append($"UniqueId: {UniqueId}").Append(StringDelimiter)
|
||||||
|
.Append($"SensorSN: {SensorId}").Append(StringDelimiter)
|
||||||
|
.Append($"CRC: {Crc:X4}").Append(StringDelimiter)
|
||||||
|
.Append($"OverflowVolume [m³]: {OverflowVolumeCm}").Append(StringDelimiter)
|
||||||
|
.Append($"OverflowTime [s]: {OverflowTimeS}").Append(StringDelimiter)
|
||||||
|
.Append($"IsValid: {IsValid}").Append(StringDelimiter)
|
||||||
|
.Append($"ReceivedTime [UTC]: {ReceivedTime}").Append(StringDelimiter)
|
||||||
|
.Append($"DecodedTime [UTC]: {DecodedTime}").Append(StringDelimiter)
|
||||||
|
.Append($"SyncMarkRecord: {SyncMarkRecord}");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+240
-179
@@ -16,21 +16,33 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.StreamingProtocol
|
|||||||
{
|
{
|
||||||
private const Double MilliLitersToQmFactor = 1.0E-6;
|
private const Double MilliLitersToQmFactor = 1.0E-6;
|
||||||
private const Double DefaultVolumeScaleRawPerMl = 1024.0;
|
private const Double DefaultVolumeScaleRawPerMl = 1024.0;
|
||||||
private const Double DefaultVolumeFactorRawToQm = MilliLitersToQmFactor /
|
private const Double DefaultVolumeFactorRawToQm = MilliLitersToQmFactor / DefaultVolumeScaleRawPerMl;
|
||||||
DefaultVolumeScaleRawPerMl;
|
|
||||||
private const Double MaxAccuVolumeRaw = 0x100000000; //2^32
|
private const Double MaxAccuVolumeRaw = 0x100000000; //2^32
|
||||||
private const Double DefaultAccuDutOverflowVolumeCm = MaxAccuVolumeRaw *
|
private const Double DefaultAccuDutOverflowVolumeCm = MaxAccuVolumeRaw * DefaultVolumeFactorRawToQm;
|
||||||
DefaultVolumeFactorRawToQm;
|
|
||||||
|
|
||||||
private const Double CpuTimeToSecondsFactor = 1.0 / 0x10000;
|
private const Double CpuTimeToSecondsFactor = 1.0 / 0x10000;
|
||||||
private const Double CpuTimeOverflowS = 0x100000000 * CpuTimeToSecondsFactor;
|
private const Double CpuTimeOverflowS = 0x100000000 * CpuTimeToSecondsFactor;
|
||||||
private const Double DisplayMlSetupDutOverflowVolumeCm = 1000.0; //overflow of LCD if set to ml
|
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 TofToSecondsFactor38Bit = 1.0 / 0x4000000000; // 2^38
|
||||||
private const Double AmplitudeToVoltFactor = 1.0 / 0x400000 / 1000.0; // 2^22
|
private const Double AmplitudeToVoltFactor = 1.0 / 0x400000 / 1000.0; // 2^22
|
||||||
private const Double PulseWidthToRelFactor = 1.0 / 0x100; // 2^8
|
private const Double PulseWidthToRelFactor = 1.0 / 0x100; // 2^8
|
||||||
private const UInt16 IsValidProtG = 1;
|
private const Double LitersPerSecondToCmPerHourFactor = 3600.0 / 1000.0;
|
||||||
private const UInt16 IsValidProtH = 0;
|
|
||||||
private CalibrationRecord _dataCalibRec;
|
/// <summary>
|
||||||
|
/// Default data for
|
||||||
|
/// </summary>
|
||||||
|
private readonly MagFluxRecord _dataMagFluxDefault = new MagFluxRecord
|
||||||
|
{
|
||||||
|
VolumeCm = 0.0,
|
||||||
|
TimeS = 0.0,
|
||||||
|
FlowRateCmPh = 0.0,
|
||||||
|
StatusBits = 0,
|
||||||
|
UniqueId = "?",
|
||||||
|
SensorId = "?",
|
||||||
|
Crc = 0xFFFF,
|
||||||
|
OverflowVolumeCm = DisplayMlSetupDutOverflowVolumeCm, //TODO THW it is a UInt64 overflow, please change
|
||||||
|
OverflowTimeS = CpuTimeOverflowS,
|
||||||
|
IsValid = false
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default data for
|
/// Default data for
|
||||||
@@ -74,13 +86,9 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.StreamingProtocol
|
|||||||
IsValid = false
|
IsValid = false
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly FlowTestRecord _dataFlowTestRec;
|
private CalibrationRecord _dataCalibRec;
|
||||||
|
private FlowTestRecord _dataFlowTestRec;
|
||||||
private String _rawMsgForCrc;
|
private MagFluxRecord _dataMagFluxRec;
|
||||||
/// <summary>
|
|
||||||
/// Calibration data
|
|
||||||
/// </summary>
|
|
||||||
public CalibrationRecord DataCalib = new CalibrationRecord();
|
|
||||||
private readonly Boolean _ignoreCorruptedData;
|
private readonly Boolean _ignoreCorruptedData;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -88,344 +96,397 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.StreamingProtocol
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public StreamingDecoder(Boolean ignoreCorruptedData = true)
|
public StreamingDecoder(Boolean ignoreCorruptedData = true)
|
||||||
{
|
{
|
||||||
|
_dataMagFluxRec = _dataMagFluxDefault;
|
||||||
_dataFlowTestRec = _dataDefault;
|
_dataFlowTestRec = _dataDefault;
|
||||||
_dataCalibRec = _rawDataDefault;
|
_dataCalibRec = _rawDataDefault;
|
||||||
_ignoreCorruptedData = ignoreCorruptedData;
|
_ignoreCorruptedData = ignoreCorruptedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calibration data
|
||||||
|
/// </summary>
|
||||||
|
public CalibrationRecord DataCalib
|
||||||
|
{
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data of MagFlux
|
||||||
|
/// </summary>
|
||||||
|
public MagFluxRecord DataMagFlux
|
||||||
|
{
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Flow test data
|
/// Flow test data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FlowTestRecord DataFlowTest { get; private set; }
|
public FlowTestRecord DataFlowTest
|
||||||
|
{
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Decoding the raw message
|
/// Decoding the raw message
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rawMsg">message received as one line delimited with LF</param>
|
/// <param name="rawMsg">message received as one line delimited with LF</param>
|
||||||
/// <returns>true if decoding was successful and data has been validated</returns>
|
/// <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>
|
||||||
public Boolean DecodeMsg(String rawMsg)
|
public Boolean DecodeMsg(String rawMsg)
|
||||||
{
|
{
|
||||||
var isSuccessfulDecoded = false;
|
var rawRecordIsValid = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// save the raw message for CRC calculation before separation to fields
|
// save the raw message for CRC calculation before separation to fields
|
||||||
_rawMsgForCrc = rawMsg;
|
//_rawMsgForCrc = rawMsg;
|
||||||
// extract message and split it to fields
|
// extract message and split it to fields
|
||||||
|
|
||||||
|
//MagFlux test string from Jan Bennett 2023-03-09
|
||||||
|
//rawMsg = "@l 34c21 e6e56 3c40f883 0 3c005d5232500120373235 0 5790"
|
||||||
|
|
||||||
//Dn 50
|
|
||||||
|
//DN50
|
||||||
//2022-07-21 07:22:06.9871 | @f 8497D 062E4216 9B2A
|
//2022-07-21 07:22:06.9871 | @f 8497D 062E4216 9B2A
|
||||||
//2022-07-21 07:22:06.9871 | @h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331
|
//2022-07-21 07:22:06.9871 | @h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331
|
||||||
//2022-07-21 07:22:07.0171 | @h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD
|
//2022-07-21 07:22:07.0171 | @h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD
|
||||||
//rawMsg = "@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E";
|
//rawMsg = "@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E";
|
||||||
|
|
||||||
//DN80
|
//DN80
|
||||||
// 2022-04-28 15:19:54.9167 | @f AA754B 4D0CEE78 5D89
|
//2022-04-28 15:19:54.9167 | @f AA754B 4D0CEE78 5D89
|
||||||
//2022-04-28 15:19:54.9337 | @h 1 0 0EF4A130 0002AAB8 000DAC8C 02B98FBD 00000200 00000FFC 643BCF30 63C9CFF6 00015096 0C 4D0CF3CC 3E87
|
//2022-04-28 15:19:54.9337 | @h 1 0 0EF4A130 0002AAB8 000DAC8C 02B98FBD 00000200 00000FFC 643BCF30 63C9CFF6 00015096 0C 4D0CF3CC 3E87
|
||||||
//2022-04-28 15:19:54.9497 | @h 2 0 0EFA3000 000283E7 000CE580 E3D5EFFA 00000200 00001000 6DC0A84E 6CD462C2 00015096 0C 4D0CF922 1646
|
//2022-04-28 15:19:54.9497 | @h 2 0 0EFA3000 000283E7 000CE580 E3D5EFFA 00000200 00001000 6DC0A84E 6CD462C2 00015096 0C 4D0CF922 1646
|
||||||
//2022-04-28 15:19:54.9627 | @h 3 0 0EFF7F91 0002AC39 000DB43D FE1C0254 00000200 00000FFE 6C932DE6 6D20005D 00015096 0C 4D0CFE76 868A
|
//2022-04-28 15:19:54.9627 | @h 3 0 0EFF7F91 0002AC39 000DB43D FE1C0254 00000200 00000FFE 6C932DE6 6D20005D 00015096 0C 4D0CFE76 868A
|
||||||
//2022-04-28 15:19:54.9787 | @f AA7C01 4D0CFE76 B08F
|
//2022-04-28 15:19:54.9787 | @f AA7C01 4D0CFE76 B08F
|
||||||
// rawMsg = "@h 3 0 0EFF7F91 0002AC39 000DB43D FE1C0254 00000200 00000FFE 6C932DE6 6D20005D 00015096 0C 4D0CFE76 868A ";
|
// rawMsg = "@h 3 0 0EFF7F91 0002AC39 000DB43D FE1C0254 00000200 00000FFE 6C932DE6 6D20005D 00015096 0C 4D0CFE76 868A ";
|
||||||
|
|
||||||
|
// The received message is a string with a line delimiter.
|
||||||
rawMsg = rawMsg.Replace('\n', ' ');
|
rawMsg = rawMsg.Replace('\n', ' ');
|
||||||
var field = rawMsg.Split(' ');
|
|
||||||
|
|
||||||
switch (field[0])
|
// 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
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
var calculatedCrc = rawMsgFields[0] == "@l" ? // for MagFlux protocol "l"
|
||||||
|
Crc16Ccitt.ModbusRtuLsbA001(byteArray) :
|
||||||
|
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":
|
||||||
|
_dataMagFluxRec.IsValid = rawRecordIsValid;
|
||||||
|
if (rawRecordIsValid || !_ignoreCorruptedData)
|
||||||
|
{
|
||||||
|
DecodeProtocolL(ref _dataMagFluxRec, rawMsgFields);
|
||||||
|
DataMagFlux = _dataMagFluxRec;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case "@f":
|
case "@f":
|
||||||
isSuccessfulDecoded = DecodeProtocolF(field);
|
_dataFlowTestRec.IsValid = rawRecordIsValid;
|
||||||
if (isSuccessfulDecoded || !_ignoreCorruptedData)
|
if (rawRecordIsValid || !_ignoreCorruptedData)
|
||||||
{
|
{
|
||||||
|
DecodeProtocolF(ref _dataFlowTestRec, rawMsgFields);
|
||||||
DataFlowTest = _dataFlowTestRec;
|
DataFlowTest = _dataFlowTestRec;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "@g":
|
case "@g":
|
||||||
|
_dataCalibRec.IsValid = rawRecordIsValid;
|
||||||
|
if (rawRecordIsValid || !_ignoreCorruptedData)
|
||||||
{
|
{
|
||||||
isSuccessfulDecoded = DecodeProtocolG(ref _dataCalibRec, field);
|
DecodeProtocolG(ref _dataCalibRec, rawMsgFields);
|
||||||
if (isSuccessfulDecoded || !_ignoreCorruptedData)
|
|
||||||
{
|
|
||||||
DataCalib = _dataCalibRec;
|
DataCalib = _dataCalibRec;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case "@h":
|
case "@h":
|
||||||
|
_dataCalibRec.IsValid = rawRecordIsValid;
|
||||||
|
if (rawRecordIsValid || !_ignoreCorruptedData)
|
||||||
{
|
{
|
||||||
|
DecodeProtocolH(ref _dataCalibRec, rawMsgFields);
|
||||||
|
|
||||||
isSuccessfulDecoded = DecodeProtocolH(ref _dataCalibRec, field);
|
|
||||||
if (isSuccessfulDecoded || !_ignoreCorruptedData)
|
|
||||||
{
|
|
||||||
DataCalib = _dataCalibRec;
|
DataCalib = _dataCalibRec;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
return isSuccessfulDecoded;
|
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>
|
||||||
|
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[(Int32)ProtLsubString.Crc], NumberStyles.HexNumber);
|
||||||
|
|
||||||
|
// build result values, the display volume can be positive or negative!
|
||||||
|
dataMagFluxRec.VolumeCm = UInt64.Parse(fields[(Int32)ProtLsubString.DisplayVolume],
|
||||||
|
NumberStyles.AllowHexSpecifier) * MilliLitersToQmFactor;
|
||||||
|
dataMagFluxRec.TimeS = UInt32.Parse(fields[(Int32)ProtLsubString.CpuTime],
|
||||||
|
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
dataMagFluxRec.UniqueId = fields[(Int32)ProtLsubString.UniqueId];
|
||||||
|
dataMagFluxRec.SensorId = fields[(Int32)ProtLsubString.SensorId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extracting message from string fields for protocol 'f'
|
/// Extracting message from string fields for protocol 'f'
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="field">Separated fields containing the measurement as string</param>
|
/// <param name="dataFlowTestRec">reference to flow test record</param>
|
||||||
|
/// <param name="fields">Separated fields containing the measurement as string</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private Boolean DecodeProtocolF(IList<String> field)
|
/// <remarks date="2023-Mar-09" author="T.Wiedebusch">
|
||||||
|
/// - Modified using common CRC check in advance.
|
||||||
|
/// </remarks>
|
||||||
|
private static void DecodeProtocolF(ref FlowTestRecord dataFlowTestRec, IList<String> fields)
|
||||||
{
|
{
|
||||||
_dataFlowTestRec.DecodedTime = DateTimeOffset.UtcNow;
|
// time stamp attachment
|
||||||
_dataFlowTestRec.IsValid = true;
|
dataFlowTestRec.DecodedTime = DateTimeOffset.UtcNow;
|
||||||
//extract bytes of raw message for CRC calculation each character removing CRC (4 char)
|
|
||||||
var byteArraySize = _rawMsgForCrc.Length - 4;
|
|
||||||
var byteArray = new Byte[byteArraySize];
|
|
||||||
for (var i = 0; i < byteArraySize; i++)
|
|
||||||
{
|
|
||||||
byteArray[i] = (Byte)_rawMsgForCrc[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
_rawMsgForCrc = null;
|
// extract received CRC
|
||||||
var calculatedCrc = Crc16Ccitt.CalculateMsb1021(byteArray);
|
dataFlowTestRec.Crc = ushort.Parse(fields[(Int32)ProtFsubString.Crc],
|
||||||
|
|
||||||
//extract received CRC
|
|
||||||
_dataFlowTestRec.Crc = ushort.Parse(field[(Int32)ProtFsubString.Crc],
|
|
||||||
NumberStyles.HexNumber);
|
NumberStyles.HexNumber);
|
||||||
//compare received with calculated CRC
|
// build result values, the display volume can be positive or negative!
|
||||||
if (calculatedCrc != _dataFlowTestRec.Crc)
|
dataFlowTestRec.VolumeCm = int.Parse(fields[(Int32)ProtFsubString.DisplayVolume],
|
||||||
{
|
|
||||||
_dataFlowTestRec.IsValid = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_dataFlowTestRec.IsValid = true;
|
|
||||||
//build result values, the display volume can be positive or negative!
|
|
||||||
_dataFlowTestRec.VolumeCm = int.Parse(field[(Int32)ProtFsubString.DisplayVolume],
|
|
||||||
NumberStyles.AllowHexSpecifier) * MilliLitersToQmFactor;
|
NumberStyles.AllowHexSpecifier) * MilliLitersToQmFactor;
|
||||||
_dataFlowTestRec.TimeS = uint.Parse(field[(Int32)ProtFsubString.CpuTime],
|
dataFlowTestRec.TimeS = uint.Parse(fields[(Int32)ProtFsubString.CpuTime],
|
||||||
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
|
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
|
||||||
return _dataFlowTestRec.IsValid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extracting message from string fields to individual raw channel for protocol 'g'
|
/// Extracting message from string fields to individual raw channel for protocol 'g'
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dataProtGRec">Reference to result structure for raw data for one channel</param>
|
/// <param name="dataProtGRec">Reference to result structure for raw data for one channel</param>
|
||||||
/// <param name="field">Separated fields containing the measurement as string</param>
|
/// <param name="fields">Separated fields containing the measurement as string</param>
|
||||||
/// <returns>true if protocol is valid</returns>
|
/// <returns>true if protocol is valid</returns>
|
||||||
/// <remarks date="2018-Mar-22" author="T.Wiedebusch">
|
/// <remarks date="2018-Mar-22" author="T.Wiedebusch">
|
||||||
/// - Usage of VolumeFactorRawToQm and calculation of AccuDutOverflowVolumeCm
|
/// - Usage of VolumeFactorRawToQm and calculation of AccuDutOverflowVolumeCm
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private Boolean DecodeProtocolG(ref CalibrationRecord dataProtGRec, IList<String> field)
|
/// <remarks date="2023-Mar-09" author="T.Wiedebusch">
|
||||||
|
/// - Modified using common CRC check in advance.
|
||||||
|
/// </remarks>
|
||||||
|
private static void DecodeProtocolG(ref CalibrationRecord dataProtGRec, IList<String> fields)
|
||||||
{
|
{
|
||||||
|
// time stamp attachment
|
||||||
dataProtGRec.DecodedTime = DateTimeOffset.UtcNow;
|
dataProtGRec.DecodedTime = DateTimeOffset.UtcNow;
|
||||||
dataProtGRec.IsValid = false;
|
|
||||||
|
|
||||||
//extract bytes of raw message for CRC calculation each character removing CRC (4 char)
|
// extract received CRC
|
||||||
var byteArraySize = _rawMsgForCrc.Length - 4;
|
dataProtGRec.Crc = ushort.Parse(fields[(Int32)ProtGsubString.Crc],
|
||||||
var byteArray = new Byte[byteArraySize];
|
|
||||||
for (var i = 0; i < byteArraySize; i++)
|
|
||||||
{
|
|
||||||
byteArray[i] = (Byte)_rawMsgForCrc[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
_rawMsgForCrc = null;
|
|
||||||
var calculatedCrc = Crc16Ccitt.CalculateMsb1021(byteArray);
|
|
||||||
|
|
||||||
//extract received CRC
|
|
||||||
dataProtGRec.Crc = ushort.Parse(field[(Int32)ProtGsubString.Crc],
|
|
||||||
NumberStyles.HexNumber);
|
NumberStyles.HexNumber);
|
||||||
|
|
||||||
dataProtGRec.Channel = ushort.Parse(field[(Int32)ProtGsubString.ChanNo],
|
dataProtGRec.Channel = ushort.Parse(fields[(Int32)ProtGsubString.ChanNo],
|
||||||
NumberStyles.HexNumber);
|
NumberStyles.HexNumber);
|
||||||
dataProtGRec.Validation = ushort.Parse(field[(Int32)ProtGsubString.Validation],
|
dataProtGRec.Validation = ushort.Parse(fields[(Int32)ProtGsubString.Validation],
|
||||||
NumberStyles.HexNumber);
|
NumberStyles.HexNumber);
|
||||||
|
|
||||||
//compare received with calculated CRC
|
|
||||||
if (calculatedCrc != dataProtGRec.Crc || IsValidProtG != dataProtGRec.Validation)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//decode rest of message only if CRC and mode are valid
|
|
||||||
dataProtGRec.IsValid = true;
|
|
||||||
|
|
||||||
// Delta time of flight
|
// Delta time of flight
|
||||||
dataProtGRec.DeltaTimeOfFlightS = int.Parse(field[(Int32)ProtGsubString.Dtof],
|
dataProtGRec.DeltaTimeOfFlightS = int.Parse(fields[(Int32)ProtGsubString.Dtof],
|
||||||
NumberStyles.AllowHexSpecifier) * TofToSecondsFactor38Bit;
|
NumberStyles.AllowHexSpecifier) * TofToSecondsFactor38Bit;
|
||||||
|
|
||||||
// Delta raw volume between last sample
|
// Delta raw volume between last sample
|
||||||
dataProtGRec.DeltaVolumeRaw = uint.Parse(field[(Int32)ProtGsubString.RawDVolume],
|
dataProtGRec.DeltaVolumeRaw = uint.Parse(fields[(Int32)ProtGsubString.RawDVolume],
|
||||||
NumberStyles.AllowHexSpecifier);
|
NumberStyles.AllowHexSpecifier);
|
||||||
|
|
||||||
//volume scaling
|
// volume scaling
|
||||||
var volumeRawScale = uint.Parse(field[(Int32)ProtGsubString.VolumeScale],
|
var volumeRawScale = uint.Parse(fields[(Int32)ProtGsubString.VolumeScale],
|
||||||
NumberStyles.AllowHexSpecifier);
|
NumberStyles.AllowHexSpecifier);
|
||||||
|
|
||||||
dataProtGRec.VolumeScaleRawPerMl = volumeRawScale != 0 ? volumeRawScale : DefaultVolumeScaleRawPerMl;
|
dataProtGRec.VolumeScaleRawPerMl = volumeRawScale != 0 ? volumeRawScale : DefaultVolumeScaleRawPerMl;
|
||||||
dataProtGRec.VolumeFactorRawToQm = MilliLitersToQmFactor / dataProtGRec.VolumeScaleRawPerMl;
|
dataProtGRec.VolumeFactorRawToQm = MilliLitersToQmFactor / dataProtGRec.VolumeScaleRawPerMl;
|
||||||
dataProtGRec.OverflowVolumeCm = MaxAccuVolumeRaw * dataProtGRec.VolumeFactorRawToQm;
|
dataProtGRec.OverflowVolumeCm = MaxAccuVolumeRaw * dataProtGRec.VolumeFactorRawToQm;
|
||||||
|
|
||||||
//Calculate volume in cubic meters out of the raw volume
|
// Calculate volume in cubic meters out of the raw volume
|
||||||
dataProtGRec.DeltaVolumeQm = dataProtGRec.DeltaVolumeRaw * dataProtGRec.VolumeFactorRawToQm;
|
dataProtGRec.DeltaVolumeQm = dataProtGRec.DeltaVolumeRaw * dataProtGRec.VolumeFactorRawToQm;
|
||||||
|
|
||||||
//accumulated volume for each channel received from water meter scaled with volumeScale
|
// accumulated volume for each channel received from water meter scaled with volumeScale
|
||||||
//the volume can just be positive
|
// the volume can just be positive
|
||||||
dataProtGRec.AccuVolumeRaw = uint.Parse(field[(Int32)ProtGsubString.AccuVolume],
|
dataProtGRec.AccuVolumeRaw = uint.Parse(fields[(Int32)ProtGsubString.AccuVolume],
|
||||||
NumberStyles.AllowHexSpecifier);
|
NumberStyles.AllowHexSpecifier);
|
||||||
dataProtGRec.VolumeCm = dataProtGRec.AccuVolumeRaw * dataProtGRec.VolumeFactorRawToQm;
|
dataProtGRec.VolumeCm = dataProtGRec.AccuVolumeRaw * dataProtGRec.VolumeFactorRawToQm;
|
||||||
|
|
||||||
//Sample interval
|
// Sample interval
|
||||||
dataProtGRec.SampleIntervalS = uint.Parse(field[(Int32)ProtGsubString.SampleInterval],
|
dataProtGRec.SampleIntervalS = uint.Parse(fields[(Int32)ProtGsubString.SampleInterval],
|
||||||
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
|
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
|
||||||
|
|
||||||
//amplitude for high threshold in V
|
// amplitude for high threshold in V
|
||||||
dataProtGRec.AmplitudeUpV = uint.Parse(field[(Int32)ProtGsubString.AmplitudeUp],
|
dataProtGRec.AmplitudeUpV = uint.Parse(fields[(Int32)ProtGsubString.AmplitudeUp],
|
||||||
NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor;
|
NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor;
|
||||||
|
|
||||||
//amplitude for low threshold in V
|
// amplitude for low threshold in V
|
||||||
dataProtGRec.AmplitudeDownV = uint.Parse(field[(Int32)ProtGsubString.AmplitudeDown],
|
dataProtGRec.AmplitudeDownV = uint.Parse(fields[(Int32)ProtGsubString.AmplitudeDown],
|
||||||
NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor;
|
NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor;
|
||||||
|
|
||||||
//pulse width ratio high threshold
|
// pulse width ratio high threshold
|
||||||
dataProtGRec.PulseWidthRatioUp = uint.Parse(field[(Int32)ProtGsubString.PulseWidthRatioUp],
|
dataProtGRec.PulseWidthRatioUp = uint.Parse(fields[(Int32)ProtGsubString.PulseWidthRatioUp],
|
||||||
NumberStyles.AllowHexSpecifier) * PulseWidthToRelFactor;
|
NumberStyles.AllowHexSpecifier) * PulseWidthToRelFactor;
|
||||||
|
|
||||||
//pulse width ratio low threshold
|
// pulse width ratio low threshold
|
||||||
dataProtGRec.PulseWidthRatioDown = uint.Parse(field[(Int32)ProtGsubString.PulseWidthRatioDown],
|
dataProtGRec.PulseWidthRatioDown = uint.Parse(fields[(Int32)ProtGsubString.PulseWidthRatioDown],
|
||||||
NumberStyles.AllowHexSpecifier) * PulseWidthToRelFactor;
|
NumberStyles.AllowHexSpecifier) * PulseWidthToRelFactor;
|
||||||
|
|
||||||
//raw temperature
|
// raw temperature
|
||||||
dataProtGRec.TemperatureRaw = int.Parse(field[(Int32)ProtGsubString.RawTemperature],
|
dataProtGRec.TemperatureRaw = int.Parse(fields[(Int32)ProtGsubString.RawTemperature],
|
||||||
NumberStyles.AllowHexSpecifier);
|
NumberStyles.AllowHexSpecifier);
|
||||||
|
|
||||||
//temperature scaling
|
// temperature scaling
|
||||||
dataProtGRec.TemperaturePowFactor = uint.Parse(field[(Int32)ProtGsubString.TemperatureScale],
|
dataProtGRec.TemperaturePowFactor = uint.Parse(fields[(Int32)ProtGsubString.TemperatureScale],
|
||||||
NumberStyles.AllowHexSpecifier);
|
NumberStyles.AllowHexSpecifier);
|
||||||
|
|
||||||
//Calculate temperature
|
// Calculate temperature
|
||||||
dataProtGRec.TemperatureDegC = dataProtGRec.TemperatureRaw /
|
dataProtGRec.TemperatureDegC = dataProtGRec.TemperatureRaw /
|
||||||
Math.Pow(2.0, dataProtGRec.TemperaturePowFactor);
|
Math.Pow(2.0, dataProtGRec.TemperaturePowFactor);
|
||||||
|
|
||||||
//absolute CPU time, started at LED mode 3 activation
|
// absolute CPU time, started at LED mode 3 activation
|
||||||
dataProtGRec.TimeS = uint.Parse(field[(Int32)ProtGsubString.CpuTime],
|
dataProtGRec.TimeS = uint.Parse(fields[(Int32)ProtGsubString.CpuTime],
|
||||||
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
|
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extracting message from string fields to individual raw channel for protocol 'g'
|
/// Extracting message from string fields to individual raw channel for protocol 'g'
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dataProtHRec">Reference to result structure for raw data for one channel</param>
|
/// <param name="dataProtHRec">Reference to result structure for raw data for one channel</param>
|
||||||
/// <param name="field">Separated fields containing the measurement as string</param>
|
/// <param name="fields">Separated fields containing the measurement as string</param>
|
||||||
/// <returns>true if protocol is valid</returns>
|
/// <returns>true if protocol is valid</returns>
|
||||||
/// <remarks date="2018-Mar-22" author="T.Wiedebusch">
|
/// <remarks date="2018-Mar-22" author="T.Wiedebusch">
|
||||||
/// - Usage of VolumeFactorRawToQm and calculation of AccuDutOverflowVolumeCm
|
/// - Usage of VolumeFactorRawToQm and calculation of AccuDutOverflowVolumeCm
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private Boolean DecodeProtocolH(ref CalibrationRecord dataProtHRec, IList<String> field)
|
/// <remarks date="2023-Mar-09" author="T.Wiedebusch">
|
||||||
|
/// - Modified using common CRC check in advance.
|
||||||
|
/// </remarks>
|
||||||
|
private static void DecodeProtocolH(ref CalibrationRecord dataProtHRec, IList<String> fields)
|
||||||
{
|
{
|
||||||
|
// time stamp attachment
|
||||||
|
|
||||||
dataProtHRec.DecodedTime = DateTimeOffset.UtcNow;
|
dataProtHRec.DecodedTime = DateTimeOffset.UtcNow;
|
||||||
dataProtHRec.IsValid = false;
|
|
||||||
|
|
||||||
//extract bytes of raw message for CRC calculation each character removing CRC (4 char)
|
// extract received CRC
|
||||||
var byteArraySize = _rawMsgForCrc.Length - 4;
|
dataProtHRec.Crc = ushort.Parse(fields[(Int32)ProtHsubString.Crc],
|
||||||
var byteArray = new Byte[byteArraySize];
|
|
||||||
for (var i = 0; i < byteArraySize; i++)
|
|
||||||
{
|
|
||||||
byteArray[i] = (Byte)_rawMsgForCrc[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
_rawMsgForCrc = null;
|
|
||||||
var calculatedCrc = Crc16Ccitt.CalculateMsb1021(byteArray);
|
|
||||||
|
|
||||||
//extract received CRC
|
|
||||||
dataProtHRec.Crc = ushort.Parse(field[(Int32)ProtHsubString.Crc],
|
|
||||||
NumberStyles.HexNumber);
|
NumberStyles.HexNumber);
|
||||||
|
|
||||||
dataProtHRec.Channel = ushort.Parse(field[(Int32)ProtHsubString.ChanNo],
|
dataProtHRec.Channel = ushort.Parse(fields[(Int32)ProtHsubString.ChanNo],
|
||||||
NumberStyles.HexNumber);
|
NumberStyles.HexNumber);
|
||||||
dataProtHRec.Validation = ushort.Parse(field[(Int32)ProtHsubString.Validation],
|
dataProtHRec.Validation = ushort.Parse(fields[(Int32)ProtHsubString.Validation],
|
||||||
NumberStyles.HexNumber);
|
NumberStyles.HexNumber);
|
||||||
|
|
||||||
|
|
||||||
//compare received with calculated CRC
|
|
||||||
if (_ignoreCorruptedData &&
|
|
||||||
(calculatedCrc != dataProtHRec.Crc || IsValidProtH != dataProtHRec.Validation))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// decode rest of message only if CRC and mode are valid
|
|
||||||
dataProtHRec.IsValid = true;
|
|
||||||
|
|
||||||
|
|
||||||
// Total time of flight
|
// Total time of flight
|
||||||
dataProtHRec.TotalTimeOfFlightS = int.Parse(field[(Int32)ProtHsubString.Ttof],
|
dataProtHRec.TotalTimeOfFlightS = int.Parse(fields[(Int32)ProtHsubString.Ttof],
|
||||||
NumberStyles.AllowHexSpecifier) * TofToSecondsFactor38Bit;
|
NumberStyles.AllowHexSpecifier) * TofToSecondsFactor38Bit;
|
||||||
//Delta time of flight
|
// Delta time of flight
|
||||||
dataProtHRec.DeltaTimeOfFlightS = int.Parse(field[(Int32)ProtHsubString.Dtof],
|
dataProtHRec.DeltaTimeOfFlightS = int.Parse(fields[(Int32)ProtHsubString.Dtof],
|
||||||
NumberStyles.AllowHexSpecifier) * TofToSecondsFactor38Bit;
|
NumberStyles.AllowHexSpecifier) * TofToSecondsFactor38Bit;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dataProtHRec.RawTotalTimeOfFlight =
|
dataProtHRec.RawTotalTimeOfFlight =
|
||||||
int.Parse(field[(Int32)ProtHsubString.Ttof], NumberStyles.AllowHexSpecifier);
|
int.Parse(fields[(Int32)ProtHsubString.Ttof], NumberStyles.AllowHexSpecifier);
|
||||||
dataProtHRec.RawDeltaTimeOfFlight = int.Parse(field[(Int32)ProtHsubString.Dtof], NumberStyles.AllowHexSpecifier);
|
dataProtHRec.RawDeltaTimeOfFlight = int.Parse(fields[(Int32)ProtHsubString.Dtof], NumberStyles.AllowHexSpecifier);
|
||||||
|
|
||||||
//Delta raw volume between two samples
|
// Delta raw volume between two samples
|
||||||
dataProtHRec.DeltaVolumeRaw = uint.Parse(field[(Int32)ProtHsubString.RawDVolume],
|
dataProtHRec.DeltaVolumeRaw = uint.Parse(fields[(Int32)ProtHsubString.RawDVolume],
|
||||||
NumberStyles.AllowHexSpecifier);
|
NumberStyles.AllowHexSpecifier);
|
||||||
|
|
||||||
//volume scaling
|
// volume scaling
|
||||||
var volumeRawScale = uint.Parse(field[(Int32)ProtHsubString.VolumeScale],
|
var volumeRawScale = uint.Parse(fields[(Int32)ProtHsubString.VolumeScale],
|
||||||
NumberStyles.AllowHexSpecifier);
|
NumberStyles.AllowHexSpecifier);
|
||||||
|
|
||||||
dataProtHRec.VolumeScaleRawPerMl = volumeRawScale != 0 ? volumeRawScale : DefaultVolumeScaleRawPerMl;
|
dataProtHRec.VolumeScaleRawPerMl = volumeRawScale != 0 ? volumeRawScale : DefaultVolumeScaleRawPerMl;
|
||||||
dataProtHRec.VolumeFactorRawToQm = MilliLitersToQmFactor / dataProtHRec.VolumeScaleRawPerMl;
|
dataProtHRec.VolumeFactorRawToQm = MilliLitersToQmFactor / dataProtHRec.VolumeScaleRawPerMl;
|
||||||
dataProtHRec.OverflowVolumeCm = MaxAccuVolumeRaw * dataProtHRec.VolumeFactorRawToQm;
|
dataProtHRec.OverflowVolumeCm = MaxAccuVolumeRaw * dataProtHRec.VolumeFactorRawToQm;
|
||||||
|
|
||||||
//Calculate volume in cubic meters out of the raw volume
|
// Calculate volume in cubic meters out of the raw volume
|
||||||
dataProtHRec.DeltaVolumeQm = dataProtHRec.DeltaVolumeRaw * dataProtHRec.VolumeFactorRawToQm;
|
dataProtHRec.DeltaVolumeQm = dataProtHRec.DeltaVolumeRaw * dataProtHRec.VolumeFactorRawToQm;
|
||||||
|
|
||||||
//accumulated volume for each channel received from water meter scaled with volumeScale
|
// accumulated volume for each channel received from water meter scaled with volumeScale
|
||||||
//the volume can just be positive
|
// the volume can just be positive
|
||||||
dataProtHRec.AccuVolumeRaw = uint.Parse(field[(Int32)ProtHsubString.AccuVolume],
|
dataProtHRec.AccuVolumeRaw = uint.Parse(fields[(Int32)ProtHsubString.AccuVolume],
|
||||||
NumberStyles.AllowHexSpecifier);
|
NumberStyles.AllowHexSpecifier);
|
||||||
dataProtHRec.VolumeCm = dataProtHRec.AccuVolumeRaw * dataProtHRec.VolumeFactorRawToQm;
|
dataProtHRec.VolumeCm = dataProtHRec.AccuVolumeRaw * dataProtHRec.VolumeFactorRawToQm;
|
||||||
|
|
||||||
//Sample interval
|
// Sample interval
|
||||||
dataProtHRec.SampleIntervalS = uint.Parse(field[(Int32)ProtHsubString.SampleInterval],
|
dataProtHRec.SampleIntervalS = uint.Parse(fields[(Int32)ProtHsubString.SampleInterval],
|
||||||
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
|
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
|
||||||
|
|
||||||
//amplitude for high threshold in V
|
// amplitude for high threshold in V
|
||||||
dataProtHRec.AmplitudeUpV = uint.Parse(field[(Int32)ProtHsubString.AmplitudeUp],
|
dataProtHRec.AmplitudeUpV = uint.Parse(fields[(Int32)ProtHsubString.AmplitudeUp],
|
||||||
NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor;
|
NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor;
|
||||||
|
|
||||||
//amplitude for low threshold in V
|
// amplitude for low threshold in V
|
||||||
dataProtHRec.AmplitudeDownV = uint.Parse(field[(Int32)ProtHsubString.AmplitudeDown],
|
dataProtHRec.AmplitudeDownV = uint.Parse(fields[(Int32)ProtHsubString.AmplitudeDown],
|
||||||
NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor;
|
NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor;
|
||||||
|
|
||||||
//raw temperature
|
// raw temperature
|
||||||
dataProtHRec.TemperatureRaw = int.Parse(field[(Int32)ProtHsubString.RawTemperature],
|
dataProtHRec.TemperatureRaw = int.Parse(fields[(Int32)ProtHsubString.RawTemperature],
|
||||||
NumberStyles.AllowHexSpecifier);
|
NumberStyles.AllowHexSpecifier);
|
||||||
|
|
||||||
//temperature scaling
|
// temperature scaling
|
||||||
dataProtHRec.TemperaturePowFactor = uint.Parse(field[(Int32)ProtHsubString.TemperatureScale],
|
dataProtHRec.TemperaturePowFactor = uint.Parse(fields[(Int32)ProtHsubString.TemperatureScale],
|
||||||
NumberStyles.AllowHexSpecifier);
|
NumberStyles.AllowHexSpecifier);
|
||||||
|
|
||||||
//Calculate temperature
|
// Calculate temperature
|
||||||
dataProtHRec.TemperatureDegC = dataProtHRec.TemperatureRaw / (
|
dataProtHRec.TemperatureDegC = dataProtHRec.TemperatureRaw / (
|
||||||
Math.Pow(2.0, dataProtHRec.TemperaturePowFactor));
|
Math.Pow(2.0, dataProtHRec.TemperaturePowFactor));
|
||||||
|
|
||||||
//absolute CPU time, started at LED mode 3 activation
|
// absolute CPU time, started at LED mode 3 activation
|
||||||
dataProtHRec.TimeS = uint.Parse(field[(Int32)ProtHsubString.CpuTime],
|
dataProtHRec.TimeS = uint.Parse(fields[(Int32)ProtHsubString.CpuTime],
|
||||||
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
|
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// field position in protocol 'l'
|
||||||
|
private enum ProtLsubString
|
||||||
|
{
|
||||||
|
//do not remove this needed for position in record
|
||||||
|
ProtType,
|
||||||
|
DisplayVolume,
|
||||||
|
CpuTime,
|
||||||
|
FlowRate,
|
||||||
|
StatusBits,
|
||||||
|
UniqueId,
|
||||||
|
SensorId,
|
||||||
|
Crc
|
||||||
|
}
|
||||||
/// field position in protocol 'f'
|
/// field position in protocol 'f'
|
||||||
private enum ProtFsubString
|
private enum ProtFsubString
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ namespace Xylem.Common.Utils.Crc16Ccitt
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const UInt16 CrcSeed3791 = 0x3791;
|
private const UInt16 CrcSeed3791 = 0x3791;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generator polynomial MagFlux - Modbus RTU
|
||||||
|
/// </summary>
|
||||||
|
private const UInt16 CrcGpA001 = 0xA001;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generator polynomial GENESIS
|
/// Generator polynomial GENESIS
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -73,6 +77,36 @@ namespace Xylem.Common.Utils.Crc16Ccitt
|
|||||||
return (UInt16)~CalculateMsb1021(data);
|
return (UInt16)~CalculateMsb1021(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the Modbus RTU CRC (LSB first, CRCCCITT 0xA001)
|
||||||
|
/// from a data array of bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">data to be processed</param>
|
||||||
|
/// <returns>Calculated CRC</returns>
|
||||||
|
public static UInt16 ModbusRtuLsbA001(Byte[] data)
|
||||||
|
{
|
||||||
|
var crc = CrcSeedFfff;
|
||||||
|
|
||||||
|
for (var t = 0; t < data.Length; t++)
|
||||||
|
{
|
||||||
|
crc ^= data[t]; // XOR byte into least sig. byte of crc
|
||||||
|
|
||||||
|
for (var i = 8; i != 0; i--)
|
||||||
|
{
|
||||||
|
if ((crc & 0x0001) != 0)
|
||||||
|
{
|
||||||
|
crc >>= 1;
|
||||||
|
crc ^= CrcGpA001;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
crc >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the CRC (MSB first, CRCCCITT 0x1021) from a data array of bytes.
|
/// Calculates the CRC (MSB first, CRCCCITT 0x1021) from a data array of bytes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user