Merge branch 'develop' into githubActions2

This commit is contained in:
Serge Camille
2021-06-05 17:52:59 +02:00
10 changed files with 279 additions and 145 deletions

View File

@@ -933,7 +933,14 @@ namespace S7.Net.UnitTest
S7TestServer.Stop(); S7TestServer.Stop();
var unreachablePlc = new Plc(CpuType.S7300, "255.255.255.255", 0, 2); 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] [TestMethod]
@@ -944,7 +951,8 @@ namespace S7.Net.UnitTest
S7TestServer.Start(TestServerPort); S7TestServer.Start(TestServerPort);
var reachablePlc = CreatePlc(); var reachablePlc = CreatePlc();
Assert.IsTrue(reachablePlc.IsAvailable); reachablePlc.Open();
Assert.IsTrue(reachablePlc.IsConnected);
} }
[TestMethod] [TestMethod]

View File

@@ -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));
}
}
}

View File

@@ -117,13 +117,24 @@ namespace S7.Net.UnitTest.TypeTests
AssertToByteArrayAndBackEquals("Abc", 4, 4, 3, (byte) 'A', (byte) 'b', (byte) 'c', 0); 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) private static void AssertFromByteArrayEquals(string expected, params byte[] bytes)
{ {
var convertedString = S7String.FromByteArray(bytes); var convertedString = S7String.FromByteArray(bytes);
Assert.AreEqual(expected, convertedString); Assert.AreEqual(expected, convertedString);
} }
private static void AssertToByteArrayAndBackEquals(string value, int reservedLength, params byte[] expected) private static void AssertToByteArrayAndBackEquals(string value, int reservedLength, params byte[] expected)
{ {
var convertedData = S7String.ToByteArray(value, reservedLength); var convertedData = S7String.ToByteArray(value, reservedLength);
@@ -131,5 +142,11 @@ namespace S7.Net.UnitTest.TypeTests
var convertedBack = S7String.FromByteArray(convertedData); var convertedBack = S7String.FromByteArray(convertedData);
Assert.AreEqual(value, convertedBack); Assert.AreEqual(value, convertedBack);
} }
private void AssertVarTypeToByteLength(VarType varType, int count, int expectedByteLength)
{
var byteLength = Plc.VarTypeToByteLength(varType, count);
Assert.AreEqual(expectedByteLength, byteLength);
}
} }
} }

View File

@@ -122,6 +122,17 @@ namespace S7.Net.UnitTest.TypeTests
Assert.AreEqual(expected, convertedString); 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) 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); var convertedBack = S7WString.FromByteArray(convertedData);
Assert.AreEqual(value, convertedBack); Assert.AreEqual(value, convertedBack);
} }
private void AssertVarTypeToByteLength(VarType varType, int count, int expectedByteLength)
{
var byteLength = Plc.VarTypeToByteLength(varType, count);
Assert.AreEqual(expectedByteLength, byteLength);
}
} }
} }

View File

@@ -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<T> Enqueue<T>(Func<Task<T>> action)
{
var tcs = new TaskCompletionSource<object>();
await Interlocked.Exchange(ref prev, tcs.Task).ConfigureAwait(false);
try
{
return await action.Invoke().ConfigureAwait(false);
}
finally
{
tcs.SetResult(Sentinel);
}
}
}
}

View File

@@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using S7.Net.Internal;
using S7.Net.Protocol; using S7.Net.Protocol;
using S7.Net.Types; using S7.Net.Types;
@@ -13,6 +15,8 @@ namespace S7.Net
/// </summary> /// </summary>
public partial class Plc : IDisposable public partial class Plc : IDisposable
{ {
private readonly TaskQueue queue = new TaskQueue();
private const int CONNECTION_TIMED_OUT_ERROR_CODE = 10060; private const int CONNECTION_TIMED_OUT_ERROR_CODE = 10060;
//TCP connection to device //TCP connection to device
@@ -77,45 +81,24 @@ namespace S7.Net
} }
/// <summary> /// <summary>
/// Returns true if a connection to the PLC can be established /// Gets a value indicating whether a connection to the PLC has been established.
/// </summary> /// </summary>
public bool IsAvailable /// <remarks>
{ /// The <see cref="IsConnected"/> property gets the connection state of the Client socket as
//TODO: Fix This /// of the last I/O operation. When it returns <c>false</c>, the Client socket was either
get /// never connected, or is no longer connected.
{ ///
try /// <para>
{ /// Because the <see cref="IsConnected"/> property only reflects the state of the connection
OpenAsync().GetAwaiter().GetResult(); /// as of the most recent operation, you should attempt to send or receive a message to
return true; /// determine the current state. After the message send fails, this property no longer
} /// returns <c>true</c>. Note that this behavior is by design. You cannot reliably test the
catch /// 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
return false; /// gracefully handle failed transmissions.
} /// </para>
} /// </remarks>
} public bool IsConnected => tcpClient?.Connected ?? false;
/// <summary>
/// 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
/// </summary>
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; }
}
}
/// <summary> /// <summary>
/// Creates a PLC object with all the parameters needed for connections. /// Creates a PLC object with all the parameters needed for connections.
@@ -263,6 +246,16 @@ namespace S7.Net
} }
} }
private Stream GetStreamIfAvailable()
{
if (_stream == null)
{
throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected");
}
return _stream;
}
#region IDisposable Support #region IDisposable Support
private bool disposedValue = false; // To detect redundant calls private bool disposedValue = false; // To detect redundant calls

View File

@@ -184,13 +184,15 @@ namespace S7.Net
switch (varType) switch (varType)
{ {
case VarType.Bit: case VarType.Bit:
return varCount + 7 / 8; return (varCount + 7) / 8;
case VarType.Byte: case VarType.Byte:
return (varCount < 1) ? 1 : varCount; return (varCount < 1) ? 1 : varCount;
case VarType.String: case VarType.String:
return varCount; return varCount;
case VarType.S7String: 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: case VarType.Word:
case VarType.Timer: case VarType.Timer:
case VarType.Int: case VarType.Int:

View File

@@ -1,11 +1,11 @@
using S7.Net.Types; using S7.Net.Types;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using S7.Net.Protocol; using S7.Net.Protocol;
using System.IO;
using System.Threading; using System.Threading;
using S7.Net.Protocol.S7; using S7.Net.Protocol.S7;
@@ -27,10 +27,15 @@ namespace S7.Net
{ {
var stream = await ConnectAsync().ConfigureAwait(false); var stream = await ConnectAsync().ConfigureAwait(false);
try try
{
await queue.Enqueue(async () =>
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
await EstablishConnection(stream, cancellationToken).ConfigureAwait(false); await EstablishConnection(stream, cancellationToken).ConfigureAwait(false);
_stream = stream; _stream = stream;
return default(object);
}).ConfigureAwait(false);
} }
catch(Exception) catch(Exception)
{ {
@@ -47,29 +52,30 @@ namespace S7.Net
return tcpClient.GetStream(); return tcpClient.GetStream();
} }
private async Task EstablishConnection(NetworkStream stream, CancellationToken cancellationToken) private async Task EstablishConnection(Stream stream, CancellationToken cancellationToken)
{ {
await RequestConnection(stream, cancellationToken).ConfigureAwait(false); await RequestConnection(stream, cancellationToken).ConfigureAwait(false);
await SetupConnection(stream, cancellationToken).ConfigureAwait(false); await SetupConnection(stream, cancellationToken).ConfigureAwait(false);
} }
private async Task RequestConnection(NetworkStream stream, CancellationToken cancellationToken) private async Task RequestConnection(Stream stream, CancellationToken cancellationToken)
{ {
var requestData = ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot); var requestData = ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot);
await stream.WriteAsync(requestData, 0, requestData.Length).ConfigureAwait(false); var response = await NoLockRequestTpduAsync(stream, requestData, cancellationToken).ConfigureAwait(false);
var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
if (response.PDUType != COTP.PduType.ConnectionConfirmed) if (response.PDUType != COTP.PduType.ConnectionConfirmed)
{ {
throw new InvalidDataException("Connection request was denied", response.TPkt.Data, 1, 0x0d); throw new InvalidDataException("Connection request was denied", response.TPkt.Data, 1, 0x0d);
} }
} }
private async Task SetupConnection(NetworkStream stream, CancellationToken cancellationToken) private async Task SetupConnection(Stream stream, CancellationToken cancellationToken)
{ {
var setupData = GetS7ConnectionSetup(); 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 NoLockRequestTsduAsync(stream, setupData, 0, setupData.Length, cancellationToken)
.ConfigureAwait(false);
if (s7data.Length < 2) if (s7data.Length < 2)
throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup"); throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup");
@@ -104,7 +110,7 @@ namespace S7.Net
{ {
//This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0. //This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
var maxToRead = Math.Min(count, MaxPDUSize - 18); 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; count -= maxToRead;
index += maxToRead; index += maxToRead;
} }
@@ -127,7 +133,7 @@ namespace S7.Net
public async Task<object?> ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0, CancellationToken cancellationToken = default) public async Task<object?> ReadAsync(DataType dataType, int db, int startByteAdr, VarType varType, int varCount, byte bitAdr = 0, CancellationToken cancellationToken = default)
{ {
int cntBytes = VarTypeToByteLength(varType, varCount); 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); return ParseBytes(varType, bytes, varCount, bitAdr);
} }
@@ -142,7 +148,7 @@ namespace S7.Net
public async Task<object?> ReadAsync(string variable, CancellationToken cancellationToken = default) public async Task<object?> ReadAsync(string variable, CancellationToken cancellationToken = default)
{ {
var adr = new PLCAddress(variable); 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);
} }
/// <summary> /// <summary>
@@ -158,7 +164,7 @@ namespace S7.Net
{ {
int numBytes = Types.Struct.GetStructSize(structType); int numBytes = Types.Struct.GetStructSize(structType);
// now read the package // 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 // and decode it
return Types.Struct.FromBytes(structType, resultBytes); return Types.Struct.FromBytes(structType, resultBytes);
@@ -175,7 +181,7 @@ namespace S7.Net
/// <returns>Returns a nulable struct. If nothing was read null will be returned.</returns> /// <returns>Returns a nulable struct. If nothing was read null will be returned.</returns>
public async Task<T?> ReadStructAsync<T>(int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : struct public async Task<T?> ReadStructAsync<T>(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?;
} }
/// <summary> /// <summary>
@@ -197,7 +203,7 @@ namespace S7.Net
} }
// now read the package // 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 // and decode it
Class.FromBytes(sourceClass, resultBytes); Class.FromBytes(sourceClass, resultBytes);
@@ -217,7 +223,7 @@ namespace S7.Net
/// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns> /// <returns>An instance of the class with the values read from the PLC. If no data has been read, null will be returned</returns>
public async Task<T?> ReadClassAsync<T>(int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : class public async Task<T?> ReadClassAsync<T>(int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : class
{ {
return await ReadClassAsync(() => Activator.CreateInstance<T>(), db, startByteAdr, cancellationToken); return await ReadClassAsync(() => Activator.CreateInstance<T>(), db, startByteAdr, cancellationToken).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@@ -234,7 +240,7 @@ namespace S7.Net
public async Task<T?> ReadClassAsync<T>(Func<T> classFactory, int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : class public async Task<T?> ReadClassAsync<T>(Func<T> classFactory, int db, int startByteAdr = 0, CancellationToken cancellationToken = default) where T : class
{ {
var instance = classFactory(); 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; int readBytes = res.Item1;
if (readBytes <= 0) if (readBytes <= 0)
{ {
@@ -260,14 +266,11 @@ namespace S7.Net
//replies with bigger PDU size in connection setup. //replies with bigger PDU size in connection setup.
AssertPduSizeForRead(dataItems); AssertPduSizeForRead(dataItems);
var stream = GetStreamIfAvailable();
try try
{ {
var dataToSend = BuildReadRequestPackage(dataItems.Select(d => DataItem.GetDataItemAddress(d)).ToList()); var dataToSend = BuildReadRequestPackage(dataItems.Select(d => DataItem.GetDataItemAddress(d)).ToList());
await stream.WriteAsync(dataToSend, 0, dataToSend.Length); var s7data = await RequestTsduAsync(dataToSend, cancellationToken);
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken);
ValidateResponseCode((ReadWriteErrorCode)s7data[14]); ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
ParseDataIntoDataItems(s7data, dataItems); ParseDataIntoDataItems(s7data, dataItems);
@@ -306,7 +309,7 @@ namespace S7.Net
while (count > 0) while (count > 0)
{ {
var maxToWrite = (int)Math.Min(count, MaxPDUSize - 35); 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; count -= maxToWrite;
localIndex += maxToWrite; localIndex += maxToWrite;
} }
@@ -328,7 +331,7 @@ namespace S7.Net
if (bitAdr < 0 || bitAdr > 7) 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)); 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);
} }
/// <summary> /// <summary>
@@ -347,7 +350,7 @@ namespace S7.Net
if (value < 0 || value > 1) if (value < 0 || value > 1)
throw new ArgumentException("Value must be 0 or 1", nameof(value)); 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);
} }
/// <summary> /// <summary>
@@ -370,7 +373,7 @@ namespace S7.Net
//Must be writing a bit value as bitAdr is specified //Must be writing a bit value as bitAdr is specified
if (value is bool boolean) 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) else if (value is int intValue)
{ {
@@ -380,11 +383,11 @@ namespace S7.Net
"Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid", "Addressing Error: You can only reference bitwise locations 0-7. Address {0} is invalid",
bitAdr), nameof(bitAdr)); 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 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);
} }
/// <summary> /// <summary>
@@ -399,7 +402,7 @@ namespace S7.Net
public async Task WriteAsync(string variable, object value, CancellationToken cancellationToken = default) public async Task WriteAsync(string variable, object value, CancellationToken cancellationToken = default)
{ {
var adr = new PLCAddress(variable); 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);
} }
/// <summary> /// <summary>
@@ -414,7 +417,7 @@ namespace S7.Net
public async Task WriteStructAsync(object structValue, int db, int startByteAdr = 0, CancellationToken cancellationToken = default) public async Task WriteStructAsync(object structValue, int db, int startByteAdr = 0, CancellationToken cancellationToken = default)
{ {
var bytes = Struct.ToBytes(structValue).ToList(); 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);
} }
/// <summary> /// <summary>
@@ -430,17 +433,14 @@ namespace S7.Net
{ {
byte[] bytes = new byte[(int)Class.GetClassSize(classValue)]; byte[] bytes = new byte[(int)Class.GetClassSize(classValue)];
Types.Class.ToBytes(classValue, bytes); 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) 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)}); 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 RequestTsduAsync(dataToSend, cancellationToken);
AssertReadResponse(s7data, count); AssertReadResponse(s7data, count);
Array.Copy(s7data, 18, buffer, offset, count); Array.Copy(s7data, 18, buffer, offset, count);
@@ -456,13 +456,11 @@ namespace S7.Net
{ {
AssertPduSizeForWrite(dataItems); AssertPduSizeForWrite(dataItems);
var stream = GetStreamIfAvailable();
var message = new ByteArray(); var message = new ByteArray();
var length = S7WriteMultiple.CreateRequest(message, dataItems); 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); S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
} }
@@ -476,15 +474,11 @@ namespace S7.Net
/// <returns>A task that represents the asynchronous write operation.</returns> /// <returns>A task that represents the asynchronous write operation.</returns>
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count, CancellationToken cancellationToken) private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count, CancellationToken cancellationToken)
{ {
try try
{ {
var stream = GetStreamIfAvailable();
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count); 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);
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken);
ValidateResponseCode((ReadWriteErrorCode)s7data[14]); ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -499,15 +493,11 @@ namespace S7.Net
private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue, CancellationToken cancellationToken) private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue, CancellationToken cancellationToken)
{ {
var stream = GetStreamIfAvailable();
try try
{ {
var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr); var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr);
var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false);
await stream.WriteAsync(dataToSend, 0, dataToSend.Length);
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken);
ValidateResponseCode((ReadWriteErrorCode)s7data[14]); ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -520,13 +510,33 @@ namespace S7.Net
} }
} }
private Stream GetStreamIfAvailable() private Task<byte[]> RequestTsduAsync(byte[] requestData, CancellationToken cancellationToken = default) =>
RequestTsduAsync(requestData, 0, requestData.Length, cancellationToken);
private Task<byte[]> RequestTsduAsync(byte[] requestData, int offset, int length, CancellationToken cancellationToken = default)
{ {
if (_stream == null) var stream = GetStreamIfAvailable();
return queue.Enqueue(() =>
NoLockRequestTsduAsync(stream, requestData, offset, length, cancellationToken));
}
private static async Task<COTP.TPDU> NoLockRequestTpduAsync(Stream stream, byte[] requestData,
CancellationToken cancellationToken = default)
{ {
throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected"); await stream.WriteAsync(requestData, 0, requestData.Length, cancellationToken).ConfigureAwait(false);
var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
return response;
} }
return _stream;
private static async Task<byte[]> 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;
} }
} }
} }

View File

@@ -2,8 +2,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using S7.Net.Protocol; using S7.Net.Protocol;
using S7.Net.Helper; using S7.Net.Helper;
@@ -298,7 +296,6 @@ namespace S7.Net
private void ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count) private void ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count)
{ {
var stream = GetStreamIfAvailable();
try try
{ {
// first create the header // first create the header
@@ -309,9 +306,7 @@ namespace S7.Net
BuildReadDataRequestPackage(package, dataType, db, startByteAdr, count); BuildReadDataRequestPackage(package, dataType, db, startByteAdr, count);
var dataToSend = package.ToArray(); var dataToSend = package.ToArray();
stream.Write(dataToSend, 0, dataToSend.Length); var s7data = RequestTsdu(dataToSend);
var s7data = COTP.TSDU.Read(stream);
AssertReadResponse(s7data, count); AssertReadResponse(s7data, count);
Array.Copy(s7data, 18, buffer, offset, count); Array.Copy(s7data, 18, buffer, offset, count);
@@ -331,13 +326,11 @@ namespace S7.Net
{ {
AssertPduSizeForWrite(dataItems); AssertPduSizeForWrite(dataItems);
var stream = GetStreamIfAvailable();
var message = new ByteArray(); var message = new ByteArray();
var length = S7WriteMultiple.CreateRequest(message, dataItems); 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); S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
} }
@@ -345,12 +338,9 @@ namespace S7.Net
{ {
try try
{ {
var stream = GetStreamIfAvailable();
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count); 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]); ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
} }
catch (Exception exc) catch (Exception exc)
@@ -425,14 +415,11 @@ namespace S7.Net
private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue) private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
{ {
var stream = GetStreamIfAvailable();
try try
{ {
var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr); 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]); ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
} }
catch (Exception exc) catch (Exception exc)
@@ -453,8 +440,6 @@ namespace S7.Net
{ {
AssertPduSizeForRead(dataItems); AssertPduSizeForRead(dataItems);
var stream = GetStreamIfAvailable();
try try
{ {
// first create the header // first create the header
@@ -468,9 +453,7 @@ namespace S7.Net
} }
var dataToSend = package.ToArray(); var dataToSend = package.ToArray();
stream.Write(dataToSend, 0, dataToSend.Length); var s7data = RequestTsdu(dataToSend);
var s7data = COTP.TSDU.Read(stream); //TODO use Async
ValidateResponseCode((ReadWriteErrorCode)s7data[14]); ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
@@ -481,5 +464,12 @@ namespace S7.Net
throw new PlcException(ErrorCode.ReadData, exc); 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)
{
return RequestTsduAsync(requestData, offset, length).GetAwaiter().GetResult();
}
} }
} }

View File

@@ -14,20 +14,51 @@
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="value">The input value to modify.</param>
/// <param name="bit">The index (zero based) of the bit to set.</param>
/// <returns>The modified value with the bit at index set.</returns>
public static byte SetBit(byte value, int bit) public static byte SetBit(byte value, int bit)
{ {
return (byte)((value | (1 << bit)) & 0xFF); SetBit(ref value, bit);
return value;
}
/// <summary>
/// Sets the value of a bit to 1 (true), given the address of the bit.
/// </summary>
/// <param name="value">The value to modify.</param>
/// <param name="bit">The index (zero based) of the bit to set.</param>
public static void SetBit(ref byte value, int bit)
{
value = (byte) ((value | (1 << bit)) & 0xFF);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="value">The input value to modify.</param>
/// <param name="bit">The index (zero based) of the bit to clear.</param>
/// <returns>The modified value with the bit at index cleared.</returns>
public static byte ClearBit(byte value, int bit)
{
ClearBit(ref value, bit);
return value;
} }
/// <summary> /// <summary>
/// Resets the value of a bit to 0 (false), given the address of the bit /// Resets the value of a bit to 0 (false), given the address of the bit
/// </summary> /// </summary>
public static byte ClearBit(byte value, int bit) /// <param name="value">The input value to modify.</param>
/// <param name="bit">The index (zero based) of the bit to clear.</param>
public static void ClearBit(ref byte value, int bit)
{ {
return (byte)((value | (~(1 << bit))) & 0xFF); value = (byte) (value & ~(1 << bit) & 0xFF);
} }
} }
} }