diff --git a/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/IperlHatProtocol/IperlHatFrameParser.cs b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/IperlHatProtocol/IperlHatFrameParser.cs index e57dafadd..46f3d02df 100644 --- a/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/IperlHatProtocol/IperlHatFrameParser.cs +++ b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/IperlHatProtocol/IperlHatFrameParser.cs @@ -56,47 +56,70 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtoc } - private static byte[] ExtractPayloadUsePrefix(byte[] data, List prefix, List end) + private static byte[] ExtractPayloadUsePrefix( + byte[] data, + List prefix, + List end) { - // payload exists only if frame longer than: - // START + DIRECTION + LEN + CTRL + END = 5 bytes - // OR VERSION_START + VERSION = 5 bytes - if (data.Length <= 5) + if (!IsPrefixValid(data, prefix, end)) return Array.Empty(); - //check prefix is equal - int prefixLength = prefix.Count; - byte[] commandPrefix = new byte[prefixLength]; - Buffer.BlockCopy(data, 0, commandPrefix, 0, prefixLength); - - if (StartsWithPrefix(end, commandPrefix)) - { - return Array.Empty(); - } - int payloadLength = data.Length - (prefix.Count + end.Count); + + if (payloadLength <= 0) + return Array.Empty(); + byte[] payload = new byte[payloadLength]; - Buffer.BlockCopy(data, prefix.Count, payload, 0, payloadLength); + + Buffer.BlockCopy( + data, + prefix.Count, + payload, + 0, + payloadLength); + return payload; } - - private static bool IsPrefixValid(byte[] data, List prefix, List end) + + private static bool IsPrefixValid( + byte[] data, + List prefix, + List end) { - int prefixLength = prefix.Count; - // payload exists only if frame longer than: - // OR VERSION_START + VERSION = 5 bytes - "?VERS" version implemented - if (data.Length <= prefixLength) // need be and on END - return false; - - //check prefix is equal - byte[] commandPrefix = new byte[prefixLength]; - Buffer.BlockCopy(data, 0, commandPrefix, 0, prefixLength); - - if (StartsWithPrefix(end, commandPrefix)) + if (data == null || + prefix == null || + end == null || + data.Length < prefix.Count + end.Count) { return false; } - + + // Check frame prefix. + byte[] commandPrefix = new byte[prefix.Count]; + + Buffer.BlockCopy( + data, + 0, + commandPrefix, + 0, + prefix.Count); + + if (!StartsWithPrefix(prefix, commandPrefix)) + return false; + + // Check frame ending. + byte[] commandEnd = new byte[end.Count]; + + Buffer.BlockCopy( + data, + data.Length - end.Count, + commandEnd, + 0, + end.Count); + + if (!StartsWithPrefix(end, commandEnd)) + return false; + return true; } diff --git a/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/IperlHatProtocol/IperlHatResponse.cs b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/IperlHatProtocol/IperlHatResponse.cs index b1ca70750..644957870 100644 --- a/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/IperlHatProtocol/IperlHatResponse.cs +++ b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/IperlHatProtocol/IperlHatResponse.cs @@ -38,7 +38,6 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtoc return default; byte raw = Payload[0]; - Type t = typeof(T); // ----- BYTE ----- @@ -88,6 +87,21 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtoc return System.Text.Encoding.ASCII.GetString(Payload, 0, length); } + + public ushort GetUInt16LittleEndian(out bool ok) + { + ok = false; + + if (!IsOk || Payload.Length != 2) + return 0; + + ushort value = (ushort)( + Payload[0] | + (Payload[1] << 8)); + + ok = true; + return value; + } } diff --git a/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/CalibrationFactorResult.cs b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/CalibrationFactorResult.cs new file mode 100644 index 000000000..a198fc62b --- /dev/null +++ b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/CalibrationFactorResult.cs @@ -0,0 +1,19 @@ +namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons +{ + public class CalibrationFactorResult + { + public ushort RawValue { get; set; } + + public double Percentage { get; set; } + + public double CorrectionPercentage { get; set; } + + public override string ToString() + { + return + $"RawValue={RawValue}, " + + $"Percentage={Percentage:F4} %, " + + $"Correction={CorrectionPercentage:+0.0000;-0.0000;0.0000} %"; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/FlipMode.cs b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/FlipMode.cs new file mode 100644 index 000000000..95c0507eb --- /dev/null +++ b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/FlipMode.cs @@ -0,0 +1,8 @@ +namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons +{ + public enum FlipMode : byte + { + Disabled = 0x00, + Enabled = 0x01 + } +} \ No newline at end of file diff --git a/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/ProtocolDeviceSubCommand.cs b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/ProtocolDeviceSubCommand.cs index 3e83e13b0..e3401ade5 100644 --- a/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/ProtocolDeviceSubCommand.cs +++ b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/ProtocolDeviceSubCommand.cs @@ -135,10 +135,18 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommon /// Set calibration structure (protected by meter seal). SetCalibrationStructure = 0x52, - /// View calibration. + /// + /// View calibration factor. + /// Response payload: uint16 little-endian calibration factor. + /// Nominal value 4096 represents 100.0 %. + /// ViewCalibration = 0x53, - /// Set calibration (protected by meter seal). + /// + /// Set calibration factor. + /// Payload: uint16 little-endian calibration factor. + /// Protected by meter seal. + /// SetCalibration = 0x54, /// View reboot count. @@ -152,6 +160,18 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommon /// Set temperature (protected by meter seal). SetTemperature = 0x58, + + // ========================================================== + // Flip mode + // ========================================================== + + /// + /// View randomized field flip-rate mode. + /// Response payload: + /// 0x00 = randomized flip rate disabled, + /// 0x01 = randomized flip rate enabled. + /// + ViewFlipMode = 0xB9, // ========================================================== // Diagnostic LED / Hardware diff --git a/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/ReadingUnits.cs b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/ReadingUnits.cs new file mode 100644 index 000000000..7fb0aece4 --- /dev/null +++ b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/ReadingUnits.cs @@ -0,0 +1,9 @@ +namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons +{ + public enum ReadingUnits : byte + { + CubicMeters = 0x00, + CubicFeet = 0x01, + UsGallons = 0x02 + } +} \ No newline at end of file diff --git a/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/VersionTypeResult.cs b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/VersionTypeResult.cs new file mode 100644 index 000000000..dd8b07815 --- /dev/null +++ b/TBF/Rig/TestMethods/iPerlCommunication/communication/C4/protocolCommons/VersionTypeResult.cs @@ -0,0 +1,59 @@ +using System.Text.RegularExpressions; + +namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons +{ + public class VersionTypeResult + { + public string TouchReadVersion { get; set; } + public string MeterDeviceType { get; set; } + public string MeterFirmwareVersion { get; set; } + + public override string ToString() + { + return $"TouchReadVersion={TouchReadVersion}, " + + $"MeterDeviceType={MeterDeviceType}, " + + $"MeterFirmwareVersion={MeterFirmwareVersion}"; + } + + + public static VersionTypeResult ParseVersionTypePayload(string payload) + { + if (string.IsNullOrWhiteSpace(payload)) + return null; + + // Example: + // vers: Harry T:B800, V:06.06.01, FW:190215, + // 7ECE, B1.6.01, HW:4, Serial:0 + + string meterDeviceType = GetPayloadValue(payload, "T"); + string touchReadVersion = GetPayloadValue(payload, "V"); + string meterFirmwareVersion = GetPayloadValue(payload, "FW"); + + if (string.IsNullOrWhiteSpace(meterDeviceType) && + string.IsNullOrWhiteSpace(touchReadVersion) && + string.IsNullOrWhiteSpace(meterFirmwareVersion)) + { + return null; + } + + return new VersionTypeResult + { + MeterDeviceType = meterDeviceType, + TouchReadVersion = touchReadVersion, + MeterFirmwareVersion = meterFirmwareVersion + }; + } + + private static string GetPayloadValue(string payload, string key) + { + Match match = Regex.Match( + payload, + @"(?:^|[\s,])" + Regex.Escape(key) + @":\s*([^,\s]+)", + RegexOptions.IgnoreCase); + + return match.Success + ? match.Groups[1].Value.Trim() + : null; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/TestMethods/iPerlCommunication/communication/RadioService.cs b/TBF/Rig/TestMethods/iPerlCommunication/communication/RadioService.cs index a6cd92bcd..a70fa75f2 100644 --- a/TBF/Rig/TestMethods/iPerlCommunication/communication/RadioService.cs +++ b/TBF/Rig/TestMethods/iPerlCommunication/communication/RadioService.cs @@ -1,7 +1,11 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; using log4net; using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed; using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol; using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.wiredProtocol; using TBF.Rig.TestMethods.iPerlCommunication.communication.Utils; using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead; @@ -153,8 +157,278 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication return ""; } - + + public VersionTypeResult SetViewVersionAndType(IperlHead iHead) + { + if (!serialDriver.IsOpen()) + { + serialDriver.Open(); + } + + byte[] request = new IperlHatFrameBuilder() + .RequestResponse(true) + .AddCommand(ProtocolCommand.ViewVersionAndType) + .BuildBytes(); + + byte[] rawData = serialDriver.SendAndWait(request, 5000); + + if (rawData == null || rawData.Length == 0) + { + log.Warn("SetViewVersionAndType: No response received."); + return null; + } + + try + { + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + + log.Debug("SetViewVersionAndType isOK: " + decoded.IsOk); + + if (!decoded.IsOk) + { + log.Warn("SetViewVersionAndType: Device returned NOK response."); + return null; + } + + if (decoded.Payload == null || decoded.Payload.Length == 0) + { + log.Warn("SetViewVersionAndType: Response payload is empty."); + return null; + } + + string payloadText = Encoding.ASCII + .GetString(decoded.Payload) + .Trim('\0', '\r', '\n', ' '); + + log.Debug("SetViewVersionAndType payload: " + payloadText); + + VersionTypeResult result = VersionTypeResult.ParseVersionTypePayload(payloadText); + + if (result == null) + { + log.Warn( + "SetViewVersionAndType: Unable to parse response payload: " + + payloadText); + + return null; + } + + log.Debug("SetViewVersionAndType result: " + result); + + return result; + } + catch (FormatException ex) + { + log.Error("SetViewVersionAndType: Invalid response frame.", ex); + return null; + } + catch (Exception ex) + { + log.Error("SetViewVersionAndType failed.", ex); + return null; + } + } + + public ReadingUnits? ViewReadingUnits(IperlHead iHead) + { + if (!serialDriver.IsOpen()) + { + serialDriver.Open(); + } + + byte[] request = new IperlHatFrameBuilder() + .RequestResponse(true) + .AddCommand(ProtocolCommand.ViewReadingUnits) + .BuildBytes(); + + byte[] rawData = serialDriver.SendAndWait(request, 5000); + + if (rawData == null || rawData.Length == 0) + { + log.Warn("ViewReadingUnits: No response received."); + return null; + } + + try + { + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + + log.Debug("ViewReadingUnits isOK: " + decoded.IsOk); + + if (!decoded.IsOk) + { + log.Warn("ViewReadingUnits: Device returned NOK response."); + return null; + } + + ReadingUnits readingUnits = + decoded.GetResponse(out bool responseOk); + + if (!responseOk) + { + string payload = BitConverter.ToString(decoded.Payload); + + log.Warn( + "ViewReadingUnits: Invalid response payload. Payload=" + + payload); + + return null; + } + + log.Debug( + $"ViewReadingUnits: {readingUnits} " + + $"(0x{Convert.ToByte(readingUnits):X2})"); + + return readingUnits; + } + catch (FormatException ex) + { + log.Error("ViewReadingUnits: Invalid response frame.", ex); + return null; + } + catch (Exception ex) + { + log.Error("ViewReadingUnits failed.", ex); + return null; + } + } + + public FlipMode? ViewFlipMode(IperlHead iHead) + { + if (!serialDriver.IsOpen()) + { + serialDriver.Open(); + } + + byte[] request = new TouchReadFrameBuilder() + .RequestResponse(true) + .AddDeviceCommand(ProtocolDeviceSubCommand.ViewFlipMode) + .BuildBytes(); + + byte[] rawData = serialDriver.SendAndWait(request, 5000); + + if (rawData == null || rawData.Length == 0) + { + log.Warn("ViewFlipMode: No response received."); + return null; + } + + try + { + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + + log.Debug("ViewFlipMode isOK: " + decoded.IsOk); + + if (!decoded.IsOk) + { + log.Warn("ViewFlipMode: Device returned NOK response."); + return null; + } + + FlipMode flipMode = + decoded.GetResponse(out bool responseOk); + + if (!responseOk) + { + log.Warn( + "ViewFlipMode: Invalid payload. Expected one byte " + + "with value 0x00 or 0x01. Payload=" + + BitConverter.ToString(decoded.Payload)); + + return null; + } + + log.Debug( + $"ViewFlipMode: {flipMode} " + + $"(0x{(byte)flipMode:X2})"); + + return flipMode; + } + catch (FormatException ex) + { + log.Error("ViewFlipMode: Invalid response frame.", ex); + return null; + } + catch (Exception ex) + { + log.Error("ViewFlipMode failed.", ex); + return null; + } + } + + public CalibrationFactorResult ViewCalibration(IperlHead iHead) + { + if (!serialDriver.IsOpen()) + { + serialDriver.Open(); + } + + byte[] request = new TouchReadFrameBuilder() + .RequestResponse(true) + .AddDeviceCommand(ProtocolDeviceSubCommand.ViewCalibration) + .BuildBytes(); + + byte[] rawData = serialDriver.SendAndWait(request, 5000); + + if (rawData == null || rawData.Length == 0) + { + log.Warn("ViewCalibration: No response received."); + return null; + } + + try + { + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + + log.Debug("ViewCalibration isOK: " + decoded.IsOk); + + if (!decoded.IsOk) + { + log.Warn("ViewCalibration: Device returned NOK response."); + return null; + } + + ushort calibrationFactor = decoded.GetUInt16LittleEndian(out bool responseOk); + + if (!responseOk) + { + log.Warn( "ViewCalibration: Invalid payload. Expected uint16 little-endian value. Payload=" + BitConverter.ToString(decoded.Payload)); + return null; + } + + double percentage = calibrationFactor * 100.0 / 4096.0; + + var result = new CalibrationFactorResult + { + RawValue = calibrationFactor, + Percentage = percentage, + CorrectionPercentage = percentage - 100.0 + }; + + log.Debug("ViewCalibration result: " + result); + + return result; + } + catch (FormatException ex) + { + log.Error("ViewCalibration: Invalid response frame.", ex); + return null; + } + catch (Exception ex) + { + log.Error("ViewCalibration failed.", ex); + return null; + } + } + + // ---------------------- + + public bool SetActivityMode_Active(IperlHead iHead) { if (!serialDriver.IsOpen()) diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj index a6e3bcf01..0063cff64 100644 --- a/TBF/TBF.csproj +++ b/TBF/TBF.csproj @@ -1964,9 +1964,13 @@ + + + + diff --git a/TBFTests/Rig/TestMethods/iPerlCommunication/communication/FakeSerialDriver.cs b/TBFTests/Rig/TestMethods/iPerlCommunication/communication/FakeSerialDriver.cs new file mode 100644 index 000000000..615f55655 --- /dev/null +++ b/TBFTests/Rig/TestMethods/iPerlCommunication/communication/FakeSerialDriver.cs @@ -0,0 +1,40 @@ +using TBF.Rig.TestMethods.iPerlCommunication.communication.Utils; + +namespace TBFTests.Rig.TestMethods.iPerlCommunication.communication +{ + internal sealed class FakeSerialDriver : ISerialDriver + { + public bool IsPortOpen { get; set; } + + public int OpenCallCount { get; private set; } + + public int SendAndWaitCallCount { get; private set; } + + public byte[] Response { get; set; } + + public byte[] LastRequest { get; private set; } + + public int LastTimeout { get; private set; } + + public bool IsOpen() + { + return IsPortOpen; + } + + public bool Open() + { + OpenCallCount++; + IsPortOpen = true; + return true; + } + + public byte[] SendAndWait(byte[] request, int timeout) + { + SendAndWaitCallCount++; + LastRequest = request; + LastTimeout = timeout; + + return Response; + } + } +} \ No newline at end of file diff --git a/TBFTests/Rig/TestMethods/iPerlCommunication/communication/IperlResponseFactory.cs b/TBFTests/Rig/TestMethods/iPerlCommunication/communication/IperlResponseFactory.cs new file mode 100644 index 000000000..e573c75d3 --- /dev/null +++ b/TBFTests/Rig/TestMethods/iPerlCommunication/communication/IperlResponseFactory.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol; + +namespace TBFTests.Rig.TestMethods.iPerlCommunication.communication +{ + internal static class IperlResponseFactory + { + public static byte[] CreateVersionResponse(string responseText) + { + if (responseText == null) + throw new ArgumentNullException(nameof(responseText)); + + byte[] payload = + System.Text.Encoding.ASCII.GetBytes(responseText); + + var response = new List + { + IperlHatProtocolConstants.Question + }; + + response.AddRange(payload); + response.Add(IperlHatProtocolConstants.End); + + return response.ToArray(); + } + + public static byte[] CreateOkResponse(params byte[] payload) + { + return CreateResponse( + IperlHatProtocolConstants.StatusOk, + payload); + } + + public static byte[] CreateNokResponse(params byte[] payload) + { + return CreateResponse( + IperlHatProtocolConstants.StatusNok, + payload); + } + + private static byte[] CreateResponse( + byte status, + params byte[] payload) + { + payload = payload ?? Array.Empty(); + + var response = new List + { + IperlHatProtocolConstants.Start, + IperlHatProtocolConstants.Read, + 0x00, + status + }; + + response.AddRange(payload); + response.Add(IperlHatProtocolConstants.End); + + response[2] = checked((byte)response.Count); + + return response.ToArray(); + } + } +} \ No newline at end of file diff --git a/TBFTests/Rig/TestMethods/iPerlCommunication/communication/RadioServiceTest.cs b/TBFTests/Rig/TestMethods/iPerlCommunication/communication/RadioServiceTest.cs new file mode 100644 index 000000000..3d0467761 --- /dev/null +++ b/TBFTests/Rig/TestMethods/iPerlCommunication/communication/RadioServiceTest.cs @@ -0,0 +1,459 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using JetBrains.Annotations; +using TBF.Rig.TestMethods.iPerlCommunication.communication; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.wiredProtocol; +using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead; + +namespace TBFTests.Rig.TestMethods.iPerlCommunication.communication +{ + [TestClass] + [TestSubject(typeof(RadioService))] + public class RadioServiceTest + { + private FakeSerialDriver serialDriver; + private RadioService service; + private IperlHead head; + + [TestInitialize] + public void TestInitialize() + { + serialDriver = new FakeSerialDriver + { + IsPortOpen = true + }; + + service = new RadioService(serialDriver); + + head = new IperlHead(); + } + + // ========================================================== + // SetViewVersionAndType + // ========================================================== + + [TestMethod] + public void SetViewVersionAndType_ValidResponse_ReturnsParsedResult() + { + // Arrange + const string responseText = + "vers: Harry T:B800, V:06.06.01, FW:190215, " + + "7ECE, B1.6.01, HW:4, Serial:0"; + + serialDriver.Response = + IperlResponseFactory.CreateVersionResponse(responseText); + + // Act + VersionTypeResult result = + service.SetViewVersionAndType(head); + + // Assert + Assert.IsNotNull(result); + + Assert.AreEqual( + "B800", + result.MeterDeviceType); + + Assert.AreEqual( + "06.06.01", + result.TouchReadVersion); + + Assert.AreEqual( + "190215", + result.MeterFirmwareVersion); + + Assert.AreEqual(1, serialDriver.SendAndWaitCallCount); + Assert.AreEqual(5000, serialDriver.LastTimeout); + } + + [TestMethod] + public void SetViewVersionAndType_NullResponse_ReturnsNull() + { + // Arrange + serialDriver.Response = null; + + // Act + VersionTypeResult result = + service.SetViewVersionAndType(head); + + // Assert + Assert.IsNull(result); + } + + [TestMethod] + public void SetViewVersionAndType_NokResponse_ReturnsNull() + { + // Arrange + serialDriver.Response = + IperlResponseFactory.CreateNokResponse(); + + // Act + VersionTypeResult result = + service.SetViewVersionAndType(head); + + // Assert + Assert.IsNull(result); + } + + [TestMethod] + public void SetViewVersionAndType_InvalidPayload_ReturnsNull() + { + // Arrange + byte[] payload = + System.Text.Encoding.ASCII.GetBytes( + "Unsupported response"); + + serialDriver.Response = + IperlResponseFactory.CreateOkResponse(payload); + + // Act + VersionTypeResult result = + service.SetViewVersionAndType(head); + + // Assert + Assert.IsNull(result); + } + + [TestMethod] + public void SetViewVersionAndType_ClosedPort_OpensPort() + { + // Arrange + serialDriver.IsPortOpen = false; + + byte[] payload = + System.Text.Encoding.ASCII.GetBytes( + "vers: Harry T:B800, V:06.06.01, FW:190215"); + + serialDriver.Response = + IperlResponseFactory.CreateOkResponse(payload); + + // Act + service.SetViewVersionAndType(head); + + // Assert + Assert.AreEqual(1, serialDriver.OpenCallCount); + Assert.IsTrue(serialDriver.IsPortOpen); + } + + // ========================================================== + // ViewReadingUnits + // ========================================================== + + [TestMethod] + public void ViewReadingUnits_ValidResponse_ReturnsReadingUnits() + { + // Arrange + serialDriver.Response = + IperlResponseFactory.CreateOkResponse( + (byte)ReadingUnits.CubicMeters); + + // Act + ReadingUnits? result = + service.ViewReadingUnits(head); + + // Assert + Assert.IsTrue(result.HasValue); + + Assert.AreEqual( + ReadingUnits.CubicMeters, + result.Value); + + byte[] expected = CreateSimpleCommandRequest( + ProtocolCommand.ViewReadingUnits); + + Console.WriteLine( + "Expected: " + BitConverter.ToString(expected)); + + Console.WriteLine( + "Actual: " + BitConverter.ToString(serialDriver.LastRequest)); + + Console.WriteLine( + $"Expected length: {expected.Length}, " + + $"actual length: {serialDriver.LastRequest.Length}"); + + CollectionAssert.AreEqual( + expected, + serialDriver.LastRequest); + } + + [TestMethod] + public void ViewReadingUnits_UnknownEnumValue_ReturnsNull() + { + // Arrange + serialDriver.Response = + IperlResponseFactory.CreateOkResponse(0x7E); + + // Act + ReadingUnits? result = + service.ViewReadingUnits(head); + + // Assert + Assert.IsFalse(result.HasValue); + } + + [TestMethod] + public void ViewReadingUnits_InvalidPayloadLength_ReturnsNull() + { + // Arrange + serialDriver.Response = + IperlResponseFactory.CreateOkResponse( + 0x00, + 0x01); + + // Act + ReadingUnits? result = + service.ViewReadingUnits(head); + + // Assert + Assert.IsFalse(result.HasValue); + } + + [TestMethod] + public void ViewReadingUnits_NokResponse_ReturnsNull() + { + // Arrange + serialDriver.Response = + IperlResponseFactory.CreateNokResponse(0x00); + + // Act + ReadingUnits? result = + service.ViewReadingUnits(head); + + // Assert + Assert.IsFalse(result.HasValue); + } + + // ========================================================== + // ViewFlipMode + // ========================================================== + + [TestMethod] + public void ViewFlipMode_DisabledResponse_ReturnsDisabled() + { + // Arrange + serialDriver.Response = + IperlResponseFactory.CreateOkResponse( + (byte)FlipMode.Disabled); + + // Act + FlipMode? result = + service.ViewFlipMode(head); + + // Assert + Assert.IsTrue(result.HasValue); + + Assert.AreEqual( + FlipMode.Disabled, + result.Value); + + CollectionAssert.AreEqual( + CreateDeviceCommandRequest( + ProtocolDeviceSubCommand.ViewFlipMode), + serialDriver.LastRequest); + } + + [TestMethod] + public void ViewFlipMode_EnabledResponse_ReturnsEnabled() + { + // Arrange + serialDriver.Response = + IperlResponseFactory.CreateOkResponse( + (byte)FlipMode.Enabled); + + // Act + FlipMode? result = + service.ViewFlipMode(head); + + // Assert + Assert.IsTrue(result.HasValue); + + Assert.AreEqual( + FlipMode.Enabled, + result.Value); + } + + [TestMethod] + public void ViewFlipMode_UnsupportedValue_ReturnsNull() + { + // Arrange + serialDriver.Response = + IperlResponseFactory.CreateOkResponse(0x02); + + // Act + FlipMode? result = + service.ViewFlipMode(head); + + // Assert + Assert.IsFalse(result.HasValue); + } + + [TestMethod] + public void ViewFlipMode_EmptyPayload_ReturnsNull() + { + // Arrange + serialDriver.Response = + IperlResponseFactory.CreateOkResponse(); + + // Act + FlipMode? result = + service.ViewFlipMode(head); + + // Assert + Assert.IsFalse(result.HasValue); + } + + // ========================================================== + // ViewCalibration + // ========================================================== + + [TestMethod] + public void ViewCalibration_NominalValue_ReturnsOneHundredPercent() + { + // Arrange + // 4096 = 0x1000 + // Little endian payload = 00 10 + serialDriver.Response = + IperlResponseFactory.CreateOkResponse( + 0x00, + 0x10); + + // Act + CalibrationFactorResult result = + service.ViewCalibration(head); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual((ushort)4096, result.RawValue); + + Assert.AreEqual( + 100.0, + result.Percentage, + 0.000001); + + Assert.AreEqual( + 0.0, + result.CorrectionPercentage, + 0.000001); + + byte[] deviceCommandRequest = CreateDeviceCommandRequest(ProtocolDeviceSubCommand.ViewCalibration); + + Console.WriteLine( "Expected: " + BitConverter.ToString(deviceCommandRequest)); + Console.WriteLine( "Actual: " + BitConverter.ToString(serialDriver.LastRequest)); + + CollectionAssert.AreEqual( + deviceCommandRequest, + serialDriver.LastRequest); + } + + [TestMethod] + public void ViewCalibration_ValueAboveNominal_ReturnsPositiveCorrection() + { + // Arrange + // 4097 = 0x1001 + // Little endian = 01 10 + serialDriver.Response = + IperlResponseFactory.CreateOkResponse( + 0x01, + 0x10); + + // Act + CalibrationFactorResult result = + service.ViewCalibration(head); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual((ushort)4097, result.RawValue); + + double expectedPercentage = + 4097 * 100.0 / 4096.0; + + Assert.AreEqual( + expectedPercentage, + result.Percentage, + 0.000001); + + Assert.AreEqual( + expectedPercentage - 100.0, + result.CorrectionPercentage, + 0.000001); + } + + [TestMethod] + public void ViewCalibration_ValueBelowNominal_ReturnsNegativeCorrection() + { + // Arrange + // 4095 = 0x0FFF + // Little endian = FF 0F + serialDriver.Response = + IperlResponseFactory.CreateOkResponse( + 0xFF, + 0x0F); + + // Act + CalibrationFactorResult result = + service.ViewCalibration(head); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual((ushort)4095, result.RawValue); + Assert.IsTrue(result.CorrectionPercentage < 0); + } + + [TestMethod] + public void ViewCalibration_InvalidPayloadLength_ReturnsNull() + { + // Arrange + serialDriver.Response = + IperlResponseFactory.CreateOkResponse(0x10); + + // Act + CalibrationFactorResult result = + service.ViewCalibration(head); + + // Assert + Assert.IsNull(result); + } + + [TestMethod] + public void ViewCalibration_NokResponse_ReturnsNull() + { + // Arrange + serialDriver.Response = + IperlResponseFactory.CreateNokResponse( + 0x00, + 0x10); + + // Act + CalibrationFactorResult result = + service.ViewCalibration(head); + + // Assert + Assert.IsNull(result); + } + + // ========================================================== + // Request helpers + // ========================================================== + + private static byte[] CreateSimpleCommandRequest( + ProtocolCommand command) + { + return new IperlHatFrameBuilder() + .RequestResponse(true) + .AddCommand(command) + .BuildBytes(); + } + + private static byte[] CreateDeviceCommandRequest( + ProtocolDeviceSubCommand subCommand) + { + return new TouchReadFrameBuilder() + .RequestResponse(true) + .AddDeviceCommand(subCommand) + .BuildBytes(); + } + } +} \ No newline at end of file diff --git a/TBFTests/TBFTests.csproj b/TBFTests/TBFTests.csproj index 0dcbe63d1..80b871637 100644 --- a/TBFTests/TBFTests.csproj +++ b/TBFTests/TBFTests.csproj @@ -137,6 +137,9 @@ + + +