From 166cb28d285a121ac75b0649fe9cba15d4c7b518 Mon Sep 17 00:00:00 2001 From: Michal Buzik Date: Sun, 1 Mar 2026 22:44:33 +0100 Subject: [PATCH] Standing Start roll over fix Add volume rollover handling enhancements and corresponding unit tests: - Improve rollover logic in `PopulateVolume` for accurate volume compensation. - Add `TestStartEndFormTest` to validate rollover scenarios with unit tests. - Refactor and expand handling of initial volume state in `findBeginStateFromForm`. - Update project files to include new test dependencies and classes. --- TBF/Rig/DataEntry/Uni/TestStartEndForm.cs | 102 ++++++++--- .../iPerlCommunication/iPerlHead/IperlHead.cs | 2 + .../Rig/DataEntry/Uni/TestStartEndFormTest.cs | 166 ++++++++++++++++++ TBFTests/TBFTests.csproj | 10 ++ 4 files changed, 258 insertions(+), 22 deletions(-) create mode 100644 TBFTests/Rig/DataEntry/Uni/TestStartEndFormTest.cs diff --git a/TBF/Rig/DataEntry/Uni/TestStartEndForm.cs b/TBF/Rig/DataEntry/Uni/TestStartEndForm.cs index d2197ec4e..d791254f7 100644 --- a/TBF/Rig/DataEntry/Uni/TestStartEndForm.cs +++ b/TBF/Rig/DataEntry/Uni/TestStartEndForm.cs @@ -651,7 +651,7 @@ namespace TBF.Rig.DataEntry.Uni /// /// /// - private void PopulateVolume(IRegReader eArgsReader, double eArgsVolume) + public void PopulateVolume(IRegReader eArgsReader, double eArgsVolume) { if (regReaders == null || Double.IsNaN(eArgsVolume) || textBoxes == null) @@ -668,27 +668,41 @@ namespace TBF.Rig.DataEntry.Uni double VolumeRaw = eArgsVolume; double v = eArgsVolume; - // // ---- normalize to [0, RANGE) in case upstream gives negative values ---- - // v = v % VOL_RANGE_LITERS; - // if (v < 0) v += VOL_RANGE_LITERS; - // VolumeRaw = v; - // //Test and compensate roll over - // if (isEnd) - // { - // if (eArgsReader != null) - // { - // double lastMod = eArgsReader.BeginWMState % VOL_RANGE_LITERS; - // if (lastMod < 0) lastMod += VOL_RANGE_LITERS; - // - // double delta = v - lastMod; - // - // // choose the shortest jump across the modulo boundary - // if (delta < -VOL_RANGE_LITERS / 2.0) delta += VOL_RANGE_LITERS; - // else if (delta > VOL_RANGE_LITERS / 2.0) delta -= VOL_RANGE_LITERS; - // - // VolumeRaw = eArgsReader.BeginWMState + delta; - // } - // } + // Test and compensate roll over + if (isEnd) + { + if (eArgsReader != null && (!Double.IsNaN(eArgsReader.BeginWMState))) + { + double lastMod = eArgsReader.BeginWMState % VOL_RANGE_LITERS; + if (lastMod < 0) lastMod += VOL_RANGE_LITERS; + + double delta = v - lastMod; + + // choose the shortest jump across the modulo boundary + if (delta < -VOL_RANGE_LITERS / 2.0) delta += VOL_RANGE_LITERS; + else if (delta > VOL_RANGE_LITERS / 2.0) delta -= VOL_RANGE_LITERS; + + VolumeRaw = eArgsReader.BeginWMState + delta; + log.Debug($"Roll over detected: {eArgsReader.Name} - {eArgsReader.BeginWMState} -> {VolumeRaw}"); + } + else + { + //volume from form + Double beginVolume = findBeginStateFromForm(); + + double lastMod = beginVolume % VOL_RANGE_LITERS; + if (lastMod < 0) lastMod += VOL_RANGE_LITERS; + + double delta = v - lastMod; + + // choose the shortest jump across the modulo boundary + if (delta < -VOL_RANGE_LITERS / 2.0) delta += VOL_RANGE_LITERS; + else if (delta > VOL_RANGE_LITERS / 2.0) delta -= VOL_RANGE_LITERS; + + VolumeRaw = beginVolume + delta; + log.Debug($"Roll over detected: {eArgsReader.Name} - {eArgsReader.BeginWMState} -> {VolumeRaw}"); + } + } for (int k = 0; k < colItems.Count && k < comboRows; k++) { @@ -722,6 +736,50 @@ namespace TBF.Rig.DataEntry.Uni } } + private Double findBeginStateFromForm() + { + + List ctValues = new List { Ct.StartState, Ct.StartStateAux }; + + int regReadersLength = regReaders.Length; + int comboRows = textBoxes.GetLength(0); + int comboCols = textBoxes.GetLength(1); + + for (int k = 0; k < colItems.Count && k < comboRows; k++) + { + Ct current = (Ct)colItems[k].Content; + if (ctValues.Contains(current)) // it define if is start or end + { + for (int ix = 0; ix < wmsCount && ix < comboCols; ix++) + { + int regIndex = ix; + + if (regIndex >= 0 && regIndex < regReadersLength) + { + var textBox = textBoxes[k, ix]; + if (textBox != null && textBox.Text != null && textBox.Text.Length > 0) + { + try + { + if (VolumeUnit == Unit.None) + VolumeUnit = Unit.l; + double VolumeRaw = Double.Parse(textBox.Text); + Double convertBeginVolumeINLiters = Units.ConvertFrom(VolumeUnit, VolumeRaw); + return convertBeginVolumeINLiters; + } + catch (Exception ex) + { + log.Error($"Error converting volume to {VolumeUnit}: {ex.Message}"); + } + } + } + } + } + } + + return 0.0D; + } + /// /// When one combo box is updated using drop-down menu, all combo boxes /// are updated by this function. diff --git a/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHead.cs b/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHead.cs index a927f6fd4..aefdf6736 100644 --- a/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHead.cs +++ b/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHead.cs @@ -1731,8 +1731,10 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead //Solve roll over if (endWMState < beginWMState) { + log.Debug($"Solve roll over! endWMState: {endWMState} < beginWMState: {beginWMState}"); const double VOL_RANGE_LITERS = 16777216.0 * 0.00025; // 4,194.304 l endWMState += VOL_RANGE_LITERS; + log.Debug($"Solve roll over! Upgraded endWMState: {endWMState}, beginWMState: {beginWMState}"); } } ReadPulses(); diff --git a/TBFTests/Rig/DataEntry/Uni/TestStartEndFormTest.cs b/TBFTests/Rig/DataEntry/Uni/TestStartEndFormTest.cs new file mode 100644 index 000000000..9268d61b6 --- /dev/null +++ b/TBFTests/Rig/DataEntry/Uni/TestStartEndFormTest.cs @@ -0,0 +1,166 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using Common; +using Config.Entities; +using JetBrains.Annotations; +using Results.Entities; +using TBF.Rig.DataEntry; +using TBF.Rig.DataEntry.Uni; +using TBF.Rig.Generic; +using TBF.Rig.GenericDevices; + + +namespace TBFTests.Rig.DataEntry.Uni +{ + [TestClass] + [TestSubject(typeof(TestStartEndForm))] + public class TestStartEndFormTest + { + [TestMethod] + public void PopulateVolume_TestRollOver() + { + Exception staEx = null; + + var t = new Thread(() => + { + try + { + // Arrange + double volRange = GetConstDouble(typeof(TestStartEndForm), "VOL_RANGE_LITERS"); + + // Example: begin = R-10, v=10 => expectedRaw = begin + 20 (shortest jump across modulo) + double begin = volRange - 10.0; + double entered = 10.0; + double expectedRaw = begin + 20.0; + + var reader = new FakeRegReader + { + Name = "R1", + BeginWMState = begin + }; + + var waterMeters = new List + { + new WaterMeter { Disabled = false } + }; + + // Use your DEItem class exactly: + // - Content must be Ct.EndState (because isEnd==true) + // - Action must not be LoadReadOnly, otherwise textbox may be disabled (not critical for setting Text, + // but keep it editable) + IList colItems = new List + { + new DEItem(Ct.EndState, "End", Ac.Clear, 120) + }; + + // Force isEnd=true (constructor: isEnd = (wmStartStateStr != null)) + // Length must match (isCompound?2:1)*waterMeters.Count -> here 1*1 = 1. + string[] wmStartStateStr = new[] { "x" }; + + var sut = new TestStartEndForm( + waterMeters: waterMeters, + regReaders: new IRegReader[] { reader }, + _lineSize: 1, + title: "test", + sz: FontSz.S, + isStartBoxAlwaysEn: false, + isLrOrder: false, + formCloseKeys: "", + colItems: colItems, + isCompound: false, + initialVolumeUnit: Unit.l, // keep conversion identity + isCameraPicture: false, + startImages: Array.Empty(), + endImages: Array.Empty(), + ocrVidi: null, + ocrMessage: "", + streamName: "", + wmStartStateStr: wmStartStateStr, + refVolume: 0, + errLimLo: 0, + errLimHi: 0 + ); + + // Act + sut.PopulateVolume(reader, entered); + + // Assert + var tb = GetTextBox(sut, row: 0, col: 0); + Assert.IsNotNull(tb, "Expected TextBox[0,0] to exist."); + Assert.AreEqual(expectedRaw.ToString(), tb.Text); + } + catch (Exception ex) + { + staEx = ex; + } + }); + + t.SetApartmentState(ApartmentState.STA); // WinForms UI objects => STA is safest + t.Start(); + t.Join(); + + if (staEx != null) + throw new AssertFailedException("PopulateVolume_TestRollOver failed.", staEx); + } + + // ----- helpers ----- + + private static double GetConstDouble(Type type, string fieldName) + { + var f = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + if (f == null) throw new MissingFieldException(type.FullName, fieldName); + + object v = f.IsLiteral ? f.GetRawConstantValue() : f.GetValue(null); + return Convert.ToDouble(v); + } + + private static System.Windows.Forms.TextBox GetTextBox(TestStartEndForm sut, int row, int col) + { + var f = typeof(TestStartEndForm).GetField("textBoxes", BindingFlags.Instance | BindingFlags.NonPublic); + if (f == null) throw new MissingFieldException(typeof(TestStartEndForm).FullName, "textBoxes"); + + var grid = (System.Windows.Forms.TextBox[,])f.GetValue(sut); + return grid[row, col]; + } + + private sealed class FakeRegReader : IRegReader + { + public string Name { get; set; } + public string ClassName { get; } + public string ParentName { get; } + public DebugMode DebugLevel { get; } + public LogLevel LogLevel { get; } + public IComponentCfg Cfg { get; } + public IList Corrections { get; set; } + public IList Uncertainties { get; set; } + public void Initialize() + { + throw new NotImplementedException(); + } + + public void StartChangeHandler() + { + throw new NotImplementedException(); + } + + public void StopChangeHandler() + { + throw new NotImplementedException(); + } + + public RegisterReaderType RegisterReaderType { get; } + public int Position { get; } + public double PulsesPerLtr { get; set; } + public double LtrsPerPulse { get; } + public string QuantityUnits { get; set; } + public int WMPulses { get; } + public int WMRefPulses { get; } + public double WMVolume { get; } + public double BeginWMState { get; set; } + public double EndWMState { get; } + } + } +} \ No newline at end of file diff --git a/TBFTests/TBFTests.csproj b/TBFTests/TBFTests.csproj index c326f5a79..f8cd2aff9 100644 --- a/TBFTests/TBFTests.csproj +++ b/TBFTests/TBFTests.csproj @@ -84,6 +84,7 @@ ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + @@ -99,6 +100,7 @@ + @@ -111,10 +113,18 @@ + + {c8939821-ba5c-4988-a3d0-bf53b74865c7} + Common + {743df7db-c7b6-42eb-986d-0f485e5588e4} Config + + {9d0dcc88-dc81-47eb-9fdd-4c3907871bfb} + Results + {8648FD92-CDA1-4C3A-B5F9-FE547CE1FA48} TBF