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.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); + } } } diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index f3a497e..8882a13 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -75,47 +75,26 @@ 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. - /// 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. diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index 4c4f8ac..07611cb 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -190,7 +190,9 @@ 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: case VarType.Timer: case VarType.Int: 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)