wrps-demo-kit/03-plc/GETTING-STARTED.md
Clio Liu 55279ca78f docs(plc): as-built copy, versions, and a getting-started guide
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.
2026-09-02 16:48:30 +10:00

159 lines
6.7 KiB
Markdown

# Getting started — three things you might need to do
Short answers. Detail is in `DEPLOY.md`, `VERSIONS.md` and `README.md`.
Before anything else, know the two hard facts about this setup:
1. **The ST → C++ compiler (STruC++) ships only inside the OpenPLC Editor GUI.**
There is no CLI, no library, no container image of it. Any change to the PLC
*logic* goes through the Editor. Nothing else will do it.
2. **The running PLC's image was made with `docker commit`** and holds the compiled
program in its writable layer. It is not in a registry and cannot be re-pulled.
---
## 1. I need to modify the PLC program
**What you need:** OpenPLC Editor **v4.2.11**, on a machine with a graphical
desktop that can reach `10.0.0.17:8443` (LAN or WireGuard VPN).
> ⚠️ **The Editor is not currently installed anywhere.** It lived on the retired
> `dev-ubuntu` host. Installing it is step one — see `DEPLOY.md` §0.
**Then:**
```bash
# 1. edit the ST - this is the only place you edit
vi src/30_prog_control.st
# 2. rebuild. This also regenerates register-map.csv
python build.py --mode sim
# 3. generate the Editor project
python gen_project.py --mode sim --out editor-project
```
Copy `editor-project/project.json` and `editor-project/pous/` into the Editor
project folder — **with the Editor closed** — then open it, Build, and Start PLC.
`DEPLOY.md` §B is the full procedure; every warning in it was earned.
**Then verify:**
```bash
python ../05-tests/verify_modbus.py --host 10.0.0.17 --port 502 --unit 1
```
### Three things to get right
- **Never hand-edit `register-map.csv`.** It is generated. Change the `REGISTERS`
table in `build.py` and rebuild. It is the contract with CI Server — if it moves,
the SCADA point list in `04-scada/modbus/` must be regenerated too.
- **Never edit the Editor project directly.** `gen_project.py` overwrites it. Fixes
go into `src/`.
- **Never edit `as-built/`.** That is a snapshot of what is deployed, not source.
### If you add or move a register
The whole chain has to be re-run, in order:
```
src/*.st → build.py → register-map.csv
04-scada/modbus/gen_scada_points.py
gen_ciserver_qli.py → re-import into CI Server
```
Skip a step and the PLC and SCADA disagree silently — the reads still succeed,
they are just wrong.
---
## 2. I need to create a new PLC container from scratch
For a **new demo**, not for recovering this one.
**What you need:** a Docker host, and the OpenPLC Editor to compile a program.
```yaml
# ~/myplc-compose.yml
services:
myplc-runtime:
image: ghcr.io/autonomy-logic/openplc-runtime:latest
container_name: myplc-runtime
restart: unless-stopped
cap_add: [SYS_NICE, SYS_RESOURCE] # required - the runtime needs
# real-time scheduling or it breaks
ports:
- "<host-lan-ip>:502:502" # Modbus TCP, for SCADA
- "<host-lan-ip>:8443:8443" # REST API, for the Editor
volumes:
- myplc-data:/var/run/runtime
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
volumes:
myplc-data:
```
```bash
docker compose -f ~/myplc-compose.yml up -d
```
**Bind the ports to the host's LAN address, never `0.0.0.0`.** Modbus has no
authentication or encryption, and 8443 is the control channel with only JWT in
front of it. On a host with a public IP, the bind address is the security control.
### Then, in the Editor — the part that is not obvious
Three steps that are configured through GUI dialogs and are not generated. The
reference copies of what they produce are in `editor-devices/`.
1. **Device → Configuration** — device `OpenPLC Runtime V4`, IP = the host's LAN
address, port `8443`, user `admin`.
2. **Device → Servers** — add a Modbus TCP server, **Enable Server** on,
`networkInterface` `0.0.0.0`, port `502`.
> **This is the step everyone misses.** It is what makes the Editor ship
> `conf/modbus_slave.json`, which enables the runtime's `modbus_slave` plugin.
> Without it the plugin stays off and **nothing ever binds port 502**, however
> the container is configured. `0.0.0.0` here is correct — it is inside the
> container; Docker's publish is what restricts exposure.
3. **Do not touch Device → Remote Devices.** That is the Modbus *master* — it makes
the PLC poll someone else, and its IO groups claim `%IW` addresses. Your SCADA
needs no entry in the PLC project at all; it is a client and simply connects.
Then Build → the Editor uploads → **Start PLC**. Port 502 only answers while a
program is running.
### What to copy from this project
The method transfers even when the plant does not:
| Take | Why |
|---|---|
| `build.py`'s `REGISTERS` table + `modbus_address()` | Generating the register map from one table is what stops the PLC and SCADA drifting. Change the tags, keep the machinery. |
| `gen_project.py` | The Editor has no flat-`.st` import, and it will not let you type located addresses by hand. This writes them into `project.json`. |
| The `IO_MUX` pattern | One POU owns every located variable; control logic reads globals only. It is what lets the same control code run against a simulation or a real field. |
| `05-tests/verify_modbus.py` | Proves the map against the live PLC before you touch SCADA. |
---
## 3. I need to move this PLC to a different host
The compiled program is in the container's **writable layer**, so `docker commit`,
never `docker pull`. Full procedure with the real checksums from the 2026-08-19
move: `../02-environment/MIGRATION.md`.
---
## Where to look when something is wrong
| Symptom | Look at |
|---|---|
| Port 502 refuses connections | `README.md` — the two conditions. A program must be **running**, and the Editor project must define a Modbus **Server**. |
| SCADA reads plausible but wrong values | `%MW` starts at **HR 1024**, not 0. `register-map.csv` has resolved addresses. |
| The alarm word goes negative | `%QW17` must be read as **unsigned** 16-bit. Bit 15 does not fit a signed INT. |
| Every measurement reads 0 | You are polling `%IW`/`%IX` (FC04/FC02). In the simulation build nothing writes those. Live values are in the `%QW` block. |
| Editor build fails on `VAR_EXTERNAL … has no matching VAR_GLOBAL` | The Editor was open while files were copied in and wrote its cached `project.json` back. `DEPLOY.md` §B2. |
| Console says "Compile only mode — skipping upload" | Compile-only is on; nothing was uploaded. `DEPLOY.md` §B4. |
| Setpoints reverted to unfamiliar values | The runtime restarted and `IO_MUX` re-seeded the defaults. Live tuning is not stored in this repo. `README.md`. |