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:
parent
2522d1da9f
commit
e26d88d437
@ -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")]
|
||||
|
||||
@ -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); }
|
||||
|
||||
|
||||
@ -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}";
|
||||
|
||||
@ -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 = 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;
|
||||
// }
|
||||
FlowRaw = 0;
|
||||
VolumeRaw = data.VolumeCm * 1000; // volume in litters
|
||||
|
||||
// ---- 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
|
||||
{
|
||||
public struct ByteArray
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
|
||||
{
|
||||
public struct Rpc
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
|
||||
{
|
||||
public struct StatusT
|
||||
{
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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();
|
||||
byte[] SendAndWait(byte[] request, int timeout);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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 ManualResetEvent _responseReceived = new ManualResetEvent(false);
|
||||
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;
|
||||
|
||||
#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,35 +144,54 @@ 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();
|
||||
var openTask = Task.Run(() => _serialPort.Open());
|
||||
|
||||
if (!openTask.Wait(openTimeoutMs))
|
||||
{
|
||||
_serialPort.Dispose();
|
||||
_serialPort = null;
|
||||
|
||||
ErrorMessage = $"COM error: Opening serial port {comPort} timed out after {openTimeoutMs} ms.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_serialPort.IsOpen)
|
||||
{
|
||||
ErrorMessage = $"COM error: Can't open {comPort}.";
|
||||
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;
|
||||
}
|
||||
|
||||
if (!_serialPort.IsOpen)
|
||||
{
|
||||
ErrorMessage = $"COM error: Can't open {comPort}.";
|
||||
return false;
|
||||
}
|
||||
|
||||
log.Debug("SerialDriver opened successfully for port: " + comPort);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CloseConnection()
|
||||
{
|
||||
if (_serialPort != null)
|
||||
{
|
||||
_serialPort.DataReceived -= DataReceivedHandler;
|
||||
if (_serialPort.IsOpen)
|
||||
_serialPort.Close();
|
||||
|
||||
@ -133,176 +202,111 @@ 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)
|
||||
{
|
||||
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()
|
||||
{
|
||||
CloseConnection();
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "SerialDriver: " + _serialPort.PortName + " (opened status:" + _serialPort.IsOpen +")";
|
||||
if (_serialPort == null)
|
||||
return "SerialDriver: <null> (opened status:false)";
|
||||
|
||||
return "SerialDriver: " + _serialPort.PortName + " (opened status:" + _serialPort.IsOpen + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,35 @@
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils
|
||||
{
|
||||
public class SerialDriverBuilder
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
|
||||
public class SerialDriverBuilder
|
||||
{
|
||||
private string _portName;
|
||||
private int _baudRate = 9600;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
Array.Clear(fifoCount, 0, fifoCount.Length);
|
||||
Array.Clear(fifoIx, 0, fifoIx.Length);
|
||||
|
||||
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);
|
||||
|
||||
sumXX = 0;
|
||||
sumX = 0;
|
||||
sumXY = 0;
|
||||
sumY = 0;
|
||||
|
||||
minSlope = 0;
|
||||
maxSlope = 0;
|
||||
firstTimestamp = double.NaN;
|
||||
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;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -9,30 +9,33 @@ 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));
|
||||
private static readonly ILog logStream = LogManager.GetLogger("StreamData");
|
||||
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
|
||||
|
||||
|
||||
#if TURA_SPECIAL
|
||||
public const int OptoDataBufferSize = 250000;
|
||||
#else
|
||||
@ -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,7 +191,11 @@ 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); } }
|
||||
public double EndWMState { get { return ResolveNaNDouble(endWMState); } }
|
||||
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); } }
|
||||
|
||||
@ -204,7 +217,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
double endWMState;
|
||||
double wmVolume;
|
||||
double wmTestTime;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// New calibration factor calculated from the original factor (argument)
|
||||
@ -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() { }
|
||||
@ -447,11 +468,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];
|
||||
@ -467,6 +509,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
synchronized2 = false;
|
||||
partOfTelegram = string.Empty;
|
||||
optoSerialPort = null;
|
||||
|
||||
|
||||
if (DebugLevel == DebugMode.Normal)
|
||||
{
|
||||
@ -474,12 +517,13 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
/// 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,21 +928,66 @@ 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;
|
||||
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 + "");
|
||||
wmRefPulses = StateMachine.ControlBoardMain.RefPulses;
|
||||
wmTestTime = timestampSec - timestampSec0;
|
||||
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(
|
||||
string comPort,
|
||||
int baudRate,
|
||||
Parity parity,
|
||||
int dataBits,
|
||||
StopBits stopBit,
|
||||
string comPort,
|
||||
int baudRate,
|
||||
Parity parity,
|
||||
int dataBits,
|
||||
StopBits stopBit,
|
||||
Handshake handshake,
|
||||
int openTimeoutMs = 3000)
|
||||
{
|
||||
@ -909,42 +998,46 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
log.FatalFormat($"{Name} OproPort simulated: {this}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
CloseOptoSerialPort();
|
||||
|
||||
var port = new SerialPort(comPort, baudRate, parity, dataBits, stopBit)
|
||||
{
|
||||
Handshake = handshake,
|
||||
NewLine = "\r\n",
|
||||
Encoding = Encoding.ASCII
|
||||
};
|
||||
log.Info($"Opening serial port {comPort} at {baudRate} baud rate");
|
||||
|
||||
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))
|
||||
if (_serialFactory != null)
|
||||
{
|
||||
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}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.FatalFormat($"{Name} OptoPort - error opening port: {this}"
|
||||
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>
|
||||
@ -1014,17 +1117,12 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
bool synchronized;
|
||||
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)
|
||||
{
|
||||
@ -1132,7 +1302,12 @@ 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;
|
||||
}
|
||||
@ -1822,9 +2006,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
log.Debug($"Try get ReadSerialNr! COM: {this.RfidComPortNr}");
|
||||
if (OptoHeadTest.ReadSerialNr())
|
||||
{
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user