Refactor Genesis use 3 chanels reader structure:

- Replace `Xylem.Common` namespace references with `TBF.Rig.RegisterReaders`.
- Introduce `GenesisSmartReaderTest` and `FakeSerialDriver` for unit testing.
- Revamp `FlowDirectionDetection` to support multiple channels.
- Make constants in `StreamingDecoder` public for enhanced accessibility.
- Update `AssemblyVersion` and `AssemblyFileVersion` to `3.9.3010.1`.
This commit is contained in:
Michal Buzik 2026-03-24 08:53:16 +01:00
parent 2522d1da9f
commit e26d88d437
42 changed files with 1124 additions and 656 deletions

View File

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

View File

@ -1,5 +1,6 @@
using System.Collections.Generic;
using TBF.Rig.Generic;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
using TBF.Rig.RegisterReaders.iPerlASICReader.implementations;
namespace TBF.Rig.RegisterReaders.GenesisRegReader
@ -10,9 +11,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
public override string ToString() { return ClassName; }
public IComponent DummyComponent() { return new SmartReader(); }
public IComponent DummyComponent() { return new GenesisSmartReader(); }
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components) { return new SmartReader(cfg); }
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components) { return new GenesisSmartReader(cfg); }
public IComponentCfg DefaultConfig() { return new GenesisCfg(this); }

View File

@ -31,6 +31,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
public int MuxBoardNr; /// 0 = use RfidComPort(Nr), otherwise mux. board nr. 1 .. 4
public int Group; /// Number written to QuidoRS to connct the watermeter to RfidComPort, 1 .. 10
public CommunicationInterface CommunicationInterface; /// Communication Interface: RFID or NFC
///
/// <summary> Procedure parameters </summary>
[XmlIgnore]
@ -46,7 +48,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
/// Private parameterless constructor invoked by all other (public) constructors
GenesisCfg()
{
Name = "iPerl";
Name = "Genesis";
ParentName = string.Empty;
OptoComPortNr = 10;
RfidComPortNr = 0; /// = use mux. board
@ -62,6 +64,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
this.Factory = factory;
}
public string ToString(int i)
{
return $"{Name} Group1 (mux#)={MuxBoardNr}, Group2={Group}, Opto=Com{OptoComPortNr}, {CommunicationInterface}=Com{RfidComPortNr}";

View File

@ -4,6 +4,9 @@
using System;
using System.Globalization;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.StreamingProtocol;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
@ -78,195 +81,75 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
{
}
/// <summary>
/// Parses optical telegram and returns OptoTelegramRaw object
/// </summary>
/// <description>
/// Create a configuration structure from a complete byte array
///
/// Telegram description:
///
/// AAAAAA[tab]BBBB[tab]CCCC[tab]DDDDDD[tab]EEEE[tab]FFFFFFFF[tab]GG[cr][lf] (42 bytes)
///
/// Data Comment Type Calculate to decimal
/// ----------------------------------------------------------------
/// AAAAAA EMF Int24 Value * 0.000000333
/// BBBB Magnetic field Int16 Value
/// CCCC Flow Int16 Value * 0.225 * Scalig factor
/// DDDDDD Volume Int24 Value / 16000 * Scaling factor
/// EEEE Impedance Int16 Value
/// FFFFFFFF Timestamp Uint32 Value / 8192
/// GG Checksum Byte
/// ----------------------------------------------------------------
///
/// Example:
/// FFFFFE 51EA 0000 65324E 0087 F6319DFF 86
/// FFDD3A 51F9 0000 65324E 0088 F631A60B 45
/// ...
/// </description>
/// <param name="data">A complete byte array data</param>
/// <returns>true = telegram OK, false = telegram NOK</returns>
// public bool UpdateFromString(string telegram, int counter, float refFlow, ref Int64 volumeRawExtLast, ref Int64 timestampExtLast, bool isLog = false)
// {
// DateTime = DateTime.Now;
// Counter = counter;
// RefFlow = refFlow;
//
// if ((telegram == null) || (telegram.Length < Length) ||
// (telegram[6] != '\t') || (telegram[11] != '\t') || (telegram[16] != '\t') ||
// (telegram[23] != '\t') || (telegram[28] != '\t') || (telegram[37] != '\t') ||
// (!isLog && (telegram[40] != '\r' || telegram[41] != '\n')))
// {
// Flags = OptoTelegramFlags.InvalidTelegram;
// return false;
// }
//
// UInt32 uEmfRaw;
// bool f1 = UInt32.TryParse(telegram.Substring(0, 6), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out uEmfRaw);
// EmfRaw = (uEmfRaw > 0x7FFFFF) ? ((int)uEmfRaw - 0x1000000) : (int)uEmfRaw;
//
// bool f2 = Int16.TryParse(telegram.Substring(7, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out MagneticFieldRaw);
// bool f3 = Int16.TryParse(telegram.Substring(12, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out FlowRaw);
// bool f4 = UInt32.TryParse(telegram.Substring(17, 6), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out VolumeRaw);
// bool f5 = Int16.TryParse(telegram.Substring(24, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out Impedance);
// bool f6 = UInt32.TryParse(telegram.Substring(29, 8), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out Timestamp);
// bool f7 = byte.TryParse(telegram.Substring(38, 2), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out CheckSum);
//
// byte calculatedCheckSum = 0;
// for (int i = 0; i < Length - 4; i++)
// {
// calculatedCheckSum += (byte)telegram[i];
// }
//
// bool allOk = f1 && f2 && f3 && f4 && f5 && f6 && f7 && (calculatedCheckSum == CheckSum);
//
// if (allOk)
// {
// ///
// /// Cope with 'VolumeRaw' overflow
// ///
// Int64 uncorrected = (Int64)(((UInt64)volumeRawExtLast & 0xFFFFFFFFFF000000UL) | VolumeRaw);
// if (Math.Abs(uncorrected - volumeRawExtLast) <= 0x800000L)
// {
// VolumeRawExt = volumeRawExtLast = uncorrected;
// }
// else if (Math.Abs(uncorrected + 0x1000000L - volumeRawExtLast) <= 0x800000L)
// {
// VolumeRawExt = volumeRawExtLast = uncorrected + 0x1000000L;
// }
// else if (Math.Abs(uncorrected - 0x1000000L - volumeRawExtLast) <= 0x800000L)
// {
// VolumeRawExt = volumeRawExtLast = uncorrected - 0x1000000L;
// }
// else
// {
// VolumeRawExt = volumeRawExtLast = uncorrected;
// }
//
// ///
// /// Cope with 'Timestamp' overflow
// ///
// uncorrected = (Int64)(((UInt64)timestampExtLast & 0xFFFFFFFF00000000UL) | Timestamp);
// if (Math.Abs(uncorrected - timestampExtLast) <= 0x80000000L)
// {
// TimestampExt = timestampExtLast = uncorrected;
// }
// else if (Math.Abs(uncorrected + 0x100000000L - timestampExtLast) <= 0x80000000L)
// {
// TimestampExt = timestampExtLast = uncorrected + 0x100000000L;
// }
// else if (Math.Abs(uncorrected - 0x100000000L - timestampExtLast) <= 0x80000000L)
// {
// TimestampExt = timestampExtLast = uncorrected - 0x100000000L;
// }
// else
// {
// TimestampExt = timestampExtLast = uncorrected;
// }
// }
//
// Flags = allOk ? OptoTelegramFlags.OK : OptoTelegramFlags.InvalidTelegram;
//
// return allOk;
// }
// -------- TIMESTAMP (seconds) --------
// bbbbbbbb unsigned 32 bit ASIC time stamp in 8192 ticks per second rolls over after 2^32
private const double TS_TICKS_PER_SEC = 8192.0;
private const double TS_RANGE = 4294967296.0 / TS_TICKS_PER_SEC; // 2^32 / 8192 = 524288 sec
private const double TS_RANGE = StreamingDecoder.CpuTimeOverflowS;
// -------- VOLUME (liters) --------
// vvvvvv is unsigned 24-bit, 1 tick = 1/4 ml = 0.00025 L
private const double VOL_LITERS_PER_TICK = 0.00025; // liters per tick
private const double VOL_RANGE = 16777216.0 * VOL_LITERS_PER_TICK; // 2^24 * 0.00025 = 4194.304 L
// -------- VOLUME (liters) --------
private const double GAL_TO_LITER = 3.785411784;
private const double VOL_RANGE = StreamingDecoder.DefaultAccuDutOverflowVolumeCm * 1000;
public void UpdateFromSmart(
Object data,
CalibrationRecord data,
int counter,
float refFlow,
ref double volumeRawExtLast,
ref double timestampExtLast)
{
int iChannel = data.Channel - 1;
DateTime = DateTime.Now;
Counter = counter;
RefFlow = refFlow;
throw new NotImplementedException();
FlowRaw = 0;
VolumeRaw = data.VolumeCm * 1000; // volume in litters
// FlowRaw = data.RawFlow;
// VolumeRaw = data.RawVolume;
//
// // ---- 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 = ts;
//
// // ---------- VOLUME UNWRAP ----------
// double v = VolumeRaw;
//
// if (double.IsNaN(volumeRawExtLast))
// {
// VolumeRawExt = volumeRawExtLast = v;
// }
// else
// {
// // nearest-lap unwrap
// //double k = Math.Round(volumeRawExtLast - v) / VOL_RANGE);
// if (v < volumeRawExtLast)
// {
// VolumeRawExt = volumeRawExtLast = v + VOL_RANGE;
// }
// else
// {
// VolumeRawExt = volumeRawExtLast = v;
// }
// }
//
// // ---------- 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;
// }
// ---- TIMESTAMP RAW (seconds, modulo TS_RANGE) ----
// If upstream conversion ever produced negative values, normalize them.
double ts = data.TimeS; // 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
{
// nearest-lap unwrap
//double k = Math.Round(volumeRawExtLast - v) / VOL_RANGE);
if (v < volumeRawExtLast)
{
VolumeRawExt = volumeRawExtLast = v + VOL_RANGE;
}
else
{
VolumeRawExt = volumeRawExtLast = v;
}
}
// ---------- 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;
}
}

View File

@ -1,5 +1,5 @@
using System;
using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments
{

View File

@ -1,5 +1,5 @@
using System;
using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments
{

View File

@ -1,5 +1,5 @@
using System;
using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments
{

View File

@ -2,7 +2,7 @@
using System.Text;
using Xylem.Common.Metrology.Measurements;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords
{
/// <inheritdoc />
/// <summary>

View File

@ -2,7 +2,7 @@
using System.Text;
using Xylem.Common.Metrology.Measurements;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords
{
/// <inheritdoc />
/// <summary>

View File

@ -2,7 +2,7 @@
using System.Text;
using Xylem.Common.Metrology.Measurements;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords
{
/// <inheritdoc />
/// <summary>

View File

@ -1,6 +1,6 @@
using System;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages
{
/// <summary>

View File

@ -1,6 +1,6 @@
using System;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages
{
/// <summary>
///

View File

@ -1,6 +1,6 @@
using System;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages
{
/// <summary>
/// Temperature Time Of Flight calculation

View File

@ -2,7 +2,6 @@
using System.Collections.Concurrent;
using System.Threading;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore.EventArguments;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol;
using BaseDataEventArgs = TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments.BaseDataEventArgs;

View File

@ -1,6 +1,5 @@
using System;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore.EventArguments;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol;
using BaseDataEventArgs = TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments.BaseDataEventArgs;

View File

@ -2,7 +2,6 @@
using System.Collections.Generic;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig;
using CommunicationConfig = TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig.CommunicationConfig;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
// ReSharper disable UnusedMember.Local
@ -15,13 +15,13 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protoco
{
private const Double MilliLitersToCmFactor = 1.0E-6;
private const Double CpuTimeToSecondsFactor = 1.0 / 0x10000;
private const Double CpuTimeOverflowS = 0x100000000 * CpuTimeToSecondsFactor;
public const Double CpuTimeOverflowS = 0x100000000 * CpuTimeToSecondsFactor;
private const Double LitersPerSecondToCmPerHourFactor = 3600.0 / 1000.0;
private const Double DefaultVolumeScaleRawPerMl = 1024.0;
private const Double DefaultVolumeFactorRawToCm = MilliLitersToCmFactor / DefaultVolumeScaleRawPerMl;
private const Double MaxGenesisAccuVolumeRaw = UInt32.MaxValue; //0x100000000; //2^32
private const Double DefaultAccuDutOverflowVolumeCm = MaxGenesisAccuVolumeRaw * DefaultVolumeFactorRawToCm;
public const Double DefaultAccuDutOverflowVolumeCm = MaxGenesisAccuVolumeRaw * DefaultVolumeFactorRawToCm;
private const Double DisplayMlSetupDutOverflowVolumeCm = 1000.0; //overflow of LCD if set to ml
private const Double TofToSecondsFactor38Bit = 1.0 / 0x4000000000; // 2^38

View File

@ -1,4 +1,4 @@
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
public struct ByteArray
{

View File

@ -1,4 +1,4 @@
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
// internal struct Enum8
public struct Enum8

View File

@ -1,4 +1,4 @@
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
public struct Rpc
{

View File

@ -1,6 +1,6 @@
using System;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
/// <summary>
/// Static type to define restore capability

View File

@ -1,4 +1,4 @@
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
public struct StatusT
{

View File

@ -1,6 +1,6 @@
using System;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
/// <summary>

View File

@ -1,6 +1,4 @@
using System;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
public struct UInt672
{

View File

@ -1,4 +1,4 @@
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
// ReSharper disable once InconsistentNaming
internal class st_radio_dewa

View File

@ -1,4 +1,4 @@
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
// ReSharper disable once InconsistentNaming
internal class st_radio_tfx

View File

@ -1,6 +1,6 @@
using System;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers
{

View File

@ -4,8 +4,7 @@ using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers
{

View File

@ -1,6 +1,6 @@
using System;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes;
using Xylem.Common.Hardware.WaterMeter.WaterMeterRegisters;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers

View File

@ -17,11 +17,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
private GenesisSmartReader genesisHead;
private SerialDriver serialDriver;
public static SerialDriver BuildConnection(GenesisSmartReader iHead)
public static SerialDriver BuildConnection(GenesisSmartReader genesidHead)
{
return new SerialDriverBuilder()
.WithPort($"COM{iHead.RfidComPortNr}")
.WithBaudRate(2400)
.WithPort($"COM{genesidHead.RfidComPortNr}")
.WithBaudRate(9600)
.WithDataBits(8)
.WithParity(Parity.None)
.WithStopBits(StopBits.One)

View File

@ -1,9 +1,25 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
using System;
using System.Text;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
{
public interface ISerialDriver
public interface ISerialDriver : IDisposable
{
bool IsOpen();
bool Open();
void CloseConnection();
void DiscardInBuffer();
void DiscardOutBuffer();
int BytesToRead { get; }
int BytesToWrite { get; }
string ReadLine();
string ReadExisting();
Encoding Encoding { get; }
byte[] SendAndWait(byte[] request, int timeout);
}
}

View File

@ -1,66 +1,73 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Threading;
using FluentNHibernate.Conventions;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger;
using System.Text;
using System.Threading.Tasks;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
{
public class SerialDriver : IDisposable, TestMethods.iPerlCommunication.communication.Utils.ISerialDriver
public class SerialDriver : IDisposable, ISerialDriver
{
readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(SerialDriver));
public string ErrorMessage { get; private set; }
private List<byte> SerialPortReadBuffer = new List<byte>();
private SerialPort _serialPort;
private readonly List<byte> _binMessages = new List<byte>();
private bool _isReading;
// Stored configuration (used by Builder)
private readonly string _portName;
private readonly int _baudRate;
private readonly int _dataBits;
private readonly Parity _parity;
private readonly StopBits _stopBits;
private readonly Handshake _handshake;
private readonly int _readTimeout;
private readonly int _writeTimeout;
private readonly int _openTimeoutMs;
private readonly string _newLine;
private readonly Encoding _encoding;
private readonly bool _dtrEnable;
private readonly bool _rtsEnable;
private readonly bool _discardInBufferOnOpen;
private readonly bool _discardOutBufferOnOpen;
private readonly ManualResetEvent _responseReceived = new ManualResetEvent(false);
#region Constructors
// Default constructor (legacy support)
public SerialDriver()
{
_serialPort = new SerialPort();
}
// Builder constructor
internal SerialDriver(
string portName,
int baudRate,
int dataBits,
Parity parity,
StopBits stopBits,
Handshake handshake,
int readTimeout,
int writeTimeout)
int writeTimeout,
int openTimeoutMs,
string newLine,
Encoding encoding,
bool dtrEnable,
bool rtsEnable,
bool discardInBufferOnOpen,
bool discardOutBufferOnOpen)
{
_portName = portName;
_baudRate = baudRate;
_dataBits = dataBits;
_parity = parity;
_stopBits = stopBits;
_handshake = handshake;
_readTimeout = readTimeout;
_writeTimeout = writeTimeout;
_openTimeoutMs = openTimeoutMs;
_newLine = newLine;
_encoding = encoding;
_dtrEnable = dtrEnable;
_rtsEnable = rtsEnable;
_discardInBufferOnOpen = discardInBufferOnOpen;
_discardOutBufferOnOpen = discardOutBufferOnOpen;
}
#endregion
#region Open / Close
// Builder-based open
public bool Open()
{
return OpenConnection(
@ -69,12 +76,19 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
_dataBits,
_parity,
_stopBits,
_handshake,
_readTimeout,
_writeTimeout
_writeTimeout,
_openTimeoutMs,
_newLine,
_encoding,
_dtrEnable,
_rtsEnable,
_discardInBufferOnOpen,
_discardOutBufferOnOpen
);
}
// Legacy API (unchanged)
public bool OpenConnection(
string comPort,
int baudrate,
@ -83,6 +97,42 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
StopBits stopbits,
int readTimeout = 1000,
int writeTimeout = 1000)
{
return OpenConnection(
comPort,
baudrate,
dataBits,
parity,
stopbits,
Handshake.None,
readTimeout,
writeTimeout,
5000,
"\n",
Encoding.ASCII,
true,
true,
true,
true
);
}
public bool OpenConnection(
string comPort,
int baudrate,
int dataBits,
Parity parity,
StopBits stopbits,
Handshake handshake,
int readTimeout,
int writeTimeout,
int openTimeoutMs,
string newLine,
Encoding encoding,
bool dtrEnable,
bool rtsEnable,
bool discardInBufferOnOpen,
bool discardOutBufferOnOpen)
{
lock (this)
{
@ -94,16 +144,23 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
_serialPort = new SerialPort(comPort, baudrate, parity, dataBits, stopbits)
{
Handshake = handshake,
NewLine = newLine,
Encoding = encoding,
ReadTimeout = readTimeout,
WriteTimeout = writeTimeout
WriteTimeout = writeTimeout,
DtrEnable = dtrEnable,
RtsEnable = rtsEnable
};
_serialPort.DataReceived += DataReceivedHandler;
_serialPort.Open();
}
catch (Exception ex)
var openTask = Task.Run(() => _serialPort.Open());
if (!openTask.Wait(openTimeoutMs))
{
ErrorMessage = $"COM error: Open failed {comPort}. {ex.Message}";
_serialPort.Dispose();
_serialPort = null;
ErrorMessage = $"COM error: Opening serial port {comPort} timed out after {openTimeoutMs} ms.";
return false;
}
@ -113,16 +170,28 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
return false;
}
if (discardInBufferOnOpen)
_serialPort.DiscardInBuffer();
if (discardOutBufferOnOpen)
_serialPort.DiscardOutBuffer();
log.Debug("SerialDriver opened successfully for port: " + comPort);
}
return true;
}
catch (Exception ex)
{
ErrorMessage = $"COM error: Open failed {comPort}. {ex.Message}";
CloseConnection();
return false;
}
}
}
public void CloseConnection()
{
if (_serialPort != null)
{
_serialPort.DataReceived -= DataReceivedHandler;
if (_serialPort.IsOpen)
_serialPort.Close();
@ -133,139 +202,75 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
public bool IsOpen() => _serialPort?.IsOpen == true;
#endregion
public void DiscardInBuffer()
{
if (_serialPort != null && _serialPort.IsOpen)
_serialPort.DiscardInBuffer();
}
#region Send / Receive
public void DiscardOutBuffer()
{
if (_serialPort != null && _serialPort.IsOpen)
_serialPort.DiscardOutBuffer();
}
public string ReadExisting()
{
if (!IsOpen())
throw new InvalidOperationException("Serial port not open");
return _serialPort.ReadExisting();
}
public Encoding Encoding => _serialPort?.Encoding ?? _encoding;
public int BytesToRead => (_serialPort != null && _serialPort.IsOpen) ? _serialPort.BytesToRead : 0;
public int BytesToWrite => (_serialPort != null && _serialPort.IsOpen) ? _serialPort.BytesToWrite : 0;
public string ReadLine()
{
if (!IsOpen())
throw new InvalidOperationException("Serial port not open");
return _serialPort.ReadLine();
}
public string ReadLine(int timeoutMs)
{
if (!IsOpen())
throw new InvalidOperationException("Serial port not open");
int originalTimeout = _serialPort.ReadTimeout;
try
{
_serialPort.ReadTimeout = timeoutMs;
return _serialPort.ReadLine();
}
finally
{
_serialPort.ReadTimeout = originalTimeout;
}
}
public bool SendMessage(byte[] sendDataBytes, int length, int readTimeout = 1000, int writeTimeout = 1000)
{
if (!IsOpen()) return false;
if (sendDataBytes.Length == 0) return true;
if (sendDataBytes == null || sendDataBytes.Length == 0) return true;
try
{
PrepareReading();
_serialPort.WriteTimeout = writeTimeout;
_serialPort.ReadTimeout = readTimeout;
_serialPort.Write(sendDataBytes, 0, length);
_isReading = true;
var stopwatch = Stopwatch.StartNew();
while (_isReading)
{
if (stopwatch.ElapsedMilliseconds > readTimeout)
{
ErrorMessage = "COM error: Receive timeout";
return false;
}
}
return true;
}
catch (Exception ex)
{
ErrorMessage = $"COM error: Transmit failed {_serialPort.PortName}. {ex.Message}";
return false;
}
return true;
}
private void PrepareReading()
{
_serialPort.DiscardInBuffer();
_binMessages.Clear();
_responseReceived.Reset();
SerialPortReadBuffer.Clear();
_isReading = true;
}
public byte[] GetRawData()
{
return _binMessages.ToArray();
}
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
lock (this)
{
if (_serialPort == null || !_serialPort.IsOpen) return;
try
{
//Thread.Sleep(5);
if (!SerialPortReadBuffer.IsEmpty())
{
SerialPortReadBuffer.Clear();
}
int iWordCounter = 0;
bool isStart = false;
bool isQuestion = false;
int iLength = 0;
while (true)//_serialPort.BytesToRead > 0
{
byte readByte = (byte)_serialPort.ReadByte();
//I have START
if (readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.IperlHatProtocolConstants.Start)
{
iWordCounter++;
isStart = true;
}
// I have QUESTION
if (readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.IperlHatProtocolConstants.Question)
{
iWordCounter++;
isQuestion = true;
}
//I count length from start
if (iWordCounter > 0)
iWordCounter++;
if (iWordCounter > 0)
{
//Store byte to data
SerialPortReadBuffer.Add(readByte);
}
// we have length
if (iLength == 0 && isStart && SerialPortReadBuffer.Count > 2 )
{
iLength = (int)SerialPortReadBuffer[2];
}
//If we have enough bytes
if (isStart && iLength > 0
&& (SerialPortReadBuffer.Count >= iLength ||
readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.IperlHatProtocolConstants.End
)
)
{
break;
}
//if we read END
if (isQuestion && readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.IperlHatProtocolConstants.End)
{
break;
}
}
if (SerialPortReadBuffer.Count > 0)
{
_binMessages.AddRange(SerialPortReadBuffer.ToArray());
_responseReceived.Set();
}
}
catch (TimeoutException te)
{
// Ignore shutdown race conditions
}
finally
{
_isReading = false;
}
}
}
public byte[] SendAndWait(byte[] data, int timeoutMs)
@ -273,27 +278,23 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
if (!IsOpen())
throw new InvalidOperationException("Serial port not open");
log.Debug("SendAndWait() - TX: " + HexFormatter.ToHex(data));
PrepareReading();
_serialPort.Write(data, 0, data.Length);
if (!_responseReceived.WaitOne(timeoutMs))
try
{
_serialPort.Write(data, 0, data.Length);
string line = ReadLine(timeoutMs);
return Encoding.GetBytes(line);
}
catch (TimeoutException)
{
log.Error("SendAndWait() - Response timeout! Details: " +
" SerialPortReadBuffer: " + HexFormatter.ToHex(SerialPortReadBuffer.ToArray()) +
" _binMessages" + HexFormatter.ToHex(_binMessages.ToArray()) +
"_responseReceived: " + _responseReceived.WaitOne(0)
);
ErrorMessage = "COM error: response timeout";
return null;
}
return GetRawData();
catch (Exception ex)
{
ErrorMessage = $"COM error: SendAndWait failed {_serialPort.PortName}. {ex.Message}";
return null;
}
}
#endregion
public void Dispose()
{
@ -302,6 +303,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
public override string ToString()
{
if (_serialPort == null)
return "SerialDriver: <null> (opened status:false)";
return "SerialDriver: " + _serialPort.PortName + " (opened status:" + _serialPort.IsOpen + ")";
}
}

View File

@ -1,8 +1,14 @@
using System;
using System.IO.Ports;
using System.Text;
using System.Threading.Tasks;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
{
using System;
using System.IO.Ports;
using System.Text;
public class SerialDriverBuilder
{
private string _portName;
@ -10,8 +16,20 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
private int _dataBits = 8;
private Parity _parity = Parity.None;
private StopBits _stopBits = StopBits.One;
private Handshake _handshake = Handshake.None;
private int _readTimeout = 1000;
private int _writeTimeout = 1000;
private int _openTimeoutMs = 5000;
private string _newLine = "\n";
private Encoding _encoding = Encoding.ASCII;
private bool _dtrEnable = true;
private bool _rtsEnable = true;
private bool _discardInBufferOnOpen = true;
private bool _discardOutBufferOnOpen = true;
public SerialDriverBuilder WithPort(string portName)
{
@ -43,6 +61,12 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
return this;
}
public SerialDriverBuilder WithHandshake(Handshake handshake)
{
_handshake = handshake;
return this;
}
public SerialDriverBuilder WithTimeouts(int readTimeout, int writeTimeout)
{
_readTimeout = readTimeout;
@ -50,32 +74,112 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
return this;
}
public SerialDriverBuilder WithReadTimeout(int readTimeout)
{
_readTimeout = readTimeout;
return this;
}
public SerialDriverBuilder WithWriteTimeout(int writeTimeout)
{
_writeTimeout = writeTimeout;
return this;
}
public SerialDriverBuilder WithOpenTimeout(int openTimeoutMs)
{
_openTimeoutMs = openTimeoutMs;
return this;
}
public SerialDriverBuilder WithNewLine(string newLine)
{
_newLine = newLine;
return this;
}
public SerialDriverBuilder WithEncoding(Encoding encoding)
{
_encoding = encoding ?? throw new ArgumentNullException(nameof(encoding));
return this;
}
public SerialDriverBuilder WithDtrEnable(bool enabled = true)
{
_dtrEnable = enabled;
return this;
}
public SerialDriverBuilder WithRtsEnable(bool enabled = true)
{
_rtsEnable = enabled;
return this;
}
public SerialDriverBuilder WithDiscardInputBufferOnOpen(bool discard = true)
{
_discardInBufferOnOpen = discard;
return this;
}
public SerialDriverBuilder WithDiscardOutputBufferOnOpen(bool discard = true)
{
_discardOutBufferOnOpen = discard;
return this;
}
private void Validate()
{
if (string.IsNullOrWhiteSpace(_portName))
throw new InvalidOperationException("Port name must be specified.");
if (_baudRate <= 0)
throw new InvalidOperationException("Baud rate must be greater than 0.");
if (_dataBits <= 0)
throw new InvalidOperationException("Data bits must be greater than 0.");
if (_openTimeoutMs <= 0)
throw new InvalidOperationException("Open timeout must be greater than 0.");
}
/// <summary>
/// Build driver WITHOUT opening connection
/// </summary>
public TestMethods.iPerlCommunication.communication.Utils.SerialDriver Build()
public SerialDriver Build()
{
return new TestMethods.iPerlCommunication.communication.Utils.SerialDriver(
Validate();
return new SerialDriver(
_portName,
_baudRate,
_dataBits,
_parity,
_stopBits,
_handshake,
_readTimeout,
_writeTimeout
_writeTimeout,
_openTimeoutMs,
_newLine,
_encoding,
_dtrEnable,
_rtsEnable,
_discardInBufferOnOpen,
_discardOutBufferOnOpen
);
}
/// <summary>
/// Build driver AND open connection
/// </summary>
public TestMethods.iPerlCommunication.communication.Utils.SerialDriver BuildAndConnect()
public SerialDriver BuildAndOpen()
{
var driver = Build();
if (!driver.Open())
{
throw new InvalidOperationException(driver.ErrorMessage);
}
return driver;
}
}

View File

@ -3,6 +3,7 @@
///
using System;
using System.Linq;
using Common;
using log4net;
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
@ -19,127 +20,154 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
private readonly double[] volumeRawFifo;
private readonly double[] timestampFifo; // centered timestamps
private int fifoCount;
private int fifoIx;
private DateTime lastFifoWriteTime;
private int iChanelsCount = 3;
private int[] fifoCount;
private int[] fifoIx;
private DateTime[] lastFifoWriteTime;
// regression sums (double is ideal here)
private double sumXX;
private double sumX;
private double sumXY;
private double sumY;
private double[] sumXX;
private double[] sumX;
private double[] sumXY;
private double[] sumY;
private double minSlope;
private double maxSlope;
private double[] minSlope;
private double[] maxSlope;
// timestamp centering for numerical stability
private double firstTimestamp = double.NaN;
private double[] firstTimestamp;
public FlowDirectionDetection()
{
volumeRawFifo = new double[FIFO_SIZE];
timestampFifo = new double[FIFO_SIZE];
fifoCount = new int[iChanelsCount];
fifoIx = new int[iChanelsCount];
lastFifoWriteTime = new DateTime[iChanelsCount];
sumXX = new double[iChanelsCount];
sumX = new double[iChanelsCount];
sumXY = new double[iChanelsCount];
sumY = new double[iChanelsCount];
minSlope = new double[iChanelsCount];
maxSlope = new double[iChanelsCount];
firstTimestamp = new []{double.NaN,double.NaN,double.NaN};
ClearFifo();
}
public void ClearFifo()
{
fifoCount = 0;
fifoIx = 0;
lastFifoWriteTime = DateTime.MinValue;
sumXX = 0;
sumX = 0;
sumXY = 0;
sumY = 0;
Array.Clear(fifoCount, 0, fifoCount.Length);
Array.Clear(fifoIx, 0, fifoIx.Length);
minSlope = 0;
maxSlope = 0;
firstTimestamp = double.NaN;
lastFifoWriteTime = Enumerable
.Repeat(DateTime.MinValue, lastFifoWriteTime.Length)
.ToArray();
Array.Clear(sumXX, 0, sumXX.Length);
Array.Clear(sumX, 0, sumX.Length);
Array.Clear(sumXY, 0, sumXY.Length);
Array.Clear(sumY, 0, sumY.Length);
Array.Clear(minSlope, 0, minSlope.Length);
Array.Clear(maxSlope, 0, maxSlope.Length);
// clear FIFO - TODO test if this is necessary
Array.Clear(volumeRawFifo, 0, volumeRawFifo.Length);
Array.Clear(timestampFifo, 0, timestampFifo.Length);
firstTimestamp = Enumerable
.Repeat(double.NaN, firstTimestamp.Length)
.ToArray();
}
/// <summary>
/// Add sample to rolling FIFO and update regression sums
/// </summary>
public void WriteToFifo(double volumeRaw, double timestamp)
public void WriteToFifo(double[] volumeRaw, double[] timestamp, int iChanel)
{
// establish time origin (CRITICAL for double precision)
if (double.IsNaN(firstTimestamp))
firstTimestamp = timestamp;
if (double.IsNaN(firstTimestamp[iChanel]))
firstTimestamp[iChanel] = timestamp[iChanel];
double x = timestamp - firstTimestamp; // centered time
double y = volumeRaw;
double x = timestamp[iChanel] - firstTimestamp[iChanel]; // centered time
double y = volumeRaw[iChanel];
// remove oldest sample if buffer full
if (fifoCount == FIFO_SIZE)
if (fifoCount[iChanel] == FIFO_SIZE)
{
double oldX = timestampFifo[fifoIx];
double oldY = volumeRawFifo[fifoIx];
double oldX = timestampFifo[fifoIx[iChanel]];
double oldY = volumeRawFifo[fifoIx[iChanel]];
sumXX -= oldX * oldX;
sumX -= oldX;
sumXY -= oldX * oldY;
sumY -= oldY;
sumXX[iChanel] -= oldX * oldX;
sumX[iChanel] -= oldX;
sumXY[iChanel] -= oldX * oldY;
sumY[iChanel] -= oldY;
}
else
{
fifoCount++;
fifoCount[iChanel]++;
}
// add new sample
sumXX += x * x;
sumX += x;
sumXY += x * y;
sumY += y;
sumXX[iChanel] += x * x;
sumX[iChanel] += x;
sumXY[iChanel] += x * y;
sumY[iChanel] += y;
// store sample
timestampFifo[fifoIx] = x;
volumeRawFifo[fifoIx] = y;
timestampFifo[fifoIx[iChanel]] = x;
volumeRawFifo[fifoIx[iChanel]] = y;
fifoIx = (fifoIx + 1) % FIFO_SIZE;
lastFifoWriteTime = DateTime.Now;
fifoIx[iChanel] = (fifoIx[iChanel] + 1) % FIFO_SIZE;
lastFifoWriteTime[iChanel] = DateTime.Now;
}
public bool AreFifoDataValid()
{
return (DateTime.Now.Subtract(lastFifoWriteTime).TotalSeconds <= MAX_OPTO_DROPOUT)
&& (fifoCount == FIFO_SIZE);
}
// public bool AreFifoDataValid()
// {
// return (DateTime.Now.Subtract(lastFifoWriteTime).TotalSeconds <= MAX_OPTO_DROPOUT)
// && (fifoCount == FIFO_SIZE);
// }
public OptoHeadState CheckFlowDirection(Counting counting, string iPerlHeadName)
{
if (!AreFifoDataValid())
return OptoHeadState.OptoNok;
try
{
double N = fifoCount;
double numer = N * sumXY - sumX * sumY;
double denom = N * sumXX - sumX * sumX;
if (Math.Abs(denom) < 1e-12)
return OptoHeadState.DirNok;
double slope = numer / denom;
if (slope > maxSlope) maxSlope = slope;
if (slope < minSlope) minSlope = slope;
if (counting == Counting.Arbitrary ||
(counting == Counting.Positive && maxSlope > Math.Abs(2 * minSlope)) ||
(counting == Counting.Negative && minSlope < -Math.Abs(2 * maxSlope)))
{
return OptoHeadState.OptoAndDirOK;
}
return OptoHeadState.DirNok;
}
catch (Exception ex)
{
log.ErrorFormat("{0} : CheckFlowDirection() failed: {1}", iPerlHeadName, ex);
return OptoHeadState.DirNok;
}
}
// public OptoHeadState CheckFlowDirection(Counting counting, string iPerlHeadName)
// {
// if (!AreFifoDataValid())
// return OptoHeadState.OptoNok;
//
// try
// {
// double N = fifoCount;
//
// double numer = N * sumXY - sumX * sumY;
// double denom = N * sumXX - sumX * sumX;
//
// if (Math.Abs(denom) < 1e-12)
// return OptoHeadState.DirNok;
//
// double slope = numer / denom;
//
// if (slope > maxSlope) maxSlope = slope;
// if (slope < minSlope) minSlope = slope;
//
// if (counting == Counting.Arbitrary ||
// (counting == Counting.Positive && maxSlope > Math.Abs(2 * minSlope)) ||
// (counting == Counting.Negative && minSlope < -Math.Abs(2 * maxSlope)))
// {
// return OptoHeadState.OptoAndDirOK;
// }
//
// return OptoHeadState.DirNok;
// }
// catch (Exception ex)
// {
// log.ErrorFormat("{0} : CheckFlowDirection() failed: {1}", iPerlHeadName, ex);
// return OptoHeadState.DirNok;
// }
// }
}
}

View File

@ -14,9 +14,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
public class GenesisImplHeadTestCtrl : IUniHeadTestCtrl<OptoReceivedEventArgs>
{
Thread optoThread;
GenesisSmartReader _genesiHead;
//GenesisSmartReader _genesiHead;
public GenesisSmartReader Head { get { return _genesiHead;} }
public GenesisSmartReader Head { get { return ISmartReader as GenesisSmartReader;} }
public ISmartReader ISmartReader { get; set; }
public bool stopWorkerThread { get; set; }
@ -69,7 +69,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{"Read PCB", Operations.ReadPcbCmd},
{"Set Test Mode", Operations.SetTestModeCmd},
{"Set Active Mode", Operations.SetActiveModeCmd},
#if DEBUG
#if TRUE
{"Start Read Opto Data", Operations.ReadOptoDataCmd},
{"Stop Read Opto Data", Operations.StopReadOptoDataCmd},
#endif
@ -136,11 +136,13 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
stopWorkerThread = true;
a.ISmartReader.StopDataStreamProcessing(); // close opto port
//Head.OptoHeadTest.StopDataStreamProcessing(); // close opto port
}
if (!optoThread.IsAlive)
{
a.ISmartReader.StartDataStreamProcessing(); // open opto port
//Head.OptoHeadTest.StartDataStreamProcessing(); // open opto port
optoThread.Start();
}

View File

@ -9,24 +9,27 @@ using Common;
using Config.Entities;
using log4net;
using NHibernate;
using Remotion.Linq.Parsing.Structure.IntermediateModel;
using Sensus.iPerl.NfcHandler;
using TBF.Rig.Generic;
using TBF.Rig.GenericDevices;
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.StreamingProtocol;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer;
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
/// <summary>
/// based on IPerlReader class
/// </summary>
public class GenesisSmartReader : ComponentBase, IDevice, IRegReaderDatastream, ISessionDataMngmnt, IOperation, /*ISmartReader,*/ IRegReaderSmart
public class GenesisSmartReader : ComponentBase, IDevice, IRegReaderDatastream, ISessionDataMngmnt, IOperation, ISmartReader, IRegReaderSmart
{
private static readonly ILog log = LogManager.GetLogger(typeof(GenesisSmartReader));
@ -60,7 +63,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
readonly GenesisCfg genesisHeadCfg;
string ISmartReader.CommInterface => _commInterface;
public int RfidComPortNr { get { return genesisHeadCfg.RfidComPortNr; } }
public bool CommFailed { get; set; }
public bool Disabled { get; set; }
public int OptoComPortNr { get { return genesisHeadCfg.OptoComPortNr; } }
public int MuxBoardNrOrGroup14 { get { return genesisHeadCfg.MuxBoardNr; } }
public int Group { get { return genesisHeadCfg.Group; } }
@ -108,8 +115,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
}
}
public bool Disabled;
public bool CommFailed;
//public bool Disabled;
//public bool CommFailed;
public int ResultCode;
@ -120,11 +127,13 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
public float[] X { get { return x; } }
private static int iChanelsCount = 3;
private int firstChanel;
/// <summary>
/// Passed to OptoTelegramRaw.UpdateFromString(...)
/// </summary>
double volumeRawExtLast;
double timestampExtLast;
double[] volumeRawExtLast;
double[] timestampExtLast;
FlowDirectionDetection flowDirectionDetection;
@ -182,6 +191,10 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
public int WMPulses { get { return wmPulses; } }
public int WMRefPulses { get { return wmRefPulses; } }
public double BeginWMState { get { return ResolveNaNDouble(beginWMState); } }
double ISmartReader.EndWMState { get; set; }
double ISmartReader.BeginWMState { get; set; }
double ICommonRegReader.EndWMState { get; set; }
double ICommonRegReader.BeginWMState { get; set; }
public double EndWMState { get { return ResolveNaNDouble(endWMState); } }
public double WMVolume { get { return ResolveNaNDouble(wmVolume); } }
public double WMTestTime { get { return ResolveNaNDouble(wmTestTime); } }
@ -390,9 +403,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
///
/// Timestamp from the opto telegram
///
private double lastTimestamp;
private double timestampSec;
private double timestampSec0;
private double[] lastTimestamp;
private double[] timestampSec;
private double[] timestampSec0;
/// Test start volume for metrology in seconds
public double TimestampSecStart
@ -413,9 +426,17 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
///
/// Volume of water from the opto telegram
///
private double lastVolumeRaw; /// Last read raw volume
private double volumeLtr;
private double volumeLtr0;
private double[] lastVolumeRaw; /// Last read raw volume
private double[] volumeLtr;
private double[] volumeLtr0;
private int channel0 = -1;
private double Average(double[] data)
{
return data.Sum() / data.Length;
}
/// Test start volume for metrology in liters
public double VolumeLtrStart
@ -437,7 +458,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
///
/// Opto serial port and worker thread related private variables
///
private SerialPort optoSerialPort;
public ISerialDriver optoSerialPort;
public GenesisSmartReader() { }
@ -448,11 +469,32 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
genesisHeadCfg = cfg as GenesisCfg;
}
private readonly Func<ISerialDriver> _serialFactory;
public GenesisSmartReader(Generic.IComponentCfg cfg, Func<ISerialDriver> serialFactory = null)
: base(cfg)
{
genesisHeadCfg = cfg as GenesisCfg;
_serialFactory = serialFactory;
}
public override void Initialize()
{
x = new float[FeatureVectorSize];
flowDirectionDetection = new FlowDirectionDetection();
volumeRawExtLast = new double[iChanelsCount];
timestampExtLast = new double[iChanelsCount];
lastTimestamp = new double[iChanelsCount];
timestampSec = new double[iChanelsCount];
timestampSec0 = new double[iChanelsCount];
lastVolumeRaw = new double[iChanelsCount]; /// Last read raw volume
volumeLtr = new double[iChanelsCount];
volumeLtr0 = new double[iChanelsCount];
/// Allocate memory for opto-data from iPerl
optoData = new OptoTelegramRaw[OptoDataBufferSize];
for (int i = 0; i < OptoDataBufferSize; i++)
@ -468,18 +510,20 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
partOfTelegram = string.Empty;
optoSerialPort = null;
if (DebugLevel == DebugMode.Normal)
{
/// Open serial port: 9600 Bd, 8 data bits, 1 stop bit, no parity
/// Check whether head is connected, working
try
{
OpenOptoSerialPort($"COM{genesisHeadCfg.OptoComPortNr}", 38400, Parity.None, 8, StopBits.One, Handshake.None);
OpenOptoSerialPort($"COM{genesisHeadCfg.OptoComPortNr}", 115200, Parity.None, 8, StopBits.One, Handshake.None);
CloseOptoSerialPort();
log.FatalFormat($"{Name} initialized: {this}");
}
catch (Exception ex)
{
log.FatalFormat($"{Name} initialization failed: {ex.Message}");
throw new Exception(ex.Message);
}
}
@ -534,31 +578,31 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
Counting currentFlowDir;
///
public OptoHeadState CheckFlowDirection()
{
return (flowDirectionDetection != null) ? flowDirectionDetection.CheckFlowDirection(currentFlowDir, Name) : OptoHeadState.DirNok;
}
///
public void ChangeFlowDirection()
{
switch (InitFlowDir)
{
case Counting.Positive:
currentFlowDir = Counting.Negative;
break;
case Counting.Negative:
currentFlowDir = Counting.Positive;
break;
case Counting.Arbitrary:
default:
currentFlowDir = Counting.Arbitrary;
break;
}
if (flowDirectionDetection != null) flowDirectionDetection.ClearFifo(); /// Clear FIFO for flow direction detection
}
// public OptoHeadState CheckFlowDirection()
// {
// return (flowDirectionDetection != null) ? flowDirectionDetection.CheckFlowDirection(currentFlowDir, Name) : OptoHeadState.DirNok;
// }
// ///
// public void ChangeFlowDirection()
// {
// switch (InitFlowDir)
// {
// case Counting.Positive:
// currentFlowDir = Counting.Negative;
// break;
//
// case Counting.Negative:
// currentFlowDir = Counting.Positive;
// break;
//
// case Counting.Arbitrary:
// default:
// currentFlowDir = Counting.Arbitrary;
// break;
// }
//
// if (flowDirectionDetection != null) flowDirectionDetection.ClearFifo(); /// Clear FIFO for flow direction detection
// }
public void RunDeviceBefore()
@ -619,10 +663,10 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
ResultCode = 0;
volumeLtr = Double.NaN;
volumeLtr0 = Double.NaN;
timestampSec = Double.NaN;
timestampSec0 = Double.NaN;
volumeLtr = Enumerable.Repeat(Double.NaN, volumeLtr.Length).ToArray();
volumeLtr0 = Enumerable.Repeat(Double.NaN, volumeLtr.Length).ToArray();
timestampSec = Enumerable.Repeat(Double.NaN, volumeLtr.Length).ToArray();
timestampSec0 = Enumerable.Repeat(Double.NaN, volumeLtr.Length).ToArray();
extraDataPath = null;
@ -884,13 +928,58 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
void ReadPulses()
{
beginWMState = volumeLtr0;
endWMState = volumeLtr;
if (channel0 == -1)
return;
if (channel0 == -2)
{
beginWMState = 0;
endWMState = CalculateVolumeByChannels();
wmVolume = Math.Abs(endWMState - beginWMState);
wmPulses = (int)(wmVolume * (double)PulsesPerLtr + 0.5);
log.Debug("wmPulses = " + wmPulses + "wmVolume = " + wmVolume + "PulsesPerLtr = " + PulsesPerLtr + "");
if (StateMachine.ControlBoardMain != null)
wmRefPulses = StateMachine.ControlBoardMain.RefPulses;
wmTestTime = timestampSec - timestampSec0;
else
wmRefPulses = 0;
wmTestTime = CalculateTimeByChannels();
return;
}
beginWMState = volumeLtr0[channel0];
endWMState = volumeLtr[channel0];
wmVolume = Math.Abs(endWMState - beginWMState);
wmPulses = (int)(wmVolume * (double)PulsesPerLtr + 0.5);
log.Debug("wmPulses = " + wmPulses + "wmVolume = " + wmVolume + "PulsesPerLtr = " + PulsesPerLtr + "");
if (StateMachine.ControlBoardMain != null)
wmRefPulses = StateMachine.ControlBoardMain.RefPulses;
else
wmRefPulses = 0;
wmTestTime = timestampSec[channel0] - timestampSec0[channel0];
}
private double CalculateTimeByChannels()
{
double[] timestampSec = new double[iChanelsCount];
for (int iChanel = 0; iChanel < iChanelsCount; iChanel++)
{
timestampSec[iChanel] = timestampSec[iChanel] - timestampSec0[iChanel];
}
return Average(timestampSec);
}
private double CalculateVolumeByChannels()
{
double[] volumeDelta = new double[iChanelsCount];
for (int iChanel = 0; iChanel < iChanelsCount; iChanel++)
{
volumeDelta[iChanel] = volumeLtr[iChanel] - volumeLtr0[iChanel];
}
return Average(volumeDelta);
}
private void OpenOptoSerialPort(
@ -914,29 +1003,33 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
CloseOptoSerialPort();
var port = new SerialPort(comPort, baudRate, parity, dataBits, stopBit)
log.Info($"Opening serial port {comPort} at {baudRate} baud rate");
if (_serialFactory != null)
{
Handshake = handshake,
NewLine = "\r\n",
Encoding = Encoding.ASCII
};
port.ReadTimeout = 5000;
port.WriteTimeout = 5000;
port.DtrEnable = true;
port.RtsEnable = true;
// Run Open() on separate task
var openTask = Task.Run(() => port.Open());
if (!openTask.Wait(openTimeoutMs))
{
port.Dispose();
throw new TimeoutException(
$"Opening serial port {comPort} timed out after {openTimeoutMs} ms.");
optoSerialPort = _serialFactory();
if (!optoSerialPort.Open())
throw new InvalidOperationException("Failed to open injected serial driver.");
}
else
{
optoSerialPort = new SerialDriverBuilder()
.WithPort(comPort)
.WithBaudRate(baudRate)
.WithParity(parity)
.WithDataBits(dataBits)
.WithStopBits(stopBit)
.WithHandshake(handshake)
.WithNewLine("\n")
.WithReadTimeout(5000)
.WithWriteTimeout(5000)
.WithDtrEnable(true)
.WithRtsEnable(true)
.WithOpenTimeout(openTimeoutMs)
.WithDiscardInputBufferOnOpen(true)
.WithDiscardOutputBufferOnOpen(true)
.BuildAndOpen();
}
optoSerialPort = port;
log.FatalFormat($"{Name} OptoPort opened: {this}");
}
@ -944,7 +1037,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
log.FatalFormat($"{Name} OptoPort - error opening port: {this}"
+ Environment.NewLine + ex.Message);
throw; // NEVER use "throw ex;" (destroys stack trace)
throw;
}
}
@ -952,7 +1045,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
if (optoSerialPort != null)
{
optoSerialPort.Close();
optoSerialPort.CloseConnection();
optoSerialPort = null;
log.FatalFormat($"{Name} OptoPort closed: {this}");
}
@ -967,7 +1060,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
try
{
OpenOptoSerialPort($"COM{genesisHeadCfg.OptoComPortNr}", 38400, Parity.None, 8, StopBits.One, Handshake.None);
OpenOptoSerialPort($"COM{genesisHeadCfg.OptoComPortNr}", 115200, Parity.None, 8, StopBits.One, Handshake.None);
}
catch (Exception)
{
@ -983,7 +1076,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
@ -991,6 +1084,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
dataStreamState = DataStreamState.ProcessAndSave;
}
public void SetCommunicationInterface(string commInterface)
{
throw new NotImplementedException();
}
/// <summary>
/// Returns true when processing and saving datastream data is in progress
/// </summary>
@ -999,6 +1097,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return dataStreamState == DataStreamState.ProcessAndSave;
}
void ISmartReader.SetRfidInterface()
{
SetRfidInterface();
}
/// <summary>
/// Stop processing and saving datastream data
/// </summary>
@ -1015,16 +1118,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
bool synchronized2;
string partOfTelegram;
DiagnosticLedParser parser = new DiagnosticLedParser(DiagnosticLedState.State4);
private string _commInterface;
/// <summary>
/// Reads opto-datastream via serial port. Invoked from RunDeviceBefore()
///
/// Telegram description:
/// AAAAAA[tab]BBBB[tab]CCCC[tab]DDDDDD[tab]EEEE[tab]FFFFFFFF[tab]GG[cr][lf] (42 bytes)
/// Example:
/// FFFFFE 51EA 0000 65324E 0087 F6319DFF 86
/// FFDD3A 51F9 0000 65324E 0088 F631A60B 45
/// ...
/// </summary>
/// <param name="optoState">OptoState.Read or OptoState.Flush</param>
@ -1041,54 +1139,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
// This will now wait max 3 seconds (ReadTimeout)
string line = optoSerialPort.ReadLine();
byte[] bytes = optoSerialPort.Encoding.GetBytes(line);
log.Debug("ComPort: " + OptoComPortNr + " OPTHO RX ← " + HexFormatter.ToSerialHex(bytes));
if (optoState == DataStreamState.ProcessAndSave)
{
DiagnosticLedState4Data data =
(DiagnosticLedState4Data)parser.ParseLine(line, false);
int bufferIx = BufferIdx(optoDataCount);
if (synchronized)
{
optoData[bufferIx].Counter = optoDataCount;
optoData[bufferIx].SetFlags(OptoTelegramFlags.SyncError);
}
if (data != null)
{
log.Debug($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
logStream.Debug($"ID: {OptoComPortNr} " + data);
optoData[bufferIx].UpdateFromSmart(
data,
optoDataCount,
Convert.ToSingle(Sequences.ProcessData.RefFlow.Val),
ref volumeRawExtLast,
ref timestampExtLast);
flowDirectionDetection.WriteToFifo(volumeRawExtLast, timestampExtLast);
OptoTelegramReceived(
optoDataCount,
true,
volumeRawExtLast,
timestampExtLast);
}
optoDataCount++;
}
else
{
// Flush mode
DiagnosticLedState4Data data =
(DiagnosticLedState4Data)parser.ParseLine(line, false);
flowDirectionDetection.WriteToFifo(volumeRawExtLast, timestampExtLast);
}
ProcessOptoLine(line, optoState);
}
}
catch (TimeoutException)
@ -1105,25 +1156,144 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
}
}
public void ProcessOptoLine(string line, DataStreamState optoState)
{
var encoding = optoSerialPort?.Encoding ?? Encoding.ASCII;
byte[] bytes = encoding.GetBytes(line);
log.Debug("ComPort: " + OptoComPortNr + " OPTHO RX ← " + HexFormatter.ToSerialHex(bytes));
StreamingDecoder streamingDecode = new StreamingDecoder(true);
streamingDecode.DecodeMsg(line);
CalibrationRecord data = streamingDecode.DataCalib;
if (optoState == DataStreamState.ProcessAndSave)
{
int bufferIx = BufferIdx(optoDataCount);
if (synchronized)
{
optoData[bufferIx].Counter = optoDataCount;
optoData[bufferIx].SetFlags(OptoTelegramFlags.SyncError);
}
if (data != null)
{
int iChanel = data.Channel - 1;
if (iChanel >= 0 && iChanel < iChanelsCount)
{
optoData[bufferIx].UpdateFromSmart(
data,
optoDataCount,
Convert.ToSingle(Sequences.ProcessData.RefFlow.Val),
ref volumeRawExtLast[iChanel],
ref timestampExtLast[iChanel]);
flowDirectionDetection.WriteToFifo(volumeRawExtLast, timestampExtLast, iChanel);
OptoTelegramReceived(
optoDataCount,
true,
volumeRawExtLast[iChanel],
timestampExtLast[iChanel],
iChanel);
}
}
optoDataCount++;
}
else
{
if (data != null)
{
int iChanel = data.Channel - 1;
if (iChanel >= 0 && iChanel < iChanelsCount)
{
flowDirectionDetection.WriteToFifo(volumeRawExtLast, timestampExtLast, iChanel);
}
}
}
}
void ISmartReader.ResetNfcInterface(bool? nfc_on)
{
ResetNfcInterface(nfc_on);
}
private string _rxBuffer = "";
public string ReadOptoData()
{
if (optoSerialPort is null) return "";
string received = ".";
lock (this)
{
try
{
string line = optoSerialPort.ReadLine(); // string
byte[] bytes = optoSerialPort.Encoding.GetBytes(line);
string line = optoSerialPort.ReadLine();
var encoding = optoSerialPort?.Encoding ?? Encoding.ASCII;
byte[] bytes = encoding.GetBytes(line);
received = HexFormatter.ToSerialHex(bytes);
log.Debug("RX ← " + received);
try
{
StreamingDecoder _streamingDecode = new StreamingDecoder(true);
_streamingDecode.DecodeMsg(line);
CalibrationRecord data = _streamingDecode.DataCalib;
log.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
}
catch (Exception ex)
{
log.Error($"OPTHO {OptoComPortNr} Read error: {ex.Message}");
}
// string line = optoSerialPort.ReadExisting();
//
// if (!string.IsNullOrEmpty(line))
// {
// string visual = line.Replace("\r", "\\r").Replace("\n", "\\n");
//
// bool hasCR = line.Contains('\r');
// bool hasLF = line.Contains('\n');
// bool hasCRLF = line.Contains("\r\n");
//
// log.Debug($"RAW: [{visual}]");
// log.Debug($"CR: {hasCR}, LF: {hasLF}, CRLF: {hasCRLF}");
//
// _rxBuffer += line;
//
// string[] parts = _rxBuffer.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.None);
//
// // incomplete tail
// if (parts.Length > 0)
// {
// _rxBuffer = parts[parts.Length - 1];
// }
// else
// {
// _rxBuffer = "";
// }
// // ~ incomplete tail
//
// for (int i = 0; i < parts.Length - 1; i++)
// {
// string parsed = parts[i];
// log.Debug($"PARSED: [{parsed}]");
//
// byte[] bytes = optoSerialPort.Encoding.GetBytes(parsed);
// received = HexFormatter.ToSerialHex(bytes);
//
// log.Debug("RX ← " + received);
// }
//}
}
catch (TimeoutException)
{
// ✅ No data received within 3 seconds
log.Debug($"ReadOptoData() OPTHO {OptoComPortNr} ReadLine timeout (3s) - continuing.");
// Just continue without parsing
log.Debug($"ReadOptoData() OPTHO {OptoComPortNr} timeout - continuing.");
}
catch (Exception ex)
{
@ -1133,6 +1303,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return received;
}
void ISmartReader.SetNfcInterface()
{
SetNfcInterface();
}
public async Task<string> ReadOptoDataWithTimeoutAsync(int timeoutMs = 5000)
{
if (optoSerialPort == null)
@ -1194,31 +1369,36 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
}
void OptoTelegramReceived(int currentIx, bool async, double volumeRawExt, double timestampRawExt)
void OptoTelegramReceived(int currentIx, bool async, double volumeRawExt, double timestampRawExt, int iChanel)
{
currentTelegramIx = currentIx;
lastVolumeRaw = volumeRawExt;
lastTimestamp = timestampRawExt;
lastVolumeRaw[iChanel] = volumeRawExt;
lastTimestamp[iChanel] = timestampRawExt;
if (Double.IsNaN(volumeLtr) && Double.IsNaN(volumeLtr0))
if (channel0 == -1)
{
volumeLtr = lastVolumeRaw;
volumeLtr0 = volumeLtr;
channel0 = iChanel;
}
if (Double.IsNaN(volumeLtr[iChanel]) && Double.IsNaN(iChanel))
{
volumeLtr[iChanel] = lastVolumeRaw[iChanel];
volumeLtr0[iChanel] = volumeLtr[iChanel];
}
else
{
volumeLtr = lastVolumeRaw;
volumeLtr[iChanel] = lastVolumeRaw[iChanel];
}
if (Double.IsNaN(timestampSec)&& Double.IsNaN(timestampSec0))
if (Double.IsNaN(timestampSec[iChanel])&& Double.IsNaN(timestampSec0[iChanel]))
{
timestampSec = lastTimestamp;
timestampSec0 = timestampSec;
timestampSec[iChanel] = lastTimestamp[iChanel];
timestampSec0[iChanel] = timestampSec[iChanel];
}
else
{
timestampSec = lastTimestamp;
timestampSec[iChanel] = lastTimestamp[iChanel];
}
}
@ -1673,10 +1853,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;
@ -1686,10 +1866,10 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return await Task.Run(() =>
{
log.Debug($"Try get End Volume! COM: {this.OptoComPortNr}");
volumeLtr = Double.NaN;
volumeLtr[channel0] = Double.NaN;
int counter = 0;
while (Double.IsNaN(volumeLtr) && counter < 2)
while (Double.IsNaN(volumeLtr[channel0]) && counter < 2)
{
counter++;
try
@ -1699,9 +1879,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
try
{
DiagnosticLedState4Data data =
(DiagnosticLedState4Data)parser.ParseLine(readOptoDataWithTimeout, false);
volumeLtr = data.RawVolume;
StreamingDecoder _streamingDecode = new StreamingDecoder(true);
_streamingDecode.DecodeMsg(readOptoDataWithTimeout);
CalibrationRecord data = _streamingDecode.DataCalib;
log.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
volumeLtr[channel0] = data.VolumeCm * 1000;
break;
}
catch (Exception ex)
@ -1718,11 +1900,11 @@ 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))
if (!Double.IsNaN(volumeLtr[channel0]))
{
endWMState = volumeLtr;
endWMState = volumeLtr[channel0];
if (!Double.IsNaN(beginWMState) && !Double.IsNaN(endWMState))
{
//Solve roll over
@ -1731,7 +1913,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
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;
volumeLtr = endWMState;
volumeLtr[channel0] = endWMState;
ReadPulses();
log.Debug($"Solve roll over! Upgraded endWMState: {endWMState}, beginWMState: {beginWMState}");
}
@ -1761,9 +1943,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
Start();
volumeLtr0 = Double.NaN;
volumeLtr0[channel0] = Double.NaN;
int counter = 0;
while (Double.IsNaN(volumeLtr0) && counter < 10)
while (Double.IsNaN(volumeLtr0[channel0]) && counter < 10)
{
counter++;
try
@ -1773,9 +1955,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
try
{
DiagnosticLedState4Data data =
(DiagnosticLedState4Data)parser.ParseLine(readOptoDataWithTimeout, false);
volumeLtr0 = data.RawVolume;
StreamingDecoder _streamingDecode = new StreamingDecoder(true);
_streamingDecode.DecodeMsg(readOptoDataWithTimeout);
CalibrationRecord data = _streamingDecode.DataCalib;
log.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
volumeLtr0[channel0] = data.VolumeCm * 1000;
break;
}
catch (Exception ex)
@ -1791,11 +1975,11 @@ 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))
if (!Double.IsNaN(volumeLtr0[channel0]))
{
beginWMState = volumeLtr0;
beginWMState = volumeLtr0[channel0];
ReadPulses();
return beginWMState;
}
@ -1823,8 +2007,6 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return await Task.Run(() =>
{
log.Debug($"Try get ReadSerialNr! COM: {this.RfidComPortNr}");
if (OptoHeadTest.ReadSerialNr())
{

View File

@ -394,6 +394,7 @@ namespace TBF.Rig.RegisterReaders.iPerlASICReader.implementations
}
catch (Exception ex)
{
log.FatalFormat("Opto-data serial port failure : {0}", ex.Message);
throw new Exception(ex.Message);
}
}

View File

@ -138,6 +138,7 @@ namespace TBF.Rig
new RegisterReaders.PoseidonReader.Factory(),
new RegisterReaders.IPerlReader.Factory(), /// the same functionality as TestMethods.iPerlCommunication.iPerlHead.Factory(),
new RegisterReaders.iPerlASICReader.Factory(), /// ASIC IPerl, C4 communication
new RegisterReaders.GenesisRegReader.Factory(), /// Genesis RegisterReader - dirrect communication with the head
new RegisterReaders.PulsesFromUniCB.Factory(), /// 'RegisterReader'
new RegisterReaders.StandingStartStop.Factory(), /// 'RegisterReader for standing start/stop'
new TestMethods.iPerlCommunication.iPerlHead.Factory(), /// 'RegisterReader for iPerl'

View File

@ -1411,7 +1411,6 @@
<Compile Include="Rig\RegisterReaders\iPerlASICReader\implementations\SmartReader.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\IperlASICUniHeadTestCtrl.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\IperlASICUniHeadTestCtrl.Designer.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\IPerlCfg.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\IPerlUniCfgCtrl.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\IPerlUniCfgCtrl.designer.cs" />
<Compile Include="Rig\RegisterReaders\iPerlReaderUNI\common\IUniHeadTestCtrl.cs" />

View File

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Text;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils;
namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
{
public sealed class FakeSerialDriver : ISerialDriver
{
private readonly Queue<string> _lines = new Queue<string>();
private bool _isOpen;
public int OpenCalls { get; private set; }
public int CloseCalls { get; private set; }
public int DiscardInCalls { get; private set; }
public int DiscardOutCalls { get; private set; }
public Encoding Encoding { get; set; } = Encoding.ASCII;
public int BytesToRead => _lines.Count > 0 ? 1 : 0;
public int BytesToWrite => 0;
public void EnqueueLine(string line) => _lines.Enqueue(line);
public bool IsOpen() => _isOpen;
public bool Open()
{
OpenCalls++;
_isOpen = true;
return true;
}
public void CloseConnection()
{
CloseCalls++;
_isOpen = false;
}
public void DiscardInBuffer()
{
DiscardInCalls++;
_lines.Clear();
}
public void DiscardOutBuffer()
{
DiscardOutCalls++;
}
public string ReadLine()
{
if (!_isOpen)
throw new InvalidOperationException("Port not open.");
if (_lines.Count == 0)
throw new TimeoutException();
return _lines.Dequeue();
}
public string ReadExisting()
{
if (!_isOpen)
throw new InvalidOperationException("Port not open.");
if (_lines.Count == 0)
return string.Empty;
return _lines.Dequeue();
}
public byte[] SendAndWait(byte[] request, int timeout)
{
return Array.Empty<byte>();
}
public void Dispose()
{
_isOpen = false;
}
}
}

View File

@ -0,0 +1,163 @@
using System;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
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 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)
{
var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
throw new MissingFieldException(target.GetType().FullName, fieldName);
field.SetValue(target, value);
}
private static T GetPrivateField<T>(object target, string fieldName)
{
var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
throw new MissingFieldException(target.GetType().FullName, fieldName);
return (T)field.GetValue(target);
}
[TestMethod]
public void Start_ShouldOpenPort_AndEnableProcessing()
{
var fake = new FakeSerialDriver();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
reader.Start();
Assert.IsTrue(fake.IsOpen());
Assert.IsTrue(fake.OpenCalls >= 1);
Assert.AreEqual(1, fake.DiscardInCalls);
}
[TestMethod]
public void Stop_ShouldClosePort()
{
var fake = new FakeSerialDriver();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
reader.Start();
Assert.IsTrue(fake.IsOpen());
reader.Stop();
Assert.IsFalse(fake.IsOpen());
Assert.AreEqual(2, fake.CloseCalls);
}
[TestMethod]
public void Run_ShouldCaptureStartTelegramIndex_WhenTimeReached()
{
var fake = new FakeSerialDriver();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
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);
var ev = reader.Run();
Assert.AreEqual(Event.ReadRegisterDone, ev);
Assert.AreEqual(12, reader.TestStartTelegramIx);
}
[TestMethod]
public void ProcessOptoLine_ShouldIncreaseOptoDataCount()
{
var fake = new FakeSerialDriver();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
// 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";
reader.optoSerialPort = fake;
fake.Open();
reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave);
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
Assert.AreEqual(1, optoDataCount);
}
[DataTestMethod]
[DataRow("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", 0)]
[DataRow("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", 1)]
[DataRow("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", 2)]
public void ProcessOptoLine_ShouldUpdateExpectedChannel(string line, int expectedChannelIndex)
{
var fake = new FakeSerialDriver();
fake.Open();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
reader.optoSerialPort = fake;
SetPrivateField(reader, "volumeRawExtLast", new double[3]);
SetPrivateField(reader, "timestampExtLast", new double[3]);
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");
}
}
}

View File

@ -103,6 +103,8 @@
<Compile Include="Rig\Network\Camera\KeyenceIV3G120\CameraTest.cs" />
<Compile Include="Rig\Network\Camera\RoiForFixedStartKeyence\RoiTest.cs" />
<Compile Include="Rig\Output\FileWriters\Enhanced\WriterTest.cs" />
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\FakeSerialDriver.cs" />
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\GenesisSmartReaderTest.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\DiagnosticLedParserTest.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\hexLogger\HexFormatterTest.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\IperlHatProtocol\IperlHatFrameBuilderTests.cs" />