Iperl -> Iperl ASIC

Refactor iPerl communication system:

- Consolidate redundant OptoHead methods.
- Introduce new utilities for diagnostic LED state handling and volume unit conversions.
- Improve FIFO logic in flow direction detection with enhanced regression calculation.
- Add enhanced debugging and logging within communication layers (`SerialDriver` and `RadioService`).
- Update communication protocols for improved response handling and configuration management.
- Replace raw volume with meaningful unit-based values (`RawVolume1to4`).
This commit is contained in:
Michal Buzik 2026-02-19 19:47:26 +01:00
parent aa0d775b8e
commit 90cd53b05f
20 changed files with 648 additions and 365 deletions

View File

@ -40,11 +40,11 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.common
public Int32 EmfRaw; /// Signed EMF from iPerl opto data
public Int16 MagneticFieldRaw;
public Int16 FlowRaw;
public UInt32 VolumeRaw;
public Int64 VolumeRawExt;
public double VolumeRaw;
public double VolumeRawExt;
public Int16 Impedance;
public UInt32 Timestamp;
public Int64 TimestampExt;
public double Timestamp;
public double TimestampExt;
public byte CheckSum;
///
@ -106,146 +106,148 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.common
/// </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;
// 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;
// }
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;
}
// -------- TIMESTAMP (seconds) --------
const double TS_TICKS_PER_SEC = 4096.0;
const double TS_RANGE = (1UL << 32) / TS_TICKS_PER_SEC;
//const double TS_HALF = TS_RANGE / 2.0;
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;
}
// -------- VOLUME (liters) --------
const double GAL_TO_LITER = 3.785411784;
const double VOL_LITERS_PER_TICK =
(4.0 / 1000.0) / GAL_TO_LITER / 2.0;
const double VOL_RANGE = (1 << 24) * VOL_LITERS_PER_TICK;
//const double VOL_HALF = VOL_RANGE / 2.0;
//New IPERL ASIC
public void UpdateFromSmart(DiagnosticLedState4Data data, int counter, float refFlow,
ref Int64 volumeRawExtLast, ref Int64 timestampExtLast)
{
DateTime = DateTime.Now;
Counter = counter;
RefFlow = refFlow;
FlowRaw = data.RawFlow;
VolumeRaw = data.RawVolume;
Timestamp = data.AsicTimestamp;
///
/// 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;
}
public void UpdateFromSmart(
DiagnosticLedState4Data data,
int counter,
float refFlow,
ref double volumeRawExtLast,
ref double timestampExtLast)
{
DateTime = DateTime.Now;
Counter = counter;
RefFlow = refFlow;
///
/// 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;
}
}
FlowRaw = data.RawFlow;
VolumeRaw = data.RawVolume;
Timestamp = data.AsicTimestamp;
// ---------- VOLUME UNWRAP ----------
double uncorrected = VolumeRaw;
if (double.IsNaN(volumeRawExtLast))
{
VolumeRawExt = volumeRawExtLast = uncorrected;
}
else
{
double k = Math.Round((volumeRawExtLast - uncorrected) / VOL_RANGE);
VolumeRawExt = volumeRawExtLast = uncorrected + k * VOL_RANGE;
}
// ---------- TIMESTAMP UNWRAP ----------
uncorrected = Timestamp;
if (double.IsNaN(timestampExtLast))
{
TimestampExt = timestampExtLast = uncorrected;
}
else
{
double k = Math.Round((timestampExtLast - uncorrected) / TS_RANGE);
TimestampExt = timestampExtLast = uncorrected + k * TS_RANGE;
}
}
/// <summary>
/// Alternative to UpdateFromString(...) when data are flushed
/// </summary>

View File

@ -39,7 +39,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.
/// </summary>
public abstract class DiagnosticLedData
{
const double GalToLiterConversion = 3.785411784D;
public abstract int GetByteCount();
/// <summary>
@ -66,10 +66,35 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.
public short RawFlow { get; protected set; }
/// <summary>
/// Unsigned 24-bit raw volume accumulation in units of ¼ milliliter per bit.
/// Unsigned 24-bit raw volume accumulation in units of ¼ milliliter per bit. it is in Gal * 2
/// </summary>
public uint RawVolume { get; protected set; }
public uint RawVolume1to4 { get; protected set; } // in 1/4 ml Gal * 2
/// <summary>
/// Raw volume in liters
/// </summary>
public double RawVolume // in liter
{
get
{
double volume = (RawVolume1to4 * 0.00025) ; // convert to liters
return volume;
}
}
/// <summary>
/// Raw volume in Gal
/// </summary>
public double RawVolumeInGal // in Gal
{
get
{
double volume = (RawVolume1to4 * 4.0) / 1000.0F; // convert to Gal
//volume = (volume / GalToLiterConversion) / 2; // convert to liter
return volume;
}
}
/// <summary>
/// Unsigned 16-bit millivolt delta measured on the field drive capacitor.
/// </summary>

View File

@ -33,7 +33,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.
Adc24 = DiagnosticHex.ParseInt24(f[0]);
FieldStrength = DiagnosticHex.ParseUInt16(f[1]);
RawFlow = DiagnosticHex.ParseInt16(f[2]);
RawVolume = DiagnosticHex.ParseUInt24(f[3]);
RawVolume1to4 = DiagnosticHex.ParseUInt24(f[3]);
CapacitorMv = DiagnosticHex.ParseUInt16(f[4]);
}

View File

@ -59,7 +59,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.
Adc24 = DiagnosticHex.ParseInt24(fields[0]);
FieldStrength = DiagnosticHex.ParseUInt16(fields[1]);
RawFlow = DiagnosticHex.ParseInt16(fields[2]);
RawVolume = DiagnosticHex.ParseUInt24(fields[3]);
RawVolume1to4 = DiagnosticHex.ParseUInt24(fields[3]);
CapacitorMv = DiagnosticHex.ParseUInt16(fields[4]);
// ---- State #2 specific ----

View File

@ -59,7 +59,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.
Adc24 = DiagnosticHex.ParseInt24(fields[0]);
FieldStrength = DiagnosticHex.ParseUInt16(fields[1]);
RawFlow = DiagnosticHex.ParseInt16(fields[2]);
RawVolume = DiagnosticHex.ParseUInt24(fields[3]);
RawVolume1to4 = DiagnosticHex.ParseUInt24(fields[3]);
CapacitorMv = DiagnosticHex.ParseUInt16(fields[4]);
// ---- State #3 specific fields ----

View File

@ -13,7 +13,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.
/// <item><term>0 xxxxxx</term><description>signed 24-bit ADC value</description></item>
/// <item><term>1 aaaa</term><description>unsigned 16-bit Field strength</description></item>
/// <item><term>2 yyyy</term><description>signed 16-bit Raw flow rate (1/4 ml per bit)</description></item>
/// <item><term>3 vvvvvv</term><description>unsigned 24-bit Raw volume accumulation</description></item>
/// <item><term>3 vvvvvv</term><description>unsigned 24 bit raw volume accumulation in ¼ ml per bit</description></item>
/// <item><term>4 cccc</term><description>unsigned 16-bit Capacitor mV delta</description></item>
/// <item><term>5 tttt</term><description>unsigned 16-bit Field calibration</description></item>
/// <item><term>6 bbbbbbbb</term><description>unsigned 32-bit ASIC timestamp</description></item>
@ -30,7 +30,19 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.
public sealed class DiagnosticLedState4Data : DiagnosticLedData
{
public ushort FieldCalibration { get; }
public uint AsicTimestamp { get; }
/// <summary>
/// ASIC timestamp in seconds
/// </summary>
public double AsicTimestamp
{
get { return AsicTimestampTicks / 8192; } //4096.0; }
}
/// <summary>
/// ASIC timestamp in units of 1 / 4096 seconds.
/// </summary>
public uint AsicTimestampTicks { get; }
public byte FieldDriveTimeUs { get; }
public int MeanFlowRate { get; }
@ -50,12 +62,13 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.
Adc24 = DiagnosticHex.ParseInt24(fields[0]);
FieldStrength = DiagnosticHex.ParseUInt16(fields[1]);
RawFlow = DiagnosticHex.ParseInt16(fields[2]);
RawVolume = DiagnosticHex.ParseUInt24(fields[3]);
RawVolume1to4 = DiagnosticHex.ParseUInt24(fields[3]); //1/4 ml Gal double
CapacitorMv = DiagnosticHex.ParseUInt16(fields[4]);
// ---- State #4 specific ----
FieldCalibration = DiagnosticHex.ParseUInt16(fields[5]);
AsicTimestamp = DiagnosticHex.ParseUInt32(fields[6]);
AsicTimestampTicks = DiagnosticHex.ParseUInt32(fields[6]);
FieldDriveTimeUs = DiagnosticHex.ParseByte(fields[7]);
MeanFlowRate = unchecked((int)DiagnosticHex.ParseUInt32(fields[8]));
@ -71,7 +84,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.
public override string ToString()
{
return $"DiagnosticLedState4Data: Adc24={Adc24}, FieldStrength={FieldStrength}, RawFlow={RawFlow}, RawVolume={RawVolume}, CapacitorMv={CapacitorMv}, FieldCalibration={FieldCalibration}, AsicTimestamp={AsicTimestamp}, FieldDriveTimeUs={FieldDriveTimeUs}, MeanFlowRate={MeanFlowRate}, Field1Measurement={Field1Measurement}, Field2Measurement={Field2Measurement}, IntegratorCalibrationPositive={IntegratorCalibrationPositive}, IntegratorCalibrationNegative={IntegratorCalibrationNegative}, AsicState={AsicState}";
return $"DiagnosticLedState4Data: Adc24={Adc24}, FieldStrength={FieldStrength}, RawFlow={RawFlow}, RawVolume={RawVolume}, RawVolume1to4 = {RawVolume1to4}, RawVolumeGal={RawVolumeInGal}, CapacitorMv={CapacitorMv}, FieldCalibration={FieldCalibration}, AsicTimestamp={AsicTimestamp}, FieldDriveTimeUs={FieldDriveTimeUs}, MeanFlowRate={MeanFlowRate}, Field1Measurement={Field1Measurement}, Field2Measurement={Field2Measurement}, IntegratorCalibrationPositive={IntegratorCalibrationPositive}, IntegratorCalibrationNegative={IntegratorCalibrationNegative}, AsicState={AsicState}, RawLine={RawLine}";
}
/// <summary>

View File

@ -69,7 +69,7 @@ public sealed class DiagnosticLedState5Data : DiagnosticLedData
Adc24 = DiagnosticHex.ParseInt24(fields[0]);
FieldStrength = DiagnosticHex.ParseUInt16(fields[1]);
RawFlow = DiagnosticHex.ParseInt16(fields[2]);
RawVolume = DiagnosticHex.ParseUInt24(fields[3]);
RawVolume1to4 = DiagnosticHex.ParseUInt24(fields[3]);
CapacitorMv = DiagnosticHex.ParseUInt16(fields[4]);
// ---- State #5 specific ----

View File

@ -69,7 +69,7 @@ public sealed class DiagnosticLedState6Data : DiagnosticLedData
Adc24 = DiagnosticHex.ParseInt24(fields[0]);
FieldStrength = DiagnosticHex.ParseUInt16(fields[1]);
RawFlow = DiagnosticHex.ParseInt16(fields[2]);
RawVolume = DiagnosticHex.ParseUInt24(fields[3]);
RawVolume1to4 = DiagnosticHex.ParseUInt24(fields[3]);
CapacitorMv = DiagnosticHex.ParseUInt16(fields[4]);
// ---- State #6 specific ----

View File

@ -98,7 +98,7 @@ public sealed class DiagnosticLedState7Data : DiagnosticLedData
Adc24 = DiagnosticHex.ParseInt24(fields[0]);
FieldStrength = DiagnosticHex.ParseUInt16(fields[1]);
RawFlow = DiagnosticHex.ParseInt16(fields[2]);
RawVolume = DiagnosticHex.ParseUInt24(fields[3]);
RawVolume1to4 = DiagnosticHex.ParseUInt24(fields[3]);
CapacitorMv = DiagnosticHex.ParseUInt16(fields[4]);
// ---- State #6 fields ----

View File

@ -0,0 +1,49 @@
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer;
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.utils
{
public class DiagnostigLedDataByUnit
{
private readonly Common.Unit _unitFlow;
private readonly Common.Unit _unitVolume;
private readonly DiagnosticLedState4Data _data;
public DiagnostigLedDataByUnit(Common.Unit unitFlow, Common.Unit unitVolume, DiagnosticLedState4Data data)
{
this._unitFlow = unitFlow;
this._unitVolume = unitVolume;
this._data = data;
}
public Common.Unit Unit => _unitVolume;
public DiagnosticLedState4Data Data => _data;
public double RawFlow {
get { return UnitVolume(_unitFlow, _data.RawFlow); }
}
public double RawVolume
{
get { return Common.Units.ConvertFrom(_unitVolume, _data.RawVolume); }
}
public double AsicTimestamp
{
get { return _data.AsicTimestamp; }
}
public static double UnitVolume(Common.Unit unit, double volume)
{
return Common.Units.ConvertFrom(unit, volume); /// 1 liter
}
public static uint DeltaTicks(uint oldTicks, uint newTicks)
{
return newTicks >= oldTicks
? newTicks - oldTicks
: uint.MaxValue - oldTicks + newTicks + 1;
}
}
}

View File

@ -8,6 +8,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger
public static class HexFormatter
{
/// <summary>
/// Byte to hex string.
/// Formats a single byte as 0xNN.
/// Example: 0x0D
/// </summary>
@ -17,7 +18,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger
}
/// <summary>
/// int to byte securely
/// int to byte - securely
/// </summary>
/// <param name="value"></param>
/// <returns></returns>

View File

@ -4,7 +4,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
{
public class OpthoHeadService
{
protected static readonly ILog rfidDataLogger = LogManager.GetLogger("RfidData");
}
}

View File

@ -10,9 +10,11 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
{
public class OptoHeadTest : IDisposable
{
protected static readonly ILog rfidDataLogger = LogManager.GetLogger("RfidData");
//protected static readonly ILog rfidDataLogger = LogManager.GetLogger("RfidData");
private static readonly ILog log = LogManager.GetLogger(typeof(OptoHeadTest));
private static SerialDriver serialDriver;
private IperlHead iperlHead;
private SerialDriver serialDriver;
public static SerialDriver BuildConnection(IperlHead iHead)
{
@ -27,29 +29,36 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
}
public OptoHeadTest(IperlHead iperlHead)
{
this.iperlHead = iperlHead;
}
private void CloseConnection()
{
if (serialDriver != null)
serialDriver.CloseConnection();
serialDriver = null;
}
public static string ReadRequest_PCB(ref IperlHead iHead)
public string ReadRequest_PCB()
{
if (iHead.DebugLevel == DebugMode.Simulate)
if (iperlHead.DebugLevel == DebugMode.Simulate)
{
return "-OK Simulated response-";
}
try
{
if (iHead != null)
if (iperlHead != null)
{
if (serialDriver == null)
serialDriver = BuildConnection(iHead);
serialDriver = BuildConnection(iperlHead);
RadioService headService = new RadioService(serialDriver);
string serialNo = headService.ReadRequest_PCB(ref iHead);
string serialNo = headService.ReadRequest_PCB(ref iperlHead);
log.Info($"PCB Number: {serialNo} on COM{iperlHead.RfidComPortNr} serialDriver: {serialDriver}");
return serialNo;
}
}
@ -66,12 +75,13 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
/// </summary>
/// <param name="iHead"></param>
/// <returns></returns>
public static bool SetTestMode(ref IperlHead iHead)
public bool SetTestMode()
{
bool activityModeActive = SetActivityMode_Active(iHead);
bool optActiveMode = SetOptTestMode(ref iHead);
log.Debug("SetTestMode called for iHead: " + iperlHead.ToString());
bool activityModeActive = SetActivityMode_Active();
bool optActiveMode = SetOptTestMode();
log.Debug("SetTestMode result: optoMod-> " + optActiveMode + " meterModeActive ->" + activityModeActive);
return (optActiveMode && activityModeActive);
}
@ -80,10 +90,11 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
/// </summary>
/// <param name="iHead"></param>
/// <returns></returns>
public static bool SetActiveMode(ref IperlHead iHead)
public bool SetActiveMode()
{
bool optActiveMode = SetOptActiveMode(iHead);
bool activityModeIdle = SetActivityMode_Idle(iHead);
log.Debug("SetActiveMode called for iHead: " + iperlHead.ToString());
bool optActiveMode = SetOptActiveMode(iperlHead);
bool activityModeIdle = SetActivityMode_Idle();
return (optActiveMode && activityModeIdle);
}
@ -94,9 +105,9 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
/// <param name="iHead"></param>
/// <param name="isTestModeSuccessful"></param>
/// <returns></returns>
public static string SetTestMode(ref IperlHead iHead, ref bool isTestModeSuccessful)
public string SetTestMode(ref bool isTestModeSuccessful)
{
if (iHead.DebugLevel == DebugMode.Simulate)
if (iperlHead.DebugLevel == DebugMode.Simulate)
{
isTestModeSuccessful = true;
return "-OK Simulated response-";
@ -104,10 +115,12 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
try
{
isTestModeSuccessful = SetTestMode(ref iHead);
return isTestModeSuccessful ? "Set Test Mode - OK" : "Set Test Mode - FAILED";
bool testMode = SetTestMode();
isTestModeSuccessful = testMode;
return testMode ? "Set Test Mode - OK" : "Set Test Mode - FAILED";
}catch (Exception ex)
{
log.Error("SetTestMode() - Exception:" + ex.StackTrace);
return "Set Test Mode - Exception";
}
}
@ -120,18 +133,19 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
/// <param name="iHead"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private static bool SetOptTestMode(ref IperlHead iHead)
private bool SetOptTestMode()
{
try
{
if (iHead != null)
if (iperlHead != null)
{
if (serialDriver == null)
serialDriver = BuildConnection(iHead);
serialDriver = BuildConnection(iperlHead);
RadioService headService = new RadioService(serialDriver);
var optTestMode = headService.SetOptTestMode(iHead);
iHead.ConfigStruct.OpthoStatusMode = optTestMode ? DiagnosticLedState.State4 : DiagnosticLedState.StatusUnknown;
bool optTestMode = headService.SetOptTestMode(iperlHead);
if (iperlHead.ConfigStruct != null)
iperlHead.ConfigStruct.OpthoStatusMode = optTestMode ? DiagnosticLedState.State4 : DiagnosticLedState.StatusUnknown;
return optTestMode;
}
}
@ -148,9 +162,9 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
/// <param name="iHead"></param>
/// <param name="isTestModeSuccessful"></param>
/// <returns></returns>
public static string SetActiveMode(ref IperlHead iHead, ref bool isTestModeSuccessful)
public string SetActiveMode(ref bool isTestModeSuccessful)
{
if (iHead.DebugLevel == DebugMode.Simulate)
if (iperlHead.DebugLevel == DebugMode.Simulate)
{
isTestModeSuccessful = true;
return "-OK Simulated response-";
@ -158,8 +172,9 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
try
{
isTestModeSuccessful = SetActiveMode(ref iHead);
return isTestModeSuccessful ? "Set Active Mode - OK" : "Set Active Mode - FAILED";
bool activeMode = SetActiveMode();
isTestModeSuccessful = activeMode;
return activeMode ? "Set Active Mode - OK" : "Set Active Mode - FAILED";
}
catch (Exception ex)
{
@ -172,7 +187,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
/// <param name="iHead"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private static bool SetOptActiveMode(IperlHead iHead)
private bool SetOptActiveMode(IperlHead iHead)
{
try
{
@ -180,7 +195,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
{
if (serialDriver == null)
serialDriver = BuildConnection(iHead);
RadioService headService = new RadioService(serialDriver);
return headService.SetOptActiveMode(iHead);
}
@ -199,17 +214,18 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
/// <param name="iHead"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private static bool SetActivityMode_Active(IperlHead iHead)
private bool SetActivityMode_Active()
{
try
{
if (iHead != null)
if (iperlHead != null)
{
if (serialDriver == null)
serialDriver = BuildConnection(iHead);
serialDriver = BuildConnection(iperlHead);
log.Debug("SetActivityMode_Active called for iHead: " + iperlHead.ToString() + " serialDriver: " + serialDriver);
RadioService headService = new RadioService(serialDriver);
return headService.SetActivityMode_Active(iHead);
return headService.SetActivityMode_Active(iperlHead);
}
}
catch (Exception ex)
@ -226,17 +242,19 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
/// <param name="iHead"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private static bool SetActivityMode_Idle(IperlHead iHead)
private bool SetActivityMode_Idle()
{
try
{
if (iHead != null)
if (iperlHead != null)
{
if (serialDriver == null)
serialDriver = BuildConnection(iHead);
serialDriver = BuildConnection(iperlHead);
log.Debug("SetActivityMode_Idle called for iHead: " + iperlHead.ToString() + " serialDriver: " + serialDriver);
RadioService headService = new RadioService(serialDriver);
return headService.SetActivityMode_Idle(iHead);
return headService.SetActivityMode_Idle(iperlHead);
}
}
catch (Exception ex)
@ -260,9 +278,9 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
/// <param name="iHead"></param>
/// <param name="ledState"></param>
/// <returns></returns>
public static bool ReadConfiguration(ref IperlHead iHead, DiagnosticLedState ledState )
public bool ReadConfiguration(DiagnosticLedState ledState )
{
if (iHead.DebugLevel == DebugMode.Simulate)
if (iperlHead.DebugLevel == DebugMode.Simulate)
{
return true;
}
@ -270,26 +288,28 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
try
{
if (iHead != null)
if (iperlHead != null)
{
iHead.ConfigStruct = new ConfigStruct();
iperlHead.ConfigStruct = new ConfigStruct();
if (serialDriver == null)
serialDriver = BuildConnection(iHead);
serialDriver = BuildConnection(iperlHead);
RadioService headService = new RadioService(serialDriver);
iHead.ConfigStruct.PCBNumberString = headService.ReadRequest_PCB(ref iHead);
iHead.ConfigStruct.StatusMode = headService.GetActivityStatusMode(iHead);
iperlHead.ConfigStruct.PCBNumberString = headService.ReadRequest_PCB(ref iperlHead);
iperlHead.ConfigStruct.StatusMode = headService.GetActivityStatusMode(iperlHead);
iperlHead.ConfigStruct.Unit = headService.GetUnit(iperlHead);
if (ledState != DiagnosticLedState.StatusUnknown) // do set
{
iHead.ConfigStruct.OpthoStatusMode = headService.SetOptoStatusMode(iHead, ledState);
iperlHead.ConfigStruct.OpthoStatusMode = headService.SetOptoStatusMode(iperlHead, ledState);
}
else
{
iHead.ConfigStruct.OpthoStatusMode = DiagnosticLedState.StatusUnknown;
iperlHead.ConfigStruct.OpthoStatusMode = DiagnosticLedState.StatusUnknown;
}
iHead.ConfigStruct.Version = headService.GetVersion(iHead);
iperlHead.ConfigStruct.Version = headService.GetVersion(iperlHead);
return true;
}

View File

@ -1,3 +1,4 @@
using log4net;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
@ -8,6 +9,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
{
public class RadioService
{
private static readonly ILog log = LogManager.GetLogger(typeof(RadioService));
static string okResponse = "Command complete, no errors";
static string errorResponse = "Unable to execute";
@ -16,6 +18,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
public RadioService(SerialDriver serialDriver)
{
this.serialDriver = serialDriver;
log.Debug("RadioService created with serialDriver= " + serialDriver + "");
}
public string ReadRequest_PCB(ref IperlHead iHead)
@ -97,6 +100,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
var parser = new IperlHatFrameParser();
IperlHatResponse decoded = parser.Parse(rawData);
log.Debug("SetOptoStatusMode isOK: " + decoded.IsOk);
// if is response ok - it set it correctly
if (!decoded.IsOk)
return DiagnosticLedState.StatusUnknown;
@ -135,6 +139,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
// parse rawData
var parser = new IperlHatFrameParser();
IperlHatResponse decoded = parser.Parse(rawData);
log.Debug("GetVersion isOK: " + decoded.IsOk);
if (decoded.IsOk)
{
return decoded.GetAsciiPayload();
@ -158,7 +163,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
.AddSubCommand(ProtocolStatuses.Active) // Active
.BuildBytes();
byte[] rawData = serialDriver.SendAndWait(request, 2000);
byte[] rawData = serialDriver.SendAndWait(request, 5000);
if (rawData == null)
return false;
@ -166,6 +171,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
// parse rawData
var parser = new IperlHatFrameParser();
IperlHatResponse decoded = parser.Parse(rawData);
log.Debug("SetActivityMode_Active isOK: " + decoded.IsOk);
return decoded.IsOk;
}
@ -191,6 +197,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
// parse rawData
var parser = new IperlHatFrameParser();
IperlHatResponse decoded = parser.Parse(rawData);
log.Debug("SetActivityMode_Idle isOK: " + decoded.IsOk);
return decoded.IsOk;
}
@ -202,11 +209,13 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
}
//Set LED to state 4
byte[] request = new IperlHatFrameBuilder()
.AddDiagnosticLedState(DiagnosticLedState.State4)
var request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.SetDiagnosticLEDState)
.AddPayload(DiagnosticLedState.State4)
.BuildBytes();
byte[] rawData = serialDriver.SendAndWait(request, 1000);
byte[] rawData = serialDriver.SendAndWait(request, 5000);
if (rawData == null)
return false;
@ -214,7 +223,9 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
// parse rawData
var parser = new IperlHatFrameParser();
IperlHatResponse decoded = parser.Parse(rawData);
return decoded.IsOk;
bool isOk = decoded.IsOk;
log.Debug("SetOptTestMode isOK: " + isOk);
return isOk;
}
@ -231,11 +242,13 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
}
//Set LED to state 1
byte[] request = new IperlHatFrameBuilder()
.AddDiagnosticLedState(DiagnosticLedState.StateOFF)
var request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.SetDiagnosticLEDState)
.AddPayload(DiagnosticLedState.StateOFF)
.BuildBytes();
byte[] rawData = serialDriver.SendAndWait(request, 2000);
byte[] rawData = serialDriver.SendAndWait(request, 5000);
if (rawData == null)
return false;
@ -243,7 +256,41 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
// parse rawData
var parser = new IperlHatFrameParser();
IperlHatResponse decoded = parser.Parse(rawData);
log.Debug("SetOptActiveMode isOK: " + decoded.IsOk);
return decoded.IsOk;
}
public string GetUnit(IperlHead iperlHead)
{
if (!serialDriver.IsOpen())
{
serialDriver.Open();
}
var request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.ViewFactoryId)
.BuildBytes();
byte[] rawData = serialDriver.SendAndWait(request, 10000);
if (rawData == null)
return null;
// parse rawData
var parser = new IperlHatFrameParser();
IperlHatResponse decoded = parser.Parse(rawData);
if (decoded.IsOk)
{
string asciiPayload = decoded.GetAsciiPayload();
if (iperlHead.ConfigStruct != null) // store mechanism
{
iperlHead.ConfigStruct.Unit = asciiPayload;
}
return asciiPayload;
}
return null;
}
}
}

View File

@ -4,11 +4,13 @@ using System.Diagnostics;
using System.IO.Ports;
using System.Threading;
using FluentNHibernate.Conventions;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger;
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.Utils
{
public class SerialDriver : IDisposable
{
readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(SerialDriver));
public string ErrorMessage { get; private set; }
private List<byte> SerialPortReadBuffer = new List<byte>();
@ -110,6 +112,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.Utils
ErrorMessage = $"COM error: Can't open {comPort}.";
return false;
}
log.Debug("SerialDriver opened successfully for port: " + comPort);
}
return true;
}
@ -172,6 +176,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.Utils
_serialPort.DiscardInBuffer();
_binMessages.Clear();
_responseReceived.Reset();
SerialPortReadBuffer.Clear();
_isReading = true;
}
@ -188,7 +193,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.Utils
try
{
Thread.Sleep(5);
//Thread.Sleep(5);
if (!SerialPortReadBuffer.IsEmpty())
{
@ -204,13 +209,13 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.Utils
byte readByte = (byte)_serialPort.ReadByte();
//I have START
if (readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.IperlHatProtocolConstants.Start)
if (readByte == C4.IperlHatProtocol.IperlHatProtocolConstants.Start)
{
iWordCounter++;
isStart = true;
}
// I have QUESTION
if (readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.IperlHatProtocolConstants.Question)
if (readByte == C4.IperlHatProtocol.IperlHatProtocolConstants.Question)
{
iWordCounter++;
isQuestion = true;
@ -231,12 +236,16 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.Utils
}
//If we have enough bytes
if (isStart && iLength > 0 && SerialPortReadBuffer.Count >= iLength)
if (isStart && iLength > 0
&& (SerialPortReadBuffer.Count >= iLength ||
readByte == C4.IperlHatProtocol.IperlHatProtocolConstants.End
)
)
{
break;
}
//if we read END
if (isQuestion && readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.IperlHatProtocolConstants.End)
if (isQuestion && readByte == C4.IperlHatProtocol.IperlHatProtocolConstants.End)
{
break;
}
@ -264,11 +273,18 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.Utils
if (!IsOpen())
throw new InvalidOperationException("Serial port not open");
log.Debug("SendAndWait() - TX: " + HexFormatter.ToHex(data));
PrepareReading();
_serialPort.Write(data, 0, data.Length);
if (!_responseReceived.WaitOne(timeoutMs))
{
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;
}
@ -283,5 +299,10 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.Utils
{
CloseConnection();
}
public override string ToString()
{
return "SerialDriver: " + _serialPort.PortName + " (opened status:" + _serialPort.IsOpen +")";
}
}
}

View File

@ -838,14 +838,27 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
/// Read configuration
if (OptoHeadTest.ReadConfiguration(ref ihead, DiagnosticLedState.StatusUnknown))
if (ihead.OptoHeadTest.ReadConfiguration(DiagnosticLedState.State4))
{
error = CommErr.None;
ihead.ConfigStruct = new ConfigStruct(); //.FromByteArray(config);
resultStr = ihead.ConfigStruct.ToString(1);
//read, set and create ConfigStruct is set directly in method ReadConfiguration
//ihead.ConfigStruct = new ConfigStruct(); //.FromByteArray(config);
if (ihead.ConfigStruct != null)
{
resultStr = ihead.ConfigStruct.ToString(1);
}
else
{
resultStr = "Data not available";
}
}
else
{
resultStr = "Failed to read configuration";
}
return error + Math.Max(0, Math.Min(readRetVal, 4));
//return error + Math.Max(0, Math.Min(readRetVal, 4));
return error;
}
@ -861,6 +874,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
/// <returns>true on success</returns>
static CommErr SetTestMode(int threadId, IperlHead ihead, ref string resultStr)
{
log.Debug("SetTestMode threadId=" + threadId + ", ihead=" + ihead.ToString());
if (ihead.ConfigStruct == null)
{
log.Debug("ConfigStruct is null - created new in SetTestMode()");
@ -874,7 +888,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
///
//string testModeConfigStr = multiTestParams[currentActivityStep].Activity.Substring(SetTestModeStr.Length + 1);
if (OptoHeadTest.SetTestMode(ref ihead))
if (ihead.OptoHeadTest.SetTestMode())
{
log.Debug("test mode activated now");
testModeConfig = ihead.ConfigStruct.OpthoStatusMode;
@ -899,7 +913,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
error = CommErr.Verify;
//TODO BUMI use read config
if (OptoHeadTest.ReadConfiguration(ref ihead, DiagnosticLedState.State4))
if (ihead.OptoHeadTest.ReadConfiguration(DiagnosticLedState.State4))
{
if ((ihead.ConfigStruct.MeterState == ProtocolStatuses.Active) &&
(ihead.ConfigStruct.TestModeConfig == testModeConfig))
@ -924,11 +938,12 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
/// <returns>true on success</returns>
static CommErr SetActiveMode(int threadId, IperlHead ihead, ref string resultStr)
{
log.Debug("SetActiveMode threadId=" + threadId);
CommErr error = CommErr.CmdActive;
/// Switch to active mode
if (OptoHeadTest.SetActiveMode(ref ihead))
if (ihead.OptoHeadTest.SetActiveMode())
{
error = CommErr.None;
}
@ -2651,7 +2666,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
switch (tag)
{
case "ReadPCB":
txt = OptoHeadTest.ReadRequest_PCB(ref iHead);
txt = iHead.OptoHeadTest.ReadRequest_PCB();
break;
case "WriteRequestPort_u8_Customer_Text":
txt = "Not Supported NOW!";//OpticalHeadTest.WriteRequestPort_u8_Customer_Text(iHead);
@ -2660,10 +2675,10 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
txt = "Not Supported NOW!";//OpticalHeadTest.OpenSealing(iHead);
break;
case "StartTestMode":
txt = OptoHeadTest.SetTestMode(ref iHead, ref success);
txt = iHead.OptoHeadTest.SetTestMode(ref success);
break;
case "TurnOffTestMode":
txt = OptoHeadTest.SetActiveMode(ref iHead, ref success);
txt = iHead.OptoHeadTest.SetActiveMode(ref success);
break;
case "TurnOffRadio":
txt = "Not Supported NOW!";//OpticalHeadTest.TurnOffRadio(iHead);

View File

@ -17,8 +17,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
public string PCBNumberString; // dynamic
public ProtocolStatuses StatusMode; // byte
public DiagnosticLedState OpthoStatusMode;// byte
public string Unit; //dynamic
public string Version; // dynamic
public ConfigStruct()
{
@ -80,20 +80,25 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
public override string ToString()
{
return string.Format(
"Config: V{0} PCB#={1} StatusMode={2}",
Version,
"Config: PCB#={0} StatusMode={1} Unit={2} V{3}",
GetPcbNrString(),
StatusMode
StatusMode,
Unit,
Version
);
}
public string ToString(int sel)
{
return string.Format("{0} PCB#={1} StatusMode={2}",
Version,
GetPcbNrString(),
StatusMode
);
return string.Format(
"Config: PCB#={0} StatusMode={1} Unit={2} V{3}",
GetPcbNrString(),
StatusMode,
Unit,
Version
);
}
public virtual void WriteBinary(BinaryWriter writer)
@ -115,16 +120,27 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
// 2) write string bytes
writer.Write(PCBNumberStringBytes);
// Convert string to bytes (UTF8 is standard)
byte[] unitBytes = Encoding.UTF8.GetBytes(Unit);
// 1) write length
writer.Write(unitBytes.Length);
// 2) write string bytes
writer.Write(unitBytes);
//writer.Write(Version);
}
public virtual void ReadBinary(BinaryReader reader)
{
int ReadBytesCount = 0;
// ---- Test Data type ----
var readByte = reader.ReadByte();
if (readByte != 0x11) return; // not correct version
ReadBytesCount++;
// ---- Version ----
// 1) read length
int length = reader.ReadInt32();
ReadBytesCount += 4 + length;
// 2) read string bytes
byte[] versionBytes = reader.ReadBytes(length);
// 3) convert back to string
@ -132,11 +148,19 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
// ---- PCB Number ----
// 1) read length
int lengthPCB = reader.ReadInt32();
ReadBytesCount += 4 + lengthPCB;
// 2) read string bytes
byte[] bytesPCB = reader.ReadBytes(lengthPCB);
// 3) convert back to string
PCBNumberString = Encoding.UTF8.GetString(bytesPCB);
// ---- Unit ----
// 1) read length
int lengthUnit = reader.ReadInt32();
ReadBytesCount += 4 + lengthUnit;
// 2) read string bytes
byte[] bytesUnit = reader.ReadBytes(lengthUnit);
// 3) convert back to string
Unit = Encoding.UTF8.GetString(bytesUnit);
}
}

View File

@ -11,40 +11,35 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
{
private static readonly ILog log = LogManager.GetLogger(typeof(IperlHead));
const int FIFO_SIZE = 64; /// 8 sec. @ 8Hz
const double MAX_OPTO_DROPOUT = 4.5; /// sec.
const int FIFO_SIZE = 64; // 8 sec @ 8Hz
const double MAX_OPTO_DROPOUT = 4.5; // sec
Int64[] volumeRawFifo; /// Volume FIFO buffer
Int64[] timestampFifo; /// Timestamp FIFO buffer
private readonly double[] volumeRawFifo;
private readonly double[] timestampFifo; // centered timestamps
int fifoCount; /// Number of valid FIFO items
int fifoIx; /// Index of the next FIFO item
DateTime lastFifoWriteTime; /// Time of the last write to FIFO
private int fifoCount;
private int fifoIx;
private DateTime lastFifoWriteTime;
///
/// Sums for linear regression calculation
///
decimal sumXX;
decimal sumX;
decimal sumXY;
decimal sumY;
decimal N;
// regression sums (double is ideal here)
private double sumXX;
private double sumX;
private double sumXY;
private double sumY;
double minSlope; /// max. slope of the regressed line, always positive or 0
double maxSlope; /// min. slope of the regressed line, always negative or 0
private double minSlope;
private double maxSlope;
// timestamp centering for numerical stability
private double firstTimestamp = double.NaN;
public FlowDirectionDetection()
{
volumeRawFifo = new Int64[FIFO_SIZE];
timestampFifo = new Int64[FIFO_SIZE];
volumeRawFifo = new double[FIFO_SIZE];
timestampFifo = new double[FIFO_SIZE];
ClearFifo();
}
/// <summary>
/// Clear FIFO data
/// </summary>
public void ClearFifo()
{
fifoCount = 0;
@ -55,94 +50,93 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
sumX = 0;
sumXY = 0;
sumY = 0;
N = 0;
minSlope = 0;
maxSlope = 0;
firstTimestamp = double.NaN;
}
/// <summary>
/// Write data to FIFO
/// Add sample to rolling FIFO and update regression sums
/// </summary>
/// <param name="volumeRaw">Volume</param>
/// <param name="timestamp">Time stamp</param>
public void WriteToFifo(Int64 volumeRaw, Int64 timestamp)
public void WriteToFifo(double volumeRaw, double timestamp)
{
///
/// Update sums for linear regression calculation
///
// establish time origin (CRITICAL for double precision)
if (double.IsNaN(firstTimestamp))
firstTimestamp = timestamp;
double x = timestamp - firstTimestamp; // centered time
double y = volumeRaw;
// remove oldest sample if buffer full
if (fifoCount == FIFO_SIZE)
{
/// Buffer is already full, the oldest item will be re-written
sumXX -= timestampFifo[fifoIx] * timestampFifo[fifoIx];
sumX -= timestampFifo[fifoIx];
sumXY -= timestampFifo[fifoIx] * volumeRawFifo[fifoIx];
sumY -= volumeRawFifo[fifoIx];
N--;
}
sumXX += timestamp * timestamp;
sumX += timestamp;
sumXY += timestamp * volumeRaw;
sumY += volumeRaw;
N++;
double oldX = timestampFifo[fifoIx];
double oldY = volumeRawFifo[fifoIx];
sumXX -= oldX * oldX;
sumX -= oldX;
sumXY -= oldX * oldY;
sumY -= oldY;
}
else
{
fifoCount++;
}
// add new sample
sumXX += x * x;
sumX += x;
sumXY += x * y;
sumY += y;
// store sample
timestampFifo[fifoIx] = x;
volumeRawFifo[fifoIx] = y;
///
/// Save new values to FIFO
///
volumeRawFifo[fifoIx] = volumeRaw;
timestampFifo[fifoIx] = timestamp;
fifoIx = (fifoIx + 1) % FIFO_SIZE;
fifoCount = Math.Min(fifoCount + 1, FIFO_SIZE);
lastFifoWriteTime = DateTime.Now;
}
/// <summary>
/// Determine whether there are enough recent FIFO data
/// </summary>
/// <returns>true when data valid</returns>
public bool AreFifoDataValid()
{
return (DateTime.Now.Subtract(lastFifoWriteTime).TotalSeconds <= MAX_OPTO_DROPOUT) && (fifoCount == FIFO_SIZE);
return (DateTime.Now.Subtract(lastFifoWriteTime).TotalSeconds <= MAX_OPTO_DROPOUT)
&& (fifoCount == FIFO_SIZE);
}
/// <summary>
/// Verify whether the flow direction is correct
/// </summary>
/// <returns>OptoHeadState.OptoAndDirOK, OptoHeadState.OptoNok or OptoHeadState.DirNok</returns>
public OptoHeadState CheckFlowDirection(Counting counting, string iPerlHeadName)
{
if (!AreFifoDataValid()) return OptoHeadState.OptoNok;
if (!AreFifoDataValid())
return OptoHeadState.OptoNok;
try
{
decimal numer = N * sumXY - sumX * sumY;
decimal denom = N * sumXX - sumX * sumX;
double N = fifoCount;
if (denom == 0) return OptoHeadState.DirNok;
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;
/// Calculate the slope of the regressed line, determine min. and max.
double slope = (double)(numer / denom);
if (slope > maxSlope) maxSlope = slope;
if (slope < minSlope) minSlope = slope;
if ( counting == Counting.Arbitrary ||
if (counting == Counting.Arbitrary ||
(counting == Counting.Positive && maxSlope > Math.Abs(2 * minSlope)) ||
(counting == Counting.Negative && minSlope < -Math.Abs(2 * maxSlope)))
{
return OptoHeadState.OptoAndDirOK;
}
else
{
return OptoHeadState.DirNok;
}
return OptoHeadState.DirNok;
}
catch (Exception)
catch (Exception ex)
{
log.ErrorFormat("{0} : CheckFlowDirection() failed", iPerlHeadName);
return OptoHeadState.DirNok; /// ???
log.ErrorFormat("{0} : CheckFlowDirection() failed: {1}", iPerlHeadName, ex);
return OptoHeadState.DirNok;
}
}
}

View File

@ -13,10 +13,13 @@ using Sensus.iPerl.NfcHandler;
using NHibernate;
using System.Linq;
using System.Text;
using System.Xml.Linq; // This line is correct and does not need to be changed.
using System.Xml.Linq;
using TBF.Rig.TestMethods.iPerlCommunication.communication; // This line is correct and does not need to be changed.
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.utils;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger;
using TBF.Rig.TestMethods.iPerlCommunication.communication.Utils;
using OptoTelegramFlags = TBF.Rig.TestMethods.iPerlCommunication.common.OptoTelegramFlags;
using OptoTelegramRaw = TBF.Rig.TestMethods.iPerlCommunication.common.OptoTelegramRaw;
@ -28,6 +31,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
public class IperlHead : ComponentBase, IDevice,IRegReaderDatastream, ISessionDataMngmnt, IOperation
{
private static readonly ILog log = LogManager.GetLogger(typeof(IperlHead));
private static readonly ILog logStream = LogManager.GetLogger("StreamData");
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
#if TURA_SPECIAL
@ -41,6 +45,19 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
public const int StartEndFilterSamplesCount2 = 20; /// StartEndFilterSamplesCount = 2 * StartEndFilterSamplesCount2 + 1
public const int FeatureVectorSize = 9;
private OptoHeadTest _optoHeadTest;
public OptoHeadTest OptoHeadTest
{
get
{
if (_optoHeadTest == null)
_optoHeadTest = new OptoHeadTest(this);
return _optoHeadTest;
}
set { _optoHeadTest = value; }
}
readonly IperlHeadCfg iperlHeadCfg;
@ -103,8 +120,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
/// <summary>
/// Passed to OptoTelegramRaw.UpdateFromString(...)
/// </summary>
Int64 volumeRawExtLast;
Int64 timestampExtLast;
double volumeRawExtLast;
double timestampExtLast;
FlowDirectionDetection flowDirectionDetection;
@ -360,7 +377,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
///
/// Timestamp from the opto telegram
///
private Int64 lastTimestamp;
private double lastTimestamp;
private double timestampSec;
private double timestampSec0;
@ -383,7 +400,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
///
/// Volume of water from the opto telegram
///
private Int64 lastVolumeRaw; /// Last read raw volume
private double lastVolumeRaw; /// Last read raw volume
private double volumeLtr;
private double volumeLtr0;
@ -589,10 +606,10 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
{
ResultCode = 0;
volumeLtr = 0;
volumeLtr0 = 0;
timestampSec = 0;
timestampSec0 = 0;
volumeLtr = Double.NaN;
volumeLtr0 = Double.NaN;
timestampSec = Double.NaN;
timestampSec0 = Double.NaN;
extraDataPath = null;
@ -858,6 +875,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
endWMState = volumeLtr;
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;
}
@ -980,8 +998,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
{
string line = optoSerialPort.ReadLine(); // string
byte[] bytes = optoSerialPort.Encoding.GetBytes(line);
Console.WriteLine("RX ← " + HexFormatter.ToSerialHex(bytes));
log.Debug("ComPort: "+ OptoComPortNr +" OPTHO RX ← " + HexFormatter.ToSerialHex(bytes));
try
{
@ -989,6 +1007,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
if (optoState == DataStreamState.ProcessAndSave)
{
DiagnosticLedState4Data data = (DiagnosticLedState4Data)parser.ParseLine(line, false);
//DiagnostigLedDataByUnit unitData = new DiagnostigLedDataByUnit(Common.Unit.m3, Common.Unit.m3, data);
int bufferIx = BufferIdx(optoDataCount);
if (synchronized)
@ -999,11 +1018,17 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
if (data != null)
{
//TODO BUMI UNITS
// apply units
log.Debug($"OPTHO {OptoComPortNr} Parsed optho data:" + data.ToString());
logStream.Debug($"ID: {OptoComPortNr} " + data.ToString());
optoData[bufferIx].UpdateFromSmart(data, optoDataCount,
Convert.ToSingle(Sequences.ProcessData.RefFlow.Val), ref volumeRawExtLast,
ref timestampExtLast);
log.Debug("OPTHO UpdateFromSmart() volumeRawExtLast:" + volumeRawExtLast + " timestampExtLast:" + timestampExtLast);
/// CR+LF was found && (pos >= OptoTelegramRaw.Length - 2) && the telegram is OK
flowDirectionDetection.WriteToFifo(volumeRawExtLast, timestampExtLast);
OptoTelegramReceived(optoDataCount, true, volumeRawExtLast,
@ -1056,31 +1081,31 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
}
void OptoTelegramReceived(int currentIx, bool async, Int64 volumeRawExt, Int64 timestampRawExt)
void OptoTelegramReceived(int currentIx, bool async, double volumeRawExt, double timestampRawExt)
{
currentTelegramIx = currentIx;
lastVolumeRaw = volumeRawExt;
lastTimestamp = timestampRawExt;
if (volumeLtr == 0 && volumeLtr0 == 0)
if (Double.IsNaN(volumeLtr) && Double.IsNaN(volumeLtr0))
{
volumeLtr = (double)lastVolumeRaw * ScalingFactor() / 16000.0;
volumeLtr = lastVolumeRaw;
volumeLtr0 = volumeLtr;
}
else
{
volumeLtr = (double)lastVolumeRaw * ScalingFactor() / 16000.0;
volumeLtr = lastVolumeRaw;
}
if (timestampSec == 0 && timestampSec0 == 0)
if (Double.IsNaN(timestampSec)&& Double.IsNaN(timestampSec0))
{
timestampSec = (double)lastTimestamp / 8192.0;
timestampSec = lastTimestamp;
timestampSec0 = timestampSec;
}
else
{
timestampSec = (double)lastTimestamp / 8192.0;
timestampSec = lastTimestamp;
}
}
@ -1185,25 +1210,50 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
/// <returns>Filtered volume</returns>
double VolumeFromSamples(OptoTelegramRaw[] optoData, int optoDataCount, int unwrappedIx, double scalingFactor, int samplesCount2 = 0)
{
if (samplesCount2 < 0) samplesCount2 = 0;
if ((unwrappedIx - samplesCount2) < 0 || (unwrappedIx + samplesCount2) >= optoDataCount) return 0;
Int64 sum = 0;
for (int i = unwrappedIx - samplesCount2; i <= unwrappedIx + samplesCount2; i++)
log.Debug("-- Get VolumeFromSamples() --");
if (unwrappedIx >= optoDataCount)
{
int wrappedIx = BufferIdx(i);
if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
{
return 0;
}
sum += optoData[wrappedIx].VolumeRawExt;
log.Debug(
$"-- FAILED VolumeFromSamples() - unwrappedIx {unwrappedIx} >= optoDataCount{optoDataCount}--");
return 0;
}
int wrappedIx = BufferIdx(unwrappedIx);
return 0.0000625 * scalingFactor * sum / (double)(2 * samplesCount2 + 1);
if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
{
log.Debug($"-- Get VolumeFromSamples() - Quit because:{optoData[wrappedIx].Flags}--");
return 0;
}
log.Debug($"Valid data VolumeRawExt: {optoData[wrappedIx].VolumeRawExt}");
return optoData[wrappedIx].VolumeRawExt;
//
// if (samplesCount2 < 0) samplesCount2 = 0;
// if ((unwrappedIx - samplesCount2) < 0 || (unwrappedIx + samplesCount2) >= optoDataCount) return 0;
//
//
// Int64 sum = 0;
// for (int i = unwrappedIx - samplesCount2; i <= unwrappedIx + samplesCount2; i++)
// {
// int wrappedIx = BufferIdx(i);
//
// if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
// optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
// optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
// {
// return 0;
// }
//
// sum += optoData[wrappedIx].VolumeRawExt;
// }
//
// return 0.0000625 * scalingFactor * sum / (double)(2 * samplesCount2 + 1);
}
/// <summary>
@ -1214,25 +1264,45 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
/// <returns>Filtered time</returns>
double TimeFromSamples(OptoTelegramRaw[] optoData, int optoDataCount, int unwrappedIx, int samplesCount2 = 0)
{
if (samplesCount2 < 0) samplesCount2 = 0;
if ((unwrappedIx - samplesCount2) < 0 || (unwrappedIx + samplesCount2) >= optoDataCount) return 0;
Int64 sum = 0;
for (int i = unwrappedIx - samplesCount2; i <= unwrappedIx + samplesCount2; i++)
log.Debug("-- Get TimeFromSamples() --");
if (unwrappedIx >= optoDataCount)
{
int wrappedIx = BufferIdx(i);
if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
{
return 0;
}
sum += optoData[wrappedIx].TimestampExt;
log.Debug(
$"-- FAILED TimeFromSamples() - unwrappedIx {unwrappedIx} >= optoDataCount{optoDataCount}--");
return 0;
}
int wrappedIx = BufferIdx(unwrappedIx);
return sum / (double)(8192 * (2 * samplesCount2 + 1));
if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
{
log.Debug($"-- Get TimeFromSamples() - Quit because:{optoData[wrappedIx].Flags}--");
return 0;
}
log.Debug($"Valid data TimestampExt: {optoData[wrappedIx].TimestampExt}");
return optoData[wrappedIx].TimestampExt;
// if (samplesCount2 < 0) samplesCount2 = 0;
// if ((unwrappedIx - samplesCount2) < 0 || (unwrappedIx + samplesCount2) >= optoDataCount) return 0;
//
// Int64 sum = 0;
// for (int i = unwrappedIx - samplesCount2; i <= unwrappedIx + samplesCount2; i++)
// {
// int wrappedIx = BufferIdx(i);
//
// if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK &&
// optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart &&
// optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd)
// {
// return 0;
// }
//
// sum += optoData[wrappedIx].TimestampExt;
// }
//
// return sum / (double)(8192 * (2 * samplesCount2 + 1));
}
/// <summary>
@ -1451,5 +1521,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
{
return this;
}
}
}

View File

@ -83,10 +83,10 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
switch (rfidCommandComboBox.SelectedValue)
{
case "ReadPCB":
rfidListItem.Text = $"PCB: {OptoHeadTest.ReadRequest_PCB(ref iPerlHead)}";
rfidListItem.Text = $"PCB: {iPerlHead.OptoHeadTest.ReadRequest_PCB()}";
break;
case "SetTestMode":
rfidListItem.Text = OptoHeadTest.SetTestMode(ref iPerlHead, ref isTestModeSuccessful);
rfidListItem.Text = iPerlHead.OptoHeadTest.SetTestMode(ref isTestModeSuccessful);
optoListBox.Items.Clear();
stopWorkerThread = false;
optoThread = new Thread(OptoWorker);
@ -98,7 +98,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
break;
case "SetActiveMode":
rfidListItem.Text = OptoHeadTest.SetActiveMode(ref iPerlHead, ref isTestModeSuccessful);
rfidListItem.Text = iPerlHead.OptoHeadTest.SetActiveMode(ref isTestModeSuccessful);
stopWorkerThread = true;
iPerlHead.StopDataStreamProcessing(); // close opto port
break;