diff --git a/TBF/Properties/AssemblyInfo.cs b/TBF/Properties/AssemblyInfo.cs index e6d3138b8..65a82848c 100644 --- a/TBF/Properties/AssemblyInfo.cs +++ b/TBF/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("3.9.3055.1")] -[assembly: AssemblyFileVersion("3.9.3055.1")] +[assembly: AssemblyVersion("3.9.3056.1")] +[assembly: AssemblyFileVersion("3.9.3056.1")] diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs index d6f1b0832..6eda98542 100644 --- a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs @@ -1299,6 +1299,15 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations void DataStreamPostProcessing() { PrepareCalculatedChannelData(); + try + { + log.DebugFormat("DataStreamPostProcessing() - harcoded call GetQ3Calibration(200.0, 120.0, 15625.0);"); + GetQ3Calibration(200.0, 120.0, 15625.0); + } + catch (Exception ex) + { + log.Error($"DataStreamPostProcessing -- Q3 CALIBRATION -- failed: {ex}"); + } } /// @@ -3886,51 +3895,86 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations public void GetQ3Calibration(double refVolume, double refTime, double initCalibFactor) { + log.Debug("=== Q3 CALIBRATION START ==="); + isQ3CalibValid = false; q3Calib = 0.0; - if (_rawStartEndByChannel == null || NoSamples) + log.Debug($"Inputs: refVolume={refVolume}, refTime={refTime}, initCalibFactor={initCalibFactor}"); + + if (_rawStartEndByChannel == null) + { + log.Debug("EXIT: _rawStartEndByChannel is NULL"); return; + } + + if (NoSamples) + { + log.Debug("EXIT: NoSamples == true"); + return; + } if (refVolume <= 0 || refTime <= 0 || initCalibFactor <= 0) + { + log.Debug("EXIT: Invalid input values (<= 0)"); return; + } OptoTelegramRaw[][] recalculatedVariablesByChannel = new OptoTelegramRaw[ChannelCount][]; + for (int channel = 0; channel < ChannelCount; channel++) { recalculatedVariablesByChannel[channel] = new OptoTelegramRaw[2]; } + log.Debug($"ChannelCount={ChannelCount}"); + for (int iChannel = 0; iChannel < ChannelCount; iChannel++) { - if (_rawStartEndByChannel[iChannel] == null || _rawStartEndByChannel[iChannel].Length < 2) - continue; + var channelData = _rawStartEndByChannel[iChannel]; - var channelStartRecord = _rawStartEndByChannel[iChannel][0]; - var channelEndRecord = _rawStartEndByChannel[iChannel][1]; - - if (channelStartRecord == null || channelEndRecord == null) + if (channelData == null || channelData.Length < 2) + { + log.Debug($"Channel {iChannel}: SKIPPED (no data)"); continue; + } + + var start = channelData[0]; + var end = channelData[1]; + + if (start == null || end == null) + { + log.Debug($"Channel {iChannel}: SKIPPED (null records)"); + continue; + } recalculatedVariablesByChannel[iChannel][0] = new OptoTelegramRaw(); recalculatedVariablesByChannel[iChannel][1] = new OptoTelegramRaw(); - recalculatedVariablesByChannel[iChannel][0].Copy(channelStartRecord); - recalculatedVariablesByChannel[iChannel][1].Copy(channelEndRecord); + recalculatedVariablesByChannel[iChannel][0].Copy(start); + recalculatedVariablesByChannel[iChannel][1].Copy(end); - double channelDeltaTime = - channelEndRecord.TimestampExt - channelStartRecord.TimestampExt; + double deltaTime = end.TimestampExt - start.TimestampExt; + double deltaVolume = end.VolumeRawExt - start.VolumeRawExt; - double channelDeltaVolume = - channelEndRecord.VolumeRawExt - channelStartRecord.VolumeRawExt; + log.Debug($"Channel {iChannel}:"); + log.Debug($" Start: T={start.TimestampExt}, V={start.VolumeRawExt}"); + log.Debug($" End: T={end.TimestampExt}, V={end.VolumeRawExt}"); + log.Debug($" Delta: dT={deltaTime}, dV={deltaVolume}"); - if (channelDeltaTime > 0) + if (deltaTime > 0) { - double timeCoef = refTime / channelDeltaTime; - double recalculatedDeltaVolume = channelDeltaVolume * timeCoef; + double timeCoef = refTime / deltaTime; + double recalculatedDeltaVolume = deltaVolume * timeCoef; recalculatedVariablesByChannel[iChannel][1].VolumeRawExt = recalculatedVariablesByChannel[iChannel][0].VolumeRawExt + recalculatedDeltaVolume; + + log.Debug($" Recalc: coef={timeCoef}, new dV={recalculatedDeltaVolume}"); + } + else + { + log.Debug($" WARNING: deltaTime <= 0 → no recalculation"); } recalculatedVariablesByChannel[iChannel][0].TimestampExt = 0; @@ -3938,29 +3982,52 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations } double scaling = 15625.0; + log.Debug($"Scaling={scaling}"); if (scaling <= 0) + { + log.Debug("EXIT: scaling <= 0"); return; + } double avgRawVolume = AverageCachedVolume(recalculatedVariablesByChannel, 0); + log.Debug($"AverageCachedVolume={avgRawVolume}"); if (avgRawVolume <= 0) + { + log.Debug("EXIT: avgRawVolume <= 0"); return; + } double measuredVolume = avgRawVolume / scaling; + log.Debug($"MeasuredVolume={measuredVolume}"); if (measuredVolume <= 0) + { + log.Debug("EXIT: measuredVolume <= 0"); return; + } q3Calib = (refVolume / measuredVolume) * initCalibFactor; - double diffPercent = Math.Abs((q3Calib - initCalibFactor) / initCalibFactor) * 100.0; + log.Debug($"Q3Calib (raw)={q3Calib}"); + + double diffPercent = + Math.Abs((q3Calib - initCalibFactor) / initCalibFactor) * 100.0; + + log.Debug($"DiffPercent={diffPercent}%"); + isQ3CalibValid = diffPercent <= 5.0; + log.Debug($"Validation: {(isQ3CalibValid ? "VALID" : "INVALID")}"); + if (!isQ3CalibValid) { q3Calib = 0.0; + log.Debug("Q3Calib reset to 0 due to invalid result"); } + + log.Debug("=== Q3 CALIBRATION END ==="); } } diff --git a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisReaderTests.cs b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisReaderTests.cs index 4964e79c7..cf2b272c4 100644 --- a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisReaderTests.cs +++ b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisReaderTests.cs @@ -3,136 +3,237 @@ using System.Linq; using System.Reflection; using Microsoft.VisualStudio.TestTools.UnitTesting; using TBF.Rig.RegisterReaders.GenesisRegReader.common; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication; using TBF.Rig.RegisterReaders.GenesisRegReader.implementations; +using TBF.Rig.Sequences; +using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common; namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations { [TestClass] public class GenesisReaderTests { + [TestMethod] - public void PreparedSimulation_ShouldCalculate_StartEndVolumes_AndTimes() + public void Debug_ProcessOptoLine_FormatVariants() { var reader = new GenesisSmartReader(); InitializeReaderForTest(reader); - // Let the production/test helper prepare valid internal start/end data - InvokePrivate(reader, "PrepareCalculatedChannelDataForTest", new object[] { true }); + string[] variants = + { + "@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", + "@he 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", + "@he\t1\t0\t0A1F59C4\t00017A43\t00115C45\t72E1596B\t00000400\t00001998\t7D91B652\t7D4F37E6\t000191E6\t0C\t062E4A9C\t5331", + "@he \t1\t0\t0A1F59C4\t00017A43\t00115C45\t72E1596B\t00000400\t00001998\t7D91B652\t7D4F37E6\t000191E6\t0C\t062E4A9C\t5331" + }; - Assert.IsFalse(reader.NoSamples, "Simulation did not prepare valid sample data."); + for (int i = 0; i < variants.Length; i++) + { + bool blockCompleted = false; + reader.ProcessOptoLine(variants[i], DataStreamState.ProcessAndSave, out blockCompleted); + + int optoDataCount = GetPrivateField(reader, "optoDataCount"); + + Console.WriteLine($"Variant {i}: {variants[i]}"); + Console.WriteLine($" blockCompleted={blockCompleted}"); + Console.WriteLine($" optoDataCount={optoDataCount}"); + Console.WriteLine("--------------------------------"); + } + + Assert.Fail("Inspect which variant, if any, is accepted."); + } + + [TestMethod] + public void RealInput_ShouldCalculate_StartEndVolumes_AndTimes() + { + var reader = new GenesisSmartReader(); + InitializeReaderForTest(reader); + + string[] realInputLines = LoadRealInputLines(); + Assert.IsTrue(realInputLines.Length > 0, "No test input lines were provided."); + + int processedCount = 0; + int firstValidIx = -1; + int lastValidIx = -1; + + Console.WriteLine("=== BEGIN INPUT PARSING ==="); + + for (int i = 0; i < realInputLines.Length; i++) + { + string line = realInputLines[i]; + + bool blockCompleted = false; + Exception parseException = null; + + try + { + reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out blockCompleted); + } + catch (Exception ex) + { + parseException = ex; + } + + int optoDataCount = GetPrivateField(reader, "optoDataCount"); + + Console.WriteLine($"[{i}] line = {line}"); + Console.WriteLine($"[{i}] blockCompleted = {blockCompleted}"); + Console.WriteLine($"[{i}] optoDataCount = {optoDataCount}"); + + if (parseException != null) + { + Console.WriteLine($"[{i}] EXCEPTION = {parseException.GetType().Name}: {parseException.Message}"); + } + + if (optoDataCount > processedCount) + { + if (firstValidIx < 0) + firstValidIx = processedCount; + + lastValidIx = optoDataCount - 1; + processedCount = optoDataCount; + + Console.WriteLine( + $"[{i}] ACCEPTED -> processedCount={processedCount}, firstValidIx={firstValidIx}, lastValidIx={lastValidIx}"); + } + else + { + Console.WriteLine($"[{i}] NOT ACCEPTED"); + } + + Console.WriteLine("--------------------------------------------------"); + } + + Console.WriteLine("=== END INPUT PARSING ==="); + Console.WriteLine($"Final processedCount = {processedCount}"); + Console.WriteLine($"Final firstValidIx = {firstValidIx}"); + Console.WriteLine($"Final lastValidIx = {lastValidIx}"); + + if (processedCount <= 0) + { + Assert.Fail( + "No valid calibration telegrams were parsed.\n" + + "Check the following:\n" + + "1. exact telegram prefix (for example @h vs @he)\n" + + "2. exact separators (spaces vs tabs)\n" + + "3. exact CRC / checksum\n" + + "4. whether ProcessOptoLine expects a complete multi-line block format\n" + + "See test output for per-line diagnostics."); + } + + reader.TestStartTelegramIx = firstValidIx; + reader.TestEndTelegramIx = lastValidIx; + + Console.WriteLine("=== BEFORE POST PROCESSING ==="); + Console.WriteLine($"TestStartTelegramIx = {reader.TestStartTelegramIx}"); + Console.WriteLine($"TestEndTelegramIx = {reader.TestEndTelegramIx}"); + + object[] markArgs = { 0, 0 }; + + try + { + InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs); + Console.WriteLine("AddTestStartEndMarksToData OK"); + + InvokePrivate(reader, "DataStreamPostProcessing"); + Console.WriteLine("DataStreamPostProcessing OK"); + + InvokePrivate(reader, "PrepareCalculatedChannelData"); + Console.WriteLine("PrepareCalculatedChannelData OK"); + } + catch (Exception ex) + { + Assert.Fail( + "Post-processing failed.\n" + + $"Exception: {ex.GetType().Name}: {ex.Message}\n" + + $"StackTrace:\n{ex.StackTrace}"); + } + + Console.WriteLine("=== CALCULATED VALUES ==="); + Console.WriteLine($"NoSamples = {reader.NoSamples}"); + Console.WriteLine($"VolumeLtrStart = {reader.VolumeLtrStart}"); + Console.WriteLine($"VolumeLtrEnd = {reader.VolumeLtrEnd}"); + Console.WriteLine($"TimestampSecStart = {reader.TimestampSecStart}"); + Console.WriteLine($"TimestampSecEnd = {reader.TimestampSecEnd}"); + + Assert.IsFalse(double.IsNaN(reader.VolumeLtrStart), "VolumeLtrStart is NaN"); + Assert.IsFalse(double.IsNaN(reader.VolumeLtrEnd), "VolumeLtrEnd is NaN"); + Assert.IsFalse(double.IsNaN(reader.TimestampSecStart), "TimestampSecStart is NaN"); + Assert.IsFalse(double.IsNaN(reader.TimestampSecEnd), "TimestampSecEnd is NaN"); + + Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart, + $"Timestamp ordering invalid: start={reader.TimestampSecStart}, end={reader.TimestampSecEnd}"); + + Assert.IsTrue(reader.VolumeLtrEnd >= reader.VolumeLtrStart, + $"Volume ordering invalid: start={reader.VolumeLtrStart}, end={reader.VolumeLtrEnd}"); + + // When parser input is finally correct, replace these expected values: + const double expectedVolumeStart = 0.0; + const double expectedVolumeEnd = 0.0; + const double expectedTimeStart = 0.0; + const double expectedTimeEnd = 0.0; + const double tolerance = 0.000001; + + Console.WriteLine("=== EXPECTED VS ACTUAL ==="); + Console.WriteLine($"expectedVolumeStart = {expectedVolumeStart}, actual = {reader.VolumeLtrStart}"); + Console.WriteLine($"expectedVolumeEnd = {expectedVolumeEnd}, actual = {reader.VolumeLtrEnd}"); + Console.WriteLine($"expectedTimeStart = {expectedTimeStart}, actual = {reader.TimestampSecStart}"); + Console.WriteLine($"expectedTimeEnd = {expectedTimeEnd}, actual = {reader.TimestampSecEnd}"); + + Assert.AreEqual(expectedVolumeStart, reader.VolumeLtrStart, tolerance, "VolumeLtrStart mismatch"); + Assert.AreEqual(expectedVolumeEnd, reader.VolumeLtrEnd, tolerance, "VolumeLtrEnd mismatch"); + Assert.AreEqual(expectedTimeStart, reader.TimestampSecStart, tolerance, "TimestampSecStart mismatch"); + Assert.AreEqual(expectedTimeEnd, reader.TimestampSecEnd, tolerance, "TimestampSecEnd mismatch"); + } + + [TestMethod] + public void RealInput_ShouldSupport_RolloverNormalization() + { + var reader = new GenesisSmartReader(); + InitializeReaderForTest(reader); + + string[] realInputLines = LoadRealInputLinesWithRollover(); + Assert.IsTrue(realInputLines.Length > 0, "No rollover input lines were provided."); + + for (int i = 0; i < realInputLines.Length; i++) + { + string line = realInputLines[i]; + + bool blockCompleted; + reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out blockCompleted); + + int optoDataCount = GetPrivateField(reader, "optoDataCount"); + + Console.WriteLine($"[{i}] line={line}"); + Console.WriteLine($"[{i}] optoDataCount={optoDataCount}, blockCompleted={blockCompleted}"); + } + + int finalOptoDataCount = GetPrivateField(reader, "optoDataCount"); + + Assert.IsTrue( + finalOptoDataCount > 1, + "Need at least 2 valid telegrams. Check exact telegram format / CRC in LoadRealInputLinesWithRollover()."); + + reader.TestStartTelegramIx = 0; + reader.TestEndTelegramIx = finalOptoDataCount - 1; + + object[] markArgs = { 0, 0 }; + InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs); + InvokePrivate(reader, "DataStreamPostProcessing"); + InvokePrivate(reader, "PrepareCalculatedChannelData"); Console.WriteLine($"VolumeLtrStart={reader.VolumeLtrStart}"); Console.WriteLine($"VolumeLtrEnd={reader.VolumeLtrEnd}"); Console.WriteLine($"TimestampSecStart={reader.TimestampSecStart}"); Console.WriteLine($"TimestampSecEnd={reader.TimestampSecEnd}"); - Assert.IsTrue(reader.VolumeLtrEnd >= reader.VolumeLtrStart, - "VolumeLtrEnd should be >= VolumeLtrStart."); - Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart, - "TimestampSecEnd should be >= TimestampSecStart."); - - Assert.IsFalse(double.IsNaN(reader.VolumeLtrStart), "VolumeLtrStart is NaN"); - Assert.IsFalse(double.IsNaN(reader.VolumeLtrEnd), "VolumeLtrEnd is NaN"); - Assert.IsFalse(double.IsNaN(reader.TimestampSecStart), "TimestampSecStart is NaN"); - Assert.IsFalse(double.IsNaN(reader.TimestampSecEnd), "TimestampSecEnd is NaN"); - } - - [TestMethod] - public void PreparedSimulation_ShouldSupport_NormalizedStartEndOrdering() - { - var reader = new GenesisSmartReader(); - InitializeReaderForTest(reader); - - InvokePrivate(reader, "PrepareCalculatedChannelDataForTest", new object[] { true }); - - Assert.IsFalse(reader.NoSamples, "Simulation did not prepare valid sample data."); - Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart, "Normalized end time should be >= start time"); Assert.IsTrue(reader.VolumeLtrEnd >= reader.VolumeLtrStart, "Normalized end volume should be >= start volume"); } - [TestMethod] - public void PreparedSimulation_Q3Calibration_ShouldReturnNonNegativeValue() - { - var reader = new GenesisSmartReader(); - InitializeReaderForTest(reader); - - InvokePrivate(reader, "PrepareCalculatedChannelDataForTest", new object[] { true }); - - Assert.IsFalse(reader.NoSamples, "Simulation did not prepare valid sample data."); - - reader.GetQ3Calibration(refVolume: 1.0, refTime: 120.0, initCalibFactor: 15625.0); - - Console.WriteLine($"Q3CalibValid={reader.Q3CalibValid}"); - Console.WriteLine($"Q3CalibValue={reader.Q3CalibValue}"); - - Assert.IsTrue(reader.Q3CalibValue >= 0.0, "Q3CalibValue should be non-negative."); - } - - [TestMethod] - public void Q3Calibration_ShouldReturnInvalid_WhenRefVolumeIsZero() - { - var reader = new GenesisSmartReader(); - InitializeReaderForTest(reader); - - InvokePrivate(reader, "PrepareCalculatedChannelDataForTest", new object[] { true }); - - Assert.IsFalse(reader.NoSamples, "Simulation did not prepare valid sample data."); - - reader.GetQ3Calibration(refVolume: 0.0, refTime: 120.0, initCalibFactor: 15625.0); - - Assert.IsFalse(reader.Q3CalibValid); - Assert.AreEqual(0.0, reader.Q3CalibValue, 0.000001); - } - - [TestMethod] - public void Q3Calibration_ShouldReturnInvalid_WhenRefTimeIsZero() - { - var reader = new GenesisSmartReader(); - InitializeReaderForTest(reader); - - InvokePrivate(reader, "PrepareCalculatedChannelDataForTest", new object[] { true }); - - Assert.IsFalse(reader.NoSamples, "Simulation did not prepare valid sample data."); - - reader.GetQ3Calibration(refVolume: 1.0, refTime: 0.0, initCalibFactor: 15625.0); - - Assert.IsFalse(reader.Q3CalibValid); - Assert.AreEqual(0.0, reader.Q3CalibValue, 0.000001); - } - - [TestMethod] - public void Q3Calibration_ShouldReturnInvalid_WhenInitCalibFactorIsZero() - { - var reader = new GenesisSmartReader(); - InitializeReaderForTest(reader); - - InvokePrivate(reader, "PrepareCalculatedChannelDataForTest", new object[] { true }); - - Assert.IsFalse(reader.NoSamples, "Simulation did not prepare valid sample data."); - - reader.GetQ3Calibration(refVolume: 1.0, refTime: 120.0, initCalibFactor: 0.0); - - Assert.IsFalse(reader.Q3CalibValid); - Assert.AreEqual(0.0, reader.Q3CalibValue, 0.000001); - } - - [TestMethod] - public void Q3Calibration_ShouldReturnInvalid_WhenRawDataIsNull() - { - var reader = new GenesisSmartReader(); - InitializeReaderForTest(reader); - - SetPrivateField(reader, "_rawStartEndByChannel", null); - - reader.GetQ3Calibration(refVolume: 1.0, refTime: 120.0, initCalibFactor: 15625.0); - - Assert.IsFalse(reader.Q3CalibValid); - Assert.AreEqual(0.0, reader.Q3CalibValue, 0.000001); - } - private static void InitializeReaderForTest(GenesisSmartReader reader) { const int channelCount = 3; @@ -167,6 +268,28 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations reader.TestEndTelegramIx = 0; } + private static string[] LoadRealInputLines() + { + return new[] + { + // Replace these with REAL, unchanged captured telegram lines from the device. + "@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", + "@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", + "@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", + }; + } + + private static string[] LoadRealInputLinesWithRollover() + { + return new[] + { + // Replace these with REAL, unchanged captured telegram lines spanning the rollover case. + "@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", + "@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", + "@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", + }; + } + private static void SetPrivateField(object target, string fieldName, object value) { Type type = target.GetType();