tbf/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs
Michal Buzik a5a68ec457 Refactor Genesis serial communication handling (ISerialDriver interface compatible with SerialPort):
- Replace method-based `IsOpen()` checks with property-based `IsOpen` usage in `ISerialDriver` and related classes.
- Simplify `Open()` and add `Close()` methods to unify connection management.
- Introduce `InitializeThreeChannelState()` helper in `GenesisSmartReaderTest` for multi-channel state initialization.
- Adjust calculations in `CalculateTimeByChannels()` and `CalculateVolumeByChannels()` for clarity.
- Add new tests for multi-channel processing and validation of updated telegram handling logic.
2026-03-24 10:04:30 +01:00

2025 lines
75 KiB
C#

using System;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Common;
using Config.Entities;
using log4net;
using NHibernate;
using Remotion.Linq.Parsing.Structure.IntermediateModel;
using Sensus.iPerl.NfcHandler;
using TBF.Rig.Generic;
using TBF.Rig.GenericDevices;
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.StreamingProtocol;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer;
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
/// <summary>
/// based on IPerlReader class
/// </summary>
public class GenesisSmartReader : ComponentBase, IDevice, IRegReaderDatastream, ISessionDataMngmnt, IOperation, ISmartReader, IRegReaderSmart
{
private static readonly ILog log = LogManager.GetLogger(typeof(GenesisSmartReader));
private static readonly ILog logStream = LogManager.GetLogger("StreamData");
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
#if TURA_SPECIAL
public const int OptoDataBufferSize = 250000;
#else
public const int OptoDataBufferSize = 40000; /// Opto data count is not limitted by the buffer size
#endif
public const string OptoDataDirectory = "C:\\TBF\\ProcessData";
public const int StartOptoDataCount = OptoDataBufferSize / 2;
public const int EndOptoDataCount = OptoDataBufferSize - StartOptoDataCount;
public const int StartEndFilterSamplesCount2 = 2; //20 /// StartEndFilterSamplesCount = 2 * StartEndFilterSamplesCount2 + 1
public const int FeatureVectorSize = 9;
private OptoHeadTest _optoHeadTest;
public OptoHeadTest OptoHeadTest
{
get
{
if (_optoHeadTest == null)
_optoHeadTest = new OptoHeadTest(this);
return _optoHeadTest;
}
set { _optoHeadTest = value; }
}
readonly GenesisCfg genesisHeadCfg;
string ISmartReader.CommInterface => _commInterface;
public int RfidComPortNr { get { return genesisHeadCfg.RfidComPortNr; } }
public bool CommFailed { get; set; }
public bool Disabled { get; set; }
public int OptoComPortNr { get { return genesisHeadCfg.OptoComPortNr; } }
public int MuxBoardNrOrGroup14 { get { return genesisHeadCfg.MuxBoardNr; } }
public int Group { get { return genesisHeadCfg.Group; } }
public MeterType MeterType { get { return genesisHeadCfg.MeterType; } }
public CommunicationInterface CommInterface { get { return genesisHeadCfg.CommunicationInterface; } }
public int Position
{
get
{
int firstDigitPos = Name.IndexOfAny(new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' });
int position;
return (firstDigitPos < 0) ? 0 : (int.TryParse(Name.Substring(firstDigitPos), out position) ? position : 0);
}
}
public RegisterReaderType RegisterReaderType { get { return RegisterReaderType.DataStream; } }
public double PulsesPerLtr {
get { return 1000.0; }
set { }
}
public double LtrsPerPulse { get { return 1 / PulsesPerLtr; } }
public string QuantityUnits { get; set; }
public double CalibTarget { get { return genesisHeadCfg.ProcParams.CalibTarget; } }
public ushort FactorLimitLo { get { return (ushort)genesisHeadCfg.ProcParams.FactorLimitLo; } }
public ushort FactorLimitHi { get { return (ushort)genesisHeadCfg.ProcParams.FactorLimitHi; } }
public Counting InitFlowDir { get { return (genesisHeadCfg != null && genesisHeadCfg.ProcParams != null) ? genesisHeadCfg.ProcParams.Counting : Counting.Arbitrary; } }
/// Properties set by the Begin and the End form
public string SerialNr
{
get
{
if (ConfigStruct != null)
return ConfigStruct.GetPcbNrString();
else if (simulatedPcbNr != null)
return simulatedPcbNr;
else
return string.Empty;
}
set
{
simulatedPcbNr = value;
}
}
//public bool Disabled;
//public bool CommFailed;
public int ResultCode;
string extraDataPath;
public string ExtraDataPath { get { return extraDataPath; } }
float[] x;
public float[] X { get { return x; } }
private static int iChanelsCount = 3;
private int firstChanel;
/// <summary>
/// Passed to OptoTelegramRaw.UpdateFromString(...)
/// </summary>
double[] volumeRawExtLast;
double[] timestampExtLast;
FlowDirectionDetection flowDirectionDetection;
public bool PositiveCounting;
public ConfigStruct ConfigStruct; /// ConfigStruct of WM obtained or updated by iPerlCommunication
public CalibrationStruct CalibrationStruct; /// CalibrationStruct of WM obtained or updated by iPerlCommunication
public CalibrationStructV4 CalibrationStructV4; /// CalibrationStruct of WM obtained or updated by iPerlCommunication
public Byte OrigTestModeConfig; /// Written to by StartTestingSealedMeter(), read from by EndTestingSealedMeter()
public ushort OrigCalibFactor;
public ushort CalibFactor
{
get
{
return (CalibrationStruct != null) ? CalibrationStruct.Calibration
: ((CalibrationStructV4 != null) ? CalibrationStructV4.Calibration
: (ushort)0);
}
}
public ushort OrigCalibFactorLNA;
public ushort CalibFactorLNA { get { return (CalibrationStructV4 != null) ? CalibrationStructV4.CalibrationLNA : (ushort)0; } }
public double Q2ErrWOCorrection;
public int Q2CorrRL;
public int Q2CorrLR;
public double Diff2Hz8Hz;
public bool Hz2CorrectionDone;
public int Hz2Correction;
public string FWVersion
{
get
{
return (CalibrationStruct != null) ? CalibrationStruct.FWVersionStr()
: ((CalibrationStructV4 != null) ? CalibrationStructV4.FWVersionStr()
: string.Empty);
}
}
/// <summary> Result of the last test used to calculate Q2 correction factors, etc </summary>
public Results.Entities.MeterTestRslt LastTestResult;
public Results.Entities.MeterTestRslt LastTestResult2;
///
/// Required for IRegisterReader interface
///
public int WMPulses { get { return wmPulses; } }
public int WMRefPulses { get { return wmRefPulses; } }
public double BeginWMState { get { return ResolveNaNDouble(beginWMState); } }
double ISmartReader.EndWMState { get; set; }
double ISmartReader.BeginWMState { get; set; }
double ICommonRegReader.EndWMState { get; set; }
double ICommonRegReader.BeginWMState { get; set; }
public double EndWMState { get { return ResolveNaNDouble(endWMState); } }
public double WMVolume { get { return ResolveNaNDouble(wmVolume); } }
public double WMTestTime { get { return ResolveNaNDouble(wmTestTime); } }
string simulatedPcbNr = null;
double ResolveNaNDouble(double d)
{
if (Double.IsNaN(d))
{
return 0.0;
}
else
return d;
}
int wmPulses;
int wmRefPulses;
double beginWMState;
double endWMState;
double wmVolume;
double wmTestTime;
/// <summary>
/// New calibration factor calculated from the original factor (argument)
/// and results of any test(s).
/// Uses also: this.CalibTarget, this.VolumeLtrStart, this.VolumeLtrEnd
/// Side effects: this.OrigCalibFactor, this.PositiveCounting
/// </summary>
/// <param name="adjustTestResult">Test result for calculations</param>
/// <param name="originalCalibrationFactor">Original calibration factor</param>
/// <param name="factorLimitLo">Lower limit for the calibration factor</param>
/// <param name="factorLimitHi">Upper limit for the calibration factor</param>
/// <returns>New calibration factor or 0 (= Out of range)</returns>
public UInt16 CalculateNewCalibFactor(Results.Entities.MeterTestRslt adjustTestResult, UInt16 originalCalibrationFactor, UInt16 factorLimitLo, UInt16 factorLimitHi)
{
double meterVolume = adjustTestResult.VolumeMeter;
double targetVolume = adjustTestResult.VolumeRef * (1.0f + CalibTarget / 100.0f);
OrigCalibFactor = originalCalibrationFactor;
if (meterVolume > 1E-2)
{
PositiveCounting = VolumeLtrEnd > VolumeLtrStart;
UInt16 newFactor = (UInt16)((double)originalCalibrationFactor * targetVolume / meterVolume + 0.5);
log.InfoFormat("Calibration factor: orig={0} new={1} V_iperl={2} V_ref={3} V_target={4}",
originalCalibrationFactor,
newFactor,
meterVolume.ToString("F3"),
adjustTestResult.VolumeRef.ToString("F3"),
targetVolume.ToString("F3"));
if (newFactor < factorLimitLo || newFactor > factorLimitHi) return 0;
return newFactor;
}
else
{
log.ErrorFormat("Calibration factor: orig={0} new={0} (unchanged!) V_iperl={1}",
originalCalibrationFactor,
meterVolume.ToString("F3"));
return originalCalibrationFactor; /// Too small volume in the denominator -> no correction at all
}
}
/// <summary>
/// Q2 correction factor calculated from the last test (Q2).
/// This factor should be used only for R800 meters.
/// </summary>
/// <param name="q2TestResult">A test result from which to calculate the factor</param>
/// <param name="nominalFlow">Nominal flow in m3/h</param>
/// <param name="currentFactor">0 or the current Q2 correction factor when updating the factor</param>
/// <returns>Calculated Q2 correction factor</returns>
public double CalculateQ2CorrectionFactor(Results.Entities.MeterTestRslt currentQ2Result, int currentFactor, double nominalFlow, double errorTarget = 0)
{
double nominalTestFlowLph = Units.ConvertTo(Unit.lph, nominalFlow);
double volumeRefShiftedToTarget = currentQ2Result.VolumeRef * (1.0 + errorTarget / 100.0);
double q2adjErrorShiftedToTarget = Config.Formulas.ErrorFromVolumes(currentQ2Result.VolumeMeter, volumeRefShiftedToTarget);
double A = 16.0 / ScalingFactor(); /// Raw units per ml: DN15=16, DN20=8, DN25=4, DN32=2, DN40=1
const double B = 8.0; /// Raw units per minute, 8
const double C = B * 60.0; /// Raw units per hour, 480
double D = C / A; /// ml correction per hour
double F = D / (nominalTestFlowLph * 10.0); /// Error corrected with 8 Raw Units per minute [%]
double G = F / B; /// Error corrected with 1 Raw Units per minute [%]
/// Do not change the factor for an invalid measurement (q2adjResult.VolumeMeter == 0)
double q2CorrectionFactor = (Math.Abs(currentQ2Result.VolumeMeter) <= float.Epsilon) ? Convert.ToDouble(currentFactor) :
Convert.ToDouble(currentFactor) - (q2adjErrorShiftedToTarget / G) * (volumeRefShiftedToTarget / currentQ2Result.VolumeMeter);
log.WarnFormat("CalculateQ2CorrectionFactor() : Pos={0}, PCB#={1}, Error={2}%, Target={3}%, Current factor={4} New factor={5}",
Name,
SerialNr,
currentQ2Result.Error.ToString("F2"),
errorTarget.ToString("F3"),
currentFactor.ToString("F1"),
q2CorrectionFactor.ToString("F1"));
return q2CorrectionFactor;
}
/// <summary>
/// 2 Hz correction factor calculated from two Q3 tests - done at 2Hz and at 8Hz.
/// This factors should be used only for DN32 and DN40 meters.
/// </summary>
/// <param name="resultAt2Hz">Test result @2Hz from which to calculate the factor</param>
/// <param name="resultAt8Hz">Test result @8Hz from which to calculate the factor</param>
/// <param name="hz2CorrectionFactor">The calculated Q2 correction factor</param>
/// <returns>true = OK, false = failed</returns>
public bool Calculate2HzCorrectionFactor(Results.Entities.MeterTestRslt resultAt2Hz,
Results.Entities.MeterTestRslt resultAt8Hz,
out double diff2Hz8Hz, out int hz2CorrectionFactor)
{
hz2CorrectionFactor = 0;
diff2Hz8Hz = 0;
if ((resultAt2Hz == null) || (resultAt8Hz == null))
{
return false; /// Test result @2Hz and/or @8Hz is missing ==> water meter failed
}
diff2Hz8Hz = resultAt2Hz.Error - resultAt8Hz.Error;
if (Math.Abs(diff2Hz8Hz) > 2.5) return false; /// Difference of errors > 2.5 % ==> water meter failed
hz2CorrectionFactor = -1 * (int)Math.Round(10 * diff2Hz8Hz);
log.WarnFormat("2Hz correction: Pos={0}, PCB#={1}, corrFactor={2}, erro@2Hz={3}%, erro@8Hz={4}%",
Name,
SerialNr,
hz2CorrectionFactor,
resultAt2Hz.Error.ToString("F2"),
resultAt8Hz.Error.ToString("F2"));
return true;
}
/// <summary>
/// Store/update values to be used as a part of the opto-data log file name.
/// Stop data stream processing and saving, if it is enabled.
/// </summary>
/// <param name="test">Currently executed test</param>
/// <param name="repetitionNr">Currently executed repetition number</param>
public void TestIsGoingToStartSoon(Test _test, int _repetitionNr)
{
/// Store/update values to be used as a part of the opto-data log file name
this.test = _test;
this.repetitionNr = _repetitionNr;
if (IsDataStreamProcessing())
{
StopDataStreamProcessing();
/// Dummy '#### start test ####' and '#### end of test ####' marks are added
/// to the raw data file on request of Joern Goege
if (optoDataCount >= 100 && optoDataCount <= optoData.Length &&
optoData[40].Flags == OptoTelegramFlags.OK &&
optoData[optoDataCount - 40].Flags == OptoTelegramFlags.OK)
{
optoData[40].Flags = OptoTelegramFlags.OK_TestStart;
optoData[optoDataCount - 40].Flags = OptoTelegramFlags.OK_TestEnd;
}
DataStreamPostProcessing();
string pcbNr = (ConfigStruct != null) ? ConfigStruct.GetPcbNrString() : "UnknownPcbNr";
string wmPosition = Name.Substring(5); /// WMPosition is extracted from a component name in form 'iPerl#'
if (wmPosition.Length == 1) wmPosition = "0" + wmPosition;
string cycleStartTime = StateMachine.CycleStartTimeStamp.ToString("HH_mm_ss");
///
string relativeDirectory = Path.Combine(StateMachine.CycleStartTimeStamp.ToString("yy"),
StateMachine.CycleStartTimeStamp.ToString("MM"),
StateMachine.CycleStartTimeStamp.ToString("dd"));
string directory = Path.Combine(OptoDataDirectory, relativeDirectory);
string fileName = string.Format("{0}_{1}_{2}_{3}.txt", pcbNr, wmPosition, "WM", cycleStartTime);
if (SaveOptoDataToFile(directory, fileName))
{
extraDataPath = Path.Combine(relativeDirectory, fileName);
}
}
}
///
Test test;
int repetitionNr;
///
/// Indices to determine centers of start / end samples
///
public int TestStartTelegramIx;
public int TestEndTelegramIx;
int endTelegramIdx1;
int endTelegramIdx2;
int endTelegramIdx3;
int currentTelegramIx;
bool startSampleAcquired;
///
/// Timestamp from the opto telegram
///
private double[] lastTimestamp;
private double[] timestampSec;
private double[] timestampSec0;
/// Test start volume for metrology in seconds
public double TimestampSecStart
{
get { return TimeFromSamples(optoData, optoDataCount, TestStartTelegramIx, StartEndFilterSamplesCount2); }
}
/// Test end time for metrology in seconds
public double TimestampSecEnd
{
get { return TimeFromSamples(optoData, optoDataCount, TestEndTelegramIx, StartEndFilterSamplesCount2); }
}
///
public bool NoSamples
{
get { return TimestampSecStart == 0 || TimestampSecEnd == 0 || (TimestampSecEnd - TimestampSecStart) < float.Epsilon; }
}
///
/// Volume of water from the opto telegram
///
private double[] lastVolumeRaw; /// Last read raw volume
private double[] volumeLtr;
private double[] volumeLtr0;
private int channel0 = -1;
private double Average(double[] data)
{
return data.Sum() / data.Length;
}
/// Test start volume for metrology in liters
public double VolumeLtrStart
{
get { return NoSamples ? 0 : VolumeFromSamples(optoData, optoDataCount, TestStartTelegramIx, ScalingFactor(), 0); }
}
/// Test end volume for metrology in liters
public double VolumeLtrEnd
{
get { return NoSamples ? 0 : VolumeFromSamples(optoData, optoDataCount, TestEndTelegramIx, ScalingFactor(), StartEndFilterSamplesCount2); }
}
OptoTelegramRaw[] optoData;
int optoDataCount; /// Real opto deta count, can be larger then optoData.Length
///
OptoTelegramRaw toBeFlushed;
///
/// Opto serial port and worker thread related private variables
///
public ISerialDriver optoSerialPort;
public GenesisSmartReader() { }
public GenesisSmartReader(Generic.IComponentCfg cfg)
: base(cfg)
{
genesisHeadCfg = cfg as GenesisCfg;
}
private readonly Func<ISerialDriver> _serialFactory;
public GenesisSmartReader(Generic.IComponentCfg cfg, Func<ISerialDriver> serialFactory = null)
: base(cfg)
{
genesisHeadCfg = cfg as GenesisCfg;
_serialFactory = serialFactory;
}
public override void Initialize()
{
x = new float[FeatureVectorSize];
flowDirectionDetection = new FlowDirectionDetection();
volumeRawExtLast = new double[iChanelsCount];
timestampExtLast = new double[iChanelsCount];
lastTimestamp = new double[iChanelsCount];
timestampSec = new double[iChanelsCount];
timestampSec0 = new double[iChanelsCount];
lastVolumeRaw = new double[iChanelsCount]; /// Last read raw volume
volumeLtr = new double[iChanelsCount];
volumeLtr0 = new double[iChanelsCount];
/// Allocate memory for opto-data from iPerl
optoData = new OptoTelegramRaw[OptoDataBufferSize];
for (int i = 0; i < OptoDataBufferSize; i++)
{
optoData[i] = new OptoTelegramRaw();
}
toBeFlushed = new OptoTelegramRaw();
dataStreamState = DataStreamState.Flush;
synchronized = false;
synchronized2 = false;
partOfTelegram = string.Empty;
optoSerialPort = null;
if (DebugLevel == DebugMode.Normal)
{
/// Open serial port: 9600 Bd, 8 data bits, 1 stop bit, no parity
/// Check whether head is connected, working
try
{
OpenOptoSerialPort($"COM{genesisHeadCfg.OptoComPortNr}", 115200, Parity.None, 8, StopBits.One, Handshake.None);
CloseOptoSerialPort();
log.FatalFormat($"{Name} initialized: {this}");
}
catch (Exception ex)
{
log.FatalFormat($"{Name} initialization failed: {ex.Message}");
throw new Exception(ex.Message);
}
}
else
{
log.FatalFormat($"{Name} simulated: {this}");
}
}
/// <summary>
/// Clear data related to a specific water meter
/// </summary>
public void StartSession()
{
ResultCode = 0;
Disabled = false;
CommFailed = false;
ConfigStruct = null;
CalibrationStruct = null;
CalibrationStructV4 = null;
OrigTestModeConfig = 0;
LastTestResult = null;
LastTestResult2 = null;
OrigCalibFactor = 0;
OrigCalibFactorLNA = 0;
Q2ErrWOCorrection = 0;
Q2CorrRL = 0;
Q2CorrLR = 0;
simulatedPcbNr = null;
dataStreamState = DataStreamState.Flush;
currentFlowDir = InitFlowDir;
}
public void SaveMark(object mark)
{
/// TODO
}
public void EndSession()
{
StopDataStreamProcessing();
}
Counting currentFlowDir;
///
// public OptoHeadState CheckFlowDirection()
// {
// return (flowDirectionDetection != null) ? flowDirectionDetection.CheckFlowDirection(currentFlowDir, Name) : OptoHeadState.DirNok;
// }
// ///
// public void ChangeFlowDirection()
// {
// switch (InitFlowDir)
// {
// case Counting.Positive:
// currentFlowDir = Counting.Negative;
// break;
//
// case Counting.Negative:
// currentFlowDir = Counting.Positive;
// break;
//
// case Counting.Arbitrary:
// default:
// currentFlowDir = Counting.Arbitrary;
// break;
// }
//
// if (flowDirectionDetection != null) flowDirectionDetection.ClearFifo(); /// Clear FIFO for flow direction detection
// }
public void RunDeviceBefore()
{
if (DebugLevel == DebugMode.Normal)
{
try
{
ReadOptoData(dataStreamState);
}
catch (Exception e)
{
DebugLevel = DebugMode.FailureDuringOperation;
log.FatalFormat("Opto-data serial port failure : {0}", e.Message);
if (e.InnerException != null)
{
log.FatalFormat("InnerMessage : {0}", e.InnerException.Message);
}
}
}
else if (DebugLevel == DebugMode.FailureDuringOperation)
{
}
}
public void RunDeviceAfter() { }
public void StopDevice()
{
try
{
if (optoSerialPort != null)
{
CloseOptoSerialPort();
}
}
catch
{
}
}
public void StopDevice2() { }
/// <summary>
/// Events: Event.ReadRegisterDone, Event.Error
/// </summary>
/// <returns>ReadWaterMeter instance reference casted to IOperaton</returns>
public IOperation ReadRegisterOp()
{
return this;
}
/// <summary>
/// Clear data/counters related to a specific tests
/// </summary>
public void Clear()
{
ResultCode = 0;
volumeLtr = Enumerable.Repeat(Double.NaN, volumeLtr.Length).ToArray();
volumeLtr0 = Enumerable.Repeat(Double.NaN, volumeLtr.Length).ToArray();
timestampSec = Enumerable.Repeat(Double.NaN, volumeLtr.Length).ToArray();
timestampSec0 = Enumerable.Repeat(Double.NaN, volumeLtr.Length).ToArray();
extraDataPath = null;
/// Clear the feature vector
for (int i = 0; i < FeatureVectorSize; i++)
{
x[i] = 0;
}
}
public void TestCompleted()
{
/// TODO: Implement
}
int timeFromStart; /// [s] Time from test start to determine when the test start sample should be taken
/// <summary>
/// Start this operation
/// </summary>
public void Start()
{
lock (this)
{
Clear();
ReadPulses();
StartDataStreamProcessing();
}
}
/// <summary>
/// Run this operation
/// </summary>
/// <returns>eventDone</returns>
public Event Run()
{
lock (this)
{
timeFromStart += StateMachine.Period;
ReadPulses();
if (!startSampleAcquired && (timeFromStart >= 8) && (currentTelegramIx >= 0))
{
/// Take the test start sample
startSampleAcquired = true;
TestStartTelegramIx = currentTelegramIx;
}
else if (startSampleAcquired)
{
/// Shift data in pipelines
TestEndTelegramIx = endTelegramIdx3;
endTelegramIdx3 = endTelegramIdx2;
endTelegramIdx2 = endTelegramIdx1;
endTelegramIdx1 = currentTelegramIx;
}
}
return Event.ReadRegisterDone;
}
/// <summary>
/// Stop this operation
/// </summary>
public void Stop()
{
log.DebugFormat("Flow filtering end, feature vector calculation start: {0:HH:mm:ss.fff}", DateTime.Now);
int startIx;
int endIx;
lock (this)
{
StopDataStreamProcessing();
AddTestStartEndMarksToData(out startIx, out endIx);
}
DataStreamPostProcessing();
// TODO: Enable when calculations completed
//
// float[] offsetV, kOhmsR, kOhmsC, dutFlow, refFlow, flowRatio, magField, emfV;
// x = Common.StatisticalMetrics.Calculate(optoData, optoDataCount, startIx, endIx, true,
// out offsetV, out kOhmsR, out kOhmsC, out dutFlow,
// out refFlow, out flowRatio, out magField, out emfV);
//
// log.DebugFormat("Feature vector calculation end, save opto-file start: {0:HH:mm:ss.fff}", DateTime.Now);
#if ORACLE_DB
if (test.RawDataId + (test.Repeats - repetitionNr) * test.RawDataIdRepetMulti != 0)
{
string relativeDirectory = Path.Combine(StateMachine.CycleStartTimeStamp.ToString("yy"),
StateMachine.CycleStartTimeStamp.ToString("MM"),
StateMachine.CycleStartTimeStamp.ToString("dd"));
string fileName = DetermineExtraDataFileName();
if (SaveOptoDataToFile(Path.Combine(OptoDataDirectory, relativeDirectory), fileName))
{
extraDataPath = Path.Combine(relativeDirectory, fileName);
}
log.WarnFormat("IperlHead.Stop() startIx={0} endIx={1} len={2} raw data file = {3}",
startIx, endIx, optoData.Length, fileName);
}
else
#endif
{
log.WarnFormat("IperlHead.Stop() startIx={0} endIx={1} len={2} no raw data file", startIx, endIx, optoData.Length);
}
if (TestStartTelegramIx == 0 || optoDataCount < 100)
{
ResultCode |= (int)Results.Entities.ResultCode.MissingOptoData;
}
else if (VolumeLtrEnd == VolumeLtrStart)
{
ResultCode |= (int)Results.Entities.ResultCode.OptoDataWithZeroFlow;
}
}
void AddTestStartEndMarksToData(out int startIx, out int endIx)
{
startIx = BufferIdx(TestStartTelegramIx);
if ((startIx > 0) && (startIx < StartOptoDataCount) && (optoData[startIx].Flags == OptoTelegramFlags.OK))
{
optoData[startIx].Flags = OptoTelegramFlags.OK_TestStart;
OptoTelegramRaw.TestStartTimestampDec = optoData[startIx].TimestampDec();
}
else
{
for (int ix = 0; ix < StartOptoDataCount; ix++)
{
if (optoData[ix].Flags == OptoTelegramFlags.OK)
{
/// This is the first correct opto-telegram received
OptoTelegramRaw.TestStartTimestampDec = optoData[ix].TimestampDec();
break;
}
}
}
endIx = BufferIdx(TestEndTelegramIx);
if ((endIx > 0) && (optoData[endIx].Flags == OptoTelegramFlags.OK))
{
optoData[endIx].Flags = OptoTelegramFlags.OK_TestEnd;
}
}
/// <summary>
/// Data stream post processing:
/// Flow from a reference flowmeter is FIR filtered
/// </summary>
void DataStreamPostProcessing()
{
if (optoDataCount <= OptoDataBufferSize)
{
FIRFilterFlow(optoData, 0, optoDataCount - 1);
}
else
{
FIRFilterFlow(optoData, 0, StartOptoDataCount - 1);
FIRFilterFlow(optoData, (optoDataCount - EndOptoDataCount), optoDataCount - 1);
}
}
/// <summary>
/// Determine opto data file name: PCB_AA_BB_HH_MI_SS..txt
/// </summary>
/// <param name="testName">Test name</param>
/// <param name="testRepeats">Test repeats count (>= 1)</param>
/// <param name="repetitionNr">Repetition number (1 .. testRepeats)</param>
/// <returns></returns>
string DetermineExtraDataFileName()
{
///
/// Get required pieces of information
///
string pcbNr = (ConfigStruct != null) ? ConfigStruct.GetPcbNrString() : "UnknownPcbNr";
string wmPosition = Name.Substring(5); /// WMPosition is extracted from a component name in form 'iPerl#'
if (wmPosition.Length == 1) wmPosition = "0" + wmPosition;
string cycleStartTime = StateMachine.CycleStartTimeStamp.ToString("HH_mm_ss");
#if ORACLE_DB
string[] designations = string.IsNullOrEmpty(test.RawDataDesignation) ? new string[0] : test.RawDataDesignation.Split(new char[] { '~' });
int testId = test.RawDataId + (test.Repeats - repetitionNr) * test.RawDataIdRepetMulti;
string designation = string.IsNullOrEmpty(test.RawDataDesignation)
? testId.ToString(testId > 0 ? "D2" : "D1") /// Name is generated from Id
: (designations.Length > repetitionNr - 1) ? designations[repetitionNr - 1] /// Name is from 'RawDataDesignation' parameter
: string.Format("{0}-{1}", designations[0], repetitionNr); /// Name is form test name and repetition nr.
#else
int testId = 0;
string designation = (test.Repeats == 1) ? test.Name : string.Format("{0}-{1}", test.Name, repetitionNr);
#endif
///
/// Return the file name
///
return string.Format("{0}_{1}_{2}_{3}.txt", pcbNr, wmPosition, designation, cycleStartTime);
}
/// <summary>
/// Save opto data to a file.
/// </summary>
bool SaveOptoDataToFile(string directory, string fileName)
{
string fullFileName = Path.Combine(directory, fileName);
log.WarnFormat("Saving {0} raw data to {1}", Name, fullFileName);
try
{
Directory.CreateDirectory(directory);
double scalFact = ScalingFactor();
using (TextWriter optoLogFile = new StreamWriter(fullFileName))
{
if (optoDataCount <= OptoDataBufferSize)
{
/// Telegrams are stored continuously, save them.
optoLogFile.WriteLine(optoData[0].ToString(scalFact, null));
for (int i = 1; i < optoDataCount; i++)
{
optoLogFile.WriteLine(optoData[i].ToString(scalFact, optoData[i - 1]));
}
}
else /// if (optoDataCount > MaxOptoDataCount)
{
/// Buffer overflow
/// First part of the buffer is saved as is
optoLogFile.WriteLine(optoData[0].ToString(scalFact, null));
for (int i = 1; i < StartOptoDataCount; i++)
{
optoLogFile.WriteLine(optoData[i].ToString(scalFact, optoData[i - 1]));
}
optoLogFile.WriteLine(" ...");
/// Second part of the buffer is an overflowed circular buffer
optoLogFile.WriteLine(optoData[BufferIdx(optoDataCount)].ToString(scalFact, null));
for (int i = optoDataCount - EndOptoDataCount + 1; i < optoDataCount; i++)
{
optoLogFile.WriteLine(optoData[BufferIdx(i)].ToString(scalFact, optoData[BufferIdx(i - 1)]));
}
}
log.WarnFormat("{0} opto data successfully saved: {1} lines", Name, optoDataCount);
optoLogFile.Close();
}
return true;
}
catch (Exception exc)
{
File.Delete(fullFileName);
log.ErrorFormat(string.Format("Error writing into file {0}", fullFileName));
log.ErrorFormat(string.Format("Exception message: {0}", exc.Message));
return false;
}
}
void ReadPulses()
{
if (channel0 == -1)
return;
if (channel0 == -2)
{
beginWMState = 0;
endWMState = CalculateVolumeByChannels();
wmVolume = Math.Abs(endWMState - beginWMState);
wmPulses = (int)(wmVolume * (double)PulsesPerLtr + 0.5);
log.Debug("wmPulses = " + wmPulses + "wmVolume = " + wmVolume + "PulsesPerLtr = " + PulsesPerLtr + "");
if (StateMachine.ControlBoardMain != null)
wmRefPulses = StateMachine.ControlBoardMain.RefPulses;
else
wmRefPulses = 0;
wmTestTime = CalculateTimeByChannels();
return;
}
beginWMState = volumeLtr0[channel0];
endWMState = volumeLtr[channel0];
wmVolume = Math.Abs(endWMState - beginWMState);
wmPulses = (int)(wmVolume * (double)PulsesPerLtr + 0.5);
log.Debug("wmPulses = " + wmPulses + "wmVolume = " + wmVolume + "PulsesPerLtr = " + PulsesPerLtr + "");
if (StateMachine.ControlBoardMain != null)
wmRefPulses = StateMachine.ControlBoardMain.RefPulses;
else
wmRefPulses = 0;
wmTestTime = timestampSec[channel0] - timestampSec0[channel0];
}
private double CalculateTimeByChannels()
{
double[] timeDelta = new double[iChanelsCount];
for (int iChanel = 0; iChanel < iChanelsCount; iChanel++)
{
timeDelta[iChanel] = this.timestampSec[iChanel] - this.timestampSec0[iChanel];
}
return Average(timeDelta);
}
private double CalculateVolumeByChannels()
{
double[] volumeDelta = new double[iChanelsCount];
for (int iChanel = 0; iChanel < iChanelsCount; iChanel++)
{
volumeDelta[iChanel] = this.volumeLtr[iChanel] - this.volumeLtr0[iChanel];
}
return Average(volumeDelta);
}
private void OpenOptoSerialPort(
string comPort,
int baudRate,
Parity parity,
int dataBits,
StopBits stopBit,
Handshake handshake,
int openTimeoutMs = 3000)
{
if (DebugLevel == DebugMode.FailureDuringOperation) DebugLevel = DebugMode.Normal;
if (DebugLevel != DebugMode.Normal)
{
optoSerialPort = null;
log.FatalFormat($"{Name} OproPort simulated: {this}");
return;
}
try
{
CloseOptoSerialPort();
log.Info($"Opening serial port {comPort} at {baudRate} baud rate");
if (_serialFactory != null)
{
optoSerialPort = _serialFactory();
optoSerialPort.Open();
if (!optoSerialPort.IsOpen)
throw new InvalidOperationException("Failed to open injected serial driver.");
}
else
{
optoSerialPort = new SerialDriverBuilder()
.WithPort(comPort)
.WithBaudRate(baudRate)
.WithParity(parity)
.WithDataBits(dataBits)
.WithStopBits(stopBit)
.WithHandshake(handshake)
.WithNewLine("\n")
.WithReadTimeout(5000)
.WithWriteTimeout(5000)
.WithDtrEnable(true)
.WithRtsEnable(true)
.WithOpenTimeout(openTimeoutMs)
.WithDiscardInputBufferOnOpen(true)
.WithDiscardOutputBufferOnOpen(true)
.BuildAndOpen();
}
log.FatalFormat($"{Name} OptoPort opened: {this}");
}
catch (Exception ex)
{
log.FatalFormat($"{Name} OptoPort - error opening port: {this}"
+ Environment.NewLine + ex.Message);
throw;
}
}
private void CloseOptoSerialPort()
{
if (optoSerialPort != null)
{
optoSerialPort.Close();
optoSerialPort = null;
log.FatalFormat($"{Name} OptoPort closed: {this}");
}
}
DataStreamState dataStreamState;
/// <summary>
/// Reset counters / indices / time and start processing and saving datastream data
/// </summary>
public void StartDataStreamProcessing()
{
try
{
OpenOptoSerialPort($"COM{genesisHeadCfg.OptoComPortNr}", 115200, Parity.None, 8, StopBits.One, Handshake.None);
}
catch (Exception)
{
}
/// Reset opto-data, etc.
optoDataCount = 0;
timeFromStart = 0;
currentTelegramIx = -1;
startSampleAcquired = false;
TestStartTelegramIx = 0;
endTelegramIdx1 = 0;
endTelegramIdx2 = 0;
endTelegramIdx3 = 0;
TestEndTelegramIx = 0;
if (optoSerialPort != null && optoSerialPort.IsOpen) optoSerialPort.DiscardInBuffer();
if (flowDirectionDetection != null) flowDirectionDetection.ClearFifo(); /// Clear FIFO for flow direction detection
/// Enable opto-data parsing and saving
dataStreamState = DataStreamState.ProcessAndSave;
}
public void SetCommunicationInterface(string commInterface)
{
throw new NotImplementedException();
}
/// <summary>
/// Returns true when processing and saving datastream data is in progress
/// </summary>
bool IsDataStreamProcessing()
{
return dataStreamState == DataStreamState.ProcessAndSave;
}
void ISmartReader.SetRfidInterface()
{
SetRfidInterface();
}
/// <summary>
/// Stop processing and saving datastream data
/// </summary>
public void StopDataStreamProcessing()
{
dataStreamState = DataStreamState.Flush;
CloseOptoSerialPort();
}
///
/// Variables storing the context of serial port data parsing (ReadOptoSerialPort(...))
///
bool synchronized;
bool synchronized2;
string partOfTelegram;
private string _commInterface;
/// <summary>
/// Reads opto-datastream via serial port. Invoked from RunDeviceBefore()
///
/// ...
/// </summary>
/// <param name="optoState">OptoState.Read or OptoState.Flush</param>
void ReadOptoData(DataStreamState optoState)
{
if (optoSerialPort is null) return;
lock (this)
{
try
{
int nrBytes = optoSerialPort.BytesToRead;
if (nrBytes > 0)
{
// This will now wait max 3 seconds (ReadTimeout)
string line = optoSerialPort.ReadLine();
ProcessOptoLine(line, optoState);
}
}
catch (TimeoutException)
{
// ✅ No data received within 3 seconds
log.Debug($"OPTHO {OptoComPortNr} ReadLine timeout (3s) - continuing.");
// Just continue without parsing
}
catch (Exception ex)
{
log.Error($"OPTHO {OptoComPortNr} Read error: {ex.Message}");
}
}
}
public void ProcessOptoLine(string line, DataStreamState optoState)
{
var encoding = optoSerialPort?.Encoding ?? Encoding.ASCII;
byte[] bytes = encoding.GetBytes(line);
log.Debug("ComPort: " + OptoComPortNr + " OPTHO RX ← " + HexFormatter.ToSerialHex(bytes));
StreamingDecoder streamingDecode = new StreamingDecoder(true);
streamingDecode.DecodeMsg(line);
CalibrationRecord data = streamingDecode.DataCalib;
if (optoState == DataStreamState.ProcessAndSave)
{
int bufferIx = BufferIdx(optoDataCount);
if (synchronized)
{
optoData[bufferIx].Counter = optoDataCount;
optoData[bufferIx].SetFlags(OptoTelegramFlags.SyncError);
}
if (data != null)
{
int iChanel = data.Channel - 1;
if (iChanel >= 0 && iChanel < iChanelsCount)
{
optoData[bufferIx].UpdateFromSmart(
data,
optoDataCount,
Convert.ToSingle(Sequences.ProcessData.RefFlow.Val),
ref volumeRawExtLast[iChanel],
ref timestampExtLast[iChanel]);
flowDirectionDetection.WriteToFifo(volumeRawExtLast, timestampExtLast, iChanel);
OptoTelegramReceived(
optoDataCount,
true,
volumeRawExtLast[iChanel],
timestampExtLast[iChanel],
iChanel);
}
}
optoDataCount++;
}
else
{
if (data != null)
{
int iChanel = data.Channel - 1;
if (iChanel >= 0 && iChanel < iChanelsCount)
{
flowDirectionDetection.WriteToFifo(volumeRawExtLast, timestampExtLast, iChanel);
}
}
}
}
void ISmartReader.ResetNfcInterface(bool? nfc_on)
{
ResetNfcInterface(nfc_on);
}
private string _rxBuffer = "";
public string ReadOptoData()
{
if (optoSerialPort is null) return "";
string received = ".";
lock (this)
{
try
{
string line = optoSerialPort.ReadLine();
var encoding = optoSerialPort?.Encoding ?? Encoding.ASCII;
byte[] bytes = encoding.GetBytes(line);
received = HexFormatter.ToSerialHex(bytes);
log.Debug("RX ← " + received);
try
{
StreamingDecoder _streamingDecode = new StreamingDecoder(true);
_streamingDecode.DecodeMsg(line);
CalibrationRecord data = _streamingDecode.DataCalib;
log.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
}
catch (Exception ex)
{
log.Error($"OPTHO {OptoComPortNr} Read error: {ex.Message}");
}
// string line = optoSerialPort.ReadExisting();
//
// if (!string.IsNullOrEmpty(line))
// {
// string visual = line.Replace("\r", "\\r").Replace("\n", "\\n");
//
// bool hasCR = line.Contains('\r');
// bool hasLF = line.Contains('\n');
// bool hasCRLF = line.Contains("\r\n");
//
// log.Debug($"RAW: [{visual}]");
// log.Debug($"CR: {hasCR}, LF: {hasLF}, CRLF: {hasCRLF}");
//
// _rxBuffer += line;
//
// string[] parts = _rxBuffer.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.None);
//
// // incomplete tail
// if (parts.Length > 0)
// {
// _rxBuffer = parts[parts.Length - 1];
// }
// else
// {
// _rxBuffer = "";
// }
// // ~ incomplete tail
//
// for (int i = 0; i < parts.Length - 1; i++)
// {
// string parsed = parts[i];
// log.Debug($"PARSED: [{parsed}]");
//
// byte[] bytes = optoSerialPort.Encoding.GetBytes(parsed);
// received = HexFormatter.ToSerialHex(bytes);
//
// log.Debug("RX ← " + received);
// }
//}
}
catch (TimeoutException)
{
log.Debug($"ReadOptoData() OPTHO {OptoComPortNr} timeout - continuing.");
}
catch (Exception ex)
{
log.Error($"ReadOptoData() OPTHO {OptoComPortNr} Read error: {ex.Message}");
}
}
return received;
}
void ISmartReader.SetNfcInterface()
{
SetNfcInterface();
}
public async Task<string> ReadOptoDataWithTimeoutAsync(int timeoutMs = 5000)
{
if (optoSerialPort == null)
return string.Empty;
var readTask = Task.Run(() =>
{
lock (this)
{
if (optoSerialPort== null) return string.Empty;
try
{
string line = optoSerialPort.ReadLine();
byte[] bytes = optoSerialPort.Encoding.GetBytes(line);
string received = HexFormatter.ToSerialHex(bytes);
log.Debug("RX ← " + received);
return line;
}
catch (TimeoutException)
{
// ✅ No data received within 3 seconds
log.Debug($"ReadOptoData() OPTHO {OptoComPortNr} ReadLine timeout (3s) - continuing.");
// Just continue without parsing
}
catch (Exception ex)
{
log.Error($"ReadOptoData() OPTHO {OptoComPortNr} Read error: {ex.Message}");
}
return string.Empty;
}
});
var completedTask = await Task.WhenAny(readTask, Task.Delay(timeoutMs));
if (completedTask == readTask)
{
return await readTask; // completed successfully
}
log.Debug("ReadOptoData timeout after " + timeoutMs + " ms");
return string.Empty; // timeout case
}
public string ReadOptoDataWithTimeout(int timeoutMs = 5000)
{
try
{
return ReadOptoDataWithTimeoutAsync(timeoutMs)
.GetAwaiter()
.GetResult();
}
catch
{
return string.Empty;
}
}
void OptoTelegramReceived(int currentIx, bool async, double volumeRawExt, double timestampRawExt, int iChanel)
{
currentTelegramIx = currentIx;
lastVolumeRaw[iChanel] = volumeRawExt;
lastTimestamp[iChanel] = timestampRawExt;
if (channel0 == -1)
{
channel0 = iChanel;
}
if (Double.IsNaN(volumeLtr[iChanel]) && Double.IsNaN(iChanel))
{
volumeLtr[iChanel] = lastVolumeRaw[iChanel];
volumeLtr0[iChanel] = volumeLtr[iChanel];
}
else
{
volumeLtr[iChanel] = lastVolumeRaw[iChanel];
}
if (Double.IsNaN(timestampSec[iChanel])&& Double.IsNaN(timestampSec0[iChanel]))
{
timestampSec[iChanel] = lastTimestamp[iChanel];
timestampSec0[iChanel] = timestampSec[iChanel];
}
else
{
timestampSec[iChanel] = lastTimestamp[iChanel];
}
}
/// <summary>
///
/// Called from the state machine when a test is selected and UI needs to be updated.
/// </summary>
public void OnOptoReceived(object sender, OptoReceivedEventArgs args)
{
if (OptoReceivedHandler == null) return;
try { OptoReceivedHandler(sender, args); }
catch (Exception) { }
}
public event EventHandler<OptoReceivedEventArgs> OptoReceivedHandler;
/// <summary>
/// Compares CalibrationStruct.MeterType with iPerlCfg.MeterType.
/// iPerlCfg.MeterType == MeterType.AutoDetect disables type checking
/// CalibrationStruct == null disables type checking ...
/// ... so that failed RFID communication does not cause type verification failure)
/// </summary>
/// <returns>true when type is OK</returns>
public bool VerifyIPerlType()
{
if (genesisHeadCfg.MeterType == MeterType.AutoDetect || CalibrationStruct == null)
{
return true;
}
return genesisHeadCfg.MeterType == CalibrationStruct.MeterType;
}
public static double UnitVolume(VolumeUnits units)
{
switch (units)
{
default:
case VolumeUnits.m3: return Common.Units.ConvertFrom(Common.Unit.m3, 1.0); /// 1 liter
case VolumeUnits.UK_gallon: return Common.Units.ConvertFrom(Common.Unit.UKgal, 1.0); /// 1 imperial gallon
case VolumeUnits.US_gallon: return Common.Units.ConvertFrom(Common.Unit.USgal, 1.0); /// 1 US gallon
}
}
public double ScalingFactor()
{
if ((genesisHeadCfg.MeterType == MeterType.AutoDetect) && (CalibrationStruct != null))
{
return ScalingFactor(CalibrationStruct.MeterType);
}
else if (genesisHeadCfg.MeterType != MeterType.AutoDetect)
{
return ScalingFactor(genesisHeadCfg.MeterType);
}
else
{
return ScalingFactor(MeterType.DN20);
}
}
/// <summary>
/// Scaling factor:
/// 0, 1 (DN15, Coax) . . . . 1
/// 2 (DN20) . . . . . . . . 2
/// 3 (DN25) . . . . . . . . 4
/// 4, 5 (DN26, DN32) . . . . 8
/// 6 (DN40) . . . . . . . . 16
/// </summary>
/// <param name="meterType">MeterType (0..6)</param>
/// <returns>Scaling factor</returns>
public static double ScalingFactor(MeterType meterType)
{
switch (meterType)
{
default:
case MeterType.DN15:
case MeterType.CoaxManifold:
return 1.0;
case MeterType.DN20:
return 2.0;
case MeterType.DN25:
return 4.0;
case MeterType.DN32:
return 8.0;
case MeterType.DN40:
return 16.0;
}
}
/// <summary>
/// Calculate a filtered volume from data stream samples
/// </summary>
/// <param name="unwrappedIx">Samples used in calculation are centered around unwrappedIx</param>
/// <param name="samplesCount2">Count of samples used in calculation is 2 * smaplesCount2 + 1</param>
/// <returns>Filtered volume</returns>
double VolumeFromSamples(OptoTelegramRaw[] optoData, int optoDataCount, int unwrappedIx, double scalingFactor, int samplesCount2 = 0)
{
log.Debug("-- Get VolumeFromSamples() --");
if (samplesCount2 == 0)
{
if (unwrappedIx >= optoDataCount)
{
log.Debug(
$"-- FAILED VolumeFromSamples() - unwrappedIx {unwrappedIx} >= optoDataCount{optoDataCount}--");
return 0;
}
int wrappedIx = BufferIdx(unwrappedIx);
if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
{
log.Debug($"-- Get VolumeFromSamples() - Quit because:{optoData[wrappedIx].Flags}--");
return 0;
}
log.Debug($"Valid data VolumeRawExt: {optoData[wrappedIx].VolumeRawExt}");
return optoData[wrappedIx].VolumeRawExt;
}
//TODO BUMI - do result as average from data - usually 5 samples
if (samplesCount2 < 0) samplesCount2 = 0;
if ((unwrappedIx - samplesCount2) < 0 || (unwrappedIx + samplesCount2) >= optoDataCount) return 0;
double sum = 0;
for (int i = unwrappedIx - samplesCount2; i <= unwrappedIx + samplesCount2; i++)
{
int wrappedIx = BufferIdx(i);
if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
{
return 0;
}
sum += optoData[wrappedIx].VolumeRawExt;
}
return sum / (double)(2 * samplesCount2 + 1);
//return 0.0000625 * scalingFactor * sum / (double)(2 * samplesCount2 + 1);
}
/// <summary>
/// Calculate a filtered time from data stream samples
/// </summary>
/// <param name="unwrappedIx">Samples used in calculation are centered around unwrappedIx</param>
/// <param name="samplesCount2">Count of samples used in calculation is 2 * smaplesCount2 + 1</param>
/// <returns>Filtered time</returns>
double TimeFromSamples(OptoTelegramRaw[] optoData, int optoDataCount, int unwrappedIx, int samplesCount2 = 0)
{
log.Debug("-- Get TimeFromSamples() --");
if (unwrappedIx >= optoDataCount)
{
log.Debug(
$"-- FAILED TimeFromSamples() - unwrappedIx {unwrappedIx} >= optoDataCount{optoDataCount}--");
return 0;
}
int wrappedIx = BufferIdx(unwrappedIx);
if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
{
log.Debug($"-- Get TimeFromSamples() - Quit because:{optoData[wrappedIx].Flags}--");
return 0;
}
log.Debug($"Valid data TimestampExt: {optoData[wrappedIx].TimestampExt}");
return optoData[wrappedIx].TimestampExt;
// if (samplesCount2 < 0) samplesCount2 = 0;
// if ((unwrappedIx - samplesCount2) < 0 || (unwrappedIx + samplesCount2) >= optoDataCount) return 0;
//
// Int64 sum = 0;
// for (int i = unwrappedIx - samplesCount2; i <= unwrappedIx + samplesCount2; i++)
// {
// int wrappedIx = BufferIdx(i);
//
// if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
// optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
// optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
// {
// return 0;
// }
//
// sum += optoData[wrappedIx].TimestampExt;
// }
//
// return sum / (double)(8192 * (2 * samplesCount2 + 1));
}
/// <summary>
/// Filter RefFlow data in an array of OptoTelegramRaw objects by a FIR filter:
///
/// kSize = 5, kSize2 = 2
///
/// i k
/// ---------------------------------------------------------------------------
/// 0 -5 filtered[0] = data[0]
/// 1 -4 filtered[1] = data[1]
/// 2 -3 filtered[2] = data[0]*k[0] + ... + data[4]*k[4]
/// 3 -2 filtered[3] = data[1]*k[0] + ... + data[5]*k[4]
/// 4 -1 filtered[4] = data[2]*k[0] + ... + data[6]*k[4]
/// 5 0 data[0] = filtered[0], filtered[0] = data[3]*k[0] + ... + data[7]*k[4]
/// 6 1 data[1] = filtered[1], filtered[1] = data[4]*k[0] + ... + data[8]*k[4]
/// 7 ...
/// </summary>
/// <param name="optoData">array of OptoTelegramRaw objects</param>
/// <param name="from">Index of the first optoData item to process</param>
/// <param name="to">Index of the last optoData item to process</param>
public static void FIRFilterFlow(OptoTelegramRaw[] optoData, int from, int to)
{
float[] kernel = new float[] { 0.1f, 0.2f, 0.4f, 0.2f, 0.1f };
int kSize = kernel.Length;
int kSize2 = kernel.Length / 2;
float[] filtered = new float[kSize];
for (int i = from; i <= to; i++)
{
if ((i < from + kSize2) || (i > to - kSize2))
{
/// Beginning or end of optoData buffer => Just copy data (=do not filter)
filtered[i % kSize] = optoData[BufferIdx(i)].RefFlow;
}
else
{
/// Make a convolution of optoData and the kernel
float weightedSum = 0;
for (int j = -kSize2; j <= kSize2; j++)
weightedSum += optoData[BufferIdx(i + j)].RefFlow * kernel[j + kSize2];
filtered[i % kSize] = weightedSum;
}
if (i >= from + kSize)
{
/// filtered[] buffer full => copy filtered data to optoData
optoData[BufferIdx(i - kSize)].RefFlow = filtered[i % kSize];
}
}
for (int i = to - kSize + 1; i <= to; i++)
{
if (i >= 0) optoData[BufferIdx(i)].RefFlow = filtered[i % kSize];
}
}
/// <summary>
/// Get index to optoData buffer
/// </summary>
/// <param name="index">Original unwrapped index</param>
/// <returns>Index to the buffer</returns>
public static int BufferIdx(int index)
{
if (index < IperlHead.OptoDataBufferSize)
{
return index;
}
else
{
return IperlHead.StartOptoDataCount + (index - IperlHead.OptoDataBufferSize) % IperlHead.EndOptoDataCount;
}
}
public void WriteBinary(BinaryWriter writer)
{
writer.Write(Disabled);
writer.Write(CommFailed);
writer.Write(ResultCode);
writer.Write(PositiveCounting);
if (ConfigStruct != null)
{
writer.Write(true);
ConfigStruct.WriteBinary(writer);
}
else writer.Write(false);
if (CalibrationStruct != null)
{
writer.Write(true);
CalibrationStruct.WriteBinary(writer);
}
else writer.Write(false);
if (CalibrationStructV4 != null)
{
writer.Write(true);
CalibrationStructV4.WriteBinary(writer);
}
else writer.Write(false);
writer.Write(OrigCalibFactor);
writer.Write(OrigCalibFactorLNA);
writer.Write(Q2ErrWOCorrection);
writer.Write(Q2CorrRL);
writer.Write(Q2CorrLR);
writer.Write(Diff2Hz8Hz);
writer.Write(Hz2CorrectionDone);
writer.Write(Hz2Correction);
if (LastTestResult != null)
{
writer.Write(true);
LastTestResult.WriteBinary(writer);
}
else writer.Write(false);
if (LastTestResult2 != null)
{
writer.Write(true);
LastTestResult2.WriteBinary(writer);
}
else writer.Write(false);
}
public void ReadBinary(BinaryReader reader)
{
Disabled = reader.ReadBoolean();
CommFailed = reader.ReadBoolean();
ResultCode = reader.ReadInt32();
PositiveCounting = reader.ReadBoolean();
if (reader.ReadBoolean()) (ConfigStruct = new ConfigStruct()).ReadBinary(reader);
if (reader.ReadBoolean()) (CalibrationStruct = new CalibrationStruct()).ReadBinary(reader);
if (reader.ReadBoolean()) (CalibrationStructV4 = new CalibrationStructV4()).ReadBinary(reader);
OrigCalibFactor = reader.ReadUInt16();
OrigCalibFactorLNA = reader.ReadUInt16();
Q2ErrWOCorrection = reader.ReadDouble();
Q2CorrRL = reader.ReadInt32();
Q2CorrLR = reader.ReadInt32();
Diff2Hz8Hz = reader.ReadDouble();
Hz2CorrectionDone = reader.ReadBoolean();
Hz2Correction = reader.ReadInt32();
if (reader.ReadBoolean()) (LastTestResult = new Results.Entities.MeterTestRslt()).ReadBinary(reader, null);
if (reader.ReadBoolean()) (LastTestResult2 = new Results.Entities.MeterTestRslt()).ReadBinary(reader, null);
}
internal void ResetNfcInterface(bool? nfc_on = null)
{
if (genesisHeadCfg.HeadCommunicationComPortNr == 0) return;
SERIAL_Driver _SERIAL_Driver_Head_Config = new SERIAL_Driver();
_SERIAL_Driver_Head_Config.OpenConnection($"COM{genesisHeadCfg.HeadCommunicationComPortNr}", 9600, 8, Parity.None, StopBits.One);
NFCHeadConfig _NFCHead_Config = new NFCHeadConfig(_SERIAL_Driver_Head_Config);
if (nfc_on == null || nfc_on == false) _NFCHead_Config.NFCHeadConfig_SetInterface(false); // set RFID interface
if (nfc_on == null || nfc_on == true ) _NFCHead_Config.NFCHeadConfig_SetInterface(true); // set NFC interface
_SERIAL_Driver_Head_Config.Close();
_SERIAL_Driver_Head_Config.Dispose();
}
internal void SetNfcInterface()
{
ResetNfcInterface(true);
}
internal void SetRfidInterface()
{
ResetNfcInterface(false);
}
internal void SetCommunicationInterface(CommunicationInterface commInterface)
{
//using (ISession session = TBF.DB.ConfigDBSessionFactory.OpenSession())
// Replace the problematic line with the following code to fix the error:
using (ISession session = TBF.DB.SessionFactories[(int)DBKind.Config].OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
try
{
var cmpntEntities = session.QueryOver<Component>()
.OrderBy(x => x.ItemNr).Asc
.List<Component>();
var cmpnt = cmpntEntities.Where(x => x.Name == Name).First();
if (cmpnt != null)
{
XDocument doc = XDocument.Parse(cmpnt.Parameters);
if (doc != null)
{
XElement element = doc.Root.Element("CommunicationInterface");
if (element != null)
{
element.Value = commInterface.ToString();
cmpnt.Parameters = doc.ToString();
session.SaveOrUpdate(cmpnt);
tx.Commit();
log.FatalFormat($"Set CommunicationInterface {Name} to {commInterface.ToString()}");
}
}
}
}
catch (Exception ex)
{
if (tx != null) tx.Rollback();
log.FatalFormat($"Set CommunicationInterface {Name} error: {ex.Message}");
}
}
}
public IOperation ReadDatastreamOp()
{
return this;
}
public async Task<string> DataEntry_ReadSerialNumber()
{
log.Debug("called DataEntry_ReadSerialNumber()");
if (!string.IsNullOrEmpty(SerialNr)) return SerialNr;
//need to find serial number
SerialNr = await DataEntry_ReadSerialNumberAsync();
return SerialNr;
}
public Task<double> DataEntry_ReadBeginVolume()
{
log.Debug("called DataEntry_ReadBeginVolumer()");
Task<double> readedVolume = DataEntry_BeginVolumeAsync();
return readedVolume;
}
public Task<double> DataEntry_ReadEndVolume()
{
log.Debug("called DataEntry_ReadBeginVolumer()");
Task<double> readedVolume = DataEntry_EndVolumeAsync();
return readedVolume;
}
public async Task<double> DataEntry_EndVolumeAsync()
{
if (optoSerialPort == null || !optoSerialPort.IsOpen)
{
StartDataStreamProcessing();
if (optoSerialPort == null || !optoSerialPort.IsOpen)
{
log.Error($"optoSerialPort COM: {this.OptoComPortNr} is not open - DataEntry_EndVolumeAsync()");
return Double.NaN;
}
}
return await Task.Run(() =>
{
log.Debug($"Try get End Volume! COM: {this.OptoComPortNr}");
volumeLtr[channel0] = Double.NaN;
int counter = 0;
while (Double.IsNaN(volumeLtr[channel0]) && counter < 2)
{
counter++;
try
{
string readOptoDataWithTimeout = ReadOptoDataWithTimeout(2000);
if (!string.IsNullOrEmpty(readOptoDataWithTimeout))
{
try
{
StreamingDecoder _streamingDecode = new StreamingDecoder(true);
_streamingDecode.DecodeMsg(readOptoDataWithTimeout);
CalibrationRecord data = _streamingDecode.DataCalib;
log.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
volumeLtr[channel0] = data.VolumeCm * 1000;
break;
}
catch (Exception ex)
{
log.Warn($"Parsing DiagnosticLedState4Data: {ex.Message}");
}
}
}
catch (Exception ex)
{
break;
}
}
log.Debug($"Try get End Volume! COM: {this.OptoComPortNr}, Volume: {volumeLtr}");
if (optoSerialPort != null && optoSerialPort.IsOpen) CloseOptoSerialPort();
if (!Double.IsNaN(volumeLtr[channel0]))
{
endWMState = volumeLtr[channel0];
if (!Double.IsNaN(beginWMState) && !Double.IsNaN(endWMState))
{
//Solve roll over
if (endWMState < beginWMState)
{
log.Debug($"Solve roll over! endWMState: {endWMState} < beginWMState: {beginWMState}");
const double VOL_RANGE_LITERS = 16777216.0 * 0.00025; // 4,194.304 l
endWMState += VOL_RANGE_LITERS;
volumeLtr[channel0] = endWMState;
ReadPulses();
log.Debug($"Solve roll over! Upgraded endWMState: {endWMState}, beginWMState: {beginWMState}");
}
}
return endWMState;
}
//}
log.Warn("Default NaN value returned! Data Opto stream reading failed!");
return Double.NaN;
}).ConfigureAwait(false);
}
public async Task<double> DataEntry_BeginVolumeAsync()
{
if (ConfigStruct == null)
{
log.Debug("ConfigStruct is null - created new in ReadSerialNr()");
ConfigStruct = new ConfigStruct();
}
return await Task.Run(() =>
{
log.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}");
Start();
volumeLtr0[channel0] = Double.NaN;
int counter = 0;
while (Double.IsNaN(volumeLtr0[channel0]) && counter < 10)
{
counter++;
try
{
string readOptoDataWithTimeout = ReadOptoDataWithTimeout(5000);
if (!string.IsNullOrEmpty(readOptoDataWithTimeout))
{
try
{
StreamingDecoder _streamingDecode = new StreamingDecoder(true);
_streamingDecode.DecodeMsg(readOptoDataWithTimeout);
CalibrationRecord data = _streamingDecode.DataCalib;
log.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
volumeLtr0[channel0] = data.VolumeCm * 1000;
break;
}
catch (Exception ex)
{
log.Warn($"Parsing DiagnosticLedState4Data: {ex.Message}");
}
}
}
catch (Exception ex)
{
break;
}
}
log.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}, Volume: {volumeLtr0}");
if (optoSerialPort != null && optoSerialPort.IsOpen) CloseOptoSerialPort();
if (!Double.IsNaN(volumeLtr0[channel0]))
{
beginWMState = volumeLtr0[channel0];
ReadPulses();
return beginWMState;
}
//}
log.Warn("Default NaN value returned! Data Opto stream reading failed!");
return Double.NaN;
}).ConfigureAwait(false);
}
public async Task<string> DataEntry_ReadSerialNumberAsync()
{
if (!string.IsNullOrEmpty(SerialNr))
return SerialNr;
if (ConfigStruct == null)
{
log.Debug("ConfigStruct is null - created new in ReadSerialNr()");
ConfigStruct = new ConfigStruct();
}
if (CommFailed || ConfigStruct == null)
return CommFailed.ToString();
return await Task.Run(() =>
{
log.Debug($"Try get ReadSerialNr! COM: {this.RfidComPortNr}");
if (OptoHeadTest.ReadSerialNr())
{
SerialNr = this.ConfigStruct.PCBNumberString;
log.Debug("ReadSerialNr successful");
}
//optoHeadTest.CloseConnection();
return SerialNr;
});
}
}
}