FM2014: - message handling improved

This commit is contained in:
Thomas Wiedebusch 2026-02-14 06:21:03 +01:00
parent 20ab81eba7
commit 8998f9058f
3 changed files with 175 additions and 199 deletions

View File

@ -433,12 +433,17 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core.Co
/// <summary> /// <summary>
/// Hexadecimal integer /// Hexadecimal integer
/// </summary> /// </summary>
INT_HEX, UINT_HEX,
/// <summary>
/// Hexadecimal byte plus additional sign
/// </summary>
BYTE_HEX_PLUS_SIGN,
/// <summary> /// <summary>
/// Decimal integer /// Decimal integer
/// </summary> /// </summary>
INT_DEZ, UINT_DEZ,
/// <summary> /// <summary>
/// Individual formatting required /// Individual formatting required
@ -565,46 +570,46 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core.Co
new Cmd(CmdName.CMD_BOOT_MODE, "$B@", CmdType.INDIVIDUAL, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgBootMode), new Cmd(CmdName.CMD_BOOT_MODE, "$B@", CmdType.INDIVIDUAL, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgBootMode),
new Cmd(CmdName.CMD_PRINT_APP_INFO, "?A", CmdType.INDIVIDUAL, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgPrintAppInfo), new Cmd(CmdName.CMD_PRINT_APP_INFO, "?A", CmdType.INDIVIDUAL, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgPrintAppInfo),
new Cmd(CmdName.CMD_PRINT_BOOT_INFO, "?B", CmdType.INDIVIDUAL, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgPrintBootInfo), new Cmd(CmdName.CMD_PRINT_BOOT_INFO, "?B", CmdType.INDIVIDUAL, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgPrintBootInfo),
new Cmd(CmdName.CMD_BAUDRATE_PC, "=BP", CmdType.INDIVIDUAL, ParaFormat.INT_DEZ, Resources.StrCmdMsgBaudrateAll), new Cmd(CmdName.CMD_BAUDRATE_PC, "=BP", CmdType.INDIVIDUAL, ParaFormat.UINT_DEZ, Resources.StrCmdMsgBaudrateAll),
new Cmd(CmdName.CMD_GET_VIRTUAL_ADDRESS, " ", CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgGetVirtualAddress), new Cmd(CmdName.CMD_GET_VIRTUAL_ADDRESS, " ", CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgGetVirtualAddress),
new Cmd(CmdName.CMD_SERIAL, "#", CmdType.INDIVIDUAL, ParaFormat.INT_DEZ, Resources.StrCmdMsgSerial), new Cmd(CmdName.CMD_SERIAL, "#", CmdType.INDIVIDUAL, ParaFormat.UINT_DEZ, Resources.StrCmdMsgSerial),
new Cmd(CmdName.CMD_GET_STATUS, UNICODE_ACK, CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgGetStatus), new Cmd(CmdName.CMD_GET_STATUS, UNICODE_ACK, CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgGetStatus),
new Cmd(CmdName.CMD_SET_VIRTUAL_ADDRESS, "N", CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgSetVirtualAddress), new Cmd(CmdName.CMD_SET_VIRTUAL_ADDRESS, "N", CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgSetVirtualAddress),
new Cmd(CmdName.CMD_PRINT_STATISTICS, "!", CmdType.INDIVIDUAL, ParaFormat.STRING, Resources.StrCmdMsgPrintStatistics), new Cmd(CmdName.CMD_PRINT_STATISTICS, "!", CmdType.INDIVIDUAL, ParaFormat.STRING, Resources.StrCmdMsgPrintStatistics),
//measurement reset //measurement reset
new Cmd(CmdName.CMD_RST_MEAS, "R", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgRstMeas), new Cmd(CmdName.CMD_RST_MEAS, "R", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgRstMeas),
// Double impulse detection settings // Double impulse detection settings
new Cmd(CmdName.CMD_SET_DPLS_LOCK_TMR, "S", CmdType.BROADCAST, ParaFormat.INT_DEZ, Resources.StrCmdMsgDplsLockTmr), new Cmd(CmdName.CMD_SET_DPLS_LOCK_TMR, "S", CmdType.BROADCAST, ParaFormat.UINT_DEZ, Resources.StrCmdMsgDplsLockTmr),
new Cmd(CmdName.CMD_SET_DPLS_LOCK_MULT, "s", CmdType.BROADCAST, ParaFormat.INT_DEZ, Resources.StrCmdMsgDplsLockMult), new Cmd(CmdName.CMD_SET_DPLS_LOCK_MULT, "s", CmdType.BROADCAST, ParaFormat.UINT_DEZ, Resources.StrCmdMsgDplsLockMult),
new Cmd(CmdName.CMD_SET_DBPL_UNLOCK, "G", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgSetDbplUnlock), new Cmd(CmdName.CMD_SET_DBPL_UNLOCK, "G", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgSetDbplUnlock),
// Main measurement pulse settings and readout // Main measurement pulse settings and readout
new Cmd(CmdName.CMD_DUT_SET_PLS, "H", CmdType.BROADCAST, ParaFormat.INT_HEX, Resources.StrCmdMsgDutSetPls), new Cmd(CmdName.CMD_DUT_SET_PLS, "H", CmdType.BROADCAST, ParaFormat.UINT_HEX, Resources.StrCmdMsgDutSetPls),
new Cmd(CmdName.CMD_REF_SET_PLS, "M", CmdType.BROADCAST, ParaFormat.INT_HEX, Resources.StrCmdMsgRefSetPls), new Cmd(CmdName.CMD_REF_SET_PLS, "M", CmdType.BROADCAST, ParaFormat.UINT_HEX, Resources.StrCmdMsgRefSetPls),
new Cmd(CmdName.CMD_DUT_GET_PLS_RMN, "I", CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgDutGetPlsRmn), new Cmd(CmdName.CMD_DUT_GET_PLS_RMN, "I", CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgDutGetPlsRmn),
new Cmd(CmdName.CMD_REF_GET_PLS_RMN, "J", CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgRefGetPlsRmn), new Cmd(CmdName.CMD_REF_GET_PLS_RMN, "J", CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgRefGetPlsRmn),
// Pulse counters start, readout and backup // Pulse counters start, readout and backup
new Cmd(CmdName.CMD_REF_STR_PLS_CTR, "O", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgRefStrPlsCtr), new Cmd(CmdName.CMD_REF_STR_PLS_CTR, "O", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgRefStrPlsCtr),
new Cmd(CmdName.CMD_DUT_STR_PLS_CTR, "Q", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgDutStrPlsCtr), new Cmd(CmdName.CMD_DUT_STR_PLS_CTR, "Q", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgDutStrPlsCtr),
new Cmd(CmdName.CMD_REF_GET_PLS_CTR, "L", CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgRefGetPlsCtr), new Cmd(CmdName.CMD_REF_GET_PLS_CTR, "L", CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgRefGetPlsCtr),
new Cmd(CmdName.CMD_DUT_GET_PLS_CTR, "U", CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgDutGetPlsCtr), new Cmd(CmdName.CMD_DUT_GET_PLS_CTR, "U", CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgDutGetPlsCtr),
new Cmd(CmdName.CMD_REF_GET_PLS_CTR_BU, "l", CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgRefGetPlsCtrBu), new Cmd(CmdName.CMD_REF_GET_PLS_CTR_BU, "l", CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgRefGetPlsCtrBu),
new Cmd(CmdName.CMD_DUT_GET_PLS_CTR_BU, "u", CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgDutGetPlsCtrBu), new Cmd(CmdName.CMD_DUT_GET_PLS_CTR_BU, "u", CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgDutGetPlsCtrBu),
new Cmd(CmdName.CMD_REF_DUT_STR_PLS_CTR, "o", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgRefDutStrPlsCtr), new Cmd(CmdName.CMD_REF_DUT_STR_PLS_CTR, "o", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgRefDutStrPlsCtr),
new Cmd(CmdName.CMD_REF_DUT_BU_PLS_CTR, "q", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgRefDutBuPlsCtr), new Cmd(CmdName.CMD_REF_DUT_BU_PLS_CTR, "q", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgRefDutBuPlsCtr),
// General measurement setup // General measurement setup
new Cmd(CmdName.CMD_REF_SET_SCALE, "K", CmdType.BROADCAST, ParaFormat.INDIVIDUAL, Resources.StrCmdMsgRefSetScale), new Cmd(CmdName.CMD_REF_SET_SCALE, "K", CmdType.BROADCAST, ParaFormat.INDIVIDUAL, Resources.StrCmdMsgRefSetScale),
new Cmd(CmdName.CMD_DUT_LPP_SCALE, "V", CmdType.INDIVIDUAL_UNRESPONSIVE, ParaFormat.INT_DEZ, Resources.StrCmdMsgDutLppScale), new Cmd(CmdName.CMD_DUT_LPP_SCALE, "V", CmdType.INDIVIDUAL_UNRESPONSIVE, ParaFormat.UINT_DEZ, Resources.StrCmdMsgDutLppScale),
new Cmd(CmdName.CMD_REF_LPP_SCALE, "W", CmdType.INDIVIDUAL_UNRESPONSIVE, ParaFormat.INT_DEZ, Resources.StrCmdMsgRefLppScale), new Cmd(CmdName.CMD_REF_LPP_SCALE, "W", CmdType.INDIVIDUAL_UNRESPONSIVE, ParaFormat.UINT_DEZ, Resources.StrCmdMsgRefLppScale),
new Cmd(CmdName.CMD_MEAS_SET_ATTN, "T", CmdType.BROADCAST, ParaFormat.INT_DEZ, Resources.StrCmdMsgMeasSetAttn), new Cmd(CmdName.CMD_MEAS_SET_ATTN, "T", CmdType.BROADCAST, ParaFormat.UINT_DEZ, Resources.StrCmdMsgMeasSetAttn),
// Measurement result request // Measurement result request
new Cmd(CmdName.CMD_DUT_GET_TMR, "X", CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgDutGetTmr), new Cmd(CmdName.CMD_DUT_GET_TMR, "X", CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgDutGetTmr),
new Cmd(CmdName.CMD_REF_GET_TMR, "Y", CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgRefGetTmr), new Cmd(CmdName.CMD_REF_GET_TMR, "Y", CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgRefGetTmr),
new Cmd(CmdName.CMD_CAL_DUT_TO_REF_TOL, "", CmdType.INDIVIDUAL, ParaFormat.DOUBLE, Resources.StrCalDutToRefTol), new Cmd(CmdName.CMD_CAL_DUT_TO_REF_TOL, "", CmdType.INDIVIDUAL, ParaFormat.DOUBLE, Resources.StrCalDutToRefTol),
new Cmd(CmdName.CMD_GET_UDTLC, "Z", CmdType.INDIVIDUAL, ParaFormat.INDIVIDUAL, Resources.StrCmdMsgGetUdtlc), new Cmd(CmdName.CMD_GET_UDTLC, "Z", CmdType.INDIVIDUAL, ParaFormat.BYTE_HEX_PLUS_SIGN, Resources.StrCmdMsgGetUdtlc),
new Cmd(CmdName.CMD_GET_DTLC, "z", CmdType.INDIVIDUAL, ParaFormat.INDIVIDUAL, Resources.StrCmdMsgGetDtlc), new Cmd(CmdName.CMD_GET_DTLC, "z", CmdType.INDIVIDUAL, ParaFormat.BYTE_HEX_PLUS_SIGN, Resources.StrCmdMsgGetDtlc),
new Cmd(CmdName.CMD_GET_REF_PERIOD, UNICODE_DLE, CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgGetRefPeriod), new Cmd(CmdName.CMD_GET_REF_PERIOD, UNICODE_DLE, CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgGetRefPeriod),
new Cmd(CmdName.CMD_GET_DUT_PERIOD, UNICODE_DC1, CmdType.INDIVIDUAL,ParaFormat.INT_HEX, Resources.StrCmdMsgGetDutPeriod), new Cmd(CmdName.CMD_GET_DUT_PERIOD, UNICODE_DC1, CmdType.INDIVIDUAL,ParaFormat.UINT_HEX, Resources.StrCmdMsgGetDutPeriod),
new Cmd(CmdName.CMD_GET_REF_FREQU, UNICODE_DC2, CmdType.INDIVIDUAL, ParaFormat.INT_HEX, Resources.StrCmdMsgGetRefFrequ), new Cmd(CmdName.CMD_GET_REF_FREQU, UNICODE_DC2, CmdType.INDIVIDUAL, ParaFormat.UINT_HEX, Resources.StrCmdMsgGetRefFrequ),
new Cmd(CmdName.CMD_CAL_FREQU_REF_PERIOD, "", CmdType.INDIVIDUAL, ParaFormat.DOUBLE, Resources.StrCalRefFrequFromRefPeriod), new Cmd(CmdName.CMD_CAL_FREQU_REF_PERIOD, "", CmdType.INDIVIDUAL, ParaFormat.DOUBLE, Resources.StrCalRefFrequFromRefPeriod),
new Cmd(CmdName.CMD_CAL_FREQU_DUT_PERIOD, "", CmdType.INDIVIDUAL, ParaFormat.DOUBLE, Resources.StrCalDutFrequFromDutPeriod), new Cmd(CmdName.CMD_CAL_FREQU_DUT_PERIOD, "", CmdType.INDIVIDUAL, ParaFormat.DOUBLE, Resources.StrCalDutFrequFromDutPeriod),
new Cmd(CmdName.CMD_CAL_FLOW_REF_FREQU, "", CmdType.INDIVIDUAL, ParaFormat.DOUBLE, Resources.StrCalRefFlowRateFromRefFrequ), new Cmd(CmdName.CMD_CAL_FLOW_REF_FREQU, "", CmdType.INDIVIDUAL, ParaFormat.DOUBLE, Resources.StrCalRefFlowRateFromRefFrequ),
@ -614,7 +619,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core.Co
new Cmd(CmdName.CMD_COR_CURRENT, "C", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgCorCurrent), new Cmd(CmdName.CMD_COR_CURRENT, "C", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgCorCurrent),
new Cmd(CmdName.CMD_SET_XTR_MAX, "+", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgSetXtrMax), new Cmd(CmdName.CMD_SET_XTR_MAX, "+", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgSetXtrMax),
new Cmd(CmdName.CMD_SET_XTR_MIN, "-", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgSetXtrMin), new Cmd(CmdName.CMD_SET_XTR_MIN, "-", CmdType.BROADCAST, ParaFormat.PARAMETERLESS, Resources.StrCmdMsgSetXtrMin),
new Cmd(CmdName.CMD_ADDR_PC, "A", CmdType.INDIVIDUAL, ParaFormat.INT_DEZ, Resources.StrCmdMsgAddrPc) new Cmd(CmdName.CMD_ADDR_PC, "A", CmdType.INDIVIDUAL, ParaFormat.UINT_DEZ, Resources.StrCmdMsgAddrPc)
}; };
/// <summary> /// <summary>
@ -675,7 +680,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core.Co
/// <summary> /// <summary>
/// Optional integer value /// Optional integer value
/// </summary> /// </summary>
public Int32? IntValue { get; private set; } public UInt32? IntValue { get; private set; }
/// <summary> /// <summary>
/// Optional double value /// Optional double value
@ -695,7 +700,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core.Co
/// <param name="intValue"></param> /// <param name="intValue"></param>
/// <param name="doubleValue"></param> /// <param name="doubleValue"></param>
/// <param name="unit"></param> /// <param name="unit"></param>
public CmdResponse(CmdName cmdName, String answerStr, Int32? intValue = null, Double? doubleValue = null, public CmdResponse(CmdName cmdName, String answerStr, UInt32? intValue = null, Double? doubleValue = null,
String unit = "") String unit = "")
{ {
CmdName = cmdName; CmdName = cmdName;

View File

@ -386,17 +386,18 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// </summary> /// </summary>
public Boolean UseDampedTolerance { get; set; } public Boolean UseDampedTolerance { get; set; }
private UInt16 _refPulsesRequired; private UInt32 _refPulsesRequired;
/// <summary> /// <summary>
/// Required REF pulses for time measurement. /// Required REF pulses for time measurement.
/// </summary> /// </summary>
public UInt16 RefPulsesRequired public UInt32 RefPulsesRequired
{ {
get => _refPulsesRequired; get => _refPulsesRequired;
private set private set
{ {
// Check limits and equality // Check limits and equality
if (value < PULSES_TIME_MEASUREMENT_INPUT_MIN || if (value < PULSES_TIME_MEASUREMENT_INPUT_MIN ||
value > PULSES_TIME_MEASUREMENT_INPUT_MAX ||
value == _refPulsesRequired) value == _refPulsesRequired)
return; return;
@ -404,17 +405,18 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
} }
} }
private UInt16 _dutPulsesRequired; private UInt32 _dutPulsesRequired;
/// <summary> /// <summary>
/// Required DUT pulses for time measurement. /// Required DUT pulses for time measurement.
/// </summary> /// </summary>
public UInt16 DutPulsesRequired public UInt32 DutPulsesRequired
{ {
get => _dutPulsesRequired; get => _dutPulsesRequired;
private set private set
{ {
// Check limits and equality // Check limits and equality
if (value < PULSES_TIME_MEASUREMENT_INPUT_MIN || if (value < PULSES_TIME_MEASUREMENT_INPUT_MIN ||
value > PULSES_TIME_MEASUREMENT_INPUT_MAX ||
value == _dutPulsesRequired) value == _dutPulsesRequired)
return; return;
@ -738,7 +740,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
do do
{ {
// Change the SI unit to ms instead of seconds // Change the SI unit to ms instead of seconds
var response = new CmdResponse(CmdName.CMD_RST_MEAS, infoStr, resetDelayCtr_ms, var response = new CmdResponse(CmdName.CMD_RST_MEAS, infoStr, (UInt32)resetDelayCtr_ms,
unit: "m" + Units.GetInfo(Units.Name.TIME_s)); unit: "m" + Units.GetInfo(Units.Name.TIME_s));
PublishResponse(firstActiveFm2014, new ProcessExecEventArgs("", PublishResponse(firstActiveFm2014, new ProcessExecEventArgs("",
actualProcessMessage: infoStr, actualProcessMessage: infoStr,
@ -836,12 +838,12 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// <remarks date="2026-Feb-13" author="Thomas Wiedebusch"> /// <remarks date="2026-Feb-13" author="Thomas Wiedebusch">
/// - Initial. /// - Initial.
/// </remarks> /// </remarks>
private static void ResponseEvent(FM2014 fm2014, CmdResponse cmdResponse, private static void ResponseEvent(FM2014 fm2014, CmdResponse cmdResponse,
StatusReturn statusReturn = StatusReturn.Unknown) StatusReturn statusReturn = StatusReturn.Unknown)
{ {
var processExecEventArgs = new ProcessExecEventArgs("", var processExecEventArgs = new ProcessExecEventArgs("",
actualProcessMessage: MeasurementInfo, actualProcessMessage: MeasurementInfo,
actualProcessPercent: ActualProcessProgress, actualProcessPercent: ActualProcessProgress_percent,
statusReturn: statusReturn, statusReturn: statusReturn,
specificInfoObj: cmdResponse); specificInfoObj: cmdResponse);
fm2014?.OnRawRecordReceived?.Invoke(fm2014, processExecEventArgs); fm2014?.OnRawRecordReceived?.Invoke(fm2014, processExecEventArgs);
@ -886,7 +888,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// <returns>true if initialization successfully executed or /// <returns>true if initialization successfully executed or
/// <see cref="SharedCyclicMeasSequ"/> marks the already started cyclic requests /// <see cref="SharedCyclicMeasSequ"/> marks the already started cyclic requests
/// for all registered FM2014s</returns> /// for all registered FM2014s</returns>
/// <remarks date="2026-Jan-10..Feb-11" author="Thomas Wiedebusch"> /// <remarks date="2026-Jan-10..Feb-14" author="Thomas Wiedebusch">
/// - Initial. /// - Initial.
/// </remarks> /// </remarks>
public static Boolean PulseCounterMeasurement(Boolean refPulseCtrOn, Boolean dutPulseCtrOn) public static Boolean PulseCounterMeasurement(Boolean refPulseCtrOn, Boolean dutPulseCtrOn)
@ -1002,7 +1004,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
if (fm2014.RefPulseCtrOn && Write(cmdNameRef, fm2014)) if (fm2014.RefPulseCtrOn && Write(cmdNameRef, fm2014))
{ {
if (Read(cmdNameRef, fm2014, out _, out var intValue)) if (Read(cmdNameRef, fm2014, out _, out var intValue, out _))
{ {
var response = new CmdResponse(cmdNameRef, infoRef, intValue); var response = new CmdResponse(cmdNameRef, infoRef, intValue);
ResponseEvent(fm2014, response); ResponseEvent(fm2014, response);
@ -1011,7 +1013,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
if (fm2014.DutPulseCtrOn && Write(cmdNameDut, fm2014)) if (fm2014.DutPulseCtrOn && Write(cmdNameDut, fm2014))
{ {
if (Read(cmdNameDut, fm2014, out _, out var intValue)) if (Read(cmdNameDut, fm2014, out _, out var intValue, out _))
{ {
var response = new CmdResponse(cmdNameDut, infoDut, intValue); var response = new CmdResponse(cmdNameDut, infoDut, intValue);
ResponseEvent(fm2014, response); ResponseEvent(fm2014, response);
@ -1068,7 +1070,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// <param name="doublePulseDeadtime_ms"></param> /// <param name="doublePulseDeadtime_ms"></param>
/// <param name="currentOutputAttenuation"></param> /// <param name="currentOutputAttenuation"></param>
/// <returns></returns> /// <returns></returns>
/// <remarks date="2026-Jan-14..Feb-11" author="Thomas Wiedebusch"> /// <remarks date="2026-Jan-14..Feb-14" author="Thomas Wiedebusch">
/// - Initial. /// - Initial.
/// </remarks> /// </remarks>
public static Boolean RegulationMeasurement(UInt32 refPulses_per_cm, UInt16 dutPulses_per_cm, public static Boolean RegulationMeasurement(UInt32 refPulses_per_cm, UInt16 dutPulses_per_cm,
@ -1156,38 +1158,26 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{ {
// Read out the damped/undamped tolerance and convert the raw value (0, +/-1..255 digits) to the // Read out the damped/undamped tolerance and convert the raw value (0, +/-1..255 digits) to the
// tolerance in percent depending on the setup to 3 % (3.3) of 5 % (5.5) // tolerance in percent depending on the setup to 3 % (3.3) of 5 % (5.5)
String responseStr;
StatusReturn statusReturn; StatusReturn statusReturn;
cmdName = fm2014.UseDampedTolerance ? CmdName.CMD_GET_DTLC : CmdName.CMD_GET_UDTLC; cmdName = fm2014.UseDampedTolerance ? CmdName.CMD_GET_DTLC : CmdName.CMD_GET_UDTLC;
var info = GetCmdInfo(cmdName); var info = GetCmdInfo(cmdName);
if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014)) if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014))
{ {
if (Read(cmdName, fm2014, out responseStr, out _)) if (Read(cmdName, fm2014, out _, out _, out var doubleValue))
{ {
// The response contains a sign! fm2014.DutToRefToleranceMeasured_percent = doubleValue *
var signMultiplier = responseStr.Contains("+") ? 1.0f : -1.0f; fm2014.CurrentOutputToleranceNominalToRealScale;
var cleanedStr = responseStr.Replace("+", "").Replace("-", ""); var response = new CmdResponse(cmdName, info,
if (int.TryParse(cleanedStr, NumberStyles.HexNumber, doubleValue: fm2014.DutToRefToleranceMeasured_percent,
new CultureInfo("en"), out var measTolerance)) unit: Units.GetInfo(Units.Name.PERCENT));
{
fm2014.DutToRefToleranceMeasured_percent =
measTolerance * fm2014.CurrentOutputToleranceNominalToRealScale * signMultiplier;
var response = new CmdResponse(cmdName, info,
doubleValue: fm2014.DutToRefToleranceMeasured_percent,
unit: Units.GetInfo(Units.Name.PERCENT));
// Check the threshold for OOR // Check the threshold for OOR
if (Math.Abs((Double)fm2014.DutToRefToleranceMeasured_percent) > if (Math.Abs((Double)fm2014.DutToRefToleranceMeasured_percent) >
fm2014.CurrentOutputToleranceNominalInt_percent) fm2014.CurrentOutputToleranceNominalInt_percent)
statusReturn = StatusReturn.MeasurementOutOfRange; statusReturn = StatusReturn.MeasurementOutOfRange;
else statusReturn = StatusReturn.Okay; else statusReturn = StatusReturn.Okay;
PublishResponse(fm2014, ResponseEvent(fm2014, response, statusReturn);
new ProcessExecEventArgs("",
actualProcessMessage: MeasurementInfo,
statusReturn: statusReturn,
specificInfoObj: response));
}
} }
} }
@ -1197,16 +1187,13 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
if (fm2014.RequestDebugInformation && SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && if (fm2014.RequestDebugInformation && SharedCyclicMeasSequ != CyclicMeasSequ.IDLE &&
Write(cmdName, fm2014)) Write(cmdName, fm2014))
{ {
if (Read(cmdName, fm2014, out responseStr, out _) && if (Read(cmdName, fm2014, out _, out var period, out _))
int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"), out var period))
{ {
// Publish the period [ms] // Publish the period [ms]
var response = new CmdResponse(cmdName, info, var response = new CmdResponse(cmdName, info,
doubleValue: TMR_RESOLUTION_s * 1000.0 * period, doubleValue: TMR_RESOLUTION_s * 1000.0 * period,
unit: "m" + Units.GetInfo(Units.Name.TIME_s)); unit: "m" + Units.GetInfo(Units.Name.TIME_s));
PublishResponse(fm2014, ResponseEvent(fm2014, response);
new ProcessExecEventArgs("", actualProcessMessage: MeasurementInfo,
specificInfoObj: response));
// Publish the frequency in [Hz] // Publish the frequency in [Hz]
cmdName = CmdName.CMD_CAL_FREQU_REF_PERIOD; cmdName = CmdName.CMD_CAL_FREQU_REF_PERIOD;
@ -1214,20 +1201,16 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
response = new CmdResponse(cmdName, info, response = new CmdResponse(cmdName, info,
doubleValue: 1.0 / (TMR_RESOLUTION_s * period), doubleValue: 1.0 / (TMR_RESOLUTION_s * period),
unit: Units.GetInfo(Units.Name.FREQUENCY_Hz)); unit: Units.GetInfo(Units.Name.FREQUENCY_Hz));
PublishResponse(fm2014, ResponseEvent(fm2014, response);
new ProcessExecEventArgs("", actualProcessMessage: MeasurementInfo,
specificInfoObj: response));
// Convert to m³/h (3600 s/h) based on REF pulse rate // Convert to m³/h (3600 s/h) based on REF pulse rate
fm2014.RefFlowRate_cm_per_h = 3600.0 / (period * TMR_RESOLUTION_s) / fm2014.RefPulses_per_cm; fm2014.RefFlowRate_cm_per_h = 3600.0 / ((Double)period * TMR_RESOLUTION_s) / fm2014.RefPulses_per_cm;
// Publish the calculated flow rate [m³/h] // Publish the calculated flow rate [m³/h]
cmdName = CmdName.CMD_CAL_FLOW_REF_PERIOD; cmdName = CmdName.CMD_CAL_FLOW_REF_PERIOD;
info = GetCmdInfo(cmdName); info = GetCmdInfo(cmdName);
response = new CmdResponse(cmdName, info, doubleValue: fm2014.RefFlowRate_cm_per_h, response = new CmdResponse(cmdName, info, doubleValue: fm2014.RefFlowRate_cm_per_h,
unit: Units.GetInfo(Units.Name.FLOW_RATE_cm_per_h)); unit: Units.GetInfo(Units.Name.FLOW_RATE_cm_per_h));
PublishResponse(fm2014, ResponseEvent(fm2014, response);
new ProcessExecEventArgs("", actualProcessMessage: MeasurementInfo,
specificInfoObj: response));
} }
} }
@ -1239,16 +1222,13 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && SharedCyclicMeasSequ != CyclicMeasSequ.IDLE &&
Write(cmdName, fm2014)) Write(cmdName, fm2014))
{ {
if (Read(cmdName, fm2014, out responseStr, out _) && if (Read(cmdName, fm2014, out _, out var period, out _))
int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"), out var period))
{ {
// Publish the period [ms] // Publish the period [ms]
var response = new CmdResponse(cmdName, info, var response = new CmdResponse(cmdName, info,
doubleValue: TMR_RESOLUTION_s * 1000.0 * period, doubleValue: TMR_RESOLUTION_s * 1000.0 * period,
unit: "m" + Units.GetInfo(Units.Name.TIME_s)); unit: "m" + Units.GetInfo(Units.Name.TIME_s));
PublishResponse(fm2014, ResponseEvent(fm2014, response);
new ProcessExecEventArgs("", actualProcessMessage: MeasurementInfo,
specificInfoObj: response));
// Publish the frequency in [Hz] // Publish the frequency in [Hz]
cmdName = CmdName.CMD_CAL_FREQU_DUT_PERIOD; cmdName = CmdName.CMD_CAL_FREQU_DUT_PERIOD;
@ -1256,32 +1236,25 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
response = new CmdResponse(cmdName, info, response = new CmdResponse(cmdName, info,
doubleValue: 1.0 / (TMR_RESOLUTION_s * period), doubleValue: 1.0 / (TMR_RESOLUTION_s * period),
unit: Units.GetInfo(Units.Name.FREQUENCY_Hz)); unit: Units.GetInfo(Units.Name.FREQUENCY_Hz));
PublishResponse(fm2014, ResponseEvent(fm2014, response);
new ProcessExecEventArgs("", actualProcessMessage: MeasurementInfo,
specificInfoObj: response));
// Convert to m³/h (3600 s/h) based on DUT pulse rate // Convert to m³/h (3600 s/h) based on DUT pulse rate
fm2014.DutFlowRate_cm_per_h = 3600.0 / (period * TMR_RESOLUTION_s) / fm2014.DutPulses_per_cm; fm2014.DutFlowRate_cm_per_h = 3600.0 / ((Double)period * TMR_RESOLUTION_s) / fm2014.DutPulses_per_cm;
// Publish the calculated flow rate [m³/h] // Publish the calculated flow rate [m³/h]
cmdName = CmdName.CMD_CAL_FLOW_DUT_PERIOD; cmdName = CmdName.CMD_CAL_FLOW_DUT_PERIOD;
info = GetCmdInfo(cmdName); info = GetCmdInfo(cmdName);
response = new CmdResponse(cmdName, info, doubleValue: fm2014.DutFlowRate_cm_per_h, response = new CmdResponse(cmdName, info, doubleValue: fm2014.DutFlowRate_cm_per_h,
unit: Units.GetInfo(Units.Name.FLOW_RATE_cm_per_h)); unit: Units.GetInfo(Units.Name.FLOW_RATE_cm_per_h));
PublishResponse(fm2014, ResponseEvent(fm2014, response);
new ProcessExecEventArgs("", actualProcessMessage: MeasurementInfo,
specificInfoObj: response));
} }
} }
// Read the REF frequency [Hz] directly and calculate the flow rate [m³/h] based on this frequency // Read the REF frequency [Hz] directly and calculate the flow rate [m³/h] based on this frequency
cmdName = CmdName.CMD_GET_REF_FREQU; cmdName = CmdName.CMD_GET_REF_FREQU;
info = GetCmdInfo(cmdName); info = GetCmdInfo(cmdName);
if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014)) if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014))
{ {
if (Read(cmdName, fm2014, out responseStr, out _) && if (Read(cmdName, fm2014, out _, out var refFrequency, out _))
int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"),
out var refFrequency))
{ {
// Check the threshold for OOR // Check the threshold for OOR
if (refFrequency < REF_FREQU_MIN || refFrequency > REF_FREQU_MAX) if (refFrequency < REF_FREQU_MIN || refFrequency > REF_FREQU_MAX)
@ -1289,25 +1262,18 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
else statusReturn = StatusReturn.Okay; else statusReturn = StatusReturn.Okay;
// Publish the frequency in [Hz] // Publish the frequency in [Hz]
var response = new CmdResponse(cmdName, info, refFrequency, var response = new CmdResponse(cmdName, info, (UInt32)refFrequency,
unit: Units.GetInfo(Units.Name.FREQUENCY_Hz)); unit: Units.GetInfo(Units.Name.FREQUENCY_Hz));
PublishResponse(fm2014, new ProcessExecEventArgs("", ResponseEvent(fm2014, response, statusReturn);
actualProcessMessage: MeasurementInfo,
statusReturn: statusReturn,
specificInfoObj: response));
// Convert to m³/h (3600 s/h) and save the value // Convert to m³/h (3600 s/h) and save the value
fm2014.RefFlowRate_cm_per_h = 3600.0 * refFrequency / fm2014.RefPulses_per_cm; fm2014.RefFlowRate_cm_per_h = 3600.0 * (Double)refFrequency / fm2014.RefPulses_per_cm;
// Publish the calculated flow rate [m³/h] // Publish the calculated flow rate [m³/h]
cmdName = CmdName.CMD_CAL_FLOW_REF_FREQU; cmdName = CmdName.CMD_CAL_FLOW_REF_FREQU;
info = GetCmdInfo(cmdName); info = GetCmdInfo(cmdName);
response = new CmdResponse(cmdName, info, doubleValue: fm2014.RefFlowRate_cm_per_h, response = new CmdResponse(cmdName, info, doubleValue: fm2014.RefFlowRate_cm_per_h,
unit: Units.GetInfo(Units.Name.FLOW_RATE_cm_per_h)); unit: Units.GetInfo(Units.Name.FLOW_RATE_cm_per_h));
PublishResponse(fm2014, ResponseEvent(fm2014, response, statusReturn);
new ProcessExecEventArgs("",
actualProcessMessage: MeasurementInfo,
statusReturn: statusReturn,
specificInfoObj: response));
} }
} }
} }
@ -1356,7 +1322,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// <param name="doublePulseDeadtime_ms">0 if double impulse deadtime detection is off</param> /// <param name="doublePulseDeadtime_ms">0 if double impulse deadtime detection is off</param>
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
/// <remarks date="2026-Jan-14..Feb-13" author="Thomas Wiedebusch"> /// <remarks date="2026-Jan-14..Feb-14" author="Thomas Wiedebusch">
/// - Initial. /// - Initial.
/// </remarks> /// </remarks>
public static Boolean PulseTimeMeasurement(UInt16? refPulsesRequired = null, UInt16? dutPulsesRequired = null, public static Boolean PulseTimeMeasurement(UInt16? refPulsesRequired = null, UInt16? dutPulsesRequired = null,
@ -1463,9 +1429,9 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
// Set up the progress maximal counter value base on required pulses for REF or DUT // Set up the progress maximal counter value base on required pulses for REF or DUT
InitActualProcessProgress( InitActualProcessProgress(
firstActiveFm2014.RefPulsesRequired != 0 (Int32)firstActiveFm2014.RefPulsesRequired != 0
? firstActiveFm2014.RefPulsesRequired ? (Int32)firstActiveFm2014.RefPulsesRequired
: firstActiveFm2014.DutPulsesRequired, disableWriteReadIncrease: true); : (Int32)firstActiveFm2014.DutPulsesRequired, disableWriteReadIncrease: true);
do do
{ {
@ -1473,24 +1439,19 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{ {
try try
{ {
String info;
String responseStr; String responseStr;
StatusReturn statusReturn; StatusReturn statusReturn;
// Read the device status to check for timeout between pulses // Read the device status to check for timeout between pulses
cmdName = CmdName.CMD_GET_STATUS; cmdName = CmdName.CMD_GET_STATUS;
info = GetCmdInfo(cmdName); var info = GetCmdInfo(cmdName);
if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014)) if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014))
{ {
if (Read(cmdName, fm2014, out responseStr, out _) && if (Read(cmdName, fm2014, out responseStr, out var status, out _))
int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"),
out var status))
{ {
info += $": {responseStr}"; info += $": {responseStr}";
var response = new CmdResponse(cmdName, info, status); var response = new CmdResponse(cmdName, info, status);
PublishResponse(fm2014, new ProcessExecEventArgs("", ResponseEvent(fm2014, response);
actualProcessPercent: ActualProcessProgress_percent,
actualProcessMessage: MeasurementInfo, specificInfoObj: response));
} }
} }
@ -1498,17 +1459,15 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
if (fm2014.RefPulsesRequired != 0) if (fm2014.RefPulsesRequired != 0)
{ {
// Feed the progress counter // Feed the progress counter
ActualProcessProgress = firstActiveFm2014.RefPulsesRequired - ActualProcessProgress = (Int32)firstActiveFm2014.RefPulsesRequired -
firstActiveFm2014.RefPulsesRemaining; firstActiveFm2014.RefPulsesRemaining;
cmdName = CmdName.CMD_REF_GET_PLS_RMN; cmdName = CmdName.CMD_REF_GET_PLS_RMN;
info = GetCmdInfo(cmdName); info = GetCmdInfo(cmdName);
if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014)) if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014))
{ {
if (Read(cmdName, fm2014, out responseStr, out _) && if (Read(cmdName, fm2014, out responseStr, out var pulses, out _))
ushort.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"),
out var pulses))
{ {
fm2014.RefPulsesRemaining = pulses; fm2014.RefPulsesRemaining = (UInt16)pulses;
// Check if remaining pulses are going to count down based on required pulses // Check if remaining pulses are going to count down based on required pulses
if (RegisteredFm2014s.Any(fm => fm.RefPulsesRemaining * 1.1 < fm.RefPulsesRequired) && if (RegisteredFm2014s.Any(fm => fm.RefPulsesRemaining * 1.1 < fm.RefPulsesRequired) &&
@ -1523,10 +1482,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
} }
var response = new CmdResponse(cmdName, info, pulses); var response = new CmdResponse(cmdName, info, pulses);
PublishResponse(fm2014, new ProcessExecEventArgs("", ResponseEvent(fm2014, response, statusReturn);
actualProcessPercent: ActualProcessProgress_percent,
statusReturn: statusReturn,
actualProcessMessage: MeasurementInfo, specificInfoObj: response));
} }
} }
} }
@ -1536,17 +1492,15 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{ {
// Feed the progress counter but only if not done for REF pulses // Feed the progress counter but only if not done for REF pulses
if (firstActiveFm2014.RefPulsesRequired == 0) if (firstActiveFm2014.RefPulsesRequired == 0)
ActualProcessProgress = firstActiveFm2014.DutPulsesRequired - ActualProcessProgress = (Int32)firstActiveFm2014.DutPulsesRequired -
firstActiveFm2014.DutPulsesRemaining; firstActiveFm2014.DutPulsesRemaining;
cmdName = CmdName.CMD_DUT_GET_PLS_RMN; cmdName = CmdName.CMD_DUT_GET_PLS_RMN;
info = GetCmdInfo(cmdName); info = GetCmdInfo(cmdName);
if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014)) if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014))
{ {
if (Read(cmdName, fm2014, out responseStr, out _) && if (Read(cmdName, fm2014, out responseStr, out var pulses, out _))
ushort.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"),
out var pulses))
{ {
fm2014.DutPulsesRemaining = pulses; fm2014.DutPulsesRemaining = (UInt16)pulses;
// Check if remaining pulses are going to count down based on required pulses // Check if remaining pulses are going to count down based on required pulses
if (RegisteredFm2014s.Any(fm => fm.DutPulsesRemaining * 1.1 < fm.DutPulsesRequired) && if (RegisteredFm2014s.Any(fm => fm.DutPulsesRemaining * 1.1 < fm.DutPulsesRequired) &&
@ -1561,10 +1515,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
} }
var response = new CmdResponse(cmdName, info, pulses); var response = new CmdResponse(cmdName, info, pulses);
PublishResponse(fm2014, new ProcessExecEventArgs("", ResponseEvent(fm2014, response, statusReturn);
actualProcessMessage: MeasurementInfo,
statusReturn: statusReturn,
specificInfoObj: response));
} }
} }
} }
@ -1582,27 +1533,19 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
info = GetCmdInfo(cmdName); info = GetCmdInfo(cmdName);
if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014)) if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014))
{ {
if (Read(cmdName, fm2014, out responseStr, out _) && if (Read(cmdName, fm2014, out responseStr, out var timerTicks, out _))
uint.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"),
out var timerTicks))
{ {
// Is converting to a real time in seconds // Is converting to a real time in seconds
fm2014.RefTimerTicksMeasured = timerTicks; fm2014.RefTimerTicksMeasured = (UInt16)timerTicks;
var response = new CmdResponse(cmdName, info, doubleValue: fm2014.RefTimeMeasured_s, var response = new CmdResponse(cmdName, info, doubleValue: fm2014.RefTimeMeasured_s,
unit: Units.GetInfo(Units.Name.TIME_s)); unit: Units.GetInfo(Units.Name.TIME_s));
PublishResponse(fm2014, new ProcessExecEventArgs("", ResponseEvent(fm2014, response);
actualProcessMessage: MeasurementInfo,
specificInfoObj: response));
} }
else else
{ {
retVal = false; retVal = false;
} }
} }
else
{
retVal = false;
}
} }
// Read the DUT timer ticks for the counted pulses // Read the DUT timer ticks for the counted pulses
@ -1612,27 +1555,19 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
info = GetCmdInfo(cmdName); info = GetCmdInfo(cmdName);
if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014)) if (SharedCyclicMeasSequ != CyclicMeasSequ.IDLE && Write(cmdName, fm2014))
{ {
if (Read(cmdName, fm2014, out responseStr, out _) && if (Read(cmdName, fm2014, out responseStr, out var timerTicks, out _))
uint.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"),
out var timerTicks))
{ {
// Is converting to a real time in seconds // Is converting to a real time in seconds
fm2014.DutTimerTicksMeasured = timerTicks; fm2014.DutTimerTicksMeasured = (UInt16)timerTicks;
var response = new CmdResponse(cmdName, info, doubleValue: fm2014.DutTimeMeasured_s, var response = new CmdResponse(cmdName, info, doubleValue: fm2014.DutTimeMeasured_s,
unit: Units.GetInfo(Units.Name.TIME_s)); unit: Units.GetInfo(Units.Name.TIME_s));
PublishResponse(fm2014, new ProcessExecEventArgs("", ResponseEvent(fm2014, response);
actualProcessMessage: MeasurementInfo,
specificInfoObj: response));
} }
else else
{ {
retVal = false; retVal = false;
} }
} }
else
{
retVal = false;
}
} }
// Calculate the DUT to REF tolerance if both timer had been active // Calculate the DUT to REF tolerance if both timer had been active
@ -1657,10 +1592,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
var response = new CmdResponse(cmdName, info, var response = new CmdResponse(cmdName, info,
doubleValue: fm2014.DutToRefToleranceMeasured_percent, doubleValue: fm2014.DutToRefToleranceMeasured_percent,
unit: Units.GetInfo(Units.Name.PERCENT)); unit: Units.GetInfo(Units.Name.PERCENT));
PublishResponse(fm2014, new ProcessExecEventArgs("", ResponseEvent(fm2014, response, statusReturn);
actualProcessMessage: MeasurementInfo,
statusReturn: statusReturn,
specificInfoObj: response));
} }
} }
@ -1692,6 +1624,55 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
return true; return true;
} }
/// <summary>
/// Common routine to parse the response from the FM2014
/// </summary>
/// <param name="cmdName"></param>
/// <param name="responseStr"></param>
/// <param name="intValue"></param>
/// <param name="doubleValue"></param>
/// <returns></returns>
/// <remarks date="2026-Feb-14" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private static StatusReturn ParseResponse(CmdName cmdName, String responseStr, out UInt32? intValue,
out Double? doubleValue)
{
intValue = null;
doubleValue = null;
var statusReturn = StatusReturn.Unknown;
var numberStyle = NumberStyles.None;
var paraFormat = GetParaFormat(cmdName);
if (paraFormat == ParaFormat.UINT_HEX || paraFormat == ParaFormat.BYTE_HEX_PLUS_SIGN)
{
numberStyle = NumberStyles.HexNumber;
}
switch (paraFormat)
{
case ParaFormat.BYTE_HEX_PLUS_SIGN:
// The response contains a sign!
var signMultiplier = responseStr.Contains("+") ? 1.0 : -1.0;
var cleanedStr = responseStr.Replace("+", "").Replace("-", "");
if (uint.TryParse(cleanedStr, numberStyle, new CultureInfo("en"), out var intValueParsed))
{
doubleValue = intValueParsed * signMultiplier;
}
break;
case ParaFormat.UINT_HEX:
case ParaFormat.UINT_DEZ:
if (uint.TryParse(responseStr, numberStyle, new CultureInfo("en"), out intValueParsed))
{
intValue = intValueParsed;
statusReturn = StatusReturn.Okay;
}
break;
}
return statusReturn;
}
/// <summary> /// <summary>
/// Read FM2014: /// Read FM2014:
/// - Accesses directly to the 'SerialPort.ReadTo', /// - Accesses directly to the 'SerialPort.ReadTo',
@ -1701,14 +1682,17 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// <param name="fm2014"></param> /// <param name="fm2014"></param>
/// <param name="responseStr">output of pure pure raw data response from FM2014</param> /// <param name="responseStr">output of pure pure raw data response from FM2014</param>
/// <param name="intValue">output of parsed response to integer value</param> /// <param name="intValue">output of parsed response to integer value</param>
/// <param name="doubleValue"></param>
/// <returns></returns> /// <returns></returns>
/// <remarks date="2026-Jan-23..Feb-13" author="Thomas Wiedebusch"> /// <remarks date="2026-Jan-23..Feb-14" author="Thomas Wiedebusch">
/// - Initial. /// - Initial.
/// </remarks> /// </remarks>
private static Boolean Read(CmdName cmdName, FM2014 fm2014, out String responseStr, out Int32? intValue) private static Boolean Read(CmdName cmdName, FM2014 fm2014, out String responseStr, out UInt32? intValue,
out Double? doubleValue)
{ {
responseStr = ""; responseStr = "";
intValue = null; intValue = null;
doubleValue = null;
var info = $"{Resources.StrCommResponse}: {GetCmdInfo(cmdName)}"; var info = $"{Resources.StrCommResponse}: {GetCmdInfo(cmdName)}";
var statusReturn = StatusReturn.Failed; var statusReturn = StatusReturn.Failed;
if (!DisableWriteReadActualProcessIncreasing) if (!DisableWriteReadActualProcessIncreasing)
@ -1738,19 +1722,11 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
var isErrorStr = StatusReturn.Failed == statusReturn ? Resources.StrError + ": " : ""; var isErrorStr = StatusReturn.Failed == statusReturn ? Resources.StrError + ": " : "";
var response = new CmdResponse(cmdName, $"{isErrorStr}{info} {responseStr}"); var response = new CmdResponse(cmdName, $"{isErrorStr}{info} {responseStr}");
PublishResponse(fm2014, new ProcessExecEventArgs("", specificInfoObj: response, statusReturn: statusReturn)); // Publish the raw response for logging
ResponseEvent(fm2014, response, statusReturn);
var numberStyle = NumberStyles.None;
if (GetParaFormat(cmdName) == ParaFormat.INT_HEX)
{
numberStyle = NumberStyles.HexNumber;
}
if (int.TryParse(responseStr, numberStyle, new CultureInfo("en"), out var intParsedResponse))
{
intValue = intParsedResponse;
}
// Parse the raw value to get a useful result
statusReturn = ParseResponse(cmdName, responseStr, out intValue, out doubleValue);
return StatusReturn.Failed != statusReturn; return StatusReturn.Failed != statusReturn;
} }
@ -1768,7 +1744,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// <param name="fm2014"></param> /// <param name="fm2014"></param>
/// <param name="dataObj"></param> /// <param name="dataObj"></param>
/// <returns></returns> /// <returns></returns>
/// <remarks date="2026-Jan-12..Feb-13" author="Thomas Wiedebusch"> /// <remarks date="2026-Jan-12..Feb-14" author="Thomas Wiedebusch">
/// - Initial. /// - Initial.
/// </remarks> /// </remarks>
private static Boolean Write(CmdName cmdName, FM2014 fm2014, Object dataObj = null) private static Boolean Write(CmdName cmdName, FM2014 fm2014, Object dataObj = null)
@ -1814,7 +1790,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
var cmdCodeStr = GetCmdStr(cmdName); var cmdCodeStr = GetCmdStr(cmdName);
var info = GetCmdInfo(cmdName) + $": {cmdCodeStr}"; var info = GetCmdInfo(cmdName) + $": {cmdCodeStr}";
var isHexFormat = GetParaFormat(cmdName) == ParaFormat.INT_HEX; var isHexFormat = GetParaFormat(cmdName) == ParaFormat.UINT_HEX;
CmdResponse response; CmdResponse response;
// Without data the command alone needs to be sent // Without data the command alone needs to be sent
if (dataObj == null) if (dataObj == null)
@ -1835,7 +1811,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
} }
// The data contains the preceding setup, the command code and string delimiter will be added here // The data contains the preceding setup, the command code and string delimiter will be added here
else if (dataObj is UInt16 || dataObj is Byte) else if (dataObj is UInt16 || dataObj is UInt32 || dataObj is Byte)
{ {
var valueStr = isHexFormat ? $"{dataObj:X}{cmdCodeStr}" : $"{dataObj}{cmdCodeStr}"; var valueStr = isHexFormat ? $"{dataObj:X}{cmdCodeStr}" : $"{dataObj}{cmdCodeStr}";
SharedSerialPort.Write($"{valueStr}{END_OF_STR}"); SharedSerialPort.Write($"{valueStr}{END_OF_STR}");
@ -1853,7 +1829,8 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
} }
var statusReturn = retVal ? StatusReturn.Okay : StatusReturn.Failed; var statusReturn = retVal ? StatusReturn.Okay : StatusReturn.Failed;
PublishResponse(fm2014, new ProcessExecEventArgs("", specificInfoObj: response, statusReturn: statusReturn)); ResponseEvent(fm2014, response, statusReturn);
return retVal; return retVal;
} }
@ -2095,7 +2072,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
} }
try try
{ {
var measurementInfoStr = Resources.StrMeasMsgStanaloneRegulation; MeasurementInfo = Resources.StrMeasMsgStanaloneRegulation;
var responseStrs = new List<String>(); var responseStrs = new List<String>();
if (Write(cmdName, this)) if (Write(cmdName, this))
{ {
@ -2105,7 +2082,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
var loopCtr = 4; var loopCtr = 4;
do do
{ {
if (Read(cmdName, this, out var responseStr, out _)) if (Read(cmdName, this, out var responseStr, out _, out _))
responseStrs.Add(responseStr); responseStrs.Add(responseStr);
} while (loopCtr-- > 0); } while (loopCtr-- > 0);
} }
@ -2115,7 +2092,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
var loopCtr = 5; var loopCtr = 5;
do do
{ {
if (Read(cmdName, this, out var responseStr, out _)) if (Read(cmdName, this, out var responseStr, out _, out _))
responseStrs.Add(responseStr); responseStrs.Add(responseStr);
} while (loopCtr-- > 0); } while (loopCtr-- > 0);
} }
@ -2126,14 +2103,14 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{ {
var strCleaned = Regex.Replace(str, "[^0-9]", string.Empty); var strCleaned = Regex.Replace(str, "[^0-9]", string.Empty);
if (byte.TryParse(strCleaned, out var attenuation) && if (byte.TryParse(strCleaned, out var attenuation) &&
attenuation > 0 && attenuation <= 9) attenuation >= CURRENT_OUTPUT_ATTENUATION_MIN &&
attenuation <= CURRENT_OUTPUT_ATTENUATION_MAX)
{ {
cmdName = CmdName.CMD_MEAS_SET_ATTN; cmdName = CmdName.CMD_MEAS_SET_ATTN;
var info = GetCmdInfo(cmdName); var info = GetCmdInfo(cmdName);
CurrentOutputAttenuation = attenuation; CurrentOutputAttenuation = attenuation;
var response = new CmdResponse(cmdName, info, CurrentOutputAttenuation); var response = new CmdResponse(cmdName, info, CurrentOutputAttenuation);
PublishResponse(this, new ProcessExecEventArgs("", ResponseEvent(this, response);
actualProcessMessage: measurementInfoStr, specificInfoObj: response));
} }
} }
if (str.Contains(StandAlonePartSearchScaleStr)) if (str.Contains(StandAlonePartSearchScaleStr))
@ -2146,9 +2123,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
var info = GetCmdInfo(cmdName); var info = GetCmdInfo(cmdName);
RefToDutScale_norm = value; RefToDutScale_norm = value;
var response = new CmdResponse(cmdName, info, doubleValue: RefToDutScale_norm); var response = new CmdResponse(cmdName, info, doubleValue: RefToDutScale_norm);
PublishResponse(this, ResponseEvent(this, response);
new ProcessExecEventArgs("", actualProcessMessage: measurementInfoStr,
specificInfoObj: response));
} }
} }
@ -2161,18 +2136,14 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
var info = GetCmdInfo(cmdName); var info = GetCmdInfo(cmdName);
DutPulses_per_cm = value; DutPulses_per_cm = value;
var response = new CmdResponse(cmdName, info, DutPulses_per_cm); var response = new CmdResponse(cmdName, info, DutPulses_per_cm);
PublishResponse(this, ResponseEvent(this, response);
new ProcessExecEventArgs("", actualProcessMessage: measurementInfoStr,
specificInfoObj: response));
// Added 2026-Jan-01 to overcome limitation of max 9999 Impulses per volume // Added 2026-Jan-01 to overcome limitation of max 9999 Impulses per volume
cmdName = CmdName.CMD_REF_LPP_SCALE; cmdName = CmdName.CMD_REF_LPP_SCALE;
info = GetCmdInfo(cmdName); info = GetCmdInfo(cmdName);
// Calculate REF pulses per cubic meter // Calculate REF pulses per cubic meter
RefPulses_per_cm = (UInt32)Math.Round(DutPulses_per_cm * RefToDutScale_norm, 1); RefPulses_per_cm = (UInt32)Math.Round(DutPulses_per_cm * RefToDutScale_norm, 1);
response = new CmdResponse(cmdName, info, (Int32)RefPulses_per_cm); response = new CmdResponse(cmdName, info, RefPulses_per_cm);
PublishResponse(this, ResponseEvent(this, response);
new ProcessExecEventArgs("", actualProcessMessage: measurementInfoStr,
specificInfoObj: response));
} }
} }
} }
@ -2294,7 +2265,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
IsLoggedOn = true; IsLoggedOn = true;
if (Read(cmdName, this, out var responseStr, out _)) if (Read(cmdName, this, out var responseStr, out _, out _))
SerialNumber = responseStr; SerialNumber = responseStr;
if (!string.IsNullOrEmpty(SerialNumber)) if (!string.IsNullOrEmpty(SerialNumber))
@ -2309,7 +2280,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
return false; return false;
} }
if (Read(cmdName, this, out responseStr, out _)) if (Read(cmdName, this, out responseStr, out _, out _))
{ {
var cleanedResponse = Regex.Replace(responseStr, "[^0-9]", string.Empty); var cleanedResponse = Regex.Replace(responseStr, "[^0-9]", string.Empty);
if (int.TryParse(cleanedResponse, out var intValue)) if (int.TryParse(cleanedResponse, out var intValue))

View File

@ -655,8 +655,8 @@ namespace Sensus.Ui.FM2014TestBench
if (null == firstFm2014) if (null == firstFm2014)
return; return;
_fm2014Config.RefPulsesRequired = firstFm2014.RefPulsesRequired; _fm2014Config.RefPulsesRequired = (UInt16)firstFm2014.RefPulsesRequired;
_fm2014Config.DutPulsesRequired = firstFm2014.DutPulsesRequired; _fm2014Config.DutPulsesRequired = (UInt16)firstFm2014.DutPulsesRequired;
_fm2014Config.DoublePulseDeadtime = firstFm2014.DoublePulseDeadtime_ms; _fm2014Config.DoublePulseDeadtime = firstFm2014.DoublePulseDeadtime_ms;
_fm2014Config.DutToRefToleranceMax = firstFm2014.DutToRefCalibrationToleranceMax_percent; _fm2014Config.DutToRefToleranceMax = firstFm2014.DutToRefCalibrationToleranceMax_percent;
_fm2014Config.DutToRefToleranceMin = firstFm2014.DutToRefCalibrationToleranceMin_percent; _fm2014Config.DutToRefToleranceMin = firstFm2014.DutToRefCalibrationToleranceMin_percent;
@ -1100,7 +1100,7 @@ namespace Sensus.Ui.FM2014TestBench
UpdateContentControl(lblActualProcessText, e.ActualProcessMessage); UpdateContentControl(lblActualProcessText, e.ActualProcessMessage);
} }
if (e.ActualProcessPercent != null) if (e.ActualProcessPercent != null && !_autoProgressBar)
{ {
UpdateProgressBar(pbActualProgress, (Double)e.ActualProcessPercent); UpdateProgressBar(pbActualProgress, (Double)e.ActualProcessPercent);
} }