diff --git a/Common/Hardware/WaterMeter/MechanicalMeter/FM2014/FM2014Core/FM2014.cs b/Common/Hardware/WaterMeter/MechanicalMeter/FM2014/FM2014Core/FM2014.cs
index 43c048ba..3e0f777b 100644
--- a/Common/Hardware/WaterMeter/MechanicalMeter/FM2014/FM2014Core/FM2014.cs
+++ b/Common/Hardware/WaterMeter/MechanicalMeter/FM2014/FM2014Core/FM2014.cs
@@ -38,6 +38,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO.Ports;
+using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@@ -46,7 +47,6 @@ using Xylem.Common.Metrology.Measurements.Consts;
using Xylem.Common.Utils.ProcessExec.EventArguments;
using static Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core.Consts.FM2014CmdDef;
-
namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{
@@ -58,24 +58,66 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
///
public class FM2014
{
- #region events
- ///
- /// Received data feedback
- ///
- public virtual event EventHandler OnRawRecordReceived;
- #endregion
-
- #region properties
- ///
- /// Shared cancellation token
- ///
- public static CancellationToken SharedCancellationToken { set; get; }
-
+ #region ------------------------------------------- constants -------------------------------------------------
///
/// Resolution of FM2014 timer for measurement of pulse width in seconds based on 2994 Hz
///
private const Double TMR_RESOLUTION_s = 334.001336e-6;
+ ///
+ /// 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'
+ ///
+ private const UInt32 RefPulsesPerVolumeRegulationSetupInvalidMarker = 1;
+
+ ///
+ /// fm2014 is the minimum value which can be stored to the FM2014 with commands
+ /// 'V' - 'DUT pulses per volume' and 'W' - 'REF pulses per volume'!
+ ///
+ private const UInt32 PulsesPerVolumeRegulationSetupMin = 1;
+
+ ///
+ /// This is the maximum value which can be stored to the FM2014 with command
+ /// 'V' - 'DUT pulses per volume'!
+ ///
+ private const UInt32 PulsesPerVolumeRegulationSetupMax = 9999;
+
+ ///
+ /// This is the maximum input value for the REF which CANNOT be stored to FM2014
+ /// but will be used to calculate the 'RefToDutScale_norm'.
+ ///
+ private const UInt32 RefPulsesPerCmRegulationInputLimitMax = 100000000;
+
+ ///
+ /// Publish the FM2014 minimum REF to DUT scale for error display.
+ ///
+ public const Double RefToDutScaleMin = 0.0001;
+ ///
+ /// Publish the FM2014 maximum REF to DUT scale for error display.
+ ///
+ public const Double RefToDutScaleMax = 999.9;
+
+ ///
+ /// The mantissa for the scale value has to be in the range from 1000 to 9999.
+ ///
+ private const UInt16 MantissaScaleMin = 1000;
+
+ #endregion ---------------------------------------- constants -------------------------------------------------
+
+ #region ------------------------------------------- events ----------------------------------------------------
+ ///
+ /// Received data feedback
+ ///
+ public virtual event EventHandler OnRawRecordReceived;
+ #endregion ---------------------------------------- events ----------------------------------------------------
+
+ #region ------------------------------------------- static properties -----------------------------------------
+ ///
+ /// Shared cancellation token
+ ///
+ public static CancellationToken SharedCancellationToken { set; get; }
+
///
/// Shared receive task is active for all connected FM2014.
/// Used to mark this event and to kill ongoing task if set inactive.
@@ -85,7 +127,27 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
///
/// Shared list of all connected FM2014
///
- public static List RegisteredFm2014s { get; private set; }
+ public static List RegisteredFm2014s { get; private set; }
+
+ ///
+ /// Serial port shared for all connected FM2014
+ ///
+ private static SerialPort SharedSerialPort { get; set; }
+
+ ///
+ /// Marker if broadcast connection was last communication
+ ///
+ private static CmdType SharedCmdTypeReminderLastCmd { get; set; }
+
+
+ ///
+ /// Reminder of last individual address of FM2014 to initiate a new communication to another device.
+ ///
+ private static Int32 ReminderLastIndividualCommFm2014Address { get; set; }
+
+ #endregion ---------------------------------------- static properties -----------------------------------------
+
+ #region ------------------------------------------- object properties -----------------------------------------
/////
///// Logger for raw data output
@@ -100,11 +162,6 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
///
public String SerialNumber { get; private set; }
- ///
- /// Serial port shared for all connected FM2014
- ///
- private static SerialPort SharedSerialPort { get; set; }
-
///
/// FM2014 identification string e.g. FM2014.112.1
///
@@ -124,11 +181,6 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
}
}
- ///
- /// Marker if broadcast connection was last communication
- ///
- private static CmdType SharedCmdTypeReminderLastCmd { get; set; }
-
///
/// FM2014 address string
///
@@ -169,11 +221,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
///
/// Use the damped tolerance output from the FM2014 to stabilize this value
///
- public Boolean UseDampedTolerance
- {
- get;
- set;
- }
+ public Boolean UseDampedTolerance { get; set; }
///
/// Publish the FM2014 minimum pulses for REF and DUT time measurement.
@@ -184,32 +232,6 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
///
public const UInt32 TimeMeasurementPulsesSetupMax = 0xFFFF;
-
- ///
- /// 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'
- ///
- private const UInt32 RefPulsesPerVolumeRegulationSetupInvalidMarker = 1;
-
- ///
- /// 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'!
- ///
- private const UInt32 PulsesPerVolumeRegulationSetupMin = 1;
-
- ///
- /// This is the maximum value which can be stored to the FM2014 with command
- /// 'V' - 'DUT pulses per volume'!
- ///
- private const UInt32 PulsesPerVolumeRegulationSetupMax = 9999;
-
- ///
- /// This is the maximum input value for the REF which CANNOT be stored to FM2014
- /// but will be used to calculate the 'RefToDutScale_norm'.
- ///
- private const UInt32 RefPulsesPerCmRegulationInputLimitMax = 100000000;
-
private UInt32 _ref_pulse_per_cm;
///
/// Reference pulses per cubic-meter
@@ -253,20 +275,6 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
}
}
- ///
- /// Publish the FM2014 minimum REF to DUT scale for error display.
- ///
- public const Double RefToDutScaleMin = 0.0001;
- ///
- /// Publish the FM2014 maximum REF to DUT scale for error display.
- ///
- public const Double RefToDutScaleMax = 999.9;
-
- ///
- /// The mantissa for the scale value has to be in the range from 1000 to 9999.
- ///
- private const UInt16 MantissaScaleMin = 1000;
-
///
/// As the scale needed to be sent to the FM2014 doesn't follow the standardized nomenclature
/// for floating point values it has to be converted to the FM2014 requirement.
@@ -381,63 +389,313 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
_actualProcessProgress_percent = 100.0 * _actualProcessProgress / MaxActualProcessProgress;
}
}
- #endregion
+ #endregion ---------------------------------------- object properties -----------------------------------------
- #region methods
+ #region ------------------------------------------- static methods --------------------------------------------
///
/// Executes all StoreConfiguration and StoreCalibration for each application.
///
/// true if all configurations are stored
- public Boolean StoreAllConfigurations()
+ public static Boolean StoreAllConfigurations()
{
return false;
}
///
- /// Common routine for reboot being able to override this routine which will be called in
- /// to simulate a reboot.
+ /// Read FM2014:
+ /// - Accesses directly to the 'SerialPort.ReadTo',
+ /// - An exception has to be caught by the caller.
///
+ ///
+ ///
+ ///
///
- public StatusReturn RebootFM2014()
+ ///
+ /// - Initial.
+ ///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
+ private static Boolean Read(CmdName cmdName, FM2014 fm2014, out String responseStr)
{
- return StatusReturn.Failed;
+ responseStr = "";
+ var info = $"{Resources.StrCommResponse}: {GetCmdInfo(cmdName)}";
+ var statusReturn = StatusReturn.Failed;
+
+ // Check for individual command without response
+ if (CmdType.INDIVIDUAL_UNRESPONSIVE == SharedCmdTypeReminderLastCmd &&
+ CmdType.INDIVIDUAL_UNRESPONSIVE == GetCmdCmdType(cmdName))
+ {
+ statusReturn = StatusReturn.Skipped;
+ }
+ else if (CmdType.INDIVIDUAL == SharedCmdTypeReminderLastCmd &&
+ CmdType.INDIVIDUAL == GetCmdCmdType(cmdName))
+ {
+ // Read or timeout with a TimeoutException
+ var answerStr = SharedSerialPort?.ReadTo(END_OF_STR);
+ if (string.IsNullOrEmpty(answerStr))
+ {
+ statusReturn = StatusReturn.Failed;
+ }
+ else
+ {
+ statusReturn = StatusReturn.Okay;
+ responseStr = answerStr;
+ }
+ }
+
+ var isErrorStr = StatusReturn.Failed == statusReturn ? Resources.StrError + ": " : "";
+ var response = new CmdResponse(cmdName, $"{isErrorStr}{info} {responseStr}");
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014, new ProcessExecEventArgs("", specificInfoObj: response,
+ statusReturn: statusReturn));
+
+ return StatusReturn.Failed != statusReturn;
}
///
- /// Connect to FM2014
+ /// Write command to FM2014:
+ /// - Accesses directly to the 'SerialPort.Write',
+ /// - Select broadcast or individual addressing based on the command table,
+ /// - Transmits optional data:
+ /// - If the dataObj is a string, this is going to be sent as is (e.g. "1000K2"),
+ /// - If the dataObj is an UInt16 or Byte this will be assembled with the command code (e.g. "3T"),
+ /// - Every 'Write' is a string delimited here with 'CR',
+ /// - An exception has to be caught by the caller.
///
- ///
- ///
+ ///
+ ///
+ ///
///
- public Boolean Connect(String comPort, Int32? address = null)
+ ///
+ /// - Initial.
+ ///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
+ private static Boolean Write(CmdName cmdName, FM2014 fm2014, Object dataObj = null)
{
+ if (fm2014 == null)
+ return false;
+
if (SharedSerialPort == null)
{
- SharedSerialPort = new SerialPort();
- SharedSerialPort.BaudRate = 1200;
- SharedSerialPort.DataBits = 7;
- SharedSerialPort.Parity = Parity.Odd;
- SharedSerialPort.PortName = comPort;
- SharedSerialPort.ReadTimeout = 1000;
- SharedSerialPort.WriteTimeout = 1000;
+ SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
+ fm2014.IsLoggedOn = false;
+ SharedCyclicReceiveTaskLoopIsActive = false;
+ return false;
}
- if (RegisteredFm2014s == null)
- RegisteredFm2014s = new List();
+ // Initiate communication using the individual or broadcast address
+ var retVal = true;
+ if (CmdType.BROADCAST == GetCmdCmdType(cmdName))
+ {
+ if (SharedCmdTypeReminderLastCmd != CmdType.BROADCAST)
+ {
+ retVal = InitiateBroadcastComm(fm2014);
+ }
+ }
+ else
+ {
+ if (ReminderLastIndividualCommFm2014Address != fm2014.Address ||
+ SharedCmdTypeReminderLastCmd != CmdType.INDIVIDUAL ||
+ SharedCmdTypeReminderLastCmd != CmdType.INDIVIDUAL)
+ {
+ retVal = InitiateIndividualComm(fm2014);
+ if (retVal)
+ ReminderLastIndividualCommFm2014Address = fm2014.Address;
+ }
+ }
- if (address != null)
- Address = (Int32)address;
+ if (!retVal)
+ {
+ return false;
+ }
- RegisteredFm2014s.Add(AddressStr);
+ var cmdCodeStr = GetCmdStr(cmdName);
+ var info = GetCmdInfo(cmdName) + $": {cmdCodeStr}";
+ CmdResponse response;
+ // Without data the command alone needs to be sent
+ if (dataObj == null)
+ {
+ SharedSerialPort.Write($"{cmdCodeStr}{END_OF_STR}");
+ var additionalInfo = GetCmdCmdType(cmdName) == CmdType.BROADCAST
+ ? Resources.StrCommDirectionSetup
+ : Resources.StrCommDirectionRequest;
+ response = new CmdResponse(cmdName, $"{additionalInfo}: {info}");
+ }
+ else
+ {
+ // A string has to be entire assembled, only the string delimiter will to be added here
+ if (dataObj is String)
+ {
+ SharedSerialPort.Write($"{dataObj}{END_OF_STR}");
+ response = new CmdResponse(cmdName, $"{Resources.StrCommDirectionSetup}: {info}: {dataObj}");
+ }
- // 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;
- SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
- return true;
+ // The data contains the preceding setup, the command code and string delimiter will be added here
+ else if (dataObj is UInt16 || dataObj is Byte)
+ {
+ SharedSerialPort.Write($"{dataObj}{cmdCodeStr}{END_OF_STR}");
+ response = new CmdResponse(cmdName,
+ $"{Resources.StrCommDirectionSetup}: {info}: {dataObj}{cmdCodeStr}");
+
+ }
+ else
+ {
+ response = new CmdResponse(cmdName,
+ $"{Resources.StrError}: {Resources.StrCommDirectionSetup}: {info} - " +
+ $"{Resources.StrCmdRespErrorPramTypeNotSupported} {dataObj}");
+ retVal = false;
+ }
+ }
+
+ var statusReturn = retVal ? StatusReturn.Okay : StatusReturn.Failed;
+ fm2014.OnRawRecordReceived?.Invoke(fm2014, new ProcessExecEventArgs("", specificInfoObj: response,
+ statusReturn: statusReturn));
+
+ return retVal;
}
+ ///
+ /// Initiate the broadcast communication:
+ /// - This 'InitiateBroadcastComm' needs to access the 'SerialPort.Write' directly as it is called from
+ /// the 'FM2014.Write' to check, based on the command table, if a broadcast or an individual communication
+ /// needs to be in initialized!
+ ///
+ ///
+ ///
+ ///
+ /// - Initial.
+ ///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
+ private static Boolean InitiateBroadcastComm(FM2014 fm2014)
+ {
+ if (fm2014 == null)
+ return false;
+
+ if (!SharedSerialPort.IsOpen)
+ {
+ SharedSerialPort.Open();
+ if (!SharedSerialPort.IsOpen)
+ {
+ var response = new CmdResponse(CmdName.CMD_ADDR_PC,
+ $"{Resources.StrCmdRespErrorBroadcast}");
+ fm2014.OnRawRecordReceived?.Invoke(fm2014, new ProcessExecEventArgs(Resources.StrError,
+ specificInfoObj: response,
+ statusReturn: StatusReturn.Failed));
+ SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
+ return false;
+ }
+ }
+
+ // If broadcast communication is already active
+ if (SharedCmdTypeReminderLastCmd == CmdType.BROADCAST)
+ {
+ return true;
+ }
+
+ try
+ {
+ // Establish connection to all FM2014s with broadcast address
+ // The direct write has to be used as this is being called from the 'Write' routine!!!!
+ SharedSerialPort.Write(BROADCAST_ADDRESS + END_OF_STR);
+ var cmdName = CmdName.CMD_CONNECT;
+ var info = GetCmdInfo(cmdName) + ": " + BROADCAST_ADDRESS;
+ var response = new CmdResponse(cmdName, info);
+ fm2014.OnRawRecordReceived?.Invoke(fm2014, new ProcessExecEventArgs("", specificInfoObj: response));
+ SharedCmdTypeReminderLastCmd = CmdType.BROADCAST;
+ return true;
+ }
+ catch (Exception e)
+ {
+ var response = new CmdResponse(CmdName.CMD_ADDR_PC,
+ $"{Resources.StrCmdRespErrorBroadcast} - {e.Message}");
+ fm2014.OnRawRecordReceived?.Invoke(fm2014, new ProcessExecEventArgs(Resources.StrError,
+ specificInfoObj: response, statusReturn: StatusReturn.Failed));
+ SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
+ return false;
+ }
+ }
+
+ ///
+ /// Initiate communication to individual node
+ /// - This 'InitiateIndividualComm' needs to access the 'SerialPort.Write' and 'SerialPort.ReadTo' as this is
+ /// called from the 'FM2014.Write' and 'FM2014.Read' to check, based on the command table, if a broadcast or an
+ /// individual communication needs to be in initialized!
+ ///
+ ///
+ ///
+ ///
+ /// - Initial.
+ ///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
+ private static Boolean InitiateIndividualComm(FM2014 fm2014)
+ {
+ if (fm2014 == null)
+ return false;
+
+ CmdResponse response;
+ if (!SharedSerialPort.IsOpen)
+ {
+ SharedSerialPort.Open();
+ if (!SharedSerialPort.IsOpen)
+ {
+ response = new CmdResponse(CmdName.CMD_ADDR_PC, $"{Resources.StrCmdRespErrorIndividual}");
+ fm2014.OnRawRecordReceived?.Invoke(fm2014, new ProcessExecEventArgs(Resources.StrError,
+ specificInfoObj: response,
+ statusReturn: StatusReturn.Failed));
+ SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
+ fm2014.IsLoggedOn = false;
+ return false;
+ }
+ }
+
+ try
+ {
+
+ // Establish connection to one specific FM2014
+ // The direct write has to be used as this is being called from the 'Write' routine!!!!
+ SharedSerialPort.Write(fm2014.AddressStr + END_OF_STR);
+ fm2014.ConnectResponse = SharedSerialPort.ReadTo(END_OF_STR);
+
+ if (!string.IsNullOrEmpty(fm2014.ConnectResponse))
+ {
+ var cmdName = CmdName.CMD_CONNECT;
+ var info = GetCmdInfo(cmdName) + ": " + fm2014.AddressStr;
+ response = new CmdResponse(cmdName, info);
+ fm2014.OnRawRecordReceived?.Invoke(fm2014, new ProcessExecEventArgs("", specificInfoObj: response));
+ fm2014.ConnectResponse = fm2014.ConnectResponse.Replace(ADDR_RECORD_STR, "");
+ var fields = fm2014.ConnectResponse.Split('.');
+ fm2014.FwVersion = fields[1];
+ SharedCmdTypeReminderLastCmd = GetCmdCmdType(cmdName);
+ return true;
+ }
+ }
+ catch (Exception e)
+ {
+ response = new CmdResponse(CmdName.CMD_CONNECT, $"{Resources.StrCmdRespErrorIndividual}" +
+ $": {fm2014.AddressStr}" + $" - {e.Message}");
+ fm2014.OnRawRecordReceived?.Invoke(fm2014, new ProcessExecEventArgs(Resources.StrError,
+ specificInfoObj: response, statusReturn: StatusReturn.Failed));
+ SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
+ fm2014.IsLoggedOn = false;
+ return false;
+ }
+
+ response = new CmdResponse(CmdName.CMD_CONNECT, $"{Resources.StrCmdRespErrorIndividual}");
+ fm2014.OnRawRecordReceived?.Invoke(fm2014, new ProcessExecEventArgs(Resources.StrError,
+ specificInfoObj: response, statusReturn: StatusReturn.Failed));
+ SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
+ return false;
+ }
+
+ #endregion ---------------------------------------- static methods --------------------------------------------
+
+ #region ------------------------------------------- object methods --------------------------------------------
///
/// Pulse counter measurement:
/// - REF counter (default ON),
@@ -454,7 +712,9 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
///
/// request to count REF pulses
/// request to count DUT pulses
- ///
+ /// true if initialization successfully executed or
+ /// marks the already started cyclic requests
+ /// for all registered FM2014s
///
/// - Initial.
///
@@ -465,12 +725,17 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// - 'u' read DUT backup pulses
/// will be used.
///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
public Boolean PulseCounterMeasurement(Boolean refCtrOn = true, Boolean dutCtrOn = false)
{
- if (!refCtrOn && !dutCtrOn)
- {
+ if (!refCtrOn && !dutCtrOn && IsLoggedOn)
return false;
- }
+
+ // If the cyclic task has already been started everything is fine
+ if (SharedCyclicReceiveTaskLoopIsActive)
+ return true;
var cmdName = CmdName.CMD_NA;
String measurementInfoStr;
@@ -495,7 +760,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
}
// Start the pulse counter measurement
- if (!Write(cmdName))
+ if (!Write(cmdName, this))
{
return false;
}
@@ -531,52 +796,54 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
do
{
- try
+ foreach (var fm2014 in RegisteredFm2014s.Where(fm2014 => fm2014.IsLoggedOn))
{
- // Make a backup of the pulse counters to synchronize those and read out the backups
- if (SharedCyclicReceiveTaskLoopIsActive && dutCtrOn && refCtrOn)
+ try
{
- // Synchronized backup of REF and DUT pulse counter
- Write(CmdName.CMD_REF_DUT_BU_PLS_CTR);
- }
-
- if (SharedCyclicReceiveTaskLoopIsActive)
- {
- if (refCtrOn && Write(cmdNameRef))
+ // Make a backup of the pulse counters to synchronize those and read out the backups
+ if (SharedCyclicReceiveTaskLoopIsActive && dutCtrOn && refCtrOn)
{
- if (Read(cmdNameRef, out var responseStr) &&
- int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"), out var intValue))
- {
- var response = new CmdResponse(cmdNameRef, infoRef, intValue);
- OnRawRecordReceived?.Invoke(this,
- new ProcessExecEventArgs("", actualProcessMessage: measurementInfoStr,
- specificInfoObj: response));
- }
+ // Synchronized backup of REF and DUT pulse counter
+ Write(CmdName.CMD_REF_DUT_BU_PLS_CTR, fm2014);
}
- if (dutCtrOn && Write(cmdNameDut))
+ if (SharedCyclicReceiveTaskLoopIsActive)
{
- if (Read(cmdNameDut, out var responseStr) &&
- int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"), out var intValue))
+ if (refCtrOn && Write(cmdNameRef, fm2014))
{
- var response = new CmdResponse(cmdNameDut, infoDut, intValue);
- OnRawRecordReceived?.Invoke(this,
- new ProcessExecEventArgs("", actualProcessMessage: measurementInfoStr,
- specificInfoObj: response));
+ if (Read(cmdNameRef, fm2014, out var responseStr) &&
+ int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"),
+ out var intValue))
+ {
+ var response = new CmdResponse(cmdNameRef, infoRef, intValue);
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014,
+ new ProcessExecEventArgs("", actualProcessMessage: measurementInfoStr,
+ specificInfoObj: response));
+ }
+ }
+
+ if (dutCtrOn && Write(cmdNameDut, fm2014))
+ {
+ if (Read(cmdNameDut, fm2014, out var responseStr) &&
+ int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"),
+ out var intValue))
+ {
+ var response = new CmdResponse(cmdNameDut, infoDut, intValue);
+ OnRawRecordReceived?.Invoke(fm2014,
+ new ProcessExecEventArgs("", actualProcessMessage: measurementInfoStr,
+ specificInfoObj: response));
+ }
}
}
}
- }
- catch (Exception e)
- {
- var response = new CmdResponse(cmdName, e.Message);
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError, specificInfoObj: response,
- statusReturn: StatusReturn.Failed));
- return false;
+ catch (Exception e)
+ {
+ var response = new CmdResponse(cmdName, e.Message);
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014, new ProcessExecEventArgs(Resources.StrError,
+ specificInfoObj: response, statusReturn: StatusReturn.Failed));
+ }
}
} while (SharedCyclicReceiveTaskLoopIsActive);
-
- return true;
}, SharedCancellationToken);
return true;
@@ -605,14 +872,22 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
///
/// - Initial.
///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
public Boolean RegulationMeasurement()
{
if (Ref_pulse_per_cm == 0 || Dut_pulse_per_cm == 0 ||
- RefToDutScale_norm < RefToDutScaleMin || RefToDutScale_norm > RefToDutScaleMax)
+ RefToDutScale_norm < RefToDutScaleMin || RefToDutScale_norm > RefToDutScaleMax ||
+ !IsLoggedOn)
{
return false;
}
+ // If the cyclic task has already been started everything is fine
+ if (SharedCyclicReceiveTaskLoopIsActive)
+ return true;
+
var cmdName = CmdName.CMD_NA;
var measurementInfo = Resources.StrMeasMsgRegulation;
@@ -620,17 +895,17 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
try
{
cmdName = CmdName.CMD_SET_DBPL_UNLOCK;
- if (!Write(cmdName))
+ if (!Write(cmdName, this))
{
return false;
}
cmdName = CmdName.CMD_MEAS_SET_ATTN;
- if (!Write(cmdName, Attenuation))
+ if (!Write(cmdName, this, Attenuation))
{
return false;
}
cmdName = CmdName.CMD_REF_SET_SCALE;
- if (!Write(cmdName, _refToDutScaleStr))
+ if (!Write(cmdName, this, _refToDutScaleStr))
{
return false;
}
@@ -651,152 +926,153 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
SharedCyclicReceiveTaskLoopIsActive = true;
do
{
- try
+ foreach (var fm2014 in RegisteredFm2014s.Where(fm2014 => fm2014.IsLoggedOn))
{
- // 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)
- String responseStr;
- cmdName = UseDampedTolerance ? CmdName.CMD_GET_DTLC : CmdName.CMD_GET_UDTLC;
- var info = GetCmdInfo(cmdName);
- if (SharedCyclicReceiveTaskLoopIsActive && Write(cmdName))
+ try
{
- if (Read(cmdName, out responseStr))
+ // 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)
+ String responseStr;
+ cmdName = UseDampedTolerance ? CmdName.CMD_GET_DTLC : CmdName.CMD_GET_UDTLC;
+ var info = GetCmdInfo(cmdName);
+ if (SharedCyclicReceiveTaskLoopIsActive && Write(cmdName, fm2014))
{
- // The response contains a sign!
- var signMultiplier = responseStr.Contains("+") ? 1.0f : -1.0f;
- var cleanedStr = responseStr.Replace("+", "").Replace("-", "");
- if (int.TryParse(cleanedStr, NumberStyles.HexNumber, new CultureInfo("en"),
- out var measTolerance))
+ if (Read(cmdName, fm2014, out responseStr))
{
+ // The response contains a sign!
+ var signMultiplier = responseStr.Contains("+") ? 1.0f : -1.0f;
+ var cleanedStr = responseStr.Replace("+", "").Replace("-", "");
+ if (int.TryParse(cleanedStr, NumberStyles.HexNumber, new CultureInfo("en"),
+ out var measTolerance))
+ {
+ var response = new CmdResponse(cmdName, info,
+ doubleValue: measTolerance * _toleranceRawToPercentScale * signMultiplier,
+ siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.PERCENTAGE_NON_SI));
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014,
+ new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
+ specificInfoObj: response));
+ }
+ }
+ }
+
+ // Read the REF period and calculate the frequency [Hz] and flow rate [m³/h] based on this period
+ cmdName = CmdName.CMD_GET_REF_PERIOD;
+ info = GetCmdInfo(cmdName);
+ if (RequestDebugInformation && SharedCyclicReceiveTaskLoopIsActive && Write(cmdName, fm2014))
+ {
+ if (Read(cmdName, fm2014, out responseStr) &&
+ int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"), out var period))
+ {
+ // Publish the period [ms]
var response = new CmdResponse(cmdName, info,
- doubleValue: measTolerance * _toleranceRawToPercentScale * signMultiplier,
- siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.PERCENTAGE_NON_SI));
- OnRawRecordReceived?.Invoke(this,
+ doubleValue: TMR_RESOLUTION_s * 1000.0 * period,
+ siUnit: "m" + SiUnits.GetInfo(SiUnits.SiUnitName.TIME));
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014,
+ new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
+ specificInfoObj: response));
+
+ // Publish the frequency in [Hz]
+ cmdName = CmdName.CMD_CAL_FREQU_REF_PERIOD;
+ info = GetCmdInfo(cmdName);
+ response = new CmdResponse(cmdName, info,
+ doubleValue: 1.0 / (TMR_RESOLUTION_s * period),
+ siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.FREQUENCY));
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014,
+ new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
+ specificInfoObj: response));
+
+ // Convert to m³/h (3600 s/h) based on REF pulse rate
+ 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);
+ response = new CmdResponse(cmdName, info, doubleValue: flowRate_cm_per_h,
+ siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.FLOW_RATE_NON_SI));
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014,
+ new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
+ specificInfoObj: response));
+ }
+ }
+
+
+ // Read the DUT period and calculate the frequency [Hz] and flow rate [m³/h] based on this period
+ cmdName = CmdName.CMD_GET_DUT_PERIOD;
+ info = GetCmdInfo(cmdName);
+ if (RequestDebugInformation && SharedCyclicReceiveTaskLoopIsActive && Write(cmdName, fm2014))
+ {
+ if (Read(cmdName, fm2014, out responseStr) &&
+ int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"), out var period))
+ {
+ // Publish the period [ms]
+ var response = new CmdResponse(cmdName, info,
+ doubleValue: TMR_RESOLUTION_s * 1000.0 * period,
+ siUnit: "m" + SiUnits.GetInfo(SiUnits.SiUnitName.TIME));
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014,
+ new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
+ specificInfoObj: response));
+
+ // Publish the frequency in [Hz]
+ cmdName = CmdName.CMD_CAL_FREQU_DUT_PERIOD;
+ info = GetCmdInfo(cmdName);
+ response = new CmdResponse(cmdName, info,
+ doubleValue: 1.0 / (TMR_RESOLUTION_s * period),
+ siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.FREQUENCY));
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014,
+ new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
+ specificInfoObj: response));
+
+ // Convert to m³/h (3600 s/h) based on DUT pulse rate
+ 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);
+ response = new CmdResponse(cmdName, info, doubleValue: flowRate_cm_per_h,
+ siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.FLOW_RATE_NON_SI));
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014,
+ new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
+ specificInfoObj: response));
+ }
+ }
+
+
+ // Read the REF frequency [Hz] directly and calculate the flow rate [m³/h] based on this frequency
+ cmdName = CmdName.CMD_GET_REF_FREQU;
+ info = GetCmdInfo(cmdName);
+ if (SharedCyclicReceiveTaskLoopIsActive && Write(cmdName, fm2014))
+ {
+ if (Read(cmdName, fm2014, out responseStr) &&
+ int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"),
+ out var refFrequency))
+ {
+ // Publish the frequency in [Hz]
+ var response = new CmdResponse(cmdName, info, refFrequency,
+ siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.FREQUENCY));
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014, new ProcessExecEventArgs("",
+ actualProcessMessage: measurementInfo,
+ specificInfoObj: response));
+
+ // Convert to m³/h (3600 s/h) and save the value
+ 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);
+ response = new CmdResponse(cmdName, info, doubleValue: RefFlowRate_cm_per_h,
+ siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.FLOW_RATE_NON_SI));
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014,
new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
specificInfoObj: response));
}
}
}
-
- // Read the REF period and calculate the frequency [Hz] and flow rate [m³/h] based on this period
- cmdName = CmdName.CMD_GET_REF_PERIOD;
- info = GetCmdInfo(cmdName);
- if (RequestDebugInformation && SharedCyclicReceiveTaskLoopIsActive && Write(cmdName))
+ catch (Exception e)
{
- if (Read(cmdName, out responseStr) &&
- int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"), out var period))
- {
- // Publish the period [ms]
- var response = new CmdResponse(cmdName, info,
- doubleValue: TMR_RESOLUTION_s * 1000.0 * period,
- siUnit: "m" + SiUnits.GetInfo(SiUnits.SiUnitName.TIME));
- OnRawRecordReceived?.Invoke(this,
- new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
- specificInfoObj: response));
-
- // Publish the frequency in [Hz]
- cmdName = CmdName.CMD_CAL_FREQU_REF_PERIOD;
- info = GetCmdInfo(cmdName);
- response = new CmdResponse(cmdName, info,
- 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.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);
- response = new CmdResponse(cmdName, info, doubleValue: flowRate_cm_per_h,
- siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.FLOW_RATE_NON_SI));
- OnRawRecordReceived?.Invoke(this,
- new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
- specificInfoObj: response));
- }
+ var response = new CmdResponse(cmdName, e.Message);
+ fm2014?.OnRawRecordReceived?.Invoke(fm2014, new ProcessExecEventArgs(Resources.StrError,
+ specificInfoObj: response, statusReturn: StatusReturn.Failed));
}
-
-
- // Read the DUT period and calculate the frequency [Hz] and flow rate [m³/h] based on this period
- cmdName = CmdName.CMD_GET_DUT_PERIOD;
- info = GetCmdInfo(cmdName);
- if (RequestDebugInformation && SharedCyclicReceiveTaskLoopIsActive && Write(cmdName))
- {
- if (Read(cmdName, out responseStr) &&
- int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"), out var period))
- {
- // Publish the period [ms]
- var response = new CmdResponse(cmdName, info,
- doubleValue: TMR_RESOLUTION_s * 1000.0 * period,
- siUnit: "m" + SiUnits.GetInfo(SiUnits.SiUnitName.TIME));
- OnRawRecordReceived?.Invoke(this,
- new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
- specificInfoObj: response));
-
- // Publish the frequency in [Hz]
- cmdName = CmdName.CMD_CAL_FREQU_DUT_PERIOD;
- info = GetCmdInfo(cmdName);
- response = new CmdResponse(cmdName, info,
- 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.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);
- response = new CmdResponse(cmdName, info, doubleValue: flowRate_cm_per_h,
- siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.FLOW_RATE_NON_SI));
- OnRawRecordReceived?.Invoke(this,
- new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
- specificInfoObj: response));
- }
- }
-
-
- // Read the REF frequency [Hz] directly and calculate the flow rate [m³/h] based on this frequency
- cmdName = CmdName.CMD_GET_REF_FREQU;
- info = GetCmdInfo(cmdName);
- if (SharedCyclicReceiveTaskLoopIsActive && Write(cmdName))
- {
- if (Read(cmdName, out responseStr) &&
- int.TryParse(responseStr, NumberStyles.HexNumber, new CultureInfo("en"), out var refFrequency))
- {
- // Publish the frequency in [Hz]
- var response = new CmdResponse(cmdName, info, refFrequency,
- siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.FREQUENCY));
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
- specificInfoObj: response));
-
- // Convert to m³/h (3600 s/h) and save the value
- 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);
- response = new CmdResponse(cmdName, info, doubleValue: RefFlowRate_cm_per_h,
- siUnit: SiUnits.GetInfo(SiUnits.SiUnitName.FLOW_RATE_NON_SI));
- OnRawRecordReceived?.Invoke(this,
- new ProcessExecEventArgs("", actualProcessMessage: measurementInfo,
- specificInfoObj: response));
- }
- }
-
- Thread.Sleep(500);
- }
- catch (Exception e)
- {
- var response = new CmdResponse(cmdName, e.Message);
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError, specificInfoObj: response,
- statusReturn: StatusReturn.Failed));
- return false;
}
} while (SharedCyclicReceiveTaskLoopIsActive);
- return true;
}, SharedCancellationToken);
return true;
@@ -812,8 +1088,15 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
///
/// - REF pulse rate can be 0 as 'invalid marker'.
///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
public Boolean SaveStandAloneMeasurement()
{
+ // If the cyclic task has already been started everything is fine
+ if (SharedCyclicReceiveTaskLoopIsActive || !IsLoggedOn)
+ return false;
+
var cmdName = CmdName.CMD_REF_LPP_SCALE;
try
{
@@ -821,32 +1104,32 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
// 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);
+ var retVal = Write(cmdName, this, (UInt16)limitedRefPulsesPerCm);
if (retVal)
{
cmdName = CmdName.CMD_DUT_LPP_SCALE;
- retVal = Write(cmdName, (UInt16)Dut_pulse_per_cm);
+ retVal = Write(cmdName, this, (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);
+ retVal = Write(cmdName, this, _refToDutScaleStr);
}
if (retVal)
{
cmdName = CmdName.CMD_MEAS_SET_ATTN;
- retVal = Write(cmdName, Attenuation);
+ retVal = Write(cmdName, this, Attenuation);
}
if (retVal)
{
// Save all settings from RAM to nonvolatile EEPROM
cmdName = CmdName.CMD_SAVE_RAM_TO_EEP_MEAS_SETUP;
- return Write(cmdName);
+ return Write(cmdName, this);
}
}
catch (Exception e)
@@ -873,8 +1156,15 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// 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³
///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
private Boolean ReadAloneMeasurementControl(CmdName cmdName)
{
+ // If the cyclic task has already been started everything is fine
+ if (SharedCyclicReceiveTaskLoopIsActive || !IsLoggedOn)
+ return false;
+
// Allow explicit standalone measurement commands
if (cmdName != CmdName.CMD_READ_EEP_MEAS_SETUP &&
cmdName != CmdName.CMD_READ_RAM_MEAS_SETUP)
@@ -885,7 +1175,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{
var measurementInfoStr = Resources.StrMeasMsgStanaloneRegulation;
var responseStrs = new List();
- if (Write(cmdName))
+ if (Write(cmdName, this))
{
if (cmdName == CmdName.CMD_READ_EEP_MEAS_SETUP)
{
@@ -893,7 +1183,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
var loopCtr = 4;
do
{
- if (Read(cmdName, out var responseStr))
+ if (Read(cmdName, this, out var responseStr))
responseStrs.Add(responseStr);
} while (loopCtr-- > 0);
}
@@ -903,7 +1193,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
var loopCtr = 5;
do
{
- if (Read(cmdName, out var responseStr))
+ if (Read(cmdName, this, out var responseStr))
responseStrs.Add(responseStr);
} while (loopCtr-- > 0);
}
@@ -999,20 +1289,28 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
///
/// - Initial.
///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
public Boolean Login()
{
IsLoggedOn = false;
+
+ // If the cyclic task has already been started everything is fine
+ if (SharedCyclicReceiveTaskLoopIsActive)
+ return false;
+
var cmdCode = GetCmdStr(CmdName.CMD_SERIAL);
const CmdName cmdName = CmdName.CMD_SERIAL;
try
{
// Read out the serial number of this FM2014
- if (!Write(cmdName))
+ if (!Write(cmdName, this))
{
return false;
}
- if (Read(cmdName, out var responseStr))
+ if (Read(cmdName, this, out var responseStr))
SerialNumber = responseStr;
if (!string.IsNullOrEmpty(SerialNumber))
@@ -1036,286 +1334,6 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
return IsLoggedOn;
}
- ///
- /// Initiate the broadcast communication:
- /// - This 'InitiateBroadcastComm' needs to access the 'SerialPort.Write' directly as it is called from
- /// the 'FM2014.Write' to check, based on the command table, if a broadcast or an individual communication
- /// needs to be in initialized!
- ///
- ///
- /// - Initial.
- ///
- private Boolean InitiateBroadcastComm()
- {
- if (SharedSerialPort == null)
- {
- var response = new CmdResponse(CmdName.CMD_ADDR_PC,
- $"{Resources.StrCmdRespErrorBroadcast}");
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError,
- specificInfoObj: response,
- statusReturn: StatusReturn.Failed));
- SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
- return false;
- }
-
- if (!SharedSerialPort.IsOpen)
- {
- SharedSerialPort.Open();
- if (!SharedSerialPort.IsOpen)
- {
- var response = new CmdResponse(CmdName.CMD_ADDR_PC,
- $"{Resources.StrCmdRespErrorBroadcast}");
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError,
- specificInfoObj: response,
- statusReturn: StatusReturn.Failed));
- SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
- return false;
- }
- }
-
- // If broadcast communication is already active
- if (SharedCmdTypeReminderLastCmd == CmdType.BROADCAST)
- {
- return true;
- }
-
- try
- {
- // Establish connection to all FM2014s with broadcast address
- // The direct write has to be used as this is being called from the 'Write' routine!!!!
- SharedSerialPort.Write(BROADCAST_ADDRESS + END_OF_STR);
- var cmdName = CmdName.CMD_CONNECT;
- var info = GetCmdInfo(cmdName) + ": " + BROADCAST_ADDRESS;
- var response = new CmdResponse(cmdName, info);
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs("", specificInfoObj: response));
- SharedCmdTypeReminderLastCmd = CmdType.BROADCAST;
- return true;
- }
- catch (Exception e)
- {
- var response = new CmdResponse(CmdName.CMD_ADDR_PC,
- $"{Resources.StrCmdRespErrorBroadcast} - {e.Message}");
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError, specificInfoObj: response,
- statusReturn: StatusReturn.Failed));
- SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
- return false;
- }
- }
-
- ///
- /// Initiate communication to individual node
- /// - This 'InitiateIndividualComm' needs to access the 'SerialPort.Write' and 'SerialPort.ReadTo' as this is
- /// called from the 'FM2014.Write' and 'FM2014.Read' to check, based on the command table, if a broadcast or an
- /// individual communication needs to be in initialized!
- ///
- ///
- ///
- /// - Initial.
- ///
- private Boolean InitiateIndividualComm()
- {
- CmdResponse response;
- if (SharedSerialPort == null)
- {
- response = new CmdResponse(CmdName.CMD_ADDR_PC, $"{Resources.StrCmdRespErrorIndividual}");
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError,
- specificInfoObj: response,
- statusReturn: StatusReturn.Failed));
- SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
- return false;
- }
-
- if (!SharedSerialPort.IsOpen)
- {
- SharedSerialPort.Open();
- if (!SharedSerialPort.IsOpen)
- {
- response = new CmdResponse(CmdName.CMD_ADDR_PC, $"{Resources.StrCmdRespErrorIndividual}");
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError,
- specificInfoObj: response,
- statusReturn: StatusReturn.Failed));
- SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
- return false;
- }
- }
-
- try
- {
-
- // Establish connection to one specific FM2014
- // The direct write has to be used as this is being called from the 'Write' routine!!!!
- SharedSerialPort.Write(AddressStr + END_OF_STR);
- ConnectResponse = SharedSerialPort.ReadTo(END_OF_STR);
-
- if (!string.IsNullOrEmpty(ConnectResponse))
- {
- var cmdName = CmdName.CMD_CONNECT;
- var info = GetCmdInfo(cmdName) + ": " + AddressStr;
- response = new CmdResponse(cmdName, info);
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs("", specificInfoObj: response));
- ConnectResponse = ConnectResponse.Replace(ADDR_RECORD_STR, "");
- var fields = ConnectResponse.Split('.');
- FwVersion = fields[1];
- SharedCmdTypeReminderLastCmd = GetCmdCmdType(cmdName);
- return true;
- }
- }
- catch (Exception e)
- {
- response = new CmdResponse(CmdName.CMD_CONNECT, $"{Resources.StrCmdRespErrorIndividual} - {e.Message}");
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs(Resources.StrError,
- specificInfoObj: response, statusReturn: StatusReturn.Failed));
- SharedCmdTypeReminderLastCmd = 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));
- SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
- return false;
- }
-
- ///
- /// Read FM2014:
- /// - Accesses directly to the 'SerialPort.ReadTo',
- /// - An exception has to be caught by the caller.
- ///
- ///
- ///
- ///
- ///
- /// - Initial.
- ///
- private Boolean Read(CmdName cmdName, out String responseStr)
- {
- responseStr = "";
- var info = $"{Resources.StrCommResponse}: {GetCmdInfo(cmdName)}";
- var statusReturn = StatusReturn.Failed;
-
- // Check for individual command without response
- if (CmdType.INDIVIDUAL_UNRESPONSIVE == SharedCmdTypeReminderLastCmd &&
- CmdType.INDIVIDUAL_UNRESPONSIVE == GetCmdCmdType(cmdName))
- {
- statusReturn = StatusReturn.Skipped;
- }
- else if (CmdType.INDIVIDUAL == SharedCmdTypeReminderLastCmd &&
- CmdType.INDIVIDUAL == GetCmdCmdType(cmdName))
- {
- // Read or timeout with a TimeoutException
- var answerStr = SharedSerialPort.ReadTo(END_OF_STR);
- if (string.IsNullOrEmpty(answerStr))
- {
- statusReturn = StatusReturn.Failed;
- }
- else
- {
- statusReturn = StatusReturn.Okay;
- responseStr = answerStr;
- }
- }
-
- var isErrorStr = StatusReturn.Failed == statusReturn ? Resources.StrError + ": " : "";
- var response = new CmdResponse(cmdName, $"{isErrorStr}{info} {responseStr}");
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs("", specificInfoObj: response,
- statusReturn: statusReturn));
-
- return StatusReturn.Failed != statusReturn;
- }
-
- ///
- /// Write command to FM2014:
- /// - Accesses directly to the 'SerialPort.Write',
- /// - Select broadcast or individual addressing based on the command table,
- /// - Transmits optional data:
- /// - If the dataObj is a string, this is going to be sent as is (e.g. "1000K2"),
- /// - If the dataObj is an UInt16 or Byte this will be assembled with the command code (e.g. "3T"),
- /// - Every 'Write' is a string delimited here with 'CR',
- /// - An exception has to be caught by the caller.
- ///
- ///
- ///
- ///
- ///
- /// - Initial.
- ///
- private Boolean Write(CmdName cmdName, Object dataObj = null)
- {
- if (SharedSerialPort == null)
- {
- SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
- IsLoggedOn = false;
- SharedCyclicReceiveTaskLoopIsActive = false;
- return false;
- }
-
- // Initiate communication using the individual or broadcast address
- var retVal = true;
- if (CmdType.BROADCAST == GetCmdCmdType(cmdName))
- {
- if (SharedCmdTypeReminderLastCmd != CmdType.BROADCAST)
- {
- retVal = InitiateBroadcastComm();
- }
- }
- else
- {
- if (SharedCmdTypeReminderLastCmd != CmdType.INDIVIDUAL || SharedCmdTypeReminderLastCmd != CmdType.INDIVIDUAL)
- {
- retVal = InitiateIndividualComm();
- }
- }
-
- if (!retVal)
- {
- return false;
- }
-
- var cmdCodeStr = GetCmdStr(cmdName);
- var info = GetCmdInfo(cmdName) + $": {cmdCodeStr}";
- CmdResponse response;
- // Without data the command alone needs to be sent
- if (dataObj == null)
- {
- SharedSerialPort.Write($"{cmdCodeStr}{END_OF_STR}");
- var additionalInfo = GetCmdCmdType(cmdName) == CmdType.BROADCAST
- ? Resources.StrCommDirectionSetup
- : Resources.StrCommDirectionRequest;
- response = new CmdResponse(cmdName, $"{additionalInfo}: {info}");
- }
- else
- {
- // A string has to be entire assembled, only the string delimiter will to be added here
- if (dataObj is String)
- {
- SharedSerialPort.Write($"{dataObj}{END_OF_STR}");
- response = new CmdResponse(cmdName, $"{Resources.StrCommDirectionSetup}: {info}: {dataObj}");
- }
-
- // The data contains the preceding setup, the command code and string delimiter will be added here
- else if (dataObj is UInt16 || dataObj is Byte)
- {
- SharedSerialPort.Write($"{dataObj}{cmdCodeStr}{END_OF_STR}");
- response = new CmdResponse(cmdName,
- $"{Resources.StrCommDirectionSetup}: {info}: {dataObj}{cmdCodeStr}");
-
- }
- else
- {
- response = new CmdResponse(cmdName,
- $"{Resources.StrError}: {Resources.StrCommDirectionSetup}: {info} - " +
- $"{Resources.StrCmdRespErrorPramTypeNotSupported} {dataObj}");
- retVal = false;
- }
- }
-
- var statusReturn = retVal ? StatusReturn.Okay : StatusReturn.Failed;
- OnRawRecordReceived?.Invoke(this, new ProcessExecEventArgs("", specificInfoObj: response,
- statusReturn: statusReturn));
-
- return retVal;
- }
-
///
/// Reset the measurement but only if receive task is active.
///
@@ -1326,6 +1344,9 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
///
/// - Delayed reset to allow other tasks to finish.
///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
public Boolean ResetMeasurement()
{
if (!SharedCyclicReceiveTaskLoopIsActive)
@@ -1341,7 +1362,7 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{
// Reset measurement
var infoStr = GetCmdInfo(CmdName.CMD_RST_MEAS);
- if (!Write(CmdName.CMD_RST_MEAS))
+ if (!Write(CmdName.CMD_RST_MEAS, this))
return;
// Wait until FM2014 calibration finished
@@ -1367,10 +1388,60 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
return true;
}
+ ///
+ /// Common routine for reboot being able to override this routine which will be called in
+ /// to simulate a reboot.
+ ///
+ ///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
+ public StatusReturn Reboot()
+ {
+ return StatusReturn.Failed;
+ }
+
+ ///
+ /// Connect to FM2014
+ ///
+ ///
+ ///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
+ public Boolean Connect(String comPort)
+ {
+ if (SharedSerialPort == null)
+ {
+ SharedSerialPort = new SerialPort();
+ SharedSerialPort.BaudRate = 1200;
+ SharedSerialPort.DataBits = 7;
+ SharedSerialPort.Parity = Parity.Odd;
+ SharedSerialPort.PortName = comPort;
+ SharedSerialPort.ReadTimeout = 1000;
+ SharedSerialPort.WriteTimeout = 1000;
+ }
+
+ if (RegisteredFm2014s == null)
+ RegisteredFm2014s = new List();
+
+ RegisteredFm2014s.Add(this);
+
+ // 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;
+ SharedCmdTypeReminderLastCmd = CmdType.NOT_INITIALIZED;
+ return true;
+ }
+
///
/// Logout from FM2014
///
///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
public Boolean Logout()
{
// Close only if all FM2014 instances are removed
@@ -1385,6 +1456,9 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
///
/// Dispose FM2014
///
+ ///
+ /// - Support for multiple FM2014s.
+ ///
public void Dispose()
{
Logout();
@@ -1392,6 +1466,6 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
RegisteredFm2014s = null;
SharedSerialPort?.Dispose();
}
- #endregion methods
+ #endregion ---------------------------------------- object methods --------------------------------------------
}
}
\ No newline at end of file
diff --git a/MiniPrf/Ui/FrmMainMiniPrf.cs b/MiniPrf/Ui/FrmMainMiniPrf.cs
index 020c9de2..ce34c734 100644
--- a/MiniPrf/Ui/FrmMainMiniPrf.cs
+++ b/MiniPrf/Ui/FrmMainMiniPrf.cs
@@ -66,11 +66,12 @@ namespace Sensus.MiniPrf.Ui
///
/// The single FM2014 for this program
///
- private FM2014 Fm2014
- {
- get;
- set;
- }
+ private FM2014 Fm2014 { get; set; }
+
+ ///
+ /// The single FM2014 for this program
+ ///
+ private FM2014 Fm2014_TEST { get; set; }
///
/// The start time is going to be used for time measurements of processes. It has to be set to
@@ -444,6 +445,12 @@ namespace Sensus.MiniPrf.Ui
//assign new meter
Fm2014 = new FM2014();
+
+ //TODO THW TEST FM2014 multiple objects
+ Fm2014_TEST = new FM2014();
+ Fm2014_TEST.Address = 2;
+ Fm2014_TEST.OnRawRecordReceived += DataReceived_Handler;
+
Fm2014.OnRawRecordReceived += DataReceived_Handler;
ResetCancellationToken();
FM2014.SharedCancellationToken = _cancellationToken;
@@ -470,10 +477,16 @@ namespace Sensus.MiniPrf.Ui
lblActualProcess.Text = Resources.StrMsgConnecting;
Fm2014.Connect(cbxFM2014ComPort.SelectedItem.ToString());
+ //TODO THW TEST FM2014 multiple objects
+ Fm2014_TEST.Connect(cbxFM2014ComPort.SelectedItem.ToString());
+
Task.Factory.StartNew(() =>
{
Fm2014.Login();
+ //TODO THW TEST FM2014 multiple objects
+ Fm2014_TEST.Login();
+
if (!Fm2014.IsLoggedOn)
{
LogErrorText(Resources.StrErrorMsgFM2014AccessDenied);