Add overflow volume/timestamp handling and related tests for GenesisSmartReader:
- Extend `CalibrationRecord` constructor to include overflow fields (`OverflowVolumeCm`, `OverflowTimeS`). - Implement additional unit tests in `OptoTelegramRawTest` to validate multi-rollover scenarios and extended calculations. - Add `GenesisSmartReaderThreadedReadTests` to cover threaded read-loop functionality and serial processing. - Enhance logging in `Pump` classes (`TurnOff` methods). - Update project file to include new test files.
This commit is contained in:
parent
d5c8fd1c8f
commit
08dff1272b
@ -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")]
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -332,6 +332,8 @@ namespace TBF.Rig.Danfoss.VLT2800
|
||||
Telegram.UpdateTelegramChecksum(msg);
|
||||
}
|
||||
SendData(msg);
|
||||
|
||||
log.DebugFormat("DONE {0}.TurnOff()", Name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -264,6 +264,8 @@ namespace TBF.Rig.Modbus.PumpFM.DanfossVLT
|
||||
|
||||
modbus.SendMessage(msg, Name);
|
||||
}
|
||||
|
||||
log.DebugFormat("DONE {0}.TurnOff()", Name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
{
|
||||
///
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<int>(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<int>(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<T>(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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<OptoTelegramRaw[]>(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<OptoTelegramRaw[]>(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<OptoTelegramRaw[]>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<double[]>(reader, "volumeRawExtLast");
|
||||
var timestampExtLast = GetPrivateField<double[]>(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<int>(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<double[]>(reader, "volumeRawExtLast");
|
||||
var timestampExtLast = GetPrivateField<double[]>(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<OptoTelegramRaw[]>(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.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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<T>(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<bool> 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<System.Threading.Tasks.Task>(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<System.Collections.Concurrent.ConcurrentQueue<string>>(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<System.Collections.Concurrent.ConcurrentQueue<string>>(reader, "_receivedLines");
|
||||
return !queue.IsEmpty;
|
||||
});
|
||||
|
||||
Assert.IsTrue(queued, "Expected line to be queued before processing.");
|
||||
|
||||
reader.RunDeviceBefore();
|
||||
|
||||
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
|
||||
var optoData = GetPrivateField<OptoTelegramRaw[]>(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<System.Collections.Concurrent.ConcurrentQueue<string>>(reader, "_receivedLines");
|
||||
return !queue.IsEmpty;
|
||||
});
|
||||
|
||||
Assert.IsTrue(queued);
|
||||
|
||||
reader.RunDeviceBefore();
|
||||
|
||||
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
|
||||
var queueAfter = GetPrivateField<System.Collections.Concurrent.ConcurrentQueue<string>>(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<System.Threading.CancellationTokenSource>(reader, "_readLoopCts");
|
||||
var task = GetPrivateField<System.Threading.Tasks.Task>(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<System.Collections.Concurrent.ConcurrentQueue<string>>(reader, "_receivedLines");
|
||||
return !queue.IsEmpty;
|
||||
});
|
||||
|
||||
Assert.IsTrue(queued);
|
||||
|
||||
int optoDataCountBefore = GetPrivateField<int>(reader, "optoDataCount");
|
||||
Assert.AreEqual(0, optoDataCountBefore, "Background thread should only enqueue, not process.");
|
||||
|
||||
reader.RunDeviceBefore();
|
||||
|
||||
int optoDataCountAfter = GetPrivateField<int>(reader, "optoDataCount");
|
||||
Assert.AreEqual(1, optoDataCountAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,9 +108,11 @@
|
||||
<Compile Include="Rig\Output\FileWriters\Enhanced\WriterTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\common\OptoTelegramRawTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\FakeSerialDriver.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\GenesisReaderTests.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\GenesisSmartReaderChannelAveragingTests.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\GenesisSmartReaderChannelTests.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\GenesisSmartReaderTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\GenesisSmartReaderThreadedReadTests.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\DiagnosticLedParserTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\hexLogger\HexFormatterTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\IperlHatProtocol\IperlHatFrameBuilderTests.cs" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user