tbf/TBFTests/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRawTest.cs
Michal Buzik 08dff1272b 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.
2026-04-02 09:25:17 +02:00

679 lines
22 KiB
C#

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.StreamingProtocol;
namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.common
{
[TestClass]
public class OptoTelegramRawTest
{
private static CalibrationRecord CreateCalibrationRecord(
int channel = 1,
double volumeCm = 1.234,
double overflowVolumeCm = 8.38860799804687,
double timeS = 12.5,
double overflowTimeS = 65536.0)
{
return new CalibrationRecord
{
Channel = channel,
VolumeCm = volumeCm,
OverflowVolumeCm = overflowVolumeCm,
TimeS = timeS,
OverflowTimeS = overflowTimeS
};
}
[TestMethod]
public void UpdateFromSmart_FirstSample_ShouldInitializeFields()
{
var telegram = new OptoTelegramRaw();
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;
telegram.UpdateFromSmart(data, counter: 7, refFlow: 1.5f, ref lastVolume, ref lastTimestamp);
Assert.AreEqual(1, telegram.IChannel());
Assert.AreEqual(7, telegram.Counter);
Assert.AreEqual(1.5f, telegram.RefFlow);
Assert.AreEqual(OptoTelegramFlags.OK, telegram.Flags);
Assert.AreEqual(2500.0, telegram.VolumeRaw, 1e-9);
Assert.AreEqual(2500.0, telegram.VolumeRawExt, 1e-9);
Assert.AreEqual(2500.0, lastVolume, 1e-9);
Assert.AreEqual(100.0, telegram.Timestamp, 1e-9);
Assert.AreEqual(100.0, telegram.TimestampExt, 1e-9);
Assert.AreEqual(100.0, lastTimestamp, 1e-9);
Assert.AreNotEqual(default(DateTime), telegram.DateTime);
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_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 overflowVolumeCm = 8.38860799804687;
var overflowRaw = overflowVolumeCm * 1000.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);
double expectedVolumeRaw = 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,
overflowVolumeCm: 8.38860799804687,
timeS: 1.0,
overflowTimeS: tsRange);
double previousExtendedVolume = 1000.0;
double previousExtendedTimestamp = tsRange - 0.25;
telegram.UpdateFromSmart(data, counter: 1, refFlow: 0.5f, ref previousExtendedVolume, ref previousExtendedTimestamp);
Assert.AreEqual(1.0, telegram.Timestamp, 1e-9);
Assert.AreEqual(tsRange + 1.0, telegram.TimestampExt, 1e-6);
Assert.AreEqual(telegram.TimestampExt, previousExtendedTimestamp, 1e-6);
}
[TestMethod]
public void SetFlags_ShouldChangeFlags()
{
var telegram = new OptoTelegramRaw();
telegram.SetFlags(OptoTelegramFlags.SyncError);
Assert.AreEqual(OptoTelegramFlags.SyncError, telegram.Flags);
}
[TestMethod]
public void Label_ShouldReturnExpectedText()
{
var telegram = new OptoTelegramRaw();
telegram.Flags = OptoTelegramFlags.OK_TestStart;
Assert.AreEqual("#### start test ####", telegram.Label());
telegram.Flags = OptoTelegramFlags.OK_TestEnd;
Assert.AreEqual("#### end of test ####", telegram.Label());
telegram.Flags = OptoTelegramFlags.OK;
Assert.AreEqual(string.Empty, telegram.Label());
}
[TestMethod]
public void TimestampDec_ShouldConvertCorrectly()
{
var telegram = new OptoTelegramRaw
{
TimestampExt = 8192.0
};
Assert.AreEqual(1.0m, telegram.TimestampDec());
}
[TestMethod]
public void VolumeDelta_ShouldReturnDifferenceAgainstPrevious()
{
var previous = new OptoTelegramRaw { VolumeRawExt = 1000.0 };
var current = new OptoTelegramRaw { VolumeRawExt = 2000.0 };
double scalingFactor = 2.0;
double expectedPrevious = 0.0000625 * scalingFactor * 1000.0;
double expectedCurrent = 0.0000625 * scalingFactor * 2000.0;
double expectedDelta = expectedCurrent - expectedPrevious;
Assert.AreEqual(expectedDelta, current.VolumeDelta(scalingFactor, previous), 1e-12);
}
[TestMethod]
public void UpdateFromStringDummy_InvalidLength_ShouldReturnFalse_AndSetInvalidFlag()
{
var telegram = new OptoTelegramRaw();
bool result = telegram.UpdateFromStringDummy("short");
Assert.IsFalse(result);
Assert.AreEqual(OptoTelegramFlags.InvalidTelegram, telegram.Flags);
}
[TestMethod]
public void UpdateFromStringDummy_ValidFormatButWrongChecksum_ShouldReturnFalse_AndSetInvalidFlag()
{
var telegram = new OptoTelegramRaw();
string dummy = "FFFFFE\t51EA\t0000\t65324E\t0087\tF6319DFF\t00\r\n";
bool result = telegram.UpdateFromStringDummy(dummy);
Assert.IsFalse(result);
Assert.AreEqual(OptoTelegramFlags.InvalidTelegram, telegram.Flags);
}
[TestMethod]
public void ToString_WhenSyncError_ShouldReturnSynchronizationErrorText()
{
var telegram = new OptoTelegramRaw
{
Flags = OptoTelegramFlags.SyncError
};
string text = telegram.ToString(1.0, null);
Assert.AreEqual("Sychronization error", text);
}
[TestMethod]
public void ToString_WhenInvalidTelegram_ShouldReturnInvalidTelegramText()
{
var telegram = new OptoTelegramRaw
{
Flags = OptoTelegramFlags.InvalidTelegram
};
string text = telegram.ToString(1.0, null);
Assert.AreEqual("Invalid telegram", text);
}
[TestMethod]
public void ToString_WhenOk_ShouldContainCounterAndLabel()
{
var telegram = new OptoTelegramRaw
{
Flags = OptoTelegramFlags.OK_TestStart,
DateTime = new DateTime(2024, 1, 1, 10, 11, 12, 123),
Counter = 5,
EmfRaw = 1,
MagneticFieldRaw = 2,
FlowRaw = 3,
VolumeRaw = 4,
Impedance = 5,
Timestamp = 6,
CheckSum = 7,
VolumeRawExt = 1000,
TimestampExt = 8192,
RefFlow = 1.25f
};
OptoTelegramRaw.TestStartTimestampDec = 0m;
string text = telegram.ToString(2.0, null);
Assert.IsTrue(text.Contains("5 :"), "Counter should be present in output.");
Assert.IsTrue(text.Contains("#### start test ####"), "Label should be present in output.");
}
}
}