Classes with arrays can now be read/written from/to the plc

This commit is contained in:
Thomas Bargetz
2017-08-07 11:51:58 +02:00
parent db229de529
commit 07fe703b3d
6 changed files with 365 additions and 173 deletions

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace S7.UnitTest.Helpers
{
public class TestClassWithArrays
{
public bool Bool { get; set; }
public bool[] BoolValues { get; set; } = new bool[2];
public bool[] Fillers { get; set; } = new bool[13];
public short[] Shorts { get; set; } = new short[2];
public ushort[] UShorts { get; set; } = new ushort[2];
public int[] Ints { get; set; } = new int[2];
public double[] Doubles { get; set; } = new double[2];
public short Short { get; set; }
public ushort UShort { get; set; }
public int Int { get; set; }
public double Double { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace S7.UnitTest.Helpers
{
public class CustomType
{
public bool[] Bools { get; set; } = new bool[2];
public bool[] Fillers { get; set; } = new bool[14];
}
public class TestClassWithCustomType
{
public int Int { get; set; }
public CustomType CustomType { get; set; }
public CustomType[] CustomTypes { get; set; } = new CustomType[2];
}
}

View File

@@ -56,6 +56,8 @@
<Compile Include="Helpers\ConsoleManager.cs" />
<Compile Include="Helpers\NativeMethods.cs" />
<Compile Include="Helpers\S7TestServer.cs" />
<Compile Include="Helpers\TestClassWithArrays.cs" />
<Compile Include="Helpers\TestClassWithCustomType.cs" />
<Compile Include="Helpers\TestClassWithPrivateSetters.cs" />
<Compile Include="Helpers\TestLongClass.cs" />
<Compile Include="Snap7\snap7.net.cs" />

View File

@@ -656,7 +656,7 @@ namespace S7.Net.UnitTest
tc.DWordVariable = 850;
plc.WriteClass(tc, DB2);
int expectedReadBytes = Types.Class.GetClassSize(tc.GetType());
int expectedReadBytes = 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
@@ -664,6 +664,77 @@ namespace S7.Net.UnitTest
Assert.AreEqual(expectedReadBytes, actualReadBytes);
}
[TestMethod]
public void T22_ReadClassWithArray()
{
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
TestClassWithArrays tc = new TestClassWithArrays();
tc.Bool = true;
tc.BoolValues[1] = true;
tc.Int = int.MinValue;
tc.Ints[0] = int.MinValue;
tc.Ints[1] = int.MaxValue;
tc.Short = short.MinValue;
tc.Shorts[0] = short.MinValue;
tc.Shorts[1] = short.MaxValue;
tc.Double = float.MinValue;
tc.Doubles[0] = float.MinValue + 1;
tc.Doubles[1] = float.MaxValue;
tc.UShort = ushort.MinValue + 1;
tc.UShorts[0] = ushort.MinValue + 1;
tc.UShorts[1] = ushort.MaxValue;
plc.WriteClass(tc, DB2);
TestClassWithArrays tc2 = plc.ReadClass<TestClassWithArrays>(DB2);
Assert.AreEqual(tc.Bool, tc2.Bool);
Assert.AreEqual(tc.BoolValues[0], tc2.BoolValues[0]);
Assert.AreEqual(tc.BoolValues[1], tc2.BoolValues[1]);
Assert.AreEqual(tc.Int, tc2.Int);
Assert.AreEqual(tc.Ints[0], tc2.Ints[0]);
Assert.AreEqual(tc.Ints[1], tc.Ints[1]);
Assert.AreEqual(tc.Short, tc2.Short);
Assert.AreEqual(tc.Shorts[0], tc2.Shorts[0]);
Assert.AreEqual(tc.Shorts[1], tc2.Shorts[1]);
Assert.AreEqual(tc.Double, tc2.Double);
Assert.AreEqual(tc.Doubles[0], tc2.Doubles[0]);
Assert.AreEqual(tc.Doubles[1], tc2.Doubles[1]);
Assert.AreEqual(tc.UShort, tc2.UShort);
Assert.AreEqual(tc.UShorts[0], tc2.UShorts[0]);
Assert.AreEqual(tc.UShorts[1], tc2.UShorts[1]);
}
[TestMethod]
public void T22_ReadClassWithArrayAndCustomType()
{
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
TestClassWithCustomType tc = new TestClassWithCustomType();
tc.Int = int.MinValue;
tc.CustomType = new CustomType();
tc.CustomType.Bools[1] = true;
tc.CustomTypes[0] = new CustomType();
tc.CustomTypes[1] = new CustomType();
tc.CustomTypes[0].Bools[0] = true;
tc.CustomTypes[1].Bools[1] = true;
plc.WriteClass(tc, DB2);
TestClassWithCustomType tc2 = plc.ReadClass<TestClassWithCustomType>(DB2);
Assert.AreEqual(tc.Int, tc2.Int);
Assert.AreEqual(tc.CustomType.Bools[0], tc2.CustomType.Bools[0]);
Assert.AreEqual(tc.CustomType.Bools[1], tc2.CustomType.Bools[1]);
Assert.AreEqual(tc.CustomTypes[0].Bools[0], tc2.CustomTypes[0].Bools[0]);
Assert.AreEqual(tc.CustomTypes[0].Bools[1], tc2.CustomTypes[0].Bools[1]);
Assert.AreEqual(tc.CustomTypes[1].Bools[0], tc2.CustomTypes[1].Bools[0]);
Assert.AreEqual(tc.CustomTypes[1].Bools[1], tc2.CustomTypes[1].Bools[1]);
}
#endregion

View File

@@ -535,12 +535,16 @@ namespace S7.Net
/// <returns>The number of read bytes</returns>
public int ReadClass(object sourceClass, int db, int startByteAdr = 0)
{
Type classType = sourceClass.GetType();
int numBytes = Types.Class.GetClassSize(classType);
int numBytes = Types.Class.GetClassSize(sourceClass);
if(numBytes <= 0)
{
throw new Exception("The size of the class is less than 1 byte and therefore cannot be read");
}
// now read the package
var resultBytes = ReadBytes(DataType.DataBlock, db, startByteAdr, numBytes);
// and decode it
Types.Class.FromBytes(sourceClass, classType, resultBytes);
Types.Class.FromBytes(sourceClass, resultBytes);
return resultBytes.Length;
}

View File

@@ -25,158 +25,268 @@ namespace S7.Net.Types
}
private static double GetIncreasedNumberOfBytes(double startingNumberOfBytes, Type type)
{
double numBytes = startingNumberOfBytes;
switch (type.Name)
{
case "Boolean":
numBytes += 0.125;
break;
case "Byte":
numBytes = Math.Ceiling(numBytes);
numBytes++;
break;
case "Int16":
case "UInt16":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
numBytes += 2;
break;
case "Int32":
case "UInt32":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
numBytes += 4;
break;
case "Float":
case "Double":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
numBytes += 4;
break;
default:
var propertyClass = Activator.CreateInstance(type);
numBytes += GetClassSize(propertyClass);
break;
}
return numBytes;
}
/// <summary>
/// Gets the size of the class in bytes.
/// </summary>
/// <param name="classType">the type of the class</param>
/// <param name="instance">An instance of the class</param>
/// <returns>the number of bytes</returns>
public static int GetClassSize(Type classType)
public static int GetClassSize(object instance)
{
double numBytes = 0.0;
var properties = GetAccessableProperties(classType);
var properties = GetAccessableProperties(instance.GetType());
foreach (var property in properties)
{
switch (property.PropertyType.Name)
if (property.PropertyType.IsArray)
{
case "Boolean":
numBytes += 0.125;
break;
case "Byte":
numBytes = Math.Ceiling(numBytes);
numBytes++;
break;
case "Int16":
case "UInt16":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
numBytes += 2;
break;
case "Int32":
case "UInt32":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
numBytes += 4;
break;
case "Float":
case "Double":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
numBytes += 4;
break;
default:
numBytes += GetClassSize(property.PropertyType);
break;
Type elementType = property.PropertyType.GetElementType();
Array array = (Array)property.GetValue(instance, null);
if (array.Length <= 0)
{
throw new Exception("Cannot determine size of class, because an array is defined which has no fixed size greater than zero.");
}
for (int i = 0; i < array.Length; i++)
{
numBytes = GetIncreasedNumberOfBytes(numBytes, elementType);
}
}
else
{
numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType);
}
}
return (int)numBytes;
}
private static object GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes)
{
object value = null;
switch (propertyType.Name)
{
case "Boolean":
// get the value
int bytePos = (int)Math.Floor(numBytes);
int bitPos = (int)((numBytes - (double)bytePos) / 0.125);
if ((bytes[bytePos] & (int)Math.Pow(2, bitPos)) != 0)
value = true;
else
value = false;
numBytes += 0.125;
break;
case "Byte":
numBytes = Math.Ceiling(numBytes);
value = (byte)(bytes[(int)numBytes]);
numBytes++;
break;
case "Int16":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
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++;
// 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++;
// hier auswerten
uint sourceUInt = DWord.FromBytes(bytes[(int)numBytes + 3],
bytes[(int)numBytes + 2],
bytes[(int)numBytes + 1],
bytes[(int)numBytes + 0]);
value = sourceUInt.ConvertToInt();
numBytes += 4;
break;
case "UInt32":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
value = DWord.FromBytes(
bytes[(int)numBytes],
bytes[(int)numBytes + 1],
bytes[(int)numBytes + 2],
bytes[(int)numBytes + 3]);
numBytes += 4;
break;
case "Double":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
value = Double.FromByteArray(
new byte[] {
bytes[(int)numBytes],
bytes[(int)numBytes + 1],
bytes[(int)numBytes + 2],
bytes[(int)numBytes + 3] });
numBytes += 4;
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;
}
break;
}
return value;
}
/// <summary>
/// Sets the object's values with the given array of bytes
/// </summary>
/// <param name="sourceClass">The object to fill in the given array of bytes</param>
/// <param name="classType">The class type</param>
/// <param name="bytes">The array of bytes</param>
public static void FromBytes(object sourceClass, Type classType, byte[] bytes)
public static void FromBytes(object sourceClass, byte[] bytes)
{
if (bytes == null)
return;
if (bytes.Length != GetClassSize(classType))
if (bytes.Length != GetClassSize(sourceClass))
return;
// and decode it
int bytePos = 0;
int bitPos = 0;
double numBytes = 0.0;
var properties = GetAccessableProperties(classType);
var properties = GetAccessableProperties(sourceClass.GetType());
foreach (var property in properties)
{
switch (property.PropertyType.Name)
if (property.PropertyType.IsArray)
{
case "Boolean":
// get the value
bytePos = (int)Math.Floor(numBytes);
bitPos = (int)((numBytes - (double)bytePos) / 0.125);
if ((bytes[bytePos] & (int)Math.Pow(2, bitPos)) != 0)
property.SetValue(sourceClass, true, null);
else
property.SetValue(sourceClass, false, null);
numBytes += 0.125;
break;
case "Byte":
numBytes = Math.Ceiling(numBytes);
property.SetValue(sourceClass, (byte)(bytes[(int)numBytes]), null);
numBytes++;
break;
case "Int16":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]);
property.SetValue(sourceClass, source.ConvertToShort(), null);
numBytes += 2;
break;
case "UInt16":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
property.SetValue(sourceClass, Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]), null);
numBytes += 2;
break;
case "Int32":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
uint sourceUInt = DWord.FromBytes(bytes[(int)numBytes + 3],
bytes[(int)numBytes + 2],
bytes[(int)numBytes + 1],
bytes[(int)numBytes + 0]);
property.SetValue(sourceClass, sourceUInt.ConvertToInt(), null);
numBytes += 4;
break;
case "UInt32":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
property.SetValue(sourceClass, DWord.FromBytes(bytes[(int)numBytes],
bytes[(int)numBytes + 1],
bytes[(int)numBytes + 2],
bytes[(int)numBytes + 3]), null);
numBytes += 4;
break;
case "Double":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
property.SetValue(sourceClass, Double.FromByteArray(new byte[] { bytes[(int)numBytes],
bytes[(int)numBytes + 1],
bytes[(int)numBytes + 2],
bytes[(int)numBytes + 3] }), null);
numBytes += 4;
break;
default:
var buffer = new byte[GetClassSize(property.PropertyType)];
if (buffer.Length == 0)
continue;
Buffer.BlockCopy(bytes, (int)Math.Ceiling(numBytes), buffer, 0, buffer.Length);
var propClass = Activator.CreateInstance(property.PropertyType);
FromBytes(propClass, property.PropertyType, buffer);
property.SetValue(sourceClass, propClass, null);
numBytes += buffer.Length;
break;
Array array = (Array)property.GetValue(sourceClass, null);
Type elementType = property.PropertyType.GetElementType();
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
{
array.SetValue(
GetPropertyValue(elementType, bytes, ref numBytes),
i);
}
}
else
{
property.SetValue(
sourceClass,
GetPropertyValue(property.PropertyType, bytes, ref numBytes),
null);
}
}
}
private static void ToBytes(Type propertyType, object propertyValue, byte[] bytes, ref double numBytes)
{
int bytePos = 0;
int bitPos = 0;
byte[] bytes2 = null;
switch (propertyType.Name)
{
case "Boolean":
// get the value
bytePos = (int)Math.Floor(numBytes);
bitPos = (int)((numBytes - (double)bytePos) / 0.125);
if ((bool)propertyValue)
bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true
else
bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false
numBytes += 0.125;
break;
case "Byte":
numBytes = (int)Math.Ceiling(numBytes);
bytePos = (int)numBytes;
bytes[bytePos] = (byte)propertyValue;
numBytes++;
break;
case "Int16":
bytes2 = Int.ToByteArray((Int16)propertyValue);
break;
case "UInt16":
bytes2 = Word.ToByteArray((UInt16)propertyValue);
break;
case "Int32":
bytes2 = DInt.ToByteArray((Int32)propertyValue);
break;
case "UInt32":
bytes2 = DWord.ToByteArray((UInt32)propertyValue);
break;
case "Double":
bytes2 = Double.ToByteArray((double)propertyValue);
break;
default:
bytes2 = ToBytes(propertyValue);
break;
}
if (bytes2 != null)
{
// add them
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
bytePos = (int)numBytes;
for (int bCnt = 0; bCnt < bytes2.Length; bCnt++)
bytes[bytePos + bCnt] = bytes2[bCnt];
numBytes += bytes2.Length;
}
}
@@ -187,67 +297,28 @@ namespace S7.Net.Types
/// <returns>A byte array or null if fails.</returns>
public static byte[] ToBytes(object sourceClass)
{
Type type = sourceClass.GetType();
int size = GetClassSize(type);
int size = GetClassSize(sourceClass);
byte[] bytes = new byte[size];
byte[] bytes2 = null;
int bytePos = 0;
int bitPos = 0;
double numBytes = 0.0;
var properties = GetAccessableProperties(sourceClass.GetType());
foreach (var property in properties)
{
bytes2 = null;
switch (property.PropertyType.Name)
if (property.PropertyType.IsArray)
{
case "Boolean":
// get the value
bytePos = (int)Math.Floor(numBytes);
bitPos = (int)((numBytes - (double)bytePos) / 0.125);
if ((bool)property.GetValue(sourceClass, null))
bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true
else
bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false
numBytes += 0.125;
break;
case "Byte":
numBytes = (int)Math.Ceiling(numBytes);
bytePos = (int)numBytes;
bytes[bytePos] = (byte)property.GetValue(sourceClass, null);
numBytes++;
break;
case "Int16":
bytes2 = Int.ToByteArray((Int16)property.GetValue(sourceClass, null));
break;
case "UInt16":
bytes2 = Word.ToByteArray((UInt16)property.GetValue(sourceClass, null));
break;
case "Int32":
bytes2 = DInt.ToByteArray((Int32)property.GetValue(sourceClass, null));
break;
case "UInt32":
bytes2 = DWord.ToByteArray((UInt32)property.GetValue(sourceClass, null));
break;
case "Double":
bytes2 = Double.ToByteArray((double)property.GetValue(sourceClass, null));
break;
Array array = (Array)property.GetValue(sourceClass, null);
Type elementType = property.PropertyType.GetElementType();
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
{
ToBytes(elementType, array.GetValue(i), bytes, ref numBytes);
}
}
if (bytes2 != null)
else
{
// add them
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
bytePos = (int)numBytes;
for (int bCnt = 0; bCnt < bytes2.Length; bCnt++)
bytes[bytePos + bCnt] = bytes2[bCnt];
numBytes += bytes2.Length;
ToBytes(property.PropertyType, property.GetValue(sourceClass, null), bytes, ref numBytes);
}
}
return bytes;
}
}
}
}