tbf/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderChannelAveragingTests.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

318 lines
12 KiB
C#

using System;
using System.Reflection;
using Common;
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 GenesisSmartReaderChannelAveragingTests
{
private const int ChannelCount = 3;
[TestInitialize]
public void TestInitialize()
{
SetPrivateStaticField(typeof(GenesisSmartReader), "chenelsSwichCount", ChannelCount);
}
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 void SetPrivateStaticField(Type type, string fieldName, object value)
{
var field = type.GetField(fieldName, BindingFlags.Static | BindingFlags.NonPublic);
if (field == null)
throw new MissingFieldException(type.FullName, fieldName);
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
{
Flags = OptoTelegramFlags.OK,
iChannel = channel,
VolumeRawExt = volumeRawExt,
TimestampExt = timestampExt,
Counter = counter,
DateTime = DateTime.Now
};
}
private static GenesisCfg CreateCfg()
{
var cfg = new GenesisCfg(null);
cfg.OptoComPortNr = 7;
cfg.RfidComPortNr = 8;
cfg.MuxBoardNr = 1;
cfg.Group = 1;
cfg.CommunicationInterface = CommunicationInterface.RFID;
if (cfg.ProcParams != null)
{
cfg.ProcParams.Counting = Counting.Arbitrary;
cfg.ProcParams.FactorLimitLo = 0;
cfg.ProcParams.FactorLimitHi = 65535;
cfg.ProcParams.CalibTarget = 0;
}
return cfg;
}
private static GenesisSmartReader CreateReaderWithOptoBuffer()
{
var reader = new GenesisSmartReader(CreateCfg(), null);
SetPrivateField(reader, "optoData", new OptoTelegramRaw[GenesisSmartReader.OptoDataBufferSize]);
var optoData = GetPrivateField<OptoTelegramRaw[]>(reader, "optoData");
for (int i = 0; i < optoData.Length; i++)
{
optoData[i] = new OptoTelegramRaw();
}
return reader;
}
[TestMethod]
public void SimulatedInterleaved20x3ChannelStream_ShouldComputeRecalculatedStartAndEndVolumeAndTimeCorrectly()
{
var reader = CreateReaderWithOptoBuffer();
var optoData = GetPrivateField<OptoTelegramRaw[]>(reader, "optoData");
int index = 0;
for (int g = 0; g < 20; g++)
{
optoData[index] = CreateOptoRecord(0, 100 + g, 1 + g, index);
index++;
optoData[index] = CreateOptoRecord(1, 200 + g, 1 + g, index);
index++;
optoData[index] = CreateOptoRecord(2, 300 + g, 1 + g, index);
index++;
}
SetPrivateField(reader, "optoDataCount", index);
reader.TestStartTelegramIx = 2;
reader.TestEndTelegramIx = index - 1;
InvokePrepareCalculatedChannelData(reader);
Assert.AreEqual(200.0, reader.VolumeLtrStart, 1e-9);
Assert.AreEqual(219.0, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(1.0, reader.TimestampSecStart, 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_ShouldRecalculateShorterChannelToLongestChannelTimeSpan()
{
var reader = CreateReaderWithOptoBuffer();
var optoData = GetPrivateField<OptoTelegramRaw[]>(reader, "optoData");
int index = 0;
for (int g = 0; g < 10; g++)
{
optoData[index] = CreateOptoRecord(0, 100 + g, 1 + g, index);
index++;
optoData[index] = CreateOptoRecord(1, 200 + g, 1 + g, index);
index++;
optoData[index] = CreateOptoRecord(2, 300 + g, 1 + g, index);
index++;
}
// 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;
SetPrivateField(reader, "optoDataCount", index);
reader.TestStartTelegramIx = 2;
reader.TestEndTelegramIx = index - 1;
InvokePrepareCalculatedChannelData(reader);
Assert.AreEqual(200.0, reader.VolumeLtrStart, 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]
public void VolumeLtrStart_And_End_ShouldBeZero_WhenAllSamplesAreInvalid()
{
var reader = CreateReaderWithOptoBuffer();
var optoData = GetPrivateField<OptoTelegramRaw[]>(reader, "optoData");
for (int i = 0; i < 9; i++)
{
optoData[i] = new OptoTelegramRaw
{
Flags = OptoTelegramFlags.InvalidTelegram,
iChannel = i % 3,
VolumeRawExt = 100 + i,
TimestampExt = 1 + i
};
}
SetPrivateField(reader, "optoDataCount", 9);
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]
public void SimulatedInterleaved20x3ChannelStream_ShouldComputePerChannelStartAndEndValues()
{
var reader = CreateReaderWithOptoBuffer();
var optoData = GetPrivateField<OptoTelegramRaw[]>(reader, "optoData");
int index = 0;
for (int g = 0; g < 20; g++)
{
optoData[index] = CreateOptoRecord(0, 100 + g, 1 + g, index);
index++;
optoData[index] = CreateOptoRecord(1, 200 + g, 1 + g, index);
index++;
optoData[index] = CreateOptoRecord(2, 300 + g, 1 + g, index);
index++;
}
SetPrivateField(reader, "optoDataCount", index);
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(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(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(219.0, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(1.0, reader.TimestampSecStart, 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);
}
}
}