15 Commits

Author SHA1 Message Date
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
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
10 changed files with 152 additions and 64 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,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,7 @@ using S7.Net.Types;
using S7.UnitTest.Helpers;
using System.Threading.Tasks;
using System.Threading;
using System.Security.Cryptography;
#endregion
@@ -154,7 +155,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 +171,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 +585,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 +635,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 +656,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 +684,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 +701,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 +809,9 @@ namespace S7.Net.UnitTest
IntVariable = -15000,
LRealVariable = -154.789,
RealVariable = -154.789f,
DWordVariable = 850
DWordVariable = 850,
WStringVariable = "ÄÜÉÊéà",
StringVariable = "Hallo"
};
plc.WriteClass(tc, DB2);

View File

@@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net.UnitTest.Helpers;
using S7.Net.Types;
using S7.UnitTest.Helpers;
using System.Security.Cryptography;
#endregion
@@ -183,6 +184,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 +198,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 +583,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 +630,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 +647,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 +677,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 +693,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))]
@@ -837,6 +853,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);

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

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

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>

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

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