docs: CLAUDE.md, rewritten for the production environment
Replaces the dev-era file entirely. The old one described PXiSEDev and
dev-ubuntu on 192.168.153.0/24, a runtime reachable only through an SSH
tunnel, a docker-compose.yml in a folder that no longer exists, and a
pending R1.03-to-R1.05 migration. None of that is true any more.
Kept: the working protocol - state the plan and its acceptance test, make
the minimal change, show the evidence, wait for confirmation, commit. It
is the reason this project has evidence behind its claims.
Changed in substance:
- the shared-host warning is now prominent. yau-sls-poc-lin001 runs ~28
containers for other projects and serves customer-facing demos; this
project owns one of them.
- a section of non-obvious truths, each of which produces plausible
wrong answers rather than errors: %MW at HR 1024, %QW17 unsigned, all
%IW reading 0 by design, port 502 needing a running program, STruC++
living only inside the Editor GUI, an item import killing every
display, PS_ naming only poll groups.
- guardrails rewritten around what is now live: the container is live
control with CI Server polling it, and %MW writes are real commands.
- a 'verification, not assertion' section. The user is auditing this
work; read the live system rather than inferring it, prove
regenerations still reproduce, and label anything about CI Server as
belief - that machine is not reachable from here.
- open items stated so they are not presented as solved.
Points at the detailed documents rather than restating them.
This commit is contained in:
parent
a7db705399
commit
a042b7936f
1 changed files with 186 additions and 0 deletions
186
CLAUDE.md
Normal file
186
CLAUDE.md
Normal file
|
|
@ -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.<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
|
||||||
|
|
||||||
|
```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.<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`.
|
||||||
Loading…
Add table
Reference in a new issue