29 Commits

Author SHA1 Message Date
Michael Croes
77dcb1778b Merge pull request #485 from dylandrush/484_Public_PLCExceptions
Made all exceptions public
2023-06-19 14:52:02 +02:00
DRUSH12
14053e342a Made all exceptions public 2023-06-19 06:14:14 -04:00
Michael Croes
ab3bd87701 chore: Delete GitVersion configuration
Rely on builtin defaults from now on.
2023-05-30 22:21:12 +02:00
Michael Croes
bc7c27e1d4 Release S7NetPlus 0.18.0
Release highlights:
- Add Memory/Span support from 0.17.0 to < net5 targets
2023-05-30 21:47:40 +02:00
Michael Croes
f0256fd0cb Merge pull request #483 from gfoidl/span-memory
Use System.Memory for < .NET 5 and avoid (some) unnecessary allocations
2023-05-30 21:44:33 +02:00
Günther Foidl
209148ab02 Use System.Memory for < .NET 5 and avoid (some) unnecessary allocations 2023-05-30 17:25:25 +02:00
Michael Croes
2fc9eaade3 Release S7NetPlus 0.17.0
Release highlights:
- Add Read-/WriteBytes overloads for Span<byte> and Memory<byte>
2023-05-30 12:19:39 +02:00
Michael Croes
ab70bfb041 Merge pull request #482 from ArgusMagnus/add_span_overloads
add Read/WriteBytes(Async) overloads accepting Span<byte>/Memory<byte> for .NET5 or greater
2023-05-30 12:14:19 +02:00
ArgusMagnus
e277cf6e6c add Read/WriteBytes(Async) overloads accepting Span<byte>/Memory<byte> for .NET5 or greater 2023-05-30 10:47:38 +02:00
Michael Croes
43b29825a4 Release S7NetPlus 0.16.0
Release highlights:
- Fix UInt32 reads in classes
- Add string support for classes
2022-12-10 21:22:16 +01:00
Michael Croes
6aa0133081 Merge pull request #415 from scamille/fb-fixClassUint32
Fix ReadClass for UInt32
2022-12-10 21:12:28 +01:00
Michael Croes
868e719b78 Merge remote-tracking branch 's7netplus/develop' into fb-fixClassUint32
# Conflicts:
#	S7.Net/Types/Class.cs
2022-12-10 21:09:29 +01:00
Michael Croes
3833d29c0e Merge pull request #459 from mycroes/test-workflow
ci: Run test on ubuntu-20.04 due to lack of snap7 on newer ubuntu
2022-12-10 21:01:21 +01:00
Michael Croes
144f814794 Merge branch 'develop' into test-workflow 2022-12-10 20:58:29 +01:00
Michael Croes
82aaf7e2cb Merge pull request #458 from MCPC10/StringSupportForClass
Added support for string/wstring in a class
2022-12-10 20:58:10 +01:00
Michael Croes
f47918946d ci: Run test on ubuntu-20.04 due to lack of snap7 on newer ubuntu 2022-12-10 20:51:44 +01:00
Mike Cremer
142f1ba90e Added support string/wstring for class type 2022-12-10 18:59:25 +01:00
Michael Croes
d99d0d0e6f Release S7NetPlus 0.15.0
Release highlights:
- Add flowed cancellation to ConnectAsync
2022-11-08 11:22:47 +01:00
Michael Croes
ce9f9f9e08 Merge pull request #423 from gfoidl/openasync_cancellation
Flowed cancellation token to TcpClient.ConnectAsync in .NET 5.0 target
2022-11-08 11:19:37 +01:00
Michael Croes
ea1140314b Merge branch 'develop' into openasync_cancellation 2022-11-08 11:13:25 +01:00
Michael Croes
e3fad0b94f Release S7NetPlus 0.14.0
Release highlights:
- Support setting the Encoding for S7String
2022-06-17 23:14:48 +02:00
Michael Croes
7f76d4fc5a Merge pull request #435 from ismdiego/develop
Allow changing the default Encoding used in S7String
2022-06-17 23:12:43 +02:00
diego
ec554ddb59 Better stringEncoding initialization, as per code review comments 2022-06-17 11:23:58 +02:00
diego
2ecd2c6b49 Changes following code review 2022-06-17 11:08:09 +02:00
diego
d808ea5eb6 Give the option of changing the Encoding used to serialize and deserialize S7String, instead of always using Encoding.ASCII 2022-06-16 17:21:23 +02:00
Günther Foidl
5d3f01e59e Updated AppVeyor image to VS 2022 to allow better conditional compilation 2021-12-26 19:26:44 +01:00
Günther Foidl
9c3f95ce73 Flowed cancellation token to TcpClient.ConnectAsync in .NET 5.0 target 2021-12-26 19:26:04 +01:00
Serge Camille
12281ec802 Fix ReadClass for Uint32
Use consistent DWord conversion for both Int32 and UInt32. Unfortunately there is no Span or even a FromByteArray function accepting a offset, so just use the same Array.Copy falls used for double.
2021-10-04 18:59:05 +02:00
Serge Camille
8df1a9c8cb Add unit test for ReadClass uint32 bug.
https://github.com/S7NetPlus/s7netplus/issues/414
2021-10-04 18:53:13 +02:00
16 changed files with 497 additions and 181 deletions

View File

@@ -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

View File

@@ -1,21 +0,0 @@
assembly-informational-format: '{NuGetVersion}'
mode: ContinuousDeployment
branches:
master:
tag: rc
increment: Minor
feature:
regex: features?[/-]
tag: rc-{BranchName}
increment: Minor
pull-request:
regex: (pull|pull\-requests|pr)[/-]
tag: rc-pr-{BranchName}
increment: Minor
hotfix:
regex: hotfix(es)?[/-]
tag: rc
increment: Patch
develop:
regex: dev(elop)?(ment)?$
tag: b

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -1,6 +1,10 @@

using System;
using System.Buffers;
using System.IO;
namespace S7.Net.Helper
{
#if !NET5_0_OR_GREATER
internal static class MemoryStreamExtension
{
/// <summary>
@@ -10,9 +14,25 @@ namespace S7.Net.Helper
/// </summary>
/// <param name="stream"></param>
/// <param name="value"></param>
public static void WriteByteArray(this System.IO.MemoryStream stream, byte[] value)
public static void Write(this MemoryStream stream, byte[] value)
{
stream.Write(value, 0, value.Length);
}
/// <summary>
/// Helper function to write the whole content of the given byte span to a memory stream.
/// </summary>
/// <param name="stream"></param>
/// <param name="value"></param>
public static void Write(this MemoryStream stream, ReadOnlySpan<byte> value)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(value.Length);
value.CopyTo(buffer);
stream.Write(buffer, 0, value.Length);
ArrayPool<byte>.Shared.Return(buffer);
}
}
#endif
}

View File

@@ -6,7 +6,7 @@ using System.Runtime.Serialization;
namespace S7.Net
{
internal class WrongNumberOfBytesException : Exception
public class WrongNumberOfBytesException : Exception
{
public WrongNumberOfBytesException() : base()
{
@@ -27,7 +27,7 @@ namespace S7.Net
#endif
}
internal class InvalidAddressException : Exception
public class InvalidAddressException : Exception
{
public InvalidAddressException() : base ()
{
@@ -48,7 +48,7 @@ namespace S7.Net
#endif
}
internal class InvalidVariableTypeException : Exception
public class InvalidVariableTypeException : Exception
{
public InvalidVariableTypeException() : base()
{
@@ -69,7 +69,7 @@ namespace S7.Net
#endif
}
internal class TPKTInvalidException : Exception
public class TPKTInvalidException : Exception
{
public TPKTInvalidException() : base()
{
@@ -90,7 +90,7 @@ namespace S7.Net
#endif
}
internal class TPDUInvalidException : Exception
public class TPDUInvalidException : Exception
{
public TPDUInvalidException() : base()
{

View File

@@ -1,7 +1,6 @@
using S7.Net.Helper;
using S7.Net.Protocol.S7;
using S7.Net.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using DateTime = S7.Net.Types.DateTime;
@@ -18,13 +17,13 @@ namespace S7.Net
private static void BuildHeaderPackage(System.IO.MemoryStream stream, int amount = 1)
{
//header size = 19 bytes
stream.WriteByteArray(new byte[] { 0x03, 0x00 });
stream.Write(new byte[] { 0x03, 0x00 });
//complete package size
stream.WriteByteArray(Types.Int.ToByteArray((short)(19 + (12 * amount))));
stream.WriteByteArray(new byte[] { 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x00, 0x00 });
stream.Write(Int.ToByteArray((short)(19 + (12 * amount))));
stream.Write(new byte[] { 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x00, 0x00 });
//data part size
stream.WriteByteArray(Types.Word.ToByteArray((ushort)(2 + (amount * 12))));
stream.WriteByteArray(new byte[] { 0x00, 0x00, 0x04 });
stream.Write(Word.ToByteArray((ushort)(2 + (amount * 12))));
stream.Write(new byte[] { 0x00, 0x00, 0x04 });
//amount of requests
stream.WriteByte((byte)amount);
}
@@ -41,7 +40,7 @@ namespace S7.Net
private static void BuildReadDataRequestPackage(System.IO.MemoryStream stream, DataType dataType, int db, int startByteAdr, int count = 1)
{
//single data req = 12
stream.WriteByteArray(new byte[] { 0x12, 0x0a, 0x10 });
stream.Write(new byte[] { 0x12, 0x0a, 0x10 });
switch (dataType)
{
case DataType.Timer:
@@ -53,8 +52,8 @@ namespace S7.Net
break;
}
stream.WriteByteArray(Word.ToByteArray((ushort)(count)));
stream.WriteByteArray(Word.ToByteArray((ushort)(db)));
stream.Write(Word.ToByteArray((ushort)(count)));
stream.Write(Word.ToByteArray((ushort)(db)));
stream.WriteByte((byte)dataType);
var overflow = (int)(startByteAdr * 8 / 0xffffU); // handles words with address bigger than 8191
stream.WriteByte((byte)overflow);
@@ -62,10 +61,10 @@ namespace S7.Net
{
case DataType.Timer:
case DataType.Counter:
stream.WriteByteArray(Types.Word.ToByteArray((ushort)(startByteAdr)));
stream.Write(Word.ToByteArray((ushort)(startByteAdr)));
break;
default:
stream.WriteByteArray(Types.Word.ToByteArray((ushort)((startByteAdr) * 8)));
stream.Write(Word.ToByteArray((ushort)((startByteAdr) * 8)));
break;
}
}

View File

@@ -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();
}
@@ -90,7 +95,6 @@ namespace S7.Net
MaxPDUSize = s7data[18] * 256 + s7data[19];
}
/// <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.
@@ -105,16 +109,34 @@ namespace S7.Net
public async Task<byte[]> ReadBytesAsync(DataType dataType, int db, int startByteAdr, int count, CancellationToken cancellationToken = default)
{
var resultBytes = new byte[count];
await ReadBytesAsync(resultBytes, dataType, db, startByteAdr, cancellationToken).ConfigureAwait(false);
return resultBytes;
}
/// <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 (count > 0)
while (buffer.Length > 0)
{
//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).ConfigureAwait(false);
count -= maxToRead;
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;
}
return resultBytes;
}
/// <summary>
@@ -290,6 +312,21 @@ namespace S7.Net
return dataItems;
}
/// <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 Task WriteBytesAsync(DataType dataType, int db, int startByteAdr, byte[] value, CancellationToken cancellationToken = default)
{
return WriteBytesAsync(dataType, db, startByteAdr, value.AsMemory(), cancellationToken);
}
/// <summary>
/// Write a number of bytes from a DB starting from a specified index. This handles more than 200 bytes with multiple requests.
@@ -302,15 +339,14 @@ namespace S7.Net
/// <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, byte[] value, CancellationToken cancellationToken = default)
public async Task WriteBytesAsync(DataType dataType, int db, int startByteAdr, ReadOnlyMemory<byte> value, CancellationToken cancellationToken = default)
{
int localIndex = 0;
int count = value.Length;
while (count > 0)
while (value.Length > 0)
{
var maxToWrite = (int)Math.Min(count, MaxPDUSize - 35);
await WriteBytesWithASingleRequestAsync(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite, cancellationToken).ConfigureAwait(false);
count -= maxToWrite;
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;
}
}
@@ -436,14 +472,14 @@ namespace S7.Net
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes, cancellationToken).ConfigureAwait(false);
}
private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, Memory<byte> buffer, CancellationToken cancellationToken)
{
var dataToSend = BuildReadRequestPackage(new[] { new DataItemAddress(dataType, db, startByteAdr, count) });
var dataToSend = BuildReadRequestPackage(new[] { new DataItemAddress(dataType, db, startByteAdr, buffer.Length) });
var s7data = await RequestTsduAsync(dataToSend, cancellationToken);
AssertReadResponse(s7data, count);
AssertReadResponse(s7data, buffer.Length);
Array.Copy(s7data, 18, buffer, offset, count);
s7data.AsSpan(18, buffer.Length).CopyTo(buffer.Span);
}
/// <summary>
@@ -472,11 +508,11 @@ namespace S7.Net
/// <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, byte[] value, int dataOffset, int count, CancellationToken cancellationToken)
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, ReadOnlyMemory<byte> value, CancellationToken cancellationToken)
{
try
{
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count);
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value.Span);
var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false);
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);

View File

@@ -39,16 +39,32 @@ namespace S7.Net
public byte[] ReadBytes(DataType dataType, int db, int startByteAdr, int count)
{
var result = new byte[count];
ReadBytes(result, dataType, db, startByteAdr);
return result;
}
/// <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 (count > 0)
while (buffer.Length > 0)
{
//This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
var maxToRead = Math.Min(count, MaxPDUSize - 18);
ReadBytesWithSingleRequest(dataType, db, startByteAdr + index, result, index, maxToRead);
count -= maxToRead;
var maxToRead = Math.Min(buffer.Length, MaxPDUSize - 18);
ReadBytesWithSingleRequest(dataType, db, startByteAdr + index, buffer.Slice(0, maxToRead));
buffer = buffer.Slice(maxToRead);
index += maxToRead;
}
return result;
}
/// <summary>
@@ -111,7 +127,6 @@ namespace S7.Net
return ReadStruct(typeof(T), db, startByteAdr) as T?;
}
/// <summary>
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
@@ -178,17 +193,29 @@ namespace S7.Net
/// <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, byte[] value)
{
WriteBytes(dataType, db, startByteAdr, value.AsSpan());
}
/// <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;
int count = value.Length;
while (count > 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(count, MaxPDUSize - 28);//TODO tested only when the MaxPDUSize is 480
WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite);
count -= maxToWrite;
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;
}
}
@@ -294,22 +321,22 @@ namespace S7.Net
WriteClassAsync(classValue, db, startByteAdr).GetAwaiter().GetResult();
}
private void ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count)
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);
const int packageSize = 19 + 12; // 19 header + 12 for 1 request
var dataToSend = new byte[packageSize];
var package = new MemoryStream(dataToSend);
BuildHeaderPackage(package);
// package.Add(0x02); // datenart
BuildReadDataRequestPackage(package, dataType, db, startByteAdr, count);
BuildReadDataRequestPackage(package, dataType, db, startByteAdr, buffer.Length);
var dataToSend = package.ToArray();
var s7data = RequestTsdu(dataToSend);
AssertReadResponse(s7data, count);
AssertReadResponse(s7data, buffer.Length);
Array.Copy(s7data, 18, buffer, offset, count);
s7data.AsSpan(18, buffer.Length).CopyTo(buffer);
}
catch (Exception exc)
{
@@ -326,7 +353,6 @@ namespace S7.Net
{
AssertPduSizeForWrite(dataItems);
var message = new ByteArray();
var length = S7WriteMultiple.CreateRequest(message, dataItems);
var response = RequestTsdu(message.Array, 0, length);
@@ -334,11 +360,11 @@ namespace S7.Net
S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
}
private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count)
private void WriteBytesWithASingleRequest(DataType dataType, int db, int startByteAdr, ReadOnlySpan<byte> value)
{
try
{
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count);
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value);
var s7data = RequestTsdu(dataToSend);
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
@@ -349,35 +375,37 @@ namespace S7.Net
}
}
private byte[] BuildWriteBytesPackage(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count)
private byte[] BuildWriteBytesPackage(DataType dataType, int db, int startByteAdr, ReadOnlySpan<byte> value)
{
int varCount = count;
int varCount = value.Length;
// first create the header
int packageSize = 35 + varCount;
var package = new MemoryStream(new byte[packageSize]);
var packageData = new byte[packageSize];
var package = new MemoryStream(packageData);
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.Write(Int.ToByteArray((short)packageSize));
// This overload doesn't allocate the byte array, it refers to assembly's static data segment
package.Write(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
package.Write(Word.ToByteArray((ushort)(varCount - 1)));
package.Write(new byte[] { 0, 0x0e });
package.Write(Word.ToByteArray((ushort)(varCount + 4)));
package.Write(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x02 });
package.Write(Word.ToByteArray((ushort)varCount));
package.Write(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)));
package.Write(Word.ToByteArray((ushort)(startByteAdr * 8)));
package.Write(new byte[] { 0, 4 });
package.Write(Word.ToByteArray((ushort)(varCount * 8)));
// now join the header and the data
package.Write(value, dataOffset, count);
package.Write(value);
return package.ToArray();
return packageData;
}
private byte[] BuildWriteBitPackage(DataType dataType, int db, int startByteAdr, bool bitValue, int bitAdr)
@@ -386,33 +414,33 @@ namespace S7.Net
int varCount = 1;
// first create the header
int packageSize = 35 + varCount;
var package = new MemoryStream(new byte[packageSize]);
var packageData = new byte[packageSize];
var package = new MemoryStream(packageData);
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, 0x01 }); //ending 0x01 is used for writing a sinlge bit
package.WriteByteArray(Word.ToByteArray((ushort)varCount));
package.WriteByteArray(Word.ToByteArray((ushort)(db)));
package.Write(Int.ToByteArray((short)packageSize));
package.Write(new byte[] { 2, 0xf0, 0x80, 0x32, 1, 0, 0 });
package.Write(Word.ToByteArray((ushort)(varCount - 1)));
package.Write(new byte[] { 0, 0x0e });
package.Write(Word.ToByteArray((ushort)(varCount + 4)));
package.Write(new byte[] { 0x05, 0x01, 0x12, 0x0a, 0x10, 0x01 }); //ending 0x01 is used for writing a sinlge bit
package.Write(Word.ToByteArray((ushort)varCount));
package.Write(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 + bitAdr)));
package.WriteByteArray(new byte[] { 0, 0x03 }); //ending 0x03 is used for writing a sinlge bit
package.WriteByteArray(Word.ToByteArray((ushort)(varCount)));
package.Write(Word.ToByteArray((ushort)(startByteAdr * 8 + bitAdr)));
package.Write(new byte[] { 0, 0x03 }); //ending 0x03 is used for writing a sinlge bit
package.Write(Word.ToByteArray((ushort)(varCount)));
// now join the header and the data
package.WriteByteArray(value);
package.Write(value);
return package.ToArray();
return packageData;
}
private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
{
try
@@ -444,7 +472,8 @@ namespace S7.Net
{
// first create the header
int packageSize = 19 + (dataItems.Count * 12);
var package = new System.IO.MemoryStream(packageSize);
var dataToSend = new byte[packageSize];
var package = new MemoryStream(dataToSend);
BuildHeaderPackage(package, dataItems.Count);
// package.Add(0x02); // datenart
foreach (var dataItem in dataItems)
@@ -452,8 +481,7 @@ namespace S7.Net
BuildReadDataRequestPackage(package, dataItem.DataType, dataItem.DB, dataItem.StartByteAdr, VarTypeToByteLength(dataItem.VarType, dataItem.Count));
}
var dataToSend = package.ToArray();
var s7data = RequestTsdu(dataToSend);
byte[] s7data = RequestTsdu(dataToSend);
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);

View File

@@ -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>
@@ -27,6 +27,10 @@
<DefineConstants>NET_FULL</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0'">
<PackageReference Include="System.Memory" Version="4.5.5" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>

View File

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

View File

@@ -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];

View File

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

View File

@@ -1,4 +1,4 @@
image: Visual Studio 2019
image: Visual Studio 2022
configuration: Release
install:
- choco install gitversion.portable -y