using System; using System.Linq; using System.Reflection; namespace S7.Net.Types { /// /// Contains the method to convert a C# struct to S7 data types /// public static class Struct { /// /// Gets the size of the struct in bytes. /// /// the type of the struct /// the number of bytes public static int GetStructSize(Type structType) { double numBytes = 0.0; var infos = structType #if NETSTANDARD1_3 .GetTypeInfo().DeclaredFields; #else .GetFields(); #endif foreach (var info in infos) { switch (info.FieldType.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": case "TimeSpan": numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; numBytes += 4; break; case "Single": numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; numBytes += 4; break; case "Double": numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; numBytes += 8; break; case "String": S7StringAttribute? attribute = info.GetCustomAttributes().SingleOrDefault(); if (attribute == default(S7StringAttribute)) throw new ArgumentException("Please add S7StringAttribute to the string field"); numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; numBytes += attribute.ReservedLengthInBytes; break; default: numBytes += GetStructSize(info.FieldType); break; } } return (int)numBytes; } /// /// Creates a struct of a specified type by an array of bytes. /// /// The struct type /// The array of bytes /// The object depending on the struct type or null if fails(array-length != struct-length public static object? FromBytes(Type structType, byte[] bytes) { if (bytes == null) return null; if (bytes.Length != GetStructSize(structType)) return null; // and decode it int bytePos = 0; int bitPos = 0; double numBytes = 0.0; object structValue = Activator.CreateInstance(structType) ?? throw new ArgumentException($"Failed to create an instance of the type {structType}.", nameof(structType)); var infos = structValue.GetType() #if NETSTANDARD1_3 .GetTypeInfo().DeclaredFields; #else .GetFields(); #endif foreach (var info in infos) { switch (info.FieldType.Name) { 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) info.SetValue(structValue, true); else info.SetValue(structValue, false); numBytes += 0.125; break; case "Byte": numBytes = Math.Ceiling(numBytes); info.SetValue(structValue, (byte)(bytes[(int)numBytes])); numBytes++; break; case "Int16": numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; // get the value ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]); info.SetValue(structValue, source.ConvertToShort()); numBytes += 2; break; case "UInt16": numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; // get the value info.SetValue(structValue, 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++; // get the value uint sourceUInt = DWord.FromBytes(bytes[(int)numBytes + 3], bytes[(int)numBytes + 2], bytes[(int)numBytes + 1], bytes[(int)numBytes + 0]); info.SetValue(structValue, sourceUInt.ConvertToInt()); numBytes += 4; break; case "UInt32": numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; // get the value info.SetValue(structValue, DWord.FromBytes(bytes[(int)numBytes], bytes[(int)numBytes + 1], bytes[(int)numBytes + 2], bytes[(int)numBytes + 3])); numBytes += 4; break; case "Single": numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; // get the value info.SetValue(structValue, Real.FromByteArray(new byte[] { 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++; // get the value var data = new byte[8]; Array.Copy(bytes, (int)numBytes, data, 0, 8); info.SetValue(structValue, LReal.FromByteArray(data)); numBytes += 8; break; case "String": S7StringAttribute? attribute = info.GetCustomAttributes().SingleOrDefault(); if (attribute == default(S7StringAttribute)) throw new ArgumentException("Please add S7StringAttribute to the string field"); numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; // get the value var sData = new byte[attribute.ReservedLengthInBytes]; Array.Copy(bytes, (int)numBytes, sData, 0, sData.Length); switch (attribute.Type) { case S7StringType.S7String: info.SetValue(structValue, S7String.FromByteArray(sData)); break; case S7StringType.S7WString: info.SetValue(structValue, S7WString.FromByteArray(sData)); break; default: throw new ArgumentException("Please use a valid string type for the S7StringAttribute"); } numBytes += sData.Length; break; case "TimeSpan": numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; // get the value info.SetValue(structValue, TimeSpan.FromByteArray(new[] { bytes[(int)numBytes + 0], bytes[(int)numBytes + 1], bytes[(int)numBytes + 2], bytes[(int)numBytes + 3] })); numBytes += 4; break; default: var buffer = new byte[GetStructSize(info.FieldType)]; if (buffer.Length == 0) continue; Buffer.BlockCopy(bytes, (int)Math.Ceiling(numBytes), buffer, 0, buffer.Length); info.SetValue(structValue, FromBytes(info.FieldType, buffer)); numBytes += buffer.Length; break; } } return structValue; } /// /// Creates a byte array depending on the struct type. /// /// The struct object /// A byte array or null if fails. public static byte[] ToBytes(object structValue) { Type type = structValue.GetType(); int size = Struct.GetStructSize(type); byte[] bytes = new byte[size]; byte[]? bytes2 = null; int bytePos = 0; int bitPos = 0; double numBytes = 0.0; var infos = type #if NETSTANDARD1_3 .GetTypeInfo().DeclaredFields; #else .GetFields(); #endif foreach (var info in infos) { static TValue GetValueOrThrow(FieldInfo fi, object structValue) where TValue : struct { var value = fi.GetValue(structValue) as TValue? ?? throw new ArgumentException($"Failed to convert value of field {fi.Name} of {structValue} to type {typeof(TValue)}"); return value; } bytes2 = null; switch (info.FieldType.Name) { case "Boolean": // get the value bytePos = (int)Math.Floor(numBytes); bitPos = (int)((numBytes - (double)bytePos) / 0.125); if (GetValueOrThrow(info, structValue)) 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] = GetValueOrThrow(info, structValue); numBytes++; break; case "Int16": bytes2 = Int.ToByteArray(GetValueOrThrow(info, structValue)); break; case "UInt16": bytes2 = Word.ToByteArray(GetValueOrThrow(info, structValue)); break; case "Int32": bytes2 = DInt.ToByteArray(GetValueOrThrow(info, structValue)); break; case "UInt32": bytes2 = DWord.ToByteArray(GetValueOrThrow(info, structValue)); break; case "Single": bytes2 = Real.ToByteArray(GetValueOrThrow(info, structValue)); break; case "Double": bytes2 = LReal.ToByteArray(GetValueOrThrow(info, structValue)); break; case "String": S7StringAttribute? attribute = info.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?)info.GetValue(structValue), attribute.ReservedLength), S7StringType.S7WString => S7WString.ToByteArray((string?)info.GetValue(structValue), attribute.ReservedLength), _ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute") }; break; case "TimeSpan": bytes2 = TimeSpan.ToByteArray((System.TimeSpan)info.GetValue(structValue)); 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; } } return bytes; } } }