Merge branch 'main' into plc-status

This commit is contained in:
Michael Croes
2023-07-29 23:12:22 +02:00
14 changed files with 76 additions and 47 deletions

View File

@@ -75,7 +75,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install Snap7 Linux
if: ${{ matrix.os == 'ubuntu-20.04' }}
@@ -90,14 +90,14 @@ jobs:
brew install snap7
- name: Setup Dotnet
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: |
6.x
7.x
- name: Nuget Cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.nuget/packages
# Look to see if there is a cache hit for the corresponding requirements file

View File

@@ -55,6 +55,7 @@ namespace S7.Net
/// See: https://tools.ietf.org/html/rfc905
/// </summary>
/// <param name="stream">The socket to read from</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
/// <returns>COTP DPDU instance</returns>
public static async Task<TPDU> ReadAsync(Stream stream, CancellationToken cancellationToken)
{
@@ -89,6 +90,7 @@ namespace S7.Net
/// See: https://tools.ietf.org/html/rfc905
/// </summary>
/// <param name="stream">The stream to read from</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
/// <returns>Data in TSDU</returns>
public static async Task<byte[]> ReadAsync(Stream stream, CancellationToken cancellationToken)
{

View File

@@ -33,8 +33,8 @@ namespace S7.Net
/// <summary>
/// Creates the header to read bytes from the PLC.
/// </summary>
/// <param name="stream">The <see cref="System.IO.MemoryStream"/> to write to.</param>
/// <param name="amount">The amount of items to read.</param>
/// <param name="stream">The stream to write to.</param>
/// <param name="amount">The number of items to read.</param>
private static void WriteReadHeader(System.IO.MemoryStream stream, int amount = 1)
{
// Header size 19, 12 bytes per item
@@ -99,7 +99,7 @@ namespace S7.Net
/// Create the bytes-package to request data from the PLC. You have to specify the memory type (dataType),
/// the address of the memory, the address of the byte and the bytes count.
/// </summary>
/// <param name="stream">The <see cref="System.IO.MemoryStream"/> to write to.</param>
/// <param name="stream">The stream to write the read data request to.</param>
/// <param name="dataType">MemoryType (DB, Timer, Counter, etc.)</param>
/// <param name="db">Address of the memory to be read</param>
/// <param name="startByteAdr">Start address of the byte</param>

View File

@@ -439,7 +439,6 @@ namespace S7.Net
/// <summary>
/// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
/// If the write was not successful, check <see cref="LastErrorCode"/> or <see cref="LastErrorString"/>.
/// </summary>
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
/// <param name="value">Value to be written to the PLC</param>
@@ -518,6 +517,7 @@ namespace S7.Net
/// <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>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</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)
{

View File

@@ -289,7 +289,6 @@ namespace S7.Net
/// <summary>
/// Writes a single variable from the PLC, takes in input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.
/// If the write was not successful, check <see cref="LastErrorCode"/> or <see cref="LastErrorString"/>.
/// </summary>
/// <param name="variable">Input strings like "DB1.DBX0.0", "DB20.DBD200", "MB20", "T45", etc.</param>
/// <param name="value">Value to be written to the PLC</param>

View File

@@ -15,12 +15,13 @@
<RepositoryType>git</RepositoryType>
<PackageTags>PLC Siemens Communication S7</PackageTags>
<Copyright>Derek Heiser 2015</Copyright>
<LangVersion>8.0</LangVersion>
<LangVersion>latest</LangVersion>
<Nullable>Enable</Nullable>
<DebugType>portable</DebugType>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591;NETSDK1138</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net452' Or '$(TargetFramework)' == 'net462' Or '$(TargetFramework)' == 'netstandard2.0' ">

View File

@@ -39,6 +39,7 @@ namespace S7.Net
/// <param name="buffer">the buffer to read into</param>
/// <param name="offset">the offset in the buffer to read into</param>
/// <param name="count">the amount of bytes to read into the buffer</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
/// <returns>returns the amount of read bytes</returns>
public static async Task<int> ReadExactAsync(this Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{

View File

@@ -29,6 +29,7 @@ namespace S7.Net
/// Reads a TPKT from the socket Async
/// </summary>
/// <param name="stream">The stream to read from</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
/// <returns>Task TPKT Instace</returns>
public static async Task<TPKT> ReadAsync(Stream stream, CancellationToken cancellationToken)
{

View File

@@ -64,7 +64,8 @@ namespace S7.Net.Types
numBytes += attribute.ReservedLengthInBytes;
break;
default:
var propertyClass = Activator.CreateInstance(type);
var propertyClass = Activator.CreateInstance(type) ??
throw new ArgumentException($"Failed to create instance of type {type}.", nameof(type));
numBytes = GetClassSize(propertyClass, numBytes, true);
break;
}
@@ -76,6 +77,8 @@ namespace S7.Net.Types
/// Gets the size of the class in bytes.
/// </summary>
/// <param name="instance">An instance of the class</param>
/// <param name="numBytes">The offset of the current field.</param>
/// <param name="isInnerProperty"><see langword="true" /> if this property belongs to a class being serialized as member of the class requested for serialization; otherwise, <see langword="false" />.</param>
/// <returns>the number of bytes</returns>
public static double GetClassSize(object instance, double numBytes = 0.0, bool isInnerProperty = false)
{
@@ -84,8 +87,10 @@ namespace S7.Net.Types
{
if (property.PropertyType.IsArray)
{
Type elementType = property.PropertyType.GetElementType();
Array array = (Array)property.GetValue(instance, null);
Type elementType = property.PropertyType.GetElementType()!;
Array array = (Array?) property.GetValue(instance, null) ??
throw new ArgumentException($"Property {property.Name} on {instance} must have a non-null value to get it's size.", nameof(instance));
if (array.Length <= 0)
{
throw new Exception("Cannot determine size of class, because an array is defined which has no fixed size greater than zero.");
@@ -199,7 +204,9 @@ namespace S7.Net.Types
numBytes += sData.Length;
break;
default:
var propClass = Activator.CreateInstance(propertyType);
var propClass = Activator.CreateInstance(propertyType) ??
throw new ArgumentException($"Failed to create instance of type {propertyType}.", nameof(propertyType));
numBytes = FromBytes(propClass, bytes, numBytes);
value = propClass;
break;
@@ -213,6 +220,8 @@ namespace S7.Net.Types
/// </summary>
/// <param name="sourceClass">The object to fill in the given array of bytes</param>
/// <param name="bytes">The array of bytes</param>
/// <param name="numBytes">The offset for the current field.</param>
/// <param name="isInnerClass"><see langword="true" /> if this class is the type of a member of the class to be serialized; otherwise, <see langword="false" />.</param>
public static double FromBytes(object sourceClass, byte[] bytes, double numBytes = 0, bool isInnerClass = false)
{
if (bytes == null)
@@ -223,9 +232,11 @@ namespace S7.Net.Types
{
if (property.PropertyType.IsArray)
{
Array array = (Array)property.GetValue(sourceClass, null);
Array array = (Array?) property.GetValue(sourceClass, null) ??
throw new ArgumentException($"Property {property.Name} on sourceClass must be an array instance.", nameof(sourceClass));
IncrementToEven(ref numBytes);
Type elementType = property.PropertyType.GetElementType();
Type elementType = property.PropertyType.GetElementType()!;
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
{
array.SetValue(
@@ -320,26 +331,30 @@ namespace S7.Net.Types
/// <summary>
/// Creates a byte array depending on the struct type.
/// </summary>
/// <param name="sourceClass">The struct object</param>
/// <param name="sourceClass">The struct object.</param>
/// <param name="bytes">The target byte array.</param>
/// <param name="numBytes">The offset for the current field.</param>
/// <returns>A byte array or null if fails.</returns>
public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = 0.0)
{
var properties = GetAccessableProperties(sourceClass.GetType());
foreach (var property in properties)
{
var value = property.GetValue(sourceClass, null) ??
throw new ArgumentException($"Property {property.Name} on sourceClass can't be null.", nameof(sourceClass));
if (property.PropertyType.IsArray)
{
Array array = (Array)property.GetValue(sourceClass, null);
Array array = (Array) value;
IncrementToEven(ref numBytes);
Type elementType = property.PropertyType.GetElementType();
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
{
numBytes = SetBytesFromProperty(array.GetValue(i), property, bytes, numBytes);
numBytes = SetBytesFromProperty(array.GetValue(i)!, property, bytes, numBytes);
}
}
else
{
numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), property, bytes, numBytes);
numBytes = SetBytesFromProperty(value, property, bytes, numBytes);
}
}
return numBytes;

View File

@@ -141,7 +141,7 @@ namespace S7.Net.Types
/// Converts an array of <see cref="T:System.DateTime"/> values to a byte array.
/// </summary>
/// <param name="dateTimes">The DateTime values to convert.</param>
/// <returns>A byte array containing the S7 date time representations of <paramref name="dateTime"/>.</returns>
/// <returns>A byte array containing the S7 date time representations of <paramref name="dateTimes"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when any value of
/// <paramref name="dateTimes"/> is before <see cref="P:SpecMinimumDateTime"/>
/// or after <see cref="P:SpecMaximumDateTime"/>.</exception>

View File

@@ -58,7 +58,7 @@ namespace S7.Net.Types
/// <param name="value">The string to convert to byte array.</param>
/// <param name="reservedLength">The length (in characters) allocated in PLC for the string.</param>
/// <returns>A <see cref="T:byte[]" /> containing the string header and string value with a maximum length of <paramref name="reservedLength"/> + 2.</returns>
public static byte[] ToByteArray(string value, int reservedLength)
public static byte[] ToByteArray(string? value, int reservedLength)
{
if (value is null)
{

View File

@@ -48,7 +48,7 @@ namespace S7.Net.Types
/// <param name="value">The string to convert to byte array.</param>
/// <param name="reservedLength">The length (in characters) allocated in PLC for the string.</param>
/// <returns>A <see cref="T:byte[]" /> containing the string header and string value with a maximum length of <paramref name="reservedLength"/> + 4.</returns>
public static byte[] ToByteArray(string value, int reservedLength)
public static byte[] ToByteArray(string? value, int reservedLength)
{
if (value is null)
{

View File

@@ -12,13 +12,15 @@
/// <param name="reservedLength">The amount of bytes reserved for the <paramref name="value"/> in the PLC.</param>
public static byte[] ToByteArray(string value, int reservedLength)
{
var length = value?.Length;
if (length > reservedLength) length = reservedLength;
var bytes = new byte[reservedLength];
if (value == null) return bytes;
if (length == null || length == 0) return bytes;
var length = value.Length;
if (length == 0) return bytes;
System.Text.Encoding.ASCII.GetBytes(value, 0, length.Value, bytes, 0);
if (length > reservedLength) length = reservedLength;
System.Text.Encoding.ASCII.GetBytes(value, 0, length, bytes, 0);
return bytes;
}

View File

@@ -98,8 +98,8 @@ namespace S7.Net.Types
int bytePos = 0;
int bitPos = 0;
double numBytes = 0.0;
object structValue = Activator.CreateInstance(structType);
object structValue = Activator.CreateInstance(structType) ??
throw new ArgumentException($"Failed to create an instance of the type {structType}.", nameof(structType));
var infos = structValue.GetType()
#if NETSTANDARD1_3
@@ -254,6 +254,14 @@ namespace S7.Net.Types
foreach (var info in infos)
{
static TValue GetValueOrThrow<TValue>(FieldInfo fi, object structValue) where TValue : struct
{
var value = fi.GetValue(structValue) as TValue? ??
throw new ArgumentException($"Failed to convert value of field {fi.Name} of {structValue} to type {typeof(TValue)}");
return value;
}
bytes2 = null;
switch (info.FieldType.Name)
{
@@ -261,7 +269,7 @@ namespace S7.Net.Types
// get the value
bytePos = (int)Math.Floor(numBytes);
bitPos = (int)((numBytes - (double)bytePos) / 0.125);
if ((bool)info.GetValue(structValue))
if (GetValueOrThrow<bool>(info, structValue))
bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true
else
bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false
@@ -270,26 +278,26 @@ namespace S7.Net.Types
case "Byte":
numBytes = (int)Math.Ceiling(numBytes);
bytePos = (int)numBytes;
bytes[bytePos] = (byte)info.GetValue(structValue);
bytes[bytePos] = GetValueOrThrow<byte>(info, structValue);
numBytes++;
break;
case "Int16":
bytes2 = Int.ToByteArray((Int16)info.GetValue(structValue));
bytes2 = Int.ToByteArray(GetValueOrThrow<short>(info, structValue));
break;
case "UInt16":
bytes2 = Word.ToByteArray((UInt16)info.GetValue(structValue));
bytes2 = Word.ToByteArray(GetValueOrThrow<ushort>(info, structValue));
break;
case "Int32":
bytes2 = DInt.ToByteArray((Int32)info.GetValue(structValue));
bytes2 = DInt.ToByteArray(GetValueOrThrow<int>(info, structValue));
break;
case "UInt32":
bytes2 = DWord.ToByteArray((UInt32)info.GetValue(structValue));
bytes2 = DWord.ToByteArray(GetValueOrThrow<uint>(info, structValue));
break;
case "Single":
bytes2 = Real.ToByteArray((float)info.GetValue(structValue));
bytes2 = Real.ToByteArray(GetValueOrThrow<float>(info, structValue));
break;
case "Double":
bytes2 = LReal.ToByteArray((double)info.GetValue(structValue));
bytes2 = LReal.ToByteArray(GetValueOrThrow<double>(info, structValue));
break;
case "String":
S7StringAttribute? attribute = info.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
@@ -298,8 +306,8 @@ namespace S7.Net.Types
bytes2 = attribute.Type switch
{
S7StringType.S7String => S7String.ToByteArray((string)info.GetValue(structValue), attribute.ReservedLength),
S7StringType.S7WString => S7WString.ToByteArray((string)info.GetValue(structValue), attribute.ReservedLength),
S7StringType.S7String => S7String.ToByteArray((string?)info.GetValue(structValue), attribute.ReservedLength),
S7StringType.S7WString => S7WString.ToByteArray((string?)info.GetValue(structValue), attribute.ReservedLength),
_ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
};
break;