common/Metrology/Measurements/BaseMeasurement.cs
2026-04-23 17:50:07 +02:00

346 lines
14 KiB
C#

using System;
using System.Timers;
using NLog;
using Xylem.Common.CommonCore.Consts;
using Xylem.Common.Metrology.Measurements.Consts;
using Xylem.Common.Utils.Logging;
namespace Xylem.Common.Metrology.Measurements
{
/// <inheritdoc />
/// <summary>
/// Holds all stuff for calibration of a Genesis Meter
/// </summary>
public class BaseMeasurement : IMeasurement
{
/// <summary>
/// Record at the start of the measurement
/// </summary>
public IMeasurementRecord DutStartRecord;
/// <summary>
/// Record at the end of the measurement
/// </summary>
public IMeasurementRecord DutStopRecord;
/// <summary>
/// Backup of intermediate record
/// </summary>
private IMeasurementRecord _lastIntermediateRecord;
/// <summary>
/// Backup of intermediate record for short term result
/// This can be used to observe a result (e.g. flow rate)
/// between two intermediate records. The update time is
/// set in the ctor by intermediateUpdateTimeS.
/// </summary>
private IMeasurementRecord _last2IntermediateRecord;
/// <summary>
/// Intermediate record for ongoing measurement
/// </summary>
public IMeasurementRecord IntermediateRecord;
/// <inheritdoc />
public event EventHandler OnActionStateHasChanged;
/// <summary>
/// Holds the accumulated overflow volume, will work only if
/// <see cref="CheckForOverFlowVolumeAndTime()" /> is being called cyclically.
/// The overflow will take place due to the fixed point number format or the
/// display resolution setting.
/// </summary>
private Double _accuDutOverflowVolumeCm;
/// <summary>
/// Holds the accumulated overflow time, will work only if
/// <see cref="CheckForOverFlowVolumeAndTime()" /> is being called cyclically.
/// The overflow takes place due to the fixed point number format.
/// </summary>
private Double _accuDutOverflowTimeS;
/// <summary>
/// Logger for debug output of measurement
/// </summary>
public readonly ILogger LoggerMeasurement;
/// <summary>
/// Physical slot of the device
/// </summary>
public readonly Int32 Slot;
/// <summary>
/// Identification of the channel
/// </summary>
private readonly Int32 _channel;
private MeasurementStates _state;
private readonly Type _measurementRecordType;
/// <inheritdoc />
/// <summary>
/// State machine for one measurement (on one meter and one channel)
/// </summary>
/// <history date="2018Jan24" author="drabesch">
/// - add State-machine for better handling
/// </history>
public MeasurementStates MeasurementState
{
get
{
return _state;
}
set
{
if (value == MeasurementStates.IsWaitingForIntermediateData &&
(_state == MeasurementStates.IsCompleted || _state == MeasurementStates.IsWaitingForEndData
|| _state == MeasurementStates.IsWaitingForStartData))
{
LoggerMeasurement.Info(
$"Tried to override measurement state with {value} while current state is {_state}");
}
else if (value == MeasurementStates.IsRunning && (_state == MeasurementStates.IsWaitingForEndData
|| _state == MeasurementStates.IsCompleted))
{
LoggerMeasurement.Info(
$"Tried to override measurement state with {value} while current state is {_state}");
}
else
{
_state = value;
}
}
}
public MeasurementDirection FlowDirectionforCalculatingVol { get; set; } = MeasurementDirection.TakeBoth;
/// <summary>
///
/// </summary>
/// <param name="slot"></param>
/// <param name="measurementRecordType"></param>
/// <param name="intermediateUpdateTimeS"></param>
/// <param name="channel"></param>
public BaseMeasurement(Int32 slot, Type measurementRecordType, Int32 intermediateUpdateTimeS, Int32 channel = 0)
{
//build the update time in milliseconds, this will be needed for overflow detection of volume and time
var sw = new Timer(intermediateUpdateTimeS * 1000);
sw.Start();
//request intermediate record for overflow detection and display update
sw.Elapsed += RequestIntermediateRecordByTimer;
Slot = slot;
_channel = channel;
_measurementRecordType = measurementRecordType;
LoggerMeasurement = NLogHelper.CreateOrGetMultiLogger($"Slot:{Slot}", "Slot", "Measurement", "MeterBase", "MeterBase");
}
/// <summary>
///
/// </summary>
/// <param name="o"></param>
/// <param name="e"></param>
public void RequestIntermediateRecordByTimer(Object o, ElapsedEventArgs e)
{
if (MeasurementState != MeasurementStates.IsRunning) return;
MeasurementState = MeasurementStates.IsWaitingForIntermediateData;
OnActionStateHasChanged?.Invoke(o, e);
}
/// <summary>
///
/// </summary>
public void RequestIntermediateRecord()
{
if (MeasurementState != MeasurementStates.IsRunning) return;
MeasurementState = MeasurementStates.IsWaitingForIntermediateData;
OnActionStateHasChanged?.Invoke(this, EventArgs.Empty);
}
/// <inheritdoc />
public new Type GetType()
{
return _measurementRecordType;
}
/// <inheritdoc />
public Int32 GetChannel()
{
return _channel;
}
/// <inheritdoc />
public void Clear()
{
DutStartRecord = null;
DutStopRecord = null;
IntermediateRecord = null;
_lastIntermediateRecord = null;
_last2IntermediateRecord = null;
MeasurementState = MeasurementStates.NotStarted;
}
/// <inheritdoc />
public void Dispose()
{
Clear();
}
/// <inheritdoc />
public MeasurementResults GetShortTermMeasurementResult(Double? refVolumeCm = null, Double? refTimeS = null)
{
if (_last2IntermediateRecord == null || _lastIntermediateRecord == null)
throw new ApplicationException("Measurement records missing");
return new MeasurementResults(_last2IntermediateRecord, _lastIntermediateRecord, _accuDutOverflowVolumeCm,
_accuDutOverflowTimeS, refVolumeCm, refTimeS, _channel);
}
/// <inheritdoc />
public MeasurementResults GetIntermediateMeasurementResult(Double? refVolumeCm = null, Double? refTimeS = null)
{
if (DutStartRecord == null || IntermediateRecord == null)
throw new ApplicationException("Measurement records missing");
return new MeasurementResults(DutStartRecord, IntermediateRecord, _accuDutOverflowVolumeCm,
_accuDutOverflowTimeS, refVolumeCm, refTimeS, _channel);
}
/// <inheritdoc />
public MeasurementResults GetFinalMeasurementResult(Double? refVolumeCm, Double ? refTimeS = null)
{
if (DutStartRecord == null || DutStopRecord == null)
throw new ApplicationException("Measurement records missing");
return new MeasurementResults(DutStartRecord, DutStopRecord, _accuDutOverflowVolumeCm,
_accuDutOverflowTimeS, refVolumeCm, refTimeS, _channel, FlowDirectionforCalculatingVol);
}
private void CheckForOverFlowVolumeAndTime()
{
if (IntermediateRecord == null || _lastIntermediateRecord == null) return;
var dutOverflowVolumeCm = IntermediateRecord.GetOverflowVolumeCm();
var currentVolumeQm = IntermediateRecord.GetVolumeCm();
var lastVolumeQm = _lastIntermediateRecord.GetVolumeCm();
//an overflow is detected when absolute volume difference is higher than 1/2 of the OverflowVolumeCm
if (Math.Abs(currentVolumeQm - lastVolumeQm) > (0.5 * dutOverflowVolumeCm))
{
//if the overflow has been detected the end volume is higher than the start volume
//indicates the negative flow direction
if (currentVolumeQm > lastVolumeQm)
{
//negative overflow
_accuDutOverflowVolumeCm -= dutOverflowVolumeCm;
LoggerMeasurement.Info($"Slot:{Slot}, Channel: {IntermediateRecord.GetChannel()} - has negative overflow. currentVolumeQm ({currentVolumeQm}) - lastVolumeQm ({lastVolumeQm}). current accuOverflowVolume is {_accuDutOverflowVolumeCm}");
}
else
{
//positive overflow
_accuDutOverflowVolumeCm += dutOverflowVolumeCm;
LoggerMeasurement.Info($"Slot:{Slot}, Channel: {IntermediateRecord.GetChannel()} - has positive overflow. currentVolumeQm ({currentVolumeQm}) - lastVolumeQm ({lastVolumeQm}). current accuOverflowVolume is {_accuDutOverflowVolumeCm}");
}
}
var dutOverflowTimeS = IntermediateRecord.GetOverflowTimeS();
var currentTimeS = IntermediateRecord.GetTimeS();
var lastTimeS = _lastIntermediateRecord.GetTimeS();
//check for timer overflow due to number range (e.g. 2^32) of raw value
if (currentTimeS < lastTimeS)
{
_accuDutOverflowTimeS += dutOverflowTimeS;
}
//remind record for short term results
_last2IntermediateRecord = _lastIntermediateRecord;
//remind this intermediate record for the next run for overflow detection
_lastIntermediateRecord = IntermediateRecord;
}
/// <inheritdoc />
public MeasurementStates GetMeasurementState()
{
return _state;
}
/// <inheritdoc />
public void StopMeasurement()
{
LoggerMeasurement.Info($"Slot:{Slot} - Stop of measurement");
MeasurementState = MeasurementStates.IsWaitingForEndData;
}
/// <inheritdoc />
public void StartMeasurement()
{
Clear();
LoggerMeasurement.Info($"Slot:{Slot} - Start of measurement");
MeasurementState = MeasurementStates.IsWaitingForStartData;
}
/// <summary>
/// Set flag for starting Calibration.
/// The next record is going to set the start values.
/// </summary>
public void StartIntermediateMeasurement()
{
LoggerMeasurement.Info($"Slot:{Slot} - Start of intermediate measurement");
MeasurementState = MeasurementStates.IsWaitingForIntermediateData;
}
/// <inheritdoc />
public void AddData(IMeasurementRecord record)
{
var dataSyncMark = record.GetDataSyncMark();
var channel = record.GetChannel();
if (MeasurementState == MeasurementStates.IsWaitingForStartData)
{
//check if the record is marked as start record
if (dataSyncMark != SyncMarkRecord.SyncStart) return;
//remind start record for the entire measurement duration
DutStartRecord = record;
// set LastIntermediateRecord for initial OverflowCalculation
_lastIntermediateRecord = DutStartRecord;
_last2IntermediateRecord = DutStartRecord;
//set intermediate record
IntermediateRecord = DutStartRecord;
LoggerMeasurement.Info($"Slot:{Slot}, Channel:{channel} - Received start record({DutStartRecord})");
LoggerMeasurement.Info($"Slot:{Slot}, Channel:{channel} - Calibration state set to 'IsRunning'");
MeasurementState = MeasurementStates.IsRunning;
}
else if (MeasurementState == MeasurementStates.IsWaitingForIntermediateData)
{
IntermediateRecord = record;
CheckForOverFlowVolumeAndTime();
LoggerMeasurement.Info($"Slot:{Slot}, Channel:{channel} - Received intermediate record({IntermediateRecord})");
LoggerMeasurement.Info($"Slot:{Slot}, Channel:{channel} - Calibration state set to 'IsRunning'");
MeasurementState = MeasurementStates.IsRunning;
}
else if (MeasurementState == MeasurementStates.IsWaitingForEndData)
{
//can use intermediate records as well
if (dataSyncMark != SyncMarkRecord.SyncEnd && dataSyncMark != SyncMarkRecord.DecodeIntermediate ) return;
DutStopRecord = record;
// set IntermediateRecord for OverflowCalculation
IntermediateRecord = DutStopRecord;
CheckForOverFlowVolumeAndTime();
LoggerMeasurement.Info($"Slot:{Slot}, Channel:{channel} - Received end record({DutStopRecord})");
LoggerMeasurement.Info($"Slot:{Slot}, Channel:{channel} - Calibration state set to 'IsCompleted'");
MeasurementState = MeasurementStates.IsCompleted;
}
}
}
}