From 8dc89867e9968751e331be2b93e09062bb68315d Mon Sep 17 00:00:00 2001 From: Tim Verwaal Date: Thu, 19 Dec 2019 10:55:51 +0100 Subject: [PATCH 1/5] Extended PLCAddress.Parse method --- S7.Net.UnitTest/PLCAddressParsingTests.cs | 156 ++++++++++++++++++++++ S7.Net.UnitTest/S7.Net.UnitTest.csproj | 1 + S7.Net/PLCAddress.cs | 9 +- 3 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 S7.Net.UnitTest/PLCAddressParsingTests.cs diff --git a/S7.Net.UnitTest/PLCAddressParsingTests.cs b/S7.Net.UnitTest/PLCAddressParsingTests.cs new file mode 100644 index 00000000..3c74a9d8 --- /dev/null +++ b/S7.Net.UnitTest/PLCAddressParsingTests.cs @@ -0,0 +1,156 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using S7.Net.Types; +using System; + +namespace S7.Net.UnitTest +{ + [TestClass] + public class PLCAddressParsingTests + { + [TestMethod] + public void T01_ParseM2000_1() + { + DataItem dataItem = DataItem.FromAddress("M2000.1"); + + Assert.AreEqual(DataType.Memory, dataItem.DataType, "Wrong datatype for M2000.1"); + Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for M2000.1"); + Assert.AreEqual(VarType.Bit, dataItem.VarType, "Wrong vartype for M2000.1"); + Assert.AreEqual(2000, dataItem.StartByteAdr, "Wrong startbyte for M2000.1"); + Assert.AreEqual(1, dataItem.BitAdr, "Wrong bit for M2000.1"); + } + + [TestMethod] + public void T02_ParseMB200() + { + DataItem dataItem = DataItem.FromAddress("MB200"); + + Assert.AreEqual(DataType.Memory, dataItem.DataType, "Wrong datatype for MB200"); + Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for MB200"); + Assert.AreEqual(VarType.Byte, dataItem.VarType, "Wrong vartype for MB200"); + Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for MB200"); + Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for MB200"); + } + + [TestMethod] + public void T03_ParseMW200() + { + DataItem dataItem = DataItem.FromAddress("MW200"); + + Assert.AreEqual(DataType.Memory, dataItem.DataType, "Wrong datatype for MW200"); + Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for MW200"); + Assert.AreEqual(VarType.Word, dataItem.VarType, "Wrong vartype for MW200"); + Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for MW200"); + Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for MW200"); + } + + [TestMethod] + public void T04_ParseMD200() + { + DataItem dataItem = DataItem.FromAddress("MD200"); + + Assert.AreEqual(DataType.Memory, dataItem.DataType, "Wrong datatype for MD200"); + Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for MD200"); + Assert.AreEqual(VarType.DWord, dataItem.VarType, "Wrong vartype for MD200"); + Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for MD200"); + Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for MD200"); + } + + + [TestMethod] + public void T05_ParseI2000_1() + { + DataItem dataItem = DataItem.FromAddress("I2000.1"); + + Assert.AreEqual(DataType.Input, dataItem.DataType, "Wrong datatype for I2000.1"); + Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for I2000.1"); + Assert.AreEqual(VarType.Bit, dataItem.VarType, "Wrong vartype for I2000.1"); + Assert.AreEqual(2000, dataItem.StartByteAdr, "Wrong startbyte for I2000.1"); + Assert.AreEqual(1, dataItem.BitAdr, "Wrong bit for I2000.1"); + } + + [TestMethod] + public void T06_ParseIB200() + { + DataItem dataItem = DataItem.FromAddress("IB200"); + + Assert.AreEqual(DataType.Input, dataItem.DataType, "Wrong datatype for IB200"); + Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for IB200"); + Assert.AreEqual(VarType.Byte, dataItem.VarType, "Wrong vartype for IB200"); + Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for IB200"); + Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for IB200"); + } + + [TestMethod] + public void T07_ParseIW200() + { + DataItem dataItem = DataItem.FromAddress("IW200"); + + Assert.AreEqual(DataType.Input, dataItem.DataType, "Wrong datatype for IW200"); + Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for IW200"); + Assert.AreEqual(VarType.Word, dataItem.VarType, "Wrong vartype for IW200"); + Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for IW200"); + Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for IW200"); + } + + [TestMethod] + public void T08_ParseID200() + { + DataItem dataItem = DataItem.FromAddress("ID200"); + + Assert.AreEqual(DataType.Input, dataItem.DataType, "Wrong datatype for ID200"); + Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for ID200"); + Assert.AreEqual(VarType.DWord, dataItem.VarType, "Wrong vartype for ID200"); + Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for ID200"); + Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for ID200"); + } + + + [TestMethod] + public void T09_ParseQ2000_1() + { + DataItem dataItem = DataItem.FromAddress("Q2000.1"); + + Assert.AreEqual(DataType.Output, dataItem.DataType, "Wrong datatype for Q2000.1"); + Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for Q2000.1"); + Assert.AreEqual(VarType.Bit, dataItem.VarType, "Wrong vartype for Q2000.1"); + Assert.AreEqual(2000, dataItem.StartByteAdr, "Wrong startbyte for Q2000.1"); + Assert.AreEqual(1, dataItem.BitAdr, "Wrong bit for Q2000.1"); + } + + [TestMethod] + public void T10_ParseQB200() + { + DataItem dataItem = DataItem.FromAddress("QB200"); + + Assert.AreEqual(DataType.Output, dataItem.DataType, "Wrong datatype for QB200"); + Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for QB200"); + Assert.AreEqual(VarType.Byte, dataItem.VarType, "Wrong vartype for QB200"); + Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for QB200"); + Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for QB200"); + } + + [TestMethod] + public void T11_ParseQW200() + { + DataItem dataItem = DataItem.FromAddress("QW200"); + + Assert.AreEqual(DataType.Output, dataItem.DataType, "Wrong datatype for QW200"); + Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for QW200"); + Assert.AreEqual(VarType.Word, dataItem.VarType, "Wrong vartype for QW200"); + Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for QW200"); + Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for QW200"); + } + + [TestMethod] + public void T12_ParseQD200() + { + DataItem dataItem = DataItem.FromAddress("QD200"); + + Assert.AreEqual(DataType.Output, dataItem.DataType, "Wrong datatype for QD200"); + Assert.AreEqual(0, dataItem.DB, "Wrong dbnumber for QD200"); + Assert.AreEqual(VarType.DWord, dataItem.VarType, "Wrong vartype for QD200"); + Assert.AreEqual(200, dataItem.StartByteAdr, "Wrong startbyte for QD200"); + Assert.AreEqual(0, dataItem.BitAdr, "Wrong bit for QD200"); + } + } +} diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index 321f0aa1..487efa84 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -63,6 +63,7 @@ + diff --git a/S7.Net/PLCAddress.cs b/S7.Net/PLCAddress.cs index e78015f4..ed542e5e 100644 --- a/S7.Net/PLCAddress.cs +++ b/S7.Net/PLCAddress.cs @@ -80,6 +80,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber, default: throw new InvalidAddressException(); } + case "IB": case "EB": // Input byte dataType = DataType.Input; @@ -87,6 +88,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber, address = int.Parse(input.Substring(2)); varType = VarType.Byte; return; + case "IW": case "EW": // Input word dataType = DataType.Input; @@ -94,6 +96,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber, address = int.Parse(input.Substring(2)); varType = VarType.Word; return; + case "ID": case "ED": // Input double-word dataType = DataType.Input; @@ -101,6 +104,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber, address = int.Parse(input.Substring(2)); varType = VarType.DWord; return; + case "QB": case "AB": // Output byte dataType = DataType.Output; @@ -108,6 +112,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber, address = int.Parse(input.Substring(2)); varType = VarType.Byte; return; + case "QW": case "AW": // Output word dataType = DataType.Output; @@ -115,6 +120,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber, address = int.Parse(input.Substring(2)); varType = VarType.Word; return; + case "QD": case "AD": // Output double-word dataType = DataType.Output; @@ -152,6 +158,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber, dataType = DataType.Input; varType = VarType.Bit; break; + case "Q": case "A": case "O": // Output @@ -161,7 +168,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber, case "M": // Memory dataType = DataType.Memory; - varType = VarType.Byte; + varType = VarType.Bit; break; case "T": // Timer From 9ea54be5245126fdfe7110f9dd8f2e20d2d0ccec Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Thu, 13 Aug 2020 21:40:44 +0200 Subject: [PATCH 2/5] Types/Class: Start arrays on even bytes Addresses #175 --- S7.Net/Types/Class.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index 227a5211..10528081 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -85,6 +85,7 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i throw new Exception("Cannot determine size of class, because an array is defined which has no fixed size greater than zero."); } + IncrementToEven(ref numBytes); for (int i = 0; i < array.Length; i++) { numBytes = GetIncreasedNumberOfBytes(numBytes, elementType); @@ -219,6 +220,7 @@ public static double FromBytes(object sourceClass, byte[] bytes, double numBytes if (property.PropertyType.IsArray) { Array array = (Array)property.GetValue(sourceClass, null); + IncrementToEven(ref numBytes); Type elementType = property.PropertyType.GetElementType(); for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) { @@ -288,10 +290,8 @@ private static double SetBytesFromProperty(object propertyValue, byte[] bytes, d if (bytes2 != null) { - // add them - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); + bytePos = (int)numBytes; for (int bCnt = 0; bCnt < bytes2.Length; bCnt++) bytes[bytePos + bCnt] = bytes2[bCnt]; @@ -314,6 +314,7 @@ public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = if (property.PropertyType.IsArray) { Array array = (Array)property.GetValue(sourceClass, null); + IncrementToEven(ref numBytes); Type elementType = property.PropertyType.GetElementType(); for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) { @@ -327,5 +328,11 @@ public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = } return numBytes; } + + private static void IncrementToEven(ref double numBytes) + { + numBytes = Math.Ceiling(numBytes); + if (numBytes % 2 > 0) numBytes++; + } } } From 9a34b14e1ea45818360d5a7568a5f5627e4436c9 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Thu, 13 Aug 2020 21:57:42 +0200 Subject: [PATCH 3/5] Tests/TypeTests: Add ClassTests from #178 Close #178. --- S7.Net.UnitTest/S7.Net.UnitTest.csproj | 1 + S7.Net.UnitTest/TypeTests/ClassTests.cs | 33 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 S7.Net.UnitTest/TypeTests/ClassTests.cs diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index 487efa84..0a14d88f 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -80,6 +80,7 @@ + diff --git a/S7.Net.UnitTest/TypeTests/ClassTests.cs b/S7.Net.UnitTest/TypeTests/ClassTests.cs new file mode 100644 index 00000000..ba99c2cc --- /dev/null +++ b/S7.Net.UnitTest/TypeTests/ClassTests.cs @@ -0,0 +1,33 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using S7.Net.Types; + +namespace S7.Net.UnitTest.TypeTests +{ + [TestClass] + public class ClassTests + { + [TestMethod] + public void GetClassSizeTest() + { + Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(1, 1)), 6); + Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(2, 15)), 6); + Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(2, 16)), 6); + Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(2, 17)), 8); + Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(3, 15)), 8); + Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(3, 17)), 10); + } + + private class TestClassUnevenSize + { + public bool Bool { get; set; } + public byte[] Bytes { get; set; } + public bool[] Bools { get; set; } + + public TestClassUnevenSize(int byteCount, int bitCount) + { + Bytes = new byte[byteCount]; + Bools = new bool[bitCount]; + } + } + } +} From 0a8ee0e091697c145404f239cd3b2b3ff36a67fa Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Thu, 13 Aug 2020 22:04:53 +0200 Subject: [PATCH 4/5] PLCAddress: Add OB, OW, OD types from PR #277 PR #246 included most types also included in #277, this adds OB, OW and OD that were only in #277. Close #277. --- S7.Net/PLCAddress.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/S7.Net/PLCAddress.cs b/S7.Net/PLCAddress.cs index ed542e5e..fd30b94f 100644 --- a/S7.Net/PLCAddress.cs +++ b/S7.Net/PLCAddress.cs @@ -106,6 +106,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber, return; case "QB": case "AB": + case "OB": // Output byte dataType = DataType.Output; dbNumber = 0; @@ -114,6 +115,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber, return; case "QW": case "AW": + case "OW": // Output word dataType = DataType.Output; dbNumber = 0; @@ -122,6 +124,7 @@ public static void Parse(string input, out DataType dataType, out int dbNumber, return; case "QD": case "AD": + case "OD": // Output double-word dataType = DataType.Output; dbNumber = 0; From 0b6226327b5f62138f4ee2583f690f6c89393e26 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Thu, 13 Aug 2020 22:47:32 +0200 Subject: [PATCH 5/5] PLC: Improve exceptions on Read Close #258. --- S7.Net/PLC.cs | 21 +++++++++++++++++++++ S7.Net/PlcAsynchronous.cs | 3 +-- S7.Net/PlcSynchronous.cs | 3 +-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index d997b7ad..8d98d4fb 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -218,6 +218,27 @@ private int GetDataLength(IEnumerable dataItems) .Sum(len => (len & 1) == 1 ? len + 1 : len); } + private static void AssertReadResponse(byte[] s7Data, int dataLength) + { + var expectedLength = dataLength + 18; + + PlcException NotEnoughBytes() => + new PlcException(ErrorCode.WrongNumberReceivedBytes, + $"Received {s7Data.Length} bytes: '{BitConverter.ToString(s7Data)}', expected {expectedLength} bytes.") + ; + + if (s7Data == null) + throw new PlcException(ErrorCode.WrongNumberReceivedBytes, "No s7Data received."); + + if (s7Data.Length < 15) throw NotEnoughBytes(); + + if (s7Data[14] != 0xff) + throw new PlcException(ErrorCode.ReadData, + $"Invalid response from PLC: '{BitConverter.ToString(s7Data)}'."); + + if (s7Data.Length < expectedLength) throw NotEnoughBytes(); + } + #region IDisposable Support private bool disposedValue = false; // To detect redundant calls diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 2a2de3e7..9e82c8d0 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -388,8 +388,7 @@ private async Task ReadBytesWithSingleRequestAsync(DataType dataType, in await stream.WriteAsync(package.Array, 0, package.Array.Length); var s7data = await COTP.TSDU.ReadAsync(stream); - if (s7data == null || s7data[14] != 0xff) - throw new PlcException(ErrorCode.WrongNumberReceivedBytes); + AssertReadResponse(s7data, count); for (int cnt = 0; cnt < count; cnt++) bytes[cnt] = s7data[cnt + 18]; diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index 7d2ef011..c101b3e0 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -353,8 +353,7 @@ private byte[] ReadBytesWithSingleRequest(DataType dataType, int db, int startBy stream.Write(package.Array, 0, package.Array.Length); var s7data = COTP.TSDU.Read(stream); - if (s7data == null || s7data[14] != 0xff) - throw new PlcException(ErrorCode.WrongNumberReceivedBytes); + AssertReadResponse(s7data, count); for (int cnt = 0; cnt < count; cnt++) bytes[cnt] = s7data[cnt + 18];