Adds what the folder was missing for someone picking it up cold.
as-built/ the STruC++ output copied out of the live container - the C++
the PLC is actually executing, plus program.st and the Modbus
buffer config. The running image was made with docker commit
and exists in no registry, so this is the only other copy of
the compiled form.
Verified while copying: the deployed program.st matches
build/wrps.st generated from src/. Identical POU structure,
ZERO differences in non-declaration lines. src/ is genuinely
canonical and the running PLC agrees with it.
VERSIONS.md every version read from the running system, not from
documentation: runtime v4.1.10, STruC++ 0.6.2, Editor
4.2.11, Debian 12, g++ 12.2, pymodbus 3.11.2, CI
Server R1.03. Plus which plugins are enabled -
ethercat is on for no reason - and a v3-vs-v4 table,
since most OpenPLC guidance online is for v3 and the
%MW HR1024 change silently produces wrong data.
GETTING-STARTED.md three questions answered in a page: how to modify the
program, how to stand up a new PLC container from
scratch, how to move this one. Includes the compose
file, the Editor steps that are not generated and are
always missed, and a symptom-to-document table.
3.4 KiB
Software and versions
Everything below was read from the running system on 2026-09-02, not taken from documentation. Where a version matters — where a different one behaves differently — that is said explicitly.
The PLC
| Component | Version | Where it runs | How to check |
|---|---|---|---|
| OpenPLC Runtime | v4.1.10 | openplc-runtime container on yau-sls-poc-lin001 |
docker exec openplc-runtime cat /workdir/VERSION |
| Docker image | openplc-runtime-migrated:v4.1.10 |
local to the host, not in any registry | docker images |
| Base OS in container | Debian 12 (bookworm) | docker exec openplc-runtime cat /etc/os-release |
|
| Python (runtime + plugins) | 3.11.2 | docker exec openplc-runtime python3 --version |
|
| g++ | 12.2.0 | used to build the PLC program | docker exec openplc-runtime g++ --version |
| GNU Make | 4.3 | drives scripts/Makefile.strucpp |
|
| pymodbus (in the slave plugin) | 3.11.2 | docker exec openplc-runtime ./venvs/modbus_slave/bin/pip list |
Runtime plugins
/workdir/plugins.conf — the third field is enabled (1) / disabled (0):
| Plugin | Enabled | Note |
|---|---|---|
modbus_slave |
1 | This is the one that matters. It is what makes the PLC a Modbus server on 502. |
modbus_master |
0 | Correct — the PLC must not poll anyone. |
opcua |
0 | |
s7comm |
0 | |
ethercat |
1 | ⚠️ Enabled for no reason. It came from an ethercat.json the Editor project emits by default (the file is 0 bytes). Harmless so far, but it is a plugin running with nothing to do. |
The toolchain
| Component | Version | Notes |
|---|---|---|
| OpenPLC Editor | v4.2.11 (AppImage) | Needs --no-sandbox (Electron) and libfuse2t64. ⚠️ Not currently installed anywhere — see DEPLOY.md §0. |
| STruC++ | v0.6.2 | The ST → C++ compiler. Ships only inside the Editor AppImage; there is no standalone binary. This is the single reason the Editor is unavoidable. |
The SCADA
| Component | Version | Where |
|---|---|---|
| Yokogawa CI Server | R1.03 | yau-poc-cicore1 (10.0.0.21) |
Versions that actually matter
OpenPLC Runtime v3 → v4 is a breaking change, and most guidance you'll find online is for v3.
| v3 | v4 (what we run) | |
|---|---|---|
| ST compiler | MatIEC | STruC++ |
Accepts a flat .st upload |
yes | no — scripts/compile.sh explicitly rejects MatIEC files (Config0.c, glueVars.c) |
| Web UI on the runtime | yes | no — REST API only, port 8443, JWT |
| Editor project format | one file | a folder — project.json + one file per POU |
%MW Modbus mapping |
starts at HR 0 | starts at HR 1024 (%QW occupies 0–1023) |
That last row is the one that silently produces wrong data rather than an error.
See register-map.csv, which carries resolved addresses.
The Modbus buffer layout is configuration, not a constant. From
core/generated/conf/modbus_slave.json in the running container:
"holding_registers": { "qw_count": 1024, "mw_count": 1024, ... }
qw_count: 1024 is why %MW0 is holding register 1024. Change that file and
every SCADA address moves.
CI Server R1.03 vs R1.05. One R1.03 behaviour is load-bearing here: an item
import is rejected if two items share an IO_ADDRESS (EQP-E-DUP_ITEM). The SCADA
design works around it by publishing one unit per register. If you move to R1.05,
re-check that before assuming the workaround is still needed.