33 Commits

Author SHA1 Message Date
Michael Croes
ced10b4eca Release S7NetPlus 0.11.0
Release highlights:
- Fix for byte length calculation of bits
- Fix for Boolean.ClearBit
- Added Boolean.ClearBit and .SetBit by reference
2021-05-10 22:55:28 +02:00
Michael Croes
632e1c14ac Merge pull request #391 from mycroes/set-bit-clear-bit
SetBit and ClearBit tests, fixes
2021-05-10 21:24:43 +02:00
Michael Croes
aa50280233 Boolean: Add SetBit and ClearBit by reference 2021-05-10 21:14:43 +02:00
Michael Croes
3a794e8a46 Cleanup trailing whitespace in PLC and PLCHelpers 2021-05-10 21:05:02 +02:00
Michael Croes
0b8bd66bf7 Fix bitwise operator in Boolean.ClearBit
Fixes #249
2021-05-10 20:50:46 +02:00
Michael Croes
e66d21af05 Add tests for Boolean.SetBit and Boolean.ClearBit 2021-05-10 20:49:15 +02:00
Michael Croes
44ee651ac4 Merge pull request #390 from mycroes/byte-length-bits
Fix byte length calculation for VarType.Bit
2021-05-10 20:32:34 +02:00
Michael Croes
a1b4694ef6 Fix calculation of byte length for VarType.Bit
Fixes #389
2021-05-10 20:25:38 +02:00
Michael Croes
d10c15b80f Release S7NetPlus 0.10.0
Release highlights:
- Additional ConfigureAwait(false) calls used internally
- Fix for S7WString length
- Remove IsAvailable property
2021-03-29 22:01:14 +02:00
Michael Croes
5225c8bffd Merge pull request #311 from mycroes/is-available
Remove IsAvailable, document IsConnected
2021-03-29 21:51:34 +02:00
Michael Croes
aa03400350 Document IsConnected, shorten implementation 2021-03-29 21:43:24 +02:00
Michael Croes
2b4ec6d9dd Remove IsAvailable
Addresses #275, #252, #208, #154.
2021-03-29 21:43:24 +02:00
Michael Croes
54f3de6c9f Merge pull request #382 from mycroes/s7wstring-length
Fix problems with S7 string types (S7String and S7WString) and calculated byte length
2021-03-29 21:12:18 +02:00
diego
e6d14587d3 Added four new test methods to check ByteLength for S7 String types 2021-03-29 20:51:11 +02:00
diego
e63d92c61c Fix ByteLength calculation for S7Strings when reserved is odd 2021-03-29 20:51:11 +02:00
diego
b4b94e1777 Fix wrong calculation for S7WString byte length 2021-03-29 20:51:11 +02:00
Michael Croes
13c25fc20b Merge pull request #379 from scamille/fix-sync-class
Sprinkle more .ConfigureAwait(false) into async library.
2021-03-29 20:30:43 +02:00
Serge Camille
82e29837a2 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
2021-03-16 08:08:39 +01:00
Michael Croes
1afb07774b Release S7NetPlus 0.9.0
Release highlights:
- Added WString support
- Added support for strings in structs
- Set type for 'Z' and 'C' to counter instead of timer
2021-03-13 13:38:38 +01:00
Michael Croes
051091919f Merge pull request #365 from MCPC10/StructStringSupport
Added support for string types in a struct
2021-03-13 13:31:51 +01:00
Michael Croes
478c1aed52 Merge branch 'develop' into StructStringSupport 2021-03-13 13:30:31 +01:00
Mike Cremer
924eb9c48f Minor changes + removed default length in S7StringAttribute 2021-03-13 12:00:32 +01:00
Michael Croes
eb8e188c86 Merge pull request #373 from scamille/patch-1
Fix PLC parse for type "Z"/"C"
2021-03-12 20:46:17 +01:00
Serge Camille
37384d2a92 Fix PLC parse for type "Z"/"C" 2021-03-12 08:17:37 +01:00
Mike Cremer
fdd4519f64 Minor changes 2021-01-29 21:13:38 +01:00
Michael Croes
52c60f6eaf Merge pull request #363 from MCPC10/WString
Added WString support + fixed max size in S7String
2021-01-27 18:55:12 +01:00
Mike Cremer
926d74f1d2 Fixed length check and corresponding message for S7String 2021-01-27 17:55:16 +01:00
Mike Cremer
9b89acfb91 Removed unnecessary bitwise and's 2021-01-25 21:42:08 +01:00
Mike Cremer
de0a9e64dc Added support for strings in Struct type class 2021-01-24 13:56:07 +01:00
Mike Cremer
dfcc4c7408 Added WString support 2021-01-24 12:16:32 +01:00
Michael Croes
c9a98fba95 Release S7NetPlus 0.8.1
Release highlights:
- Include documentation
2021-01-05 22:18:36 +01:00
Michael Croes
40edecad43 Merge pull request #356 from mycroes/enable-documentation
Generate documentation file
2021-01-05 22:14:26 +01:00
Michael Croes
0b04c86cb9 Generate documentation file 2021-01-05 22:07:11 +01:00
18 changed files with 562 additions and 106 deletions

View File

@@ -1,4 +1,5 @@

using S7.Net.Types;
namespace S7.Net.UnitTest.Helpers
{
public struct TestStruct
@@ -7,6 +8,7 @@ namespace S7.Net.UnitTest.Helpers
/// DB1.DBX0.0
/// </summary>
public bool BitVariable00;
public bool BitVariable01;
public bool BitVariable02;
public bool BitVariable03;
@@ -19,6 +21,7 @@ namespace S7.Net.UnitTest.Helpers
/// DB1.DBX1.0
/// </summary>
public bool BitVariable10;
public bool BitVariable11;
public bool BitVariable12;
public bool BitVariable13;
@@ -51,5 +54,17 @@ namespace S7.Net.UnitTest.Helpers
/// DB1.DBD16
/// </summary>
public ushort DWordVariable;
/// <summary>
/// DB1.DBX20.0
/// </summary>
[S7String(S7StringType.S7WString, 10)]
public string WStringVariable;
/// <summary>
/// DB1.DBX44.0
/// </summary>
[S7String(S7StringType.S7String, 10)]
public string StringVariable;
}
}

View File

@@ -211,7 +211,9 @@ namespace S7.Net.UnitTest
IntVariable = -15000,
LRealVariable = -154.789,
RealVariable = -154.789f,
DWordVariable = 850
DWordVariable = 850,
WStringVariable = "ÄÜÉÊéà",
StringVariable = "Hallo"
};
plc.WriteStruct(tc, DB2);
// Values that are read from a struct are stored in a new struct, returned by the funcion ReadStruct
@@ -223,6 +225,8 @@ namespace S7.Net.UnitTest
Assert.AreEqual(tc.LRealVariable, tc2.LRealVariable);
Assert.AreEqual(tc.RealVariable, tc2.RealVariable);
Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable);
Assert.AreEqual(tc.WStringVariable, tc2.WStringVariable);
Assert.AreEqual(tc.StringVariable, tc2.StringVariable);
}
/// <summary>
@@ -739,7 +743,9 @@ namespace S7.Net.UnitTest
IntVariable = -15000,
LRealVariable = -154.789,
RealVariable = -154.789f,
DWordVariable = 850
DWordVariable = 850,
WStringVariable = "ÄÜÉÊéà",
StringVariable = "Hallo"
};
plc.WriteStruct(ts, DB2);
@@ -756,6 +762,8 @@ namespace S7.Net.UnitTest
Assert.AreEqual(ts2.LRealVariable, ts2Generic.LRealVariable);
Assert.AreEqual(ts2.RealVariable, ts2Generic.RealVariable);
Assert.AreEqual(ts2.DWordVariable, ts2Generic.DWordVariable);
Assert.AreEqual(ts2.WStringVariable, ts2Generic.WStringVariable);
Assert.AreEqual(ts2.StringVariable, ts2Generic.StringVariable);
}
[TestMethod]

View File

@@ -212,6 +212,9 @@ namespace S7.Net.UnitTest
tc.LRealVariable = -154.789;
tc.RealVariable = -154.789f;
tc.DWordVariable = 850;
tc.WStringVariable = "ÄÜÉÊéà";
tc.StringVariable = "Hallo";
plc.WriteStruct(tc, DB2);
// Values that are read from a struct are stored in a new struct, returned by the funcion ReadStruct
TestStruct tc2 = (TestStruct)plc.ReadStruct(typeof(TestStruct), DB2);
@@ -222,6 +225,8 @@ namespace S7.Net.UnitTest
Assert.AreEqual(tc.LRealVariable, tc2.LRealVariable);
Assert.AreEqual(tc.RealVariable, tc2.RealVariable);
Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable);
Assert.AreEqual(tc.WStringVariable, tc2.WStringVariable);
Assert.AreEqual(tc.StringVariable, tc2.StringVariable);
}
/// <summary>
@@ -783,6 +788,8 @@ namespace S7.Net.UnitTest
ts.LRealVariable = -154.789;
ts.RealVariable = -154.789f;
ts.DWordVariable = 850;
ts.WStringVariable = "ÄÜÉÊéà";
ts.StringVariable = "Hallo";
plc.WriteStruct(ts, DB2);
@@ -797,6 +804,8 @@ namespace S7.Net.UnitTest
Assert.AreEqual(ts2.LRealVariable, ts2Generic.LRealVariable);
Assert.AreEqual(ts2.RealVariable, ts2Generic.RealVariable);
Assert.AreEqual(ts2.DWordVariable, ts2Generic.DWordVariable);
Assert.AreEqual(ts2.WStringVariable, ts2Generic.WStringVariable);
Assert.AreEqual(ts2.StringVariable, ts2Generic.StringVariable);
}
[TestMethod, ExpectedException(typeof(PlcException))]
@@ -924,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]
@@ -935,7 +951,8 @@ namespace S7.Net.UnitTest
S7TestServer.Start(TestServerPort);
var reachablePlc = CreatePlc();
Assert.IsTrue(reachablePlc.IsAvailable);
reachablePlc.Open();
Assert.IsTrue(reachablePlc.IsConnected);
}
[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

@@ -100,7 +100,7 @@ namespace S7.Net.UnitTest.TypeTests
}
[TestMethod]
public void WriteAbcWithStringLargetThanReservedLength()
public void WriteAbcWithStringLargerThanReservedLength()
{
Assert.ThrowsException<ArgumentException>(() => S7String.ToByteArray("Abc", 2));
}
@@ -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);
}
}
}

View File

@@ -0,0 +1,151 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net.Types;
using System;
namespace S7.Net.UnitTest.TypeTests
{
[TestClass]
public class S7WStringTests
{
[TestMethod]
public void ReadEmptyStringWithZeroLength()
{
AssertFromByteArrayEquals("", 0, 0 , 0, 0);
}
[TestMethod]
public void ReadEmptyStringWithOneCharLength()
{
AssertFromByteArrayEquals("", 0, 1, 0, 0, 0, 0);
}
[TestMethod]
public void ReadEmptyStringWithOneCharGarbage()
{
AssertFromByteArrayEquals("", 0, 1, 0, 0, 0x00, 0x41);
}
[TestMethod]
public void ReadMalformedStringTooShort()
{
Assert.ThrowsException<PlcException>(() => AssertFromByteArrayEquals("", 0, 1));
}
[TestMethod]
public void ReadMalformedStringSizeLargerThanCapacity()
{
Assert.ThrowsException<PlcException>(() => S7WString.FromByteArray(new byte[] { 0, 3, 0, 5, 0, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41}));
}
[TestMethod]
public void ReadMalformedStringCapacityTooLarge()
{
Assert.ThrowsException<ArgumentException>(() => AssertToByteArrayAndBackEquals("", 20000, 0));
}
[TestMethod]
public void ReadA()
{
AssertFromByteArrayEquals("A", 0, 1, 0, 1, 0x00, 0x41);
}
[TestMethod]
public void ReadAbc()
{
AssertFromByteArrayEquals("Abc", 0, 3, 0, 3, 0x00, 0x41, 0x00, 0x62, 0x00, 0x63);
}
[TestMethod]
public void WriteNullWithReservedLengthZero()
{
Assert.ThrowsException<ArgumentNullException>(() => AssertToByteArrayAndBackEquals(null, 0, 0, 0, 0, 0));
}
[TestMethod]
public void WriteEmptyStringWithReservedLengthZero()
{
AssertToByteArrayAndBackEquals("", 0, 0, 0, 0, 0);
}
[TestMethod]
public void WriteAWithReservedLengthZero()
{
AssertToByteArrayAndBackEquals("", 0, 0, 0, 0, 0);
}
[TestMethod]
public void WriteNullWithReservedLengthOne()
{
Assert.ThrowsException<ArgumentNullException>(() => AssertToByteArrayAndBackEquals(null, 1, 0, 1 , 0, 0));
}
[TestMethod]
public void WriteEmptyStringWithReservedLengthOne()
{
AssertToByteArrayAndBackEquals("", 1, 0, 1, 0, 0, 0, 0);
}
[TestMethod]
public void WriteAWithReservedLengthOne()
{
AssertToByteArrayAndBackEquals("A", 1, 0, 1, 0, 1, 0x00, 0x41);
}
[TestMethod]
public void WriteAWithReservedLengthTwo()
{
AssertToByteArrayAndBackEquals("A", 2, 0, 2, 0, 1, 0x00, 0x41, 0, 0);
}
[TestMethod]
public void WriteAbcWithStringLargerThanReservedLength()
{
Assert.ThrowsException<ArgumentException>(() => S7WString.ToByteArray("Abc", 2));
}
[TestMethod]
public void WriteAbcWithReservedLengthThree()
{
AssertToByteArrayAndBackEquals("Abc", 3, 0, 3, 0, 3, 0x00, 0x41, 0x00, 0x62, 0x00, 0x63);
}
[TestMethod]
public void WriteAbcWithReservedLengthFour()
{
AssertToByteArrayAndBackEquals("Abc", 4, 0, 4, 0, 3, 0x00, 0x41, 0x00, 0x62, 0x00, 0x63, 0 , 0);
}
private static void AssertFromByteArrayEquals(string expected, params byte[] bytes)
{
var convertedString = S7WString.FromByteArray(bytes);
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)
{
var convertedData = S7WString.ToByteArray(value, reservedLength);
CollectionAssert.AreEqual(expected, convertedData);
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);
}
}
}

View File

@@ -178,6 +178,11 @@
/// </summary>
S7String,
/// <summary>
/// S7 WString variable type (variable)
/// </summary>
S7WString,
/// <summary>
/// Timer variable type
/// </summary>

View File

@@ -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;
@@ -75,47 +75,26 @@ namespace S7.Net
if (tcpClient != null) tcpClient.SendTimeout = writeTimeout;
}
}
/// <summary>
/// Returns true if a connection to the PLC can be established
/// </summary>
public bool IsAvailable
{
//TODO: Fix This
get
{
try
{
OpenAsync().GetAwaiter().GetResult();
return true;
}
catch
{
return 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
/// Gets a value indicating whether a connection to the PLC has been established.
/// </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; }
}
}
/// <remarks>
/// The <see cref="IsConnected"/> property gets the connection state of the Client socket as
/// of the last I/O operation. When it returns <c>false</c>, the Client socket was either
/// never connected, or is no longer connected.
///
/// <para>
/// Because the <see cref="IsConnected"/> 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 <c>true</c>. 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.
/// </para>
/// </remarks>
public bool IsConnected => tcpClient?.Connected ?? false;
/// <summary>
/// Creates a PLC object with all the parameters needed for connections.

View File

@@ -183,7 +183,7 @@
case "Z":
case "C":
// Counter
dataType = DataType.Timer;
dataType = DataType.Counter;
dbNumber = 0;
address = int.Parse(input.Substring(1));
varType = VarType.Counter;
@@ -204,4 +204,4 @@
}
}
}
}
}

View File

@@ -30,8 +30,8 @@ namespace S7.Net
}
/// <summary>
/// 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.
/// </summary>
/// <param name="dataType">MemoryType (DB, Timer, Counter, etc.)</param>
/// <param name="db">Address of the memory to be read</param>
@@ -125,6 +125,8 @@ namespace S7.Net
return Types.String.FromByteArray(bytes);
case VarType.S7String:
return S7String.FromByteArray(bytes);
case VarType.S7WString:
return S7WString.FromByteArray(bytes);
case VarType.Timer:
if (varCount == 1)
@@ -182,13 +184,15 @@ 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:
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:

View File

@@ -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<object?> 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<object?> 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);
}
/// <summary>
@@ -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>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
{
return await ReadStructAsync(typeof(T), db, startByteAdr, cancellationToken) as T?;
return await ReadStructAsync(typeof(T), db, startByteAdr, cancellationToken).ConfigureAwait(false) as T?;
}
/// <summary>
@@ -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
/// <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
{
return await ReadClassAsync(() => Activator.CreateInstance<T>(), db, startByteAdr, cancellationToken);
return await ReadClassAsync(() => Activator.CreateInstance<T>(), db, startByteAdr, cancellationToken).ConfigureAwait(false);
}
/// <summary>
@@ -234,7 +234,7 @@ namespace S7.Net
public async Task<T?> ReadClassAsync<T>(Func<T> 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);
}
/// <summary>
@@ -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);
}
/// <summary>
@@ -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);
}
/// <summary>
@@ -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);
}
/// <summary>
@@ -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);
}
/// <summary>
@@ -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)

View File

@@ -17,10 +17,14 @@ namespace S7.Net.Protocol
{
throw new Exception($"DataItem.Value is null, cannot serialize. StartAddr={dataItem.StartByteAdr} VarType={dataItem.VarType}");
}
if (dataItem.Value is string s)
return dataItem.VarType == VarType.S7String
? S7String.ToByteArray(s, dataItem.Count)
: Types.String.ToByteArray(s, dataItem.Count);
return dataItem.VarType switch
{
VarType.S7String => S7String.ToByteArray(s, dataItem.Count),
VarType.S7WString => S7WString.ToByteArray(s, dataItem.Count),
_ => Types.String.ToByteArray(s, dataItem.Count)
};
return SerializeValue(dataItem.Value);
}
@@ -46,7 +50,7 @@ namespace S7.Net.Protocol
case "Double":
return Types.LReal.ToByteArray((double)value);
case "DateTime":
return Types.DateTime.ToByteArray((System.DateTime) value);
return Types.DateTime.ToByteArray((System.DateTime)value);
case "Byte[]":
return (byte[])value;
case "Int16[]":
@@ -64,10 +68,10 @@ namespace S7.Net.Protocol
case "String":
// Hack: This is backwards compatible with the old code, but functionally it's broken
// if the consumer does not pay attention to string length.
var stringVal = (string) value;
var stringVal = (string)value;
return Types.String.ToByteArray(stringVal, stringVal.Length);
case "DateTime[]":
return Types.DateTime.ToByteArray((System.DateTime[]) value);
return Types.DateTime.ToByteArray((System.DateTime[])value);
case "DateTimeLong[]":
return Types.DateTimeLong.ToByteArray((System.DateTime[])value);
default:

View File

@@ -20,6 +20,7 @@
<DebugType>portable</DebugType>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net452' Or '$(TargetFramework)' == 'netstandard2.0' ">

View File

@@ -14,20 +14,51 @@
}
/// <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>
/// <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)
{
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>
/// Resets the value of a bit to 0 (false), given the address of the bit
/// </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);
}
}
}

View File

@@ -45,7 +45,7 @@ namespace S7.Net.Types
/// Converts a <see cref="T:string"/> to S7 string with 2-byte header.
/// </summary>
/// <param name="value">The string to convert to byte array.</param>
/// <param name="reservedLength">The length (in bytes) allocated in PLC for string excluding header.</param>
/// <param name="reservedLength">The length (in characters) allocated in PLC for the string.</param>
/// <returns>A <see cref="T:byte[]" /> containing the string header and string value with a maximum length of <paramref name="reservedLength"/> + 2.</returns>
public static byte[] ToByteArray(string value, int reservedLength)
{
@@ -54,7 +54,7 @@ namespace S7.Net.Types
throw new ArgumentNullException(nameof(value));
}
if (reservedLength > byte.MaxValue) throw new ArgumentException($"The maximum string length supported is {byte.MaxValue}.");
if (reservedLength > 254) throw new ArgumentException($"The maximum string length supported is 254.");
var bytes = Encoding.ASCII.GetBytes(value);
if (bytes.Length > reservedLength) throw new ArgumentException($"The provided string length ({bytes.Length} is larger than the specified reserved length ({reservedLength}).");

View File

@@ -0,0 +1,67 @@
using System;
namespace S7.Net.Types
{
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public sealed class S7StringAttribute : Attribute
{
private readonly S7StringType type;
private readonly int reservedLength;
/// <summary>
/// Initializes a new instance of the <see cref="S7StringAttribute"/> class.
/// </summary>
/// <param name="type">The string type.</param>
/// <param name="reservedLength">Reserved length of the string in characters.</param>
/// <exception cref="ArgumentException">Please use a valid value for the string type</exception>
public S7StringAttribute(S7StringType type, int reservedLength)
{
if (!Enum.IsDefined(typeof(S7StringType), type))
throw new ArgumentException("Please use a valid value for the string type");
this.type = type;
this.reservedLength = reservedLength;
}
/// <summary>
/// Gets the type of the string.
/// </summary>
/// <value>
/// The string type.
/// </value>
public S7StringType Type => type;
/// <summary>
/// Gets the reserved length of the string in characters.
/// </summary>
/// <value>
/// The reserved length of the string in characters.
/// </value>
public int ReservedLength => reservedLength;
/// <summary>
/// Gets the reserved length in bytes.
/// </summary>
/// <value>
/// The reserved length in bytes.
/// </value>
public int ReservedLengthInBytes => type == S7StringType.S7String ? reservedLength + 2 : (reservedLength * 2) + 4;
}
/// <summary>
/// String type.
/// </summary>
public enum S7StringType
{
/// <summary>
/// ASCII string.
/// </summary>
S7String = VarType.S7String,
/// <summary>
/// Unicode string.
/// </summary>
S7WString = VarType.S7WString
}
}

72
S7.Net/Types/S7WString.cs Normal file
View File

@@ -0,0 +1,72 @@
using System;
using System.Text;
namespace S7.Net.Types
{
/// <summary>
/// Contains the methods to convert from S7 wstrings to C# strings
/// An S7 WString has a preceding 4 byte header containing its capacity and length
/// </summary>
public static class S7WString
{
/// <summary>
/// Converts S7 bytes to a string
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string FromByteArray(byte[] bytes)
{
if (bytes.Length < 4)
{
throw new PlcException(ErrorCode.ReadData, "Malformed S7 WString / too short");
}
int size = (bytes[0] << 8) | bytes[1];
int length = (bytes[2] << 8) | bytes[3];
if (length > size)
{
throw new PlcException(ErrorCode.ReadData, "Malformed S7 WString / length larger than capacity");
}
try
{
return Encoding.BigEndianUnicode.GetString(bytes, 4, length * 2);
}
catch (Exception e)
{
throw new PlcException(ErrorCode.ReadData,
$"Failed to parse {VarType.S7WString} from data. Following fields were read: size: '{size}', actual length: '{length}', total number of bytes (including header): '{bytes.Length}'.",
e);
}
}
/// <summary>
/// Converts a <see cref="T:string"/> to S7 wstring with 4-byte header.
/// </summary>
/// <param name="value">The string to convert to byte array.</param>
/// <param name="reservedLength">The length (in characters) allocated in PLC for the string.</param>
/// <returns>A <see cref="T:byte[]" /> containing the string header and string value with a maximum length of <paramref name="reservedLength"/> + 4.</returns>
public static byte[] ToByteArray(string value, int reservedLength)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
if (reservedLength > 16382) throw new ArgumentException("The maximum string length supported is 16382.");
var buffer = new byte[4 + reservedLength * 2];
buffer[0] = (byte)((reservedLength >> 8) & 0xFF);
buffer[1] = (byte)(reservedLength & 0xFF);
buffer[2] = (byte)((value.Length >> 8) & 0xFF);
buffer[3] = (byte)(value.Length & 0xFF);
var stringLength = Encoding.BigEndianUnicode.GetBytes(value, 0, value.Length, buffer, 4) / 2;
if (stringLength > reservedLength) throw new ArgumentException($"The provided string length ({stringLength} is larger than the specified reserved length ({reservedLength}).");
return buffer;
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Reflection;
namespace S7.Net.Types
@@ -18,11 +19,11 @@ namespace S7.Net.Types
double numBytes = 0.0;
var infos = structType
#if NETSTANDARD1_3
#if NETSTANDARD1_3
.GetTypeInfo().DeclaredFields;
#else
#else
.GetFields();
#endif
#endif
foreach (var info in infos)
{
@@ -61,6 +62,16 @@ namespace S7.Net.Types
numBytes++;
numBytes += 8;
break;
case "String":
S7StringAttribute? attribute = info.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
if (attribute == default(S7StringAttribute))
throw new ArgumentException("Please add S7StringAttribute to the string field");
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
numBytes += attribute.ReservedLengthInBytes;
break;
default:
numBytes += GetStructSize(info.FieldType);
break;
@@ -91,11 +102,11 @@ namespace S7.Net.Types
var infos = structValue.GetType()
#if NETSTANDARD1_3
#if NETSTANDARD1_3
.GetTypeInfo().DeclaredFields;
#else
#else
.GetFields();
#endif
#endif
foreach (var info in infos)
{
@@ -120,7 +131,7 @@ namespace S7.Net.Types
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
// get the value
ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]);
info.SetValue(structValue, source.ConvertToShort());
numBytes += 2;
@@ -129,7 +140,7 @@ namespace S7.Net.Types
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
// get the value
info.SetValue(structValue, Word.FromBytes(bytes[(int)numBytes + 1],
bytes[(int)numBytes]));
numBytes += 2;
@@ -138,7 +149,7 @@ namespace S7.Net.Types
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
// get the value
uint sourceUInt = DWord.FromBytes(bytes[(int)numBytes + 3],
bytes[(int)numBytes + 2],
bytes[(int)numBytes + 1],
@@ -150,7 +161,7 @@ namespace S7.Net.Types
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
// get the value
info.SetValue(structValue, DWord.FromBytes(bytes[(int)numBytes],
bytes[(int)numBytes + 1],
bytes[(int)numBytes + 2],
@@ -161,7 +172,7 @@ namespace S7.Net.Types
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
// get the value
info.SetValue(structValue, Real.FromByteArray(new byte[] { bytes[(int)numBytes],
bytes[(int)numBytes + 1],
bytes[(int)numBytes + 2],
@@ -172,12 +183,38 @@ namespace S7.Net.Types
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
// get the value
var data = new byte[8];
Array.Copy(bytes, (int)numBytes, data, 0, 8);
info.SetValue(structValue, LReal.FromByteArray(data));
numBytes += 8;
break;
case "String":
S7StringAttribute? attribute = info.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
if (attribute == default(S7StringAttribute))
throw new ArgumentException("Please add S7StringAttribute to the string field");
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// get the value
var sData = new byte[attribute.ReservedLengthInBytes];
Array.Copy(bytes, (int)numBytes, sData, 0, sData.Length);
switch (attribute.Type)
{
case S7StringType.S7String:
info.SetValue(structValue, S7String.FromByteArray(sData));
break;
case S7StringType.S7WString:
info.SetValue(structValue, S7WString.FromByteArray(sData));
break;
default:
throw new ArgumentException("Please use a valid string type for the S7StringAttribute");
}
numBytes += sData.Length;
break;
default:
var buffer = new byte[GetStructSize(info.FieldType)];
if (buffer.Length == 0)
@@ -209,11 +246,11 @@ namespace S7.Net.Types
double numBytes = 0.0;
var infos = type
#if NETSTANDARD1_3
#if NETSTANDARD1_3
.GetTypeInfo().DeclaredFields;
#else
#else
.GetFields();
#endif
#endif
foreach (var info in infos)
{
@@ -254,6 +291,18 @@ namespace S7.Net.Types
case "Double":
bytes2 = LReal.ToByteArray((double)info.GetValue(structValue));
break;
case "String":
S7StringAttribute? attribute = info.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
if (attribute == default(S7StringAttribute))
throw new ArgumentException("Please add S7StringAttribute to the string field");
bytes2 = attribute.Type switch
{
S7StringType.S7String => S7String.ToByteArray((string)info.GetValue(structValue), attribute.ReservedLength),
S7StringType.S7WString => S7WString.ToByteArray((string)info.GetValue(structValue), attribute.ReservedLength),
_ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
};
break;
}
if (bytes2 != null)
{
@@ -269,7 +318,5 @@ namespace S7.Net.Types
}
return bytes;
}
}
}