diff --git a/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertBothWays.cs b/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertBothWays.cs index 5d264a1..b3956ac 100644 --- a/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertBothWays.cs +++ b/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertBothWays.cs @@ -1,45 +1,25 @@ using NUnit.Framework; -using Sharp7.Rx.Interfaces; using Shouldly; namespace Sharp7.Rx.Tests.S7ValueConverterTests; [TestFixture] -public class ConvertBothWays +internal class ConvertBothWays : ConverterTestBase { - static readonly IS7VariableNameParser parser = new S7VariableNameParser(); - - [TestCase(true, "DB0.DBx0.0")] - [TestCase(false, "DB0.DBx0.0")] - [TestCase(true, "DB0.DBx0.4")] - [TestCase(false, "DB0.DBx0.4")] - [TestCase((byte) 18, "DB0.DBB0")] - [TestCase((short) 4660, "DB0.INT0")] - [TestCase((short)-3532, "DB0.INT0")] - [TestCase(-3532, "DB0.INT0")] - [TestCase(305419879, "DB0.DINT0")] - [TestCase(-231451033, "DB0.DINT0")] - [TestCase(1311768394163015151L, "DB0.dul0")] - [TestCase(-994074615050678801L, "DB0.dul0")] - [TestCase(1311768394163015151uL, "DB0.dul0")] - [TestCase(17452669458658872815uL, "DB0.dul0")] - [TestCase(new byte[] { 0x12, 0x34, 0x56, 0x67 }, "DB0.DBB0.4")] - [TestCase(0.25f, "DB0.D0")] - [TestCase("ABCD", "DB0.string0.4")] - [TestCase("ABCD", "DB0.string0.4")] // Clip to length in Address - [TestCase("ABCD", "DB0.DBB0.4")] - public void Write(T input, string address) + [TestCaseSource(nameof(GetValidTestCases))] + public void Convert(ConverterTestCase tc) { //Arrange - var variableAddress = parser.Parse(address); - var buffer = new byte[variableAddress.BufferLength]; + var buffer = new byte[tc.VariableAddress.BufferLength]; + + var write = CreateWriteMethod(tc); + var read = CreateReadMethod(tc); //Act - S7ValueConverter.WriteToBuffer(buffer, input, variableAddress); - var result = S7ValueConverter.ConvertToType(buffer, variableAddress); + write.Invoke(null, [buffer, tc.Value, tc.VariableAddress]); + var result = read.Invoke(null, [buffer, tc.VariableAddress]); //Assert - result.ShouldBe(input); + result.ShouldBe(tc.Value); } - } diff --git a/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertToType.cs b/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertToType.cs deleted file mode 100644 index caa37a9..0000000 --- a/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertToType.cs +++ /dev/null @@ -1,66 +0,0 @@ -using NUnit.Framework; -using Sharp7.Rx.Interfaces; -using Shouldly; - -namespace Sharp7.Rx.Tests.S7ValueConverterTests; - -[TestFixture] -public class ConvertToType -{ - static readonly IS7VariableNameParser parser = new S7VariableNameParser(); - - [TestCase(true, "DB0.DBx0.0", new byte[] {0x01})] - [TestCase(false, "DB0.DBx0.0", new byte[] {0x00})] - [TestCase(true, "DB0.DBx0.4", new byte[] {0x10})] - [TestCase(false, "DB0.DBx0.4", new byte[] {0})] - [TestCase(true, "DB0.DBx0.4", new byte[] {0x1F})] - [TestCase(false, "DB0.DBx0.4", new byte[] {0xEF})] - [TestCase((byte) 18, "DB0.DBB0", new byte[] {0x12})] - [TestCase((short) 4660, "DB0.INT0", new byte[] {0x12, 0x34})] - [TestCase((short) -3532, "DB0.INT0", new byte[] {0xF2, 0x34})] - [TestCase(-3532, "DB0.INT0", new byte[] {0xF2, 0x34})] - [TestCase(305419879, "DB0.DINT0", new byte[] {0x12, 0x34, 0x56, 0x67})] - [TestCase(-231451033, "DB0.DINT0", new byte[] {0xF2, 0x34, 0x56, 0x67})] - [TestCase(1311768394163015151L, "DB0.dul0", new byte[] {0x12, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF})] - [TestCase(-994074615050678801L, "DB0.dul0", new byte[] {0xF2, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF})] - [TestCase(1311768394163015151uL, "DB0.dul0", new byte[] {0x12, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF})] - [TestCase(17452669458658872815uL, "DB0.dul0", new byte[] {0xF2, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF})] - [TestCase(new byte[] {0x12, 0x34, 0x56, 0x67}, "DB0.DBB0.4", new byte[] {0x12, 0x34, 0x56, 0x67})] - [TestCase(0.25f, "DB0.D0", new byte[] {0x3E, 0x80, 0x00, 0x00})] - [TestCase("ABCD", "DB0.string0.4", new byte[] {0x00, 0x04, 0x41, 0x42, 0x43, 0x44})] - [TestCase("ABCD", "DB0.string0.4", new byte[] {0x00, 0xF0, 0x41, 0x42, 0x43, 0x44})] // Clip to length in Address - [TestCase("ABCD", "DB0.DBB0.4", new byte[] {0x41, 0x42, 0x43, 0x44})] - public void Parse(T expected, string address, byte[] data) - { - //Arrange - var variableAddress = parser.Parse(address); - - //Act - var result = S7ValueConverter.ConvertToType(data, variableAddress); - - //Assert - result.ShouldBe(expected); - } - - [TestCase((char) 18, "DB0.DBB0", new byte[] {0x12})] - [TestCase((ushort) 3532, "DB0.INT0", new byte[] {0xF2, 0x34})] - [TestCase(0.25, "DB0.D0", new byte[] {0x3E, 0x80, 0x00, 0x00})] - public void Invalid(T template, string address, byte[] data) - { - //Arrange - var variableAddress = parser.Parse(address); - - //Act - Should.Throw(() => S7ValueConverter.ConvertToType(data, variableAddress)); - } - - [TestCase(3532, "DB0.DINT0", new byte[] {0xF2, 0x34})] - public void Argument(T template, string address, byte[] data) - { - //Arrange - var variableAddress = parser.Parse(address); - - //Act - Should.Throw(() => S7ValueConverter.ConvertToType(data, variableAddress)); - } -} diff --git a/Sharp7.Rx.Tests/S7ValueConverterTests/ConverterTestBase.cs b/Sharp7.Rx.Tests/S7ValueConverterTests/ConverterTestBase.cs new file mode 100644 index 0000000..273029c --- /dev/null +++ b/Sharp7.Rx.Tests/S7ValueConverterTests/ConverterTestBase.cs @@ -0,0 +1,86 @@ +using System.Reflection; +using Sharp7.Rx.Interfaces; + +namespace Sharp7.Rx.Tests.S7ValueConverterTests; + +internal abstract class ConverterTestBase +{ + protected static readonly IS7VariableNameParser Parser = new S7VariableNameParser(); + + public static MethodInfo CreateReadMethod(ConverterTestCase tc) + { + var convertMi = typeof(S7ValueConverter).GetMethod(nameof(S7ValueConverter.ReadFromBuffer)); + var convert = convertMi!.MakeGenericMethod(tc.Value.GetType()); + return convert; + } + + public static MethodInfo CreateWriteMethod(ConverterTestCase tc) + { + var writeMi = typeof(ConverterTestBase).GetMethod(nameof(WriteToBuffer)); + var write = writeMi!.MakeGenericMethod(tc.Value.GetType()); + return write; + } + + public static IEnumerable GetValidTestCases() + { + yield return new ConverterTestCase(true, "DB99.bit5.4", [0x10]); + yield return new ConverterTestCase(false, "DB99.bit5.4", [0x00]); + + yield return new ConverterTestCase((byte) 18, "DB99.Byte5", [0x12]); + yield return new ConverterTestCase((short) 4660, "DB99.Int5", [0x12, 0x34]); + yield return new ConverterTestCase((short) -3532, "DB99.Int5", [0xF2, 0x34]); + yield return new ConverterTestCase((ushort) 4660, "DB99.UInt5", [0x12, 0x34]); + yield return new ConverterTestCase((ushort) 3532, "DB99.UInt5", [0xF2, 0x34]); + yield return new ConverterTestCase(305419879, "DB99.DInt5", [0x12, 0x34, 0x56, 0x67]); + yield return new ConverterTestCase(-231451033, "DB99.DInt5", [0xF2, 0x34, 0x56, 0x67]); + yield return new ConverterTestCase(305419879u, "DB99.UDInt5", [0x12, 0x34, 0x56, 0x67]); + yield return new ConverterTestCase(231451033u, "DB99.UDInt5", [0xF2, 0x34, 0x56, 0x67]); + yield return new ConverterTestCase(1311768394163015151L, "DB99.LInt5", [0x12, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF]); + yield return new ConverterTestCase(-994074615050678801L, "DB99.LInt5", [0xF2, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF]); + yield return new ConverterTestCase(1311768394163015151uL, "DB99.ULInt5", [0x12, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF]); + yield return new ConverterTestCase(17452669458658872815uL, "DB99.ULInt5", [0xF2, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF]); + yield return new ConverterTestCase(0.25f, "DB99.Real5", [0x3E, 0x80, 0x00, 0x00]); + yield return new ConverterTestCase(0.25, "DB99.LReal5", [0x3E, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + + yield return new ConverterTestCase(new byte[] {0x12, 0x34, 0x56, 0x67}, "DB99.Byte5.4", [0x12, 0x34, 0x56, 0x67]); + + yield return new ConverterTestCase("ABCD", "DB99.String10.4", [0x04, 0x04, 0x41, 0x42, 0x43, 0x44]); + yield return new ConverterTestCase("ABCD", "DB99.String10.6", [0x06, 0x04, 0x41, 0x42, 0x43, 0x44, 0x00, 0x00]); + yield return new ConverterTestCase("ABCD", "DB99.WString10.4", [0x00, 0x04, 0x00, 0x04, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x44]); + yield return new ConverterTestCase("ABCD", "DB99.WString10.6", [0x00, 0x06, 0x00, 0x04, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00]); + yield return new ConverterTestCase("ABCD", "DB99.Byte5.4", [0x41, 0x42, 0x43, 0x44]); + + yield return new ConverterTestCase(true, "DB99.DBx0.0", [0x01]); + yield return new ConverterTestCase(false, "DB99.DBx0.0", [0x00]); + yield return new ConverterTestCase(true, "DB99.DBx0.4", [0x10]); + yield return new ConverterTestCase(false, "DB99.DBx0.4", [0]); + yield return new ConverterTestCase((byte) 18, "DB99.DBB0", [0x12]); + yield return new ConverterTestCase((short) 4660, "DB99.INT0", [0x12, 0x34]); + yield return new ConverterTestCase((short) -3532, "DB99.INT0", [0xF2, 0x34]); + yield return new ConverterTestCase(-3532, "DB99.INT0", [0xF2, 0x34]); + yield return new ConverterTestCase(305419879, "DB99.DINT0", [0x12, 0x34, 0x56, 0x67]); + yield return new ConverterTestCase(-231451033, "DB99.DINT0", [0xF2, 0x34, 0x56, 0x67]); + yield return new ConverterTestCase(1311768394163015151L, "DB99.dul0", [0x12, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF]); + yield return new ConverterTestCase(-994074615050678801L, "DB99.dul0", [0xF2, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF]); + yield return new ConverterTestCase(1311768394163015151uL, "DB99.dul0", [0x12, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF]); + yield return new ConverterTestCase(17452669458658872815uL, "DB99.dul0", [0xF2, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF]); + yield return new ConverterTestCase(new byte[] {0x12, 0x34, 0x56, 0x67}, "DB99.DBB0.4", [0x12, 0x34, 0x56, 0x67]); + yield return new ConverterTestCase(0.25f, "DB99.D0", [0x3E, 0x80, 0x00, 0x00]); + } + + /// + /// This helper method exists, since I could not manage to invoke a generic method + /// accepring a Span<T> as parameter. + /// + public static void WriteToBuffer(byte[] buffer, TValue value, S7VariableAddress address) + { + S7ValueConverter.WriteToBuffer(buffer, value, address); + } + + public record ConverterTestCase(object Value, string Address, byte[] Data) + { + public S7VariableAddress VariableAddress => Parser.Parse(Address); + + public override string ToString() => $"{Address} {Value} ({Value.GetType().Name})"; + } +} diff --git a/Sharp7.Rx.Tests/S7ValueConverterTests/ReadFromBuffer.cs b/Sharp7.Rx.Tests/S7ValueConverterTests/ReadFromBuffer.cs new file mode 100644 index 0000000..cf9cc4d --- /dev/null +++ b/Sharp7.Rx.Tests/S7ValueConverterTests/ReadFromBuffer.cs @@ -0,0 +1,51 @@ +using NUnit.Framework; +using Shouldly; + +namespace Sharp7.Rx.Tests.S7ValueConverterTests; + +[TestFixture] +internal class ReadFromBuffer : ConverterTestBase +{ + [TestCaseSource(nameof(GetValidTestCases))] + [TestCaseSource(nameof(GetAdditinalReadTestCases))] + public void Read(ConverterTestCase tc) + { + //Arrange + var convert = CreateReadMethod(tc); + + //Act + var result = convert.Invoke(null, [tc.Data, tc.VariableAddress]); + + //Assert + result.ShouldBe(tc.Value); + } + + public static IEnumerable GetAdditinalReadTestCases() + { + yield return new ConverterTestCase(true, "DB0.DBx0.4", [0x1F]); + yield return new ConverterTestCase(false, "DB0.DBx0.4", [0xEF]); + yield return new ConverterTestCase("ABCD", "DB0.string0.10", [0x04, 0x04, 0x41, 0x42, 0x43, 0x44]); // Length in address exceeds PLC string length + } + + [TestCase((char) 18, "DB0.DBB0", new byte[] {0x12})] + [TestCase((ushort) 3532, "DB0.INT0", new byte[] {0xF2, 0x34})] + [TestCase(0.25, "DB0.D0", new byte[] {0x3E, 0x80, 0x00, 0x00})] + public void Invalid(T template, string address, byte[] data) + { + //Arrange + var variableAddress = Parser.Parse(address); + + //Act + Should.Throw(() => S7ValueConverter.ReadFromBuffer(data, variableAddress)); + } + + [TestCase(3532, "DB0.DINT0", new byte[] {0xF2, 0x34})] + public void Argument(T template, string address, byte[] data) + { + //Arrange + var variableAddress = Parser.Parse(address); + + //Act + Should.Throw(() => S7ValueConverter.ReadFromBuffer(data, variableAddress)); + } +} diff --git a/Sharp7.Rx.Tests/S7ValueConverterTests/WriteToBuffer.cs b/Sharp7.Rx.Tests/S7ValueConverterTests/WriteToBuffer.cs index c33209f..4a93719 100644 --- a/Sharp7.Rx.Tests/S7ValueConverterTests/WriteToBuffer.cs +++ b/Sharp7.Rx.Tests/S7ValueConverterTests/WriteToBuffer.cs @@ -5,41 +5,20 @@ using Shouldly; namespace Sharp7.Rx.Tests.S7ValueConverterTests; [TestFixture] -public class WriteToBuffer +internal class WriteToBuffer:ConverterTestBase { - static readonly IS7VariableNameParser parser = new S7VariableNameParser(); - - [TestCase(true, "DB0.DBx0.0", new byte[] {0x01})] - [TestCase(false, "DB0.DBx0.0", new byte[] {0x00})] - [TestCase(true, "DB0.DBx0.4", new byte[] {0x10})] - [TestCase(false, "DB0.DBx0.4", new byte[] {0})] - [TestCase((byte) 18, "DB0.DBB0", new byte[] {0x12})] - [TestCase((short) 4660, "DB0.INT0", new byte[] {0x12, 0x34})] - [TestCase((short) -3532, "DB0.INT0", new byte[] {0xF2, 0x34})] - [TestCase(-3532, "DB0.INT0", new byte[] {0xF2, 0x34})] - [TestCase(305419879, "DB0.DINT0", new byte[] {0x12, 0x34, 0x56, 0x67})] - [TestCase(-231451033, "DB0.DINT0", new byte[] {0xF2, 0x34, 0x56, 0x67})] - [TestCase(1311768394163015151L, "DB0.dul0", new byte[] {0x12, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF})] - [TestCase(-994074615050678801L, "DB0.dul0", new byte[] {0xF2, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF})] - [TestCase(1311768394163015151uL, "DB0.dul0", new byte[] {0x12, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF})] - [TestCase(17452669458658872815uL, "DB0.dul0", new byte[] {0xF2, 0x34, 0x56, 0x67, 0x89, 0xAB, 0xCD, 0xEF})] - [TestCase(new byte[] {0x12, 0x34, 0x56, 0x67}, "DB0.DBB0.4", new byte[] {0x12, 0x34, 0x56, 0x67})] - [TestCase(0.25f, "DB0.D0", new byte[] {0x3E, 0x80, 0x00, 0x00})] - [TestCase("ABCD", "DB0.string0.4", new byte[] {0x04, 0x04, 0x41, 0x42, 0x43, 0x44})] - [TestCase("ABCD", "DB0.string0.8", new byte[] {0x08, 0x04, 0x41, 0x42, 0x43, 0x44, 0x00, 0x00, 0x00, 0x00})] - [TestCase("ABCD", "DB0.string0.2", new byte[] {0x02, 0x02, 0x41, 0x42})] - [TestCase("ABCD", "DB0.DBB0.4", new byte[] {0x41, 0x42, 0x43, 0x44})] - public void Write(T input, string address, byte[] expected) + [TestCaseSource(nameof(GetValidTestCases))] + public void Write(ConverterTestCase tc) { //Arrange - var variableAddress = parser.Parse(address); - var buffer = new byte[variableAddress.BufferLength]; + var buffer = new byte[tc.VariableAddress.BufferLength]; + var write = CreateWriteMethod(tc); //Act - S7ValueConverter.WriteToBuffer(buffer, input, variableAddress); + write.Invoke(null, [buffer, tc.Value, tc.VariableAddress]); //Assert - buffer.ShouldBe(expected); + buffer.ShouldBe(tc.Data); } [TestCase((char) 18, "DB0.DBB0")] @@ -47,7 +26,7 @@ public class WriteToBuffer public void Invalid(T input, string address) { //Arrange - var variableAddress = parser.Parse(address); + var variableAddress = Parser.Parse(address); var buffer = new byte[variableAddress.BufferLength]; //Act diff --git a/Sharp7.Rx/S7ValueConverter.cs b/Sharp7.Rx/S7ValueConverter.cs index 247070a..9618a70 100644 --- a/Sharp7.Rx/S7ValueConverter.cs +++ b/Sharp7.Rx/S7ValueConverter.cs @@ -7,7 +7,7 @@ namespace Sharp7.Rx; internal static class S7ValueConverter { - public static TValue ConvertToType(byte[] buffer, S7VariableAddress address) + public static TValue ReadFromBuffer(byte[] buffer, S7VariableAddress address) { if (typeof(TValue) == typeof(bool)) return (TValue) (object) (((buffer[0] >> address.Bit) & 1) > 0); diff --git a/Sharp7.Rx/S7VariableNameParser.cs b/Sharp7.Rx/S7VariableNameParser.cs index 683ec18..bf5d7ca 100644 --- a/Sharp7.Rx/S7VariableNameParser.cs +++ b/Sharp7.Rx/S7VariableNameParser.cs @@ -28,12 +28,15 @@ internal class S7VariableNameParser : IS7VariableNameParser {"real", DbType.Single}, {"lreal", DbType.Double}, - // used for legacy compatability - {"b", DbType.Byte}, - {"d", DbType.Single}, + // S7 notation {"dbb", DbType.Byte}, {"dbw", DbType.Int}, {"dbx", DbType.Bit}, + {"dbd", DbType.DInt}, + + // used for legacy compatability + {"b", DbType.Byte}, + {"d", DbType.Single}, {"dul", DbType.ULInt}, {"dulint", DbType.ULInt}, {"dulong", DbType.ULInt}, diff --git a/Sharp7.Rx/Sharp7Plc.cs b/Sharp7.Rx/Sharp7Plc.cs index c8401f1..46936ef 100644 --- a/Sharp7.Rx/Sharp7Plc.cs +++ b/Sharp7.Rx/Sharp7Plc.cs @@ -90,7 +90,7 @@ public class Sharp7Plc : IPlc Observable.FromAsync(() => GetValue(variableName)) .Concat( disposeableContainer.Observable - .Select(bytes => S7ValueConverter.ConvertToType(bytes, address)) + .Select(bytes => S7ValueConverter.ReadFromBuffer(bytes, address)) ); if (transmissionMode == TransmissionMode.OnChange) @@ -121,7 +121,7 @@ public class Sharp7Plc : IPlc if (address == null) throw new ArgumentException("Input variable name is not valid", nameof(variableName)); var data = await s7Connector.ReadBytes(address.Operand, address.Start, address.Length, address.DbNr, token); - return S7ValueConverter.ConvertToType(data, address); + return S7ValueConverter.ReadFromBuffer(data, address); } public async Task InitializeAsync()