Visual C# .NET to Siemens S7-300 Communication: OPC, Prodave, and Open-Source Paths
When porting an HMI or supervisory application from a Beckhoff TwinCAT environment to a Siemens S7-300 controller, the first engineering question is almost always the same: "How do I read and write PLC tags from a Visual C# .NET program over Industrial Ethernet?" Siemens offers two first-party toolkits (SIMATIC NET OPC Server and the Prodave/Comfort libraries) and the open-source community has produced a third path (Snap7, libnodave, S7.NetPlus) that often beats the licensed options on both cost and ease of deployment. This reference walks through every layer of the stack, the trade-offs of each option, and a working C# implementation path for both the licensed and open-source routes.
1. Problem Definition: Porting from TwinCAT ADS to SIMATIC S7
Beckhoff TwinCAT exposes the ADS (Automation Device Specification) protocol as a pure .NET class library (TcAdsDll, TcAdsApi). The application calls AdsClient.ReadAny, WriteAny, and AddDeviceNotification directly, with the TwinCAT router acting as a TCP broker on the engineering or target IPC. The mental model is "PLC = object, tag = property".
On a SIMATIC S7-300 the mental model is different. There is no in-process router and no first-party .NET class library for arbitrary tag access. Every PC-side path terminates on either:
- A Siemens-supplied server (SIMATIC NET, Prodave, WinCC) that exposes data through a Microsoft-defined interface, or
- A third-party or open-source implementation of the S7 communication protocol stack over ISO-on-TCP (port 102) or TCP (port 102 with TPKT).
The target functionality is identical: read a tag, write a tag, and ideally register for value changes. Only the transport and the abstraction layer change.
2. S7-300 Communication Stack: What Is Actually on the Wire
An S7-300 CPU with a PROFINET interface (CPU 31x-2 PN/DP) or a separate CP 343-1 Lean/Advanced provides the following external interfaces:
| Protocol | Transport | Port | Use Case | S7-300 Support |
|---|---|---|---|---|
| S7 Communication (S7Comm / Put/Get) | ISO-on-TCP (RFC 1006) | 102 | PG/HMI tag access | Yes (firmware ≥ V2.x) |
| Open User Communication (OUC / T-blocks) | TCP / UDP / ISO | configurable | Free-form partner messaging | Yes (CP 343-1 / CPU PN) |
| PROFINET IO | Real-time Ethernet | n/a | Distributed I/O | CPU 31x-2 PN only |
| MPI / PROFIBUS-DP | RS-485 / PROFIBUS | n/a | Legacy HMI | All S7-300 CPUs |
| Web Server (S7-300 firmware ≥ V3.2 on PN CPUs) | HTTP | 80 | Diagnostics pages only, not tag access | Limited |
For the C# → S7-300 link the relevant layer is the S7 Communication protocol (commonly called S7Comm or the Put/Get service). It runs on top of ISO-on-TCP (RFC 1006), uses TPKT/COTP framing, and exchanges user data through PDU types 0x32 (read), 0x05 (write), 0x32 with subfunction 0x04 (read SZL), and 0x00 (CPU services). The CPU must allow external Put/Get access — by default on a fresh S7-300 this is enabled, but a locked configuration in STEP 7 will block it silently with an 0x8104 error.
AddDeviceNotification. Any "change notification" implementation must be polled by the PC side, or implemented on the S7-400 with WinCC/PCS7 using the Symbolic-Triggered connection, which is a CPU feature (not a software feature) and is not available on S7-300. See SIMATIC S7-300 CPU 31xC and CPU 31x: Technical specifications for the per-CPU service list.3. Option A — SIMATIC NET S7 OPC Server (the recommended path)
The most flexible long-term answer is the SIMATIC NET S7 OPC Server bundled with the SIMATIC NET PC software DVD. The PC-side topology is:
3.1 Why OPC wins for multi-vendor fleets
If the same C# application is later expected to read tags from an Allen-Bradley ControlLogix, a Schneider Modicon M340, or a GE RX3i, only the OPC server (or the equivalent DA Server) needs to change. The C# client code stays identical because OPC Data Access 3.0 is vendor-neutral. This is the central reason the field report converged on OPC as the long-term answer.
3.2 License variants
| SIMATIC NET variant | Order number (MLFB) | Max S7 connections | OPC DA | OPC UA | Typical use |
|---|---|---|---|---|---|
| SOFTNET-IE S7 Lean | 6GK1704-1LW64-3AA0 | 8 | Yes | No | HMI / SCADA, single CPU |
| SOFTNET-IE S7 | 6GK1704-1LE64-3AA0 | 64 | Yes | No | Multi-CPU HMI |
| SOFTNET-IE S7 V15+ (OPC UA) | 6GK1704-1LE70-3AA0 | 64 | Yes | Yes | Modern firewalls, DCOM-free |
| CP 1616 / CP 1626 hardnet | 6GK1616-1AT01 / 6GK1626-1AA00 | ≥ 120 | Yes | Yes | High-availability / deterministic |
For a single S7-300 the SOFTNET-IE S7 Lean license is the correct minimum order. A working C# example against this server is published at the Siemens support entry SIMATIC NET: Programming an OPC DA Client with C# (.NET).
4. Option B — Prodave (in-process S7 library)
Prodave (current name: SIMATIC S7-PLCSIM / Prodave V6, historically distributed as Prodave MPI/IE) is a Windows DLL that exposes the S7Comm protocol directly to user code without a server. Variants:
| Prodave variant | Transport | Operating system | .NET callable |
|---|---|---|---|
| Prodave MPI/IE V5.6 | MPI, IE | Windows XP/7 32-bit | Yes (P/Invoke) |
| Prodave V6.0 (Win 7/10) | IE (ISO-on-TCP), MPI optional | Windows 7 32-bit only | Yes (P/Invoke, 32-bit host required) |
| LibProdave 6.2 (.NET wrapper) | IE | Windows 7/10/11 32 & 64 | Yes (managed, no P/Invoke) |
Prodave is officially callable from C/C++ and VB. A .NET caller must declare the functions with [DllImport], or use the third-party LibProdave 6.2 managed wrapper. There is no Microsoft-supported direct C# binding. For a Visual C# project this is a non-trivial constraint: the application must either be compiled as x86 to host the 32-bit Prodave DLL, or the managed wrapper must be sourced separately.
4.1 Prodave vs OPC decision matrix
| Criterion | SIMATIC NET OPC | Prodave / LibProdave |
|---|---|---|
| Deployment surface | 1 server + 1 client | 1 DLL in-process |
| DCOM configuration | Required for OPC DA | None |
| Multi-vendor support | Yes (swap server) | No (Siemens only) |
| .NET integration | OPC automation wrappers, OPC .NET API | P/Invoke or 3rd-party wrapper |
| Cost | Per S7 connection | Per PC license |
| Support contract | Siemens, global | End-of-life, partner-dependent |
5. Option C — Open-Source / Third-Party Native Libraries
For engineers who do not want a licensed server and do not want DCOM, the S7-300 S7Comm protocol has been reverse-engineered to a high degree of fidelity. The three notable stacks are:
| Library | Language | License | .NET wrapper | S7-300 tested |
|---|---|---|---|---|
| Snap7 (github.com/davenardella/snap7) | C / C++ | LGPL-3 | Sharp7 | Yes (CPU 312-319) |
| libnodave (Thomas Hergenhahn) | C | GPL-2 | Manual P/Invoke | Yes (older) |
| S7.NetPlus (github.com/S7NetPlus/s7netplus) | C# | MIT | Native | Yes |
Snap7 plus the managed Sharp7 wrapper is the current industry favorite for greenfield .NET integrations. It implements the S7Comm PDU types in-process, supports parallel partner connections, and is actively maintained (current stable 1.4.x). S7.NetPlus is a pure C# implementation that wraps the same protocol and is easier to ship in a single assembly — the S7.NetPlus NuGet package installs into any .NET Framework 4.6+ or .NET 6+ project without an external native dependency.
6. Change Notification: Why S7-300 Cannot Push
The field report explicitly raised "registering for variable changes". On Beckhoff this is a first-class feature (IAdsNotification). On SIMATIC the equivalent is split across two capabilities:
- S7-300 cyclic service — the PC (or server) is the polling master; the CPU returns the current value on each request. There is no CPU-initiated push.
- S7-400 "trigger by symbol" — only available on S7-400 CPUs (e.g., 416-3, 417-4) paired with WinCC or PCS7, and only for DB tags. The CPU detects a defined value change and pushes a single telegram; the WinCC station receives it without polling.
There is no analogue on the S7-300. A C# → S7-300 change-notification feature must be emulated on the PC side with a polling loop (typically 100–500 ms, fast enough to look like an event but slow enough to avoid overwhelming the S7Comm queue). The recommended pattern is:
- Open a single S7.NetPlus or Snap7 connection to the CPU (rack 0, slot 2 by default for S7-300 PN).
- Use a
System.Threading.Timeror async loop at the desired poll interval. - Read the data block(s) in bulk (a single PDU can carry up to 480 bytes of contiguous data).
- Compare to the cached copy; only raise the C# event when the value has actually changed.
7. Multi-Vendor Strategy: Adding Allen-Bradley ControlLogix
The original requirement mentioned a follow-on project using Allen-Bradley ControlLogix. The OPC choice preserves the C# code base because:
- Siemens path: SIMATIC NET S7 OPC server (DA 3.0) reads S7-300 DBs.
- Rockwell path: RSLinx Enterprise (now FactoryTalk Linx) exposes ControlLogix tags through the same OPC DA 3.0 interface, or through OPC UA.
- Universal path: Kepware KEPServerEX or Softing's OPC Router exposes both vendors in one server.
The C# client code is identical in all three cases because OPC DA 3.0 server enumeration (IOPCServerList) and item access (IOPCItemMgt, IOPCSyncIO) are vendor-neutral COM interfaces, and the .NET OPC Foundation wrapper (OPCAutomation or the newer OPC .NET Standard) consumes them uniformly.
8. Step-by-Step: Building a C# OPC DA Client for S7-300
Prerequisites:
- SIMATIC NET PC software 2022 or newer installed (v15.1, v16, v17, v18, v19 supported). The S7 OPC server is registered as a local COM service under Siemens.SimaticNET.OPCServer.DA.
- STEP 7 V5.7 (for S7-300) or TIA Portal V15+ (for newer projects) project with the CPU's PROFINET IP address, rack 0, slot 2.
- Visual Studio 2019/2022 with a C# project targeting .NET Framework 4.8 (OPC DA via COM interop) or .NET 6+ with OPC UA.
8.1 Add the OPC reference
- In Visual Studio choose Add Reference → COM → OPC Automation 2.0 (this is the wrapper provided by the OPC Foundation; SIMATIC NET installs it).
- If your project targets .NET 6 or later you cannot consume the classic COM wrapper directly. Use the OPC .NET Standard package (1.5.x) plus a UA wrapper on the server side, or fall back to the .NET Framework 4.8 project type for OPC DA 3.0.
8.2 Connect to the S7 OPC server
using OPCAutomation;
using System;
namespace S7OpcClientDemo
{
internal class Program
{
static void Main()
{
var opcServer = new OPCServer();
// Siemens ProgID as registered by SIMATIC NET
string progId = "Siemens.SimaticNET.OPCServer.DA";
string node = Environment.MachineName; // local OPC server
opcServer.Connect(progId, node);
Console.WriteLine("Server state: " + opcServer.ServerState);
opcServer.Disconnect();
}
}
}
8.3 Browse and add items
Items are addressed by the Siemens item ID convention: S7:[CPU_ip]DB<DBnum>,<byte_offset><type_letter><bit>. Example: S7:[192.168.0.10]DB1,REAL0 reads a 32-bit REAL at offset 0 of DB1.
OPCGroups groups = opcServer.OPCGroups;
OPCGroup group = groups.Add("Group1");
group.IsActive = true;
group.IsSubscribed = true;
group.UpdateRate = 250; // ms
OPCItems items = group.OPCItems;
int clientHandle = 1;
object itemIds = new object[] { "S7:[192.168.0.10]DB1,REAL0",
"S7:[192.168.0.10]DB1,REAL4",
"S7:[192.168.0.10]DB10,BYTE0" };
int[] serverHandles = new int[3];
int[] errors = new int[3];
items.Add((int)itemIds.Length,
ref itemIds,
ref clientHandle,
out serverHandles[0],
out errors[0]);
8.4 Read, write, and async data change
// Synchronous read
object values; object qualities; object timestamps;
items.Read((short)serverHandles.Length,
serverHandles,
out values,
out qualities,
out timestamps,
out errors);
// Asynchronous subscription (closest analogue to TwinCAT AddDeviceNotification)
group.DataChange += OnGroupDataChange;
private static void OnGroupDataChange(int transactionId, int numItems, ref Array clientHandles,
ref Array itemValues, ref Array qualities, ref Array timestamps)
{
for (int i = 1; i <= numItems; i++)
{
Console.WriteLine($"handle={clientHandles.GetValue(i)} val={itemValues.GetValue(i)} ts={timestamps.GetValue(i)}");
}
}
OpcEnum (port 135 + dynamic RPC), and run both sides under matching user accounts. Modern projects should pick the OPC UA variant of SIMATIC NET to skip DCOM entirely. See SIMATIC NET: OPC UA configuration in the S7 OPC UA server.9. Step-by-Step: Using S7.NetPlus (open-source) from C#
This is the path that avoids any Siemens license when the project is single-vendor and the same C# code is the only consumer.
9.1 Install the package
dotnet add package S7NetPlus --version 2.2.0
9.2 Connect and read
using S7.Net;
using S7.Net.Types;
using var plc = new Plc(CpuType.S7300,
"192.168.0.10",
0, // rack
2); // slot (CPU 31x-2 PN default)
plc.Open();
if (plc.IsConnected)
{
float temperature = plc.Read("DB1.DBD0"); // REAL at DB1 byte 0
bool valveOpen = plc.Read("DB1.DBX10.0");
ushort word = plc.Read("DB10.DBW2");
Console.WriteLine($"{temperature} {valveOpen} {word}");
}
9.3 Write a value
plc.Write("DB1.DBD0", 23.5f); // REAL
plc.Write("DB1.DBX10.0", true); // BOOL bit
plc.Write("DB10.DBW2", (ushort)42);
9.4 Emulated change notification (polling)
var lastSeen = new Dictionary<string, object>();
var timer = new System.Threading.Timer(_ => {
float t = plc.Read("DB1.DBD0");
if (!lastSeen.TryGetValue("temp", out var prev) || !prev.Equals(t))
{
lastSeen["temp"] = t;
OnTemperatureChanged?.Invoke(t);
}
}, null, 0, 250);
10. S7-300 Hardware Sizing for PC Communication
When the S7-300 is in the cabinet and a new C# application will be added, verify the following before commissioning:
| Item | Check | Acceptance |
|---|---|---|
| Connection resources | PG/OP connections configured in STEP 7 | At least 1 free for the C# client (max 16 on CPU 315-2 PN) |
| Put/Get permission | CPU properties → Protection → Permit access with PUT/GET | Enabled (default on, but can be locked) |
| CP firmware | CP 343-1 firmware ≥ V2.0 | Supports S7 communication |
| DB accessibility | DB optimized block attribute | Must be non-optimized (standard access) for S7Comm byte-offset addressing |
| Cycle / OB1 | Max cycle time vs. requested poll rate | Poll rate ≥ 2× OB1 cycle to avoid blocking the PG queue |
| Network | CP 343-1 IP, subnet, gateway | Reachable from the PC, no managed switch storm control |
11. Performance, Timing, and Error Codes
11.1 Typical round-trip times
| Path | Typical read latency (single tag, 100 Mbps LAN) | CPU load impact |
|---|---|---|
| SIMATIC NET OPC — single-item read | 15–40 ms | Low (1 PDU per request) |
| SIMATIC NET OPC — subscribed group (10 items, 250 ms) | ~250 ms effective | Low (10 PDUs / 250 ms) |
| Prodave single read | 3–8 ms | Low |
| Snap7 / Sharp7 single read | 3–10 ms | Low |
| S7.NetPlus single read | 4–12 ms | Low |
| Bulk PDU read of full DB (240 bytes) | 6–15 ms | Lowest per-byte cost |
11.2 Common S7Comm error codes
| Error byte | Meaning | Likely cause |
|---|---|---|
| 0x8104 | No connection / wrong slot | CPU not reachable on IP/port, or rack/slot mismatch |
| 0x8105 | Unknown PDU | Old CPU firmware; update CPU or CP |
| 0x8304 | Read denied — object does not exist | DB number wrong, or DB optimized-block attribute set |
| 0x8402 | CPU in STOP | CPU is not in RUN; PLC diagnostics required |
| 0x8404 | Sequence error | Re-establish connection |
| 0x8500 | Wrong PDU size | Reduce request to ≤ 480 bytes per PDU |
| 0xD401 / 0xD402 / 0xD403 | Function not allowed / invalid param / ISO-on-TCP resource | Put/Get not permitted, security level wrong, or CP resources exhausted |
12. Verification & Diagnostics
-
Layer 1–2: From the PC,
ping 192.168.0.10. Usearp -ato confirm MAC learning on the switch. -
Layer 3: From the PC open TCP/102 with
Test-NetConnection 192.168.0.10 -Port 102(PowerShell) ornc -vz 192.168.0.10 102. A successful connect proves the CP 343-1 is accepting ISO-on-TCP. - STEP 7 online: Open the S7 project online, expand Accessible nodes, confirm the CPU appears. This proves that the same connection budget the C# application will use is functional from STEP 7.
- OPC ping: The SIMATIC NET OPC Scout tool (Start → SIMATIC → SIMATIC NET → OPC Scout) browses the server, adds a single item, and forces a read/write. If OPC Scout works and the C# client does not, the problem is in the C# client, not the network or the S7-300.
-
End-to-end smoke test: In the C# application, read a known DB byte (e.g.
DB1.DBB0set to a constant in OB1) and assert the value matches.
13. Troubleshooting Matrix
| Symptom | First check | Most likely cause | Fix |
|---|---|---|---|
| OPC Scout shows "Quality: Bad" | CPU online diagnostic buffer | Connection rejected, Put/Get disabled | Enable Put/Get in CPU properties, download HW config |
C# client receives 0x8104
|
Ping + port 102 | Firewall or wrong IP | Open TCP/102, verify CP IP |
| Data read is constant 0 / always old value | STEP 7 monitor on the DB | Optimized DB access | Uncheck "Optimized block access" |
| Tag values are scrambled bytes | Byte order | S7 is big-endian, C# is little-endian | Use BitConverter.ToSingle(BitConverter.GetBytes(value), 0) or library conversion |
| DCOM error 0x80070005 in C# | dcomcnfg | Access denied across machines | Configure DCOM security, use matching accounts, or switch to OPC UA |
| OPC server disconnects every ~30 s | Keep-alive setting | PC network adapter power saving | Disable NIC power management |
| Snap7 / S7.NetPlus reports "ISO: Invalid PDU" | PCAP/Wireshark | Router or firewall modifies TPKT | Use direct PC↔CP connection for test |
| CPU diagnostic buffer: "Connection broken, local ID xxxx" | Connection resources | More than 16 S7Comm connections requested | Reduce concurrent clients or upgrade to CPU 319 PN/DP |
14. Selection Flowchart
15. Field-Engineer Notes
- License of the OPC server is per S7 connection, not per PC. If the C# application reads 4 different S7-300 CPUs, count the OPC connection licenses, not the number of running clients.
- Symbolic addressing on S7-300 with S7Comm works only on firmware that supports it (V3.x on 31x-2 PN, V2.x on CP 343-1 V2.x). On older firmware the address must be absolute (DB number + byte offset).
- DCOM is the #1 reason OPC projects fail late. If you can choose OPC UA from the start, choose it. The SIMATIC NET variants supporting OPC UA are the V15+ SOFTNET-IE S7 and the newer SIMATIC NET 2022 editions.
- Snap7 + Sharp7 is the path of least resistance for a single-vendor, license-free .NET deployment and is the de-facto standard for small SCADA / data-acquisition projects talking to S7-300 / S7-400 / S7-1200 / S7-1500.
-
Do not attempt to emulate
AddDeviceNotificationwith sub-second polls. S7-300 OB1 cycle plus the S7Comm queue cannot absorb < 50 ms polling from multiple clients. Stay at 200–500 ms per subscription group.
What is the minimum license to read S7-300 tags from a C# OPC client?
For a single CPU, order SIMATIC NET SOFTNET-IE S7 Lean (MLFB 6GK1704-1LW64-3AA0). It exposes the Siemens.SimaticNET.OPCServer.DA ProgID and supports up to 8 S7 connections. For a multi-vendor fleet, the V15+ SOFTNET-IE S7 with OPC UA (6GK1704-1LE70-3AA0) is the modern choice and removes DCOM.
Can an S7-300 push tag changes to the PC the way TwinCAT does?
No. Tag-change subscription is a CPU feature of the S7-400 family running WinCC/PCS7 only. On S7-300 the PC must poll, typically at 200–500 ms, and raise the C# event only when the polled value differs from the cached one. This emulates change notification at the application layer.
Why does my C# client read constant 0 from a DB that clearly has data in STEP 7?
Most often the DB has the "Optimized block access" attribute set in TIA Portal, which hides the byte layout. S7Comm (S7.NetPlus, Snap7, OPC) addresses by absolute byte offset, so it cannot find the data. Uncheck the optimized flag in the DB properties, recompile, and download the blocks to the CPU.
Prodave or OPC — which is faster to commission?
For a single-vendor, single-PC project, Prodave (or its managed wrapper LibProdave 6.2) is faster: it is an in-process DLL with no DCOM, no server, and one line of code to read a tag. For any project that may grow to Allen-Bradley, Modicon, or remote PC clients, OPC is the correct choice because the C# client code is vendor-neutral and survives hardware migrations.
How many S7-300 connections can my C# application use?
An S7-300 CPU 315-2 PN/DP allows 16 active S7 connections by default. Each PG, each HMI, the SIMATIC NET server (if used), and the C# application each consume one. If you exceed the budget the CPU drops new connection requests with diagnostic-buffer entry "connection resources exhausted". Switch to a CPU 317-2 PN/DP or 319-3 PN/DP for higher limits, or use a CP 343-1 with its own connection pool.
Is the open-source S7.NetPlus library production-safe?
For non-safety, non-silver-certificate HMI and data-acquisition use, yes. The library is MIT-licensed, has been used in industrial installations for over a decade, and is the same protocol stack (S7Comm over ISO-on-TCP/102) that SIMATIC NET uses. For safety-relevant control, retain the licensed SIMATIC NET path or the safety-relevant Profibus/PROFIsafe stack.