Zeus PLC IDE — Help

Program, download and monitor a Zeus soft‑PLC controller (Raspberry Pi CM4/CM5) in IEC 61131‑3 Structured Text, Ladder and SFC.

This page is the quick help. The full Operation Manual — step‑by‑step procedures, the complete Structured Text reference, every built‑in function and library block, limits, and worked example projects — is a PDF: open the Operation Manual.

Overview

The Zeus PLC IDE runs in your browser and talks to a Zeus controller (or the built‑in simulator). You write control logic as programs (POUs), configure the IO hardware, download to the controller, and watch it run online. Everything the controller runs is compiled from your project; the controller itself runs a small real‑time scan engine.

Key concepts

TermMeaning
ProjectEverything on disk for one machine: programs, global variables, the IO/device tree, HMI screens, controller settings. One project can run on several controllers.
Controller / TargetThe device the IDE connects to and downloads to — a real CM4/CM5, or the local Simulator.
POUProgram Organisation Unit: a PROGRAM, FUNCTION_BLOCK or FUNCTION, written in ST or drawn as Ladder.
ScanThe controller reads inputs → runs the program → writes outputs, over and over at the task cycle time.
Process imageThe IO memory: %I inputs, %Q outputs, %M internal memory.
Program vs Hardware downloadDownload program sends the logic without stopping the loop. Download hardware sends the IO configuration and restarts the runtime (briefly stops control).

Getting started

New to the IDE? Project → Getting started tutorial… is a 10-minute guided tour that ticks itself off as you go: open an example, build it, run it in the simulator, watch it live, force an input and see it as ladder. Project → Open an example project… opens the nine worked examples of the Operation Manual (Appendix G) - each runs in the simulator as it is.
  1. Project… → open or create a project.
  2. Target… → choose the Simulator (safe) or your controller.
  3. Add IO: IO Devices → Add device, or Scan for RIO… to discover real hardware.
  4. New program → write ST, or choose Ladder Program (LD).
  5. Debug → Build (F7) to compile; fix any red messages.
  6. Debug → Download program (F8) — or Download all the first time (sends hardware + program).
  7. Debug → Online (◉) to watch live values.

Opening and closing the IDE

Start the IDE with Zeus PLC IDE.cmd (or its shortcut). It opens in its own window; no console window is left open. Closing the window does not stop the IDE server: it keeps running in the background, so the next start opens instantly. To stop it completely, run "Zeus PLC IDE.cmd" stop (status shows whether it is running). The controller is not affected either way — it keeps running its program whether or not an IDE is connected.

New to the controller? Do everything against the Simulator first — it runs the same binary and the same addresses, with no risk to real outputs.

The interface

Top toolbar

ItemWhat it does
Project…Open / create / switch project.
Save project (Ctrl+S)Saves unsaved programs and globals. The device tree/rack are saved as you edit them.
Save As…Save the active editor under a new name.
Debug ▾Build, Download & Upload, Online, login, run/pause/step, verify, forces — see below.
Import ST…Import from PLCopen XML or a text export (.exp/.export).
Browse IO… (Ctrl+Space)Browse IO addresses and insert a declaration.
? HelpOpens this manual.
⟳ Check for updatesAsks optizeus.org whether a newer IDE release is out and answers in a window. While one is waiting, the blue ⬆ Update 0.1.x.y button takes its place - see Updates.
Target…Choose which controller to connect/download to.

Status badges (top‑right)

Device tree (left)

The tree is the project: the board and process image, Application (POUs, Task Configuration, Global Variables, HMI, Library), IO Devices, Network Interfaces, Modbus TCP Slave, OPC UA Server, Users & Roles, Controller Settings, and the Simulator. Right‑click nodes for actions.

POUs & editors

Find and replace

Errors in the code

After Build or Build All each message is also shown in the code: a wavy underline (red = error, amber = warning) under the word, and the message at the end of the line. Clicking a message in the Messages panel opens the right POU at the right line - including errors the controller reports on a download. The underlines go as soon as you edit. Build All builds what you see: an open POU with unsaved edits is built from the editor. It compiles several POUs at once, so a project of a few dozen POUs takes a few seconds.

Task configuration

Task Configuration → MainTask sets the cycle time (how often the program runs), the real‑time priority, and which program(s) the task calls. A shorter cycle reacts faster but leaves less CPU headroom; watch the overrun counter online.

Programs at different rates

Every PROGRAM in the project runs on every scan by default, top to bottom. A controller usually wants otherwise: an interlock at 10 ms beside a totaliser that has no business running a hundred times a second and costs real time when it does.

Task Configuration → Properties lists the programs with a period each. A period must be a whole multiple of the task period — 25 ms on a 10 ms scan is refused, naming 20 and 30, rather than being rounded to something nobody chose.

"task": {
  "period_ms": 10,
  "programs": [
    { "name": "Reporting", "period_ms": 1000, "offset": 3 },
    { "name": "Totaliser", "period_ms": 1000, "offset": 7 }
  ]
}

Offsets stagger them. Two 1-second programs on a 10 ms scan otherwise land on the same cycle, and that one scan in a hundred carries the cost of both — which shows up as jitter nobody can account for.

These are rates of one clock, not separate threads. A slow program still runs inside the scan that triggers it, so its execution time lands on that cycle. The trade is deliberate: preemptive tasks would need the process image locked or copied per task, and nothing on the cyclic path here takes a lock. It also means two programs can never see a half-updated view of each other — the failure nobody debugs successfully.

A program that sits out a scan loses nothing: its variables, timers and function-block instances are exactly as it left them, because a skipped cycle is no different to it than the gap between two scans. The controller reports the list it is really running, so a period named for a program that was renamed shows up as scheduling nothing rather than failing silently.

Library versions

A library file declares itself in a header comment:

(* @library zeus-control @version 1.4.0 *)

Library → Versions… shows what the library is against what this project was locked to, and Lock this library records it — a version and a SHA-256 per file, with who locked it and why.

The hash is what makes the version trustworthy. A version number is a claim; an edited file that kept its number is the case worth catching, and it is the one that looks correct on every other screen. A block that has been deleted is reported as REMOVED rather than quietly dropped, because the alternative is unknown type 'PID' at the next compile.

Global variables

The Global Variables file (globals.st) holds VAR_GLOBAL … END_VAR and is prepended to every POU on Build and Download. Use a global anywhere by name — no re‑declaration.

VAR_GLOBAL
    machine_state : INT;
    e_stop_ok     : BOOL;
END_VAR
VAR_GLOBAL RETAIN         (* survives a power cycle *)
    batch_number  : DINT;
END_VAR
A VAR_GLOBAL block put at the top of a normal POU file is visible only to POUs in that file. For globals shared across the project, use the Global Variables file. After editing it, Download for the change to take effect.

Data types (DUT)

Application → Data Types (DUT) holds your own types — a structure, an enumeration or an alias. Right‑click → New DUT…, choose the kind and name it. Each one is saved as dut/<Name>.st and compiled ahead of the global variables and every POU, so any of them can use it.

TYPE Door :
STRUCT
    Name    : STRING;
    Open    : BOOL;
    Closed  : BOOL;
    Travel  : TIME := T#8S;
END_STRUCT
END_TYPE
TYPE DoorState : (Idle, Opening, Opened, Closing, Faulted); END_TYPE

Use it like a built‑in type:

VAR_GLOBAL
    doors  : ARRAY[1..4] OF Door;
    state1 : DoorState := DoorState.Idle;
END_VAR

doors[2].Open := %IX0.3;
doors[3] := doors[2];              (* whole-structure copy *)
IF state1 = DoorState.Opening THEN ... END_IF

Projects, archives & data

A project is a directory, not a file: controller.json, globals.st, pou/, dut/, lib/, hmi/ and data/. Project menu:

CommandWhat it does
Save project (Ctrl+S)Writes every unsaved program and the globals. The device tree and rack are written as you edit them, so they are already saved.
Save As… (POU)Copies the active POU to a new name. Enabled only on a POU tab — the globals, task and HMI are single project files.
Save project as…Copies the whole project to a new directory and opens the copy; the original is left as it was. Unsaved editors are saved first. backups/ and uploads/ stay with the original — they are its history, not the copy's.
📦 Create archive…Packs the project into one .tar.gz for a handover, a validation file or an off-site copy.
📂 Restore archive…Unpacks an archive into a new directory and opens it.
An archive is plain tar + gzip: tar xzf or 7‑Zip opens it years from now without this IDE. It carries a manifest (zeus-archive.json) naming the project, the date, the IDE version and a SHA‑256 per file, and the restore compares every file against it — so a damaged archive is reported rather than half‑restored. backups/ is excluded; it is usually most of the size.
Restore refuses a directory that already holds files. An archive never overwrites a project that is in use.

The controller's data travels with the program

Source alone does not bring a machine back: setpoints, recipe parameters and tuning constants live in the running controller. So, whenever a controller is connected:

Only variables the program reads but does not write hold — setpoints, mode selectors, recipe parameters. Anything the program computes each scan reverts on the next one, and hardware‑mapped inputs cannot be written at all. Those are listed as skipped, together with values that already match and variables that cannot be written, so a restore sends only what can actually change.

Library & methods

The Library holds reusable function blocks (motors, valves, alternation, trip logic, signal scaling…) that are compiled ahead of your program, so you can call them from any POU. A FUNCTION_BLOCK can also have methods (Add method…). Call a block by declaring an instance and invoking it:

VAR  m1 : MOTOR_DOL;  END_VAR
    m1(run_cmd := start AND e_stop_ok, fb_running := aux1, reset := ack);
    motor_out := m1.run_cmd;   (* read the block's outputs by name *)

HVAC & control library blocks

Application blocks in lib/40_hvac.st and lib/22_autotune.st, built on the library PID. They are FUNCTION_BLOCKs (they hold state across scans). Outputs come as both a percentage (_Eu, 0–100 %) and a scaled raw analog value (_Ao, using Ao_low/Ao_high).

PID — closed-loop control (the building block)

A single feedback loop with anti-windup. reverse := TRUE when more output lowers the measurement (cooling, level draining); man := TRUE holds man_out and tracks for a bumpless return; enable := FALSE parks it at out_min. Tune with kp, integral time ti_s (s, 0 = off) and derivative td_s (s, 0 = off) — or let PID_AutoTune below find them.

VAR heat : PID; END_VAR
    heat(pv := TT_TEMP.EU_Value, sp := 22.0,
         kp := 3.0, ti_s := 90.0, td_s := 0.0,
         out_min := 0.0, out_max := 100.0,
         reverse := FALSE, enable := run);      (* heating: more output = warmer *)
    valve_ao := REAL_TO_INT(heat.out * 273.0);  (* 0..100 % -> 0..27300 raw *)
    (* heat.err, heat.sat (at a limit), heat.integral are exposed for diagnosis *)

TempHumidityCoolReheat — cooling + heating coil, humidity reduce‑only

One air handler, two transmitters (temperature and humidity/dewpoint), two valves. Temperature is one split‑range PID: above mid‑scale the heating coil opens, below it the cooling coil opens — it heats or cools, never both. Humidity is a reduce‑only, reverse‑acting loop: opening the cooling coil condenses moisture, so its output is an extra cooling demand. The cooling coil follows the greater of the two; when drying over‑cools the space, the temperature loop calls for heat — that is the reheat.

Energy interlock: dehumidification only runs when humidity is genuinely high (with hysteresis Hum_Hyst) and cool+heat never run together unless actually drying — so a dry space never wastes energy cooling‑and‑reheating.

Key inputs: Enable, SetPoint_Temp/PresentValue_Temp, SetPoint_Hum/PresentValue_Hum, Hum_Hyst, Deadband, per‑loop Kp_/Ti_/Td_Temp & _Hum, Manual/Fail_Safe overrides, Ao_low/Ao_high. Outputs: Cool_Eu/Cool_Ao, Heat_Eu/Heat_Ao, Dehumidifying, Mode (0 satisfied·1 heat·2 cool·3 dehumidify+reheat·4 manual·5 fail‑safe).

VAR AHU : TempHumidityCoolReheat; END_VAR
    AHU(Enable := TRUE,
        SetPoint_Temp := 22.0, PresentValue_Temp := TT_TEMP.EU_Value,
        SetPoint_Hum  := 8.0,  PresentValue_Hum  := TT_DEWPT.EU_Value);   (* dewpoint °C *)
    cool_ao := AHU.Cool_Ao;  heat_ao := AHU.Heat_Ao;

AhuTempHumidity — cooling + heating + humidifier (humidity both ways)

Everything the cool‑reheat block does, plus a humidifier: humidity below setpoint is raised, not just humidity above setpoint lowered. Three loops — temperature (split‑range), dehumidify (adds cooling), humidify (drives the humidifier). A humidity deadband Hum_DB around setpoint (with hysteresis) runs neither inside the band, so the humidifier and cooling coil never fight.

Extra I/O over the cool‑reheat block: inputs Hum_DB, ManualHumid, FailSafeHumid; outputs Humid_Eu/Humid_Ao, Humidifying, and Mode gains 6 = humidify.

VAR AHU : AhuTempHumidity; END_VAR
    AHU(Enable := TRUE,
        SetPoint_Temp := 22.0, PresentValue_Temp := TT_TEMP.EU_Value,
        SetPoint_Hum  := 45.0, PresentValue_Hum  := TT_HUM.EU_Value,
        Hum_DB := 3.0, Kp_Hum := 2.0, Ti_Hum := 180.0);
    cool_ao := AHU.Cool_Ao;  heat_ao := AHU.Heat_Ao;  humid_ao := AHU.Humid_Ao;

Commissioning: for the dehumidify side control dewpoint (or absolute humidity), not %RH — cooling air raises its %RH until it condenses, so a %RH loop can push the wrong way.

PID_AutoTune — relay‑feedback auto‑tuner

Finds PID gains automatically. Instead of the PID, a relay steps the output around a bias to force a steady oscillation; from its amplitude and period it computes the ultimate gain/period and the gains (Tyreus‑Luyben by default — robust; Ziegler‑Nichols via rule := 0). Drive the actuator from out while tuning; when done is TRUE, copy Kp, Ti_s, Td_s into your PID and hand control back. Set reverse := TRUE for cooling. It deliberately oscillates the loop, so tune during commissioning, supervised.

VAR tuner : PID_AutoTune;  loop : PID;  kp:REAL:=1.0; ti:REAL:=10.0; td:REAL:=0.0;  busy:BOOL; END_VAR
    IF Start_Tune THEN busy := TRUE; END_IF;
    tuner(Start := Start_Tune, pv := TT.EU_Value, sp := 22.0,
          reverse := TRUE, out_bias := 40.0, relay_amp := 15.0);
    IF tuner.done THEN kp := tuner.Kp; ti := tuner.Ti_s; td := tuner.Td_s; busy := FALSE; END_IF;
    loop(pv := TT.EU_Value, sp := 22.0, kp := kp, ti_s := ti, td_s := td,
         reverse := TRUE, enable := NOT busy);
    IF busy THEN valve := tuner.out; ELSE valve := loop.out; END_IF;

Function & block examples

These are the IEC 61131‑3 standard blocks the runtime implements, with worked examples. In Ladder, drop a Block and pick the type; in ST, declare an instance and call it. Every instance needs its own variable (in Ladder, Add missing declarations does this for you).

TON — on‑delay timer

Q turns on PT after IN turns on; drops immediately when IN drops.

VAR  t1 : TON;  END_VAR
    t1(IN := level_low, PT := T#5s);
    start_pump := t1.Q;          (* pump starts 5 s after the level stays low *)
    (* t1.ET is the elapsed time so far *)

TOF — off‑delay timer

Q follows IN up immediately, and stays on for PT after IN drops (e.g. a run‑on fan).

VAR  fan_off : TOF;  END_VAR
    fan_off(IN := heater_on, PT := T#30s);
    fan := fan_off.Q;            (* fan runs 30 s after the heater turns off *)

TP — pulse timer

A rising edge on IN gives exactly PT of Q, ignoring further edges during the pulse.

VAR  horn : TP;  END_VAR
    horn(IN := start_button, PT := T#2s);
    beeper := horn.Q;            (* a 2‑second beep on each press *)

CTU — count up (with CTD / CTUD)

CV counts rising edges of CU; Q is true when CV ≥ PV; R resets.

VAR  parts : CTU;  END_VAR
    parts(CU := part_sensor, R := reset_button, PV := 100);
    total   := parts.CV;
    box_full := parts.Q;         (* true at 100 parts *)

CTD counts down from PV (LD loads PV, Q when CV ≤ 0). CTUD does both (CU/CD, QU/QD).

R_TRIG / F_TRIG — edge detection

Q is true for one scan on the rising (R_TRIG) or falling (F_TRIG) edge of CLK. In Ladder these are the P and N contacts.

VAR  press : R_TRIG;  END_VAR
    press(CLK := button);
    IF press.Q THEN count := count + 1; END_IF;   (* once per press, not every scan *)

RS / SR — latches (memory)

RS is reset‑dominant, SR is set‑dominant. In Ladder these are the Set/Reset coils.

VAR  latch : RS;  END_VAR
    latch(S := start_pb, R1 := stop_pb OR trip);
    running := latch.Q;          (* start latches on; stop/trip wins *)

Motor seal‑in (start/stop, no block)

    running := (start_pb OR running) AND NOT stop_pb AND e_stop_ok;
    motor   := running;

In Ladder: start (NO) in parallel with a running (NO) contact, then stop (NC) in series, driving the running coil.

Scale an analog input to engineering units

Use the library scaling block (raw counts → EU), then act on the value.

VAR  s1 : SCALE;  END_VAR
    s1(raw := level_raw, raw_min := 0, raw_max := 27648, eu_min := 0.0, eu_max := 100.0);
    tank_pct := s1.eu;
    high_alarm := s1.eu > 90.0;
Times use IEC duration literals: T#500ms, T#5s, T#2m30s. Numbers can be hex (16#FF) or binary (2#1010).

Ladder editor

Ladder POUs open in a graphical editor (contacts, coils, compare boxes, function‑block boxes, parallel branches, online power‑flow, and per‑rung ST editing). It has its own detailed help: open the Ladder editor help →

Editing an element offers the variables that fit that element, grouped by where they come from: This POU, Global, and Block outputs (T1.Q, T1.ET), each shown with its type. A contact or coil lists the BOOLs; a compare lists the numeric variables. A name that is not declared yet can still be typed — Add missing declarations then writes it.

SFC — sequences

A Sequential Function Chart describes a sequence: steps that are active or not, transitions between them, and actions that run while a step is active. It is how a batch or a phase is written, and it is read as a chart rather than as a page of IF statements.

Create one with New POU → Program — SFC (sequence), which writes the opening step for you. Any POU whose body starts with INITIAL_STEP is a chart. The tree then offers a Chart view under that POU, which draws the sequence and lights up the step the controller is in, with the time it has been there.

PROGRAM Fill
VAR start : BOOL; full : BOOL; pump : BOOL; END_VAR
  INITIAL_STEP Idle:
  END_STEP

  STEP Filling:
    ACTION RunPump(N);        (* while the step is active   *)
    ACTION CountBatch(P);     (* once, when it is entered   *)
  END_STEP

  STEP Draining:
    pump := FALSE;            (* a statement works too      *)
  END_STEP

  ACTION RunPump:
    pump := TRUE;
  END_ACTION

  TRANSITION FROM Idle     TO Filling  := start;     END_TRANSITION
  TRANSITION FROM Filling  TO Draining := full;      END_TRANSITION
  TRANSITION FROM Draining TO Idle     := NOT full;  END_TRANSITION
END_PROGRAM
QualifierWhen the action runs
NEvery scan the step is active. The default.
POnce, on the scan the step becomes active.
SSets a BOOL and leaves it set: ACTION pump(S);
RResets it.

The timed qualifiers (L, D, SD, DS, SL) are refused with a message rather than approximated. Use the step’s own timer, which does the same job in a form anyone can read: IF Filling.T > T#5s THEN …

Every step is a variable

Filling.X is TRUE while the step is active and Filling.T is how long it has been active — ordinary symbols, so they can be watched on the variables page, plotted on the trace, read over OPC UA, or used in your own logic. A step that is not active reads T#0ms, never the time it spent there last time round.

Parallel and alternative branches

Two steps at once — a simultaneous divergence — is a list on either side:

TRANSITION FROM Start TO (LeftArm, RightArm) := go;   END_TRANSITION
TRANSITION FROM (LeftArm, RightArm) TO Done  := join; END_TRANSITION

The join waits for both arms. Where two transitions leave the same step, that is an alternative branch and the first one written wins — the chart is never in two places at once.

How it runs

On each scan: the elapsed times update, the actions of active steps run, and then every transition is evaluated before any of them is applied. That means one transition per branch per scan: a chart cannot race through five steps in one cycle, and every step it passes through is visible. A step entered on this scan runs its actions on the next one.

The chart view is a view. The text file is what the compiler reads and what the view draws, so what you see is always what will be downloaded. Click a step to jump to its line in the editor.

IO devices & scanning

Under IO Devices, add and configure the fieldbus/IO the controller talks to. Supported drivers include Profinet, Modbus TCP, EtherCAT, EtherNet/IP, and the sim driver (generates test signals against the real address map).

Changing IO devices only reaches the controller with Download hardware, which restarts the runtime and briefly stops control. On a real machine, make sure that is safe.
The sim driver means the badge shows SIM IO (amber): the values are generated, not read from real hardware. Switch the device to a real driver and Download hardware to read the real IO.

Addresses & process image

IO points live at %I (inputs), %Q (outputs), %M (memory), by bit (%IX0.0), byte (%IB34), word (%IW2) or dword. Declare an alias and use the name in your logic:

VAR
    start_pb  AT %IX0.0 : BOOL;    (* a real input bit  *)
    level     AT %IW2   : INT;     (* an analog input   *)
    pump      AT %QX0.0 : BOOL;    (* a real output bit *)
END_VAR

Browse IO… (Ctrl+Space, outside the code editor's body pane) lists the addresses the device tree defines and inserts the declaration for you.

Signal ranges & scaling

An analogue card puts raw counts on the wire — the range it is wired for changes what those counts mean without changing the counts. The IDE reads the card's type and the ranges it supports from the vendor file (GSDML / ESI / EDS) and shows them on the device page and in the device tree:

2  CT-3238   8×AI · 0-20mA/4-20mA   %IW2 ×8
4  CT-4234   4×AO · 0-20mA/4-20mA   %IB34 ×1 · %QW0 ×4

In the device page's Modules table, the Signal range column opens a dialog with one row per channel — inputs and outputs — each with its own range, plus a "set every channel to" control for the common case. A rack routinely mixes a 4‑20 mA transmitter and a 0‑10 V positioner on one card, which is why the setting is per channel and not per card.

Pick a range and that channel is converted in the controller, the way an Allen‑Bradley analogue module does it:

RangeWhat the program reads / writes
4-20mA4000 … 20000 (microamps)
0-20mA0 … 20000 (microamps)
0-10V0 … 10000 (millivolts)

Outputs are converted the other way: the program writes 4000…20000 and the card receives its counts. Leave a channel on raw counts to keep the previous behaviour and scale in the program.

The raw count scale comes from the card's manual, not the vendor file — the file names the ranges, not the counts. The ODOT C‑series figure used here is 0…27700 full scale, the same number the Transmitter block defaults Ai_High to. A wrong raw limit gives a plausible reading that is wrong by a constant, which is the hardest kind to spot.
Scaling changes what %IW holds: engineering units, not what the card sent. It takes effect on the next Download hardware.

An EtherCAT IO‑Link master module (for example the ODOT IP‑EC‑8IA) is added to the rack from its full ESI. Its device page then shows IO-Link ports (N)…: one page per port, with

These are sent to the module as start‑up SDOs while the bus starts (PRE‑OP), and take effect on Download hardware. In the program, the IO‑Link library blocks (lib/70_iolink.st) read and write ISDU parameters, read events, and decode the big‑endian process data of an IO‑Link device.

IO‑Link is supported through the EtherCAT master. The "NS" (no‑startup) ESI of a module carries no port parameters — use the full ESI.

Cores & threads

Task Configuration → Task Groups shows every long‑lived thread on the controller and which core it runs on: MainTask (the scan), each async IO driver, the OPC UA, HMI/web, Modbus and Monitor services. One Apply core assignment saves them together.

The program itself is single‑threaded: every PROGRAM in the task runs in sequence, in one thread, on one core. More cores do not make a scan faster — they keep other work off its back.

Pinning services is about interference, not speed: cache pressure and interrupts on the core the scan is using. The scan is real‑time priority and preempts them wherever they run.
The best setting is kernel isolation. With isolcpus=3 nohz_full=3 rcu_nocbs=3 in cmdline.txt and MainTask pinned to core 3, Linux keeps every ordinary thread off that core by itself — stronger than pinning, and the services can stay unpinned. The per‑service cores are for a controller whose kernel command line cannot be changed. The one worth pinning either way is the async IO driver thread, the other time‑sensitive thread on the box.

Panel lamps

LampMeaning
RUN solidThe scan is cycling and the program is in sole control.
RUN fast blinkCycling, but one or more points are forced — the machine is not obeying its program alone.
RUN offStopped, faulted, or the runtime is not running.
IO solidRemote IO configured and exchanging data.
IO blinkConfigured but not all devices are connected.
IO offNo remote IO configured — nothing to be wrong.
The forced blink is deliberately twice as fast as the IO fault blink, so the two are still distinguishable when both are true. One forced point is enough to start it; a stopped controller stays dark whatever is forced.

Build & download

ActionEffect
Build (F7) / Build All (Ctrl+F7)Compile the active / every enabled POU. Errors show in the Messages panel with clickable positions.
Download program (F8)Send the logic to the running controller — the scan is not stopped (online change). If the controller's settings differ from the project they are sent with it: without a question when they apply while running, otherwise after asking.
Download hardwareSend the controller configuration. Without stopping when only the OPC UA server, screens server, Modbus slave or EtherNet/IP tag server changed (the toast says "the controller kept running"); a restart (control stops briefly) for IO devices, process image, task timing, accounts and the rest.
Download allHardware then program, in order. Use it the first time or after IO changes.
Download screensSend the HMI screens so the controller serves them itself.
UploadRead the running program back off the controller.
Compare… / VerifyCheck the controller runs exactly what is open here (program and hardware).

Online & debugging

How long things have been running

Minor faults: what stops the PLC and what does not

The controller stops only for what makes the program's state untrustworthy. Arithmetic that has no answer does not stop it — the same call a Logix controller makes:

ConditionResult
Divide or MOD by zeroMinor — counted and reported with its line; the scan keeps running. Integer division yields 0; REAL follows IEEE (±INF, NaN).
Array index out of rangeMajor — the scan stops. The program's idea of its own memory is wrong.
Runaway loop (instruction budget), stack overflowMajor — the scan stops.
Minor faults appear in Messages with the source line, and their count is in the controller status. They are never silent — but a REAL that goes to INF or NaN travels into later maths, so treat the count as something to fix, not to live with.

Where is this used? (cross-reference)

Put the caret on a variable and press Where used in the editor toolbar. It searches every POU and globals.st, and classifies each hit: a declaration, a write, or a read. That distinction is the point — before changing a value on a running machine you need to know who depends on it and who else can change it, and one undifferentiated list makes you work that out by eye.

Each hit names the scope it is in, so Main3.st:12 tells you whether it is the global or a local of the same name. Mentions inside comments and string literals are not reported.

If a name is declared more than once, the dialog says so first. Inside a POU that declares it, the local wins — while the variables table, the ladder display and this page all resolve the name to the first match, which may be the other one. Two variables with one name will disagree on screen and nothing will look wrong. The compiler also warns about it at build time. This is a real fault that has cost a day: a timer that never ran, because batch_total was a global DINT and a local BOOL at the same time.

Online change — downloading without losing the batch

A download normally starts the new program from its initial values, which for a machine mid-batch throws away the batch. Online change is on by default: every variable that still exists with the same name and the same type keeps its value.

What happens to a variableWhen
Carried overSame name, same type.
Reset to its initial valueNew, or the type changed. An INT that became a REAL is a different variable that happens to share a name — copying the bits would give a plausible reading that is wrong.
Dropped, and named in the reportIt no longer exists. The value that was lost is usually the one that mattered, so it is named rather than counted.

Matching is by name and type, never by address: offsets shift as soon as a declaration is added, and copying by offset would quietly move one variable's value into another.

Timers and counters keep their state — a valve that has been open 40 s does not become one that has been open for nothing because a rung was edited — but their preset (PT, PV) comes from the new program text, so shortening a delay takes effect immediately. A sequence stays in the step it is in.

The scan pauses briefly while the program is swapped; outputs hold. This is state-preserving download, not patching code in a running scan. The controller records the change in its audit trail with who sent it, and the IDE reports how many variables were carried over, reset and dropped.

Variable tables: write & force

A POU's Variables view and the Global Variables page show, when connected, a live table: Expression · Type · Value · Prepared value · Address. Open a function‑block instance, a structure or an array with ⊞ (or ⊞ Expand all).

  1. Type the new value into Prepared value. For a BOOL, click the box to cycle TRUE / FALSE / (nothing). Double‑click a Value to copy it into the box. Enter only moves to the next box — it never writes.
  2. Write values (Ctrl+F7) sends every prepared value once. The program may change it again on its next scan if it assigns that variable.
  3. Force values (F7) holds the prepared values of %‑addressed points every scan until released. Forced rows are highlighted and the FORCE badge appears.
  4. Release force (Alt+F7) releases the forces on the rows shown; the FORCE badge releases them all.

Right‑click a row for Write all prepared values (N), write only that row, force or release.

Writing or forcing changes a running machine immediately. Confirm it is safe before you do it on real hardware, and release every force before you leave the machine.
A very large program lists at most about 12 MB of symbols. If the list is cut, the IDE says so; filter the table or watch fewer instances.

Trace / scope

Online → Trace / scope… records up to eight variables at scan rate and plots them. It answers a different question from the monitor page. The monitor says what a value is, a few times a second; a trace says what it did — between two scans, during the step, while the valve was opening. A PID that overshoots, a sensor that glitches for one cycle, an interlock that drops for 20 ms: none of that is visible to anything that polls.

Pick the variables, choose a depth and press Arm & record. Members of a function block are listed with their instance — a timer’s ET, a transmitter’s flags — and can be recorded; the instance itself has no single value to plot and is refused.

ControlWhat it does
DepthHow many samples the controller’s ring holds. When it is full the oldest are overwritten, so depth sets how far back the plot reaches.
Sample everyRecord one scan in N. At 10 ms, “every 20 scans” gives 200 ms resolution over twenty times the history.
Stop / ClearStop leaves the samples on screen. Clear releases the buffer on the controller.
Export CSVScan number, time in milliseconds, and one column per channel.

The x axis is the controller’s scan number, converted to time with the task period — not when the browser received the samples. Two traces of the same machine line up by it.

Each channel is scaled to its own range, as on any trend tool: a 0–27648 analogue input and a BOOL on one shared axis would draw the BOOL flat on the floor. The legend carries each channel’s minimum and maximum, and the value under the crosshair as you move across the plot. BOOLs, counters and timers are drawn as steps, because they held their value until a scan changed it; a REAL is drawn straight between samples.

If your browser falls behind, the controller’s ring laps and samples are lost. The page says how many and breaks the line where they are missing. A trace drawn smoothly across a gap would show a machine that nobody observed.

A download stops the recording rather than sampling memory that now belongs to a different program. Arming is recorded in the audit trail with the user, the channels and the depth; reading a trace someone else armed needs only viewer rights, arming one needs engineer.

Batch phases (ISA-88)

A recipe says “charge 500 kg, then heat to 60 °C”. It does not say which valve, and it must never need to. Between the recipe and the machine sits a phase: a fixed interface the batch engine drives, so one recipe can drive any phase and one phase can be written once. Without it every phase is bespoke and every recipe change touches PLC code — which in a regulated plant means revalidation for a change of setpoint.

Create one with New POU → Program — S88 phase (batch). The template is already wired: the state machine is called every scan, and a chart underneath it does the work.

The phase state machine

StateMeaning
IDLEWaiting to be started
RUNNINGDoing its work
COMPLETEFinished normally; waits for RESET
HOLDING → HELDGoing to a safe condition, then held
RESTARTINGComing back from HELD
STOPPING → STOPPEDControlled stop; needs RESET
ABORTING → ABORTEDFastest safe end; needs RESET
RESETTINGClearing back to IDLE

Commands are cmd: 1 START, 2 HOLD, 3 RESTART, 4 STOP, 5 ABORT, 6 RESET, 7 PAUSE, 8 RESUME.

HOLD, STOP and ABORT are not interchangeable. HOLD means the batch is not over — go to a safe condition and wait; the product is still good. STOP ends the phase in a controlled way. ABORT is the fastest safe end and usually loses the batch. A phase that treats them alike will either ruin batches that could have been saved, or take too long to stop when something is wrong.

An interlock that drops mid-run holds the phase rather than aborting it, for the same reason. A failure aborts, and its code is kept so the record can say what failed.

Writing the logic

The chart reads the phase’s outputs instead of decoding the state:

OutputTrue while
ph.run_logicRUNNING or RESTARTING — do the work
ph.hold_logicHOLDING — go to the safe condition
ph.stop_logic / ph.abort_logicSTOPPING / ABORTING

Set step_done when the work is finished, or failed with a fail_code when it cannot continue.

A POU may contain statements and then a chart. That is how a phase is written: the state-machine block is called every scan whatever step the chart is in. Put that call inside a step instead and it stops being called the moment the chart leaves that step — exactly when the phase most needs to be told to abort.

Units, arbitration and mode

BlockWhat it is for
S88_UNITThe vessel or skid a batch is made in: which batch owns it, its mode, whether a phase is active
S88_RESOURCEShared equipment — a transfer line, a CIP skid. First come, first served, with a queue you can see
S88_MODEAUTO / SEMI / MANUAL and PROGRAM / OPERATOR / EXTERNAL: who may command this equipment
Arbitration is not optional on a multi-product plant. One transfer line and two batches, with nothing arbitrating, means both drive it — a cross-contamination event, and the failure a regulator asks about first. A second batch requesting a line that is in use is queued, and only the owner can release it.

Controller alarms

Online → Alarms… shows the alarms the controller raises. They are evaluated on the scan thread, so the time on an alarm is when the condition changed — not when a screen noticed. On a 10 ms scan behind a screen polling twice a second those differ by enough to put two alarms in the wrong order, and “what happened first” is the question an excursion is investigated with.

StateMeaning
UNACK_ACTIVEActive, nobody has seen it
ACK_ACTIVEActive, somebody has
UNACK_RTNIt went away before anybody saw it — kept, not forgotten
SHELVEDAn operator silenced it, temporarily
SUPPRESSEDThe design says it is meaningless now (low flow on a stopped pump)
OUT_OF_SERVICEMaintenance removed it

Shelving is always temporary. It expires by itself and the attempt to shelve for zero time is refused — an alarm silenced for ever is a deleted alarm. Every acknowledgement, shelve and out-of-service is recorded with the name of whoever did it.

Configuring them

"alarms": [
  { "id": 1, "name": "TT_01_HIGH", "message": "Reactor temperature high",
    "priority": "high", "area": "M", "offset": 0, "bit": 0, "delay_ms": 500 },
  { "id": 2, "name": "LOW_FLOW", "priority": "low",
    "area": "M", "offset": 0, "bit": 2,
    "suppress": { "area": "M", "offset": 0, "bit": 3 } }
]

The condition is not configured here. level > 9000 AND pump_running is logic and belongs in the program; the program sets a bit and the alarm watches it. That keeps the cost at one bit test per alarm and keeps the alarm’s meaning readable in the program.

Electronic signatures

Online → Sign… records that a named person took responsibility for one specific thing, at a stated time, for a stated reason — the three components 21 CFR Part 11 requires.

Logging in is not signing. The controller verifies your password again as part of signing. A signature an open session could produce is one anybody passing an unattended screen could produce.

A signature carries a meaning from a fixed list — approved, reviewed, released, rejected, verified, authored — because that field is read across hundreds of records and free text turns it into archaeology. It binds to a SHA-256 rather than to the document, so anyone can check that what they are holding is what was signed.

An account that still carries the password it was created with cannot sign at all: a credential anybody could guess is not evidence that a particular person took responsibility.

Online → Signatures… lists what this controller has taken since it started; its audit log holds them all, and that is the record of legal weight.

Validation documents

If you are putting this controller into a GxP plant, your validation team will ask for a specification chain before they ask for anything else. It ships with the product, in docs/:

DocumentWhat it is for
27 — Validation PlanHow the platform is validated, and where our responsibility stops. The boundary is the download: the platform is ours, the application program is yours.
28 — User Requirements99 requirements, each risk-classed and each tied to the regulation or process need behind it. Your own URS is written against this one.
29 — Functional SpecificationHow the product satisfies each requirement.
30 — IQ / OQ protocolsReady to execute on your installation, step by step, with the evidence to attach at each step.
31 — Traceability MatrixRequirement → design → test evidence, checked in both directions.
25 — QA and Memory ReportWhat was actually run, what it found, and what was done about it — including the defects.
The protocols ship unexecuted, and that is deliberate. Only you can qualify your installation on your hardware. A supplier who hands you completed, signed IQ and OQ records has qualified their bench, not your plant.

This controller is not a safety system. It carries no SIL or PL rating and must not implement a safety function. Emergency stop and guard interlocking belong in a certified safety relay, wired independently of this controller. No document in the set changes that.

Online → Controller

Everything about the machine rather than the project on it, grouped at the top of the Online menu: its log, its clock, its versions, and its run state.

Controller log… reads the runtime log without an SSH session. Set clock / time… reads and sets the controller time and writes the hardware RTC. Versions… shows what the IDE and the runtime are. License… shows whether this controller is licensed, its hardware id, and installs a license file. Check for updates… asks optizeus.org for a newer IDE.

The last three stop a plant. Restart runtime reloads the boot program and briefly stops the control loop; Reboot takes the whole operating system down for 30–60 seconds; Shut down halts it and it does not come back until somebody restores power at the machine. Each asks for confirmation, and each is engineer only.

Targets & connection

Target… lists the controllers this IDE can use: the local Simulator and your real controllers (by host/IP, optionally over an SSH tunnel). The active target is what Build/Download/Online act on. Login is per connection — a reconnect starts a fresh session, so log in again after reconnecting.

OPC UA server

OPC UA Server publishes the process image and program variables to OPC UA clients. Settings: port (default 4840), security policies and certificate, anonymous vs username/password, and which tags to expose. See the server's page for details; changes reach the controller on Download hardware.

Modbus TCP slave

Modbus TCP Slave exposes ranges of the process image as Modbus coils/registers for a SCADA/HMI master. Map each range to an area and object type, enable it, and Download hardware.

EtherNet/IP tags & EDS

Controller Settings → HMI tag server (EtherNet/IP) serves Zeus symbols by name to an EtherNet/IP HMI or SCADA — including function‑block members such as TT_01_01.EU_Value — with no register mapping. On the panel, use its Allen‑Bradley CompactLogix / TreeTag driver. Off by default, and read‑only unless you enable writes.

Create EDS file…

An EtherNet/IP engineering tool shows the controller as an unknown device until its EDS is registered on that PC. Controller Settings → Create EDS file… generates it for the model you pick (IPC‑CM400200, IPC‑CM500200, or both). Register it with the tool's EDS hardware installation utility, then browse again.

The vendor ID in the EDS is a placeholder until ODVA assigns one. Regenerate and re‑register the EDS whenever the identity or the major revision changes.

HMI screens

HMI holds operator Screens, Alarms and Events. Add screens (Add operator screens…), draw them, and Download screens so the controller serves them. Bind screen objects to program/global variables; alarms are defined against tags.

Besides the live widgets there are plain shapes — Rectangle, Ellipse, Line, Polygon — and Image, which shows a picture from the project's image pool (Images…: PNG/JPEG up to 1 MB, or SVG cleaned like an imported symbol). Every widget has a Style section: fill, line colour and width, corner radius, rotation, opacity, text colour and alignment.

Symbol library. Symbol library… in the screen editor (also at the bottom of a plant symbol's Shape list) browses about 1,080 built-in plant drawings in 39 categories — tanks, pumps, valves, instruments, piping, 3D equipment, HVAC, callouts… — with a search over every title and tag. Click a thumbnail to insert it at the centre of the screen, double-click to insert and close. The drawing is copied into the project (hmi/symbols/, cleaned like Import SVG…) and goes to the controller with Download screens; inserting it again reuses the copy. Colour with state (imported drawings only): none — drawn as it is (the default for library drawings); whole symbol — the whole drawing tinted green running / red fault / grey off or unknown, shading kept; elements with class zeus-state — only the marked parts follow. Manual §32.4.4.

Dynamics make one property of a widget follow one variable, with a declared rule rather than code:

PropertyRules
X, Y, Width, Height, Rotation, Opacity, Line widthScale — from a value range to a property range, linear and clamped
Fill, Line colour, Text colourBool (one colour when TRUE, another when FALSE) or Map (first matching range wins, else a default)
Visible, Blink (1 Hz)Cond — a comparison: == != < <= > >= and a value
TextFormat (%d %i %u %x %X %f %.1f %e %s %%, e.g. %.1f °C) or Map
Enabled (buttons, input fields)Cond

A rule whose variable the controller does not report draws the widget grey with ? — never the healthy colour — and disables it if it writes. Rules that cannot be drawn (wrong rule type, bad operator, colour or format) are refused when the screen is saved. Manual chapter 32, §32.4.6–32.4.8, has a worked example.

Faceplates (templates). A template is drawn once for a kind of equipment with its variables written against a parameter — $M.running, or ${M}_speed when more letters follow — and placed many times: create it with New screen… → Template (faceplate), list its parameters (up to 16) in the properties panel with nothing selected, then put it on screens with Add: Faceplate, choosing the template and an argument for every parameter (M = P101). Fit keeps proportions or stretches to the box. A Screen link with Open as dialog over this screen opens a template (with arguments) or a screen as a dialog — close with × or Esc, up to 3 stacked. $M in text and titles is replaced too. Saving refuses a reference to an undeclared parameter, a missing or extra argument, a template that does not exist, nesting deeper than 4 and loops (A framing B framing A) — save the template first. Generate faceplate… builds a template from a function-block type (library or project): parameter I, lamps for BOOL outputs, values for numeric outputs, buttons for BOOL inputs (momentary for start/stop/reset/ack, toggle otherwise) and input fields for numeric inputs. A start-like input next to a stop-like one (MOTOR_DOL start/stop, read as levels) becomes a latching pair — START = reset stop, set start (asks first); STOP = reset start, set stop — and inputs the field drives (fb_…, ext_…, …running/trip/fault/interlock…) are lamps, not buttons. Check the buttons against how the block reads its inputs. Manual chapter 32, §32.4.9.

Input elements. Slider and Knob (minimum, maximum, step; written once, on release), Meter (round gauge with coloured bands), 7-segment, Checkbox (BOOL), Radio buttons and Drop-down (options: value written + text shown), Array table (bind the ARRAY; rows are the name[i] elements the controller reports, columns are members of an array of structures; optionally Editable), Tabs (pages: a screen, or a template with arguments, checked like a Faceplate) and Date/time (the panel clock, or a DT/TIME variable, with a format such as yyyy-MM-dd HH:mm:ss). A variable the controller does not report — or a value matching no option — shows grey with ?. An Input field (and an editable table) with Limits ticked refuses a value outside minimum … maximum on the panel before anything is written.

Keypad. An input field opens an on-screen keypad (Keypad: on touch panels — the default, when the device has no fine pointer — always or never): numbers with the allowed range, or QWERTY for a STRING. OK writes; Cancel, Esc or a tap outside write nothing.

Actions. A Button, or any display element with On click ticked, can run a list of actions in order: set, reset, toggle, write a constant, increase/decrease by a step (clamped), go to a screen, open a dialog (screen or template with arguments), close this dialog. The confirm text is asked once; the list stops at the first refused write; an element disabled by an Enabled rule does nothing. A button has either On press or actions, never both. Targets and arguments are checked on save.

Hotkeys. With nothing selected, Hotkeys binds F1…F12 or Esc (optionally with Shift) to an actions list. They act on the top view only (the top dialog while one is open), never while typing in a field or with the keypad open. Manual chapter 32, §32.4.10–32.4.13, has a worked example.

Alarm table. Shows the controller's alarms (manual chapter 27) — not bits of the screen. Choose and order the columns (time, priority, state, name, message, value, acknowledged by), the lowest priority shown, an optional name filter (exact names or prefixes like TT_*), a row limit and a colour per priority. Unacknowledged alarms come first and blink until acknowledged. Acknowledge takes the selected row, Acknowledge all shown (after a confirmation) the unacknowledged rows the table shows; both need an operator log-in and are recorded with the user. History shows the controller's journal, newest first. A time marked ~ was stamped while the controller clock was not synchronised. Bind @alarms.unacked or @alarms.active (read-only counts) to a lamp or value, or follow them with a rule, for a header lamp that blinks while anything is unacknowledged. Manual §32.4.14.

Text lists and languages. Text lists… edits hmi/texts.json: the languages, the default, and named lists of key → text per language (import/export CSV list,key,en,he…). In any text write @List.key for an entry, or @List on a value, lamp, button, checkbox or symbol for the entry whose key is its value (0, 1, TRUE…); also in option texts, alarm-list rows, tab and dialog titles and map text rules. A missing translation falls back to the default language, then to the key. Language selector in the header (screen properties) lets the panel choose; the choice is kept per panel; Hebrew/Arabic set the text direction (the layout is not mirrored). Manual §32.4.15.

Access. Every widget: Visible to (viewer/operator/engineer) and Operated by (operator/engineer) — below it the element is hidden or drawn locked, from the logged-in user's role. A writing element needs an operator by default. Display only: the controller still refuses what the role may not do. Manual §32.4.16.

Recorded trends. Tick Recorded on a trend (optional sample period and samples kept): the controller records it in memory and the panel shows the history when the screen opens, then continues live. The IDE builds web.trends from every recorded trend on every screen and faceplate (arguments filled in, duplicates merged, at most 32 — the rest is a warning in Messages) — it takes effect after Download hardware. Manual §32.4.17.

Recipes. Recipes… defines a recipe: variables in write order, label, unit, optional min/max (hmi/recipes/<name>.json). The Recipe widget lets the operator choose a set, Load from PLC, edit values on the keypad (range-checked), Save / Save as… / Delete (sets stored on the controller) and Download to PLC — every value checked first, then written in order after a confirmation, stopping at the first refusal. Saving, deleting and downloading need an operator. Manual §32.4.18.

Log-in on a panel. With accounts, the operator page shows its own log-in page (keypad on touch panels), shows the user and Log out in its header, counts down a lockout after failed log-ins, and returns to the log-in page when the session ends. Manual §32.6.3.

Local display (HDMI). The controller can show its screens on a monitor or touch panel on its own HDMI port: Controller Settings → Local display (HDMI) (engineer, a controller target — not the Simulator). Install on controller… once (installs a full-screen browser over SSH with sudo; the controller needs internet access for about 250 MB), then set Enable, Start screen, Rotation, Hide cursor, Wait timeout and press Apply…; Restart display… reloads it. The display opens http://127.0.0.1:<web port>/, so the screens server must be enabled on 127.0.0.1 or 0.0.0.0. With accounts it shows the panel log-in page with the keypad. Not yet bench-tested on every display. Manual §32.11.

Users & roles

Users & Roles defines who may connect to the controller and what each may do (the same accounts are used by the IDE and by the operator screens). Set passwords here; a user still on its creation password may be blocked from downloading until it is changed.

Simulator

Factory I/O. A Modbus TCP device on this PC (host 127.0.0.1) is not simulated: the simulator really connects to it, so a 3D Factory I/O scene (Modbus TCP/IP Server driver, port 502) can be driven from your program - with no time limit. Open the example F1-FactoryIO-Conveyor; Factory I/O "Input n" is %IX(n/8).(n mod 8), "Output n" is %QX(n/8).(n mod 8), register input/output n is %IW/%QW(2+2n). Operation Manual 26.6a.

The Simulator runs the exact controller binary locally with your configuration, so you can develop and test with no hardware. Start/stop and configure it from its tree node; a program downloaded to it is compiled and bound against the same addresses the real controller uses. A restart clears the downloaded program (there is no boot application), so re‑download after starting it.

License & demo time

The IDE is free. The runtime decides what it may do: a Zeus controller - IPC-CM400200 (CM4) or IPC-CM500200 (CM5) - carries a license, signed by Zeus Automation, for its own CPU serial number, and runs without a limit.

RuntimeBehaviour
Zeus controller (licensed)No limit.
Simulator, or any other hardware, with simulated IO onlyNo limit - learn, test and train freely.
Simulator or other hardware with real IO: any fieldbus device (Profinet, EtherCAT, EtherNet/IP, Modbus TCP RIO) or the Modbus TCP slaveDemo: the program runs for 2 hours, then stops - the controller goes to CONFIG (outputs no longer driven, inputs still read) and refuses Run until the runtime is restarted (another 2 hours) or a license is installed.

The DEMO pill counts down; Messages warns at 10 minutes and 1 minute, and a window says when the program has stopped. Online → Controller → License… shows the status and the machine's hardware id (with Copy): send it to Zeus Automation, and install the file you receive with Install license file… - it takes effect at once, also after the demo has ended (the controller goes to PAUSED; press Run). Not for production on anything but a Zeus controller.

Updates

15 seconds after it starts and every 12 hours the IDE reads a small file on optizeus.org with the latest release number (nothing about this PC or its projects is sent). When a newer one is out, a blue ⬆ Update 0.1.x.y button appears in the top bar: click it for what is new, then Download, Later or Skip this version. ⟳ Check for updates asks at once. Offline it stays silent; ZPR_UPDATE_URL=off turns the check off. To update, unzip the new package next to the old one (Operation Manual 2.8) - projects are not touched, and the controller's runtime is updated separately with Deploy runtime.

Keyboard shortcuts

KeyAction
Ctrl+SSave project
Ctrl+Shift+SSave As…
F7 / Ctrl+F7Build / Build All
F8Download program
Ctrl+FFind in this POU
Ctrl+Shift+FFind / replace in the whole project
EscIn the code editor online: bring the inline values back
F2Rename variable at caret
Shift+F12Where used
Ctrl+SpaceBrowse IO / insert declaration; in the code editor's body pane, the completion list only
F10 / F11 / Shift+F11 / F5Step over / into / out / Continue
Ctrl+Z / Ctrl+YUndo / redo in the code editor
Alt+dragColumn selection in the code editor
In an online variable table
Ctrl+F7Write every prepared value once
F7Force the prepared values of %‑addressed points
Alt+F7Release the forces on the rows shown
EscClear the prepared value being edited
In a variable table F7 and Ctrl+F7 force and write; everywhere else they build.

Limits

ItemLimit
POUs in one download (library blocks, FBs, methods, functions, programs)256
Parameters of one FB or function64
Members of one STRUCT or FB128
Programs in the task call list16
IO devices16
Controller alarms10 000
OPC UA instance folders1 000
Array elements listed per array online100 (the rest are reachable by index)
Symbol list sent to the IDEabout 12 MB
Trace channels8
Signal‑range scalings per device32

The Operation Manual, Appendix D, lists every limit and what to do when you reach one.

Troubleshooting

SymptomCause & fix
Badge shows SIM IO (amber) though hardware is wiredThe IO device uses the sim driver. Switch it to the real driver (e.g. Profinet) and Download hardware.
PROGRAM CHANGED after editingThe open program differs from the controller. Download it. If you are editing a program the controller isn't running, that's expected until you download it.
Online shows no valuesTurn Online on, make sure you are logged in, and that this program is the one downloaded.
"not in static storage" writing a variableThe name collides with a library block member (start, stop, reset…). Use a distinct name, and make sure the program that declares it is the one running.
Can't connect over OPC UACheck the endpoint/port, security policy, and whether anonymous is allowed or a username is required.
A rung's contacts all read TRUE but the timer never startsA local variable shadows a global of the same name. The program uses the local; the monitor, the ladder's live values, a force and a write all resolve the name to the global, so the screen shows a different variable. Build shows a warning naming both declarations — rename one of them.
An analogue channel reads a plausible but wrong numberCheck the channel's signal range on the device page, and the raw full‑scale figure against the card's manual. A wrong raw limit is wrong by a constant.
Restoring a data snapshot takes a long timeAn older runtime writes one value per request. Deploy the current runtime: the whole set goes in one request, and values that cannot change are skipped before sending.
"Connection time not reported by this runtime"The controller runs a runtime older than this field. Deploy the current runtime.
Controller not connectedCheck the Target… host/IP and that the runtime is running; log in again after a reconnect.