mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-17 22:38:27 +08:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fc9eaade3 | ||
|
|
ab70bfb041 | ||
|
|
e277cf6e6c | ||
|
|
43b29825a4 | ||
|
|
6aa0133081 | ||
|
|
868e719b78 | ||
|
|
3833d29c0e | ||
|
|
144f814794 | ||
|
|
82aaf7e2cb | ||
|
|
f47918946d | ||
|
|
142f1ba90e | ||
|
|
d99d0d0e6f | ||
|
|
ce9f9f9e08 | ||
|
|
ea1140314b | ||
|
|
e3fad0b94f | ||
|
|
7f76d4fc5a | ||
|
|
ec554ddb59 | ||
|
|
2ecd2c6b49 | ||
|
|
d808ea5eb6 | ||
|
|
5d3f01e59e | ||
|
|
9c3f95ce73 | ||
|
|
12281ec802 | ||
|
|
8df1a9c8cb |
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@@ -14,14 +14,14 @@ jobs:
|
||||
DOTNET_NOLOGO : 1
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
os: [windows-latest, ubuntu-20.04, macos-latest]
|
||||
test-framework: [netcoreapp3.1, net5.0]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
- os: ubuntu-20.04
|
||||
test-framework: netcoreapp3.1
|
||||
installSnap7: true
|
||||
dotnet-sdk: '3.1.x'
|
||||
- os: ubuntu-latest
|
||||
- os: ubuntu-20.04
|
||||
test-framework: net5.0
|
||||
installSnap7: true
|
||||
dotnet-sdk: '5.0.x'
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Snap7 Linux
|
||||
if: ${{ matrix.installSnap7 && matrix.os == 'ubuntu-latest' }}
|
||||
if: ${{ matrix.installSnap7 && matrix.os == 'ubuntu-20.04' }}
|
||||
run: |
|
||||
sudo add-apt-repository ppa:gijzelaar/snap7
|
||||
sudo apt-get update
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
|
||||
using S7.Net.Types;
|
||||
|
||||
namespace S7.Net.UnitTest.Helpers
|
||||
{
|
||||
class TestClass
|
||||
@@ -51,5 +53,16 @@ namespace S7.Net.UnitTest.Helpers
|
||||
/// DB1.DBD16
|
||||
/// </summary>
|
||||
public ushort DWordVariable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DB1.DBX20.0
|
||||
/// </summary>
|
||||
[S7String(S7StringType.S7WString, 10)]
|
||||
public string WStringVariable { get; set; }
|
||||
/// <summary>
|
||||
/// DB1.DBX44.0
|
||||
/// </summary>
|
||||
[S7String(S7StringType.S7String, 10)]
|
||||
public string StringVariable { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,12 @@ using S7.Net.Types;
|
||||
using S7.UnitTest.Helpers;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
using System.Buffers;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -138,6 +144,33 @@ namespace S7.Net.UnitTest
|
||||
CollectionAssert.AreEqual(data, readData);
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
/// <summary>
|
||||
/// Write/Read a large amount of data to test PDU max
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task Test_Async_WriteLargeByteArrayWithMemory()
|
||||
{
|
||||
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
|
||||
|
||||
var randomEngine = new Random();
|
||||
using var dataOwner = MemoryPool<byte>.Shared.Rent(8192);
|
||||
var data = dataOwner.Memory.Slice(0, 8192);
|
||||
var db = 2;
|
||||
randomEngine.NextBytes(data.Span);
|
||||
|
||||
await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data);
|
||||
|
||||
using var readDataOwner = MemoryPool<byte>.Shared.Rent(data.Length);
|
||||
var readData = readDataOwner.Memory.Slice(0, data.Length);
|
||||
await plc.ReadBytesAsync(readData, DataType.DataBlock, db, 0);
|
||||
|
||||
CollectionAssert.AreEqual(data.ToArray(), readData.ToArray());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Read/Write a class that has the same properties of a DB with the same field in the same order
|
||||
/// </summary>
|
||||
@@ -154,7 +187,9 @@ namespace S7.Net.UnitTest
|
||||
IntVariable = -15000,
|
||||
LRealVariable = -154.789,
|
||||
RealVariable = -154.789f,
|
||||
DWordVariable = 850
|
||||
DWordVariable = 850,
|
||||
WStringVariable = "ÄÜÉÊéà",
|
||||
StringVariable = "Hallo"
|
||||
};
|
||||
|
||||
await plc.WriteClassAsync(tc, DB2);
|
||||
@@ -168,6 +203,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);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -580,7 +617,9 @@ namespace S7.Net.UnitTest
|
||||
IntVariable = -15000,
|
||||
LRealVariable = -154.789,
|
||||
RealVariable = -154.789f,
|
||||
DWordVariable = 850
|
||||
DWordVariable = 850,
|
||||
WStringVariable = "ÄÜÉÊéà",
|
||||
StringVariable = "Hallo"
|
||||
};
|
||||
|
||||
await plc.WriteClassAsync(tc, DB2);
|
||||
@@ -628,7 +667,10 @@ namespace S7.Net.UnitTest
|
||||
IntVariable = -15000,
|
||||
LRealVariable = -154.789,
|
||||
RealVariable = -154.789f,
|
||||
DWordVariable = 850
|
||||
DWordVariable = 850,
|
||||
WStringVariable = "ÄÜÉÊéà",
|
||||
StringVariable = "Hallo"
|
||||
|
||||
};
|
||||
|
||||
await plc.WriteClassAsync(tc, DB2);
|
||||
@@ -646,6 +688,9 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(Math.Round(tc2.LRealVariable, 3), Math.Round(tc2Generic.LRealVariable, 3));
|
||||
Assert.AreEqual(tc2.RealVariable, tc2Generic.RealVariable);
|
||||
Assert.AreEqual(tc2.DWordVariable, tc2Generic.DWordVariable);
|
||||
Assert.AreEqual(tc2.WStringVariable, tc2Generic.WStringVariable);
|
||||
Assert.AreEqual(tc2.StringVariable, tc2Generic.StringVariable);
|
||||
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -671,7 +716,9 @@ namespace S7.Net.UnitTest
|
||||
IntVariable = -15000,
|
||||
LRealVariable = -154.789,
|
||||
RealVariable = -154.789f,
|
||||
DWordVariable = 850
|
||||
DWordVariable = 850,
|
||||
WStringVariable = "ÄÜÉÊéà",
|
||||
StringVariable = "Hallo"
|
||||
};
|
||||
|
||||
await plc.WriteClassAsync(tc, DB2);
|
||||
@@ -686,6 +733,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(Math.Round(tc2Generic.LRealVariable, 3), Math.Round(tc2GenericWithClassFactory.LRealVariable, 3));
|
||||
Assert.AreEqual(tc2Generic.RealVariable, tc2GenericWithClassFactory.RealVariable);
|
||||
Assert.AreEqual(tc2Generic.DWordVariable, tc2GenericWithClassFactory.DWordVariable);
|
||||
Assert.AreEqual(tc2Generic.WStringVariable, tc2GenericWithClassFactory.WStringVariable);
|
||||
Assert.AreEqual(tc2Generic.StringVariable, tc2GenericWithClassFactory.StringVariable);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -792,7 +841,9 @@ namespace S7.Net.UnitTest
|
||||
IntVariable = -15000,
|
||||
LRealVariable = -154.789,
|
||||
RealVariable = -154.789f,
|
||||
DWordVariable = 850
|
||||
DWordVariable = 850,
|
||||
WStringVariable = "ÄÜÉÊéà",
|
||||
StringVariable = "Hallo"
|
||||
};
|
||||
plc.WriteClass(tc, DB2);
|
||||
|
||||
@@ -914,6 +965,31 @@ namespace S7.Net.UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
[TestMethod]
|
||||
public async Task Test_Async_ReadWriteBytesManyWithMemory()
|
||||
{
|
||||
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
|
||||
|
||||
using var data = MemoryPool<byte>.Shared.Rent(2000);
|
||||
for (int i = 0; i < data.Memory.Length; i++)
|
||||
data.Memory.Span[i] = (byte)(i % 256);
|
||||
|
||||
await plc.WriteBytesAsync(DataType.DataBlock, 2, 0, data.Memory);
|
||||
|
||||
using var readData = MemoryPool<byte>.Shared.Rent(data.Memory.Length);
|
||||
|
||||
await plc.ReadBytesAsync(readData.Memory.Slice(0, data.Memory.Length), DataType.DataBlock, 2, 0);
|
||||
|
||||
for (int x = 0; x < data.Memory.Length; x++)
|
||||
{
|
||||
Assert.AreEqual(x % 256, readData.Memory.Span[x], string.Format("Bit {0} failed", x));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write a large amount of data and test cancellation
|
||||
/// </summary>
|
||||
@@ -950,6 +1026,47 @@ namespace S7.Net.UnitTest
|
||||
Console.WriteLine("Task was not cancelled as expected.");
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
/// <summary>
|
||||
/// Write a large amount of data and test cancellation
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task Test_Async_WriteLargeByteArrayWithCancellationWithMemory()
|
||||
{
|
||||
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
|
||||
|
||||
var cancellationSource = new CancellationTokenSource();
|
||||
var cancellationToken = cancellationSource.Token;
|
||||
|
||||
using var dataOwner = MemoryPool<byte>.Shared.Rent(8192);
|
||||
var data = dataOwner.Memory.Slice(0, 8192);
|
||||
var randomEngine = new Random();
|
||||
var db = 2;
|
||||
randomEngine.NextBytes(data.Span);
|
||||
|
||||
cancellationSource.CancelAfter(TimeSpan.FromMilliseconds(5));
|
||||
try
|
||||
{
|
||||
await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// everything is good, that is the exception we expect
|
||||
Console.WriteLine("Operation was cancelled as expected.");
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Fail($"Wrong exception type received. Expected {typeof(OperationCanceledException)}, received {e.GetType()}.");
|
||||
}
|
||||
|
||||
// Depending on how tests run, this can also just succeed without getting cancelled at all. Do nothing in this case.
|
||||
Console.WriteLine("Task was not cancelled as expected.");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write a large amount of data and test cancellation
|
||||
/// </summary>
|
||||
@@ -982,6 +1099,7 @@ namespace S7.Net.UnitTest
|
||||
};
|
||||
await plc.ReadMultipleVarsAsync(dataItems, CancellationToken.None);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using S7.Net.UnitTest.Helpers;
|
||||
using S7.Net.Types;
|
||||
using S7.UnitTest.Helpers;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
using System.Buffers;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -183,6 +188,9 @@ namespace S7.Net.UnitTest
|
||||
tc.LRealVariable = -154.789;
|
||||
tc.RealVariable = -154.789f;
|
||||
tc.DWordVariable = 850;
|
||||
tc.WStringVariable = "ÄÜÉÊéà";
|
||||
tc.StringVariable = "Hallo";
|
||||
|
||||
plc.WriteClass(tc, DB2);
|
||||
TestClass tc2 = new TestClass();
|
||||
// Values that are read from a class are stored inside the class itself, that is passed by reference
|
||||
@@ -194,6 +202,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>
|
||||
@@ -577,6 +587,8 @@ namespace S7.Net.UnitTest
|
||||
tc.LRealVariable = -154.789;
|
||||
tc.RealVariable = -154.789f;
|
||||
tc.DWordVariable = 850;
|
||||
tc.WStringVariable = "ÄÜÉÊéà";
|
||||
tc.StringVariable = "Hallo";
|
||||
|
||||
plc.WriteClass(tc, DB2);
|
||||
|
||||
@@ -622,6 +634,8 @@ namespace S7.Net.UnitTest
|
||||
tc.LRealVariable = -154.789;
|
||||
tc.RealVariable = -154.789f;
|
||||
tc.DWordVariable = 850;
|
||||
tc.WStringVariable = "ÄÜÉÊéà";
|
||||
tc.StringVariable = "Hallo";
|
||||
|
||||
plc.WriteClass(tc, DB2);
|
||||
|
||||
@@ -637,6 +651,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(Math.Round(tc2.LRealVariable, 3), Math.Round(tc2Generic.LRealVariable, 3));
|
||||
Assert.AreEqual(tc2.RealVariable, tc2Generic.RealVariable);
|
||||
Assert.AreEqual(tc2.DWordVariable, tc2Generic.DWordVariable);
|
||||
Assert.AreEqual(tc2.WStringVariable, tc2Generic.WStringVariable);
|
||||
Assert.AreEqual(tc2.StringVariable, tc2Generic.StringVariable);
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(PlcException))]
|
||||
@@ -665,6 +681,8 @@ namespace S7.Net.UnitTest
|
||||
tc.LRealVariable = -154.789;
|
||||
tc.RealVariable = -154.789f;
|
||||
tc.DWordVariable = 850;
|
||||
tc.WStringVariable = "ÄÜÉÊéà";
|
||||
tc.StringVariable = "Hallo";
|
||||
|
||||
plc.WriteClass(tc, DB2);
|
||||
|
||||
@@ -679,6 +697,8 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(Math.Round(tc2Generic.LRealVariable, 3), Math.Round(tc2GenericWithClassFactory.LRealVariable, 3));
|
||||
Assert.AreEqual(tc2Generic.RealVariable, tc2GenericWithClassFactory.RealVariable);
|
||||
Assert.AreEqual(tc2Generic.DWordVariable, tc2GenericWithClassFactory.DWordVariable);
|
||||
Assert.AreEqual(tc2Generic.WStringVariable, tc2GenericWithClassFactory.WStringVariable);
|
||||
Assert.AreEqual(tc2Generic.StringVariable, tc2GenericWithClassFactory.StringVariable);
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(PlcException))]
|
||||
@@ -762,6 +782,33 @@ namespace S7.Net.UnitTest
|
||||
CollectionAssert.AreEqual(data, readData);
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
/// <summary>
|
||||
/// Write/Read a large amount of data to test PDU max
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void T33_WriteLargeByteArrayWithSpan()
|
||||
{
|
||||
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
|
||||
|
||||
var randomEngine = new Random();
|
||||
using var dataOwner = MemoryPool<byte>.Shared.Rent(8192);
|
||||
var data = dataOwner.Memory.Span.Slice(0, 8192);
|
||||
var db = 2;
|
||||
randomEngine.NextBytes(data);
|
||||
|
||||
plc.WriteBytes(DataType.DataBlock, db, 0, data);
|
||||
|
||||
using var readDataOwner = MemoryPool<byte>.Shared.Rent(data.Length);
|
||||
var readData = readDataOwner.Memory.Span.Slice(0, data.Length);
|
||||
plc.ReadBytes(readData, DataType.DataBlock, db, 0);
|
||||
|
||||
CollectionAssert.AreEqual(data.ToArray(), readData.ToArray());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[TestMethod, ExpectedException(typeof(PlcException))]
|
||||
public void T18_ReadStructThrowsIfPlcIsNotConnected()
|
||||
{
|
||||
@@ -837,6 +884,9 @@ namespace S7.Net.UnitTest
|
||||
tc.LRealVariable = -154.789;
|
||||
tc.RealVariable = -154.789f;
|
||||
tc.DWordVariable = 850;
|
||||
tc.WStringVariable = "ÄÜÉÊéà";
|
||||
tc.StringVariable = "Hallo";
|
||||
|
||||
plc.WriteClass(tc, DB2);
|
||||
|
||||
int expectedReadBytes = (int)Types.Class.GetClassSize(tc);
|
||||
@@ -987,6 +1037,32 @@ namespace S7.Net.UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
[TestMethod]
|
||||
public void T27_ReadWriteBytesManyWithSpan()
|
||||
{
|
||||
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
|
||||
|
||||
using var dataOwner = MemoryPool<byte>.Shared.Rent(2000);
|
||||
var data = dataOwner.Memory.Span;
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
data[i] = (byte)(i % 256);
|
||||
|
||||
plc.WriteBytes(DataType.DataBlock, 2, 0, data);
|
||||
|
||||
using var readDataOwner = MemoryPool<byte>.Shared.Rent(data.Length);
|
||||
var readData = readDataOwner.Memory.Span.Slice(0, data.Length);
|
||||
plc.ReadBytes(readData, DataType.DataBlock, 2, 0);
|
||||
|
||||
for (int x = 0; x < data.Length; x++)
|
||||
{
|
||||
Assert.AreEqual(x % 256, readData[x], $"Mismatch at offset {x}, expected {x % 256}, actual {readData[x]}.");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[TestMethod]
|
||||
public void T28_ReadClass_DoesntCrash_When_ReadingLessThan1Byte()
|
||||
{
|
||||
@@ -1041,7 +1117,7 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(test_value, test_value2, "Compare DateTimeLong Write/Read");
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Private methods
|
||||
|
||||
|
||||
@@ -17,6 +17,19 @@ namespace S7.Net.UnitTest.TypeTests
|
||||
Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(3, 17)), 10);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure Uint32 is correctly parsed through ReadClass functions. Adresses issue https://github.com/S7NetPlus/s7netplus/issues/414
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestUint32Read()
|
||||
{
|
||||
var result = new TestUint32();
|
||||
var data = new byte[4] { 0, 0, 0, 5 };
|
||||
var bytesRead = Class.FromBytes(result, data);
|
||||
Assert.AreEqual(bytesRead, data.Length);
|
||||
Assert.AreEqual(5u, result.Value1);
|
||||
}
|
||||
|
||||
private class TestClassUnevenSize
|
||||
{
|
||||
public bool Bool { get; set; }
|
||||
@@ -29,5 +42,10 @@ namespace S7.Net.UnitTest.TypeTests
|
||||
Bools = new bool[bitCount];
|
||||
}
|
||||
}
|
||||
|
||||
private class TestUint32
|
||||
{
|
||||
public uint Value1 { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace S7.Net
|
||||
/// <returns>A task that represents the asynchronous open operation.</returns>
|
||||
public async Task OpenAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var stream = await ConnectAsync().ConfigureAwait(false);
|
||||
var stream = await ConnectAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await queue.Enqueue(async () =>
|
||||
@@ -44,11 +44,16 @@ namespace S7.Net
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<NetworkStream> ConnectAsync()
|
||||
private async Task<NetworkStream> ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
tcpClient = new TcpClient();
|
||||
ConfigureConnection();
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
await tcpClient.ConnectAsync(IP, Port, cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
await tcpClient.ConnectAsync(IP, Port).ConfigureAwait(false);
|
||||
#endif
|
||||
return tcpClient.GetStream();
|
||||
}
|
||||
|
||||
@@ -117,6 +122,34 @@ namespace S7.Net
|
||||
return resultBytes;
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
/// <summary>
|
||||
/// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
||||
/// If the read was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer to receive the read bytes. The <see cref="Memory{T}.Length"/> determines the number of bytes to read.</param>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>Returns the bytes in an array</returns>
|
||||
public async Task ReadBytesAsync(Memory<byte> buffer, DataType dataType, int db, int startByteAdr, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int index = 0;
|
||||
while (buffer.Length > 0)
|
||||
{
|
||||
//This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
|
||||
var maxToRead = Math.Min(buffer.Length, MaxPDUSize - 18);
|
||||
await ReadBytesWithSingleRequestAsync(dataType, db, startByteAdr + index, buffer.Slice(0, maxToRead), cancellationToken).ConfigureAwait(false);
|
||||
buffer = buffer.Slice(maxToRead);
|
||||
index += maxToRead;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Read and decode a certain number of bytes of the "VarType" provided.
|
||||
/// This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc).
|
||||
@@ -315,6 +348,33 @@ namespace S7.Net
|
||||
}
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
/// <summary>
|
||||
/// Write a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
||||
/// If the write was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
|
||||
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
|
||||
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteBytesAsync(DataType dataType, int db, int startByteAdr, ReadOnlyMemory<byte> value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int localIndex = 0;
|
||||
while (value.Length > 0)
|
||||
{
|
||||
var maxToWrite = (int)Math.Min(value.Length, MaxPDUSize - 35);
|
||||
await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value.Slice(0, maxToWrite), cancellationToken).ConfigureAwait(false);
|
||||
value = value.Slice(maxToWrite);
|
||||
localIndex += maxToWrite;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write a single bit from a DB with the specified index.
|
||||
/// </summary>
|
||||
@@ -446,6 +506,20 @@ namespace S7.Net
|
||||
Array.Copy(s7data, 18, buffer, offset, count);
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, Memory<byte> buffer, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataToSend = BuildReadRequestPackage(new[] { new DataItemAddress(dataType, db, startByteAdr, buffer.Length) });
|
||||
|
||||
var s7data = await RequestTsduAsync(dataToSend, cancellationToken);
|
||||
AssertReadResponse(s7data, buffer.Length);
|
||||
|
||||
s7data.AsSpan(18, buffer.Length).CopyTo(buffer.Span);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write DataItem(s) to the PLC. Throws an exception if the response is invalid
|
||||
/// or when the PLC reports errors for item(s) written.
|
||||
@@ -491,6 +565,37 @@ namespace S7.Net
|
||||
}
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
/// <summary>
|
||||
/// Writes up to 200 bytes to the PLC. You must specify the memory area type, memory are address, byte start address and bytes count.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <param name="value">Bytes to write. The lenght of this parameter can't be higher than 200. If you need more, use recursion.</param>
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, ReadOnlyMemory<byte> value, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value.Span);
|
||||
var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new PlcException(ErrorCode.WriteData, exc);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -51,6 +51,32 @@ namespace S7.Net
|
||||
return result;
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
/// <summary>
|
||||
/// Reads a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
||||
/// If the read was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer to receive the read bytes. The <see cref="Span{T}.Length"/> determines the number of bytes to read.</param>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
|
||||
/// <returns>Returns the bytes in an array</returns>
|
||||
public void ReadBytes(Span<byte> buffer, DataType dataType, int db, int startByteAdr)
|
||||
{
|
||||
int index = 0;
|
||||
while (buffer.Length > 0)
|
||||
{
|
||||
//This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
|
||||
var maxToRead = Math.Min(buffer.Length, MaxPDUSize - 18);
|
||||
ReadBytesWithSingleRequest(dataType, db, startByteAdr + index, buffer.Slice(0, maxToRead));
|
||||
buffer = buffer.Slice(maxToRead);
|
||||
index += maxToRead;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Read and decode a certain number of bytes of the "VarType" provided.
|
||||
/// This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc).
|
||||
@@ -193,6 +219,33 @@ namespace S7.Net
|
||||
}
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
/// <summary>
|
||||
/// Write a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
|
||||
/// If the write was not successful, check LastErrorCode or LastErrorString.
|
||||
/// </summary>
|
||||
/// <param name="dataType">Data type of the memory area, can be DB, Timer, Counter, Merker(Memory), Input, Output.</param>
|
||||
/// <param name="db">Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.</param>
|
||||
/// <param name="startByteAdr">Start byte address. If you want to write DB1.DBW200, this is 200.</param>
|
||||
/// <param name="value">Bytes to write. If more than 200, multiple requests will be made.</param>
|
||||
public void WriteBytes(DataType dataType, int db, int startByteAdr, ReadOnlySpan<byte> value)
|
||||
{
|
||||
int localIndex = 0;
|
||||
while (value.Length > 0)
|
||||
{
|
||||
//TODO: Figure out how to use MaxPDUSize here
|
||||
//Snap7 seems to choke on PDU sizes above 256 even if snap7
|
||||
//replies with bigger PDU size in connection setup.
|
||||
var maxToWrite = Math.Min(value.Length, MaxPDUSize - 28);//TODO tested only when the MaxPDUSize is 480
|
||||
WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value.Slice(0, maxToWrite));
|
||||
value = value.Slice(maxToWrite);
|
||||
localIndex += maxToWrite;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write a single bit from a DB with the specified index.
|
||||
/// </summary>
|
||||
@@ -317,6 +370,33 @@ namespace S7.Net
|
||||
}
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
private void ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, Span<byte> buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
// first create the header
|
||||
int packageSize = 19 + 12; // 19 header + 12 for 1 request
|
||||
var package = new System.IO.MemoryStream(packageSize);
|
||||
BuildHeaderPackage(package);
|
||||
// package.Add(0x02); // datenart
|
||||
BuildReadDataRequestPackage(package, dataType, db, startByteAdr, buffer.Length);
|
||||
|
||||
var dataToSend = package.ToArray();
|
||||
var s7data = RequestTsdu(dataToSend);
|
||||
AssertReadResponse(s7data, buffer.Length);
|
||||
|
||||
s7data.AsSpan(18, buffer.Length).CopyTo(buffer);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new PlcException(ErrorCode.ReadData, exc);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Write DataItem(s) to the PLC. Throws an exception if the response is invalid
|
||||
/// or when the PLC reports errors for item(s) written.
|
||||
@@ -349,6 +429,25 @@ namespace S7.Net
|
||||
}
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, ReadOnlySpan<byte> value)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value);
|
||||
var s7data = RequestTsdu(dataToSend);
|
||||
|
||||
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new PlcException(ErrorCode.WriteData, exc);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private byte[] BuildWriteBytesPackage(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count)
|
||||
{
|
||||
int varCount = count;
|
||||
@@ -380,6 +479,41 @@ namespace S7.Net
|
||||
return package.ToArray();
|
||||
}
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
private byte[] BuildWriteBytesPackage(DataType dataType, int db, int startByteAdr, ReadOnlySpan<byte> value)
|
||||
{
|
||||
int varCount = value.Length;
|
||||
// first create the header
|
||||
int packageSize = 35 + varCount;
|
||||
var package = new MemoryStream(new byte[packageSize]);
|
||||
|
||||
package.WriteByte(3);
|
||||
package.WriteByte(0);
|
||||
//complete package size
|
||||
package.WriteByteArray(Int.ToByteArray((short)packageSize));
|
||||
package.WriteByteArray(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
|
||||
package.WriteByteArray(Word.ToByteArray((ushort)(varCount - 1)));
|
||||
package.WriteByteArray(new byte[] { 0, 0x0e });
|
||||
package.WriteByteArray(Word.ToByteArray((ushort)(varCount + 4)));
|
||||
package.WriteByteArray(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x02 });
|
||||
package.WriteByteArray(Word.ToByteArray((ushort)varCount));
|
||||
package.WriteByteArray(Word.ToByteArray((ushort)(db)));
|
||||
package.WriteByte((byte)dataType);
|
||||
var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
|
||||
package.WriteByte((byte)overflow);
|
||||
package.WriteByteArray(Word.ToByteArray((ushort)(startByteAdr * 8)));
|
||||
package.WriteByteArray(new byte[] { 0, 4 });
|
||||
package.WriteByteArray(Word.ToByteArray((ushort)(varCount * 8)));
|
||||
|
||||
// now join the header and the data
|
||||
package.Write(value);
|
||||
|
||||
return package.ToArray();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private byte[] BuildWriteBitPackage(DataType dataType, int db, int startByteAdr, bool bitValue, int bitAdr)
|
||||
{
|
||||
var value = new[] { bitValue ? (byte)1 : (byte)0 };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;netstandard2.0;netstandard1.3</TargetFrameworks>
|
||||
<TargetFrameworks>net452;netstandard2.0;netstandard1.3;net5.0</TargetFrameworks>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>Properties\S7.Net.snk</AssemblyOriginatorKeyFile>
|
||||
<InternalsVisibleTo>S7.Net.UnitTest</InternalsVisibleTo>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
@@ -25,7 +26,7 @@ namespace S7.Net.Types
|
||||
|
||||
}
|
||||
|
||||
private static double GetIncreasedNumberOfBytes(double numBytes, Type type)
|
||||
private static double GetIncreasedNumberOfBytes(double numBytes, Type type, PropertyInfo? propertyInfo)
|
||||
{
|
||||
switch (type.Name)
|
||||
{
|
||||
@@ -38,30 +39,30 @@ namespace S7.Net.Types
|
||||
break;
|
||||
case "Int16":
|
||||
case "UInt16":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
IncrementToEven(ref numBytes);
|
||||
numBytes += 2;
|
||||
break;
|
||||
case "Int32":
|
||||
case "UInt32":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
IncrementToEven(ref numBytes);
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Single":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
IncrementToEven(ref numBytes);
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Double":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
IncrementToEven(ref numBytes);
|
||||
numBytes += 8;
|
||||
break;
|
||||
case "String":
|
||||
S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
|
||||
if (attribute == default(S7StringAttribute))
|
||||
throw new ArgumentException("Please add S7StringAttribute to the string field");
|
||||
|
||||
IncrementToEven(ref numBytes);
|
||||
numBytes += attribute.ReservedLengthInBytes;
|
||||
break;
|
||||
default:
|
||||
var propertyClass = Activator.CreateInstance(type);
|
||||
numBytes = GetClassSize(propertyClass, numBytes, true);
|
||||
@@ -93,12 +94,12 @@ namespace S7.Net.Types
|
||||
IncrementToEven(ref numBytes);
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
numBytes = GetIncreasedNumberOfBytes(numBytes, elementType);
|
||||
numBytes = GetIncreasedNumberOfBytes(numBytes, elementType, property);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType);
|
||||
numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType, property);
|
||||
}
|
||||
}
|
||||
if (false == isInnerProperty)
|
||||
@@ -111,7 +112,7 @@ namespace S7.Net.Types
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
private static object? GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes)
|
||||
private static object? GetPropertyValue(Type propertyType, PropertyInfo? propertyInfo, byte[] bytes, ref double numBytes)
|
||||
{
|
||||
object? value = null;
|
||||
|
||||
@@ -133,50 +134,35 @@ namespace S7.Net.Types
|
||||
numBytes++;
|
||||
break;
|
||||
case "Int16":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
IncrementToEven(ref numBytes);
|
||||
// hier auswerten
|
||||
ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]);
|
||||
value = source.ConvertToShort();
|
||||
numBytes += 2;
|
||||
break;
|
||||
case "UInt16":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
IncrementToEven(ref numBytes);
|
||||
// hier auswerten
|
||||
value = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]);
|
||||
numBytes += 2;
|
||||
break;
|
||||
case "Int32":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
// hier auswerten
|
||||
uint sourceUInt = DWord.FromBytes(bytes[(int)numBytes + 3],
|
||||
bytes[(int)numBytes + 2],
|
||||
bytes[(int)numBytes + 1],
|
||||
bytes[(int)numBytes + 0]);
|
||||
IncrementToEven(ref numBytes);
|
||||
var wordBuffer = new byte[4];
|
||||
Array.Copy(bytes, (int)numBytes, wordBuffer, 0, wordBuffer.Length);
|
||||
uint sourceUInt = DWord.FromByteArray(wordBuffer);
|
||||
value = sourceUInt.ConvertToInt();
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "UInt32":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
// hier auswerten
|
||||
value = DWord.FromBytes(
|
||||
bytes[(int)numBytes],
|
||||
bytes[(int)numBytes + 1],
|
||||
bytes[(int)numBytes + 2],
|
||||
bytes[(int)numBytes + 3]);
|
||||
IncrementToEven(ref numBytes);
|
||||
var wordBuffer2 = new byte[4];
|
||||
Array.Copy(bytes, (int)numBytes, wordBuffer2, 0, wordBuffer2.Length);
|
||||
value = DWord.FromByteArray(wordBuffer2);
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Single":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
IncrementToEven(ref numBytes);
|
||||
// hier auswerten
|
||||
value = Real.FromByteArray(
|
||||
new byte[] {
|
||||
@@ -187,15 +173,31 @@ namespace S7.Net.Types
|
||||
numBytes += 4;
|
||||
break;
|
||||
case "Double":
|
||||
numBytes = Math.Ceiling(numBytes);
|
||||
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
|
||||
numBytes++;
|
||||
IncrementToEven(ref numBytes);
|
||||
var buffer = new byte[8];
|
||||
Array.Copy(bytes, (int)numBytes, buffer, 0, 8);
|
||||
// hier auswerten
|
||||
value = LReal.FromByteArray(buffer);
|
||||
numBytes += 8;
|
||||
break;
|
||||
case "String":
|
||||
S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
|
||||
if (attribute == default(S7StringAttribute))
|
||||
throw new ArgumentException("Please add S7StringAttribute to the string field");
|
||||
|
||||
IncrementToEven(ref numBytes);
|
||||
|
||||
// get the value
|
||||
var sData = new byte[attribute.ReservedLengthInBytes];
|
||||
Array.Copy(bytes, (int)numBytes, sData, 0, sData.Length);
|
||||
value = attribute.Type switch
|
||||
{
|
||||
S7StringType.S7String => S7String.FromByteArray(sData),
|
||||
S7StringType.S7WString => S7WString.FromByteArray(sData),
|
||||
_ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
|
||||
};
|
||||
numBytes += sData.Length;
|
||||
break;
|
||||
default:
|
||||
var propClass = Activator.CreateInstance(propertyType);
|
||||
numBytes = FromBytes(propClass, bytes, numBytes);
|
||||
@@ -227,7 +229,7 @@ namespace S7.Net.Types
|
||||
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
|
||||
{
|
||||
array.SetValue(
|
||||
GetPropertyValue(elementType, bytes, ref numBytes),
|
||||
GetPropertyValue(elementType, property, bytes, ref numBytes),
|
||||
i);
|
||||
}
|
||||
}
|
||||
@@ -235,7 +237,7 @@ namespace S7.Net.Types
|
||||
{
|
||||
property.SetValue(
|
||||
sourceClass,
|
||||
GetPropertyValue(property.PropertyType, bytes, ref numBytes),
|
||||
GetPropertyValue(property.PropertyType, property, bytes, ref numBytes),
|
||||
null);
|
||||
}
|
||||
}
|
||||
@@ -243,7 +245,7 @@ namespace S7.Net.Types
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
private static double SetBytesFromProperty(object propertyValue, byte[] bytes, double numBytes)
|
||||
private static double SetBytesFromProperty(object propertyValue, PropertyInfo? propertyInfo, byte[] bytes, double numBytes)
|
||||
{
|
||||
int bytePos = 0;
|
||||
int bitPos = 0;
|
||||
@@ -285,6 +287,18 @@ namespace S7.Net.Types
|
||||
case "Double":
|
||||
bytes2 = LReal.ToByteArray((double)propertyValue);
|
||||
break;
|
||||
case "String":
|
||||
S7StringAttribute? attribute = propertyInfo?.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)propertyValue, attribute.ReservedLength),
|
||||
S7StringType.S7WString => S7WString.ToByteArray((string)propertyValue, attribute.ReservedLength),
|
||||
_ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
|
||||
};
|
||||
break;
|
||||
default:
|
||||
numBytes = ToBytes(propertyValue, bytes, numBytes);
|
||||
break;
|
||||
@@ -320,12 +334,12 @@ namespace S7.Net.Types
|
||||
Type elementType = property.PropertyType.GetElementType();
|
||||
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
|
||||
{
|
||||
numBytes = SetBytesFromProperty(array.GetValue(i), bytes, numBytes);
|
||||
numBytes = SetBytesFromProperty(array.GetValue(i), property, bytes, numBytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), bytes, numBytes);
|
||||
numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), property, bytes, numBytes);
|
||||
}
|
||||
}
|
||||
return numBytes;
|
||||
|
||||
@@ -9,6 +9,18 @@ namespace S7.Net.Types
|
||||
/// </summary>
|
||||
public static class S7String
|
||||
{
|
||||
private static Encoding stringEncoding = Encoding.ASCII;
|
||||
|
||||
/// <summary>
|
||||
/// The Encoding used when serializing and deserializing S7String (Encoding.ASCII by default)
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">StringEncoding must not be null</exception>
|
||||
public static Encoding StringEncoding
|
||||
{
|
||||
get => stringEncoding;
|
||||
set => stringEncoding = value ?? throw new ArgumentNullException(nameof(StringEncoding));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts S7 bytes to a string
|
||||
/// </summary>
|
||||
@@ -30,7 +42,7 @@ namespace S7.Net.Types
|
||||
|
||||
try
|
||||
{
|
||||
return Encoding.ASCII.GetString(bytes, 2, length);
|
||||
return StringEncoding.GetString(bytes, 2, length);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -38,7 +50,6 @@ namespace S7.Net.Types
|
||||
$"Failed to parse {VarType.S7String} from data. Following fields were read: size: '{size}', actual length: '{length}', total number of bytes (including header): '{bytes.Length}'.",
|
||||
e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -56,7 +67,7 @@ namespace S7.Net.Types
|
||||
|
||||
if (reservedLength > 254) throw new ArgumentException($"The maximum string length supported is 254.");
|
||||
|
||||
var bytes = Encoding.ASCII.GetBytes(value);
|
||||
var bytes = StringEncoding.GetBytes(value);
|
||||
if (bytes.Length > reservedLength) throw new ArgumentException($"The provided string length ({bytes.Length} is larger than the specified reserved length ({reservedLength}).");
|
||||
|
||||
var buffer = new byte[2 + reservedLength];
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
|
||||
public sealed class S7StringAttribute : Attribute
|
||||
{
|
||||
private readonly S7StringType type;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
image: Visual Studio 2019
|
||||
image: Visual Studio 2022
|
||||
configuration: Release
|
||||
install:
|
||||
- choco install gitversion.portable -y
|
||||
|
||||
Reference in New Issue
Block a user