MiniPrf: - allow higher REF pulses per volume,

- Rounding of Ref to DUT scale
This commit is contained in:
Thomas Wiedebusch 2026-01-21 14:08:28 +01:00
parent 71ea89e0e7
commit 9bb3791b20
4 changed files with 219 additions and 161 deletions

View File

@ -277,7 +277,13 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core.Co
/// <summary>
/// Broadcast command without answer to address 0 or individual without answer
/// </summary>
BROADCAST
BROADCAST,
/// <summary>
/// Uninitialized addressing command type
/// </summary>
NOT_INITIALIZED,
}
/// <summary>
@ -363,7 +369,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core.Co
/// <summary>
/// Broadcast address definition
/// </summary>
public static readonly String BROADCAST_ADDRESS = $"{ADDR_RECORD_STR}{ADDR_RECORD_STR}0{ADDR_VALIDATION_STR}";
public static readonly String BROADCAST_ADDRESS = $"{ADDR_RECORD_STR}0{ADDR_VALIDATION_STR}";
// Unicode Strings for command table
private const String UNICODE_ACK = "\u0006";

View File

@ -61,9 +61,9 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
#region properties
/// <summary>
/// Resolution of FM2014 timer for measurement of pulse width in seconds
/// Resolution of FM2014 timer for measurement of pulse width in seconds based on 2994 Hz
/// </summary>
public const Single TMR_RESOLUTION_s = 334e-6f;
private const Double TMR_RESOLUTION_s = 334.001336e-6;
/// <inheritdoc/>
public Boolean CyclicReceiveTaskLoopIsActive
@ -109,14 +109,14 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
set
{
_address = value;
AddressStr = $"{ADDR_RECORD_STR}{ADDR_RECORD_STR}{_address}{ADDR_VALIDATION_STR}";
AddressStr = $"{ADDR_RECORD_STR}{_address}{ADDR_VALIDATION_STR}";
}
}
/// <summary>
/// Marker if broadcast connection was last communication
/// </summary>
private Boolean BroadcastCommIsActive
private CmdType CmdType
{
get;
set;
@ -168,68 +168,85 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// <summary>
/// Publish the FM2014 minimum pulses for REF and DUT time measurement.
/// </summary>
public const UInt16 TimeMeasurementPulsesSetupMin = 1;
public const UInt32 TimeMeasurementPulsesSetupMin = 1;
/// <summary>
/// Publish the FM2014 maximum pulses for REF and DUT time measurement.
/// </summary>
public const UInt16 TimeMeasurementPulsesSetupMax = 0xFFFF;
public const UInt32 TimeMeasurementPulsesSetupMax = 0xFFFF;
/// <summary>
/// Publish the FM2014 minimum pulses for REF and DUT regulation measurement.
/// This is the invalid marker which can be stored to the FM2014 with command
/// 'W' - 'REF pulses per volume'! The REF pulses/cm can then be restored using
/// 'Ref_pulses_per_cm = Dut_pulses_per_cm * RefToDutScale_norm'
/// </summary>
public const UInt16 PulsesPerCmRegulationSetupMin = 1;
/// <summary>
/// Publish the FM2014 maximum pulses for REF and DUT regulation measurement.
/// </summary>
public const UInt16 PulsesPerCmRegulationSetupMax = 9999;
private const UInt32 RefPulsesPerVolumeRegulationSetupInvalidMarker = 1;
private UInt16 _ref_pulse_per_cm = 1000;
/// <summary>
/// This is the minimum value which can be stored to the FM2014 with commands
/// 'V' - 'DUT pulses per volume' and 'W' - 'REF pulses per volume'!
/// </summary>
private const UInt32 PulsesPerVolumeRegulationSetupMin = 1;
/// <summary>
/// This is the maximum value which can be stored to the FM2014 with command
/// 'V' - 'DUT pulses per volume'!
/// </summary>
private const UInt32 PulsesPerVolumeRegulationSetupMax = 9999;
/// <summary>
/// This is the maximum input value for the REF which CANNOT be stored to FM2014
/// but will be used to calculate the 'RefToDutScale_norm'.
/// </summary>
private const UInt32 RefPulsesPerCmRegulationInputLimitMax = 100000000;
private UInt32 _ref_pulse_per_cm;
/// <inheritdoc/>
public UInt16 Ref_pulse_per_cm
public UInt32 Ref_pulse_per_cm
{
get => _ref_pulse_per_cm;
set
{
// Check limits and equality
if (value < PulsesPerCmRegulationSetupMin ||
value > PulsesPerCmRegulationSetupMax ||
if (value < PulsesPerVolumeRegulationSetupMin ||
value > RefPulsesPerCmRegulationInputLimitMax ||
value == _ref_pulse_per_cm)
return;
_ref_pulse_per_cm = value;
if (_dut_pulse_per_cm != 0)
RefToDutScale_norm = (Single)_ref_pulse_per_cm / _dut_pulse_per_cm;
if (_dut_pulse_per_cm != 0 && _ref_pulse_per_cm != 0)
RefToDutScale_norm = Math.Round((Double)_ref_pulse_per_cm / _dut_pulse_per_cm, 2);
}
}
private UInt16 _dut_pulse_per_cm = 100;
private UInt32 _dut_pulse_per_cm;
/// <inheritdoc/>
public UInt16 Dut_pulse_per_cm
public UInt32 Dut_pulse_per_cm
{
get => _dut_pulse_per_cm;
set
{
// Check limits and equality
if (value < PulsesPerCmRegulationSetupMin ||
value > PulsesPerCmRegulationSetupMax ||
// Check limits and equality, the DUT max input is 9999 as this will be
// directly stored to FM2014'V' - 'DUT pulses per volume'
if (value < PulsesPerVolumeRegulationSetupMin ||
value > PulsesPerVolumeRegulationSetupMax ||
value == _dut_pulse_per_cm)
return;
_dut_pulse_per_cm = value;
if (_dut_pulse_per_cm != 0)
RefToDutScale_norm = (Single)_ref_pulse_per_cm / _dut_pulse_per_cm;
if (_dut_pulse_per_cm != 0 && _ref_pulse_per_cm != 0)
RefToDutScale_norm = Math.Round((Double)_ref_pulse_per_cm / _dut_pulse_per_cm, 2);
}
}
/// <summary>
/// Publish the FM2014 minimum REF to DUT scale for error display.
/// </summary>
public const Single RefToDutScaleMin = 0.0001f;
public const Double RefToDutScaleMin = 0.0001;
/// <summary>
/// Publish the FM2014 maximum REF to DUT scale for error display.
/// </summary>
public const Single RefToDutScaleMax = 999.9f;
public const Double RefToDutScaleMax = 999.9;
/// <summary>
/// As the scale needed to be sent to the FM2014 doesn't follow the standardized nomenclature
@ -245,11 +262,14 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// "1000K3" = 100.0
/// Max value is "9999K3" = 999.9
/// </summary>
public String RefToDutScaleStr = "1000K+2";
private Single _refToDutScale_norm = 10.0f;
private String _refToDutScaleStr = "1000K+2";
private Double _refToDutScale_norm = 10.0f;
/// <inheritdoc/>
public Single RefToDutScale_norm
/// <remarks date="2026-Jan-14..20" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public Double RefToDutScale_norm
{
get => _refToDutScale_norm;
private set
@ -267,7 +287,9 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
}
var mantissa = (UInt16)number;
RefToDutScaleStr = $"{mantissa}{GetCmdStr(CmdName.CMD_REF_SET_SCALE)}{exponent}";
// The pre-generated string will be used to send it to the FM2014 directly
_refToDutScaleStr = $"{mantissa}{GetCmdStr(CmdName.CMD_REF_SET_SCALE)}{exponent}";
}
}
@ -288,7 +310,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// <summary>
/// The measured flow rate in cubic meters per hour using the REF
/// </summary>
public Single RefFlowRate_cm_per_h
public Double RefFlowRate_cm_per_h
{
internal set;
get;
@ -375,6 +397,10 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
SerialPort.PortName = comPort;
SerialPort.ReadTimeout = 1000;
SerialPort.WriteTimeout = 1000;
// Reset pulse ratios to force a preset with 'standalone' settings from
// FM2014 wit '%D@' - Read 'Default Measurement Setup'
_ref_pulse_per_cm = 0;
_dut_pulse_per_cm = 0;
return true;
}
@ -516,12 +542,13 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// - Set <see cref="CyclicReceiveTaskLoopIsActive"/> to false.
/// </summary>
/// <returns></returns>
/// <remarks date="2026-Jan-14..20" author="Thomas Wiedebusch">
/// <remarks date="2026-Jan-14..21" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public Boolean RegulationMeasurement()
{
if (Ref_pulse_per_cm == 0 || Dut_pulse_per_cm == 0 || RefToDutScale_norm == 0.0f)
if (Ref_pulse_per_cm == 0 || Dut_pulse_per_cm == 0 ||
RefToDutScale_norm < RefToDutScaleMin || RefToDutScale_norm > RefToDutScaleMax)
{
return false;
}
@ -543,7 +570,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
return false;
}
cmdName = CmdName.CMD_REF_SET_SCALE;
if (!Write(cmdName, RefToDutScaleStr))
if (!Write(cmdName, _refToDutScaleStr))
{
return false;
}
@ -598,7 +625,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{
// Publish the period [ms]
var response = new CmdResponse(cmdName, info,
doubleValue: TMR_RESOLUTION_s * 1000.0f * period,
doubleValue: TMR_RESOLUTION_s * 1000.0 * period,
siUnit: "m" + SiUnits.GetInfo(SiUnits.SiUnitName.TIME));
OnRawRecordReceived?.Invoke(this,
new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
@ -608,14 +635,14 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
cmdName = CmdName.CMD_CAL_FREQU_REF_PERIOD;
info = GetCmdInfo(cmdName);
response = new CmdResponse(cmdName, info,
doubleValue: 1.0f / (TMR_RESOLUTION_s * period),
doubleValue: 1.0 / (TMR_RESOLUTION_s * period),
siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.FREQUENCY));
OnRawRecordReceived?.Invoke(this,
new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
specificInfoObj: response));
// Convert to m³/h (3600 s/h) based on REF pulse rate
var flowRate_cm_per_h = 3600.0f / (period * TMR_RESOLUTION_s) / Ref_pulse_per_cm;
var flowRate_cm_per_h = 3600.0 / (period * TMR_RESOLUTION_s) / Ref_pulse_per_cm;
// Publish the calculated flow rate [m³/h]
cmdName = CmdName.CMD_CAL_FLOW_REF_PERIOD;
info = GetCmdInfo(cmdName);
@ -638,7 +665,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{
// Publish the period [ms]
var response = new CmdResponse(cmdName, info,
doubleValue: TMR_RESOLUTION_s * 1000.0f * period,
doubleValue: TMR_RESOLUTION_s * 1000.0 * period,
siUnit: "m" + SiUnits.GetInfo(SiUnits.SiUnitName.TIME));
OnRawRecordReceived?.Invoke(this,
new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
@ -648,14 +675,14 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
cmdName = CmdName.CMD_CAL_FREQU_DUT_PERIOD;
info = GetCmdInfo(cmdName);
response = new CmdResponse(cmdName, info,
doubleValue: 1.0f / (TMR_RESOLUTION_s * period),
doubleValue: 1.0 / (TMR_RESOLUTION_s * period),
siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.FREQUENCY));
OnRawRecordReceived?.Invoke(this,
new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
specificInfoObj: response));
// Convert to m³/h (3600 s/h) based on DUT pulse rate
var flowRate_cm_per_h = 3600.0f / (period * TMR_RESOLUTION_s) / Dut_pulse_per_cm;
var flowRate_cm_per_h = 3600.0 / (period * TMR_RESOLUTION_s) / Dut_pulse_per_cm;
// Publish the calculated flow rate [m³/h]
cmdName = CmdName.CMD_CAL_FLOW_DUT_PERIOD;
info = GetCmdInfo(cmdName);
@ -683,7 +710,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
specificInfoObj: response));
// Convert to m³/h (3600 s/h) and save the value
RefFlowRate_cm_per_h = 3600.0f * refFrequency / Ref_pulse_per_cm;
RefFlowRate_cm_per_h = 3600.0 * refFrequency / Ref_pulse_per_cm;
// Publish the calculated flow rate [m³/h]
cmdName = CmdName.CMD_CAL_FLOW_REF_FREQU;
info = GetCmdInfo(cmdName);
@ -716,24 +743,34 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// Transfer all standalone settings to FM2014 RAM and safe those to nonvolatile EEPROM
/// </summary>
/// <returns></returns>
/// <remarks date="2023-Jan-04" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2023-Jan-21" author="Thomas Wiedebusch">
/// - REF pulse rate can be 0 as 'invalid marker'.
/// </remarks>
public Boolean SaveStandAloneMeasurement()
{
var cmdName = CmdName.CMD_REF_LPP_SCALE;
try
{
var retVal = Write(cmdName, Ref_pulse_per_cm);
// Set to zero if it doesn't fit to the limit which can be stored as indicator for invalid value
// which should be recovered by 'Ref_pulses_per_cm = Dut_pulses_per_cm * RefToDutScale_norm'
var limitedRefPulsesPerCm = Ref_pulse_per_cm <= PulsesPerVolumeRegulationSetupMax ?
Ref_pulse_per_cm : RefPulsesPerVolumeRegulationSetupInvalidMarker;
var retVal = Write(cmdName, (UInt16)limitedRefPulsesPerCm);
if (retVal)
{
cmdName = CmdName.CMD_DUT_LPP_SCALE;
retVal = Write(cmdName, Dut_pulse_per_cm);
retVal = Write(cmdName, (UInt16)Dut_pulse_per_cm);
}
if (retVal)
{
cmdName = CmdName.CMD_REF_SET_SCALE;
// During setup of the 'Ref_pulse_per_cm' the 'RefToDutScaleStr' will be generated
retVal = Write(cmdName, RefToDutScaleStr);
// During setup of the 'Ref_pulse_per_cm' the '_refToDutScaleStr' will be generated
retVal = Write(cmdName, _refToDutScaleStr);
}
if (retVal)
@ -768,6 +805,11 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// <remarks date="2026-Jan-18" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2023-Jan-21" author="Thomas Wiedebusch">
/// - 'Ref_pulse_per_cm' will be calculated by 'Ref_pulses_per_cm = Dut_pulses_per_cm * RefToDutScale_norm'
/// to avoid an overflow as the REF pulses per volume can store max 9999 pulses. As the Dut_pulse_per_cm
/// is for the smallest meter 1000 Impulses/m³
/// </remarks>
private Boolean ReadAloneMeasurementControl(CmdName cmdName)
{
// Allow explicit standalone measurement commands
@ -834,36 +876,46 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{
cmdName = CmdName.CMD_REF_SET_SCALE;
var info = GetCmdInfo(cmdName);
RefToDutScale_norm = (Single)value;
RefToDutScale_norm = value;
var response = new CmdResponse(cmdName, info, doubleValue: RefToDutScale_norm);
OnRawRecordReceived?.Invoke(this,
new ProcessExecEventArgs("", actualProcessMessage: measurementInfoStr,
specificInfoObj: response));
}
}
if (str.Contains(StandAlonePartSearchRefPpvStr))
{
var strCleaned = Regex.Replace(str, "[^0-9]", string.Empty);
if (ushort.TryParse(strCleaned, out var value))
{
cmdName = CmdName.CMD_REF_LPP_SCALE;
var info = GetCmdInfo(cmdName);
Ref_pulse_per_cm = value;
var response = new CmdResponse(cmdName, info, Ref_pulse_per_cm);
OnRawRecordReceived?.Invoke(this,
new ProcessExecEventArgs("", actualProcessMessage: measurementInfoStr,
specificInfoObj: response));
}
}
// REMOVED 2026-Jan-21
//if (str.Contains(StandAlonePartSearchRefPpvStr))
//{
// var strCleaned = Regex.Replace(str, "[^0-9]", string.Empty);
// if (ushort.TryParse(strCleaned, out var value))
// {
// cmdName = CmdName.CMD_REF_LPP_SCALE;
// var info = GetCmdInfo(cmdName);
// Ref_pulse_per_cm = value;
// var response = new CmdResponse(cmdName, info, Ref_pulse_per_cm);
// OnRawRecordReceived?.Invoke(this,
// new ProcessExecEventArgs("", actualProcessMessage: measurementInfoStr,
// specificInfoObj: response));
// }
//}
if (str.Contains(StandAlonePartSearchDutPpvStr))
{
var strCleaned = Regex.Replace(str, "[^0-9]", string.Empty);
if (ushort.TryParse(strCleaned, out var value))
if (uint.TryParse(strCleaned, out var value))
{
cmdName = CmdName.CMD_DUT_LPP_SCALE;
var info = GetCmdInfo(cmdName);
Dut_pulse_per_cm = value;
var response = new CmdResponse(cmdName, info, Dut_pulse_per_cm);
var response = new CmdResponse(cmdName, info, (Int32)Dut_pulse_per_cm);
OnRawRecordReceived?.Invoke(this,
new ProcessExecEventArgs("", actualProcessMessage: measurementInfoStr,
specificInfoObj: response));
// Added 2026-Jan-01 to overcome limitation of max 9999 Impulses per volume
cmdName = CmdName.CMD_REF_LPP_SCALE;
info = GetCmdInfo(cmdName);
// Calculate REF pulses per cubic meter
Ref_pulse_per_cm = (UInt32)Math.Round(Dut_pulse_per_cm * RefToDutScale_norm, 2);
response = new CmdResponse(cmdName, info, (Int32)Ref_pulse_per_cm);
OnRawRecordReceived?.Invoke(this,
new ProcessExecEventArgs("", actualProcessMessage: measurementInfoStr,
specificInfoObj: response));
@ -881,8 +933,8 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
}
return true;
}
/// <summary>
/// Login to individual address.
/// </summary>
@ -947,7 +999,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError,
specificInfoObj: response,
statusReturn: StatusReturn.Failed));
BroadcastCommIsActive = false;
CmdType = CmdType.NOT_INITIALIZED;
return false;
}
@ -961,13 +1013,13 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError,
specificInfoObj: response,
statusReturn: StatusReturn.Failed));
BroadcastCommIsActive = false;
CmdType = CmdType.NOT_INITIALIZED;
return false;
}
}
// If broadcast communication is already active
if (BroadcastCommIsActive)
if (CmdType == CmdType.BROADCAST)
{
return true;
}
@ -981,7 +1033,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
var info = GetCmdInfo(cmdName) + ": " + BROADCAST_ADDRESS;
var response = new CmdResponse(cmdName, info);
OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs("", specificInfoObj: response));
BroadcastCommIsActive = true;
CmdType = CmdType.BROADCAST;
return true;
}
catch (Exception e)
@ -990,7 +1042,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
$"{Resources.StrCmdRespErrorBroadcast} - {e.Message}");
OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError, specificInfoObj: response,
statusReturn: StatusReturn.Failed));
BroadcastCommIsActive = false;
CmdType = CmdType.NOT_INITIALIZED;
return false;
}
}
@ -999,7 +1051,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// Initiate communication to individual node
/// </summary>
/// <returns></returns>
/// <remarks date="2026-Jan-08..20" author="Thomas Wiedebusch">
/// <remarks date="2026-Jan-08..21" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private Boolean InitiateIndividualComm()
@ -1011,6 +1063,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError,
specificInfoObj: response,
statusReturn: StatusReturn.Failed));
CmdType = CmdType.NOT_INITIALIZED;
return false;
}
@ -1023,6 +1076,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError,
specificInfoObj: response,
statusReturn: StatusReturn.Failed));
CmdType = CmdType.NOT_INITIALIZED;
return false;
}
}
@ -1044,7 +1098,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
FullId = FullId.Replace(ADDR_RECORD_STR, "");
var fields = FullId.Split('.');
FwVersion = fields[1];
BroadcastCommIsActive = false;
CmdType = GetCmdCmdType(cmdName);
return true;
}
}
@ -1053,12 +1107,14 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
response = new CmdResponse(CmdName.CMD_CONNECT, $"{Resources.StrCmdRespErrorIndividual} - {e.Message}");
OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError,
specificInfoObj: response, statusReturn: StatusReturn.Failed));
CmdType = CmdType.NOT_INITIALIZED;
return false;
}
response = new CmdResponse(CmdName.CMD_CONNECT, $"{Resources.StrCmdRespErrorIndividual}");
OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError,
specificInfoObj: response, statusReturn: StatusReturn.Failed));
CmdType = CmdType.NOT_INITIALIZED;
return false;
}
@ -1080,7 +1136,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{
if (SerialPort == null || !SerialPort.IsOpen)
{
BroadcastCommIsActive = false;
CmdType = CmdType.NOT_INITIALIZED;
IsLoggedOn = false;
CyclicReceiveTaskLoopIsActive = false;
return false;
@ -1090,14 +1146,14 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
var retVal = true;
if (CmdType.BROADCAST == GetCmdCmdType(cmdName))
{
if (!BroadcastCommIsActive)
if (CmdType != CmdType.BROADCAST)
{
retVal = InitiateBroadcastComm();
}
}
else
{
if (BroadcastCommIsActive)
if (CmdType != CmdType.INDIVIDUAL_WA || CmdType != CmdType.INDIVIDUAL_WA)
{
retVal = InitiateIndividualComm();
}
@ -1110,7 +1166,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
var cmdCodeStr = GetCmdStr(cmdName);
var info = GetCmdInfo(cmdName);
var response = new CmdResponse();
CmdResponse response;
// Without data the command alone needs to be sent
if (dataObj == null)
{
@ -1130,12 +1186,16 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
}
// The data contains the preceding setup, the command code and string delimiter will be added here
if (dataObj is UInt16 || dataObj is Byte)
else if (dataObj is UInt16 || dataObj is Byte)
{
SerialPort.Write($"{dataObj}{cmdCodeStr}{END_OF_STR}");
response = new CmdResponse(cmdName, Resources.StrCommDirectionSetup + ": " + info +
$": {dataObj}{cmdCodeStr}");
}
else
{
return false;
}
}
OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs("", specificInfoObj: response));
@ -1189,7 +1249,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{
ResetMeasurement();
IsLoggedOn = false;
BroadcastCommIsActive = false;
CmdType = CmdType.NOT_INITIALIZED;
SerialPort?.Close();
return SerialPort != null && !SerialPort.IsOpen;
}

View File

@ -123,7 +123,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// <summary>
/// Reference pulses per cubic-meter
/// </summary>
UInt16 Ref_pulse_per_cm
UInt32 Ref_pulse_per_cm
{
get;
}
@ -131,12 +131,12 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// <summary>
/// REF to DUT scale normalized
/// </summary>
Single RefToDutScale_norm { get; }
Double RefToDutScale_norm { get; }
/// <summary>
/// Device under test pulses per cubic-meter
/// </summary>
UInt16 Dut_pulse_per_cm
UInt32 Dut_pulse_per_cm
{
get;
}

View File

@ -90,6 +90,9 @@ namespace Sensus.MiniPrf.Ui
private static readonly Color ColorProcessFailed = Color.Red;
//private static readonly Color ColorOngoingProcess = Color.Blue;
//private static readonly Color ColorUnknownStatus = Color.Gray;
private static Color ColorStandardInputField;
private static Color ColorStandardDisplayField;
//private const String SuccessSign = @"✔";
//private const String FailedSign = @"✘";
@ -130,6 +133,9 @@ namespace Sensus.MiniPrf.Ui
//Thread.CurrentThread.CurrentUICulture = cultureInfo;
//Thread.CurrentThread.CurrentCulture = cultureInfo;
_version = Assembly.GetExecutingAssembly().GetName().Version;
ColorStandardDisplayField = lblFM2014SerialPort.BackColor;
ColorStandardInputField = cbxFM2014ComPort.BackColor;
}
/// <summary>
@ -140,7 +146,7 @@ namespace Sensus.MiniPrf.Ui
/// </remarks>
private void Init()
{
ViewProcessControl(false);
ActionControl(false);
_autoProgressBar = false;
_regulationSetupHasChanged = false;
grpBoxDebug.Visible = false;
@ -165,7 +171,7 @@ namespace Sensus.MiniPrf.Ui
{
cbxFM2014ComPort.Items.Add(comPort);
}
cbxFM2014ComPort.Text = cbxFM2014ComPort.Items[_comPortIdx].ToString();
var itemContent = _fm2014Config?.Address ?? 1;
for (var idx = 0; idx < cbxFM2014Address.MaxDropDownItems; idx++)
@ -189,7 +195,7 @@ namespace Sensus.MiniPrf.Ui
// FM2014 group box
lblConnectFM2014.Text = Resources.StrLblFM2014NotConnected;
lblConnectFM2014.ForeColor = Color.Red;
lblConnectFM2014.ForeColor = ColorProcessFailed;
tbxFM2014FullId.Text = "";
tbxFM2014ApplicationFwVersion.Text = "";
lblFM2014SerialNumber.Text = Resources.StrLblFM2014SerialNumber;
@ -300,7 +306,6 @@ namespace Sensus.MiniPrf.Ui
#endregion FormControls
#region TimerControls
private void tmrProgressUpdate_Tick(Object sender, EventArgs e)
{
if (_autoProgressBar || barSingleProgressUpdate.Visible)
@ -370,7 +375,6 @@ namespace Sensus.MiniPrf.Ui
cbxFM2014Address.Enabled = true;
cbxFM2014ComPort.Enabled = true;
cbxFM2014TolerancePercent.Enabled = true;
}
/// <summary>
@ -388,11 +392,9 @@ namespace Sensus.MiniPrf.Ui
}
SetFM2014AccessEnabled();
}
#endregion ActivationControls
#region BoardControls
/// <summary>
/// Establish connection
/// </summary>
@ -508,94 +510,84 @@ namespace Sensus.MiniPrf.Ui
#region ProcessControls
/// <summary>
/// Common method to (de-)activate controls and timer.
/// </summary>
/// <remarks date="2026-Jan-14..20" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private void ActionControl(Boolean isActive)
{
if (isActive)
Invoke(new Action(() =>
{
Invoke(new Action(() =>
if (isActive)
{
ViewProcessControl(true);
lblActualProcess.Visible = true;
barSingleProgressUpdate.Visible = true;
SetFM2014AccessLocked();
tmrProgressUpdate.Enabled = true;
}));
}
else
{
Invoke(new Action(() =>
}
else
{
ViewProcessControl(false);
lblActualProcess.Visible = false;
barSingleProgressUpdate.Visible = false;
tmrProgressUpdate.Enabled = false;
lblWaitingForMeterResponse.Visible = false;
lblActualProcess.Text = "";
CheckUpdateEnabled();
}));
}
}
/// <summary>
/// View all process bars and labels
/// </summary>
private void ViewProcessControl(Boolean view)
{
if (view)
{
lblActualProcess.Visible = true;
barSingleProgressUpdate.Visible = true;
tmrProgressUpdate.Enabled = true;
SetFM2014AccessLocked();
}
else
{
lblActualProcess.Visible = false;
barSingleProgressUpdate.Visible = false;
tmrProgressUpdate.Enabled = false;
lblWaitingForMeterResponse.Visible = false;
lblActualProcess.Text = "";
SetFM2014AccessEnabled();
}
//common actions and settings
lblActualProcess.Update();
barSingleProgressUpdate.Value = 0;
barSingleProgressUpdate.Update();
Update();
}
//common actions and settings
lblActualProcess.Update();
barSingleProgressUpdate.Value = 0;
barSingleProgressUpdate.Update();
Update();
}));
}
/// <summary>
/// Common method to prepare for changed setup.
/// </summary>
/// <remarks date="2026-Jan-14..20" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private void SetupHasChanged()
{
_regulationSetupHasChanged = true;
btnSaveRegulationSetup.Enabled = true;
tbxRefFrequencyHz.Text = "";
tbxRefFrequencyHz.BackColor = gbxDutToRefRegulation.BackColor;
tbxRefFrequencyHz.BackColor = ColorStandardDisplayField;
tbxActualFlowRateCmPerHour.Text = "";
tbxActualFlowRateCmPerHour.BackColor = gbxDutToRefRegulation.BackColor;
tbxActualFlowRateCmPerHour.BackColor = ColorStandardDisplayField;
tbxActualMeasuredToleranceDutToRef.Text = "";
tbxActualMeasuredToleranceDutToRef.BackColor = gbxDutToRefRegulation.BackColor;
tbxRefPulsePerCm.BackColor = Color.White;
tbxDutPulsePerCm.BackColor = Color.White;
tbxScaleRefToDut.BackColor = lblRefPulsePerVolume.BackColor;
tbxActualMeasuredToleranceDutToRef.BackColor = ColorStandardDisplayField;
tbxManualInputVolumeLiters.BackColor = Color.White;
tbxRefCalibrationResultPulsePerCm.BackColor = lblRefPulsePerVolume.BackColor;
tbxRefPulsePerCm.BackColor = ColorStandardInputField;
tbxDutPulsePerCm.BackColor = ColorStandardInputField;
tbxScaleRefToDut.BackColor = ColorStandardDisplayField;
tbxManualInputVolumeLiters.BackColor = ColorStandardInputField;
tbxRefCalibrationResultPulsePerCm.BackColor = ColorStandardDisplayField;
}
/// <summary>
/// Common routine for REF to DUT scale check and update
/// </summary>
/// <returns></returns>
/// <remarks date="2026-Jan-14..21" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private Boolean UpdateRefToDutScale()
{
tbxScaleRefToDut.Text = $@"{Fm2014.RefToDutScale_norm:F4}";
// Display error if scale doesn't fit
if (Math.Abs(Fm2014.RefToDutScale_norm -
if (Math.Abs(Fm2014.RefToDutScale_norm -
(Single)Fm2014.Ref_pulse_per_cm / Fm2014.Dut_pulse_per_cm) > FM2014.RefToDutScaleMin ||
Fm2014.RefToDutScale_norm < FM2014.RefToDutScaleMin ||
Fm2014.RefToDutScale_norm > FM2014.RefToDutScaleMax )
Fm2014.RefToDutScale_norm < FM2014.RefToDutScaleMin ||
Fm2014.RefToDutScale_norm > FM2014.RefToDutScaleMax)
{
tbxScaleRefToDut.BackColor = Color.Red;
tbxScaleRefToDut.BackColor = ColorProcessFailed;
tbxScaleRefToDut.Text = Resources.StrError;
btnSaveRegulationSetup.Enabled = false;
return false;
@ -888,14 +880,14 @@ namespace Sensus.MiniPrf.Ui
//Backup the actual setting to detect changes
var backupPulses_per_cm = Fm2014.Ref_pulse_per_cm;
Fm2014.Ref_pulse_per_cm = (UInt16)pulses_per_cm;
Fm2014.Ref_pulse_per_cm = (UInt32)pulses_per_cm;
// Output the Fm2014 setting to avoid wrong display of invalid ranges as this will be
// limited during the setup of the FM2014 property!
tbxRefPulsePerCm.Text = $@"{Fm2014.Ref_pulse_per_cm:D}";
// During setup of the 'Ref_pulse_per_cm' the 'RefToDutScaleStr' will be generated
if (!UpdateRefToDutScale())
{
tbxRefPulsePerCm.BackColor = Color.Red;
tbxRefPulsePerCm.BackColor = ColorProcessFailed;
return;
}
// Check if values have changed and need to be updated in the standalone setup
@ -958,14 +950,14 @@ namespace Sensus.MiniPrf.Ui
//Backup the actual setting to detect changes
var backupPulses_per_cm = Fm2014.Dut_pulse_per_cm;
// Try to set the new calculated REF pulses limited by the property setter
Fm2014.Dut_pulse_per_cm = (UInt16)pulses_per_cm;
Fm2014.Dut_pulse_per_cm = (UInt32)pulses_per_cm;
// Output the Fm2014 setting to avoid wrong display of invalid ranges as this will be
// limited during the setup of the FM2014 property!
tbxDutPulsePerCm.Text = $@"{Fm2014.Dut_pulse_per_cm:D}";
// During setup of the 'Dut_pulse_per_cm' the 'RefToDutScaleStr' will be generated
if (!UpdateRefToDutScale())
{
tbxDutPulsePerCm.BackColor = Color.Red;
tbxDutPulsePerCm.BackColor = ColorProcessFailed;
return;
}
@ -1022,12 +1014,12 @@ namespace Sensus.MiniPrf.Ui
{
tbxRefCalibrationResultPulsePerCm.Text = $@"{Fm2014.Ref_pulse_per_cm:D}";
tbxRefPulsePerCm.Text = $@"{Fm2014.Ref_pulse_per_cm:D}";
tbxManualInputVolumeLiters.BackColor = Color.White;
tbxRefCalibrationResultPulsePerCm.BackColor = lblRefPulsePerVolume.BackColor;
tbxManualInputVolumeLiters.BackColor = ColorStandardInputField;
tbxRefCalibrationResultPulsePerCm.BackColor = ColorStandardDisplayField;
if (!UpdateRefToDutScale())
{
tbxManualInputVolumeLiters.BackColor = Color.Red;
tbxManualInputVolumeLiters.BackColor = ColorProcessFailed;
return;
}
@ -1039,8 +1031,8 @@ namespace Sensus.MiniPrf.Ui
}
else
{
tbxManualInputVolumeLiters.BackColor = Color.Red;
tbxRefCalibrationResultPulsePerCm.BackColor = Color.Red;
tbxManualInputVolumeLiters.BackColor = ColorProcessFailed;
tbxRefCalibrationResultPulsePerCm.BackColor = ColorProcessFailed;
tbxRefCalibrationResultPulsePerCm.Text = Resources.StrError;
}
}
@ -1069,7 +1061,7 @@ namespace Sensus.MiniPrf.Ui
/// <summary>
/// Feedback from FM2014being parsed to GUI
/// </summary>
/// <remarks date="2023-Jan-04..19" author="Thomas Wiedebusch">
/// <remarks date="2023-Jan-04..21" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private void DataReceived_Handler(Object sender, ProcessExecEventArgs e)
@ -1121,13 +1113,13 @@ namespace Sensus.MiniPrf.Ui
tbxRefFrequencyDirectHz.Text = $@"{resp.IntValue:D}";
if (resp.IntValue < 1 || resp.IntValue > 254)
{
tbxRefFrequencyHz.BackColor = Color.Red;
tbxActualFlowRateCmPerHour.BackColor = Color.Red;
tbxRefFrequencyHz.BackColor = ColorProcessFailed;
tbxActualFlowRateCmPerHour.BackColor = ColorProcessFailed;
}
else
{
tbxRefFrequencyHz.BackColor = gbxDutToRefRegulation.BackColor;
tbxActualFlowRateCmPerHour.BackColor = gbxDutToRefRegulation.BackColor;
tbxRefFrequencyHz.BackColor = ColorStandardDisplayField;
tbxActualFlowRateCmPerHour.BackColor = ColorStandardDisplayField;
}
break;
}
@ -1143,15 +1135,15 @@ namespace Sensus.MiniPrf.Ui
if (resp.DoubleValue < -Fm2014.Tolerance_percent ||
resp.DoubleValue > Fm2014.Tolerance_percent)
{
tbxActualMeasuredToleranceDutToRef.BackColor = Color.Red;
tbxActualMeasuredToleranceDutToRef.BackColor = ColorProcessFailed;
}
else
{
tbxActualMeasuredToleranceDutToRef.BackColor = gbxDutToRefRegulation.BackColor;
tbxActualMeasuredToleranceDutToRef.BackColor = ColorStandardDisplayField;
}
break;
case FM2014CmdDef.CmdName.CMD_REF_SET_SCALE:
tbxScaleRefToDut.Text = $@"{resp.DoubleValue:F4}" ;
tbxScaleRefToDut.Text = $@"{resp.DoubleValue:F4}";
break;
// DEBUG
case FM2014CmdDef.CmdName.CMD_GET_REF_PERIOD: