Merge branch 'develop' into patch-1

This commit is contained in:
Michael Croes
2020-09-16 22:07:15 +02:00
committed by GitHub
22 changed files with 206 additions and 150 deletions

View File

@@ -34,6 +34,5 @@ PM> Install-Package S7netplus
## Running the tests ## Running the tests
Unit tests use Snap7 server, so port 102 must be not in use. Unit tests use Snap7 server.
If you have Siemens Step7 installed, the service s7oiehsx64 is stopped when running unit tests. On Windows, the DLL is included with the test project. On other platforms, Snap7 must be installed manually before running tests.
You have to restart the service manually if you need it.

View File

@@ -29,7 +29,7 @@ namespace S7.Net.UnitTest.Helpers
Console.WriteLine(Server.EventText(ref Event)); Console.WriteLine(Server.EventText(ref Event));
} }
public static void Start() public static void Start(short port)
{ {
Server = new S7Server(); Server = new S7Server();
// Share some resources with our virtual PLC // Share some resources with our virtual PLC
@@ -59,7 +59,14 @@ namespace S7.Net.UnitTest.Helpers
// Start the server onto the default adapter. // Start the server onto the default adapter.
// To select an adapter we have to use Server->StartTo("192.168.x.y"). // To select an adapter we have to use Server->StartTo("192.168.x.y").
// Start() is the same of StartTo("0.0.0.0"); // Start() is the same of StartTo("0.0.0.0");
Server.SetParam(S7Consts.p_u16_LocalPort, ref port);
int Error = Server.Start(); int Error = Server.Start();
if (Error != 0)
{
throw new Exception($"Error starting Snap7 server: {Server.ErrorText(Error)}");
}
//if (Error == 0) //if (Error == 0)
//{ //{
// // Now the server is running ... wait a key to terminate // // Now the server is running ... wait a key to terminate

View File

@@ -7,13 +7,15 @@ using S7.Net;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using S7.Net.Protocol;
using System.Collections;
namespace S7.Net.UnitTest namespace S7.Net.UnitTest
{ {
[TestClass] [TestClass]
public class ProtocolUnitTest public class ProtocolUnitTest
{ {
private TestContext TestContext { get; set; } public TestContext TestContext { get; set; }
[TestMethod] [TestMethod]
public async Task TPKT_Read() public async Task TPKT_Read()
@@ -64,6 +66,32 @@ namespace S7.Net.UnitTest
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray(); .ToArray();
} }
[TestMethod]
public void TestResponseCode()
{
var expected = StringToByteArray("320700000400000800080001120411440100ff09000400000000");
var m = new MemoryStream(StringToByteArray("0300000702f0000300000702f0000300002102f080320700000400000800080001120411440100ff09000400000000"));
var t = COTP.TSDU.Read(m);
Assert.IsTrue(expected.SequenceEqual(t));
// Test all possible byte values. Everything except 0xff should throw an exception.
var testData = Enumerable.Range(0, 256).Select(i => new { StatusCode = (ReadWriteErrorCode)i, ThrowsException = i != (byte)ReadWriteErrorCode.Success });
foreach (var entry in testData)
{
if (entry.ThrowsException)
{
Assert.ThrowsException<Exception>(() => Plc.ValidateResponseCode(entry.StatusCode));
}
else
{
Plc.ValidateResponseCode(entry.StatusCode);
}
}
}
} }
} }

View File

@@ -1,15 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452</TargetFrameworks> <TargetFrameworks>net452;netcoreapp3.1</TargetFrameworks>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>Properties\S7.Net.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>Properties\S7.Net.snk</AssemblyOriginatorKeyFile>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Copyright>Copyright © 2014</Copyright> <Copyright>Copyright © 2014</Copyright>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="GitHubActionsTestLogger" Version="1.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" /> <PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" /> <PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
@@ -24,13 +26,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System.ServiceProcess" /> <None Update="runtimes\win-x64\native\snap7.dll" Link="snap7.dll">
</ItemGroup>
<ItemGroup>
<None Update="snap7.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,12 +1,8 @@
#region Using #region Using
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net;
using S7.Net.UnitTest.Helpers; using S7.Net.UnitTest.Helpers;
using S7.Net.UnitTest;
using System.ServiceProcess;
using S7.Net.Types; using S7.Net.Types;
using S7.UnitTest.Helpers; using S7.UnitTest.Helpers;
using System.Threading.Tasks; using System.Threading.Tasks;

View File

@@ -1,12 +1,8 @@
#region Using #region Using
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net;
using S7.Net.UnitTest.Helpers; using S7.Net.UnitTest.Helpers;
using S7.Net.UnitTest;
using System.ServiceProcess;
using S7.Net.Types; using S7.Net.Types;
using S7.UnitTest.Helpers; using S7.UnitTest.Helpers;
@@ -41,6 +37,8 @@ namespace S7.Net.UnitTest
#region Constants #region Constants
const int DB2 = 2; const int DB2 = 2;
const int DB4 = 4; const int DB4 = 4;
const short TestServerPort = 31122;
const string TestServerIp = "127.0.0.1";
#endregion #endregion
#region Private fields #region Private fields
@@ -53,16 +51,19 @@ namespace S7.Net.UnitTest
/// </summary> /// </summary>
public S7NetTests() public S7NetTests()
{ {
plc = new Plc(CpuType.S7300, "127.0.0.1", 0, 2); plc = CreatePlc();
//ConsoleManager.Show();
ShutDownServiceS7oiehsx64();
} }
private static Plc CreatePlc()
{
return new Plc(CpuType.S7300, TestServerIp, TestServerPort, 0, 2);
}
[TestInitialize] [TestInitialize]
public void Setup() public void Setup()
{ {
S7TestServer.Start(); S7TestServer.Start(TestServerPort);
plc.Open(); plc.Open();
} }
@@ -931,9 +932,9 @@ namespace S7.Net.UnitTest
{ {
plc.Close(); plc.Close();
S7TestServer.Stop(); S7TestServer.Stop();
S7TestServer.Start(); S7TestServer.Start(TestServerPort);
var reachablePlc = new Plc(CpuType.S7300, "127.0.0.1", 0, 2); var reachablePlc = CreatePlc();
Assert.IsTrue(reachablePlc.IsAvailable); Assert.IsTrue(reachablePlc.IsAvailable);
} }
@@ -1026,18 +1027,6 @@ namespace S7.Net.UnitTest
#endregion #endregion
#region Private methods #region Private methods
private static void ShutDownServiceS7oiehsx64()
{
ServiceController[] services = ServiceController.GetServices();
var service = services.FirstOrDefault(s => s.ServiceName == "s7oiehsx64");
if (service != null)
{
if (service.Status == ServiceControllerStatus.Running)
{
service.Stop();
}
}
}
#region IDisposable Support #region IDisposable Support
private bool disposedValue = false; // To detect redundant calls private bool disposedValue = false; // To detect redundant calls

View File

@@ -36,11 +36,8 @@ namespace Snap7
public class S7Consts public class S7Consts
{ {
#if __MonoCS__ // Assuming that we are using Unix release of Mono (otherwise modify it) public const string Snap7LibName = "snap7";
public const string Snap7LibName = "libsnap7.so";
#else
public const string Snap7LibName = "snap7.dll";
#endif
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// PARAMS LIST // PARAMS LIST
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@@ -65,7 +65,7 @@ namespace S7.Net.UnitTest
[TestClass] [TestClass]
public class StreamTests public class StreamTests
{ {
private TestContext TestContext { get; set; } public TestContext TestContext { get; set; }
[TestMethod] [TestMethod]
public async Task TPKT_ReadRestrictedStreamAsync() public async Task TPKT_ReadRestrictedStreamAsync()

View File

@@ -7,7 +7,7 @@ using System.Linq;
namespace S7.Net.UnitTest.TypeTests namespace S7.Net.UnitTest.TypeTests
{ {
[TestClass] [TestClass]
public class StringExTests public class S7StringTests
{ {
[TestMethod] [TestMethod]
public void ReadEmptyStringWithZeroByteLength() public void ReadEmptyStringWithZeroByteLength()
@@ -36,7 +36,7 @@ namespace S7.Net.UnitTest.TypeTests
[TestMethod] [TestMethod]
public void ReadMalformedStringSizeLargerThanCapacity() public void ReadMalformedStringSizeLargerThanCapacity()
{ {
Assert.ThrowsException<PlcException>(() => StringEx.FromByteArray(new byte[] { 3, 5, 0, 1, 2 })); Assert.ThrowsException<PlcException>(() => S7String.FromByteArray(new byte[] { 3, 5, 0, 1, 2 }));
} }
[TestMethod] [TestMethod]
@@ -102,7 +102,7 @@ namespace S7.Net.UnitTest.TypeTests
[TestMethod] [TestMethod]
public void WriteAbcWithStringLargetThanReservedLength() public void WriteAbcWithStringLargetThanReservedLength()
{ {
Assert.ThrowsException<ArgumentException>(() => StringEx.ToByteArray("Abc", 2)); Assert.ThrowsException<ArgumentException>(() => S7String.ToByteArray("Abc", 2));
} }
[TestMethod] [TestMethod]
@@ -119,16 +119,16 @@ namespace S7.Net.UnitTest.TypeTests
private static void AssertFromByteArrayEquals(string expected, params byte[] bytes) private static void AssertFromByteArrayEquals(string expected, params byte[] bytes)
{ {
var convertedString = StringEx.FromByteArray(bytes); var convertedString = S7String.FromByteArray(bytes);
Assert.AreEqual(expected, convertedString); Assert.AreEqual(expected, convertedString);
} }
private static void AssertToByteArrayAndBackEquals(string value, int reservedLength, params byte[] expected) private static void AssertToByteArrayAndBackEquals(string value, int reservedLength, params byte[] expected)
{ {
var convertedData = StringEx.ToByteArray(value, reservedLength); var convertedData = S7String.ToByteArray(value, reservedLength);
CollectionAssert.AreEqual(expected, convertedData); CollectionAssert.AreEqual(expected, convertedData);
var convertedBack = StringEx.FromByteArray(convertedData); var convertedBack = S7String.FromByteArray(convertedData);
Assert.AreEqual(value, convertedBack); Assert.AreEqual(value, convertedBack);
} }
} }

Binary file not shown.

Binary file not shown.

View File

@@ -169,14 +169,14 @@
LReal, LReal,
/// <summary> /// <summary>
/// String variable type (variable) /// Char Array / C-String variable type (variable)
/// </summary> /// </summary>
String, String,
/// <summary> /// <summary>
/// String variable type (variable) /// S7 String variable type (variable)
/// </summary> /// </summary>
StringEx, S7String,
/// <summary> /// <summary>
/// Timer variable type /// Timer variable type

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using S7.Net.Protocol;
using S7.Net.Types; using S7.Net.Types;
@@ -18,8 +19,8 @@ namespace S7.Net
private TcpClient? tcpClient; private TcpClient? tcpClient;
private NetworkStream? _stream; private NetworkStream? _stream;
private int readTimeout = System.Threading.Timeout.Infinite; private int readTimeout = 0; // default no timeout
private int writeTimeout = System.Threading.Timeout.Infinite; private int writeTimeout = 0; // default no timeout
/// <summary> /// <summary>
/// IP address of the PLC /// IP address of the PLC
@@ -234,13 +235,34 @@ namespace S7.Net
if (s7Data.Length < 15) throw NotEnoughBytes(); if (s7Data.Length < 15) throw NotEnoughBytes();
if (s7Data[14] != 0xff) ValidateResponseCode((ReadWriteErrorCode)s7Data[14]);
throw new PlcException(ErrorCode.ReadData,
$"Invalid response from PLC: '{BitConverter.ToString(s7Data)}'.");
if (s7Data.Length < expectedLength) throw NotEnoughBytes(); if (s7Data.Length < expectedLength) throw NotEnoughBytes();
} }
internal static void ValidateResponseCode(ReadWriteErrorCode statusCode)
{
switch (statusCode)
{
case ReadWriteErrorCode.ObjectDoesNotExist:
throw new Exception("Received error from PLC: Object does not exist.");
case ReadWriteErrorCode.DataTypeInconsistent:
throw new Exception("Received error from PLC: Data type inconsistent.");
case ReadWriteErrorCode.DataTypeNotSupported:
throw new Exception("Received error from PLC: Data type not supported.");
case ReadWriteErrorCode.AccessingObjectNotAllowed:
throw new Exception("Received error from PLC: Accessing object not allowed.");
case ReadWriteErrorCode.AddressOutOfRange:
throw new Exception("Received error from PLC: Address out of range.");
case ReadWriteErrorCode.HardwareFault:
throw new Exception("Received error from PLC: Hardware fault.");
case ReadWriteErrorCode.Success:
break;
default:
throw new Exception( $"Invalid response from PLC: statusCode={(byte)statusCode}.");
}
}
#region IDisposable Support #region IDisposable Support
private bool disposedValue = false; // To detect redundant calls private bool disposedValue = false; // To detect redundant calls

View File

@@ -122,8 +122,8 @@ namespace S7.Net
case VarType.String: case VarType.String:
return Types.String.FromByteArray(bytes); return Types.String.FromByteArray(bytes);
case VarType.StringEx: case VarType.S7String:
return StringEx.FromByteArray(bytes); return S7String.FromByteArray(bytes);
case VarType.Timer: case VarType.Timer:
if (varCount == 1) if (varCount == 1)
@@ -186,7 +186,7 @@ namespace S7.Net
return (varCount < 1) ? 1 : varCount; return (varCount < 1) ? 1 : varCount;
case VarType.String: case VarType.String:
return varCount; return varCount;
case VarType.StringEx: case VarType.S7String:
return varCount + 2; return varCount + 2;
case VarType.Word: case VarType.Word:
case VarType.Timer: case VarType.Timer:

View File

@@ -259,8 +259,7 @@ namespace S7.Net
await stream.WriteAsync(dataToSend, 0, dataToSend.Length); await stream.WriteAsync(dataToSend, 0, dataToSend.Length);
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken); //TODO use Async var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken); //TODO use Async
if (s7data == null || s7data[14] != 0xff) ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
ParseDataIntoDataItems(s7data, dataItems); ParseDataIntoDataItems(s7data, dataItems);
} }
@@ -483,10 +482,7 @@ namespace S7.Net
await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken); await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken);
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken); var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken);
if (s7data == null || s7data[14] != 0xff) ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
{
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -509,10 +505,7 @@ namespace S7.Net
await stream.WriteAsync(dataToSend, 0, dataToSend.Length); await stream.WriteAsync(dataToSend, 0, dataToSend.Length);
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken); var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken);
if (s7data == null || s7data[14] != 0xff) ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
{
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {

View File

@@ -398,10 +398,7 @@ namespace S7.Net
stream.Write(dataToSend, 0, dataToSend.Length); stream.Write(dataToSend, 0, dataToSend.Length);
var s7data = COTP.TSDU.Read(stream); var s7data = COTP.TSDU.Read(stream);
if (s7data == null || s7data[14] != 0xff) ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
{
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
} }
catch (Exception exc) catch (Exception exc)
{ {
@@ -483,10 +480,7 @@ namespace S7.Net
stream.Write(dataToSend, 0, dataToSend.Length); stream.Write(dataToSend, 0, dataToSend.Length);
var s7data = COTP.TSDU.Read(stream); var s7data = COTP.TSDU.Read(stream);
if (s7data == null || s7data[14] != 0xff) ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
{
throw new PlcException(ErrorCode.WrongNumberReceivedBytes);
}
} }
catch (Exception exc) catch (Exception exc)
{ {
@@ -524,8 +518,8 @@ namespace S7.Net
stream.Write(dataToSend, 0, dataToSend.Length); stream.Write(dataToSend, 0, dataToSend.Length);
var s7data = COTP.TSDU.Read(stream); //TODO use Async var s7data = COTP.TSDU.Read(stream); //TODO use Async
if (s7data == null || s7data[14] != 0xff)
throw new PlcException(ErrorCode.WrongNumberReceivedBytes); ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
ParseDataIntoDataItems(s7data, dataItems); ParseDataIntoDataItems(s7data, dataItems);
} }

View File

@@ -0,0 +1,15 @@

namespace S7.Net.Protocol
{
internal enum ReadWriteErrorCode : byte
{
Reserved = 0x00,
HardwareFault = 0x01,
AccessingObjectNotAllowed = 0x03,
AddressOutOfRange = 0x05,
DataTypeNotSupported = 0x06,
DataTypeInconsistent = 0x07,
ObjectDoesNotExist = 0x0a,
Success = 0xff
}
}

View File

@@ -94,11 +94,16 @@ namespace S7.Net.Protocol
for (int i = 0; i < dataItems.Length; i++) for (int i = 0; i < dataItems.Length; i++)
{ {
var result = itemResults[i]; try
if (result == 0xff) continue; {
Plc.ValidateResponseCode((ReadWriteErrorCode)itemResults[i]);
}
catch(Exception e)
{
if (errors == null) errors = new List<Exception>(); if (errors == null) errors = new List<Exception>();
errors.Add(new Exception($"Write of dataItem {dataItems[i]} failed with error code {result}.")); errors.Add(new Exception($"Write of dataItem {dataItems[i]} failed: {e.Message}."));
}
} }
if (errors != null) if (errors != null)

View File

@@ -18,8 +18,8 @@ namespace S7.Net.Protocol
throw new Exception($"DataItem.Value is null, cannot serialize. StartAddr={dataItem.StartByteAdr} VarType={dataItem.VarType}"); throw new Exception($"DataItem.Value is null, cannot serialize. StartAddr={dataItem.StartByteAdr} VarType={dataItem.VarType}");
} }
if (dataItem.Value is string s) if (dataItem.Value is string s)
return dataItem.VarType == VarType.StringEx return dataItem.VarType == VarType.S7String
? StringEx.ToByteArray(s, dataItem.Count) ? S7String.ToByteArray(s, dataItem.Count)
: Types.String.ToByteArray(s, dataItem.Count); : Types.String.ToByteArray(s, dataItem.Count);
return SerializeValue(dataItem.Value); return SerializeValue(dataItem.Value);

69
S7.Net/Types/S7String.cs Normal file
View File

@@ -0,0 +1,69 @@
using System;
using System.Text;
namespace S7.Net.Types
{
/// <summary>
/// Contains the methods to convert from S7 strings to C# strings
/// An S7 String has a preceeding 2 byte header containing its capacity and length
/// </summary>
public static class S7String
{
/// <summary>
/// Converts S7 bytes to a string
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string FromByteArray(byte[] bytes)
{
if (bytes.Length < 2)
{
throw new PlcException(ErrorCode.ReadData, "Malformed S7 String / too short");
}
int size = bytes[0];
int length = bytes[1];
if (length > size)
{
throw new PlcException(ErrorCode.ReadData, "Malformed S7 String / length larger than capacity");
}
try
{
return Encoding.ASCII.GetString(bytes, 2, length);
}
catch (Exception e)
{
throw new PlcException(ErrorCode.ReadData,
$"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>
/// Converts a <see cref="T:string"/> to S7 string with 2-byte header.
/// </summary>
/// <param name="value">The string to convert to byte array.</param>
/// <param name="reservedLength">The length (in bytes) allocated in PLC for string excluding header.</param>
/// <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)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
if (reservedLength > byte.MaxValue) throw new ArgumentException($"The maximum string length supported is {byte.MaxValue}.");
var bytes = Encoding.ASCII.GetBytes(value);
if (bytes.Length > reservedLength) throw new ArgumentException($"The provided string length ({bytes.Length} is larger than the specified reserved length ({reservedLength}).");
var buffer = new byte[2 + reservedLength];
Array.Copy(bytes, 0, buffer, 2, bytes.Length);
buffer[0] = (byte)reservedLength;
buffer[1] = (byte)bytes.Length;
return buffer;
}
}
}

View File

@@ -1,7 +1,7 @@
namespace S7.Net.Types namespace S7.Net.Types
{ {
/// <summary> /// <summary>
/// Contains the methods to convert from S7 strings to C# strings /// Contains the methods to convert from S7 Array of Chars (like a const char[N] C-String) to C# strings
/// </summary> /// </summary>
public class String public class String
{ {

View File

@@ -1,70 +1,15 @@
using System; using System;
using System.Text;
namespace S7.Net.Types namespace S7.Net.Types
{ {
/// <summary> /// <inheritdoc cref="S7String"/>
/// Contains the methods to convert from S7 strings to C# strings [Obsolete("Please use S7String class")]
/// there are two kinds how strings a send. This one is with a pre of two bytes
/// they contain the length of the string
/// </summary>
public static class StringEx public static class StringEx
{ {
/// <summary> /// <inheritdoc cref="S7String.FromByteArray(byte[])"/>
/// Converts S7 bytes to a string public static string FromByteArray(byte[] bytes) => S7String.FromByteArray(bytes);
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string FromByteArray(byte[] bytes)
{
if (bytes.Length < 2)
{
throw new PlcException(ErrorCode.ReadData, "Malformed S7 String / too short");
}
int size = bytes[0]; /// <inheritdoc cref="S7String.ToByteArray(string, int)"/>
int length = bytes[1]; public static byte[] ToByteArray(string value, int reservedLength) => S7String.ToByteArray(value, reservedLength);
if (length > size)
{
throw new PlcException(ErrorCode.ReadData, "Malformed S7 String / length larger than capacity");
}
try
{
return Encoding.ASCII.GetString(bytes, 2, length);
}
catch (Exception e)
{
throw new PlcException(ErrorCode.ReadData,
$"Failed to parse {VarType.StringEx} from data. Following fields were read: size: '{size}', actual length: '{length}', total number of bytes (including header): '{bytes.Length}'.",
e);
}
}
/// <summary>
/// Converts a <see cref="T:string"/> to S7 string with 2-byte header.
/// </summary>
/// <param name="value">The string to convert to byte array.</param>
/// <param name="reservedLength">The length (in bytes) allocated in PLC for string excluding header.</param>
/// <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)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
if (reservedLength > byte.MaxValue) throw new ArgumentException($"The maximum string length supported is {byte.MaxValue}.");
var bytes = Encoding.ASCII.GetBytes(value);
if (bytes.Length > reservedLength) throw new ArgumentException($"The provided string length ({bytes.Length} is larger than the specified reserved length ({reservedLength}).");
var buffer = new byte[2 + reservedLength];
Array.Copy(bytes, 0, buffer, 2, bytes.Length);
buffer[0] = (byte)reservedLength;
buffer[1] = (byte)bytes.Length;
return buffer;
}
} }
} }