scada-points.csv led with a scada_tag column of PS_* names derived from
the PLC register map - PS_STN_WET_WELL_LEVEL and the like. CI Server never
adopted them. They named nothing that exists, nothing in this repo read
them, and read as if authoritative they have already caused a real defect
in a downstream project.
Replaced with ci-server-points.csv, the as-built delivery from the CI
Server side (copied from yau-plant-assistant, not re-derived). It is keyed
on the CI Server item, with the station and point name beside it:
ci_item,ci_station,ci_point,description,poll_group,...
gen_scada_points.py now reproduces that file byte-identically, taking the
names from the LEAF table in gen_ciserver_qli.py so the CSV and the .qli
cannot disagree. tag_for() is gone. Verified: the CSV and all three .qli
regenerate unchanged, and all six displays still build.
The four poll groups keep their PS_ names - PS_STATUS_BITS, PS_PUBLISHED,
PS_SETPOINTS, PS_SIM_CONTROL. Those are live configuration, and are the
only legitimate PS_ names.
modbus_points/README.md gains a 'four namespaces' section - instrument tag,
PLC address, CI Server point, CI Server item - since confusing them is what
produced the placeholder column in the first place.
Also settles the historian drift left open in the previous commit. The
live export is the authority: the server runs WRPS_ONE_SEC at 5 SECONDS,
not 1, and WRPS_THIRTY_SEC at 30 seconds rather than a 60 second
WRPS_ONE_MIN. Code assuming 60 s was wrong by twelvefold. Recorded in
historian/README.md with a caution never to hardcode a sample interval.
Every .qli in the delivery was byte-identical to ours - only the CSV
differed.
115 lines
5.2 KiB
Markdown
115 lines
5.2 KiB
Markdown
# 04-scada — the CI Server side
|
|
|
|
Everything that lives on `yau-poc-cicore1` (`10.0.0.21`): the tag database that
|
|
reads the PLC, and the displays an operator looks at.
|
|
|
|
## Two halves, two deployment mechanisms
|
|
|
|
This is the distinction to get straight before anything else. Conflating them is
|
|
what makes this folder confusing.
|
|
|
|
| | **Configuration** | **Displays** |
|
|
|---|---|---|
|
|
| What | sections, items, Modbus points, historian groups | the `.xml` screens |
|
|
| Lives in | `modbus_points/` | `hmi/` |
|
|
| **Deployed by** | **`dssqld`** — the quickload utility | **file copy** into the displays directory |
|
|
| Generated from | `03-plc/register-map.csv` | `build_display.py` |
|
|
| Binds by | `NSID` / `ID_NUMBER` | **itemId** — healed by name on import |
|
|
|
|
`dssqld` is for configuration only. It has nothing to do with displays. See
|
|
`QUICKLOAD.md`.
|
|
|
|
## The chain
|
|
|
|
Everything derives from the PLC. The PLC side leads, always.
|
|
|
|
```
|
|
03-plc/src/*.st
|
|
│ build.py
|
|
▼
|
|
03-plc/register-map.csv the contract - 69 points
|
|
│ gen_scada_points.py
|
|
▼
|
|
modbus_points/ci-server-points.csv the SCADA view - 49 points, poll groups, units
|
|
│ gen_ciserver_qli.py
|
|
▼
|
|
modbus_points/*.qli 6 sections · 49 Modbus points · 49 items
|
|
│ dssqld -i
|
|
▼
|
|
CI SERVER ◄──── Modbus TCP ──── the PLC on 10.0.0.17:502
|
|
▲
|
|
│ file copy
|
|
│
|
|
hmi/out/*.xml 6 displays
|
|
▲ build_display.py
|
|
│
|
|
hmi/item-ids.csv 49 itemIds, MEASURED from CI Server
|
|
```
|
|
|
|
**Nothing in this chain is hand-edited.** Change a register in `03-plc/build.py`,
|
|
rebuild, then re-run both generators. If CI Server needs a field the generators do
|
|
not emit, it goes in the generator.
|
|
|
|
**One exception:** the historian has no generator. See
|
|
`modbus_points/historian/README.md`.
|
|
|
|
## Layout
|
|
|
|
```
|
|
QUICKLOAD.md dssqld - export and import configuration
|
|
modbus_points/ the tag database (named for the protocol: CI Server
|
|
configures other protocols differently)
|
|
historian/ collection groups and item bindings - HAND-MADE
|
|
hmi/ displays, and the generator that builds them
|
|
ciserver-backup-2026-08/ ⚠️ outdated exports, evidence only - never import
|
|
```
|
|
|
|
## ⚠️ "The values on my display are not updating"
|
|
|
|
**First, and before any other investigation:**
|
|
|
|
> **Open the display in CI Server's Editor Module and validate it.
|
|
> Then report what happened.**
|
|
|
|
Validation resolves every connection by name and repairs the file. It fixes the
|
|
overwhelmingly common cause in seconds — an itemId that no longer matches, which
|
|
happens **every time the items are re-imported**. CI Server recreates items on
|
|
import rather than updating them in place, so every id in every display moves at
|
|
once, and every value goes dead together.
|
|
|
|
That simultaneity is the signature: everything dying at the same moment is ids,
|
|
not communications.
|
|
|
|
**Only once validation has been ruled out**, work down these:
|
|
|
|
| Symptom | Cause |
|
|
|---|---|
|
|
| Some values dead, others fine | Those items were never measured. Harvest the ids — see `hmi/DEPLOY.md`. |
|
|
| Every analogue reads 0, always | You are polling `%IW`/`%IX` (FC04/FC02). Those are **field** inputs; in the simulation build nothing writes them. Live values are in the `%QW` block (FC03). |
|
|
| Values frozen at a plausible number | Communications stopped. Is the container up? Is a **program running**? The runtime opens its Modbus slave only while a program runs — a stopped program means port 502 refuses. |
|
|
| Values moving but wrong | Addressing. `%MW` starts at holding register **1024**, not 0. A wrong-by-1024 read succeeds and returns plausible nonsense. |
|
|
| Alarm word goes negative | `%QW17` must be read **unsigned**. Bit 15 does not fit a signed INT. |
|
|
|
|
**What this repo cannot tell you:** whether CI Server's own poll groups are
|
|
actually scanning. That lives on `cicore1`. If the PLC polls correctly with
|
|
`05-tests/verify_modbus.py` and the ids are good, that is the remaining place to
|
|
look.
|
|
|
|
## Status
|
|
|
|
| | |
|
|
|---|---|
|
|
| Points and items | **Imported and reading live PLC values** (2026-08-14), verified against a direct Modbus read |
|
|
| Generators | Verified reproducible 2026-09-02 — `ci-server-points.csv` and all three `.qli` regenerate byte-identically |
|
|
| Displays | Six built; `build_display.py` runs clean |
|
|
| Historian | 3 groups, 49 bindings — **hand-made, and the live rates differ from these files.** The server runs `WRPS_ONE_SEC` at 5 s and `WRPS_THIRTY_SEC` at 30 s. See `modbus_points/historian/README.md`. |
|
|
| CI Server state | ⚠️ **Unverified.** No access to `cicore1` during this audit. Everything here is what the repo believes, not what the server holds. |
|
|
|
|
### Two things left for you
|
|
|
|
- **Alarm and trend limits.** Every item imports with alarming off and limits at 0.
|
|
Which points alarm, at what threshold and priority, is engineering judgement
|
|
about the plant — not something to derive from a register map.
|
|
- **Re-export the configuration from `cicore1`** and diff it against this folder.
|
|
Until then, `ciserver-backup-2026-08/` is the only record of the server's own
|
|
state, and it is known to disagree with `modbus_points/historian/`.
|