# 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: ```json "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.