wrps-demo-kit/README.md
Clio Liu 5be48b6bbb fix: CI Server on cicore1 is R1.05, not R1.03
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.
2026-09-02 17:34:58 +10:00

139 lines
7 KiB
Markdown

# 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.csv` and all three `.qli` **regenerate
byte-identically**
- All six displays build clean
- `verify_modbus.py` against `fake_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.*