Add iPerl communication services and utilities. Include OptoTelegramRaw, RadioService, OpthoHeadService, SerialDriver, and related diagnostic parsers for enhanced communication with iPerl devices.
This commit is contained in:
parent
e839148a6f
commit
66b43350cc
327
TBF/Rig/TestMethods/iPerlCommunication/common/OptoTelegramRaw.cs
Normal file
327
TBF/Rig/TestMethods/iPerlCommunication/common/OptoTelegramRaw.cs
Normal file
@ -0,0 +1,327 @@
|
||||
///
|
||||
/// Copyright (c) 2015-2021 Sensus Metering Systems
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.common
|
||||
{
|
||||
public enum OptoTelegramFlags : byte
|
||||
{
|
||||
OK = 0,
|
||||
OK_TestStart,
|
||||
OK_TestEnd,
|
||||
InvalidTelegram, /// Wrong telegram format of checksum error
|
||||
SyncError,
|
||||
}
|
||||
|
||||
public class OptoTelegramRaw
|
||||
{
|
||||
public static readonly int Length = 42;
|
||||
private static CultureInfo culture;
|
||||
|
||||
|
||||
///
|
||||
/// Strobed value
|
||||
///
|
||||
public static decimal TestStartTimestampDec;
|
||||
|
||||
///
|
||||
/// Stored values
|
||||
///
|
||||
public OptoTelegramFlags Flags;
|
||||
|
||||
public DateTime DateTime; /// From PC
|
||||
public float RefFlow; /// [m3/h]
|
||||
public int Counter;
|
||||
|
||||
public Int32 EmfRaw; /// Signed EMF from iPerl opto data
|
||||
public Int16 MagneticFieldRaw;
|
||||
public Int16 FlowRaw;
|
||||
public UInt32 VolumeRaw;
|
||||
public Int64 VolumeRawExt;
|
||||
public Int16 Impedance;
|
||||
public UInt32 Timestamp;
|
||||
public Int64 TimestampExt;
|
||||
public byte CheckSum;
|
||||
|
||||
///
|
||||
/// Calculated values
|
||||
///
|
||||
public double EMF()
|
||||
{
|
||||
return 0.000000333 * (double)EmfRaw;
|
||||
}
|
||||
public double MagneticField() { return (double)MagneticFieldRaw; }
|
||||
public double Flow(double scalingFactor) { return 0.225 * scalingFactor * (double)FlowRaw; }
|
||||
public double Volume(double scalingFactor) { return 0.0000625 * scalingFactor * (double)VolumeRawExt; }
|
||||
public Int32 FlipTime() { return Impedance; }
|
||||
public decimal TimestampDec() { return (decimal)TimestampExt / (decimal)8192; }
|
||||
public double VolumeDelta(double scalingFactor, OptoTelegramRaw previous) { return (previous == null) ? 0 : Volume(scalingFactor) - previous.Volume(scalingFactor); }
|
||||
public decimal TimeDelta() { return TimestampDec() - TestStartTimestampDec; }
|
||||
public string Label()
|
||||
{
|
||||
if (Flags == OptoTelegramFlags.OK_TestStart) return "#### start test ####";
|
||||
else if (Flags == OptoTelegramFlags.OK_TestEnd) return "#### end of test ####";
|
||||
else return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
static OptoTelegramRaw()
|
||||
{
|
||||
culture = CultureInfo.CreateSpecificCulture("DE"); /// This is to use comma as decimal number separator
|
||||
}
|
||||
|
||||
public OptoTelegramRaw()
|
||||
{
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
///
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alternative to UpdateFromString(...) when data are flushed
|
||||
/// </summary>
|
||||
public bool UpdateFromStringDummy(string telegram)
|
||||
{
|
||||
DateTime = DateTime.Now;
|
||||
RefFlow = 0;
|
||||
|
||||
if ((telegram == null) || (telegram.Length < Length) ||
|
||||
(telegram[6] != '\t') || (telegram[11] != '\t') || (telegram[16] != '\t') ||
|
||||
(telegram[23] != '\t') || (telegram[28] != '\t') || (telegram[37] != '\t') ||
|
||||
(telegram[40] != '\r') || (telegram[41] != '\n'))
|
||||
{
|
||||
Flags = OptoTelegramFlags.InvalidTelegram;
|
||||
return false;
|
||||
}
|
||||
|
||||
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 = f7 && (calculatedCheckSum == CheckSum);
|
||||
|
||||
Flags = allOk ? OptoTelegramFlags.OK : OptoTelegramFlags.InvalidTelegram;
|
||||
|
||||
return allOk;
|
||||
}
|
||||
|
||||
|
||||
public void SetFlags(OptoTelegramFlags flags)
|
||||
{
|
||||
this.Flags = flags;
|
||||
}
|
||||
|
||||
|
||||
public string ToString(double scalingFactor, OptoTelegramRaw previous)
|
||||
{
|
||||
if (Flags == OptoTelegramFlags.SyncError)
|
||||
{
|
||||
return "Sychronization error";
|
||||
}
|
||||
else if (Flags == OptoTelegramFlags.InvalidTelegram)
|
||||
{
|
||||
return "Invalid telegram";
|
||||
}
|
||||
else /// if (flags == OptoTelegramFlags.OK / OptoTelegramFlags.OK_TestStart / OptoTelegramFlags.OK_TestEnd)
|
||||
{
|
||||
return string.Format("{0}:{1}:{2}.{3}\t{4} :\t{5}\t{6}\t{7}\t{8}\t{9}\t{10}\t{11}\t{12}\t{13}\t{14}\t{15}\t{16}\t{17}\t{18}\t{19}\t{20}\t{21}\t{22}",
|
||||
DateTime.Hour.ToString("D2"),
|
||||
DateTime.Minute.ToString("D2"),
|
||||
DateTime.Second.ToString("D2"),
|
||||
DateTime.Millisecond.ToString("D4"),
|
||||
Counter,
|
||||
(EmfRaw & 0x00FFFFFF).ToString("X6"),
|
||||
MagneticFieldRaw.ToString("X4"),
|
||||
FlowRaw.ToString("X4"),
|
||||
VolumeRaw.ToString("X6"),
|
||||
Impedance.ToString("X4"),
|
||||
Timestamp.ToString("X8"),
|
||||
CheckSum.ToString("X2"),
|
||||
EMF().ToString("F4", culture),
|
||||
MagneticField().ToString("F0", culture),
|
||||
Flow(scalingFactor).ToString("F2", culture),
|
||||
Volume(scalingFactor).ToString("F4", culture),
|
||||
FlipTime().ToString("F0", culture),
|
||||
TimestampDec().ToString("F4", culture),
|
||||
(RefFlow * 1000).ToString("F2", culture),
|
||||
VolumeDelta(scalingFactor, previous).ToString("F4", culture),
|
||||
TimeDelta().ToString("F3", culture),
|
||||
scalingFactor.ToString("F1", culture),
|
||||
Label());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const byte Start = 0x53; //'S'
|
||||
public const byte Write = 0x57; // 'W'
|
||||
public const byte Read = 0x52; // 'R'
|
||||
public const byte End = 0x0D; //'.'
|
||||
public const byte Question = (byte)0x3F; // '?'
|
||||
public static readonly byte[] Version = {0x76, 0x65, 0x72, 0x73 }; // 'v' 'e' 'r' 's'
|
||||
|
||||
public const byte StatusOk = 0x01;
|
||||
public const byte StatusNok = 0x00;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol
|
||||
{
|
||||
public sealed class IperlHatFrame
|
||||
{
|
||||
public byte Start { get; }
|
||||
public byte Direction { get; }
|
||||
public byte End { get; }
|
||||
public byte Length { get; }
|
||||
|
||||
public byte[] CommandInformation { get; }
|
||||
public byte[] Payload { get; }
|
||||
|
||||
public IperlHatFrame(byte start, byte direction, byte length, byte[] commandBytes, byte[] payload, byte end)
|
||||
{
|
||||
Start = start;
|
||||
Direction = direction;
|
||||
Length = length;
|
||||
CommandInformation = commandBytes ?? Array.Empty<byte>();
|
||||
Payload = payload ?? Array.Empty<byte>();
|
||||
End = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol
|
||||
{
|
||||
public sealed class IperlHatFrameBuilder
|
||||
{
|
||||
|
||||
private byte _direction;
|
||||
private readonly List<byte> _commandBytes = new List<byte>();
|
||||
private readonly List<byte> _payload = new List<byte>();
|
||||
|
||||
public IperlHatFrameBuilder RequestResponse(bool enabled)
|
||||
{
|
||||
_direction = enabled ? TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.Write : TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.Read;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IperlHatFrameBuilder AddCommand(ProtocolCommand command)
|
||||
{
|
||||
_commandBytes.Add((byte)command);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IperlHatFrameBuilder AddSubCommand(ProtocolCommand subCommand)
|
||||
{
|
||||
if (_commandBytes.Count == 0 ||
|
||||
_commandBytes[0] != (byte)ProtocolCommand.DeviceSpecific)
|
||||
throw new InvalidOperationException(
|
||||
"Sub-command is only valid for DeviceSpecific (0xFD) commands.");
|
||||
|
||||
_commandBytes.Add((byte)subCommand);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IperlHatFrameBuilder AddSubCommand(ProtocolStatuses subCommand)
|
||||
{
|
||||
if (_commandBytes.Count == 0 ||
|
||||
_commandBytes[0] != (byte)ProtocolCommand.SetState)
|
||||
throw new InvalidOperationException(
|
||||
"Sub-command is only valid for SetState (0xA1) commands.");
|
||||
|
||||
_commandBytes.Add((byte)subCommand);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IperlHatFrameBuilder AddDeviceCommand(
|
||||
ProtocolDeviceSubCommand subCommand)
|
||||
{
|
||||
_commandBytes.Add((byte)ProtocolCommand.DeviceSpecific);
|
||||
_commandBytes.Add((byte)subCommand);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IperlHatFrameBuilder SetVersionCommand()
|
||||
{
|
||||
_commandBytes.Add((byte)ProtocolCommand.Question);
|
||||
_payload.AddRange(TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.Version);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IperlHatFrameBuilder AddPayload(byte[] payload)
|
||||
{
|
||||
if (payload != null)
|
||||
_payload.AddRange(payload);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IperlHatFrameBuilder AddPayload(DiagnosticLedState state)
|
||||
{
|
||||
_payload.Add((byte)state);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IperlHatFrameBuilder AddPayload(byte payload)
|
||||
{
|
||||
_payload.Add(payload);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IperlHatFrameBuilder AddDiagnosticLedState(DiagnosticLedState state)
|
||||
{
|
||||
RequestResponse(true);
|
||||
AddDeviceCommand(ProtocolDeviceSubCommand.SetDiagnosticLEDState);
|
||||
AddPayload((byte)state);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IperlHatFrameBuilder AddNullTerminatedAscii(string text)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
_commandBytes.AddRange(
|
||||
System.Text.Encoding.ASCII.GetBytes(text));
|
||||
|
||||
_commandBytes.Add(0x00);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IperlHatFrame BuildFrame()
|
||||
{
|
||||
if (_commandBytes.Count == 0)
|
||||
throw new InvalidOperationException("No command specified.");
|
||||
|
||||
byte length = (byte)(4 + _commandBytes.Count + _payload.Count); // 4 = START + dirrection + LEN + END
|
||||
|
||||
|
||||
return new IperlHatFrame(
|
||||
TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.Start,
|
||||
_direction,
|
||||
length,
|
||||
_commandBytes.ToArray(),
|
||||
_payload.ToArray(),
|
||||
TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.End);
|
||||
}
|
||||
|
||||
public byte[] BuildBytes()
|
||||
{
|
||||
IperlHatFrame frame = BuildFrame();
|
||||
|
||||
if (frame.CommandInformation.Length > 0 && frame.CommandInformation[0] == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.Question)
|
||||
{
|
||||
var bytes = new List<byte>
|
||||
{
|
||||
frame.Start,
|
||||
frame.Direction,
|
||||
};
|
||||
|
||||
bytes.AddRange(frame.CommandInformation);
|
||||
bytes.AddRange(frame.Payload);
|
||||
bytes.Add(frame.End);
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
var bytes = new List<byte>
|
||||
{
|
||||
frame.Start,
|
||||
frame.Direction,
|
||||
frame.Length,
|
||||
};
|
||||
|
||||
bytes.AddRange(frame.CommandInformation);
|
||||
bytes.AddRange(frame.Payload);
|
||||
bytes.Add(frame.End);
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol
|
||||
{
|
||||
public sealed class IperlHatFrameParser
|
||||
{
|
||||
|
||||
public IperlHatResponse Parse(byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
|
||||
if (data.Length < 5)
|
||||
throw new FormatException("Frame too short.");
|
||||
|
||||
|
||||
|
||||
if (data[0] != TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.Start)
|
||||
{
|
||||
//if version parse version
|
||||
if (data[0] == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.Question)
|
||||
{
|
||||
//Define Question answer
|
||||
var prefix = new List<byte>{ TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.Question };
|
||||
var end = new List<byte>{ TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.End };
|
||||
|
||||
if (IsPrefixValid(data, prefix, end))
|
||||
{
|
||||
//whole payload may be like "vers: Harry T:B800, V:06.06.01, FW:190215, 7ECE, B1.6.01, HW:4, Serial:0"
|
||||
prefix = new List<byte>{ TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.Question };
|
||||
byte[] payloadVersion = ExtractPayloadUsePrefix(data, prefix, end);
|
||||
return new IperlHatResponse(TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.Question, payloadVersion.Length > 0 ? TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.StatusOk : TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.StatusNok, payloadVersion);
|
||||
}
|
||||
}
|
||||
|
||||
throw new FormatException("Invalid START byte.");
|
||||
}
|
||||
|
||||
if (data[1] != TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.Read)
|
||||
throw new FormatException("Frame is no Response.");
|
||||
|
||||
byte length = data[2];
|
||||
if (length != data.Length)
|
||||
throw new FormatException("Length mismatch.");
|
||||
|
||||
byte direction = data[1];
|
||||
byte status = data[3];
|
||||
|
||||
var prefixCommand = new List<byte>{ TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.Start,direction,length,status };
|
||||
var endCommand = new List<byte>{ TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.End };
|
||||
|
||||
byte[] payload = ExtractPayloadUsePrefix(data,prefixCommand,endCommand);
|
||||
|
||||
return new IperlHatResponse(0x00, status, payload);
|
||||
}
|
||||
|
||||
|
||||
private static byte[] ExtractPayloadUsePrefix(byte[] data, List<byte> prefix, List<byte> end)
|
||||
{
|
||||
// payload exists only if frame longer than:
|
||||
// START + DIRECTION + LEN + CTRL + END = 5 bytes
|
||||
// OR VERSION_START + VERSION = 5 bytes
|
||||
if (data.Length <= 5)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
//check prefix is equal
|
||||
int prefixLength = prefix.Count;
|
||||
byte[] commandPrefix = new byte[prefixLength];
|
||||
Buffer.BlockCopy(data, 0, commandPrefix, 0, prefixLength);
|
||||
|
||||
if (StartsWithPrefix(end, commandPrefix))
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
int payloadLength = data.Length - (prefix.Count + end.Count);
|
||||
byte[] payload = new byte[payloadLength];
|
||||
Buffer.BlockCopy(data, prefix.Count, payload, 0, payloadLength);
|
||||
return payload;
|
||||
}
|
||||
|
||||
private static bool IsPrefixValid(byte[] data, List<byte> prefix, List<byte> end)
|
||||
{
|
||||
int prefixLength = prefix.Count;
|
||||
// payload exists only if frame longer than:
|
||||
// OR VERSION_START + VERSION = 5 bytes - "?VERS" version implemented
|
||||
if (data.Length <= prefixLength) // need be and on END
|
||||
return false;
|
||||
|
||||
//check prefix is equal
|
||||
byte[] commandPrefix = new byte[prefixLength];
|
||||
Buffer.BlockCopy(data, 0, commandPrefix, 0, prefixLength);
|
||||
|
||||
if (StartsWithPrefix(end, commandPrefix))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool StartsWithPrefix(List<byte> data, byte[] prefix)
|
||||
{
|
||||
if (data.Count < prefix.Length)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < prefix.Length; i++)
|
||||
{
|
||||
if (data[i] != prefix[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static byte[] ExtractVersionPayload(byte[] data)
|
||||
{
|
||||
// payload exists only if frame longer than:
|
||||
// START + LEN + CTRL + STATUS + CHK_HI + CHK_LO = 6 bytes
|
||||
if (data.Length <= 5)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
int payloadLength = data.Length - 4;
|
||||
byte[] payload = new byte[payloadLength];
|
||||
Buffer.BlockCopy(data, 5, payload, 0, payloadLength);
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol
|
||||
{
|
||||
public static class IperlHatProtocol
|
||||
{
|
||||
public const byte START = 0x0D;
|
||||
|
||||
// Control bits (CNTRL1)
|
||||
public const byte RESPONSE_FLAG = 0x08; // RF
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol
|
||||
{
|
||||
public sealed class IperlHatResponse
|
||||
{
|
||||
public byte Control { get; } //classic control byte - valid for question now
|
||||
private byte Status { get; }
|
||||
public byte[] Payload { get; }
|
||||
|
||||
public bool IsOk => Status == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.StatusOk;
|
||||
|
||||
public IperlHatResponse(byte control, byte status, byte[] payload)
|
||||
{
|
||||
Control = control;
|
||||
Status = status;
|
||||
Payload = payload ?? Array.Empty<byte>();
|
||||
}
|
||||
|
||||
|
||||
public string GetAsciiPayload()
|
||||
{
|
||||
if (Payload.Length == 0)
|
||||
return null;
|
||||
|
||||
int length = Array.IndexOf(Payload, (byte)0x00);
|
||||
if (length < 0)
|
||||
length = Payload.Length;
|
||||
|
||||
return System.Text.Encoding.ASCII.GetString(Payload, 0, length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed
|
||||
{
|
||||
public sealed class DiagnosticLedParser
|
||||
{
|
||||
private readonly DiagnosticLedState _state;
|
||||
|
||||
public DiagnosticLedParser(DiagnosticLedState state)
|
||||
{
|
||||
_state = state;
|
||||
}
|
||||
|
||||
public DiagnosticLedData ParseLine(string line, bool checkLineTermination = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(line))
|
||||
throw new ArgumentNullException(nameof(line));
|
||||
|
||||
if (checkLineTermination && !line.EndsWith("\r\n"))
|
||||
throw new FormatException("Invalid diagnostic LED line termination");
|
||||
|
||||
string trimmed = line.TrimEnd('\r', '\n');
|
||||
string[] parts = trimmed.Split('\t');
|
||||
|
||||
if (parts.Length < 2)
|
||||
throw new FormatException("Too few diagnostic LED fields");
|
||||
|
||||
// ---- Checksum ----
|
||||
string checksumHex = parts[parts.Length - 1];
|
||||
|
||||
int lastTab = trimmed.LastIndexOf('\t');
|
||||
if (lastTab < 0)
|
||||
throw new FormatException("Checksum separator not found");
|
||||
|
||||
string beforeChecksum = trimmed.Substring(0, lastTab + 1);
|
||||
|
||||
byte expected = DiagnosticChecksum.Compute(beforeChecksum);
|
||||
byte actual = DiagnosticHex.ParseByte(checksumHex);
|
||||
|
||||
if (expected != actual)
|
||||
throw new FormatException("Diagnostic LED checksum mismatch");
|
||||
|
||||
// ---- Dispatch ----
|
||||
switch (_state)
|
||||
{
|
||||
case DiagnosticLedState.State1:
|
||||
return new DiagnosticLedState1Data(line, parts);
|
||||
|
||||
case DiagnosticLedState.State2:
|
||||
return new DiagnosticLedState2Data(line, parts);
|
||||
|
||||
case DiagnosticLedState.State3:
|
||||
return new DiagnosticLedState3Data(line, parts);
|
||||
|
||||
case DiagnosticLedState.State4:
|
||||
return new DiagnosticLedState4Data(line, parts);
|
||||
|
||||
case DiagnosticLedState.State5:
|
||||
return new DiagnosticLedState5Data(line, parts);
|
||||
|
||||
case DiagnosticLedState.State6:
|
||||
return new DiagnosticLedState6Data(line, parts);
|
||||
|
||||
case DiagnosticLedState.State7:
|
||||
return new DiagnosticLedState7Data(line, parts);
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("Unknown diagnostic LED state");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed
|
||||
{
|
||||
/// <summary>
|
||||
/// Diagnostic LED output mode.
|
||||
/// <para>
|
||||
/// Determines the format and content of high-speed serial diagnostic data
|
||||
/// emitted by the meter when the diagnostic LED is enabled.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Each state corresponds to a specific TAB-separated ASCII HEX frame layout
|
||||
/// as defined in the iPERL TouchRead protocol documentation.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// See <see cref="ProtocolDeviceSubCommand.SetDiagnosticLEDState"/>
|
||||
/// diagnostic LED States.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public enum DiagnosticLedState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Diagnostic LED OFF - State #0.
|
||||
/// <para>
|
||||
/// Basic diagnostic output containing raw ADC, field strength,
|
||||
/// flow rate, volume accumulator, and capacitor voltage.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
StateOFF = 0x00,
|
||||
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #1.
|
||||
/// <para>
|
||||
/// Basic diagnostic output containing raw ADC, field strength,
|
||||
/// flow rate, volume accumulator, and capacitor voltage.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
State1 = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #2.
|
||||
/// <para>
|
||||
/// Extends State #1 with LCD volume, meter state,
|
||||
/// and low-flow cutoff indication.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
State2 = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #3.
|
||||
/// <para>
|
||||
/// Extends State #1 with field calibration value,
|
||||
/// ASIC timestamp, and field drive time.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
State3 = 0x03,
|
||||
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #4.
|
||||
/// <para>
|
||||
/// Extended diagnostic output including mean flow rate,
|
||||
/// field measurements, integrator calibration values,
|
||||
/// and ASIC state.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
State4 = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #5.
|
||||
/// <para>
|
||||
/// Extends State #4 with water impedance measurement.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
State5 = 0x05,
|
||||
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #6.
|
||||
/// <para>
|
||||
/// Extends State #5 with electrode delta, spike detection data,
|
||||
/// pipe status, LCD volume, and additional ASIC state.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
State6 = 0x06,
|
||||
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #7.
|
||||
/// <para>
|
||||
/// Extends State #6 with raw ADC before offset correction,
|
||||
/// detrended ADC value, imaginary water impedance,
|
||||
/// electrode voltage noise, and ADC offset learning status.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
State7 = 0x07
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all Diagnostic LED data frames.
|
||||
///
|
||||
/// <para>
|
||||
/// The iPERL meter emits diagnostic LED frames when the
|
||||
/// Diagnostic LED is enabled using the
|
||||
/// <c>Set Diagnostic LED State (0xFD 0x60)</c> command.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// All diagnostic LED states (State #1 – State #7) share a common
|
||||
/// set of leading fields, followed by state-specific extensions.
|
||||
/// This class represents those common fields.
|
||||
/// </para>
|
||||
///
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Pos</term>
|
||||
/// <description>Common field description</description>
|
||||
/// </listheader>
|
||||
/// <item><term>0 – xxxxxx</term><description>Signed 24-bit ADC value (two’s complement)</description></item>
|
||||
/// <item><term>1 – aaaa</term><description>Unsigned 16-bit field strength (internal units)</description></item>
|
||||
/// <item><term>2 – yyyy</term><description>Signed 16-bit raw flow rate (¼ ml per bit)</description></item>
|
||||
/// <item><term>3 – vvvvvv</term><description>Unsigned 24-bit raw volume accumulation (¼ ml per bit)</description></item>
|
||||
/// <item><term>4 – cccc</term><description>Unsigned 16-bit millivolt delta on the field drive capacitor</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>
|
||||
/// Each derived state class parses additional fields starting at
|
||||
/// position 5, according to the selected diagnostic LED state.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The raw ASCII line (including checksum and CRLF) is preserved
|
||||
/// for logging, debugging, and offline analysis.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class DiagnosticLedData
|
||||
{
|
||||
|
||||
public abstract int GetByteCount();
|
||||
|
||||
/// <summary>
|
||||
/// Raw diagnostic LED line exactly as received from the meter,
|
||||
/// including checksum and CRLF.
|
||||
/// </summary>
|
||||
public string RawLine { get; }
|
||||
|
||||
// ----- Common fields (present in all LED states) -----
|
||||
|
||||
/// <summary>
|
||||
/// Signed 24-bit ADC value (two’s complement).
|
||||
/// </summary>
|
||||
public int Adc24 { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unsigned 16-bit field strength in internal (non-legacy) units.
|
||||
/// </summary>
|
||||
public ushort FieldStrength { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signed 16-bit raw flow rate in units of ¼ milliliter per bit.
|
||||
/// </summary>
|
||||
public short RawFlow { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unsigned 24-bit raw volume accumulation in units of ¼ milliliter per bit.
|
||||
/// </summary>
|
||||
public uint RawVolume { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unsigned 16-bit millivolt delta measured on the field drive capacitor.
|
||||
/// </summary>
|
||||
public ushort CapacitorMv { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the base diagnostic LED data with the raw input line.
|
||||
/// </summary>
|
||||
/// <param name="raw">
|
||||
/// Raw ASCII line received from the diagnostic LED output.
|
||||
/// </param>
|
||||
protected DiagnosticLedData(string raw)
|
||||
{
|
||||
RawLine = raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
public static class DiagnosticLedFrameSpec
|
||||
{
|
||||
public static int GetExpectedAsciiLength(DiagnosticLedState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case DiagnosticLedState.State1: return 33;
|
||||
case DiagnosticLedState.State2: return 48;
|
||||
case DiagnosticLedState.State3: return 50;
|
||||
case DiagnosticLedState.State4: return 84;
|
||||
case DiagnosticLedState.State5: return 89;
|
||||
case DiagnosticLedState.State6: return 112;
|
||||
case DiagnosticLedState.State7: return 139;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(state));
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetExpectedFieldCount(DiagnosticLedState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case DiagnosticLedState.State1: return 6;
|
||||
case DiagnosticLedState.State2: return 9;
|
||||
case DiagnosticLedState.State3: return 9;
|
||||
case DiagnosticLedState.State4: return 15;
|
||||
case DiagnosticLedState.State5: return 16;
|
||||
case DiagnosticLedState.State6: return 21;
|
||||
case DiagnosticLedState.State7: return 26;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(state));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #1 data frame.
|
||||
///
|
||||
/// <para>
|
||||
/// Frame format: TAB-separated ASCII hexadecimal fields, terminated by CRLF.
|
||||
/// The checksum is an 8-bit sum of all previous ASCII bytes including
|
||||
/// the TAB character before the checksum field.
|
||||
/// </para>
|
||||
///
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Pos</term>
|
||||
/// <description>Field description</description>
|
||||
/// </listheader>
|
||||
/// <item><term>0 – xxxxxx</term><description>Signed 24-bit ADC value (two’s complement)</description></item>
|
||||
/// <item><term>1 – aaaa</term><description>Unsigned 16-bit field strength (internal units)</description></item>
|
||||
/// <item><term>2 – yyyy</term><description>Signed 16-bit raw flow rate (¼ ml per bit)</description></item>
|
||||
/// <item><term>3 – vvvvvv</term><description>Unsigned 24-bit raw volume accumulation (¼ ml per bit)</description></item>
|
||||
/// <item><term>4 – cccc</term><description>Unsigned 16-bit millivolt delta on the field drive capacitor</description></item>
|
||||
/// <item><term>5 – ss</term><description>Unsigned 8-bit checksum (sum of all previous ASCII bytes
|
||||
/// including the TAB before the checksum field)</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class DiagnosticLedState1Data : DiagnosticLedData
|
||||
{
|
||||
public DiagnosticLedState1Data(string raw, string[] f)
|
||||
: base(raw)
|
||||
{
|
||||
Adc24 = DiagnosticHex.ParseInt24(f[0]);
|
||||
FieldStrength = DiagnosticHex.ParseUInt16(f[1]);
|
||||
RawFlow = DiagnosticHex.ParseInt16(f[2]);
|
||||
RawVolume = DiagnosticHex.ParseUInt24(f[3]);
|
||||
CapacitorMv = DiagnosticHex.ParseUInt16(f[4]);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"DiagnosticLedState1Data: Adc24={Adc24}, FieldStrength={FieldStrength}, RawFlow={RawFlow}, RawVolume={RawVolume}, CapacitorMv={CapacitorMv}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format: xxxxxx aaaa yyyy vvvvvv cccc ss
|
||||
/// Chars total = 26
|
||||
/// Tabs = 5
|
||||
/// CRLF = 2
|
||||
/// Total bytes = 33
|
||||
/// </summary>
|
||||
/// <returns> Total bytes</returns>
|
||||
public override int GetByteCount()
|
||||
{
|
||||
return 33;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #2 data frame.
|
||||
///
|
||||
/// <para>
|
||||
/// State #2 extends the common diagnostic LED fields with information
|
||||
/// about the LCD-displayed volume, the current meter operating state,
|
||||
/// and whether the meter is in low-flow cutoff mode.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Frame format: TAB-separated ASCII hexadecimal fields, terminated by CRLF.
|
||||
/// The checksum is an 8-bit sum of all previous ASCII bytes including
|
||||
/// the TAB character before the checksum field.
|
||||
/// </para>
|
||||
///
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Pos</term>
|
||||
/// <description>Field description</description>
|
||||
/// </listheader>
|
||||
/// <item><term>0 – xxxxxx</term><description>Signed 24-bit ADC value (two’s complement)</description></item>
|
||||
/// <item><term>1 – aaaa</term><description>Unsigned 16-bit field strength (internal units)</description></item>
|
||||
/// <item><term>2 – yyyy</term><description>Signed 16-bit raw flow rate (¼ ml per bit)</description></item>
|
||||
/// <item><term>3 – vvvvvv</term><description>Unsigned 24-bit raw volume accumulation (¼ ml per bit)</description></item>
|
||||
/// <item><term>4 – cccc</term><description>Unsigned 16-bit millivolt delta on the field drive capacitor</description></item>
|
||||
/// <item><term>5 – gggggggg</term><description>Unsigned 32-bit volume displayed on the LCD</description></item>
|
||||
/// <item><term>6 – mm</term><description>Unsigned 8-bit meter state (see Table 17-23 in protocol documentation)</description></item>
|
||||
/// <item><term>7 – ff</term><description>Unsigned 8-bit boolean flag indicating low-flow cutoff
|
||||
/// state (0 = false, 1 = true)</description></item>
|
||||
/// <item><term>8 – ss</term><description>Unsigned 8-bit checksum (sum of all previous ASCII bytes including
|
||||
/// the TAB before the checksum field)</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class DiagnosticLedState2Data : DiagnosticLedData
|
||||
{
|
||||
/// <summary>
|
||||
/// Volume displayed on LCD (raw units).
|
||||
/// </summary>
|
||||
public uint LcdVolume { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Meter state (see Table 17-23).
|
||||
/// </summary>
|
||||
public byte MeterState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// True if meter is in low-flow cutoff.
|
||||
/// </summary>
|
||||
public bool IsLowFlowCutoff { get; }
|
||||
|
||||
public DiagnosticLedState2Data(string rawLine, string[] fields)
|
||||
: base(rawLine)
|
||||
{
|
||||
// ---- Common fields (0–4) ----
|
||||
Adc24 = DiagnosticHex.ParseInt24(fields[0]);
|
||||
FieldStrength = DiagnosticHex.ParseUInt16(fields[1]);
|
||||
RawFlow = DiagnosticHex.ParseInt16(fields[2]);
|
||||
RawVolume = DiagnosticHex.ParseUInt24(fields[3]);
|
||||
CapacitorMv = DiagnosticHex.ParseUInt16(fields[4]);
|
||||
|
||||
// ---- State #2 specific ----
|
||||
LcdVolume = DiagnosticHex.ParseUInt32(fields[5]);
|
||||
MeterState = DiagnosticHex.ParseByte(fields[6]);
|
||||
IsLowFlowCutoff = DiagnosticHex.ParseByte(fields[7]) != 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"DiagnosticLedState2Data: Adc24={Adc24}, FieldStrength={FieldStrength}, RawFlow={RawFlow}, RawVolume={RawVolume}, CapacitorMv={CapacitorMv}, LcdVolume={LcdVolume}, MeterState={MeterState}, IsLowFlowCutoff={IsLowFlowCutoff}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format: xxxxxx aaaa yyyy vvvvvv cccc gggggggg mm ff ss
|
||||
/// Chars total = 38
|
||||
/// Tabs = 8
|
||||
/// CRLF = 2
|
||||
/// Total bytes = 48
|
||||
/// </summary>
|
||||
/// <returns> Total bytes</returns>
|
||||
public override int GetByteCount()
|
||||
{
|
||||
return 48;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #3 data frame.
|
||||
///
|
||||
/// <para>
|
||||
/// State #3 extends the common diagnostic LED fields with calibration
|
||||
/// and timing information related to the field drive and ASIC operation.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Frame format: TAB-separated ASCII hexadecimal fields, terminated by CRLF.
|
||||
/// The checksum is an 8-bit sum of all previous ASCII bytes including
|
||||
/// the TAB character before the checksum field.
|
||||
/// </para>
|
||||
///
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Pos</term>
|
||||
/// <description>Field description</description>
|
||||
/// </listheader>
|
||||
/// <item><term>0 – xxxxxx</term><description>Signed 24-bit ADC value (two’s complement)</description></item>
|
||||
/// <item><term>1 – aaaa</term><description>Unsigned 16-bit field strength (internal units)</description></item>
|
||||
/// <item><term>2 – yyyy</term><description>Signed 16-bit raw flow rate (¼ ml per bit)</description></item>
|
||||
/// <item><term>3 – vvvvvv</term><description>Unsigned 24-bit raw volume accumulation (¼ ml per bit)</description></item>
|
||||
/// <item><term>4 – cccc</term><description>Unsigned 16-bit millivolt delta on the field drive capacitor</description></item>
|
||||
/// <item><term>5 – tttt</term><description>Unsigned 16-bit field calibration value</description></item>
|
||||
/// <item><term>6 – bbbbbbbb</term><description>Unsigned 32-bit ASIC timestamp (8192 ticks per second,
|
||||
/// rolls over at 2^32)</description></item>
|
||||
/// <item><term>7 – ff</term><description>Unsigned 8-bit field drive time in microseconds</description></item>
|
||||
/// <item><term>8 – ss</term><description>Unsigned 8-bit checksum (sum of all previous ASCII bytes including
|
||||
/// the TAB before the checksum field)</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class DiagnosticLedState3Data : DiagnosticLedData
|
||||
{
|
||||
/// <summary>
|
||||
/// Unsigned 16-bit field calibration value.
|
||||
/// </summary>
|
||||
public ushort FieldCalibration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// ASIC timestamp in units of 1 / 8192 seconds.
|
||||
/// Rolls over at 2^32.
|
||||
/// </summary>
|
||||
public uint AsicTimestamp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Field drive time in microseconds.
|
||||
/// </summary>
|
||||
public byte FieldDriveTimeUs { get; }
|
||||
|
||||
public DiagnosticLedState3Data(string rawLine, string[] fields)
|
||||
: base(rawLine)
|
||||
{
|
||||
// ---- Common fields (0–4) ----
|
||||
Adc24 = DiagnosticHex.ParseInt24(fields[0]);
|
||||
FieldStrength = DiagnosticHex.ParseUInt16(fields[1]);
|
||||
RawFlow = DiagnosticHex.ParseInt16(fields[2]);
|
||||
RawVolume = DiagnosticHex.ParseUInt24(fields[3]);
|
||||
CapacitorMv = DiagnosticHex.ParseUInt16(fields[4]);
|
||||
|
||||
// ---- State #3 specific fields ----
|
||||
FieldCalibration = DiagnosticHex.ParseUInt16(fields[5]);
|
||||
AsicTimestamp = DiagnosticHex.ParseUInt32(fields[6]);
|
||||
FieldDriveTimeUs = DiagnosticHex.ParseByte(fields[7]);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"DiagnosticLedState3Data: Adc24={Adc24}, FieldStrength={FieldStrength}, RawFlow={RawFlow}, RawVolume={RawVolume}, CapacitorMv={CapacitorMv}, FieldCalibration={FieldCalibration}, AsicTimestamp={AsicTimestamp}, FieldDriveTimeUs={FieldDriveTimeUs}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format: xxxxxx aaaa yyyy vvvvvv cccc tttt bbbbbbbb ff ss
|
||||
/// Chars total = 40
|
||||
/// Tabs = 8
|
||||
/// CRLF = 2
|
||||
/// Total bytes = 50
|
||||
/// </summary>
|
||||
/// <returns> Total bytes</returns>
|
||||
public override int GetByteCount()
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #4 data frame.
|
||||
/// <para>Frame format (TAB-separated ASCII HEX fields, CRLF terminated).</para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term># / Field</term>
|
||||
/// <description>Description</description>
|
||||
/// </listheader>
|
||||
/// <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>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>
|
||||
/// <item><term>7 – ff</term><description>unsigned 8-bit Field drive time (µs)</description></item>
|
||||
/// <item><term>8 – mmmmmmmm</term><description>signed 32-bit Mean flow rate</description></item>
|
||||
/// <item><term>9 – gggg</term><description>unsigned 16-bit Field 1 measurement</description></item>
|
||||
/// <item><term>10 – hhhh</term><description>unsigned 16-bit Field 2 measurement</description></item>
|
||||
/// <item><term>11 – cccc</term><description>unsigned 16-bit Integrator calibration positive</description></item>
|
||||
/// <item><term>12 – nnnn</term><description>unsigned 16-bit Integrator calibration negative</description></item>
|
||||
/// <item><term>13 – qq</term><description>unsigned 8-bit ASIC state</description></item>
|
||||
/// <item><term>14 – ss</term><description>unsigned 8-bit Checksum</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class DiagnosticLedState4Data : DiagnosticLedData
|
||||
{
|
||||
public ushort FieldCalibration { get; }
|
||||
public uint AsicTimestamp { get; }
|
||||
public byte FieldDriveTimeUs { get; }
|
||||
|
||||
public int MeanFlowRate { get; }
|
||||
|
||||
public ushort Field1Measurement { get; }
|
||||
public ushort Field2Measurement { get; }
|
||||
|
||||
public ushort IntegratorCalibrationPositive { get; }
|
||||
public ushort IntegratorCalibrationNegative { get; }
|
||||
|
||||
public byte AsicState { get; }
|
||||
|
||||
public DiagnosticLedState4Data(string rawLine, string[] fields)
|
||||
: base(rawLine)
|
||||
{
|
||||
// ---- Common fields (0–4) ----
|
||||
Adc24 = DiagnosticHex.ParseInt24(fields[0]);
|
||||
FieldStrength = DiagnosticHex.ParseUInt16(fields[1]);
|
||||
RawFlow = DiagnosticHex.ParseInt16(fields[2]);
|
||||
RawVolume = DiagnosticHex.ParseUInt24(fields[3]);
|
||||
CapacitorMv = DiagnosticHex.ParseUInt16(fields[4]);
|
||||
|
||||
// ---- State #4 specific ----
|
||||
FieldCalibration = DiagnosticHex.ParseUInt16(fields[5]);
|
||||
AsicTimestamp = DiagnosticHex.ParseUInt32(fields[6]);
|
||||
FieldDriveTimeUs = DiagnosticHex.ParseByte(fields[7]);
|
||||
|
||||
MeanFlowRate = unchecked((int)DiagnosticHex.ParseUInt32(fields[8]));
|
||||
|
||||
Field1Measurement = DiagnosticHex.ParseUInt16(fields[9]);
|
||||
Field2Measurement = DiagnosticHex.ParseUInt16(fields[10]);
|
||||
|
||||
IntegratorCalibrationPositive = DiagnosticHex.ParseUInt16(fields[11]);
|
||||
IntegratorCalibrationNegative = DiagnosticHex.ParseUInt16(fields[12]);
|
||||
|
||||
AsicState = DiagnosticHex.ParseByte(fields[13]);
|
||||
}
|
||||
|
||||
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}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format: xxxxxx aaaa yyyy vvvvvv cccc tttt bbbbbbbb mmmmmmmm ff gggg hhhh cccc nnnn qq ss
|
||||
/// Chars total = 68
|
||||
/// Tabs = 14
|
||||
/// CRLF = 2
|
||||
/// Total bytes = 84
|
||||
/// </summary>
|
||||
/// <returns> Total bytes</returns>
|
||||
public override int GetByteCount()
|
||||
{
|
||||
return 84;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #5 data frame.
|
||||
/// <para>
|
||||
/// Frame format: TAB-separated ASCII HEX fields, CRLF terminated.
|
||||
/// </para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term># / Field</term>
|
||||
/// <description>Description</description>
|
||||
/// </listheader>
|
||||
/// <item><term>0 – xxxxxx</term><description>signed 24-bit ADC value</description></item>
|
||||
/// <item><term>1 – aaaa</term><description>unsigned 16-bit Field strength (internal units)</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 (1/4 ml per bit)</description></item>
|
||||
/// <item><term>4 – cccc</term><description>unsigned 16-bit Millivolts delta on field drive capacitor</description></item>
|
||||
/// <item><term>5 – tttt</term><description>unsigned 16-bit Field calibration value</description></item>
|
||||
/// <item><term>6 – bbbbbbbb</term><description>unsigned 32-bit ASIC timestamp (8192 ticks/sec, rolls over at 2^32)</description></item>
|
||||
/// <item><term>7 – ff</term><description>unsigned 8-bit Field drive time in microseconds</description></item>
|
||||
/// <item><term>8 – mmmmmmmm</term><description>signed 32-bit Mean flow rate (rolls over at 2^32)</description></item>
|
||||
/// <item><term>9 – gggg</term><description>unsigned 16-bit Field 1 measurement</description></item>
|
||||
/// <item><term>10 – hhhh</term><description>unsigned 16-bit Field 2 measurement</description></item>
|
||||
/// <item><term>11 – cccc</term><description>unsigned 16-bit Integrator calibration positive</description></item>
|
||||
/// <item><term>12 – nnnn</term><description>unsigned 16-bit Integrator calibration negative</description></item>
|
||||
/// <item><term>13 – qq</term><description>unsigned 8-bit ASIC state</description></item>
|
||||
/// <item><term>14 – iiii</term><description>signed 16-bit Water impedance measurement</description></item>
|
||||
/// <item><term>15 – ss</term><description>unsigned 8-bit Checksum</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class DiagnosticLedState5Data : DiagnosticLedData
|
||||
{
|
||||
/// <summary>Field calibration value (tttt).</summary>
|
||||
public ushort FieldCalibration { get; }
|
||||
|
||||
/// <summary>ASIC timestamp (bbbbbbbb), 8192 ticks per second.</summary>
|
||||
public uint AsicTimestamp { get; }
|
||||
|
||||
/// <summary>Field drive time in microseconds (ff).</summary>
|
||||
public byte FieldDriveTimeUs { get; }
|
||||
|
||||
/// <summary>Mean flow rate (mmmmmmmm), signed 32-bit.</summary>
|
||||
public int MeanFlowRate { get; }
|
||||
|
||||
/// <summary>Field 1 measurement (gggg).</summary>
|
||||
public ushort Field1Measurement { get; }
|
||||
|
||||
/// <summary>Field 2 measurement (hhhh).</summary>
|
||||
public ushort Field2Measurement { get; }
|
||||
|
||||
/// <summary>Integrator calibration positive (cccc).</summary>
|
||||
public ushort IntegratorCalibrationPositive { get; }
|
||||
|
||||
/// <summary>Integrator calibration negative (nnnn).</summary>
|
||||
public ushort IntegratorCalibrationNegative { get; }
|
||||
|
||||
/// <summary>ASIC state (qq).</summary>
|
||||
public byte AsicState { get; }
|
||||
|
||||
/// <summary>Water impedance measurement (iiii), signed 16-bit.</summary>
|
||||
public short WaterImpedance { get; }
|
||||
|
||||
public DiagnosticLedState5Data(string rawLine, string[] fields)
|
||||
: base(rawLine)
|
||||
{
|
||||
// ---- Common fields ----
|
||||
Adc24 = DiagnosticHex.ParseInt24(fields[0]);
|
||||
FieldStrength = DiagnosticHex.ParseUInt16(fields[1]);
|
||||
RawFlow = DiagnosticHex.ParseInt16(fields[2]);
|
||||
RawVolume = DiagnosticHex.ParseUInt24(fields[3]);
|
||||
CapacitorMv = DiagnosticHex.ParseUInt16(fields[4]);
|
||||
|
||||
// ---- State #5 specific ----
|
||||
FieldCalibration = DiagnosticHex.ParseUInt16(fields[5]);
|
||||
AsicTimestamp = DiagnosticHex.ParseUInt32(fields[6]);
|
||||
FieldDriveTimeUs = DiagnosticHex.ParseByte(fields[7]);
|
||||
|
||||
MeanFlowRate = unchecked((int)DiagnosticHex.ParseUInt32(fields[8]));
|
||||
|
||||
Field1Measurement = DiagnosticHex.ParseUInt16(fields[9]);
|
||||
Field2Measurement = DiagnosticHex.ParseUInt16(fields[10]);
|
||||
|
||||
IntegratorCalibrationPositive = DiagnosticHex.ParseUInt16(fields[11]);
|
||||
IntegratorCalibrationNegative = DiagnosticHex.ParseUInt16(fields[12]);
|
||||
|
||||
AsicState = DiagnosticHex.ParseByte(fields[13]);
|
||||
WaterImpedance = DiagnosticHex.ParseInt16(fields[14]);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"DiagnosticLedState5Data: 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}, WaterImpedance={WaterImpedance}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format: xxxxxx aaaa yyyy vvvvvv cccc tttt bbbbbbbb mmmmmmmm ff iiii ss
|
||||
/// Chars total = 72
|
||||
/// Tabs = 15
|
||||
/// CRLF = 2
|
||||
/// Total bytes = 89
|
||||
/// </summary>
|
||||
/// <returns> Total bytes</returns>
|
||||
public override int GetByteCount()
|
||||
{
|
||||
return 89;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #6 data frame.
|
||||
/// <para>
|
||||
/// Frame format: TAB-separated ASCII HEX fields, CRLF terminated.
|
||||
/// </para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term># / Field</term>
|
||||
/// <description>Description</description>
|
||||
/// </listheader>
|
||||
/// <item><term>0 – xxxxxx</term><description>signed 24-bit ADC value</description></item>
|
||||
/// <item><term>1 – aaaa</term><description>unsigned 16-bit Field strength (internal units)</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 (1/4 ml per bit)</description></item>
|
||||
/// <item><term>4 – cccc</term><description>unsigned 16-bit Millivolts delta on field drive capacitor</description></item>
|
||||
/// <item><term>5 – tttt</term><description>unsigned 16-bit Field calibration value</description></item>
|
||||
/// <item><term>6 – bbbbbbbb</term><description>unsigned 32-bit ASIC timestamp (8192 ticks/sec, rolls over at 2^32)</description></item>
|
||||
/// <item><term>7 – ff</term><description>unsigned 8-bit Field drive time in microseconds</description></item>
|
||||
/// <item><term>8 – mmmmmmmm</term><description>signed 32-bit Mean flow rate (rolls over at 2^32)</description></item>
|
||||
/// <item><term>9 – gggg</term><description>unsigned 16-bit Field 1 measurement</description></item>
|
||||
/// <item><term>10 – hhhh</term><description>unsigned 16-bit Field 2 measurement</description></item>
|
||||
/// <item><term>11 – cccc</term><description>unsigned 16-bit Integrator calibration positive</description></item>
|
||||
/// <item><term>12 – nnnn</term><description>unsigned 16-bit Integrator calibration negative</description></item>
|
||||
/// <item><term>13 – qq</term><description>unsigned 8-bit ASIC state 0</description></item>
|
||||
/// <item><term>14 – iiii</term><description>signed 16-bit Water impedance measurement</description></item>
|
||||
/// <item><term>15 – rrrr</term><description>signed 16-bit Electrode delta (mV)</description></item>
|
||||
/// <item><term>16 – pp</term><description>unsigned 8-bit Spike detection diagnostic</description></item>
|
||||
/// <item><term>17 – ll</term><description>unsigned 8-bit Pipe status</description></item>
|
||||
/// <item><term>18 – dddddddd</term><description>unsigned 32-bit LCD volume</description></item>
|
||||
/// <item><term>19 – oo</term><description>unsigned 8-bit ASIC state 1</description></item>
|
||||
/// <item><term>20 – ss</term><description>unsigned 8-bit Checksum</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class DiagnosticLedState6Data : DiagnosticLedData
|
||||
{
|
||||
public ushort FieldCalibration { get; }
|
||||
public uint AsicTimestamp { get; }
|
||||
public byte FieldDriveTimeUs { get; }
|
||||
|
||||
public int MeanFlowRate { get; }
|
||||
|
||||
public ushort Field1Measurement { get; }
|
||||
public ushort Field2Measurement { get; }
|
||||
|
||||
public ushort IntegratorCalibrationPositive { get; }
|
||||
public ushort IntegratorCalibrationNegative { get; }
|
||||
|
||||
public byte AsicState0 { get; }
|
||||
|
||||
public short WaterImpedance { get; }
|
||||
public short ElectrodeDeltaMv { get; }
|
||||
|
||||
public byte SpikeDetection { get; }
|
||||
public byte PipeStatus { get; }
|
||||
|
||||
public uint LcdVolume { get; }
|
||||
|
||||
public byte AsicState1 { get; }
|
||||
|
||||
public DiagnosticLedState6Data(string rawLine, string[] fields)
|
||||
: base(rawLine)
|
||||
{
|
||||
// ---- Common fields (0–4) ----
|
||||
Adc24 = DiagnosticHex.ParseInt24(fields[0]);
|
||||
FieldStrength = DiagnosticHex.ParseUInt16(fields[1]);
|
||||
RawFlow = DiagnosticHex.ParseInt16(fields[2]);
|
||||
RawVolume = DiagnosticHex.ParseUInt24(fields[3]);
|
||||
CapacitorMv = DiagnosticHex.ParseUInt16(fields[4]);
|
||||
|
||||
// ---- State #6 specific ----
|
||||
FieldCalibration = DiagnosticHex.ParseUInt16(fields[5]);
|
||||
AsicTimestamp = DiagnosticHex.ParseUInt32(fields[6]);
|
||||
FieldDriveTimeUs = DiagnosticHex.ParseByte(fields[7]);
|
||||
|
||||
MeanFlowRate = unchecked((int)DiagnosticHex.ParseUInt32(fields[8]));
|
||||
|
||||
Field1Measurement = DiagnosticHex.ParseUInt16(fields[9]);
|
||||
Field2Measurement = DiagnosticHex.ParseUInt16(fields[10]);
|
||||
|
||||
IntegratorCalibrationPositive = DiagnosticHex.ParseUInt16(fields[11]);
|
||||
IntegratorCalibrationNegative = DiagnosticHex.ParseUInt16(fields[12]);
|
||||
|
||||
AsicState0 = DiagnosticHex.ParseByte(fields[13]);
|
||||
|
||||
WaterImpedance = DiagnosticHex.ParseInt16(fields[14]);
|
||||
ElectrodeDeltaMv = DiagnosticHex.ParseInt16(fields[15]);
|
||||
|
||||
SpikeDetection = DiagnosticHex.ParseByte(fields[16]);
|
||||
PipeStatus = DiagnosticHex.ParseByte(fields[17]);
|
||||
|
||||
LcdVolume = DiagnosticHex.ParseUInt32(fields[18]);
|
||||
AsicState1 = DiagnosticHex.ParseByte(fields[19]);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Pipe status interpreted as <see cref="PipeStatus"/>.
|
||||
/// If the value is outside the defined range, returns null.
|
||||
/// </summary>
|
||||
public PipeStatus PipeStatusEnumValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(PipeStatus), PipeStatus))
|
||||
throw new InvalidOperationException(
|
||||
"Unknown pipe status value: 0x" + PipeStatus.ToString("X2"));
|
||||
|
||||
return (PipeStatus)PipeStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spike Detection interpreted as <see cref="SpikeDetectionStatus"/>.
|
||||
/// If the value is outside the defined range, returns null.
|
||||
/// </summary>
|
||||
public SpikeDetectionStatus SpikeDetectionEnumValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(SpikeDetectionStatus), SpikeDetection))
|
||||
throw new InvalidOperationException(
|
||||
"Unknown Spike Detection value: 0x" + SpikeDetection.ToString("X2"));
|
||||
|
||||
return (SpikeDetectionStatus)SpikeDetection;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"DiagnosticLedState6Data: 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}, AsicState0={AsicState0}, WaterImpedance={WaterImpedance}, SpikeDetection={SpikeDetection}, PipeStatus={PipeStatus}, LcdVolume={LcdVolume}, AsicState1={AsicState1}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format: xxxxxx aaaa yyyy vvvvvv cccc tttt bbbbbbbb mmmmmmmm ff iiii rrrr pp ll dddddddd oo ss
|
||||
/// Chars total = 90
|
||||
/// Tabs = 20
|
||||
/// CRLF = 2
|
||||
/// Total bytes = 112
|
||||
/// </summary>
|
||||
/// <returns> Total bytes</returns>
|
||||
public override int GetByteCount()
|
||||
{
|
||||
return 112;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,155 @@
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
/// <summary>
|
||||
/// Diagnostic LED State #7 data frame.
|
||||
/// <para>
|
||||
/// Frame format: TAB-separated ASCII HEX fields, CRLF terminated.
|
||||
/// This state extends State #6 with additional ADC and learning diagnostics.
|
||||
/// </para>
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term># / Field</term>
|
||||
/// <description>Description</description>
|
||||
/// </listheader>
|
||||
/// <item><term>0 – xxxxxx</term><description>signed 24-bit ADC value</description></item>
|
||||
/// <item><term>1 – aaaa</term><description>unsigned 16-bit Field strength (internal units)</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 (1/4 ml per bit)</description></item>
|
||||
/// <item><term>4 – cccc</term><description>unsigned 16-bit Millivolts delta on field drive capacitor</description></item>
|
||||
/// <item><term>5 – tttt</term><description>unsigned 16-bit Field calibration value</description></item>
|
||||
/// <item><term>6 – bbbbbbbb</term><description>unsigned 32-bit ASIC timestamp (8192 ticks/sec)</description></item>
|
||||
/// <item><term>7 – ff</term><description>unsigned 8-bit Field drive time (µs)</description></item>
|
||||
/// <item><term>8 – mmmmmmmm</term><description>signed 32-bit Mean flow rate</description></item>
|
||||
/// <item><term>9 – gggg</term><description>unsigned 16-bit Field 1 measurement</description></item>
|
||||
/// <item><term>10 – hhhh</term><description>unsigned 16-bit Field 2 measurement</description></item>
|
||||
/// <item><term>11 – cccc</term><description>unsigned 16-bit Integrator calibration positive</description></item>
|
||||
/// <item><term>12 – nnnn</term><description>unsigned 16-bit Integrator calibration negative</description></item>
|
||||
/// <item><term>13 – qq</term><description>unsigned 8-bit ASIC state 0</description></item>
|
||||
/// <item><term>14 – iiii</term><description>signed 16-bit Water impedance measurement</description></item>
|
||||
/// <item><term>15 – rrrr</term><description>signed 16-bit Electrode delta (mV)</description></item>
|
||||
/// <item><term>16 – pp</term><description>unsigned 8-bit Spike detection diagnostic</description></item>
|
||||
/// <item><term>17 – ll</term><description>unsigned 8-bit Pipe status</description></item>
|
||||
/// <item><term>18 – dddddddd</term><description>unsigned 32-bit LCD volume</description></item>
|
||||
/// <item><term>19 – oo</term><description>unsigned 8-bit ASIC state 1</description></item>
|
||||
/// <item><term>20 – xxxxxx</term><description>signed 24-bit Raw ADC value (before offset correction)</description></item>
|
||||
/// <item><term>21 – yyyyyy</term><description>signed 24-bit Detrended ADC value</description></item>
|
||||
/// <item><term>22 – iiii</term><description>signed 16-bit Imaginary water impedance</description></item>
|
||||
/// <item><term>23 – nnnn</term><description>unsigned 16-bit Electrode voltage noise level</description></item>
|
||||
/// <item><term>24 – aa</term><description>unsigned 8-bit ADC offset learning status</description></item>
|
||||
/// <item><term>25 – ss</term><description>unsigned 8-bit Checksum</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class DiagnosticLedState7Data : DiagnosticLedData
|
||||
{
|
||||
// ----- State #6 fields -----
|
||||
|
||||
public ushort FieldCalibration { get; }
|
||||
public uint AsicTimestamp { get; }
|
||||
public byte FieldDriveTimeUs { get; }
|
||||
|
||||
public int MeanFlowRate { get; }
|
||||
|
||||
public ushort Field1Measurement { get; }
|
||||
public ushort Field2Measurement { get; }
|
||||
|
||||
public ushort IntegratorCalibrationPositive { get; }
|
||||
public ushort IntegratorCalibrationNegative { get; }
|
||||
|
||||
public byte AsicState0 { get; }
|
||||
|
||||
public short WaterImpedance { get; }
|
||||
public short ElectrodeDeltaMv { get; }
|
||||
|
||||
public byte SpikeDetection { get; }
|
||||
public byte PipeStatus { get; }
|
||||
|
||||
public uint LcdVolume { get; }
|
||||
|
||||
public byte AsicState1 { get; }
|
||||
|
||||
// ----- State #7 extensions -----
|
||||
|
||||
/// <summary>Raw ADC value before offset correction (signed 24-bit).</summary>
|
||||
public int RawAdcBeforeOffset { get; }
|
||||
|
||||
/// <summary>Detrended ADC value (signed 24-bit).</summary>
|
||||
public int DetrendedAdc { get; }
|
||||
|
||||
/// <summary>Imaginary water impedance (signed 16-bit).</summary>
|
||||
public short ImaginaryWaterImpedance { get; }
|
||||
|
||||
/// <summary>Electrode voltage noise level (unsigned 16-bit).</summary>
|
||||
public ushort ElectrodeVoltageNoise { get; }
|
||||
|
||||
/// <summary>
|
||||
/// ADC offset learning status bitfield.
|
||||
/// Bit 0: currently learning
|
||||
/// Bit 1: completed first learning cycle
|
||||
/// Other bits reserved.
|
||||
/// </summary>
|
||||
public byte AdcOffsetLearningStatus { get; }
|
||||
|
||||
public DiagnosticLedState7Data(string rawLine, string[] fields)
|
||||
: base(rawLine)
|
||||
{
|
||||
// ---- Common fields (0–4) ----
|
||||
Adc24 = DiagnosticHex.ParseInt24(fields[0]);
|
||||
FieldStrength = DiagnosticHex.ParseUInt16(fields[1]);
|
||||
RawFlow = DiagnosticHex.ParseInt16(fields[2]);
|
||||
RawVolume = DiagnosticHex.ParseUInt24(fields[3]);
|
||||
CapacitorMv = DiagnosticHex.ParseUInt16(fields[4]);
|
||||
|
||||
// ---- State #6 fields ----
|
||||
FieldCalibration = DiagnosticHex.ParseUInt16(fields[5]);
|
||||
AsicTimestamp = DiagnosticHex.ParseUInt32(fields[6]);
|
||||
FieldDriveTimeUs = DiagnosticHex.ParseByte(fields[7]);
|
||||
|
||||
MeanFlowRate = unchecked((int)DiagnosticHex.ParseUInt32(fields[8]));
|
||||
|
||||
Field1Measurement = DiagnosticHex.ParseUInt16(fields[9]);
|
||||
Field2Measurement = DiagnosticHex.ParseUInt16(fields[10]);
|
||||
|
||||
IntegratorCalibrationPositive = DiagnosticHex.ParseUInt16(fields[11]);
|
||||
IntegratorCalibrationNegative = DiagnosticHex.ParseUInt16(fields[12]);
|
||||
|
||||
AsicState0 = DiagnosticHex.ParseByte(fields[13]);
|
||||
|
||||
WaterImpedance = DiagnosticHex.ParseInt16(fields[14]);
|
||||
ElectrodeDeltaMv = DiagnosticHex.ParseInt16(fields[15]);
|
||||
|
||||
SpikeDetection = DiagnosticHex.ParseByte(fields[16]);
|
||||
PipeStatus = DiagnosticHex.ParseByte(fields[17]);
|
||||
|
||||
LcdVolume = DiagnosticHex.ParseUInt32(fields[18]);
|
||||
AsicState1 = DiagnosticHex.ParseByte(fields[19]);
|
||||
|
||||
// ---- State #7 extensions ----
|
||||
RawAdcBeforeOffset = DiagnosticHex.ParseInt24(fields[20]);
|
||||
DetrendedAdc = DiagnosticHex.ParseInt24(fields[21]);
|
||||
ImaginaryWaterImpedance = DiagnosticHex.ParseInt16(fields[22]);
|
||||
ElectrodeVoltageNoise = DiagnosticHex.ParseUInt16(fields[23]);
|
||||
AdcOffsetLearningStatus = DiagnosticHex.ParseByte(fields[24]);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"DiagnosticLedState7Data: 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}, AsicState0={AsicState0}, WaterImpedance={WaterImpedance}, ElectrodeDeltaMv={ElectrodeDeltaMv}, SpikeDetection={SpikeDetection}, PipeStatus={PipeStatus}, LcdVolume={LcdVolume}, AsicState1={AsicState1}, RawAdcBeforeOffset={RawAdcBeforeOffset}, DetrendedAdc={DetrendedAdc}, ImaginaryWaterImpedance={ImaginaryWaterImpedance}, ElectrodeVoltageNoise={ElectrodeVoltageNoise}, AdcOffsetLearningStatus={AdcOffsetLearningStatus}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format:
|
||||
/// Chars total = 112
|
||||
/// Tabs = 25
|
||||
/// CRLF = 2
|
||||
/// Total bytes = 139
|
||||
/// </summary>
|
||||
/// <returns> Total bytes</returns>
|
||||
public override int GetByteCount()
|
||||
{
|
||||
return 139;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
public enum PipeStatus : byte
|
||||
{
|
||||
MetroLowFlowCut = 0,
|
||||
MetroFlowReverse = 1,
|
||||
MetroFlowForward = 2,
|
||||
MetroEmptyPipe = 3
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
public enum SpikeDetectionStatus : byte
|
||||
{
|
||||
NoSpike = 0,
|
||||
AdcSpike = 1,
|
||||
SpikeHoldOff = 2,
|
||||
SpikeHighFlow = 5
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.utils
|
||||
{
|
||||
internal static class DiagnosticChecksum
|
||||
{
|
||||
public static byte Compute(string lineWithoutChecksum)
|
||||
{
|
||||
byte sum = 0;
|
||||
foreach (char c in lineWithoutChecksum)
|
||||
sum += (byte)c;
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.utils
|
||||
{
|
||||
internal static class DiagnosticHex
|
||||
{
|
||||
public static int ParseInt24(string hex)
|
||||
{
|
||||
int value = Convert.ToInt32(hex, 16);
|
||||
if ((value & 0x800000) != 0)
|
||||
value |= unchecked((int)0xFF000000); // sign extend
|
||||
return value;
|
||||
}
|
||||
|
||||
public static uint ParseUInt24(string hex)
|
||||
{
|
||||
return Convert.ToUInt32(hex, 16);
|
||||
}
|
||||
|
||||
public static short ParseInt16(string hex)
|
||||
{
|
||||
return unchecked((short)Convert.ToUInt16(hex, 16));
|
||||
}
|
||||
|
||||
public static ushort ParseUInt16(string hex)
|
||||
{
|
||||
return Convert.ToUInt16(hex, 16);
|
||||
}
|
||||
|
||||
public static uint ParseUInt32(string hex)
|
||||
{
|
||||
return Convert.ToUInt32(hex, 16);
|
||||
}
|
||||
|
||||
public static byte ParseByte(string hex)
|
||||
{
|
||||
return Convert.ToByte(hex, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger
|
||||
{
|
||||
public static class HexFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// Formats a single byte as 0xNN.
|
||||
/// Example: 0x0D
|
||||
/// </summary>
|
||||
public static string ToHex(byte value)
|
||||
{
|
||||
return "0x" + value.ToString("X2");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// int to byte securely
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public static byte ToHexByte(int value)
|
||||
{
|
||||
if (value < 0 || value > 255)
|
||||
throw new ArgumentOutOfRangeException(nameof(value),
|
||||
"Value must be between 0 and 255.");
|
||||
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a byte array as 0xNN 0xNN ...
|
||||
/// </summary>
|
||||
public static string ToHex(byte[] data)
|
||||
{
|
||||
if (data == null || data.Length == 0)
|
||||
return "<empty>";
|
||||
|
||||
var sb = new System.Text.StringBuilder();
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
sb.Append(' ');
|
||||
|
||||
sb.Append("0x");
|
||||
sb.Append(data[i].ToString("X2"));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a byte array exactly as shown in serial terminals.
|
||||
/// Example: "0D 04 08 01 00 1A"
|
||||
/// </summary>
|
||||
public static string ToSerialHex(byte[] data)
|
||||
{
|
||||
if (data == null || data.Length == 0)
|
||||
return string.Empty;
|
||||
|
||||
var sb = new System.Text.StringBuilder();
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
sb.Append(' ');
|
||||
|
||||
sb.Append(data[i].ToString("X2"));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
public static string ToHexWithAscii(byte value)
|
||||
{
|
||||
char c = (value >= 32 && value <= 126) ? (char)value : '.';
|
||||
return $"0x{value:X2} ('{c}')";
|
||||
}
|
||||
|
||||
public static string ToSerialHexWithAscii(byte[] data)
|
||||
{
|
||||
if (data == null || data.Length == 0)
|
||||
return string.Empty;
|
||||
|
||||
var hex = new StringBuilder(data.Length * 3);
|
||||
var ascii = new StringBuilder(data.Length);
|
||||
|
||||
foreach (byte b in data)
|
||||
{
|
||||
hex.Append(b.ToString("X2")).Append(' ');
|
||||
|
||||
// Printable ASCII range
|
||||
if (b >= 32 && b <= 126)
|
||||
{
|
||||
ascii.Append((char)b);
|
||||
}
|
||||
// Binary numbers 0–9 -> show digit
|
||||
else if (b <= 9)
|
||||
{
|
||||
ascii.Append((char)('0' + b));
|
||||
}
|
||||
else
|
||||
{
|
||||
ascii.Append('.');
|
||||
}
|
||||
}
|
||||
|
||||
// remove last trailing space in hex
|
||||
if (hex.Length > 0)
|
||||
hex.Length--;
|
||||
|
||||
return $"{hex} | {ascii}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static string ToHex(int value)
|
||||
{
|
||||
return $"0x{(byte)value:X2}";
|
||||
}
|
||||
|
||||
public static byte[] IntToBytesBE(int value, int byteCount)
|
||||
{
|
||||
var result = new byte[byteCount];
|
||||
|
||||
for (int i = 0; i < byteCount; i++)
|
||||
result[byteCount - 1 - i] = (byte)(value >> (8 * i));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] IntToBytesLE(int value, int byteCount)
|
||||
{
|
||||
var result = new byte[byteCount];
|
||||
|
||||
for (int i = 0; i < byteCount; i++)
|
||||
result[i] = (byte)(value >> (8 * i));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] AsciiToBytes(string text)
|
||||
{
|
||||
return string.IsNullOrEmpty(text)
|
||||
? Array.Empty<byte>()
|
||||
: System.Text.Encoding.ASCII.GetBytes(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a hex string to a byte array.
|
||||
/// Like: string hex = "3F 76 65 72 73 3A 20 48 61 72 72 79 20 54 3A 42 38 30 30 2C 20";
|
||||
/// </summary>
|
||||
/// <param name="hex"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static byte[] HexStringToByteArray(string hex)
|
||||
{
|
||||
if (hex == null)
|
||||
throw new ArgumentNullException(nameof(hex));
|
||||
|
||||
return hex
|
||||
.Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(b => byte.Parse(b, NumberStyles.HexNumber, CultureInfo.InvariantCulture))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger
|
||||
{
|
||||
public class IpelHatCommandDecoder
|
||||
{
|
||||
public static string DescribeCommand(byte command)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
public static string DescribeDirection(byte direction)
|
||||
{
|
||||
if (direction == IperlHatProtocol.Constants.Write)
|
||||
return "(WRITE - OUTGOING)";
|
||||
|
||||
if (direction == IperlHatProtocol.Constants.Read)
|
||||
return "(READ - INCOMING)";
|
||||
|
||||
return "INVALID CONTROL BITS (unsupported pattern)";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.wiredProtocol;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger
|
||||
{
|
||||
public static class IperlHatLogger
|
||||
{
|
||||
public static string DescribeTx(byte[] frame)
|
||||
{
|
||||
if (frame == null || frame.Length < 5)
|
||||
return "Invalid frame";
|
||||
|
||||
if (frame[2] == IperlHatProtocol.Constants.Question)
|
||||
{
|
||||
return
|
||||
"TX Frame\n" +
|
||||
$" START : {HexFormatter.ToHex(frame[0])}\n" +
|
||||
$" DIRECTION : {HexFormatter.ToHex(frame[1])} ({IpelHatCommandDecoder.DescribeDirection(frame[1])})\n" +
|
||||
$" COMMAND : {HexFormatter.ToHexWithAscii(frame[2])}\n" +
|
||||
$" INFO : {HexFormatter.ToSerialHexWithAscii(GetInformatioQuestion(frame))}\n" +
|
||||
$" END : {HexFormatter.ToHex(frame[frame.Length - 1])}\n" +
|
||||
$" RAW : {HexFormatter.ToHex(frame)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return
|
||||
"TX Frame\n" +
|
||||
$" START : {HexFormatter.ToHex(frame[0])}\n" +
|
||||
$" DIRECTION : {HexFormatter.ToHex(frame[1])} ({HexFormatter.ToHexWithAscii(frame[1])}) {IpelHatCommandDecoder.DescribeDirection(frame[1])}\n" +
|
||||
$" LEN : {HexFormatter.ToHex(frame[2])} - {(int)frame[2]}\n" +
|
||||
$" COMMAND : {HexFormatter.ToHexWithAscii(frame[3])}\n" +
|
||||
$" INFO : {HexFormatter.ToSerialHexWithAscii(GetInformation(frame))}\n" +
|
||||
$" END : {HexFormatter.ToHex(frame[frame.Length - 1])}\n" +
|
||||
$" RAW : {HexFormatter.ToHex(frame)}";
|
||||
}
|
||||
}
|
||||
|
||||
//payload
|
||||
private static byte[] GetInformation(byte[] frame)
|
||||
{
|
||||
int infoLength = frame.Length - 5; // START + DIRECTION + LEN + COMMAND + END
|
||||
if (infoLength <= 0)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
var info = new byte[infoLength];
|
||||
Buffer.BlockCopy(frame, 4, info, 0, infoLength);
|
||||
return info;
|
||||
}
|
||||
|
||||
//payload for question
|
||||
private static byte[] GetInformatioQuestion(byte[] frame)
|
||||
{
|
||||
int infoLength = frame.Length - 4; // START + DIRECTION + COMMAND + END
|
||||
if (infoLength <= 0)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
var info = new byte[infoLength];
|
||||
Buffer.BlockCopy(frame, 3, info, 0, infoLength);
|
||||
return info;
|
||||
}
|
||||
|
||||
public static string DescribeRx(byte[] frame, TouchReadResponse response)
|
||||
{
|
||||
return
|
||||
"RX Frame\n" +
|
||||
$" START : {HexFormatter.ToHex(frame[0])}\n" +
|
||||
$" LEN : {HexFormatter.ToHex(frame[1])} ({frame[1]})\n" +
|
||||
$" CONTROL : {HexFormatter.ToHex(response.Control)}\n" +
|
||||
$" STATUS : {HexFormatter.ToHex(response.Status)} ({DescribeStatus(response.Status)})\n" +
|
||||
$" PAYLOAD : {HexFormatter.ToHex(response.Payload)}\n" +
|
||||
$" RAW : {HexFormatter.ToHex(frame)}";
|
||||
}
|
||||
|
||||
private static string DescribeStatus(byte status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case 0x01: return "Command complete, no errors";
|
||||
case 0x02: return "Unable to execute";
|
||||
case 0x04: return "Unsupported control bits";
|
||||
default: return "Unknown status";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger
|
||||
{
|
||||
public static class TouchReadControlDecoder
|
||||
{
|
||||
public static string Describe(byte control)
|
||||
{
|
||||
if (control == 0x00)
|
||||
return "RF=0 (No response expected)";
|
||||
|
||||
if (control == 0x08)
|
||||
return "RF=1 (Response expected)";
|
||||
|
||||
return "INVALID CONTROL BITS (unsupported pattern)";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.wiredProtocol;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger
|
||||
{
|
||||
public static class TouchReadLogger
|
||||
{
|
||||
public static string DescribeTx(byte[] frame)
|
||||
{
|
||||
if (frame == null || frame.Length < 6)
|
||||
return "Invalid frame";
|
||||
|
||||
return
|
||||
"TX Frame\n" +
|
||||
$" START : {HexFormatter.ToHex(frame[0])}\n" +
|
||||
$" LEN : {HexFormatter.ToHex(frame[1])} ({frame[1]})\n" +
|
||||
$" CONTROL : {HexFormatter.ToHex(frame[2])} - {TouchReadControlDecoder.Describe(frame[2])}\n" +
|
||||
$" INFO : {HexFormatter.ToHex(GetInformation(frame))}\n" +
|
||||
$" CHECKSUM: {HexFormatter.ToHex(frame[frame.Length - 2])} {HexFormatter.ToHex(frame[frame.Length - 1])}\n" +
|
||||
$" RAW : {HexFormatter.ToHex(frame)}";
|
||||
}
|
||||
|
||||
private static byte[] GetInformation(byte[] frame)
|
||||
{
|
||||
int infoLength = frame.Length - 5; // CTRL + INFO + CHK(2)
|
||||
if (infoLength <= 0)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
var info = new byte[infoLength];
|
||||
Buffer.BlockCopy(frame, 3, info, 0, infoLength);
|
||||
return info;
|
||||
}
|
||||
|
||||
public static string DescribeRx(byte[] frame, TouchReadResponse response)
|
||||
{
|
||||
return
|
||||
"RX Frame\n" +
|
||||
$" START : {HexFormatter.ToHex(frame[0])}\n" +
|
||||
$" LEN : {HexFormatter.ToHex(frame[1])} ({frame[1]})\n" +
|
||||
$" CONTROL : {HexFormatter.ToHex(response.Control)}\n" +
|
||||
$" STATUS : {HexFormatter.ToHex(response.Status)} ({DescribeStatus(response.Status)})\n" +
|
||||
$" PAYLOAD : {HexFormatter.ToHex(response.Payload)}\n" +
|
||||
$" RAW : {HexFormatter.ToHex(frame)}";
|
||||
}
|
||||
|
||||
private static string DescribeStatus(byte status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case 0x01: return "Command complete, no errors";
|
||||
case 0x02: return "Unable to execute";
|
||||
case 0x04: return "Unsupported control bits";
|
||||
default: return "Unknown status";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.led
|
||||
{
|
||||
public interface ITouchReadLedParser
|
||||
{
|
||||
TouchReadLedData Parse(TouchReadLedMessage message);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.led
|
||||
{
|
||||
public class ShortVariableLedParser : ITouchReadLedParser
|
||||
{
|
||||
public TouchReadLedData Parse(TouchReadLedMessage msg)
|
||||
{
|
||||
return new TouchReadLedData(msg.Raw)
|
||||
{
|
||||
MeterId = msg.Fields[0],
|
||||
Reading = decimal.Parse(msg.Fields[1],
|
||||
CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.led
|
||||
{
|
||||
/// <summary>
|
||||
/// Parsed data from a unidirectional TouchRead LED message.
|
||||
/// The exact populated fields depend on the configured reading mode.
|
||||
/// </summary>
|
||||
public sealed class TouchReadLedData
|
||||
{
|
||||
/// <summary>
|
||||
/// Raw LED message including delimiters.
|
||||
/// Example: ";12345678,00012345.67,m3;"
|
||||
/// </summary>
|
||||
public string Raw { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Meter factory ID or serial number (if present).
|
||||
/// </summary>
|
||||
public string MeterId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Customer programmable ID (if present).
|
||||
/// </summary>
|
||||
public string CustomerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parsed meter reading value.
|
||||
/// </summary>
|
||||
public decimal? Reading { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Engineering units (e.g. "m3", "ft3", "gal").
|
||||
/// </summary>
|
||||
public string Units { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional alarm/status field (bitfield or text).
|
||||
/// </summary>
|
||||
public string AlarmStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp when the LED data was received.
|
||||
/// </summary>
|
||||
public DateTime Timestamp { get; }
|
||||
|
||||
public TouchReadLedData(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
throw new ArgumentException("Raw LED data must not be null or empty.", nameof(raw));
|
||||
|
||||
Raw = raw;
|
||||
Timestamp = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to safely parse a decimal value using invariant culture.
|
||||
/// </summary>
|
||||
public static decimal? ParseDecimal(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return null;
|
||||
|
||||
if (decimal.TryParse(
|
||||
value,
|
||||
NumberStyles.Number,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.led
|
||||
{
|
||||
public class TouchReadLedMessage
|
||||
{
|
||||
public string Raw { get; }
|
||||
public string[] Fields { get; }
|
||||
|
||||
public TouchReadLedMessage(string raw)
|
||||
{
|
||||
Raw = raw ?? throw new ArgumentNullException(nameof(raw));
|
||||
|
||||
if (!raw.StartsWith(";") || !raw.EndsWith(";"))
|
||||
throw new FormatException("Invalid LED message framing");
|
||||
|
||||
string content = raw.Substring(1, raw.Length - 2);
|
||||
Fields = content.Split(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons
|
||||
{
|
||||
/// <summary>
|
||||
/// Common iPERL TouchRead bidirectional commands.
|
||||
/// These commands consist of a single-byte command code
|
||||
/// placed in the Information field.
|
||||
/// </summary>
|
||||
public enum ProtocolCommand : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple (legacy) commands (e.g. View Factory ID = 0x01)
|
||||
/// </summary>
|
||||
Simple = 0x00,
|
||||
|
||||
/// <summary>
|
||||
/// View Factory ID (ex-works serial number).
|
||||
/// Returns a 0–12 byte ASCII string terminated by NULL.
|
||||
/// Response only if RF flag is set.
|
||||
/// </summary>
|
||||
ViewFactoryId = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Set Factory ID (0–12 ASCII characters, NULL terminated).
|
||||
/// Protected by meter seal.
|
||||
/// </summary>
|
||||
SetFactoryId = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// View Customer Programmable ID (1–12 ASCII characters).
|
||||
/// </summary>
|
||||
ViewProgrammableId = 0x03,
|
||||
|
||||
/// <summary>
|
||||
/// Set Customer Programmable ID (1–12 ASCII characters, NULL terminated).
|
||||
/// </summary>
|
||||
SetProgrammableId = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// View Version and Type string.
|
||||
/// Example: B1.22,SMW002,B0.02
|
||||
/// </summary>
|
||||
ViewVersionAndType = 0x05,
|
||||
|
||||
/// <summary>
|
||||
/// View Customer Programmable Text (0–20 ASCII characters).
|
||||
/// </summary>
|
||||
ViewProgrammableText = 0x07,
|
||||
|
||||
/// <summary>
|
||||
/// Set Customer Programmable Text (0–20 ASCII characters, NULL terminated).
|
||||
/// </summary>
|
||||
SetProgrammableText = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// View number of reading digits and decimal shift.
|
||||
/// Payload: uint8 digits, int8 decimal shift.
|
||||
/// </summary>
|
||||
ViewNumberOfReadingDigits = 0x09,
|
||||
|
||||
/// <summary>
|
||||
/// Set number of reading digits and decimal shift.
|
||||
/// Digits range: 4–8, Decimal shift: -5..0.
|
||||
/// </summary>
|
||||
SetNumberOfReadingDigits = 0x0A,
|
||||
|
||||
/// <summary>
|
||||
/// View reading units.
|
||||
/// Returns numeric unit code (m3, ft3, gallons).
|
||||
/// </summary>
|
||||
ViewReadingUnits = 0x0B,
|
||||
|
||||
/// <summary>
|
||||
/// Set reading units.
|
||||
/// Valid values: 0x00=m3, 0x01=ft3, 0x04=US gallons, 0xFF=off.
|
||||
/// </summary>
|
||||
SetReadingUnits = 0x0C,
|
||||
|
||||
/// <summary>
|
||||
/// View reading multiplier (resolution).
|
||||
/// Range: -7..+5 or 0x80 (disabled).
|
||||
/// </summary>
|
||||
ViewReadingMultiplier = 0x0F,
|
||||
|
||||
/// <summary>
|
||||
/// Set reading multiplier (resolution).
|
||||
/// </summary>
|
||||
SetReadingMultiplier = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// View preset total (volume accumulator).
|
||||
/// Returns 8 ASCII digits + NULL.
|
||||
/// </summary>
|
||||
ViewPresetTotal = 0x13,
|
||||
|
||||
/// <summary>
|
||||
/// Set preset total (0–8 ASCII digits, NULL terminated).
|
||||
/// Protected by meter seal.
|
||||
/// </summary>
|
||||
SetPresetTotal = 0x14,
|
||||
|
||||
/// <summary>
|
||||
/// View reading mode (unidirectional TouchRead format).
|
||||
/// </summary>
|
||||
ViewReadingMode = 0x15,
|
||||
|
||||
/// <summary>
|
||||
/// Set reading mode.
|
||||
/// Values: Short Variable, Extended, Fixed, Smart Meter.
|
||||
/// </summary>
|
||||
SetReadingMode = 0x16,
|
||||
|
||||
/// <summary>
|
||||
/// View build information (firmware details).
|
||||
/// </summary>
|
||||
ViewBuildInformation = 0x17,
|
||||
|
||||
/// <summary>
|
||||
/// View meter state.
|
||||
/// </summary>
|
||||
ViewState = 0x19,
|
||||
|
||||
/// <summary>
|
||||
/// Set meter state (operating mode).
|
||||
/// Protected by meter seal.
|
||||
/// </summary>
|
||||
SetState = 0x1A,
|
||||
|
||||
/// <summary>
|
||||
/// Device-specific command prefix.
|
||||
/// Must be followed by a device sub-command byte.
|
||||
/// </summary>
|
||||
DeviceSpecific = 0xFD,
|
||||
|
||||
/// <summary>
|
||||
/// Question - specific switch to add additional payload request like "vers"
|
||||
/// Mandatory add payload
|
||||
/// </summary>
|
||||
Question = 0x3F,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,203 @@
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons
|
||||
{
|
||||
/// <summary>
|
||||
/// Device-specific TouchRead sub-commands.
|
||||
/// These sub-commands are used together with the
|
||||
/// <see cref="TouchReadCommand.DeviceSpecific"/> (0xFD) command.
|
||||
/// </summary>
|
||||
public enum ProtocolDeviceSubCommand : byte
|
||||
{
|
||||
// ==========================================================
|
||||
// System / Time
|
||||
// ==========================================================
|
||||
|
||||
/// <summary>
|
||||
/// View system time.
|
||||
/// Returns uint32 seconds since 2000-01-01 00:00:00.
|
||||
/// </summary>
|
||||
ViewSystemTime = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Set system time.
|
||||
/// Payload: uint32 seconds since 2000-01-01.
|
||||
/// If set to zero, the meter resets and erases data.
|
||||
/// Protected by meter seal.
|
||||
/// </summary>
|
||||
SetSystemTime = 0x11,
|
||||
|
||||
// ==========================================================
|
||||
// Alarm Mask / Alarm Configuration
|
||||
// ==========================================================
|
||||
|
||||
/// <summary>View alarm mask (lower 16 bits).</summary>
|
||||
ViewAlarmMask = 0x31,
|
||||
|
||||
/// <summary>Set alarm mask (lower 16 bits).</summary>
|
||||
SetAlarmMask = 0x32,
|
||||
|
||||
/// <summary>View alarm persistence period (days).</summary>
|
||||
ViewPersistence = 0x33,
|
||||
|
||||
/// <summary>Set alarm persistence period (days).</summary>
|
||||
SetPersistence = 0x34,
|
||||
|
||||
/// <summary>View leak duration (hours).</summary>
|
||||
ViewLeakDuration = 0x35,
|
||||
|
||||
/// <summary>Set leak duration (hours).</summary>
|
||||
SetLeakDuration = 0x36,
|
||||
|
||||
/// <summary>View current alarm states.</summary>
|
||||
ViewAlarms = 0x37,
|
||||
|
||||
/// <summary>Set alarm states (protected by meter seal).</summary>
|
||||
SetAlarms = 0x38,
|
||||
|
||||
// ==========================================================
|
||||
// Manufacture / Counters
|
||||
// ==========================================================
|
||||
|
||||
/// <summary>View manufacture date.</summary>
|
||||
ViewManufactureDate = 0x39,
|
||||
|
||||
/// <summary>Set manufacture date (protected by meter seal).</summary>
|
||||
SetManufactureDate = 0x3A,
|
||||
|
||||
/// <summary>View seconds idle.</summary>
|
||||
ViewSecondsIdle = 0x3B,
|
||||
|
||||
/// <summary>View seconds active.</summary>
|
||||
ViewSecondsActive = 0x3D,
|
||||
|
||||
/// <summary>View seconds used.</summary>
|
||||
ViewSecondsUsed = 0x3F,
|
||||
|
||||
// ==========================================================
|
||||
// Snapshot / Datalog
|
||||
// ==========================================================
|
||||
|
||||
/// <summary>View snapshot data.</summary>
|
||||
ViewSnapshotData = 0x41,
|
||||
|
||||
/// <summary>View datalog duration.</summary>
|
||||
ViewDatalogDuration = 0x43,
|
||||
|
||||
/// <summary>Set datalog duration.</summary>
|
||||
SetDatalogDuration = 0x44,
|
||||
|
||||
/// <summary>Read datalog.</summary>
|
||||
ReadDatalog = 0x45,
|
||||
|
||||
/// <summary>Clear datalog.</summary>
|
||||
ClearDatalog = 0x46,
|
||||
|
||||
// ==========================================================
|
||||
// History
|
||||
// ==========================================================
|
||||
|
||||
/// <summary>View history mask.</summary>
|
||||
ViewHistoryMask = 0x47,
|
||||
|
||||
/// <summary>Set history mask.</summary>
|
||||
SetHistoryMask = 0x48,
|
||||
|
||||
/// <summary>Read history.</summary>
|
||||
ReadHistory = 0x49,
|
||||
|
||||
/// <summary>Clear history.</summary>
|
||||
ClearHistory = 0x4A,
|
||||
|
||||
// ==========================================================
|
||||
// Diagnostics / Status
|
||||
// ==========================================================
|
||||
|
||||
/// <summary>View diagnostics.</summary>
|
||||
ViewDiagnostics = 0x4B,
|
||||
|
||||
/// <summary>Reset diagnostics.</summary>
|
||||
ResetDiagnostics = 0x4C,
|
||||
|
||||
/// <summary>View status file.</summary>
|
||||
ViewStatusFile = 0x4F,
|
||||
|
||||
/// <summary>Set status file (protected by meter seal).</summary>
|
||||
SetStatusFile = 0x50,
|
||||
|
||||
// ==========================================================
|
||||
// Calibration / Configuration
|
||||
// ==========================================================
|
||||
|
||||
/// <summary>View calibration structure.</summary>
|
||||
ViewCalibrationStructure = 0x51,
|
||||
|
||||
/// <summary>Set calibration structure (protected by meter seal).</summary>
|
||||
SetCalibrationStructure = 0x52,
|
||||
|
||||
/// <summary>View calibration.</summary>
|
||||
ViewCalibration = 0x53,
|
||||
|
||||
/// <summary>Set calibration (protected by meter seal).</summary>
|
||||
SetCalibration = 0x54,
|
||||
|
||||
/// <summary>View reboot count.</summary>
|
||||
ViewRebootCount = 0x55,
|
||||
|
||||
/// <summary>Set reboot count (protected by meter seal).</summary>
|
||||
SetRebootCount = 0x56,
|
||||
|
||||
/// <summary>View temperature.</summary>
|
||||
ViewTemperature = 0x57,
|
||||
|
||||
/// <summary>Set temperature (protected by meter seal).</summary>
|
||||
SetTemperature = 0x58,
|
||||
|
||||
// ==========================================================
|
||||
// Diagnostic LED / Hardware
|
||||
// ==========================================================
|
||||
|
||||
/// <summary>
|
||||
/// Set diagnostic LED state.
|
||||
/// Enables or disables high-speed LED serial output.
|
||||
/// <para>
|
||||
/// See <see cref="DiagnosticLedState"/>
|
||||
/// diagnostic LED output modes.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
SetDiagnosticLEDState = 0x60,
|
||||
|
||||
|
||||
// ==========================================================
|
||||
// Build / Firmware Info
|
||||
// ==========================================================
|
||||
|
||||
/// <summary>View iPERL build information.</summary>
|
||||
ViewIPerlBuild = 0x65,
|
||||
|
||||
/// <summary>Set iPERL build (protected by meter seal).</summary>
|
||||
SetIPerlBuild = 0x66,
|
||||
|
||||
// ==========================================================
|
||||
// Bootloader (DANGEROUS – use with care)
|
||||
// ==========================================================
|
||||
|
||||
/// <summary>Enter bootloader mode.</summary>
|
||||
EnterBootloader = 0x81,
|
||||
|
||||
/// <summary>Read FLASH memory.</summary>
|
||||
ReadFlash = 0x82,
|
||||
|
||||
/// <summary>Erase all FLASH memory.</summary>
|
||||
EraseAll = 0x83,
|
||||
|
||||
/// <summary>Erase FLASH segment.</summary>
|
||||
EraseSegment = 0x84,
|
||||
|
||||
/// <summary>Update firmware code.</summary>
|
||||
UpdateCode = 0x85,
|
||||
|
||||
/// <summary>Exit bootloader mode.</summary>
|
||||
ExitBootloader = 0x86
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons
|
||||
{
|
||||
public enum ProtocolStatuses : byte
|
||||
{
|
||||
Idle = 0x01,
|
||||
Active = 0x02,
|
||||
Inactive = 0x03,
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.wiredProtocol
|
||||
{
|
||||
public sealed class TouchReadFrame
|
||||
{
|
||||
public byte Start { get; }
|
||||
public byte Length { get; }
|
||||
public byte Control { get; }
|
||||
public byte[] Information { get; }
|
||||
public ushort Checksum { get; }
|
||||
|
||||
public TouchReadFrame(
|
||||
byte start,
|
||||
byte length,
|
||||
byte control,
|
||||
byte[] information,
|
||||
ushort checksum)
|
||||
{
|
||||
Start = start;
|
||||
Length = length;
|
||||
Control = control;
|
||||
Information = information ?? Array.Empty<byte>();
|
||||
Checksum = checksum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.wiredProtocol
|
||||
{
|
||||
public sealed class TouchReadFrameBuilder
|
||||
{
|
||||
private const byte START = 0x0D;
|
||||
private byte _control;
|
||||
private readonly List<byte> _information = new List<byte>();
|
||||
|
||||
public TouchReadFrameBuilder RequestResponse(bool enabled)
|
||||
{
|
||||
_control = enabled ? (byte)0x08 : (byte)0x00;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TouchReadFrameBuilder AddCommand(ProtocolCommand command)
|
||||
{
|
||||
_information.Add((byte)command);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TouchReadFrameBuilder AddSubCommand(ProtocolDeviceSubCommand subCommand)
|
||||
{
|
||||
if (_information.Count == 0 ||
|
||||
_information[0] != (byte)ProtocolCommand.DeviceSpecific)
|
||||
throw new InvalidOperationException(
|
||||
"Sub-command is only valid for DeviceSpecific (0xFD) commands.");
|
||||
|
||||
_information.Add((byte)subCommand);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TouchReadFrameBuilder AddDeviceCommand(
|
||||
ProtocolDeviceSubCommand subCommand)
|
||||
{
|
||||
_information.Add((byte)ProtocolCommand.DeviceSpecific);
|
||||
_information.Add((byte)subCommand);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TouchReadFrameBuilder AddPayload(byte[] payload)
|
||||
{
|
||||
if (payload != null)
|
||||
_information.AddRange(payload);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TouchReadFrameBuilder AddDiagnosticLedState(DiagnosticLedState state)
|
||||
{
|
||||
_information.Add((byte)ProtocolCommand.DeviceSpecific);
|
||||
_information.Add((byte)ProtocolDeviceSubCommand.SetDiagnosticLEDState);
|
||||
_information.Add((byte)state);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TouchReadFrameBuilder AddNullTerminatedAscii(string text)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
_information.AddRange(
|
||||
System.Text.Encoding.ASCII.GetBytes(text));
|
||||
|
||||
_information.Add(0x00);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TouchReadFrame BuildFrame()
|
||||
{
|
||||
if (_information.Count == 0)
|
||||
throw new InvalidOperationException("No command specified.");
|
||||
|
||||
byte length = (byte)(1 + _information.Count + 2);
|
||||
|
||||
var raw = new List<byte>
|
||||
{
|
||||
START,
|
||||
length,
|
||||
_control
|
||||
};
|
||||
|
||||
raw.AddRange(_information);
|
||||
|
||||
ushort checksum = CalculateChecksum(raw);
|
||||
raw.Add((byte)(checksum >> 8));
|
||||
raw.Add((byte)(checksum & 0xFF));
|
||||
|
||||
return new TouchReadFrame(
|
||||
START,
|
||||
length,
|
||||
_control,
|
||||
_information.ToArray(),
|
||||
checksum);
|
||||
}
|
||||
|
||||
public byte[] BuildBytes()
|
||||
{
|
||||
TouchReadFrame frame = BuildFrame();
|
||||
|
||||
var bytes = new List<byte>
|
||||
{
|
||||
frame.Start,
|
||||
frame.Length,
|
||||
frame.Control
|
||||
};
|
||||
|
||||
bytes.AddRange(frame.Information);
|
||||
bytes.Add((byte)(frame.Checksum >> 8));
|
||||
bytes.Add((byte)(frame.Checksum & 0xFF));
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
public static ushort CalculateChecksum(IEnumerable<byte> data)
|
||||
{
|
||||
ushort sum = 0;
|
||||
foreach (var b in data)
|
||||
sum += b;
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.wiredProtocol
|
||||
{
|
||||
public sealed class TouchReadFrameParser
|
||||
{
|
||||
private const byte START = 0x0D;
|
||||
|
||||
public TouchReadResponse Parse(byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
|
||||
if (data.Length < 6)
|
||||
throw new FormatException("Frame too short.");
|
||||
|
||||
if (data[0] != START)
|
||||
throw new FormatException("Invalid START byte.");
|
||||
|
||||
byte length = data[1];
|
||||
if (length + 2 != data.Length)
|
||||
throw new FormatException("Length mismatch.");
|
||||
|
||||
ushort receivedChecksum =
|
||||
(ushort)((data[data.Length - 2] << 8) |
|
||||
data[data.Length - 1]);
|
||||
|
||||
ushort calculatedChecksum = CalculateChecksum(data, data.Length - 2);
|
||||
if (receivedChecksum != calculatedChecksum)
|
||||
throw new FormatException("Checksum error.");
|
||||
|
||||
byte control = data[2];
|
||||
byte status = data[3];
|
||||
|
||||
byte[] payload = ExtractPayload(data);
|
||||
|
||||
return new TouchReadResponse(control, status, payload);
|
||||
}
|
||||
|
||||
private static ushort CalculateChecksum(byte[] data, int count)
|
||||
{
|
||||
ushort sum = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
sum += data[i];
|
||||
return sum;
|
||||
}
|
||||
|
||||
private static byte[] ExtractPayload(byte[] data)
|
||||
{
|
||||
// payload exists only if frame longer than:
|
||||
// START + LEN + CTRL + STATUS + CHK_HI + CHK_LO = 6 bytes
|
||||
if (data.Length <= 6)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
int payloadLength = data.Length - 6;
|
||||
byte[] payload = new byte[payloadLength];
|
||||
Buffer.BlockCopy(data, 4, payload, 0, payloadLength);
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.wiredProtocol
|
||||
{
|
||||
public static class TouchReadProtocol
|
||||
{
|
||||
public const byte START = 0x0D;
|
||||
|
||||
// Control bits (CNTRL1)
|
||||
public const byte RESPONSE_FLAG = 0x08; // RF
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.wiredProtocol
|
||||
{
|
||||
public sealed class TouchReadResponse
|
||||
{
|
||||
public byte Control { get; }
|
||||
public byte Status { get; }
|
||||
public byte[] Payload { get; }
|
||||
|
||||
public bool IsOk => Status == 0x01;
|
||||
|
||||
public TouchReadResponse(byte control, byte status, byte[] payload)
|
||||
{
|
||||
Control = control;
|
||||
Status = status;
|
||||
Payload = payload ?? Array.Empty<byte>();
|
||||
}
|
||||
|
||||
public string GetAsciiPayload()
|
||||
{
|
||||
if (Payload.Length == 0)
|
||||
return null;
|
||||
|
||||
int length = Array.IndexOf(Payload, (byte)0x00);
|
||||
if (length < 0)
|
||||
length = Payload.Length;
|
||||
|
||||
return System.Text.Encoding.ASCII.GetString(Payload, 0, length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
using log4net;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication
|
||||
{
|
||||
public class OpthoHeadService
|
||||
{
|
||||
protected static readonly ILog rfidDataLogger = LogManager.GetLogger("RfidData");
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using Common;
|
||||
using log4net;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.Utils;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication
|
||||
{
|
||||
public class OptoHeadTest : IDisposable
|
||||
{
|
||||
protected static readonly ILog rfidDataLogger = LogManager.GetLogger("RfidData");
|
||||
|
||||
private static SerialDriver serialDriver;
|
||||
|
||||
public static SerialDriver BuildConnection(IperlHead iHead)
|
||||
{
|
||||
return new SerialDriverBuilder()
|
||||
.WithPort($"COM{iHead.RfidComPortNr}")
|
||||
.WithBaudRate(2400)
|
||||
.WithDataBits(8)
|
||||
.WithParity(Parity.None)
|
||||
.WithStopBits(StopBits.One)
|
||||
.WithTimeouts(4000, 2000)
|
||||
.BuildAndConnect();
|
||||
|
||||
}
|
||||
|
||||
public void CloseConnection()
|
||||
{
|
||||
if (serialDriver != null)
|
||||
serialDriver.CloseConnection();
|
||||
}
|
||||
|
||||
|
||||
public static string ReadRequest_PCB(IperlHead iHead)
|
||||
{
|
||||
if (iHead.DebugLevel == DebugMode.Simulate)
|
||||
{
|
||||
return "-OK Simulated response-";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (iHead != null)
|
||||
{
|
||||
if (serialDriver == null)
|
||||
serialDriver = BuildConnection(iHead);
|
||||
|
||||
RadioService headService = new RadioService(serialDriver);
|
||||
string serialNo = headService.ReadRequest_PCB(iHead);
|
||||
return serialNo;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (ex.Message.ToString());
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public static string SetTestMode(IperlHead iHead)
|
||||
{
|
||||
if (iHead.DebugLevel == DebugMode.Simulate)
|
||||
{
|
||||
return "-OK Simulated response-";
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (iHead != null)
|
||||
{
|
||||
if (serialDriver == null)
|
||||
serialDriver = BuildConnection(iHead);
|
||||
|
||||
RadioService headService = new RadioService(serialDriver);
|
||||
string answer = headService.SetTestMode(iHead);
|
||||
return answer;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (ex.Message.ToString());
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public static string SetActiveMode(IperlHead iHead)
|
||||
{
|
||||
if (iHead.DebugLevel == DebugMode.Simulate)
|
||||
{
|
||||
return "-OK Simulated response-";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (iHead != null)
|
||||
{
|
||||
if (serialDriver == null)
|
||||
serialDriver = BuildConnection(iHead);
|
||||
|
||||
RadioService headService = new RadioService(serialDriver);
|
||||
string answer = headService.SetActiveMode(iHead);
|
||||
return answer;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (ex.Message.ToString());
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CloseConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.Utils;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication
|
||||
{
|
||||
public class RadioService
|
||||
{
|
||||
|
||||
static string okResponse = "Command complete, no errors";
|
||||
static string errorResponse = "Unable to execute";
|
||||
|
||||
private SerialDriver serialDriver;
|
||||
public RadioService(SerialDriver serialDriver)
|
||||
{
|
||||
this.serialDriver = serialDriver;
|
||||
}
|
||||
|
||||
public string ReadRequest_PCB(IperlHead iHead)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return decoded.GetAsciiPayload();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string SetTestMode(IperlHead iHead)
|
||||
{
|
||||
if (!serialDriver.IsOpen())
|
||||
{
|
||||
serialDriver.Open();
|
||||
}
|
||||
|
||||
//Set LED to state 4
|
||||
byte[] request = new IperlHatFrameBuilder()
|
||||
.AddDiagnosticLedState(DiagnosticLedState.State4)
|
||||
.BuildBytes();
|
||||
|
||||
byte[] rawData = serialDriver.SendAndWait(request, 1000);
|
||||
if (rawData == null)
|
||||
return null;
|
||||
|
||||
|
||||
// parse rawData
|
||||
var parser = new IperlHatFrameParser();
|
||||
IperlHatResponse decoded = parser.Parse(rawData);
|
||||
if (decoded.IsOk)
|
||||
{
|
||||
|
||||
|
||||
//correct or incorrect response
|
||||
//okResponse, errorResponse
|
||||
|
||||
return "Set Test Mode - OK";
|
||||
}
|
||||
|
||||
return "Set Test Mode - FAILED";
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stop data streaming by LED
|
||||
/// </summary>
|
||||
/// <param name="iHead"></param>
|
||||
/// <returns></returns>
|
||||
public string SetActiveMode(IperlHead iHead)
|
||||
{
|
||||
if (!serialDriver.IsOpen())
|
||||
{
|
||||
serialDriver.Open();
|
||||
}
|
||||
|
||||
//Set LED to state 1
|
||||
byte[] request = new IperlHatFrameBuilder()
|
||||
.AddDiagnosticLedState(DiagnosticLedState.StateOFF)
|
||||
.BuildBytes();
|
||||
|
||||
byte[] rawData = serialDriver.SendAndWait(request, 1000);
|
||||
if (rawData == null)
|
||||
return null;
|
||||
|
||||
|
||||
// parse rawData
|
||||
var parser = new IperlHatFrameParser();
|
||||
IperlHatResponse decoded = parser.Parse(rawData);
|
||||
if (decoded.IsOk)
|
||||
{
|
||||
|
||||
return "Set Active Mode - OK";
|
||||
}
|
||||
|
||||
return "Set Active Mode - FAILED";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,287 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Ports;
|
||||
using System.Threading;
|
||||
using FluentNHibernate.Conventions;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.Utils
|
||||
{
|
||||
public class SerialDriver : IDisposable
|
||||
{
|
||||
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 int _readTimeout;
|
||||
private readonly int _writeTimeout;
|
||||
|
||||
private readonly ManualResetEvent _responseReceived = new ManualResetEvent(false);
|
||||
|
||||
#region Constructors
|
||||
|
||||
// Default constructor (legacy support)
|
||||
public SerialDriver()
|
||||
{
|
||||
_serialPort = new SerialPort();
|
||||
}
|
||||
|
||||
// Builder constructor
|
||||
internal SerialDriver(
|
||||
string portName,
|
||||
int baudRate,
|
||||
int dataBits,
|
||||
Parity parity,
|
||||
StopBits stopBits,
|
||||
int readTimeout,
|
||||
int writeTimeout)
|
||||
{
|
||||
_portName = portName;
|
||||
_baudRate = baudRate;
|
||||
_dataBits = dataBits;
|
||||
_parity = parity;
|
||||
_stopBits = stopBits;
|
||||
_readTimeout = readTimeout;
|
||||
_writeTimeout = writeTimeout;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Open / Close
|
||||
|
||||
// Builder-based open
|
||||
public bool Open()
|
||||
{
|
||||
return OpenConnection(
|
||||
_portName,
|
||||
_baudRate,
|
||||
_dataBits,
|
||||
_parity,
|
||||
_stopBits,
|
||||
_readTimeout,
|
||||
_writeTimeout
|
||||
);
|
||||
}
|
||||
|
||||
// Legacy API (unchanged)
|
||||
public bool OpenConnection(
|
||||
string comPort,
|
||||
int baudrate,
|
||||
int dataBits,
|
||||
Parity parity,
|
||||
StopBits stopbits,
|
||||
int readTimeout = 1000,
|
||||
int writeTimeout = 1000)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
CloseConnection();
|
||||
|
||||
try
|
||||
{
|
||||
ErrorMessage = string.Empty;
|
||||
|
||||
_serialPort = new SerialPort(comPort, baudrate, parity, dataBits, stopbits)
|
||||
{
|
||||
ReadTimeout = readTimeout,
|
||||
WriteTimeout = writeTimeout
|
||||
};
|
||||
|
||||
_serialPort.DataReceived += DataReceivedHandler;
|
||||
_serialPort.Open();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorMessage = $"COM error: Open failed {comPort}. {ex.Message}";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_serialPort.IsOpen)
|
||||
{
|
||||
ErrorMessage = $"COM error: Can't open {comPort}.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CloseConnection()
|
||||
{
|
||||
if (_serialPort != null)
|
||||
{
|
||||
_serialPort.DataReceived -= DataReceivedHandler;
|
||||
if (_serialPort.IsOpen)
|
||||
_serialPort.Close();
|
||||
|
||||
_serialPort.Dispose();
|
||||
_serialPort = null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOpen() => _serialPort?.IsOpen == true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send / Receive
|
||||
|
||||
public bool SendMessage(byte[] sendDataBytes, int length, int readTimeout = 1000, int writeTimeout = 1000)
|
||||
{
|
||||
if (!IsOpen()) return false;
|
||||
if (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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
_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.Constants.Start)
|
||||
{
|
||||
iWordCounter++;
|
||||
isStart = true;
|
||||
}
|
||||
// I have QUESTION
|
||||
if (readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.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)
|
||||
{
|
||||
break;
|
||||
}
|
||||
//if we read END
|
||||
if (isQuestion && readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.Constants.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");
|
||||
|
||||
PrepareReading();
|
||||
_serialPort.Write(data, 0, data.Length);
|
||||
|
||||
if (!_responseReceived.WaitOne(timeoutMs))
|
||||
{
|
||||
ErrorMessage = "COM error: response timeout";
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetRawData();
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CloseConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
|
||||
namespace TBF.Rig.TestMethods.iPerlCommunication.communication.Utils
|
||||
{
|
||||
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 int _readTimeout = 1000;
|
||||
private int _writeTimeout = 1000;
|
||||
|
||||
public SerialDriverBuilder WithPort(string portName)
|
||||
{
|
||||
_portName = portName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SerialDriverBuilder WithBaudRate(int baudRate)
|
||||
{
|
||||
_baudRate = baudRate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SerialDriverBuilder WithDataBits(int dataBits)
|
||||
{
|
||||
_dataBits = dataBits;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SerialDriverBuilder WithParity(Parity parity)
|
||||
{
|
||||
_parity = parity;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SerialDriverBuilder WithStopBits(StopBits stopBits)
|
||||
{
|
||||
_stopBits = stopBits;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SerialDriverBuilder WithTimeouts(int readTimeout, int writeTimeout)
|
||||
{
|
||||
_readTimeout = readTimeout;
|
||||
_writeTimeout = writeTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build driver WITHOUT opening connection
|
||||
/// </summary>
|
||||
public SerialDriver Build()
|
||||
{
|
||||
return new SerialDriver(
|
||||
_portName,
|
||||
_baudRate,
|
||||
_dataBits,
|
||||
_parity,
|
||||
_stopBits,
|
||||
_readTimeout,
|
||||
_writeTimeout
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build driver AND open connection
|
||||
/// </summary>
|
||||
public SerialDriver BuildAndConnect()
|
||||
{
|
||||
var driver = Build();
|
||||
if (!driver.Open())
|
||||
{
|
||||
throw new InvalidOperationException(driver.ErrorMessage);
|
||||
}
|
||||
return driver;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1444,6 +1444,50 @@
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\CommCompletedEventArgs.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\common\OptoTelegramRaw.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\DiagnosticLedParser.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\DiagnosticLedState.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\parserer\DiagnosticLedData.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\parserer\DiagnosticLedFrameSpec.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\parserer\DiagnosticLedState1Data.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\parserer\DiagnosticLedState2Data.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\parserer\DiagnosticLedState3Data.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\parserer\DiagnosticLedState4Data.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\parserer\DiagnosticLedState5Data.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\parserer\DiagnosticLedState6Data.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\parserer\DiagnosticLedState7Data.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\parserer\PipeStatus.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\parserer\SpikeDetectionStatus.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\utils\DiagnosticChecksum.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\diagnosticLed\utils\DiagnosticHex.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\hexLogger\HexFormatter.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\hexLogger\IpelHatCommandDecoder.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\hexLogger\IperlHatLogger.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\hexLogger\TouchReadControlDecoder.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\hexLogger\TouchReadLogger.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\IperlHatProtocol\Constants.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\IperlHatProtocol\IperlHatFrame.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\IperlHatProtocol\IperlHatFrameBuilder.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\IperlHatProtocol\IperlHatFrameParser.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\IperlHatProtocol\IperlHatProtocol.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\IperlHatProtocol\IperlHatResponse.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\led\ITouchReadLedParser.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\led\ShortVariableLedParser.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\led\TouchReadLedData.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\led\TouchReadLedMessage.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\protocolCommons\ProtocolCommand.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\protocolCommons\ProtocolDeviceSubCommand.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\protocolCommons\ProtocolStatuses.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\wiredProtocol\TouchReadFrame.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\wiredProtocol\TouchReadFrameBuilder.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\wiredProtocol\TouchReadFrameParser.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\wiredProtocol\TouchReadProtocol.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\C4\wiredProtocol\TouchReadResponse.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\OpthoHeadService.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\OptoHeadTest.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\RadioService.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\Utils\SerialDriver.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\Utils\SerialDriverBuilder.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\iPerlCommunicationForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user