From 5d59c8284dc1e759cc0ee7f42a1f12f683cfcebc Mon Sep 17 00:00:00 2001 From: Serge Camille Date: Sun, 16 Aug 2020 22:31:26 +0200 Subject: [PATCH 1/6] Add DTL type Add new class Types.Dtl by taking the DateTime type and adjusting things. Also add unit test with binary data calculated by hand. (Need to verify with actual S7 data) --- S7.Net.UnitTest/S7.Net.UnitTest.csproj | 1 + S7.Net.UnitTest/TypeTests/DtlTests.cs | 171 +++++++++++++++++++++++++ S7.Net/Types/Dtl.cs | 158 +++++++++++++++++++++++ 3 files changed, 330 insertions(+) create mode 100644 S7.Net.UnitTest/TypeTests/DtlTests.cs create mode 100644 S7.Net/Types/Dtl.cs diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index 0a14d88..a87d0e2 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -81,6 +81,7 @@ + diff --git a/S7.Net.UnitTest/TypeTests/DtlTests.cs b/S7.Net.UnitTest/TypeTests/DtlTests.cs new file mode 100644 index 0000000..23b6217 --- /dev/null +++ b/S7.Net.UnitTest/TypeTests/DtlTests.cs @@ -0,0 +1,171 @@ +using System; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace S7.Net.UnitTest.TypeTests +{ + public static class DtlTests + { + private static readonly DateTime SampleDateTime = new DateTime(1993, 12, 25, 8, 12, 34, 567); + + private static readonly byte[] SampleByteArray = {0x07, 0xC9, 0x0C, 0x19, 0x07, 0x08, 0x0C, 0x22, 0x21, 0xCB, 0xBB, 0xC0 }; + + private static readonly byte[] SpecMinByteArray = + { + 0x07, 0xB2, 0x01, 0x01, (byte) (int) (Types.Dtl.SpecMinimumDateTime.DayOfWeek + 1), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static readonly byte[] SpecMaxByteArray = + { + 0x08, 0xD6, 0x04, 0x0B, (byte) (int) (Types.Dtl.SpecMaximumDateTime.DayOfWeek + 1), 0x17, 0x2F, 0x10, 0x32, 0xE7, 0x01, 0x80 + }; + + [TestClass] + public class FromByteArray + { + [TestMethod] + public void Sample() + { + AssertFromByteArrayEquals(SampleDateTime, SampleByteArray); + } + + [TestMethod] + public void SpecMinimum() + { + AssertFromByteArrayEquals(Types.Dtl.SpecMinimumDateTime, SpecMinByteArray); + } + + [TestMethod] + public void SpecMaximum() + { + AssertFromByteArrayEquals(Types.Dtl.SpecMaximumDateTime, SpecMaxByteArray); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnLessThan12Bytes() + { + Types.Dtl.FromByteArray(new byte[11]); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnMoreTHan12Bytes() + { + Types.Dtl.FromByteArray(new byte[13]); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnInvalidYear() + { + Types.Dtl.FromByteArray(MutateSample(0, 0xa0)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnZeroMonth() + { + Types.Dtl.FromByteArray(MutateSample(2, 0x00)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnTooLargeMonth() + { + Types.Dtl.FromByteArray(MutateSample(2, 0x13)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnZeroDay() + { + Types.Dtl.FromByteArray(MutateSample(3, 0x00)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnTooLargeDay() + { + Types.Dtl.FromByteArray(MutateSample(3, 0x32)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnInvalidHour() + { + Types.Dtl.FromByteArray(MutateSample(5, 0x24)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnInvalidMinute() + { + Types.Dtl.FromByteArray(MutateSample(6, 0x60)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnInvalidSecond() + { + Types.Dtl.FromByteArray(MutateSample(7, 0x60)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnInvalidNanosecondsFirstDigit() + { + Types.Dtl.FromByteArray(MutateSample(8, 0x3B)); + } + + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnZeroDayOfWeek() + { + Types.Dtl.FromByteArray(MutateSample(4, 0)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnTooLargeDayOfWeek() + { + Types.Dtl.FromByteArray(MutateSample(4, 8)); + } + + private static void AssertFromByteArrayEquals(DateTime expected, params byte[] bytes) + { + Assert.AreEqual(expected, Types.Dtl.FromByteArray(bytes)); + } + + private static byte[] MutateSample(int index, byte value) => + SampleByteArray.Select((b, i) => i == index ? value : b).ToArray(); + } + + [TestClass] + public class ToByteArray + { + [TestMethod] + public void Sample() + { + AssertToByteArrayEquals(SampleDateTime, SampleByteArray); + } + + [TestMethod] + public void SpecMinimum() + { + AssertToByteArrayEquals(Types.Dtl.SpecMinimumDateTime, SpecMinByteArray); + } + + [TestMethod] + public void SpecMaximum() + { + AssertToByteArrayEquals(Types.Dtl.SpecMaximumDateTime, SpecMaxByteArray); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnTimeBeforeSpecMinimum() + { + Types.Dtl.ToByteArray(new DateTime(1950, 1, 1)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnTimeAfterSpecMaximum() + { + Types.Dtl.ToByteArray(new DateTime(2790, 1, 1)); + } + + private static void AssertToByteArrayEquals(DateTime value, params byte[] expected) + { + CollectionAssert.AreEqual(expected, Types.Dtl.ToByteArray(value)); + } + } + } +} diff --git a/S7.Net/Types/Dtl.cs b/S7.Net/Types/Dtl.cs new file mode 100644 index 0000000..1c95911 --- /dev/null +++ b/S7.Net/Types/Dtl.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace S7.Net.Types +{ + /// + /// Contains the methods to convert between and S7 representation of DTL values. + /// + public static class Dtl + { + /// + /// The minimum value supported by the specification. + /// + public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1970, 1, 1); + + /// + /// The maximum value supported by the specification. + /// + public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2262, 4, 11, 23, 47, 16, 854); + + /// + /// 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 12 or any value in + /// is outside the valid range of values. + public static System.DateTime FromByteArray(byte[] bytes) + { + return FromByteArrayImpl(bytes); + } + + /// + /// 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 12 or any value in + /// is outside the valid range of values. + public static System.DateTime[] ToArray(byte[] bytes) + { + if (bytes.Length % 12 != 0) + throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, + $"Parsing an array of Dtl requires a multiple of 12 bytes of input data, input data is '{bytes.Length}' long."); + + var cnt = bytes.Length / 12; + var result = new System.DateTime[cnt]; + + for (var i = 0; i < cnt; i++) + { + var slice = new byte[12]; + Array.Copy(bytes, i * 12, slice, 0, 12); + result[i] = FromByteArrayImpl(slice); + } + + return result; + } + + private static System.DateTime FromByteArrayImpl(byte[] bytes) + { + if (bytes.Length != 12) + throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, + $"Parsing a Dtl requires exactly 12 bytes of input data, input data is {bytes.Length} bytes long."); + + + int year = AssertRangeInclusive(Word.FromBytes(bytes[1], bytes[0]), 1970, 2262, "year"); + int month = AssertRangeInclusive(bytes[2], 1, 12, "month"); + int day = AssertRangeInclusive(bytes[3], 1, 31, "day of month"); + var dayOfWeek = AssertRangeInclusive(bytes[4], 1, 7, "day of week"); + int hour = AssertRangeInclusive(bytes[5], 0, 23, "hour"); + int minute = AssertRangeInclusive(bytes[6], 0, 59, "minute"); + int second = AssertRangeInclusive(bytes[7], 0, 59, "second"); ; + + var nanoseconds = AssertRangeInclusive(DWord.FromBytes(bytes[11], bytes[10], bytes[9], bytes[8]), 0, 999999999, "nanoseconds"); + + var time = new System.DateTime(year, month, day, hour, minute, second); + return time.AddTicks(nanoseconds / 100); + } + + /// + /// Converts a value to a byte array. + /// + /// The DateTime value to convert. + /// A byte array containing the S7 DTL representation of . + /// Thrown when the value of + /// is before + /// or after . + public static byte[] ToByteArray(System.DateTime dateTime) + { + if (dateTime < SpecMinimumDateTime) + throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, + $"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 DTL representation."); + + if (dateTime > SpecMaximumDateTime) + throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, + $"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 DTL representation."); + + var stream = new MemoryStream(12); + // Convert Year + stream.Write(Word.ToByteArray(Convert.ToUInt16(dateTime.Year)), 0, 2); + + // Convert Month + stream.WriteByte(Convert.ToByte(dateTime.Month)); + + // Convert Day + stream.WriteByte(Convert.ToByte(dateTime.Day)); + + // Convert WeekDay. NET DateTime starts with Sunday = 0, while S7DT has Sunday = 1. + stream.WriteByte(Convert.ToByte(dateTime.DayOfWeek + 1)); + + // Convert Hour + stream.WriteByte(Convert.ToByte(dateTime.Hour)); + + // Convert Minutes + stream.WriteByte(Convert.ToByte(dateTime.Minute)); + + // Convert Seconds + stream.WriteByte(Convert.ToByte(dateTime.Second)); + + // Convert Nanoseconds. Net DateTime has a representation of 1 Tick = 100ns. + // Thus First take the ticks Mod 1 Second (1s = 10'000'000 ticks), and then Convert to nanoseconds. + stream.Write(DWord.ToByteArray(Convert.ToUInt32(dateTime.Ticks % 10000000 * 100)), 0, 4); + + return stream.ToArray(); + } + + /// + /// Converts an array of values to a byte array. + /// + /// The DateTime values to convert. + /// A byte array containing the S7 DTL representations of . + /// Thrown when any value of + /// is before + /// or after . + public static byte[] ToByteArray(System.DateTime[] dateTimes) + { + var bytes = new List(dateTimes.Length * 8); + foreach (var dateTime in dateTimes) bytes.AddRange(ToByteArray(dateTime)); + + return bytes.ToArray(); + } + + private static T AssertRangeInclusive(T input, T min, T max, string field) where T : IComparable + { + if (input.CompareTo(min) < 0) + throw new ArgumentOutOfRangeException(nameof(input), input, + $"Value '{input}' is lower than the minimum '{min}' allowed for {field}."); + if (input.CompareTo(max) > 0) + throw new ArgumentOutOfRangeException(nameof(input), input, + $"Value '{input}' is higher than the maximum '{max}' allowed for {field}."); + + return input; + } + } +} \ No newline at end of file From 6614c7330a4f07578aad3d7ac0c2646a87db62ae Mon Sep 17 00:00:00 2001 From: Serge Camille Date: Sun, 16 Aug 2020 22:36:23 +0200 Subject: [PATCH 2/6] Hook up DTL to VarType enum and PLCHelper. --- S7.Net/Enums.cs | 7 ++++++- S7.Net/PLCHelpers.cs | 11 +++++++++++ S7.Net/Protocol/Serialization.cs | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/S7.Net/Enums.cs b/S7.Net/Enums.cs index f4b4778..452fa44 100644 --- a/S7.Net/Enums.cs +++ b/S7.Net/Enums.cs @@ -186,6 +186,11 @@ /// /// DateTIme variable type /// - DateTime + DateTime, + + /// + /// DTL variable type + /// + Dtl } } diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index 2ea37cd..6d8fd5b 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -156,6 +156,15 @@ namespace S7.Net { return DateTime.ToArray(bytes); } + case VarType.Dtl: + if (varCount == 1) + { + return Dtl.FromByteArray(bytes); + } + else + { + return Dtl.ToArray(bytes); + } default: return null; } @@ -190,6 +199,8 @@ namespace S7.Net return varCount * 4; case VarType.DateTime: return varCount * 8; + case VarType.Dtl: + return varCount * 12; default: return 0; } diff --git a/S7.Net/Protocol/Serialization.cs b/S7.Net/Protocol/Serialization.cs index 613c9f4..56be593 100644 --- a/S7.Net/Protocol/Serialization.cs +++ b/S7.Net/Protocol/Serialization.cs @@ -64,6 +64,8 @@ namespace S7.Net.Protocol return Types.String.ToByteArray(stringVal, stringVal.Length); case "DateTime[]": return Types.DateTime.ToByteArray((System.DateTime[]) value); + case "Dtl[]": + return Types.Dtl.ToByteArray((System.DateTime[])value); default: throw new InvalidVariableTypeException(); } From 4be5765fc92f47e20f9ddf2cfeb8dc623400517f Mon Sep 17 00:00:00 2001 From: Serge Camille Date: Sun, 16 Aug 2020 22:50:23 +0200 Subject: [PATCH 3/6] Run Resharper cleanup on DTL class, fix Dtl.ToByteArray list capacity. --- S7.Net/Types/Dtl.cs | 90 +++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 32 deletions(-) diff --git a/S7.Net/Types/Dtl.cs b/S7.Net/Types/Dtl.cs index 1c95911..22b0897 100644 --- a/S7.Net/Types/Dtl.cs +++ b/S7.Net/Types/Dtl.cs @@ -5,46 +5,52 @@ using System.IO; namespace S7.Net.Types { /// - /// Contains the methods to convert between and S7 representation of DTL values. + /// Contains the methods to convert between and S7 representation of DTL values. /// public static class Dtl { /// - /// The minimum value supported by the specification. + /// The minimum value supported by the specification. /// public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1970, 1, 1); /// - /// The maximum value supported by the specification. + /// The maximum value supported by the specification. /// public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2262, 4, 11, 23, 47, 16, 854); /// - /// Parses a value from bytes. + /// 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 12 or any value in - /// is outside the valid range of values. + /// A object representing the value read from PLC. + /// + /// Thrown when the length of + /// is not 12 or any value in + /// is outside the valid range of values. + /// public static System.DateTime FromByteArray(byte[] bytes) { return FromByteArrayImpl(bytes); } /// - /// Parses an array of values from bytes. + /// 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 12 or any value in - /// is outside the valid range of values. + /// An array of objects representing the values read from PLC. + /// + /// Thrown when the length of + /// is not a multiple of 12 or any value in + /// is outside the valid range of values. + /// public static System.DateTime[] ToArray(byte[] bytes) { if (bytes.Length % 12 != 0) + { throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, $"Parsing an array of Dtl requires a multiple of 12 bytes of input data, input data is '{bytes.Length}' long."); + } var cnt = bytes.Length / 12; var result = new System.DateTime[cnt]; @@ -62,41 +68,51 @@ namespace S7.Net.Types private static System.DateTime FromByteArrayImpl(byte[] bytes) { if (bytes.Length != 12) + { throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, $"Parsing a Dtl requires exactly 12 bytes of input data, input data is {bytes.Length} bytes long."); + } - int year = AssertRangeInclusive(Word.FromBytes(bytes[1], bytes[0]), 1970, 2262, "year"); - int month = AssertRangeInclusive(bytes[2], 1, 12, "month"); - int day = AssertRangeInclusive(bytes[3], 1, 31, "day of month"); + var year = AssertRangeInclusive(Word.FromBytes(bytes[1], bytes[0]), 1970, 2262, "year"); + var month = AssertRangeInclusive(bytes[2], 1, 12, "month"); + var day = AssertRangeInclusive(bytes[3], 1, 31, "day of month"); var dayOfWeek = AssertRangeInclusive(bytes[4], 1, 7, "day of week"); - int hour = AssertRangeInclusive(bytes[5], 0, 23, "hour"); - int minute = AssertRangeInclusive(bytes[6], 0, 59, "minute"); - int second = AssertRangeInclusive(bytes[7], 0, 59, "second"); ; + var hour = AssertRangeInclusive(bytes[5], 0, 23, "hour"); + var minute = AssertRangeInclusive(bytes[6], 0, 59, "minute"); + var second = AssertRangeInclusive(bytes[7], 0, 59, "second"); + ; - var nanoseconds = AssertRangeInclusive(DWord.FromBytes(bytes[11], bytes[10], bytes[9], bytes[8]), 0, 999999999, "nanoseconds"); + var nanoseconds = AssertRangeInclusive(DWord.FromBytes(bytes[11], bytes[10], bytes[9], bytes[8]), 0, + 999999999, "nanoseconds"); var time = new System.DateTime(year, month, day, hour, minute, second); return time.AddTicks(nanoseconds / 100); } /// - /// Converts a value to a byte array. + /// Converts a value to a byte array. /// /// The DateTime value to convert. - /// A byte array containing the S7 DTL representation of . - /// Thrown when the value of - /// is before - /// or after . + /// A byte array containing the S7 DTL representation of . + /// + /// Thrown when the value of + /// is before + /// or after . + /// public static byte[] ToByteArray(System.DateTime dateTime) { if (dateTime < SpecMinimumDateTime) + { throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, $"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 DTL representation."); + } if (dateTime > SpecMaximumDateTime) + { throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, $"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 DTL representation."); + } var stream = new MemoryStream(12); // Convert Year @@ -128,17 +144,22 @@ namespace S7.Net.Types } /// - /// Converts an array of values to a byte array. + /// Converts an array of values to a byte array. /// /// The DateTime values to convert. - /// A byte array containing the S7 DTL representations of . - /// Thrown when any value of - /// is before - /// or after . + /// A byte array containing the S7 DTL representations of . + /// + /// Thrown when any value of + /// is before + /// or after . + /// public static byte[] ToByteArray(System.DateTime[] dateTimes) { - var bytes = new List(dateTimes.Length * 8); - foreach (var dateTime in dateTimes) bytes.AddRange(ToByteArray(dateTime)); + var bytes = new List(dateTimes.Length * 12); + foreach (var dateTime in dateTimes) + { + bytes.AddRange(ToByteArray(dateTime)); + } return bytes.ToArray(); } @@ -146,11 +167,16 @@ namespace S7.Net.Types private static T AssertRangeInclusive(T input, T min, T max, string field) where T : IComparable { if (input.CompareTo(min) < 0) + { throw new ArgumentOutOfRangeException(nameof(input), input, $"Value '{input}' is lower than the minimum '{min}' allowed for {field}."); + } + if (input.CompareTo(max) > 0) + { throw new ArgumentOutOfRangeException(nameof(input), input, $"Value '{input}' is higher than the maximum '{max}' allowed for {field}."); + } return input; } From a1d87de2d9ca91bf7a12aa6e4872415050262f5a Mon Sep 17 00:00:00 2001 From: Serge Camille Date: Mon, 17 Aug 2020 19:14:28 +0200 Subject: [PATCH 4/6] Rename DTL to DateTimeLong --- S7.Net.UnitTest/S7.Net.UnitTest.csproj | 2 +- .../{DtlTests.cs => DateTimeLongTests.cs} | 48 +++++++++---------- S7.Net/Enums.cs | 4 +- S7.Net/PLCHelpers.cs | 8 ++-- S7.Net/Protocol/Serialization.cs | 4 +- S7.Net/Types/{Dtl.cs => DateTimeLong.cs} | 16 +++---- 6 files changed, 41 insertions(+), 41 deletions(-) rename S7.Net.UnitTest/TypeTests/{DtlTests.cs => DateTimeLongTests.cs} (67%) rename S7.Net/Types/{Dtl.cs => DateTimeLong.cs} (90%) diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index a87d0e2..62d54d0 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -81,7 +81,7 @@ - + diff --git a/S7.Net.UnitTest/TypeTests/DtlTests.cs b/S7.Net.UnitTest/TypeTests/DateTimeLongTests.cs similarity index 67% rename from S7.Net.UnitTest/TypeTests/DtlTests.cs rename to S7.Net.UnitTest/TypeTests/DateTimeLongTests.cs index 23b6217..2ef8fb2 100644 --- a/S7.Net.UnitTest/TypeTests/DtlTests.cs +++ b/S7.Net.UnitTest/TypeTests/DateTimeLongTests.cs @@ -4,7 +4,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; namespace S7.Net.UnitTest.TypeTests { - public static class DtlTests + public static class DateTimeLongTests { private static readonly DateTime SampleDateTime = new DateTime(1993, 12, 25, 8, 12, 34, 567); @@ -12,12 +12,12 @@ namespace S7.Net.UnitTest.TypeTests private static readonly byte[] SpecMinByteArray = { - 0x07, 0xB2, 0x01, 0x01, (byte) (int) (Types.Dtl.SpecMinimumDateTime.DayOfWeek + 1), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x07, 0xB2, 0x01, 0x01, (byte) (int) (Types.DateTimeLong.SpecMinimumDateTime.DayOfWeek + 1), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; private static readonly byte[] SpecMaxByteArray = { - 0x08, 0xD6, 0x04, 0x0B, (byte) (int) (Types.Dtl.SpecMaximumDateTime.DayOfWeek + 1), 0x17, 0x2F, 0x10, 0x32, 0xE7, 0x01, 0x80 + 0x08, 0xD6, 0x04, 0x0B, (byte) (int) (Types.DateTimeLong.SpecMaximumDateTime.DayOfWeek + 1), 0x17, 0x2F, 0x10, 0x32, 0xE7, 0x01, 0x80 }; [TestClass] @@ -32,97 +32,97 @@ namespace S7.Net.UnitTest.TypeTests [TestMethod] public void SpecMinimum() { - AssertFromByteArrayEquals(Types.Dtl.SpecMinimumDateTime, SpecMinByteArray); + AssertFromByteArrayEquals(Types.DateTimeLong.SpecMinimumDateTime, SpecMinByteArray); } [TestMethod] public void SpecMaximum() { - AssertFromByteArrayEquals(Types.Dtl.SpecMaximumDateTime, SpecMaxByteArray); + AssertFromByteArrayEquals(Types.DateTimeLong.SpecMaximumDateTime, SpecMaxByteArray); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnLessThan12Bytes() { - Types.Dtl.FromByteArray(new byte[11]); + Types.DateTimeLong.FromByteArray(new byte[11]); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnMoreTHan12Bytes() { - Types.Dtl.FromByteArray(new byte[13]); + Types.DateTimeLong.FromByteArray(new byte[13]); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnInvalidYear() { - Types.Dtl.FromByteArray(MutateSample(0, 0xa0)); + Types.DateTimeLong.FromByteArray(MutateSample(0, 0xa0)); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnZeroMonth() { - Types.Dtl.FromByteArray(MutateSample(2, 0x00)); + Types.DateTimeLong.FromByteArray(MutateSample(2, 0x00)); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnTooLargeMonth() { - Types.Dtl.FromByteArray(MutateSample(2, 0x13)); + Types.DateTimeLong.FromByteArray(MutateSample(2, 0x13)); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnZeroDay() { - Types.Dtl.FromByteArray(MutateSample(3, 0x00)); + Types.DateTimeLong.FromByteArray(MutateSample(3, 0x00)); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnTooLargeDay() { - Types.Dtl.FromByteArray(MutateSample(3, 0x32)); + Types.DateTimeLong.FromByteArray(MutateSample(3, 0x32)); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnInvalidHour() { - Types.Dtl.FromByteArray(MutateSample(5, 0x24)); + Types.DateTimeLong.FromByteArray(MutateSample(5, 0x24)); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnInvalidMinute() { - Types.Dtl.FromByteArray(MutateSample(6, 0x60)); + Types.DateTimeLong.FromByteArray(MutateSample(6, 0x60)); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnInvalidSecond() { - Types.Dtl.FromByteArray(MutateSample(7, 0x60)); + Types.DateTimeLong.FromByteArray(MutateSample(7, 0x60)); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnInvalidNanosecondsFirstDigit() { - Types.Dtl.FromByteArray(MutateSample(8, 0x3B)); + Types.DateTimeLong.FromByteArray(MutateSample(8, 0x3B)); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnZeroDayOfWeek() { - Types.Dtl.FromByteArray(MutateSample(4, 0)); + Types.DateTimeLong.FromByteArray(MutateSample(4, 0)); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnTooLargeDayOfWeek() { - Types.Dtl.FromByteArray(MutateSample(4, 8)); + Types.DateTimeLong.FromByteArray(MutateSample(4, 8)); } private static void AssertFromByteArrayEquals(DateTime expected, params byte[] bytes) { - Assert.AreEqual(expected, Types.Dtl.FromByteArray(bytes)); + Assert.AreEqual(expected, Types.DateTimeLong.FromByteArray(bytes)); } private static byte[] MutateSample(int index, byte value) => @@ -141,30 +141,30 @@ namespace S7.Net.UnitTest.TypeTests [TestMethod] public void SpecMinimum() { - AssertToByteArrayEquals(Types.Dtl.SpecMinimumDateTime, SpecMinByteArray); + AssertToByteArrayEquals(Types.DateTimeLong.SpecMinimumDateTime, SpecMinByteArray); } [TestMethod] public void SpecMaximum() { - AssertToByteArrayEquals(Types.Dtl.SpecMaximumDateTime, SpecMaxByteArray); + AssertToByteArrayEquals(Types.DateTimeLong.SpecMaximumDateTime, SpecMaxByteArray); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnTimeBeforeSpecMinimum() { - Types.Dtl.ToByteArray(new DateTime(1950, 1, 1)); + Types.DateTimeLong.ToByteArray(new DateTime(1950, 1, 1)); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void ThrowsOnTimeAfterSpecMaximum() { - Types.Dtl.ToByteArray(new DateTime(2790, 1, 1)); + Types.DateTimeLong.ToByteArray(new DateTime(2790, 1, 1)); } private static void AssertToByteArrayEquals(DateTime value, params byte[] expected) { - CollectionAssert.AreEqual(expected, Types.Dtl.ToByteArray(value)); + CollectionAssert.AreEqual(expected, Types.DateTimeLong.ToByteArray(value)); } } } diff --git a/S7.Net/Enums.cs b/S7.Net/Enums.cs index 452fa44..6082d28 100644 --- a/S7.Net/Enums.cs +++ b/S7.Net/Enums.cs @@ -189,8 +189,8 @@ DateTime, /// - /// DTL variable type + /// DateTimeLong variable type /// - Dtl + DateTimeLong } } diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index 6d8fd5b..602c6a6 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -156,14 +156,14 @@ namespace S7.Net { return DateTime.ToArray(bytes); } - case VarType.Dtl: + case VarType.DateTimeLong: if (varCount == 1) { - return Dtl.FromByteArray(bytes); + return DateTimeLong.FromByteArray(bytes); } else { - return Dtl.ToArray(bytes); + return DateTimeLong.ToArray(bytes); } default: return null; @@ -199,7 +199,7 @@ namespace S7.Net return varCount * 4; case VarType.DateTime: return varCount * 8; - case VarType.Dtl: + case VarType.DateTimeLong: return varCount * 12; default: return 0; diff --git a/S7.Net/Protocol/Serialization.cs b/S7.Net/Protocol/Serialization.cs index 56be593..66142c2 100644 --- a/S7.Net/Protocol/Serialization.cs +++ b/S7.Net/Protocol/Serialization.cs @@ -64,8 +64,8 @@ namespace S7.Net.Protocol return Types.String.ToByteArray(stringVal, stringVal.Length); case "DateTime[]": return Types.DateTime.ToByteArray((System.DateTime[]) value); - case "Dtl[]": - return Types.Dtl.ToByteArray((System.DateTime[])value); + case "DateTimeLong[]": + return Types.DateTimeLong.ToByteArray((System.DateTime[])value); default: throw new InvalidVariableTypeException(); } diff --git a/S7.Net/Types/Dtl.cs b/S7.Net/Types/DateTimeLong.cs similarity index 90% rename from S7.Net/Types/Dtl.cs rename to S7.Net/Types/DateTimeLong.cs index 22b0897..452543f 100644 --- a/S7.Net/Types/Dtl.cs +++ b/S7.Net/Types/DateTimeLong.cs @@ -5,9 +5,9 @@ using System.IO; namespace S7.Net.Types { /// - /// Contains the methods to convert between and S7 representation of DTL values. + /// Contains the methods to convert between and S7 representation of DateTimeLong (DTL) values. /// - public static class Dtl + public static class DateTimeLong { /// /// The minimum value supported by the specification. @@ -49,7 +49,7 @@ namespace S7.Net.Types if (bytes.Length % 12 != 0) { throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, - $"Parsing an array of Dtl requires a multiple of 12 bytes of input data, input data is '{bytes.Length}' long."); + $"Parsing an array of DateTimeLong requires a multiple of 12 bytes of input data, input data is '{bytes.Length}' long."); } var cnt = bytes.Length / 12; @@ -70,7 +70,7 @@ namespace S7.Net.Types if (bytes.Length != 12) { throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, - $"Parsing a Dtl requires exactly 12 bytes of input data, input data is {bytes.Length} bytes long."); + $"Parsing a DateTimeLong requires exactly 12 bytes of input data, input data is {bytes.Length} bytes long."); } @@ -94,7 +94,7 @@ namespace S7.Net.Types /// Converts a value to a byte array. /// /// The DateTime value to convert. - /// A byte array containing the S7 DTL representation of . + /// A byte array containing the S7 DateTimeLong representation of . /// /// Thrown when the value of /// is before @@ -105,13 +105,13 @@ namespace S7.Net.Types if (dateTime < SpecMinimumDateTime) { throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, - $"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 DTL representation."); + $"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 DateTimeLong representation."); } if (dateTime > SpecMaximumDateTime) { throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, - $"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 DTL representation."); + $"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 DateTimeLong representation."); } var stream = new MemoryStream(12); @@ -147,7 +147,7 @@ namespace S7.Net.Types /// Converts an array of values to a byte array. /// /// The DateTime values to convert. - /// A byte array containing the S7 DTL representations of . + /// A byte array containing the S7 DateTimeLong representations of . /// /// Thrown when any value of /// is before From 28257f28b3540c3f9eb3ae70e3eab68505066ff4 Mon Sep 17 00:00:00 2001 From: Serge Camille Date: Mon, 17 Aug 2020 19:20:19 +0200 Subject: [PATCH 5/6] Dtl: Add TypeLengthInBytes constant instead of always rewriting 12. --- S7.Net/Types/DateTimeLong.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/S7.Net/Types/DateTimeLong.cs b/S7.Net/Types/DateTimeLong.cs index 452543f..6479f6f 100644 --- a/S7.Net/Types/DateTimeLong.cs +++ b/S7.Net/Types/DateTimeLong.cs @@ -9,6 +9,7 @@ namespace S7.Net.Types /// public static class DateTimeLong { + public const int TypeLengthInBytes = 12; /// /// The minimum value supported by the specification. /// @@ -46,19 +47,19 @@ namespace S7.Net.Types /// public static System.DateTime[] ToArray(byte[] bytes) { - if (bytes.Length % 12 != 0) + if (bytes.Length % TypeLengthInBytes != 0) { throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, $"Parsing an array of DateTimeLong requires a multiple of 12 bytes of input data, input data is '{bytes.Length}' long."); } - var cnt = bytes.Length / 12; + var cnt = bytes.Length / TypeLengthInBytes; var result = new System.DateTime[cnt]; for (var i = 0; i < cnt; i++) { - var slice = new byte[12]; - Array.Copy(bytes, i * 12, slice, 0, 12); + var slice = new byte[TypeLengthInBytes]; + Array.Copy(bytes, i * TypeLengthInBytes, slice, 0, TypeLengthInBytes); result[i] = FromByteArrayImpl(slice); } @@ -67,7 +68,7 @@ namespace S7.Net.Types private static System.DateTime FromByteArrayImpl(byte[] bytes) { - if (bytes.Length != 12) + if (bytes.Length != TypeLengthInBytes) { throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, $"Parsing a DateTimeLong requires exactly 12 bytes of input data, input data is {bytes.Length} bytes long."); @@ -114,7 +115,7 @@ namespace S7.Net.Types $"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 DateTimeLong representation."); } - var stream = new MemoryStream(12); + var stream = new MemoryStream(TypeLengthInBytes); // Convert Year stream.Write(Word.ToByteArray(Convert.ToUInt16(dateTime.Year)), 0, 2); @@ -155,7 +156,7 @@ namespace S7.Net.Types /// public static byte[] ToByteArray(System.DateTime[] dateTimes) { - var bytes = new List(dateTimes.Length * 12); + var bytes = new List(dateTimes.Length * TypeLengthInBytes); foreach (var dateTime in dateTimes) { bytes.AddRange(ToByteArray(dateTime)); From 3258c84fbc5719b8f54d1c57f5512935c4d635e3 Mon Sep 17 00:00:00 2001 From: Serge Camille Date: Mon, 17 Aug 2020 19:20:47 +0200 Subject: [PATCH 6/6] Add DateTimeLong test to S7NetTestsSync --- S7.Net.UnitTest/S7NetTestsSync.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs index c096643..3b7589b 100644 --- a/S7.Net.UnitTest/S7NetTestsSync.cs +++ b/S7.Net.UnitTest/S7NetTestsSync.cs @@ -997,6 +997,20 @@ namespace S7.Net.UnitTest Assert.AreEqual(test_value, test_value2, "Compare Write/Read"); //No delta, datatype matches } + [TestMethod] + public void T33_ReadWriteDateTimeLong() + { + var test_value = System.DateTime.Now; + var db = 1; + var offset = 0; + + plc.WriteBytes(DataType.DataBlock, db, offset, Types.DateTimeLong.ToByteArray(test_value)); + var test_value2 = plc.Read(DataType.DataBlock, db, offset, VarType.DateTimeLong, 1); + Assert.IsInstanceOfType(test_value2, typeof(System.DateTime)); + + Assert.AreEqual(test_value, test_value2, "Compare DateTimeLong Write/Read"); + } + #endregion #region Private methods