Refactor Genesis serial communication handling (ISerialDriver interface compatible with SerialPort):

- Replace method-based `IsOpen()` checks with property-based `IsOpen` usage in `ISerialDriver` and related classes.
- Simplify `Open()` and add `Close()` methods to unify connection management.
- Introduce `InitializeThreeChannelState()` helper in `GenesisSmartReaderTest` for multi-channel state initialization.
- Adjust calculations in `CalculateTimeByChannels()` and `CalculateVolumeByChannels()` for clarity.
- Add new tests for multi-channel processing and validation of updated telegram handling logic.
This commit is contained in:
Michal Buzik 2026-03-24 10:04:30 +01:00
parent e26d88d437
commit a5a68ec457
6 changed files with 119 additions and 66 deletions

View File

@ -5,8 +5,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
{
public interface ISerialDriver : IDisposable
{
bool IsOpen();
bool Open();
bool IsOpen { get; }
void Open();
void Close();
void CloseConnection();
void DiscardInBuffer();

View File

@ -68,9 +68,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
_discardOutBufferOnOpen = discardOutBufferOnOpen;
}
public bool Open()
public void Open()
{
return OpenConnection(
OpenConnection(
_portName,
_baudRate,
_dataBits,
@ -188,6 +188,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
}
}
public void Close()
{
CloseConnection();
}
public void CloseConnection()
{
if (_serialPort != null)
@ -200,7 +205,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
}
}
public bool IsOpen() => _serialPort?.IsOpen == true;
public bool IsOpen => _serialPort?.IsOpen == true;
public void DiscardInBuffer()
{
@ -216,7 +221,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
public string ReadExisting()
{
if (!IsOpen())
if (!IsOpen)
throw new InvalidOperationException("Serial port not open");
return _serialPort.ReadExisting();
@ -230,7 +235,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
public string ReadLine()
{
if (!IsOpen())
if (!IsOpen)
throw new InvalidOperationException("Serial port not open");
return _serialPort.ReadLine();
@ -238,7 +243,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
public string ReadLine(int timeoutMs)
{
if (!IsOpen())
if (!IsOpen)
throw new InvalidOperationException("Serial port not open");
int originalTimeout = _serialPort.ReadTimeout;
@ -256,7 +261,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
public bool SendMessage(byte[] sendDataBytes, int length, int readTimeout = 1000, int writeTimeout = 1000)
{
if (!IsOpen()) return false;
if (!IsOpen) return false;
if (sendDataBytes == null || sendDataBytes.Length == 0) return true;
try
@ -275,7 +280,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
public byte[] SendAndWait(byte[] data, int timeoutMs)
{
if (!IsOpen())
if (!IsOpen)
throw new InvalidOperationException("Serial port not open");
try

View File

@ -175,7 +175,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
public SerialDriver BuildAndOpen()
{
var driver = Build();
if (!driver.Open())
driver.Open();
if (!driver.IsOpen)
{
throw new InvalidOperationException(driver.ErrorMessage);
}

View File

@ -962,13 +962,13 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
private double CalculateTimeByChannels()
{
double[] timestampSec = new double[iChanelsCount];
double[] timeDelta = new double[iChanelsCount];
for (int iChanel = 0; iChanel < iChanelsCount; iChanel++)
{
timestampSec[iChanel] = timestampSec[iChanel] - timestampSec0[iChanel];
timeDelta[iChanel] = this.timestampSec[iChanel] - this.timestampSec0[iChanel];
}
return Average(timestampSec);
return Average(timeDelta);
}
private double CalculateVolumeByChannels()
@ -976,7 +976,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
double[] volumeDelta = new double[iChanelsCount];
for (int iChanel = 0; iChanel < iChanelsCount; iChanel++)
{
volumeDelta[iChanel] = volumeLtr[iChanel] - volumeLtr0[iChanel];
volumeDelta[iChanel] = this.volumeLtr[iChanel] - this.volumeLtr0[iChanel];
}
return Average(volumeDelta);
@ -1008,7 +1008,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
if (_serialFactory != null)
{
optoSerialPort = _serialFactory();
if (!optoSerialPort.Open())
optoSerialPort.Open();
if (!optoSerialPort.IsOpen)
throw new InvalidOperationException("Failed to open injected serial driver.");
}
else
@ -1045,7 +1046,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
if (optoSerialPort != null)
{
optoSerialPort.CloseConnection();
optoSerialPort.Close();
optoSerialPort = null;
log.FatalFormat($"{Name} OptoPort closed: {this}");
}
@ -1076,7 +1077,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
endTelegramIdx3 = 0;
TestEndTelegramIx = 0;
if (optoSerialPort != null && optoSerialPort.IsOpen()) optoSerialPort.DiscardInBuffer();
if (optoSerialPort != null && optoSerialPort.IsOpen) optoSerialPort.DiscardInBuffer();
if (flowDirectionDetection != null) flowDirectionDetection.ClearFifo(); /// Clear FIFO for flow direction detection
@ -1853,10 +1854,10 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
public async Task<double> DataEntry_EndVolumeAsync()
{
if (optoSerialPort == null || !optoSerialPort.IsOpen())
if (optoSerialPort == null || !optoSerialPort.IsOpen)
{
StartDataStreamProcessing();
if (optoSerialPort == null || !optoSerialPort.IsOpen())
if (optoSerialPort == null || !optoSerialPort.IsOpen)
{
log.Error($"optoSerialPort COM: {this.OptoComPortNr} is not open - DataEntry_EndVolumeAsync()");
return Double.NaN;
@ -1900,7 +1901,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
log.Debug($"Try get End Volume! COM: {this.OptoComPortNr}, Volume: {volumeLtr}");
if (optoSerialPort != null && optoSerialPort.IsOpen()) CloseOptoSerialPort();
if (optoSerialPort != null && optoSerialPort.IsOpen) CloseOptoSerialPort();
if (!Double.IsNaN(volumeLtr[channel0]))
{
@ -1975,7 +1976,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
}
log.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}, Volume: {volumeLtr0}");
if (optoSerialPort != null && optoSerialPort.IsOpen()) CloseOptoSerialPort();
if (optoSerialPort != null && optoSerialPort.IsOpen) CloseOptoSerialPort();
if (!Double.IsNaN(volumeLtr0[channel0]))
{

View File

@ -24,13 +24,17 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
public void EnqueueLine(string line) => _lines.Enqueue(line);
public bool IsOpen() => _isOpen;
public bool IsOpen => _isOpen;
public bool Open()
public void Open()
{
OpenCalls++;
_isOpen = true;
return true;
}
public void Close()
{
CloseConnection();
}
public void CloseConnection()

View File

@ -6,34 +6,18 @@ using TBF.Rig;
using TBF.Rig.RegisterReaders.GenesisRegReader;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
{
[TestClass]
[TestSubject(typeof(GenesisSmartReader))]
public class GenesisSmartReaderTest
{
private const int ChannelCount = 3;
private static GenesisCfg CreateCfg()
{
Factory factory = new Factory();
return new GenesisCfg(factory);
// {
// OptoComPortNr = 7,
// RfidComPortNr = 8,
// MuxBoardNr = 1,
// Group = 1,
// CommunicationInterface = CommunicationInterface.RFID,
// ProcParams = new ProcParams
// {
// CalibTarget = 0,
// FactorLimitLo = 0,
// FactorLimitHi = 65535,
// Counting = Counting.Arbitrary
// }
// };
}
private static void SetPrivateField(object target, string fieldName, object value)
@ -54,6 +38,21 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
return (T)field.GetValue(target);
}
private static void InitializeThreeChannelState(GenesisSmartReader reader)
{
SetPrivateField(reader, "volumeRawExtLast", new double[ChannelCount]);
SetPrivateField(reader, "timestampExtLast", new double[ChannelCount]);
SetPrivateField(reader, "lastVolumeRaw", new double[ChannelCount]);
SetPrivateField(reader, "lastTimestamp", new double[ChannelCount]);
SetPrivateField(reader, "volumeLtr", new[] { 0.0, 0.0, 0.0 });
SetPrivateField(reader, "volumeLtr0", new[] { 0.0, 0.0, 0.0 });
SetPrivateField(reader, "timestampSec", new[] { 0.0, 0.0, 0.0 });
SetPrivateField(reader, "timestampSec0", new[] { 0.0, 0.0, 0.0 });
}
[TestMethod]
public void Start_ShouldOpenPort_AndEnableProcessing()
{
@ -62,8 +61,8 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.Initialize();
reader.Start();
Assert.IsTrue(fake.IsOpen());
Assert.IsTrue(fake.IsOpen);
Assert.IsTrue(fake.OpenCalls >= 1);
Assert.AreEqual(1, fake.DiscardInCalls);
}
@ -77,11 +76,11 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.Initialize();
reader.Start();
Assert.IsTrue(fake.IsOpen());
Assert.IsTrue(fake.IsOpen);
reader.Stop();
Assert.IsFalse(fake.IsOpen());
Assert.IsFalse(fake.IsOpen);
Assert.AreEqual(2, fake.CloseCalls);
}
@ -96,11 +95,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
SetPrivateField(reader, "currentTelegramIx", 12);
SetPrivateField(reader, "timeFromStart", 7);
// prevent ReadPulses() from crashing if needed
SetPrivateField(reader, "volumeLtr", 0.0);
SetPrivateField(reader, "volumeLtr0", 0.0);
SetPrivateField(reader, "timestampSec", 0.0);
SetPrivateField(reader, "timestampSec0", 0.0);
InitializeThreeChannelState(reader);
var ev = reader.Run();
@ -115,17 +110,13 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
InitializeThreeChannelState(reader);
// Important if these arrays are not initialized elsewhere
SetPrivateField(reader, "volumeRawExtLast", new double[3]);
SetPrivateField(reader, "timestampExtLast", new double[3]);
// valid sample line for your protocol
var line = "FFFFFE\t51EA\t0000\t65324E\t0087\tF6319DFF\t86";
var line = "@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331";
reader.optoSerialPort = fake;
fake.Open();
reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave);
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
@ -145,19 +136,69 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.Initialize();
reader.optoSerialPort = fake;
SetPrivateField(reader, "volumeRawExtLast", new double[3]);
SetPrivateField(reader, "timestampExtLast", new double[3]);
InitializeThreeChannelState(reader);
reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave);
var volumeRawExtLast = GetPrivateField<double[]>(reader, "volumeRawExtLast");
var timestampExtLast = GetPrivateField<double[]>(reader, "timestampExtLast");
Assert.IsTrue(
volumeRawExtLast[expectedChannelIndex] != 0 ||
timestampExtLast[expectedChannelIndex] != 0,
$"Channel {expectedChannelIndex} was not updated");
for (int i = 0; i < ChannelCount; i++)
{
if (i == expectedChannelIndex)
{
Assert.IsTrue(
volumeRawExtLast[i] != 0 || timestampExtLast[i] != 0,
$"Channel {i} was expected to be updated, but it was not.");
}
else
{
Assert.AreEqual(0.0, volumeRawExtLast[i], $"Unexpected volume update in channel {i}");
Assert.AreEqual(0.0, timestampExtLast[i], $"Unexpected timestamp update in channel {i}");
}
}
}
[TestMethod]
public void Run_WhenChannel0IsAverageModeMinus2_ShouldReadUsingAllThreeChannels()
{
var fake = new FakeSerialDriver();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
reader.Start();
SetPrivateField(reader, "channel0", -2);
SetPrivateField(reader, "volumeLtr0", new[] { 10.0, 20.0, 30.0 });
SetPrivateField(reader, "volumeLtr", new[] { 13.0, 24.0, 35.0 });
SetPrivateField(reader, "timestampSec0", new[] { 100.0, 100.0, 100.0 });
SetPrivateField(reader, "timestampSec", new[] { 110.0, 112.0, 114.0 });
var ev = reader.Run();
Assert.AreEqual(Event.ReadRegisterDone, ev);
Assert.AreEqual(4.0, reader.WMVolume, 1E-6); // average of (3, 4, 5)
Assert.AreEqual(4000, reader.WMPulses);
}
[TestMethod]
public void ProcessOptoLine_FirstValidTelegram_ShouldSetCurrentTelegramIndex()
{
var fake = new FakeSerialDriver();
fake.Open();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
reader.optoSerialPort = fake;
InitializeThreeChannelState(reader);
var line = "@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD";
reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave);
int currentTelegramIx = GetPrivateField<int>(reader, "currentTelegramIx");
Assert.AreEqual(0, currentTelegramIx);
}
}
}