wrps-demo-kit/CLAUDE.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

8 KiB
Raw Blame History

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.05 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 lin001azureuser@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 01023; %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.<SECTION>.<LEAF>.
  • 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

# 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.<SECTION>.<LEAF>.
  • 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.