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 { /// /// /// Holds all stuff for calibration of a Genesis Meter /// public class BaseMeasurement : IMeasurement { /// /// Record at the start of the measurement /// public IMeasurementRecord DutStartRecord; /// /// Record at the end of the measurement /// public IMeasurementRecord DutStopRecord; /// /// Backup of intermediate record /// private IMeasurementRecord _lastIntermediateRecord; /// /// 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. /// private IMeasurementRecord _last2IntermediateRecord; /// /// Intermediate record for ongoing measurement /// public IMeasurementRecord IntermediateRecord; /// public event EventHandler OnActionStateHasChanged; /// /// Holds the accumulated overflow volume, will work only if /// is being called cyclically. /// The overflow will take place due to the fixed point number format or the /// display resolution setting. /// private Double _accuDutOverflowVolumeCm; /// /// Holds the accumulated overflow time, will work only if /// is being called cyclically. /// The overflow takes place due to the fixed point number format. /// private Double _accuDutOverflowTimeS; /// /// Logger for debug output of measurement /// public readonly ILogger LoggerMeasurement; /// /// Physical slot of the device /// public readonly Int32 Slot; /// /// Identification of the channel /// private readonly Int32 _channel; private MeasurementStates _state; private readonly Type _measurementRecordType; /// /// /// State machine for one measurement (on one meter and one channel) /// /// /// - add State-machine for better handling /// 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; /// /// /// /// /// /// /// 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"); } /// /// /// /// /// public void RequestIntermediateRecordByTimer(Object o, ElapsedEventArgs e) { if (MeasurementState != MeasurementStates.IsRunning) return; MeasurementState = MeasurementStates.IsWaitingForIntermediateData; OnActionStateHasChanged?.Invoke(o, e); } /// /// /// public void RequestIntermediateRecord() { if (MeasurementState != MeasurementStates.IsRunning) return; MeasurementState = MeasurementStates.IsWaitingForIntermediateData; OnActionStateHasChanged?.Invoke(this, EventArgs.Empty); } /// public new Type GetType() { return _measurementRecordType; } /// public Int32 GetChannel() { return _channel; } /// public void Clear() { DutStartRecord = null; DutStopRecord = null; IntermediateRecord = null; _lastIntermediateRecord = null; _last2IntermediateRecord = null; MeasurementState = MeasurementStates.NotStarted; } /// public void Dispose() { Clear(); } /// 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); } /// 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); } /// 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; } /// public MeasurementStates GetMeasurementState() { return _state; } /// public void StopMeasurement() { LoggerMeasurement.Info($"Slot:{Slot} - Stop of measurement"); MeasurementState = MeasurementStates.IsWaitingForEndData; } /// public void StartMeasurement() { Clear(); LoggerMeasurement.Info($"Slot:{Slot} - Start of measurement"); MeasurementState = MeasurementStates.IsWaitingForStartData; } /// /// Set flag for starting Calibration. /// The next record is going to set the start values. /// public void StartIntermediateMeasurement() { LoggerMeasurement.Info($"Slot:{Slot} - Start of intermediate measurement"); MeasurementState = MeasurementStates.IsWaitingForIntermediateData; } /// 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; } } } }