From 370fd6b3d9edebaf488c19d4099331f61ca5623f Mon Sep 17 00:00:00 2001 From: Adam Oleksy Date: Wed, 26 Sep 2018 13:20:21 +0200 Subject: [PATCH 1/2] Fix reading nested classes --- S7.Net.UnitTest/Helpers/S7TestServer.cs | 4 +- .../Helpers/TestClassWithNestedClass.cs | 51 +++++++++++++++++++ S7.Net.UnitTest/S7.Net.UnitTest.csproj | 1 + S7.Net.UnitTest/S7NetTestsAsync.cs | 24 ++++++++- S7.Net.UnitTest/S7NetTestsSync.cs | 26 +++++++++- S7.Net/PlcAsynchronous.cs | 2 +- S7.Net/PlcSynchronous.cs | 2 +- S7.Net/Types/Class.cs | 47 +++++++---------- 8 files changed, 123 insertions(+), 34 deletions(-) create mode 100644 S7.Net.UnitTest/Helpers/TestClassWithNestedClass.cs diff --git a/S7.Net.UnitTest/Helpers/S7TestServer.cs b/S7.Net.UnitTest/Helpers/S7TestServer.cs index e8d6cf4..8db9328 100644 --- a/S7.Net.UnitTest/Helpers/S7TestServer.cs +++ b/S7.Net.UnitTest/Helpers/S7TestServer.cs @@ -9,6 +9,7 @@ namespace S7.Net.UnitTest.Helpers static private byte[] DB1 = new byte[1024]; // Our DB1 static private byte[] DB2 = new byte[64000]; // Our DB2 static private byte[] DB3 = new byte[1024]; // Our DB3 + static private byte[] DB4 = new byte[6] { 3, 128, 1, 0, 197, 104 }; // Our DB4 private static S7Server.TSrvCallback TheEventCallBack; // <== Static var containig the callback private static S7Server.TSrvCallback TheReadCallBack; // <== Static var containig the callback @@ -36,9 +37,10 @@ namespace S7.Net.UnitTest.Helpers 1, // Its number is 1 (DB1) DB1, // Our buffer for DB1 DB1.Length); // Its size - // Do the same for DB2 and DB3 + // Do the same for DB2, DB3, and DB4 Server.RegisterArea(S7Server.srvAreaDB, 2, DB2, DB2.Length); Server.RegisterArea(S7Server.srvAreaDB, 3, DB3, DB3.Length); + Server.RegisterArea(S7Server.srvAreaDB, 4, DB4, DB4.Length); // Exclude read event to avoid the double report // Set the callbacks (using the static var to avoid the garbage collect) diff --git a/S7.Net.UnitTest/Helpers/TestClassWithNestedClass.cs b/S7.Net.UnitTest/Helpers/TestClassWithNestedClass.cs new file mode 100644 index 0000000..0d5d417 --- /dev/null +++ b/S7.Net.UnitTest/Helpers/TestClassWithNestedClass.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace S7.Net.UnitTest.Helpers +{ + class TestClassInnerWithBool + { + public bool BitVariable00 { get; set; } + } + + class TestClassInnerWithByte + { + public byte ByteVariable00 { get; set; } + } + + class TestClassInnerWithShort + { + public short ShortVarialbe00 { get; set; } + } + + class TestClassWithNestedClass + { + /// + /// DB1.DBX0.0 + /// + public bool BitVariable00 { get; set; } + + /// + /// DB1.DBX0.1 + /// + public TestClassInnerWithBool BitVariable01 { get; set; } = new TestClassInnerWithBool(); + + /// + /// DB1.DBB1.0 + /// + public TestClassInnerWithByte ByteVariable02 { get; set; } = new TestClassInnerWithByte(); + + /// + /// DB1.DBX2.0 + /// + public bool BitVariable03 { get; set; } + + /// + /// DB1.DBW4 + /// + public TestClassInnerWithShort ShortVariable04 { get; set; } = new TestClassInnerWithShort(); + } +} diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index cbe6c47..dabda6d 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -62,6 +62,7 @@ + diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs index 0adb91b..31f99eb 100644 --- a/S7.Net.UnitTest/S7NetTestsAsync.cs +++ b/S7.Net.UnitTest/S7NetTestsAsync.cs @@ -663,6 +663,28 @@ namespace S7.Net.UnitTest } } + [TestMethod] + public async Task Test_Async_ReadClassWithNestedClassAfterBit() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + Assert.AreEqual(6, Types.Class.GetClassSize(new TestClassWithNestedClass())); + + TestClassWithNestedClass tc = new TestClassWithNestedClass(); + tc.BitVariable00 = true; + tc.BitVariable01.BitVariable00 = true; + tc.ByteVariable02.ByteVariable00 = 128; + tc.BitVariable03 = true; + tc.ShortVariable04.ShortVarialbe00 = -15000; + + TestClassWithNestedClass tc2 = await plc.ReadClassAsync(DB4); + Assert.AreEqual(tc.BitVariable00, tc2.BitVariable00); + Assert.AreEqual(tc.BitVariable01.BitVariable00, tc2.BitVariable01.BitVariable00); + Assert.AreEqual(tc.ByteVariable02.ByteVariable00, tc2.ByteVariable02.ByteVariable00); + Assert.AreEqual(tc.BitVariable03, tc2.BitVariable03); + Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00); + } + [TestMethod] [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadStructThrowsExceptionPlcIsNotConnected() @@ -737,7 +759,7 @@ namespace S7.Net.UnitTest }; plc.WriteClass(tc, DB2); - int expectedReadBytes = Types.Class.GetClassSize(tc); + int expectedReadBytes = (int)Types.Class.GetClassSize(tc); TestClass tc2 = new TestClass(); // Values that are read from a class are stored inside the class itself, that is passed by reference diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs index a3b01c9..1b648f2 100644 --- a/S7.Net.UnitTest/S7NetTestsSync.cs +++ b/S7.Net.UnitTest/S7NetTestsSync.cs @@ -40,6 +40,7 @@ namespace S7.Net.UnitTest { #region Constants const int DB2 = 2; + const int DB4 = 4; #endregion #region Private fields @@ -694,6 +695,29 @@ namespace S7.Net.UnitTest } } + [TestMethod] + public void T31_ReadClassWithNestedClassAfterBit() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + Assert.AreEqual(6, Types.Class.GetClassSize(new TestClassWithNestedClass())); + + TestClassWithNestedClass tc = new TestClassWithNestedClass(); + tc.BitVariable00 = true; + tc.BitVariable01.BitVariable00 = true; + tc.ByteVariable02.ByteVariable00 = 128; + tc.BitVariable03 = true; + tc.ShortVariable04.ShortVarialbe00 = -15000; + + TestClassWithNestedClass tc2 = new TestClassWithNestedClass(); + plc.ReadClass(tc2, DB4); + Assert.AreEqual(tc.BitVariable00, tc2.BitVariable00); + Assert.AreEqual(tc.BitVariable01.BitVariable00, tc2.BitVariable01.BitVariable00); + Assert.AreEqual(tc.ByteVariable02.ByteVariable00, tc2.ByteVariable02.ByteVariable00); + Assert.AreEqual(tc.BitVariable03, tc2.BitVariable03); + Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00); + } + [TestMethod, ExpectedException(typeof(PlcException))] public void T18_ReadStructThrowsIfPlcIsNotConnected() { @@ -767,7 +791,7 @@ namespace S7.Net.UnitTest tc.DWordVariable = 850; plc.WriteClass(tc, DB2); - int expectedReadBytes = Types.Class.GetClassSize(tc); + int expectedReadBytes = (int)Types.Class.GetClassSize(tc); TestClass tc2 = new TestClass(); // Values that are read from a class are stored inside the class itself, that is passed by reference diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 38cb7c6..f3457ae 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -141,7 +141,7 @@ namespace S7.Net /// The number of read bytes public async Task> ReadClassAsync(object sourceClass, int db, int startByteAdr = 0) { - int numBytes = Class.GetClassSize(sourceClass); + int numBytes = (int)Class.GetClassSize(sourceClass); if (numBytes <= 0) { throw new Exception("The size of the class is less than 1 byte and therefore cannot be read"); diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index dd7030c..4c30636 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -162,7 +162,7 @@ namespace S7.Net /// The number of read bytes public int ReadClass(object sourceClass, int db, int startByteAdr = 0) { - int numBytes = Class.GetClassSize(sourceClass); + int numBytes = (int)Class.GetClassSize(sourceClass); if (numBytes <= 0) { throw new Exception("The size of the class is less than 1 byte and therefore cannot be read"); diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index 81f2c19..c1dc14f 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -25,10 +25,8 @@ namespace S7.Net.Types } - private static double GetIncreasedNumberOfBytes(double startingNumberOfBytes, Type type) + private static double GetIncreasedNumberOfBytes(double numBytes, Type type) { - double numBytes = startingNumberOfBytes; - switch (type.Name) { case "Boolean": @@ -61,7 +59,7 @@ namespace S7.Net.Types break; default: var propertyClass = Activator.CreateInstance(type); - numBytes += GetClassSize(propertyClass); + numBytes = GetClassSize(propertyClass, numBytes, true); break; } @@ -73,10 +71,8 @@ namespace S7.Net.Types /// /// An instance of the class /// the number of bytes - public static int GetClassSize(object instance) + public static double GetClassSize(object instance, double numBytes = 0.0, bool isInnerProperty = false) { - double numBytes = 0.0; - var properties = GetAccessableProperties(instance.GetType()); foreach (var property in properties) { @@ -99,11 +95,14 @@ namespace S7.Net.Types numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType); } } - // enlarge numBytes to next even number because S7-Structs in a DB always will be resized to an even byte count - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; - return (int)numBytes; + if (false == isInnerProperty) + { + // enlarge numBytes to next even number because S7-Structs in a DB always will be resized to an even byte count + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + } + return numBytes; } private static object GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes) @@ -196,14 +195,8 @@ namespace S7.Net.Types break; default: var propClass = Activator.CreateInstance(propertyType); - var buffer = new byte[GetClassSize(propClass)]; - if (buffer.Length > 0) - { - Buffer.BlockCopy(bytes, (int)Math.Ceiling(numBytes), buffer, 0, buffer.Length); - FromBytes(propClass, buffer); - value = propClass; - numBytes += buffer.Length; - } + numBytes = FromBytes(propClass, bytes, numBytes); + value = propClass; break; } @@ -215,16 +208,10 @@ namespace S7.Net.Types /// /// The object to fill in the given array of bytes /// The array of bytes - public static void FromBytes(object sourceClass, byte[] bytes) + public static double FromBytes(object sourceClass, byte[] bytes, double numBytes = 0, bool isInnerClass = false) { if (bytes == null) - return; - - if (bytes.Length != GetClassSize(sourceClass)) - return; - - // and decode it - double numBytes = 0.0; + return numBytes; var properties = GetAccessableProperties(sourceClass.GetType()); foreach (var property in properties) @@ -248,6 +235,8 @@ namespace S7.Net.Types null); } } + + return numBytes; } private static void ToBytes(object propertyValue, byte[] bytes, ref double numBytes) @@ -317,7 +306,7 @@ namespace S7.Net.Types /// A byte array or null if fails. public static byte[] ToBytes(object sourceClass) { - int size = GetClassSize(sourceClass); + int size = (int)GetClassSize(sourceClass); byte[] bytes = new byte[size]; double numBytes = 0.0; From de084394a682fee21728290aa8c9d0cef68a599a Mon Sep 17 00:00:00 2001 From: Adam Oleksy Date: Wed, 26 Sep 2018 14:38:12 +0200 Subject: [PATCH 2/2] Fix writing nested classes --- S7.Net.UnitTest/S7NetTestsAsync.cs | 25 +++++++++++++++++++++++++ S7.Net.UnitTest/S7NetTestsSync.cs | 25 +++++++++++++++++++++++++ S7.Net/PlcAsynchronous.cs | 5 +++-- S7.Net/Types/Class.cs | 18 ++++++++---------- 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs index 31f99eb..86d7f22 100644 --- a/S7.Net.UnitTest/S7NetTestsAsync.cs +++ b/S7.Net.UnitTest/S7NetTestsAsync.cs @@ -160,6 +160,31 @@ namespace S7.Net.UnitTest Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable); } + [TestMethod] + public async Task Test_Async_ReadAndWriteNestedClass() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + TestClassWithNestedClass tc = new TestClassWithNestedClass + { + BitVariable00 = true, + BitVariable01 = new TestClassInnerWithBool { BitVariable00 = true }, + ByteVariable02 = new TestClassInnerWithByte { ByteVariable00 = 128 }, + BitVariable03 = true, + ShortVariable04 = new TestClassInnerWithShort { ShortVarialbe00 = -15000 } + }; + + await plc.WriteClassAsync(tc, DB4); + TestClassWithNestedClass tc2 = new TestClassWithNestedClass(); + // Values that are read from a class are stored inside the class itself, that is passed by reference + await plc.ReadClassAsync(tc2, DB4); + Assert.AreEqual(tc.BitVariable00, tc2.BitVariable00); + Assert.AreEqual(tc.BitVariable01.BitVariable00, tc2.BitVariable01.BitVariable00); + Assert.AreEqual(tc.ByteVariable02.ByteVariable00, tc2.ByteVariable02.ByteVariable00); + Assert.AreEqual(tc.BitVariable03, tc2.BitVariable03); + Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00); + } + /// /// Read/Write a struct that has the same properties of a DB with the same field in the same order /// diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs index 1b648f2..c096643 100644 --- a/S7.Net.UnitTest/S7NetTestsSync.cs +++ b/S7.Net.UnitTest/S7NetTestsSync.cs @@ -718,6 +718,31 @@ namespace S7.Net.UnitTest Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00); } + [TestMethod] + public void T32_ReadAndWriteNestedClass() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + TestClassWithNestedClass tc = new TestClassWithNestedClass + { + BitVariable00 = true, + BitVariable01 = new TestClassInnerWithBool { BitVariable00 = true }, + ByteVariable02 = new TestClassInnerWithByte { ByteVariable00 = 128 }, + BitVariable03 = true, + ShortVariable04 = new TestClassInnerWithShort { ShortVarialbe00 = -15000 } + }; + + plc.WriteClass(tc, DB4); + TestClassWithNestedClass tc2 = new TestClassWithNestedClass(); + // Values that are read from a class are stored inside the class itself, that is passed by reference + plc.ReadClass(tc2, DB4); + Assert.AreEqual(tc.BitVariable00, tc2.BitVariable00); + Assert.AreEqual(tc.BitVariable01.BitVariable00, tc2.BitVariable01.BitVariable00); + Assert.AreEqual(tc.ByteVariable02.ByteVariable00, tc2.ByteVariable02.ByteVariable00); + Assert.AreEqual(tc.BitVariable03, tc2.BitVariable03); + Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00); + } + [TestMethod, ExpectedException(typeof(PlcException))] public void T18_ReadStructThrowsIfPlcIsNotConnected() { diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index f3457ae..1c28503 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -370,8 +370,9 @@ namespace S7.Net /// A task that represents the asynchronous write operation. public async Task WriteClassAsync(object classValue, int db, int startByteAdr = 0) { - var bytes = Types.Class.ToBytes(classValue).ToList(); - await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray()); + byte[] bytes = new byte[(int)Class.GetClassSize(classValue)]; + Types.Class.ToBytes(classValue, bytes); + await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes); } private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, int count) diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index c1dc14f..227a521 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -239,7 +239,7 @@ namespace S7.Net.Types return numBytes; } - private static void ToBytes(object propertyValue, byte[] bytes, ref double numBytes) + private static double SetBytesFromProperty(object propertyValue, byte[] bytes, double numBytes) { int bytePos = 0; int bitPos = 0; @@ -282,7 +282,7 @@ namespace S7.Net.Types bytes2 = Single.ToByteArray((float)propertyValue); break; default: - bytes2 = ToBytes(propertyValue); + numBytes = ToBytes(propertyValue, bytes, numBytes); break; } @@ -297,6 +297,8 @@ namespace S7.Net.Types bytes[bytePos + bCnt] = bytes2[bCnt]; numBytes += bytes2.Length; } + + return numBytes; } /// @@ -304,12 +306,8 @@ namespace S7.Net.Types /// /// The struct object /// A byte array or null if fails. - public static byte[] ToBytes(object sourceClass) + public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = 0.0) { - int size = (int)GetClassSize(sourceClass); - byte[] bytes = new byte[size]; - double numBytes = 0.0; - var properties = GetAccessableProperties(sourceClass.GetType()); foreach (var property in properties) { @@ -319,15 +317,15 @@ namespace S7.Net.Types Type elementType = property.PropertyType.GetElementType(); for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) { - ToBytes(array.GetValue(i), bytes, ref numBytes); + numBytes = SetBytesFromProperty(array.GetValue(i), bytes, numBytes); } } else { - ToBytes(property.GetValue(sourceClass, null), bytes, ref numBytes); + numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), bytes, numBytes); } } - return bytes; + return numBytes; } } }