# 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: - ":502:502" # Modbus TCP, for SCADA - ":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`. |