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;