Repository: fbarresi/Sharp7 Branch: master Commit: a3f11b2e0526 Files: 23 Total size: 159.0 KB Directory structure: gitextract_ioo2kq9b/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── Sharp7/ │ ├── MsgSocket.cs │ ├── S7.cs │ ├── S7Area.cs │ ├── S7Client.cs │ ├── S7Consts.cs │ ├── S7MultiVar.cs │ ├── S7Timer.cs │ ├── S7WordLength.cs │ └── Sharp7.csproj ├── Sharp7.Tests/ │ ├── ClientTest.cs │ ├── ClientWithoutServer.cs │ ├── PropertiesTests.cs │ ├── S7Server.cs │ ├── ServerClientTestBase.cs │ ├── ServerTestBase.cs │ ├── Sharp7.Tests.csproj │ └── TestUtilities.cs └── Sharp7.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: fbarresi ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # .NET Core project.lock.json project.fragment.lock.json artifacts/ **/Properties/launchSettings.json *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Typescript v1 declaration files typings/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs run_tests_and_coverage.bat coverage/ ================================================ FILE: CHANGELOG.md ================================================ # Change Log All notable changed to this project will be documented in this file. ## [1.1.84] - 11.05.2023 ### Added ### Changed ### Fixed - Fixed ReadSZL [#36](https://github.com/fbarresi/Sharp7/issues/36) ## [1.1.82] - 18.05.2022 ### Added ### Changed ### Fixed - Fixed string length check into SetStringAt [#28](https://github.com/fbarresi/Sharp7/issues/28) ## [1.1.81] - 18.05.2022 ### Added ### Changed ### Fixed - Removed obsolete SetBitAt [#27](https://github.com/fbarresi/Sharp7/issues/27) ## [1.1.80] - 18.05.2022 ### Added ### Changed ### Fixed - Fixed casting in GetIntAt [#29](https://github.com/fbarresi/Sharp7/issues/29) ## [1.1.79] - 09.02.2021 ### Added - Constant for no errors `S7Consts.ResultOK` ### Changed ### Fixed ## [1.1.78] - 04.02.2021 ### Added - Property `PLCIpAddress` - Overload of `ToString` for S7Client - Property `Name` as constructor param in S7Client ### Changed ### Fixed ## [1.1.75] - 03.09.2020 ### Added ### Changed - CHANGELOG.md - README.md ### Fixed - [#18](https://github.com/fbarresi/Sharp7/issues/18) - New overloaded extension method signatures for SetBitAt - Removed not necessary casting to short for GetIntAt - Added overloads for S7 `Time_Of_Day` functions - Added more tests - made old methods obsolete ## [1.1.71] - 14.08.2020 ### Added - CHANGELOG.md ### Changed - README.md - Referenced changelog into nuget release notes ### Fixed ## [1.1.69] - 11.06.2020 ### Added ### Changed - README.md ### Fixed ## [1.1.68] - 11.06.2020 ### Added - Added enums and methods overloads for S7Wordlength and S7Area (#11) ### Changed - README.md - Changed properties to expression body solution wide - Changed S7 calls to extension methods - Usage of overloaded methods with enums instead of constants ### Fixed - Check connection on socket close - Null propagation on disconnect method - Soved usage ob obsolete constants ## [1.1.63] - 06.05.2020 ### Added ### Changed ### Fixed - style and security enhancement ## [1.1.62] - 06.05.2020 ### Added ### Changed ### Fixed - Unused exception handling ## [1.1.61] - 06.05.2020 ### Added ### Changed ### Fixed - Usage of Timeouts from [philfontaine/Sharp7@fb01cc0](https://github.com/philfontaine/Sharp7@fb01cc0) ## [1.1.60] - 06.05.2020 ### Added ### Changed ### Fixed - connection check into overload of SendPacket (#8) ## [1.1.59] - 06.05.2020 ### Added - Added unit tests ### Changed - Created extension methods from S7 ### Fixed - Fixed naming rules ## [1.0.50] - 17.11.2019 ### Added ### Changed - README.md ### Fixed ## [1.0.49] - 17.11.2019 ### Added ### Changed - README.md ### Fixed ## [1.0.48] - 17.11.2019 ### Added ### Changed - README.md ### Fixed ## [1.0.25] - 02.04.2019 ### Added - Added nuget package description ### Changed ### Fixed ## [1.0.22] - 17.01.2019 ### Added - Added debug symbols to Nuget ### Changed ### Fixed ## [1.0.18] - 21.11.2018 ### Added ### Changed - Migrate solution to project SDK (#2) ### Fixed ## [1.0.0] - 21.01.2018 ### Added - imported initial project from source forge ### Changed ### Fixed ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Federico Barresi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # ![Sharp7](https://raw.githubusercontent.com/fbarresi/sharp7/master/doc/images/logo.jpg) [![Build status](https://ci.appveyor.com/api/projects/status/2i77qfjjq8aep50b?svg=true)](https://ci.appveyor.com/project/fbarresi/sharp7) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/4ff75e759a66416a84052769a71b70c6)](https://www.codacy.com/manual/fbarresi/Sharp7?utm_source=github.com&utm_medium=referral&utm_content=fbarresi/Sharp7&utm_campaign=Badge_Grade) [![codecov](https://codecov.io/gh/fbarresi/Sharp7/branch/master/graph/badge.svg)](https://codecov.io/gh/fbarresi/Sharp7) ![Licence](https://img.shields.io/github/license/fbarresi/sharp7.svg) [![Nuget Version](https://img.shields.io/nuget/v/Sharp7.svg)](https://www.nuget.org/packages/Sharp7/) Nuget package for Sharp7 - The multi-platform Ethernet S7 PLC communication suite Sharp7 is a C# port of [Snap7](http://snap7.sourceforge.net) library For usage and documentation you can visit the [official page](http://snap7.sourceforge.net) or read the [Wiki](https://github.com/fbarresi/Sharp7/wiki). # Changelog See the changelog [here](https://github.com/fbarresi/Sharp7/blob/master/CHANGELOG.md) # How to install ## Package Manager or dotnet CLI ``` PM> Install-Package Sharp7 ``` or ``` > dotnet add package Sharp7 ``` # Do you need more power? Try [Sharp7Reactive](https://github.com/evopro-ag/Sharp7Reactive) # Get Started ## Supported Targets - S7 300/400/WinAC CPU (fully supported) - S7 1200/1500 CPU - CP (Communication processor - 343/443/IE) ## S7 1200/1500 Notes An external equipment can access to S71200/1500 CPU using the S7 'base' protocol, only working as an HMI, i.e. only basic data transfer are allowed. All other PG operations (control/directory/etc..) must follow the extended protocol, not implemented yet. Particularly **to access a DB in S71500 some additional setting plc-side are needed**. - Only global DBs can be accessed. - The optimized block access must be turned off. ![DB_props](http://snap7.sourceforge.net/snap7_client_file/db_1500.bmp) - The access level must be “full” and the “connection mechanism” must allow GET/PUT. ![DB_sec](http://snap7.sourceforge.net/snap7_client_file/cpu_1500.bmp) ================================================ FILE: Sharp7/MsgSocket.cs ================================================ using System; using System.Threading; using System.Net.Sockets; namespace Sharp7 { class MsgSocket { private Socket TCPSocket; private int _ReadTimeout = 2000; private int _WriteTimeout = 2000; private int _ConnectTimeout = 1000; private int LastError; public MsgSocket() { } ~MsgSocket() { Close(); } public void Close() { if (TCPSocket != null) { TCPSocket.Dispose(); TCPSocket = null; } } private void CreateSocket() { TCPSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); TCPSocket.NoDelay = true; } private void TCPPing(string Host, int Port) { // To Ping the PLC an Asynchronous socket is used rather then an ICMP packet. // This allows the use also across Internet and Firewalls (obviously the port must be opened) LastError = 0; Socket PingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { IAsyncResult result = PingSocket.BeginConnect(Host, Port, null, null); bool success = result.AsyncWaitHandle.WaitOne(_ConnectTimeout, true); if (!success) { LastError = S7Consts.errTCPConnectionFailed; } } catch { LastError = S7Consts.errTCPConnectionFailed; } PingSocket.Close(); } public int Connect(string Host, int Port) { LastError = 0; if (!Connected) { TCPPing(Host, Port); if (LastError == 0) try { CreateSocket(); TCPSocket.Connect(Host, Port); } catch { LastError = S7Consts.errTCPConnectionFailed; } } return LastError; } private int WaitForData(int Size, int Timeout) { bool Expired = false; int SizeAvail; int Elapsed = Environment.TickCount; LastError = 0; try { SizeAvail = TCPSocket.Available; while ((SizeAvail < Size) && (!Expired)) { Thread.Sleep(2); SizeAvail = TCPSocket.Available; Expired = Environment.TickCount - Elapsed > Timeout; // If timeout we clean the buffer if (Expired && (SizeAvail > 0)) { try { byte[] Flush = new byte[SizeAvail]; TCPSocket.Receive(Flush, 0, SizeAvail, SocketFlags.None); } catch { LastError = S7Consts.errTCPDataReceive; } } } } catch { LastError = S7Consts.errTCPDataReceive; } if (Expired) { LastError = S7Consts.errTCPDataReceive; } return LastError; } public int Receive(byte[] Buffer, int Start, int Size) { int BytesRead = 0; LastError = WaitForData(Size, _ReadTimeout); if (LastError == 0) { try { BytesRead = TCPSocket.Receive(Buffer, Start, Size, SocketFlags.None); } catch { LastError = S7Consts.errTCPDataReceive; } if (BytesRead == 0) // Connection Reset by the peer { LastError = S7Consts.errTCPDataReceive; Close(); } } return LastError; } public int Send(byte[] Buffer, int Size) { LastError = 0; try { TCPSocket.Send(Buffer, Size, SocketFlags.None); } catch { LastError = S7Consts.errTCPDataSend; Close(); } return LastError; } public bool Connected => (TCPSocket != null) && (TCPSocket.Connected); public int ReadTimeout { get => _ReadTimeout; set => _ReadTimeout = value; } public int WriteTimeout { get => _WriteTimeout; set => _WriteTimeout = value; } public int ConnectTimeout { get => _ConnectTimeout; set => _ConnectTimeout = value; } } } ================================================ FILE: Sharp7/S7.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Sharp7 { public static class S7 { #region [Help Functions] private static Int64 bias = 621355968000000000; // "decimicros" between 0001-01-01 00:00:00 and 1970-01-01 00:00:00 private static int BCDtoByte(byte B) { return ((B >> 4) * 10) + (B & 0x0F); } private static byte ByteToBCD(int value) { return (byte) (((value / 10) << 4) | (value % 10)); } public static int DataSizeByte(this int wordLength) { switch (wordLength) { case (int)S7WordLength.Bit: return 1; // S7 sends 1 byte per bit case (int)S7WordLength.Byte: return 1; case (int)S7WordLength.Char: return 1; case (int)S7WordLength.Word: return 2; case (int)S7WordLength.DWord: return 4; case (int)S7WordLength.Int: return 2; case (int)S7WordLength.DInt: return 4; case (int)S7WordLength.Real: return 4; case (int)S7WordLength.Counter: return 2; case (int)S7WordLength.Timer: return 2; default: return 0; } } #region Get/Set the bit at Pos.Bit public static bool GetBitAt(this byte[] buffer, int pos, int bit) { byte[] Mask = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; if (bit < 0) bit = 0; if (bit > 7) bit = 7; return (buffer[pos] & Mask[bit]) != 0; } public static void SetBitAt(this byte[] buffer, int pos, int bit, bool value) { byte[] Mask = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; if (bit < 0) { bit = 0; } if (bit > 7) { bit = 7; } if (value) { buffer[pos] = (byte) (buffer[pos] | Mask[bit]); } else { buffer[pos] = (byte) (buffer[pos] & ~Mask[bit]); } } #endregion #region Get/Set 8 bit signed value (S7 SInt) -128..127 public static int GetSIntAt(this byte[] buffer, int pos) { int value = buffer[pos]; if (value < 128) return value; return (value - 256); } public static void SetSIntAt(this byte[] buffer, int pos, int value) { if (value < -128) value = -128; if (value > 127) value = 127; buffer[pos] = (byte) value; } #endregion #region Get/Set 16 bit signed value (S7 int) -32768..32767 public static short GetIntAt(this byte[] buffer, int pos) { return (short)((buffer[pos] << 8) | buffer[pos + 1]); } public static void SetIntAt(this byte[] buffer, int pos, Int16 value) { buffer[pos] = (byte) (value >> 8); buffer[pos + 1] = (byte) (value & 0x00FF); } #endregion #region Get/Set 32 bit signed value (S7 DInt) -2147483648..2147483647 public static int GetDIntAt(this byte[] buffer, int pos) { int result; result = buffer[pos]; result <<= 8; result += buffer[pos + 1]; result <<= 8; result += buffer[pos + 2]; result <<= 8; result += buffer[pos + 3]; return result; } public static void SetDIntAt(this byte[] buffer, int pos, int value) { buffer[pos + 3] = (byte) (value & 0xFF); buffer[pos + 2] = (byte) ((value >> 8) & 0xFF); buffer[pos + 1] = (byte) ((value >> 16) & 0xFF); buffer[pos] = (byte) ((value >> 24) & 0xFF); } #endregion #region Get/Set 64 bit signed value (S7 LInt) -9223372036854775808..9223372036854775807 public static Int64 GetLIntAt(this byte[] buffer, int pos) { Int64 result; result = buffer[pos]; result <<= 8; result += buffer[pos + 1]; result <<= 8; result += buffer[pos + 2]; result <<= 8; result += buffer[pos + 3]; result <<= 8; result += buffer[pos + 4]; result <<= 8; result += buffer[pos + 5]; result <<= 8; result += buffer[pos + 6]; result <<= 8; result += buffer[pos + 7]; return result; } public static void SetLIntAt(this byte[] buffer, int pos, Int64 value) { buffer[pos + 7] = (byte) (value & 0xFF); buffer[pos + 6] = (byte) ((value >> 8) & 0xFF); buffer[pos + 5] = (byte) ((value >> 16) & 0xFF); buffer[pos + 4] = (byte) ((value >> 24) & 0xFF); buffer[pos + 3] = (byte) ((value >> 32) & 0xFF); buffer[pos + 2] = (byte) ((value >> 40) & 0xFF); buffer[pos + 1] = (byte) ((value >> 48) & 0xFF); buffer[pos] = (byte) ((value >> 56) & 0xFF); } #endregion #region Get/Set 8 bit unsigned value (S7 USInt) 0..255 public static byte GetUSIntAt(this byte[] buffer, int pos) { return buffer[pos]; } public static void SetUSIntAt(this byte[] buffer, int pos, byte value) { buffer[pos] = value; } #endregion #region Get/Set 16 bit unsigned value (S7 UInt) 0..65535 public static UInt16 GetUIntAt(this byte[] buffer, int pos) { return (UInt16) ((buffer[pos] << 8) | buffer[pos + 1]); } public static void SetUIntAt(this byte[] buffer, int pos, UInt16 value) { buffer[pos] = (byte) (value >> 8); buffer[pos + 1] = (byte) (value & 0x00FF); } #endregion #region Get/Set 32 bit unsigned value (S7 UDInt) 0..4294967296 public static UInt32 GetUDIntAt(this byte[] buffer, int pos) { UInt32 result; result = buffer[pos]; result <<= 8; result |= buffer[pos + 1]; result <<= 8; result |= buffer[pos + 2]; result <<= 8; result |= buffer[pos + 3]; return result; } public static void SetUDIntAt(this byte[] buffer, int pos, UInt32 value) { buffer[pos + 3] = (byte) (value & 0xFF); buffer[pos + 2] = (byte) ((value >> 8) & 0xFF); buffer[pos + 1] = (byte) ((value >> 16) & 0xFF); buffer[pos] = (byte) ((value >> 24) & 0xFF); } #endregion #region Get/Set 64 bit unsigned value (S7 ULint) 0..18446744073709551616 public static UInt64 GetULIntAt(this byte[] buffer, int pos) { UInt64 result; result = buffer[pos]; result <<= 8; result |= buffer[pos + 1]; result <<= 8; result |= buffer[pos + 2]; result <<= 8; result |= buffer[pos + 3]; result <<= 8; result |= buffer[pos + 4]; result <<= 8; result |= buffer[pos + 5]; result <<= 8; result |= buffer[pos + 6]; result <<= 8; result |= buffer[pos + 7]; return result; } public static void SetULintAt(this byte[] buffer, int pos, UInt64 value) { buffer[pos + 7] = (byte) (value & 0xFF); buffer[pos + 6] = (byte) ((value >> 8) & 0xFF); buffer[pos + 5] = (byte) ((value >> 16) & 0xFF); buffer[pos + 4] = (byte) ((value >> 24) & 0xFF); buffer[pos + 3] = (byte) ((value >> 32) & 0xFF); buffer[pos + 2] = (byte) ((value >> 40) & 0xFF); buffer[pos + 1] = (byte) ((value >> 48) & 0xFF); buffer[pos] = (byte) ((value >> 56) & 0xFF); } #endregion #region Get/Set 8 bit word (S7 Byte) 16#00..16#FF public static byte GetByteAt(this byte[] buffer, int pos) { return buffer[pos]; } public static void SetByteAt(this byte[] buffer, int pos, byte value) { buffer[pos] = value; } #endregion #region Get/Set 16 bit word (S7 Word) 16#0000..16#FFFF public static UInt16 GetWordAt(this byte[] buffer, int pos) { return GetUIntAt(buffer, pos); } public static void SetWordAt(this byte[] buffer, int pos, UInt16 value) { SetUIntAt(buffer, pos, value); } #endregion #region Get/Set 32 bit word (S7 DWord) 16#00000000..16#FFFFFFFF public static UInt32 GetDWordAt(this byte[] buffer, int pos) { return GetUDIntAt(buffer, pos); } public static void SetDWordAt(this byte[] buffer, int pos, UInt32 value) { SetUDIntAt(buffer, pos, value); } #endregion #region Get/Set 64 bit word (S7 LWord) 16#0000000000000000..16#FFFFFFFFFFFFFFFF public static UInt64 GetLWordAt(this byte[] buffer, int pos) { return GetULIntAt(buffer, pos); } public static void SetLWordAt(this byte[] buffer, int pos, UInt64 value) { SetULintAt(buffer, pos, value); } #endregion #region Get/Set 32 bit floating point number (S7 Real) (Range of Single) public static Single GetRealAt(this byte[] buffer, int pos) { UInt32 value = GetUDIntAt(buffer, pos); byte[] bytes = BitConverter.GetBytes(value); return BitConverter.ToSingle(bytes, 0); } public static void SetRealAt(this byte[] buffer, int pos, Single value) { byte[] FloatArray = BitConverter.GetBytes(value); buffer[pos] = FloatArray[3]; buffer[pos + 1] = FloatArray[2]; buffer[pos + 2] = FloatArray[1]; buffer[pos + 3] = FloatArray[0]; } #endregion #region Get/Set 64 bit floating point number (S7 LReal) (Range of Double) public static Double GetLRealAt(this byte[] buffer, int pos) { UInt64 value = GetULIntAt(buffer, pos); byte[] bytes = BitConverter.GetBytes(value); return BitConverter.ToDouble(bytes, 0); } public static void SetLRealAt(this byte[] buffer, int pos, Double value) { byte[] FloatArray = BitConverter.GetBytes(value); buffer[pos] = FloatArray[7]; buffer[pos + 1] = FloatArray[6]; buffer[pos + 2] = FloatArray[5]; buffer[pos + 3] = FloatArray[4]; buffer[pos + 4] = FloatArray[3]; buffer[pos + 5] = FloatArray[2]; buffer[pos + 6] = FloatArray[1]; buffer[pos + 7] = FloatArray[0]; } #endregion #region Get/Set DateTime (S7 DATE_AND_TIME) public static DateTime GetDateTimeAt(this byte[] buffer, int pos) { int Year, Month, Day, Hour, Min, Sec, MSec; Year = BCDtoByte(buffer[pos]); if (Year < 90) Year += 2000; else Year += 1900; Month = BCDtoByte(buffer[pos + 1]); Day = BCDtoByte(buffer[pos + 2]); Hour = BCDtoByte(buffer[pos + 3]); Min = BCDtoByte(buffer[pos + 4]); Sec = BCDtoByte(buffer[pos + 5]); MSec = (BCDtoByte(buffer[pos + 6]) * 10) + (BCDtoByte(buffer[pos + 7]) / 10); try { return new DateTime(Year, Month, Day, Hour, Min, Sec, MSec); } catch (System.ArgumentOutOfRangeException) { return new DateTime(0); } } public static void SetDateTimeAt(this byte[] buffer, int pos, DateTime value) { int Year = value.Year; int Month = value.Month; int Day = value.Day; int Hour = value.Hour; int Min = value.Minute; int Sec = value.Second; int Dow = (int) value.DayOfWeek + 1; // MSecH = First two digits of miliseconds int MsecH = value.Millisecond / 10; // MSecL = Last digit of miliseconds int MsecL = value.Millisecond % 10; if (Year > 1999) Year -= 2000; buffer[pos] = ByteToBCD(Year); buffer[pos + 1] = ByteToBCD(Month); buffer[pos + 2] = ByteToBCD(Day); buffer[pos + 3] = ByteToBCD(Hour); buffer[pos + 4] = ByteToBCD(Min); buffer[pos + 5] = ByteToBCD(Sec); buffer[pos + 6] = ByteToBCD(MsecH); buffer[pos + 7] = ByteToBCD(MsecL * 10 + Dow); } #endregion #region Get/Set DATE (S7 DATE) public static DateTime GetDateAt(this byte[] buffer, int pos) { try { return new DateTime(1990, 1, 1).AddDays(GetIntAt(buffer, pos)); } catch (System.ArgumentOutOfRangeException) { return new DateTime(0); } } public static void SetDateAt(this byte[] buffer, int pos, DateTime value) { SetIntAt(buffer, pos, (Int16) (value - new DateTime(1990, 1, 1)).Days); } #endregion #region Get/Set TOD (S7 TIME_OF_DAY) [Obsolete("Use GetTODAsDateTimeAt or GetTODAsTimeSpanAt instead")] public static DateTime GetTODAt(this byte[] buffer, int pos) { return buffer.GetTODAsDateTimeAt(pos); } public static DateTime GetTODAsDateTimeAt(this byte[] buffer, int pos) { try { return new DateTime(0).AddMilliseconds(buffer.GetDIntAt(pos)); } catch (ArgumentOutOfRangeException) { return new DateTime(0); } } public static void SetTODAt(this byte[] buffer, int pos, DateTime value) { TimeSpan Time = value.TimeOfDay; SetDIntAt(buffer, pos, (Int32) Math.Round(Time.TotalMilliseconds)); } public static TimeSpan GetTODAsTimeSpanAt(this byte[] buffer, int pos) { try { return TimeSpan.FromMilliseconds(buffer.GetDIntAt(pos)); } catch (ArgumentOutOfRangeException) { return TimeSpan.Zero; } } public static void SetTODAt(this byte[] buffer, int pos, TimeSpan value) { SetDIntAt(buffer, pos, (Int32) Math.Round(value.TotalMilliseconds)); } #endregion #region Get/Set LTOD (S7 1500 LONG TIME_OF_DAY) [Obsolete("Use GetLTODAsDateTimeAt or GetLTODAsTimeSpanAt instead")] public static DateTime GetLTODAt(this byte[] buffer, int pos) { return buffer.GetLTODAsDateTimeAt(pos); } public static DateTime GetLTODAsDateTimeAt(this byte[] buffer, int pos) { // .NET Tick = 100 ns, S71500 Tick = 1 ns try { return new DateTime(Math.Abs(GetLIntAt(buffer, pos) / 100)); } catch (System.ArgumentOutOfRangeException) { return new DateTime(0); } } public static void SetLTODAt(this byte[] buffer, int pos, DateTime value) { TimeSpan Time = value.TimeOfDay; SetLIntAt(buffer, pos, (Int64) Time.Ticks * 100); } public static TimeSpan GetLTODAsTimeSpanAt(this byte[] buffer, int pos) { try { return TimeSpan.FromTicks(Math.Abs(GetLIntAt(buffer, pos) / 100)); } catch (System.ArgumentOutOfRangeException) { return TimeSpan.Zero; } } public static void SetLTODAt(this byte[] buffer, int pos, TimeSpan value) { SetLIntAt(buffer, pos, (Int64) value.Ticks * 100); } #endregion #region GET/SET LDT (S7 1500 Long Date and Time) public static DateTime GetLDTAt(this byte[] buffer, int pos) { try { return new DateTime((GetLIntAt(buffer, pos) / 100) + bias); } catch (System.ArgumentOutOfRangeException) { return new DateTime(0); } } public static void SetLDTAt(this byte[] buffer, int pos, DateTime value) { SetLIntAt(buffer, pos, (value.Ticks - bias) * 100); } #endregion #region Get/Set DTL (S71200/1500 Date and Time) // Thanks to Johan Cardoen for GetDTLAt public static DateTime GetDTLAt(this byte[] buffer, int pos) { int Year, Month, Day, Hour, Min, Sec, MSec; Year = buffer[pos] * 256 + buffer[pos + 1]; Month = buffer[pos + 2]; Day = buffer[pos + 3]; Hour = buffer[pos + 5]; Min = buffer[pos + 6]; Sec = buffer[pos + 7]; MSec = (int) GetUDIntAt(buffer, pos + 8) / 1000000; try { return new DateTime(Year, Month, Day, Hour, Min, Sec, MSec); } catch (System.ArgumentOutOfRangeException) { return new DateTime(0); } } public static void SetDTLAt(this byte[] buffer, int pos, DateTime value) { short Year = (short) value.Year; byte Month = (byte) value.Month; byte Day = (byte) value.Day; byte Hour = (byte) value.Hour; byte Min = (byte) value.Minute; byte Sec = (byte) value.Second; byte Dow = (byte) (value.DayOfWeek + 1); Int32 NanoSecs = value.Millisecond * 1000000; var bytes_short = BitConverter.GetBytes(Year); buffer[pos] = bytes_short[1]; buffer[pos + 1] = bytes_short[0]; buffer[pos + 2] = Month; buffer[pos + 3] = Day; buffer[pos + 4] = Dow; buffer[pos + 5] = Hour; buffer[pos + 6] = Min; buffer[pos + 7] = Sec; SetDIntAt(buffer, pos + 8, NanoSecs); } #endregion #region Get/Set String (S7 String) // Thanks to Pablo Agirre public static string GetStringAt(this byte[] buffer, int pos) { int size = (int) buffer[pos + 1]; return Encoding.UTF8.GetString(buffer, pos + 2, size); } public static void SetStringAt(this byte[] buffer, int pos, int MaxLen, string value) { int length = value.Length; // checking current length against MaxLen if (length > MaxLen) length = MaxLen; buffer[pos] = (byte) MaxLen; buffer[pos + 1] = (byte) length; Encoding.UTF8.GetBytes(value, 0, length, buffer, pos + 2); } #endregion #region Get/Set Array of char (S7 ARRAY OF CHARS) public static string GetCharsAt(this byte[] buffer, int pos, int Size) { return Encoding.UTF8.GetString(buffer, pos, Size); } public static void SetCharsAt(this byte[] buffer, int pos, string value) { int MaxLen = buffer.Length - pos; // Truncs the string if there's no room enough if (MaxLen > value.Length) MaxLen = value.Length; Encoding.UTF8.GetBytes(value, 0, MaxLen, buffer, pos); } #endregion #region Get/Set Counter public static int GetCounter(this ushort value) { return BCDtoByte((byte) value) * 100 + BCDtoByte((byte) (value >> 8)); } public static int GetCounterAt(this ushort[] buffer, int Index) { return GetCounter(buffer[Index]); } public static ushort ToCounter(this int value) { return (ushort) (ByteToBCD(value / 100) + (ByteToBCD(value % 100) << 8)); } public static void SetCounterAt(this ushort[] buffer, int pos, int value) { buffer[pos] = ToCounter(value); } #endregion #region Get/Set Timer public static S7Timer GetS7TimerAt(this byte[] buffer, int pos) { return new S7Timer(new List(buffer).GetRange(pos, 12).ToArray()); } public static void SetS7TimespanAt(this byte[] buffer, int pos, TimeSpan value) { SetDIntAt(buffer, pos, (Int32) value.TotalMilliseconds); } public static TimeSpan GetS7TimespanAt(this byte[] buffer, int pos) { if (buffer.Length < pos + 4) { return new TimeSpan(); } Int32 a; a = buffer[pos + 0]; a <<= 8; a += buffer[pos + 1]; a <<= 8; a += buffer[pos + 2]; a <<= 8; a += buffer[pos + 3]; TimeSpan sp = new TimeSpan(0, 0, 0, 0, a); return sp; } #endregion #endregion [Help Functions] } } ================================================ FILE: Sharp7/S7Area.cs ================================================ namespace Sharp7 { public enum S7Area { PE = 0x81, PA = 0x82, MK = 0x83, DB = 0x84, CT = 0x1C, TM = 0x1D, } } ================================================ FILE: Sharp7/S7Client.cs ================================================ using System; using System.Linq; using System.Runtime.InteropServices; namespace Sharp7 { public class S7Client { #region [Constants and TypeDefs] // Block type public const int Block_OB = 0x38; public const int Block_DB = 0x41; public const int Block_SDB = 0x42; public const int Block_FC = 0x43; public const int Block_SFC = 0x44; public const int Block_FB = 0x45; public const int Block_SFB = 0x46; // Sub Block Type public const byte SubBlk_OB = 0x08; public const byte SubBlk_DB = 0x0A; public const byte SubBlk_SDB = 0x0B; public const byte SubBlk_FC = 0x0C; public const byte SubBlk_SFC = 0x0D; public const byte SubBlk_FB = 0x0E; public const byte SubBlk_SFB = 0x0F; // Block languages public const byte BlockLangAWL = 0x01; public const byte BlockLangKOP = 0x02; public const byte BlockLangFUP = 0x03; public const byte BlockLangSCL = 0x04; public const byte BlockLangDB = 0x05; public const byte BlockLangGRAPH = 0x06; // Max number of vars (multiread/write) public static readonly int MaxVars = 20; // Result transport size const byte TS_ResBit = 0x03; const byte TS_ResByte = 0x04; const byte TS_ResInt = 0x05; const byte TS_ResReal = 0x07; const byte TS_ResOctet = 0x09; const ushort Code7Ok = 0x0000; const ushort Code7AddressOutOfRange = 0x0005; const ushort Code7InvalidTransportSize = 0x0006; const ushort Code7WriteDataSizeMismatch = 0x0007; const ushort Code7ResItemNotAvailable = 0x000A; const ushort Code7ResItemNotAvailable1 = 0xD209; const ushort Code7InvalidValue = 0xDC01; const ushort Code7NeedPassword = 0xD241; const ushort Code7InvalidPassword = 0xD602; const ushort Code7NoPasswordToClear = 0xD604; const ushort Code7NoPasswordToSet = 0xD605; const ushort Code7FunNotAvailable = 0x8104; const ushort Code7DataOverPDU = 0x8500; // Client Connection Type public static readonly UInt16 CONNTYPE_PG = 0x01; // Connect to the PLC as a PG public static readonly UInt16 CONNTYPE_OP = 0x02; // Connect to the PLC as an OP public static readonly UInt16 CONNTYPE_BASIC = 0x03; // Basic connection public int _LastError = 0; public struct S7DataItem { public int Area; public int WordLen; public int Result; public int DBNumber; public int Start; public int Amount; public IntPtr pData; } // Order Code + Version public struct S7OrderCode { public string Code; // such as "6ES7 151-8AB01-0AB0" public byte V1; // Version 1st digit public byte V2; // Version 2nd digit public byte V3; // Version 3th digit }; // CPU Info public struct S7CpuInfo { public string ModuleTypeName; public string SerialNumber; public string ASName; public string Copyright; public string ModuleName; } public struct S7CpInfo { public int MaxPduLength; public int MaxConnections; public int MaxMpiRate; public int MaxBusRate; }; // Block List [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct S7BlocksList { public Int32 OBCount; public Int32 FBCount; public Int32 FCCount; public Int32 SFBCount; public Int32 SFCCount; public Int32 DBCount; public Int32 SDBCount; }; // Managed Block Info public struct S7BlockInfo { public int BlkType; public int BlkNumber; public int BlkLang; public int BlkFlags; public int MC7Size; // The real size in bytes public int LoadSize; public int LocalData; public int SBBLength; public int CheckSum; public int Version; // Chars info public string CodeDate; public string IntfDate; public string Author; public string Family; public string Header; }; // See §33.1 of "System Software for S7-300/400 System and Standard Functions" // and see SFC51 description too [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct SZL_HEADER { public UInt16 LENTHDR; public UInt16 N_DR; }; [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct S7SZL { public SZL_HEADER Header; [MarshalAs(UnmanagedType.ByValArray)] public byte[] Data; }; // SZL List of available SZL IDs : same as SZL but List items are big-endian adjusted [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct S7SZLList { public SZL_HEADER Header; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x2000 - 2)] public UInt16[] Data; }; // S7 Protection // See §33.19 of "System Software for S7-300/400 System and Standard Functions" public struct S7Protection { public ushort sch_schal; public ushort sch_par; public ushort sch_rel; public ushort bart_sch; public ushort anl_sch; }; #endregion #region [S7 Telegrams] // ISO Connection Request telegram (contains also ISO Header and COTP Header) byte[] ISO_CR = { // TPKT (RFC1006 Header) 0x03, // RFC 1006 ID (3) 0x00, // Reserved, always 0 0x00, // High part of packet lenght (entire frame, payload and TPDU included) 0x16, // Low part of packet lenght (entire frame, payload and TPDU included) // COTP (ISO 8073 Header) 0x11, // PDU Size Length 0xE0, // CR - Connection Request ID 0x00, // Dst Reference HI 0x00, // Dst Reference LO 0x00, // Src Reference HI 0x01, // Src Reference LO 0x00, // Class + Options Flags 0xC0, // PDU Max Length ID 0x01, // PDU Max Length HI 0x0A, // PDU Max Length LO 0xC1, // Src TSAP Identifier 0x02, // Src TSAP Length (2 bytes) 0x01, // Src TSAP HI (will be overwritten) 0x00, // Src TSAP LO (will be overwritten) 0xC2, // Dst TSAP Identifier 0x02, // Dst TSAP Length (2 bytes) 0x01, // Dst TSAP HI (will be overwritten) 0x02 // Dst TSAP LO (will be overwritten) }; // TPKT + ISO COTP Header (Connection Oriented Transport Protocol) byte[] TPKT_ISO = { // 7 bytes 0x03,0x00, 0x00,0x1f, // Telegram Length (Data Size + 31 or 35) 0x02,0xf0,0x80 // COTP (see above for info) }; // S7 PDU Negotiation Telegram (contains also ISO Header and COTP Header) byte[] S7_PN = { 0x03, 0x00, 0x00, 0x19, 0x02, 0xf0, 0x80, // TPKT + COTP (see above for info) 0x32, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x08, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x1e // PDU Length Requested = HI-LO Here Default 480 bytes }; // S7 Read/Write Request Header (contains also ISO Header and COTP Header) byte[] S7_RW = { // 31-35 bytes 0x03,0x00, 0x00,0x1f, // Telegram Length (Data Size + 31 or 35) 0x02,0xf0, 0x80, // COTP (see above for info) 0x32, // S7 Protocol ID 0x01, // Job Type 0x00,0x00, // Redundancy identification 0x05,0x00, // PDU Reference 0x00,0x0e, // Parameters Length 0x00,0x00, // Data Length = Size(bytes) + 4 0x04, // Function 4 Read Var, 5 Write Var 0x01, // Items count 0x12, // Var spec. 0x0a, // Length of remaining bytes 0x10, // Syntax ID (byte)S7WordLength.Byte, // Transport Size idx=22 0x00,0x00, // Num Elements 0x00,0x00, // DB Number (if any, else 0) 0x84, // Area Type 0x00,0x00,0x00, // Area Offset // WR area 0x00, // Reserved 0x04, // Transport size 0x00,0x00, // Data Length * 8 (if not bit or timer or counter) }; private static int Size_RD = 31; // Header Size when Reading private static int Size_WR = 35; // Header Size when Writing // S7 Variable MultiRead Header byte[] S7_MRD_HEADER = { 0x03,0x00, 0x00,0x1f, // Telegram Length 0x02,0xf0, 0x80, // COTP (see above for info) 0x32, // S7 Protocol ID 0x01, // Job Type 0x00,0x00, // Redundancy identification 0x05,0x00, // PDU Reference 0x00,0x0e, // Parameters Length 0x00,0x00, // Data Length = Size(bytes) + 4 0x04, // Function 4 Read Var, 5 Write Var 0x01 // Items count (idx 18) }; // S7 Variable MultiRead Item byte[] S7_MRD_ITEM = { 0x12, // Var spec. 0x0a, // Length of remaining bytes 0x10, // Syntax ID (byte)S7WordLength.Byte, // Transport Size idx=3 0x00,0x00, // Num Elements 0x00,0x00, // DB Number (if any, else 0) 0x84, // Area Type 0x00,0x00,0x00 // Area Offset }; // S7 Variable MultiWrite Header byte[] S7_MWR_HEADER = { 0x03,0x00, 0x00,0x1f, // Telegram Length 0x02,0xf0, 0x80, // COTP (see above for info) 0x32, // S7 Protocol ID 0x01, // Job Type 0x00,0x00, // Redundancy identification 0x05,0x00, // PDU Reference 0x00,0x0e, // Parameters Length (idx 13) 0x00,0x00, // Data Length = Size(bytes) + 4 (idx 15) 0x05, // Function 5 Write Var 0x01 // Items count (idx 18) }; // S7 Variable MultiWrite Item (Param) byte[] S7_MWR_PARAM = { 0x12, // Var spec. 0x0a, // Length of remaining bytes 0x10, // Syntax ID (byte)S7WordLength.Byte, // Transport Size idx=3 0x00,0x00, // Num Elements 0x00,0x00, // DB Number (if any, else 0) 0x84, // Area Type 0x00,0x00,0x00, // Area Offset }; // SZL First telegram request byte[] S7_SZL_FIRST = { 0x03, 0x00, 0x00, 0x21, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x05, 0x00, // Sequence out 0x00, 0x08, 0x00, 0x08, 0x00, 0x01, 0x12, 0x04, 0x11, 0x44, 0x01, 0x00, 0xff, 0x09, 0x00, 0x04, 0x00, 0x00, // ID (29) 0x00, 0x00 // Index (31) }; // SZL Next telegram request byte[] S7_SZL_NEXT = { 0x03, 0x00, 0x00, 0x21, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x06, 0x00, 0x00, 0x0c, 0x00, 0x04, 0x00, 0x01, 0x12, 0x08, 0x12, 0x44, 0x01, 0x01, // Sequence 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00 }; // Get Date/Time request byte[] S7_GET_DT = { 0x03, 0x00, 0x00, 0x1d, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x38, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x01, 0x12, 0x04, 0x11, 0x47, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00 }; // Set Date/Time command byte[] S7_SET_DT = { 0x03, 0x00, 0x00, 0x27, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x89, 0x03, 0x00, 0x08, 0x00, 0x0e, 0x00, 0x01, 0x12, 0x04, 0x11, 0x47, 0x02, 0x00, 0xff, 0x09, 0x00, 0x0a, 0x00, 0x19, // Hi part of Year (idx=30) 0x13, // Lo part of Year 0x12, // Month 0x06, // Day 0x17, // Hour 0x37, // Min 0x13, // Sec 0x00, 0x01 // ms + Day of week }; // S7 Set Session Password byte[] S7_SET_PWD = { 0x03, 0x00, 0x00, 0x25, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x27, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x01, 0x12, 0x04, 0x11, 0x45, 0x01, 0x00, 0xff, 0x09, 0x00, 0x08, // 8 Char Encoded Password 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // S7 Clear Session Password byte[] S7_CLR_PWD = { 0x03, 0x00, 0x00, 0x1d, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x29, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x01, 0x12, 0x04, 0x11, 0x45, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00 }; // S7 STOP request byte[] S7_STOP = { 0x03, 0x00, 0x00, 0x21, 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x10, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x50, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x41, 0x4d }; // S7 HOT Start request byte[] S7_HOT_START = { 0x03, 0x00, 0x00, 0x25, 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x14, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x09, 0x50, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x41, 0x4d }; // S7 COLD Start request byte[] S7_COLD_START = { 0x03, 0x00, 0x00, 0x27, 0x02, 0xf0, 0x80, 0x32, 0x01, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x16, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x02, 0x43, 0x20, 0x09, 0x50, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x41, 0x4d }; const byte pduStart = 0x28; // CPU start const byte pduStop = 0x29; // CPU stop const byte pduAlreadyStarted = 0x02; // CPU already in run mode const byte pduAlreadyStopped = 0x07; // CPU already in stop mode // S7 Get PLC Status byte[] S7_GET_STAT = { 0x03, 0x00, 0x00, 0x21, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x01, 0x12, 0x04, 0x11, 0x44, 0x01, 0x00, 0xff, 0x09, 0x00, 0x04, 0x04, 0x24, 0x00, 0x00 }; // S7 Get Block Info Request Header (contains also ISO Header and COTP Header) byte[] S7_BI = { 0x03, 0x00, 0x00, 0x25, 0x02, 0xf0, 0x80, 0x32, 0x07, 0x00, 0x00, 0x05, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x01, 0x12, 0x04, 0x11, 0x43, 0x03, 0x00, 0xff, 0x09, 0x00, 0x08, 0x30, 0x41, // Block Type 0x30, 0x30, 0x30, 0x30, 0x30, // ASCII Block Number 0x41 }; #endregion #region [Internals] // Defaults private static int ISOTCP = 102; // ISOTCP Port private static int MinPduSize = 16; private static int MinPduSizeToRequest = 240; private static int MaxPduSizeToRequest = 960; private static int DefaultTimeout = 2000; private static int IsoHSize = 7; // TPKT+COTP Header Size // Properties private int _PDULength = 0; private int _PduSizeRequested = 480; private int _PLCPort = ISOTCP; // Privates private string IPAddress; private byte LocalTSAP_HI; private byte LocalTSAP_LO; private byte RemoteTSAP_HI; private byte RemoteTSAP_LO; private byte LastPDUType; private ushort ConnType = CONNTYPE_PG; private byte[] PDU = new byte[2048]; private MsgSocket Socket = null; private int Time_ms = 0; private void CreateSocket() { Socket = new MsgSocket(); Socket.ConnectTimeout = DefaultTimeout; Socket.ReadTimeout = DefaultTimeout; Socket.WriteTimeout = DefaultTimeout; } private int TCPConnect() { if (_LastError==0) { try { _LastError=Socket.Connect(IPAddress, _PLCPort); } catch { _LastError = S7Consts.errTCPConnectionFailed; } } return _LastError; } private void RecvPacket(byte[] Buffer, int Start, int Size) { if (Connected) _LastError = Socket.Receive(Buffer, Start, Size); else _LastError = S7Consts.errTCPNotConnected; } private void SendPacket(byte[] Buffer, int Len) { if (Connected) _LastError = Socket.Send(Buffer, Len); else _LastError = S7Consts.errTCPNotConnected; } private void SendPacket(byte[] Buffer) { SendPacket(Buffer, Buffer.Length); } private int RecvIsoPacket() { Boolean Done = false; int Size = 0; while ((_LastError == 0) && !Done) { // Get TPKT (4 bytes) RecvPacket(PDU, 0, 4); if (_LastError == 0) { Size = PDU.GetWordAt(2); // Check 0 bytes Data Packet (only TPKT+COTP = 7 bytes) if (Size == IsoHSize) RecvPacket(PDU, 4, 3); // Skip remaining 3 bytes and Done is still false else { if ((Size > _PduSizeRequested + IsoHSize) || (Size < MinPduSize)) _LastError = S7Consts.errIsoInvalidPDU; else Done = true; // a valid Length !=7 && >16 && <247 } } } if (_LastError == 0) { RecvPacket(PDU, 4, 3); // Skip remaining 3 COTP bytes LastPDUType = PDU[5]; // Stores PDU Type, we need it // Receives the S7 Payload RecvPacket(PDU, 7, Size - IsoHSize); } if (_LastError == 0) { return Size; } return 0; } private int ISOConnect() { int Size; ISO_CR[16] = LocalTSAP_HI; ISO_CR[17] = LocalTSAP_LO; ISO_CR[20] = RemoteTSAP_HI; ISO_CR[21] = RemoteTSAP_LO; // Sends the connection request telegram SendPacket(ISO_CR); if (_LastError == 0) { // Gets the reply (if any) Size = RecvIsoPacket(); if (_LastError == 0) { if (Size == 22) { if (LastPDUType != (byte)0xD0) // 0xD0 = CC Connection confirm _LastError = S7Consts.errIsoConnect; } else _LastError = S7Consts.errIsoInvalidPDU; } } return _LastError; } private int NegotiatePduLength() { int Length; // Set PDU Size Requested S7_PN.SetWordAt(23, (ushort)_PduSizeRequested); // Sends the connection request telegram SendPacket(S7_PN); if (_LastError == 0) { Length = RecvIsoPacket(); if (_LastError == 0) { // check S7 Error if ((Length == 27) && (PDU[17] == 0) && (PDU[18] == 0)) // 20 = size of Negotiate Answer { // Get PDU Size Negotiated _PDULength = PDU.GetWordAt(25); if (_PDULength <= 0) _LastError = S7Consts.errCliNegotiatingPDU; } else _LastError = S7Consts.errCliNegotiatingPDU; } } return _LastError; } private int CpuError(ushort Error) { switch(Error) { case 0 : return 0; case Code7AddressOutOfRange : return S7Consts.errCliAddressOutOfRange; case Code7InvalidTransportSize : return S7Consts.errCliInvalidTransportSize; case Code7WriteDataSizeMismatch : return S7Consts.errCliWriteDataSizeMismatch; case Code7ResItemNotAvailable : case Code7ResItemNotAvailable1 : return S7Consts.errCliItemNotAvailable; case Code7DataOverPDU : return S7Consts.errCliSizeOverPDU; case Code7InvalidValue : return S7Consts.errCliInvalidValue; case Code7FunNotAvailable : return S7Consts.errCliFunNotAvailable; case Code7NeedPassword : return S7Consts.errCliNeedPassword; case Code7InvalidPassword : return S7Consts.errCliInvalidPassword; case Code7NoPasswordToSet : case Code7NoPasswordToClear : return S7Consts.errCliNoPasswordToSetOrClear; default: return S7Consts.errCliFunctionRefused; }; } #endregion #region [Class Control] public S7Client(string name) : this() { Name = name; } public string Name { get; } public S7Client() { CreateSocket(); } ~S7Client() { Disconnect(); } public override string ToString() { return $"PLC {Name ?? string.Empty}@{PLCIpAddress ?? "0.0.0.0"}"; } public int Connect() { _LastError = 0; Time_ms = 0; int Elapsed = Environment.TickCount; if (!Connected) { TCPConnect(); // First stage : TCP Connection if (_LastError == 0) { ISOConnect(); // Second stage : ISOTCP (ISO 8073) Connection if (_LastError == 0) { _LastError = NegotiatePduLength(); // Third stage : S7 PDU negotiation } } } if (_LastError != 0) Disconnect(); else Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int ConnectTo(string Address, int Rack, int Slot) { UInt16 RemoteTSAP = (UInt16)((ConnType << 8) + (Rack * 0x20) + Slot); SetConnectionParams(Address, 0x0100, RemoteTSAP); return Connect(); } public int SetConnectionParams(string Address, ushort LocalTSAP, ushort RemoteTSAP) { int LocTSAP = LocalTSAP & 0x0000FFFF; int RemTSAP = RemoteTSAP & 0x0000FFFF; IPAddress = Address; LocalTSAP_HI = (byte)(LocTSAP >> 8); LocalTSAP_LO = (byte)(LocTSAP & 0x00FF); RemoteTSAP_HI = (byte)(RemTSAP >> 8); RemoteTSAP_LO = (byte)(RemTSAP & 0x00FF); return 0; } public int SetConnectionType(ushort ConnectionType) { ConnType = ConnectionType; return 0; } public int Disconnect() { Socket?.Close(); return 0; } public int GetParam(Int32 ParamNumber, ref int Value) { int Result = 0; switch (ParamNumber) { case S7Consts.p_u16_RemotePort: { Value = PLCPort; break; } case S7Consts.p_i32_PingTimeout: { Value = ConnTimeout; break; } case S7Consts.p_i32_SendTimeout: { Value = SendTimeout; break; } case S7Consts.p_i32_RecvTimeout: { Value = RecvTimeout; break; } case S7Consts.p_i32_PDURequest: { Value = PduSizeRequested; break; } default: { Result = S7Consts.errCliInvalidParamNumber; break; } } return Result; } // Set Properties for compatibility with Snap7.net.cs public int SetParam(Int32 ParamNumber, ref int Value) { int Result = 0; switch(ParamNumber) { case S7Consts.p_u16_RemotePort: { PLCPort = Value; break; } case S7Consts.p_i32_PingTimeout: { ConnTimeout = Value; break; } case S7Consts.p_i32_SendTimeout: { SendTimeout = Value; break; } case S7Consts.p_i32_RecvTimeout: { RecvTimeout = Value; break; } case S7Consts.p_i32_PDURequest: { PduSizeRequested = Value; break; } default: { Result = S7Consts.errCliInvalidParamNumber; break; } } return Result; } public delegate void S7CliCompletion(IntPtr usrPtr, int opCode, int opResult); public int SetAsCallBack(S7CliCompletion Completion, IntPtr usrPtr) { return S7Consts.errCliFunctionNotImplemented; } #endregion #region [Data I/O main functions] public int ReadArea(S7Area Area, int DBNumber, int Start, int Amount, S7WordLength WordLen, byte[] Buffer) { return ReadArea((int)Area, DBNumber, Start, Amount, (int)WordLen, Buffer); } public int ReadArea(S7Area Area, int DBNumber, int Start, int Amount, S7WordLength WordLen, byte[] Buffer, ref int BytesRead) { return ReadArea((int)Area, DBNumber, Start, Amount, (int)WordLen, Buffer, ref BytesRead); } public int ReadArea(int Area, int DBNumber, int Start, int Amount, int WordLen, byte[] Buffer) { int BytesRead = 0; return ReadArea(Area, DBNumber, Start, Amount, WordLen, Buffer, ref BytesRead); } public int ReadArea(int Area, int DBNumber, int Start, int Amount, int WordLen, byte[] Buffer, ref int BytesRead) { int Address; int NumElements; int MaxElements; int TotElements; int SizeRequested; int Length; int Offset = 0; int WordSize = 1; _LastError = 0; Time_ms = 0; int Elapsed = Environment.TickCount; // Some adjustment if (Area == (int)S7Area.CT) WordLen = (int)S7WordLength.Counter; if (Area == (int)S7Area.TM) WordLen = (int)S7WordLength.Timer; // Calc Word size WordSize = WordLen.DataSizeByte(); if (WordSize == 0) return S7Consts.errCliInvalidWordLen; if (WordLen == (int)S7WordLength.Bit) Amount = 1; // Only 1 bit can be transferred at time else { if ((WordLen != (int)S7WordLength.Counter) && (WordLen != (int)S7WordLength.Timer)) { Amount = Amount * WordSize; WordSize = 1; WordLen = (int)S7WordLength.Byte; } } MaxElements = (_PDULength - 18) / WordSize; // 18 = Reply telegram header TotElements = Amount; while ((TotElements > 0) && (_LastError == 0)) { NumElements = TotElements; if (NumElements > MaxElements) NumElements = MaxElements; SizeRequested = NumElements * WordSize; // Setup the telegram Array.Copy(S7_RW, 0, PDU, 0, Size_RD); // Set DB Number PDU[27] = (byte)Area; // Set Area if (Area == (int)S7Area.DB) PDU.SetWordAt(25, (ushort)DBNumber); // Adjusts Start and word length if ((WordLen == (int)S7WordLength.Bit) || (WordLen == (int)S7WordLength.Counter) || (WordLen == (int)S7WordLength.Timer)) { Address = Start; PDU[22] = (byte)WordLen; } else Address = Start << 3; // Num elements PDU.SetWordAt(23, (ushort)NumElements); // Address into the PLC (only 3 bytes) PDU[30] = (byte)(Address & 0x0FF); Address = Address >> 8; PDU[29] = (byte)(Address & 0x0FF); Address = Address >> 8; PDU[28] = (byte)(Address & 0x0FF); SendPacket(PDU, Size_RD); if (_LastError == 0) { Length = RecvIsoPacket(); if (_LastError == 0) { if (Length<25) _LastError = S7Consts.errIsoInvalidDataSize; else { if (PDU[21] != 0xFF) _LastError = CpuError(PDU[21]); else { Array.Copy(PDU, 25, Buffer, Offset, SizeRequested); Offset += SizeRequested; } } } } TotElements -= NumElements; Start += NumElements * WordSize; } if (_LastError == 0) { BytesRead = Offset; Time_ms = Environment.TickCount - Elapsed; } else BytesRead = 0; return _LastError; } public int WriteArea(S7Area Area, int DBNumber, int Start, int Amount, S7WordLength WordLen, byte[] Buffer) { int BytesWritten = 0; return WriteArea((int) Area, DBNumber, Start, Amount, (int) WordLen, Buffer, ref BytesWritten); } public int WriteArea(S7Area Area, int DBNumber, int Start, int Amount, S7WordLength WordLen, byte[] Buffer, ref int BytesWritten) { return WriteArea((int) Area, DBNumber, Start, Amount, (int) WordLen, Buffer, ref BytesWritten); } public int WriteArea(int Area, int DBNumber, int Start, int Amount, int WordLen, byte[] Buffer) { int BytesWritten = 0; return WriteArea(Area, DBNumber, Start, Amount, WordLen, Buffer, ref BytesWritten); } public int WriteArea(int Area, int DBNumber, int Start, int Amount, int WordLen, byte[] Buffer, ref int BytesWritten) { int Address; int NumElements; int MaxElements; int TotElements; int DataSize; int IsoSize; int Length; int Offset = 0; int WordSize = 1; _LastError = 0; Time_ms = 0; int Elapsed = Environment.TickCount; // Some adjustment if (Area == (int)S7Area.CT) WordLen = (int)S7WordLength.Counter; if (Area == (int)S7Area.TM) WordLen = (int)S7WordLength.Timer; // Calc Word size WordSize = WordLen.DataSizeByte(); if (WordSize == 0) return S7Consts.errCliInvalidWordLen; if (WordLen == (int)S7WordLength.Bit) // Only 1 bit can be transferred at time Amount = 1; else { if ((WordLen != (int)S7WordLength.Counter) && (WordLen != (int)S7WordLength.Timer)) { Amount = Amount * WordSize; WordSize = 1; WordLen = (int)S7WordLength.Byte; } } MaxElements = (_PDULength - 35) / WordSize; // 35 = Reply telegram header TotElements = Amount; while ((TotElements > 0) && (_LastError == 0)) { NumElements = TotElements; if (NumElements > MaxElements) NumElements = MaxElements; DataSize = NumElements * WordSize; IsoSize = Size_WR + DataSize; // Setup the telegram Array.Copy(S7_RW, 0, PDU, 0, Size_WR); // Whole telegram Size PDU.SetWordAt(2, (ushort)IsoSize); // Data Length Length = DataSize + 4; PDU.SetWordAt(15, (ushort)Length); // Function PDU[17] = (byte)0x05; // Set DB Number PDU[27] = (byte)Area; if (Area == (int)S7Area.DB) PDU.SetWordAt(25, (ushort)DBNumber); // Adjusts Start and word length if ((WordLen == (int)S7WordLength.Bit) || (WordLen == (int)S7WordLength.Counter) || (WordLen == (int)S7WordLength.Timer)) { Address = Start; Length = DataSize; PDU[22] = (byte)WordLen; } else { Address = Start << 3; Length = DataSize << 3; } // Num elements PDU.SetWordAt(23, (ushort)NumElements); // Address into the PLC PDU[30] = (byte)(Address & 0x0FF); Address = Address >> 8; PDU[29] = (byte)(Address & 0x0FF); Address = Address >> 8; PDU[28] = (byte)(Address & 0x0FF); // Transport Size switch (WordLen) { case (int)S7WordLength.Bit: PDU[32] = TS_ResBit; break; case (int)S7WordLength.Counter: case (int)S7WordLength.Timer: PDU[32] = TS_ResOctet; break; default: PDU[32] = TS_ResByte; // byte/word/dword etc. break; }; // Length PDU.SetWordAt(33, (ushort)Length); // Copies the Data Array.Copy(Buffer, Offset, PDU, 35, DataSize); SendPacket(PDU, IsoSize); if (_LastError == 0) { Length = RecvIsoPacket(); if (_LastError == 0) { if (Length == 22) { if (PDU[21] != (byte)0xFF) _LastError = CpuError(PDU[21]); } else _LastError = S7Consts.errIsoInvalidPDU; } } Offset += DataSize; TotElements -= NumElements; Start += NumElements * WordSize; } if (_LastError == 0) { BytesWritten = Offset; Time_ms = Environment.TickCount - Elapsed; } else BytesWritten = 0; return _LastError; } public int ReadMultiVars(S7DataItem[] Items, int ItemsCount) { int Offset; int Length; int ItemSize; byte[] S7Item = new byte[12]; byte[] S7ItemRead = new byte[1024]; _LastError = 0; Time_ms = 0; int Elapsed = Environment.TickCount; // Checks items if (ItemsCount > MaxVars) return S7Consts.errCliTooManyItems; // Fills Header Array.Copy(S7_MRD_HEADER, 0, PDU, 0, S7_MRD_HEADER.Length); PDU.SetWordAt(13, (ushort)(ItemsCount * S7Item.Length + 2)); PDU[18] = (byte)ItemsCount; // Fills the Items Offset = 19; for (int c = 0; c < ItemsCount; c++) { Array.Copy(S7_MRD_ITEM, S7Item, S7Item.Length); S7Item[3] = (byte)Items[c].WordLen; S7Item.SetWordAt(4, (ushort)Items[c].Amount); if (Items[c].Area == (int)S7Area.DB) S7Item.SetWordAt(6, (ushort)Items[c].DBNumber); S7Item[8] = (byte)Items[c].Area; // Address into the PLC int Address = Items[c].Start; S7Item[11] = (byte)(Address & 0x0FF); Address = Address >> 8; S7Item[10] = (byte)(Address & 0x0FF); Address = Address >> 8; S7Item[09] = (byte)(Address & 0x0FF); Array.Copy(S7Item, 0, PDU, Offset, S7Item.Length); Offset += S7Item.Length; } if (Offset > _PDULength) return S7Consts.errCliSizeOverPDU; PDU.SetWordAt(2, (ushort)Offset); // Whole size SendPacket(PDU, Offset); if (_LastError != 0) return _LastError; // Get Answer Length = RecvIsoPacket(); if (_LastError != 0) return _LastError; // Check ISO Length if (Length < 22) { _LastError = S7Consts.errIsoInvalidPDU; // PDU too Small return _LastError; } // Check Global Operation Result _LastError = CpuError(PDU.GetWordAt(17)); if (_LastError != 0) return _LastError; // Get true ItemsCount int ItemsRead = PDU.GetByteAt(20); if ((ItemsRead != ItemsCount) || (ItemsRead>MaxVars)) { _LastError = S7Consts.errCliInvalidPlcAnswer; return _LastError; } // Get Data Offset = 21; for (int c = 0; c < ItemsCount; c++) { // Get the Item Array.Copy(PDU, Offset, S7ItemRead, 0, Length-Offset); if (S7ItemRead[0] == 0xff) { ItemSize = (int)S7ItemRead.GetWordAt(2); if ((S7ItemRead[1] != TS_ResOctet) && (S7ItemRead[1] != TS_ResReal) && (S7ItemRead[1] != TS_ResBit)) ItemSize = ItemSize >> 3; Marshal.Copy(S7ItemRead, 4, Items[c].pData, ItemSize); Items[c].Result = 0; if (ItemSize % 2 != 0) ItemSize++; // Odd size are rounded Offset = Offset + 4 + ItemSize; } else { Items[c].Result = CpuError(S7ItemRead[0]); Offset += 4; // Skip the Item header } } Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int WriteMultiVars(S7DataItem[] Items, int ItemsCount) { int Offset; int ParLength; int DataLength; int ItemDataSize; byte[] S7ParItem = new byte[S7_MWR_PARAM.Length]; byte[] S7DataItem = new byte[1024]; _LastError = 0; Time_ms = 0; int Elapsed = Environment.TickCount; // Checks items if (ItemsCount > MaxVars) return S7Consts.errCliTooManyItems; // Fills Header Array.Copy(S7_MWR_HEADER, 0, PDU, 0, S7_MWR_HEADER.Length); ParLength = ItemsCount * S7_MWR_PARAM.Length + 2; PDU.SetWordAt(13, (ushort)ParLength); PDU[18] = (byte)ItemsCount; // Fills Params Offset = S7_MWR_HEADER.Length; for (int c=0; c> 8; S7ParItem[10] = (byte)(Address & 0x0FF); Address = Address >> 8; S7ParItem[09] = (byte)(Address & 0x0FF); Array.Copy(S7ParItem, 0, PDU, Offset, S7ParItem.Length); Offset += S7_MWR_PARAM.Length; } // Fills Data DataLength = 0; for (int c = 0; c < ItemsCount; c++) { S7DataItem[0] = 0x00; switch (Items[c].WordLen) { case (int)S7WordLength.Bit: S7DataItem[1] = TS_ResBit; break; case (int)S7WordLength.Counter: case (int)S7WordLength.Timer: S7DataItem[1] = TS_ResOctet; break; default: S7DataItem[1] = TS_ResByte; // byte/word/dword etc. break; }; if ((Items[c].WordLen==(int)S7WordLength.Timer) || (Items[c].WordLen == (int)S7WordLength.Counter)) ItemDataSize = Items[c].Amount * 2; else ItemDataSize = Items[c].Amount; if ((S7DataItem[1] != TS_ResOctet) && (S7DataItem[1] != TS_ResBit)) S7DataItem.SetWordAt(2, (ushort)(ItemDataSize*8)); else S7DataItem.SetWordAt(2, (ushort)ItemDataSize); Marshal.Copy(Items[c].pData, S7DataItem, 4, ItemDataSize); if (ItemDataSize % 2 != 0) { S7DataItem[ItemDataSize+4] = 0x00; ItemDataSize++; } Array.Copy(S7DataItem, 0, PDU, Offset, ItemDataSize+4); Offset = Offset + ItemDataSize + 4; DataLength = DataLength + ItemDataSize + 4; } // Checks the size if (Offset > _PDULength) return S7Consts.errCliSizeOverPDU; PDU.SetWordAt(2, (ushort)Offset); // Whole size PDU.SetWordAt(15, (ushort)DataLength); // Whole size SendPacket(PDU, Offset); RecvIsoPacket(); if (_LastError==0) { // Check Global Operation Result _LastError = CpuError(PDU.GetWordAt(17)); if (_LastError != 0) return _LastError; // Get true ItemsCount int ItemsWritten = PDU.GetByteAt(20); if ((ItemsWritten != ItemsCount) || (ItemsWritten > MaxVars)) { _LastError = S7Consts.errCliInvalidPlcAnswer; return _LastError; } for (int c=0; c> 8); sBuffer[c * 2] = (byte)(Buffer[c] & 0x00FF); } return WriteArea(S7Area.TM, 0, Start, Amount, S7WordLength.Timer, sBuffer); } public int CTRead(int Start, int Amount, ushort[] Buffer) { byte[] sBuffer = new byte[Amount * 2]; int Result = ReadArea(S7Area.CT, 0, Start, Amount, S7WordLength.Counter, sBuffer); if (Result==0) { for (int c=0; c>8); sBuffer[c * 2]= (byte)(Buffer[c] & 0x00FF); } return WriteArea(S7Area.CT, 0, Start, Amount, S7WordLength.Counter, sBuffer); } #endregion #region [Directory functions] public int ListBlocks(ref S7BlocksList List) { return S7Consts.errCliFunctionNotImplemented; } private string SiemensTimestamp(long EncodedDate) { DateTime DT = new DateTime(1984, 1, 1).AddSeconds(EncodedDate*86400); #if WINDOWS_UWP || NETFX_CORE || CORE_CLR return DT.ToString(System.Globalization.DateTimeFormatInfo.CurrentInfo.ShortDatePattern); #else return DT.ToShortDateString(); #endif } public int GetAgBlockInfo(int BlockType, int BlockNum, ref S7BlockInfo Info) { _LastError = 0; Time_ms = 0; int Elapsed = Environment.TickCount; S7_BI[30] = (byte)BlockType; // Block Number S7_BI[31] = (byte)((BlockNum / 10000) + 0x30); BlockNum = BlockNum % 10000; S7_BI[32] = (byte)((BlockNum / 1000) + 0x30); BlockNum = BlockNum % 1000; S7_BI[33] = (byte)((BlockNum / 100) + 0x30); BlockNum = BlockNum % 100; S7_BI[34] = (byte)((BlockNum / 10) + 0x30); BlockNum = BlockNum % 10; S7_BI[35] = (byte)((BlockNum / 1) + 0x30); SendPacket(S7_BI); if (_LastError == 0) { int Length = RecvIsoPacket(); if (Length > 32) // the minimum expected { ushort Result = PDU.GetWordAt(27); if (Result == 0) { Info.BlkFlags= PDU[42]; Info.BlkLang = PDU[43]; Info.BlkType = PDU[44]; Info.BlkNumber = PDU.GetWordAt(45); Info.LoadSize = PDU.GetDIntAt(47); Info.CodeDate = SiemensTimestamp(PDU.GetWordAt(59)); Info.IntfDate = SiemensTimestamp(PDU.GetWordAt(65)); Info.SBBLength = PDU.GetWordAt(67); Info.LocalData = PDU.GetWordAt(71); Info.MC7Size = PDU.GetWordAt(73); Info.Author = PDU.GetCharsAt(75, 8).Trim(new char[]{(char)0}); Info.Family = PDU.GetCharsAt(83, 8).Trim(new char[]{(char)0}); Info.Header = PDU.GetCharsAt(91, 8).Trim(new char[]{(char)0}); Info.Version = PDU[99]; Info.CheckSum = PDU.GetWordAt(101); } else _LastError = CpuError(Result); } else _LastError = S7Consts.errIsoInvalidPDU; } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int GetPgBlockInfo(ref S7BlockInfo Info, byte[] Buffer, int Size) { return S7Consts.errCliFunctionNotImplemented; } public int ListBlocksOfType(int BlockType, ushort[] List, ref int ItemsCount) { return S7Consts.errCliFunctionNotImplemented; } #endregion #region [Blocks functions] public int Upload(int BlockType, int BlockNum, byte[] UsrData, ref int Size) { return S7Consts.errCliFunctionNotImplemented; } public int FullUpload(int BlockType, int BlockNum, byte[] UsrData, ref int Size) { return S7Consts.errCliFunctionNotImplemented; } public int Download(int BlockNum, byte[] UsrData, int Size) { return S7Consts.errCliFunctionNotImplemented; } public int Delete(int BlockType, int BlockNum) { return S7Consts.errCliFunctionNotImplemented; } public int DBGet(int DBNumber, byte[] UsrData, ref int Size) { S7BlockInfo BI = new S7BlockInfo(); int Elapsed = Environment.TickCount; Time_ms = 0; _LastError = GetAgBlockInfo(Block_DB, DBNumber, ref BI); if (_LastError==0) { int DBSize = BI.MC7Size; if (DBSize <= UsrData.Length) { Size = DBSize; _LastError = DBRead(DBNumber, 0, DBSize, UsrData); if (_LastError == 0) Size = DBSize; } else _LastError = S7Consts.errCliBufferTooSmall; } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int DBFill(int DBNumber, int FillChar) { S7BlockInfo BI = new S7BlockInfo(); int Elapsed = Environment.TickCount; Time_ms = 0; _LastError = GetAgBlockInfo(Block_DB, DBNumber, ref BI); if (_LastError == 0) { byte[] Buffer = new byte[BI.MC7Size]; for (int c = 0; c < BI.MC7Size; c++) Buffer[c] = (byte)FillChar; _LastError = DBWrite(DBNumber, 0, BI.MC7Size, Buffer); } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } #endregion #region [Date/Time functions] public int GetPlcDateTime(ref DateTime DT) { int Length; _LastError = 0; Time_ms = 0; int Elapsed = Environment.TickCount; SendPacket(S7_GET_DT); if (_LastError == 0) { Length = RecvIsoPacket(); if (Length > 30) // the minimum expected { if ((PDU.GetWordAt(27) == 0) && (PDU[29] == 0xFF)) { DT = PDU.GetDateTimeAt(35); } else _LastError = S7Consts.errCliInvalidPlcAnswer; } else _LastError = S7Consts.errIsoInvalidPDU; } if(_LastError==0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int SetPlcDateTime(DateTime DT) { int Length; _LastError = 0; Time_ms = 0; int Elapsed = Environment.TickCount; S7_SET_DT.SetDateTimeAt(31, DT); SendPacket(S7_SET_DT); if (_LastError == 0) { Length = RecvIsoPacket(); if (Length > 30) // the minimum expected { if (PDU.GetWordAt(27) != 0) _LastError = S7Consts.errCliInvalidPlcAnswer; } else _LastError = S7Consts.errIsoInvalidPDU; } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int SetPlcSystemDateTime() { return SetPlcDateTime(DateTime.Now); } #endregion #region [System Info functions] public int GetOrderCode(ref S7OrderCode Info) { S7SZL SZL = new S7SZL(); int Size = 1024; SZL.Data = new byte[Size]; int Elapsed = Environment.TickCount; _LastError = ReadSZL(0x0011, 0x000, ref SZL, ref Size); if (_LastError == 0) { Info.Code = SZL.Data.GetCharsAt(2, 20); Info.V1 = SZL.Data[Size - 3]; Info.V2 = SZL.Data[Size - 2]; Info.V3 = SZL.Data[Size - 1]; } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int GetCpuInfo(ref S7CpuInfo Info) { S7SZL SZL = new S7SZL(); int Size = 1024; SZL.Data = new byte[Size]; int Elapsed = Environment.TickCount; _LastError = ReadSZL(0x001C, 0x000, ref SZL, ref Size); if (_LastError == 0) { Info.ModuleTypeName = SZL.Data.GetCharsAt(172, 32); Info.SerialNumber = SZL.Data.GetCharsAt(138, 24); Info.ASName = SZL.Data.GetCharsAt(2, 24); Info.Copyright = SZL.Data.GetCharsAt(104, 26); Info.ModuleName = SZL.Data.GetCharsAt(36, 24); } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int GetCpInfo(ref S7CpInfo Info) { S7SZL SZL = new S7SZL(); int Size = 1024; SZL.Data = new byte[Size]; int Elapsed = Environment.TickCount; _LastError = ReadSZL(0x0131, 0x001, ref SZL, ref Size); if (_LastError == 0) { Info.MaxPduLength = PDU.GetIntAt(2); Info.MaxConnections = PDU.GetIntAt(4); Info.MaxMpiRate = PDU.GetDIntAt(6); Info.MaxBusRate = PDU.GetDIntAt(10); } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int ReadSZL(int ID, int Index, ref S7SZL SZL, ref int Size) { int Length; int DataSZL; int Offset = 0; bool Done = false; bool First = true; byte Seq_in = 0x00; ushort Seq_out = 0x0000; _LastError = 0; Time_ms = 0; int Elapsed = Environment.TickCount; SZL.Header.LENTHDR = 0; do { if (First) { S7_SZL_FIRST.SetWordAt(11, ++Seq_out); S7_SZL_FIRST.SetWordAt(29, (ushort)ID); S7_SZL_FIRST.SetWordAt(31, (ushort)Index); SendPacket(S7_SZL_FIRST); } else { S7_SZL_NEXT.SetWordAt(11, ++Seq_out); S7_SZL_NEXT[24] = (byte)Seq_in; SendPacket(S7_SZL_NEXT); } if (_LastError != 0) return _LastError; Length = RecvIsoPacket(); if (_LastError == 0) { if (First) { if (Length > 32) // the minimum expected { if ((PDU.GetWordAt(27) == 0) && (PDU[29] == (byte)0xFF)) { // Gets Amount of this slice DataSZL = PDU.GetWordAt(31) - 8; // Skips extra params (ID, Index ...) Done = PDU[26] == 0x00; Seq_in = (byte)PDU[24]; // Slice sequence SZL.Header.LENTHDR = PDU.GetWordAt(37); SZL.Header.N_DR = PDU.GetWordAt(39); Array.Copy(PDU, 41, SZL.Data, Offset, DataSZL); // SZL.Copy(PDU, 41, Offset, DataSZL); Offset += DataSZL; SZL.Header.LENTHDR += SZL.Header.LENTHDR; } else _LastError = S7Consts.errCliInvalidPlcAnswer; } else _LastError = S7Consts.errIsoInvalidPDU; } else { if (Length > 32) // the minimum expected { if ((PDU.GetWordAt(27) == 0) && (PDU[29] == (byte)0xFF)) { // Gets Amount of this slice DataSZL = PDU.GetWordAt(31); Done = PDU[26] == 0x00; Seq_in = (byte)PDU[24]; // Slice sequence Array.Copy(PDU, 37, SZL.Data, Offset, DataSZL); Offset += DataSZL; SZL.Header.LENTHDR += SZL.Header.LENTHDR; } else _LastError = S7Consts.errCliInvalidPlcAnswer; } else _LastError = S7Consts.errIsoInvalidPDU; } } First = false; } while (!Done && (_LastError == 0)); if (_LastError==0) { Size = SZL.Header.LENTHDR; Time_ms = Environment.TickCount - Elapsed; } return _LastError; } public int ReadSZLList(ref S7SZLList List, ref Int32 ItemsCount) { return S7Consts.errCliFunctionNotImplemented; } #endregion #region [Control functions] public int PlcHotStart() { _LastError = 0; int Elapsed = Environment.TickCount; SendPacket(S7_HOT_START); if (_LastError == 0) { int Length = RecvIsoPacket(); if (Length > 18) // 18 is the minimum expected { if (PDU[19] != pduStart) _LastError = S7Consts.errCliCannotStartPLC; else { if (PDU[20] == pduAlreadyStarted) _LastError = S7Consts.errCliAlreadyRun; else _LastError = S7Consts.errCliCannotStartPLC; } } else _LastError = S7Consts.errIsoInvalidPDU; } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int PlcColdStart() { _LastError = 0; int Elapsed = Environment.TickCount; SendPacket(S7_COLD_START); if (_LastError == 0) { int Length = RecvIsoPacket(); if (Length > 18) // 18 is the minimum expected { if (PDU[19] != pduStart) _LastError = S7Consts.errCliCannotStartPLC; else { if (PDU[20] == pduAlreadyStarted) _LastError = S7Consts.errCliAlreadyRun; else _LastError = S7Consts.errCliCannotStartPLC; } } else _LastError = S7Consts.errIsoInvalidPDU; } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int PlcStop() { _LastError = 0; int Elapsed = Environment.TickCount; SendPacket(S7_STOP); if (_LastError == 0) { int Length = RecvIsoPacket(); if (Length > 18) // 18 is the minimum expected { if (PDU[19]!=pduStop) _LastError = S7Consts.errCliCannotStopPLC; else { if (PDU[20]== pduAlreadyStopped) _LastError = S7Consts.errCliAlreadyStop; else _LastError = S7Consts.errCliCannotStopPLC; } } else _LastError = S7Consts.errIsoInvalidPDU; } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int PlcCopyRamToRom(UInt32 Timeout) { return S7Consts.errCliFunctionNotImplemented; } public int PlcCompress(UInt32 Timeout) { return S7Consts.errCliFunctionNotImplemented; } public int PlcGetStatus(ref Int32 Status) { _LastError = 0; int Elapsed = Environment.TickCount; SendPacket(S7_GET_STAT); if (_LastError == 0) { int Length = RecvIsoPacket(); if (Length > 30) // the minimum expected { ushort Result = PDU.GetWordAt(27); if (Result == 0) { switch (PDU[44]) { case S7Consts.S7CpuStatusUnknown: case S7Consts.S7CpuStatusRun: case S7Consts.S7CpuStatusStop: { Status = PDU[44]; break; } default: { // Since RUN status is always 0x08 for all CPUs and CPs, STOP status // sometime can be coded as 0x03 (especially for old cpu...) Status = S7Consts.S7CpuStatusStop; break; } } } else _LastError = CpuError(Result); } else _LastError = S7Consts.errIsoInvalidPDU; } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } #endregion #region [Security functions] public int SetSessionPassword(string Password) { byte[] pwd = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }; int Length; _LastError = 0; int Elapsed = Environment.TickCount; // Encodes the Password pwd.SetCharsAt(0, Password); pwd[0] = (byte)(pwd[0] ^ 0x55); pwd[1] = (byte)(pwd[1] ^ 0x55); for (int c = 2; c < 8; c++) { pwd[c] = (byte)(pwd[c] ^ 0x55 ^ pwd[c - 2]); } Array.Copy(pwd, 0, S7_SET_PWD, 29, 8); // Sends the telegrem SendPacket(S7_SET_PWD); if (_LastError == 0) { Length = RecvIsoPacket(); if (Length > 32) // the minimum expected { ushort Result = PDU.GetWordAt(27); if (Result != 0) _LastError = CpuError(Result); } else _LastError = S7Consts.errIsoInvalidPDU; } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; return _LastError; } public int ClearSessionPassword() { int Length; _LastError = 0; int Elapsed = Environment.TickCount; SendPacket(S7_CLR_PWD); if (_LastError == 0) { Length = RecvIsoPacket(); if (Length > 30) // the minimum expected { ushort Result = PDU.GetWordAt(27); if (Result != 0) _LastError = CpuError(Result); } else _LastError = S7Consts.errIsoInvalidPDU; } return _LastError; } public int GetProtection(ref S7Protection Protection) { S7Client.S7SZL SZL = new S7Client.S7SZL(); int Size = 256; SZL.Data = new byte[Size]; _LastError = ReadSZL(0x0232, 0x0004, ref SZL, ref Size); if (_LastError == 0) { Protection.sch_schal = SZL.Data.GetWordAt(2); Protection.sch_par = SZL.Data.GetWordAt(4); Protection.sch_rel = SZL.Data.GetWordAt(6); Protection.bart_sch = SZL.Data.GetWordAt(8); Protection.anl_sch = SZL.Data.GetWordAt(10); } return _LastError; } #endregion #region [Low Level] public int IsoExchangeBuffer(byte[] Buffer, ref Int32 Size) { _LastError = 0; Time_ms = 0; int Elapsed = Environment.TickCount; Array.Copy(TPKT_ISO, 0, PDU, 0, TPKT_ISO.Length); PDU.SetWordAt(2, (ushort)(Size + TPKT_ISO.Length)); try { Array.Copy(Buffer, 0, PDU, TPKT_ISO.Length, Size); } catch { return S7Consts.errIsoInvalidPDU; } SendPacket(PDU, TPKT_ISO.Length + Size); if (_LastError==0) { int Length=RecvIsoPacket(); if (_LastError==0) { Array.Copy(PDU, TPKT_ISO.Length, Buffer, 0, Length - TPKT_ISO.Length); Size = Length - TPKT_ISO.Length; } } if (_LastError == 0) Time_ms = Environment.TickCount - Elapsed; else Size = 0; return _LastError; } #endregion #region [Async functions (not implemented)] public int AsReadArea(int Area, int DBNumber, int Start, int Amount, int WordLen, byte[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsWriteArea(int Area, int DBNumber, int Start, int Amount, int WordLen, byte[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsDBRead(int DBNumber, int Start, int Size, byte[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsDBWrite(int DBNumber, int Start, int Size, byte[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsMBRead(int Start, int Size, byte[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsMBWrite(int Start, int Size, byte[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsEBRead(int Start, int Size, byte[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsEBWrite(int Start, int Size, byte[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsABRead(int Start, int Size, byte[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsABWrite(int Start, int Size, byte[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsTMRead(int Start, int Amount, ushort[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsTMWrite(int Start, int Amount, ushort[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsCTRead(int Start, int Amount, ushort[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsCTWrite(int Start, int Amount, ushort[] Buffer) { return S7Consts.errCliFunctionNotImplemented; } public int AsListBlocksOfType(int BlockType, ushort[] List) { return S7Consts.errCliFunctionNotImplemented; } public int AsReadSZL(int ID, int Index, ref S7SZL Data, ref Int32 Size) { return S7Consts.errCliFunctionNotImplemented; } public int AsReadSZLList(ref S7SZLList List, ref Int32 ItemsCount) { return S7Consts.errCliFunctionNotImplemented; } public int AsUpload(int BlockType, int BlockNum, byte[] UsrData, ref int Size) { return S7Consts.errCliFunctionNotImplemented; } public int AsFullUpload(int BlockType, int BlockNum, byte[] UsrData, ref int Size) { return S7Consts.errCliFunctionNotImplemented; } public int ASDownload(int BlockNum, byte[] UsrData, int Size) { return S7Consts.errCliFunctionNotImplemented; } public int AsPlcCopyRamToRom(UInt32 Timeout) { return S7Consts.errCliFunctionNotImplemented; } public int AsPlcCompress(UInt32 Timeout) { return S7Consts.errCliFunctionNotImplemented; } public int AsDBGet(int DBNumber, byte[] UsrData, ref int Size) { return S7Consts.errCliFunctionNotImplemented; } public int AsDBFill(int DBNumber, int FillChar) { return S7Consts.errCliFunctionNotImplemented; } public bool CheckAsCompletion(ref int opResult) { opResult = 0; return false; } public int WaitAsCompletion(int Timeout) { return S7Consts.errCliFunctionNotImplemented; } #endregion #region [Info Functions / Properties] public string ErrorText(int Error) { switch (Error) { case 0: return "OK"; case S7Consts.errTCPSocketCreation: return "SYS: Error creating the Socket"; case S7Consts.errTCPConnectionTimeout: return "TCP: Connection Timeout"; case S7Consts.errTCPConnectionFailed: return "TCP: Connection Error"; case S7Consts.errTCPReceiveTimeout: return "TCP: Data receive Timeout"; case S7Consts.errTCPDataReceive: return "TCP: Error receiving Data"; case S7Consts.errTCPSendTimeout: return "TCP: Data send Timeout"; case S7Consts.errTCPDataSend: return "TCP: Error sending Data"; case S7Consts.errTCPConnectionReset: return "TCP: Connection reset by the Peer"; case S7Consts.errTCPNotConnected: return "CLI: Client not connected"; case S7Consts.errTCPUnreachableHost: return "TCP: Unreachable host"; case S7Consts.errIsoConnect: return "ISO: Connection Error"; case S7Consts.errIsoInvalidPDU: return "ISO: Invalid PDU received"; case S7Consts.errIsoInvalidDataSize: return "ISO: Invalid Buffer passed to Send/Receive"; case S7Consts.errCliNegotiatingPDU: return "CLI: Error in PDU negotiation"; case S7Consts.errCliInvalidParams: return "CLI: Invalid param(s) supplied"; case S7Consts.errCliJobPending: return "CLI: Job pending"; case S7Consts.errCliTooManyItems: return "CLI: Too many items (>20) in multi read/write"; case S7Consts.errCliInvalidWordLen: return "CLI: Invalid WordLength"; case S7Consts.errCliPartialDataWritten: return "CLI: Partial data written"; case S7Consts.errCliSizeOverPDU: return "CPU: Total data exceeds the PDU size"; case S7Consts.errCliInvalidPlcAnswer: return "CLI: Invalid CPU answer"; case S7Consts.errCliAddressOutOfRange: return "CPU: Address out of range"; case S7Consts.errCliInvalidTransportSize: return "CPU: Invalid Transport size"; case S7Consts.errCliWriteDataSizeMismatch: return "CPU: Data size mismatch"; case S7Consts.errCliItemNotAvailable: return "CPU: Item not available"; case S7Consts.errCliInvalidValue: return "CPU: Invalid value supplied"; case S7Consts.errCliCannotStartPLC: return "CPU: Cannot start PLC"; case S7Consts.errCliAlreadyRun: return "CPU: PLC already RUN"; case S7Consts.errCliCannotStopPLC: return "CPU: Cannot stop PLC"; case S7Consts.errCliCannotCopyRamToRom: return "CPU: Cannot copy RAM to ROM"; case S7Consts.errCliCannotCompress: return "CPU: Cannot compress"; case S7Consts.errCliAlreadyStop: return "CPU: PLC already STOP"; case S7Consts.errCliFunNotAvailable: return "CPU: Function not available"; case S7Consts.errCliUploadSequenceFailed: return "CPU: Upload sequence failed"; case S7Consts.errCliInvalidDataSizeRecvd: return "CLI: Invalid data size received"; case S7Consts.errCliInvalidBlockType: return "CLI: Invalid block type"; case S7Consts.errCliInvalidBlockNumber: return "CLI: Invalid block number"; case S7Consts.errCliInvalidBlockSize: return "CLI: Invalid block size"; case S7Consts.errCliNeedPassword: return "CPU: Function not authorized for current protection level"; case S7Consts.errCliInvalidPassword: return "CPU: Invalid password"; case S7Consts.errCliNoPasswordToSetOrClear: return "CPU: No password to set or clear"; case S7Consts.errCliJobTimeout: return "CLI: Job Timeout"; case S7Consts.errCliFunctionRefused: return "CLI: Function refused by CPU (Unknown error)"; case S7Consts.errCliPartialDataRead: return "CLI: Partial data read"; case S7Consts.errCliBufferTooSmall: return "CLI: The buffer supplied is too small to accomplish the operation"; case S7Consts.errCliDestroying: return "CLI: Cannot perform (destroying)"; case S7Consts.errCliInvalidParamNumber: return "CLI: Invalid Param Number"; case S7Consts.errCliCannotChangeParam: return "CLI: Cannot change this param now"; case S7Consts.errCliFunctionNotImplemented: return "CLI: Function not implemented"; default: return "CLI: Unknown error (0x" + Convert.ToString(Error, 16) + ")"; }; } public int LastError() { return _LastError; } public int RequestedPduLength() { return _PduSizeRequested; } public int NegotiatedPduLength() { return _PDULength; } public int ExecTime() { return Time_ms; } public int ExecutionTime => Time_ms; public int PduSizeNegotiated => _PDULength; public int PduSizeRequested { get => _PduSizeRequested; set { if (value < MinPduSizeToRequest) value = MinPduSizeToRequest; if (value > MaxPduSizeToRequest) value = MaxPduSizeToRequest; _PduSizeRequested = value; } } public string PLCIpAddress => IPAddress; public int PLCPort { get => _PLCPort; set => _PLCPort = value; } public int ConnTimeout { get => Socket.ConnectTimeout; set => Socket.ConnectTimeout = value; } public int RecvTimeout { get => Socket.ReadTimeout; set => Socket.ReadTimeout = value; } public int SendTimeout { get => Socket.WriteTimeout; set => Socket.WriteTimeout = value; } public bool Connected => (Socket != null) && (Socket.Connected); #endregion } } ================================================ FILE: Sharp7/S7Consts.cs ================================================ using System; using System.Linq; using System.Runtime.InteropServices; namespace Sharp7 { public static class S7Consts { #region [Exported Consts] // Error codes //------------------------------------------------------------------------------ // ERRORS //------------------------------------------------------------------------------ public const int ResultOK = 0x00000000; public const int errTCPSocketCreation = 0x00000001; public const int errTCPConnectionTimeout = 0x00000002; public const int errTCPConnectionFailed = 0x00000003; public const int errTCPReceiveTimeout = 0x00000004; public const int errTCPDataReceive = 0x00000005; public const int errTCPSendTimeout = 0x00000006; public const int errTCPDataSend = 0x00000007; public const int errTCPConnectionReset = 0x00000008; public const int errTCPNotConnected = 0x00000009; public const int errTCPUnreachableHost = 0x00002751; public const int errIsoConnect = 0x00010000; // Connection error public const int errIsoInvalidPDU = 0x00030000; // Bad format public const int errIsoInvalidDataSize = 0x00040000; // Bad Datasize passed to send/recv : buffer is invalid public const int errCliNegotiatingPDU = 0x00100000; public const int errCliInvalidParams = 0x00200000; public const int errCliJobPending = 0x00300000; public const int errCliTooManyItems = 0x00400000; public const int errCliInvalidWordLen = 0x00500000; public const int errCliPartialDataWritten = 0x00600000; public const int errCliSizeOverPDU = 0x00700000; public const int errCliInvalidPlcAnswer = 0x00800000; public const int errCliAddressOutOfRange = 0x00900000; public const int errCliInvalidTransportSize = 0x00A00000; public const int errCliWriteDataSizeMismatch = 0x00B00000; public const int errCliItemNotAvailable = 0x00C00000; public const int errCliInvalidValue = 0x00D00000; public const int errCliCannotStartPLC = 0x00E00000; public const int errCliAlreadyRun = 0x00F00000; public const int errCliCannotStopPLC = 0x01000000; public const int errCliCannotCopyRamToRom = 0x01100000; public const int errCliCannotCompress = 0x01200000; public const int errCliAlreadyStop = 0x01300000; public const int errCliFunNotAvailable = 0x01400000; public const int errCliUploadSequenceFailed = 0x01500000; public const int errCliInvalidDataSizeRecvd = 0x01600000; public const int errCliInvalidBlockType = 0x01700000; public const int errCliInvalidBlockNumber = 0x01800000; public const int errCliInvalidBlockSize = 0x01900000; public const int errCliNeedPassword = 0x01D00000; public const int errCliInvalidPassword = 0x01E00000; public const int errCliNoPasswordToSetOrClear = 0x01F00000; public const int errCliJobTimeout = 0x02000000; public const int errCliPartialDataRead = 0x02100000; public const int errCliBufferTooSmall = 0x02200000; public const int errCliFunctionRefused = 0x02300000; public const int errCliDestroying = 0x02400000; public const int errCliInvalidParamNumber = 0x02500000; public const int errCliCannotChangeParam = 0x02600000; public const int errCliFunctionNotImplemented = 0x02700000; //------------------------------------------------------------------------------ // PARAMS LIST FOR COMPATIBILITY WITH Snap7.net.cs //------------------------------------------------------------------------------ public const Int32 p_u16_LocalPort = 1; // Not applicable here public const Int32 p_u16_RemotePort = 2; public const Int32 p_i32_PingTimeout = 3; public const Int32 p_i32_SendTimeout = 4; public const Int32 p_i32_RecvTimeout = 5; public const Int32 p_i32_WorkInterval = 6; // Not applicable here public const Int32 p_u16_SrcRef = 7; // Not applicable here public const Int32 p_u16_DstRef = 8; // Not applicable here public const Int32 p_u16_SrcTSap = 9; // Not applicable here public const Int32 p_i32_PDURequest = 10; public const Int32 p_i32_MaxClients = 11; // Not applicable here public const Int32 p_i32_BSendTimeout = 12; // Not applicable here public const Int32 p_i32_BRecvTimeout = 13; // Not applicable here public const Int32 p_u32_RecoveryTime = 14; // Not applicable here public const Int32 p_u32_KeepAliveTime = 15; // Not applicable here // Area ID [Obsolete("Use enum S7Area.PE instead")]public const byte S7AreaPE = 0x81; [Obsolete("Use enum S7Area.PA instead")]public const byte S7AreaPA = 0x82; [Obsolete("Use enum S7Area.MK instead")]public const byte S7AreaMK = 0x83; [Obsolete("Use enum S7Area.DB instead")]public const byte S7AreaDB = 0x84; [Obsolete("Use enum S7Area.CT instead")]public const byte S7AreaCT = 0x1C; [Obsolete("Use enum S7Area.TM instead")]public const byte S7AreaTM = 0x1D; // Word Length [Obsolete("Use enum S7WordLength.Bit instead")]public const int S7WLBit = 0x01; [Obsolete("Use enum S7WordLength.Byte instead")]public const int S7WLByte = 0x02; [Obsolete("Use enum S7WordLength.Char instead")]public const int S7WLChar = 0x03; [Obsolete("Use enum S7WordLength.Word instead")]public const int S7WLWord = 0x04; [Obsolete("Use enum S7WordLength.Int instead")]public const int S7WLInt = 0x05; [Obsolete("Use enum S7WordLength.DWord instead")]public const int S7WLDWord = 0x06; [Obsolete("Use enum S7WordLength.DInt instead")]public const int S7WLDInt = 0x07; [Obsolete("Use enum S7WordLength.Real instead")]public const int S7WLReal = 0x08; [Obsolete("Use enum S7WordLength.Counter instead")]public const int S7WLCounter = 0x1C; [Obsolete("Use enum S7WordLength.Timer instead")]public const int S7WLTimer = 0x1D; // PLC Status public const int S7CpuStatusUnknown = 0x00; public const int S7CpuStatusRun = 0x08; public const int S7CpuStatusStop = 0x04; [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct S7Tag { public Int32 Area; public Int32 DBNumber; public Int32 Start; public Int32 Elements; public Int32 WordLen; } #endregion } } ================================================ FILE: Sharp7/S7MultiVar.cs ================================================ using System; using System.Linq; using System.Runtime.InteropServices; namespace Sharp7 { public class S7MultiVar { #region [MultiRead/Write Helper] private S7Client FClient; private GCHandle[] Handles = new GCHandle[S7Client.MaxVars]; private int Count; private S7Client.S7DataItem[] Items = new S7Client.S7DataItem[S7Client.MaxVars]; public int[] Results { get; } = new int[S7Client.MaxVars]; private bool AdjustWordLength(int Area, ref int WordLen, ref int Amount, ref int Start) { // Calc Word size int WordSize = WordLen.DataSizeByte(); if (WordSize == 0) return false; if (Area == (int)S7Area.CT) WordLen = (int)S7WordLength.Counter; if (Area == (int)S7Area.TM) WordLen = (int)S7WordLength.Timer; if (WordLen == (int)S7WordLength.Bit) Amount = 1; // Only 1 bit can be transferred at time else { if ((WordLen != (int)S7WordLength.Counter) && (WordLen != (int)S7WordLength.Timer)) { Amount = Amount * WordSize; Start = Start * 8; WordLen = (int)S7WordLength.Byte; } } return true; } public S7MultiVar(S7Client Client) { FClient = Client; for (int c = 0; c < S7Client.MaxVars; c++) Results[c] = S7Consts.errCliItemNotAvailable; } ~S7MultiVar() { Clear(); } public bool Add(S7Consts.S7Tag Tag, ref T[] Buffer, int Offset) { return Add(Tag.Area, Tag.WordLen, Tag.DBNumber, Tag.Start, Tag.Elements, ref Buffer, Offset); } public bool Add(S7Consts.S7Tag Tag, ref T[] Buffer) { return Add(Tag.Area, Tag.WordLen, Tag.DBNumber, Tag.Start, Tag.Elements, ref Buffer); } public bool Add(Int32 Area, Int32 WordLen, Int32 DBNumber, Int32 Start, Int32 Amount, ref T[] Buffer) { return Add(Area, WordLen, DBNumber, Start, Amount, ref Buffer, 0); } public bool Add(Int32 Area, Int32 WordLen, Int32 DBNumber, Int32 Start, Int32 Amount, ref T[] Buffer, int Offset) { if (Count < S7Client.MaxVars) { if (AdjustWordLength(Area, ref WordLen, ref Amount, ref Start)) { Items[Count].Area = Area; Items[Count].WordLen = WordLen; Items[Count].Result = (int)S7Consts.errCliItemNotAvailable; Items[Count].DBNumber = DBNumber; Items[Count].Start = Start; Items[Count].Amount = Amount; GCHandle handle = GCHandle.Alloc(Buffer, GCHandleType.Pinned); if (IntPtr.Size == 4) Items[Count].pData = (IntPtr)(handle.AddrOfPinnedObject().ToInt32() + Offset * Marshal.SizeOf(typeof(T))); else Items[Count].pData = (IntPtr)(handle.AddrOfPinnedObject().ToInt64() + Offset * Marshal.SizeOf(typeof(T))); Handles[Count] = handle; Count++; return true; } return false; } return false; } public int Read() { int FunctionResult; int GlobalResult; try { if (Count > 0) { FunctionResult = FClient.ReadMultiVars(Items, Count); if (FunctionResult == 0) for (int c = 0; c < S7Client.MaxVars; c++) Results[c] = Items[c].Result; GlobalResult = FunctionResult; } else GlobalResult = S7Consts.errCliFunctionRefused; } finally { Clear(); // handles are no more needed and MUST be freed } return GlobalResult; } public int Write() { int FunctionResult; int GlobalResult; try { if (Count > 0) { FunctionResult = FClient.WriteMultiVars(Items, Count); if (FunctionResult == 0) for (int c = 0; c < S7Client.MaxVars; c++) Results[c] = Items[c].Result; GlobalResult = FunctionResult; } else GlobalResult = S7Consts.errCliFunctionRefused; } finally { Clear(); // handles are no more needed and MUST be freed } return GlobalResult; } public void Clear() { for (int c = 0; c < Count; c++) { if (Handles[c] != null) Handles[c].Free(); } Count = 0; } #endregion } } ================================================ FILE: Sharp7/S7Timer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Sharp7 { public class S7Timer { #region S7Timer TimeSpan pt; TimeSpan et; bool input ; bool q; public S7Timer(byte[] buff, int position) { if (position + 12 < buff.Length) { SetTimer(new List(buff).GetRange(position, 12).ToArray()); } } public S7Timer(byte[] buff) { SetTimer(buff); } private void SetTimer(byte[] buff) { if (buff.Length != 12) { this.pt = new TimeSpan(0); this.et = new TimeSpan(0); } else { Int32 resPT; resPT = buff[0]; resPT <<= 8; resPT += buff[1]; resPT <<= 8; resPT += buff[2]; resPT <<= 8; resPT += buff[3]; this.pt = new TimeSpan(0, 0, 0, 0, resPT); Int32 resET; resET = buff[4]; resET <<= 8; resET += buff[5]; resET <<= 8; resET += buff[6]; resET <<= 8; resET += buff[7]; this.et = new TimeSpan(0, 0, 0, 0, resET); this.input = (buff[8] & 0x01) == 0x01; this.q = (buff[8] & 0x02) == 0x02; } } public TimeSpan PT => pt; public TimeSpan ET => et; public bool IN => input; public bool Q => q; #endregion } } ================================================ FILE: Sharp7/S7WordLength.cs ================================================ namespace Sharp7 { public enum S7WordLength { Bit = 0x01, Byte = 0x02, Char = 0x03, Word = 0x04, Int = 0x05, DWord = 0x06, DInt = 0x07, Real = 0x08, Counter = 0x1C, Timer = 0x1D, } } ================================================ FILE: Sharp7/Sharp7.csproj ================================================  net40;netstandard2.0 $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb true snupkg Federico Barresi The multi-platform Ethernet S7 PLC communication suite true https://github.com/fbarresi/Sharp7 https://raw.githubusercontent.com/fbarresi/Sharp7/master/LICENSE snap7 s7 simatic siemens 1.0.0 https://raw.githubusercontent.com/fbarresi/Sharp7/master/doc/images/logo.jpg https://github.com/fbarresi/Sharp7/blob/master/CHANGELOG.md ================================================ FILE: Sharp7.Tests/ClientTest.cs ================================================ using System; using Shouldly; using Xunit; namespace Sharp7.Tests { public class ClientTest : ServerClientTestBase { [Fact] public void ClientIsNotNull() { Client.ShouldNotBeNull(); } [Fact] public void ServerIsNotNull() { Server.ShouldNotBeNull(); } [Fact] public void Timeout() { Client.ConnTimeout.ShouldBe(2000); } [Fact] public void Connected() { Client.Connected.ShouldBe(true); } [Fact] public void Port() { Client.PLCPort.ShouldBe(102); } [Fact] public void ReadWriteDb() { var bytes = new byte[] { 1, 2, 3 }; var index = 3; Server.RegisterArea(S7Server.SrvAreaDB, index, ref bytes, bytes.Length); var buffer = new byte[bytes.Length]; var rc = Client.DBRead(index, 0, bytes.Length, buffer); //test read rc.ShouldBe(Sharp7.S7Consts.ResultOK); buffer.ShouldBe(bytes); buffer = new byte[] { 3, 2, 1 }; rc = Client.DBWrite(index, 0, bytes.Length, buffer); //test write rc.ShouldBe(Sharp7.S7Consts.ResultOK); bytes.ShouldBe(buffer); } [Fact] public void ReadWriteAb() { var bytes = new byte[] { 1, 2, 3 }; Server.RegisterArea(S7Server.SrvAreaPa, 0, ref bytes, bytes.Length); var buffer = new byte[bytes.Length]; var rc = Client.ABRead(0, bytes.Length, buffer); //test read rc.ShouldBe(Sharp7.S7Consts.ResultOK); buffer.ShouldBe(bytes); buffer = new byte[] { 3, 2, 1 }; rc = Client.ABWrite(0, bytes.Length, buffer); //test write rc.ShouldBe(Sharp7.S7Consts.ResultOK); bytes.ShouldBe(buffer); } [Fact] public void ReadWriteCt() { var bytes = new byte[] { 0,1,2,3,4,5,6,7,8}; var index = 3; Server.RegisterArea(S7Server.SrvAreaCt, index, ref bytes, bytes.Length/2); var buffer = new ushort[2]; var rc = Client.CTRead(0, 2, buffer); //test read rc.ShouldBe(Sharp7.S7Consts.ResultOK); buffer.ShouldBe(new ushort[]{0x0100,0x0302}); buffer = new ushort[] {0x0403, 0x0201 }; rc = Client.CTWrite(0, 2, buffer); //test write rc.ShouldBe(Sharp7.S7Consts.ResultOK); bytes.ShouldBe(new byte[] { 3, 4, 1, 2, 4, 5, 6, 7, 8 }); } [Fact] public void ReadWriteMb() { var bytes = new byte[] { 0, 1, 2}; Server.RegisterArea(S7Server.SrvAreaMk, 0, ref bytes, bytes.Length); var buffer = new byte[bytes.Length]; var rc = Client.MBRead(0,bytes.Length,buffer); //test read rc.ShouldBe(Sharp7.S7Consts.ResultOK); buffer.ShouldBe(bytes); buffer = new byte[] { 3, 2, 1 }; rc = Client.MBWrite(0, bytes.Length, buffer); //test write rc.ShouldBe(Sharp7.S7Consts.ResultOK); bytes.ShouldBe(buffer); } [Fact] public void ReadWriteTm() { var bytes = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; var index = 5; Server.RegisterArea(S7Server.SrvAreaTm, index, ref bytes, bytes.Length / 2); var buffer = new ushort[2]; var rc = Client.TMRead(0, 2, buffer); //test read rc.ShouldBe(Sharp7.S7Consts.ResultOK); buffer.ShouldBe(new ushort[] { 0x0100, 0x0302 }); buffer = new ushort[] { 0x0403, 0x0201 }; rc = Client.TMWrite(0, 2, buffer); //test write rc.ShouldBe(Sharp7.S7Consts.ResultOK); bytes.ShouldBe(new byte[] {3, 4, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11}); } [Fact] public void ReadWriteEb() { var bytes = new byte[] { 0, 1, 2 }; Server.RegisterArea(S7Server.SrvAreaPe, 0, ref bytes, bytes.Length); var buffer = new byte[bytes.Length]; var rc = Client.EBRead(0, bytes.Length, buffer); //test read rc.ShouldBe(Sharp7.S7Consts.ResultOK); buffer.ShouldBe(bytes); buffer = new byte[] { 3, 2, 1 }; rc = Client.EBWrite(0, bytes.Length, buffer); //test write rc.ShouldBe(Sharp7.S7Consts.ResultOK); bytes.ShouldBe(buffer); } [Fact] public void Multivars() { var bytes = new byte[] { 1, 2, 3,4,5,6,7,8,9,0 }; var index = 30; Server.RegisterArea(S7Server.SrvAreaDB, index, ref bytes, bytes.Length); var buffer = new byte[bytes.Length]; var multivar = new S7MultiVar(Client); multivar.ShouldNotBeNull(); multivar.Add(new Sharp7.S7Consts.S7Tag(){Area = (int)S7Area.DB, DBNumber = index, Elements = 2,Start = 0, WordLen = 2}, ref buffer).ShouldBe(true); multivar.Read().ShouldBe(Sharp7.S7Consts.ResultOK); multivar.Add(new Sharp7.S7Consts.S7Tag() { Area = (int)S7Area.DB, DBNumber = index, Elements = 2, Start = 0, WordLen = 2 }, ref buffer).ShouldBe(true); multivar.Write().ShouldBe(Sharp7.S7Consts.ResultOK); } } } ================================================ FILE: Sharp7.Tests/ClientWithoutServer.cs ================================================ using System; using System.Linq; using Shouldly; using Xunit; namespace Sharp7.Tests { public class ClientWithoutServer { private S7Client client; public ClientWithoutServer() { client = new S7Client(); } public void Dispose() { client.Disconnect(); } [Fact] public void CannotConnectTest() { var rc = client.ConnectTo("127.0.1.2", 0, 2); rc.ShouldBe(Sharp7.S7Consts.errTCPConnectionFailed); } [Fact] public void GetLastErrorTest() { var rc = client.LastError(); rc.ShouldBe(0); } [Fact] public void GetRequestedPduTest() { var rc = client.RequestedPduLength(); rc.ShouldBe(480); client.PduSizeRequested.ShouldBe(480); } [Fact] public void GetNegotiatedPduTest() { var rc = client.NegotiatedPduLength(); rc.ShouldBe(0); client.PduSizeNegotiated.ShouldBe(0); } [Fact] public void SetPlcPortTest() { client.PLCPort = 104; client.PLCPort.ShouldBe(104); } [Fact] public void SetPduRequestedTest() { client.PduSizeRequested = 239; client.PduSizeRequested.ShouldBe(240); client.PduSizeRequested = 961; client.PduSizeRequested.ShouldBe(960); client.PduSizeRequested = 481; client.PduSizeRequested.ShouldBe(481); } [Fact] public void SetTimeoutTest() { client.ConnTimeout = 239; client.ConnTimeout.ShouldBe(239); client.RecvTimeout = 239; client.RecvTimeout.ShouldBe(239); client.SendTimeout = 239; client.SendTimeout.ShouldBe(239); } [Fact] public void GetExecTimeTest() { client.ExecutionTime.ShouldBe(client.ExecutionTime); } [Theory] [InlineData(1, 0)] [InlineData(2, 102)] [InlineData(3, 2000)] [InlineData(4, 2000)] [InlineData(5, 2000)] [InlineData(6, 0)] [InlineData(7, 0)] [InlineData(8, 0)] [InlineData(9, 0)] [InlineData(10, 480)] [InlineData(11, 0)] [InlineData(12, 0)] [InlineData(13, 0)] [InlineData(14, 0)] [InlineData(15, 0)] public void GetParameterTest(int parameterNumber, int expected) { int value = -1; var result = client.GetParam(parameterNumber, ref value); if(result == 0) value.ShouldBe(expected); else result.ShouldBe(0x02500000); } [Theory] [InlineData(1, 0)] [InlineData(2, 103)] [InlineData(3, 2001)] [InlineData(4, 2001)] [InlineData(5, 2001)] [InlineData(6, 0)] [InlineData(7, 0)] [InlineData(8, 0)] [InlineData(9, 0)] [InlineData(10, 482)] [InlineData(11, 0)] [InlineData(12, 0)] [InlineData(13, 0)] [InlineData(14, 0)] [InlineData(15, 0)] public void SetParameterTest(int parameterNumber, int newValue) { var result = client.SetParam(parameterNumber, ref newValue); if (result == 0) { int readValue = -1; client.GetParam(parameterNumber, ref readValue); readValue.ShouldBe(newValue); } else result.ShouldBe(0x02500000); } } } ================================================ FILE: Sharp7.Tests/PropertiesTests.cs ================================================ using Shouldly; using Xunit; namespace Sharp7.Tests { public class PropertiesTests { [Fact] public void PlcHasNoName() { var client = new S7Client(); client.Name.ShouldBeNull(); string.IsNullOrEmpty(client.Name).ShouldBeTrue(); } [Fact] public void PlcHasAName() { var name = "test"; var client = new S7Client(name); client.Name.ShouldNotBeNull(); string.IsNullOrEmpty(client.Name).ShouldBeFalse(); client.Name.ShouldBe(name); } [Fact] public void PlcHasIp() { var ip = "10.10.10.10"; var client = new S7Client(); client.SetConnectionParams(ip, 0, 0); client.PLCIpAddress.ShouldNotBeNull(); string.IsNullOrEmpty(client.PLCIpAddress).ShouldBeFalse(); client.PLCIpAddress.ShouldBe(ip); } [Fact] public void PlcHasNoIp() { var client = new S7Client(); client.PLCIpAddress.ShouldBeNull(); string.IsNullOrEmpty(client.PLCIpAddress).ShouldBeTrue(); } [Fact] public void PlcToString() { var client = new S7Client(); client.ToString().ShouldBe("PLC @0.0.0.0"); } [Fact] public void PlcToStringWithName() { var client = new S7Client("Test"); client.ToString().ShouldBe("PLC Test@0.0.0.0"); } [Fact] public void PlcToStringWithNameAndIp() { var client = new S7Client("Test"); client.SetConnectionParams("1.2.3.4", 0, 0); client.ToString().ShouldBe("PLC Test@1.2.3.4"); } } } ================================================ FILE: Sharp7.Tests/S7Server.cs ================================================ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; namespace Sharp7.Tests { internal class S7Consts { public const string S7LibName = "snap7.dll"; } public class S7Server { #region [Constants, private vars and TypeDefs] private const int MsgTextLen = 1024; private const int MkEvent = 0; private const int MkLog = 1; // Server Area ID (use with Register/unregister - Lock/unlock Area) public static readonly int SrvAreaPe = 0; public static readonly int SrvAreaPa = 1; public static readonly int SrvAreaMk = 2; public static readonly int SrvAreaCt = 3; public static readonly int SrvAreaTm = 4; public static readonly int SrvAreaDB = 5; // S7 Server Event Code public static readonly uint EvcPdUincoming = 0x00010000; public static readonly uint EvcDataRead = 0x00020000; public static readonly uint EvcDataWrite = 0x00040000; public static readonly uint EvcNegotiatePdu = 0x00080000; public static readonly uint EvcReadSzl = 0x00100000; public static readonly uint EvcClock = 0x00200000; public static readonly uint EvcUpload = 0x00400000; public static readonly uint EvcDownload = 0x00800000; public static readonly uint EvcDirectory = 0x01000000; public static readonly uint EvcSecurity = 0x02000000; public static readonly uint EvcControl = 0x04000000; [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct USrvEvent { public IntPtr EvtTime; // It's platform dependent (32 or 64 bit) public Int32 EvtSender; public UInt32 EvtCode; public ushort EvtRetCode; public ushort EvtParam1; public ushort EvtParam2; public ushort EvtParam3; public ushort EvtParam4; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct S7Tag { public Int32 Area; public Int32 DBNumber; public Int32 Start; public Int32 Elements; public Int32 WordLen; } private Dictionary hArea; private IntPtr server; #endregion #region [Class Control] [DllImport(S7Consts.S7LibName)] private static extern IntPtr Srv_Create(); /// /// Create an instace of S7Server /// public S7Server() { server = Srv_Create(); hArea = new Dictionary(); } [DllImport(S7Consts.S7LibName)] private static extern int Srv_Destroy(ref IntPtr server); /// /// Destroy the S7Server and free the memory /// ~S7Server() { foreach (var item in hArea) { GCHandle handle = item.Value; if (handle != null) handle.Free(); } Srv_Destroy(ref server); } [DllImport(S7Consts.S7LibName)] private static extern int Srv_StartTo(IntPtr server, [MarshalAs(UnmanagedType.LPStr)] string address); /// /// Start the server to a specific Address /// /// Address for adapter selection /// 0: No errors. Otherwise see errorcodes public int StartTo(string address) { return Srv_StartTo(server, address); } [DllImport(S7Consts.S7LibName)] private static extern int Srv_Start(IntPtr server); /// /// start the server /// /// 0: No errors. Otherwise see errorcodes public int Start() { return Srv_Start(server); } [DllImport(S7Consts.S7LibName)] private static extern int Srv_Stop(IntPtr server); /// /// Stop the server /// /// 0: No errors. Otherwise see errorcodes public int Stop() { return Srv_Stop(server); } #endregion #region [Data Areas functions] [DllImport(S7Consts.S7LibName)] private static extern int Srv_RegisterArea(IntPtr server, Int32 areaCode, Int32 index, IntPtr pUsrData, Int32 size); /// /// Register a PLC Area /// /// /// Code for area identification (e.g. S7Server.SrvAreaDB) /// Area index /// Content of the area by reference /// Allocation size /// 0: No errors. Otherwise see errorcodes public int RegisterArea(Int32 areaCode, Int32 index, ref T pUsrData, Int32 size) { Int32 areaUid = (areaCode << 16) + index; GCHandle handle = GCHandle.Alloc(pUsrData, GCHandleType.Pinned); int result = Srv_RegisterArea(server, areaCode, index, handle.AddrOfPinnedObject(), size); if (result == 0) hArea.Add(areaUid, handle); else handle.Free(); return result; } [DllImport(S7Consts.S7LibName)] private static extern int Srv_UnregisterArea(IntPtr server, Int32 areaCode, Int32 index); /// /// Unregister a PLC area /// /// Code for area identification (e.g. S7Server.SrvAreaDB) /// Area index /// 0: No errors. Otherwise see errorcodes public int UnregisterArea(Int32 areaCode, Int32 index) { int result = Srv_UnregisterArea(server, areaCode, index); if (result == 0) { Int32 areaUid = (areaCode << 16) + index; if (hArea.ContainsKey(areaUid)) // should be always true { GCHandle handle = hArea[areaUid]; if (handle != null) // should be always true handle.Free(); hArea.Remove(areaUid); } } return result; } [DllImport(S7Consts.S7LibName)] private static extern int Srv_LockArea(IntPtr server, Int32 areaCode, Int32 index); /// /// Lock a memory area /// /// Code for area identification (e.g. S7Server.SrvAreaDB) /// Area index /// 0: No errors. Otherwise see errorcodes public int LockArea(Int32 areaCode, Int32 index) { return Srv_LockArea(server, areaCode, index); } [DllImport(S7Consts.S7LibName)] private static extern int Srv_UnlockArea(IntPtr server, Int32 areaCode, Int32 index); /// /// Unlock a memory area /// /// Code for area identification (e.g. S7Server.SrvAreaDB) /// Area index /// 0: No errors. Otherwise see errorcodes public int UnlockArea(Int32 areaCode, Int32 index) { return Srv_UnlockArea(server, areaCode, index); } #endregion #region [Notification functions (Events)] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct RwBuffer { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)] // A telegram cannot exceed PDU size (960 bytes) public byte[] Data; } /// /// Callback delegate /// /// User pointer passed back /// Event information structure /// Size public delegate void SrvCallback(IntPtr usrPtr, ref USrvEvent Event, int size); /// /// Callback delegate for RW operation /// /// User pointer passed back /// Sender /// Operation type /// Operation Tag /// RW Buffer /// 0: No errors. Otherwise see errorcodes public delegate int SrvRwAreaCallback(IntPtr usrPtr, int sender, int operation, ref S7Tag tag, ref RwBuffer buffer); [DllImport(S7Consts.S7LibName)] private static extern int Srv_SetEventsCallback(IntPtr server, SrvCallback callback, IntPtr usrPtr); /// /// Set a function callback /// /// Callback delegate /// User pointer passed back /// 0: No errors. Otherwise see errorcodes public int SetEventsCallBack(SrvCallback callback, IntPtr usrPtr) { return Srv_SetEventsCallback(server, callback, usrPtr); } [DllImport(S7Consts.S7LibName)] private static extern int Srv_SetReadEventsCallback(IntPtr server, SrvCallback callback, IntPtr usrPtr); /// /// Set a function callback for read events /// /// Callback delegate /// User pointer passed back /// 0: No errors. Otherwise see errorcodes public int SetReadEventsCallBack(SrvCallback callback, IntPtr usrPtr) { return Srv_SetReadEventsCallback(server, callback, usrPtr); } [DllImport(S7Consts.S7LibName)] private static extern int Srv_SetRWAreaCallback(IntPtr server, SrvRwAreaCallback callback, IntPtr usrPtr); /// /// Set a function callback for read-write events /// /// Callback delegate /// User pointer passed back /// 0: No errors. Otherwise see errorcodes public int SetRwAreaCallBack(SrvRwAreaCallback callback, IntPtr usrPtr) { return Srv_SetRWAreaCallback(server, callback, usrPtr); } [DllImport(S7Consts.S7LibName)] private static extern int Srv_PickEvent(IntPtr server, ref USrvEvent Event, ref Int32 evtReady); /// /// Extracts an event (if available) from the Events queue. /// /// Reference of User event /// 0: No errors. Otherwise see errorcodes public bool PickEvent(ref USrvEvent Event) { Int32 evtReady = new Int32(); if (Srv_PickEvent(server, ref Event, ref evtReady) == 0) return evtReady != 0; else return false; } [DllImport(S7Consts.S7LibName)] private static extern int Srv_ClearEvents(IntPtr server); /// /// clear the event queue /// /// 0: No errors. Otherwise see errorcodes public int ClearEvents() { return Srv_ClearEvents(server); } [DllImport(S7Consts.S7LibName, CharSet = CharSet.Ansi)] private static extern int Srv_EventText(ref USrvEvent Event, StringBuilder evtMsg, int textSize); /// /// retrieve a message from an event /// /// Reference to Event /// The message for an event public string EventText(ref USrvEvent Event) { StringBuilder message = new StringBuilder(MsgTextLen); Srv_EventText(ref Event, message, MsgTextLen); return message.ToString(); } /// /// Convet an the event time to datetime object /// /// Event Time pointer /// the datetime for the timestamp public DateTime EvtTimeToDateTime(IntPtr timeStamp) { DateTime unixStartEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0); return unixStartEpoch.AddSeconds(Convert.ToDouble(timeStamp)); } [DllImport(S7Consts.S7LibName)] private static extern int Srv_GetMask(IntPtr server, Int32 maskKind, ref UInt32 mask); [DllImport(S7Consts.S7LibName)] private static extern int Srv_SetMask(IntPtr server, Int32 maskKind, UInt32 mask); // Property LogMask R/W /// /// Activate o deactivate the LogMask /// public UInt32 LogMask { get { UInt32 mask = new UInt32(); if (Srv_GetMask(server, S7Server.MkLog, ref mask) == 0) return mask; else return 0; } set => Srv_SetMask(server, S7Server.MkLog, value); } // Property EventMask R/W /// /// Activate o deactivate the EventMask /// public UInt32 EventMask { get { UInt32 mask = new UInt32(); if (Srv_GetMask(server, S7Server.MkEvent, ref mask) == 0) return mask; else return 0; } set => Srv_SetMask(server, S7Server.MkEvent, value); } #endregion #region [Info functions] [DllImport(S7Consts.S7LibName)] private static extern int Srv_GetStatus(IntPtr server, ref Int32 serverStatus, ref Int32 cpuStatus, ref Int32 clientsCount); [DllImport(S7Consts.S7LibName)] private static extern int Srv_SetCpuStatus(IntPtr server, Int32 cpuStatus); // Property Virtual CPU status R/W public int CpuStatus { get { Int32 cStatus = new Int32(); Int32 sStatus = new Int32(); Int32 cCount = new Int32(); if (Srv_GetStatus(server, ref sStatus, ref cStatus, ref cCount) == 0) return cStatus; else return -1; } set => Srv_SetCpuStatus(server, value); } // Property Server Status Read Only public int ServerStatus { get { Int32 cStatus = new Int32(); Int32 sStatus = new Int32(); Int32 cCount = new Int32(); if (Srv_GetStatus(server, ref sStatus, ref cStatus, ref cCount) == 0) return sStatus; else return -1; } } // Property Clients Count Read Only public int ClientsCount { get { Int32 cStatus = new Int32(); Int32 sStatus = new Int32(); Int32 cCount = new Int32(); if (Srv_GetStatus(server, ref cStatus, ref sStatus, ref cCount) == 0) return cCount; else return -1; } } [DllImport(S7Consts.S7LibName, CharSet = CharSet.Ansi)] private static extern int Srv_ErrorText(int error, StringBuilder errMsg, int textSize); /// /// Retrieve the error message for an error code /// /// Error code /// Message for the error code public string ErrorText(int error) { StringBuilder message = new StringBuilder(MsgTextLen); Srv_ErrorText(error, message, MsgTextLen); return message.ToString(); } #endregion } } ================================================ FILE: Sharp7.Tests/ServerClientTestBase.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using Shouldly; namespace Sharp7.Tests { public class ServerClientTestBase : ServerTestBase, IDisposable { private S7Client client; public S7Client Client => client; public ServerClientTestBase() : base() { client = new S7Client("Test Plc"); var rc = client.ConnectTo(Localhost, 0, 2); rc.ShouldBe(Sharp7.S7Consts.ResultOK); } public new void Dispose() { client.Disconnect(); base.Dispose(); } } } ================================================ FILE: Sharp7.Tests/ServerTestBase.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using Shouldly; namespace Sharp7.Tests { public class ServerTestBase : IDisposable { private readonly S7Server server; protected readonly string Localhost = "127.0.0.1"; public ServerTestBase() { server = new S7Server(); var rc = server.StartTo(Localhost); rc.ShouldBe(0); } public S7Server Server => server; public void Dispose() { server.Stop(); } } } ================================================ FILE: Sharp7.Tests/Sharp7.Tests.csproj ================================================  net45 AnyCPU;x86 false x86 x86 true all runtime; build; native; contentfiles; analyzers; buildtransitive PreserveNewest ================================================ FILE: Sharp7.Tests/TestUtilities.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Shouldly; using Xunit; namespace Sharp7.Tests { public class TestUtilities { [Theory] [InlineData(1,1)] [InlineData(2,1)] [InlineData(3,1)] [InlineData(4,2)] [InlineData(5,2)] [InlineData(6,4)] [InlineData(7,4)] [InlineData(8,4)] [InlineData(0x1D,2)] [InlineData(0x1C,2)] [InlineData(0,0)] public void TestDataSizeByte(int wordLength, int expected) { S7.DataSizeByte(wordLength).ShouldBe(expected); } [Fact] public void TestGetBitAt() { S7.GetBitAt(new byte[] {1,2,3,4}, 0, 0).ShouldBe(true); } [Fact] public void TestSetBitAt() { var buffer = new byte[] {1,2,3,4}; S7.SetBitAt(buffer, 0, 1, true); buffer.ShouldBe(new byte[] {3, 2, 3, 4}); } [Fact] public void TestSetBitAtAsExtensionMethod() { var buffer = new byte[] {1,2,3,4}; buffer.SetBitAt(0, 1, true); buffer.ShouldBe(new byte[] {3, 2, 3, 4}); } //unsigned [Theory] [InlineData(new byte[] { 1, 2, 3, 4 }, 0, 1)] [InlineData(new byte[] { 129, 2, 3, 4 }, 0, 129)] public void TestGetUSIntAt(byte[] buffer, int pos, byte expected) { S7.GetUSIntAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 2, 3, 4 }, 0, 1, new byte[] { 1, 2, 3, 4 })] [InlineData(new byte[] { 0, 2, 3, 4 }, 0, 127, new byte[] { 127, 2, 3, 4 })] public void TestSetUSIntAt(byte[] buffer, int pos, byte value, byte[] expected) { S7.SetUSIntAt(buffer, pos, value); buffer.ShouldBe(expected); } [Theory] [InlineData( new byte[] { 1, 1, 0, 0 }, 0, 257)] public void TestGetUIntAt(byte[] buffer, int pos, ushort expected) { S7.GetUIntAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[]{0,0,0,0}, 0, 1, new byte[] {0,1,0,0})] public void TestSetUIntAt(byte[] buffer, int pos, ushort value, byte[] expected) { S7.SetUIntAt(buffer, pos, value); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 2 }, 0, 2)] public void TestGetUDIntAt(byte[] buffer, int pos, uint expected) { S7.GetUDIntAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[] {0, 0, 0, 0}, 0, 1, new byte[] {0, 0, 0, 1})] public void TestSetUDIntAt(byte[] buffer, int pos, uint value, byte[] expected) { S7.SetUDIntAt(buffer, pos, value); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0, 0, 0, 0, 2 }, 0, 2L)] public void TestGetULIntAt(byte[] buffer, int pos, ulong expected) { S7.GetULIntAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[] {0, 0, 0, 0, 0, 0, 0, 0}, 0, 1L, new byte[] { 0, 0, 0, 0, 0, 0, 0, 1})] public void TestSetULIntAt(byte[] buffer, int pos, ulong value, byte[] expected) { S7.SetULintAt(buffer, pos, value); buffer.ShouldBe(expected); } // signed [Theory] [InlineData(new byte[] { 1, 2, 3, 4 }, 0, 1)] [InlineData(new byte[] { 129, 2, 3, 4 }, 0, -127)] public void TestGetSIntAt(byte[] buffer, int pos, int expected) { S7.GetSIntAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 2, 3, 4 }, 0, 1, new byte[] { 1, 2, 3, 4 })] [InlineData(new byte[] { 0, 2, 3, 4 }, 0, -127, new byte[] { 129, 2, 3, 4 })] public void TestSetSIntAt(byte[] buffer, int pos, int value, byte[] expected) { S7.SetSIntAt(buffer, pos, value); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 1, 1, 0, 0 }, 0, 257)] public void TestGetIntAt(byte[] buffer, int pos, short expected) { S7.GetIntAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0 }, 0, 1, new byte[] { 0, 1, 0, 0 })] public void TestSetIntAt(byte[] buffer, int pos, Int16 value, byte[] expected) { S7.SetIntAt(buffer, pos, value); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 2 }, 0, 2)] public void TestGetDIntAt(byte[] buffer, int pos, int expected) { S7.GetDIntAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0 }, 0, 1, new byte[] { 0, 0, 0, 1 })] public void TestSetDIntAt(byte[] buffer, int pos, int value, byte[] expected) { S7.SetDIntAt(buffer, pos, value); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0, 0, 0, 0, 2 }, 0, 2L)] public void TestGetLIntAt(byte[] buffer, int pos, long expected) { S7.GetLIntAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 1L, new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 })] public void TestSetLIntAt(byte[] buffer, int pos, long value, byte[] expected) { S7.SetLIntAt(buffer, pos, value); buffer.ShouldBe(expected); } [Fact] public void TestGetByteAt() { S7.GetByteAt(new byte[] {1,2,3,4}, 1).ShouldBe((byte)2); } [Fact] public void TestSetByteAt() { var buffer = new byte[] { 1, 2, 3, 4 }; S7.SetByteAt(buffer, 0, (byte)5); buffer.ShouldBe(new byte[] { 5, 2, 3, 4 }); } [Theory] [InlineData(new byte[] {0, 2, 0, 0, 0, 0, 0, 0}, 0, 2)] public void TestGetWordAt(byte[] buffer, int pos, ushort expected) { S7.GetWordAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[] {0, 0, 0, 0, 0, 0, 0, 0}, 0, 1, new byte[] {0, 1, 0, 0, 0, 0, 0, 0})] public void TestSetWordAt(byte[] buffer, int pos, ushort value, byte[] expected) { S7.SetWordAt(buffer, pos, value); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 3, 0, 0, 0, 0 }, 0, 3)] public void TestGetDWordAt(byte[] buffer, int pos, uint expected) { S7.GetDWordAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 1, new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 })] public void TestSetDWordAt(byte[] buffer, int pos, uint value, byte[] expected) { S7.SetDWordAt(buffer, pos, value); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0, 0, 0, 0, 4 }, 0, 4L)] public void TestGetLWordAt(byte[] buffer, int pos, ulong expected) { S7.GetLWordAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 1L, new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 })] public void TestSetLWordAt(byte[] buffer, int pos, ulong value, byte[] expected) { S7.SetLWordAt(buffer, pos, value); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 1, 2, 3, 0, 0, 11, 5, 4 }, 0, 2.387938E-38f)] public void TestGetRealAt(byte[] buffer, int pos, float expected) { S7.GetRealAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 4f, new byte[] { 64, 128, 0, 0, 0, 0, 0, 0 })] public void TestSetRealAt(byte[] buffer, int pos, float value, byte[] expected) { S7.SetRealAt(buffer, pos, value); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 64, 128, 0, 0, 0, 0, 0, 0}, 0, 512d)] public void TestGetLRealAt(byte[] buffer, int pos, double expected) { S7.GetLRealAt(buffer, pos).ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 4d, new byte[] { 64, 16, 0, 0, 0, 0, 0, 0 })] public void TestSetLRealAt(byte[] buffer, int pos, double value, byte[] expected) { S7.SetLRealAt(buffer, pos, value); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] {16,17,18,19,20,21,0,6})] public void TestGetDateTimeAt(byte[] buffer) { var time = new DateTime(2010, 11, 12, 13, 14, 15); S7.GetDateTimeAt(buffer, 0).ShouldBe(time); } [Theory] [InlineData(new byte[] {16, 17, 18, 19, 20, 21, 0, 6})] public void TestSetDateTimeAt(byte[] expected) { var time = new DateTime(2010, 11, 12, 13, 14, 15); var buffer = new byte[8]; S7.SetDateTimeAt(buffer, 0, time); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] {0,2})] public void TestGetDateAt(byte[] buffer) { var date = new DateTime(1990, 1, 3); S7.GetDateAt(buffer, 0).ShouldBe(date); } [Theory] [InlineData(new byte[] { 0,3 })] public void TestSetDateAt(byte[] expected) { var buffer = new byte[2]; var date = new DateTime(1990,1,4); S7.SetDateAt(buffer, 0, date); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] {0, 0,0,2}, 2)] public void TestGetTODAt(byte[] buffer, int milliseconds) { S7.GetTODAt(buffer, 0).ShouldBe(new DateTime(0).AddMilliseconds(milliseconds)); S7.GetTODAsDateTimeAt(buffer, 0).ShouldBe(new DateTime(0).AddMilliseconds(milliseconds)); S7.GetTODAsTimeSpanAt(buffer, 0).ShouldBe(TimeSpan.FromMilliseconds(milliseconds)); } [Theory] [InlineData(new byte[] {0, 0,0,2}, 2)] public void TestSetTODAt(byte[] expected, int milliseconds) { var buffer = new byte[4]; S7.SetTODAt(buffer, 0, new DateTime(0).AddMilliseconds(milliseconds)); buffer.ShouldBe(expected); buffer = new byte[4]; S7.SetTODAt(buffer, 0, TimeSpan.FromMilliseconds(milliseconds)); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0,0,0,0,200 }, 2)] public void TestGetLTODAt(byte[] buffer, int ticks) { S7.GetLTODAt(buffer, 0).ShouldBe(new DateTime(ticks)); S7.GetLTODAsDateTimeAt(buffer, 0).ShouldBe(new DateTime(ticks)); S7.GetLTODAsTimeSpanAt(buffer, 0).ShouldBe(TimeSpan.FromTicks(ticks)); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0,0,0,0,200 }, 2)] public void TestSetLTODAt(byte[] expected, int ticks) { var buffer = new byte[8]; S7.SetLTODAt(buffer, 0, new DateTime(ticks)); buffer.ShouldBe(expected); buffer = new byte[8]; S7.SetLTODAt(buffer, 0, TimeSpan.FromTicks(ticks)); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0, 0, 0, 0, 200 }, 2)] public void TestGetLDTAt(byte[] buffer, int ticks) { S7.GetLDTAt(buffer, 0).ShouldBe(new DateTime(1970, 1, 1).AddTicks(ticks)); } [Theory] [InlineData(new byte[] { 0, 0, 0, 0, 0, 0, 0, 200 }, 2)] public void TestSetLDTAt(byte[] expected, int ticks) { var buffer = new byte[8]; S7.SetLDTAt(buffer, 0, new DateTime(1970,1,1).AddTicks(ticks)); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0 })] public void TestGetDTLAt(byte[] buffer) { S7.GetDTLAt(buffer, 0).ShouldBe(new DateTime(2570, 10, 10,10,10,0)); } [Theory] [InlineData(new byte[] { 10, 10, 10, 10, 4, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0 })] public void TestSetDTLAt(byte[] expected) { var buffer = new byte[16]; S7.SetDTLAt(buffer, 0, new DateTime(2570, 10, 10, 10, 10, 0)); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] {0, 3, 55,55,55,0,0,0,0,0}, "777")] public void TestGetStringAt(byte[] buffer, string expected) { S7.GetStringAt(buffer, 0).ShouldBe(expected); } [Theory] [InlineData("888", 200, new byte[] {200, 3, 56,56,56})] [InlineData("888888", 5, new byte[] {5, 5, 56,56,56,56,56})] public void TestSetStringAt(string test, int maxLength, byte[] expected) { var buffer = new byte[maxLength+2]; S7.SetStringAt(buffer, 0, maxLength, test); buffer.Take(expected.Length).ToArray().ShouldBe(expected); } [Theory] [InlineData(new byte[] { 55, 55, 55 }, "777")] [InlineData(new byte[] { 56, 56, 56 }, "888")] public void TestGetCharsAt(byte[] buffer, string expected) { S7.GetCharsAt(buffer, 0, buffer.Length).ShouldBe(expected); } [Theory] [InlineData("777", new byte[] {55, 55, 55})] [InlineData("888", new byte[] {56, 56, 56})] public void TestSetCharsAt(string chars, byte[] expected) { var buffer = new byte[chars.Length]; S7.SetCharsAt(buffer, 0, chars); buffer.ShouldBe(expected); } [Theory] [InlineData(12, 1200)] [InlineData(13, 1300)] public void TestGetCounter(ushort value, int expected) { S7.GetCounter(value).ShouldBe(expected); } [Theory] [InlineData(new ushort[]{12},0, 1200)] [InlineData(new ushort[]{0,12},1, 1200)] public void TestGetCounterAt(ushort[] buffer, int index, int expected) { S7.GetCounterAt(buffer, index).ShouldBe(expected); } [Theory] [InlineData(1200, 18)] [InlineData(1300, 19)] public void TestToCounter(int value, ushort expected) { S7.ToCounter(value).ShouldBe(expected); } [Theory] [InlineData(0, 1200, new ushort[] {18,0})] [InlineData(1, 1200, new ushort[] {0, 18})] public void TestSetCounterAt(int index, int counter, ushort[] expected) { var buffer = new ushort[2]; S7.SetCounterAt(buffer, index, counter); buffer.ShouldBe(expected); } [Theory] [InlineData(new byte[] { 18, 0,0,0,0,4,5,6,7,8,0,0,0,0,0,0,0,0,0 })] public void TestGetS7TimerAt(byte[] buffer) { new S7TimerEqualityComparer().Equals(S7.GetS7TimerAt(buffer, 0), new S7Timer(buffer.Take(12).ToArray())).ShouldBe(true); } [Theory] [InlineData(10,new byte[] { 0, 0, 0, 10 })] public void TestSetS7TimespanAt(int milliseconds, byte[] expected) { var buffer = new byte[8]; S7.SetS7TimespanAt(buffer, 0, TimeSpan.FromMilliseconds(milliseconds)); buffer.Take(expected.Length).ToArray().ShouldBe(expected); } [Theory] [InlineData(new byte[] { 0, 0, 0, 10 }, 10)] public void TestGetS7TimespanAt(byte[] buffer, int milliseconds) { S7.GetS7TimespanAt(buffer, 0).ShouldBe(TimeSpan.FromMilliseconds(milliseconds)); } } internal sealed class S7TimerEqualityComparer : IEqualityComparer { public bool Equals(S7Timer x, S7Timer y) { if (ReferenceEquals(x, y)) return true; if (ReferenceEquals(x, null)) return false; if (ReferenceEquals(y, null)) return false; if (x.GetType() != y.GetType()) return false; return x.PT.Equals(y.PT) && x.ET.Equals(y.ET) && x.IN == y.IN && x.Q == y.Q; } public int GetHashCode(S7Timer obj) { unchecked { var hashCode = obj.PT.GetHashCode(); hashCode = (hashCode * 397) ^ obj.ET.GetHashCode(); hashCode = (hashCode * 397) ^ obj.IN.GetHashCode(); hashCode = (hashCode * 397) ^ obj.Q.GetHashCode(); return hashCode; } } } } ================================================ FILE: Sharp7.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29306.81 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharp7", "Sharp7\Sharp7.csproj", "{4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sharp7.Tests", "Sharp7.Tests\Sharp7.Tests.csproj", "{A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Debug|Any CPU.Build.0 = Debug|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Debug|x64.ActiveCfg = Debug|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Debug|x64.Build.0 = Debug|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Debug|x86.ActiveCfg = Debug|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Debug|x86.Build.0 = Debug|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Release|Any CPU.ActiveCfg = Release|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Release|Any CPU.Build.0 = Release|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Release|x64.ActiveCfg = Release|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Release|x64.Build.0 = Release|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Release|x86.ActiveCfg = Release|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Release|x86.Build.0 = Release|Any CPU {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Debug|Any CPU.ActiveCfg = Debug|x86 {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Debug|Any CPU.Build.0 = Debug|x86 {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Debug|x64.ActiveCfg = Debug|Any CPU {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Debug|x64.Build.0 = Debug|Any CPU {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Debug|x86.ActiveCfg = Debug|Any CPU {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Debug|x86.Build.0 = Debug|Any CPU {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Release|Any CPU.ActiveCfg = Release|x86 {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Release|Any CPU.Build.0 = Release|x86 {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Release|x64.ActiveCfg = Release|Any CPU {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Release|x64.Build.0 = Release|Any CPU {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Release|x86.ActiveCfg = Release|Any CPU {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5BDF8DCB-3749-4D92-AF54-2B6EA9C808E6} EndGlobalSection EndGlobal