A redundant CODESYS PLC pair behind one floating IP
Two controllers, one address: CODESYS redundancy on WAGO PFC300s, a virtual IP that follows the active PLC, and an OPC UA struct that every HMI reads, so failover never shows up downstream.
The goal was a controller pair that fails over without any HMI noticing. That meant no client-side failover logic, no “which PLC is active?” lookups, and no second set of addresses to maintain. The finished system has two PLCs, one IP address and one data structure. Everything downstream talks to the address and reads the structure.
The layers
| Layer | What it is |
|---|---|
| Field network | Two WAGO Lean Managed Switches (852-1812) with three WAGO Modbus TCP fieldbus couplers (750-362) in an RSTP ring between them. Each PLC reaches both switches. |
| Controllers | 2× WAGO PFC300 (750-8302) running CODESYS 3.5 with the Redundancy add-on, active/standby. Sync runs over a dedicated direct cable on its own port and subnet. |
| Floating IP | A virtual IP that always sits on whichever PLC is active. |
| OPC UA | The active PLC’s built-in OPC UA server publishes one struct, GVL.Visu, on the virtual IP. |
| HMIs | An Ignition Edge Perspective project and a small Node.js dashboard, both running on a WAGO Edge PC (752-9400) and both independent OPC UA clients of that one endpoint. |
HMI (Ignition) HMI (Node.js)
\ /
OPC UA :4840 on the VIP
|
┌─────────┴─────────┐
PLC A ◄── sync ──► PLC B (VIP lives on whichever is active)
│ │
switch 1 ─────────── switch 2
└── RIO ─ RIO ─ RIO ┘ (RSTP ring)
Two design rules
- One source of truth, many readers. The PLC already knows the status of every device. It is also the only place that knows its own redundancy state. Exposing one struct means no HMI re-polls the field devices, and the redundancy state becomes visible to every client.
- Failover is invisible downstream. Every client connects to the floating IP. After a switchover, their OPC UA sessions reconnect to the same address and carry on.
The floating IP
CODESYS redundancy decides which controller is active, but it doesn’t move an IP address for you. A tiny program does it. It runs in its own task:
IF GVL.Visu.Redundancy.bIsActive THEN
sCmd := '/sbin/ip addr replace <vip>/24 dev br0 label br0:vip';
ELSE
sCmd := '/sbin/ip addr del <vip>/24 dev br0 2>/dev/null; true';
END_IF
SysProcess.SysProcessExecuteCommand(pszCommand := sCmd, pResult := 0);
It looks naive, and that’s deliberate:
- No state variables and no edge detection. CODESYS redundancy copies all PROGRAM-local variables from the active PLC to the standby every cycle. A
bWasActiveflag would inherit the old active PLC’s value across a switchover and never fire. Instead, both commands are idempotent and run every time. - It has its own task, at the lowest priority.
SysProcessExecuteCommandforks a process and blocks for roughly 50–100 ms. At a higher priority than the visualization task, it starved WebVisu badly enough to cause an “an error occurred” popup every 8–10 seconds. - CODESYS Visualization Redundancy stays disabled. Turning it on adds port-8080 redirects that break the single-address design.
Switchover from the HMI
The HMIs get a “Switch active PLC” button, which writes Redundancy.bRequestSwitchover over OPC UA. The PLC acts only on a rising edge, and only from a stable Active or Standby state. It then clears the request every scan, so one tap fires exactly once. A refused request sets bSwitchoverError, so the operator sees why nothing happened.
In testing, a switchover started from the HMI moved the roles and the virtual IP, and both HMIs’ sessions recovered by themselves.
OPC UA on a PFC: two switches in two places
This one catches most people who are new to WAGO PFCs:
- CODESYS Symbol Configuration decides what is published. Tick
GVL.Visuand all its members follow. After changing a type inside the struct, rebuild the symbol configuration explicitly. A build and download alone once left the published tree empty. - The controller’s web management page (WBM) → Fieldbus → OPC UA decides whether the server runs at all. It’s firmware configuration, not a CODESYS device object, so it doesn’t travel with the application. Set it on both controllers.
The server listens on every interface, so it answers on the virtual IP as soon as that address lands on the active PLC. The real acceptance test is a forced switchover followed by a browse on the virtual IP. A browse against the first PLC proves nothing about the second one.
Why OPC UA instead of the alternatives
| Option | Why not |
|---|---|
| HMIs poll the couplers directly over Modbus | They can’t see the redundancy state, and it doubles the load on the couplers. |
| PLC as a Modbus TCP server | A flat register map with no names or structure, which has to be maintained by hand for every new field. |
| OPC UA on the virtual IP | Named, typed and browsable, with subscriptions instead of polling. It follows the active PLC automatically. |
Lessons that cost real time
- Download to the active node, and check which one that is in CODESYS’s online view first. The standby gets the application over sync. “Download succeeded” doesn’t mean the controller behind the virtual IP changed.
- Gate Modbus client function blocks on
bIsActive. Otherwise both PLCs connect to the same devices. Keep the instances non-RETAIN so they cold-start cleanly on the new active PLC. - Know where the sync single point of failure is. One direct sync cable is simple and isolated, but cutting it sends both PLCs to Standalone, and recovering can need manual re-sync. Carrying sync as a VLAN over the RSTP-protected trunks removes that weak point, in exchange for more switch configuration.
- Losing the process network doesn’t trigger failover by itself. If the active PLC loses its field connection while sync stays up, it stays active. Test that case, and keep the HMI switchover button as the manual remedy.
- Renaming a published struct breaks clients you forgot about. Before renaming anything in it, search every project that ever copied a node path. This rollout found four consumers, one of them days later.
- The OPC UA server here runs without security, which is acceptable only because the PLC network is isolated and reachable solely through enrolled devices (see the remote access note). For a production deployment it’s the first thing to harden: signed and encrypted policies, certificates, and user authentication for writes.