MiniPrf: - next step

This commit is contained in:
Thomas Wiedebusch 2026-01-06 15:28:26 +01:00
parent 505d78a7e0
commit 78f64ca075
11 changed files with 203 additions and 42 deletions

View File

@ -1,4 +1,5 @@
using System;
using System.IO.Ports;
namespace Xylem.Common.Hardware.Interfaces.Ports.PortCore
{
@ -8,7 +9,7 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.PortCore
public struct TransmitPortSettings
{
/// <summary>
/// The transmission protocol needs to inform the communication port how to setup,
/// The transmission protocol needs to inform the communication port how to set up,
/// this is the data container.
/// </summary>
/// <param name="protSyncByte"></param>
@ -18,8 +19,17 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.PortCore
/// <param name="baudRate"></param>
/// <param name="receiveBufferFlushThreshold"></param>
/// <param name="doubleSyncByte"></param>
public TransmitPortSettings(Byte? protSyncByte, UInt16? protLengthIndex, UInt16 protAddLength, Int32 responseTimeoutMs, UInt32 baudRate,
UInt32? receiveBufferFlushThreshold, Boolean doubleSyncByte = false)
/// <param name="stringDelimiter"></param>
/// <param name="dataBits"></param>
/// <param name="parity"></param>
/// <remarks date="2026-Jan-05" author="T.Wiedebusch">
/// - String delimiter for ASCII to support others than LF "\n",
/// - Flexible DataBits,
/// - Flexible parity.
/// </remarks>
public TransmitPortSettings(Byte? protSyncByte, UInt16? protLengthIndex, UInt16 protAddLength, Int32 responseTimeoutMs,
UInt32 baudRate, UInt32? receiveBufferFlushThreshold, Boolean doubleSyncByte = false, Char stringDelimiter = '\n',
Int32 dataBits = 8, Parity parity = Parity.None )
{
ProtSyncByte = protSyncByte;
ProtLengthIndex = protLengthIndex;
@ -28,7 +38,25 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.PortCore
BaudRate = baudRate;
ReceiveBufferFlushThreshold = receiveBufferFlushThreshold;
DoubleSyncByte = doubleSyncByte;
StringDelimiter = stringDelimiter;
DataBits = dataBits;
Parity = parity;
}
/// <summary>
/// String delimiter for ASCII to support others than LF "\n"
/// </summary>
public readonly Char StringDelimiter;
/// <summary>
/// Flexible DataBits to support 7 bits.
/// </summary>
public readonly Int32 DataBits;
/// <summary>
/// Flexible parity
/// </summary>
public readonly Parity Parity;
/// <summary>
/// start of receiving synchronization byte at byte receive routine
/// syncByte == null: use the readLine routine (ASCII) and NOT the BYTE routine,
@ -76,6 +104,6 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.PortCore
/// <summary>
/// a special implementation may require the doubling of the sync byte to re-synchronize
/// </summary>
public Boolean DoubleSyncByte;
public readonly Boolean DoubleSyncByte;
}
}

View File

@ -294,7 +294,7 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
//put the actual time stamp to this record being able to assign it correctly
//even if the decoding is delayed. This time stamp will be used to do the first
//synchronization at start and stop of the measurement. Therefore the serial buffer
//synchronization at start and stop of the measurement. Therefore, the serial buffer
//has to be flushed in advance to avoid wrong time stamp to "old" records
_receiveTimeStampPc = DateTimeOffset.UtcNow;
@ -426,6 +426,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
/// <remarks date="2025-Mai-12..14" author="T.Wiedebusch">
/// - Dispose procedure changed.
/// </remarks>
/// <remarks date="2026-Jan-06" author="T.Wiedebusch">
/// - Replaced 'ReadLine' with 'ReadTo' using a string delimiter to support others than LF "\n".
/// </remarks>
private void ReadingThreadLoop()
{
//put the receive thread to JoinWaitSleep to avoid reading and logger output for timeout on

View File

@ -618,6 +618,9 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol
/// - Removed useless too short comments as every string is going to be delimited by a 0 and usually won't match
/// to a 4 byte chunk.
/// </remarks>
/// <remarks date="2026-Jan-06" author="T.Wiedebusch">
/// - Removed redundant 'return cRecord'.
/// </remarks>
public RequestRecord CommandToMeter(Byte command, RegisterDefinition meterRegister = null,
Byte[] payload = null, Int32? expectedLength = null, Boolean hideDataInLog = false,
UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode)
@ -638,8 +641,6 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol
cRecord.ResponsePayload = completeResponse;
OnMeterRegisterUpdated?.Invoke(this,
new RegisterUpdatedEventArgs(meterRegister, completeResponse.ToArray()));
return cRecord;
}
return cRecord;

View File

@ -34,8 +34,9 @@
//using NLog;
using System;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments;
namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
@ -44,8 +45,20 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// <inheritdoc/>
public class FM2014 : IFM2014
{
#region events
/// <inheritdoc />
public virtual event EventHandler<StringPortDataEventArgs> OnRawRecordReceived;
#endregion
#region properties
/// <inheritdoc/>
public Boolean ReceiveTaskIsActive
{
get;
internal set;
}
/// <summary>
/// Logger for raw data output
/// </summary>
@ -164,6 +177,43 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
return true;
}
public Boolean ManualRefCalibration()
{
if (SerialPort == null || !SerialPort.IsOpen)
{
return false;
}
// Stop receive task
if (ReceiveTaskIsActive)
{
ReceiveTaskIsActive = false;
return true;
}
ReceiveTaskIsActive = true;
Task.Run(() =>
{
var ctr = 0;
do
{
SerialPort.Write("#\r");
SerialNumber = SerialPort.ReadTo("\r");
if (ctr++ >= 100)
{
ReceiveTaskIsActive = false;
}
OnRawRecordReceived?.Invoke(this, new StringPortDataEventArgs($"{ctr:D3}"));
} while (ReceiveTaskIsActive);
ReceiveTaskIsActive = false;
});
return true;
}
/// <inheritdoc/>
public Boolean Login()
{

View File

@ -303,6 +303,10 @@
<Project>{1B02C79E-0B19-43E2-8F6B-71EF0C786C97}</Project>
<Name>CommonCore</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\..\Interfaces\Ports\PortCore\PortCore.csproj">
<Project>{2e139f63-b6fe-4ea9-a098-85364fe9b26c}</Project>
<Name>PortCore</Name>
</ProjectReference>
<ProjectReference Include="..\FM2014Config\FM2014Config.csproj">
<Project>{3862d89c-e32d-4182-bb58-38777d43edd9}</Project>
<Name>FM2014Config</Name>

View File

@ -35,6 +35,7 @@ using System;
using System.IO.Ports;
//using NLog;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments;
namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
{
@ -46,8 +47,23 @@ namespace Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core
/// </remarks>
public interface IFM2014
{
#region events
/// <summary>
/// String data feedback
/// </summary>
event EventHandler<StringPortDataEventArgs> OnRawRecordReceived;
#endregion
#region properties
/// <summary>
/// Receive task is active:
/// Used to mark this event and to kill ongoing task if set inactive.
/// </summary>
Boolean ReceiveTaskIsActive
{
get;
}
/// <summary>
/// Serial number of the FM2014
/// </summary>
String SerialNumber

View File

@ -36,6 +36,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utils", "Utils", "{755396FE
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging", "..\Common\Utils\Logging\Logging.csproj", "{4B88B0D3-E791-4774-9521-EABEACDC420E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interfaces", "Interfaces", "{14FE7B6A-2029-4DCD-AD07-2BDF77D42D94}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Ports", "Ports", "{83A52B6E-DB81-4B43-B5C1-F9B637D135BE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Protocols", "Protocols", "{4E27EA04-8B52-4681-BB5E-4658BC3FF9A6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PortCore", "..\Common\Hardware\Interfaces\Ports\PortCore\PortCore.csproj", "{2E139F63-B6FE-4EA9-A098-85364FE9B26C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -62,6 +70,10 @@ Global
{4B88B0D3-E791-4774-9521-EABEACDC420E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B88B0D3-E791-4774-9521-EABEACDC420E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4B88B0D3-E791-4774-9521-EABEACDC420E}.Release|Any CPU.Build.0 = Release|Any CPU
{2E139F63-B6FE-4EA9-A098-85364FE9B26C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E139F63-B6FE-4EA9-A098-85364FE9B26C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E139F63-B6FE-4EA9-A098-85364FE9B26C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E139F63-B6FE-4EA9-A098-85364FE9B26C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -78,6 +90,10 @@ Global
{0B205BE7-90CD-4C3A-8EBD-1E414E39528A} = {02CE88CB-677E-45A7-9B33-5028110379E5}
{755396FE-7AD3-4D4A-9416-4C212A3BEF19} = {1C6D29D2-AC0D-48B2-932A-C9B94C3562F6}
{4B88B0D3-E791-4774-9521-EABEACDC420E} = {755396FE-7AD3-4D4A-9416-4C212A3BEF19}
{14FE7B6A-2029-4DCD-AD07-2BDF77D42D94} = {2C357743-AB88-48BF-805A-5DEED44934A3}
{83A52B6E-DB81-4B43-B5C1-F9B637D135BE} = {14FE7B6A-2029-4DCD-AD07-2BDF77D42D94}
{4E27EA04-8B52-4681-BB5E-4658BC3FF9A6} = {14FE7B6A-2029-4DCD-AD07-2BDF77D42D94}
{2E139F63-B6FE-4EA9-A098-85364FE9B26C} = {83A52B6E-DB81-4B43-B5C1-F9B637D135BE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B72A8444-29F9-4FA4-82BE-B6E230750911}

View File

@ -514,6 +514,7 @@
this.btnManualRefCalibration.TabIndex = 38;
this.btnManualRefCalibration.Text = "Start Calibration";
this.btnManualRefCalibration.UseVisualStyleBackColor = true;
this.btnManualRefCalibration.Click += new System.EventHandler(this.btnManualRefCalibration_Click);
//
// gbxDutToRefRegulation
//

View File

@ -35,6 +35,7 @@
using Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Config;
using Sensus.Common.Hardware.WaterMeter.MechanicalMeter.FM2014.FM2014Core;
using Sensus.MiniPrf.Ui.Properties;
using System;
using System.Drawing;
using System.IO;
@ -43,7 +44,7 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Forms;
using Sensus.MiniPrf.Ui.Properties;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments;
namespace Sensus.MiniPrf.Ui
{
@ -109,9 +110,15 @@ namespace Sensus.MiniPrf.Ui
/// <summary>
/// Clear data table and set genesis to not connected
/// </summary>
/// <remarks date="2025-Nov-13" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private void Init()
{
ViewProcessControl(false);
btnManualRefCalibration.Enabled = false;
btnDutToRefRegulation.Enabled = false;
// Load the configuration from the local file stored in AppData\FM2014
_fm2014Config.ReadFM2014Config();
@ -202,6 +209,7 @@ namespace Sensus.MiniPrf.Ui
lblWaitingForMeterResponse.Visible = false;
SetFM2014AccessLocked();
btnFM2014Connect.Enabled = true;
btnFM2014Connect.Focus();
}
@ -276,33 +284,13 @@ namespace Sensus.MiniPrf.Ui
#endregion TimerControls
#region ActivationControls
/// <summary>
/// Disable all buttons except the connect button
/// </summary>
private void DisableToolButtons()
{
btnSaveRegulationSetup.Enabled = false;
btnManualRefCalibration.Enabled = false;
btnDutToRefRegulation.Enabled = false;
}
/// <summary>
/// Enable all buttons except the connect button
/// </summary>
private void EnableToolButtons()
{
btnSaveRegulationSetup.Enabled = true;
btnManualRefCalibration.Enabled = true;
btnDutToRefRegulation.Enabled = true;
}
/// <summary>
/// Lock all buttons, enable the connect button
/// </summary>
private void SetFM2014AccessLocked()
{
btnFM2014Connect.Enabled = true;
DisableToolButtons();
btnSaveRegulationSetup.Enabled = false;
btnFM2014Connect.Enabled = false;
}
/// <summary>
@ -310,8 +298,12 @@ namespace Sensus.MiniPrf.Ui
/// </summary>
private void SetFM2014AccessEnabled()
{
btnSaveRegulationSetup.Enabled = true;
btnManualRefCalibration.Enabled = true;
btnDutToRefRegulation.Enabled = true;
btnFM2014Connect.Enabled = true;
EnableToolButtons();
btnManualRefCalibration.Text = Resources.StrBtnStartCalibration;
btnDutToRefRegulation.Text = Resources.StrBtnStartRegulation;
}
/// <summary>
@ -327,7 +319,7 @@ namespace Sensus.MiniPrf.Ui
SetFM2014AccessEnabled();
}
#endregion ACtivationControls
#endregion ActivationControls
#region BoardControls
@ -358,12 +350,17 @@ namespace Sensus.MiniPrf.Ui
ActionControl(true);
_fm2014?.Dispose();
//dispose old meter
_fm2014 = null;
if (_fm2014 != null)
{
_fm2014.OnRawRecordReceived -= DataReceived_Handler;
_fm2014.Dispose();
//dispose old meter
_fm2014 = null;
}
//assign new meter
_fm2014 = new FM2014();
_fm2014.OnRawRecordReceived += DataReceived_Handler;
if (int.TryParse(cbxFM2014Address.SelectedItem.ToString(), out var address))
{
_fm2014.Address = address;
@ -434,6 +431,11 @@ namespace Sensus.MiniPrf.Ui
_fm2014?.Dispose();
}
}
private void _fm2014_OnRawRecordReceived(object sender, StringPortDataEventArgs e)
{
throw new NotImplementedException();
}
#endregion BoardControls
#region ProcessControls
@ -610,15 +612,38 @@ namespace Sensus.MiniPrf.Ui
#region Buttons and Controls
private void btnConnect_Click(Object sender, EventArgs e)
{
if (_resetTimeMeasurement)
{
_startTime = DateTimeOffset.UtcNow;
_resetTimeMeasurement = false;
}
//if (_resetTimeMeasurement)
//{
_startTime = DateTimeOffset.UtcNow;
_resetTimeMeasurement = false;
//}
Connect();
}
private void btnManualRefCalibration_Click(object sender, EventArgs e)
{
if (!_fm2014.ReceiveTaskIsActive)
{
_startTime = DateTimeOffset.UtcNow;
_resetTimeMeasurement = true;
ActionControl(true);
btnDutToRefRegulation.Enabled = false;
if (_fm2014.ManualRefCalibration())
{
btnManualRefCalibration.Text = Resources.StrBtnStopCalibration;
}
}
else
{
if (_fm2014.ManualRefCalibration())
{
ActionControl(false);
btnManualRefCalibration.Text = Resources.StrBtnStartCalibration;
}
}
}
private void cbxFM2014ComPort_SelectedValueChanged(Object sender, EventArgs e)
{
if (_toleranceIdx != cbxFM2014TolerancePercent.SelectedIndex ||
@ -632,5 +657,18 @@ namespace Sensus.MiniPrf.Ui
}
}
#endregion Buttons and Controls
#region Event handler
private void DataReceived_Handler(Object sender, BasePortDataEventArgs e)
{
LogText(e.GetData().ToString());
// Return to standby
if (!_fm2014.ReceiveTaskIsActive)
{
ActionControl(false);
}
}
#endregion
}
}

View File

@ -109,6 +109,10 @@
<None Include="Resources\Sensus Logo.JPG" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Common\Hardware\Interfaces\Ports\PortCore\PortCore.csproj">
<Project>{2e139f63-b6fe-4ea9-a098-85364fe9b26c}</Project>
<Name>PortCore</Name>
</ProjectReference>
<ProjectReference Include="..\..\Common\Hardware\WaterMeter\MechanicalMeter\FM2014\FM2014Config\FM2014Config.csproj">
<Project>{3862D89C-E32D-4182-BB58-38777D43EDD9}</Project>
<Name>FM2014Config</Name>

View File

@ -221,6 +221,6 @@
<value>Zeit [min:s]:</value>
</data>
<data name="StrMsgPcDateTime" xml:space="preserve">
<value>Datum und -zeit [min:s]:</value>
<value>Datum und Zeit [min:s]:</value>
</data>
</root>