346 lines
14 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|