From 82e29837a2c629f567bd1e019593a8a9f9cc1773 Mon Sep 17 00:00:00 2001 From: Serge Camille Date: Tue, 16 Mar 2021 08:08:39 +0100 Subject: [PATCH 01/19] Sprinkle more .ConfigureAwait(false) into async library. This helps to avoid deadlocks when using synchronous API which references the async one in applications with a synchronization context, like a GUI. Should fix #344 --- S7.Net/PlcAsynchronous.cs | 48 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 42b61a7..de8f0c8 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -104,7 +104,7 @@ namespace S7.Net { //This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0. var maxToRead = Math.Min(count, MaxPDUSize - 18); - await ReadBytesWithSingleRequestAsync(dataType, db, startByteAdr + index, resultBytes, index, maxToRead, cancellationToken); + await ReadBytesWithSingleRequestAsync(dataType, db, startByteAdr + index, resultBytes, index, maxToRead, cancellationToken).ConfigureAwait(false); count -= maxToRead; index += maxToRead; } @@ -127,7 +127,7 @@ namespace S7.Net public async Task ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0, CancellationToken cancellationToken = default) { int cntBytes = VarTypeToByteLength(varType, varCount); - byte[] bytes = await ReadBytesAsync(dataType, db, startByteAdr, cntBytes, cancellationToken); + byte[] bytes = await ReadBytesAsync(dataType, db, startByteAdr, cntBytes, cancellationToken).ConfigureAwait(false); return ParseBytes(varType, bytes, varCount, bitAdr); } @@ -142,7 +142,7 @@ namespace S7.Net public async Task ReadAsync(string variable, CancellationToken cancellationToken = default) { var adr = new PLCAddress(variable); - return await ReadAsync(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber, cancellationToken); + return await ReadAsync(adr.DataType, adr.DbNumber, adr.StartByte, adr.VarType, 1, (byte)adr.BitNumber, cancellationToken).ConfigureAwait(false); } /// @@ -158,7 +158,7 @@ namespace S7.Net { int numBytes = Types.Struct.GetStructSize(structType); // now read the package - var resultBytes = await ReadBytesAsync(DataType.DataBlock, db, startByteAdr, numBytes, cancellationToken); + var resultBytes = await ReadBytesAsync(DataType.DataBlock, db, startByteAdr, numBytes, cancellationToken).ConfigureAwait(false); // and decode it return Types.Struct.FromBytes(structType, resultBytes); @@ -175,7 +175,7 @@ namespace S7.Net /// Returns a nulable struct. If nothing was read null will be returned. public async Task ReadStructAsync(int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : struct { - return await ReadStructAsync(typeof(T), db, startByteAdr, cancellationToken) as T?; + return await ReadStructAsync(typeof(T), db, startByteAdr, cancellationToken).ConfigureAwait(false) as T?; } /// @@ -197,7 +197,7 @@ namespace S7.Net } // now read the package - var resultBytes = await ReadBytesAsync(DataType.DataBlock, db, startByteAdr, numBytes, cancellationToken); + var resultBytes = await ReadBytesAsync(DataType.DataBlock, db, startByteAdr, numBytes, cancellationToken).ConfigureAwait(false); // and decode it Class.FromBytes(sourceClass, resultBytes); @@ -217,7 +217,7 @@ namespace S7.Net /// An instance of the class with the values read from the PLC. If no data has been read, null will be returned public async Task ReadClassAsync(int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : class { - return await ReadClassAsync(() => Activator.CreateInstance(), db, startByteAdr, cancellationToken); + return await ReadClassAsync(() => Activator.CreateInstance(), db, startByteAdr, cancellationToken).ConfigureAwait(false); } /// @@ -234,7 +234,7 @@ namespace S7.Net public async Task ReadClassAsync(Func classFactory, int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : class { var instance = classFactory(); - var res = await ReadClassAsync(instance, db, startByteAdr, cancellationToken); + var res = await ReadClassAsync(instance, db, startByteAdr, cancellationToken).ConfigureAwait(false); int readBytes = res.Item1; if (readBytes <= 0) { @@ -265,9 +265,9 @@ namespace S7.Net try { var dataToSend = BuildReadRequestPackage(dataItems.Select(d => DataItem.GetDataItemAddress(d)).ToList()); - await stream.WriteAsync(dataToSend, 0, dataToSend.Length); + await stream.WriteAsync(dataToSend, 0, dataToSend.Length).ConfigureAwait(false); - var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken); + var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); ValidateResponseCode((ReadWriteErrorCode)s7data[14]); ParseDataIntoDataItems(s7data, dataItems); @@ -306,7 +306,7 @@ namespace S7.Net while (count > 0) { var maxToWrite = (int)Math.Min(count, MaxPDUSize - 35); - await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite, cancellationToken); + await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite, cancellationToken).ConfigureAwait(false); count -= maxToWrite; localIndex += maxToWrite; } @@ -328,7 +328,7 @@ namespace S7.Net if (bitAdr < 0 || bitAdr > 7) throw new InvalidAddressException(string.Format("Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr)); - await WriteBitWithASingleRequestAsync(dataType, db, startByteAdr, bitAdr, value, cancellationToken); + await WriteBitWithASingleRequestAsync(dataType, db, startByteAdr, bitAdr, value, cancellationToken).ConfigureAwait(false); } /// @@ -347,7 +347,7 @@ namespace S7.Net if (value < 0 || value > 1) throw new ArgumentException("Value must be 0 or 1", nameof(value)); - await WriteBitAsync(dataType, db, startByteAdr, bitAdr, value == 1, cancellationToken); + await WriteBitAsync(dataType, db, startByteAdr, bitAdr, value == 1, cancellationToken).ConfigureAwait(false); } /// @@ -370,7 +370,7 @@ namespace S7.Net //Must be writing a bit value as bitAdr is specified if (value is bool boolean) { - await WriteBitAsync(dataType, db, startByteAdr, bitAdr, boolean, cancellationToken); + await WriteBitAsync(dataType, db, startByteAdr, bitAdr, boolean, cancellationToken).ConfigureAwait(false); } else if (value is int intValue) { @@ -380,11 +380,11 @@ namespace S7.Net "Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", bitAdr), nameof(bitAdr)); - await WriteBitAsync(dataType, db, startByteAdr, bitAdr, intValue == 1, cancellationToken); + await WriteBitAsync(dataType, db, startByteAdr, bitAdr, intValue == 1, cancellationToken).ConfigureAwait(false); } else throw new ArgumentException("Value must be a bool or an int to write a bit", nameof(value)); } - else await WriteBytesAsync(dataType, db, startByteAdr, Serialization.SerializeValue(value), cancellationToken); + else await WriteBytesAsync(dataType, db, startByteAdr, Serialization.SerializeValue(value), cancellationToken).ConfigureAwait(false); } /// @@ -399,7 +399,7 @@ namespace S7.Net public async Task WriteAsync(string variable, object value, CancellationToken cancellationToken = default) { var adr = new PLCAddress(variable); - await WriteAsync(adr.DataType, adr.DbNumber, adr.StartByte, value, adr.BitNumber, cancellationToken); + await WriteAsync(adr.DataType, adr.DbNumber, adr.StartByte, value, adr.BitNumber, cancellationToken).ConfigureAwait(false); } /// @@ -414,7 +414,7 @@ namespace S7.Net public async Task WriteStructAsync(object structValue, int db, int startByteAdr = 0, CancellationToken cancellationToken = default) { var bytes = Struct.ToBytes(structValue).ToList(); - await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray(), cancellationToken); + await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray(), cancellationToken).ConfigureAwait(false); } /// @@ -430,7 +430,7 @@ namespace S7.Net { byte[] bytes = new byte[(int)Class.GetClassSize(classValue)]; Types.Class.ToBytes(classValue, bytes); - await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes, cancellationToken); + await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes, cancellationToken).ConfigureAwait(false); } private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count, CancellationToken cancellationToken) @@ -440,7 +440,7 @@ namespace S7.Net var dataToSend = BuildReadRequestPackage(new [] { new DataItemAddress(dataType, db, startByteAdr, count)}); await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken); - var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken); + var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); AssertReadResponse(s7data, count); Array.Copy(s7data, 18, buffer, offset, count); @@ -482,9 +482,9 @@ namespace S7.Net var stream = GetStreamIfAvailable(); var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count); - await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken); + await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken).ConfigureAwait(false); - var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken); + var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); ValidateResponseCode((ReadWriteErrorCode)s7data[14]); } catch (OperationCanceledException) @@ -505,9 +505,9 @@ namespace S7.Net { var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr); - await stream.WriteAsync(dataToSend, 0, dataToSend.Length); + await stream.WriteAsync(dataToSend, 0, dataToSend.Length).ConfigureAwait(false); - var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken); + var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); ValidateResponseCode((ReadWriteErrorCode)s7data[14]); } catch (OperationCanceledException) From b4b94e177759b77d0045c8d64e57e30491a63a8b Mon Sep 17 00:00:00 2001 From: diego Date: Mon, 15 Mar 2021 19:08:40 +0100 Subject: [PATCH 02/19] Fix wrong calculation for S7WString byte length --- S7.Net/PLCHelpers.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index 4c4f8ac..b7a02b1 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -191,6 +191,8 @@ namespace S7.Net return varCount; case VarType.S7String: return varCount + 2; + case VarType.S7WString: + return (varCount * 2) + 4; case VarType.Word: case VarType.Timer: case VarType.Int: From e63d92c61cf8c5874e36adec3d17d8dfdcc6a928 Mon Sep 17 00:00:00 2001 From: diego Date: Mon, 15 Mar 2021 19:56:22 +0100 Subject: [PATCH 03/19] Fix ByteLength calculation for S7Strings when reserved is odd --- S7.Net/PLCHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index b7a02b1..07611cb 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -190,7 +190,7 @@ namespace S7.Net case VarType.String: return varCount; case VarType.S7String: - return varCount + 2; + return ((varCount + 2) & 1) == 1 ? (varCount + 3) : (varCount + 2); case VarType.S7WString: return (varCount * 2) + 4; case VarType.Word: From e6d14587d36174159332abc59680f06c703120bf Mon Sep 17 00:00:00 2001 From: diego Date: Mon, 15 Mar 2021 19:57:00 +0100 Subject: [PATCH 04/19] Added four new test methods to check ByteLength for S7 String types --- S7.Net.UnitTest/TypeTests/S7StringTests.cs | 19 ++++++++++++++++++- S7.Net.UnitTest/TypeTests/S7WStringTests.cs | 17 +++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/S7.Net.UnitTest/TypeTests/S7StringTests.cs b/S7.Net.UnitTest/TypeTests/S7StringTests.cs index e1cdc91..8c23135 100644 --- a/S7.Net.UnitTest/TypeTests/S7StringTests.cs +++ b/S7.Net.UnitTest/TypeTests/S7StringTests.cs @@ -117,13 +117,24 @@ namespace S7.Net.UnitTest.TypeTests AssertToByteArrayAndBackEquals("Abc", 4, 4, 3, (byte) 'A', (byte) 'b', (byte) 'c', 0); } + [TestMethod] + public void OddS7StringByteLength() + { + AssertVarTypeToByteLength(VarType.S7String, 1, 4); + } + + [TestMethod] + public void EvenS7StringByteLength() + { + AssertVarTypeToByteLength(VarType.S7String, 2, 4); + } + private static void AssertFromByteArrayEquals(string expected, params byte[] bytes) { var convertedString = S7String.FromByteArray(bytes); Assert.AreEqual(expected, convertedString); } - private static void AssertToByteArrayAndBackEquals(string value, int reservedLength, params byte[] expected) { var convertedData = S7String.ToByteArray(value, reservedLength); @@ -131,5 +142,11 @@ namespace S7.Net.UnitTest.TypeTests var convertedBack = S7String.FromByteArray(convertedData); Assert.AreEqual(value, convertedBack); } + + private void AssertVarTypeToByteLength(VarType varType, int count, int expectedByteLength) + { + var byteLength = Plc.VarTypeToByteLength(varType, count); + Assert.AreEqual(expectedByteLength, byteLength); + } } } diff --git a/S7.Net.UnitTest/TypeTests/S7WStringTests.cs b/S7.Net.UnitTest/TypeTests/S7WStringTests.cs index 877777c..119678c 100644 --- a/S7.Net.UnitTest/TypeTests/S7WStringTests.cs +++ b/S7.Net.UnitTest/TypeTests/S7WStringTests.cs @@ -122,6 +122,17 @@ namespace S7.Net.UnitTest.TypeTests Assert.AreEqual(expected, convertedString); } + [TestMethod] + public void OddS7WStringByteLength() + { + AssertVarTypeToByteLength(VarType.S7WString, 1, 6); + } + + [TestMethod] + public void EvenS7WStringByteLength() + { + AssertVarTypeToByteLength(VarType.S7WString, 2, 8); + } private static void AssertToByteArrayAndBackEquals(string value, int reservedLength, params byte[] expected) { @@ -130,5 +141,11 @@ namespace S7.Net.UnitTest.TypeTests var convertedBack = S7WString.FromByteArray(convertedData); Assert.AreEqual(value, convertedBack); } + + private void AssertVarTypeToByteLength(VarType varType, int count, int expectedByteLength) + { + var byteLength = Plc.VarTypeToByteLength(varType, count); + Assert.AreEqual(expectedByteLength, byteLength); + } } } From 2b4ec6d9dd64b69af954d29fdbf667324419757a Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Tue, 8 Sep 2020 23:04:26 +0200 Subject: [PATCH 05/19] Remove IsAvailable Addresses #275, #252, #208, #154. --- S7.Net.UnitTest/S7NetTestsSync.cs | 12 ++++++++++-- S7.Net/PLC.cs | 20 -------------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs index 377506b..c84c1a7 100644 --- a/S7.Net.UnitTest/S7NetTestsSync.cs +++ b/S7.Net.UnitTest/S7NetTestsSync.cs @@ -933,7 +933,14 @@ namespace S7.Net.UnitTest S7TestServer.Stop(); var unreachablePlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 2); - Assert.IsFalse(unreachablePlc.IsAvailable); + try + { + unreachablePlc.Open(); + } + catch + { + } + Assert.IsFalse(unreachablePlc.IsConnected); } [TestMethod] @@ -944,7 +951,8 @@ namespace S7.Net.UnitTest S7TestServer.Start(TestServerPort); var reachablePlc = CreatePlc(); - Assert.IsTrue(reachablePlc.IsAvailable); + reachablePlc.Open(); + Assert.IsTrue(reachablePlc.IsConnected); } [TestMethod] diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index f3a497e..de8b646 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -75,26 +75,6 @@ namespace S7.Net if (tcpClient != null) tcpClient.SendTimeout = writeTimeout; } } - - /// - /// Returns true if a connection to the PLC can be established - /// - public bool IsAvailable - { - //TODO: Fix This - get - { - try - { - OpenAsync().GetAwaiter().GetResult(); - return true; - } - catch - { - return false; - } - } - } /// /// Checks if the socket is connected and polls the other peer (the PLC) to see if it's connected. From aa034003507784620e54b2882b4dd382c04a1dcf Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Tue, 8 Sep 2020 23:07:59 +0200 Subject: [PATCH 06/19] Document IsConnected, shorten implementation --- S7.Net/PLC.cs | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index de8b646..8882a13 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -77,25 +77,24 @@ namespace S7.Net } /// - /// Checks if the socket is connected and polls the other peer (the PLC) to see if it's connected. - /// This is the variable that you should continously check to see if the communication is working - /// See also: http://stackoverflow.com/questions/2661764/how-to-check-if-a-socket-is-connected-disconnected-in-c + /// Gets a value indicating whether a connection to the PLC has been established. /// - public bool IsConnected - { - get - { - try - { - if (tcpClient == null) - return false; - - //TODO: Actually check communication by sending an empty TPDU - return tcpClient.Connected; - } - catch { return false; } - } - } + /// + /// The property gets the connection state of the Client socket as + /// of the last I/O operation. When it returns false, the Client socket was either + /// never connected, or is no longer connected. + /// + /// + /// Because the property only reflects the state of the connection + /// as of the most recent operation, you should attempt to send or receive a message to + /// determine the current state. After the message send fails, this property no longer + /// returns true. Note that this behavior is by design. You cannot reliably test the + /// state of the connection because, in the time between the test and a send/receive, the + /// connection could have been lost. Your code should assume the socket is connected, and + /// gracefully handle failed transmissions. + /// + /// + public bool IsConnected => tcpClient?.Connected ?? false; /// /// Creates a PLC object with all the parameters needed for connections. From a1b4694ef671d6509aa27c6f4c66ce738ebaadf8 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Mon, 10 May 2021 20:23:32 +0200 Subject: [PATCH 07/19] Fix calculation of byte length for VarType.Bit Fixes #389 --- S7.Net/PLCHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index 07611cb..ff56d27 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -184,7 +184,7 @@ namespace S7.Net switch (varType) { case VarType.Bit: - return varCount + 7 / 8; + return (varCount + 7) / 8; case VarType.Byte: return (varCount < 1) ? 1 : varCount; case VarType.String: From e66d21af05ae73a7a62fb970cec13e027df36ab6 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Mon, 10 May 2021 20:49:15 +0200 Subject: [PATCH 08/19] Add tests for Boolean.SetBit and Boolean.ClearBit --- S7.Net.UnitTest/TypeTests/BooleanTests.cs | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 S7.Net.UnitTest/TypeTests/BooleanTests.cs diff --git a/S7.Net.UnitTest/TypeTests/BooleanTests.cs b/S7.Net.UnitTest/TypeTests/BooleanTests.cs new file mode 100644 index 0000000..3390b79 --- /dev/null +++ b/S7.Net.UnitTest/TypeTests/BooleanTests.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Boolean = S7.Net.Types.Boolean; + +namespace S7.Net.UnitTest.TypeTests +{ + [TestClass] + public class BooleanTests + { + [DataTestMethod] + [DataRow(0)] + [DataRow(1)] + [DataRow(2)] + [DataRow(3)] + [DataRow(4)] + [DataRow(5)] + [DataRow(6)] + [DataRow(7)] + public void TestValidSetBitValues(int index) + { + Assert.AreEqual(Math.Pow(2, index), Boolean.SetBit(0, index)); + } + + [DataTestMethod] + [DataRow(0)] + [DataRow(1)] + [DataRow(2)] + [DataRow(3)] + [DataRow(4)] + [DataRow(5)] + [DataRow(6)] + [DataRow(7)] + public void TestValidClearBitValues(int index) + { + Assert.AreEqual((byte) ((uint) Math.Pow(2, index) ^ uint.MaxValue), Boolean.ClearBit(byte.MaxValue, index)); + } + } +} From 0b8bd66bf73f6236d18e1284a4a40b6bfadac613 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Mon, 10 May 2021 20:50:03 +0200 Subject: [PATCH 09/19] Fix bitwise operator in Boolean.ClearBit Fixes #249 --- S7.Net/Types/Boolean.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/S7.Net/Types/Boolean.cs b/S7.Net/Types/Boolean.cs index b83369d..cfb52f8 100644 --- a/S7.Net/Types/Boolean.cs +++ b/S7.Net/Types/Boolean.cs @@ -26,7 +26,7 @@ /// public static byte ClearBit(byte value, int bit) { - return (byte)((value | (~(1 << bit))) & 0xFF); + return (byte)((value & (~(1 << bit))) & 0xFF); } } From 3a794e8a4664d7ecf117b4bc7177ee2db96e78f1 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Mon, 10 May 2021 21:05:02 +0200 Subject: [PATCH 10/19] Cleanup trailing whitespace in PLC and PLCHelpers --- S7.Net/PLC.cs | 2 +- S7.Net/PLCHelpers.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index 8882a13..3a73b4c 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -14,7 +14,7 @@ namespace S7.Net public partial class Plc : IDisposable { private const int CONNECTION_TIMED_OUT_ERROR_CODE = 10060; - + //TCP connection to device private TcpClient? tcpClient; private NetworkStream? _stream; diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index ff56d27..5b2d77f 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -30,8 +30,8 @@ namespace S7.Net } /// - /// Create the bytes-package to request data from the PLC. You have to specify the memory type (dataType), - /// the address of the memory, the address of the byte and the bytes count. + /// Create the bytes-package to request data from the PLC. You have to specify the memory type (dataType), + /// the address of the memory, the address of the byte and the bytes count. /// /// MemoryType (DB, Timer, Counter, etc.) /// Address of the memory to be read From aa5028023371710152fd356bd2053d46747d7df9 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Mon, 10 May 2021 21:14:43 +0200 Subject: [PATCH 11/19] Boolean: Add SetBit and ClearBit by reference --- S7.Net/Types/Boolean.cs | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/S7.Net/Types/Boolean.cs b/S7.Net/Types/Boolean.cs index cfb52f8..f7bc83e 100644 --- a/S7.Net/Types/Boolean.cs +++ b/S7.Net/Types/Boolean.cs @@ -14,20 +14,51 @@ } /// - /// Sets the value of a bit to 1 (true), given the address of the bit + /// Sets the value of a bit to 1 (true), given the address of the bit. Returns + /// a copy of the value with the bit set. /// + /// The input value to modify. + /// The index (zero based) of the bit to set. + /// The modified value with the bit at index set. public static byte SetBit(byte value, int bit) { - return (byte)((value | (1 << bit)) & 0xFF); + SetBit(ref value, bit); + + return value; + } + + /// + /// Sets the value of a bit to 1 (true), given the address of the bit. + /// + /// The value to modify. + /// The index (zero based) of the bit to set. + public static void SetBit(ref byte value, int bit) + { + value = (byte) ((value | (1 << bit)) & 0xFF); + } + + /// + /// Resets the value of a bit to 0 (false), given the address of the bit. Returns + /// a copy of the value with the bit cleared. + /// + /// The input value to modify. + /// The index (zero based) of the bit to clear. + /// The modified value with the bit at index cleared. + public static byte ClearBit(byte value, int bit) + { + ClearBit(ref value, bit); + + return value; } /// /// Resets the value of a bit to 0 (false), given the address of the bit /// - public static byte ClearBit(byte value, int bit) + /// The input value to modify. + /// The index (zero based) of the bit to clear. + public static void ClearBit(ref byte value, int bit) { - return (byte)((value & (~(1 << bit))) & 0xFF); + value = (byte) (value & ~(1 << bit) & 0xFF); } - } } From 1ded47971b2a780e594205267bf61b618d3d0df8 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Mon, 31 May 2021 21:05:34 +0200 Subject: [PATCH 12/19] Consolidate async stream calls --- S7.Net/PlcAsynchronous.cs | 91 +++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index de8f0c8..7730b3a 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -25,16 +25,15 @@ namespace S7.Net /// A task that represents the asynchronous open operation. public async Task OpenAsync(CancellationToken cancellationToken = default) { - var stream = await ConnectAsync().ConfigureAwait(false); + _stream = await ConnectAsync().ConfigureAwait(false); try { cancellationToken.ThrowIfCancellationRequested(); - await EstablishConnection(stream, cancellationToken).ConfigureAwait(false); - _stream = stream; + await EstablishConnection(cancellationToken).ConfigureAwait(false); } catch(Exception) { - stream.Dispose(); + _stream.Dispose(); throw; } } @@ -47,29 +46,28 @@ namespace S7.Net return tcpClient.GetStream(); } - private async Task EstablishConnection(NetworkStream stream, CancellationToken cancellationToken) + private async Task EstablishConnection(CancellationToken cancellationToken) { - await RequestConnection(stream, cancellationToken).ConfigureAwait(false); - await SetupConnection(stream, cancellationToken).ConfigureAwait(false); + await RequestConnection(cancellationToken).ConfigureAwait(false); + await SetupConnection(cancellationToken).ConfigureAwait(false); } - private async Task RequestConnection(NetworkStream stream, CancellationToken cancellationToken) + private async Task RequestConnection(CancellationToken cancellationToken) { var requestData = ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot); - await stream.WriteAsync(requestData, 0, requestData.Length).ConfigureAwait(false); - var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + var response = await RequestTpduAsync(requestData, cancellationToken).ConfigureAwait(false); + if (response.PDUType != COTP.PduType.ConnectionConfirmed) { throw new InvalidDataException("Connection request was denied", response.TPkt.Data, 1, 0x0d); } } - private async Task SetupConnection(NetworkStream stream, CancellationToken cancellationToken) + private async Task SetupConnection(CancellationToken cancellationToken) { var setupData = GetS7ConnectionSetup(); - await stream.WriteAsync(setupData, 0, setupData.Length).ConfigureAwait(false); - var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + var s7data = await RequestTsduAsync(setupData, cancellationToken).ConfigureAwait(false); if (s7data.Length < 2) throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); @@ -112,7 +110,7 @@ namespace S7.Net } /// - /// Read and decode a certain number of bytes of the "VarType" provided. + /// Read and decode a certain number of bytes of the "VarType" provided. /// This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc). /// If the read was not successful, check LastErrorCode or LastErrorString. /// @@ -179,10 +177,10 @@ namespace S7.Net } /// - /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. + /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. /// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. /// - /// Instance of the class that will store the values + /// Instance of the class that will store the values /// Index of the DB; es.: 1 is for DB1 /// Start byte address. If you want to read DB1.DBW200, this is 200. /// The token to monitor for cancellation requests. The default value is None. @@ -205,7 +203,7 @@ namespace S7.Net } /// - /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. + /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. /// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. To instantiate the class defined by the generic /// type, the class needs a default constructor. /// @@ -221,7 +219,7 @@ namespace S7.Net } /// - /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. + /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. /// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. /// /// The class that will be instantiated @@ -245,10 +243,10 @@ namespace S7.Net } /// - /// Reads multiple vars in a single request. + /// Reads multiple vars in a single request. /// You have to create and pass a list of DataItems and you obtain in response the same list with the values. /// Values are stored in the property "Value" of the dataItem and are already converted. - /// If you don't want the conversion, just create a dataItem of bytes. + /// If you don't want the conversion, just create a dataItem of bytes. /// The number of DataItems as well as the total size of the requested data can not exceed a certain limit (protocol restriction). /// /// List of dataitems that contains the list of variables that must be read. @@ -256,18 +254,15 @@ namespace S7.Net /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases. public async Task> ReadMultipleVarsAsync(List dataItems, CancellationToken cancellationToken = default) { - //Snap7 seems to choke on PDU sizes above 256 even if snap7 + //Snap7 seems to choke on PDU sizes above 256 even if snap7 //replies with bigger PDU size in connection setup. AssertPduSizeForRead(dataItems); - var stream = GetStreamIfAvailable(); - try { var dataToSend = BuildReadRequestPackage(dataItems.Select(d => DataItem.GetDataItemAddress(d)).ToList()); - await stream.WriteAsync(dataToSend, 0, dataToSend.Length).ConfigureAwait(false); + var s7data = await RequestTsduAsync(dataToSend, cancellationToken); - var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); ValidateResponseCode((ReadWriteErrorCode)s7data[14]); ParseDataIntoDataItems(s7data, dataItems); @@ -435,12 +430,9 @@ namespace S7.Net private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - var stream = GetStreamIfAvailable(); - var dataToSend = BuildReadRequestPackage(new [] { new DataItemAddress(dataType, db, startByteAdr, count)}); - await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken); - var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + var s7data = await RequestTsduAsync(dataToSend, cancellationToken); AssertReadResponse(s7data, count); Array.Copy(s7data, 18, buffer, offset, count); @@ -456,13 +448,11 @@ namespace S7.Net { AssertPduSizeForWrite(dataItems); - var stream = GetStreamIfAvailable(); - var message = new ByteArray(); var length = S7WriteMultiple.CreateRequest(message, dataItems); - await stream.WriteAsync(message.Array, 0, length).ConfigureAwait(false); - var response = await COTP.TSDU.ReadAsync(stream, CancellationToken.None).ConfigureAwait(false); + var response = await RequestTsduAsync(message.Array, 0, length).ConfigureAwait(false); + S7WriteMultiple.ParseResponse(response, response.Length, dataItems); } @@ -476,15 +466,11 @@ namespace S7.Net /// A task that represents the asynchronous write operation. private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count, CancellationToken cancellationToken) { - try { - var stream = GetStreamIfAvailable(); var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count); + var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false); - await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken).ConfigureAwait(false); - - var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); ValidateResponseCode((ReadWriteErrorCode)s7data[14]); } catch (OperationCanceledException) @@ -499,15 +485,11 @@ namespace S7.Net private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue, CancellationToken cancellationToken) { - var stream = GetStreamIfAvailable(); - try { var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr); + var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false); - await stream.WriteAsync(dataToSend, 0, dataToSend.Length).ConfigureAwait(false); - - var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); ValidateResponseCode((ReadWriteErrorCode)s7data[14]); } catch (OperationCanceledException) @@ -528,5 +510,28 @@ namespace S7.Net } return _stream; } + + private async Task RequestTpduAsync(byte[] requestData, CancellationToken cancellationToken = default) + { + var stream = GetStreamIfAvailable(); + + await stream.WriteAsync(requestData, 0, requestData.Length, cancellationToken).ConfigureAwait(false); + var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + + return response; + } + + private Task RequestTsduAsync(byte[] requestData, CancellationToken cancellationToken = default) => + RequestTsduAsync(requestData, 0, requestData.Length, cancellationToken); + + private async Task RequestTsduAsync(byte[] requestData, int offset, int length, CancellationToken cancellationToken = default) + { + var stream = GetStreamIfAvailable(); + + await stream.WriteAsync(requestData, offset, length, cancellationToken).ConfigureAwait(false); + var response = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + + return response; + } } } From 2afed882316f3d81c085dae9f7881cfd6f57f858 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Mon, 31 May 2021 21:17:03 +0200 Subject: [PATCH 13/19] Consolidate sync stream calls --- S7.Net/PlcSynchronous.cs | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index fb6c825..9f90f7a 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -298,7 +298,6 @@ namespace S7.Net private void ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count) { - var stream = GetStreamIfAvailable(); try { // first create the header @@ -309,9 +308,7 @@ namespace S7.Net BuildReadDataRequestPackage(package, dataType, db, startByteAdr, count); var dataToSend = package.ToArray(); - stream.Write(dataToSend, 0, dataToSend.Length); - - var s7data = COTP.TSDU.Read(stream); + var s7data = RequestTsdu(dataToSend); AssertReadResponse(s7data, count); Array.Copy(s7data, 18, buffer, offset, count); @@ -331,13 +328,11 @@ namespace S7.Net { AssertPduSizeForWrite(dataItems); - var stream = GetStreamIfAvailable(); var message = new ByteArray(); var length = S7WriteMultiple.CreateRequest(message, dataItems); - stream.Write(message.Array, 0, length); + var response = RequestTsdu(message.Array, 0, length); - var response = COTP.TSDU.Read(stream); S7WriteMultiple.ParseResponse(response, response.Length, dataItems); } @@ -345,12 +340,9 @@ namespace S7.Net { try { - var stream = GetStreamIfAvailable(); var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count); + var s7data = RequestTsdu(dataToSend); - stream.Write(dataToSend, 0, dataToSend.Length); - - var s7data = COTP.TSDU.Read(stream); ValidateResponseCode((ReadWriteErrorCode)s7data[14]); } catch (Exception exc) @@ -425,14 +417,11 @@ namespace S7.Net private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue) { - var stream = GetStreamIfAvailable(); try { var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr); + var s7data = RequestTsdu(dataToSend); - stream.Write(dataToSend, 0, dataToSend.Length); - - var s7data = COTP.TSDU.Read(stream); ValidateResponseCode((ReadWriteErrorCode)s7data[14]); } catch (Exception exc) @@ -453,8 +442,6 @@ namespace S7.Net { AssertPduSizeForRead(dataItems); - var stream = GetStreamIfAvailable(); - try { // first create the header @@ -468,9 +455,7 @@ namespace S7.Net } var dataToSend = package.ToArray(); - stream.Write(dataToSend, 0, dataToSend.Length); - - var s7data = COTP.TSDU.Read(stream); //TODO use Async + var s7data = RequestTsdu(dataToSend); ValidateResponseCode((ReadWriteErrorCode)s7data[14]); @@ -481,5 +466,17 @@ namespace S7.Net throw new PlcException(ErrorCode.ReadData, exc); } } + + private byte[] RequestTsdu(byte[] requestData) => RequestTsdu(requestData, 0, requestData.Length); + + private byte[] RequestTsdu(byte[] requestData, int offset, int length) + { + var stream = GetStreamIfAvailable(); + + stream.Write(requestData, offset, length); + var response = COTP.TSDU.Read(stream); + + return response; + } } } From 8ed1b840bcaaca35a80b7b608b9d70470339dbd4 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Mon, 31 May 2021 21:17:23 +0200 Subject: [PATCH 14/19] PlcSynchronous: Clenaup trailing whitespace --- S7.Net/PlcSynchronous.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index 9f90f7a..61b99af 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -54,7 +54,7 @@ namespace S7.Net } /// - /// Read and decode a certain number of bytes of the "VarType" provided. + /// Read and decode a certain number of bytes of the "VarType" provided. /// This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc). /// If the read was not successful, check LastErrorCode or LastErrorString. /// @@ -115,10 +115,10 @@ namespace S7.Net /// - /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. + /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. /// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. /// - /// Instance of the class that will store the values + /// Instance of the class that will store the values /// Index of the DB; es.: 1 is for DB1 /// Start byte address. If you want to read DB1.DBW200, this is 200. /// The number of read bytes @@ -138,7 +138,7 @@ namespace S7.Net } /// - /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. + /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. /// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. To instantiate the class defined by the generic /// type, the class needs a default constructor. /// @@ -152,7 +152,7 @@ namespace S7.Net } /// - /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. + /// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC. /// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. /// /// The class that will be instantiated @@ -186,7 +186,7 @@ namespace S7.Net while (count > 0) { //TODO: Figure out how to use MaxPDUSize here - //Snap7 seems to choke on PDU sizes above 256 even if snap7 + //Snap7 seems to choke on PDU sizes above 256 even if snap7 //replies with bigger PDU size in connection setup. var maxToWrite = Math.Min(count, MaxPDUSize - 28);//TODO tested only when the MaxPDUSize is 480 WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite); @@ -431,10 +431,10 @@ namespace S7.Net } /// - /// Reads multiple vars in a single request. + /// Reads multiple vars in a single request. /// You have to create and pass a list of DataItems and you obtain in response the same list with the values. /// Values are stored in the property "Value" of the dataItem and are already converted. - /// If you don't want the conversion, just create a dataItem of bytes. + /// If you don't want the conversion, just create a dataItem of bytes. /// The number of DataItems as well as the total size of the requested data can not exceed a certain limit (protocol restriction). /// /// List of dataitems that contains the list of variables that must be read. From 5636b93a53cef44590727d752267186cce1c5740 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Mon, 31 May 2021 21:17:42 +0200 Subject: [PATCH 15/19] PlcSynchronous: Remove unused usings --- S7.Net/PlcSynchronous.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index 61b99af..881b7bf 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -2,8 +2,6 @@ using System; using System.IO; using System.Collections.Generic; -using System.Linq; -using System.Net.Sockets; using S7.Net.Protocol; using S7.Net.Helper; From df4f258290e7f667b9828441454c5f11bac790f5 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Mon, 31 May 2021 21:23:32 +0200 Subject: [PATCH 16/19] Move GetStreamIfAvailable to PLC.cs --- S7.Net/PLC.cs | 11 +++++++++++ S7.Net/PlcAsynchronous.cs | 10 ---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index 3a73b4c..b7ede94 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Sockets; using S7.Net.Protocol; @@ -242,6 +243,16 @@ namespace S7.Net } } + private Stream GetStreamIfAvailable() + { + if (_stream == null) + { + throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected"); + } + + return _stream; + } + #region IDisposable Support private bool disposedValue = false; // To detect redundant calls diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 7730b3a..3e523d8 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Net.Sockets; using System.Threading.Tasks; using S7.Net.Protocol; -using System.IO; using System.Threading; using S7.Net.Protocol.S7; @@ -502,15 +501,6 @@ namespace S7.Net } } - private Stream GetStreamIfAvailable() - { - if (_stream == null) - { - throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected"); - } - return _stream; - } - private async Task RequestTpduAsync(byte[] requestData, CancellationToken cancellationToken = default) { var stream = GetStreamIfAvailable(); From 8035f71a1659b0129c80d1eff7e6a3d709b9225e Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Mon, 31 May 2021 22:57:13 +0200 Subject: [PATCH 17/19] Apply synchronization to stream actions --- S7.Net/Internal/TaskQueue.cs | 28 ++++++++++++++++++++++++++++ S7.Net/PLC.cs | 3 +++ S7.Net/PlcAsynchronous.cs | 22 ++++++++++++++-------- S7.Net/PlcSynchronous.cs | 7 +------ 4 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 S7.Net/Internal/TaskQueue.cs diff --git a/S7.Net/Internal/TaskQueue.cs b/S7.Net/Internal/TaskQueue.cs new file mode 100644 index 0000000..bd0cd6b --- /dev/null +++ b/S7.Net/Internal/TaskQueue.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace S7.Net.Internal +{ + internal class TaskQueue + { + private static readonly object Sentinel = new object(); + + private Task prev = Task.FromResult(Sentinel); + + public async Task Enqueue(Func> action) + { + var tcs = new TaskCompletionSource(); + await Interlocked.Exchange(ref prev, tcs.Task).ConfigureAwait(false); + + try + { + return await action.Invoke().ConfigureAwait(false); + } + finally + { + tcs.SetResult(Sentinel); + } + } + } +} \ No newline at end of file diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index b7ede94..6180ad5 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Sockets; +using S7.Net.Internal; using S7.Net.Protocol; using S7.Net.Types; @@ -14,6 +15,8 @@ namespace S7.Net /// public partial class Plc : IDisposable { + private readonly TaskQueue queue = new TaskQueue(); + private const int CONNECTION_TIMED_OUT_ERROR_CODE = 10060; //TCP connection to device diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 3e523d8..91c2922 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -501,27 +501,33 @@ namespace S7.Net } } - private async Task RequestTpduAsync(byte[] requestData, CancellationToken cancellationToken = default) + private Task RequestTpduAsync(byte[] requestData, CancellationToken cancellationToken = default) { var stream = GetStreamIfAvailable(); - await stream.WriteAsync(requestData, 0, requestData.Length, cancellationToken).ConfigureAwait(false); - var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + return queue.Enqueue(async () => + { + await stream.WriteAsync(requestData, 0, requestData.Length, cancellationToken).ConfigureAwait(false); + var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); - return response; + return response; + }); } private Task RequestTsduAsync(byte[] requestData, CancellationToken cancellationToken = default) => RequestTsduAsync(requestData, 0, requestData.Length, cancellationToken); - private async Task RequestTsduAsync(byte[] requestData, int offset, int length, CancellationToken cancellationToken = default) + private Task RequestTsduAsync(byte[] requestData, int offset, int length, CancellationToken cancellationToken = default) { var stream = GetStreamIfAvailable(); - await stream.WriteAsync(requestData, offset, length, cancellationToken).ConfigureAwait(false); - var response = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + return queue.Enqueue(async () => + { + await stream.WriteAsync(requestData, offset, length, cancellationToken).ConfigureAwait(false); + var response = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); - return response; + return response; + }); } } } diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index 881b7bf..80e4738 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -469,12 +469,7 @@ namespace S7.Net private byte[] RequestTsdu(byte[] requestData, int offset, int length) { - var stream = GetStreamIfAvailable(); - - stream.Write(requestData, offset, length); - var response = COTP.TSDU.Read(stream); - - return response; + return RequestTsduAsync(requestData, offset, length).GetAwaiter().GetResult(); } } } From e93a6563128431c5d5a2d1c607f9a83e635beb65 Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Tue, 1 Jun 2021 20:28:34 +0200 Subject: [PATCH 18/19] Fix locking for OpenAsync --- S7.Net/PlcAsynchronous.cs | 69 ++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 91c2922..5482ec9 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -1,6 +1,7 @@ using S7.Net.Types; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Sockets; using System.Threading.Tasks; @@ -24,15 +25,21 @@ namespace S7.Net /// A task that represents the asynchronous open operation. public async Task OpenAsync(CancellationToken cancellationToken = default) { - _stream = await ConnectAsync().ConfigureAwait(false); + var stream = await ConnectAsync().ConfigureAwait(false); try { - cancellationToken.ThrowIfCancellationRequested(); - await EstablishConnection(cancellationToken).ConfigureAwait(false); + await queue.Enqueue(async () => + { + cancellationToken.ThrowIfCancellationRequested(); + await EstablishConnection(stream, cancellationToken).ConfigureAwait(false); + _stream = stream; + + return default(object); + }); } catch(Exception) { - _stream.Dispose(); + stream.Dispose(); throw; } } @@ -45,16 +52,16 @@ namespace S7.Net return tcpClient.GetStream(); } - private async Task EstablishConnection(CancellationToken cancellationToken) + private async Task EstablishConnection(Stream stream, CancellationToken cancellationToken) { - await RequestConnection(cancellationToken).ConfigureAwait(false); - await SetupConnection(cancellationToken).ConfigureAwait(false); + await RequestConnection(stream, cancellationToken).ConfigureAwait(false); + await SetupConnection(stream, cancellationToken).ConfigureAwait(false); } - private async Task RequestConnection(CancellationToken cancellationToken) + private async Task RequestConnection(Stream stream, CancellationToken cancellationToken) { var requestData = ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot); - var response = await RequestTpduAsync(requestData, cancellationToken).ConfigureAwait(false); + var response = await NoLockRequestTpduAsync(stream, requestData, cancellationToken).ConfigureAwait(false); if (response.PDUType != COTP.PduType.ConnectionConfirmed) { @@ -62,11 +69,13 @@ namespace S7.Net } } - private async Task SetupConnection(CancellationToken cancellationToken) + private async Task SetupConnection(Stream stream, CancellationToken cancellationToken) { var setupData = GetS7ConnectionSetup(); - var s7data = await RequestTsduAsync(setupData, cancellationToken).ConfigureAwait(false); + var s7data = await NoLockRequestTsduAsync(stream, setupData, 0, setupData.Length, cancellationToken) + .ConfigureAwait(false); + if (s7data.Length < 2) throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); @@ -501,19 +510,6 @@ namespace S7.Net } } - private Task RequestTpduAsync(byte[] requestData, CancellationToken cancellationToken = default) - { - var stream = GetStreamIfAvailable(); - - return queue.Enqueue(async () => - { - await stream.WriteAsync(requestData, 0, requestData.Length, cancellationToken).ConfigureAwait(false); - var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); - - return response; - }); - } - private Task RequestTsduAsync(byte[] requestData, CancellationToken cancellationToken = default) => RequestTsduAsync(requestData, 0, requestData.Length, cancellationToken); @@ -521,13 +517,26 @@ namespace S7.Net { var stream = GetStreamIfAvailable(); - return queue.Enqueue(async () => - { - await stream.WriteAsync(requestData, offset, length, cancellationToken).ConfigureAwait(false); - var response = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + return queue.Enqueue(() => + NoLockRequestTsduAsync(stream, requestData, offset, length, cancellationToken)); + } - return response; - }); + private static async Task NoLockRequestTpduAsync(Stream stream, byte[] requestData, + CancellationToken cancellationToken = default) + { + await stream.WriteAsync(requestData, 0, requestData.Length, cancellationToken).ConfigureAwait(false); + var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + + return response; + } + + private static async Task NoLockRequestTsduAsync(Stream stream, byte[] requestData, int offset, int length, + CancellationToken cancellationToken = default) + { + await stream.WriteAsync(requestData, offset, length, cancellationToken).ConfigureAwait(false); + var response = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false); + + return response; } } } From f67b1e773f23262406c1e41cb9be1776a34e149e Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Tue, 1 Jun 2021 20:59:20 +0200 Subject: [PATCH 19/19] Add missing ConfigureAwait in OpenAsync --- S7.Net/PlcAsynchronous.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 5482ec9..c7a5868 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -35,7 +35,7 @@ namespace S7.Net _stream = stream; return default(object); - }); + }).ConfigureAwait(false); } catch(Exception) {