diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ba6ae53 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,186 @@ +# CLAUDE.md β€” wrps-demo-kit + +Instructions for Claude Code working in this repository. Human-facing orientation +is in `README.md`; read that first, then this. + +--- + +> [!IMPORTANT] +> ## πŸͺœ Working protocol β€” one small step at a time +> +> This project is built **incrementally, with every step tested and confirmed +> before the next one starts.** Do not batch phases or run ahead. +> +> 1. **State the plan** β€” what you are about to change, and the **acceptance test** +> that will prove it works. +> 2. **Make the minimal change** for that step alone. +> 3. **Test it and show the evidence** β€” command output, a Modbus read, a +> container log. Not a claim. +> 4. **Wait for the user to confirm** before starting the next step. Never proceed +> on an untested or failing step; fix and re-test. +> 5. After each confirmed step, **commit** with a clear message. +> +> If a step is too big for one testable change, split it. When unsure whether +> something counts as done, ask. + +--- + +## 1. What this is + +A reusable method for building demo automation use cases on **Yokogawa CI Server** +(SCADA) and **OpenPLC Runtime v4** (soft PLC, in Docker), with the Waterloo Road +Pump Station as its worked example. + +**The demo is live and in use.** A simulated three-pump station runs on the PLC and +is polled by CI Server. Treat everything on those machines as production. + +## 2. The two machines + +| Role | Host | Address | +|---|---|---| +| SCADA β€” CI Server R1.03 | `yau-poc-cicore1` | `10.0.0.21` | +| PLC β€” OpenPLC Runtime v4 container | `yau-sls-poc-lin001` | `10.0.0.17`, public `20.211.144.151` | + +SSH alias `lin001` β†’ `azureuser@20.211.144.151`. `10.0.0.17` is only routable from +inside the VNet or over the WireGuard VPN. + +> [!WARNING] +> **`yau-sls-poc-lin001` is a shared, live host** running ~28 containers for +> several unrelated projects behind Caddy and Authelia, serving customer-facing +> demos. **This project owns exactly one container, `openplc-runtime`.** +> +> Never restart Caddy or Authelia. Never publish a port on `0.0.0.0` β€” the host has +> a public IP and no firewall (`ufw` inactive; the Azure NSG governs). Never put +> growing data on the 62 GB root disk. + +Full detail: `02-environment/README.md`. The host owner's own brief, including +rules that exist because breaking them caused outages: +`02-environment/YAU_Linux_Host_Onboarding.md`. + +## 3. Architecture invariant β€” do not violate + +**OpenPLC Runtime is the Modbus TCP _server_ (slave) on port 502. CI Server is the +_client_ (master) and polls it.** Never the reverse. The PLC must never be +configured as a Modbus master. + +Modbus unit / slave id: `1`. + +## 4. The generation chain β€” never hand-edit downstream + +``` +03-plc/src/*.st ─build.py─► 03-plc/register-map.csv ─gen_scada_points.py─► + 04-scada/modbus_points/ci-server-points.csv ─gen_ciserver_qli.py─► *.qli +``` + +To change an address, type or scaling: edit the **`REGISTERS` table in +`03-plc/build.py`**, rebuild, then re-run both SCADA generators. Propagate all the +way or the two sides disagree silently β€” the reads still succeed, they are just +wrong. + +Every generated file reproduces byte-identically. **If a regeneration produces a +diff you did not intend, stop and find out why** before committing it. + +Hand-maintained exceptions, both documented in place: +`04-scada/modbus_points/historian/` and `03-plc/editor-devices/`. + +## 5. Things that are true and non-obvious + +Getting any of these wrong produces plausible-looking wrong answers rather than +errors. + +- **`%MW` is not holding register 0.** `%QW` occupies HR 0–1023; `%MW` starts at + **HR 1024**, so `%MW3` is HR 1027. A wrong-by-1024 read succeeds and returns + nonsense. +- **`%QW17` (alarm bitmask) is unsigned.** Bit 15 does not fit a signed INT. + `%QW7` is genuinely signed. +- **The deployed build is the SIMULATION build.** All `%IW`/`%IX` read 0 β€” correct, + the mux takes simulated values. Do not poll FC04/FC02 expecting live data. +- **Port 502 answers only while a program is running** *and* the Editor project + defines a Modbus Server. A refusal looks like a firewall drop and is not. +- **STruC++ ships only inside the OpenPLC Editor GUI.** There is no CLI. Any change + to PLC logic goes through the Editor β€” and **the Editor is not currently + installed anywhere** (`03-plc/DEPLOY.md` Β§0). +- **The runtime image was made with `docker commit`** and exists in no registry. + Deleting it loses the compiled program. +- **Every CI Server item import renumbers every item**, killing every display. The + fix is to **validate the display in CI Server's Editor Module** β€” one action, + before any other diagnosis. +- **`PS_*` names the four Modbus poll groups and nothing else.** Placeholder `PS_` + *tag* names were removed; they named nothing that exists and caused a real + defect. CI Server items are `AID.WRPS.
.`. +- **Live setpoint tuning is not in this repo.** Operators retune over Modbus; those + values live only in the container volume and revert on restart. + +## 6. Guardrails + +**MAY do freely:** edit repo files, SSH to `lin001` for **read-only** inspection +(`docker ps`, `docker logs`, `docker inspect`, reading files), run the generators, +run `05-tests/` against a fake PLC or a live read. + +**MUST ask first:** +- anything that stops, restarts, recreates or updates the `openplc-runtime` + container β€” **it is live control and CI Server is polling it** +- anything touching another container on that host, or any host-level config +- changing the register map β€” it is a contract with a system you cannot see +- writing to `%MW` on the live PLC (setpoints and the command word are real + commands; `%MW1` executes on a rising edge) +- pushing to `origin`, or any history rewrite + +**MUST NOT:** +- advance past an untested or unconfirmed step +- commit secrets β€” `*.local.md`, `*.pem`, tokens +- edit `99-reference/`, or `03-plc/as-built/` +- hand-edit any generated file +- change CI Server configuration β€” the user does that; you prepare the files +- widen a port bind to `0.0.0.0` + +## 7. Verification, not assertion + +The user is an automation engineer auditing this work. **Show evidence.** + +- Read the live PLC rather than inferring its state. +- After changing a generator, prove the output still reproduces. +- Say plainly when something is unverified. `yau-poc-cicore1` is **not reachable** + from here β€” anything about CI Server's own state is belief, not fact, and must be + labelled as such. +- Do not describe a step as done until its acceptance test has passed. + +## 8. Key commands + +```bash +# the PLC container (read-only) +ssh lin001 "docker ps --filter name=openplc-runtime" +ssh lin001 "docker logs --tail 50 openplc-runtime" + +# poll the live PLC from inside its own network (10.0.0.17 is not routable here) +ssh lin001 "docker run --rm --network openplc-net python:3.12-alpine \ + sh -c 'pip install -q pymodbus && python -c \"...\"'" + +# rebuild the chain +cd 03-plc && python build.py --mode sim +cd 04-scada/modbus_points && python gen_scada_points.py && python gen_ciserver_qli.py +cd 04-scada/hmi && python build_display.py + +# verify the map (needs a route to 10.0.0.17, or use fake_plc.py locally) +cd 05-tests && python verify_modbus.py --host 10.0.0.17 --port 502 --unit 1 +``` + +## 9. Conventions + +- **ST style:** IEC keywords uppercase, 4-space indent, one action per line, a + `(* ... *)` header on every POU. No vendor extensions. +- **Control and simulation never interleave.** Separate POUs; only `10_globals.st` + and `50_prog_io_mux.st` may touch located variables, and `build.py` enforces it. +- **CI Server items:** `AID.WRPS.
.`. +- **Commits:** one per confirmed step. Say what was proven, not just what changed. + +## 10. Open items + +Do not present these as solved. `README.md` carries the full list; the two that +most often catch people out: + +1. **None of the 20 acceptance tests in `01-design/WRPS-CTL-003` have ever been + run.** The program compiles and publishes correct registers; its control + behaviour is unverified. +2. **There is no route to deploy a new program** until the OpenPLC Editor is + installed somewhere that can reach `10.0.0.17:8443`.