Add DiagnosticLedState classes for States #4-#7, enums for PipeStatus and SpikeDetectionStatus, and implement TouchReadProtocol commands, frames, and builders.
This commit is contained in:
parent
988a8c6642
commit
2f1ada3f5b
@ -0,0 +1,134 @@
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4
|
||||
{
|
||||
/// <summary>
|
||||
/// Common iPERL TouchRead bidirectional commands.
|
||||
/// These commands consist of a single-byte command code
|
||||
/// placed in the Information field.
|
||||
/// </summary>
|
||||
public enum TouchReadCommand : 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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4
|
||||
{
|
||||
/// <summary>
|
||||
/// Device-specific TouchRead sub-commands.
|
||||
/// These commands are used with the DeviceSpecific (0xFD) command.
|
||||
/// </summary>
|
||||
public enum TouchReadDeviceSubCommand : byte
|
||||
{
|
||||
// ---- Alarm / Mask -----------------------------------------
|
||||
/// <summary>
|
||||
/// View Alarm Mask (lower 16 bits).
|
||||
/// Returns a 2-byte little-endian alarm bit field.
|
||||
/// </summary>
|
||||
ViewAlarmMask = 0x31,
|
||||
|
||||
/// <summary>
|
||||
/// Set Alarm Mask (lower 16 bits).
|
||||
/// Payload: 2-byte little-endian alarm bit field.
|
||||
/// </summary>
|
||||
SetAlarmMask = 0x32,
|
||||
|
||||
/// <summary>
|
||||
/// View alarm persistence period (days).
|
||||
/// Range: 8–90 days.
|
||||
/// </summary>
|
||||
ViewPersistence = 0x33,
|
||||
|
||||
/// <summary>
|
||||
/// Set alarm persistence period (days).
|
||||
/// Range: 8–90 days.
|
||||
/// </summary>
|
||||
SetPersistence = 0x34,
|
||||
|
||||
/// <summary>
|
||||
/// View leak duration (hours).
|
||||
/// Range: 24–180 hours.
|
||||
/// </summary>
|
||||
ViewLeakDuration = 0x35,
|
||||
|
||||
/// <summary>
|
||||
/// Set leak duration (hours).
|
||||
/// Range: 24–180 hours.
|
||||
/// </summary>
|
||||
SetLeakDuration = 0x36,
|
||||
|
||||
ViewAlarms = 0x37,
|
||||
SetAlarms = 0x38,
|
||||
|
||||
/// <summary>
|
||||
/// View system time.
|
||||
/// Returns uint32 seconds since 2000-01-01.
|
||||
/// </summary>
|
||||
ViewSystemTime = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Set system time.
|
||||
/// Payload: uint32 seconds since 2000-01-01.
|
||||
/// If zero, device will reset and erase data.
|
||||
/// Protected by meter seal.
|
||||
/// </summary>
|
||||
SetSystemTime = 0x11,
|
||||
|
||||
|
||||
// ---- Manufacture / Time -----------------------------------
|
||||
ViewManufactureDate = 0x39,
|
||||
SetManufactureDate = 0x3A,
|
||||
|
||||
ViewSecondsIdle = 0x3B,
|
||||
ViewSecondsActive = 0x3D,
|
||||
ViewSecondsUsed = 0x3F,
|
||||
|
||||
// ---- Snapshot / Logging -----------------------------------
|
||||
ViewSnapshotData = 0x41,
|
||||
ViewDatalogDuration = 0x43,
|
||||
SetDatalogDuration = 0x44,
|
||||
ReadDatalog = 0x45,
|
||||
ClearDatalog = 0x46,
|
||||
|
||||
// ---- History ----------------------------------------------
|
||||
ViewHistoryMask = 0x47,
|
||||
SetHistoryMask = 0x48,
|
||||
ReadHistory = 0x49,
|
||||
ClearHistory = 0x4A,
|
||||
|
||||
// ---- Diagnostics ------------------------------------------
|
||||
ViewDiagnostics = 0x4B,
|
||||
ResetDiagnostics = 0x4C,
|
||||
|
||||
// ---- Calibration / Build ----------------------------------
|
||||
ViewCalibration = 0x53,
|
||||
SetCalibration = 0x54,
|
||||
|
||||
ViewIPerlBuild = 0x65,
|
||||
SetIPerlBuild = 0x66,
|
||||
|
||||
// ---- Bootloader (dangerous!) ------------------------------
|
||||
EnterBootloader = 0x81,
|
||||
ReadFlash = 0x82,
|
||||
EraseAll = 0x83,
|
||||
EraseSegment = 0x84,
|
||||
UpdateCode = 0x85,
|
||||
ExitBootloader = 0x86
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4
|
||||
{
|
||||
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,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4
|
||||
{
|
||||
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(TouchReadCommand command)
|
||||
{
|
||||
_information.Add((byte)command);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TouchReadFrameBuilder AddSubCommand(TouchReadDeviceSubCommand subCommand)
|
||||
{
|
||||
if (_information.Count == 0 ||
|
||||
_information[0] != (byte)TouchReadCommand.DeviceSpecific)
|
||||
throw new InvalidOperationException(
|
||||
"Sub-command is only valid for DeviceSpecific (0xFD) commands.");
|
||||
|
||||
_information.Add((byte)subCommand);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TouchReadFrameBuilder AddDeviceCommand(
|
||||
TouchReadDeviceSubCommand subCommand)
|
||||
{
|
||||
_information.Add((byte)TouchReadCommand.DeviceSpecific);
|
||||
_information.Add((byte)subCommand);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TouchReadFrameBuilder AddPayload(byte[] payload)
|
||||
{
|
||||
if (payload != null)
|
||||
_information.AddRange(payload);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private 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.RegisterReaders.iPerlASICReader.communication.C4
|
||||
{
|
||||
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.RegisterReaders.iPerlASICReader.communication.C4
|
||||
{
|
||||
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.RegisterReaders.iPerlASICReader.communication.C4
|
||||
{
|
||||
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,75 @@
|
||||
using System;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed.parserer;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed
|
||||
{
|
||||
public sealed class DiagnosticLedParser
|
||||
{
|
||||
private readonly DiagnosticLedState _state;
|
||||
|
||||
public DiagnosticLedParser(DiagnosticLedState state)
|
||||
{
|
||||
_state = state;
|
||||
}
|
||||
|
||||
public DiagnosticLedData ParseLine(string line)
|
||||
{
|
||||
if (string.IsNullOrEmpty(line))
|
||||
throw new ArgumentNullException(nameof(line));
|
||||
|
||||
if (!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,13 @@
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed
|
||||
{
|
||||
public enum DiagnosticLedState : byte
|
||||
{
|
||||
State1 = 0x01,
|
||||
State2 = 0x02,
|
||||
State3 = 0x03,
|
||||
State4 = 0x04,
|
||||
State5 = 0x05,
|
||||
State6 = 0x06,
|
||||
State7 = 0x07
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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
|
||||
{
|
||||
/// <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,40 @@
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,137 @@
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed.utils;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
public enum PipeStatus : byte
|
||||
{
|
||||
MetroLowFlowCut = 0,
|
||||
MetroFlowReverse = 1,
|
||||
MetroFlowForward = 2,
|
||||
MetroEmptyPipe = 3
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed.parserer
|
||||
{
|
||||
public enum SpikeDetectionStatus : byte
|
||||
{
|
||||
NoSpike = 0,
|
||||
AdcSpike = 1,
|
||||
SpikeHoldOff = 2,
|
||||
SpikeHighFlow = 5
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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.RegisterReaders.iPerlASICReader.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,59 @@
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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>
|
||||
/// 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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,56 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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.RegisterReaders.iPerlASICReader.communication.C4.led
|
||||
{
|
||||
public interface ITouchReadLedParser
|
||||
{
|
||||
TouchReadLedData Parse(TouchReadLedMessage message);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.iPerlASICReader.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.RegisterReaders.iPerlASICReader.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.RegisterReaders.iPerlASICReader.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(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1251,6 +1251,34 @@
|
||||
<Compile Include="Rig\RegisterReaders\DataStream\Reader\ProcParams.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\DataStream\Reader\Reader.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\DataStream\Reader\ReaderCfg.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\DiagnosticLedParser.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\DiagnosticLedState.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\parserer\DiagnosticLedData.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\parserer\DiagnosticLedState1Data.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\parserer\DiagnosticLedState2Data.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\parserer\DiagnosticLedState3Data.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\parserer\DiagnosticLedState4Data.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\parserer\DiagnosticLedState5Data.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\parserer\DiagnosticLedState6Data.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\parserer\DiagnosticLedState7Data.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\parserer\PipeStatus.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\parserer\SpikeDetectionStatus.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\utils\DiagnosticChecksum.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\utils\DiagnosticHex.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\hexLogger\HexFormatter.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\hexLogger\TouchReadControlDecoder.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\hexLogger\TouchReadLogger.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\led\ITouchReadLedParser.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\led\ShortVariableLedParser.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\led\TouchReadLedData.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\led\TouchReadLedMessage.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\TouchReadCommand.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\TouchReadDeviceSubCommand.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\TouchReadFrame.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\TouchReadFrameBuilder.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\TouchReadFrameParser.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\TouchReadProtocol.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\TouchReadResponse.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlReaderUNI\common\IUniHeadTestCtrl.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlReaderUNI\Factory.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlReaderUNI\IPerlUniCfgCtrl.cs">
|
||||
|
||||
@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.hexLogger;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4
|
||||
{
|
||||
[TestClass]
|
||||
public class TouchReadBaudRateDetectionTests
|
||||
{
|
||||
private const string ComPort = "COM3"; // COM PORT OF THE ASIC
|
||||
private const int ReadTimeoutMs = 1500;
|
||||
|
||||
private static readonly int[] StandardBaudRates =
|
||||
{
|
||||
300, 600, 7812, 1200, 18432, 2400, 4800,
|
||||
9600, 10400, 15625, 19200, 31250, 36864,
|
||||
38400, 50000, 57600, 62500, 76800, 115200
|
||||
};
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("Hardware")]
|
||||
[TestCategory("Serial")]
|
||||
public void Detect_BaudRate_By_ViewFactoryId()
|
||||
{
|
||||
byte[] request = new TouchReadFrameBuilder()
|
||||
.RequestResponse(true)
|
||||
.AddCommand(TouchReadCommand.ViewFactoryId)
|
||||
.BuildBytes();
|
||||
|
||||
var parser = new TouchReadFrameParser();
|
||||
|
||||
foreach (int baud in StandardBaudRates)
|
||||
{
|
||||
Console.WriteLine($"--- Testing baud rate: {baud} ---");
|
||||
|
||||
try
|
||||
{
|
||||
using (var port = new SerialPort(ComPort, baud, Parity.None, 7, StopBits.One))
|
||||
{
|
||||
port.ReadTimeout = ReadTimeoutMs;
|
||||
port.WriteTimeout = 500;
|
||||
port.Open();
|
||||
|
||||
port.DiscardInBuffer();
|
||||
port.DiscardOutBuffer();
|
||||
|
||||
Console.WriteLine("TX → " + HexFormatter.ToSerialHex(request));
|
||||
port.Write(request, 0, request.Length);
|
||||
|
||||
byte[] response = ReadFullFrame(port);
|
||||
|
||||
Console.WriteLine("RX ← " + HexFormatter.ToSerialHex(response));
|
||||
|
||||
TouchReadResponse decoded = parser.Parse(response);
|
||||
|
||||
if (decoded.IsOk)
|
||||
{
|
||||
string factoryId = decoded.GetAsciiPayload();
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("VALID RESPONSE");
|
||||
Console.WriteLine("Baud rate : " + baud);
|
||||
Console.WriteLine("Factory ID : " + factoryId);
|
||||
Console.WriteLine();
|
||||
|
||||
Assert.IsFalse(string.IsNullOrEmpty(factoryId),
|
||||
"Factory ID is empty");
|
||||
|
||||
return; // SUCCESS → stop scanning
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
Console.WriteLine("Timeout");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Error: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Fail("No valid baud rate detected.");
|
||||
}
|
||||
|
||||
private static byte[] ReadFullFrame(SerialPort port)
|
||||
{
|
||||
byte start = (byte)port.ReadByte();
|
||||
if (start != 0x0D)
|
||||
throw new InvalidOperationException("Invalid START byte");
|
||||
|
||||
byte length = (byte)port.ReadByte();
|
||||
|
||||
int remaining = length;
|
||||
byte[] buffer = new byte[2 + remaining];
|
||||
|
||||
buffer[0] = start;
|
||||
buffer[1] = length;
|
||||
|
||||
int offset = 2;
|
||||
while (remaining > 0)
|
||||
{
|
||||
int read = port.Read(buffer, offset, remaining);
|
||||
offset += read;
|
||||
remaining -= read;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.hexLogger;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(TouchReadFrameBuilder))]
|
||||
public class TouchReadFrameBuilderTest
|
||||
{
|
||||
|
||||
[TestMethod]
|
||||
public void Encode_ViewFactoryId_Command()
|
||||
{
|
||||
byte[] frame = new TouchReadFrameBuilder()
|
||||
.RequestResponse(true)
|
||||
.AddCommand(TouchReadCommand.ViewFactoryId)
|
||||
.BuildBytes();
|
||||
|
||||
byte[] expected =
|
||||
{
|
||||
0x0D, // START
|
||||
0x04, // LEN
|
||||
0x08, // CONTROL (RF)
|
||||
0x01, // COMMAND
|
||||
0x00, // CHECKSUM HI
|
||||
0x1A // CHECKSUM LO
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(expected, frame);
|
||||
|
||||
string log = TouchReadLogger.DescribeTx(frame);
|
||||
Console.WriteLine(log);
|
||||
Console.WriteLine(@"Raw: < {0} >", HexFormatter.ToSerialHex(frame));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Encode_ViewProgrammableId_Command()
|
||||
{
|
||||
byte[] frame = new TouchReadFrameBuilder()
|
||||
.RequestResponse(true)
|
||||
.AddCommand(TouchReadCommand.ViewProgrammableId)
|
||||
.BuildBytes();
|
||||
|
||||
byte[] expected =
|
||||
{
|
||||
0x0D, // START
|
||||
0x04, // LEN
|
||||
0x08, // CONTROL (RF)
|
||||
0x03, // COMMAND
|
||||
0x00, // CHECKSUM HI
|
||||
0x1C // CHECKSUM LO
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(expected, frame);
|
||||
|
||||
string log = TouchReadLogger.DescribeTx(frame);
|
||||
Console.WriteLine(log);
|
||||
Console.WriteLine(@"Raw: <{0}>", HexFormatter.ToSerialHex(frame));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Encode_SetState_Idle()
|
||||
{
|
||||
// Arrange
|
||||
byte[] frame = new TouchReadFrameBuilder()
|
||||
.RequestResponse(true)
|
||||
.AddCommand(TouchReadCommand.SetState)
|
||||
.AddPayload(new byte[] { 0x01 }) // Idle
|
||||
.BuildBytes();
|
||||
|
||||
byte[] expected =
|
||||
{
|
||||
0x0D, // START
|
||||
0x05, // LEN
|
||||
0x08, // CONTROL (RF)
|
||||
0x1A, // COMMAND (Set State)
|
||||
0x01, // PAYLOAD (Idle)
|
||||
0x00, // CHECKSUM HI
|
||||
0x35 // CHECKSUM LO
|
||||
};
|
||||
|
||||
// Assert
|
||||
CollectionAssert.AreEqual(expected, frame,
|
||||
$"Encoded frame mismatch.\nExpected: {HexFormatter.ToSerialHex(expected)}\nActual: {HexFormatter.ToSerialHex(frame)}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
public void Decode_InvalidStart_Throws()
|
||||
{
|
||||
byte[] response =
|
||||
{
|
||||
0x00, // invalid START
|
||||
0x04,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x12
|
||||
};
|
||||
|
||||
var parser = new TouchReadFrameParser();
|
||||
|
||||
parser.Parse(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.hexLogger;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4
|
||||
{
|
||||
[TestClass]
|
||||
public class TouchReadSerialIntegrationTests
|
||||
{
|
||||
private const string ComPort = "COM3"; // CHANGE THIS
|
||||
private const int BaudRate = 9600;//38400;//115200;//9600; // VERIFY FROM METER DOC
|
||||
private const int ReadTimeoutMs = 2000;
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("Hardware")]
|
||||
[TestCategory("Serial")]
|
||||
public void Serial_ViewFactoryId_ReadSerialNumber()
|
||||
{
|
||||
// -------- Arrange --------
|
||||
byte[] request = new TouchReadFrameBuilder()
|
||||
.RequestResponse(true)
|
||||
.AddCommand(TouchReadCommand.ViewFactoryId)
|
||||
.BuildBytes();
|
||||
|
||||
var parser = new TouchReadFrameParser();
|
||||
|
||||
using (var port = new SerialPort(ComPort, BaudRate, Parity.None, 8, StopBits.One))
|
||||
{
|
||||
port.ReadTimeout = ReadTimeoutMs;
|
||||
port.WriteTimeout = 500;
|
||||
port.Open();
|
||||
|
||||
// Flush buffers
|
||||
port.DiscardInBuffer();
|
||||
port.DiscardOutBuffer();
|
||||
|
||||
// -------- Act --------
|
||||
port.Write(request, 0, request.Length);
|
||||
|
||||
Console.WriteLine("TX → " + HexFormatter.ToSerialHex(request));
|
||||
|
||||
byte[] response = ReadFullFrame(port);
|
||||
|
||||
Console.WriteLine("RX ← " + HexFormatter.ToSerialHex(response));
|
||||
|
||||
TouchReadResponse decoded = parser.Parse(response);
|
||||
|
||||
// -------- Assert --------
|
||||
Assert.AreEqual(0x01, decoded.Status, "Meter returned error status");
|
||||
|
||||
string serialNumber = decoded.GetAsciiPayload();
|
||||
|
||||
Assert.IsFalse(string.IsNullOrEmpty(serialNumber),
|
||||
"Factory ID (serial number) is empty");
|
||||
|
||||
Console.WriteLine("Meter Factory ID: " + serialNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a full TouchRead frame from the serial port.
|
||||
/// Blocks until complete frame or timeout.
|
||||
/// </summary>
|
||||
private static byte[] ReadFullFrame(SerialPort port)
|
||||
{
|
||||
// Read START + LEN first
|
||||
byte start = (byte)port.ReadByte();
|
||||
if (start != 0x0D)
|
||||
throw new InvalidOperationException("Invalid START byte from meter");
|
||||
|
||||
byte length = (byte)port.ReadByte();
|
||||
|
||||
// LEN counts from CONTROL to CHECKSUM
|
||||
int remaining = length;
|
||||
|
||||
byte[] buffer = new byte[2 + remaining];
|
||||
buffer[0] = start;
|
||||
buffer[1] = length;
|
||||
|
||||
int offset = 2;
|
||||
while (remaining > 0)
|
||||
{
|
||||
int read = port.Read(buffer, offset, remaining);
|
||||
offset += read;
|
||||
remaining -= read;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("Hardware")]
|
||||
public void Serial_RawSniff()
|
||||
{
|
||||
using (var port = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One))
|
||||
{
|
||||
port.ReadTimeout = 500;
|
||||
port.Open();
|
||||
|
||||
Console.WriteLine("Listening for 5 seconds...");
|
||||
DateTime end = DateTime.Now.AddSeconds(5);
|
||||
|
||||
while (DateTime.Now < end)
|
||||
{
|
||||
try
|
||||
{
|
||||
int b = port.ReadByte();
|
||||
Console.Write($"{b:X2} ");
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("\nDone.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,189 @@
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed.parserer;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.diagnosticLed
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(DiagnosticLedParser))]
|
||||
public class DiagnosticLedParserTest
|
||||
{
|
||||
private static string WithChecksum(string bodyWithoutChecksum)
|
||||
{
|
||||
byte sum = 0;
|
||||
foreach (char c in bodyWithoutChecksum)
|
||||
sum += (byte)c;
|
||||
|
||||
return bodyWithoutChecksum + sum.ToString("X2") + "\r\n";
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_DiagnosticLed_State1()
|
||||
{
|
||||
string body =
|
||||
"FFFF9C\t" + // signed 24-bit ADC = -100
|
||||
"2020\t" + // field strength
|
||||
"FFFA\t" + // raw flow (-6)
|
||||
"0050FC\t" + // raw volume
|
||||
"0054\t"; // capacitor mV
|
||||
|
||||
string line = WithChecksum(body);
|
||||
|
||||
var parser = new DiagnosticLedParser(DiagnosticLedState.State1);
|
||||
var data = (DiagnosticLedState1Data)parser.ParseLine(line);
|
||||
|
||||
Assert.AreEqual(-100, data.Adc24);
|
||||
Assert.AreEqual((ushort)0x2020, data.FieldStrength);
|
||||
Assert.AreEqual((short)-6, data.RawFlow);
|
||||
Assert.AreEqual((uint)0x0050FC, data.RawVolume);
|
||||
Assert.AreEqual((ushort)0x0054, data.CapacitorMv);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_DiagnosticLed_State2()
|
||||
{
|
||||
string line =
|
||||
"00004F\t029A\t0000\tFFD3B1\t005C\t3B9AC9B1\t02\t01\t0D\r\n";
|
||||
|
||||
var parser = new DiagnosticLedParser(DiagnosticLedState.State2);
|
||||
var data = (DiagnosticLedState2Data)parser.ParseLine(line);
|
||||
|
||||
Assert.AreEqual(79, data.Adc24);
|
||||
Assert.AreEqual((ushort)666, data.FieldStrength);
|
||||
Assert.AreEqual((short)0, data.RawFlow);
|
||||
Assert.AreEqual(0xFFD3B1u, data.RawVolume);
|
||||
Assert.AreEqual((ushort)92, data.CapacitorMv);
|
||||
Assert.AreEqual(0x3B9AC9B1u, data.LcdVolume);
|
||||
Assert.AreEqual((byte)0x02, data.MeterState);
|
||||
Assert.IsTrue(data.IsLowFlowCutoff);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_DiagnosticLed_State3()
|
||||
{
|
||||
string body =
|
||||
"FFCC09\t2020\tFFFA\t0050FC\t0054\t0B01\t048000\tA7\t";
|
||||
|
||||
string line = WithChecksum(body);
|
||||
|
||||
var parser = new DiagnosticLedParser(DiagnosticLedState.State3);
|
||||
var data = (DiagnosticLedState3Data)parser.ParseLine(line);
|
||||
|
||||
Assert.AreEqual(-13303, data.Adc24);
|
||||
Assert.AreEqual((ushort)0x2020, data.FieldStrength);
|
||||
Assert.AreEqual((short)-6, data.RawFlow);
|
||||
Assert.AreEqual((uint)0x0050FC, data.RawVolume);
|
||||
Assert.AreEqual((ushort)0x0054, data.CapacitorMv);
|
||||
Assert.AreEqual((ushort)0x0B01, data.FieldCalibration);
|
||||
Assert.AreEqual((uint)0x048000, data.AsicTimestamp);
|
||||
Assert.AreEqual((byte)0xA7, data.FieldDriveTimeUs);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_DiagnosticLed_State4()
|
||||
{
|
||||
string body =
|
||||
"000ABC\t2020\tFFFA\t0050FC\t0054\t0B01\t048000\tA7\t" +
|
||||
"00001234\t00F0\t00F1\t0100\t0200\t03\t";
|
||||
|
||||
string line = WithChecksum(body);
|
||||
|
||||
var parser = new DiagnosticLedParser(DiagnosticLedState.State4);
|
||||
var data = (DiagnosticLedState4Data)parser.ParseLine(line);
|
||||
|
||||
Assert.AreEqual(2748, data.Adc24);
|
||||
Assert.AreEqual((ushort)0x2020, data.FieldStrength);
|
||||
Assert.AreEqual((short)-6, data.RawFlow);
|
||||
Assert.AreEqual((uint)0x0050FC, data.RawVolume);
|
||||
Assert.AreEqual((ushort)0x0054, data.CapacitorMv);
|
||||
|
||||
Assert.AreEqual((ushort)0x0B01, data.FieldCalibration);
|
||||
Assert.AreEqual((uint)0x048000, data.AsicTimestamp);
|
||||
Assert.AreEqual((byte)0xA7, data.FieldDriveTimeUs);
|
||||
|
||||
Assert.AreEqual(0x00001234, data.MeanFlowRate);
|
||||
Assert.AreEqual((ushort)0x00F0, data.Field1Measurement);
|
||||
Assert.AreEqual((ushort)0x00F1, data.Field2Measurement);
|
||||
Assert.AreEqual((ushort)0x0100, data.IntegratorCalibrationPositive);
|
||||
Assert.AreEqual((ushort)0x0200, data.IntegratorCalibrationNegative);
|
||||
Assert.AreEqual((byte)0x03, data.AsicState);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_DiagnosticLed_State5()
|
||||
{
|
||||
string body =
|
||||
"FFCC09\t2020\tFFFA\t0050FC\t0054\t0B01\t048000\tA7\t" +
|
||||
"00001234\t00F0\t00F1\t0100\t0200\t03\tFFEC\t";
|
||||
|
||||
string line = WithChecksum(body);
|
||||
|
||||
var parser = new DiagnosticLedParser(DiagnosticLedState.State5);
|
||||
var data = (DiagnosticLedState5Data)parser.ParseLine(line);
|
||||
|
||||
Assert.AreEqual((short)-20, data.WaterImpedance);
|
||||
Assert.AreEqual((byte)0x03, data.AsicState);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_DiagnosticLed_State6()
|
||||
{
|
||||
string body =
|
||||
"FFCC09\t2020\tFFFA\t0050FC\t0054\t0B01\t048000\tA7\t" +
|
||||
"00001234\t00F0\t00F1\t0100\t0200\t03\tFFEC\t0010\t" +
|
||||
"02\t" + // pp spike detection
|
||||
"02\t" + // ll pipe status
|
||||
"00000099\t" + // LCD volume
|
||||
"01\t"; // ASIC state1
|
||||
|
||||
|
||||
string line = WithChecksum(body);
|
||||
|
||||
var parser = new DiagnosticLedParser(DiagnosticLedState.State6);
|
||||
var data = (DiagnosticLedState6Data)parser.ParseLine(line);
|
||||
|
||||
Assert.AreEqual((short)-20, data.WaterImpedance);
|
||||
Assert.AreEqual((short)0x0010, data.ElectrodeDeltaMv);
|
||||
Assert.AreEqual((byte)0x02, data.SpikeDetection);
|
||||
Assert.AreEqual((byte)0x02, data.PipeStatus);
|
||||
Assert.AreEqual((uint)0x99, data.LcdVolume);
|
||||
Assert.AreEqual((byte)0x01, data.AsicState1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_DiagnosticLed_State7()
|
||||
{
|
||||
string body =
|
||||
"FFCC09\t2020\tFFFA\t0050FC\t0054\t0B01\t048000\tA7\t" +
|
||||
"00001234\t00F0\t00F1\t0100\t0200\t03\tFFEC\t0010\t" +
|
||||
"02\t" + // pp spike detection
|
||||
"02\t" + // ll pipe status
|
||||
"00000099\t" + // LCD volume
|
||||
"01\t" + // ASIC state1
|
||||
"FFAA10\t" + // raw ADC before offset
|
||||
"000123\t" + // detrended ADC
|
||||
"FFEE\t" + // imaginary water impedance
|
||||
"0011\t" + // electrode voltage noise
|
||||
"03\t"; // ADC offset learning status
|
||||
|
||||
string line = WithChecksum(body);
|
||||
|
||||
var parser = new DiagnosticLedParser(DiagnosticLedState.State7);
|
||||
var data = (DiagnosticLedState7Data)parser.ParseLine(line);
|
||||
|
||||
Assert.AreEqual(-22000, data.RawAdcBeforeOffset);
|
||||
Assert.AreEqual(0x000123, data.DetrendedAdc);
|
||||
Assert.AreEqual((short)-18, data.ImaginaryWaterImpedance);
|
||||
Assert.AreEqual((ushort)0x0011, data.ElectrodeVoltageNoise);
|
||||
Assert.AreEqual((byte)0x03, data.AdcOffsetLearningStatus);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.led;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.led
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(ShortVariableLedParser))]
|
||||
public class ShortVariableLedParserTest
|
||||
{
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_ValidShortVariableMessage()
|
||||
{
|
||||
// Arrange
|
||||
string raw = ";12345678,00012345.67;";
|
||||
var message = new TouchReadLedMessage(raw);
|
||||
var parser = new ShortVariableLedParser();
|
||||
|
||||
// Act
|
||||
TouchReadLedData data = parser.Parse(message);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(data);
|
||||
Assert.AreEqual(raw, data.Raw);
|
||||
Assert.AreEqual("12345678", data.MeterId);
|
||||
Assert.AreEqual(12345.67m, data.Reading);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
public void Parse_InvalidDecimal_Throws()
|
||||
{
|
||||
// Arrange
|
||||
string raw = ";12345678,ABCDEF;";
|
||||
var message = new TouchReadLedMessage(raw);
|
||||
var parser = new ShortVariableLedParser();
|
||||
|
||||
// Act
|
||||
parser.Parse(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.iPerlASICReader.communication.C4.led;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.led
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(TouchReadLedMessage))]
|
||||
public class TouchReadLedMessageTest
|
||||
{
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_LedMessage_Basic()
|
||||
{
|
||||
string raw = ";12345678,00012345.67;";
|
||||
|
||||
var msg = new TouchReadLedMessage(raw);
|
||||
|
||||
Assert.AreEqual(2, msg.Fields.Length);
|
||||
Assert.AreEqual("12345678", msg.Fields[0]);
|
||||
Assert.AreEqual("00012345.67", msg.Fields[1]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TouchReadLedData_Parse_Extended()
|
||||
{
|
||||
string raw = ";12345678,ABC123,00012345.67,m3;";
|
||||
|
||||
var msg = new TouchReadLedMessage(raw);
|
||||
|
||||
var data = new TouchReadLedData(raw)
|
||||
{
|
||||
MeterId = msg.Fields[0],
|
||||
CustomerId = msg.Fields[1],
|
||||
Reading = TouchReadLedData.ParseDecimal(msg.Fields[2]),
|
||||
Units = msg.Fields[3]
|
||||
};
|
||||
|
||||
Assert.AreEqual("12345678", data.MeterId);
|
||||
Assert.AreEqual("ABC123", data.CustomerId);
|
||||
Assert.AreEqual(12345.67m, data.Reading);
|
||||
Assert.AreEqual("m3", data.Units);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,6 +103,12 @@
|
||||
<Compile Include="Rig\Network\Camera\KeyenceIV3G120\CameraTest.cs" />
|
||||
<Compile Include="Rig\Network\Camera\RoiForFixedStartKeyence\RoiTest.cs" />
|
||||
<Compile Include="Rig\Output\FileWriters\Enhanced\WriterTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\DiagnosticLedParserTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\led\ShortVariableLedParserTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\led\TouchReadLedMessageTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\TouchReadBaudRateDetectionTests.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\TouchReadFrameBuilderTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\TouchReadSerialIntegrationTests.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReaderTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonReader\UniHeadTestCtrlTest.cs" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user