diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs index 5d598824..20338845 100644 --- a/S7.Net.UnitTest/S7NetTestsAsync.cs +++ b/S7.Net.UnitTest/S7NetTestsAsync.cs @@ -1006,7 +1006,7 @@ public async Task Test_Async_WriteLargeByteArrayWithCancellation() var db = 2; randomEngine.NextBytes(data); - cancellationSource.CancelAfter(TimeSpan.FromMilliseconds(5)); + cancellationSource.CancelAfter(System.TimeSpan.FromMilliseconds(5)); try { await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data, cancellationToken); @@ -1045,7 +1045,7 @@ public async Task Test_Async_WriteLargeByteArrayWithCancellationWithMemory() var db = 2; randomEngine.NextBytes(data.Span); - cancellationSource.CancelAfter(TimeSpan.FromMilliseconds(5)); + cancellationSource.CancelAfter(System.TimeSpan.FromMilliseconds(5)); try { await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data, cancellationToken); diff --git a/S7.Net.UnitTest/TypeTests/TimeSpanTests.cs b/S7.Net.UnitTest/TypeTests/TimeSpanTests.cs new file mode 100644 index 00000000..5f129fb4 --- /dev/null +++ b/S7.Net.UnitTest/TypeTests/TimeSpanTests.cs @@ -0,0 +1,82 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace S7.Net.UnitTest.TypeTests +{ + public static class TimeSpanTests + { + private static readonly TimeSpan SampleTimeSpan = new TimeSpan(12, 0, 59, 37, 856); + + private static readonly byte[] SampleByteArray = { 0x3E, 0x02, 0xE8, 0x00 }; + + private static readonly byte[] SpecMinByteArray = { 0x80, 0x00, 0x00, 0x00 }; + + private static readonly byte[] SpecMaxByteArray = { 0x7F, 0xFF, 0xFF, 0xFF }; + + [TestClass] + public class FromByteArray + { + [TestMethod] + public void Sample() + { + AssertFromByteArrayEquals(SampleTimeSpan, SampleByteArray); + } + + [TestMethod] + public void SpecMinimum() + { + AssertFromByteArrayEquals(Types.TimeSpan.SpecMinimumTimeSpan, SpecMinByteArray); + } + + [TestMethod] + public void SpecMaximum() + { + AssertFromByteArrayEquals(Types.TimeSpan.SpecMaximumTimeSpan, SpecMaxByteArray); + } + + private static void AssertFromByteArrayEquals(TimeSpan expected, params byte[] bytes) + { + Assert.AreEqual(expected, Types.TimeSpan.FromByteArray(bytes)); + } + } + + [TestClass] + public class ToByteArray + { + [TestMethod] + public void Sample() + { + AssertToByteArrayEquals(SampleTimeSpan, SampleByteArray); + } + + [TestMethod] + public void SpecMinimum() + { + AssertToByteArrayEquals(Types.TimeSpan.SpecMinimumTimeSpan, SpecMinByteArray); + } + + [TestMethod] + public void SpecMaximum() + { + AssertToByteArrayEquals(Types.TimeSpan.SpecMaximumTimeSpan, SpecMaxByteArray); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnTimeBeforeSpecMinimum() + { + Types.TimeSpan.ToByteArray(TimeSpan.FromDays(-25)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnTimeAfterSpecMaximum() + { + Types.TimeSpan.ToByteArray(new TimeSpan(30, 15, 15, 15, 15)); + } + + private static void AssertToByteArrayEquals(TimeSpan value, params byte[] expected) + { + CollectionAssert.AreEqual(expected, Types.TimeSpan.ToByteArray(value)); + } + } + } +} diff --git a/S7.Net/Enums.cs b/S7.Net/Enums.cs index fc412d3e..080dce65 100644 --- a/S7.Net/Enums.cs +++ b/S7.Net/Enums.cs @@ -206,6 +206,11 @@ public enum VarType /// /// DateTimeLong variable type /// - DateTimeLong + DateTimeLong, + + /// + /// S7 TIME variable type - serialized as S7 DInt and deserialized as C# TimeSpan + /// + Time } } diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index 4109e536..fa016726 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -235,6 +235,15 @@ private static void BuildReadDataRequestPackage(System.IO.MemoryStream stream, D { return DateTimeLong.ToArray(bytes); } + case VarType.Time: + if (varCount == 1) + { + return TimeSpan.FromByteArray(bytes); + } + else + { + return TimeSpan.ToArray(bytes); + } default: return null; } @@ -268,6 +277,7 @@ internal static int VarTypeToByteLength(VarType varType, int varCount = 1) case VarType.DWord: case VarType.DInt: case VarType.Real: + case VarType.Time: return varCount * 4; case VarType.LReal: case VarType.DateTime: diff --git a/S7.Net/Types/Struct.cs b/S7.Net/Types/Struct.cs index 6f29447d..136638ac 100644 --- a/S7.Net/Types/Struct.cs +++ b/S7.Net/Types/Struct.cs @@ -45,6 +45,7 @@ public static int GetStructSize(Type structType) break; case "Int32": case "UInt32": + case "TimeSpan": numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; @@ -215,6 +216,21 @@ public static int GetStructSize(Type structType) 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) @@ -311,6 +327,9 @@ static TValue GetValueOrThrow(FieldInfo fi, object structValue) where TV _ => 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) { diff --git a/S7.Net/Types/TimeSpan.cs b/S7.Net/Types/TimeSpan.cs new file mode 100644 index 00000000..20f05d87 --- /dev/null +++ b/S7.Net/Types/TimeSpan.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; + +namespace S7.Net.Types +{ + /// + /// Contains the methods to convert between and S7 representation of TIME values. + /// + public static class TimeSpan + { + /// + /// The minimum value supported by the specification. + /// + public static readonly System.TimeSpan SpecMinimumTimeSpan = System.TimeSpan.FromMilliseconds(int.MinValue); + + /// + /// The maximum value supported by the specification. + /// + public static readonly System.TimeSpan SpecMaximumTimeSpan = System.TimeSpan.FromMilliseconds(int.MaxValue); + + /// + /// Parses a value from bytes. + /// + /// Input bytes read from PLC. + /// A object representing the value read from PLC. + /// Thrown when the length of + /// is not 4 or any value in + /// is outside the valid range of values. + public static System.TimeSpan FromByteArray(byte[] bytes) + { + var milliseconds = DInt.FromByteArray(bytes); + return System.TimeSpan.FromMilliseconds(milliseconds); + } + + /// + /// Parses an array of values from bytes. + /// + /// Input bytes read from PLC. + /// An array of objects representing the values read from PLC. + /// Thrown when the length of + /// is not a multiple of 4 or any value in + /// is outside the valid range of values. + public static System.TimeSpan[] ToArray(byte[] bytes) + { + const int singleTimeSpanLength = 4; + + if (bytes.Length % singleTimeSpanLength != 0) + throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, + $"Parsing an array of {nameof(System.TimeSpan)} requires a multiple of {singleTimeSpanLength} bytes of input data, input data is '{bytes.Length}' long."); + + var result = new System.TimeSpan[bytes.Length / singleTimeSpanLength]; + + var milliseconds = DInt.ToArray(bytes); + for (var i = 0; i < milliseconds.Length; i++) + result[i] = System.TimeSpan.FromMilliseconds(milliseconds[i]); + + return result; + } + + /// + /// Converts a value to a byte array. + /// + /// The TimeSpan value to convert. + /// A byte array containing the S7 date time representation of . + /// Thrown when the value of + /// is before + /// or after . + public static byte[] ToByteArray(System.TimeSpan timeSpan) + { + if (timeSpan < SpecMinimumTimeSpan) + throw new ArgumentOutOfRangeException(nameof(timeSpan), timeSpan, + $"Time span '{timeSpan}' is before the minimum '{SpecMinimumTimeSpan}' supported in S7 time representation."); + + if (timeSpan > SpecMaximumTimeSpan) + throw new ArgumentOutOfRangeException(nameof(timeSpan), timeSpan, + $"Time span '{timeSpan}' is after the maximum '{SpecMaximumTimeSpan}' supported in S7 time representation."); + + return DInt.ToByteArray(Convert.ToInt32(timeSpan.TotalMilliseconds)); + } + + /// + /// Converts an array of values to a byte array. + /// + /// The TimeSpan values to convert. + /// A byte array containing the S7 date time representations of . + /// Thrown when any value of + /// is before + /// or after . + public static byte[] ToByteArray(System.TimeSpan[] timeSpans) + { + var bytes = new List(timeSpans.Length * 4); + foreach (var timeSpan in timeSpans) bytes.AddRange(ToByteArray(timeSpan)); + + return bytes.ToArray(); + } + } +} \ No newline at end of file