diff --git a/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertBothWays.cs b/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertBothWays.cs new file mode 100644 index 0000000..5d264a1 --- /dev/null +++ b/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertBothWays.cs @@ -0,0 +1,45 @@ +using NUnit.Framework; +using Sharp7.Rx.Interfaces; +using Shouldly; + +namespace Sharp7.Rx.Tests.S7ValueConverterTests; + +[TestFixture] +public class ConvertBothWays +{ + 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) + { + //Arrange + var variableAddress = parser.Parse(address); + var buffer = new byte[variableAddress.BufferLength]; + + //Act + S7ValueConverter.WriteToBuffer(buffer, input, variableAddress); + var result = S7ValueConverter.ConvertToType(buffer, variableAddress); + + //Assert + result.ShouldBe(input); + } + +} diff --git a/Sharp7.Rx.Tests/S7ValueConverterTests/S7ValueConverterTests.cs b/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertToType.cs similarity index 95% rename from Sharp7.Rx.Tests/S7ValueConverterTests/S7ValueConverterTests.cs rename to Sharp7.Rx.Tests/S7ValueConverterTests/ConvertToType.cs index 23052d2..caa37a9 100644 --- a/Sharp7.Rx.Tests/S7ValueConverterTests/S7ValueConverterTests.cs +++ b/Sharp7.Rx.Tests/S7ValueConverterTests/ConvertToType.cs @@ -16,7 +16,6 @@ public class ConvertToType [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((char) 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})] @@ -28,7 +27,6 @@ public class ConvertToType [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(0.25, "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})] @@ -44,8 +42,10 @@ public class ConvertToType result.ShouldBe(expected); } + [TestCase((char) 18, "DB0.DBB0", new byte[] {0x12})] [TestCase((ushort) 3532, "DB0.INT0", new byte[] {0xF2, 0x34})] - public void Invalid(T expected, string address, byte[] data) + [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); @@ -55,7 +55,7 @@ public class ConvertToType } [TestCase(3532, "DB0.DINT0", new byte[] {0xF2, 0x34})] - public void Argument(T expected, string address, byte[] data) + public void Argument(T template, string address, byte[] data) { //Arrange var variableAddress = parser.Parse(address); diff --git a/Sharp7.Rx.Tests/S7ValueConverterTests/WriteToBuffer.cs b/Sharp7.Rx.Tests/S7ValueConverterTests/WriteToBuffer.cs index 137bd07..c33209f 100644 --- a/Sharp7.Rx.Tests/S7ValueConverterTests/WriteToBuffer.cs +++ b/Sharp7.Rx.Tests/S7ValueConverterTests/WriteToBuffer.cs @@ -13,10 +13,7 @@ public class WriteToBuffer [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((char) 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})] @@ -28,20 +25,32 @@ public class WriteToBuffer [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(0.25, "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.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 expected, string address, byte[] data) + public void Write(T input, string address, byte[] expected) { //Arrange var variableAddress = parser.Parse(address); - var buffer = new byte[variableAddress.Length]; + var buffer = new byte[variableAddress.BufferLength]; //Act - S7ValueConverter.WriteToBuffer(buffer, expected, variableAddress); + S7ValueConverter.WriteToBuffer(buffer, input, variableAddress); //Assert - buffer.ShouldBe(data); + buffer.ShouldBe(expected); + } + + [TestCase((char) 18, "DB0.DBB0")] + [TestCase(0.25, "DB0.D0")] + public void Invalid(T input, string address) + { + //Arrange + var variableAddress = parser.Parse(address); + var buffer = new byte[variableAddress.BufferLength]; + + //Act + Should.Throw(() => S7ValueConverter.WriteToBuffer(buffer, input, variableAddress)); } } diff --git a/Sharp7.Rx/S7ValueConverter.cs b/Sharp7.Rx/S7ValueConverter.cs index 612e159..247070a 100644 --- a/Sharp7.Rx/S7ValueConverter.cs +++ b/Sharp7.Rx/S7ValueConverter.cs @@ -7,10 +7,6 @@ namespace Sharp7.Rx; internal static class S7ValueConverter { - public static void WriteToBuffer(Span buffer, TValue value, S7VariableAddress address) - { - } - public static TValue ConvertToType(byte[] buffer, S7VariableAddress address) { if (typeof(TValue) == typeof(bool)) @@ -37,21 +33,10 @@ internal static class S7ValueConverter if (typeof(TValue) == typeof(byte)) return (TValue) (object) buffer[0]; - if (typeof(TValue) == typeof(char)) - return (TValue) (object) (char) buffer[0]; if (typeof(TValue) == typeof(byte[])) return (TValue) (object) buffer; - - if (typeof(TValue) == typeof(double)) - { - var d = new UInt32SingleMap - { - UInt32 = BinaryPrimitives.ReadUInt32BigEndian(buffer) - }; - return (TValue) (object) (double) d.Single; - } - + if (typeof(TValue) == typeof(float)) { var d = new UInt32SingleMap @@ -75,7 +60,83 @@ internal static class S7ValueConverter else return (TValue) (object) Encoding.ASCII.GetString(buffer).Trim(); - throw new InvalidOperationException(string.Format("type '{0}' not supported.", typeof(TValue))); + throw new InvalidOperationException($"type '{typeof(TValue)}' not supported."); + } + + public static void WriteToBuffer(Span buffer, TValue value, S7VariableAddress address) + { + if (buffer.Length < address.BufferLength) + throw new ArgumentException($"buffer must be at least {address.BufferLength} bytes long for {address}", nameof(buffer)); + + if (typeof(TValue) == typeof(bool)) + { + var byteValue = (bool) (object) value ? (byte) 1 : (byte) 0; + var shifted = (byte) (byteValue << address.Bit); + buffer[0] = shifted; + } + + else if (typeof(TValue) == typeof(int)) + { + if (address.Length == 2) + BinaryPrimitives.WriteInt16BigEndian(buffer, (short) (int) (object) value); + else + BinaryPrimitives.WriteInt32BigEndian(buffer, (int) (object) value); + } + else if (typeof(TValue) == typeof(short)) + { + if (address.Length == 2) + BinaryPrimitives.WriteInt16BigEndian(buffer, (short) (object) value); + else + BinaryPrimitives.WriteInt32BigEndian(buffer, (short) (object) value); + } + else if (typeof(TValue) == typeof(long)) + BinaryPrimitives.WriteInt64BigEndian(buffer, (long) (object) value); + else if (typeof(TValue) == typeof(ulong)) + BinaryPrimitives.WriteUInt64BigEndian(buffer, (ulong) (object) value); + else if (typeof(TValue) == typeof(byte)) + buffer[0] = (byte) (object) value; + else if (typeof(TValue) == typeof(byte[])) + { + var source = (byte[]) (object) value; + + var length = Math.Min(Math.Min(source.Length, buffer.Length), address.Length); + + source.AsSpan(0, length).CopyTo(buffer); + } + else if (typeof(TValue) == typeof(float)) + { + var map = new UInt32SingleMap + { + Single = (float) (object) value + }; + + BinaryPrimitives.WriteUInt32BigEndian(buffer, map.UInt32); + } + else if (typeof(TValue) == typeof(string)) + { + if (value is not string stringValue) throw new ArgumentException("Value must be of type string", nameof(value)); + + // Todo: Serialize directly to Span, when upgrading to .net + var stringBytes = Encoding.ASCII.GetBytes(stringValue); + + var length = Math.Min(address.Length, stringValue.Length); + + int stringOffset; + if (address.Type == DbType.String) + { + stringOffset = 2; + buffer[0] = (byte) address.Length; + buffer[1] = (byte) length; + } + else + stringOffset = 0; + + stringBytes.AsSpan(0, length).CopyTo(buffer.Slice(stringOffset)); + } + else + { + throw new InvalidOperationException($"type '{typeof(TValue)}' not supported."); + } } [StructLayout(LayoutKind.Explicit)] diff --git a/Sharp7.Rx/S7VariableAddress.cs b/Sharp7.Rx/S7VariableAddress.cs index ae5e440..b12d55f 100644 --- a/Sharp7.Rx/S7VariableAddress.cs +++ b/Sharp7.Rx/S7VariableAddress.cs @@ -12,4 +12,6 @@ internal class S7VariableAddress public ushort Length { get; set; } public byte Bit { get; set; } public DbType Type { get; set; } + + public ushort BufferLength => Type == DbType.String ? (ushort)(Length + 2) : Length; }