From 142f1ba90eed639e7b3a7063f6522b5a444de86d Mon Sep 17 00:00:00 2001 From: Mike Cremer <8211507+MCPC10@users.noreply.github.com> Date: Sat, 10 Dec 2022 18:59:25 +0100 Subject: [PATCH] Added support string/wstring for class type --- S7.Net.UnitTest/Helpers/TestClass.cs | 13 ++++ S7.Net.UnitTest/S7NetTestsAsync.cs | 29 +++++++-- S7.Net.UnitTest/S7NetTestsSync.cs | 19 ++++++ S7.Net/Types/Class.cs | 97 +++++++++++++++++----------- S7.Net/Types/S7StringAttribute.cs | 2 +- 5 files changed, 115 insertions(+), 45 deletions(-) diff --git a/S7.Net.UnitTest/Helpers/TestClass.cs b/S7.Net.UnitTest/Helpers/TestClass.cs index c810adce..2f2c7750 100644 --- a/S7.Net.UnitTest/Helpers/TestClass.cs +++ b/S7.Net.UnitTest/Helpers/TestClass.cs @@ -1,4 +1,6 @@  +using S7.Net.Types; + namespace S7.Net.UnitTest.Helpers { class TestClass @@ -51,5 +53,16 @@ class TestClass /// DB1.DBD16 /// public ushort DWordVariable { get; set; } + + /// + /// DB1.DBX20.0 + /// + [S7String(S7StringType.S7WString, 10)] + public string WStringVariable { get; set; } + /// + /// DB1.DBX44.0 + /// + [S7String(S7StringType.S7String, 10)] + public string StringVariable { get; set; } } } diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs index f94ddb81..731e802f 100644 --- a/S7.Net.UnitTest/S7NetTestsAsync.cs +++ b/S7.Net.UnitTest/S7NetTestsAsync.cs @@ -7,6 +7,7 @@ using S7.UnitTest.Helpers; using System.Threading.Tasks; using System.Threading; +using System.Security.Cryptography; #endregion @@ -154,7 +155,9 @@ public async Task Test_Async_ReadAndWriteClass() IntVariable = -15000, LRealVariable = -154.789, RealVariable = -154.789f, - DWordVariable = 850 + DWordVariable = 850, + WStringVariable = "ÄÜÉÊéà", + StringVariable = "Hallo" }; await plc.WriteClassAsync(tc, DB2); @@ -168,6 +171,8 @@ public async Task Test_Async_ReadAndWriteClass() Assert.AreEqual(tc.LRealVariable, tc2.LRealVariable); Assert.AreEqual(tc.RealVariable, tc2.RealVariable); Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable); + Assert.AreEqual(tc.WStringVariable, tc2.WStringVariable); + Assert.AreEqual(tc.StringVariable, tc2.StringVariable); } [TestMethod] @@ -580,7 +585,9 @@ public async Task Test_Async_ReadClassIgnoresNonPublicSetters() IntVariable = -15000, LRealVariable = -154.789, RealVariable = -154.789f, - DWordVariable = 850 + DWordVariable = 850, + WStringVariable = "ÄÜÉÊéà", + StringVariable = "Hallo" }; await plc.WriteClassAsync(tc, DB2); @@ -628,7 +635,10 @@ public async Task Test_Async_ReadClassWithGenericReturnsSameResultAsReadClassWit IntVariable = -15000, LRealVariable = -154.789, RealVariable = -154.789f, - DWordVariable = 850 + DWordVariable = 850, + WStringVariable = "ÄÜÉÊéà", + StringVariable = "Hallo" + }; await plc.WriteClassAsync(tc, DB2); @@ -646,6 +656,9 @@ public async Task Test_Async_ReadClassWithGenericReturnsSameResultAsReadClassWit Assert.AreEqual(Math.Round(tc2.LRealVariable, 3), Math.Round(tc2Generic.LRealVariable, 3)); Assert.AreEqual(tc2.RealVariable, tc2Generic.RealVariable); Assert.AreEqual(tc2.DWordVariable, tc2Generic.DWordVariable); + Assert.AreEqual(tc2.WStringVariable, tc2Generic.WStringVariable); + Assert.AreEqual(tc2.StringVariable, tc2Generic.StringVariable); + } [TestMethod] @@ -671,7 +684,9 @@ public async Task Test_Async_ReadClassWithGenericAndClassFactoryReturnsSameResul IntVariable = -15000, LRealVariable = -154.789, RealVariable = -154.789f, - DWordVariable = 850 + DWordVariable = 850, + WStringVariable = "ÄÜÉÊéà", + StringVariable = "Hallo" }; await plc.WriteClassAsync(tc, DB2); @@ -686,6 +701,8 @@ public async Task Test_Async_ReadClassWithGenericAndClassFactoryReturnsSameResul Assert.AreEqual(Math.Round(tc2Generic.LRealVariable, 3), Math.Round(tc2GenericWithClassFactory.LRealVariable, 3)); Assert.AreEqual(tc2Generic.RealVariable, tc2GenericWithClassFactory.RealVariable); Assert.AreEqual(tc2Generic.DWordVariable, tc2GenericWithClassFactory.DWordVariable); + Assert.AreEqual(tc2Generic.WStringVariable, tc2GenericWithClassFactory.WStringVariable); + Assert.AreEqual(tc2Generic.StringVariable, tc2GenericWithClassFactory.StringVariable); } [TestMethod] @@ -792,7 +809,9 @@ public async Task Test_Async_ReadClassReturnsNumberOfReadBytesFromThePlc() IntVariable = -15000, LRealVariable = -154.789, RealVariable = -154.789f, - DWordVariable = 850 + DWordVariable = 850, + WStringVariable = "ÄÜÉÊéà", + StringVariable = "Hallo" }; plc.WriteClass(tc, DB2); diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs index c84c1a70..76317c0f 100644 --- a/S7.Net.UnitTest/S7NetTestsSync.cs +++ b/S7.Net.UnitTest/S7NetTestsSync.cs @@ -5,6 +5,7 @@ using S7.Net.UnitTest.Helpers; using S7.Net.Types; using S7.UnitTest.Helpers; +using System.Security.Cryptography; #endregion @@ -183,6 +184,9 @@ public void T04_ReadAndWriteClass() tc.LRealVariable = -154.789; tc.RealVariable = -154.789f; tc.DWordVariable = 850; + tc.WStringVariable = "ÄÜÉÊéà"; + tc.StringVariable = "Hallo"; + plc.WriteClass(tc, DB2); TestClass tc2 = new TestClass(); // Values that are read from a class are stored inside the class itself, that is passed by reference @@ -194,6 +198,8 @@ public void T04_ReadAndWriteClass() Assert.AreEqual(tc.LRealVariable, tc2.LRealVariable); Assert.AreEqual(tc.RealVariable, tc2.RealVariable); Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable); + Assert.AreEqual(tc.WStringVariable, tc2.WStringVariable); + Assert.AreEqual(tc.StringVariable, tc2.StringVariable); } /// @@ -577,6 +583,8 @@ public void T12_ReadClassIgnoresNonPublicSetters() tc.LRealVariable = -154.789; tc.RealVariable = -154.789f; tc.DWordVariable = 850; + tc.WStringVariable = "ÄÜÉÊéà"; + tc.StringVariable = "Hallo"; plc.WriteClass(tc, DB2); @@ -622,6 +630,8 @@ public void T14_ReadClassWithGenericReturnsSameResultAsReadClassWithoutGeneric() tc.LRealVariable = -154.789; tc.RealVariable = -154.789f; tc.DWordVariable = 850; + tc.WStringVariable = "ÄÜÉÊéà"; + tc.StringVariable = "Hallo"; plc.WriteClass(tc, DB2); @@ -637,6 +647,8 @@ public void T14_ReadClassWithGenericReturnsSameResultAsReadClassWithoutGeneric() Assert.AreEqual(Math.Round(tc2.LRealVariable, 3), Math.Round(tc2Generic.LRealVariable, 3)); Assert.AreEqual(tc2.RealVariable, tc2Generic.RealVariable); Assert.AreEqual(tc2.DWordVariable, tc2Generic.DWordVariable); + Assert.AreEqual(tc2.WStringVariable, tc2Generic.WStringVariable); + Assert.AreEqual(tc2.StringVariable, tc2Generic.StringVariable); } [TestMethod, ExpectedException(typeof(PlcException))] @@ -665,6 +677,8 @@ public void T16_ReadClassWithGenericAndClassFactoryReturnsSameResultAsReadClassW tc.LRealVariable = -154.789; tc.RealVariable = -154.789f; tc.DWordVariable = 850; + tc.WStringVariable = "ÄÜÉÊéà"; + tc.StringVariable = "Hallo"; plc.WriteClass(tc, DB2); @@ -679,6 +693,8 @@ public void T16_ReadClassWithGenericAndClassFactoryReturnsSameResultAsReadClassW Assert.AreEqual(Math.Round(tc2Generic.LRealVariable, 3), Math.Round(tc2GenericWithClassFactory.LRealVariable, 3)); Assert.AreEqual(tc2Generic.RealVariable, tc2GenericWithClassFactory.RealVariable); Assert.AreEqual(tc2Generic.DWordVariable, tc2GenericWithClassFactory.DWordVariable); + Assert.AreEqual(tc2Generic.WStringVariable, tc2GenericWithClassFactory.WStringVariable); + Assert.AreEqual(tc2Generic.StringVariable, tc2GenericWithClassFactory.StringVariable); } [TestMethod, ExpectedException(typeof(PlcException))] @@ -837,6 +853,9 @@ public void T21_ReadClassReturnsNumberOfReadBytesFromThePlc() tc.LRealVariable = -154.789; tc.RealVariable = -154.789f; tc.DWordVariable = 850; + tc.WStringVariable = "ÄÜÉÊéà"; + tc.StringVariable = "Hallo"; + plc.WriteClass(tc, DB2); int expectedReadBytes = (int)Types.Class.GetClassSize(tc); diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index 0dd78fa3..1aefc002 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; namespace S7.Net.Types { @@ -25,7 +26,7 @@ private static IEnumerable GetAccessableProperties(Type classType) } - private static double GetIncreasedNumberOfBytes(double numBytes, Type type) + private static double GetIncreasedNumberOfBytes(double numBytes, Type type, PropertyInfo? propertyInfo) { switch (type.Name) { @@ -38,30 +39,30 @@ private static double GetIncreasedNumberOfBytes(double numBytes, Type type) break; case "Int16": case "UInt16": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); numBytes += 2; break; case "Int32": case "UInt32": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); numBytes += 4; break; case "Single": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); numBytes += 4; break; case "Double": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); numBytes += 8; break; + case "String": + S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes().SingleOrDefault(); + if (attribute == default(S7StringAttribute)) + throw new ArgumentException("Please add S7StringAttribute to the string field"); + + IncrementToEven(ref numBytes); + numBytes += attribute.ReservedLengthInBytes; + break; default: var propertyClass = Activator.CreateInstance(type); numBytes = GetClassSize(propertyClass, numBytes, true); @@ -93,12 +94,12 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i IncrementToEven(ref numBytes); for (int i = 0; i < array.Length; i++) { - numBytes = GetIncreasedNumberOfBytes(numBytes, elementType); + numBytes = GetIncreasedNumberOfBytes(numBytes, elementType, property); } } else { - numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType); + numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType, property); } } if (false == isInnerProperty) @@ -111,7 +112,7 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i return numBytes; } - private static object? GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes) + private static object? GetPropertyValue(Type propertyType, PropertyInfo? propertyInfo, byte[] bytes, ref double numBytes) { object? value = null; @@ -133,26 +134,20 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i numBytes++; break; case "Int16": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); // hier auswerten ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]); value = source.ConvertToShort(); numBytes += 2; break; case "UInt16": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); // hier auswerten value = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]); numBytes += 2; break; case "Int32": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); // hier auswerten uint sourceUInt = DWord.FromBytes(bytes[(int)numBytes + 3], bytes[(int)numBytes + 2], @@ -162,9 +157,7 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i numBytes += 4; break; case "UInt32": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); // hier auswerten value = DWord.FromBytes( bytes[(int)numBytes], @@ -174,9 +167,7 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i numBytes += 4; break; case "Single": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); // hier auswerten value = Real.FromByteArray( new byte[] { @@ -187,15 +178,31 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i numBytes += 4; break; case "Double": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); var buffer = new byte[8]; Array.Copy(bytes, (int)numBytes, buffer, 0, 8); // hier auswerten value = LReal.FromByteArray(buffer); numBytes += 8; break; + case "String": + S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes().SingleOrDefault(); + if (attribute == default(S7StringAttribute)) + throw new ArgumentException("Please add S7StringAttribute to the string field"); + + IncrementToEven(ref numBytes); + + // get the value + var sData = new byte[attribute.ReservedLengthInBytes]; + Array.Copy(bytes, (int)numBytes, sData, 0, sData.Length); + value = attribute.Type switch + { + S7StringType.S7String => S7String.FromByteArray(sData), + S7StringType.S7WString => S7WString.FromByteArray(sData), + _ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute") + }; + numBytes += sData.Length; + break; default: var propClass = Activator.CreateInstance(propertyType); numBytes = FromBytes(propClass, bytes, numBytes); @@ -227,7 +234,7 @@ public static double FromBytes(object sourceClass, byte[] bytes, double numBytes for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) { array.SetValue( - GetPropertyValue(elementType, bytes, ref numBytes), + GetPropertyValue(elementType, property, bytes, ref numBytes), i); } } @@ -235,7 +242,7 @@ public static double FromBytes(object sourceClass, byte[] bytes, double numBytes { property.SetValue( sourceClass, - GetPropertyValue(property.PropertyType, bytes, ref numBytes), + GetPropertyValue(property.PropertyType, property, bytes, ref numBytes), null); } } @@ -243,7 +250,7 @@ public static double FromBytes(object sourceClass, byte[] bytes, double numBytes return numBytes; } - private static double SetBytesFromProperty(object propertyValue, byte[] bytes, double numBytes) + private static double SetBytesFromProperty(object propertyValue, PropertyInfo? propertyInfo, byte[] bytes, double numBytes) { int bytePos = 0; int bitPos = 0; @@ -285,6 +292,18 @@ private static double SetBytesFromProperty(object propertyValue, byte[] bytes, d case "Double": bytes2 = LReal.ToByteArray((double)propertyValue); break; + case "String": + S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes().SingleOrDefault(); + if (attribute == default(S7StringAttribute)) + throw new ArgumentException("Please add S7StringAttribute to the string field"); + + bytes2 = attribute.Type switch + { + S7StringType.S7String => S7String.ToByteArray((string)propertyValue, attribute.ReservedLength), + S7StringType.S7WString => S7WString.ToByteArray((string)propertyValue, attribute.ReservedLength), + _ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute") + }; + break; default: numBytes = ToBytes(propertyValue, bytes, numBytes); break; @@ -320,12 +339,12 @@ public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = Type elementType = property.PropertyType.GetElementType(); for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) { - numBytes = SetBytesFromProperty(array.GetValue(i), bytes, numBytes); + numBytes = SetBytesFromProperty(array.GetValue(i), property, bytes, numBytes); } } else { - numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), bytes, numBytes); + numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), property, bytes, numBytes); } } return numBytes; diff --git a/S7.Net/Types/S7StringAttribute.cs b/S7.Net/Types/S7StringAttribute.cs index 4d6e1075..768667d0 100644 --- a/S7.Net/Types/S7StringAttribute.cs +++ b/S7.Net/Types/S7StringAttribute.cs @@ -2,7 +2,7 @@ namespace S7.Net.Types { - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] public sealed class S7StringAttribute : Attribute { private readonly S7StringType type;