Store changes on Iperl

Add and expand unit tests for iPerl communication, refactor OptoHead methods, update log4net reference, and increment AssemblyVersion.
This commit is contained in:
Michal Buzik 2026-03-03 14:34:30 +01:00
parent 8229c5eb63
commit bcb33cde78
8 changed files with 378 additions and 9 deletions

View File

@ -29,5 +29,5 @@ using System.Runtime.InteropServices;
// Build Number
// Revision
//
[assembly: AssemblyVersion("3.9.2200.1")]
[assembly: AssemblyFileVersion("3.9.2200.1")]
[assembly: AssemblyVersion("3.9.2201.1")]
[assembly: AssemblyFileVersion("3.9.2201.1")]

View File

@ -110,8 +110,8 @@
<Reference Include="Iesi.Collections">
<HintPath>..\packages\Iesi.Collections.4.0.0.4000\lib\net40\Iesi.Collections.dll</HintPath>
</Reference>
<Reference Include="log4net, Version=2.0.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\packages\log4net.2.0.15\lib\net45\log4net.dll</HintPath>
<Reference Include="log4net, Version=3.2.0.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\packages\log4net.3.2.0\lib\net462\log4net.dll</HintPath>
</Reference>
<Reference Include="mscorlib" />
<Reference Include="MySql.Data">
@ -1460,6 +1460,7 @@
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\parserer\SpikeDetectionStatus.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\utils\DiagnosticChecksum.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\utils\DiagnosticHex.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\utils\DiagnostigLedDataByUnit.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\hexLogger\HexFormatter.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\hexLogger\IpelHatCommandDecoder.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\hexLogger\IperlHatLogger.cs" />

View File

@ -3,6 +3,10 @@ using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Text;
using log4net;
using log4net.Appender;
using log4net.Layout;
using log4net.Repository.Hierarchy;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer;
@ -16,6 +20,33 @@ namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.IperlHat
[TestClass]
public class IperlHatIntegrationTests
{
public static class LogConfig
{
public static void Configure()
{
var hierarchy = (Hierarchy)LogManager.GetRepository();
hierarchy.Root.RemoveAllAppenders();
var patternLayout = new PatternLayout
{
ConversionPattern = "%date{yyyy-MM-dd HH:mm:ss.fff} [%thread] %-5level %logger - %message%newline"
};
patternLayout.ActivateOptions();
var consoleAppender = new ConsoleAppender
{
Layout = patternLayout
};
consoleAppender.ActivateOptions();
hierarchy.Root.AddAppender(consoleAppender);
hierarchy.Root.Level = log4net.Core.Level.Debug;
hierarchy.Configured = true;
}
}
ILog log = LogManager.GetLogger(typeof(IperlHatIntegrationTests));
private const string ComPort = "COM3"; // CHANGE THIS
private const int BaudRate = 2400;
private const int BaudRateOpto = 38400;
@ -461,7 +492,7 @@ namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.IperlHat
//method test connection to optho head
private void Serial_OptoRawSniff()
{
using (var port = new SerialPort("COM4", BaudRateOpto, Parity.None, 8, StopBits.One))
using (var port = new SerialPort("COM15", BaudRateOpto, Parity.None, 8, StopBits.One))
{
port.Handshake = Handshake.None;
port.ReadTimeout = 10000;
@ -489,7 +520,8 @@ namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.IperlHat
try
{
var data = (DiagnosticLedState4Data)parser.ParseLine(line,false);
Console.WriteLine("Parsed: " + data);
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Time: Parsed: {data}");
log.Info("Parsed: " + data);
}catch(Exception e)
{
Console.WriteLine("Failed to parse: " + e.Message);

View File

@ -15,8 +15,9 @@ namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication
public void ReadRequest_PCB_Test()
{
IperlHead iHead = new IperlHead();
OptoHeadTest optoHeadTest = new OptoHeadTest(iHead);
OptoHeadTest.ReadRequest_PCB(ref iHead);
optoHeadTest.ReadRequest_PCB();
}
}
}

View File

@ -0,0 +1,143 @@
using System;
using JetBrains.Annotations;
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
{
private const double EPS = 1e-6;
private static OptoTelegramRaw Create() => new OptoTelegramRaw();
private static DiagnosticLedState4Data CreateTelegram(uint rawVolume24, uint timestamp32)
{
string HexU(uint value, int bytes) => value.ToString($"X{bytes * 2}");
string[] fields =
{
"000000","0000","0000",
HexU(rawVolume24,3),"0000","0000",
HexU(timestamp32,4),"00","00000000",
"0000","0000","0000","0000","00"
};
return new DiagnosticLedState4Data("TEST", fields);
}
// helper: compute liters per tick exactly as production
private static double LitersPerTick =>
(4.0 / 1000.0) / 3.785411784 / 2.0;
private static double TimestampPerTick => 1.0 / 4096.0;
[TestMethod]
public void NormalProgression_NoWrap()
{
var sample = Create();
var d1 = CreateTelegram(1000, 2000);
double volLast = d1.RawVolume;
double tsLast = d1.AsicTimestamp;
var d2 = CreateTelegram(1010, 2010);
sample.UpdateFromSmart(d2, 0, 0, ref volLast, ref tsLast);
Assert.AreEqual(d2.RawVolume, sample.VolumeRawExt, EPS);
Assert.AreEqual(d2.AsicTimestamp, sample.TimestampExt, EPS);
}
[TestMethod]
public void VolumeForwardWrap_AddsOneFullVolumeRange()
{
var sample = Create();
var dLast = CreateTelegram((uint)((1 << 24) - 5), 0);
double volLast = dLast.RawVolume;
double tsLast = dLast.AsicTimestamp;
var dNew = CreateTelegram(3u, 0u);
sample.UpdateFromSmart(dNew, 0, 0, ref volLast, ref tsLast);
double expectedIncrease = (1 << 24) * LitersPerTick;
Assert.AreEqual(dNew.RawVolume + expectedIncrease, sample.VolumeRawExt, 1e-3);
}
[TestMethod]
public void TimestampForwardWrap_AddsOneFullTimeRange()
{
var sample = Create();
var dLast = CreateTelegram(0, (uint)((1UL << 32) - 10));
double volLast = dLast.RawVolume;
double tsLast = dLast.AsicTimestamp;
var dNew = CreateTelegram(0, 5);
sample.UpdateFromSmart(dNew, 0, 0, ref volLast, ref tsLast);
double expectedIncrease = (1UL << 32) * TimestampPerTick;
Assert.AreEqual(dNew.AsicTimestamp + expectedIncrease, sample.TimestampExt, 1e-3);
}
[TestMethod]
public void HugeJump_UsesFallback()
{
var sample = Create();
var dLast = CreateTelegram(1000, 1000);
double volLast = dLast.RawVolume;
double tsLast = dLast.AsicTimestamp;
var dNew = CreateTelegram((uint)(1 << 23), (uint)(1UL << 31));
sample.UpdateFromSmart(dNew, 0, 0, ref volLast, ref tsLast);
Assert.AreEqual(dNew.RawVolume, sample.VolumeRawExt, EPS);
Assert.AreEqual(dNew.AsicTimestamp, sample.TimestampExt, EPS);
}
[TestMethod]
public void LongRun_MultipleWraps_RemainsMonotonic()
{
var sample = new OptoTelegramRaw();
double volLast = double.NaN;
double tsLast = double.NaN;
uint volCounter = (uint)((1 << 24) - 100);
uint tsCounter = (uint)((1UL << 32) - 1000);
double prevVol = double.NegativeInfinity;
double prevTs = double.NegativeInfinity;
for (int i = 0; i < 500; i++)
{
unchecked
{
volCounter += 200_000u; // < 2^23
tsCounter += 50_000_000u; // < 2^31
}
var data = CreateTelegram(volCounter, tsCounter);
sample.UpdateFromSmart(data, 0, 0, ref volLast, ref tsLast);
Assert.IsTrue(sample.VolumeRawExt >= prevVol, $"Volume went backwards at i={i}");
Assert.IsTrue(sample.TimestampExt >= prevTs, $"Timestamp went backwards at i={i}");
prevVol = sample.VolumeRawExt;
prevTs = sample.TimestampExt;
}
}
}
}

View File

@ -0,0 +1,159 @@
using JetBrains.Annotations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
namespace TBFTests.Rig.TestMethods.iPerlCommunication.iPerlHead
{
using System;
using System.Threading;
using JetBrains.Annotations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
using Common;
namespace TBFTests.Rig.TestMethods.iPerlCommunication.iPerlHead
{
[TestClass]
[TestSubject(typeof(FlowDirectionDetection))]
public class FlowDirectionDetectionTests
{
private FlowDirectionDetection Create() => new FlowDirectionDetection();
// helper: feed linear data
private void FeedLinearData(FlowDirectionDetection d, double slope, int samples = 64)
{
double volume = 0;
double time = 0;
for (int i = 0; i < samples; i++)
{
volume += slope; // linear growth
time += 0.125; // 8 Hz sampling
d.WriteToFifo(volume, time);
}
}
// --------------------------------------------------------
[TestMethod]
public void Fifo_NotFull_DataInvalid()
{
var d = Create();
d.WriteToFifo(1, 1);
Assert.IsFalse(d.AreFifoDataValid());
}
// --------------------------------------------------------
[TestMethod]
public void Fifo_Full_DataValid()
{
var d = Create();
FeedLinearData(d, 1.0);
Assert.IsTrue(d.AreFifoDataValid());
}
// --------------------------------------------------------
[TestMethod]
public void PositiveFlow_Detected()
{
var d = Create();
FeedLinearData(d, slope: 10); // increasing volume
var result = d.CheckFlowDirection(Counting.Positive, "HEAD");
Assert.AreEqual(OptoHeadState.OptoAndDirOK, result);
}
// --------------------------------------------------------
[TestMethod]
public void NegativeFlow_Detected()
{
var d = Create();
FeedLinearData(d, slope: -10); // decreasing volume
var result = d.CheckFlowDirection(Counting.Negative, "HEAD");
Assert.AreEqual(OptoHeadState.OptoAndDirOK, result);
}
// --------------------------------------------------------
[TestMethod]
public void WrongDirection_Fails()
{
var d = Create();
FeedLinearData(d, slope: -10); // negative flow
var result = d.CheckFlowDirection(Counting.Positive, "HEAD");
Assert.AreEqual(OptoHeadState.DirNok, result);
}
// --------------------------------------------------------
[TestMethod]
public void ArbitraryCounting_AlwaysPassesWhenSignalGood()
{
var d = Create();
FeedLinearData(d, slope: -5);
var result = d.CheckFlowDirection(Counting.Arbitrary, "HEAD");
Assert.AreEqual(OptoHeadState.OptoAndDirOK, result);
}
// --------------------------------------------------------
[TestMethod]
public void NoFlow_ReturnsDirNok()
{
var d = Create();
// flat signal → slope ≈ 0
for (int i = 0; i < 64; i++)
d.WriteToFifo(100, i * 0.125);
var result = d.CheckFlowDirection(Counting.Positive, "HEAD");
Assert.AreEqual(OptoHeadState.DirNok, result);
}
// --------------------------------------------------------
[TestMethod]
public void FifoOverwrite_StillDetectsCorrectly()
{
var d = Create();
// write more than FIFO size → overwrite happens
FeedLinearData(d, slope: 5, samples: 200);
var result = d.CheckFlowDirection(Counting.Positive, "HEAD");
Assert.AreEqual(OptoHeadState.OptoAndDirOK, result);
}
// --------------------------------------------------------
[TestMethod]
public void Timeout_InvalidatesFifo()
{
var d = Create();
FeedLinearData(d, slope: 5);
// simulate dropout > 4.5 sec
Thread.Sleep(5000);
Assert.IsFalse(d.AreFifoDataValid());
}
}
}
}

View File

@ -0,0 +1,30 @@
using JetBrains.Annotations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
namespace TBFTests.Rig.TestMethods.iPerlCommunication.iPerlHead
{
[TestClass]
[TestSubject(typeof(IperlHead))]
public class IperlHeadTest
{
[TestMethod]
public void VolumeLtrStart()
{
}
[TestMethod]
public void VolumeLtrEnd()
{
}
[TestMethod]
public void NoSamples()
{
}
}
}

View File

@ -44,8 +44,8 @@
<Reference Include="JetBrains.Annotations, Version=4242.42.42.42, Culture=neutral, PublicKeyToken=1010a0d8d6380325, processorArchitecture=MSIL">
<HintPath>..\packages\JetBrains.Annotations.2023.3.0\lib\net20\JetBrains.Annotations.dll</HintPath>
</Reference>
<Reference Include="log4net, Version=2.0.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\packages\log4net.2.0.15\lib\net45\log4net.dll</HintPath>
<Reference Include="log4net, Version=3.2.0.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\packages\log4net.3.2.0\lib\net462\log4net.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Configuration.Abstractions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Configuration.Abstractions.5.0.0\lib\net461\Microsoft.Extensions.Configuration.Abstractions.dll</HintPath>
@ -114,6 +114,9 @@
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\OptoHeadTestTest.cs" />
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTest.cs" />
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReaderTest.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\common\OptoTelegramRawTest.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\iPerlHead\FlowDirectionDetectionTest.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\iPerlHead\IperlHeadTest.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />