Resolving Siemens S7 Communication with AdvancedHMI via libnodave

David Krause17 min read
HMI / SCADASiemensTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Overview of Siemens S7 Communication Paths for AdvancedHMI

AdvancedHMI is a Visual Studio-hosted HMI framework that ships with strong native drivers for Allen-Bradley (DF1, EtherNet/IP), Modbus TCP/RTU, and a number of smaller automation vendor protocols, but it does not ship with a built-in Siemens S7 driver. Engineers integrating an S7-200, S7-300, S7-400, S7-1200, or S7-1500 controller against an AdvancedHMI front end therefore have to select one of three integration paths:

  1. A direct driver wrapper written around libnodave, the open-source C library originally developed by Thomas Valesky. This path works on S7-200 (PPI over Ethernet via CP243-1), S7-300/400 (MPI/ISO-on-TCP), and the S7-1200/1500 with caveats.
  2. A wrapper around Snap7, the actively maintained C++/C#/.NET client library by Davide Nardella that natively speaks S7 communication.
  3. An OPC / OPC UA server acting as a bridge. Siemens offers SIMATIC NET OPC servers, and third-party servers such as Kepware KEPServerEX, Softing's S7 OPC, or the open-source opc-ua-s7 bridge expose S7 tags through a standardized interface that AdvancedHMI can consume through its OPC client plugin.

The decision between these paths is driven primarily by controller firmware (S7-1200 V4+ blocks the legacy S7 communication used by libnodave), available connection resources on the CPU (PG/OP/Route/HMI), and the operational risk tolerance of running a Windows service that brokers OPC traffic.

Siemens S7 Communication Stack and Connection Limit Constraints

Every S7 CPU has a hard ceiling on the number of simultaneous S7 communication partners it will accept. The published budget is divided into PG, OP, S7 (basic S7 communication), and Route connections. Crossing the limit yields a CPU-side Wireshark trace with ISO-on-TCP CR packets that the PLC refuses with a DT T-disconnect, and on the HMI side a 0x8104 or "Resource exhausted" return code from the driver.

CPU Max PG Max OP Max S7 Max Route Total S7 Connections
S7-200 (Ethernet via CP243-1) 1 0 (no OP) 8 n/a 8
S7-300 (e.g., 315-2 PN/DP) 4 4 16 4 16
S7-400 (e.g., 416-3) 8 8 32 16 32
S7-1200 (Firmware V4.x) 3 8 8 4 Up to 16
S7-1500 4 16 16 8 Up to 32
Each AdvancedHMI instance opening its own S7 socket consumes one S7 connection resource even when it only polls a handful of tags. Consolidating multiple HMIs and SCADA clients behind a single OPC server therefore reduces the connection count from N (one per HMI) to 1 (the OPC server) plus the consumers on the SCADA side, and leaves more S7 connections free for engineering access.

For firmware-specific slot-by-slot limits refer to the Siemens SIMATIC S7-1500 manual set and the S7-1200 programmable controller system manual.

Library Option 1 — libnodave with AdvancedHMI

libnodave was released by Thomas Valesky as a C library that speaks ISO-on-TCP (RFC 1006), MPI over TCP, and PPI. Official project documentation is hosted on libnodave.sourceforge.net. The protocol library exposes a small API:

Function Purpose
daveNewConnection() Create a TCP/MPI socket to the PLC at IP/port 102.
daveConnectPLC() Negotiate the S7 communication session.
daveReadBytes() Read DB/M/I/Q/PI/PQ/PE/PA memory areas.
daveWriteBytes() Write DB/M/I/Q/PI/PQ/PE/PA memory areas.
davePrepareReadBits() / daveExecRead() Batch multiple bit-area reads in one transaction.
daveGetError() Return error string after a failure.

To bind libnodave to AdvancedHMI you write a managed wrapper that loads the compiled DLL and exposes its functionality through a CommunicationInterface-derived class. The AdvancedHMI driver model expects two entry points: Read() and Write() against a string Key that ends with a numeric offset (e.g., DB100.DBD0, MW10).

Wrapper Skeleton (C#)

using System;
using System.Runtime.InteropServices;
using AdvancedHMIDriverCommons;
using libnodave;          // imported class library

namespace AdvHMI.Siemens
{
    public class SiemensTcpDriver : EthernetPLCBase
    {
        private daveConn _conn;

        public override void Connect()
        {
            int rack = Properties.Settings.Default.Rack;
            int slot = Properties.Settings.Default.Slot;

            int fds = libnodave.openSocket(
                Properties.Settings.Default.IPAddress,
                102);
            _conn = new daveConn(
                libnodave.daveNewConnection(fds, 0, 0, rack, slot));

            int rc = libnodave.daveConnectPLC(_conn);
            if (rc != 0)
                throw new System.IO.IOException(
                    "S7 connect failed, libnodave rc=" + rc + " : " +
                    libnodave.daveGetError(rc));
        }

        public override short Read(string key)
        {
            // key like "MW10" or "DB100.DBB0"
            Area area; int dbNum; int byteStart; int bit;
            if (!libnodave.parseKey(key, out area, out dbNum,
                                    out byteStart, out bit))
                throw new ArgumentException("Bad key: " + key);

            byte[] buf = new byte[2];
            int rc = libnodave.daveReadBytes(_conn, area,
                                            dbNum, byteStart, 2, buf);
            if (rc != 0) throw new IOException(
                              "libnodave read err " + rc);
            return (short)((buf[0] << 8) | buf[1]);
        }

        public override void Write(string key, short value)
        {
            Area area; int dbNum; int byteStart; int bit;
            if (!libnodave.parseKey(key, out area, out dbNum,
                                    out byteStart, out bit))
                throw new ArgumentException("Bad key: " + key);

            byte[] buf = BitConverter.GetBytes(value);
            int rc = libnodave.daveWriteBytes(_conn, area,
                                             dbNum, byteStart, 2, buf);
            if (rc != 0) throw new IOException(
                              "libnodave write err " + rc);
        }
    }
}

Connecting to Specific CPU Families

CPU Transport Rack / Slot (TSAP) libnodave Result Code Map
S7-300 ISO-on-TCP port 102 Rack 0, Slot 2 typical 0x0101 connect-ok, 0x8104 rejected, 0xFFFC CPU busy
S7-400 ISO-on-TCP port 102 Rack 0, Slot 3 typical same as S7-300
S7-200 via CP243-1 ISO-on-TCP port 102 Rack 0, Slot 0 or 1 same plus 0x0203 if "connect to active partner" not enabled
S7-1200 V4+ ISO-on-TCP port 102 — but PUT/GET disabled by default Rack 0, Slot 1 0x8104 or "ISO: Connection refused" until PUT/GET permitted

S7-1200/1500 PUT/GET Enablement

libnodave relies on the legacy S7 PUT/GET service, which Siemens disabled by default starting with S7-1200 firmware V4.0. The PLC will reject every connection request with a connection-fault error until the option is enabled. From TIA Portal:

  1. Open the PLC's Device Configuration.
  2. Select the CPU and open Properties > General > Protection & Security (for S7-1500) or Properties > General > Access Control (for S7-1200 V4).
  3. Under Connection Mechanisms, check Permit access with PUT/GET communication from remote partner.
  4. Re-compile and download the hardware configuration.
Do not enable PUT/GET on a publicly reachable S7-1200/1500 unless the controller sits behind a firewall that blocks inbound TCP 102 from the internet. The same S7 transport used for PUT/GET is also the vector that enables remote programming.

Known libnodave Tag-Address Limitation

The reference implementation of the libnodave AdvancedHMI wrapper distributed in early community samples only resolves bit-area addresses M0.0 through M0.7. Bytes M1.x and M2.x return an out-of-range parser error and an ArgumentException on read. The fix is to update the parseKey() routine so that it accepts multi-byte M-area offsets and returns the byte index plus optional bit position. Verified patch scope: M0.0 to M2.7 inclusive. Beyond M2.7 the parser is fine; the PLC response is unchanged.

Library Option 2 — Snap7 Wrapper for AdvancedHMI

Snap7 (hosted at snap7.sourceforge.net) is a portable IEC 61131-3-aware client that wraps the S7 protocol into a coherent set of S7Client, S7Server, and S7Partner classes. It is the modern replacement for libnodave. The .NET binding snap7.net drops straight into AdvancedHMI because AdvancedHMI projects are .NET Framework 4.x assemblies.

Snap7 .NET Wrapper Skeleton

using Snap7;
using AdvancedHMIDriverCommons;

namespace AdvHMI.Siemens.Snap7
{
    public class Snap7Driver : EthernetPLCBase
    {
        private readonly S7Client _client = new S7Client();
        private S7Client.S7DataItem[] _batch;

        public override void Connect()
        {
            int rc = _client.ConnectTo(
                Properties.Settings.Default.IPAddress,
                rack: Properties.Settings.Default.Rack,
                slot: Properties.Settings.Default.Slot);
            if (rc != 0)
                throw new System.IO.IOException(
                    "Snap7 connect failed: " + _client.Text(rc));
        }

        public override short Read(string key)
        {
            // Examples:
            //   "MW10"   → Area MK, Length 2
            //   "DB100.DBD0" → Area DB, DB 100, Length 4
            var area = Parse(key);
            byte[] buf = new byte[4];
            int rc = _client.ReadArea(area.Area, area.DB,
                                      area.Start, area.Length,
                                      buf);
            if (rc != 0) throw new IOException(
                              "Snap7 read err " + _client.Text(rc));
            return (short)((buf[0] << 8) | buf[1]);
        }

        public override void Write(string key, short value)
        {
            var area = Parse(key);
            byte[] buf = BitConverter.GetBytes(value);
            int rc = _client.WriteArea(area.Area, area.DB,
                                       area.Start, area.Length,
                                       buf);
            if (rc != 0) throw new IOException(
                              "Snap7 write err " + _client.Text(rc));
        }
    }
}

Snap7 Connection Parameters and Error Codes

Snap7 Result Code CPU Code Meaning Correction
0x00000000 CPU returned no error none
0x00000001 0xFFFE CPU fault / system error Inspect CPU diagnostics buffer
0x00190002 0x8104 Connection refused Enable PUT/GET, check Rack/Slot
0x00000010 Invalid area Check DB exists, PLC is in RUN, no compile error
0xFFFF0110 ISO: Invalid TP header Bad TSAP or rack/slot mismatch
0xFFFF0115 TCP disconnect by remote Network path, ACL, firewall

Snap7 PDU Sizing

Snap7 negotiates the maximum PDU length at connect time (typically 960 bytes for S7-300/400, 480 bytes for S7-1200/1500 firmware V4+). Larger PDUs mean fewer round trips for batched reads. The companion S7Client.SetPDULength() call forces a renegotiate; leave at 0 to let Snap7 determine the maximum accepted.

OPC Server Intermediary Approach

Where a fully custom driver is impractical — multi-protocol sites, regulated environments, or shops that already operate an OPC server for MES/ERP integration — bridging through OPC is the lower-risk path. AdvancedHMI ships an OPC client plug-in that consumes any OPC DA 2.0/3.0 server. The standard servers that expose Siemens S7 are:

Server Vendor Bridges To License
SIMATIC NET OPC Server Siemens S7-200/300/400, S7-1200/1500 Bundled in SIMATIC NET
KEPServerEX S7 Ethernet Driver PTC/Kepware S7-200/300/400 (Siemens, Microwin); S7-1200/1500 via +PLUS Commercial, per-CPU
Softing S7 OPC Server Softing S7-300/400, S7-1200/1500 Commercial
opcua-s7-bridge (open source) open source S7-1500 TCP GPL/MIT

The decision has implications:

  • Single point of consumption of S7 connections (one socket per CPU).
  • Tag browsing in AdvancedHMI uses the OPC browser, not S7-area raw notation.
  • Tag updates are async via OPC callbacks — AdvancedHMI uses subscription-based polling internally.
  • DCOM becomes a configuration surface (see below).

OPC Bridge Sequence of Operations

  1. OPC server service starts on a Windows host (typically the engineering server).
  2. OPC server holds one ISO-on-TCP socket per PLC; on connect it negotiates the S7 PDU length and caches the negotiated value across reconnects.
  3. AdvancedHMI OPC client enumerates server tags via the IOPCServer::Browse() interface.
  4. AdvancedHMI subscribes to tags via IOPCAsyncIO2::ReadMaxAge() with a polling rate matching the subscribed refresh interval (typical 100–500 ms per group).
  5. Each subscribed update is delivered to AdvancedHMI through the OnDataChange callback, which pushes the value into the matching BasicValueDisplay, PIDFaceplate, or DataGridView tag instance.

DCOM Configuration for OPC Servers

OPC DA servers run as either out-of-proc COM objects or as Windows services. When AdvancedHMI sits on a different host than the OPC server (or the OPC server runs under a Windows service account), DCOM authentication and launch/activation permissions govern whether the client can reach the server. The known failure modes are clustered around five Windows nodes:

Node Required Setting Symptom of Misconfiguration
Component Services > Computers > My Computer > DCOM Config > <Server ProgID> Authentication Level = Connect or Default 0x80070005 Access Denied on CoCreateInstanceEx
Same node > Security tab Launch/Activation: grant "Local Launch", "Remote Launch", "Local Activation", "Remote Activation" to client user/account 0x80080005 Server execution failed
Same node > Identity tab "This user" pointing to a service account with read on S7-Network, or "Interactive User" for local HMIs Server starts but disconnects under session isolation
My Computer > COM Security > Access Permissions Allow anonymous logon and the matching user/group 0x80070005 under "Default Access Permission"
Windows Firewall Inbound rule allowing %SystemRoot%\System32\dllhost.exe (or the OPC server service executable) and DCOM port range Connection hangs at IRundown::Connect

OPC Server Hardening for Production

  • Run the OPC server as a dedicated Active Directory service account, not as SYSTEM, so DCOM audit logs identify the OPC user.
  • Set Default Authentication Level for Machine to Connect across both the OPC server host and the AdvancedHMI host.
  • Use a fixed TCP port for the OPC server's remote connection (DCOM defaults to a dynamic port range above 49152). Lock the firewall to that static port range.
  • On the AdvancedHMI host, add the OPC server hostname to the Trusted Sites and Local Intranet zones if the OPC server publishes a remote access XML bridge.
On modern Windows 10/11 and Windows Server 2016+ the default DCOM ResolutionMode is "High" which can break legacy OPC servers. Set HKLM\Software\Microsoft\Rpc\ClientProtocols\NamedPipe and Netbios priorities, and explicitly set Default ResolutionMode to "Default" in Component Services > Computers > My Computer if the OPC server is older.

Tag Addressing Reference

Tag syntax differs between the raw-driver path and the OPC path. Use this mapping to design tag archives:

S7 Notation Meaning AdvancedHMI raw Driver String KEPServerEX/SIMATIC NET OPC Browser Path
I0.0 Discrete input, byte 0 bit 0 I0.0 S7-1200 PLC_1.Inputs.I0_0
Q0.7 Discrete output, byte 0 bit 7 Q0.7 S7-1200 PLC_1.Outputs.Q0_7
MW10 Merker word at byte 10 MW10 S7-1200 PLC_1.Merker.MW10
M0.7 Merker bit, byte 0 bit 7 M0.7 S7-1200 PLC_1.Merker.M0_7
DB100.DBD0 DB100, double word at byte 0 DB100.DBD0 S7-1200 PLC_1.DataBlocks.DB100.DBD0
DB100.DBX0.0 DB100, bit at byte 0 bit 0 DB100.DBX0.0 S7-1200 PLC_1.DataBlocks.DB100.DBX0_0
PIW256 Peripheral input word at 256 PIW256 S7-1200 PLC_1.Inputs.PIW256
PQW512 Peripheral output word at 512 PQW512 S7-1200 PLC_1.Outputs.PQW512

Hardware and Firmware Verification

Before commissioning, verify the PLC, the network, and the Windows host simultaneously. A connection failure can be at any of these layers:

  1. PLC side: Open TIA Portal, online > accessible nodes, and confirm that the PLC responds on TCP 102. Note the firmware version (S7-1200 V4+ and S7-1500 require PUT/GET enablement).
  2. Network: ping <PLC-IP> from the AdvancedHMI host; tcping <PLC-IP> 102 (or PowerShell Test-NetConnection -Port 102) to verify the TCP path.
  3. Windows host: open PowerShell as Administrator and check the firewall with Get-NetFirewallRule | Where DisplayName -like "*Siemens*". If a deny rule exists, scope it before troubleshooting the PLC.
  4. Driver test: once Snap7 or libnodave is bound into AdvancedHMI, add a BasicValueDisplay linked to DB1.DBD0 and a PIDFaceplate linked to DB1.DBD4. Both should bind to the AdvancedHMI tag database on save.

Performance, Polling, and Connection Sizing

The wire-level traffic is dominated by polling. Each AdvancedHMI tag in poll mode becomes one S7 read request, unless the driver is configured to group reads by area, in which case one request can carry up to (PDU-length / 19) variable items.

PDU Length (bytes) Variable Items / Read Optimal for
240 ~12 S7-200
480 ~25 S7-1200/1500 default
960 ~50 S7-300/400 max

For a tag count N with average poll rate R Hz per tag, the full-cycle read consumes:

WireRate (bps) = N * R * ( Request(19B) + Response(19B + payload) )

For a flat 100-tag screen refreshing at 250 ms, with average payload of 4 bytes per tag, the line carries:

WireRate = 100 * 4 * (19 + 23) = 16,800 B/s ~= 135 kbps

This is negligible against a 100 Mbps Ethernet, but the CPU-side stress at the OB1 cyclic update ticks must still be considered: every read of an Input/Output/DB area forces the CPU's communication task to serialize the response with the scan. For S7-1200/1500, keep total S7 polling load under 5% of the scan time budget to leave headroom for PG, OP, and route connections.

Troubleshooting Matrix

Symptom Most Likely Cause First Diagnostic Step Corrective Action
Connection refused on TCP 102 S7-1200/1500 PUT/GET disabled Open TIA Portal > Properties > Protection Enable "Permit access with PUT/GET"
libnodave rc 0x0101 then 0x8104 CPU rejected because connection table full Check CPU diagnostic buffer for "max PG/OP reached" Reduce number of S7 clients or close inactive sessions
Snap7 rc 0xFFFF0115 TCP closed by remote (firewall reset) Inspect intermediate firewall and switch Allow ISO-on-TCP TCP 102 path; verify ACLs
OPC 0x80070005 Access Denied DCOM launch/activation permission missing Component Services > Security tab Grant "Remote Launch + Remote Activation" to OPC client user
Tag values are stale OPC subscription default refresh set too low Inspect group's UpdateRate Set UpdateRate = 100 ms, Deadband = 0
M area addresses M1.x, M2.x throw ArgumentException Original libnodave AdvancedHMI wrapper parser limitation Look at parseKey() boundary code Extend parser to multi-byte M offsets
Disconnect after 60s idle Keep-alive disabled on HMI host Wireshark observe FIN after 60s Enable TCP keep-alive or implement keep-alive S7 request every 10–20 s
Browses tags in OPC but quality = Bad Server cannot reach PLC Open the OPC server's diagnostics Address the underlying S7 reachability issue first
Reads return zeroes after writes Optimized DB access in S7-1500 prevents unconfirmed-bit read Check DB properties in TIA Portal Disable "Optimized Block Access" on the DB being read

Recommended Solution Pattern by Plant Profile

Plant Profile Recommended Path Rationale
Single S7-300 line, 10–50 tags, no existing OPC libnodave advanced-hmi wrapper Lowest deploy cost; zero server footprint
Multi-controller line with existing MES/OPC OPC bridge (KEPServerEX or SIMATIC NET) Single point of consumption, layered architecture
Brownfield S7-1200/1500 plant, IT-secured Snap7 with PUT/GET enabled and IP-restricted VLAN Modern library, no DCOM surface area
Regulated environment (FDA/GxP) OPC server with audit trail Auditable change of values, no custom DLL
Mixed Allen-Bradley + Siemens shop AdvancedHMI running against both drivers One engineering environment, two drivers

Migration Notes From libnodave to Snap7

Engineers supporting deployed libnodave-based integrations should plan a migration when the customer requires S7-1200 V4+ or S7-1500 firmware V2.5+ where the libnodave parser is brittle. The migration generally follows these data-flow equivalences:

  • daveConnectPLC()S7Client.ConnectTo(ip, rack, slot)
  • daveReadBytes(conn, area, db, start, len, buf)S7Client.ReadArea(areaCode, db, start, len, buf)
  • daveWriteBytes(conn, area, db, start, len, buf)S7Client.WriteArea(areaCode, db, start, len, buf)
  • daveGetError(rc)S7Client.Text(rc)

Address syntax M0.7 remains identical because both libraries speak the underlying S7 protocol; only the wrapper signatures change. This makes it practical to provide a parallel Snap7 driver during the proof of concept and retire the libnodave driver once the NewTek-style work is accepted.

Safety and Security Caveats

  • Never expose TCP 102 to the corporate public network. S7 is a clear-text, unauthenticated-by-default protocol and is the common vector for ICS/SCADA malware as documented in industrial cyber-incident reports published by CISA.
  • When enabling PUT/GET on an S7-1200/1500, restrict the connection at the firewall level to the known HMI/server hosts. Do not rely on PUT/GET alone for traffic authorization.
  • Lock the AdvancedHMI host with strong user ACLs so the OPC client cannot be redirected to an attacker-controlled server.
  • Where write-from-HMI is required for operator actions (start/stop, setpoint), implement a separate confirmation token handshake inside the PLC before the write executes.

Verification Checklist Before Site Acceptance

  1. Confirm tcping <PLC-IP> 102 succeeds from the HMI host with a sustained < 5 ms latency.
  2. Confirm every pollable tag rendered in AdvancedHMI displays its expected value within 2 update cycles.
  3. Execute a forced write-back test (operator sets a setpoint to mid-range and verifies the S7 tag updates).
  4. Force a CPU STOP/RUN cycle and observe the driver reconnect automatically (AdvancedHMI internal timers should perform a back-off retry between 2 s and 30 s).
  5. Disable the OPC server service and observe the documented error path in the AdvancedHMI status panel; restore service and confirm re-sync.
  6. Pull a Wireshark capture during a 60-second idle interval to verify periodic keep-alive is on the wire and no rogue TCP 102 traffic appears from other hosts.

Can AdvancedHMI talk directly to an S7-1200/1500 without an OPC server?

Yes, but only when PUT/GET communication is enabled in the CPU's protection settings and the driver wrapper uses Snap7 (recommended) or a patched libnodave. Drivers built against legacy libnodave typically fail on S7-1200 firmware V4.x and S7-1500 firmware V2.0+ with result code 0x8104 until PUT/GET is permitted.

Why does my M-area address above M0.7 fail with ArgumentException?

The reference libnodave-AdvancedHMI parser bundled in many community samples only accepts Mx.y where x = 0. You must extend the parseKey routine to accept multi-byte offsets. After the patch, addresses M1.0 to M2.7 and beyond resolve correctly because libnodave itself supports them; the limitation is purely in the C# wrapper.

How many AdvancedHMI clients can share one S7 CPU?

That is bounded by the CPU's OP and S7 connection resource counters, not by AdvancedHMI itself. S7-300 typically allows 4 OP + 16 S7 partners (16 total), while S7-1500 allows 16 OP + 16 S7 (32 total). Each AdvancedHMI instance consumes one OP or one S7 partner. When in doubt, consolidate clients behind a single OPC server to use only one slot.

What is the difference between libnodave and Snap7?

libnodave is the older open-source C library; it works reliably on S7-300/400 over ISO-on-TCP. Snap7 is a modern, actively maintained client/server library with a cleaner .NET binding, larger PDU default, and clearer error codes. For S7-1200/1500 firmware V4+ projects, Snap7 is the path of least resistance.

Why does my OPC client throw 0x80070005 against my OPC server?

That is Windows access-denied for DCOM. Open Component Services > Computers > My Computer > DCOM Config > your server. On the Security tab grant the OPC client user/group Remote Launch and Remote Activation. Also raise the default authentication to Connect on both ends. Adding the OPC service account to the local Distributed COM Users group typically resolves the error.

Can AdvancedHMI replace WinCC when communicating with Siemens PLCs?

AdvancedHMI can deliver comparable graphic and tag-binding function, but it does not ship deep integration with Siemens' alarm archive, audit trail, or recipe services. For applications that require GxP-style audit logging or TIA Portal HMI commissioning flows, keep WinCC or migrate to SIMATIC WinCC Unified. For basic visualization and operator panel applications, AdvancedHMI plus libnodave/Snap7 is a viable path.

Back to blog