1. Problem Definition
WinCC V7.x and V8.x SCADA Runtime on a PC station does not expose a built-in UDP listener for arbitrary external devices. Operators frequently need to receive short datagrams from third-party PCs, embedded controllers, custom C# services, or sensor gateways that only support connectionless transport (NMEA-0183, OPC UA Pub/Sub on UADP, TFTP, DNS, custom telemetry). Unlike the SIMATIC S7-1200 G2 Unified Panel or S7-1500 CPU with open user communication over UDP, the WinCC PC Runtime has no native open-communication channel for UDP datagrams. Integrators must therefore add a bridging layer, call the Windows WinSock2 API directly from a WinCC Global Script, or move to OPC UA Pub/Sub with broker discovery.
This article documents a working path that does not require the legacy add-on PM-OPEN TCP/IP (Siemens order number 9AE7105-1SS01-1AA0, last re-released in the SIMATIC HMI catalog for WinCC V6/V7 on Windows 7/Server 2008) and that survives the discontinuation of that add-on for current WinCC V7.4 SP1 and WinCC V8.0/V8.1 installations on Windows 10 IoT Enterprise LTSC 2019/2021 and Windows Server 2019/2022.
2. UDP vs TCP in a WinCC Context
UDP (RFC 768) provides connectionless, fire-and-forget datagram service. Before writing any code, the engineering decision between UDP and TCP must be revisited because the choice dictates the entire architecture.
| Property | UDP | TCP | Implication for WinCC |
|---|---|---|---|
| Connection state | None | SYN/ACK handshake | UDP listener can be started with one call; no client tracking |
| Delivery guarantee | None, datagram may be lost or duplicated | Acknowledged, ordered, retransmitted | UDP code must sequence-number and re-request on the application layer |
| Max payload | Theoretical 65 507 bytes minus IP/UDP header | Stream, no per-packet limit | WinCC tag types DWORD and STRING are sized to fit cleanly |
| Latency | Sub-millisecond on a LAN, no Ack roundtrip | Ack-clocked, 0.5 to 5 ms typical on LAN | Favours UDP for 100 Hz telemetry from a sensor |
| Firewall posture | Often blocked, stateless | Connection-tracked, usually open outbound | Requires an explicit Windows Defender Firewall inbound rule for the WinCC process or for the UDP port |
| Multicast / broadcast | Native (IGMP, subnet directed broadcast) | Not supported | UDP is the only option for OPC UA Pub/Sub UADP multicast |
If the remote side can speak TCP, prefer the SIMATIC S7-1200/1500 open user communication (TSEND_C/TRCV_C over ISO-on-TCP or TCP) and feed tags into WinCC through a standard S7 driver channel. Use UDP only when the remote side truly has no TCP stack, when multicast/broadcast is required, or when the protocol header (e.g. NMEA 0183 sentences or NTP) mandates UDP.
3. WinCC Runtime Architecture Constraints
WinCC RT is a 32-bit process (or 64-bit depending on installed version) on Windows 10 IoT Enterprise LTSC 2019/2021. The runtime core loads the Graphics Runtime, Tag Logging, Alarm Logging, and the Global Script RT engine. Each Global Script action (C or VBScript) executes in-process in the WinCC RT context. Calling Winsock2.dll or Ws2_32.dll from a C action is permitted, but the call is synchronous and blocks the WinCC scheduler for the duration of the blocking recvfrom() call. Direct consequences for the design:
- A blocking C action with a long timeout can starve the picture change cycle (default 2 s in WinCC V7, configurable via "Computer > Properties > Graphics Runtime > Cycle time").
- Global Script C actions run under the user account that started the WinCC RT, not under SYSTEM, so the firewall rule must allow that user, not just any service.
- VBScript does not have a native WinSock binding; use the C action editor for any socket code. VBScript is reserved for triggering, parsing, and writing to internal tags.
- WinCC RT is single-process; you cannot bind to the same UDP port from two C actions. Use a single global "UDP receiver" action and publish the parsed payload into internal tags.
On a 64-bit WinCC V8 installation, the C action is still compiled by the WinCC Global Script editor as 32-bit, so all pointer and sockaddr_in structures must follow the ILP32 model. Mixing WinSock 32-bit and 64-bit in the same project leads to stack corruption; this is the most common cause of an immediate WinCC RT crash on first datagram receipt.
4. Solution Matrix
| Option | Mechanism | Licensing | Suitable for current WinCC versions | Maintenance burden |
|---|---|---|---|---|
| PM-OPEN TCP/IP add-on | Native WinCC channel, internal tags | Separate license 9AE7105-1SS01-1AA0
|
Designed for V6/V7 on Windows 7; effectively out of support on V8 | Low if licensed |
| WinSock2 in C action (this article) | Global Script C, internal tag as mailbox | None | WinCC V7.0 SP3 to V8.1 | Medium, owner-maintained |
| External Windows service + OPC DA/UA | C# / C++ service exposes OPC UA server, WinCC connects | None (use free OPC Foundation .NET Standard stack) | WinCC V7.4+ with OPC UA channel | High, separate deployment |
| OPC UA Pub/Sub on UADP multicast | Publisher sends UDP multicast, WinCC TIA Unified subscribes | Requires WinCC Unified V16+ | WinCC Unified only, not classic V7/V8 | Low if both sides support it |
| SIMATIC S7-1200/1500 as bridge | CPU receives UDP, exposes tags, WinCC polls via S7 channel | S7-1200 G2 / S7-1500 open user communication licence-free | All WinCC versions | Low, reuses existing PLC |
The remainder of this article focuses on the WinSock2 C action path because it ships with WinCC, requires no extra license, and is portable from V7.0 SP3 to V8.1.
5. Prerequisites
- WinCC V7.0 SP3 or newer with the Global Script option licensed. Verify in SIEMENS Automation License Manager that the entry
WinCC/RCshows "Valid". - Administrative rights on the engineering station to compile C actions (WinCC creates a project-specific DLL under
<project>\library\). - Microsoft Visual C++ build environment that matches the WinCC C compiler (WinCC V7 uses MSVC 2010 toolset, WinCC V8 uses MSVC 2015/2017). The WinCC installation ships the required headers in
\Api\MSVC\. - An internal tag of type STRING of at least 256 characters, named for example
UDP_Payload. Create in Tag Management > Internal Tags. - A free UDP port agreed with the sender. Avoid ports below 1024 (privileged) and ports already used by SIMATIC NET, WinCC WebNavigator, or OPC UA.
50000is a common SCADA default;47899is used by some vendors. - A Windows Defender Firewall with Advanced Security inbound rule allowing the WinCC RT process to listen on the chosen UDP port. Create as follows:
wf.msc > Inbound Rules > New Rule > Port > UDP > Specific local ports: 50000 > Allow the connection > Profile: Domain / Private > Name: "WinCC UDP 50000".
6. Step-by-Step C Action Implementation
6.1 Create the global C action
In WinCC Explorer, right-click Global Script > C-Editor > Actions > Global Actions > New. Name the action g_udp_receiver. Set the trigger to "500 ms" in the property dialog; the action will be re-invoked even if recvfrom returned SOCKET_ERROR.
6.2 Include the WinSock headers
The WinCC C editor does not expose winsock2.h by default. Add the include path in the action header area:
#pragma code("kernel32.dll")
#pragma code("ws2_32.dll")
#include "apdefap.h"
#include "winsock2.h"
#include "windows.h"
#pragma code()
The two #pragma code directives force the linker to keep the import tables for kernel32.dll and ws2_32.dll; without them, the WinCC build will silently drop the WSA* calls.
6.3 One-shot Winsock initialisation
WSAStartup must run exactly once per process. Use a static flag initialised to zero at compile time:
static int g_wsa_ready = 0;
static SOCKET g_sock = INVALID_SOCKET;
static struct sockaddr_in g_local;
if (g_wsa_ready == 0) {
WSADATA wsa;
int rc = WSAStartup(MAKEWORD(2,2), &wsa);
if (rc != 0) {
printf("WSAStartup failed: %d\r\n", rc); /* visible in WinCC diag window */
return 0;
}
g_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (g_sock == INVALID_SOCKET) {
printf("socket() failed: %d\r\n", WSAGetLastError());
WSACleanup();
return 0;
}
/* SO_REUSEADDR allows quick restart after a crash */
int yes = 1;
setsockopt(g_sock, SOL_SOCKET, SO_REUSEADDR,
(const char*)&yes, sizeof(yes));
memset(&g_local, 0, sizeof(g_local));
g_local.sin_family = AF_INET;
g_local.sin_port = htons(50000);
g_local.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(g_sock, (struct sockaddr*)&g_local, sizeof(g_local)) == SOCKET_ERROR) {
printf("bind() failed: %d\r\n", WSAGetLastError());
closesocket(g_sock);
WSACleanup();
g_sock = INVALID_SOCKET;
return 0;
}
/* Set non-blocking so we never stall the 500 ms cycle */
u_long mode = 1;
ioctlsocket(g_sock, FIONBIO, &mode);
g_wsa_ready = 1;
}
6.4 Non-blocking receive and tag write
Read up to 1024 bytes per cycle, parse a known frame layout, and write to internal tags. Replace the parse block with your protocol:
char buf[1024];
struct sockaddr_in from;
int fromlen = sizeof(from);
int n = recvfrom(g_sock, buf, sizeof(buf), 0,
(struct sockaddr*)&from, &fromlen);
if (n == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK) {
/* Real error: log and reopen */
printf("recvfrom err: %d\r\n", err);
closesocket(g_sock);
WSACleanup();
g_wsa_ready = 0;
return 0;
}
return 0; /* No datagram this cycle, normal */
}
if (n < 8) return 0; /* header minimum */
/* Example frame: 4-byte magic "SCDA", 2-byte seq, 2-byte len, payload */
unsigned short seq, len;
memcpy(&seq, buf + 4, 2);
memcpy(&len, buf + 6, 2);
if (len > 240) return 0;
char payload[256];
memcpy(payload, buf + 8, len);
payload[len] = 0;
/* Write to internal tags - SetTagXxx are WinCC Global Script helpers */
SetTagChar("UDP_SourceIP",
(unsigned char)(from.sin_addr.s_addr & 0xFF));
SetTagWord("UDP_Seq", seq);
SetTagString("UDP_Payload", payload);
return 0;
6.5 Compile and assign
Press F7 in the C editor to compile. The output window must end with *** Compile finished ***; any error C2065 indicates a missing include path. Right-click the action and select Assign to > WinCC Runtime so the compiled DLL is loaded on RT start.
7. Sender-Side Cross-Check
Validate that the receiver works before blaming the network. From any Windows host with PowerShell:
$udp = New-Object System.Net.Sockets.UdpClient
$payload = [Text.Encoding]::ASCII.GetBytes("SCDA`x01`x00`x05`x00HELLO")
$udp.Send($payload, $payload.Length, "192.168.10.20", 50000) | Out-Null
$udp.Close()
From Linux, use nc -u 192.168.10.20 50000 followed by the payload bytes. Wireshark capture filter udp.port == 50000 shows the datagram and confirms source IP for firewall debug.
8. Verification Procedure
- Start WinCC RT in simulation mode (Start > WinCC Runtime). The
g_udp_receiveraction should print no errors in the WinCC diagnostic window (Apdiag.exe from the WinCC installation directory). - Send a test datagram with the PowerShell snippet above. Within 500 ms the internal tag
UDP_Payloadmust containHELLO. Verify in WinCC Tag Simulator or in a faceplate I/O field bound to the tag. - Confirm the tag status is
good; a value of0x00with statusbad / no connectionindicates the action is not assigned to RT or the cycle is not firing. - Capture a Wireshark trace on the WinCC host. The first datagram should produce an ARP request from the sender if the WinCC IP is unknown, then a UDP datagram. No response is expected (UDP is unidirectional).
- Force a fault by stopping the sender. The receiver should report
WSAEWOULDBLOCKcontinuously, notSOCKET_ERROR, andUDP_Seqmust retain its last value. - Restart the WinCC RT while the sender is firing. Within 2 s of RT start, a new datagram must be visible in the tag. If not, the firewall rule is missing or the port is held by a previous socket in
TIME_WAIT(mitigated by theSO_REUSEADDRoption in step 6.3).
9. Bridging a SIMATIC S7-1200 G2 with WinCC
If the remote side is a controller rather than a PC, the SIMATIC S7-1200 G2 (and the S7-1500) supports open user communication over UDP natively. The TIA Portal help system documents the instruction set under Communication > PROFINET > Open user communication > TCP, ISO-on-TCP and UDP. Configure a TCON connection with Connection type = 17 (UDP) and use TUSEND and TURCV in the user program to write the relevant process values into a DB. The WinCC side then polls that DB through a standard S7 channel; no WinSock code is required on the PC. This is the path to take whenever the sender is a Siemens PLC, because it eliminates a custom protocol and reuses the certified S7 driver. Refer to the TIA Portal help entry UDP open user communication in the S7-1200 G2 manual collection for the exact connection parameters.
10. Non-WinCC Reference: BRX Do-more UDP
For engineers who also maintain AutomationDirect BRX Do-more PLCs, the same pattern applies: the Do-more Designer "UDP Port Device" maps a UDP socket to a memory range that ladder logic can read like any other register. The video BRX Do-more PLC UDP Messaging shows the configuration end to end. The conceptual difference from WinCC is that the Do-more embeds the socket inside the CPU firmware, so the integrator only sees a configuration dialog; on WinCC the socket must be created in user code as shown above.
11. Error Code Reference
| WSA code | Decimal | Cause | Remediation |
|---|---|---|---|
| WSANOTINITIALISED | 10093 | WSAStartup not called or failed | Re-run the init block in the C action |
| WSAEADDRINUSE | 10048 | Port already bound by another process | Run netstat -ano -p udp | findstr 50000 and kill the PID; verify PM-OPEN is not running |
| WSAEACCES | 10013 | Attempted to bind to privileged port | Use a port > 1024 |
| WSAEWOULDBLOCK | 10035 | Non-blocking socket, no data | Normal, ignore |
| WSAECONNRESET | 10054 | Previous send triggered ICMP unreachable | Verify the sender IP and route; consider disabling WSAEACCES on the receive path |
| WSAETIMEDOUT | 10060 | Unusual for UDP, indicates stack corruption | Check 32/64-bit mixing in the C action |
12. Security Hardening
- Restrict the Windows Defender Firewall rule to the specific remote IP. Replace the broad Any IP address scope with the sender's IP to limit the attack surface.
- Validate the source port. Many real devices use ephemeral source ports, but well-known ports (e.g. 161 SNMP, 47899 vendor-specific) can be filtered in the parse block.
- Add a sequence-number check as in the example to drop replayed datagrams.
- Set
SO_RCVBUFexplicitly (e.g. 8 192 bytes) to limit the kernel queue size and prevent memory exhaustion from a malicious flood. - Log every WSA error to the WinCC alarm log using
MSRTCreateMsgso security events are auditable from the standard HMI alarm view.
13. Performance and Reliability Notes
The 500 ms trigger used in the example gives a practical throughput of 4 datagrams per second under the assumption that recvfrom drains the queue on every cycle. If the sender bursts more than 2 datagrams per cycle, the WinSock kernel queue fills (default 8 192 bytes) and datagrams are dropped silently. To increase throughput, lower the trigger to 100 ms and add a loop that calls recvfrom until WSAEWOULDBLOCK is returned, with a hard limit of 50 iterations to avoid monopolising the cycle. For sustained rates above 100 Hz, abandon the Global Script path and implement a dedicated Windows service that exposes an OPC UA server; WinCC connects via the OPC UA channel and the cycle trigger moves out of the SCADA layer.
For long-running installations, the Global Script C action survives project activation and deactivation but is reloaded on a complete WinCC RT stop/start. If the RT is restarted frequently, confirm that WSAStartup is called only once by the static g_wsa_ready flag; calling it twice returns WSASYSNOTREADY on the second call within the same process and is a common cause of the receiver working once and then failing on every subsequent RT start.
Can WinCC V7 or V8 subscribe to OPC UA Pub/Sub over UDP multicast?
No. WinCC Unified V16 and newer supports OPC UA Pub/Sub UADP multicast, but classic WinCC V7 and V8 do not. Use the Global Script WinSock2 path described above, or upgrade to WinCC Unified if Pub/Sub is a hard requirement.
Is the PM-OPEN TCP/IP add-on still available for WinCC V8?
Siemens order number 9AE7105-1SS01-1AA0 covers PM-OPEN TCP/IP for WinCC V6/V7 on Windows 7 / Server 2008. The package is generally out of stock and not updated for WinCC V8 on Windows 10 IoT Enterprise LTSC 2019/2021. Treat the add-on as legacy and use the C action approach for new projects.
Why does the C action crash the WinCC RT on the first datagram?
Almost always a 32-bit / 64-bit mismatch in the WinSock structures. The Global Script C editor compiles 32-bit even on a 64-bit WinCC V8 installation; ensure all sockaddr_in and WSADATA instances follow the ILP32 layout and that no external 64-bit DLL is linked in.
How do I make the receiver survive a WinCC RT restart without manual intervention?
Use the static g_wsa_ready initialisation pattern shown in step 6.3, set SO_REUSEADDR on the socket, and re-call WSAStartup only when the static flag is zero. The receiver will re-bind on the next cycle after a crash and resume accepting datagrams within the trigger interval.
Can I use VBScript instead of C for the UDP listener?
No. VBScript on WinCC has no native socket API and cannot import Ws2_32.dll. Use C for the binding and recvfrom loop, then call a VBScript helper through SetTagString for any complex parsing if required.