Skip to content

Commit 5318f94

Browse files
committed
Release S7NetPlus 0.12.0
Release highlights: - Add synchronization to methods interacting with the PLC
2 parents ced10b4 + ea3beff commit 5318f94

File tree

4 files changed

+119
-77
lines changed

4 files changed

+119
-77
lines changed

S7.Net/Internal/TaskQueue.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
namespace S7.Net.Internal
6+
{
7+
internal class TaskQueue
8+
{
9+
private static readonly object Sentinel = new object();
10+
11+
private Task prev = Task.FromResult(Sentinel);
12+
13+
public async Task<T> Enqueue<T>(Func<Task<T>> action)
14+
{
15+
var tcs = new TaskCompletionSource<object>();
16+
await Interlocked.Exchange(ref prev, tcs.Task).ConfigureAwait(false);
17+
18+
try
19+
{
20+
return await action.Invoke().ConfigureAwait(false);
21+
}
22+
finally
23+
{
24+
tcs.SetResult(Sentinel);
25+
}
26+
}
27+
}
28+
}

S7.Net/PLC.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.IO;
34
using System.Linq;
45
using System.Net.Sockets;
6+
using S7.Net.Internal;
57
using S7.Net.Protocol;
68
using S7.Net.Types;
79

@@ -13,6 +15,8 @@ namespace S7.Net
1315
/// </summary>
1416
public partial class Plc : IDisposable
1517
{
18+
private readonly TaskQueue queue = new TaskQueue();
19+
1620
private const int CONNECTION_TIMED_OUT_ERROR_CODE = 10060;
1721

1822
//TCP connection to device
@@ -242,6 +246,16 @@ internal static void ValidateResponseCode(ReadWriteErrorCode statusCode)
242246
}
243247
}
244248

249+
private Stream GetStreamIfAvailable()
250+
{
251+
if (_stream == null)
252+
{
253+
throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected");
254+
}
255+
256+
return _stream;
257+
}
258+
245259
#region IDisposable Support
246260
private bool disposedValue = false; // To detect redundant calls
247261

S7.Net/PlcAsynchronous.cs

Lines changed: 57 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
using S7.Net.Types;
22
using System;
33
using System.Collections.Generic;
4+
using System.IO;
45
using System.Linq;
56
using System.Net.Sockets;
67
using System.Threading.Tasks;
78
using S7.Net.Protocol;
8-
using System.IO;
99
using System.Threading;
1010
using S7.Net.Protocol.S7;
1111

@@ -28,9 +28,14 @@ public async Task OpenAsync(CancellationToken cancellationToken = default)
2828
var stream = await ConnectAsync().ConfigureAwait(false);
2929
try
3030
{
31-
cancellationToken.ThrowIfCancellationRequested();
32-
await EstablishConnection(stream, cancellationToken).ConfigureAwait(false);
33-
_stream = stream;
31+
await queue.Enqueue(async () =>
32+
{
33+
cancellationToken.ThrowIfCancellationRequested();
34+
await EstablishConnection(stream, cancellationToken).ConfigureAwait(false);
35+
_stream = stream;
36+
37+
return default(object);
38+
}).ConfigureAwait(false);
3439
}
3540
catch(Exception)
3641
{
@@ -47,29 +52,30 @@ private async Task<NetworkStream> ConnectAsync()
4752
return tcpClient.GetStream();
4853
}
4954

50-
private async Task EstablishConnection(NetworkStream stream, CancellationToken cancellationToken)
55+
private async Task EstablishConnection(Stream stream, CancellationToken cancellationToken)
5156
{
5257
await RequestConnection(stream, cancellationToken).ConfigureAwait(false);
5358
await SetupConnection(stream, cancellationToken).ConfigureAwait(false);
5459
}
5560

56-
private async Task RequestConnection(NetworkStream stream, CancellationToken cancellationToken)
61+
private async Task RequestConnection(Stream stream, CancellationToken cancellationToken)
5762
{
5863
var requestData = ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot);
59-
await stream.WriteAsync(requestData, 0, requestData.Length).ConfigureAwait(false);
60-
var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
64+
var response = await NoLockRequestTpduAsync(stream, requestData, cancellationToken).ConfigureAwait(false);
65+
6166
if (response.PDUType != COTP.PduType.ConnectionConfirmed)
6267
{
6368
throw new InvalidDataException("Connection request was denied", response.TPkt.Data, 1, 0x0d);
6469
}
6570
}
6671

67-
private async Task SetupConnection(NetworkStream stream, CancellationToken cancellationToken)
72+
private async Task SetupConnection(Stream stream, CancellationToken cancellationToken)
6873
{
6974
var setupData = GetS7ConnectionSetup();
70-
await stream.WriteAsync(setupData, 0, setupData.Length).ConfigureAwait(false);
7175

72-
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
76+
var s7data = await NoLockRequestTsduAsync(stream, setupData, 0, setupData.Length, cancellationToken)
77+
.ConfigureAwait(false);
78+
7379
if (s7data.Length < 2)
7480
throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup");
7581

@@ -112,7 +118,7 @@ public async Task<byte[]> ReadBytesAsync(DataType dataType, int db, int startByt
112118
}
113119

114120
/// <summary>
115-
/// Read and decode a certain number of bytes of the "VarType" provided.
121+
/// Read and decode a certain number of bytes of the "VarType" provided.
116122
/// This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc).
117123
/// If the read was not successful, check LastErrorCode or LastErrorString.
118124
/// </summary>
@@ -179,10 +185,10 @@ public async Task<byte[]> ReadBytesAsync(DataType dataType, int db, int startByt
179185
}
180186

181187
/// <summary>
182-
/// 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.
188+
/// 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.
183189
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
184190
/// </summary>
185-
/// <param name="sourceClass">Instance of the class that will store the values</param>
191+
/// <param name="sourceClass">Instance of the class that will store the values</param>
186192
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
187193
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
188194
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
@@ -205,7 +211,7 @@ public async Task<Tuple<int, object>> ReadClassAsync(object sourceClass, int db,
205211
}
206212

207213
/// <summary>
208-
/// 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.
214+
/// 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.
209215
/// 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
210216
/// type, the class needs a default constructor.
211217
/// </summary>
@@ -221,7 +227,7 @@ public async Task<Tuple<int, object>> ReadClassAsync(object sourceClass, int db,
221227
}
222228

223229
/// <summary>
224-
/// 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.
230+
/// 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.
225231
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
226232
/// </summary>
227233
/// <typeparam name="T">The class that will be instantiated</typeparam>
@@ -245,29 +251,26 @@ public async Task<Tuple<int, object>> ReadClassAsync(object sourceClass, int db,
245251
}
246252

247253
/// <summary>
248-
/// Reads multiple vars in a single request.
254+
/// Reads multiple vars in a single request.
249255
/// You have to create and pass a list of DataItems and you obtain in response the same list with the values.
250256
/// Values are stored in the property "Value" of the dataItem and are already converted.
251-
/// If you don't want the conversion, just create a dataItem of bytes.
257+
/// If you don't want the conversion, just create a dataItem of bytes.
252258
/// The number of DataItems as well as the total size of the requested data can not exceed a certain limit (protocol restriction).
253259
/// </summary>
254260
/// <param name="dataItems">List of dataitems that contains the list of variables that must be read.</param>
255261
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
256262
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
257263
public async Task<List<DataItem>> ReadMultipleVarsAsync(List<DataItem> dataItems, CancellationToken cancellationToken = default)
258264
{
259-
//Snap7 seems to choke on PDU sizes above 256 even if snap7
265+
//Snap7 seems to choke on PDU sizes above 256 even if snap7
260266
//replies with bigger PDU size in connection setup.
261267
AssertPduSizeForRead(dataItems);
262268

263-
var stream = GetStreamIfAvailable();
264-
265269
try
266270
{
267271
var dataToSend = BuildReadRequestPackage(dataItems.Select(d => DataItem.GetDataItemAddress(d)).ToList());
268-
await stream.WriteAsync(dataToSend, 0, dataToSend.Length).ConfigureAwait(false);
272+
var s7data = await RequestTsduAsync(dataToSend, cancellationToken);
269273

270-
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
271274
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
272275

273276
ParseDataIntoDataItems(s7data, dataItems);
@@ -435,12 +438,9 @@ public async Task WriteClassAsync(object classValue, int db, int startByteAdr =
435438

436439
private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
437440
{
438-
var stream = GetStreamIfAvailable();
439-
440441
var dataToSend = BuildReadRequestPackage(new [] { new DataItemAddress(dataType, db, startByteAdr, count)});
441-
await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken);
442442

443-
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
443+
var s7data = await RequestTsduAsync(dataToSend, cancellationToken);
444444
AssertReadResponse(s7data, count);
445445

446446
Array.Copy(s7data, 18, buffer, offset, count);
@@ -456,13 +456,11 @@ public async Task WriteAsync(params DataItem[] dataItems)
456456
{
457457
AssertPduSizeForWrite(dataItems);
458458

459-
var stream = GetStreamIfAvailable();
460-
461459
var message = new ByteArray();
462460
var length = S7WriteMultiple.CreateRequest(message, dataItems);
463-
await stream.WriteAsync(message.Array, 0, length).ConfigureAwait(false);
464461

465-
var response = await COTP.TSDU.ReadAsync(stream, CancellationToken.None).ConfigureAwait(false);
462+
var response = await RequestTsduAsync(message.Array, 0, length).ConfigureAwait(false);
463+
466464
S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
467465
}
468466

@@ -476,15 +474,11 @@ public async Task WriteAsync(params DataItem[] dataItems)
476474
/// <returns>A task that represents the asynchronous write operation.</returns>
477475
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count, CancellationToken cancellationToken)
478476
{
479-
480477
try
481478
{
482-
var stream = GetStreamIfAvailable();
483479
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count);
480+
var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false);
484481

485-
await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken).ConfigureAwait(false);
486-
487-
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
488482
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
489483
}
490484
catch (OperationCanceledException)
@@ -499,15 +493,11 @@ private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db,
499493

500494
private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue, CancellationToken cancellationToken)
501495
{
502-
var stream = GetStreamIfAvailable();
503-
504496
try
505497
{
506498
var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr);
499+
var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false);
507500

508-
await stream.WriteAsync(dataToSend, 0, dataToSend.Length).ConfigureAwait(false);
509-
510-
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
511501
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
512502
}
513503
catch (OperationCanceledException)
@@ -520,13 +510,33 @@ private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, in
520510
}
521511
}
522512

523-
private Stream GetStreamIfAvailable()
513+
private Task<byte[]> RequestTsduAsync(byte[] requestData, CancellationToken cancellationToken = default) =>
514+
RequestTsduAsync(requestData, 0, requestData.Length, cancellationToken);
515+
516+
private Task<byte[]> RequestTsduAsync(byte[] requestData, int offset, int length, CancellationToken cancellationToken = default)
524517
{
525-
if (_stream == null)
526-
{
527-
throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected");
528-
}
529-
return _stream;
518+
var stream = GetStreamIfAvailable();
519+
520+
return queue.Enqueue(() =>
521+
NoLockRequestTsduAsync(stream, requestData, offset, length, cancellationToken));
522+
}
523+
524+
private static async Task<COTP.TPDU> NoLockRequestTpduAsync(Stream stream, byte[] requestData,
525+
CancellationToken cancellationToken = default)
526+
{
527+
await stream.WriteAsync(requestData, 0, requestData.Length, cancellationToken).ConfigureAwait(false);
528+
var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
529+
530+
return response;
531+
}
532+
533+
private static async Task<byte[]> NoLockRequestTsduAsync(Stream stream, byte[] requestData, int offset, int length,
534+
CancellationToken cancellationToken = default)
535+
{
536+
await stream.WriteAsync(requestData, offset, length, cancellationToken).ConfigureAwait(false);
537+
var response = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
538+
539+
return response;
530540
}
531541
}
532542
}

0 commit comments

Comments
 (0)