diff --git a/TBF/Properties/AssemblyInfo.cs b/TBF/Properties/AssemblyInfo.cs
index 3533ba3a4..4891e220b 100644
--- a/TBF/Properties/AssemblyInfo.cs
+++ b/TBF/Properties/AssemblyInfo.cs
@@ -29,5 +29,5 @@ using System.Runtime.InteropServices;
// Build Number
// Revision
//
-[assembly: AssemblyVersion("3.9.3019.1")]
-[assembly: AssemblyFileVersion("3.9.3019.1")]
+[assembly: AssemblyVersion("3.9.3030.1")]
+[assembly: AssemblyFileVersion("3.9.3030.1")]
diff --git a/TBF/Rig/BuiltIn/PumpTandem/Pump.cs b/TBF/Rig/BuiltIn/PumpTandem/Pump.cs
index a6a5ab259..4e7377466 100644
--- a/TBF/Rig/BuiltIn/PumpTandem/Pump.cs
+++ b/TBF/Rig/BuiltIn/PumpTandem/Pump.cs
@@ -111,6 +111,7 @@ namespace TBF.Rig.BuiltIn.PumpTandem
public void TurnOff()
{
+ log.DebugFormat("{0}.TurnOff()", Name);
if ((pumpFm1 != null) && (pumpFm2 != null))
{
pumpFm1.TurnOff();
@@ -120,6 +121,7 @@ namespace TBF.Rig.BuiltIn.PumpTandem
{
// TODO
}
+ log.DebugFormat("DONE {0}.TurnOff()", Name);
}
diff --git a/TBF/Rig/Danfoss/VLT2800/Pump.cs b/TBF/Rig/Danfoss/VLT2800/Pump.cs
index 093b4aa8d..7dd59e327 100644
--- a/TBF/Rig/Danfoss/VLT2800/Pump.cs
+++ b/TBF/Rig/Danfoss/VLT2800/Pump.cs
@@ -332,6 +332,8 @@ namespace TBF.Rig.Danfoss.VLT2800
Telegram.UpdateTelegramChecksum(msg);
}
SendData(msg);
+
+ log.DebugFormat("DONE {0}.TurnOff()", Name);
}
diff --git a/TBF/Rig/Modbus/PumpFM/DanfossVLT/Pump.cs b/TBF/Rig/Modbus/PumpFM/DanfossVLT/Pump.cs
index e1b932283..8512ff737 100644
--- a/TBF/Rig/Modbus/PumpFM/DanfossVLT/Pump.cs
+++ b/TBF/Rig/Modbus/PumpFM/DanfossVLT/Pump.cs
@@ -264,6 +264,8 @@ namespace TBF.Rig.Modbus.PumpFM.DanfossVLT
modbus.SendMessage(msg, Name);
}
+
+ log.DebugFormat("DONE {0}.TurnOff()", Name);
}
diff --git a/TBF/Rig/Modbus/PumpFM/Grundfoss/Pump.cs b/TBF/Rig/Modbus/PumpFM/Grundfoss/Pump.cs
index ba8792c0b..f2300603a 100644
--- a/TBF/Rig/Modbus/PumpFM/Grundfoss/Pump.cs
+++ b/TBF/Rig/Modbus/PumpFM/Grundfoss/Pump.cs
@@ -416,6 +416,8 @@ namespace TBF.Rig.Modbus.PumpFM.Grundfoss
// --- DIAGNOSTIKA ---
LiveLogDiag.Log1("GF TurnOff msg = " + BitConverter.ToString(msg));
}
+
+ log.DebugFormat("DONE {0}.TurnOff()", Name);
}
///
diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRaw.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRaw.cs
index 54b952346..f8dc924ff 100644
--- a/TBF/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRaw.cs
+++ b/TBF/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRaw.cs
@@ -6,7 +6,7 @@ using System;
using System.Globalization;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.StreamingProtocol;
-
+using Xylem.Common.Metrology.Measurements;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
@@ -25,6 +25,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
public static readonly int Length = 42;
private static CultureInfo culture;
+ public MeasurementRecord data;
///
/// Strobed value
@@ -124,11 +125,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
}
- // -------- TIMESTAMP (seconds) --------
- private const double TS_RANGE = StreamingDecoder.CpuTimeOverflowS;
- // -------- VOLUME (liters) --------
- private const double VOL_RANGE = StreamingDecoder.DefaultAccuDutOverflowVolumeCm * 1000;
///
/// update data by CalibrationRecord
@@ -149,7 +146,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
if (data == null)
throw new ArgumentNullException(nameof(data));
- UpdateData( data.Channel, data.VolumeCm, data.TimeS, counter, refFlow, ref volumeRawExtLast, ref timestampExtLast);
+ this.data = data;
+ UpdateData( data.Channel, data.VolumeCm, data.OverflowVolumeCm, data.TimeS, data.OverflowTimeS, counter, refFlow, ref volumeRawExtLast, ref timestampExtLast);
}
///
@@ -170,8 +168,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
{
if (data == null)
throw new ArgumentNullException(nameof(data));
-
- UpdateData( data.Channel, data.VolumeCm, data.TimeS, counter, refFlow, ref volumeRawExtLast, ref timestampExtLast);
+ this.data = data;
+ UpdateData( data.Channel, data.VolumeCm, data.OverflowVolumeCm, data.TimeS, data.OverflowTimeS, counter, refFlow, ref volumeRawExtLast, ref timestampExtLast);
}
///
@@ -187,7 +185,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
private void UpdateData(
int channel,
double volumeCm,
+ double OverflowVolumeCm,
double timeS,
+ double OverflowTimeS,
int counter,
float refFlow,
ref double volumeRawExtLast,
@@ -200,29 +200,30 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
Flags = OptoTelegramFlags.OK;
FlowRaw = 0;
- VolumeRaw = volumeCm * 1000.0; // liters
+ double nV = NormalizeByOverflow(volumeCm, OverflowVolumeCm);
+ VolumeRaw = nV * 1000.0; // liters
CheckSum = 0;
Impedance = 0;
EmfRaw = 0;
MagneticFieldRaw = 0;
- double ts = NormalizeTimestamp(timeS);
+ double ts = NormalizeByOverflow(timeS, OverflowTimeS);
Timestamp = ts;
- VolumeRawExt = UnwrapVolume(VolumeRaw, ref volumeRawExtLast);
- TimestampExt = UnwrapTimestamp(ts, ref timestampExtLast);
+ VolumeRawExt = UnwrapVolume(VolumeRaw,OverflowVolumeCm * 1000, ref volumeRawExtLast);
+ TimestampExt = UnwrapTimestamp(ts,OverflowTimeS, ref timestampExtLast);
}
- private static double NormalizeTimestamp(double timeS)
+ private static double NormalizeByOverflow(double value, double overfValue = 0)
{
- double ts = timeS % TS_RANGE;
- if (ts < 0)
- ts += TS_RANGE;
+ double nValue = value % overfValue;
+ if (nValue < 0)
+ nValue += overfValue;
- return ts;
+ return nValue;
}
- private static double UnwrapVolume(double currentVolume, ref double volumeRawExtLast)
+ private static double UnwrapVolume(double currentVolume, double overfValue, ref double volumeRawExtLast)
{
double result;
@@ -232,16 +233,25 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
}
else
{
- result = currentVolume < volumeRawExtLast
- ? currentVolume + VOL_RANGE
- : currentVolume;
+ double lastMod = volumeRawExtLast % overfValue;
+ if (lastMod < 0)
+ lastMod += overfValue;
+
+ double delta = currentVolume - lastMod;
+
+ if (delta < -overfValue / 2.0)
+ delta += overfValue;
+ else if (delta > overfValue / 2.0)
+ delta -= overfValue;
+
+ result = volumeRawExtLast + delta;
}
volumeRawExtLast = result;
return result;
}
- private static double UnwrapTimestamp(double currentTimestamp, ref double timestampExtLast)
+ private static double UnwrapTimestamp(double currentTimestamp,double overfValue, ref double timestampExtLast)
{
double result;
@@ -251,16 +261,16 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
}
else
{
- double lastMod = timestampExtLast % TS_RANGE;
+ double lastMod = timestampExtLast % overfValue;
if (lastMod < 0)
- lastMod += TS_RANGE;
+ lastMod += overfValue;
double delta = currentTimestamp - lastMod;
- if (delta < -TS_RANGE / 2.0)
- delta += TS_RANGE;
- else if (delta > TS_RANGE / 2.0)
- delta -= TS_RANGE;
+ if (delta < -overfValue / 2.0)
+ delta += overfValue;
+ else if (delta > overfValue / 2.0)
+ delta -= overfValue;
result = timestampExtLast + delta;
}
@@ -367,5 +377,15 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
this.CheckSum = optoTelegramRaw.CheckSum;
this.iChannel = optoTelegramRaw.iChannel;
}
+
+ public string rawDataToString()
+ {
+ if (data != null)
+ {
+ return string.Format("Channel: {0}, VolumeCm:{1}, OverflowVolumeCm:{2}, TimeS: {3}, OverflowTimeS:{4} ",
+ data.Channel, data.VolumeCm, data.OverflowVolumeCm, data.TimeS, data.OverflowTimeS);
+ }
+ return string.Empty;
+ }
}
}
diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs
index fbeeddcd5..ab81d0415 100644
--- a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs
+++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs
@@ -1,15 +1,18 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Common;
using Config.Entities;
using log4net;
using NHibernate;
+using NHibernate.Hql.Ast;
using Sensus.iPerl.NfcHandler;
using TBF.Rig.Generic;
using TBF.Rig.GenericDevices;
@@ -36,16 +39,30 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
public override string ToString()
{
- string cfgText;
+ try
+ {
+ string cfgText;
- if (genesisHeadCfg != null)
- cfgText = genesisHeadCfg.ToString(-1);
- else if (Cfg != null)
- cfgText = Cfg.ToString();
- else
- cfgText = "";
+ try
+ {
+ if (genesisHeadCfg != null)
+ cfgText = genesisHeadCfg.ToString(-1);
+ else if (Cfg != null)
+ cfgText = Cfg.ToString();
+ else
+ cfgText = "";
+ }
+ catch (Exception ex)
+ {
+ cfgText = $"";
+ }
- return string.Format("{0}({1})", ClassName, cfgText);
+ return $"{GetType().Name}({cfgText})";
+ }
+ catch
+ {
+ return GetType().Name;
+ }
}
#if TURA_SPECIAL
@@ -477,6 +494,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
/// Currently executed repetition number
public void TestIsGoingToStartSoon(Test _test, int _repetitionNr)
{
+
+ log.Debug($"TestIsGoingToStartSoon({_test.Name}, {_repetitionNr})");
/// Store/update values to be used as a part of the opto-data log file name
this.test = _test;
this.repetitionNr = _repetitionNr;
@@ -514,6 +533,21 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
extraDataPath = Path.Combine(relativeDirectory, fileName);
}
}
+
+
+ try
+ {
+ log.DebugFormat("TestIsGoingToStartSoon - OpenOptoSerialPortIfNotInit called");
+ OpenOptoSerialPortIfNotInit ($"COM{genesisHeadCfg.OptoComPortNr}",
+ 115200,
+ Parity.None,
+ 8,
+ StopBits.One,
+ Handshake.None);
+ }
+ catch (Exception)
+ {
+ }
}
///
@@ -626,8 +660,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
get
{
- if (NoSamples) return 0;
- return AverageNullable(VolumeStartPerChannel(optoData, optoDataCount, TestStartTelegramIx));
+ return VolumeLtrStartRaw;
}
}
@@ -636,13 +669,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
get
{
- if (NoSamples) return 0;
- return AverageNullable(
- VolumeEndPerChannel(
- optoData,
- optoDataCount,
- TestEndTelegramIx,
- 2 * StartEndFilterSamplesCount2 + 1));
+ return VolumeLtrEndRaw;
}
}
@@ -659,6 +686,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
/// Opto serial port and worker thread related private variables
///
public ISerialDriver optoSerialPort;
+ private volatile bool startDataProcessing = false;
public GenesisSmartReader()
@@ -683,6 +711,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
public override void Initialize()
{
+ log.DebugFormat("Initialize() called");
x = new float[FeatureVectorSize];
flowDirectionDetection = new FlowDirectionDetection();
@@ -711,8 +740,10 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
synchronized2 = false;
partOfTelegram = string.Empty;
optoSerialPort = null;
+ startDataProcessing = false;
+
-
+ log.DebugFormat($"Initialize - OpenOptoSerialPort: {genesisHeadCfg.OptoComPortNr}");
if (DebugLevel == DebugMode.Normal)
{
/// Open serial port: 9600 Bd, 8 data bits, 1 stop bit, no parity
@@ -721,9 +752,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
OpenOptoSerialPort($"COM{genesisHeadCfg.OptoComPortNr}", 115200, Parity.None, 8, StopBits.One,
Handshake.None);
+ ResetDataBuffer();
CloseOptoSerialPort();
log.FatalFormat($"{Name} initialized: {this}");
- ResetDataBuffer();
}
catch (Exception ex)
{
@@ -735,6 +766,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
log.FatalFormat($"{Name} simulated: {this}");
}
+
}
///
@@ -742,6 +774,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
///
public void StartSession()
{
+ log.DebugFormat("StartSession() called");
ResultCode = 0;
Disabled = false;
@@ -769,6 +802,30 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
currentFlowDir = InitFlowDir;
ResetBlockCountersAndState();
+
+ startDataProcessing = false;
+ //Start connection
+ log.DebugFormat($"StartSession - OpenOptoSerialPort: {genesisHeadCfg.OptoComPortNr}");
+ 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);
+ log.FatalFormat($"{Name} StartSession - OpenOptoSerialPort: {genesisHeadCfg.OptoComPortNr}");
+ }
+ catch (Exception ex)
+ {
+ log.FatalFormat($"{Name} initialization failed: {ex}");
+ throw;
+ }
+ }
+ else
+ {
+ log.FatalFormat($"{Name} simulated: {this}");
+ }
}
public void SaveMark(object mark)
@@ -814,6 +871,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
public void RunDeviceBefore()
{
+ //log.DebugFormat("RunDeviceBefore() called");
if (DebugLevel == DebugMode.Normal)
{
try
@@ -842,6 +900,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
public void StopDevice()
{
+ log.DebugFormat("StopDevice() called");
try
{
if (optoSerialPort != null)
@@ -903,6 +962,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
///
public void Start()
{
+ log.DebugFormat("Start: {0:HH:mm:ss.fff}", DateTime.Now);
lock (this)
{
Clear();
@@ -920,22 +980,28 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
lock (this)
{
+ log.DebugFormat("Run: {0:HH:mm:ss.fff}", DateTime.Now);
timeFromStart += StateMachine.Period;
ReadPulses();
- if (!startSampleAcquired && (timeFromStart >= 8) && (currentTelegramIx >= 0))
+ // Start sample after 1 second
+ if (!startSampleAcquired && (timeFromStart >= 1) && (currentTelegramIx >= 0))
{
- /// Take the test start sample
startSampleAcquired = true;
TestStartTelegramIx = currentTelegramIx;
+
+ // initialize end at the same point
+ TestEndTelegramIx = currentTelegramIx;
+
+ log.DebugFormat(
+ "Test start acquired at ix={0}, timeFromStart={1}",
+ TestStartTelegramIx,
+ timeFromStart);
}
- else if (startSampleAcquired)
+ else if (startSampleAcquired && currentTelegramIx >= 0)
{
- /// Shift data in pipelines
- TestEndTelegramIx = endTelegramIdx3;
- endTelegramIdx3 = endTelegramIdx2;
- endTelegramIdx2 = endTelegramIdx1;
- endTelegramIdx1 = currentTelegramIx;
+ // always keep latest telegram as end
+ TestEndTelegramIx = currentTelegramIx;
}
}
@@ -947,51 +1013,40 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
///
public void Stop()
{
- log.DebugFormat("Flow filtering end, feature vector calculation start: {0:HH:mm:ss.fff}", DateTime.Now);
+ log.DebugFormat("Stop: {0:HH:mm:ss.fff}", DateTime.Now);
int startIx;
int endIx;
lock (this)
{
- StopDataStreamProcessing();
+ // 1. stop new reads from serial
+ StopOptoReadLoop();
+
+ // 2. clear data stream processing
+ ClearReceivedLines();//Empty othrs
+ // 2. process everything already queued
+ //DrainQueuedLines();
+
+ ResetDataBuffer();
+
+ // 3. now it is safe to close the port
+ CloseOptoSerialPort();
+
+ // 4. mark state as stopped
+ dataStreamState = DataStreamState.Flush;
+ startDataProcessing = false;
+
+ // 5. add marks using fully processed data
AddTestStartEndMarksToData(out startIx, out endIx);
}
DataStreamPostProcessing();
- PrepareCalculatedChannelData();
- ResetDataBuffer();
+
+
- // 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);
- }
+ log.WarnFormat("Genesis.Stop() startIx={0} endIx={1} len={2} no raw data file",
+ startIx, endIx, optoData.Length);
if (TestStartTelegramIx == 0 || optoDataCount < 100)
{
@@ -1003,6 +1058,73 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
}
}
+
+ private volatile bool _stopQueueData = false;
+
+ public bool StopQueueData
+ {
+ get => _stopQueueData;
+ set
+ {
+ if (value == true)
+ {
+ log.DebugFormat("StopQueueData: --true--");
+ }
+
+ _stopQueueData = value;
+ }
+ }
+
+ private void ClearReceivedLines()
+ {
+ StopQueueData = true;
+ try
+ {
+ while (_receivedLines.TryDequeue(out _)) { }
+ }
+ finally
+ {
+ //StopQueueData = false;
+ }
+ }
+
+ private void DrainQueuedLines()
+ {
+ StopQueueData = true;
+ try
+ {
+ while (_receivedLines.TryDequeue(out var item))
+ {
+ try
+ {
+ DateTime timestamp = item.Timestamp;
+ string line = item.Line;
+
+ log.DebugFormat(
+ "DrainQueuedLines() real incoming TimeStamp: {0} processing queued line: {1}",
+ timestamp.ToString("HH:mm:ss.fff"),
+ line);
+
+ bool blockCompleted;
+ ProcessOptoLine(line, dataStreamState, out blockCompleted);
+
+ if (blockCompleted)
+ {
+ log.Debug("DrainQueuedLines() completed flow block detected.");
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error($"DrainQueuedLines() failed: {ex.Message}");
+ }
+ }
+ }
+ finally
+ {
+ StopQueueData = false;
+ }
+ }
+
void AddTestStartEndMarksToData(out int startIx, out int endIx)
{
startIx = BufferIdx(TestStartTelegramIx);
@@ -1037,15 +1159,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
///
void DataStreamPostProcessing()
{
- if (optoDataCount <= OptoDataBufferSize)
- {
- FIRFilterFlow(optoData, 0, optoDataCount - 1);
- }
- else
- {
- FIRFilterFlow(optoData, 0, StartOptoDataCount - 1);
- FIRFilterFlow(optoData, (optoDataCount - EndOptoDataCount), optoDataCount - 1);
- }
+ PrepareCalculatedChannelData();
}
///
@@ -1201,6 +1315,21 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return Average(volumeDelta);
}
+ private void OpenOptoSerialPortIfNotInit(
+ string comPort,
+ int baudRate,
+ Parity parity,
+ int dataBits,
+ StopBits stopBit,
+ Handshake handshake,
+ int openTimeoutMs = 3000)
+ {
+ if (optoSerialPort == null)
+ {
+ OpenOptoSerialPort(comPort, baudRate, parity, dataBits, stopBit, handshake, openTimeoutMs);
+ }
+ }
+
private void OpenOptoSerialPort(
string comPort,
int baudRate,
@@ -1263,11 +1392,31 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
private void CloseOptoSerialPort()
{
- if (optoSerialPort != null)
+ // 1. Stop background reading FIRST
+ StopOptoReadLoop();
+
+ // 2. Synchronize with ReadLine()
+ lock (_serialReadSync)
{
- optoSerialPort.Close();
- optoSerialPort = null;
- log.FatalFormat($"{Name} OptoPort closed: {this}");
+ if (optoSerialPort != null)
+ {
+ try
+ {
+ if (optoSerialPort.IsOpen)
+ {
+ optoSerialPort.Close();
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error($"Error closing opto port: {ex.Message}");
+ }
+ finally
+ {
+ optoSerialPort = null;
+ log.FatalFormat($"{Name} OptoPort closed: {this}");
+ }
+ }
}
}
@@ -1278,9 +1427,16 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
///
public void StartDataStreamProcessing()
{
+ log.DebugFormat("StartDataStreamProcessing({0}) called", DateTime.Now.ToString("HH:mm:ss.fff"));
try
{
- OpenOptoSerialPort($"COM{genesisHeadCfg.OptoComPortNr}", 115200, Parity.None, 8, StopBits.One,
+ log.DebugFormat("OpenOptoSerialPortIfNotInit StartDataStreamProcessing() called");
+ OpenOptoSerialPortIfNotInit(
+ $"COM{genesisHeadCfg.OptoComPortNr}",
+ 115200,
+ Parity.None,
+ 8,
+ StopBits.One,
Handshake.None);
}
catch (Exception)
@@ -1292,20 +1448,51 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
timeFromStart = 0;
currentTelegramIx = -1;
startSampleAcquired = false;
- TestStartTelegramIx = 0;
- endTelegramIdx1 = 0;
- endTelegramIdx2 = 0;
- endTelegramIdx3 = 0;
- TestEndTelegramIx = 0;
+ TestStartTelegramIx = -1;
+ TestEndTelegramIx = -1;
- if (optoSerialPort != null && optoSerialPort.IsOpen) optoSerialPort.DiscardInBuffer();
+ // optional: keep these if fields still exist, but no longer used
+ endTelegramIdx1 = -1;
+ endTelegramIdx2 = -1;
+ endTelegramIdx3 = -1;
- if (flowDirectionDetection != null)
- flowDirectionDetection.ClearFifo(); /// Clear FIFO for flow direction detection
+ // RESET UNWRAP STATE
+ for (int i = 0; i < iChanelsCount; i++)
+ {
+ volumeRawExtLast[i] = double.NaN;
+ timestampExtLast[i] = double.NaN;
- /// Enable opto-data parsing and saving
- dataStreamState = DataStreamState.ProcessAndSave;
- ResetDataBuffer();
+ lastVolumeRaw[i] = double.NaN;
+ lastTimestamp[i] = double.NaN;
+ volumeLtr[i] = double.NaN;
+ volumeLtr0[i] = double.NaN;
+ timestampSec[i] = double.NaN;
+ timestampSec0[i] = double.NaN;
+ }
+
+ channel0 = -1;
+
+ try
+ {
+ StopQueueData = true;
+ if (optoSerialPort != null && optoSerialPort.IsOpen)
+ {
+ ResetDataBuffer();
+ startDataProcessing = true;
+ }
+
+ ClearReceivedLines();
+
+ if (flowDirectionDetection != null)
+ flowDirectionDetection.ClearFifo();
+
+ dataStreamState = DataStreamState.ProcessAndSave;
+ StartOptoReadLoop();
+ }
+ finally
+ {
+ StopQueueData = false;
+ }
}
public void SetCommunicationInterface(string commInterface)
@@ -1331,8 +1518,29 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
///
public void StopDataStreamProcessing()
{
+ log.DebugFormat("StopDataStreamProcessing({0}) called", DateTime.Now.ToString("HH:mm:ss.fff"));
dataStreamState = DataStreamState.Flush;
- CloseOptoSerialPort();
+ startDataProcessing = false;
+ //stop data processing end
+
+ StopQueueData = true;
+
+ lock (this)
+ {
+ // 1. stop new reads from serial
+ //StopOptoReadLoop();
+
+ // 2. clear data stream processing
+ ClearReceivedLines();//Empty othrs
+ // 2. process everything already queued
+ //DrainQueuedLines();
+
+ //ResetDataBuffer();
+
+ // 3. now it is safe to close the port
+ //CloseOptoSerialPort();
+
+ }
}
///
@@ -1350,7 +1558,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
//block varaibles to check results block
private int _completedBlockCount = 0;
- private int _resetAfterBlockRepetitions = 1;
+ private int _resetAfterBlockRepetitions = 2;
private bool _blockStartedWithF = false;
private bool _channel1SeenInBlock = false;
@@ -1406,7 +1614,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
///
/// Handles @f marker. First @f starts block, second @f closes it if h1/h2/h3 were seen.
- /// Returns true when buffers should be reset.
+ /// Returns true when the configured number of complete flow blocks has been reached.
///
private bool HandleFlowMarker()
{
@@ -1444,6 +1652,105 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
#endregion
+ private CancellationTokenSource _readLoopCts;
+ private Task _readLoopTask;
+ private readonly ConcurrentQueue<(DateTime Timestamp, string Line)> _receivedLines = new ConcurrentQueue<(DateTime Timestamp, string Line)>();
+ //private readonly ConcurrentQueue _receivedLines = new ConcurrentQueue();
+ private readonly object _serialReadSync = new object();
+
+ private void StartOptoReadLoop()
+ {
+ if (optoSerialPort == null || !optoSerialPort.IsOpen)
+ return;
+
+ StopOptoReadLoop();
+
+ _readLoopCts = new CancellationTokenSource();
+ var token = _readLoopCts.Token;
+
+ _readLoopTask = Task.Run(() =>
+ {
+ log.Debug($"OPTHO {OptoComPortNr} background read loop started.");
+
+ while (!token.IsCancellationRequested)
+ {
+ try
+ {
+ if (optoSerialPort == null || !optoSerialPort.IsOpen)
+ {
+ Thread.Sleep(20);
+ continue;
+ }
+
+ string line;
+ lock (_serialReadSync)
+ {
+ if (optoSerialPort == null || !optoSerialPort.IsOpen)
+ continue;
+
+ line = optoSerialPort.ReadLine();
+ }
+
+ //switch stopQueneData
+ if (!StopQueueData && !string.IsNullOrWhiteSpace(line))
+ {
+ _receivedLines.Enqueue((DateTime.UtcNow, line));
+ //log.Debug($"OPTHO COM{OptoComPortNr} queued line: {line}");
+ }
+ }
+ catch (TimeoutException)
+ {
+ // normal: just continue
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch (Exception ex)
+ {
+ log.Error($"OPTHO {OptoComPortNr} background read error: {ex.Message}");
+ Thread.Sleep(100);
+ }
+ }
+
+ log.Debug($"OPTHO {OptoComPortNr} background read loop stopped.");
+ }, token);
+ }
+
+ private void StopOptoReadLoop()
+ {
+ try
+ {
+ StopQueueData = true;
+
+ if (_readLoopCts != null)
+ {
+ _readLoopCts.Cancel();
+ }
+
+ if (_readLoopTask != null)
+ {
+ try
+ {
+ _readLoopTask.Wait(1000);
+ }
+ catch (AggregateException)
+ {
+ }
+ }
+ }
+ finally
+ {
+ _readLoopTask = null;
+
+ if (_readLoopCts != null)
+ {
+ _readLoopCts.Dispose();
+ _readLoopCts = null;
+ }
+ }
+ }
+
///
/// Reads opto-datastream via serial port. Invoked from RunDeviceBefore()
///
@@ -1456,45 +1763,43 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
lock (this)
{
- try
+ while (_receivedLines.TryDequeue(out var item))
{
- int nrBytes = optoSerialPort.BytesToRead;
- if (nrBytes > 0)
+ try
{
- // This will now wait max 3 seconds (ReadTimeout)
- string line = optoSerialPort.ReadLine();
- log.DebugFormat("Read Opto Data Line: {0}", line);
- bool resetBuffer;
- ProcessOptoLine(line, optoState, out resetBuffer);
-
- if (resetBuffer)
+ DateTime timestamp = item.Timestamp;
+ string line = item.Line;
+ // 🔴 STEP 1: Check if we should start processing
+ if (startDataProcessing && optoState == DataStreamState.ProcessAndSave)
{
- ResetDataBuffer();
+ log.DebugFormat("Read Opto Data Line timestamp:{0} to process from queue: {1}",timestamp.ToString("HH:mm:ss.fff") , line);
+ bool blockCompleted;
+ ProcessOptoLine(line, optoState, out blockCompleted);
+
+ if (blockCompleted)
+ {
+ log.Debug("ReadOptoData() completed flow block detected.");
+ if (resetSerialBuffersOnCompletedFlowBlock) // DO NOT call ResetDataBuffer() here
+ ResetDataBuffer();
+ }
}
}
- }
- 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}");
+ catch (Exception ex)
+ {
+ log.Error($"OPTHO {OptoComPortNr} processing queued line failed: {ex.Message}");
+ }
}
}
}
- public void ProcessOptoLine(string line, DataStreamState optoState, out bool resetBuffer)
+ public void ProcessOptoLine(string line, DataStreamState optoState, out bool blockCompleted)
{
var encoding = optoSerialPort != null ? optoSerialPort.Encoding : Encoding.ASCII;
byte[] bytes = encoding.GetBytes(line);
log.Debug("ComPort: " + OptoComPortNr + " OPTHO RX ← " + HexFormatter.ToSerialHex(bytes));
- resetBuffer = false;
+ blockCompleted = false;
var streamingDecode = new StreamingDecoder(true);
streamingDecode.DecodeMsg(line);
@@ -1503,10 +1808,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
if (streamingDecode.DataFlowTest != null && streamingDecode.DataFlowTest.IsValid)
{
- resetBuffer = HandleFlowMarker();
+ blockCompleted = HandleFlowMarker();
log.Debug("ComPort: " + OptoComPortNr + " Decoded Flow data: " + streamingDecode.DataFlowTest +
- " OPTHO RX ← " +
- HexFormatter.ToSerialHex(bytes));
+ " OPTHO RX ← " + HexFormatter.ToSerialHex(bytes));
}
if (calibData != null && calibData.IsValid)
@@ -1533,11 +1837,16 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
int iChanel = calibData.Channel - 1;
if (iChanel >= 0 && iChanel < iChanelsCount)
{
- log.Debug("ComPort: " + OptoComPortNr + " Channel: " + iChanel);
+ log.Debug(
+ $"Before UpdateFromSmart ch={iChanel + 1}: " +
+ $"volumeRawExtLast={volumeRawExtLast[iChanel]}, " +
+ $"timestampExtLast={timestampExtLast[iChanel]}, " +
+ $"VolumeCm={calibData.VolumeCm}, OverflowVolumeCm={calibData.OverflowVolumeCm}");
+
optoData[bufferIx].UpdateFromSmart(
calibData,
optoDataCount,
- Convert.ToSingle(Sequences.ProcessData.RefFlow.Val),
+ GetReferenceFlowSafe(),
ref volumeRawExtLast[iChanel],
ref timestampExtLast[iChanel]);
@@ -1553,14 +1862,20 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
optoDataCount++;
}
- else
+ }
+
+ private float GetReferenceFlowSafe()
+ {
+ try
{
- int iChanel = calibData.Channel - 1;
- if (iChanel >= 0 && iChanel < iChanelsCount)
- {
- flowDirectionDetection.WriteToFifo(volumeRawExtLast, timestampExtLast, iChanel);
- }
+ if (Sequences.ProcessData.RefFlow != null)
+ return Convert.ToSingle(Sequences.ProcessData.RefFlow.Val);
}
+ catch
+ {
+ }
+
+ return 0.0f;
}
@@ -1610,7 +1925,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
log.Info($"OPTHO {OptoComPortNr} FLOW Parsed opto data: " + dataFlow + " RX ← " + received);
if (HandleFlowMarker())
- ResetDataBuffer();
+ {
+ log.Debug("ReadOptoData() completed flow block detected.");
+ if (resetSerialBuffersOnCompletedFlowBlock)
+ ResetDataBuffer(); // no ResetDataBuffer() here
+ }
}
}
@@ -1672,16 +1991,19 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return received;
}
+ private const bool resetSerialBuffersOnCompletedFlowBlock = true;
private void ResetDataBuffer()
{
- if (optoSerialPort != null)
+ lock (_serialReadSync)
{
- optoSerialPort.DiscardInBuffer();
- optoSerialPort.DiscardOutBuffer();
- log.Debug("-- Reaset Data Buffer --");
- return;
+ if (optoSerialPort != null && optoSerialPort.IsOpen)
+ {
+ optoSerialPort.DiscardInBuffer();
+ optoSerialPort.DiscardOutBuffer();
+ log.Debug("-- Reaset Data Buffer --");
+ return;
+ }
}
-
log.Debug("-- Reaset Data Buffer - no serial port --");
}
@@ -1697,11 +2019,13 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
var readTask = Task.Run(() =>
{
- lock (this)
+ try
{
- if (optoSerialPort == null) return string.Empty;
- try
+ lock (_serialReadSync)
{
+ if (optoSerialPort == null || !optoSerialPort.IsOpen)
+ return string.Empty;
+
string line = optoSerialPort.ReadLine();
byte[] bytes = optoSerialPort.Encoding.GetBytes(line);
string received = HexFormatter.ToSerialHex(bytes);
@@ -1709,31 +2033,26 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
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;
}
+ catch (TimeoutException)
+ {
+ log.Debug($"ReadOptoData() OPTHO {OptoComPortNr} ReadLine timeout (3s) - continuing.");
+ }
+ 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
- }
+ return await readTask;
log.Debug("ReadOptoData timeout after " + timeoutMs + " ms");
- return string.Empty; // timeout case
+ return string.Empty;
}
public string ReadOptoDataWithTimeout(int timeoutMs = 5000)
@@ -2261,10 +2580,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return await Task.Run(() =>
{
log.Debug($"Try get End Volume! COM: {this.OptoComPortNr}");
- volumeLtr[channel0] = Double.NaN;
+ int ch = channel0 >= 0 ? channel0 : 0;
+ volumeLtr[ch] = Double.NaN;
int counter = 0;
- while (Double.IsNaN(volumeLtr[channel0]) && counter < 2)
+ while (Double.IsNaN(volumeLtr[ch]) && counter < 10)
{
counter++;
try
@@ -2277,9 +2597,19 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
StreamingDecoder _streamingDecode = new StreamingDecoder(true);
_streamingDecode.DecodeMsg(readOptoDataWithTimeout);
CalibrationRecord data = _streamingDecode.DataCalib;
+ if (data == null || !data.IsValid)
+ continue;
+
log.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
- volumeLtr[channel0] = data.VolumeCm * 1000;
- break;
+
+ int dch = data.Channel - 1;
+ if (dch >= 0 && dch < iChanelsCount)
+ {
+ volumeLtr[dch] = data.VolumeCm * 1000;
+ channel0 = dch;
+ ch = dch;
+ break;
+ }
}
catch (Exception ex)
{
@@ -2339,10 +2669,12 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
log.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}");
Start();
+
+ int ch = channel0 >= 0 ? channel0 : 0;
+ volumeLtr0[ch] = Double.NaN;
- volumeLtr0[channel0] = Double.NaN;
int counter = 0;
- while (Double.IsNaN(volumeLtr0[channel0]) && counter < 10)
+ while (Double.IsNaN(volumeLtr0[ch]) && counter < 10)
{
counter++;
try
@@ -2356,8 +2688,18 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
_streamingDecode.DecodeMsg(readOptoDataWithTimeout);
CalibrationRecord data = _streamingDecode.DataCalib;
log.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
- volumeLtr0[channel0] = data.VolumeCm * 1000;
- break;
+ if (data == null || !data.IsValid)
+ continue;
+
+ int dch = data.Channel - 1;
+ if (dch >= 0 && dch < iChanelsCount)
+ {
+ volumeLtr0[dch] = data.VolumeCm * 1000;
+ channel0 = dch;
+ ch = dch;
+ break;
+ }
+
}
catch (Exception ex)
{
@@ -2374,9 +2716,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
log.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}, Volume: {volumeLtr0}");
if (optoSerialPort != null && optoSerialPort.IsOpen) CloseOptoSerialPort();
- if (!Double.IsNaN(volumeLtr0[channel0]))
+ if (!Double.IsNaN(volumeLtr0[ch]))
{
- beginWMState = volumeLtr0[channel0];
+ beginWMState = volumeLtr0[ch];
ReadPulses();
return beginWMState;
}
@@ -2675,9 +3017,13 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
private OptoTelegramRaw[][] _rawStartEndByChannel;
private OptoTelegramRaw[][] _recalculatedStartEndByChannel;
- private OptoTelegramRaw[][] BuildStartEndByChannel(OptoTelegramRaw[] optoData, int optoDataCount)
+ private OptoTelegramRaw[][] BuildStartEndByChannel(
+ OptoTelegramRaw[] optoData,
+ int optoDataCount,
+ int startIx,
+ int endIx)
{
- var grouped = GroupRecordsPerChannel(optoData, 0, optoDataCount - 1, optoDataCount);
+ var grouped = GroupRecordsPerChannel(optoData, startIx, endIx, optoDataCount);
var result = new OptoTelegramRaw[ChannelCount][];
for (int ch = 0; ch < ChannelCount; ch++)
@@ -2706,8 +3052,52 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return;
}
- _rawStartEndByChannel = BuildStartEndByChannel(optoData, optoDataCount);
- _recalculatedStartEndByChannel = RecalculateVolumeAndTimeDeltaPerChannel(optoData, optoDataCount);
+ log.Debug("-- BuildStartEndByChannel --");
+ _rawStartEndByChannel = BuildStartEndByChannel(optoData, optoDataCount, TestStartTelegramIx, TestEndTelegramIx);
+ log.Debug("-- RecalculateVolumeAndTimeDeltaPerChannel --");
+ _recalculatedStartEndByChannel = RecalculateVolumeAndTimeDeltaPerChannel(optoData, optoDataCount, TestStartTelegramIx, TestEndTelegramIx);
+
+ log.Debug($"PrepareCalculatedChannelData: optoDataCount={optoDataCount}");
+
+ // 🔹 RAW DATA LOG
+ if (_rawStartEndByChannel != null)
+ {
+ for (int ch = 0; ch < ChannelCount; ch++)
+ {
+ var start = _rawStartEndByChannel[ch]?[0];
+ var end = _rawStartEndByChannel[ch]?[1];
+
+ log.Debug(
+ $"RAW Ch{ch + 1}: " +
+ $"Start(V={start?.VolumeRawExt}, T={start?.TimestampExt}) | " +
+ $"End(V={end?.VolumeRawExt}, T={end?.TimestampExt})");
+ }
+ }
+ else
+ {
+ log.Warn("RAW data is NULL");
+ }
+
+ // 🔹 RECALCULATED DATA LOG
+ if (_recalculatedStartEndByChannel != null)
+ {
+ for (int ch = 0; ch < ChannelCount; ch++)
+ {
+ var start = _recalculatedStartEndByChannel[ch]?[0];
+ var end = _recalculatedStartEndByChannel[ch]?[1];
+
+ log.Debug(
+ $"RECALC Ch{ch + 1}: " +
+ $"Start(V={start?.VolumeRawExt}, T={start?.TimestampExt}) | " +
+ $"End(V={end?.VolumeRawExt}, T={end?.TimestampExt}) | " +
+ $"ΔV={(end != null && start != null ? end.VolumeRawExt - start.VolumeRawExt : 0)} | " +
+ $"ΔT={(end != null && start != null ? end.TimestampExt - start.TimestampExt : 0)}");
+ }
+ }
+ else
+ {
+ log.Warn("RECALCULATED data is NULL");
+ }
}
@@ -2729,18 +3119,34 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
var first = records[0];
var last = records[records.Length - 1];
- result[ch] = (
- ch,
- last.TimestampExt - first.TimestampExt,
- last.VolumeRawExt - first.VolumeRawExt
- );
+ double rawDelta = last.TimestampExt - first.TimestampExt;
+ double rawDeltaVol = last.VolumeRawExt - first.VolumeRawExt;
+
+ log.Debug($"COM{OptoComPortNr} First raw: {first.rawDataToString()}");
+ log.Debug($"COM{OptoComPortNr} Last raw: {last.rawDataToString()}");
+
+ if (rawDeltaVol < 0)
+ {
+ log.Error($"Negative delta volume for channel {ch + 1}: {rawDeltaVol}");
+ }
+
+ if (rawDelta < 0)
+ {
+ log.Error($"Negative delta time for channel {ch + 1}: {rawDelta}");
+ }
+
+ result[ch] = (ch, rawDelta, rawDeltaVol);
+
+ log.Debug($" ---- COM{OptoComPortNr} ----");
+ log.Debug($"COM{OptoComPortNr} Ch{ch + 1}: First raw: {first.rawDataToString()}");
+ log.Debug($"COM{OptoComPortNr} Ch{ch + 1}: Last raw: {last.rawDataToString()}");
+ log.Debug($"COM{OptoComPortNr} Ch{ch + 1}: rawΔT={rawDelta} , rawΔVol={rawDeltaVol} ");
+ log.Debug($" --- ---");
}
return result;
}
-
-
private OptoTelegramRaw[][] GroupRecordsPerChannel(
OptoTelegramRaw[] data,
int startIx,
@@ -2786,12 +3192,14 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
private OptoTelegramRaw[][] RecalculateVolumeAndTimeDeltaPerChannel(
OptoTelegramRaw[] optoData,
- int optoDataCount)
+ int optoDataCount,
+ int startIx,
+ int endIx)
{
var recordByChannel = GroupRecordsPerChannel(
optoData,
- 0,
- optoDataCount - 1,
+ startIx,
+ endIx,
optoDataCount);
var timeDelta = TimeDeltaPerChannel(recordByChannel);
diff --git a/TBF/Rig/Sequences/SequenceBase.cs b/TBF/Rig/Sequences/SequenceBase.cs
index 55859fe1c..17f972735 100644
--- a/TBF/Rig/Sequences/SequenceBase.cs
+++ b/TBF/Rig/Sequences/SequenceBase.cs
@@ -678,7 +678,8 @@ namespace TBF.Rig.Sequences
/// On error or when STOP pressed
///
foreach (var fmPump in PumpsWithFM) fmPump.TurnOff();
-
+ log.Debug("STOP or ERROR: Pumps with FM stopped!");
+
if (inPath != null)
{
///
diff --git a/TBF/Rig/TestMethods/FlyingStartMassCollection/FlyingStartMassCollectionSeq.cs b/TBF/Rig/TestMethods/FlyingStartMassCollection/FlyingStartMassCollectionSeq.cs
index 500086910..47a5c81eb 100644
--- a/TBF/Rig/TestMethods/FlyingStartMassCollection/FlyingStartMassCollectionSeq.cs
+++ b/TBF/Rig/TestMethods/FlyingStartMassCollection/FlyingStartMassCollectionSeq.cs
@@ -170,8 +170,8 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
string testName = Results.Utils.GetTestName(test.Name, test.Repeats, repetitionNr);
TestStartTime = DateTime.Now;
- bool atleastOneGenesis = false;
- atleastOneGenesis = GenesisHeadBatch.Start(sensPath.RegisterReaders, Program.LocalSettings.LastSNTexts);
+ // bool atleastOneGenesis = false;
+ // atleastOneGenesis = GenesisHeadBatch.Start(sensPath.RegisterReaders, Program.LocalSettings.LastSNTexts);
//------------------------------------------------
Bridge.OnActivity(this, Strings.Checking_tank_capacity);
@@ -452,23 +452,23 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
}
}
- if (atleastOneGenesis)
- {
- log.Info($"Genesis - Starting... Test Name:{test.Name.ToLower()}.");
- if (test.Name.ToLower().Contains("calib"))
- {
- log.Info("Genesis - Calibration starting.");
- GenesisHeadBatch.BatchHolder.Value.MetersLogin();
- GenesisHeadBatch.BatchHolder.Value.MetersInitCalibration();
-
- }
- if (test.Name.ToLower().Contains("init"))
- {
- log.Info("Genesis - init starting.");
- GenesisHeadBatch.BatchHolder.Value.MetersLogin();
- GenesisHeadBatch.BatchHolder.Value.MetersInitMeasurement();
- }
- }
+ // if (atleastOneGenesis)
+ // {
+ // log.Info($"Genesis - Starting... Test Name:{test.Name.ToLower()}.");
+ // if (test.Name.ToLower().Contains("calib"))
+ // {
+ // log.Info("Genesis - Calibration starting.");
+ // GenesisHeadBatch.BatchHolder.Value.MetersLogin();
+ // GenesisHeadBatch.BatchHolder.Value.MetersInitCalibration();
+ //
+ // }
+ // if (test.Name.ToLower().Contains("init"))
+ // {
+ // log.Info("Genesis - init starting.");
+ // GenesisHeadBatch.BatchHolder.Value.MetersLogin();
+ // GenesisHeadBatch.BatchHolder.Value.MetersInitMeasurement();
+ // }
+ // }
if (drainTheTank)
{
@@ -610,19 +610,32 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
/// Measurement loop end
StopRecordingStatistics();
- if (atleastOneGenesis)
- {
- if (test.Name.ToLower().Contains("calib"))
- {
- log.Info("Genesis - Calibration stopped.");
- GenesisHeadBatch.BatchHolder.Value.MetersStopCalibration();
- }
- else
- {
- log.Info("Genesis - Init measurement stopped.");
- GenesisHeadBatch.BatchHolder.Value.MetersStopMeasurement();
- }
- }
+
+
+ if (sensPath != null && sensPath.RegisterReaders != null)
+ {
+ foreach (var rr in sensPath.RegisterReaders)
+ {
+ var datastreamRR = rr as ISmartReader;
+ if (datastreamRR != null)
+ {
+ datastreamRR.StopDataStreamProcessing();
+ }
+ }
+ }
+ // if (atleastOneGenesis)
+ // {
+ // if (test.Name.ToLower().Contains("calib"))
+ // {
+ // log.Info("Genesis - Calibration stopped.");
+ // GenesisHeadBatch.BatchHolder.Value.MetersStopCalibration();
+ // }
+ // else
+ // {
+ // log.Info("Genesis - Init measurement stopped.");
+ // GenesisHeadBatch.BatchHolder.Value.MetersStopMeasurement();
+ // }
+ // }
///
/// (Berlin:) Water is stopped immediately after the test and before the 2nd mass measurement in case:
diff --git a/TBFTests/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRawTest.cs b/TBFTests/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRawTest.cs
index 7b7db3a39..a20383dc5 100644
--- a/TBFTests/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRawTest.cs
+++ b/TBFTests/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRawTest.cs
@@ -12,13 +12,17 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.common
private static CalibrationRecord CreateCalibrationRecord(
int channel = 1,
double volumeCm = 1.234,
- double timeS = 12.5)
+ double overflowVolumeCm = 8.38860799804687,
+ double timeS = 12.5,
+ double overflowTimeS = 65536.0)
{
return new CalibrationRecord
{
Channel = channel,
VolumeCm = volumeCm,
- TimeS = timeS
+ OverflowVolumeCm = overflowVolumeCm,
+ TimeS = timeS,
+ OverflowTimeS = overflowTimeS
};
}
@@ -26,7 +30,12 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.common
public void UpdateFromSmart_FirstSample_ShouldInitializeFields()
{
var telegram = new OptoTelegramRaw();
- var data = CreateCalibrationRecord(channel: 2, volumeCm: 2.5, timeS: 100.0);
+ var data = CreateCalibrationRecord(
+ channel: 2,
+ volumeCm: 2.5,
+ overflowVolumeCm: 8.38860799804687,
+ timeS: 100.0,
+ overflowTimeS: 65536.0);
double lastVolume = double.NaN;
double lastTimestamp = double.NaN;
@@ -54,32 +63,479 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.common
Assert.AreEqual(0, telegram.MagneticFieldRaw);
}
+ [TestMethod]
+ public void UpdateFromSmart_RealGenesisDecodedSample_ShouldMapExpectedValues()
+ {
+ var telegram = new OptoTelegramRaw();
+
+ var data = CreateCalibrationRecord(
+ channel: 2,
+ volumeCm: 0.48025591796875,
+ overflowVolumeCm: 8.38860799804687,
+ timeS: 62727.8408813477,
+ overflowTimeS: 65536.0);
+
+ double lastVolume = double.NaN;
+ double lastTimestamp = double.NaN;
+
+ telegram.UpdateFromSmart(data, counter: 1, refFlow: 0.0f, ref lastVolume, ref lastTimestamp);
+
+ Assert.AreEqual(1, telegram.IChannel());
+ Assert.AreEqual(1, telegram.Counter);
+ Assert.AreEqual(0.0f, telegram.RefFlow);
+ Assert.AreEqual(OptoTelegramFlags.OK, telegram.Flags);
+
+ Assert.AreEqual(480.25591796875, telegram.VolumeRaw, 1e-9);
+ Assert.AreEqual(480.25591796875, telegram.VolumeRawExt, 1e-9);
+ Assert.AreEqual(480.25591796875, lastVolume, 1e-9);
+
+ Assert.AreEqual(62727.8408813477, telegram.Timestamp, 1e-9);
+ Assert.AreEqual(62727.8408813477, telegram.TimestampExt, 1e-9);
+ Assert.AreEqual(62727.8408813477, lastTimestamp, 1e-9);
+
+ Assert.AreEqual(0, telegram.FlowRaw);
+ Assert.AreEqual(0, telegram.CheckSum);
+ Assert.AreEqual(0, telegram.Impedance);
+ Assert.AreEqual(0, telegram.EmfRaw);
+ Assert.AreEqual(0, telegram.MagneticFieldRaw);
+ }
+
[TestMethod]
public void UpdateFromSmart_WhenVolumeDecreases_ShouldApplyVolumeRollover()
{
var telegram = new OptoTelegramRaw();
- var data = CreateCalibrationRecord(channel: 1, volumeCm: 1.0, timeS: 10.0);
+ var overflowVolumeCm = 8.38860799804687;
+ var overflowRaw = overflowVolumeCm * 1000.0;
- double previousExtendedVolume = 5000.0;
+ var data = CreateCalibrationRecord(
+ channel: 1,
+ volumeCm: 1.0,
+ overflowVolumeCm: overflowVolumeCm,
+ timeS: 10.0,
+ overflowTimeS: 65536.0);
+
+ // Must be close enough to overflow so that current=1000 is interpreted as a wrap
+ double previousExtendedVolume = 8000.0;
double previousExtendedTimestamp = 10.0;
- telegram.UpdateFromSmart(data, counter: 1, refFlow: 0.5f, ref previousExtendedVolume, ref previousExtendedTimestamp);
+ telegram.UpdateFromSmart(
+ data,
+ counter: 1,
+ refFlow: 0.5f,
+ ref previousExtendedVolume,
+ ref previousExtendedTimestamp);
double expectedVolumeRaw = 1000.0;
- double expectedExtended = expectedVolumeRaw + StreamingDecoder.DefaultAccuDutOverflowVolumeCm * 1000.0;
+ double expectedExtended = expectedVolumeRaw + overflowRaw;
Assert.AreEqual(expectedVolumeRaw, telegram.VolumeRaw, 1e-9);
Assert.AreEqual(expectedExtended, telegram.VolumeRawExt, 1e-6);
Assert.AreEqual(expectedExtended, previousExtendedVolume, 1e-6);
}
+
+ [TestMethod]
+ public void UpdateFromSmart_MultipleVolumeRollover_ShouldKeepIncreasingExtendedVolume()
+ {
+ var telegram = new OptoTelegramRaw();
+ double overflowVolumeCm = 8.38860799804687;
+ double lastVolumeExt = double.NaN;
+ double lastTimestampExt = double.NaN;
+
+ // Safe sequence: small forward steps, natural wrap, small forward steps
+ double[] samples =
+ {
+ 7.5,
+ 8.0,
+ 0.2,
+ 1.0,
+ 1.8,
+ 2.6,
+ 3.4,
+ 4.2,
+ 5.0,
+ 5.8,
+ 6.6,
+ 7.4,
+ 8.1,
+ 0.3,
+ 1.1
+ };
+
+ double previousExt = double.NaN;
+
+ for (int i = 0; i < samples.Length; i++)
+ {
+ var data = CreateCalibrationRecord(
+ channel: 1,
+ volumeCm: samples[i],
+ overflowVolumeCm: overflowVolumeCm,
+ timeS: i,
+ overflowTimeS: 65536.0);
+
+ telegram.UpdateFromSmart(data, i, 0, ref lastVolumeExt, ref lastTimestampExt);
+
+ double expectedRaw = samples[i] * 1000.0;
+ Assert.AreEqual(expectedRaw, telegram.VolumeRaw, 1e-9, $"Raw mismatch at index {i}");
+
+ if (!double.IsNaN(previousExt))
+ {
+ Assert.IsTrue(
+ telegram.VolumeRawExt >= previousExt,
+ $"Volume should not decrease at index {i}. Prev={previousExt}, Current={telegram.VolumeRawExt}");
+ }
+
+ previousExt = telegram.VolumeRawExt;
+ }
+ }
+
+ [TestMethod]
+ public void UpdateFromSmart_MultipleVolumeRollover_FirstToLastDeltaShouldBePositive()
+ {
+ var telegram = new OptoTelegramRaw();
+ double overflowVolumeCm = 8.38860799804687;
+
+ double lastVolumeExt = double.NaN;
+ double lastTimestampExt = double.NaN;
+
+ double[] samples =
+ {
+ 7.5, 8.0, 0.2, 1.0, 1.8, 2.6, 3.4, 4.2, 5.0, 5.8, 6.6, 7.4, 8.1, 0.3, 1.1
+ };
+
+ double firstExt = double.NaN;
+
+ for (int i = 0; i < samples.Length; i++)
+ {
+ var data = CreateCalibrationRecord(
+ channel: 1,
+ volumeCm: samples[i],
+ overflowVolumeCm: overflowVolumeCm,
+ timeS: i,
+ overflowTimeS: 65536.0);
+
+ telegram.UpdateFromSmart(data, i, 0, ref lastVolumeExt, ref lastTimestampExt);
+
+ if (i == 0)
+ firstExt = telegram.VolumeRawExt;
+ }
+
+ Assert.IsTrue(lastVolumeExt > firstExt,
+ $"Volume should increase from first to last. First={firstExt}, Last={lastVolumeExt}");
+ }
+
+ [TestMethod]
+ public void UpdateFromSmart_MultipleVolumeRollover_ShouldMatchPhysicalDelta()
+ {
+ var telegram = new OptoTelegramRaw();
+ double overflowVolumeCm = 8.38860799804687;
+
+ double lastVolumeExt = double.NaN;
+ double lastTimestampExt = double.NaN;
+
+ double[] samples =
+ {
+ 7.5,
+ 8.0,
+ 0.2,
+ 1.0,
+ 1.8,
+ 2.6,
+ 3.4,
+ 4.2,
+ 5.0,
+ 5.8,
+ 6.6,
+ 7.4,
+ 8.1,
+ 0.3,
+ 1.1
+ };
+
+ double firstExt = double.NaN;
+ double lastExt = double.NaN;
+
+ for (int i = 0; i < samples.Length; i++)
+ {
+ var data = CreateCalibrationRecord(
+ channel: 1,
+ volumeCm: samples[i],
+ overflowVolumeCm: overflowVolumeCm,
+ timeS: i,
+ overflowTimeS: 65536.0);
+
+ telegram.UpdateFromSmart(data, i, 0, ref lastVolumeExt, ref lastTimestampExt);
+
+ if (i == 0)
+ firstExt = telegram.VolumeRawExt;
+
+ if (i == samples.Length - 1)
+ lastExt = telegram.VolumeRawExt;
+ }
+
+ double start = samples[0];
+ double end = samples[samples.Length - 1];
+
+ int wraps = 0;
+ for (int i = 1; i < samples.Length; i++)
+ {
+ if (samples[i] < samples[i - 1])
+ wraps++;
+ }
+
+ double expectedDeltaCm = wraps * overflowVolumeCm + end - start;
+ double expectedDeltaRaw = expectedDeltaCm * 1000.0;
+ double actualDeltaRaw = lastExt - firstExt;
+
+ Assert.AreEqual(
+ expectedDeltaRaw,
+ actualDeltaRaw,
+ 1e-6,
+ $"Delta mismatch. Wraps={wraps}, Expected={expectedDeltaRaw}, Actual={actualDeltaRaw}");
+ }
+
+ [TestMethod]
+ public void UpdateFromSmart_MultipleTimestampWrap_ShouldMatchPhysicalDelta()
+ {
+ var telegram = new OptoTelegramRaw();
+
+ double overflowTimeS = 65536.0;
+
+ double lastVolumeExt = double.NaN;
+ double lastTimestampExt = double.NaN;
+
+ // Safe sequence: small forward steps, natural wrap, small forward steps, second wrap
+ double[] times =
+ {
+ 65530.0,
+ 65535.0,
+ 2.0, // wrap 1
+ 10.0,
+ 100.0,
+ 1000.0,
+ 10000.0,
+ 30000.0,
+ 50000.0,
+ 65534.0,
+ 3.0, // wrap 2
+ 20.0
+ };
+
+ double firstExt = double.NaN;
+ double lastExt = double.NaN;
+
+ for (int i = 0; i < times.Length; i++)
+ {
+ var data = CreateCalibrationRecord(
+ channel: 1,
+ volumeCm: 1.0,
+ overflowVolumeCm: 8.38860799804687,
+ timeS: times[i],
+ overflowTimeS: overflowTimeS);
+
+ telegram.UpdateFromSmart(data, i, 0, ref lastVolumeExt, ref lastTimestampExt);
+
+ if (i == 0)
+ firstExt = telegram.TimestampExt;
+
+ if (i == times.Length - 1)
+ lastExt = telegram.TimestampExt;
+ }
+
+ double start = times[0];
+ double end = times[times.Length - 1];
+
+ int wraps = 0;
+ for (int i = 1; i < times.Length; i++)
+ {
+ if (times[i] < times[i - 1])
+ wraps++;
+ }
+
+ double expectedDelta = wraps * overflowTimeS + end - start;
+ double actualDelta = lastExt - firstExt;
+
+ Assert.AreEqual(
+ expectedDelta,
+ actualDelta,
+ 1e-6,
+ $"Timestamp delta mismatch. Wraps={wraps}, Expected={expectedDelta}, Actual={actualDelta}");
+ }
+
+ [TestMethod]
+ public void UpdateFromSmart_MultipleTimestampWrap_ShouldKeepIncreasingExtendedTimestamp()
+ {
+ var telegram = new OptoTelegramRaw();
+
+ double overflowTimeS = 65536.0;
+
+ double lastVolumeExt = double.NaN;
+ double lastTimestampExt = double.NaN;
+
+ double[] times =
+ {
+ 65530.0,
+ 65535.0,
+ 2.0,
+ 10.0,
+ 100.0,
+ 1000.0,
+ 10000.0,
+ 30000.0,
+ 50000.0,
+ 65534.0,
+ 3.0,
+ 20.0
+ };
+
+ double previousExt = double.NaN;
+
+ for (int i = 0; i < times.Length; i++)
+ {
+ var data = CreateCalibrationRecord(
+ channel: 1,
+ volumeCm: 1.0,
+ overflowVolumeCm: 8.38860799804687,
+ timeS: times[i],
+ overflowTimeS: overflowTimeS);
+
+ telegram.UpdateFromSmart(data, i, 0, ref lastVolumeExt, ref lastTimestampExt);
+
+ if (!double.IsNaN(previousExt))
+ {
+ Assert.IsTrue(
+ telegram.TimestampExt >= previousExt,
+ $"Timestamp should not decrease at index {i}. Prev={previousExt}, Current={telegram.TimestampExt}");
+ }
+
+ previousExt = telegram.TimestampExt;
+ }
+ }
+
+ [TestMethod]
+ public void UpdateFromSmart_MultipleVolumeRollover_ShouldProduceExpectedExtendedValues()
+ {
+ var telegram = new OptoTelegramRaw();
+ double overflowVolumeCm = 8.38860799804687;
+ double overflowRaw = overflowVolumeCm * 1000.0;
+
+ double lastVolumeExt = double.NaN;
+ double lastTimestampExt = double.NaN;
+
+ double[] samples =
+ {
+ 7.5,
+ 8.0,
+ 0.2,
+ 0.8,
+ 1.4
+ };
+
+ double[] expectedExt =
+ {
+ 7500.0,
+ 8000.0,
+ 200.0 + overflowRaw,
+ 800.0 + overflowRaw,
+ 1400.0 + overflowRaw
+ };
+
+ for (int i = 0; i < samples.Length; i++)
+ {
+ var data = CreateCalibrationRecord(
+ channel: 1,
+ volumeCm: samples[i],
+ overflowVolumeCm: overflowVolumeCm,
+ timeS: i,
+ overflowTimeS: 65536.0);
+
+ telegram.UpdateFromSmart(data, i, 0, ref lastVolumeExt, ref lastTimestampExt);
+
+ Assert.AreEqual(samples[i] * 1000.0, telegram.VolumeRaw, 1e-9, $"Raw mismatch at index {i}");
+ Assert.AreEqual(expectedExt[i], telegram.VolumeRawExt, 1e-6, $"Mismatch at index {i}");
+ }
+ }
+
+ [TestMethod]
+ public void UpdateFromSmart_TwoVolumeRollovers_ShouldKeepExtendedVolumeIncreasing()
+ {
+ var telegram = new OptoTelegramRaw();
+ double overflowVolumeCm = 8.38860799804687;
+ double overflowRaw = overflowVolumeCm * 1000.0;
+
+ double lastVolumeExt = double.NaN;
+ double lastTimestampExt = double.NaN;
+
+ double[] samples =
+ {
+ 6.8,
+ 8.1,
+ 0.3,
+ 1.2,
+ 2.1,
+ 3.0,
+ 4.0,
+ 7.5,
+ 8.1,
+ 0.4,
+ 1.1
+ };
+
+ double[] expectedExt =
+ {
+ 6800.0,
+ 8100.0,
+ overflowRaw + 300.0,
+ overflowRaw + 1200.0,
+ overflowRaw + 2100.0,
+ overflowRaw + 3000.0,
+ overflowRaw + 4000.0,
+ overflowRaw + 7500.0,
+ overflowRaw + 8100.0,
+ 2.0 * overflowRaw + 400.0,
+ 2.0 * overflowRaw + 1100.0
+ };
+
+ double previous = double.NaN;
+
+ for (int i = 0; i < samples.Length; i++)
+ {
+ var data = CreateCalibrationRecord(
+ channel: 1,
+ volumeCm: samples[i],
+ overflowVolumeCm: overflowVolumeCm,
+ timeS: i,
+ overflowTimeS: 65536.0);
+
+ telegram.UpdateFromSmart(data, i, 0, ref lastVolumeExt, ref lastTimestampExt);
+
+ Assert.AreEqual(samples[i] * 1000.0, telegram.VolumeRaw, 1e-9, $"Raw mismatch at index {i}");
+ Assert.AreEqual(expectedExt[i], telegram.VolumeRawExt, 1e-6, $"Mismatch at index {i}");
+
+ if (!double.IsNaN(previous))
+ {
+ Assert.IsTrue(
+ telegram.VolumeRawExt > previous,
+ $"Extended volume did not increase at index {i}. Prev={previous}, Current={telegram.VolumeRawExt}");
+ }
+
+ previous = telegram.VolumeRawExt;
+ }
+ }
+
+
[TestMethod]
public void UpdateFromSmart_WhenTimestampWraps_ShouldUnwrapForward()
{
var telegram = new OptoTelegramRaw();
double tsRange = StreamingDecoder.CpuTimeOverflowS;
- var data = CreateCalibrationRecord(channel: 1, volumeCm: 1.0, timeS: 1.0);
+ var data = CreateCalibrationRecord(
+ channel: 1,
+ volumeCm: 1.0,
+ overflowVolumeCm: 8.38860799804687,
+ timeS: 1.0,
+ overflowTimeS: tsRange);
double previousExtendedVolume = 1000.0;
double previousExtendedTimestamp = tsRange - 0.25;
diff --git a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisReaderTests.cs b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisReaderTests.cs
new file mode 100644
index 000000000..4ba98404a
--- /dev/null
+++ b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisReaderTests.cs
@@ -0,0 +1,220 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using TBF.Rig.RegisterReaders.GenesisRegReader.common;
+using TBF.Rig.RegisterReaders.GenesisRegReader.communication;
+using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
+using TBF.Rig.Sequences;
+using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
+
+namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
+{
+
+
+
+ [TestClass]
+ public class GenesisReaderTests
+ {
+ [TestMethod]
+ public void RealInput_ShouldCalculate_StartEndVolumes_AndTimes()
+ {
+ var reader = new GenesisSmartReader();
+ InitializeReaderForTest(reader);
+
+ string[] realInputLines = LoadRealInputLines();
+ Assert.IsTrue(realInputLines.Length > 0, "No test input lines were provided.");
+
+ int processedCount = 0;
+ int firstValidIx = -1;
+ int lastValidIx = -1;
+
+ foreach (var line in realInputLines)
+ {
+ bool blockCompleted;
+ reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out blockCompleted);
+
+ int optoDataCount = GetPrivateField(reader, "optoDataCount");
+ if (optoDataCount > processedCount)
+ {
+ if (firstValidIx < 0)
+ firstValidIx = processedCount;
+
+ lastValidIx = optoDataCount - 1;
+ processedCount = optoDataCount;
+ }
+ }
+
+ Assert.IsTrue(processedCount > 0, "No valid calibration telegrams were parsed.");
+
+ reader.TestStartTelegramIx = firstValidIx;
+ reader.TestEndTelegramIx = lastValidIx;
+
+ object[] markArgs = { 0, 0 };
+ InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs);
+ InvokePrivate(reader, "DataStreamPostProcessing");
+ InvokePrivate(reader, "PrepareCalculatedChannelData");
+
+ // first run: inspect values from debugger/log/output and then replace
+ Console.WriteLine($"VolumeLtrStart={reader.VolumeLtrStart}");
+ Console.WriteLine($"VolumeLtrEnd={reader.VolumeLtrEnd}");
+ Console.WriteLine($"TimestampSecStart={reader.TimestampSecStart}");
+ Console.WriteLine($"TimestampSecEnd={reader.TimestampSecEnd}");
+
+ const double expectedVolumeStart = 0.0; // replace with real value
+ const double expectedVolumeEnd = 0.0; // replace with real value
+ const double expectedTimeStart = 0.0; // replace with real value
+ const double expectedTimeEnd = 0.0; // replace with real value
+
+ const double tolerance = 0.000001;
+
+ Assert.AreEqual(expectedVolumeStart, reader.VolumeLtrStart, tolerance, "VolumeLtrStart mismatch");
+ Assert.AreEqual(expectedVolumeEnd, reader.VolumeLtrEnd, tolerance, "VolumeLtrEnd mismatch");
+ Assert.AreEqual(expectedTimeStart, reader.TimestampSecStart, tolerance, "TimestampSecStart mismatch");
+ Assert.AreEqual(expectedTimeEnd, reader.TimestampSecEnd, tolerance, "TimestampSecEnd mismatch");
+ }
+
+ [TestMethod]
+ public void RealInput_ShouldSupport_RolloverNormalization()
+ {
+ var reader = new GenesisSmartReader();
+ InitializeReaderForTest(reader);
+
+ string[] realInputLines = LoadRealInputLinesWithRollover();
+ Assert.IsTrue(realInputLines.Length > 0, "No rollover input lines were provided.");
+
+ foreach (var line in realInputLines)
+ {
+ bool blockCompleted;
+ reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out blockCompleted);
+ }
+
+ int optoDataCount = GetPrivateField(reader, "optoDataCount");
+ Assert.IsTrue(optoDataCount > 1, "Need at least 2 valid telegrams.");
+
+ reader.TestStartTelegramIx = 0;
+ reader.TestEndTelegramIx = optoDataCount - 1;
+
+ object[] markArgs = { 0, 0 };
+ InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs);
+ InvokePrivate(reader, "DataStreamPostProcessing");
+ InvokePrivate(reader, "PrepareCalculatedChannelData");
+
+ Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart,
+ "Normalized end time should be >= start time");
+ Assert.IsTrue(reader.VolumeLtrEnd >= reader.VolumeLtrStart,
+ "Normalized end volume should be >= start volume");
+ }
+
+ private static void InitializeReaderForTest(GenesisSmartReader reader)
+ {
+ const int channelCount = 3;
+
+ SetPrivateField(reader, "volumeRawExtLast", new double[channelCount]);
+ SetPrivateField(reader, "timestampExtLast", new double[channelCount]);
+
+ SetPrivateField(reader, "lastTimestamp", new double[channelCount]);
+ SetPrivateField(reader, "timestampSec", Enumerable.Repeat(double.NaN, channelCount).ToArray());
+ SetPrivateField(reader, "timestampSec0", Enumerable.Repeat(double.NaN, channelCount).ToArray());
+
+ SetPrivateField(reader, "lastVolumeRaw", new double[channelCount]);
+ SetPrivateField(reader, "volumeLtr", Enumerable.Repeat(double.NaN, channelCount).ToArray());
+ SetPrivateField(reader, "volumeLtr0", Enumerable.Repeat(double.NaN, channelCount).ToArray());
+
+ var optoData = new OptoTelegramRaw[GenesisSmartReader.OptoDataBufferSize];
+ for (int i = 0; i < optoData.Length; i++)
+ optoData[i] = new OptoTelegramRaw();
+
+ SetPrivateField(reader, "optoData", optoData);
+ SetPrivateField(reader, "optoDataCount", 0);
+ SetPrivateField(reader, "toBeFlushed", new OptoTelegramRaw());
+
+ SetPrivateField(reader, "flowDirectionDetection", new FlowDirectionDetection());
+ SetPrivateField(reader, "dataStreamState", DataStreamState.ProcessAndSave);
+ SetPrivateField(reader, "synchronized", false);
+ SetPrivateField(reader, "synchronized2", false);
+ SetPrivateField(reader, "partOfTelegram", string.Empty);
+ SetPrivateField(reader, "startDataProcessing", true);
+
+ reader.TestStartTelegramIx = 0;
+ reader.TestEndTelegramIx = 0;
+
+ }
+
+
+ private static string[] LoadRealInputLines()
+ {
+ return new[]
+ {
+ // group 1
+ "@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
+ "@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD",
+ "@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E",
+
+ // group 2 (next real lines from your log)
+ "@h 1 0 0A1F59C5 00017A44 00115C46 72E1596C 00000400 00001999 7D91B653 7D4F37E7 000191E7 0C 062E4A9D 5332",
+ "@h 2 0 0A1B1FE9 00017B80 00116B57 72B77428 00000400 0000199B 7DC09689 7B570007 000191E7 0C 062E5327 95BE",
+ "@h 3 0 0A1DF1D6 00017EA9 00118038 741F80F4 00000400 00001999 7E58A62F 7CC2C607 000191E7 0C 062E5BAF D40F"
+ };
+ }
+
+ private static string[] LoadRealInputLinesWithRollover()
+ {
+ return new[]
+ {
+ // --- FIRST 3 (before rollover) ---
+ "@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
+ "@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD",
+ "@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E",
+
+ // --- LAST 3 (after rollover / later in log) ---
+ "@h 1 0 00000010 00000020 00000030 00000040 00000400 00000001 00000050 00000060 00000070 0C 00000080 1111",
+ "@h 2 0 00000011 00000021 00000031 00000041 00000400 00000002 00000051 00000061 00000071 0C 00000081 2222",
+ "@h 3 0 00000012 00000022 00000032 00000042 00000400 00000003 00000052 00000062 00000072 0C 00000082 3333",
+ };
+ }
+
+ private static void SetPrivateField(object target, string fieldName, object value)
+ {
+ var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
+ Assert.IsNotNull(field, $"Field '{fieldName}' not found.");
+ field.SetValue(target, value);
+ }
+
+ private static T GetPrivateField(object target, string fieldName)
+ {
+ var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
+ Assert.IsNotNull(field, $"Field '{fieldName}' not found.");
+ return (T)field.GetValue(target);
+ }
+
+ private static object InvokePrivate(object target, string methodName, object[] args = null)
+ {
+ var methods = target.GetType()
+ .GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
+ .Where(m => m.Name == methodName)
+ .ToList();
+
+ Assert.IsTrue(methods.Count > 0, $"Method '{methodName}' not found.");
+
+ var method = methods.First();
+
+ if (args == null)
+ return method.Invoke(target, null);
+
+ var parameters = method.GetParameters();
+ if (parameters.Length != args.Length)
+ {
+ throw new InvalidOperationException(
+ $"Method '{methodName}' expects {parameters.Length} parameters, but {args.Length} were provided.");
+ }
+
+ var invokeArgs = new object[args.Length];
+ Array.Copy(args, invokeArgs, args.Length);
+
+ return method.Invoke(target, invokeArgs);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderChannelAveragingTests.cs b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderChannelAveragingTests.cs
index 7c9302682..0a2a2dccf 100644
--- a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderChannelAveragingTests.cs
+++ b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderChannelAveragingTests.cs
@@ -46,6 +46,18 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
field.SetValue(null, value);
}
+ private static void InvokePrepareCalculatedChannelData(GenesisSmartReader reader)
+ {
+ var method = typeof(GenesisSmartReader).GetMethod(
+ "PrepareCalculatedChannelData",
+ BindingFlags.Instance | BindingFlags.NonPublic);
+
+ if (method == null)
+ throw new MissingMethodException(typeof(GenesisSmartReader).FullName, "PrepareCalculatedChannelData");
+
+ method.Invoke(reader, null);
+ }
+
private static OptoTelegramRaw CreateOptoRecord(int channel, double volumeRawExt, double timestampExt, int counter)
{
return new OptoTelegramRaw
@@ -96,7 +108,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
}
[TestMethod]
- public void SimulatedInterleaved20x3ChannelStream_ShouldComputeStartAndEndVolumeAndTimeCorrectly()
+ public void SimulatedInterleaved20x3ChannelStream_ShouldComputeRecalculatedStartAndEndVolumeAndTimeCorrectly()
{
var reader = CreateReaderWithOptoBuffer();
var optoData = GetPrivateField(reader, "optoData");
@@ -118,15 +130,20 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.TestStartTelegramIx = 2;
reader.TestEndTelegramIx = index - 1;
+ InvokePrepareCalculatedChannelData(reader);
+
Assert.AreEqual(200.0, reader.VolumeLtrStart, 1e-9);
- Assert.AreEqual(217.0, reader.VolumeLtrEnd, 1e-9);
+ Assert.AreEqual(219.0, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(1.0, reader.TimestampSecStart, 1e-9);
- Assert.AreEqual(18.0, reader.TimestampSecEnd, 1e-9);
+ Assert.AreEqual(20.0, reader.TimestampSecEnd, 1e-9);
+
+ Assert.AreEqual(200.0, reader.VolumeLtrStartRaw, 1e-9);
+ Assert.AreEqual(219.0, reader.VolumeLtrEndRaw, 1e-9);
}
[TestMethod]
- public void VolumeLtrEnd_ShouldIgnoreInvalidTrailingRecords_AndUsePreviousValidPerChannelSamples()
+ public void VolumeLtrEnd_ShouldRecalculateShorterChannelToLongestChannelTimeSpan()
{
var reader = CreateReaderWithOptoBuffer();
var optoData = GetPrivateField(reader, "optoData");
@@ -145,7 +162,11 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
index++;
}
- // group 8 ch1 is index 25, group 9 ch1 is index 28
+ // channel 1 loses its last two valid records
+ // valid ch1 becomes: 200..207 with timestamps 1..8
+ // longest channel deltaTime is 9 (channels 0 and 2)
+ // ch1 deltaVolume = 7, deltaTime = 7 => recalculated deltaVolume = 7 * 9 / 7 = 9
+ // recalculated end ch1 = 200 + 9 = 209
optoData[25].Flags = OptoTelegramFlags.InvalidTelegram;
optoData[28].Flags = OptoTelegramFlags.InvalidTelegram;
@@ -153,8 +174,31 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.TestStartTelegramIx = 2;
reader.TestEndTelegramIx = index - 1;
+ InvokePrepareCalculatedChannelData(reader);
+
Assert.AreEqual(200.0, reader.VolumeLtrStart, 1e-9);
- Assert.AreEqual(206.33333333333334, reader.VolumeLtrEnd, 1e-9);
+ Assert.AreEqual(209.0, reader.VolumeLtrEnd, 1e-9);
+
+ Assert.AreEqual(208.33333333333334, reader.VolumeLtrEndRaw, 1e-9);
+
+ Assert.AreEqual(100.0, reader.VolumeLtrStartCh1, 1e-9);
+ Assert.AreEqual(200.0, reader.VolumeLtrStartCh2, 1e-9);
+ Assert.AreEqual(300.0, reader.VolumeLtrStartCh3, 1e-9);
+
+ Assert.AreEqual(109.0, reader.VolumeLtrEndCh1, 1e-9);
+ Assert.AreEqual(209.0, reader.VolumeLtrEndCh2, 1e-9);
+ Assert.AreEqual(309.0, reader.VolumeLtrEndCh3, 1e-9);
+
+ Assert.AreEqual(1.0, reader.TimestampSecStart, 1e-9);
+ Assert.AreEqual(10.0, reader.TimestampSecEnd, 1e-9);
+
+ Assert.AreEqual(1.0, reader.TimestampSecStartCh1, 1e-9);
+ Assert.AreEqual(1.0, reader.TimestampSecStartCh2, 1e-9);
+ Assert.AreEqual(1.0, reader.TimestampSecStartCh3, 1e-9);
+
+ Assert.AreEqual(10.0, reader.TimestampSecEndCh1, 1e-9);
+ Assert.AreEqual(10.0, reader.TimestampSecEndCh2, 1e-9);
+ Assert.AreEqual(10.0, reader.TimestampSecEndCh3, 1e-9);
}
[TestMethod]
@@ -178,8 +222,12 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.TestStartTelegramIx = 2;
reader.TestEndTelegramIx = 8;
+ InvokePrepareCalculatedChannelData(reader);
+
Assert.AreEqual(0.0, reader.VolumeLtrStart, 1e-9);
Assert.AreEqual(0.0, reader.VolumeLtrEnd, 1e-9);
+ Assert.AreEqual(0.0, reader.TimestampSecStart, 1e-9);
+ Assert.AreEqual(0.0, reader.TimestampSecEnd, 1e-9);
}
[TestMethod]
@@ -205,26 +253,66 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.TestStartTelegramIx = 2;
reader.TestEndTelegramIx = index - 1;
+ InvokePrepareCalculatedChannelData(reader);
+
Assert.AreEqual(100.0, reader.VolumeLtrStartCh1, 1e-9);
Assert.AreEqual(200.0, reader.VolumeLtrStartCh2, 1e-9);
Assert.AreEqual(300.0, reader.VolumeLtrStartCh3, 1e-9);
- Assert.AreEqual(117.0, reader.VolumeLtrEndCh1, 1e-9);
- Assert.AreEqual(217.0, reader.VolumeLtrEndCh2, 1e-9);
- Assert.AreEqual(317.0, reader.VolumeLtrEndCh3, 1e-9);
+ Assert.AreEqual(119.0, reader.VolumeLtrEndCh1, 1e-9);
+ Assert.AreEqual(219.0, reader.VolumeLtrEndCh2, 1e-9);
+ Assert.AreEqual(319.0, reader.VolumeLtrEndCh3, 1e-9);
Assert.AreEqual(1.0, reader.TimestampSecStartCh1, 1e-9);
Assert.AreEqual(1.0, reader.TimestampSecStartCh2, 1e-9);
Assert.AreEqual(1.0, reader.TimestampSecStartCh3, 1e-9);
- Assert.AreEqual(18.0, reader.TimestampSecEndCh1, 1e-9);
- Assert.AreEqual(18.0, reader.TimestampSecEndCh2, 1e-9);
- Assert.AreEqual(18.0, reader.TimestampSecEndCh3, 1e-9);
+ Assert.AreEqual(20.0, reader.TimestampSecEndCh1, 1e-9);
+ Assert.AreEqual(20.0, reader.TimestampSecEndCh2, 1e-9);
+ Assert.AreEqual(20.0, reader.TimestampSecEndCh3, 1e-9);
Assert.AreEqual(200.0, reader.VolumeLtrStart, 1e-9);
- Assert.AreEqual(217.0, reader.VolumeLtrEnd, 1e-9);
+ Assert.AreEqual(219.0, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(1.0, reader.TimestampSecStart, 1e-9);
- Assert.AreEqual(18.0, reader.TimestampSecEnd, 1e-9);
+ Assert.AreEqual(20.0, reader.TimestampSecEnd, 1e-9);
+
+ Assert.AreEqual(200.0, reader.VolumeLtrStartRaw, 1e-9);
+ Assert.AreEqual(219.0, reader.VolumeLtrEndRaw, 1e-9);
+ }
+
+ [TestMethod]
+ public void VolumeLtrEnd_ShouldAverageOnlyAvailableChannels()
+ {
+ var reader = CreateReaderWithOptoBuffer();
+ var optoData = GetPrivateField(reader, "optoData");
+
+ int idx = 0;
+
+ for (int i = 0; i < 5; i++)
+ {
+ optoData[idx++] = CreateOptoRecord(0, 10 + i, 100 + i, idx);
+ optoData[idx++] = CreateOptoRecord(1, 20 + i, 100 + i, idx);
+ }
+
+ SetPrivateField(reader, "optoDataCount", idx);
+ reader.TestStartTelegramIx = 1;
+ reader.TestEndTelegramIx = idx - 1;
+
+ InvokePrepareCalculatedChannelData(reader);
+
+ Assert.AreEqual(15.0, reader.VolumeLtrStart, 1e-9);
+ Assert.AreEqual(19.0, reader.VolumeLtrEnd, 1e-9);
+
+ Assert.AreEqual(10.0, reader.VolumeLtrStartCh1, 1e-9);
+ Assert.AreEqual(20.0, reader.VolumeLtrStartCh2, 1e-9);
+ Assert.AreEqual(0.0, reader.VolumeLtrStartCh3, 1e-9);
+
+ Assert.AreEqual(14.0, reader.VolumeLtrEndCh1, 1e-9);
+ Assert.AreEqual(24.0, reader.VolumeLtrEndCh2, 1e-9);
+ Assert.AreEqual(0.0, reader.VolumeLtrEndCh3, 1e-9);
+
+ Assert.AreEqual(100.0, reader.TimestampSecStart, 1e-9);
+ Assert.AreEqual(104.0, reader.TimestampSecEnd, 1e-9);
}
}
}
\ No newline at end of file
diff --git a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderTest.cs b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderTest.cs
index 6db6517e9..01d5f7e74 100644
--- a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderTest.cs
+++ b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderTest.cs
@@ -39,6 +39,18 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
return (T)field.GetValue(target);
}
+ private static void InvokePrepareCalculatedChannelData(GenesisSmartReader reader)
+ {
+ var method = typeof(GenesisSmartReader).GetMethod(
+ "PrepareCalculatedChannelData",
+ BindingFlags.Instance | BindingFlags.NonPublic);
+
+ if (method == null)
+ throw new MissingMethodException(typeof(GenesisSmartReader).FullName, "PrepareCalculatedChannelData");
+
+ method.Invoke(reader, null);
+ }
+
private static void InitializeThreeChannelState(GenesisSmartReader reader)
{
SetPrivateField(reader, "volumeRawExtLast", new double[ChannelCount]);
@@ -66,7 +78,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
Assert.IsTrue(fake.IsOpen);
Assert.IsTrue(fake.OpenCalls >= 1);
Assert.AreEqual(2, fake.DiscardInCalls);
- Assert.AreEqual(1, fake.DiscardOutCalls);
+ Assert.AreEqual(2, fake.DiscardOutCalls);
}
[TestMethod]
@@ -136,15 +148,9 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
}
[DataTestMethod]
- [DataRow(
- "@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
- 0)]
- [DataRow(
- "@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD",
- 1)]
- [DataRow(
- "@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E",
- 2)]
+ [DataRow("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", 0)]
+ [DataRow("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", 1)]
+ [DataRow("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", 2)]
public void ProcessOptoLine_ShouldUpdateExpectedChannel(string line, int expectedChannelIndex)
{
var fake = new FakeSerialDriver();
@@ -156,7 +162,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.optoSerialPort = fake;
InitializeThreeChannelState(reader);
- reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out bool ResetDataBuffer);
+ reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out bool resetDataBuffer);
var volumeRawExtLast = GetPrivateField(reader, "volumeRawExtLast");
var timestampExtLast = GetPrivateField(reader, "timestampExtLast");
@@ -195,7 +201,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
var ev = reader.Run();
Assert.AreEqual(Event.ReadRegisterDone, ev);
- Assert.AreEqual(4.0, reader.WMVolume, 1E-6); // average of (3, 4, 5)
+ Assert.AreEqual(4.0, reader.WMVolume, 1E-6);
Assert.AreEqual(4000, reader.WMPulses);
}
@@ -214,16 +220,16 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
var line =
"@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD";
- reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out bool ResetDataBuffer);
+ reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out bool resetDataBuffer);
int currentTelegramIx = GetPrivateField(reader, "currentTelegramIx");
Assert.AreEqual(0, currentTelegramIx);
}
[DataTestMethod]
- [DataRow( "@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", 0)]
- [DataRow( "@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", 1)]
- [DataRow( "@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", 2)]
+ [DataRow("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", 0)]
+ [DataRow("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", 1)]
+ [DataRow("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", 2)]
public void ProcessOptoLine_ShouldInsertTelegramAndUpdateExpectedChannel(string line, int expectedChannelIndex)
{
var fake = new FakeSerialDriver();
@@ -233,7 +239,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.Initialize();
reader.optoSerialPort = fake;
- reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out bool ResetDataBuffer);
+ reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out bool resetDataBuffer);
var volumeRawExtLast = GetPrivateField(reader, "volumeRawExtLast");
var timestampExtLast = GetPrivateField(reader, "timestampExtLast");
@@ -276,10 +282,8 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
"Inserted telegram TimestampExt should match updated channel cache.");
}
-
-
[TestMethod]
- public void VolumeLtrStart_And_VolumeLtrEnd_ShouldUsePerChannelAverages()
+ public void VolumeLtrStart_And_VolumeLtrEnd_ShouldUsePreparedCachedChannelData()
{
var fake = new FakeSerialDriver();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
@@ -287,14 +291,6 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
var optoData = GetPrivateField(reader, "optoData");
- // Build 18 records: 6 groups of channels 0,1,2
- // Start group average = (10 + 20 + 30)/3 = 20
- // Last 5 per channel:
- // ch0: 11,12,13,14,15 => avg 13
- // ch1: 21,22,23,24,25 => avg 23
- // ch2: 31,32,33,34,35 => avg 33
- // final average = (13 + 23 + 33)/3 = 23
-
int idx = 0;
for (int group = 0; group < 6; group++)
{
@@ -304,11 +300,19 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
}
SetPrivateField(reader, "optoDataCount", 18);
- reader.TestStartTelegramIx = 2; // first full 3-channel group
- reader.TestEndTelegramIx = 17; // last record
+ reader.TestStartTelegramIx = 1;
+ reader.TestEndTelegramIx = 17;
- Assert.AreEqual(20.0, reader.VolumeLtrStart, 1e-9);
- Assert.AreEqual(23.0, reader.VolumeLtrEnd, 1e-9);
+ InvokePrepareCalculatedChannelData(reader);
+
+ Assert.AreEqual((11.0 + 20.0 + 30.0) / 3.0, reader.VolumeLtrStart, 1e-9);
+ Assert.AreEqual((15.0 + 25.0 + 35.0) / 3.0, reader.VolumeLtrEnd, 1e-9);
+
+ Assert.AreEqual(100.0, reader.TimestampSecStart, 1e-9);
+ Assert.AreEqual(105.0, reader.TimestampSecEnd, 1e-9);
+
+ Assert.AreEqual((11.0 + 20.0 + 30.0) / 3.0, reader.VolumeLtrStartRaw, 1e-9);
+ Assert.AreEqual((15.0 + 25.0 + 35.0) / 3.0, reader.VolumeLtrEndRaw, 1e-9);
}
private static OptoTelegramRaw CreateOptoRecord(int channel, double volumeRawExt, double timestampExt)
@@ -321,7 +325,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
iChannel = channel
};
}
-
+
[TestMethod]
public void VolumeLtrEnd_ShouldAverageOnlyAvailableChannels()
{
@@ -333,7 +337,6 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
int idx = 0;
- // only channels 0 and 1
for (int i = 0; i < 5; i++)
{
optoData[idx++] = CreateOptoRecord(0, 10 + i, 100 + i);
@@ -344,10 +347,12 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.TestStartTelegramIx = 1;
reader.TestEndTelegramIx = idx - 1;
- // ch0 avg = 12, ch1 avg = 22 => total avg = 17
- Assert.AreEqual(17.0, reader.VolumeLtrEnd, 1e-9);
+ InvokePrepareCalculatedChannelData(reader);
+
+ Assert.AreEqual(15.5, reader.VolumeLtrStart, 1e-9);
+ Assert.AreEqual(19.0, reader.VolumeLtrEnd, 1e-9);
}
-
+
[TestMethod]
public void ProcessOptoLine_Block_f_h1_h2_h3_f_ShouldResetBuffersOnlyAfterLastF()
{
@@ -361,30 +366,25 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
bool reset;
- // first @f
reader.ProcessOptoLine("@f AA754B 4D0CEE78 5D89", DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen on the first @f.");
- // @h 1
reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen after @h 1.");
- // @h 2
reader.ProcessOptoLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD",
DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen after @h 2.");
- // @h 3
reader.ProcessOptoLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E",
DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen immediately after @h 3.");
- // trailing @f
reader.ProcessOptoLine("@f AA7C01 4D0CFE76 B08F", DataStreamState.ProcessAndSave, out reset);
Assert.IsTrue(reset, "Reset must happen after trailing @f that closes the h1/h2/h3 block.");
}
-
+
[TestMethod]
public void ReadOptoData_FullBlock_ShouldDiscardBuffersAfterClosingF()
{
@@ -399,8 +399,8 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
- fake.Open(); // important
- reader.optoSerialPort = fake; // assign opened fake
+ fake.Open();
+ reader.optoSerialPort = fake;
reader.ResetAfterBlockRepetitions = 1;
var readMethod = typeof(GenesisSmartReader).GetMethod(
@@ -420,7 +420,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
Assert.AreEqual(1, fake.DiscardInCalls, "Input buffer should be discarded once after completed block.");
Assert.AreEqual(1, fake.DiscardOutCalls, "Output buffer should be discarded once after completed block.");
}
-
+
[TestMethod]
public void ProcessOptoLine_LastF_AfterH3_ShouldRequestBufferReset()
{
@@ -431,7 +431,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.Initialize();
reader.optoSerialPort = fake;
reader.ResetAfterBlockRepetitions = 1;
-
+
bool reset;
reader.ProcessOptoLine("@f AA754B 4D0CEE78 5D89", DataStreamState.ProcessAndSave, out reset);
@@ -449,7 +449,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.ProcessOptoLine("@f AA7C01 4D0CFE76 B08F", DataStreamState.ProcessAndSave, out reset);
Assert.IsTrue(reset, "Closing @f after @h 3 must request reset.");
}
-
+
[TestMethod]
public void ProcessOptoLine_LastF_AfterH3_ShouldNotRequestReset_WhenRepetitionCountIsGreaterThanOne()
{
@@ -478,7 +478,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.ProcessOptoLine("@f AA7C01 4D0CFE76 B08F", DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen after the first completed block when repetition count is 3.");
}
-
+
[TestMethod]
public void ProcessOptoLine_ShouldResetOnlyAfterThirdCompletedBlock()
{
@@ -517,9 +517,9 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
Assert.IsTrue(reset, "Reset must happen on third full block.");
}
}
-
+
[TestMethod]
- public void ProcessOptoLine_ShouldResetOnlyAfterThirdCompletedBlock_2()
+ public void ProcessOptoLine_ShouldResetOnlyAfterFifthCompletedBlock()
{
var fake = new FakeSerialDriver();
fake.Open();
@@ -551,11 +551,10 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.ProcessOptoLine("@f AA7C01 4D0CFE76 B08F", DataStreamState.ProcessAndSave, out reset);
if (repetition < 5)
- Assert.IsFalse(reset, "Reset must not happen before third full block.");
+ Assert.IsFalse(reset, "Reset must not happen before fifth full block.");
else
- Assert.IsTrue(reset, "Reset must happen on third full block.");
+ Assert.IsTrue(reset, "Reset must happen on fifth full block.");
}
}
-
}
}
\ No newline at end of file
diff --git a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderThreadedReadTests.cs b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderThreadedReadTests.cs
new file mode 100644
index 000000000..ba0158fe7
--- /dev/null
+++ b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderThreadedReadTests.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Reflection;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using TBF.Rig.RegisterReaders.GenesisRegReader;
+using TBF.Rig.RegisterReaders.GenesisRegReader.common;
+using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
+
+namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
+{
+ [TestClass]
+ public class GenesisSmartReaderThreadedReadTests
+ {
+ private const int ChannelCount = 3;
+
+ private static GenesisCfg CreateCfg()
+ {
+ var cfg = new GenesisCfg(null);
+ cfg.OptoComPortNr = 7;
+ cfg.RfidComPortNr = 8;
+ return cfg;
+ }
+
+ private static void SetPrivateField(object target, string fieldName, object value)
+ {
+ var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
+ if (field == null)
+ throw new MissingFieldException(target.GetType().FullName, fieldName);
+
+ field.SetValue(target, value);
+ }
+
+ private static T GetPrivateField(object target, string fieldName)
+ {
+ var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
+ if (field == null)
+ throw new MissingFieldException(target.GetType().FullName, fieldName);
+
+ return (T)field.GetValue(target);
+ }
+
+ private static bool WaitUntil(Func condition, int timeoutMs = 1500, int pollMs = 20)
+ {
+ var start = Environment.TickCount;
+
+ while (Environment.TickCount - start < timeoutMs)
+ {
+ if (condition())
+ return true;
+
+ Thread.Sleep(pollMs);
+ }
+
+ return condition();
+ }
+
+ private static void InitializeThreeChannelState(GenesisSmartReader reader)
+ {
+ SetPrivateField(reader, "volumeRawExtLast", new double[ChannelCount]);
+ SetPrivateField(reader, "timestampExtLast", new double[ChannelCount]);
+
+ SetPrivateField(reader, "lastVolumeRaw", new double[ChannelCount]);
+ SetPrivateField(reader, "lastTimestamp", new double[ChannelCount]);
+
+ SetPrivateField(reader, "volumeLtr", new[] { 0.0, 0.0, 0.0 });
+ SetPrivateField(reader, "volumeLtr0", new[] { 0.0, 0.0, 0.0 });
+
+ SetPrivateField(reader, "timestampSec", new[] { 0.0, 0.0, 0.0 });
+ SetPrivateField(reader, "timestampSec0", new[] { 0.0, 0.0, 0.0 });
+ }
+
+ [TestMethod]
+ public void Start_ShouldOpenPort_AndStartBackgroundReadLoop()
+ {
+ var fake = new FakeSerialDriver();
+ var reader = new GenesisSmartReader(CreateCfg(), () => fake);
+
+ reader.Initialize();
+ reader.Start();
+
+ Assert.IsTrue(fake.IsOpen);
+ Assert.IsTrue(fake.OpenCalls >= 1);
+
+ var readLoopTask = GetPrivateField(reader, "_readLoopTask");
+ Assert.IsNotNull(readLoopTask);
+ }
+
+ [TestMethod]
+ public void BackgroundReadLoop_ShouldMoveIncomingLinesToQueue()
+ {
+ var fake = new FakeSerialDriver();
+ var reader = new GenesisSmartReader(CreateCfg(), () => fake);
+
+ reader.Initialize();
+ InitializeThreeChannelState(reader);
+ reader.Start();
+
+ fake.EnqueueLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331");
+
+ bool queued = WaitUntil(() =>
+ {
+ var queue = GetPrivateField>(reader, "_receivedLines");
+ return !queue.IsEmpty;
+ });
+
+ Assert.IsTrue(queued, "Expected background task to enqueue received serial line.");
+ }
+
+ [TestMethod]
+ public void RunDeviceBefore_ShouldDrainQueue_AndInsertTelegram()
+ {
+ var fake = new FakeSerialDriver();
+ var reader = new GenesisSmartReader(CreateCfg(), () => fake);
+
+ reader.Initialize();
+ InitializeThreeChannelState(reader);
+ reader.Start();
+
+ fake.EnqueueLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD");
+
+ bool queued = WaitUntil(() =>
+ {
+ var queue = GetPrivateField>(reader, "_receivedLines");
+ return !queue.IsEmpty;
+ });
+
+ Assert.IsTrue(queued, "Expected line to be queued before processing.");
+
+ reader.RunDeviceBefore();
+
+ int optoDataCount = GetPrivateField(reader, "optoDataCount");
+ var optoData = GetPrivateField(reader, "optoData");
+
+ Assert.AreEqual(1, optoDataCount);
+ Assert.AreEqual(1, optoData[0].IChannel());
+ }
+
+ [TestMethod]
+ public void RunDeviceBefore_ShouldDrainAllQueuedLines()
+ {
+ var fake = new FakeSerialDriver();
+ var reader = new GenesisSmartReader(CreateCfg(), () => fake);
+
+ reader.Initialize();
+ InitializeThreeChannelState(reader);
+ reader.Start();
+
+ fake.EnqueueLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331");
+ fake.EnqueueLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD");
+ fake.EnqueueLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E");
+
+ bool queued = WaitUntil(() =>
+ {
+ var queue = GetPrivateField>(reader, "_receivedLines");
+ return !queue.IsEmpty;
+ });
+
+ Assert.IsTrue(queued);
+
+ reader.RunDeviceBefore();
+
+ int optoDataCount = GetPrivateField(reader, "optoDataCount");
+ var queueAfter = GetPrivateField>(reader, "_receivedLines");
+
+ Assert.AreEqual(3, optoDataCount);
+ Assert.IsTrue(queueAfter.IsEmpty, "Queue should be empty after RunDeviceBefore drains it.");
+ }
+
+ [TestMethod]
+ public void StopDataStreamProcessing_ShouldStopReadLoop_AndClosePort()
+ {
+ var fake = new FakeSerialDriver();
+ var reader = new GenesisSmartReader(CreateCfg(), () => fake);
+
+ reader.Initialize();
+ reader.Start();
+
+ Assert.IsTrue(fake.IsOpen);
+
+ reader.StopDataStreamProcessing();
+
+ Assert.IsFalse(fake.IsOpen);
+
+ var cts = GetPrivateField(reader, "_readLoopCts");
+ var task = GetPrivateField(reader, "_readLoopTask");
+
+ Assert.IsNull(cts);
+ Assert.IsNull(task);
+ }
+
+ [TestMethod]
+ public void IncomingLines_ShouldNotBeProcessedUntilRunDeviceBeforeIsCalled()
+ {
+ var fake = new FakeSerialDriver();
+ var reader = new GenesisSmartReader(CreateCfg(), () => fake);
+
+ reader.Initialize();
+ InitializeThreeChannelState(reader);
+ reader.Start();
+
+ fake.EnqueueLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E");
+
+ bool queued = WaitUntil(() =>
+ {
+ var queue = GetPrivateField>(reader, "_receivedLines");
+ return !queue.IsEmpty;
+ });
+
+ Assert.IsTrue(queued);
+
+ int optoDataCountBefore = GetPrivateField(reader, "optoDataCount");
+ Assert.AreEqual(0, optoDataCountBefore, "Background thread should only enqueue, not process.");
+
+ reader.RunDeviceBefore();
+
+ int optoDataCountAfter = GetPrivateField(reader, "optoDataCount");
+ Assert.AreEqual(1, optoDataCountAfter);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TBFTests/TBFTests.csproj b/TBFTests/TBFTests.csproj
index 7dd5a077b..d4ba19ace 100644
--- a/TBFTests/TBFTests.csproj
+++ b/TBFTests/TBFTests.csproj
@@ -108,9 +108,11 @@
+
+