Check if a CODESYS runtime is running or stopped

5 min read · Published November 2, 2026

Poll the CODESYSControlService Windows service for the runtime process, and count an IEC heartbeat tag for the application. Service up plus a frozen heartbeat means the application is stopped; no connection means the runtime is down. Poll every 3 s and alarm after 3 unchanged reads.

Treat the question as two checks, not one. Layer one is the host: on a Windows-hosted CODESYS runtime, the CODESYSControlService entry in the service list appears while the runtime is started and leaves when it stops, and that change can happen with no Windows restart, so the check has to run on a poll. Layer two is the application: a free-running heartbeat counter in the IEC 61131-3 program that only moves when the task scans. Poll both every 3 s. Service running plus a frozen heartbeat means the application is stopped while the runtime is up. No connection at all means the runtime or the network is down. Together the two layers give a state you can alarm on within about 9 s of a stop.

Two states, not one

The layers answer different questions, and a watcher that merges them reports false health.

Layer Question it answers What looks identical from there
Windows service state Is the runtime process up on the host? An application in RUN, an application in STOP, and no application downloaded
Heartbeat tag Is the application scanning? Host health, disk, and the network between collector and host

The service layer is the coarse one and it is free. On these installs the service is installed to start by default, and the process entry tracks the runtime, coming and going without a reboot. That is the observation to build on, and it is why the check must poll and never cache an answer read once at startup.

What you need

  • Read access to the Windows host, or an agent there, able to run sc or PowerShell.
  • One published tag on the channel your collector already reads: OPC UA, Modbus TCP or a database table.
  • The exact service name as installed on that host.

Query the service

  1. Find the exact name once, on the target host. Service and executable names vary with the runtime variant and version:
Get-Service | Where-Object Name -like "*CODESYS*"

Hard-code the name you find. This article uses CODESYSControlService.

  1. Poll it from the collector and parse the state line:
sc query CODESYSControlService | find "STATE"

Running prints STATE : 4 RUNNING. Stopped prints STATE : 1 STOPPED. An absent service returns a failure message instead; treat absent the same as stopped, because on these installs the entry leaves the list when the runtime stops.

  1. Make the start type explicit if the host must come back on its own after a reboot:
sc config CODESYSControlService start= auto

The space after start= is required; without it the command parses wrong. To restart the service 60 s after a crash, with the failure counter reset after 86400 s:

sc failure CODESYSControlService reset= 86400 actions= restart/60000

A shortcut in the startup folder adds nothing here. The service already starts by default, and the config above makes that deterministic.

Add a heartbeat to the application

The only proof that program logic is scanning is a value that moves because the logic scanned. Put this in a task with a known cycle time and publish the tags on the channel your collector already reads, the same path described for logging PLC variables to a database from CODESYS:

PROGRAM PRG_Heartbeat
VAR
    wHeartbeat   : WORD;    (* published tag *)
    dwCycleCount : DWORD;   (* increments every scan *)
    tonPulse     : TON;
END_VAR
dwCycleCount := dwCycleCount + 1;
tonPulse(IN := NOT tonPulse.Q, PT := T#1S);
IF tonPulse.Q THEN
    wHeartbeat := wHeartbeat + 1;   (* wraps at 65535 *)
END_IF

Set the poll interval to at least three times the heartbeat period, so one missed sample raises no alarm. With the 1 s pulse above, that means a 3 s poll and an alarm after 3 unchanged consecutive reads. The counter wraps at 65535 and that is fine: the collector tests whether the value changed and never reads it as a level.

What three consecutive polls see Report
Value changed each poll Running, application scanning
Value frozen, connection open Application in STOP, no boot application, or a task exception
Connection refused or timeout Runtime down or a network fault, cross-check the service query

Read the two answers together

Combine both into one state word so the diagnosis is unambiguous at the dashboard:

Service query Heartbeat Diagnosis First action
Running Changing every poll Healthy None
Running Frozen Application stopped, no boot application, or a task exception Go online, read the application state and the runtime log
Running No connection Communication server down, firewall, or wrong endpoint Check the listening ports and firewall on the host
Stopped or absent No connection Runtime down Start the service, read the Windows event log for the reason
Stopped or absent Changing Wrong host, or a second runtime instance answering Verify the endpoint, look for duplicate instances

The frozen heartbeat with the service up is the case people miss, and it is the one that costs scrap. The faults behind it are covered in PLC access violation troubleshooting for sporadic runtime faults.

Recommendation: run both checks on every collection cycle. Query the service to separate a down runtime from a stopped application, and read the heartbeat to catch what the service state cannot show. Poll at 3 s and alarm after 3 unchanged reads; with a 1 s heartbeat that meets any written detection budget of 10 s or looser. Tighten to a 500 ms heartbeat and 1 s polls only when a budget sits below 10 s.

Example: a 15 second detection budget

A filler line wants a stopped controller flagged on the line board within 15 s. The heartbeat pulses at 1 s, the collector polls at 3 s, and the alarm needs 3 consecutive equal reads. Worst case, the application stops immediately after a poll has picked up a fresh value. The next polls land at +3 s, +6 s and +9 s, all returning the frozen number, so the alarm fires on the third poll, 9 s after the stop. That leaves 6 s of the 15 s budget for the board to refresh, and the requirement is met with margin.

Now tighten the same line to a 5 s budget: set PT := T#500MS, poll at 1 s, keep 3 samples. Worst case becomes 3 x 1 s = 3 s, leaving 2 s for the board. Decision: 1 s heartbeat with 3 s polls for budgets of 10 s and looser; 500 ms heartbeat with 1 s polls for anything tighter.

Put the state where people see it

Publish the combined state word on the channel your dashboards already read. A frozen heartbeat in a log is one thing; on the line it should be a red banner. How a screen like this compares with a full dashboard is covered in live factory data display, and small status screens for the shop floor sit in the factory screens section.

Check it works

  1. With the runtime started, run the service query and confirm STATE : 4 RUNNING.
  2. Stop the runtime from the development system and re-run the query within 5 s. Confirm the state flips to STOPPED or the entry is absent, and record which behavior your install shows. The parser must handle both.
  3. Start the runtime again and confirm the entry returns with no Windows restart in between.
  4. Download the application and set it running. Confirm the heartbeat changes on three consecutive polls through the collector channel, in the development system's online view as well.
  5. Put the application in STOP with the runtime still running. Confirm the collector reports the frozen state inside the 9 s window.

Questions

Why does CODESYSControlService disappear from Task Manager without a restart?

Because on these installs the process entry tracks the runtime: it is present while the runtime is started and leaves the list when the runtime is stopped, with no Windows restart involved. Poll for it instead of caching the answer when your collector starts. If your runtime was launched as a console or tray application instead of a service, watch the process name the same way.

How do I make the application start in RUN when the runtime starts?

Download the application and set it as the boot application in the development system; the runtime then loads it at startup. If it still comes up stopped, read the runtime log for the stop reason before downloading again. Until a boot application is in place, expect the heartbeat to start frozen, which the collector correctly reports as stopped.

The heartbeat is a WORD. What happens at the wrap?

The counter reaches 65535, wraps to 0 and keeps counting, which at one increment per second takes about 18 hours per pass. Detection compares consecutive polls for change, so a wrap never reads as a stop. If you want no wrap at all, use a DWORD, which at one tick per second runs about 136 years.

Planning information, not legal or engineering advice. Rules differ by place: check codes and permits with your local authority and your licensed trades.

Keep reading

Build your signAsk for a quote