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:
parent
a34740e838
commit
d5c8fd1c8f
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user