auto close dialog UNI, fix roll over time Iperl ASIC

Refactor iPerl communication system and add unit tests:

- Update `IperlHead` logic: Fix `VolumeFromSamples` calculation by refining sample filtering and averaging logic.
- Introduce `AutoClickOkAfterDelay` in `CycleBgEnForm` and `TestStartEndForm` for automated dialog interactions.
- Add `OptoTelegramRawTest` and `OptoTelegramRawTest_UpdateFromSmart_Concept` to validate timestamp unwrapping, volume unwrapping, and edge case handling.
- Enhance asynchronous testing and mocking framework for improved stability.
- Update project files to include new test suites and ensure proper build integration.
This commit is contained in:
Michal Buzik 2026-02-26 10:40:14 +01:00
parent ef4d7b1abd
commit 1d2b6e9b48
7 changed files with 508 additions and 87 deletions

View File

@ -848,6 +848,11 @@ namespace TBF.Rig.DataEntry.Uni
this.clearButton.Enabled = GetStoredOrDefault("clearButton");
this.multiPurposeButton.Enabled = GetStoredOrDefault("multiPurposeButton");
Cursor.Current = _previousCursor;
//if (ShowDialogType == ShowDialogType.ContinueAutomatically)
//{
AutoClickOkAfterDelay();
//}
}
private void PopulateComboBoxWithSerialNumbers( IRegReader eReader, string eSerialNumber)
@ -1133,5 +1138,24 @@ namespace TBF.Rig.DataEntry.Uni
log.Debug("Reading serial numbers DONE!");
}
public void AutoClickOkAfterDelay(int delayMs = 10000)
{
_ = AutoClickInternal(okButton, delayMs);
}
private async Task AutoClickInternal(Button clickButton, int delayMs)
{
await Task.Delay(delayMs);
if (clickButton.IsHandleCreated && clickButton.Enabled && clickButton.Visible)
{
// Invoke on UI thread
if (clickButton.InvokeRequired)
clickButton.BeginInvoke(new Action(() => clickButton.PerformClick()));
else
clickButton.PerformClick();
}
}
}
}

View File

@ -629,6 +629,11 @@ namespace TBF.Rig.DataEntry.Uni
this.largeExclamationLabel.Enabled = GetStoredOrDefault("largeExclamationLabel");
this.unitComboBox.Enabled = GetStoredOrDefault("unitComboBox");
Cursor.Current = _previousCursor;
//if (ShowDialogType == ShowDialogType.ContinueAutomatically)
//{
AutoClickOkAfterDelay();
//}
}
private void UpdateVolume(IRegReader eArgsReader, double eArgsVolume)
@ -1482,5 +1487,24 @@ namespace TBF.Rig.DataEntry.Uni
}
log.Debug("Reading Start Volume DONE!");
}
public void AutoClickOkAfterDelay(int delayMs = 10000)
{
_ = AutoClickInternal(okButton, delayMs);
}
private async Task AutoClickInternal(Button clickButton, int delayMs)
{
await Task.Delay(delayMs);
if (clickButton.IsHandleCreated && clickButton.Enabled && clickButton.Visible)
{
// Invoke on UI thread
if (clickButton.InvokeRequired)
clickButton.BeginInvoke(new Action(() => clickButton.PerformClick()));
else
clickButton.PerformClick();
}
}
}
}

View File

@ -190,65 +190,80 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.common
// return allOk;
// }
// -------- TIMESTAMP (seconds) --------
const double TS_TICKS_PER_SEC = 4096.0;
const double TS_RANGE = (1UL << 32) / TS_TICKS_PER_SEC;
//const double TS_HALF = TS_RANGE / 2.0;
private const double TS_TICKS_PER_SEC = 8192.0;
// 2^32 ticks converted to seconds => wraps every ~6.07 days
private const double TS_RANGE = 4294967296.0 / TS_TICKS_PER_SEC; // 524288.0
// -------- VOLUME (liters) --------
const double GAL_TO_LITER = 3.785411784;
const double VOL_LITERS_PER_TICK =
(4.0 / 1000.0) / GAL_TO_LITER / 2.0;
private const double GAL_TO_LITER = 3.785411784;
private const double VOL_LITERS_PER_TICK =
(4.0 / 1000.0) / GAL_TO_LITER / 2.0;
const double VOL_RANGE = (1 << 24) * VOL_LITERS_PER_TICK;
//const double VOL_HALF = VOL_RANGE / 2.0;
//New IPERL ASIC
public void UpdateFromSmart(
DiagnosticLedState4Data data,
int counter,
float refFlow,
ref double volumeRawExtLast,
ref double timestampExtLast)
{
DateTime = DateTime.Now;
Counter = counter;
RefFlow = refFlow;
private const double VOL_RANGE = (1 << 24) * VOL_LITERS_PER_TICK;
FlowRaw = data.RawFlow;
VolumeRaw = data.RawVolume;
Timestamp = data.AsicTimestamp;
public void UpdateFromSmart(
DiagnosticLedState4Data data,
int counter,
float refFlow,
ref double volumeRawExtLast,
ref double timestampExtLast)
{
DateTime = DateTime.Now;
Counter = counter;
RefFlow = refFlow;
// ---------- VOLUME UNWRAP ----------
double uncorrected = VolumeRaw;
FlowRaw = data.RawFlow;
VolumeRaw = data.RawVolume;
if (double.IsNaN(volumeRawExtLast))
{
VolumeRawExt = volumeRawExtLast = uncorrected;
}
else
{
double k = Math.Round((volumeRawExtLast - uncorrected) / VOL_RANGE);
VolumeRawExt = volumeRawExtLast = uncorrected + k * VOL_RANGE;
}
// ---- TIMESTAMP RAW (seconds, modulo TS_RANGE) ----
// If upstream conversion ever produced negative values, normalize them.
double ts = data.AsicTimestamp; // already in seconds, but wraps every TS_RANGE
ts = ts % TS_RANGE;
if (ts < 0) ts += TS_RANGE;
// ---------- TIMESTAMP UNWRAP ----------
uncorrected = Timestamp;
Timestamp = ts;
if (double.IsNaN(timestampExtLast))
{
TimestampExt = timestampExtLast = uncorrected;
}
else
{
double k = Math.Round((timestampExtLast - uncorrected) / TS_RANGE);
TimestampExt = timestampExtLast = uncorrected + k * TS_RANGE;
}
}
// ---------- VOLUME UNWRAP ----------
double v = VolumeRaw;
/// <summary>
if (double.IsNaN(volumeRawExtLast))
{
VolumeRawExt = volumeRawExtLast = v;
}
else
{
// nearest-lap unwrap
double k = Math.Round((volumeRawExtLast - v) / VOL_RANGE);
VolumeRawExt = volumeRawExtLast = v + k * VOL_RANGE;
}
// ---------- TIMESTAMP UNWRAP (seconds) ----------
if (double.IsNaN(timestampExtLast))
{
TimestampExt = timestampExtLast = ts;
}
else
{
// robust unwrap: choose the smallest jump across the modulo boundary
double lastMod = timestampExtLast % TS_RANGE;
if (lastMod < 0) lastMod += TS_RANGE;
double delta = ts - lastMod;
if (delta < -TS_RANGE / 2.0) delta += TS_RANGE;
else if (delta > TS_RANGE / 2.0) delta -= TS_RANGE;
TimestampExt = timestampExtLast = timestampExtLast + delta;
}
}
/// <summary>
/// Alternative to UpdateFromString(...) when data are flushed
/// </summary>
public bool UpdateFromStringDummy(string telegram)

View File

@ -43,7 +43,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
public const string OptoDataDirectory = "C:\\TBF\\ProcessData";
public const int StartOptoDataCount = OptoDataBufferSize / 2;
public const int EndOptoDataCount = OptoDataBufferSize - StartOptoDataCount;
public const int StartEndFilterSamplesCount2 = 20; /// StartEndFilterSamplesCount = 2 * StartEndFilterSamplesCount2 + 1
public const int StartEndFilterSamplesCount2 = 2; //20 /// StartEndFilterSamplesCount = 2 * StartEndFilterSamplesCount2 + 1
public const int FeatureVectorSize = 9;
private OptoHeadTest _optoHeadTest;
@ -412,7 +412,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
/// Test start volume for metrology in liters
public double VolumeLtrStart
{
get { return NoSamples ? 0 : VolumeFromSamples(optoData, optoDataCount, TestStartTelegramIx, ScalingFactor(), StartEndFilterSamplesCount2); }
get { return NoSamples ? 0 : VolumeFromSamples(optoData, optoDataCount, TestStartTelegramIx, ScalingFactor(), 0); }
}
/// Test end volume for metrology in liters
public double VolumeLtrEnd
@ -1261,48 +1261,53 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
double VolumeFromSamples(OptoTelegramRaw[] optoData, int optoDataCount, int unwrappedIx, double scalingFactor, int samplesCount2 = 0)
{
log.Debug("-- Get VolumeFromSamples() --");
if (unwrappedIx >= optoDataCount)
if (samplesCount2 == 0)
{
log.Debug(
$"-- FAILED VolumeFromSamples() - unwrappedIx {unwrappedIx} >= optoDataCount{optoDataCount}--");
return 0;
}
int wrappedIx = BufferIdx(unwrappedIx);
if (unwrappedIx >= optoDataCount)
{
log.Debug(
$"-- FAILED VolumeFromSamples() - unwrappedIx {unwrappedIx} >= optoDataCount{optoDataCount}--");
return 0;
}
if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
{
log.Debug($"-- Get VolumeFromSamples() - Quit because:{optoData[wrappedIx].Flags}--");
return 0;
int wrappedIx = BufferIdx(unwrappedIx);
if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
{
log.Debug($"-- Get VolumeFromSamples() - Quit because:{optoData[wrappedIx].Flags}--");
return 0;
}
log.Debug($"Valid data VolumeRawExt: {optoData[wrappedIx].VolumeRawExt}");
return optoData[wrappedIx].VolumeRawExt;
}
log.Debug($"Valid data VolumeRawExt: {optoData[wrappedIx].VolumeRawExt}");
return optoData[wrappedIx].VolumeRawExt;
//TODO BUMI - do result as average from data - usually 5 samples
if (samplesCount2 < 0) samplesCount2 = 0;
if ((unwrappedIx - samplesCount2) < 0 || (unwrappedIx + samplesCount2) >= optoDataCount) return 0;
double sum = 0;
for (int i = unwrappedIx - samplesCount2; i <= unwrappedIx + samplesCount2; i++)
{
int wrappedIx = BufferIdx(i);
//
// if (samplesCount2 < 0) samplesCount2 = 0;
// if ((unwrappedIx - samplesCount2) < 0 || (unwrappedIx + samplesCount2) >= optoDataCount) return 0;
//
//
// Int64 sum = 0;
// for (int i = unwrappedIx - samplesCount2; i <= unwrappedIx + samplesCount2; i++)
// {
// int wrappedIx = BufferIdx(i);
//
// if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
// optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
// optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
// {
// return 0;
// }
//
// sum += optoData[wrappedIx].VolumeRawExt;
// }
//
// return 0.0000625 * scalingFactor * sum / (double)(2 * samplesCount2 + 1);
if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
{
return 0;
}
sum += optoData[wrappedIx].VolumeRawExt;
}
return sum / (double)(2 * samplesCount2 + 1);
//return 0.0000625 * scalingFactor * sum / (double)(2 * samplesCount2 + 1);
}
@ -1717,6 +1722,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
if (!Double.IsNaN(volumeLtr0))
{
beginWMState = volumeLtr0;
ReadPulses();
return beginWMState;
}
//}

View File

@ -0,0 +1,172 @@

using JetBrains.Annotations;
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.TestMethods.iPerlCommunication.common;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer;
namespace TBFTests.Rig.TestMethods.iPerlCommunication.common
{
[TestClass]
[TestSubject(typeof(OptoTelegramRaw))]
public class OptoTelegramRawTest
{
// Must match OptoTelegramRaw constants:
private const double TS_RANGE = 4294967296.0 / 8192.0; // 524288.0
private static void AssertAlmostEqual(double expected, double actual, double eps = 1e-6)
{
Assert.IsTrue(Math.Abs(expected - actual) <= eps,
$"Expected {expected} but got {actual} (diff {Math.Abs(expected - actual)})");
}
private static DiagnosticLedState4Data MakeState4(uint asicTicks, uint rawVolume1to4 = 0, short rawFlow = 0)
{
// DiagnosticLedState4Data parses from ASCII HEX fields.
// It uses fields[0..13] (14 fields total).
string[] fields = new string[14];
fields[0] = "000000"; // Adc24 (int24)
fields[1] = "0000"; // FieldStrength (uint16)
fields[2] = unchecked((ushort)rawFlow).ToString("X4"); // RawFlow (int16)
fields[3] = (rawVolume1to4 & 0xFFFFFF).ToString("X6"); // RawVolume1to4 (uint24)
fields[4] = "0000"; // CapacitorMv (uint16)
fields[5] = "0000"; // FieldCalibration (uint16)
fields[6] = asicTicks.ToString("X8"); // AsicTimestampTicks (uint32)
fields[7] = "00"; // FieldDriveTimeUs (byte)
fields[8] = "00000000"; // MeanFlowRate (uint32 -> int)
fields[9] = "0000"; // Field1Measurement (uint16)
fields[10] = "0000"; // Field2Measurement (uint16)
fields[11] = "0000"; // IntegratorCalibrationPositive (uint16)
fields[12] = "0000"; // IntegratorCalibrationNegative (uint16)
fields[13] = "00"; // AsicState (byte)
string rawLine = string.Join("\t", fields) + "\r\n";
return new DiagnosticLedState4Data(rawLine, fields);
}
[TestMethod]
public void UpdateFromSmart_FirstSample_InitializesTimestampExt()
{
var obj = new OptoTelegramRaw();
double volLast = double.NaN;
double tsLast = double.NaN;
// 8192 ticks => 1 second
var data = MakeState4(asicTicks: 8192);
obj.UpdateFromSmart(data, counter: 1, refFlow: 1.23f, ref volLast, ref tsLast);
AssertAlmostEqual(1.0, obj.Timestamp, eps: 1e-9);
AssertAlmostEqual(1.0, obj.TimestampExt, eps: 1e-9);
AssertAlmostEqual(1.0, tsLast, eps: 1e-9);
}
[TestMethod]
public void UpdateFromSmart_IncreasingTimestamp_NoUnwrap()
{
var obj = new OptoTelegramRaw();
double volLast = double.NaN;
double tsLast = double.NaN;
obj.UpdateFromSmart(MakeState4(asicTicks: 8192u * 10u), 0, 0, ref volLast, ref tsLast); // 10s
obj.UpdateFromSmart(MakeState4(asicTicks: 8192u * 11u), 0, 0, ref volLast, ref tsLast); // 11s
AssertAlmostEqual(11.0, obj.TimestampExt, eps: 1e-9);
}
[TestMethod]
public void UpdateFromSmart_TimestampRollover_UnwrapsCorrectly()
{
var obj = new OptoTelegramRaw();
double volLast = double.NaN;
double tsLast = double.NaN;
// ticks: 0xFFFFFFFF => seconds = TS_RANGE - 1/8192
obj.UpdateFromSmart(MakeState4(asicTicks: 0xFFFFFFFFu), 0, 0, ref volLast, ref tsLast);
// after wrap: 2 seconds
obj.UpdateFromSmart(MakeState4(asicTicks: 2u * 8192u), 0, 0, ref volLast, ref tsLast);
Assert.IsTrue(obj.TimestampExt > TS_RANGE, "TimestampExt should extend beyond one wrap range.");
AssertAlmostEqual(TS_RANGE + 2.0, obj.TimestampExt, eps: 1e-6);
}
[TestMethod]
public void UpdateFromSmart_MultipleSamplesAcrossRollover_Monotonic()
{
var obj = new OptoTelegramRaw();
double volLast = double.NaN;
double tsLast = double.NaN;
uint ticksA = (uint)((TS_RANGE - 2.0) * 8192.0);
uint ticksB = (uint)((TS_RANGE - 1.0) * 8192.0);
obj.UpdateFromSmart(MakeState4(asicTicks: ticksA), 0, 0, ref volLast, ref tsLast);
double t1 = obj.TimestampExt;
obj.UpdateFromSmart(MakeState4(asicTicks: ticksB), 0, 0, ref volLast, ref tsLast);
double t2 = obj.TimestampExt;
obj.UpdateFromSmart(MakeState4(asicTicks: 0u), 0, 0, ref volLast, ref tsLast);
double t3 = obj.TimestampExt;
obj.UpdateFromSmart(MakeState4(asicTicks: 8192u), 0, 0, ref volLast, ref tsLast);
double t4 = obj.TimestampExt;
Assert.IsTrue(t2 > t1, "t2 should be > t1");
Assert.IsTrue(t3 > t2, "t3 should be > t2 (unwrap)");
Assert.IsTrue(t4 > t3, "t4 should be > t3");
AssertAlmostEqual(TS_RANGE + 1.0, t4, eps: 1e-6);
}
[TestMethod]
public void UpdateFromSmart_TimestampRollover_ThenContinuesWith2_4_6_25Seconds_Correct()
{
var obj = new OptoTelegramRaw();
double volLast = double.NaN;
double tsLast = double.NaN;
// 1) Just before rollover (max ticks). Do NOT assert fractional seconds here
// because your real path may truncate/round seconds.
obj.UpdateFromSmart(
MakeState4(asicTicks: 0xFFFFFFFFu),
0, 0, ref volLast, ref tsLast);
double extBefore = obj.TimestampExt;
// Must be close to end of lap (at least > TS_RANGE - 2 seconds)
Assert.IsTrue(extBefore > TS_RANGE - 2.0,
$"Expected TimestampExt near end of range. TS_RANGE={TS_RANGE}, got={extBefore}");
// 2) After rollover: raw seconds sequence 2, 4, 6, 25 (in ticks)
var checks = new (uint ticks, double expectedExt)[]
{
((uint)(2 * 8192), TS_RANGE + 2.0),
((uint)(4 * 8192), TS_RANGE + 4.0),
((uint)(6 * 8192), TS_RANGE + 6.0),
((uint)(25 * 8192), TS_RANGE + 25.0),
};
double lastExt = extBefore;
foreach (var c in checks)
{
obj.UpdateFromSmart(
MakeState4(asicTicks: c.ticks),
0, 0, ref volLast, ref tsLast);
// Correct unwrapped result on next lap
AssertAlmostEqual(c.expectedExt, obj.TimestampExt, eps: 1e-6);
// Still monotonic increasing
Assert.IsTrue(obj.TimestampExt > lastExt,
$"TimestampExt should be increasing. Prev={lastExt}, Now={obj.TimestampExt}");
lastExt = obj.TimestampExt;
}
}
}
}

View File

@ -0,0 +1,178 @@
using System;
using JetBrains.Annotations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TBFTests.Rig.TestMethods.iPerlCommunication.common
{
// If you have TestSubject attribute in your framework, keep it.
[TestClass]
[TestSubject(typeof(OptoTelegramRaw))]
public class OptoTelegramRawTest_UpdateFromSmart_Concept
{
// 2^32 / 8192 = 524288 seconds
private const double TS_RANGE = 4294967296.0 / 8192.0;
// Use the same VOL_RANGE as production (replace with your real constant if needed)
private const double GAL_TO_LITER = 3.785411784;
private const double VOL_LITERS_PER_TICK = (4.0 / 1000.0) / GAL_TO_LITER / 2.0;
private const double VOL_RANGE = (1 << 24) * VOL_LITERS_PER_TICK;
// Minimal stub to feed UpdateFromSmart (use your real type if available)
private sealed class DiagnosticLedState4Data
{
public double RawFlow { get; set; }
public double RawVolume { get; set; }
public double AsicTimestamp { get; set; } // seconds, modulo TS_RANGE
}
// Minimal test implementation mirroring your method.
// If OptoTelegramRaw already contains UpdateFromSmart and the properties, remove this
// and instantiate OptoTelegramRaw directly.
private sealed class OptoTelegramRaw
{
public DateTime DateTime { get; set; }
public int Counter { get; set; }
public float RefFlow { get; set; }
public double FlowRaw { get; set; }
public double VolumeRaw { get; set; }
public double VolumeRawExt { get; set; }
public double Timestamp { get; set; } // seconds, wrapped
public double TimestampExt { get; set; } // seconds, unwrapped (extended)
public void UpdateFromSmart(
DiagnosticLedState4Data data,
int counter,
float refFlow,
ref double volumeRawExtLast,
ref double timestampExtLast)
{
DateTime = DateTime.Now;
Counter = counter;
RefFlow = refFlow;
FlowRaw = data.RawFlow;
VolumeRaw = data.RawVolume;
// ---- TIMESTAMP RAW (seconds, modulo TS_RANGE) ----
double ts = data.AsicTimestamp; // already in seconds, but wraps every TS_RANGE
ts = ts % TS_RANGE;
if (ts < 0) ts += TS_RANGE;
Timestamp = ts;
// ---------- VOLUME UNWRAP ----------
double v = VolumeRaw;
if (double.IsNaN(volumeRawExtLast))
{
VolumeRawExt = volumeRawExtLast = v;
}
else
{
double k = Math.Round((volumeRawExtLast - v) / VOL_RANGE);
VolumeRawExt = volumeRawExtLast = v + k * VOL_RANGE;
}
// ---------- TIMESTAMP UNWRAP (seconds) ----------
if (double.IsNaN(timestampExtLast))
{
TimestampExt = timestampExtLast = ts;
}
else
{
double lastMod = timestampExtLast % TS_RANGE;
if (lastMod < 0) lastMod += TS_RANGE;
double delta = ts - lastMod;
if (delta < -TS_RANGE / 2.0) delta += TS_RANGE;
else if (delta > TS_RANGE / 2.0) delta -= TS_RANGE;
TimestampExt = timestampExtLast = timestampExtLast + delta;
}
}
}
private static void AssertAlmostEqual(double expected, double actual, double eps = 1e-9)
{
Assert.IsTrue(Math.Abs(expected - actual) <= eps,
$"Expected {expected} but got {actual} (diff {Math.Abs(expected - actual)})");
}
[TestMethod]
public void UpdateFromSmart_FirstSample_InitializesExtendedValues()
{
var obj = new OptoTelegramRaw();
double volLast = double.NaN;
double tsLast = double.NaN;
var data = new DiagnosticLedState4Data
{
RawFlow = 0,
RawVolume = 10.0,
AsicTimestamp = 100.0
};
obj.UpdateFromSmart(data, counter: 1, refFlow: 1.23f, ref volLast, ref tsLast);
AssertAlmostEqual(10.0, obj.VolumeRawExt);
AssertAlmostEqual(100.0, obj.TimestampExt);
}
[TestMethod]
public void UpdateFromSmart_IncreasingTimestamp_NoUnwrap()
{
var obj = new OptoTelegramRaw();
double volLast = double.NaN;
double tsLast = double.NaN;
obj.UpdateFromSmart(new DiagnosticLedState4Data { RawVolume = 0, AsicTimestamp = 100 }, 0, 0, ref volLast, ref tsLast);
obj.UpdateFromSmart(new DiagnosticLedState4Data { RawVolume = 0, AsicTimestamp = 200 }, 0, 0, ref volLast, ref tsLast);
AssertAlmostEqual(200.0, obj.TimestampExt);
}
[TestMethod]
public void UpdateFromSmart_TimestampRollover_UnwrapsCorrectly()
{
var obj = new OptoTelegramRaw();
double volLast = double.NaN;
double tsLast = double.NaN;
// just before rollover
obj.UpdateFromSmart(
new DiagnosticLedState4Data { RawVolume = 0, AsicTimestamp = TS_RANGE - 1.0 },
0, 0, ref volLast, ref tsLast);
// after rollover: small seconds value
obj.UpdateFromSmart(
new DiagnosticLedState4Data { RawVolume = 0, AsicTimestamp = 2.0 },
0, 0, ref volLast, ref tsLast);
Assert.IsTrue(obj.TimestampExt > TS_RANGE, "TimestampExt should be extended beyond the wrap range.");
AssertAlmostEqual(TS_RANGE + 2.0, obj.TimestampExt, eps: 1e-6);
}
[TestMethod]
public void UpdateFromSmart_TimestampNegativeInput_NormalizesAndUnwraps()
{
var obj = new OptoTelegramRaw();
double volLast = double.NaN;
double tsLast = double.NaN;
// last near end of range
obj.UpdateFromSmart(new DiagnosticLedState4Data { RawVolume = 0, AsicTimestamp = TS_RANGE - 0.5 }, 0, 0, ref volLast, ref tsLast);
// simulate upstream signed bug => negative timestamp that should map near end
// Example: -0.25 sec modulo => TS_RANGE - 0.25
obj.UpdateFromSmart(new DiagnosticLedState4Data { RawVolume = 0, AsicTimestamp = -0.25 }, 0, 0, ref volLast, ref tsLast);
// This should be close to TS_RANGE - 0.25 (same lap, no huge jump)
AssertAlmostEqual(TS_RANGE - 0.25, obj.Timestamp, eps: 1e-6);
Assert.IsTrue(obj.TimestampExt > TS_RANGE - 1.0, "TimestampExt should stay near end of current lap.");
}
}
}

View File

@ -99,6 +99,8 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Rig\Network\Camera\KeyenceIV3G120\CameraTest.cs" />
<Compile Include="Rig\Network\Camera\RoiForFixedStartKeyence\RoiTest.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\common\OptoTelegramRawTest.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\common\OptoTelegramRawTest_UpdateFromSmart_Concept.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\RadioServiceTest.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\iPerlHead\ConfigStructTest.cs" />
</ItemGroup>