1191 lines
42 KiB
C#
1191 lines
42 KiB
C#
using System;
|
||
using NLog;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using Newtonsoft.Json;
|
||
using Xylem.Common.CommonCore.Consts;
|
||
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
|
||
using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments;
|
||
using Xylem.Common.Hardware.Interfaces.Ports.SerialPorts;
|
||
using Xylem.Common.Hardware.Interfaces.Protocols.ProtocolCore;
|
||
using Xylem.Common.Hardware.Interfaces.Protocols.ProtocolCore.EventArguments;
|
||
using Xylem.Common.Hardware.Interfaces.Protocols.TransmitProtocol;
|
||
using Xylem.Common.Hardware.WaterMeter.MagFlux.DataPackages.DataTypes;
|
||
using Xylem.Common.Hardware.WaterMeter.MagFlux.Protocols.StreamingProtocol;
|
||
using Xylem.Common.Hardware.WaterMeter.MagFlux.DataPackages.EventArguments;
|
||
using Xylem.Common.Hardware.WaterMeter.MagFlux.DataPackages.MeasurementRecords;
|
||
using Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxConfig;
|
||
using Xylem.Common.Hardware.WaterMeter.MagFlux.Protocols.RequestProtocol;
|
||
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
|
||
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore.Consts;
|
||
using Xylem.Common.Metrology.Measurements;
|
||
using Xylem.Common.Metrology.Measurements.Consts;
|
||
using Xylem.Common.Utils.Logging;
|
||
using XYLEM.Device;
|
||
|
||
|
||
namespace Xylem.Common.Hardware.WaterMeter.MagFlux.MagFluxCore
|
||
{
|
||
/// <inheritdoc cref="IMeter" />
|
||
/// <summary>
|
||
/// MagFlux meter is the core class for the MJK MagFlux water meter.
|
||
/// One meter always is always linked to two port, one for communication and one for LED streaming.
|
||
/// </summary>
|
||
public class MagFluxMeter : IMeter, IMeterEvents
|
||
{
|
||
#region Variables and Properties
|
||
|
||
/// <summary>
|
||
/// Mark the streaming catcher as active
|
||
/// </summary>
|
||
private Boolean _streamingCatcherIsActive;
|
||
|
||
/// <summary>
|
||
/// Enable raw record logging, if set every incoming package will be logged
|
||
/// </summary>
|
||
private Boolean _enableRawDataLogging;
|
||
|
||
/// <summary>
|
||
/// to reduce the IrdA communication in test bench, preparation will be done once (when SkipPreparationForTestBench is true)
|
||
/// </summary>
|
||
public Boolean SkipPreparationForTestBench
|
||
{
|
||
get; set;
|
||
}
|
||
/// <summary>
|
||
/// List of ongoing measurements, base- and calibration-measurements
|
||
/// </summary>
|
||
private List<IMeasurement> _listOfIMeasurement = new List<IMeasurement>();
|
||
|
||
/// <summary>
|
||
/// Quality watch mode can be used to decode intermediate records and check
|
||
/// for actual quality of the measurements.
|
||
/// </summary>
|
||
public Boolean QualityWatchMode;
|
||
|
||
/// <summary>
|
||
/// Represent FW version
|
||
/// </summary>
|
||
public String FwVersion
|
||
{
|
||
get;
|
||
private set;
|
||
} = "?";
|
||
|
||
/// <summary>
|
||
/// Represent meter size in mm
|
||
/// </summary>
|
||
public Int32 MeterSizeMm
|
||
{
|
||
get;
|
||
private set;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Represent FW version
|
||
/// </summary>
|
||
public String FwBuildDate
|
||
{
|
||
get;
|
||
private set;
|
||
} = "?";
|
||
|
||
/// <summary>
|
||
/// Represent FW GIT hash
|
||
/// </summary>
|
||
public String FwGitHash
|
||
{
|
||
get;
|
||
private set;
|
||
} = "?";
|
||
|
||
/// <summary>
|
||
/// Represent pcb serial number
|
||
/// </summary>
|
||
public String PcbId
|
||
{
|
||
get; private set;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Represent unique id
|
||
/// </summary>
|
||
public String UniqueId
|
||
{
|
||
get; private set;
|
||
}
|
||
|
||
private String _currentActionText = "";
|
||
/// <summary>
|
||
/// Hold the current process name e.g. FlowTest, Preadjustment for logging
|
||
/// </summary>
|
||
public String CurrentActionText
|
||
{
|
||
get => _currentActionText;
|
||
set
|
||
{
|
||
_currentActionText = value;
|
||
SetLogger();
|
||
}
|
||
}
|
||
|
||
///// <summary>
|
||
///// Add on ctor a password and it will be used for login in if no other password is set
|
||
///// </summary>
|
||
//private String _password;
|
||
|
||
/// <summary>
|
||
/// request port assignment/info
|
||
/// </summary>
|
||
private IPort RequestPort
|
||
{
|
||
get;
|
||
set;
|
||
}
|
||
|
||
/// <summary>
|
||
/// request protocol assignment/info
|
||
/// </summary>
|
||
private RequestProtocol RequestProtocol;
|
||
|
||
/// <summary>
|
||
/// streaming port assignment/info
|
||
/// </summary>
|
||
private IPort StreamingPort
|
||
{
|
||
get;
|
||
set;
|
||
}
|
||
|
||
/// <summary>
|
||
/// streaming protocol assignment/info
|
||
/// </summary>
|
||
private StreamingProtocol StreamingProtocol;
|
||
|
||
/// <inheritdoc />
|
||
public String SerialNumber
|
||
{
|
||
get; set;
|
||
}
|
||
|
||
/// <summary>
|
||
/// do not use it to set ProcessStatus
|
||
/// if you want to change ProcessStatus of this genesis use <see cref="ProcessStatus" />
|
||
/// <see cref="_myProcessStatus" /> is just store for some routines
|
||
/// </summary>
|
||
private ProcessState _myProcessStatus;
|
||
|
||
/// <inheritdoc />
|
||
/// if state is change
|
||
/// <see cref="OnProcessStatusChanged" />
|
||
/// will be invoked.
|
||
/// on some states other events will be invoke as well
|
||
public ProcessState ProcessStatus
|
||
{
|
||
get => _myProcessStatus;
|
||
|
||
private set
|
||
{
|
||
if (_myProcessStatus == value)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_myProcessStatus = value;
|
||
Invoker(OnProcessStatusChanged);
|
||
switch (value)
|
||
{
|
||
case ProcessState.InitIsActive:
|
||
Invoker(OnInitCompleted);
|
||
break;
|
||
case ProcessState.MeasurementIsActive:
|
||
Invoker(OnMeasurementInitCompleted);
|
||
break;
|
||
case ProcessState.MeasurementIsDone:
|
||
Invoker(OnMeasurementCompleted);
|
||
break;
|
||
case ProcessState.CalibrationIsActive:
|
||
Invoker(OnCalibInitCompleted);
|
||
break;
|
||
case ProcessState.CalibrationIsDone:
|
||
Invoker(OnCalibCompleted);
|
||
break;
|
||
case ProcessState.QRefDone:
|
||
case ProcessState.WaitForQRef:
|
||
case ProcessState.WaitForCalibration:
|
||
case ProcessState.WaitForMeasurement:
|
||
case ProcessState.IsNotInit:
|
||
//no extra event needed
|
||
break;
|
||
default:
|
||
throw new OverflowException("MagFlux measurement unknown process status");
|
||
}
|
||
}
|
||
}
|
||
|
||
public Boolean IsLoggedOn
|
||
{
|
||
private set;
|
||
get;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public ErrorState ErrorStatus
|
||
{
|
||
private set;
|
||
get;
|
||
} = ErrorState.None;
|
||
|
||
/// <inheritdoc />
|
||
public Int32 Slot
|
||
{
|
||
get; set;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Int32 IntermediateUpdateTimeS
|
||
{
|
||
get;
|
||
set;
|
||
} = CommunicationConfig.MeasurementUpdateTimeS;
|
||
|
||
|
||
/// <summary>
|
||
/// Process configuration
|
||
/// </summary>
|
||
public ProcessConfig Configuration = new ProcessConfig();
|
||
|
||
/// <summary>
|
||
/// Transmit protocol access for underlie objects to change response timeout
|
||
/// </summary>
|
||
private ITransmitProtocol TransmitProtocol;
|
||
|
||
//initially do not signal event
|
||
private readonly AutoResetEvent _onSyncKeepSessionThread = new AutoResetEvent(false);
|
||
private Thread _keepSessionThread;
|
||
private readonly CancellationTokenSource _keepSessionToken = new CancellationTokenSource();
|
||
|
||
private ILogger _logger;
|
||
|
||
#endregion
|
||
|
||
#region Events
|
||
|
||
event EventHandler IMeter.OnDisposeCompleted
|
||
{
|
||
add
|
||
{
|
||
// throw new NotImplementedException();
|
||
}
|
||
|
||
remove
|
||
{
|
||
// throw new NotImplementedException();
|
||
}
|
||
}
|
||
//public event EventHandler<StringEventArgs> HasInformationForCaller;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnCalibCompleted;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnErrorStatusChanged;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnProcessStatusChanged;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnCalibInitCompleted;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnInitCompleted;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnMeasurementInitCompleted;
|
||
|
||
/// <inheritdoc cref="IMeter" />
|
||
public event EventHandler OnMeasurementCompleted;
|
||
|
||
/// <summary>
|
||
/// Response received after request for record
|
||
/// </summary>
|
||
public event EventHandler<RequestResponseDataEventArgs> OnRequestRecordReceived;
|
||
|
||
/// <summary>
|
||
/// Streaming record is decoded
|
||
/// </summary>
|
||
public event EventHandler<MagFluxFlowDataEventArgs> OnStreamingRecordDecoded;
|
||
|
||
///// <summary>
|
||
///// call this if session is expired and you need an re-authorization
|
||
///// </summary>
|
||
//public EventHandler<AuthorizationRequiredEventArgs> AuthorizationRequired;
|
||
|
||
/// <summary>
|
||
/// A helper to invoke events only if someone is listening, otherwise nothing will happen
|
||
/// </summary>
|
||
/// <param name="handler">Event handler to call</param>
|
||
/// <param name="sender">Sender (can be null)</param>
|
||
/// <param name="e">Event arguments (can be null)</param>
|
||
private void Invoker(EventHandler handler, Object sender = null, EventArgs e = null)
|
||
{
|
||
if (handler != null)
|
||
{
|
||
if (sender == null)
|
||
{
|
||
sender = this;
|
||
}
|
||
|
||
if (e == null)
|
||
{
|
||
e = new EventArgs();
|
||
}
|
||
|
||
handler.Invoke(sender, e);
|
||
}
|
||
// ReSharper disable once RedundantIfElseBlock
|
||
else
|
||
{
|
||
//nobody is listening
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Track all incoming led record packages (Calibration and Flow)
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e">
|
||
/// I should be <see cref="MagFluxRecord" />
|
||
/// otherwise this method does nothing
|
||
/// </param>
|
||
private void StreamingProtocol_ApplyDecodedRecord(Object sender, BaseDataEventArgs e)
|
||
{
|
||
var result = e.GetEventData();
|
||
if (result is IMeasurementRecord)
|
||
{
|
||
if (result is MagFluxRecord)
|
||
{
|
||
if (!((MagFluxRecord)result).IsValid)
|
||
{
|
||
return;
|
||
}
|
||
|
||
}
|
||
|
||
AddData(((IMeasurementRecord)result));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// tracking request record processing
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void RequestProtocol_ApplyDecodedRecord(Object sender, BaseDataEventArgs e)
|
||
{
|
||
if (e is RequestResponseDataEventArgs)
|
||
{
|
||
OnRequestRecordReceived?.Invoke(sender, (RequestResponseDataEventArgs)e);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Kick off the streaming catcher.
|
||
/// </summary>
|
||
public void StartStreamingCatcher()
|
||
{
|
||
if (_streamingCatcherIsActive)
|
||
{
|
||
return;
|
||
}
|
||
if (!(StreamingPort is LedSerialPort))
|
||
{
|
||
throw new ApplicationException("No valid streaming port");
|
||
}
|
||
|
||
SyncStreamingBuffer(SyncMarkRecord.DecodeEveryPackage);
|
||
StreamingProtocol.OnRecordIsDecoded += StreamingCatcher_OnRawRecordReceived;
|
||
LogRawData(true);
|
||
_streamingCatcherIsActive = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Cyclic execution of catcher on incoming events.
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void StreamingCatcher_OnRawRecordReceived(Object sender, BaseDataEventArgs e)
|
||
{
|
||
OnStreamingRecordDecoded?.Invoke(this, (MagFluxFlowDataEventArgs)e);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Stop the streaming catcher.
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public void StopStreamingCatcher()
|
||
{
|
||
if (StreamingPort == null || !(StreamingPort is LedSerialPort))
|
||
{
|
||
throw new ApplicationException("No valid streaming port");
|
||
}
|
||
LogRawData(false);
|
||
StreamingProtocol.OnRecordIsDecoded -= StreamingCatcher_OnRawRecordReceived;
|
||
SyncStreamingBuffer(SyncMarkRecord.SkipDecoding);
|
||
_streamingCatcherIsActive = false;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region class handling
|
||
|
||
/// <summary>
|
||
/// Ctor - default
|
||
/// </summary>
|
||
/// <remarks date="2023-Apr-20" author="Thomas Wiedebusch">
|
||
/// - Initial
|
||
/// </remarks>
|
||
public MagFluxMeter()
|
||
{
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void Dispose()
|
||
{
|
||
_logger.Trace($"Slot:{Slot} - Start to dispose");
|
||
|
||
StreamingProtocol.OnRecordIsDecoded -= StreamingProtocol_ApplyDecodedRecord;
|
||
RequestProtocol.OnRecordIsDecoded -= RequestProtocol_ApplyDecodedRecord;
|
||
|
||
LogRawData(false);
|
||
LogManager.Flush();
|
||
//isPart of Logout
|
||
//StopKeepSession();
|
||
|
||
//Cancel receive tokens
|
||
_keepSessionToken?.Cancel();
|
||
|
||
try
|
||
{
|
||
Logout();
|
||
}
|
||
catch (Exception)
|
||
{
|
||
// ignored
|
||
}
|
||
|
||
|
||
//Run thread again to notice CancellationToken has changed
|
||
_onSyncKeepSessionThread?.Set();
|
||
|
||
//this timeout counter is being used for dispose only
|
||
var timeoutCounter = 100;
|
||
if (_keepSessionThread != null)
|
||
{
|
||
while (_keepSessionThread.ThreadState != ThreadState.Stopped && timeoutCounter > 0)
|
||
{
|
||
Thread.Sleep(1);
|
||
timeoutCounter -= 1;
|
||
}
|
||
|
||
if (_keepSessionThread.ThreadState != ThreadState.Stopped)
|
||
{
|
||
//if thread is still running try to abort
|
||
// try to avoid abort (takes age to run and is not safe)
|
||
_keepSessionThread.Abort();
|
||
}
|
||
}
|
||
|
||
_keepSessionThread = null;
|
||
|
||
RequestPort?.Dispose();
|
||
StreamingPort?.Dispose();
|
||
|
||
RequestProtocol?.Dispose();
|
||
StreamingProtocol?.Dispose();
|
||
_logger.Trace($"Slot:{Slot} - Dispose is finished");
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Ctor
|
||
/// </summary>
|
||
/// <param name="slot">Slot number for test-bench </param>
|
||
/// <param name="requestPort"><see cref="PortConfig" /> for request Port</param>
|
||
/// <param name="streamingPort"><see cref="PortConfig" /> for streaming Port</param>
|
||
/// <param name="ignoreCorruptedData">don´t send CRC error or telegrams with error flag to caller</param>
|
||
/// <param name="password">
|
||
/// If you have a password for highest access level you need, if you don't want to access the meter
|
||
/// (just read led record) than you can leave it null
|
||
/// </param>
|
||
/// <remarks date="2023-Apr-20" author="Thomas Wiedebusch">
|
||
/// - Initial
|
||
/// </remarks>
|
||
public MagFluxMeter(Int32 slot, PortConfig? requestPort, PortConfig? streamingPort,
|
||
Boolean ignoreCorruptedData = true, String password = null)
|
||
{
|
||
_logger = NLogHelper.CreateOrGetMultiLogger($"Slot:{slot}", "Slot", "Meter", "MeterBase", "MeterBase");
|
||
SetupMagFluxMeter(slot, requestPort, streamingPort, ignoreCorruptedData, password);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region MagFlux handling
|
||
/// <summary>
|
||
/// just internal setup. called on ctor or from vb6 setup function
|
||
/// </summary>
|
||
/// <param name="slot">Slot Number for test-bench </param>
|
||
/// <param name="requestPort"><see cref="PortConfig" /> for RFID/UART/IrDA Port</param>
|
||
/// <param name="streamingPort"><see cref="PortConfig" /> for LED Port</param>
|
||
/// <param name="ignoreCorruptedData">don´t send CRC error or telegrams with error flag to caller</param>
|
||
/// <param name="password">
|
||
/// If you have a password for highest access level you need, if you don't want to access the meter
|
||
/// (just read led record) than you can leave it nullS
|
||
/// </param>
|
||
private void SetupMagFluxMeter(Int32 slot, PortConfig? requestPort, PortConfig? streamingPort,
|
||
Boolean ignoreCorruptedData = true, String password = "")
|
||
{
|
||
Slot = slot;
|
||
//_password = password;
|
||
|
||
if (requestPort != null)
|
||
{
|
||
if (requestPort.Value.Type == "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.UartSerialPort")
|
||
{
|
||
var requestIdent = $"Slot:{Slot}, Port:{requestPort.Value.PortName}, Protocol:Request, Type:UART -";
|
||
|
||
RequestProtocol = new RequestProtocol(requestIdent);
|
||
TransmitProtocol = new UartTransmitProtocol(requestPort.Value.PortName);
|
||
//RequestProtocol.SetTransmitProtocol(TransmitProtocol);
|
||
|
||
// request port is needed to assign the port name from file, else it is not used
|
||
// as the IMagFluxRequestProtocol implements the port handling and communication
|
||
RequestPort = new UartSerialPort(requestIdent, requestPort.Value.GetSerialPort(),
|
||
TransmitProtocol.GetTransmitPortSettings());
|
||
}
|
||
}
|
||
|
||
if (streamingPort != null)
|
||
{
|
||
var ledTransmit = new LedTransmitProtocol(streamingPort.Value.PortName);
|
||
|
||
var streamingIdent = $"Slot:{Slot}, Port:{streamingPort.Value.PortName}, Protocol:LED";
|
||
|
||
StreamingPort = new LedSerialPort(streamingIdent, streamingPort.Value.GetSerialPort(),
|
||
ledTransmit.GetTransmitPortSettings());
|
||
StreamingProtocol = new StreamingProtocol(streamingIdent, ignoreCorruptedData);
|
||
}
|
||
Configuration = new ProcessConfig(ProgramConfig.ConfigFileName);
|
||
|
||
SetLogger();
|
||
_logger.Debug($"Slot:{Slot} - Setup done");
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
/// <summary>
|
||
/// Start Event listening, has to be called after <see cref="SetupMagFluxMeter" />
|
||
/// </summary>
|
||
public void ConnectMeter()
|
||
{
|
||
if (RequestPort != null && RequestPort.GetPortName() != BaseSerialPort.PortNotAssigned)
|
||
{
|
||
if (RequestProtocol.Connect(RequestPort.GetPortName()))
|
||
{
|
||
_logger.Trace($"Slot:{Slot} - RequestPort and RequestProtocol linked");
|
||
RequestProtocol.OnRecordIsDecoded += RequestProtocol_ApplyDecodedRecord;
|
||
RequestProtocol.CloseConnection();
|
||
}
|
||
else
|
||
{
|
||
_logger.Trace($"Slot:{Slot} - RequestPort Connection failed");
|
||
}
|
||
}
|
||
if (StreamingPort != null && StreamingPort.GetPortName() != BaseSerialPort.PortNotAssigned)
|
||
{
|
||
LinkPortToProtocol(StreamingPort, StreamingProtocol);
|
||
_logger.Trace($"Slot:{Slot} - StreamingPort and StreamingProtocol linked");
|
||
StreamingProtocol.OnRecordIsDecoded += StreamingProtocol_ApplyDecodedRecord;
|
||
}
|
||
|
||
_logger.Debug($"Slot:{Slot} - Connected and communication started");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Add a port to working queue
|
||
/// </summary>
|
||
/// <param name="port">On <see cref="IPort" /> should one once added in runtime</param>
|
||
/// <param name="protocol"></param>
|
||
private static void LinkPortToProtocol(IPort port, IProtocol protocol)
|
||
{
|
||
//for streaming mode only
|
||
if (port == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
port.OnRawRecordReceived += delegate (Object o, BasePortDataEventArgs rawMsg)
|
||
{
|
||
protocol.FillDecodingBuffer(rawMsg);
|
||
};
|
||
try
|
||
{
|
||
if (!port.IsOpen())
|
||
{
|
||
port.Open();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new ApplicationException($"Cannot Open Comport. Check TaskManager for other Genesis relevant programs and close them. Also check if the comport is valid! { Environment.NewLine }{ ex.Message}");
|
||
}
|
||
|
||
protocol.OnRecordReadyToSend += delegate (Object o, BasePortDataEventArgs dataToSend)
|
||
{
|
||
var data = (List<Byte>)dataToSend.GetData();
|
||
port.PortWrite(data.ToArray());
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// Synchronization of the record for the streaming interface
|
||
/// </summary>
|
||
/// <param name="markRecord"></param>
|
||
/// <exception cref="ApplicationException"></exception>
|
||
private void SyncStreamingBuffer(SyncMarkRecord markRecord)
|
||
{
|
||
if (StreamingPort == null)
|
||
{
|
||
throw new ApplicationException("Synchronization not possible due to undefined streaming port");
|
||
}
|
||
|
||
StreamingPort.SynchronizeReceiveBuffer(markRecord);
|
||
}
|
||
|
||
private void RequestProtocol_AuthorizationGrant(Object sender, EventArgs e)
|
||
{
|
||
_logger.Debug($"Slot:{Slot} - Authorization succeeded");
|
||
}
|
||
|
||
public void SetupFromConfigFile(Int32 slot, Boolean ignoreCorruptedData = true)
|
||
{
|
||
SetupFromConfigFile(slot, ignoreCorruptedData, true, true);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Copy list of flow results to <see cref="MagFlux.DataPackages.DataTypes.Calibration"/> and kick off the
|
||
/// writing of calibrations to MagFlux.
|
||
/// </summary>
|
||
/// <param name="results">List of flow results</param>
|
||
public void WriteCalibrationTestBench(List<WaterMeter.DataPackages.MeasurementRecords.FlowDeviations> results)
|
||
{
|
||
if (RequestProtocol == null) return;
|
||
var calibrationsCmPerH = new List<Calibration>();
|
||
var calibrationCmPerH = new Calibration();
|
||
foreach (var result in results)
|
||
{
|
||
calibrationCmPerH.refFlowRate = (Single)result.MeasuredRefFlowQmPerH;
|
||
calibrationCmPerH.dutFlowRate = (Single)result.MeasuredDutFlowQmPerH;
|
||
calibrationsCmPerH.Add(calibrationCmPerH);
|
||
}
|
||
|
||
RequestProtocol.SetCalibrationPoints(calibrationsCmPerH);
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
void IMeter.SetupFromConfigFile(Int32 slot, Boolean ignoreCorruptedData, Boolean useRequest, Boolean useStreaming)
|
||
{
|
||
SetupFromConfigFile(slot, ignoreCorruptedData);
|
||
}
|
||
/// <summary>
|
||
/// Setup water meter from configuration file:
|
||
/// -slot,
|
||
/// -streaming protocol,
|
||
/// -streaming port
|
||
/// -request port
|
||
/// </summary>
|
||
/// <param name="slot"></param>
|
||
/// <param name="ignoreCorruptedData"></param>
|
||
/// <param name="useRequest"></param>
|
||
/// <param name="useStreaming"></param>
|
||
/// <exception cref="ApplicationException"></exception>
|
||
private void SetupFromConfigFile(Int32 slot, Boolean ignoreCorruptedData, Boolean useRequest, Boolean useStreaming)
|
||
{
|
||
Slot = slot;
|
||
var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(MagFlux), ProgramConfig.SerialConfigFileName);
|
||
|
||
if (!File.Exists(configFile))
|
||
{
|
||
configFile = Path.Combine(ProgramConfig.SerialConfigFileName);
|
||
if (!File.Exists(configFile))
|
||
{
|
||
throw new ApplicationException($"Configuration file {configFile} not found ");
|
||
}
|
||
}
|
||
var tr = new StreamReader(configFile);
|
||
var slotConfigList = JsonConvert.DeserializeObject<SlotConfig[]>(tr.ReadToEnd());
|
||
|
||
var slotConfig = slotConfigList.FirstOrDefault(t => t.Slot == slot);
|
||
|
||
if (slotConfig == null)
|
||
{
|
||
throw new ApplicationException($"Slot:{slot} - File " +
|
||
$"{ProgramConfig.SerialConfigFileName} does not contain valid configuration");
|
||
}
|
||
if (!useRequest)
|
||
{
|
||
SetupMagFluxMeter(slot, null, slotConfig.Streaming, ignoreCorruptedData);
|
||
}
|
||
else if (!useStreaming)
|
||
{
|
||
SetupMagFluxMeter(slot, slotConfig.Request, null, ignoreCorruptedData);
|
||
}
|
||
else
|
||
{
|
||
SetupMagFluxMeter(slot, slotConfig.Request, slotConfig.Streaming, ignoreCorruptedData);
|
||
}
|
||
SetLogger();
|
||
_logger.Debug($"Slot:{slot} - Configuration loaded");
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Boolean Logout()
|
||
{
|
||
RequestProtocol.CloseConnection();
|
||
IsLoggedOn = false;
|
||
return true;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
/// <remarks date="2023-Mai-30" author="T.Wiedebusch">
|
||
/// - Unique Id introduced,
|
||
/// - Sensor Serial number mapped as PcbId.
|
||
/// </remarks>
|
||
public Boolean Login()
|
||
{
|
||
if (!RequestProtocol.Connect(RequestPort.GetPortName()))
|
||
return IsLoggedOn;
|
||
|
||
IsLoggedOn = true;
|
||
if (RequestProtocol.GetFirmwareVersion(out var fwVersion))
|
||
{
|
||
FwVersion = fwVersion;
|
||
}
|
||
if (RequestProtocol.GetFwBuildDate(out var fwBuildDate))
|
||
{
|
||
FwBuildDate = fwBuildDate;
|
||
}
|
||
if (RequestProtocol.GetMeterSizeMm(out var meterSizeMm))
|
||
{
|
||
MeterSizeMm = meterSizeMm;
|
||
}
|
||
if (RequestProtocol.GetFwGitHash(out var gitHash))
|
||
{
|
||
FwGitHash = gitHash;
|
||
}
|
||
if (RequestProtocol.GetUniqueId(out var uniqueId))
|
||
{
|
||
UniqueId = uniqueId;
|
||
}
|
||
// The PcbId will be named as SensorSerialNo by MJK
|
||
if (RequestProtocol.GetPcbId(out var pcbId))
|
||
{
|
||
PcbId = pcbId;
|
||
}
|
||
|
||
StreamingProtocol?.SetDecodingMask(pcbId, uniqueId);
|
||
return IsLoggedOn;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Boolean Login(String password, Boolean runImmediately = true, Boolean skipReadMeterFwAndAssignRegisters = false)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void InitMeasurement()
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Init measurement");
|
||
PreMeasurement();
|
||
}
|
||
|
||
private void PreMeasurement()
|
||
{
|
||
if (!SkipPreparationForTestBench)
|
||
{
|
||
SkipPreparationForTestBench = true;
|
||
}
|
||
else
|
||
{
|
||
WriteLog("Wait on measurement start");
|
||
}
|
||
ProcessStatus = ProcessState.WaitForMeasurement;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void StartMeasurement()
|
||
{
|
||
_listOfIMeasurement = new List<IMeasurement>
|
||
{
|
||
new BaseMeasurement(Slot, typeof(MagFluxRecord), IntermediateUpdateTimeS)
|
||
};
|
||
|
||
foreach (var measurement in _listOfIMeasurement)
|
||
{
|
||
measurement.OnActionStateHasChanged += Measurement_ActionStateHasChanged;
|
||
}
|
||
|
||
_logger.Info($"Slot:{Slot} - Start measurement");
|
||
|
||
ProcessStatus = ProcessState.MeasurementIsActive;
|
||
_listOfIMeasurement.ForEach(m => m.MeasurementState = MeasurementStates.IsWaitingForStartData);
|
||
SyncStreamingBuffer(SyncMarkRecord.SyncStart);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void StopMeasurement()
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Stop measurement");
|
||
ProcessStatus = ProcessState.MeasurementIsDone;
|
||
_listOfIMeasurement.ForEach(m => m.MeasurementState = MeasurementStates.IsWaitingForEndData);
|
||
SyncStreamingBuffer(SyncMarkRecord.SyncEnd);
|
||
}
|
||
|
||
private void Measurement_ActionStateHasChanged(Object sender, EventArgs e)
|
||
{
|
||
SyncStreamingBuffer(SyncMarkRecord.DecodeIntermediate);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public MeasurementResults GetMainMeasurementResult(Double? refVolume, Double? refTimeS,
|
||
Boolean intermediateMeasurement = false)
|
||
{
|
||
if (!intermediateMeasurement && GetMeasurementState(0) != MeasurementStates.IsCompleted)
|
||
{
|
||
throw new ApplicationException("Measurement is not completed");
|
||
}
|
||
return GetAllMeasurementResults(refVolume, refTimeS, intermediateMeasurement, 0).First();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public List<MeasurementResults> GetAllMeasurementResults(Double? refVolume, Double? refTimeS, Boolean intermediateMeasurement = false,
|
||
Int32? channel = null)
|
||
{
|
||
if (!intermediateMeasurement && GetMeasurementState(channel) != MeasurementStates.IsCompleted)
|
||
{
|
||
throw new ApplicationException("Measurement is not completed");
|
||
}
|
||
var mainMeasurement = _listOfIMeasurement.FirstOrDefault(m => m.GetType() == typeof(MagFluxRecord));
|
||
|
||
if (mainMeasurement == null)
|
||
{
|
||
throw new ApplicationException("Can not find an ongoing measurement");
|
||
}
|
||
|
||
var results = new List<MeasurementResults>();
|
||
var mainResult = intermediateMeasurement
|
||
? mainMeasurement.GetIntermediateMeasurementResult(refVolume, refTimeS)
|
||
: mainMeasurement.GetFinalMeasurementResult(refVolume, refTimeS);
|
||
results.Add(mainResult);
|
||
_logger.Info($"Slot:{Slot}, Channel:{mainMeasurement.GetChannel()} - MeasuredTime({mainResult.DutTimeS}s), " +
|
||
$"MeasuredVolume({mainResult.DutVolumeCm}Qm)");
|
||
|
||
foreach (var otherMeasurement in _listOfIMeasurement.Where(m => m.GetType() != typeof(MagFluxRecord)))
|
||
{
|
||
try
|
||
{
|
||
var otherResults = intermediateMeasurement
|
||
? otherMeasurement.GetIntermediateMeasurementResult(refVolume, refTimeS)
|
||
: otherMeasurement.GetFinalMeasurementResult(refVolume, refTimeS);
|
||
_logger.Info($"Slot:{Slot}, Channel:{otherMeasurement.GetChannel()} - ChannelTime({otherResults.DutTimeS}s), " +
|
||
$"ChannelVolume({otherResults.DutVolumeCm}Qm)");
|
||
results.Add(otherResults);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Warn($"Slot:{Slot}, No Results for channel:{otherMeasurement.GetChannel()} Ex: {ex.Message}");
|
||
if (channel == null)
|
||
{
|
||
throw new ApplicationException($"Can not read result from channel:{otherMeasurement.GetChannel() } Ex: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public MeasurementStates GetMeasurementState(Int32? onlyChannelNr = null)
|
||
{
|
||
_logger.Trace($"Slot:{Slot} - Get measurement state");
|
||
if (!_listOfIMeasurement.Any())
|
||
{
|
||
return MeasurementStates.NotStarted;
|
||
}
|
||
|
||
if (onlyChannelNr.HasValue)
|
||
{
|
||
var measurement = _listOfIMeasurement.First(f => f.GetChannel() == onlyChannelNr.Value);
|
||
return measurement.GetMeasurementState();
|
||
}
|
||
return _listOfIMeasurement.Min(m => m.MeasurementState);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Boolean RequestIntermediateMeasurementState(Int32? onlyChannelNr = null)
|
||
{
|
||
_logger.Trace($"Slot:{Slot} - Get measurement state");
|
||
if (!_listOfIMeasurement.Any())
|
||
{
|
||
return false;
|
||
}
|
||
Boolean? ret = null;
|
||
foreach (var item in _listOfIMeasurement)
|
||
{
|
||
if (!onlyChannelNr.HasValue || item.GetChannel() == onlyChannelNr.Value)
|
||
{
|
||
var measurement = _listOfIMeasurement.First(f => onlyChannelNr != null && f.GetChannel() == onlyChannelNr.Value);
|
||
if ((measurement.MeasurementState == MeasurementStates.IsRunning
|
||
|| measurement.MeasurementState == MeasurementStates.IsWaitingForIntermediateData))
|
||
{
|
||
measurement.RequestIntermediateRecord();
|
||
if (!ret.HasValue)
|
||
{
|
||
ret = true;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ret = false;
|
||
}
|
||
}
|
||
}
|
||
return ret.HasValue ? ret.Value : false;
|
||
}
|
||
|
||
|
||
/// <inheritdoc/>>
|
||
public String GetPcbId(Int32 expectedLength = 12)
|
||
{
|
||
if (!string.IsNullOrEmpty(PcbId) && !string.Equals("?", PcbId))
|
||
{
|
||
return PcbId;
|
||
}
|
||
var pcbId = PcbId;
|
||
RequestProtocol?.GetPcbId(out pcbId);
|
||
PcbId = pcbId;
|
||
return pcbId;
|
||
}
|
||
/// <summary>
|
||
/// Write calibration values to MagFlux
|
||
/// </summary>
|
||
public void WriteCalibration(List<Calibration> calibrationsCmPerH)
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Write calibration values to MagFlux");
|
||
RequestProtocol?.SetCalibrationPoints(calibrationsCmPerH);
|
||
}
|
||
/// <summary>
|
||
/// Read calibration values to MagFlux
|
||
/// </summary>
|
||
public void ReadCalibration(out List<Calibration> calibrationsCmPerH)
|
||
{
|
||
calibrationsCmPerH = null;
|
||
_logger.Info($"Slot:{Slot} - Read calibration values from MagFlux");
|
||
RequestProtocol?.GetCalibrationPoints(out calibrationsCmPerH);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void InitCalibration()
|
||
{
|
||
_logger.Info($"Slot:{Slot} - Init calibration and set calibration values of all channels to default");
|
||
if (RequestProtocol == null)
|
||
return;
|
||
if (RequestProtocol.PrepareDeviceForCalibration())
|
||
PreMeasurement();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void StartCalibration()
|
||
{
|
||
StartMeasurement();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void StopCalibration()
|
||
{
|
||
StopMeasurement();
|
||
RequestProtocol?.AbortDeviceForCalibration();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public MeasurementStates GetCalibrationState()
|
||
{
|
||
return GetMeasurementState();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void BuildAndCheckCalibFactorsAllChannels(Double refVolumeCm, Double? refTimeS = null,
|
||
Double reqDeviationPercent = 0, Double maxCalibFactorTolerancePercent = 0)
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void SetCalibFactorsAllChannels(Boolean justLog = false)
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void SetLcdText(Boolean release, Byte[] textInHex)
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void SetProcessState(DisplayCodes newState, Boolean webRequest = true)
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public DisplayCodes GetLastProcessState()
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void WriteLog(String text)
|
||
{
|
||
_logger.Info($"Slot:{Slot} - {text}");
|
||
//if (_enableRawDataLogging)
|
||
//{
|
||
// if (_loggerRawData == null)
|
||
// {
|
||
// _loggerRawData = NLogHelper.CreateOrGetMultiLogger(
|
||
// StreamingPort.GetPortName(), "", "LedRawData", "TargetLedRawDataBase", "LedRawDataBase");
|
||
|
||
// }
|
||
// _loggerRawData.Info($"{text}");
|
||
//}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void LogRawData(Boolean on)
|
||
{
|
||
_enableRawDataLogging = on;
|
||
if (StreamingPort is LedSerialPort port)
|
||
{
|
||
port.RecordStreamingRawData = _enableRawDataLogging;
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Boolean CheckStateAfterMeasurement()
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void DisposeMeter()
|
||
{
|
||
Dispose();
|
||
}
|
||
|
||
private void AddData(IMeasurementRecord newRecord)
|
||
{
|
||
|
||
foreach (var measurement in _listOfIMeasurement)
|
||
{
|
||
if (measurement.GetType() != newRecord.GetType())
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (measurement.GetChannel() == newRecord.GetChannel())
|
||
{
|
||
measurement.AddData(newRecord);
|
||
}
|
||
}
|
||
|
||
// when every measurement has state IsRunning or every measurement has state IsCompleted, the streaming buffer set to skip decoding
|
||
if (_listOfIMeasurement.All(m => m.MeasurementState == MeasurementStates.IsRunning)
|
||
|| _listOfIMeasurement.All(m => m.MeasurementState == MeasurementStates.IsCompleted))
|
||
{
|
||
SyncStreamingBuffer(QualityWatchMode
|
||
? SyncMarkRecord.DecodeIntermediateQuality
|
||
: SyncMarkRecord.SkipDecoding);
|
||
}
|
||
|
||
}
|
||
#endregion
|
||
|
||
#region logging
|
||
private void SetLogger()
|
||
{
|
||
if (_logger != null)
|
||
{
|
||
LogManager.Flush();
|
||
}
|
||
// the id is a combination of UniqueId stored in PcbId and SensorSN (SensorId)
|
||
var id = !string.IsNullOrEmpty(PcbId) ? $"PCBID{PcbId}" : "?";
|
||
id += !string.IsNullOrEmpty(SerialNumber) ? $"_SN{SerialNumber}" : "?";
|
||
var fileName = $"MagFluxRawData_Slot_{Slot}";
|
||
|
||
if (string.IsNullOrEmpty(CurrentActionText))
|
||
{
|
||
_logger = NLogHelper.CreateOrGetMultiLogger($"Slot:{Slot}", $"", "Meter", "MeterBase", "MeterBase");
|
||
if (RequestProtocol != null)
|
||
{
|
||
var pathName = NLogHelper.GetPath(_logger);
|
||
var path = Path.GetDirectoryName(pathName);
|
||
RequestProtocol.SetLogLocation(path, fileName);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_logger = NLogHelper.CreateOrGetMultiLogger($"Slot:{Slot}_{id}_{CurrentActionText}", "Slot", "Meter", "MeterBase", "MeterBase");
|
||
if (RequestProtocol != null)
|
||
{
|
||
var pathName = NLogHelper.GetPath(_logger);
|
||
var path = Path.GetDirectoryName(pathName);
|
||
RequestProtocol.SetLogLocation(path, fileName);
|
||
}
|
||
|
||
//TODO check with roland
|
||
if (StreamingPort is LedSerialPort)
|
||
{
|
||
var streamingIdent = $"LedRawData_Slot_{Slot}_{id}_{CurrentActionText}";
|
||
|
||
((LedSerialPort)StreamingPort).Ident = streamingIdent;
|
||
((LedSerialPort)StreamingPort).RefreshLogger();
|
||
|
||
|
||
if (!string.IsNullOrEmpty(SerialNumber) && CurrentActionText.Contains("PG"))
|
||
{
|
||
|
||
//var pathMain = NLogHelper.GetPath(_logger);
|
||
//var pathRaw = NLogHelper.GetPath(((LedSerialPort)StreamingPort).GetRawLogger());
|
||
|
||
//var basePath = Path.GetDirectoryName(pathMain);
|
||
|
||
//var testRunString = CurrentActionText.Replace("PG", "");
|
||
//var lastChat = pathMain.LastIndexOf('\\');
|
||
//pathMain = pathMain.Substring(lastChat + 1);
|
||
//pathRaw = Path.GetFileName(pathRaw);
|
||
|
||
//var tmpUrl = $"http://10.49.40.25/MeterProcessState/api/TestBench/SetMeterLogPathInfo?SerialNumber={SerialNumber}&TestRunId={testRunString}&BasePath={basePath}&MainLogName={pathMain}&LEDName={pathRaw}";
|
||
//_logger.Debug($"Request Url: { tmpUrl}");
|
||
////ToDO: Update to service URl
|
||
//LocalWebRequest.GetRequest(tmpUrl);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
}
|
||
}
|