tbf/TBFTests/Rig/RegisterReaders/iPerlASICReader/communication/C4/IperlHatProtocol/IperlHatIntegrationTests.cs

1020 lines
39 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.IperlHatProtocol
{
[TestClass]
[TestCategory("HardwareIntegration")]
public class IperlHatIntegrationTests
{
private const string ComPort = "COM12"; // CHANGE THIS
private const string ComPortOptho = "COM13";
private const int BaudRate = 2400;
private const int BaudRateOpto = 38400;
private const int ReadTimeoutMs = 2000;
[TestInitialize]
public void RequireExplicitHardwareConfiguration()
{
if (!string.Equals(Environment.GetEnvironmentVariable("IPERL_ASIC_HW_TESTS"), "1",
StringComparison.Ordinal))
{
Assert.Inconclusive("Set IPERL_ASIC_HW_TESTS=1 to run iPerl ASIC serial hardware integration tests.");
}
string[] ports = SerialPort.GetPortNames();
if (!ports.Any(port => string.Equals(port, ComPort, StringComparison.OrdinalIgnoreCase)) ||
!ports.Any(port => string.Equals(port, ComPortOptho, StringComparison.OrdinalIgnoreCase)))
{
Assert.Inconclusive($"iPerl ASIC hardware integration requires configured ports {ComPort} and {ComPortOptho}.");
}
}
[TestMethod]
[TestCategory("Hardware")]
[TestCategory("Serial")]
public void Serial_ViewFactoryId_ReadSerialNumber()
{
// -------- Arrange --------
byte[] request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.ViewFactoryId)
.BuildBytes();
var parser = new IperlHatFrameParser();
using (var port = new SerialPort(ComPort, BaudRate, Parity.None, 8, StopBits.One))
{
port.Handshake = Handshake.None;
port.ReadTimeout = 5000;
port.Open();
DateTime end = DateTime.Now.AddSeconds(10);
Console.WriteLine("TX → " + HexFormatter.ToSerialHexWithAscii(request));
port.Write(request, 0, request.Length);
Console.WriteLine("Listening for 10 seconds...");
byte[] response = null;
while (DateTime.Now < end)
{
try
{
response = ReadResponse(port);
Console.WriteLine("RX ← " + HexFormatter.ToSerialHexWithAscii(response));
break;
}
catch (TimeoutException e)
{
Assert.Fail("Timeout: ", e);
}
}
IperlHatResponse iperlHatResponse = parser.Parse(response);
Assert.IsTrue(iperlHatResponse.IsOk);
Assert.IsFalse(iperlHatResponse.Payload.Length < 8);
Console.WriteLine("\nDone.");
}
}
[TestMethod]
[TestCategory("Hardware")]
[TestCategory("Serial")]
public void Serial_SetStatus_Idle_Active()
{
byte[] requestStatus = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.ViewState)
.BuildBytes();
var parser = new IperlHatFrameParser();
using (var port = new SerialPort(ComPort, BaudRate, Parity.None, 8, StopBits.One))
{
port.Handshake = Handshake.None;
port.ReadTimeout = 5000;
port.Open();
DateTime end = DateTime.Now.AddSeconds(10);
Console.WriteLine("TX → " + HexFormatter.ToSerialHexWithAscii(requestStatus));
port.Write(requestStatus, 0, requestStatus.Length);
Console.WriteLine("Listening for 10 seconds...");
byte[] resStart = null;
while (DateTime.Now < end)
{
try
{
resStart = ReadResponse(port);
Console.WriteLine("RX ← " + HexFormatter.ToSerialHexWithAscii(resStart));
break;
}
catch (TimeoutException e)
{
Assert.Fail("Timeout: ", e);
}
}
IperlHatResponse iperlHatResponseOld = parser.Parse(resStart);
Assert.IsTrue(iperlHatResponseOld.IsOk, "Idle No set!");
Console.WriteLine("We start with status: " + HexFormatter.ToHex(iperlHatResponseOld.Payload[0]));
//----------------------------------------------------------------
// -------- Arrange --------
byte[] request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.SetState)
.AddSubCommand(ProtocolStatuses.Idle)
.BuildBytes();
// -- set idle
end = DateTime.Now.AddSeconds(10);
Console.WriteLine("TX → " + HexFormatter.ToSerialHexWithAscii(request));
port.Write(request, 0, request.Length);
Console.WriteLine("Listening for 10 seconds...");
byte[] response = null;
while (DateTime.Now < end)
{
try
{
response = ReadResponse(port);
Console.WriteLine("RX ← " + HexFormatter.ToSerialHexWithAscii(response));
break;
}
catch (TimeoutException e)
{
Assert.Fail("Timeout: ", e);
}
}
IperlHatResponse iperlHatResponse = parser.Parse(response);
Assert.IsTrue(iperlHatResponse.IsOk, "Idle No set!");
//----------------------------------------------------------------
request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.SetState)
.AddSubCommand(ProtocolStatuses.Active)
.BuildBytes();
end = DateTime.Now.AddSeconds(10);
Console.WriteLine("TX → " + HexFormatter.ToSerialHexWithAscii(request));
port.Write(request, 0, request.Length);
Console.WriteLine("Listening for 10 seconds...");
response = null;
while (DateTime.Now < end)
{
try
{
response = ReadResponse(port);
Console.WriteLine("RX ← " + HexFormatter.ToSerialHexWithAscii(response));
break;
}
catch (TimeoutException e)
{
Assert.Fail("Timeout: ", e);
}
}
iperlHatResponse = parser.Parse(response);
Assert.IsTrue(iperlHatResponse.IsOk, "Idle No set!");
//--------------------------------------------------------------------
Console.WriteLine("\nDone.");
}
}
[TestMethod]
[TestCategory("Hardware")]
public void Serial_RawSniff()
{
using (var port = new SerialPort(ComPort, BaudRate, Parity.None, 8, StopBits.One))
{
port.Handshake = Handshake.None;
port.ReadTimeout = 5000;
port.Open();
DateTime end = DateTime.Now.AddSeconds(10);
//welcome message
byte[] frame = HexFormatter.HexStringToByteArray("53 57 3F 76 65 72 73 0D");
Console.WriteLine("TX → " + HexFormatter.ToSerialHexWithAscii(frame));
port.Write(frame, 0, frame.Length);
Console.WriteLine("Listening for 10 seconds...");
while (DateTime.Now < end)
{
try
{
byte[] response = ReadResponse(port);
Console.WriteLine("RX ← " + HexFormatter.ToSerialHexWithAscii(response));
break;
}
catch (TimeoutException e)
{
Assert.Fail("Timeout: ",e);
}
}
end = DateTime.Now.AddSeconds(10);
byte[] frameID = HexFormatter.HexStringToByteArray("53 57 05 01 0D");
Console.WriteLine("TX → " + HexFormatter.ToSerialHexWithAscii(frameID));
port.Write(frameID, 0, frameID.Length);
Console.WriteLine("Listening for 10 seconds...");
while (DateTime.Now < end)
{
try
{
byte[] response = ReadResponse(port);
Console.WriteLine("RX ← " + HexFormatter.ToSerialHexWithAscii(response));
break;
}
catch (TimeoutException e)
{
Assert.Fail("Timeout: ",e);
}
}
Console.WriteLine("\nDone.");
}
}
public static byte[] ReadResponse(SerialPort port)
{
var result = new List<byte>();
try
{
while (true)
{
int value = port.ReadByte(); // blocks until byte or timeout
if (value < 0)
throw new IOException("Serial port returned end of stream.");
byte b = HexFormatter.ToHexByte(value);
result.Add(b);
// stop when CR received
if (b == 0x0D)
break;
}
return result.ToArray();
}
catch (TimeoutException ex)
{
if (result.Count > 0)
return result.ToArray();
throw new TimeoutException("Timeout reading response from serial port.", ex);
}
}
[TestMethod]
[TestCategory("Hardware")]
public void OptoCommunicationON_ReadOpto_CommunicatonOFF()
{
//OPEN COMMUNICATION to Iperl Hat
var parser = new IperlHatFrameParser();
using (var port = new SerialPort(ComPort, BaudRate, Parity.None, 8, StopBits.One))
{
port.Handshake = Handshake.None;
port.ReadTimeout = 5000;
port.Open();
//----------------------------------------------------------------
// ------ Set active mode ------
//----------------------------------------------------------------
byte[] requestStatus = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.SetState)
.AddSubCommand(ProtocolStatuses.Active)
.BuildBytes();
DateTime end = DateTime.Now.AddSeconds(10);
Console.WriteLine("TX → " + HexFormatter.ToSerialHexWithAscii(requestStatus));
port.Write(requestStatus, 0, requestStatus.Length);
Console.WriteLine("Set active mode - Listening for 10 seconds...");
byte[] resStart = null;
while (DateTime.Now < end)
{
try
{
resStart = ReadResponse(port);
Console.WriteLine("RX ← " + HexFormatter.ToSerialHexWithAscii(resStart));
break;
}
catch (TimeoutException e)
{
Assert.Fail("Timeout: ", e);
}
}
IperlHatResponse iperlHatResponseOld = parser.Parse(resStart);
Assert.IsTrue(iperlHatResponseOld.IsOk, "Active No set!");
//----------------------------------------------------------------
// ------ Enable Opto data ------
//----------------------------------------------------------------
// ----------- Arrange -----------
byte[] request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.SetDiagnosticLEDState)
.AddPayload(DiagnosticLedState.State4)
.BuildBytes();
end = DateTime.Now.AddSeconds(10);
Console.WriteLine("TX → " + HexFormatter.ToSerialHexWithAscii(request));
port.Write(request, 0, request.Length);
Console.WriteLine("Enable Opto data - Listening for 10 seconds...");
byte[] response = null;
while (DateTime.Now < end)
{
try
{
response = ReadResponse(port);
Console.WriteLine("RX ← " + HexFormatter.ToSerialHexWithAscii(response));
break;
}
catch (TimeoutException e)
{
Assert.Fail("Timeout: ", e);
}
}
IperlHatResponse iperlHatResponse = parser.Parse(response);
Assert.IsTrue(iperlHatResponse.IsOk, "Opto data not set!");
//----------------------------------------------------------------
// ------ Test Opto data ------
//----------------------------------------------------------------
//Now try test opto data
Serial_OptoRawSniff();
//----------------------------------------------------------------
// ------ Disable Opto data ------
//----------------------------------------------------------------
// ----------- Arrange -----------
request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.SetDiagnosticLEDState)
.AddPayload(DiagnosticLedState.StateOFF)
.BuildBytes();
end = DateTime.Now.AddSeconds(10);
Console.WriteLine("TX → " + HexFormatter.ToSerialHexWithAscii(request));
port.Write(request, 0, request.Length);
Console.WriteLine("Disable Opto data - Listening for 10 seconds...");
response = null;
while (DateTime.Now < end)
{
try
{
response = ReadResponse(port);
Console.WriteLine("RX ← " + HexFormatter.ToSerialHexWithAscii(response));
break;
}
catch (TimeoutException e)
{
Assert.Fail("Timeout: ", e);
}
}
iperlHatResponse = parser.Parse(response);
Assert.IsTrue(iperlHatResponse.IsOk, "Disabled opto LED not set!");
//----------------------------------------------------------------
// ------ Set Idle mode ------
//----------------------------------------------------------------
// ----------- Arrange -----------
request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.SetState)
.AddSubCommand(ProtocolStatuses.Idle)
.BuildBytes();
end = DateTime.Now.AddSeconds(10);
Console.WriteLine("TX → " + HexFormatter.ToSerialHexWithAscii(request));
port.Write(request, 0, request.Length);
Console.WriteLine("Set Idle mode - Listening for 10 seconds...");
response = null;
while (DateTime.Now < end)
{
try
{
response = ReadResponse(port);
Console.WriteLine("RX ← " + HexFormatter.ToSerialHexWithAscii(response));
break;
}
catch (TimeoutException e)
{
Assert.Fail("Timeout: ", e);
}
}
iperlHatResponse = parser.Parse(response);
Assert.IsTrue(iperlHatResponse.IsOk, "Idle status not set!");
//----------------------------------------------------------------
// ------ Test finished ------
//----------------------------------------------------------------
Console.WriteLine("\nDone.");
}
}
/// <summary>
/// This test work only if Opto data are active
/// Use OptoCommunicationON_ReadOpto_CommunicatonOFF() test method
/// </summary>
[TestMethod]
[TestCategory("Hardware")]
public void Integration_Serial_OptoRawSniff_Standalone()
{
Serial_OptoRawSniff();
}
//method test connection to optho head
private void Serial_OptoRawSniff()
{
using (var port = new SerialPort(ComPortOptho, BaudRateOpto, Parity.None, 8, StopBits.One))
{
port.Handshake = Handshake.None;
port.ReadTimeout = 10000;
// 🔑 CRLF handling
port.NewLine = "\r\n";
port.Encoding = Encoding.ASCII; // or UTF8 if needed
port.Open();
Console.WriteLine("Listening for 10 seconds...");
DateTime end = DateTime.Now.AddSeconds(10);
var parser = new DiagnosticLedParser(DiagnosticLedState.State4);
while (DateTime.Now < end)
{
try
{
string line = port.ReadLine(); // string
byte[] bytes = port.Encoding.GetBytes(line);
// Original byte-level output. Keep it for diagnostics.
Console.WriteLine( "RX ASCII bytes ← " + HexFormatter.ToSerialHex(bytes));
// Customer-compatible optical HEX packet.
string opticalHex = FormatOpticalHexPacket(line);
Console.WriteLine( "OPTO HEX ← " + opticalHex);
try
{
var data = (DiagnosticLedState4Data)parser.ParseLine(line,false);
Console.WriteLine("Parsed: " + data);
}catch(Exception e)
{
Console.WriteLine("Failed to parse: " + e.Message);
}
}
catch (TimeoutException)
{
Assert.Inconclusive("No optical packet was received within 10 seconds. Enable optical test mode before running this sniff integration test.");
}
}
Console.WriteLine("\nDone.");
}
}
// ---- NEXT COMMUNICATION ----
[TestMethod]
[TestCategory("Hardware")]
[TestCategory("Serial")]
public void Serial_ViewConfigurationCommands_ReturnValidResponses()
{
using (var port = new SerialPort(
ComPort,
BaudRate,
Parity.None,
8,
StopBits.One))
{
port.Handshake = Handshake.None;
port.ReadTimeout = 5000;
port.Open();
TestViewVersionAndType(port);
TestViewReadingUnits(port);
TestViewFlipMode(port);
TestViewCalibration(port);
}
}
[TestMethod]
[TestCategory("Hardware")]
[TestCategory("Serial")]
public void Serial_SetFlipModes_ActiveOptical_Idle_VerifyReadBack()
{
using (var port = new SerialPort(
ComPort,
BaudRate,
Parity.None,
8,
StopBits.One))
{
port.Handshake = Handshake.None;
port.ReadTimeout = ReadTimeoutMs;
port.WriteTimeout = ReadTimeoutMs;
Console.WriteLine(
"Flip-mode integration: command={0}/{1}, optical={2}/{3}",
ComPort,
BaudRate,
ComPortOptho,
BaudRateOpto);
port.Open();
bool opticalOutputEnabled = false;
bool mainTestCompleted = false;
Exception cleanupFailure = null;
try
{
SetAndVerifyMeterState(port, ProtocolStatuses.Idle);
SetAndVerifyFlipMode(port, FlipMode.Constant, "Constant");
SetAndVerifyMeterState(port, ProtocolStatuses.Active);
SetDiagnosticLed(port, DiagnosticLedState.State4);
opticalOutputEnabled = true;
Console.WriteLine(
"Reading optical packets from {0} while the meter is Active...",
ComPortOptho);
ReadAndVerifyOpticalPacket();
SetAndVerifyFlipMode(port, FlipMode.Randomized, "Randomized");
mainTestCompleted = true;
}
finally
{
if (opticalOutputEnabled && port.IsOpen)
{
try
{
SetDiagnosticLed(port, DiagnosticLedState.StateOFF);
}
catch (Exception ex)
{
cleanupFailure = ex;
Console.WriteLine("CLEANUP FAIL: optical output could not be disabled: " + ex);
}
}
if (port.IsOpen)
{
try
{
SetAndVerifyMeterState(port, ProtocolStatuses.Idle);
}
catch (Exception ex)
{
if (cleanupFailure == null)
cleanupFailure = ex;
Console.WriteLine("CLEANUP FAIL: Idle state could not be restored: " + ex);
}
}
if (mainTestCompleted && cleanupFailure != null)
{
Assert.Fail(
"Main flip-mode scenario passed, but cleanup failed: " +
cleanupFailure.Message);
}
}
}
}
private static void SetAndVerifyFlipMode(
SerialPort port,
FlipMode expectedMode,
string modeName)
{
byte[] setRequest = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.SetFlipMode)
.AddPayload((byte)expectedMode)
.BuildBytes();
SendRequest(
port,
setRequest,
"SetFlipMode" + modeName,
string.Format(
"Sets flip mode to {0}; payload=0x{1:X2}.",
modeName,
(byte)expectedMode));
byte[] viewRequest = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.ViewFlipMode)
.BuildBytes();
IperlHatResponse viewResponse = SendRequest(
port,
viewRequest,
"ViewFlipMode after " + modeName,
"Reads flip mode back to verify the SetFlipMode command.");
FlipMode actualMode = viewResponse.GetResponse<FlipMode>(out bool responseOk);
Assert.IsTrue(
responseOk,
"ViewFlipMode returned an invalid payload after setting " + modeName + ".");
Assert.AreEqual(
expectedMode,
actualMode,
string.Format(
"Flip-mode verification failed. Expected {0} (0x{1:X2}), actual {2} (0x{3:X2}).",
modeName,
(byte)expectedMode,
actualMode,
(byte)actualMode));
Console.WriteLine(
"VERIFY PASS: FlipMode={0}, value=0x{1:X2}",
modeName,
(byte)actualMode);
}
private static void SetAndVerifyMeterState(
SerialPort port,
ProtocolStatuses expectedState)
{
byte[] setRequest = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.SetState)
.AddSubCommand(expectedState)
.BuildBytes();
SendRequest(
port,
setRequest,
"SetState " + expectedState,
"Sets the meter activity state to " + expectedState + ".");
byte[] viewRequest = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.ViewState)
.BuildBytes();
IperlHatResponse viewResponse = SendRequest(
port,
viewRequest,
"ViewState after " + expectedState,
"Reads the activity state back to verify SetState.");
ProtocolStatuses actualState =
viewResponse.GetResponse<ProtocolStatuses>(out bool responseOk);
Assert.IsTrue(
responseOk,
"ViewState returned an invalid payload after setting " + expectedState + ".");
Assert.AreEqual(
expectedState,
actualState,
"Meter-state verification failed.");
Console.WriteLine("VERIFY PASS: MeterState=" + actualState);
}
private static void SetDiagnosticLed(
SerialPort port,
DiagnosticLedState state)
{
byte[] request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.SetDiagnosticLEDState)
.AddPayload(state)
.BuildBytes();
SendRequest(
port,
request,
"SetDiagnosticLEDState " + state,
"Controls optical output on " + ComPortOptho + ".");
}
private static void ReadAndVerifyOpticalPacket()
{
using (var opticalPort = new SerialPort(
ComPortOptho,
BaudRateOpto,
Parity.None,
8,
StopBits.One))
{
opticalPort.Handshake = Handshake.None;
opticalPort.ReadTimeout = 10000;
opticalPort.NewLine = "\r\n";
opticalPort.Encoding = Encoding.ASCII;
opticalPort.Open();
string line = opticalPort.ReadLine();
string opticalHex = FormatOpticalHexPacket(line);
Console.WriteLine("OPTO HEX COM13 <- " + opticalHex);
var parser = new DiagnosticLedParser(DiagnosticLedState.State4);
DiagnosticLedState4Data parsed =
(DiagnosticLedState4Data)parser.ParseLine(line, false);
Assert.IsNotNull(parsed, "COM13 optical parser returned null.");
Console.WriteLine("OPTO PARSE PASS COM13: " + parsed);
}
}
private static IperlHatResponse SendRequest(
SerialPort port,
byte[] request,
string commandName,
string commandDescription)
{
var parser = new IperlHatFrameParser();
Console.WriteLine();
Console.WriteLine("==================================================");
Console.WriteLine($"Command : {commandName}");
Console.WriteLine($"Description : {commandDescription}");
Console.WriteLine($"TX packet : {HexFormatter.ToSerialHexWithAscii(request)}");
port.DiscardInBuffer();
port.Write(request, 0, request.Length);
byte[] response;
try
{
response = ReadResponse(port);
}
catch (TimeoutException ex)
{
Assert.Fail($"{commandName}: Timeout while waiting for response." +
Environment.NewLine +
$"Description: {commandDescription}" +
Environment.NewLine +
$"TX packet: {HexFormatter.ToSerialHexWithAscii(request)}" +
Environment.NewLine +
$"Error: {ex.Message}");
return null;
}
Assert.IsNotNull(response,
$"{commandName}: Response is null. TX packet: {HexFormatter.ToSerialHexWithAscii(request)}");
Assert.IsTrue(response.Length > 0,
$"{commandName}: Response is empty. TX packet: {HexFormatter.ToSerialHexWithAscii(request)}");
Console.WriteLine($"RX packet : {HexFormatter.ToSerialHexWithAscii(response)}");
try
{
IperlHatResponse decoded = parser.Parse(response);
Assert.IsNotNull(
decoded,
$"{commandName}: Parser returned null." +
Environment.NewLine +
$"TX packet: {HexFormatter.ToSerialHexWithAscii(request)}" +
Environment.NewLine +
$"RX packet: {HexFormatter.ToSerialHexWithAscii(response)}");
Assert.IsTrue(
decoded.IsOk,
$"{commandName}: Device returned NOK." +
Environment.NewLine +
$"TX packet: {HexFormatter.ToSerialHexWithAscii(request)}" +
Environment.NewLine +
$"RX packet: {HexFormatter.ToSerialHexWithAscii(response)}" +
Environment.NewLine +
$"Payload: {BitConverter.ToString(decoded.Payload ?? new byte[0])}");
Console.WriteLine($"Result : OK");
Console.WriteLine($"Payload : {BitConverter.ToString(decoded.Payload ?? new byte[0])}");
return decoded;
}
catch (AssertFailedException)
{
// Zachová pôvodnú a podrobnejšiu Assert chybu.
throw;
}
catch (Exception ex)
{
Assert.Fail(
$"{commandName}: Response frame could not be parsed." +
Environment.NewLine +
$"Description: {commandDescription}" +
Environment.NewLine +
$"TX packet: {HexFormatter.ToSerialHexWithAscii(request)}" +
Environment.NewLine +
$"RX packet: {HexFormatter.ToSerialHexWithAscii(response)}" +
Environment.NewLine +
$"Error: {ex}");
return null;
}
}
private static void TestViewVersionAndType(
SerialPort port)
{
byte[] request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.ViewVersionAndType)
.BuildBytes();
IperlHatResponse response = SendRequest(
port,
request,
nameof(ProtocolCommand.ViewVersionAndType),
"Reads the TouchRead version, meter device type and meter firmware version.");
Assert.IsNotNull( response.Payload, "ViewVersionAndType: Payload is null.");
Assert.IsTrue( response.Payload.Length > 0, "ViewVersionAndType: Payload is empty.");
string payloadText = Encoding.ASCII.GetString(response.Payload).Trim('\0', '\r', '\n', ' ');
Console.WriteLine( "ViewVersionAndType payload: " + payloadText);
VersionTypeResult result = VersionTypeResult.ParseVersionTypePayload(payloadText);
Assert.IsNotNull( result, "ViewVersionAndType: Payload cannot be parsed. " + $"Payload='{payloadText}'");
Assert.IsFalse( string.IsNullOrWhiteSpace(result.TouchReadVersion), "ViewVersionAndType: TouchReadVersion is empty.");
Assert.IsFalse( string.IsNullOrWhiteSpace(result.MeterDeviceType), "ViewVersionAndType: MeterDeviceType is empty.");
Assert.IsFalse( string.IsNullOrWhiteSpace(result.MeterFirmwareVersion), "ViewVersionAndType: MeterFirmwareVersion is empty.");
Console.WriteLine( "ViewVersionAndType result: " + result);
}
private static void TestViewReadingUnits(SerialPort port)
{
byte[] request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.ViewReadingUnits)
.BuildBytes();
IperlHatResponse response = SendRequest(
port,
request,
nameof(ProtocolCommand.ViewReadingUnits),
"Reads the measurement units currently configured in the meter.");
ReadingUnits units = response.GetResponse<ReadingUnits>(out bool responseOk);
Assert.IsTrue( responseOk, $"ViewReadingUnits: Invalid payload. Payload={BitConverter.ToString(response.Payload ?? new byte[0])}");
Assert.IsTrue( Enum.IsDefined(typeof(ReadingUnits), units), $"ViewReadingUnits: Unknown value 0x{Convert.ToByte(units):X2}.");
Console.WriteLine( $"ViewReadingUnits result: {units} " + $"(0x{Convert.ToByte(units):X2})");
}
private static void TestViewFlipMode(SerialPort port)
{
byte[] request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.ViewFlipMode)
.BuildBytes();
IperlHatResponse response = SendRequest(
port,
request,
nameof(ProtocolDeviceSubCommand.ViewFlipMode),
"Reads the current display flip mode.");
FlipMode flipMode = response.GetResponse<FlipMode>(out bool responseOk);
Assert.IsTrue( responseOk, $"ViewFlipMode: Invalid payload. Payload={BitConverter.ToString(response.Payload ?? new byte[0])}");
Assert.IsTrue( Enum.IsDefined(typeof(FlipMode), flipMode),$"ViewFlipMode: Unknown value 0x{Convert.ToByte(flipMode):X2}.");
Console.WriteLine( $"ViewFlipMode result: {flipMode} (0x{Convert.ToByte(flipMode):X2})");
}
private static void TestViewCalibration(SerialPort port)
{
byte[] request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.ViewCalibration)
.BuildBytes();
IperlHatResponse response = SendRequest(
port,
request,
nameof(ProtocolDeviceSubCommand.ViewCalibration),
"Reads the raw two-byte calibration factor used to calculate the calibration percentage.");
ushort rawValue = response.GetUInt16LittleEndian(out bool responseOk);
Assert.IsTrue( responseOk, "ViewCalibration: Expected a two-byte little-endian payload. " +
$"Payload={BitConverter.ToString(response.Payload ?? new byte[0])}");
double percentage = rawValue * 100.0 / 4096.0;
double correctionPercentage = percentage - 100.0;
Console.WriteLine( $"ViewCalibration result: RawValue={rawValue}, Percentage={percentage:F4} %, " +
$"Correction={correctionPercentage:+0.0000;-0.0000;0.0000} %");
Assert.IsTrue( rawValue > 0, "ViewCalibration: Calibration factor must be greater than zero.");
Assert.IsTrue( percentage > 0.0, "ViewCalibration: Calculated percentage must be greater than zero.");
}
private static string FormatOpticalHexPacket(string asciiPacket)
{
if (string.IsNullOrWhiteSpace(asciiPacket))
throw new FormatException("Optical packet is empty.");
string[] fields = asciiPacket.Split(
new[] { ' ', '\t', '\r', '\n' },
StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < fields.Length; i++)
{
string field = fields[i].Trim();
if (!IsHexadecimal(field))
{
throw new FormatException(
$"Optical packet contains a non-HEX field at index {i}: '{field}'.");
}
fields[i] = field.ToUpperInvariant();
}
return string.Join(" ", fields);
}
private static bool IsHexadecimal(string value)
{
if (string.IsNullOrEmpty(value))
return false;
for (int i = 0; i < value.Length; i++)
{
char c = value[i];
bool isHexDigit =
(c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'F') ||
(c >= 'a' && c <= 'f');
if (!isHexDigit)
return false;
}
return true;
}
}
}