From b1d2d119046bb4d65a4426130ccb6760016a1918 Mon Sep 17 00:00:00 2001 From: max Date: Wed, 10 Jul 2019 18:17:02 +0200 Subject: [PATCH 1/9] add Port Setting for PLC. is required for port forwarding --- S7.Net.UnitTest/S7NetTestsAsync.cs | 10 ++++----- S7.Net.UnitTest/S7NetTestsSync.cs | 16 +++++++------- S7.Net/PLC.cs | 35 +++++++++++++++++++++++++++++- S7.Net/PlcAsynchronous.cs | 2 +- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs index 86d7f22..0bc9941 100644 --- a/S7.Net.UnitTest/S7NetTestsAsync.cs +++ b/S7.Net.UnitTest/S7NetTestsAsync.cs @@ -594,7 +594,7 @@ namespace S7.Net.UnitTest [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadBytesReturnsNullIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); TestClass tc = new TestClass(); @@ -640,7 +640,7 @@ namespace S7.Net.UnitTest [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadClassWithGenericReturnsNullIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); TestClass tc = await notConnectedPlc.ReadClassAsync(DB2); @@ -681,7 +681,7 @@ namespace S7.Net.UnitTest [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadClassWithGenericAndClassFactoryThrowsExceptionPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255",102, 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); TestClass tc = await notConnectedPlc.ReadClassAsync(() => new TestClass(), DB2); @@ -714,7 +714,7 @@ namespace S7.Net.UnitTest [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadStructThrowsExceptionPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); object tsObj = await notConnectedPlc.ReadStructAsync(typeof(TestStruct), DB2); @@ -757,7 +757,7 @@ namespace S7.Net.UnitTest [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadStructWithGenericThrowsExceptionIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); object tsObj = await notConnectedPlc.ReadStructAsync(DB2); diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs index c096643..7142596 100644 --- a/S7.Net.UnitTest/S7NetTestsSync.cs +++ b/S7.Net.UnitTest/S7NetTestsSync.cs @@ -53,7 +53,7 @@ namespace S7.Net.UnitTest /// public S7NetTests() { - plc = new Plc(CpuType.S7300, "127.0.0.1", 0, 2); + plc = new Plc(CpuType.S7300, "127.0.0.1", 102, 0, 2); //ConsoleManager.Show(); ShutDownServiceS7oiehsx64(); @@ -602,7 +602,7 @@ namespace S7.Net.UnitTest [TestMethod, ExpectedException(typeof(PlcException))] public void T13_ReadBytesThrowsIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); TestClass tc = new TestClass(); @@ -643,7 +643,7 @@ namespace S7.Net.UnitTest [TestMethod, ExpectedException(typeof(PlcException))] public void T15_ReadClassWithGenericThrowsIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); @@ -685,7 +685,7 @@ namespace S7.Net.UnitTest [TestMethod, ExpectedException(typeof(PlcException))] public void T17_ReadClassWithGenericAndClassFactoryThrowsIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); @@ -746,7 +746,7 @@ namespace S7.Net.UnitTest [TestMethod, ExpectedException(typeof(PlcException))] public void T18_ReadStructThrowsIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); @@ -788,7 +788,7 @@ namespace S7.Net.UnitTest [TestMethod, ExpectedException(typeof(PlcException))] public void T20_ReadStructThrowsIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); @@ -909,7 +909,7 @@ namespace S7.Net.UnitTest plc.Close(); S7TestServer.Stop(); - var unreachablePlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 2); + var unreachablePlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 2); Assert.IsFalse(unreachablePlc.IsAvailable); } @@ -920,7 +920,7 @@ namespace S7.Net.UnitTest S7TestServer.Stop(); S7TestServer.Start(); - var reachablePlc = new Plc(CpuType.S7300, "127.0.0.1", 0, 2); + var reachablePlc = new Plc(CpuType.S7300, "127.0.0.1", 102, 0, 2); Assert.IsTrue(reachablePlc.IsAvailable); } diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index 853c4ed..b5c65c7 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -23,6 +23,11 @@ namespace S7.Net /// public string IP { get; private set; } + /// + /// PORT Number of the PLC, default is 102 + /// + public int PORT { get; private set; } + /// /// CPU type of the PLC /// @@ -107,7 +112,34 @@ namespace S7.Net catch { return false; } } } - + + /// + /// Creates a PLC object with all the parameters needed for connections. + /// For S7-1200 and S7-1500, the default is rack = 0 and slot = 0. + /// You need slot > 0 if you are connecting to external ethernet card (CP). + /// For S7-300 and S7-400 the default is rack = 0 and slot = 2. + /// + /// CpuType of the PLC (select from the enum) + /// Ip address of the PLC + /// Port address of the PLC, default 102 + /// rack of the PLC, usually it's 0, but check in the hardware configuration of Step7 or TIA portal + /// slot of the CPU of the PLC, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500. + /// If you use an external ethernet card, this must be set accordingly. + public Plc(CpuType cpu, string ip,int port, Int16 rack, Int16 slot) + { + if (!Enum.IsDefined(typeof(CpuType), cpu)) + throw new ArgumentException($"The value of argument '{nameof(cpu)}' ({cpu}) is invalid for Enum type '{typeof(CpuType).Name}'.", nameof(cpu)); + + if (string.IsNullOrEmpty(ip)) + throw new ArgumentException("IP address must valid.", nameof(ip)); + + CPU = cpu; + IP = ip; + PORT = port; + Rack = rack; + Slot = slot; + MaxPDUSize = 240; + } /// /// Creates a PLC object with all the parameters needed for connections. /// For S7-1200 and S7-1500, the default is rack = 0 and slot = 0. @@ -129,6 +161,7 @@ namespace S7.Net CPU = cpu; IP = ip; + PORT = 102; Rack = rack; Slot = slot; MaxPDUSize = 240; diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 291a938..cef0414 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -45,7 +45,7 @@ namespace S7.Net { tcpClient = new TcpClient(); ConfigureConnection(); - await tcpClient.ConnectAsync(IP, 102); + await tcpClient.ConnectAsync(IP, PORT); stream = tcpClient.GetStream(); } From f2d33855ca1656418d604c56adc67e560f2501ad Mon Sep 17 00:00:00 2001 From: max Date: Wed, 10 Jul 2019 18:44:04 +0200 Subject: [PATCH 2/9] add Port Setting for PLC. is required for port forwarding --- S7.Net/PlcSynchronous.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index 95d1baa..c825650 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -51,7 +51,7 @@ namespace S7.Net { tcpClient = new TcpClient(); ConfigureConnection(); - tcpClient.Connect(IP, 102); + tcpClient.Connect(IP, PORT); stream = tcpClient.GetStream(); } catch (SocketException sex) From b2183dd7605b52124b61dcb3db854babe085ed30 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 10 Jul 2019 20:42:52 +0200 Subject: [PATCH 3/9] rename PORT to Port add a Space --- S7.Net/PLC.cs | 8 ++++---- S7.Net/PlcAsynchronous.cs | 2 +- S7.Net/PlcSynchronous.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index b5c65c7..d62e783 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -26,7 +26,7 @@ namespace S7.Net /// /// PORT Number of the PLC, default is 102 /// - public int PORT { get; private set; } + public int Port { get; private set; } /// /// CPU type of the PLC @@ -125,7 +125,7 @@ namespace S7.Net /// rack of the PLC, usually it's 0, but check in the hardware configuration of Step7 or TIA portal /// slot of the CPU of the PLC, usually it's 2 for S7300-S7400, 0 for S7-1200 and S7-1500. /// If you use an external ethernet card, this must be set accordingly. - public Plc(CpuType cpu, string ip,int port, Int16 rack, Int16 slot) + public Plc(CpuType cpu, string ip, int port, Int16 rack, Int16 slot) { if (!Enum.IsDefined(typeof(CpuType), cpu)) throw new ArgumentException($"The value of argument '{nameof(cpu)}' ({cpu}) is invalid for Enum type '{typeof(CpuType).Name}'.", nameof(cpu)); @@ -135,7 +135,7 @@ namespace S7.Net CPU = cpu; IP = ip; - PORT = port; + Port = port; Rack = rack; Slot = slot; MaxPDUSize = 240; @@ -161,7 +161,7 @@ namespace S7.Net CPU = cpu; IP = ip; - PORT = 102; + Port = 102; Rack = rack; Slot = slot; MaxPDUSize = 240; diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index cef0414..eb44f87 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -45,7 +45,7 @@ namespace S7.Net { tcpClient = new TcpClient(); ConfigureConnection(); - await tcpClient.ConnectAsync(IP, PORT); + await tcpClient.ConnectAsync(IP, Port); stream = tcpClient.GetStream(); } diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index c825650..69c6056 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -51,7 +51,7 @@ namespace S7.Net { tcpClient = new TcpClient(); ConfigureConnection(); - tcpClient.Connect(IP, PORT); + tcpClient.Connect(IP, Port); stream = tcpClient.GetStream(); } catch (SocketException sex) From 3b23ab76e73dc5ad64fd16138531de97920739a1 Mon Sep 17 00:00:00 2001 From: max Date: Thu, 11 Jul 2019 18:07:34 +0200 Subject: [PATCH 4/9] correction of wrong changes --- S7.Net.UnitTest/S7NetTestsAsync.cs | 10 +++++----- S7.Net.UnitTest/S7NetTestsSync.cs | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs index 0bc9941..86d7f22 100644 --- a/S7.Net.UnitTest/S7NetTestsAsync.cs +++ b/S7.Net.UnitTest/S7NetTestsAsync.cs @@ -594,7 +594,7 @@ namespace S7.Net.UnitTest [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadBytesReturnsNullIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); TestClass tc = new TestClass(); @@ -640,7 +640,7 @@ namespace S7.Net.UnitTest [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadClassWithGenericReturnsNullIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); TestClass tc = await notConnectedPlc.ReadClassAsync(DB2); @@ -681,7 +681,7 @@ namespace S7.Net.UnitTest [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadClassWithGenericAndClassFactoryThrowsExceptionPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255",102, 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); TestClass tc = await notConnectedPlc.ReadClassAsync(() => new TestClass(), DB2); @@ -714,7 +714,7 @@ namespace S7.Net.UnitTest [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadStructThrowsExceptionPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); object tsObj = await notConnectedPlc.ReadStructAsync(typeof(TestStruct), DB2); @@ -757,7 +757,7 @@ namespace S7.Net.UnitTest [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadStructWithGenericThrowsExceptionIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); object tsObj = await notConnectedPlc.ReadStructAsync(DB2); diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs index 7142596..c096643 100644 --- a/S7.Net.UnitTest/S7NetTestsSync.cs +++ b/S7.Net.UnitTest/S7NetTestsSync.cs @@ -53,7 +53,7 @@ namespace S7.Net.UnitTest /// public S7NetTests() { - plc = new Plc(CpuType.S7300, "127.0.0.1", 102, 0, 2); + plc = new Plc(CpuType.S7300, "127.0.0.1", 0, 2); //ConsoleManager.Show(); ShutDownServiceS7oiehsx64(); @@ -602,7 +602,7 @@ namespace S7.Net.UnitTest [TestMethod, ExpectedException(typeof(PlcException))] public void T13_ReadBytesThrowsIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); TestClass tc = new TestClass(); @@ -643,7 +643,7 @@ namespace S7.Net.UnitTest [TestMethod, ExpectedException(typeof(PlcException))] public void T15_ReadClassWithGenericThrowsIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); @@ -685,7 +685,7 @@ namespace S7.Net.UnitTest [TestMethod, ExpectedException(typeof(PlcException))] public void T17_ReadClassWithGenericAndClassFactoryThrowsIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); @@ -746,7 +746,7 @@ namespace S7.Net.UnitTest [TestMethod, ExpectedException(typeof(PlcException))] public void T18_ReadStructThrowsIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); @@ -788,7 +788,7 @@ namespace S7.Net.UnitTest [TestMethod, ExpectedException(typeof(PlcException))] public void T20_ReadStructThrowsIfPlcIsNotConnected() { - using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 0)) + using (var notConnectedPlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 0)) { Assert.IsFalse(notConnectedPlc.IsConnected); @@ -909,7 +909,7 @@ namespace S7.Net.UnitTest plc.Close(); S7TestServer.Stop(); - var unreachablePlc = new Plc(CpuType.S7300, "255.255.255.255", 102, 0, 2); + var unreachablePlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 2); Assert.IsFalse(unreachablePlc.IsAvailable); } @@ -920,7 +920,7 @@ namespace S7.Net.UnitTest S7TestServer.Stop(); S7TestServer.Start(); - var reachablePlc = new Plc(CpuType.S7300, "127.0.0.1", 102, 0, 2); + var reachablePlc = new Plc(CpuType.S7300, "127.0.0.1", 0, 2); Assert.IsTrue(reachablePlc.IsAvailable); } From cf64c65c23c9a0d4c0ee01557ca54d599227b8a8 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Tue, 31 Jul 2018 21:32:41 +0200 Subject: [PATCH 5/9] Provide a clear message on Encoding.ASCII.GetString exceptions --- S7.Net/Types/StringEx.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/S7.Net/Types/StringEx.cs b/S7.Net/Types/StringEx.cs index 1aec9ab..81733ca 100644 --- a/S7.Net/Types/StringEx.cs +++ b/S7.Net/Types/StringEx.cs @@ -22,7 +22,17 @@ namespace S7.Net.Types int size = bytes[0]; int length = bytes[1]; - return System.Text.Encoding.ASCII.GetString(bytes, 2, length); + try + { + return Encoding.ASCII.GetString(bytes, 2, length); + } + catch (Exception e) + { + throw new PlcException(ErrorCode.ReadData, + $"Failed to parse {VarType.StringEx} from data. Following fields were read: size: '{size}', actual length: '{length}', total number of bytes (including header): '{bytes.Length}'.", + e); + } + } /// From 2aa4f088368c3e4308be97ce40980c976d0eb16f Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Fri, 21 Sep 2018 21:08:59 +0200 Subject: [PATCH 6/9] Add Types.DateTime --- S7.Net/Types/DateTime.cs | 111 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 S7.Net/Types/DateTime.cs diff --git a/S7.Net/Types/DateTime.cs b/S7.Net/Types/DateTime.cs new file mode 100644 index 0000000..8dc7582 --- /dev/null +++ b/S7.Net/Types/DateTime.cs @@ -0,0 +1,111 @@ +using System; + +namespace S7.Net.Types +{ + /// + /// Contains the methods to convert between and S7 representation of datetime values. + /// + public static class DateTime + { + /// + /// The minimum value supported by the specification. + /// + public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1990, 1, 1); + + /// + /// The maximum value supported by the specification. + /// + public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2089, 12, 31, 23, 59, 59, 999); + + /// + /// 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 8 or any value in + /// is outside the valid range of values. + public static System.DateTime FromByteArray(byte[] bytes) + { + if (bytes.Length != 8) + throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, + $"Parsing a DateTime requires exactly 8 bytes of input data, input data is {bytes.Length} bytes long."); + + int DecodeBcd(byte input) => 10 * (input >> 4) + (input & 0b00001111); + + int ByteToYear(byte bcdYear) + { + var input = DecodeBcd(bcdYear); + if (input < 90) return input + 2000; + if (input < 100) return input + 1900; + + throw new ArgumentOutOfRangeException(nameof(bcdYear), bcdYear, + $"Value '{input}' is higher than the maximum '99' of S7 date and time representation."); + } + + int AssertRangeInclusive(int input, byte min, byte max, string field) + { + if (input < min) + throw new ArgumentOutOfRangeException(nameof(input), input, + $"Value '{input}' is lower than the minimum '{min}' allowed for {field}."); + if (input > max) + throw new ArgumentOutOfRangeException(nameof(input), input, + $"Value '{input}' is higher than the maximum '{max}' allowed for {field}."); + + return input; + } + + var year = ByteToYear(bytes[0]); + var month = AssertRangeInclusive(DecodeBcd(bytes[1]), 1, 12, "month"); + var day = AssertRangeInclusive(DecodeBcd(bytes[2]), 1, 31, "day of month"); + var hour = AssertRangeInclusive(DecodeBcd(bytes[3]), 0, 23, "hour"); + var minute = AssertRangeInclusive(DecodeBcd(bytes[4]), 0, 59, "minute"); + var second = AssertRangeInclusive(DecodeBcd(bytes[5]), 0, 59, "second"); + var hsec = AssertRangeInclusive(DecodeBcd(bytes[6]), 0, 99, "first two millisecond digits"); + var msec = AssertRangeInclusive(bytes[7] >> 4, 0, 9, "third millisecond digit"); + var dayOfWeek = AssertRangeInclusive(bytes[7] & 0b00001111, 1, 7, "day of week"); + + return new System.DateTime(year, month, day, hour, minute, second, hsec * 10 + msec); + } + + /// + /// Converts a value to a byte array. + /// + /// The DateTime 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.DateTime dateTime) + { + byte EncodeBcd(int value) + { + return (byte) ((value / 10 << 4) | value % 10); + } + + if (dateTime < SpecMinimumDateTime) + throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, + $"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 date time representation."); + + if (dateTime > SpecMaximumDateTime) + throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime, + $"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 date time representation."); + + byte MapYear(int year) => (byte) (year < 2000 ? year - 1900 : year - 2000); + + int DayOfWeekToInt(DayOfWeek dayOfWeek) => (int) dayOfWeek + 1; + + return new[] + { + EncodeBcd(MapYear(dateTime.Year)), + EncodeBcd(dateTime.Month), + EncodeBcd(dateTime.Day), + EncodeBcd(dateTime.Hour), + EncodeBcd(dateTime.Minute), + EncodeBcd(dateTime.Second), + EncodeBcd(dateTime.Millisecond / 10), + (byte) (dateTime.Millisecond % 10 << 4 | DayOfWeekToInt(dateTime.DayOfWeek)) + }; + } + } +} \ No newline at end of file From 735f1d4533969ec9a304a16d003ed1f25859acd8 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Fri, 21 Sep 2018 21:37:52 +0200 Subject: [PATCH 7/9] Add support for DateTime array conversion --- S7.Net/Types/DateTime.cs | 49 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/S7.Net/Types/DateTime.cs b/S7.Net/Types/DateTime.cs index 8dc7582..9cafa67 100644 --- a/S7.Net/Types/DateTime.cs +++ b/S7.Net/Types/DateTime.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace S7.Net.Types { @@ -27,9 +28,37 @@ namespace S7.Net.Types /// is outside the valid range of values. public static System.DateTime FromByteArray(byte[] bytes) { - if (bytes.Length != 8) + 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 8 or any value in + /// is outside the valid range of values. + public static System.DateTime[] ToArray(byte[] bytes) + { + if (bytes.Length % 8 != 0) throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length, - $"Parsing a DateTime requires exactly 8 bytes of input data, input data is {bytes.Length} bytes long."); + $"Parsing an array of DateTime requires a multiple of 8 bytes of input data, input data is '{bytes.Length}' long."); + + var cnt = bytes.Length / 8; + var result = new System.DateTime[bytes.Length / 8]; + + for (var i = 0; i < cnt; i++) + result[i] = FromByteArrayImpl(new ArraySegment(bytes, i * 8, 8)); + + return result; + } + + private static System.DateTime FromByteArrayImpl(IList bytes) + { + if (bytes.Count != 8) + throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Count, + $"Parsing a DateTime requires exactly 8 bytes of input data, input data is {bytes.Count} bytes long."); int DecodeBcd(byte input) => 10 * (input >> 4) + (input & 0b00001111); @@ -107,5 +136,21 @@ namespace S7.Net.Types (byte) (dateTime.Millisecond % 10 << 4 | DayOfWeekToInt(dateTime.DayOfWeek)) }; } + + /// + /// Converts an array of values to a byte array. + /// + /// The DateTime 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.DateTime[] dateTimes) + { + var bytes = new List(dateTimes.Length * 8); + foreach (var dateTime in dateTimes) bytes.AddRange(ToByteArray(dateTime)); + + return bytes.ToArray(); + } } } \ No newline at end of file From 427d8124ded2354668d4640f16d23119bd07544e Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Fri, 21 Sep 2018 21:38:23 +0200 Subject: [PATCH 8/9] Add DateTime support for read/write methods --- S7.Net/Enums.cs | 7 ++++++- S7.Net/PLCHelpers.cs | 12 ++++++++++++ S7.Net/Protocol/Serialization.cs | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/S7.Net/Enums.cs b/S7.Net/Enums.cs index ff33d04..f4b4778 100644 --- a/S7.Net/Enums.cs +++ b/S7.Net/Enums.cs @@ -181,6 +181,11 @@ /// /// Counter variable type /// - Counter + Counter, + + /// + /// DateTIme variable type + /// + DateTime } } diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index d4e5f18..803d349 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using DateTime = S7.Net.Types.DateTime; namespace S7.Net { @@ -146,6 +147,15 @@ namespace S7.Net { return Bit.ToBitArray(bytes); } + case VarType.DateTime: + if (varCount == 1) + { + return DateTime.FromByteArray(bytes); + } + else + { + return DateTime.ToArray(bytes); + } default: return null; } @@ -178,6 +188,8 @@ namespace S7.Net case VarType.DInt: case VarType.Real: return varCount * 4; + case VarType.DateTime: + return varCount * 8; default: return 0; } diff --git a/S7.Net/Protocol/Serialization.cs b/S7.Net/Protocol/Serialization.cs index 40cb629..613c9f4 100644 --- a/S7.Net/Protocol/Serialization.cs +++ b/S7.Net/Protocol/Serialization.cs @@ -41,6 +41,8 @@ namespace S7.Net.Protocol return Types.Double.ToByteArray((double)value); case "Single": return Types.Single.ToByteArray((float)value); + case "DateTime": + return Types.DateTime.ToByteArray((System.DateTime) value); case "Byte[]": return (byte[])value; case "Int16[]": @@ -60,6 +62,8 @@ namespace S7.Net.Protocol // if the consumer does not pay attention to string length. var stringVal = (string) value; return Types.String.ToByteArray(stringVal, stringVal.Length); + case "DateTime[]": + return Types.DateTime.ToByteArray((System.DateTime[]) value); default: throw new InvalidVariableTypeException(); } From 555d1e8956881ef93486dd4f7d81f17c155146be Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Fri, 21 Sep 2018 22:35:48 +0200 Subject: [PATCH 9/9] Add unit test for Types.DateTime --- S7.Net.UnitTest/S7.Net.UnitTest.csproj | 1 + S7.Net.UnitTest/TypeTests/DateTimeTests.cs | 176 +++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 S7.Net.UnitTest/TypeTests/DateTimeTests.cs diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index dabda6d..321f0aa 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -79,6 +79,7 @@ + diff --git a/S7.Net.UnitTest/TypeTests/DateTimeTests.cs b/S7.Net.UnitTest/TypeTests/DateTimeTests.cs new file mode 100644 index 0000000..d87e31f --- /dev/null +++ b/S7.Net.UnitTest/TypeTests/DateTimeTests.cs @@ -0,0 +1,176 @@ +using System; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace S7.Net.UnitTest.TypeTests +{ + public static class DateTimeTests + { + private static readonly DateTime SampleDateTime = new DateTime(1993, 12, 25, 8, 12, 34, 567); + + private static readonly byte[] SampleByteArray = {0x93, 0x12, 0x25, 0x08, 0x12, 0x34, 0x56, 7 << 4 | 7}; + + private static readonly byte[] SpecMinByteArray = + { + 0x90, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, (byte) (int) (Types.DateTime.SpecMinimumDateTime.DayOfWeek + 1) + }; + + private static readonly byte[] SpecMaxByteArray = + { + 0x89, 0x12, 0x31, 0x23, 0x59, 0x59, 0x99, (byte) (9 << 4 | (int) (Types.DateTime.SpecMaximumDateTime.DayOfWeek + 1)) + }; + + [TestClass] + public class FromByteArray + { + [TestMethod] + public void Sample() + { + AssertFromByteArrayEquals(SampleDateTime, SampleByteArray); + } + + [TestMethod] + public void SpecMinimum() + { + AssertFromByteArrayEquals(Types.DateTime.SpecMinimumDateTime, SpecMinByteArray); + } + + [TestMethod] + public void SpecMaximum() + { + AssertFromByteArrayEquals(Types.DateTime.SpecMaximumDateTime, SpecMaxByteArray); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnLessThan8Bytes() + { + Types.DateTime.FromByteArray(new byte[7]); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnMoreTHan8Bytes() + { + Types.DateTime.FromByteArray(new byte[9]); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnInvalidYear() + { + Types.DateTime.FromByteArray(MutateSample(0, 0xa0)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnZeroMonth() + { + Types.DateTime.FromByteArray(MutateSample(1, 0x00)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnTooLargeMonth() + { + Types.DateTime.FromByteArray(MutateSample(1, 0x13)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnZeroDay() + { + Types.DateTime.FromByteArray(MutateSample(2, 0x00)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnTooLargeDay() + { + Types.DateTime.FromByteArray(MutateSample(2, 0x32)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnInvalidHour() + { + Types.DateTime.FromByteArray(MutateSample(3, 0x24)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnInvalidMinute() + { + Types.DateTime.FromByteArray(MutateSample(4, 0x60)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnInvalidSecond() + { + Types.DateTime.FromByteArray(MutateSample(5, 0x60)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnInvalidFirstTwoMillisecondDigits() + { + Types.DateTime.FromByteArray(MutateSample(6, 0xa0)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnInvalidThirdMillisecondDigit() + { + Types.DateTime.FromByteArray(MutateSample(7, 10 << 4)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnZeroDayOfWeek() + { + Types.DateTime.FromByteArray(MutateSample(7, 0)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnTooLargeDayOfWeek() + { + Types.DateTime.FromByteArray(MutateSample(7, 8)); + } + + private static void AssertFromByteArrayEquals(DateTime expected, params byte[] bytes) + { + Assert.AreEqual(expected, Types.DateTime.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.DateTime.SpecMinimumDateTime, SpecMinByteArray); + } + + [TestMethod] + public void SpecMaximum() + { + AssertToByteArrayEquals(Types.DateTime.SpecMaximumDateTime, SpecMaxByteArray); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnTimeBeforeSpecMinimum() + { + Types.DateTime.ToByteArray(new DateTime(1970, 1, 1)); + } + + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] + public void ThrowsOnTimeAfterSpecMaximum() + { + Types.DateTime.ToByteArray(new DateTime(2090, 1, 1)); + } + + private static void AssertToByteArrayEquals(DateTime value, params byte[] expected) + { + CollectionAssert.AreEqual(expected, Types.DateTime.ToByteArray(value)); + } + } + } +}