diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRaw.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRaw.cs index 69c0d7e64..54b952346 100644 --- a/TBF/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRaw.cs +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRaw.cs @@ -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; } } } diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs index 4ac2be485..fbeeddcd5 100644 --- a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs @@ -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; } } } \ No newline at end of file diff --git a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderChannelTests.cs b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderChannelTests.cs new file mode 100644 index 000000000..5034ce379 --- /dev/null +++ b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderChannelTests.cs @@ -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(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[])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(_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); + } + } +} \ No newline at end of file diff --git a/TBFTests/TBFTests.csproj b/TBFTests/TBFTests.csproj index ed1f7ef05..7dd5a077b 100644 --- a/TBFTests/TBFTests.csproj +++ b/TBFTests/TBFTests.csproj @@ -109,6 +109,7 @@ +