Corrected in README.md, CLAUDE.md, 02-environment/README.md and 03-plc/VERSIONS.md. The @VERSION 1.03.00 header inside the .qli files is the quickload FILE FORMAT version, not the CI Server release. Left alone. This also sharpens a caveat that was previously stated wrongly. The EQP-E-DUP_ITEM rejection - an item import fails if two items share an IO_ADDRESS - was observed on 2026-08-14 on the DEVELOPMENT machine, which ran R1.03. Production runs R1.05, where it has never been tested. The design still works around it by publishing level and speed in % only, and that workaround costs nothing; but anyone wanting a second unit for a measurement should try it on R1.05 before assuming they need a second PLC register to carry it. |
||
|---|---|---|
| 01-design | ||
| 02-environment | ||
| 03-plc | ||
| 04-scada | ||
| 05-tests | ||
| 99-reference | ||
| .gitattributes | ||
| .gitignore | ||
| CLAUDE.md | ||
| README.md | ||
wrps-demo-kit
A working method for building demo automation use cases on Yokogawa CI Server (SCADA) and OpenPLC Runtime (soft PLC, in Docker) — with the Waterloo Road Pump Station as its first complete example.
The demo is live: a simulated three-pump station runs on a soft PLC and is polled over Modbus TCP by CI Server, with operator displays that command it.
Note
The pump station is not real. There is no Waterloo Road Pump Station. The whole design set was generated with AI from a one-page idea, deliberately following a real project lifecycle. That method is the most reusable thing here — see
01-design/README.md.
The two machines
| Role | Host | Address |
|---|---|---|
| SCADA — Yokogawa CI Server R1.05 | yau-poc-cicore1 |
10.0.0.21 |
| PLC — OpenPLC Runtime v4, in Docker | yau-sls-poc-lin001 |
10.0.0.17 |
Architecture invariant: OpenPLC Runtime is the Modbus TCP server (slave) on port 502. CI Server is the Modbus client (master) and polls it. It is never the other way round.
The chain — everything derives from the PLC
This is the spine of the repository. One table in 03-plc/build.py generates the
contract; everything downstream is derived from it, so the two sides cannot drift
apart.
03-plc/src/*.st the PLC program - hand written
│ build.py
▼
03-plc/register-map.csv THE CONTRACT - 69 Modbus points
│ gen_scada_points.py
▼
04-scada/modbus_points/
ci-server-points.csv the SCADA view - 49 points, poll groups, units
│ gen_ciserver_qli.py
▼
wrps_*.qli 6 sections · 49 points · 49 items
│ dssqld -i (quickload)
▼
CI SERVER ◄──── Modbus TCP ──── the PLC on 10.0.0.17:502
▲
│ file copy
│
04-scada/hmi/out/*.xml 6 operator displays
Never hand-edit anything downstream of src/. Change the REGISTERS table in
03-plc/build.py, rebuild, then re-run the SCADA generators. Verified 2026-09-02:
every generated file in that chain reproduces byte-identically.
Where to start
| If you want to… | Read |
|---|---|
| understand the demo and why it exists | 01-design/00-origin/ — two minutes |
| build design docs for your own demo | 01-design/README.md — the AI-generated lifecycle method |
| change the PLC program | 03-plc/GETTING-STARTED.md |
| understand how the PLC program is built | 03-plc/ARCHITECTURE.md |
| stand up a new PLC container | 03-plc/GETTING-STARTED.md §2 |
| know what version of everything is running | 03-plc/VERSIONS.md |
| get onto the machines | 02-environment/README.md |
| import or export CI Server configuration | 04-scada/QUICKLOAD.md |
| fix a display whose values are dead | 04-scada/README.md |
Layout
01-design/ the design authority — and how it was produced
00-origin/ the one-page idea everything grew from
02-environment/ the two hosts, access, ports, the container, migration
03-plc/ the PLC: ST sources, generators, the register map
as-built/ what is ACTUALLY running, copied from the container
04-scada/ the CI Server side
modbus_points/ the tag database, and the historian
hmi/ the displays and their generator
ciserver-backup-2026-08/ ⚠️ outdated exports, evidence only
05-tests/ Modbus verification harness, and a fake PLC
99-reference/ read-only worked examples — sorts last on purpose
What to reuse for the next demo
| Reuse as-is | Adapt per demo | WRPS-only |
|---|---|---|
03-plc/gen_project.py — the Editor has no flat-.st import |
03-plc/build.py's REGISTERS table — change the tags, keep the machinery |
The design documents |
05-tests/verify_modbus.py — proves the map before you touch SCADA |
04-scada/modbus_points/gen_ciserver_qli.py — sections, NSIDs, units |
The ST control logic |
05-tests/fake_plc.py — tests the SCADA side with no PLC at all |
04-scada/hmi/build_display.py — the layout is WRPS, the component kit is not |
The displays |
The IO_MUX pattern — one POU owns every located variable |
02-environment/openplc-compose.yml |
03-plc/as-built/ |
01-design/README.md's document-generation method |
The single most transferable idea is the generated contract: one table produces the PLC's register map, the SCADA point list and the CI Server import files. Nothing is transcribed by hand, so nothing silently disagrees.
⚠️ Open items — read before relying on this
| No acceptance tests have been run | 01-design/WRPS-CTL-003 specifies 14 pass-1 and 6 pass-2 tests. None have been executed. The program compiles, runs and publishes its registers correctly — which says nothing about whether the control logic is right. This is the largest gap in the project. |
| No deployment toolchain | The OpenPLC Editor lived only on the retired dev-ubuntu host. STruC++ ships only inside it, so the PLC program is effectively frozen until someone installs the Editor. 03-plc/DEPLOY.md §0. |
| CI Server state unverified | yau-poc-cicore1 was unreachable during the 2026-09 audit. Everything in 04-scada/ is what the repo believes, not what the server holds. |
| The historian has no generator | It is the one hand-maintained link in a generated chain, and it has drifted: the server runs WRPS_ONE_SEC at 5 s, not 1, and WRPS_THIRTY_SEC at 30 s rather than a 60 s WRPS_ONE_MIN. Never hardcode a sample interval. |
| PLC scan overruns | The 100 ms task overran ~100 times over nine days. Logged, tolerated, uninvestigated. |
| The runtime image is irreplaceable | Made with docker commit, in no registry. Deleting it loses the compiled program. 03-plc/as-built/ is the only other copy. |
| Live demo tuning is not in this repo | Operators retune setpoints over Modbus; those live only in the container volume and revert on restart. |
Conventions
src/is canonical. The repo leads; the servers follow. Never treat a file on a host as the source of truth.- Generated files are never hand-edited. Every one says so in its header.
- Secrets are never committed. They live in
02-environment/secrets.local.md, which is git-ignored. A template shows the shape. 99-reference/is read-only. Never edited, never deployed.
Verified 2026-09-02
Everything below was checked against the running systems during the audit, not taken from documentation:
- The deployed PLC program matches
03-plc/src/— identical POU structure, zero differences in non-declaration lines register-map.csv,ci-server-points.csvand all three.qliregenerate byte-identically- All six displays build clean
verify_modbus.pyagainstfake_plc.py: 69/69 read, 15/15 writable, exit 0- The live PLC is running the simulation build, wet-weather scenario, and CI Server is polling it
Assembled from the WRPS working repo, September 2026.