Refactor GenesisSmartReader with enhanced channel-specific volume and timestamp processing:

- Replace `AverageNullable` with `AverageCachedTime` and introduce `GetCachedVolume`/`GetCachedTime` helpers.
- Add recalibration logic with `_recalculatedStartEndByChannel` and `_rawStartEndByChannel`.
- Implement `PrepareCalculatedChannelData` for refined multi-channel synchronization.
- Add unit tests (`GenesisSmartReaderChannelTests`) for improved coverage and validation of new methods.
- Update `OptoTelegramRaw.Copy` to fix field assignment issues.
This commit is contained in:
Michal Buzik 2026-03-27 15:12:43 +01:00
parent a34740e838
commit d5c8fd1c8f
4 changed files with 695 additions and 181 deletions

View File

@ -352,20 +352,20 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
public void Copy(OptoTelegramRaw optoTelegramRaw)
{
this.Flags = Flags;
this.DateTime = DateTime;
this.RefFlow = RefFlow;
this.Counter = Counter;
this.EmfRaw = EmfRaw;
this.MagneticFieldRaw = MagneticFieldRaw;
this.FlowRaw = FlowRaw;
this.VolumeRaw = VolumeRaw;
this.VolumeRawExt = VolumeRawExt;
this.Impedance = Impedance;
this.Timestamp = Timestamp;
this.TimestampExt = TimestampExt;
this.CheckSum = CheckSum;
this.iChannel = iChannel;
this.Flags = optoTelegramRaw.Flags;
this.DateTime = optoTelegramRaw.DateTime;
this.RefFlow = optoTelegramRaw.RefFlow;
this.Counter = optoTelegramRaw.Counter;
this.EmfRaw = optoTelegramRaw.EmfRaw;
this.MagneticFieldRaw = optoTelegramRaw.MagneticFieldRaw;
this.FlowRaw = optoTelegramRaw.FlowRaw;
this.VolumeRaw = optoTelegramRaw.VolumeRaw;
this.VolumeRawExt = optoTelegramRaw.VolumeRawExt;
this.Impedance = optoTelegramRaw.Impedance;
this.Timestamp = optoTelegramRaw.Timestamp;
this.TimestampExt = optoTelegramRaw.TimestampExt;
this.CheckSum = optoTelegramRaw.CheckSum;
this.iChannel = optoTelegramRaw.iChannel;
}
}
}

View File

@ -551,11 +551,12 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
if (TestStartTelegramIx < 0 || TestStartTelegramIx >= optoDataCount)
return 0;
return AverageNullable(TimeStartPerChannel(optoData, optoDataCount, TestStartTelegramIx));
return AverageCachedTime(_recalculatedStartEndByChannel, 0);
}
}
/// Test end time for metrology in seconds
public double TimestampSecEnd
{
get
@ -563,12 +564,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
if (TestEndTelegramIx < 0 || TestEndTelegramIx >= optoDataCount)
return 0;
return AverageNullable(
TimeEndPerChannel(
optoData,
optoDataCount,
TestEndTelegramIx,
2 * StartEndFilterSamplesCount2 + 1));
return AverageCachedTime(_recalculatedStartEndByChannel, 1);
}
}
@ -582,6 +578,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
TestEndTelegramIx < 0 ||
TestStartTelegramIx >= optoDataCount ||
TestEndTelegramIx >= optoDataCount ||
_recalculatedStartEndByChannel == null ||
TimestampSecEnd < TimestampSecStart;
}
}
@ -603,7 +600,27 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
return data.Sum() / data.Length;
}
public double VolumeLtrStartCh1 => NoSamples ? 0 : GetCachedVolume(_recalculatedStartEndByChannel, 0, 0);
public double VolumeLtrStartCh2 => NoSamples ? 0 : GetCachedVolume(_recalculatedStartEndByChannel, 1, 0);
public double VolumeLtrStartCh3 => NoSamples ? 0 : GetCachedVolume(_recalculatedStartEndByChannel, 2, 0);
public double VolumeLtrEndCh1 => NoSamples ? 0 : GetCachedVolume(_recalculatedStartEndByChannel, 0, 1);
public double VolumeLtrEndCh2 => NoSamples ? 0 : GetCachedVolume(_recalculatedStartEndByChannel, 1, 1);
public double VolumeLtrEndCh3 => NoSamples ? 0 : GetCachedVolume(_recalculatedStartEndByChannel, 2, 1);
public double TimestampSecStartCh1 => NoSamples ? 0 : GetCachedTime(_recalculatedStartEndByChannel, 0, 0);
public double TimestampSecStartCh2 => NoSamples ? 0 : GetCachedTime(_recalculatedStartEndByChannel, 1, 0);
public double TimestampSecStartCh3 => NoSamples ? 0 : GetCachedTime(_recalculatedStartEndByChannel, 2, 0);
public double TimestampSecEndCh1 => NoSamples ? 0 : GetCachedTime(_recalculatedStartEndByChannel, 0, 1);
public double TimestampSecEndCh2 => NoSamples ? 0 : GetCachedTime(_recalculatedStartEndByChannel, 1, 1);
public double TimestampSecEndCh3 => NoSamples ? 0 : GetCachedTime(_recalculatedStartEndByChannel, 2, 1);
public double VolumeLtrStartRaw => NoSamples ? 0 : AverageCachedVolume(_rawStartEndByChannel, 0);
public double VolumeLtrEndRaw => NoSamples ? 0 : AverageCachedVolume(_rawStartEndByChannel, 1);
/// Test start volume for metrology in liters
public double VolumeLtrStart
{
@ -942,6 +959,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
}
DataStreamPostProcessing();
PrepareCalculatedChannelData();
ResetDataBuffer();
// TODO: Enable when calculations completed
@ -2653,11 +2671,50 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return result;
}
private OptoTelegramRaw[][] _rawStartEndByChannel;
private OptoTelegramRaw[][] _recalculatedStartEndByChannel;
private OptoTelegramRaw[][] BuildStartEndByChannel(OptoTelegramRaw[] optoData, int optoDataCount)
{
var grouped = GroupRecordsPerChannel(optoData, 0, optoDataCount - 1, optoDataCount);
var result = new OptoTelegramRaw[ChannelCount][];
private (int channel, double? delta, double? deltaVolume)[] TimeDeltaPerChannel(
for (int ch = 0; ch < ChannelCount; ch++)
{
result[ch] = new OptoTelegramRaw[2];
if (grouped[ch] == null || grouped[ch].Length < 2)
continue;
result[ch][0] = new OptoTelegramRaw();
result[ch][0].Copy(grouped[ch][0]);
result[ch][1] = new OptoTelegramRaw();
result[ch][1].Copy(grouped[ch][grouped[ch].Length - 1]);
}
return result;
}
private void PrepareCalculatedChannelData()
{
if (optoData == null || optoDataCount <= 0)
{
_rawStartEndByChannel = null;
_recalculatedStartEndByChannel = null;
return;
}
_rawStartEndByChannel = BuildStartEndByChannel(optoData, optoDataCount);
_recalculatedStartEndByChannel = RecalculateVolumeAndTimeDeltaPerChannel(optoData, optoDataCount);
}
private (int channel, double? deltaTime, double? deltaVolume)[] TimeDeltaPerChannel(
OptoTelegramRaw[][] grouped)
{
var result = new (int channel, double? delta, double? deltaVolume)[ChannelCount];
var result = new (int channel, double? deltaTime, double? deltaVolume)[ChannelCount];
for (int ch = 0; ch < ChannelCount; ch++)
{
@ -2669,31 +2726,21 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
continue;
}
double minTime = double.MaxValue;
double maxTime = double.MinValue;
var first = records[0];
var last = records[records.Length - 1];
double minVol = double.MaxValue;
double maxVol = double.MinValue;
foreach (var r in records)
{
double t = r.TimestampExt; // TimestampExt is in seconds
double v = r.VolumeRawExt; // VolumeRawExt is in mL
if (t < minTime) minTime = t;
if (t > maxTime) maxTime = t;
if (v < minVol) minVol = v;
if (v > maxVol) maxVol = v;
}
result[ch] = (ch, maxTime - minTime, maxVol - minVol);
result[ch] = (
ch,
last.TimestampExt - first.TimestampExt,
last.VolumeRawExt - first.VolumeRawExt
);
}
return result;
}
private OptoTelegramRaw[][] GroupRecordsPerChannel(
OptoTelegramRaw[] data,
int startIx,
@ -2736,72 +2783,77 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
// convert List[] → array[]
return result.Select(l => l.ToArray()).ToArray();
}
private void RecalculateVolumeAndTimeDeltaPerChannel(OptoTelegramRaw[] optoData, int optoDataCount, int unwrappedIx, double?[] time )
{
OptoTelegramRaw[][] recordByChannel = GroupRecordsPerChannel(
private OptoTelegramRaw[][] RecalculateVolumeAndTimeDeltaPerChannel(
OptoTelegramRaw[] optoData,
int optoDataCount)
{
var recordByChannel = GroupRecordsPerChannel(
optoData,
0,
optoDataCount - 2,
optoDataCount - 1,
optoDataCount);
(int channel, double? deltaTime, double? deltaVolume)[] timeDelta = TimeDeltaPerChannel(recordByChannel);
//Time max delta value
var timeDelta = TimeDeltaPerChannel(recordByChannel);
var result = timeDelta
.Where(x => x.deltaTime.HasValue)
.OrderByDescending(x => x.deltaTime)
.OrderByDescending(x => x.deltaTime.Value)
.FirstOrDefault();
int maxChannel = result.channel;
double? maxValueTime = result.deltaTime;
// optional safety if all null
if (!result.deltaTime.HasValue)
{
maxChannel = -1;
maxValueTime = null;
}
//Recalculate volume
var recalculatedVariablesByChannel = new OptoTelegramRaw[ChannelCount][];
if (maxChannel < 0 || !maxValueTime.HasValue)
return null;
if (recordByChannel[maxChannel] == null || recordByChannel[maxChannel].Length < 2)
return null;
var maxStartRecord = recordByChannel[maxChannel][0];
var maxEndRecord = recordByChannel[maxChannel][recordByChannel[maxChannel].Length - 1];
OptoTelegramRaw[][] recalculatedVariablesByChannel = new OptoTelegramRaw[ChannelCount][];
for (int channel = 0; channel < ChannelCount; channel++)
{
recalculatedVariablesByChannel[channel] = new OptoTelegramRaw[2];
} // [per chanel] [2 - start val, end val]
for (int channel = 0; channel < ChannelCount; channel++)
{
recalculatedVariablesByChannel[channel][0] = new OptoTelegramRaw(); //start val
recalculatedVariablesByChannel[channel][0].Copy( recordByChannel[channel][0]);
recalculatedVariablesByChannel[channel][1] = new OptoTelegramRaw(); //end val
recalculatedVariablesByChannel[channel][1].Copy( recordByChannel[channel][1]);
//set the same time
recalculatedVariablesByChannel[channel][0].TimestampExt = recordByChannel[maxChannel][0].TimestampExt;
recalculatedVariablesByChannel[channel][1].TimestampExt = recordByChannel[maxChannel][1].TimestampExt;
//start, end val - end in case if valid data are not accessible
recalculatedVariablesByChannel[channel][0].VolumeRawExt = recordByChannel[maxChannel][0].VolumeRawExt;
recalculatedVariablesByChannel[channel][1].VolumeRawExt = recordByChannel[maxChannel][1].VolumeRawExt;
}
//recalculation volumes to max Time
for (int channel = 0; channel < ChannelCount; channel++)
{
double? timeKoef = maxValueTime / timeDelta[channel].deltaTime;
double? deltaVolKoef = timeDelta[channel].deltaVolume * timeKoef;
if (recordByChannel[channel] == null || recordByChannel[channel].Length < 2)
continue;
if (deltaVolKoef.HasValue)
var channelStartRecord = recordByChannel[channel][0];
var channelEndRecord = recordByChannel[channel][recordByChannel[channel].Length - 1];
recalculatedVariablesByChannel[channel][0] = new OptoTelegramRaw();
recalculatedVariablesByChannel[channel][1] = new OptoTelegramRaw();
recalculatedVariablesByChannel[channel][0].Copy(channelStartRecord);
recalculatedVariablesByChannel[channel][1].Copy(channelEndRecord);
recalculatedVariablesByChannel[channel][0].TimestampExt = maxStartRecord.TimestampExt;
recalculatedVariablesByChannel[channel][1].TimestampExt = maxEndRecord.TimestampExt;
var channelDeltaTime = timeDelta[channel].deltaTime;
var channelDeltaVolume = timeDelta[channel].deltaVolume;
if (channelDeltaTime.HasValue &&
channelDeltaVolume.HasValue &&
channelDeltaTime.Value != 0)
{
recalculatedVariablesByChannel[channel][1].VolumeRawExt = deltaVolKoef.Value;
double timeCoef = maxValueTime.Value / channelDeltaTime.Value;
double recalculatedDeltaVolume = channelDeltaVolume.Value * timeCoef;
recalculatedVariablesByChannel[channel][1].VolumeRawExt =
recalculatedVariablesByChannel[channel][0].VolumeRawExt + recalculatedDeltaVolume;
}
}
return recalculatedVariablesByChannel;
}
private double?[] TimeStartPerChannel(OptoTelegramRaw[] optoData, int optoDataCount, int unwrappedIx)
{
var result = new double?[chenelsSwichCount];
@ -2867,120 +2919,74 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return result;
}
public double VolumeLtrStartCh1
private static double GetCachedVolume(OptoTelegramRaw[][] data, int channel, int startEndIndex)
{
get
{
if (NoSamples) return 0;
return VolumeStartPerChannel(optoData, optoDataCount, TestStartTelegramIx)[0] ?? 0;
}
if (data == null ||
channel < 0 || channel >= data.Length ||
data[channel] == null ||
startEndIndex < 0 || startEndIndex >= data[channel].Length ||
data[channel][startEndIndex] == null)
return 0;
return data[channel][startEndIndex].VolumeRawExt;
}
public double VolumeLtrStartCh2
private static double GetCachedTime(OptoTelegramRaw[][] data, int channel, int startEndIndex)
{
get
{
if (NoSamples) return 0;
return VolumeStartPerChannel(optoData, optoDataCount, TestStartTelegramIx)[1] ?? 0;
}
if (data == null ||
channel < 0 || channel >= data.Length ||
data[channel] == null ||
startEndIndex < 0 || startEndIndex >= data[channel].Length ||
data[channel][startEndIndex] == null)
return 0;
return data[channel][startEndIndex].TimestampExt;
}
public double VolumeLtrStartCh3
private static double AverageCachedVolume(OptoTelegramRaw[][] data, int startEndIndex)
{
get
double sum = 0;
int count = 0;
if (data == null)
return 0;
for (int ch = 0; ch < ChannelCount; ch++)
{
if (NoSamples) return 0;
return VolumeStartPerChannel(optoData, optoDataCount, TestStartTelegramIx)[2] ?? 0;
if (data[ch] != null &&
data[ch].Length > startEndIndex &&
data[ch][startEndIndex] != null)
{
sum += data[ch][startEndIndex].VolumeRawExt;
count++;
}
}
return count > 0 ? sum / count : 0;
}
public double VolumeLtrEndCh1
private static double AverageCachedTime(OptoTelegramRaw[][] data, int startEndIndex)
{
get
{
if (NoSamples) return 0;
return VolumeEndPerChannel(optoData, optoDataCount, TestEndTelegramIx,
2 * StartEndFilterSamplesCount2 + 1)[0] ?? 0;
}
}
double sum = 0;
int count = 0;
public double VolumeLtrEndCh2
{
get
{
if (NoSamples) return 0;
return VolumeEndPerChannel(optoData, optoDataCount, TestEndTelegramIx,
2 * StartEndFilterSamplesCount2 + 1)[1] ?? 0;
}
}
if (data == null)
return 0;
public double VolumeLtrEndCh3
{
get
for (int ch = 0; ch < ChannelCount; ch++)
{
if (NoSamples) return 0;
return VolumeEndPerChannel(optoData, optoDataCount, TestEndTelegramIx,
2 * StartEndFilterSamplesCount2 + 1)[2] ?? 0;
if (data[ch] != null &&
data[ch].Length > startEndIndex &&
data[ch][startEndIndex] != null)
{
sum += data[ch][startEndIndex].TimestampExt;
count++;
}
}
}
public double TimestampSecStartCh1
{
get
{
if (NoSamples) return 0;
return TimeStartPerChannel(optoData, optoDataCount, TestStartTelegramIx)[0] ?? 0;
}
}
public double TimestampSecStartCh2
{
get
{
if (NoSamples) return 0;
return TimeStartPerChannel(optoData, optoDataCount, TestStartTelegramIx)[1] ?? 0;
}
}
public double TimestampSecStartCh3
{
get
{
if (NoSamples) return 0;
return TimeStartPerChannel(optoData, optoDataCount, TestStartTelegramIx)[2] ?? 0;
}
}
public double TimestampSecEndCh1
{
get
{
if (NoSamples) return 0;
return TimeEndPerChannel(optoData, optoDataCount, TestEndTelegramIx,
2 * StartEndFilterSamplesCount2 + 1)[0] ?? 0;
}
}
public double TimestampSecEndCh2
{
get
{
if (NoSamples) return 0;
return TimeEndPerChannel(optoData, optoDataCount, TestEndTelegramIx,
2 * StartEndFilterSamplesCount2 + 1)[1] ?? 0;
}
}
public double TimestampSecEndCh3
{
get
{
if (NoSamples) return 0;
return TimeEndPerChannel(optoData, optoDataCount, TestEndTelegramIx,
2 * StartEndFilterSamplesCount2 + 1)[2] ?? 0;
}
return count > 0 ? sum / count : 0;
}
}
}

View File

@ -0,0 +1,507 @@
using System;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
{
[TestClass]
public class GenesisSmartReaderChannelTests
{
private GenesisSmartReader _reader;
[TestInitialize]
public void Setup()
{
_reader = new GenesisSmartReader();
}
private static OptoTelegramRaw Rec(
int channel,
double time,
double volume,
OptoTelegramFlags flags = OptoTelegramFlags.OK)
{
return new OptoTelegramRaw
{
iChannel = channel,
TimestampExt = time,
VolumeRawExt = volume,
Flags = flags
};
}
private object InvokePrivate(string methodName, params object[] args)
{
var mi = typeof(GenesisSmartReader).GetMethod(
methodName,
BindingFlags.NonPublic | BindingFlags.Instance);
Assert.IsNotNull(mi, $"Method '{methodName}' was not found.");
return mi.Invoke(_reader, args);
}
private static T GetPrivateField<T>(object instance, string fieldName)
{
var fi = instance.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
Assert.IsNotNull(fi, $"Field '{fieldName}' was not found.");
return (T)fi.GetValue(instance);
}
private static void SetPrivateField(object instance, string fieldName, object value)
{
var fi = instance.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
Assert.IsNotNull(fi, $"Field '{fieldName}' was not found.");
fi.SetValue(instance, value);
}
[TestMethod]
public void GroupRecordsPerChannel_Groups_Valid_Records_By_Channel()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(1, 11, 200),
Rec(2, 12, 300),
Rec(0, 13, 110),
Rec(1, 14, 220),
Rec(2, 15, 330),
};
var grouped = (OptoTelegramRaw[][])InvokePrivate(
"GroupRecordsPerChannel",
(object)input, 0, input.Length - 1, input.Length);
Assert.AreEqual(3, grouped.Length);
Assert.AreEqual(2, grouped[0].Length);
Assert.AreEqual(2, grouped[1].Length);
Assert.AreEqual(2, grouped[2].Length);
Assert.AreEqual(100, grouped[0][0].VolumeRawExt);
Assert.AreEqual(110, grouped[0][1].VolumeRawExt);
Assert.AreEqual(200, grouped[1][0].VolumeRawExt);
Assert.AreEqual(220, grouped[1][1].VolumeRawExt);
Assert.AreEqual(300, grouped[2][0].VolumeRawExt);
Assert.AreEqual(330, grouped[2][1].VolumeRawExt);
}
[TestMethod]
public void GroupRecordsPerChannel_Ignores_Invalid_Records()
{
var input = new[]
{
Rec(0, 10, 100, OptoTelegramFlags.OK),
Rec(1, 11, 200, OptoTelegramFlags.InvalidTelegram),
Rec(2, 12, 300, OptoTelegramFlags.OK),
Rec(0, 13, 110, OptoTelegramFlags.SyncError),
Rec(1, 14, 220, OptoTelegramFlags.OK),
};
var grouped = (OptoTelegramRaw[][])InvokePrivate(
"GroupRecordsPerChannel",
(object)input, 0, input.Length - 1, input.Length);
Assert.AreEqual(1, grouped[0].Length);
Assert.AreEqual(1, grouped[1].Length);
Assert.AreEqual(1, grouped[2].Length);
Assert.AreEqual(100, grouped[0][0].VolumeRawExt);
Assert.AreEqual(220, grouped[1][0].VolumeRawExt);
Assert.AreEqual(300, grouped[2][0].VolumeRawExt);
}
[TestMethod]
public void GroupRecordsPerChannel_Swaps_Start_And_End_When_Reversed()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(1, 11, 200),
Rec(0, 12, 120),
};
var grouped = (OptoTelegramRaw[][])InvokePrivate(
"GroupRecordsPerChannel",
(object)input, 2, 0, input.Length);
Assert.AreEqual(2, grouped[0].Length);
Assert.AreEqual(1, grouped[1].Length);
Assert.AreEqual(0, grouped[2].Length);
}
[TestMethod]
public void GroupRecordsPerChannel_Returns_Empty_For_Invalid_Range()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(1, 11, 200),
};
var grouped = (OptoTelegramRaw[][])InvokePrivate(
"GroupRecordsPerChannel",
(object)input, -1, 1, input.Length);
Assert.AreEqual(3, grouped.Length);
Assert.IsTrue(grouped.All(x => x.Length == 0));
}
[TestMethod]
public void TimeDeltaPerChannel_Computes_First_Last_Deltas()
{
var grouped = new[]
{
new[]
{
Rec(0, 10, 100),
Rec(0, 18, 170),
Rec(0, 15, 120)
},
new[]
{
Rec(1, 20, 500),
Rec(1, 23, 530)
},
new[]
{
Rec(2, 30, 900)
}
};
var result = (ValueTuple<int, double?, double?>[])InvokePrivate(
"TimeDeltaPerChannel",
(object)grouped);
Assert.AreEqual(0, result[0].Item1);
Assert.AreEqual(5, result[0].Item2);
Assert.AreEqual(20, result[0].Item3);
Assert.AreEqual(1, result[1].Item1);
Assert.AreEqual(3, result[1].Item2);
Assert.AreEqual(30, result[1].Item3);
Assert.AreEqual(2, result[2].Item1);
Assert.IsNull(result[2].Item2);
Assert.IsNull(result[2].Item3);
}
[TestMethod]
public void VolumeStartPerChannel_Returns_Last_Seen_Value_Backward_From_Index()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(1, 11, 200),
Rec(0, 12, 120),
Rec(2, 13, 300),
Rec(1, 14, 220),
};
var result = (double?[])InvokePrivate(
"VolumeStartPerChannel",
(object)input, input.Length, 4);
Assert.AreEqual(120, result[0]);
Assert.AreEqual(220, result[1]);
Assert.AreEqual(300, result[2]);
}
[TestMethod]
public void TimeStartPerChannel_Returns_Last_Seen_Time_Backward_From_Index()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(1, 11, 200),
Rec(0, 12, 120),
Rec(2, 13, 300),
Rec(1, 14, 220),
};
var result = (double?[])InvokePrivate(
"TimeStartPerChannel",
(object)input, input.Length, 4);
Assert.AreEqual(12, result[0]);
Assert.AreEqual(14, result[1]);
Assert.AreEqual(13, result[2]);
}
[TestMethod]
public void VolumeEndPerChannel_Averages_Last_N_Samples_Per_Channel()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(1, 11, 200),
Rec(2, 12, 300),
Rec(0, 13, 120),
Rec(1, 14, 220),
Rec(2, 15, 330),
Rec(0, 16, 140),
Rec(1, 17, 240),
Rec(2, 18, 360),
};
var result = (double?[])InvokePrivate(
"VolumeEndPerChannel",
(object)input, input.Length, 8, 2);
Assert.AreEqual((140 + 120) / 2.0, result[0]);
Assert.AreEqual((240 + 220) / 2.0, result[1]);
Assert.AreEqual((360 + 330) / 2.0, result[2]);
}
[TestMethod]
public void TimeEndPerChannel_Averages_Last_N_Samples_Per_Channel()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(1, 11, 200),
Rec(2, 12, 300),
Rec(0, 13, 120),
Rec(1, 14, 220),
Rec(2, 15, 330),
Rec(0, 16, 140),
Rec(1, 17, 240),
Rec(2, 18, 360),
};
var result = (double?[])InvokePrivate(
"TimeEndPerChannel",
(object)input, input.Length, 8, 2);
Assert.AreEqual((16 + 13) / 2.0, result[0]);
Assert.AreEqual((17 + 14) / 2.0, result[1]);
Assert.AreEqual((18 + 15) / 2.0, result[2]);
}
[TestMethod]
public void VolumeStartPerChannel_Returns_All_Nulls_For_OutOfRange_Index()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(1, 11, 200),
Rec(2, 12, 300),
};
var result = (double?[])InvokePrivate(
"VolumeStartPerChannel",
(object)input, input.Length, -1);
Assert.IsNull(result[0]);
Assert.IsNull(result[1]);
Assert.IsNull(result[2]);
}
[TestMethod]
public void TimeEndPerChannel_Uses_One_Sample_When_SamplesPerChannel_Is_Zero()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(1, 11, 200),
Rec(2, 12, 300),
Rec(0, 13, 120),
Rec(1, 14, 220),
Rec(2, 15, 330),
};
var result = (double?[])InvokePrivate(
"TimeEndPerChannel",
(object)input, input.Length, 5, 0);
Assert.AreEqual(13, result[0]);
Assert.AreEqual(14, result[1]);
Assert.AreEqual(15, result[2]);
}
[TestMethod]
public void Precreated_OptoData_Private_Methods_Read_Correct_Values()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(1, 11, 200),
Rec(2, 12, 300),
Rec(0, 13, 120),
Rec(1, 14, 220),
Rec(2, 15, 330),
Rec(0, 16, 140),
Rec(1, 17, 240),
Rec(2, 18, 360),
};
SetPrivateField(_reader, "optoData", input);
SetPrivateField(_reader, "optoDataCount", input.Length);
_reader.TestStartTelegramIx = 5;
_reader.TestEndTelegramIx = 8;
Assert.AreEqual(input.Length, GetPrivateField<int>(_reader, "optoDataCount"));
var startVolumes = (double?[])InvokePrivate(
"VolumeStartPerChannel",
(object)input, input.Length, 5);
var endVolumes = (double?[])InvokePrivate(
"VolumeEndPerChannel",
(object)input, input.Length, 8, 2 * GenesisSmartReader.StartEndFilterSamplesCount2 + 1);
var startTimes = (double?[])InvokePrivate(
"TimeStartPerChannel",
(object)input, input.Length, 5);
var endTimes = (double?[])InvokePrivate(
"TimeEndPerChannel",
(object)input, input.Length, 8, 2 * GenesisSmartReader.StartEndFilterSamplesCount2 + 1);
Assert.AreEqual(120, startVolumes[0]);
Assert.AreEqual(220, startVolumes[1]);
Assert.AreEqual(330, startVolumes[2]);
Assert.AreEqual(13, startTimes[0]);
Assert.AreEqual(14, startTimes[1]);
Assert.AreEqual(15, startTimes[2]);
Assert.AreEqual((140 + 120 + 100) / 3.0, endVolumes[0]);
Assert.AreEqual((240 + 220 + 200) / 3.0, endVolumes[1]);
Assert.AreEqual((360 + 330 + 300) / 3.0, endVolumes[2]);
Assert.AreEqual((16 + 13 + 10) / 3.0, endTimes[0]);
Assert.AreEqual((17 + 14 + 11) / 3.0, endTimes[1]);
Assert.AreEqual((18 + 15 + 12) / 3.0, endTimes[2]);
}
[TestMethod]
public void Public_Channel_Properties_Default_To_Zero_When_NoSamples_Is_True()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(1, 11, 200),
Rec(2, 12, 300),
};
SetPrivateField(_reader, "optoData", input);
SetPrivateField(_reader, "optoDataCount", input.Length);
_reader.TestStartTelegramIx = 5;
_reader.TestEndTelegramIx = 8;
Assert.IsTrue(_reader.NoSamples);
Assert.AreEqual(0, _reader.VolumeLtrStartCh1);
Assert.AreEqual(0, _reader.VolumeLtrStartCh2);
Assert.AreEqual(0, _reader.VolumeLtrStartCh3);
Assert.AreEqual(0, _reader.VolumeLtrEndCh1);
Assert.AreEqual(0, _reader.VolumeLtrEndCh2);
Assert.AreEqual(0, _reader.VolumeLtrEndCh3);
Assert.AreEqual(0, _reader.TimestampSecStartCh1);
Assert.AreEqual(0, _reader.TimestampSecStartCh2);
Assert.AreEqual(0, _reader.TimestampSecStartCh3);
Assert.AreEqual(0, _reader.TimestampSecEndCh1);
Assert.AreEqual(0, _reader.TimestampSecEndCh2);
Assert.AreEqual(0, _reader.TimestampSecEndCh3);
}
[TestMethod]
public void Copy_Should_Copy_From_Source_Record()
{
var source = Rec(2, 123.5, 456.7, OptoTelegramFlags.OK_TestEnd);
source.Counter = 77;
source.RefFlow = 9.5f;
source.FlowRaw = 111;
source.VolumeRaw = 222;
source.Impedance = 333;
source.CheckSum = 44;
var target = new OptoTelegramRaw();
target.Copy(source);
Assert.AreEqual(source.iChannel, target.iChannel);
Assert.AreEqual(source.TimestampExt, target.TimestampExt);
Assert.AreEqual(source.VolumeRawExt, target.VolumeRawExt);
Assert.AreEqual(source.Flags, target.Flags);
Assert.AreEqual(source.Counter, target.Counter);
Assert.AreEqual(source.RefFlow, target.RefFlow);
Assert.AreEqual(source.FlowRaw, target.FlowRaw);
Assert.AreEqual(source.VolumeRaw, target.VolumeRaw);
Assert.AreEqual(source.Impedance, target.Impedance);
Assert.AreEqual(source.CheckSum, target.CheckSum);
}
[TestMethod]
public void RecalculateVolumeAndTimeDeltaPerChannel_Returns_Recalculated_Output()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(0, 20, 200),
Rec(1, 10, 500),
Rec(1, 30, 560),
Rec(2, 10, 1000),
Rec(2, 15, 1050),
};
var result = (OptoTelegramRaw[][])InvokePrivate(
"RecalculateVolumeAndTimeDeltaPerChannel",
(object)input,
input.Length);
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Length);
for (int ch = 0; ch < 3; ch++)
{
Assert.IsNotNull(result[ch]);
Assert.AreEqual(2, result[ch].Length);
Assert.IsNotNull(result[ch][0]);
Assert.IsNotNull(result[ch][1]);
Assert.AreEqual(10, result[ch][0].TimestampExt, 0.0001);
Assert.AreEqual(30, result[ch][1].TimestampExt, 0.0001);
}
Assert.AreEqual(100, result[0][0].VolumeRawExt, 0.0001);
Assert.AreEqual(300, result[0][1].VolumeRawExt, 0.0001);
Assert.AreEqual(500, result[1][0].VolumeRawExt, 0.0001);
Assert.AreEqual(560, result[1][1].VolumeRawExt, 0.0001);
Assert.AreEqual(1000, result[2][0].VolumeRawExt, 0.0001);
Assert.AreEqual(1200, result[2][1].VolumeRawExt, 0.0001);
}
[TestMethod]
public void RecalculateVolumeAndTimeDeltaPerChannel_Returns_Null_When_No_Channel_Has_Two_Records()
{
var input = new[]
{
Rec(0, 10, 100),
Rec(1, 20, 200),
Rec(2, 30, 300),
};
var result = (OptoTelegramRaw[][])InvokePrivate(
"RecalculateVolumeAndTimeDeltaPerChannel",
(object)input,
input.Length);
Assert.IsNull(result);
}
}
}

View File

@ -109,6 +109,7 @@
<Compile Include="Rig\RegisterReaders\GenesisRegReader\common\OptoTelegramRawTest.cs" />
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\FakeSerialDriver.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\iPerlASICReader\communication\C4\diagnosticLed\DiagnosticLedParserTest.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\hexLogger\HexFormatterTest.cs" />