diff --git a/README.md b/README.md index 28df558..bec1d46 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,5 @@ PM> Install-Package S7netplus ## Running the tests -Unit tests use Snap7 server, so port 102 must be not in use. -If you have Siemens Step7 installed, the service s7oiehsx64 is stopped when running unit tests. -You have to restart the service manually if you need it. +Unit tests use Snap7 server. +On Windows, the DLL is included with the test project. On other platforms, Snap7 must be installed manually before running tests. diff --git a/S7.Net.UnitTest/Helpers/S7TestServer.cs b/S7.Net.UnitTest/Helpers/S7TestServer.cs index 8db9328..401587f 100644 --- a/S7.Net.UnitTest/Helpers/S7TestServer.cs +++ b/S7.Net.UnitTest/Helpers/S7TestServer.cs @@ -29,7 +29,7 @@ namespace S7.Net.UnitTest.Helpers Console.WriteLine(Server.EventText(ref Event)); } - public static void Start() + public static void Start(short port) { Server = new S7Server(); // Share some resources with our virtual PLC @@ -59,7 +59,14 @@ namespace S7.Net.UnitTest.Helpers // Start the server onto the default adapter. // To select an adapter we have to use Server->StartTo("192.168.x.y"). // Start() is the same of StartTo("0.0.0.0"); + + Server.SetParam(S7Consts.p_u16_LocalPort, ref port); + int Error = Server.Start(); + if (Error != 0) + { + throw new Exception($"Error starting Snap7 server: {Server.ErrorText(Error)}"); + } //if (Error == 0) //{ // // Now the server is running ... wait a key to terminate diff --git a/S7.Net.UnitTest/ProtocolTests.cs b/S7.Net.UnitTest/ProtocolTests.cs index 4d53f17..2051edb 100644 --- a/S7.Net.UnitTest/ProtocolTests.cs +++ b/S7.Net.UnitTest/ProtocolTests.cs @@ -7,13 +7,15 @@ using S7.Net; using System.IO; using System.Threading.Tasks; +using S7.Net.Protocol; +using System.Collections; namespace S7.Net.UnitTest { [TestClass] public class ProtocolUnitTest { - private TestContext TestContext { get; set; } + public TestContext TestContext { get; set; } [TestMethod] public async Task TPKT_Read() @@ -64,6 +66,32 @@ namespace S7.Net.UnitTest .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) .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(() => Plc.ValidateResponseCode(entry.StatusCode)); + } + else + { + Plc.ValidateResponseCode(entry.StatusCode); + } + } + } } } diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index 70f0e07..e836b62 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -1,15 +1,17 @@  - net452 + net452;netcoreapp3.1 true Properties\S7.Net.snk false Copyright © 2014 + x64 - + + @@ -24,13 +26,8 @@ - - - - - + PreserveNewest - diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs index 604e2bb..f90f250 100644 --- a/S7.Net.UnitTest/S7NetTestsAsync.cs +++ b/S7.Net.UnitTest/S7NetTestsAsync.cs @@ -1,12 +1,8 @@ #region Using using System; using System.Collections.Generic; -using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; -using S7.Net; using S7.Net.UnitTest.Helpers; -using S7.Net.UnitTest; -using System.ServiceProcess; using S7.Net.Types; using S7.UnitTest.Helpers; using System.Threading.Tasks; diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs index fd63448..162c5a2 100644 --- a/S7.Net.UnitTest/S7NetTestsSync.cs +++ b/S7.Net.UnitTest/S7NetTestsSync.cs @@ -1,12 +1,8 @@ #region Using using System; using System.Collections.Generic; -using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; -using S7.Net; using S7.Net.UnitTest.Helpers; -using S7.Net.UnitTest; -using System.ServiceProcess; using S7.Net.Types; using S7.UnitTest.Helpers; @@ -41,6 +37,8 @@ namespace S7.Net.UnitTest #region Constants const int DB2 = 2; const int DB4 = 4; + const short TestServerPort = 31122; + const string TestServerIp = "127.0.0.1"; #endregion #region Private fields @@ -53,16 +51,19 @@ namespace S7.Net.UnitTest /// public S7NetTests() { - plc = new Plc(CpuType.S7300, "127.0.0.1", 0, 2); - //ConsoleManager.Show(); - ShutDownServiceS7oiehsx64(); + plc = CreatePlc(); } + private static Plc CreatePlc() + { + return new Plc(CpuType.S7300, TestServerIp, TestServerPort, 0, 2); + } + [TestInitialize] public void Setup() { - S7TestServer.Start(); + S7TestServer.Start(TestServerPort); plc.Open(); } @@ -931,9 +932,9 @@ namespace S7.Net.UnitTest { plc.Close(); 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); } @@ -1026,18 +1027,6 @@ namespace S7.Net.UnitTest #endregion #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 private bool disposedValue = false; // To detect redundant calls diff --git a/S7.Net.UnitTest/Snap7/snap7.net.cs b/S7.Net.UnitTest/Snap7/snap7.net.cs index 98efa75..b6ae831 100644 --- a/S7.Net.UnitTest/Snap7/snap7.net.cs +++ b/S7.Net.UnitTest/Snap7/snap7.net.cs @@ -36,11 +36,8 @@ namespace Snap7 public class S7Consts { - #if __MonoCS__ // Assuming that we are using Unix release of Mono (otherwise modify it) - public const string Snap7LibName = "libsnap7.so"; - #else - public const string Snap7LibName = "snap7.dll"; - #endif + public const string Snap7LibName = "snap7"; + //------------------------------------------------------------------------------ // PARAMS LIST //------------------------------------------------------------------------------ diff --git a/S7.Net.UnitTest/StreamTests.cs b/S7.Net.UnitTest/StreamTests.cs index 375c02c..9cb0969 100644 --- a/S7.Net.UnitTest/StreamTests.cs +++ b/S7.Net.UnitTest/StreamTests.cs @@ -65,7 +65,7 @@ namespace S7.Net.UnitTest [TestClass] public class StreamTests { - private TestContext TestContext { get; set; } + public TestContext TestContext { get; set; } [TestMethod] public async Task TPKT_ReadRestrictedStreamAsync() diff --git a/S7.Net.UnitTest/TypeTests/StringExTests.cs b/S7.Net.UnitTest/TypeTests/S7StringTests.cs similarity index 90% rename from S7.Net.UnitTest/TypeTests/StringExTests.cs rename to S7.Net.UnitTest/TypeTests/S7StringTests.cs index aecf905..0bc8ef9 100644 --- a/S7.Net.UnitTest/TypeTests/StringExTests.cs +++ b/S7.Net.UnitTest/TypeTests/S7StringTests.cs @@ -7,7 +7,7 @@ using System.Linq; namespace S7.Net.UnitTest.TypeTests { [TestClass] - public class StringExTests + public class S7StringTests { [TestMethod] public void ReadEmptyStringWithZeroByteLength() @@ -36,7 +36,7 @@ namespace S7.Net.UnitTest.TypeTests [TestMethod] public void ReadMalformedStringSizeLargerThanCapacity() { - Assert.ThrowsException(() => StringEx.FromByteArray(new byte[] { 3, 5, 0, 1, 2 })); + Assert.ThrowsException(() => S7String.FromByteArray(new byte[] { 3, 5, 0, 1, 2 })); } [TestMethod] @@ -102,7 +102,7 @@ namespace S7.Net.UnitTest.TypeTests [TestMethod] public void WriteAbcWithStringLargetThanReservedLength() { - Assert.ThrowsException(() => StringEx.ToByteArray("Abc", 2)); + Assert.ThrowsException(() => S7String.ToByteArray("Abc", 2)); } [TestMethod] @@ -119,16 +119,16 @@ namespace S7.Net.UnitTest.TypeTests private static void AssertFromByteArrayEquals(string expected, params byte[] bytes) { - var convertedString = StringEx.FromByteArray(bytes); + var convertedString = S7String.FromByteArray(bytes); Assert.AreEqual(expected, convertedString); } 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); - var convertedBack = StringEx.FromByteArray(convertedData); + var convertedBack = S7String.FromByteArray(convertedData); Assert.AreEqual(value, convertedBack); } } diff --git a/S7.Net.UnitTest/runtimes/win-x64/native/snap7.dll b/S7.Net.UnitTest/runtimes/win-x64/native/snap7.dll new file mode 100644 index 0000000..e1baafc Binary files /dev/null and b/S7.Net.UnitTest/runtimes/win-x64/native/snap7.dll differ diff --git a/S7.Net.UnitTest/snap7.dll b/S7.Net.UnitTest/snap7.dll deleted file mode 100644 index d52c361..0000000 Binary files a/S7.Net.UnitTest/snap7.dll and /dev/null differ diff --git a/S7.Net/Enums.cs b/S7.Net/Enums.cs index fdb96ff..c523ad7 100644 --- a/S7.Net/Enums.cs +++ b/S7.Net/Enums.cs @@ -169,14 +169,14 @@ LReal, /// - /// String variable type (variable) + /// Char Array / C-String variable type (variable) /// String, /// - /// String variable type (variable) + /// S7 String variable type (variable) /// - StringEx, + S7String, /// /// Timer variable type diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index e4a7f7f..d95973d 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; +using S7.Net.Protocol; using S7.Net.Types; @@ -18,8 +19,8 @@ namespace S7.Net private TcpClient? tcpClient; private NetworkStream? _stream; - private int readTimeout = System.Threading.Timeout.Infinite; - private int writeTimeout = System.Threading.Timeout.Infinite; + private int readTimeout = 0; // default no timeout + private int writeTimeout = 0; // default no timeout /// /// IP address of the PLC @@ -234,13 +235,34 @@ namespace S7.Net if (s7Data.Length < 15) throw NotEnoughBytes(); - if (s7Data[14] != 0xff) - throw new PlcException(ErrorCode.ReadData, - $"Invalid response from PLC: '{BitConverter.ToString(s7Data)}'."); + ValidateResponseCode((ReadWriteErrorCode)s7Data[14]); 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 private bool disposedValue = false; // To detect redundant calls diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs index ae1ff29..f2c757d 100644 --- a/S7.Net/PLCHelpers.cs +++ b/S7.Net/PLCHelpers.cs @@ -122,8 +122,8 @@ namespace S7.Net case VarType.String: return Types.String.FromByteArray(bytes); - case VarType.StringEx: - return StringEx.FromByteArray(bytes); + case VarType.S7String: + return S7String.FromByteArray(bytes); case VarType.Timer: if (varCount == 1) @@ -186,7 +186,7 @@ namespace S7.Net return (varCount < 1) ? 1 : varCount; case VarType.String: return varCount; - case VarType.StringEx: + case VarType.S7String: return varCount + 2; case VarType.Word: case VarType.Timer: diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 1de0ac8..fba1f9f 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -259,8 +259,7 @@ namespace S7.Net await stream.WriteAsync(dataToSend, 0, dataToSend.Length); var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken); //TODO use Async - if (s7data == null || s7data[14] != 0xff) - throw new PlcException(ErrorCode.WrongNumberReceivedBytes); + ValidateResponseCode((ReadWriteErrorCode)s7data[14]); ParseDataIntoDataItems(s7data, dataItems); } @@ -483,10 +482,7 @@ namespace S7.Net await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken); var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken); - if (s7data == null || s7data[14] != 0xff) - { - throw new PlcException(ErrorCode.WrongNumberReceivedBytes); - } + ValidateResponseCode((ReadWriteErrorCode)s7data[14]); } catch (OperationCanceledException) { @@ -509,10 +505,7 @@ namespace S7.Net await stream.WriteAsync(dataToSend, 0, dataToSend.Length); var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken); - if (s7data == null || s7data[14] != 0xff) - { - throw new PlcException(ErrorCode.WrongNumberReceivedBytes); - } + ValidateResponseCode((ReadWriteErrorCode)s7data[14]); } catch (OperationCanceledException) { diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index 8cf5f89..ddd165c 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -398,10 +398,7 @@ namespace S7.Net stream.Write(dataToSend, 0, dataToSend.Length); var s7data = COTP.TSDU.Read(stream); - if (s7data == null || s7data[14] != 0xff) - { - throw new PlcException(ErrorCode.WrongNumberReceivedBytes); - } + ValidateResponseCode((ReadWriteErrorCode)s7data[14]); } catch (Exception exc) { @@ -483,10 +480,7 @@ namespace S7.Net stream.Write(dataToSend, 0, dataToSend.Length); var s7data = COTP.TSDU.Read(stream); - if (s7data == null || s7data[14] != 0xff) - { - throw new PlcException(ErrorCode.WrongNumberReceivedBytes); - } + ValidateResponseCode((ReadWriteErrorCode)s7data[14]); } catch (Exception exc) { @@ -524,8 +518,8 @@ namespace S7.Net stream.Write(dataToSend, 0, dataToSend.Length); 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); } diff --git a/S7.Net/Protocol/ReadWriteErrorCode.cs b/S7.Net/Protocol/ReadWriteErrorCode.cs new file mode 100644 index 0000000..9d6b943 --- /dev/null +++ b/S7.Net/Protocol/ReadWriteErrorCode.cs @@ -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 + } +} diff --git a/S7.Net/Protocol/S7WriteMultiple.cs b/S7.Net/Protocol/S7WriteMultiple.cs index 853e1e5..9f0257f 100644 --- a/S7.Net/Protocol/S7WriteMultiple.cs +++ b/S7.Net/Protocol/S7WriteMultiple.cs @@ -94,11 +94,16 @@ namespace S7.Net.Protocol for (int i = 0; i < dataItems.Length; i++) { - var result = itemResults[i]; - if (result == 0xff) continue; + try + { + Plc.ValidateResponseCode((ReadWriteErrorCode)itemResults[i]); + } + catch(Exception e) + { + if (errors == null) errors = new List(); + errors.Add(new Exception($"Write of dataItem {dataItems[i]} failed: {e.Message}.")); + } - if (errors == null) errors = new List(); - errors.Add(new Exception($"Write of dataItem {dataItems[i]} failed with error code {result}.")); } if (errors != null) diff --git a/S7.Net/Protocol/Serialization.cs b/S7.Net/Protocol/Serialization.cs index b9ac9a5..ceecaa6 100644 --- a/S7.Net/Protocol/Serialization.cs +++ b/S7.Net/Protocol/Serialization.cs @@ -18,8 +18,8 @@ namespace S7.Net.Protocol throw new Exception($"DataItem.Value is null, cannot serialize. StartAddr={dataItem.StartByteAdr} VarType={dataItem.VarType}"); } if (dataItem.Value is string s) - return dataItem.VarType == VarType.StringEx - ? StringEx.ToByteArray(s, dataItem.Count) + return dataItem.VarType == VarType.S7String + ? S7String.ToByteArray(s, dataItem.Count) : Types.String.ToByteArray(s, dataItem.Count); return SerializeValue(dataItem.Value); diff --git a/S7.Net/Types/S7String.cs b/S7.Net/Types/S7String.cs new file mode 100644 index 0000000..6210e53 --- /dev/null +++ b/S7.Net/Types/S7String.cs @@ -0,0 +1,69 @@ +using System; +using System.Text; + +namespace S7.Net.Types +{ + /// + /// 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 + /// + public static class S7String + { + /// + /// Converts S7 bytes to a string + /// + /// + /// + 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); + } + + } + + /// + /// Converts a to S7 string with 2-byte header. + /// + /// The string to convert to byte array. + /// The length (in bytes) allocated in PLC for string excluding header. + /// A containing the string header and string value with a maximum length of + 2. + 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; + } + } +} diff --git a/S7.Net/Types/String.cs b/S7.Net/Types/String.cs index aaf3428..3917635 100644 --- a/S7.Net/Types/String.cs +++ b/S7.Net/Types/String.cs @@ -1,7 +1,7 @@ namespace S7.Net.Types { /// - /// 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 /// public class String { diff --git a/S7.Net/Types/StringEx.cs b/S7.Net/Types/StringEx.cs index 5f152b2..6c0381a 100644 --- a/S7.Net/Types/StringEx.cs +++ b/S7.Net/Types/StringEx.cs @@ -1,70 +1,15 @@ using System; -using System.Text; namespace S7.Net.Types { - /// - /// Contains the methods to convert from S7 strings to C# strings - /// there are two kinds how strings a send. This one is with a pre of two bytes - /// they contain the length of the string - /// + /// + [Obsolete("Please use S7String class")] public static class StringEx { - /// - /// Converts S7 bytes to a string - /// - /// - /// - public static string FromByteArray(byte[] bytes) - { - if (bytes.Length < 2) - { - throw new PlcException(ErrorCode.ReadData, "Malformed S7 String / too short"); - } + /// + public static string FromByteArray(byte[] bytes) => S7String.FromByteArray(bytes); - 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.StringEx} from data. Following fields were read: size: '{size}', actual length: '{length}', total number of bytes (including header): '{bytes.Length}'.", - e); - } - - } - - /// - /// Converts a to S7 string with 2-byte header. - /// - /// The string to convert to byte array. - /// The length (in bytes) allocated in PLC for string excluding header. - /// A containing the string header and string value with a maximum length of + 2. - 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; - } + /// + public static byte[] ToByteArray(string value, int reservedLength) => S7String.ToByteArray(value, reservedLength); } }