From 947f632d7f82f7749db6e3e18ed30de985c44929 Mon Sep 17 00:00:00 2001 From: Clio Liu Date: Wed, 2 Sep 2026 17:16:18 +1000 Subject: [PATCH] feat(scada): CI Server tag database, historian, displays The old 05-scada/, restructured around the distinction its README never drew: configuration is deployed with dssqld, displays are deployed by file copy. Conflating the two is what made the folder confusing. modbus_points/ the tag database - named for the protocol, since CI Server configures others differently modbus_points/historian/ 3 groups, 49 bindings - HAND-MADE, no generator, and drifted from the server hmi/ displays and their generator ciserver-backup-2026-08/ outdated exports, evidence only, never import QUICKLOAD.md dssqld export/import, the 5 classes, the import order, and why an item import kills every display README.md the chain end to end, and the not-updating triage Verified during the move - the whole chain is reproducible: scada-points.csv and all three .qli regenerate byte-identically all six displays build clean Removed the K offset machinery from build_display.py, item-ids.meta.json and DEPLOY.md. K was a consistency check on measured ids, not a source of them, and diagnosing a dead screen by arithmetic is wasted effort when validating the display in CI Server's Editor Module fixes it outright. The guidance now leads with that one action. Recorded, not fixed: the repo's historian config disagrees with the 2026-08 CI Server export - WRPS_THIRTY_SEC and a FIVE_SECONDS group exist on the server and not here. cicore1 was unreachable during this audit, so which is correct is unknown. Not carried across: __pycache__, out/*.xml, and the top-level WRPS_Overview.xml that was tracked despite .gitignore declaring the display XMLs to be build output. --- .gitignore | 4 + 04-scada/QUICKLOAD.md | 119 + 04-scada/README.md | 115 + 04-scada/ciserver-backup-2026-08/README.md | 42 + .../export_his_group.qli | 373 ++ .../export_item_his_df.qli | 61 + .../ciserver-backup-2026-08/export_items.qli | 4268 +++++++++++++++++ 04-scada/hmi/DEPLOY.md | 356 ++ 04-scada/hmi/README.md | 146 + 04-scada/hmi/build_display.py | 1591 ++++++ 04-scada/hmi/build_tagtest.py | 327 ++ 04-scada/hmi/check_layout.py | 225 + 04-scada/hmi/component-kit.md | 104 + 04-scada/hmi/item-ids.csv | 50 + 04-scada/hmi/item-ids.meta.json | 8 + 04-scada/hmi/point_format.py | 126 + 04-scada/hmi/render_preview.py | 319 ++ 04-scada/modbus_points/README.md | 222 + 04-scada/modbus_points/gen_ciserver_qli.py | 416 ++ 04-scada/modbus_points/gen_scada_points.py | 265 + 04-scada/modbus_points/historian/README.md | 62 + .../modbus_points/historian/his_group.qli | 44 + 04-scada/modbus_points/historian/item_his.qli | 312 ++ 04-scada/modbus_points/scada-points.csv | 50 + 04-scada/modbus_points/wrps_item_df.qli | 1162 +++++ .../modbus_points/wrps_modbus_point_df.qli | 362 ++ 04-scada/modbus_points/wrps_section_df.qli | 40 + 27 files changed, 11169 insertions(+) create mode 100644 04-scada/QUICKLOAD.md create mode 100644 04-scada/README.md create mode 100644 04-scada/ciserver-backup-2026-08/README.md create mode 100644 04-scada/ciserver-backup-2026-08/export_his_group.qli create mode 100644 04-scada/ciserver-backup-2026-08/export_item_his_df.qli create mode 100644 04-scada/ciserver-backup-2026-08/export_items.qli create mode 100644 04-scada/hmi/DEPLOY.md create mode 100644 04-scada/hmi/README.md create mode 100644 04-scada/hmi/build_display.py create mode 100644 04-scada/hmi/build_tagtest.py create mode 100644 04-scada/hmi/check_layout.py create mode 100644 04-scada/hmi/component-kit.md create mode 100644 04-scada/hmi/item-ids.csv create mode 100644 04-scada/hmi/item-ids.meta.json create mode 100644 04-scada/hmi/point_format.py create mode 100644 04-scada/hmi/render_preview.py create mode 100644 04-scada/modbus_points/README.md create mode 100644 04-scada/modbus_points/gen_ciserver_qli.py create mode 100644 04-scada/modbus_points/gen_scada_points.py create mode 100644 04-scada/modbus_points/historian/README.md create mode 100644 04-scada/modbus_points/historian/his_group.qli create mode 100644 04-scada/modbus_points/historian/item_his.qli create mode 100644 04-scada/modbus_points/scada-points.csv create mode 100644 04-scada/modbus_points/wrps_item_df.qli create mode 100644 04-scada/modbus_points/wrps_modbus_point_df.qli create mode 100644 04-scada/modbus_points/wrps_section_df.qli diff --git a/.gitignore b/.gitignore index d9ef0fd..3339437 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,7 @@ venv/ # Generated by 03-plc/gen_project.py - the OpenPLC Editor v4 project, rebuilt # from src/. Regenerate it; never hand-edit it, never commit it. 03-plc/editor-project/ + +# Generated by 04-scada/hmi/build_display.py - rebuild, don't commit. +# (build_display.py is the source; the display XMLs are its output.) +04-scada/hmi/out/ diff --git a/04-scada/QUICKLOAD.md b/04-scada/QUICKLOAD.md new file mode 100644 index 0000000..c5537c3 --- /dev/null +++ b/04-scada/QUICKLOAD.md @@ -0,0 +1,119 @@ +# Quickload — moving configuration in and out of CI Server + +`dssqld` is CI Server's quickload utility. It exports and imports **configuration +datasets** as `.qli` files: sections, items, Modbus points, historian groups. + +> [!IMPORTANT] +> **Quickload is for configuration only. It is not how displays are deployed.** +> HMI displays are plain file copy/replace into the deployment's `displays` +> directory — see `hmi/DEPLOY.md`. Do not look for a `dssqld` command for them; +> there isn't one. + +## The two commands + +**Export** — writes every record of one class to a file: + +```bash +dssqld -d MODBUS_POINT_DF -e modbus-points.qli +``` + +`-d` names the class, `-e` the output file. + +**Import** — loads a file back in: + +```bash +dssqld -i modbus-points.qli +``` + +The class is read from the file's own `@` header, so import takes no `-d`. + +> Other classes may need different flags. These two forms are the ones proven on +> this project. **If a command fails, ask** — do not guess at flags against a live +> configuration. + +## The classes this project uses + +| Class | File | Holds | Generated? | +|---|---|---|---| +| `SECTION_DF` | `modbus_points/wrps_section_df.qli` | the 6 `AID.WRPS.*` sections | ✅ `gen_ciserver_qli.py` | +| `MODBUS_POINT_DF` | `modbus_points/wrps_modbus_point_df.qli` | 49 Modbus point definitions | ✅ | +| `ITEM_DF` | `modbus_points/wrps_item_df.qli` | 49 items, bound to those points | ✅ | +| `HIS_GROUP_DF` | `modbus_points/historian/his_group.qli` | 3 collection groups | ❌ hand-made | +| `ITEM_HIS_DF` | `modbus_points/historian/item_his.qli` | 49 item→group bindings | ❌ hand-made | + +## Import order — it matters + +```bash +dssqld -i wrps_section_df.qli # 1. sections +dssqld -i wrps_modbus_point_df.qli # 2. points +dssqld -i wrps_item_df.qli # 3. items +dssqld -i historian/his_group.qli # 4. collection groups +dssqld -i historian/item_his.qli # 5. item -> group bindings +``` + +Each step depends on the one before it: + +- **Sections first.** CI Server derives its hierarchy from the dots in a name, and + a section must exist before an item can be created inside it. +- **Points before items**, because an item binds to a point. +- **Groups before bindings**, because `ITEM_HIS_DF` names both an item and a group + and needs both to exist. + +## ⚠️ After importing items, every display goes dead + +**An item import renumbers every item.** Proven on 2026-08-14: items were +re-imported to change engineering units, keeping every `NAME`, `NSID` and +`ID_NUMBER` byte-identical — and every itemId still moved. CI Server recreates +items on import rather than updating them in place. + +Displays bind by **itemId** at runtime, so every value on every screen dies at +once. + +**The fix is one action, and it is not a rebuild:** + +> **Open each display in CI Server's Editor Module and validate it.** + +Validation resolves every connection by name and rewrites the ids. Then re-harvest +so the next build ships the current ids: + +```bash +python hmi/build_display.py --harvest .xml +``` + +`WRPS_TagTest` binds every project item, so harvesting that one display measures +all 49 in a single pass. + +**Moving displays to a different CI Server** needs no rebuild and no re-harvest: +importing a display into the target resolves its connections by name on the way +in. **Import, then validate.** The files built for one server are the files you +ship to the next — provided the item names are identical, which means `ROOT`, +`INSTALL` and `STATION` in `gen_ciserver_qli.py` must match on both. + +## Exporting, to check what the server actually holds + +The repo cannot see `cicore1`, so the only way to confirm the server agrees with +this folder is to export and diff: + +```bash +dssqld -d SECTION_DF -e check_section_df.qli +dssqld -d MODBUS_POINT_DF -e check_modbus_point_df.qli +dssqld -d ITEM_DF -e check_item_df.qli +dssqld -d HIS_GROUP_DF -e check_his_group.qli +dssqld -d ITEM_HIS_DF -e check_item_his_df.qli +``` + +An export contains **every** record of that class, not just this project's — the +2026-08 export in `ciserver-backup-2026-08/` holds 147 WRPS item records among +others. Filter on `AID.WRPS` when comparing. + +**This has already caught a real drift.** The 2026-08 export shows historian +groups (`WRPS_THIRTY_SEC`, `FIVE_SECONDS`) that +`modbus_points/historian/his_group.qli` does not define. One of the two is stale +and nobody currently knows which. + +## Format reference + +CI Server publishes no documentation for the `.qli` format. Ten real exports are +kept in `99-reference/ciserver-qli-exports/` — every field layout and constant in +`gen_ciserver_qli.py` was copied from them. If you need a class this project does +not use, export one record of it and read the header. diff --git a/04-scada/README.md b/04-scada/README.md new file mode 100644 index 0000000..bb76171 --- /dev/null +++ b/04-scada/README.md @@ -0,0 +1,115 @@ +# 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/scada-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 — `scada-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 drifted from the server** | +| 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/`. diff --git a/04-scada/ciserver-backup-2026-08/README.md b/04-scada/ciserver-backup-2026-08/README.md new file mode 100644 index 0000000..e49d21b --- /dev/null +++ b/04-scada/ciserver-backup-2026-08/README.md @@ -0,0 +1,42 @@ +# ciserver-backup-2026-08 — ⚠️ OUTDATED. Evidence only. + +> [!CAUTION] +> **Do not import anything in this folder.** These are exports taken *out of* +> CI Server around **2026-08-20**. They are a record of what the server held at +> that moment, not source, and they are known to be superseded. + +## Why they are kept + +The CI Server side of this project has no other record. `cicore1` was not +reachable during the 2026-09 audit, so nothing here could be checked against the +live system. These exports are the only evidence of what was actually configured, +as opposed to what the repo believes was configured. + +| File | Class | Contains | +|---|---|---| +| `export_items.qli` | `ITEM_DF` | 147 records mentioning `AID.WRPS` | +| `export_his_group.qli` | `HIS_GROUP_DF` | historian collection groups | +| `export_item_his_df.qli` | `ITEM_HIS_DF` | item → group bindings | + +An export holds **every** record of its class, not only this project's. Filter on +`AID.WRPS` when comparing. + +## The drift they revealed + +Comparing these against `../modbus_points/historian/` shows the two do not agree: + +| In this export | In the repo's source | +|---|---| +| `WRPS_EVENT`, `WRPS_ONE_SEC` | ✅ both | +| `WRPS_THIRTY_SEC` | ❌ absent | +| `FIVE_SECONDS` (binding `STN.LEVEL`, `STN.INFLOW`) | ❌ absent — not a WRPS group | +| — | `WRPS_ONE_MIN`, absent from the export | + +That is the whole reason this folder exists rather than being deleted. + +## Replacing it + +When `cicore1` is reachable, re-export the five classes (`../../QUICKLOAD.md`), +diff them against `../modbus_points/`, resolve the differences, and **replace this +folder** with a dated export that has been verified. Do not accumulate backups — +one known-good snapshot is more useful than three of uncertain age. diff --git a/04-scada/ciserver-backup-2026-08/export_his_group.qli b/04-scada/ciserver-backup-2026-08/export_his_group.qli new file mode 100644 index 0000000..5a679a1 --- /dev/null +++ b/04-scada/ciserver-backup-2026-08/export_his_group.qli @@ -0,0 +1,373 @@ + +@LANGUAGE +ENGLISH + + +@VERSION +1.03.00 + + +!================================================================================================================================== + +@FIELDS +NAME,DESCRIPTION,START_TIME,STOP_TIME,FIRST_ROLLOVER +LIFE_TIME,WARN_TIME,ROLLOVER_INT,FORCE_ROLLOVER,FILL_OLD,STORE_QUALITY +SAVE_TIME,SCAN_INTERVAL,CORRECT_DAYLIGHT,EXCLUDE_ARCHIVE,DATA_COMP,AGG_PERIOD_HOUR +AGG_PERIOD_SHIFT,AGG_PERIOD_DAY,AGG_PERIOD_WEEK,AGG_PERIOD_MONTH,AGG_PERIOD_YEAR,AGG_PERIOD_30MIN +AGG_CORRECT_DAY_DST,AGG_CORRECT_SHIFT_DST,NUMBER,NODE,NODE_NAME,NEXT_UNIT_SEQ +NEXT_DATA_SEQ,AVERAGING_INTERVAL,PRIORITY,SAMPLES_RECORD,TYPE,AVERAGING_METHOD +COL_STOR_TYPE,AGG_SHIFT_LENGTH,AGG_WEEK_START,AGG_SHIFT_START,AGG_DAY_START,AGG_COMP_NOT_ITEM + +@HIS_GROUP_DF +"ADT","","04-11-2025 14:43:17","","20-08-2026 14:43:17",\ +"52 weeks","","1 days",0,0,0,\ +0,0,1,0,0,0,\ +0,0,0,0,0,0,\ +0,0,2,1,"",68,\ +2,0,0,0,"Audit","",\ +"0","0","Thursday","00:00","00:00","" + +"ALARM_COUNT","","04-11-2025 14:44:36","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,5,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ALARM_CURRENT","","04-11-2025 14:44:36","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,6,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ALARM_EXT_CURR","","04-11-2025 14:44:36","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,7,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ALARM_HISTORY","","04-11-2025 14:44:36","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,8,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ALARM_SHELVE","","04-11-2025 14:44:36","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,9,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ALH","","04-11-2025 14:43:19","","20-08-2026 14:43:19",\ +"52 weeks","","1 days",0,0,0,\ +0,0,1,0,0,0,\ +0,0,0,0,0,0,\ +0,0,3,1,"",68,\ +2,0,0,0,"Alarm","",\ +"0","0","Thursday","00:00","00:00","" + +"ALM_ATTRS_CUR","","04-11-2025 14:44:36","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,10,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ALM_ATTRS_HIS","","04-11-2025 14:44:36","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,11,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"AUDIT_TRAIL","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,12,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"AUTH_GROUP","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,13,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"EVENT","","","","21-08-2026 00:00:00",\ +"1 weeks","","1 days",0,0,1,\ +0,0,0,0,0,1,\ +0,0,0,0,0,0,\ +0,0,33,1,"",71,\ +2,0,11,100,"Item","",\ +"Event/Item","8 hours","Monday","00:00","00:00","" + +"FIVE_MINUTES","","","","21-08-2026 00:00:00",\ +"1 weeks","","1 days",0,0,1,\ +0,300,1,0,0,1,\ +0,0,0,0,0,0,\ +0,0,34,1,"",68,\ +2,0,3,0,"Item","",\ +"Scan/Time","8 hours","Monday","00:00","00:00","" + +"FIVE_SECONDS","","","","20-08-2026 10:00:00",\ +"1 days","","1 hours",0,0,1,\ +0,5,0,0,0,1,\ +0,0,0,0,0,0,\ +0,0,35,1,"",70,\ +2,0,8,0,"Item","",\ +"Scan/Time","8 hours","Monday","00:00","00:00","" + +"ITEM_ACK","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,14,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ITEM_ALM","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,15,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ITEM_EQP","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,16,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ITEM_FO","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,17,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ITEM_HISTORY","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,18,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ITEM_HISTORY_AG","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,19,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ITEM_ID","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,20,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ITEM_ITM","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,21,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ITEM_OTH","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,22,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ITEM_TYPE","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,23,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ITEM_VAL_STA","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,24,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ITM_VAL_CSV","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,25,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"ONE_HOUR","","","","21-08-2026 00:00:00",\ +"52 weeks","","1 days",0,0,1,\ +0,60,1,0,0,1,\ +0,0,0,0,0,0,\ +0,0,36,1,"",69,\ +2,3600,1,0,"Item","Normal",\ +"Scan/Time","8 hours","Monday","00:00","00:00","" + +"ONE_MINUTE","","","","20-08-2026 12:00:00",\ +"2 days","","12 hours",0,0,1,\ +0,60,0,0,0,1,\ +0,0,0,0,0,0,\ +0,0,37,1,"",68,\ +2,0,5,0,"Item","",\ +"Scan/Time","8 hours","Monday","00:00","00:00","" + +"STATION","","04-11-2025 14:44:37","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,26,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"STATION_STAT","","04-11-2025 14:43:21","","20-08-2026 14:43:21",\ +"31 days","","1 days",0,0,0,\ +0,0,1,1,0,0,\ +0,0,0,0,0,0,\ +0,0,4,1,"",68,\ +2,0,0,0,"Station I/O","",\ +"0","0","Thursday","00:00","00:00","" + +"STATION_STATS","","04-11-2025 14:44:38","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,27,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"SYSTEM_LOG","","04-11-2025 14:44:38","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,28,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"SYSTEM_LOG_HTML","","04-11-2025 14:44:38","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,29,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"SYS_LOG","","04-11-2025 14:43:16","","20-08-2026 14:43:16",\ +"31 days","","1 days",0,0,0,\ +0,0,1,0,0,0,\ +0,0,0,0,0,0,\ +0,0,1,1,"",68,\ +2,0,0,0,"System Log","",\ +"0","0","Thursday","00:00","00:00","" + +"TEN_SECONDS","","","","20-08-2026 10:00:00",\ +"1 days","","1 hours",0,0,1,\ +0,10,0,0,0,1,\ +0,0,0,0,0,0,\ +0,0,38,1,"",68,\ +2,0,7,0,"Item","",\ +"Scan/Time","8 hours","Monday","00:00","00:00","" + +"THIRTY_MINUTES","","","","21-08-2026 00:00:00",\ +"30 days","","1 days",0,0,1,\ +0,30,1,0,0,1,\ +0,0,0,0,0,0,\ +0,0,39,1,"",68,\ +2,1800,2,0,"Item","Normal",\ +"Scan/Time","8 hours","Monday","00:00","00:00","" + +"THIRTY_SECONDS","","","","20-08-2026 12:00:00",\ +"2 days","","6 hours",0,0,1,\ +0,30,0,0,0,1,\ +0,0,0,0,0,0,\ +0,0,40,1,"",68,\ +2,0,6,0,"Item","",\ +"Scan/Time","8 hours","Monday","00:00","00:00","" + +"TWO_MINUTES","","","","21-08-2026 00:00:00",\ +"1 weeks","","1 days",0,0,1,\ +0,120,0,0,0,1,\ +0,0,0,0,0,0,\ +0,0,41,1,"",68,\ +2,0,4,0,"Item","",\ +"Scan/Time","8 hours","Monday","00:00","00:00","" + +"UA_SERVER_AUDIT","","04-11-2025 14:44:38","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,30,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"USER","","04-11-2025 14:44:39","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,31,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"USER_VAL","","04-11-2025 14:44:39","","",\ +"1 weeks","","",0,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,0,32,1,"",69,\ +2,0,0,0,"Report","",\ +"0","0","Thursday","00:00","00:00","" + +"WRPS_EVENT","","","","21-08-2026 00:00:00",\ +"1 weeks","","1 days",0,0,1,\ +0,0,0,0,0,1,\ +0,0,0,0,0,0,\ +0,0,42,1,"",1,\ +2,0,11,100,"Item","",\ +"Event/Item","8 hours","Monday","00:00","00:00","" + +"WRPS_ONE_SEC","","","","20-08-2026 10:00:00",\ +"1 weeks","","1 hours",0,0,1,\ +0,5,0,0,0,1,\ +0,0,0,0,0,0,\ +0,0,43,1,"",1,\ +2,0,8,0,"Item","",\ +"Scan/Time","8 hours","Monday","00:00","00:00","" + +"WRPS_THIRTY_SEC","","","","20-08-2026 12:00:00",\ +"1 weeks","","6 hours",0,0,1,\ +0,30,0,0,0,1,\ +0,0,0,0,0,0,\ +0,0,44,1,"",1,\ +2,0,6,0,"Item","",\ +"Scan/Time","8 hours","Monday","00:00","00:00","" + diff --git a/04-scada/ciserver-backup-2026-08/export_item_his_df.qli b/04-scada/ciserver-backup-2026-08/export_item_his_df.qli new file mode 100644 index 0000000..6e5b2c6 --- /dev/null +++ b/04-scada/ciserver-backup-2026-08/export_item_his_df.qli @@ -0,0 +1,61 @@ + +@LANGUAGE +ENGLISH + + +@VERSION +1.03.00 + + +!================================================================================================================================== + +@FIELDS +NAME,ITEM_NAME,GROUP_NAME,ON_EACH_UPDATE,ON_FIRST_UPDATE +ON_OPTION_CHANGE,ON_PASS_PIT,ON_QUALITY_CHANGE,ON_STATUS_CHANGE,ON_VALUE_CHANGE,STORE_DEADBAND +STORE_HIGH_HIGH_LIMIT,STORE_HIGH_LIMIT,STORE_LOW_LIMIT,STORE_LOW_LOW_LIMIT,STORE_VALUE,STORE_AGG_MAX +STORE_AGG_MIN,STORE_AGG_AVG,STORE_AGG_INTG,STORE_AGG_STDV,STORE_AGG_CNT,STORE_AGG_DIFF_SUM +AGG_DIFF_SUM_RANGE,AGG_DIFF_SUM_PERC,AGG_INTG_PERIOD + +@ITEM_HIS_DF +"EVENT:ESDS32DCD.HeTwTwLocksClos","ESDS32DCD.HeTwTwLocksClos","EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"EVENT:ESDS32DCD.HeTwTwLocksClosTest","ESDS32DCD.HeTwTwLocksClosTest","EVENT",0,0,\ +0,0,0,1,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"FIVE_SECONDS:AID.WRPS.STN.INFLOW","AID.WRPS.STN.INFLOW","FIVE_SECONDS",0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"FIVE_SECONDS:AID.WRPS.STN.LEVEL","AID.WRPS.STN.LEVEL","FIVE_SECONDS",0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"ONE_HOUR:Site1.Comms_Status","Site1.Comms_Status","ONE_HOUR",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.STN.CMD_ACK","AID.WRPS.STN.CMD_ACK","WRPS_EVENT",1,1,\ +1,0,1,1,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_ONE_SEC:AID.WRPS.STN.LEVEL","AID.WRPS.STN.LEVEL","WRPS_ONE_SEC",0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + diff --git a/04-scada/ciserver-backup-2026-08/export_items.qli b/04-scada/ciserver-backup-2026-08/export_items.qli new file mode 100644 index 0000000..e7e38f7 --- /dev/null +++ b/04-scada/ciserver-backup-2026-08/export_items.qli @@ -0,0 +1,4268 @@ + +@LANGUAGE +ENGLISH + + +@VERSION +1.03.00 + + +!================================================================================================================================== + +@FIELDS +NAME,NSID,PARENT_NSID,SECTION_PATH,DESCRIPTION +DEADBAND,LOW_LIMIT,HIGH_LIMIT,LOW_LOW_LIMIT,HIGH_HIGH_LIMIT,T_VALUE +P_VALUE,I_VALUE,TREND_UP_LIMIT,TREND_LOW_LIMIT,SCALE_HIGH_LIMIT,SCALE_LOW_LIMIT +DUMMY2,COMMENT_1,COMMENT_2,VALUE_FORMAT,POSITIONED,LONGITUDE +LATITUDE,ALARMING,DIAG,NAME_IN_ITM_TAB,STORAGE,AUDIT_INFO +FO_ITEM,BLOCKED,PARENT_BLOCKED,ALARM_INHIBIT,PARENT_ALARM_INHIBIT,OPC_VISIBLE +PARENT_OPC_VISIBLE,OPC_READ,OPC_WRITE,OPC_ALARM_DETECTION,HAS_SUB,STRING_LENGTH +DELAY,REPEAT,ID_GROUP,ID_NUMBER,ITEM_REP,ITEM_TYPE +ITEM_SPECIAL,ACKN_TYPE,ALARM_GROUP,FRONT_END_NODE,DISTR_TYPE,INSTALL +UNIT,TAG,LIMIT_CLAMP,OUT_OF_RANGE,COL_GROUP,STATION +POINT,FO_GROUP,ITEM_STAT_1,ITEM_STAT_2,ITEM_STAT_3,ITEM_STAT_4 +ITEM_STAT_5,ITEM_STAT_6,ALARM_STATE_1,ALARM_STATE_2,ALARM_STATE_3,ALARM_STATE_4 +ALARM_STATE_5,ALARM_STATE_6,PRIORITY_1,PRIORITY_2,PRIORITY_3,PRIORITY_4 +PRIORITY_5,PRIORITY_6,ALARM_TEXT_1,ALARM_TEXT_2,ALARM_TEXT_3,ALARM_TEXT_4 +ALARM_TEXT_5,ALARM_TEXT_6,ALARM_COLOR_1,ALARM_COLOR_2,ALARM_COLOR_3,ALARM_COLOR_4 +ALARM_COLOR_5,ALARM_COLOR_6,MNEMONIC_1,MNEMONIC_2,MNEMONIC_3,MNEMONIC_4 +MNEMONIC_5,MNEMONIC_6,AOI_1,AOI_2,AOI_3,AOI_4 +AOI_5,AOI_6,AOI_7,AOI_8,AOI_9,AOI_10 +AOI_11,AOI_12,AOI_13,AOI_14,AOI_15,AOI_16 +ENG_UNIT,PROCESS_LIST,POINT_NAME,OPC_AE_STATION_NAME,OPC_EVENT_SOURCE,OPC_EVENT_SOURCE_NAME +CREATED_BY,SHELVE_ENABLED_1,SHELVE_ENABLED_2,SHELVE_ENABLED_3,SHELVE_ENABLED_4,SHELVE_ENABLED_5 +SHELVE_ENABLED_6,AGG_INTERVAL + +@ITEM_DF +"AID.WRPS.PU301.AVAILABLE",332,172,"AID.WRPS.PU301","Available",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,291,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","AVAILABLE","",0,"","WRPS_PLC",\ +"PU301_AVAILABLE","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU301_AVAILABLE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU301.RUNNING",329,172,"AID.WRPS.PU301","Running",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,288,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","RUNNING","",0,"","WRPS_PLC",\ +"PU301_RUNNING","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU301_RUNNING","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU301.RUN_CMD",326,172,"AID.WRPS.PU301","Run command",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,285,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","RUN_CMD","",0,"","WRPS_PLC",\ +"PU301_RUN_CMD","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU301_RUN_CMD","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU301.RUN_HOURS",349,172,"AID.WRPS.PU301","Run hours",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,308,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","RUN_HOURS","",0,"","WRPS_PLC",\ +"PU301_RUN_HOURS","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"h","","WRPS_PLC:PU301_RUN_HOURS","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU301.STATE",354,172,"AID.WRPS.PU301","Pump state (enum 3.2)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,313,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","STATE","",0,"","WRPS_PLC",\ +"PU301_STATE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU301_STATE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU301.TRIPPED",338,172,"AID.WRPS.PU301","Tripped",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,297,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","TRIPPED","",0,"","WRPS_PLC",\ +"PU301_TRIPPED","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU301_TRIPPED","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU302.AVAILABLE",333,173,"AID.WRPS.PU302","Available",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,292,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","AVAILABLE","",0,"","WRPS_PLC",\ +"PU302_AVAILABLE","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU302_AVAILABLE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU302.RUNNING",330,173,"AID.WRPS.PU302","Running",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,289,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","RUNNING","",0,"","WRPS_PLC",\ +"PU302_RUNNING","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU302_RUNNING","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU302.RUN_CMD",327,173,"AID.WRPS.PU302","Run command",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,286,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","RUN_CMD","",0,"","WRPS_PLC",\ +"PU302_RUN_CMD","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU302_RUN_CMD","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU302.RUN_HOURS",350,173,"AID.WRPS.PU302","Run hours",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,309,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","RUN_HOURS","",0,"","WRPS_PLC",\ +"PU302_RUN_HOURS","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"h","","WRPS_PLC:PU302_RUN_HOURS","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU302.STATE",355,173,"AID.WRPS.PU302","Pump state (enum 3.2)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,314,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","STATE","",0,"","WRPS_PLC",\ +"PU302_STATE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU302_STATE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU302.TRIPPED",339,173,"AID.WRPS.PU302","Tripped",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,298,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","TRIPPED","",0,"","WRPS_PLC",\ +"PU302_TRIPPED","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU302_TRIPPED","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU303.AVAILABLE",334,174,"AID.WRPS.PU303","Available",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,293,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","AVAILABLE","",0,"","WRPS_PLC",\ +"PU303_AVAILABLE","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU303_AVAILABLE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU303.RUNNING",331,174,"AID.WRPS.PU303","Running",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,290,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","RUNNING","",0,"","WRPS_PLC",\ +"PU303_RUNNING","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU303_RUNNING","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU303.RUN_CMD",328,174,"AID.WRPS.PU303","Run command",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,287,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","RUN_CMD","",0,"","WRPS_PLC",\ +"PU303_RUN_CMD","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU303_RUN_CMD","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU303.RUN_HOURS",351,174,"AID.WRPS.PU303","Run hours",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,310,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","RUN_HOURS","",0,"","WRPS_PLC",\ +"PU303_RUN_HOURS","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"h","","WRPS_PLC:PU303_RUN_HOURS","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU303.STATE",356,174,"AID.WRPS.PU303","Pump state (enum 3.2)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,315,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","STATE","",0,"","WRPS_PLC",\ +"PU303_STATE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU303_STATE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU303.TRIPPED",340,174,"AID.WRPS.PU303","Tripped",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,299,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","TRIPPED","",0,"","WRPS_PLC",\ +"PU303_TRIPPED","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU303_TRIPPED","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SIM.INFLOW",371,176,"AID.WRPS.SIM","manual inflow (mode 0)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,330,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","INFLOW","",0,"","WRPS_PLC",\ +"SIM_INFLOW","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"m3/h","","WRPS_PLC:SIM_INFLOW","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SIM.RESET",373,176,"AID.WRPS.SIM","write 1 to reset scenario, self-clearing",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,332,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","RESET","",0,"","WRPS_PLC",\ +"SIM_RESET","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:SIM_RESET","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SIM.SCENARIO",372,176,"AID.WRPS.SIM","scenario 0=man 1=diurnal 2=wet 3=ref",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,331,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","SCENARIO","",0,"","WRPS_PLC",\ +"SIM_SCENARIO","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:SIM_SCENARIO","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SIM.TIME_SCALE",374,176,"AID.WRPS.SIM","time scale 1-120",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,333,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","TIME_SCALE","",0,"","WRPS_PLC",\ +"SIM_TIME_SCALE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"x","","WRPS_PLC:SIM_TIME_SCALE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.CMD_PARAM",362,175,"AID.WRPS.SP","Command parameter (pump number)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,321,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","CMD_PARAM","",0,"","WRPS_PLC",\ +"SP_CMD_PARAM","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:SP_CMD_PARAM","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.CMD_WORD",361,175,"AID.WRPS.SP","Command word (section 3.3)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,320,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","CMD_WORD","",0,"","WRPS_PLC",\ +"SP_CMD_WORD","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:SP_CMD_WORD","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.HIGH_ALARM",368,175,"AID.WRPS.SP","High level alarm",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,327,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","HIGH_ALARM","",0,"","WRPS_PLC",\ +"SP_HIGH_ALARM","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_HIGH_ALARM","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.LEVEL_SP",363,175,"AID.WRPS.SP","Level control setpoint",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,322,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","LEVEL_SP","",0,"","WRPS_PLC",\ +"SP_LEVEL_SP","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_LEVEL_SP","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.MIN_SPEED",369,175,"AID.WRPS.SP","Minimum drive speed",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,328,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","MIN_SPEED","",0,"","WRPS_PLC",\ +"SP_MIN_SPEED","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_MIN_SPEED","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.MODE",360,175,"AID.WRPS.SP","Station mode: 1 = auto, 2 = off",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,319,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","MODE","",0,"","WRPS_PLC",\ +"SP_MODE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:SP_MODE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.SERVICE_HRS",370,175,"AID.WRPS.SP","Service interval",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,329,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","SERVICE_HRS","",0,"","WRPS_PLC",\ +"SP_SERVICE_HRS","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"h","","WRPS_PLC:SP_SERVICE_HRS","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.START_DUTY",364,175,"AID.WRPS.SP","Start duty level",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,323,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","START_DUTY","",0,"","WRPS_PLC",\ +"SP_START_DUTY","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_START_DUTY","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.START_P2",365,175,"AID.WRPS.SP","Start pump 2 level",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,324,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","START_P2","",0,"","WRPS_PLC",\ +"SP_START_P2","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_START_P2","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.START_P3",366,175,"AID.WRPS.SP","Start pump 3 level",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,325,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","START_P3","",0,"","WRPS_PLC",\ +"SP_START_P3","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_START_P3","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.STOP_ALL",367,175,"AID.WRPS.SP","Stop all level",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,326,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","STOP_ALL","",0,"","WRPS_PLC",\ +"SP_STOP_ALL","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_STOP_ALL","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.ALARM_WORD",358,171,"AID.WRPS.STN","Alarm bitmask (section 6) - READ AS UNSIGNED",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,317,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","ALARM_WORD","",0,"","WRPS_PLC",\ +"STN_ALARM_WORD","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_ALARM_WORD","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.CMD_ACK",359,171,"AID.WRPS.STN","Command acknowledge (echoes %MW1)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,318,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","CMD_ACK","",0,"","WRPS_PLC",\ +"STN_CMD_ACK","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_CMD_ACK","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.DISCHARGE",343,171,"AID.WRPS.STN","Total discharge flow",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,302,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","DISCHARGE","",0,"","WRPS_PLC",\ +"STN_DISCHARGE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"m3/h","","WRPS_PLC:STN_DISCHARGE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.DUTY_PUMP",357,171,"AID.WRPS.STN","Current duty pump (0 = none, 1-3)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,316,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","DUTY_PUMP","",0,"","WRPS_PLC",\ +"STN_DUTY_PUMP","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_DUTY_PUMP","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.HIGH_LEVEL",336,171,"AID.WRPS.STN","High level alarm",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,295,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","HIGH_LEVEL","",0,"","WRPS_PLC",\ +"STN_HIGH_LEVEL","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_HIGH_LEVEL","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.INFLOW",342,171,"AID.WRPS.STN","Inflow",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,301,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","INFLOW","",0,"","WRPS_PLC",\ +"STN_INFLOW","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"m3/h","","WRPS_PLC:STN_INFLOW","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.IN_AUTO",335,171,"AID.WRPS.STN","Station in auto",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,294,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","IN_AUTO","",0,"","WRPS_PLC",\ +"STN_IN_AUTO","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_IN_AUTO","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.LEVEL",341,171,"AID.WRPS.STN","Wet well level",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,300,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","LEVEL","",0,"","WRPS_PLC",\ +"STN_LEVEL","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:STN_LEVEL","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.NET_ACCUM",348,171,"AID.WRPS.STN","Net accumulation (signed)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,307,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","NET_ACCUM","",0,"","WRPS_PLC",\ +"STN_NET_ACCUM","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"m3/h","","WRPS_PLC:STN_NET_ACCUM","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.PUMPS_RUNNING",344,171,"AID.WRPS.STN","Pumps running",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,303,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","PUMPS_RUNNING","",0,"","WRPS_PLC",\ +"STN_PUMPS_RUNNING","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"count","","WRPS_PLC:STN_PUMPS_RUNNING","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.SPEED",345,171,"AID.WRPS.STN","Common drive speed",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,304,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","SPEED","",0,"","WRPS_PLC",\ +"STN_SPEED","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:STN_SPEED","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.SPILL_ACTIVE",337,171,"AID.WRPS.STN","Spill active",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,296,"Boolean","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","SPILL_ACTIVE","",0,"","WRPS_PLC",\ +"STN_SPILL_ACTIVE","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_SPILL_ACTIVE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.STATE",353,171,"AID.WRPS.STN","Station state (enum 3.1)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,312,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","STATE","",0,"","WRPS_PLC",\ +"STN_STATE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_STATE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.TIME_TO_LSHH",347,171,"AID.WRPS.STN","Time to LSHH (32767 = drawing down)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,306,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","TIME_TO_LSHH","",0,"","WRPS_PLC",\ +"STN_TIME_TO_LSHH","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"s","","WRPS_PLC:STN_TIME_TO_LSHH","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.TIME_TO_SPILL",346,171,"AID.WRPS.STN","Time to spill weir (32767 = drawing down)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,305,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","TIME_TO_SPILL","",0,"","WRPS_PLC",\ +"STN_TIME_TO_SPILL","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"s","","WRPS_PLC:STN_TIME_TO_SPILL","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.VOL_TO_SPILL",352,171,"AID.WRPS.STN","Volume remaining to spill",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,311,"Real","",\ +"","","","UNLICENCED","Local Host","AID",\ +"WRPS","VOL_TO_SPILL","",0,"","WRPS_PLC",\ +"STN_VOL_TO_SPILL","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"m3","","WRPS_PLC:STN_VOL_TO_SPILL","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AOG.Templates.Alarm",5,2,"AOG.Templates","Template item for the AOG base classes",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,1,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,3,"Integer","",\ +"","MANUAL","","UNLICENCED","Local Host","AOG",\ +"Templates","Alarm","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","AOI_COMMS","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AOG.Templates.Integer",4,2,"AOG.Templates","Template item for the AOG base classes",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,2,"Integer","",\ +"","","","UNLICENCED","Local Host","AOG",\ +"Templates","Integer","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AOG.Templates.PV",6,2,"AOG.Templates","Template item for the AOG base classes",\ +0,10,90,5,95,0,\ +0,0,0,0,100,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,4,"Real","",\ +"","MANUAL","","UNLICENCED","Local Host","AOG",\ +"Templates","PV","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AOG.Templates.Real",3,2,"AOG.Templates","Template item for the AOG base classes",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,1,"Real","",\ +"","","","UNLICENCED","Local Host","AOG",\ +"Templates","Real","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.ContWeightHex",147,57,"ESDS32DCD","CONTAINER WEIGHT TO DISPLAY (Tons, HEX)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,116,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","ContWeightHex","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.EnergyConsumptionTotal",143,57,"ESDS32DCD","Energy consumption kW (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,112,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","EnergyConsumptionTotal","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.EngineHoursTotal",131,57,"ESDS32DCD","Engine hours (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,100,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","EngineHoursTotal","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.FuelConsumptionTotal",142,57,"ESDS32DCD","Fuel consumption l/h (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,111,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","FuelConsumptionTotal","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeAllLandPinsUp",73,57,"ESDS32DCD","NORMAL & TWINLIFT LANDPINS ARE UP",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,42,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeAllLandPinsUp","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeAllTwLoBoxUp",65,57,"ESDS32DCD","ALL TWISTLOCK BOXES ARE UP",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,34,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeAllTwLoBoxUp","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeAllTwiTwLoClo",61,57,"ESDS32DCD","ALL TWINLIFT TWISTLOCKS ARE CLOSED",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,30,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeAllTwiTwLoClo","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeAllTwiTwLoOpe",67,57,"ESDS32DCD","ALL TWINLIFT TWISTLOCKS ARE OPEN",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,36,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeAllTwiTwLoOpe","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeGap_SingleMod",104,57,"ESDS32DCD","GAP & SINGLE MODE",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,73,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeGap_SingleMod","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeGap_TwinMode",103,57,"ESDS32DCD","GAP & TWIN MODE",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,72,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeGap_TwinMode","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeHoist5",101,57,"ESDS32DCD","HELP BIT FOR HOISTING 5 (TWINLIFT)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,70,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeHoist5","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeNoGap_SingleM",106,57,"ESDS32DCD","NO GAP & SINGLE MODE",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,75,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeNoGap_SingleM","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeNoGap_TwinMod",105,57,"ESDS32DCD","NO GAP & TWIN MODE",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,74,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeNoGap_TwinMod","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeOneLandPin",100,57,"ESDS32DCD","AT LEAST ONE OF LANDPINS ARE ON",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,69,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeOneLandPin","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeOneTwLandPinsUp",99,57,"ESDS32DCD","AT LEAST ONE OF TWINLIFT LANDPINS ARE UP",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,68,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeOneTwLandPinsUp","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeSingleMode",64,57,"ESDS32DCD","SINGLE MODE",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,33,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeSingleMode","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeSingleTwinOn",68,57,"ESDS32DCD","SINGLE TWIN ON",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,37,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeSingleTwinOn","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeSprT_BeLandPiUpFr",77,57,"ESDS32DCD","SPREADER T-BEAM FRONT LANDPINS ARE UP",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,46,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeSprT_BeLandPiUpFr","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeSprT_BeLandPiUpRe",78,57,"ESDS32DCD","SPREADER T-BEAM REAR LANDPINS ARE UP",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,47,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeSprT_BeLandPiUpRe","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeT_BeLandPinsUp",102,57,"ESDS32DCD","T-BEAM LANDPINS ARE UP AT SELECTED END (FRONT/REAR) OR AT BOTH END",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,71,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeT_BeLandPinsUp","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwBoOrLandErFL",83,57,"ESDS32DCD","TWINLIFT TWISTLOCKBOX/ LANDPIN SENSOR ERROR FRONT LEFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,52,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwBoOrLandErFL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwBoOrLandErFR",84,57,"ESDS32DCD","TWINLIFT TWISTLOCKBOX/ LANDPIN SENSOR ERROR FRONT RIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,53,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwBoOrLandErFR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwBoOrLandErRL",85,57,"ESDS32DCD","TWINLIFT TWISTLOCKBOX/ LANDPIN SENSOR ERROR REAR LEFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,54,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwBoOrLandErRL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwBoOrLandErRR",86,57,"ESDS32DCD","TWINLIFT TWISTLOCKBOX/ LANDPIN SENSOR ERROR REAR RIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,55,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwBoOrLandErRR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwBoxOrLandErr",97,57,"ESDS32DCD","TWINLIFT TWISTLOCKBOX / LANDPIN ERROR",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,66,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwBoxOrLandErr","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwFreeCircVal",98,57,"ESDS32DCD","TWINLIFT CONTROL FOR FREE CIRC VALVE",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,67,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwFreeCircVal","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwFrontLandPi",74,57,"ESDS32DCD","TWINLIFT, FRONT CONTAINER LANDPINS ARE UP",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,43,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwFrontLandPi","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwLoCloFr",80,57,"ESDS32DCD","TWISTLOCK CLOSE FRONT T-BEAM AND TWINLIFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,49,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwLoCloFr","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwLoCloRe",81,57,"ESDS32DCD","TWISTLOCK CLOSE REAR T-BEAM AND TWINLIFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,50,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwLoCloRe","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwLoOpeFr",79,57,"ESDS32DCD","TWISTLOCK OPEN FRONT T-BEAM AND TWINLIFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,48,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwLoOpeFr","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwLoOpeRe",82,57,"ESDS32DCD","TWISTLOCK OPEN REAR T-BEAM AND TWINLIFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,51,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwLoOpeRe","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwLockCloFr",70,57,"ESDS32DCD","TWISTLOCKS CLOSE (FRONT WHEN TWINLIFT)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,39,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwLockCloFr","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwLockCloseRe",71,57,"ESDS32DCD","TWISTLOCKS CLOSE REAR WHEN TWINLIFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,40,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwLockCloseRe","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwLockOpeFr",69,57,"ESDS32DCD","TWISTLOCKS OPEN (FRONT WHEN TWINLIFT)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,38,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwLockOpeFr","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwLockOpenRe",72,57,"ESDS32DCD","TWISTLOCK OPEN REAR WHEN TWINLIFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,41,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwLockOpenRe","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwRearLandPin",75,57,"ESDS32DCD","TWINLIFT, REAR CONTAINER LANDPINS ARE UP",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,44,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwRearLandPin","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwTwLoErrFL",87,57,"ESDS32DCD","TWINLIFT TWISTLOCK ERROR FRONT LEFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,56,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwTwLoErrFL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwTwLoErrFR",88,57,"ESDS32DCD","TWINLIFT TWISTLOCK ERROR FRONT RIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,57,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwTwLoErrFR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwTwLoErrRL",89,57,"ESDS32DCD","TWINLIFT TWISTLOCK ERROR REAR LEFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,58,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwTwLoErrRL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwTwLoErrRR",90,57,"ESDS32DCD","TWINLIFT TWISTLOCK ERROR REAR RIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,59,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwTwLoErrRR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwTwLocksClos",58,57,"ESDS32DCD","TWINLIFT TWISTLOCKS ARE CLOSED",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,25,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwTwLocksClos","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwTwLocksClosTest",59,57,"ESDS32DCD","TWINLIFT TWISTLOCKS ARE CLOSED",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,26,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwTwLocksClosTest","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwTwLocksOpen",60,57,"ESDS32DCD","TWINLIFT TWISTLOCKS ARE OPEN",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,29,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwTwLocksOpen","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwinDisplay",62,57,"ESDS32DCD","TWINLIFT DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,31,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwinDisplay","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwinGapSenErrHoPrev",92,57,"ESDS32DCD","TWINGAP SENSOR ERROR HOIST PREVENTION WHEN TWLOCKS CLOSED",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,61,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwinGapSenErrHoPrev","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwinGapSensEr",91,57,"ESDS32DCD","TWINLIFT GAP SENSOR ERROR",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,60,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwinGapSensEr","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HeTwinLiftGapR",76,57,"ESDS32DCD","GAP BETWEEN TWINLIFT CONTAINERS REAR",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,45,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HeTwinLiftGapR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HoistLowerTotalHours",135,57,"ESDS32DCD","Hoisting and Lowering hours (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,104,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HoistLowerTotalHours","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.HybridHoursTotal",132,57,"ESDS32DCD","Hybrid hours (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,101,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","HybridHoursTotal","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.KeAutoTwLoBoxUpAllow",94,57,"ESDS32DCD","AUTOMATIC TWISTLOCK BOXES UP ALLOWED",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,63,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","KeAutoTwLoBoxUpAllow","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.KeTwBoxUpFr",93,57,"ESDS32DCD","TWINLIFT TWISTLOCK BOXES UP FRONT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,62,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","KeTwBoxUpFr","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.KeTwinBoxUpRe",109,57,"ESDS32DCD","TWIN BOX UP REAR",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,78,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","KeTwinBoxUpRe","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.KeTwinMode",63,57,"ESDS32DCD","TWIN MODE",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,32,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","KeTwinMode","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.KeTwinOffSelect",108,57,"ESDS32DCD","TWIN OFF SELECTION WHEN OPEN TOP CONTAINER",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,77,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","KeTwinOffSelect","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.KeTwinOnSelect",107,57,"ESDS32DCD","TWIN ON SELECTION WHEN GAP SENSOR DOESN'T SEE THE GAP",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,76,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","KeTwinOnSelect","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.MachineNumber",130,57,"ESDS32DCD","Machine number",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,99,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","MachineNumber","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.MachineType",129,57,"ESDS32DCD","Machine type (1=ESC, 2=ESH, 3=HSC, 4=HSH, 5=FSC, 6=FSH)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,98,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","MachineType","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsCentrLub",159,57,"ESDS32DCD","NUMBER OF CENTRAL LUBRICATIONS FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,128,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsCentrLub","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsEngTemp",148,57,"ESDS32DCD","ENGINE TEMP TO NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,117,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsEngTemp","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsEngineHoursTrip",166,57,"ESDS32DCD","ENGINE HOURS (TRIP) FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,135,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsEngineHoursTrip","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsFuelConsLTim",165,57,"ESDS32DCD","ENGINE FUEL CONSUMPTION LONG TIME l/h FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,134,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsFuelConsLTim","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsFuelLevelPercentVal",146,57,"ESDS32DCD","FUEL LEVEL IN % TO DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,115,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsFuelLevelPercentVal","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsFuelRate",163,57,"ESDS32DCD","FUEL RATE l/h FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,132,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsFuelRate","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsHoistHoursTrip",156,57,"ESDS32DCD","HOISTING HOURS (TRIP) FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,125,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsHoistHoursTrip","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsHoursBwdTrip",152,57,"ESDS32DCD","BACKWARD HOURS (TRIP) FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,121,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsHoursBwdTrip","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsHoursFwdTrip",151,57,"ESDS32DCD","FORWARD HOURS (TRIP) FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,120,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsHoursFwdTrip","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsHoursInPlaceTrip",153,57,"ESDS32DCD","IN PLACE HOURS (TRIP) FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,122,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsHoursInPlaceTrip","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsKmsBwdTrip",155,57,"ESDS32DCD","REVERSE KMS (TRIP) FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,124,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsKmsBwdTrip","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsKmsFwdTrip",154,57,"ESDS32DCD","FORWARD KMS (TRIP) FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,123,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsKmsFwdTrip","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsNrOf20contTrip",157,57,"ESDS32DCD","NUMBER OF 20' CONTAINERS (TRIP) FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,126,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsNrOf20contTrip","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsNrOf2x20contTrip",160,57,"ESDS32DCD","NUMBER OF TWIN / SINGLE LIFTS (TRIP) FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,129,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsNrOf2x20contTrip","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsNrOf40contTrip",158,57,"ESDS32DCD","NUMBER OF 40' CONTAINERS (TRIP) FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,127,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsNrOf40contTrip","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsRPM",144,57,"ESDS32DCD","RPM TO NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,113,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsRPM","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsSprHeight",149,57,"ESDS32DCD","SPREADER HEIGHT TO NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,118,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsSprHeight","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsSprLength",150,57,"ESDS32DCD","SPREADER LENGTH TO NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,119,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsSprLength","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsSprLengthFr",161,57,"ESDS32DCD","SPREADER LENGTH FRONT FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,130,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsSprLengthFr","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsSprLengthRe",162,57,"ESDS32DCD","SPREADER LENGTH REAR FOR DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,131,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsSprLengthRe","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsStability",145,57,"ESDS32DCD","STABILITY TO NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,114,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsStability","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsTotFuelUsed",164,57,"ESDS32DCD","TOTAL FUEL USED FOR NS DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,133,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsTotFuelUsed","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.NsTwinliftSel",66,57,"ESDS32DCD","TWINLIFT SELECTION TO DISPLAY",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,35,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","NsTwinliftSel","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS600TwLoBoUpA1RR",121,57,"ESDS32DCD","TWISTLOCK BOX A1 UP REAR RIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,90,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS600TwLoBoUpA1RR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS601TwLoA1CloseRR",122,57,"ESDS32DCD","TWINLIFT TWISTLOCKS CLOSE A1 REAR RIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,91,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS601TwLoA1CloseRR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS602TwLoA1OpeRR",123,57,"ESDS32DCD","TWINLIFT TWISTLOCKS OPEN A1 REAR RIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,92,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS602TwLoA1OpeRR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS603TwA1LandPRR",124,57,"ESDS32DCD","TWINLIFT LANDPINS A1 UP REAR RIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,93,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS603TwA1LandPRR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS604TwLoBoUpA2RL",117,57,"ESDS32DCD","TWISTLOCK BOX A2 UP REAR LEFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,86,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS604TwLoBoUpA2RL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS605TwLoA2CloseRL",118,57,"ESDS32DCD","TWINLIFT TWISTLOCKS CLOSE A2 REAR LEFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,87,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS605TwLoA2CloseRL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS606TwLoA2OpeRL",119,57,"ESDS32DCD","TWINLIFT TWISTLOCKS OPEN A2 REAR LEFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,88,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS606TwLoA2OpeRL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS607TwA2LandPRL",120,57,"ESDS32DCD","TWINLIFT LANDPINS UP A2 REAR LEFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,89,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS607TwA2LandPRL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS608TwLoBoUpB1FR",113,57,"ESDS32DCD","TWISTLOCK BOX B1 UP FRONT RIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,82,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS608TwLoBoUpB1FR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS609TwLoB1CloseFR",114,57,"ESDS32DCD","TWINLIFT TWISTLOCKS CLOSE B1 FRONT RIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,83,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS609TwLoB1CloseFR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS610TwLoB1OpeFR",115,57,"ESDS32DCD","TWINLIFT TWISTLOCKS OPEN B1 FRONT RIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,84,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS610TwLoB1OpeFR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS611TwB1LandPFR",116,57,"ESDS32DCD","TWINLIFT LANDPINS B1 UP FRONT RIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,85,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS611TwB1LandPFR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS613TwLoB2CloseFL",110,57,"ESDS32DCD","TWINLIFT TWISTLOCKS CLOSE B2 FRONT LEFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,79,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS613TwLoB2CloseFL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS614TwLoB2OpeFL",111,57,"ESDS32DCD","TWINLIFT TWISTLOCKS OPEN B2 FRONT LEFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,80,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS614TwLoB2OpeFL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS615TwB2LandPFL",112,57,"ESDS32DCD","TWINLIFT LANDPINS UP B2 FRONT LEFT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,81,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS615TwB2LandPFL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS700_1GapContR",125,57,"ESDS32DCD","TWINLIFT GAP BETWEEN TWO CONTAINERS WHEN \0\, REAR",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,94,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS700_1GapContR","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS700_2GapContF",127,57,"ESDS32DCD","TWINLIFT GAP BETWEEN TWO CONTAINERS WHEN \0\, FRONT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,96,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS700_2GapContF","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS702_1TwLiCoRe",128,57,"ESDS32DCD","TWINLIFT CONTAINERS (NOT TOP OPEN CONTAINER), REAR",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,97,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS702_1TwLiCoRe","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.PInS702_2TwLiCoFr",126,57,"ESDS32DCD","TWINLIFT CONTAINERS (NOT TOP OPEN CONTAINER), FRONT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,95,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","PInS702_2TwLiCoFr","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.POuTwBoxDownL",95,57,"ESDS32DCD","TWINLIFT TW-LO BOX DOWN LIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,64,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","POuTwBoxDownL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.POuTwBoxUpL",96,57,"ESDS32DCD","TWINLIFT TW-LO BOX UP LIGHT",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,65,"Boolean","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","POuTwBoxUpL","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.StandingTotalHours",136,57,"ESDS32DCD","Standing hours (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,105,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","StandingTotalHours","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.TravelingDistanceTotalBwd",138,57,"ESDS32DCD","Traveling distance backward (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,107,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","TravelingDistanceTotalBwd","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.TravelingDistanceTotalFwd",137,57,"ESDS32DCD","Traveling distance forward (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,106,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","TravelingDistanceTotalFwd","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.TravelingHoursTotalBwd",134,57,"ESDS32DCD","Traveling hours backward (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,103,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","TravelingHoursTotalBwd","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.TravelingHoursTotalFwd",133,57,"ESDS32DCD","Traveling hours forward (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,102,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","TravelingHoursTotalFwd","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD.TwinCountTotal",141,57,"ESDS32DCD","Number of twin picks (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,110,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","TwinCountTotal","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD._20ftCountTotal",139,57,"ESDS32DCD","Number of 20ft containers (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,108,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","_20ftCountTotal","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"ESDS32DCD._40ftCountTotal",140,57,"ESDS32DCD","Number of 40ft containers (total)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,109,"Real","",\ +"","","","UNLICENCED","Local Host","ESDS32DCD",\ +"","_40ftCountTotal","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"Site1.Comms_Status",50,47,"Site1","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,28,"Integer","",\ +"","","","UNLICENCED","Local Host","Site1",\ +"","Comms_Status","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"Site1.Crane1.Heartbeat",52,48,"Site1.Crane1","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,1,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,1,\ +0,0,1,27,"Boolean","",\ +"","","","UNLICENCED","Local Host","Site1",\ +"Crane1","Heartbeat","",0,"","",\ +"","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","",":","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.AI_01",23,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99.99",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,13,"Real","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","AI_01","",0,"","MODSIM_STATION",\ +"AI_01","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","MODSIM_STATION:AI_01","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.AI_02",24,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99.99",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,14,"Real","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","AI_02","",0,"","MODSIM_STATION",\ +"AI_02","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","MODSIM_STATION:AI_02","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.AI_04",33,12,"TRAINING.MOD_SIM.IO","",\ +0,20,80,10,90,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,1,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,21,"Integer","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","AI_04","",0,"","MODSIM_STATION",\ +"AI_04","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","MODSIM_STATION:AI_04","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.AI_05",25,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99.99",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,15,"Real","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","AI_05","",0,"","MODSIM_STATION",\ +"AI_05","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","MODSIM_STATION:AI_05","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.AI_06",26,12,"TRAINING.MOD_SIM.IO","",\ +0,0,80,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99.99",0,0,\ +0,1,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,16,"Real","",\ +"","MANUAL","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","AI_06","",0,"","MODSIM_STATION",\ +"AI_06","","HIGH","","","",\ +"","","Alarm 1","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"AI_06 High Alarm","","","",\ +"","","178.42.42.210","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","MODSIM_STATION:AI_06","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.AI_07",36,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99.99",0,0,\ +0,1,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,1,\ +0,0,1,24,"Real","",\ +"","MANUAL","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","AI_07","",0,"","MODSIM_STATION",\ +"AI_07","","HIGH","","","",\ +"","","Alarm 1","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"AI_06 High Alarm","","","",\ +"","","178.42.42.210","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","MODSIM_STATION:AI_07","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.AO_01",27,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99.99",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,17,"Real","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","AO_01","",0,"","MODSIM_STATION",\ +"AO_01","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","MODSIM_STATION:AO_01","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.AO_02",28,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99.99",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,18,"Real","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","AO_02","",0,"","MODSIM_STATION",\ +"AO_02","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","MODSIM_STATION:AO_02","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.AO_05",29,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99.99",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,19,"Real","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","AO_05","",0,"","MODSIM_STATION",\ +"AO_05","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","MODSIM_STATION:AO_05","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.AO_06",30,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99.99",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,20,"Real","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","AO_06","",0,"","MODSIM_STATION",\ +"AO_06","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","MODSIM_STATION:AO_06","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.DI_01",15,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,1,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,1,\ +0,0,1,5,"Boolean","",\ +"","EX1","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","DI_01","",0,"","MODSIM_STATION",\ +"DI_01","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Alarm 1","Normal","Normal",\ +"Normal","Normal",0,1,0,0,\ +0,0,"Normal","Alarm","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","MODSIM_STATION:DI_01","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.DI_02",16,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,1,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,6,"Boolean","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","DI_02","",0,"","MODSIM_STATION",\ +"DI_02","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Alarm 1","Normal","Normal",\ +"Normal","Normal",0,2,0,0,\ +0,0,"Normal","Priority 2","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","MODSIM_STATION:DI_02","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.DI_03",34,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,1,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,22,"Boolean","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","DI_03","",0,"","MODSIM_STATION",\ +"DI_03","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Alarm 1","Normal","Normal",\ +"Normal","Normal",0,3,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","MODSIM_STATION:DI_03","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.DI_04",35,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,1,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,23,"Boolean","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","DI_04","",0,"","MODSIM_STATION",\ +"DI_04","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Alarm 1","Normal","Normal",\ +"Normal","Normal",0,4,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","MODSIM_STATION:DI_04","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.DI_09",17,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,7,"Boolean","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","DI_09","",0,"","MODSIM_STATION",\ +"DI_09","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","MODSIM_STATION:DI_09","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.DI_10",18,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,8,"Boolean","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","DI_10","",0,"","MODSIM_STATION",\ +"DI_10","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","MODSIM_STATION:DI_10","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.DO_01",19,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,9,"Boolean","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","DO_01","",0,"","MODSIM_STATION",\ +"DO_01","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","MODSIM_STATION:DO_01","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.DO_02",20,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,10,"Boolean","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","DO_02","",0,"","MODSIM_STATION",\ +"DO_02","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","MODSIM_STATION:DO_02","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.DO_09",21,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,11,"Boolean","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","DO_09","",0,"","MODSIM_STATION",\ +"DO_09","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","MODSIM_STATION:DO_09","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"TRAINING.MOD_SIM.IO.DO_10",22,12,"TRAINING.MOD_SIM.IO","",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,12,"Boolean","",\ +"","","","UNLICENCED","Local Host","TRAINING",\ +"MOD_SIM","DO_10","",0,"","MODSIM_STATION",\ +"DO_10","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","MODSIM_STATION:DO_10","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + diff --git a/04-scada/hmi/DEPLOY.md b/04-scada/hmi/DEPLOY.md new file mode 100644 index 0000000..cd6903b --- /dev/null +++ b/04-scada/hmi/DEPLOY.md @@ -0,0 +1,356 @@ +# Deploying the WRPS displays + +Six displays are generated into `04-scada/hmi/out/` by `build_display.py`, plus +`WRPS_TagTest.xml` by `build_tagtest.py`: + +| File | What | Size | Components | +|---|---|---|---| +| `WRPS_Overview.xml` | The operator display | 1920 × 1080 | 374 | +| `WRPS_Tank.xml` | Wet well detail; carries the setpoint ladder, opened from the WELL | 1920 × 1080 | 180 | +| `WRPS_FP_PU301.xml` | PU-301 pump faceplate | 760 × 700 | 109 | +| `WRPS_FP_PU302.xml` | PU-302 pump faceplate | 760 × 700 | 109 | +| `WRPS_FP_PU303.xml` | PU-303 pump faceplate | 760 × 700 | 109 | +| `WRPS_FP_Setpoints.xml` | Station setpoint faceplate | 900 × 700 | 51 | +| `WRPS_TagTest.xml` | Diagnostic list — every project item, plain vs masked | — | 49 items, 98 bindings | + +Sizes and counts are what the build printed on 2026-08-20. It prints them on +every run — trust that over this table. + +> `04-scada/hmi/WRPS_Overview.xml` — at the **root**, not in `out/` — is not one +> of these. It is the copy CI View itself saved during the id harvest, kept as +> evidence for the `` finding below (it carries exactly one ``, +> the `99.99` set by hand). Do not deploy it and do not rebuild over it. + +Regenerate with: + +```bash +python 04-scada/hmi/build_display.py && python 04-scada/hmi/build_tagtest.py +``` + +> [!IMPORTANT] +> **After any `.qli` import, re-harvest the item ids before rebuilding** — an +> import moves every id and the screens go dead without one visible symptom. +> The build now refuses to run when it cannot prove the ids are current; see +> **Item ids** below for the four-line ritual. +> +> This applies to a `.qli` import on **the same** server. Moving a display to a +> **different** CI Server instance is a different case and needs none of it — +> see *Moving a display to another CI Server instance*. + +> **Never edit the XML by hand and never edit anything under +> `C:\Users\Public\Yokogawa\tls`.** Change `build_display.py` and rebuild — +> otherwise the next build silently discards the edit. + +## WHERE / WHAT / WHAT FOR + +**WHERE:** the CI Server machine (`PXiSEDev`). + +**WHAT:** copy the files into the displays directory of the deployment — +alongside the existing `Straddle_*.xml`, i.e. the same folder the copy in +`99-reference/ciserver-hmi-deployment/displays/` came from. Then +open `WRPS_Overview` in the CI View editor. + +**WHAT FOR:** CI Server loads displays from that directory by file name; +`WRPS_Overview` is what `actionActivateDisplay` targets from the faceplates, and +the faceplate names are what the pump symbols and the SETPOINTS… button target. + +## Import order + +The items must exist before the displays can bind to them: + +1. `04-scada/modbus_points/wrps_section_df.qli` — sections +2. `04-scada/modbus_points/wrps_modbus_point_df.qli` — Modbus points +3. `04-scada/modbus_points/wrps_item_df.qli` — items +4. these displays + +## What is live, and what is not + +**Live on first open:** + +- every number, bound to its item through `number.value` → `ItemValue` +- every button that writes — station mode, reset trips, reset hours, all four + simulation scenarios, time scale, reset scenario — via `actionSetItemAttribute` + with the value carried in the filter +- navigation — pump symbols open `WRPS_FP_Pump`, SETPOINTS… opens + `WRPS_FP_Setpoints`, both faceplates return to `WRPS_Overview` +- **the wet well fill**, a `dataBar` bound to `STN.LEVEL`, 0–100% +- **every setpoint is editable** — a `numberField` on the eight boxes of + `WRPS_FP_Setpoints` and on SETPOINT in the overview rail + +**Not yet, and deliberately:** + +- **State colouring is static.** The pump bodies and status boxes are drawn in + their normal colours. Driving a *colour* from a value still needs item alarm + limits (which you deferred along with trends and alarms) or a threshold + configuration I could not confirm from the deployment, and guessing would have + produced silently wrong colours. The layout has the right shapes in the right + places, so this is a bind, not a redraw. + The level *fill* is no longer in this category: a `dataBar` carries its own + `lowLimit`/`highLimit`, so it needs no item limits at all. That was a wrong + assumption here, corrected after you added one by hand. +- **The alarm banner is not in this build**, for the same reason — it exists in + the preview and in `component-kit.md`, and follows once alarm limits are set. +- **Blink** likewise: the display sets `blinkEnabled` and `blinkDelay` 700 ms, so + the mechanism is ready, but nothing drives it yet. + +## Item ids — the one thing that makes a screen lie + +A connection needs **both** `itemName` and `itemId`. With the name alone every +value reads 0, and the screen looks perfectly healthy while meaning nothing. +This has bitten twice. It is now enforced by the build rather than by memory. + +### If a display's values are not updating, do this first + +> **Open the display in CI Server's Editor Module and validate it.** + +Validation resolves every connection by name and rewrites the ids in the file. It +fixes the common case in seconds. Investigate nothing else until it has been ruled +out. + +### Every item import kills every display + +On 2026-08-14 the items were re-imported to change engineering units, keeping +every `NAME`, `NSID` and `ID_NUMBER` byte-identical. **Every itemId still moved.** +CI Server recreates items on import rather than updating them in place. Every +value on every screen went dead at once — that simultaneity is the signature. + +So the sequence after any `dssqld -i` of items is always: **import → validate the +displays → re-harvest**, so the next build ships the current ids. + +### Nothing is inferred — every id is measured + +An id comes from CI Server or the build stops. There is no rule, no default and +no fallback. An id we invent that happens to be wrong produces the failure that +is hardest to see: a screen full of live-looking zeroes. + +`WRPS_TagTest` binds **every** project item, which makes it the measuring +instrument: save it once in CI View and all 49 ids come back at once. + +### CI View repairs ids on save — that is what makes this workable + +Confirmed 2026-08-14. `WRPS_TagTest` was deployed with 25 of its 49 ids as +placeholders; those 25 rows read **0** while the other 24 read correctly. One +binding was then added by hand in CI View and the file saved — and CI View +rewrote **every** connection in the file with the right id, resolving them by +name. All 49 rows went live, and the saved file harvested all 49 in one pass. + +Two things follow, and they are not in tension: + +- **At runtime the id is what binds.** A wrong or placeholder id reads 0, name + notwithstanding. That is why the build refuses to emit an unmeasured id. +- **In the editor the name is authoritative.** So a display can always be healed + by opening and saving it, and that same save is the measurement. + +This is why the bootstrap works at all: ship placeholders, let CI View resolve +them, harvest the result. + +### Moving a display to another CI Server instance — import, then validate + +Observed 2026-08-20, migrating to node `CORE_0001`. + +The ids in `out/*.xml` are `PXiSEDev`'s. A different instance is a different +import, so a different `K`, so every one of them is wrong on the target. That +looks like it needs the full harvest ritual below. **It does not.** Importing +the display into the target resolves every connection by name — the same repair +described above, applied on the way in. The ids are healed by the import itself. + +So moving displays between instances is **import, then validate.** No +re-harvest, no rebuild, no `.qli` round-trip, no regenerated XML. The files +built for one server are the files you ship to the next. + +Validate after importing — the screen reading plausible live values rather than +a field of `0` is the signal. For the rigorous check, save the imported display +in CI View on the target and run `--verify` against it. + +> [!WARNING] +> `--verify` compares against `item-ids.csv`, which still holds the **source** +> server's numbering, so expect it to flag every item on a freshly migrated +> target. That is the id file being stale for this server, not the display being +> broken. `--harvest` the saved file to re-pin it — but note `item-ids.csv` +> describes exactly one server at a time, so harvesting against `CORE_0001` +> makes it wrong for `PXiSEDev`. Branch the file per instance before doing this +> if both servers still matter. + +Two things this depends on: + +- **Item names must be identical.** The binding survives the move because the + name does. `AID.WRPS.*` is built from `ROOT`, `INSTALL` and `STATION` in + `gen_ciserver_qli.py:53-54` — if the target imported under different values, + the names differ and nothing resolves. +- **The node name is not in any display.** `FRONT_END_NODE` (`UNLICENCED` → + `CORE_0001`) is a `.qli` field only, `gen_ciserver_qli.py:289`. No display + carries a node reference, so a node rename needs no edit to `out/*.xml`. + +### The ritual — after ANY .qli import + +```bash +python build_tagtest.py # the measuring display +# deploy WRPS_TagTest.xml, open it in CI View, link one value, save +python build_display.py --harvest # all 49 measured +python build_display.py && python build_tagtest.py # now the real screens +# deploy out/*.xml, open WRPS_Overview in CI View, save +python build_display.py --verify # prove it +``` + +`build_tagtest.py` is the one build allowed to run with unmeasured ids — it has +to be, or there would be no way to measure the first one. It prints exactly how +many are placeholders, and those read 0 by design until harvested. Every other +display refuses to build until all of its items are measured. + +`--verify` is the part that makes a dead screen impossible to miss. CI View +resolves every connection **by name** when it saves, so the ids in a saved file +are CI Server's own truth. The check compares them against what the build emits +and names any item that would read 0, exiting non-zero. Nobody has to look at a +screen and decide whether a `0` is a real zero. + +### The guards, all hard failures + +| Guard | Catches | +|---|---| +| **Every bound item must have a measured id** — `check_ids()` lists all gaps at once | an inferred id, which is a guess about someone else's numbering | +| `item-ids.meta.json` records the SHA of the `wrps_item_df.qli` harvested against | items regenerated, so an import is coming or has happened | +| No measured ids at all → build fails | an uncalibrated build, which is a dead screen | +| `--verify` mismatch → exit 1 | ids that are already wrong, item by item | + +None is a warning. Each error names the same one-command cure. + +## `` is a digit mask — the reason values read 0 + +`` on a `number` is a **digit mask**, not a Java `DecimalFormat` +pattern: + +```xml +99.99 +``` + +Each `9` is one digit position; the positions after the point are the decimals +always shown. This is what CI View itself wrote when the FIT-201 INLET value was +set to two decimals by hand, and it matches the `VALUE_FORMAT` masks in the item +exports (`"99999"` on our items, `"99.99"` on the reference analog IO). + +The first build emitted `0` and `0.0` — +`DecimalFormat` patterns, which read as masks **one digit wide**. Nothing wider +than a single digit had anywhere to render, so every level, flow and timer came +up empty. That is the whole bug. + +**A mask must therefore be wide enough for the point's full engineering range.** +The masks live in `point_format.py`, one per item, sized from the register map +and shared between points of the same kind so the screens stay consistent: + +| Kind | Mask | Why | +|---|---|---| +| level & level setpoints (%) | `999.9` | % of the 6.000 m spill weir | +| flows, net accumulation (m³/h) | `9999.9` | three pumps reach ~1300 | +| drive speed, min speed (%) | `999.9` | % of 50 Hz | +| durations (s), hour counters (h) | `99999` | 32767 = "drawing down" sentinel | +| volume to spill (m³) | `9999` | | +| alarm bitmask | `99999` | unsigned 16-bit | +| enums, counts, modes, booleans | `9` | single digit by definition | + +Decimals follow each point's real resolution rather than a fixed two. Level in % +of the spill weir resolves to 0.017% because the register underneath is mm, and +a flow in m³/h to 0.1 — so both get one decimal, and a second would be invented +precision. Integer registers get none. `point_format.check()` fails the build if +a new item has no mask, and the masks are cross-checked against `format_mask` in +`04-scada/modbus_points/scada-points.csv`, which is what CI Server imports as +`VALUE_FORMAT`. + +Element order matters to match what CI View saves: ``, ``, +``, …, ``. A masked number carries no ``. + +### Two earlier conclusions here were wrong + +- **"Actions must be shared objects."** Not true. `WRPS_TagTest` round 1 bound + all 49 items twice — once as `` in `globalSection`, once inline as + `` — and both columns updated identically. The generator + still emits shared objects because that is what CI View writes on save and it + de-duplicates, but it is a preference. +- **"A bound number needs `` and must not have ``."** Also not + true, and it only looked true because every `` seen up to that point + was a one-digit DecimalFormat pattern. A correctly sized mask works, and + `` is just the placeholder shown before the first update. + +What round 1 *did* establish and still holds: the itemIds are right, including +all 23 that were only ever derived from the creation-index rule, and the CI +Server side — items, sections, Modbus points — is fine. + +## Fixed after the first deploy + +Three things the screenshot showed, all now corrected in the generator: + +- **Popups opened full-screen.** `actionActivateDisplay` needs + `` plus `FixedPopup` and + `default`; without them it replaces the current + display. Faceplate CLOSE buttons now use `actionExitDisplay`, which closes the + popup rather than activating another display over it. +- **Grey blobs over the pump symbols.** I had used a real `button` as the click + target, so it drew its own face. Replaced with a fully transparent rectangle + (`a="0"` paint) carrying the action — which is how the Straddle screens do + invisible hit areas. +- **Value boxes sitting on the pipes.** The per-pump boxes were at fixed + positions that crossed the discharge lines. Pump data now sits in its own + column at x=1380, the ladder labels moved outside the well, and the mimic was + re-spaced around a wider well. + +## Other things I could not verify + +1. **Text placement is approximate.** `keepOriginalSize` is set and the width is + estimated from character count, so a label may sit a few pixels off. The editor + will normalise on save. +3. **Pipes are rectangles, not `line` components.** The `line` component positions + itself with `curvePoints`/`curveSegments`, and the single example in the + deployment did not make its anchor convention unambiguous. Filled rectangles + are exact and are what the site's own screens use. Cost: no diagonals, which a + P&ID does not need. +4. **`number.format`** is emitted as a Java `DecimalFormat` pattern (`0`, `0.0`). + If the editor shows a different precision, that is the property to adjust. + +## If the display opens empty or unbound + +Deploy `WRPS_TagTest.xml` and open it — it takes graphics, layout and every +other display out of the picture, and shows all 49 items twice: unmasked on the +left, masked on the right. A blank on the right beside a live value on the left +is a mask that is too narrow. If it is live +and another display is not, the fault is in that display, not in the items or +the Modbus chain. + +Otherwise check in this order: the `` mask width (above) → +items imported (step 3 above) → item names match `AID.WRPS.*` exactly → the +`itemId` note above. `build_display.py` refuses to +build if any display binds to an item that is not in `wrps_item_df.qli`, so a +name mismatch cannot be the cause unless the items were imported under a +different root. + +## Editable fields and bars — the two components CI View taught us + +Both were added by hand in CI View on 2026-08-14 and copied out of the saved +file, rather than guessed from the component definitions: + +| | `numberField` | `dataBar` | +|---|---|---| +| Property | `numberField.value` | `dataBar.value` | +| Geometry | **element attributes**, not child elements | same | +| Connection | `ItemValue`, **no** `` | ordinary read connection | +| Range | n/a | `` / `` **on the component** | +| Format | none — a mask fights the editing | n/a | + +The geometry difference is the trap: every other component here stores `x`, `y`, +`top`, `bottom`, `left`, `right` as child elements, and these two store them as +attributes on the opening tag. + +`dataBar` needing no item configuration is what makes the live well fill +possible — `lowLimit` 0, `highLimit` 100 against a level that now reads in % of +the spill weir, so the bar maps onto the well with no scaling anywhere. +`Straddle_Detail.xml` in the reference deployment does the same for a fuel level. + +## Layout is audited, not eyeballed + +```bash +python check_layout.py +``` + +Estimates each text run's drawn extent and reports collisions. It caught the +wet well caption running into the top ladder label after the units relabel — +the 100% mark *is* the well top, so the caption and that label share a line. +Approximate by construction (it estimates glyph widths), so it is a net for +gross collisions, not a substitute for looking at the screen. diff --git a/04-scada/hmi/README.md b/04-scada/hmi/README.md new file mode 100644 index 0000000..f13cfa5 --- /dev/null +++ b/04-scada/hmi/README.md @@ -0,0 +1,146 @@ +# hmi — what CI Server can draw, and the kit we build with + +Study of the live CI Server deployment copied to +`99-reference/ciserver-hmi-deployment/`: 53 components, 3130 +symbols (177 of them the AOG standard set), 74 displays, 2 layouts. + +> **`C:\Users\Public\Yokogawa\tls` is reference only.** Never edit it. Everything +> is authored in this repo and handed over for the user to deploy. + +## 1. Components — the primitives a display is made of + +| Group | Components | +|---|---| +| Drawing | `Rectangle` `Ellipse` `Arc` `Line` `Polygon` `Spline` `Text` `Icon` | +| Structure | `Display` `GraphicSet` `Symbol` `SymbolInstance` `VisibilityGroup` `VisualizationLayer` `VisualizationParameter` `Link` | +| Data display | `Number` `DataBar` `CircularScale` `RectangularScale` `Radial` `MicroTrend` `Chart` `DtsChart` | +| Input | `JButton` `JToggleButton` `JCheckBox` `JRadioButton` `JComboBox` `JSlider` `JSpinner` `JFormattedNumberField` `JFormattedTextField` `JTextArea` `JDateTimeField` `JList` `JNavigationTree` | +| System | `AlarmOverview` `ShelvedOverview` `BlockedItems` `AlarmSound` `Playback` `Reporting` `MapViewer` `PDFViewer` `DatasetTable` `DatasetForm` `ScriptedFunction` | + +So the earlier claim that CI View was "rectangles and text only" was wrong — that +was an artefact of the *Straddle* screens, which happen to use little else. +Ellipses, lines, polygons, trends, gauges and real input widgets are all available. + +## 2. AOG — the standard symbol library + +`symbols/AOG_*.xml` is Yokogawa's **Advanced Operating Graphics** set: a +high-performance HMI standard in the ISA-101 tradition. Relevant here: + +| Purpose | Symbol | +|---|---| +| Analog value | `AOG_ProcessIndicator`, `AOG_NumberIndicator` | +| Typed instrument | `AOG_IndicatorLevel` `AOG_IndicatorFlow` `AOG_IndicatorPressure` `AOG_IndicatorTemperature` | +| Bar / needle | `AOG_VerticalBarIndicator` `AOG_HorizontalBarIndicator` `AOG_*NeedleIndicator` `AOG_RadialNeedleIndicator` | +| Digital state | `AOG_OnOffIndicator` `AOG_AlarmIndicator` `AOG_TextIndicator` `AOG_ManualIndicator` | +| Equipment | `AOG_Pump` `AOG_PumpSimple` `AOG_PumpWithOutlet` `AOG_Motor` `AOG_Tank` `AOG_Vessel` `AOG_AtmosphericTank` | +| Valves | `AOG_CheckValve` `AOG_Horizontal2WayValve` `AOG_VerticalOnOffValve` `AOG_ButterflyValve` `AOG_BlockValveStation` | +| Instrument bubble | `AOG_Instrument` `AOG_Transmitter` | +| Faceplate | `AOG_Faceplate` `AOG_FP_Frame` `AOG_FP_PVValue` `AOG_FP_SVValue` `AOG_FP_Mode` `AOG_FP_Status` `AOG_FP_BarIndicator` | +| Trend | `AOG_Trend` `AOG_TrendIndicator` | +| Screen furniture | `AOG_DisplayTitle` `AOG_SelectionBox` `AOG_NavigationSelectionBox` `AOG_TagNameComponent` | + +An `AOG_O*` variant exists for most (`AOG_OPump`, `AOG_ONumberIndicator` …). + +### The parameter contract — this is what standardisation means here + +Every dynamic AOG symbol takes the same external parameters: + +``` +item the SCADA item to display (AID.WRPS.STN.LEVEL) +setPointItem optional companion item +tagName label, with alwaysShowTagName / neverShowTagName +format numeric format +showBackground draw the indicator box +showAlarmIndicator built-in alarm annunciation +selectable can the operator click it +displayToActivate display opened on click <- this is how faceplates work +``` + +Two consequences worth stating plainly: + +1. **Analog values are consistent by construction.** Every one is an + `AOG_ProcessIndicator`; nothing is hand-drawn, so nothing can drift. +2. **Faceplates are separate displays**, not overlays. A symbol names one in + `displayToActivate`. `AOG_ItemFaceplate` and `AOG_ObjectFacePlate` already + exist and can be used as-is, or copied and specialised for a pump. + +### Ready-made displays + +`displays/AOG_displays/` includes `AOG_DisplayTemplate` (start a new display from +it), `AOG_Style` (the colour tokens), `AOG_ItemFaceplate`, `AOG_ItemTuningPanel`, +`AOG_AlarmBanner`, `AOG_AlarmCurrent`, `AOG_MenuMain`, `AOG_SetNumberPV/SV/SH/SL` +(the standard numeric-entry popups — use these for setpoint writes rather than +building an entry field). + +## 3. Two styles exist on this system — pick one + +| | AOG standard | Site style (Straddle screens) | +|---|---|---| +| Ground | `#E3E3E3` light grey | `#171B22` near-black | +| Panels | `#F0F0F0` / `#F2F2F2` | `#262D38` / `#333B47` | +| Text | `#404040` / `#6D6D6D` | `#C8D0DA` | +| Accent | none — colour is reserved | `#5FB8D0` cyan | +| Alarm | `#FF0000` `#FFA500` `#FFFF00` | `#D65B5B` | +| Built from | AOG symbols | hand-drawn rectangles + text | + +The AOG palette follows the high-performance HMI convention: a grey plant, and +**colour only where something is abnormal**. The site's own Straddle screens are a +custom dark theme that does not use the AOG library at all — no display in the +deployment instantiates a single `AOG_*` symbol. + +**Recommendation: AOG.** It is the product standard, it brings the faceplate +machinery and alarm annunciation for free, and its parameter contract is what +keeps every indicator identical. The cost is that the demo will not match the +Straddle screens an operator may already know. + +## 4. Decision: our own kit, AOG as reference only + +Settled 2026-08-14 with the user: + +- **Build our own components**, not AOG instances — only what this project needs. + AOG's *parameter contract* is the thing worth copying, not its artwork. +- **Light theme, but not AOG's.** The customer reads AOG as dated; the cause is + the bevelled 3D symbols and mid-grey ground, so the kit is flat, square, hairline + bordered, white on cool neutral. +- **16:9**, and **operators may write** setpoint, command and simulation items. + +The kit is specified in `component-kit.md`. The table below is superseded by it +and kept only to show which AOG symbol each of our components was modelled on. + +## 5. AOG reference for each component we built + +What each element of the display is built from, so the same thing always looks the +same: + +| Element | Component | Bound to | +|---|---|---| +| Title bar | `AOG_DisplayTitle` | — | +| Wet well | `AOG_Tank` + `AOG_VerticalBarIndicator` | `STN.LEVEL` | +| Level, flows, pressures, speed | `AOG_ProcessIndicator` | `STN.*`, `PU30x.*` | +| Level switches, run/avail/trip | `AOG_OnOffIndicator` | `PU30x.*`, `STN.*` | +| Pumps | `AOG_Pump`, `displayToActivate` = pump faceplate | `PU30x.RUNNING` | +| Check / isolation valves | `AOG_CheckValve`, `AOG_Horizontal2WayValve` | static | +| Instrument bubbles | `AOG_Instrument` | tag text | +| Setpoint entry | `AOG_SetNumberSV` popup | `SP.*` | +| Pump faceplate | copy of `AOG_ItemFaceplate` | `PU30x.*` | +| Buttons (mode, commands, simulation) | `AOG_SelectionBox` / `JButton` | `SP.MODE`, `SP.CMD_WORD`, `SIM.*` | + +## Scripts in this directory + +| Command | What it does | +|---|---| +| `python build_display.py` | Builds the three displays into `out/`. **Refuses to build if the item ids cannot be proven current.** | +| `python build_display.py --harvest ` | Re-calibrates the item ids from a display CI View saved, and records provenance | +| `python build_display.py --verify ` | Compares CI Server's own ids against what the build emits; exit 1 on any mismatch | +| `python build_tagtest.py` | Builds the diagnostic display that lists every item | +| `python check_layout.py` | Reports text that collides with other text; exit 1 on any collision | + +The two `--` modes exist because of the failure that keeps recurring: an item +id that no longer matches CI Server binds silently to nothing, and the screen +looks fine. `DEPLOY.md` has the ritual. + +## Status + +Study complete; nothing built yet. The layout is under review as a mockup — see +the preview link in the conversation, and `04-scada/hmi/` will hold the display +XML once the style question above is settled. diff --git a/04-scada/hmi/build_display.py b/04-scada/hmi/build_display.py new file mode 100644 index 0000000..184ef47 --- /dev/null +++ b/04-scada/hmi/build_display.py @@ -0,0 +1,1591 @@ +#!/usr/bin/env python3 +"""Generate the CI Server display XML for the WRPS demo. + + python build_display.py -> ./out/*.xml + +Emits three displays: + + WRPS_Overview.xml the single operator display, 1920 x 1080 + WRPS_FP_Pump.xml pump faceplate (opened per pump) + WRPS_FP_Setpoints.xml station setpoint faceplate + +Everything here follows the file format as observed in the live +deployment copied to 99-reference/ciserver-hmi-deployment, not a manual: + + geometry x,y is the CENTRE; left/right/top/bottom are half-extents + colours shared paints, referenced as ref="NN" + live value + actionConnectTo -> property number.value + -> connection {direction 1, itemName, itemAttribute ItemValue} + write actionSetItemAttribute + filter (the value) + connection + {direction 2, itemName, itemAttribute ItemValue} + navigation actionActivateDisplay + NAME + +Style tokens and component rules come from component-kit.md. Nothing is +hand-placed twice: a value box is one function, so every value box on +every display is identical by construction. + +KNOWN GAP - state colouring is static in this version. Driving a fill +colour from a value needs either item alarm limits (deferred by the user +along with trends and alarms) or a threshold/transformation set up in the +editor, which is not safe to guess at. Numbers, navigation and writes +are live. See the README section "What still needs doing in the editor". +""" + +import datetime +import pathlib +from pathlib import Path + +import point_format as pf + +OUT = Path(__file__).resolve().parent / "out" +W, H = 1920, 1080 + +# ------------------------------------------------------------- item ids +# A connection needs BOTH itemName and itemId; with the name alone every +# value reads 0 - the whole screen looks alive and means nothing. Getting +# this wrong is the single failure mode that has cost this project the most +# time, so the rules below are enforced by the build, not by memory. +# +# THE POLICY: EVERY ID IS MEASURED. NOTHING IS INFERRED. +# ------------------------------------------------------- +# An id comes from CI Server or the build stops. There is no rule, no +# default and no fallback, because an id we invented that happens to be +# wrong produces exactly the failure that is hardest to see: a screen full +# of live-looking zeroes. +# +# Ids are measured by saving a display in CI View - it resolves every +# connection by NAME on save - and reading them back: +# +# python build_display.py --harvest +# +# WRPS_TagTest binds every project item, so saving that ONE display +# measures all 49 in a single pass. That is the intended route. +# +# An earlier version derived the ids it had not measured, by arithmetic on +# the item NSIDs. It no longer does: an id we invent that happens to be +# wrong produces the failure that is hardest to see - a screen full of +# live-looking zeroes. Every id is measured or the build stops. +# +# WHY THEY GO STALE +# ----------------- +# **Every .qli item import renumbers every item.** On 2026-08-14 the items +# were re-imported to change engineering units, keeping every NAME, NSID and +# ID_NUMBER byte-identical - and every id still moved. CI Server recreates +# items on import rather than updating them in place. Every value on every +# screen went dead at once. +# +# The cure is not arithmetic. It is: VALIDATE the display in CI Server's +# Editor Module. That resolves every connection by name and heals the file. +# Then re-harvest, so the next build ships the new ids. +# +# THE GUARDS - all hard failures, none is a warning +# ------------------------------------------------ +# 1. Every bound item must have a measured id. check_ids() lists all the +# gaps at once, since one harvest fixes them together. +# 2. item-ids.meta.json pins the SHA of the wrps_item_df.qli the ids were +# harvested against. If that file has changed, the items have been or +# will be re-imported, so the ids are presumed stale. +# 3. --verify compares CI Server's own resolved ids against +# what this build emits, and exits 1 naming every item that would read 0. +# +# The cure for all of them is the same one command, and every error says so. +IDS_FILE = Path(__file__).resolve().parent / "item-ids.csv" +IDS_META = Path(__file__).resolve().parent / "item-ids.meta.json" +ITEM_FILE = Path(__file__).resolve().parents[1] / "modbus_points" / "wrps_item_df.qli" + +HARVEST_ALL = ( + " Fix: get the ids from CI Server - do not infer them.\n" + " 1. in CI View open WRPS_TagTest, which binds EVERY project item\n" + " 2. link any one value by hand and save; CI View then resolves and\n" + " writes the id of every connection in the file\n" + " 3. python build_display.py --harvest \n" + " That measures all 49 in one pass. Any display works, but only\n" + " WRPS_TagTest covers the whole item list." +) +REHARVEST = HARVEST_ALL + +_ids = {} +_missing = set() + + +def item_file_sha(): + import hashlib + if not ITEM_FILE.is_file(): + return "" + return hashlib.sha256(ITEM_FILE.read_bytes()).hexdigest() + + +def item_nsids(): + """{item name: NSID} from the item export - the axis ids are built on.""" + import re + if not ITEM_FILE.is_file(): + return {} + q = ITEM_FILE.read_text(encoding="utf-8") + return {m.group(1): int(m.group(2)) + for m in re.finditer(r'^"(AID\.WRPS\.[A-Z0-9_.]+)",(\d+),', q, re.M)} + + +def item_reps(): + import re + if not ITEM_FILE.is_file(): + return {} + q = ITEM_FILE.read_text(encoding="utf-8") + return dict(re.findall(r'^"(AID\.WRPS\.[A-Z0-9_.]+)".*?"(Real|Boolean)"', + q, re.M | re.S)) + + +def measured_ids(): + import csv + if not IDS_FILE.is_file(): + return {} + return {r["item"]: r["itemId"] + for r in csv.DictReader(IDS_FILE.open(encoding="utf-8"))} + + +def load_ids(): + """The measured ids, checked for internal consistency. No derivation.""" + import json + import sys + ids = measured_ids() + if not item_nsids(): + return + + if not ids: + sys.exit("BUILD FAILED: no item ids have ever been measured.\n" + " Every value would render as 0 on a screen that looks fine.\n" + + REHARVEST) + + if IDS_META.is_file(): + meta = json.loads(IDS_META.read_text(encoding="utf-8")) + if meta.get("item_file_sha") != item_file_sha(): + sys.exit("BUILD FAILED: %s has changed since the ids were harvested.\n" + " Harvested against : %s\n" + " Now : %s\n" + " The items have been regenerated, so they will be (or have\n" + " been) re-imported - and every import moves every id.\n" + % (ITEM_FILE.name, meta.get("item_file_sha", "?")[:16], + item_file_sha()[:16]) + REHARVEST) + else: + sys.exit("BUILD FAILED: %s is missing, so the ids cannot be shown to\n" + " match the current items.\n" % IDS_META.name + REHARVEST) + + _ids.update(ids) + + +def item_id(item): + """The MEASURED id for an item. Nothing is derived. + + An earlier version filled gaps from `NSID + K`. The rule held every + time it was checked, but it is still an inference about someone + else's numbering, and an inferred id that is wrong produces a screen + that looks alive and reads 0 - the exact failure this project keeps + paying for. So: if CI Server has not told us an id, the build stops. + Every item can be measured at once - see check_ids(). + """ + if not _ids: + load_ids() + if item not in _ids: + # Recorded, not raised: check_ids() reports every missing item in + # one list, and nothing is written to disk before it runs. + _missing.add(item) + return "UNMEASURED" + return _ids[item] + +# ---------------------------------------------------------------- style +# component-kit.md. Kept as (r,g,b) so they land straight in colorSet. +C = { + "BG": (237, 240, 244), + "SF": (255, 255, 255), + "SF2": (245, 248, 251), + "BD": (195, 204, 216), + "BD2": (223, 229, 236), + "T1": (14, 22, 33), + "T2": (70, 84, 104), + "AC": (11, 125, 168), + "AC2": (18, 165, 217), + "RUN": (15, 138, 69), + "RUNBG": (228, 246, 234), + "WRN": (168, 91, 0), + "WRNBG": (255, 239, 210), + "ALM": (211, 32, 41), + "ALMBG": (253, 232, 233), + "ACBG": (222, 240, 248), + "ALMBD": (150, 19, 26), + "PRC": (43, 63, 82), + "WTR": (159, 207, 230), + "WHITE": (255, 255, 255), +} + +LAB = "Segoe UI" +VAL = "Consolas" + +# item namespace +NS = "AID.WRPS" + + +def text_width(value, size, font=LAB): + """Rough rendered width of a string, in px. + + Only an estimate - the components keep their original size and CI View + lays them out itself. It is shared rather than inlined because the + ladder marks place a live number immediately left of a label, and if the + two disagreed about how wide the label is they would drift apart. + """ + return len(value) * size * (0.62 if font == VAL else 0.55) + + +# ------------------------------------------------------------- plumbing +class Display: + """Accumulates shared objects and components, then serialises.""" + + def __init__(self, title, width=W, height=H): + self.title = title + self.w, self.h = width, height + self.refs = [] # (id, xml) + self.ref_cache = {} + self.parts = [] + self.next_ref = 1000 + self.next_html = 100 + + # --- shared objects ------------------------------------------------ + def _ref(self, key, xml): + if key in self.ref_cache: + return self.ref_cache[key] + rid = self.next_ref + self.next_ref += 1 + self.refs.append((rid, xml)) + self.ref_cache[key] = rid + return rid + + def paint(self, rgb): + r, g, b = rgb + return self._ref(("paint", rgb), + '' + '' + '' % (r, g, b)) + + def conn(self, item, attribute="ItemValue", direction=1): + """Write connection, as a shared object - the form the editor saves.""" + return self._ref(("conn", item, attribute, direction), + '' + '%d' + '%s' + '%s' + '%s' + '' % (direction, item, item_id(item), attribute)) + + def read_conn(self, item): + """Read connection, inline and without itemAttribute. + + This is exactly what CI View wrote when the item was linked by hand; + the earlier version omitted itemId and every value read 0. + """ + return ('' + "1" + "%s" + "%s" + "" % (item, item_id(item))) + + def filt(self, value="0.0"): + return self._ref(("filter", value), + '%s' + '' % value) + + def direct(self): + return self._ref(("tdirect",), + '' + 'java.lang.Double' + 'java.lang.Double' + '') + + def named_colour(self, name): + """A colour CI View names rather than mixes - `gainsboro` and friends. + + The rest of the kit gives colours as RGB; the microTrend background is + the one place CI View wrote a name, so it is kept as one. + """ + return self._ref(("colorSet", name), + '%s' + "" % name) + + def comp_prop(self, name, value=None): + """A componentProperty default, shared. Empty value stays empty.""" + return self._ref(("cprop", name, value), + '%s' + "%s" + % (name, "" if value is None else + "%s" % value)) + + def plain_conn(self, item): + """Read connection as a SHARED object, with no itemAttribute. + + `conn()` always writes an itemAttribute and `read_conn()` is inline; + the microTrend needs the third shape - shared and bare - which is what + CI View wrote for the trend's realTime/fillWithHistory bindings. + """ + return self._ref(("plainconn", item), + '' + "1" + "%s%s" + "" % (item, item_id(item))) + + def hid(self): + self.next_html += 1 + return self.next_html + + # --- state: making the screen show what the plant is doing ---------- + # + # All three of these were copied from the site's own displays, which + # use them heavily - rectangle.fillPaint 276 times, rectangle.visible + # 29, line.visible 25. The note in DEPLOY.md that said driving a + # shape from a value needed item alarm limits was simply wrong, and + # it is why this screen showed a grey pump whether it was running, + # stopped or tripped. + + def parameter(self, name, kind="Boolean", default="false"): + """A display parameter: the wire between a function and a shape.""" + self.parts.append( + '' + '%s' + '' + "%s" + % (name, kind, default)) + return name + + def param_conn(self, name, direction=1): + return ('%d' + "%s%s" + % (direction, name, + "ItemValue" if direction == 2 else "")) + + def equals(self, item, value, pname): + """item == value -> a boolean parameter, via genericFunctions. + + The only way to test an enum. STATE 2 means "Pumping" and the + operator should read that word, not the number 2. + """ + self.parameter(pname) + n = len([p for p in self.parts if "genericFunctions" in p]) + gx, gy = 40 + (n % 30) * 62, 1068 + (n // 30) * 10 + self.parts.append( + '' + "%s" + "%.1f%.1f4.04.0" + "28.028.0" + "0.00.0" + "%.1f" + '' + '' + '' + '' + '%s' + '' + '' + '%s' + "" + % (pname, gx, gy, float(value), self.filt(), self.read_conn(item), + self.filt(), self.param_conn(pname, 2))) + return pname + + def vis_action(self, kind, item=None, param=None): + """The action alone, so it can share a data block with another one.""" + conn = self.read_conn(item) if item else self.param_conn(param, 1) + return ('' + '' + '%s' % (kind, self.filt(), conn)) + + def vis(self, kind, item=None, param=None): + """Shared data object binding .visible to a bool item/param.""" + return self._ref( + ("vis", kind, item, param), + '%s' + % self.vis_action(kind, item, param)) + + def moves_y(self, kind, item, y_at_0, y_at_full, full=100.0): + """Bind .y to an item, so a mark sits at the level it means. + + A setpoint that can be typed into and a line drawn at a fixed height + are two claims about the same number, and only one of them updates. + This binds the second to the first. + + The `transformationLinear` shape is the one CI View wrote when the + user moved the STOP ALL mark by hand on 2026-08-17, including the + absent `inputValueStart` - the editor omits it because it defaults to + 0, and every scale here does start at 0. + + `x`/`y` are declared properties on Rectangle, Text and Number alike + (checked in components/*.xml), so the line, its live number and its + label can all ride the same item. All three carry `html="noConnect"` + though: in CI Server's WEB client these bindings may not animate. The + desktop CI View client is what this project targets. + """ + return ('' + '' + '' + '' + "java.lang.Double" + "java.lang.Double" + "%.1f" + "%.1f" + "%.1f" + "%s" + % (kind, self.filt(), full, y_at_0, y_at_full, self.read_conn(item))) + + def data(self, actions): + """Register actions as a shared data object and return its ref. + + Actions in globalSection as , referenced + with . This is a preference, not a requirement: + WRPS_TagTest bound all 49 items both this way and inline as + , and both columns updated identically. Shared is + kept because it is what CI View writes on save and it de-duplicates + the repeated actions. + + (An earlier note here claimed inline bindings never update and that + this was why values read 0. That was wrong - see the mask + note on number() for the actual cause.) + """ + return self._ref(("data", actions), + '%s' % actions) + + def open_display(self, name, event="Clicked b1"): + """Shared data object that opens a display AS A POPUP. + + ActivateSpecific + layout FixedPopup is what the site's own screens + use; without it the display replaces the current one full-screen. + """ + return self._ref(("open", name, event), + '' + '' + '' + '' + '%s' + 'FixedPopup' + 'default' + '' % (event, name)) + + def hit_area(self, x, y, w, h, display): + """Invisible click target: a fully transparent rectangle. + + Using a real button here drew a grey button face over the symbol + underneath, which is exactly what happened on the first deploy. + """ + cx, cy = x + w / 2.0, y + h / 2.0 + clear = self._ref(("paint", "clear"), + '' + '' + "") + s = ['', "%d" % self.hid(), + "%.1f%.1f" % (cx, cy), + "%.1f%.1f" % (h / 2.0, h / 2.0), + "%.1f%.1f" % (w / 2.0, w / 2.0), + "0.00.0", + '', + '' % clear, + '' % clear, + '' % self.open_display(display), + ""] + self.parts.append("".join(s)) + + # --- primitives ---------------------------------------------------- + def rect(self, x, y, w, h, fill, stroke=None, width=1.0, + show_item=None, show_param=None, move=None): + """Rectangle by top-left + size; converted to centre/half-extent. + + show_item / show_param bind `visible`, so a shape can appear only + when a pump is running, a state is active, or a mode is selected. + `move` takes a moves_y() action and rides in the SAME data block - + a component carries one, so the two cannot be separate. + """ + cx, cy = x + w / 2.0, y + h / 2.0 + s = ['', "%d" % self.hid(), + "%.1f%.1f" % (cx, cy), + "%.1f%.1f" % (h / 2.0, h / 2.0), + "%.1f%.1f" % (w / 2.0, w / 2.0), + "0.00.0", + '' % width, + '' % self.paint(stroke or fill), + '' % self.paint(fill)] + acts = (self.vis_action("rectangle", show_item, show_param) + if (show_item or show_param) else "") + (move or "") + if acts: + s.append('' % self.data(acts)) + s.append("") + self.parts.append("".join(s)) + + def line(self, x1, y1, x2, y2, colour, width=2.0, dash=False, move=None): + """Orthogonal line drawn as a thin rectangle. + + The `line` component positions itself with curvePoints/curveSegments, + whose anchor convention could not be determined unambiguously from + the one example in the deployment. A filled rectangle is exact, + needs no guessing, and is what the site's own screens use for rules + and pipes. Diagonals are therefore not available - which is no loss, + because a P&ID is drawn orthogonally anyway. + """ + if dash: + # a dashed line is a run of rectangles, so there is no single one + # to move; nothing needs a moving dashed line, so this is a bug + assert move is None, "a dashed line cannot be moved by an item" + # dashed instrument signal: a run of short rectangles + if y1 == y2: + x = min(x1, x2) + while x < max(x1, x2): + self.rect(x, y1 - width / 2.0, min(7.0, max(x1, x2) - x), width, colour) + x += 12.0 + else: + y = min(y1, y2) + while y < max(y1, y2): + self.rect(x1 - width / 2.0, y, width, min(7.0, max(y1, y2) - y), colour) + y += 12.0 + return + if y1 == y2: + self.rect(min(x1, x2), y1 - width / 2.0, abs(x2 - x1), width, colour, + move=move) + elif x1 == x2: + self.rect(x1 - width / 2.0, min(y1, y2), width, abs(y2 - y1), colour, + move=move) + else: + assert move is None, "an L-shaped line is two rectangles; move one" + # route orthogonally: across, then down + self.line(x1, y1, x2, y1, colour, width) + self.line(x2, y1, x2, y2, colour, width) + + def ellipse(self, cx, cy, r, fill, stroke, width=2.0, + show_item=None, show_param=None): + s = ['', "%d" % self.hid(), + "%.1f%.1f" % (cx, cy), + "%.1f%.1f" % (r, r), + "%.1f%.1f" % (r, r), + "0.00.0", + '' % width, + '' % self.paint(stroke), + '' % self.paint(fill)] + if show_item or show_param: + s.append('' % self.vis("ellipse", show_item, show_param)) + s.append("") + self.parts.append("".join(s)) + + def text(self, x, y, value, size=14, colour=None, bold=False, font=LAB, + anchor="left", show_item=None, show_param=None, move=None): + colour = colour or C["T1"] + # width estimate: enough for the editor to place it; it keeps original size + tw = text_width(value, size, font) + cx = x + tw / 2.0 if anchor == "left" else (x - tw / 2.0 if anchor == "right" else x) + s = ['', "%d" % self.hid(), + "%s" % esc(value), + '' % (font, "true" if bold else "false", size), + '' % self.paint(colour), + '', + "true", + "%.1f%.1f" % (cx, y), + "%.1f%.1f" % (size * 0.7, size * 0.7), + "%.1f%.1f" % (tw / 2.0, tw / 2.0), + "0.00.0"] + acts = (self.vis_action("text", show_item, show_param) + if (show_item or show_param) else "") + (move or "") + if acts: + s.append('' % self.data(acts)) + s.append("") + self.parts.append("".join(s)) + + def number(self, x, y, item, size=22, colour=None, bold=True, decimals=0, + width=110, move=None): + """A live value. This is the only way a number reaches the screen. + + is a DIGIT MASK, not a Java DecimalFormat pattern. The first + build emitted "0" and "0.0" - masks one digit wide - so nothing wider + than a single digit had anywhere to render, and every value read 0. + Confirmed by CI View itself: setting the FIT-201 INLET value to two + decimals by hand wrote 99.99, immediately after + and with no . That element order is copied here. + + Masks are per point and live in point_format.py, sized to each + point's engineering range. `decimals` is ignored - the mask decides - + but is kept in the signature because callers pass it. + """ + colour = colour or C["T1"] + cx = x + width / 2.0 + s = ['', "%d" % self.hid(), + "%s" % pf.mask(item), + '' % (VAL, "true" if bold else "false", size), + '' % self.paint(colour), + '', + "true", + "%.1f%.1f" % (cx, y), + "%.1f%.1f" % (size * 0.7, size * 0.7), + "%.1f%.1f" % (width / 2.0, width / 2.0), + "0.00.0", + '' % self.data( + '' + '' + '%s' + % (self.filt(), self.read_conn(item)) + (move or "")), + ""] + self.parts.append("".join(s)) + + def number_field(self, x, y, w, h, item, size=14): + """An EDITABLE value - the operator types into it and it writes. + + Copied from what CI View itself wrote when the user added a level + setpoint field by hand on 2026-08-14. Two things differ from + every other component here and both are load-bearing: + + - geometry is on the ELEMENT as attributes, not child elements + - the connection carries ItemValue + and NO ; a numberField is read-write by nature + + It also takes no : the field shows what the operator can + type, and a mask would fight the editing. + """ + cx, cy = x + w / 2.0, y + h / 2.0 + s = ['' % (cx, cy, h / 2.0, h / 2.0, w / 2.0, w / 2.0), + "%d" % self.hid(), + '' % (LAB, size), + '' + '' + '' + '' + "%s%s" + "ItemValue" + "" % (self.filt(), item, item_id(item)), + ""] + self.parts.append("".join(s)) + + def data_bar(self, x, y, w, h, item, low=0.0, high=100.0, + fill=None, back=None, up=True, opens=None): + """A filled bar driven by an item - the live level in the well. + + The range lives on the COMPONENT (`lowLimit`/`highLimit`), not on + the item, so this needs no alarm or scale limits configured in CI + Server - which is what kept the well fill static until now. The + site's own Straddle_Detail uses exactly this for a fuel level %, + with highLimit 100. + + `opens` puts an actionActivateDisplay in the SAME data block as the + value binding, so the bar is both the reading and the way in to the + numbers behind it. A hit_area over the well would have done the job + too, but this is the mechanism the user built by hand in CI View on + 2026-08-17 and proved works, and an invisible rectangle laid over the + well would sit on top of every level mark in it. + """ + cx, cy = x + w / 2.0, y + h / 2.0 + s = ['' % (cx, cy, h / 2.0, h / 2.0, w / 2.0, w / 2.0), + "%d" % self.hid(), + "true", + '' + "" % (back or C["SF"]), + '' + "" % (fill or C["WTR"]), + "%.1f%.1f" % (low, high), + '' + % ("Up" if up else "Down"), + '' + '' + '%s%s' + % (self.filt(), self.read_conn(item), + '' + '' + '' + "%s" + "%sdefault" + "" + % (opens, "FixedPopup" if opens.startswith("WRPS_FP") else "default") + if opens else ""), + ""] + self.parts.append("".join(s)) + + def micro_trend(self, x, y, w, h, item, scale_max): + """A strip trend of one item, under or over the box that reads it. + + Transcribed from the two the user drew by hand in CI View on + 2026-08-17, so the generated ones are the same component and not a + near miss. Three things in here are CI View's shape, not a choice: + + - the item arrives through `microTrend.itemName[0]` with + `ItemName` - the trend is told + WHICH item to pull history for, so it wants the name, not the + value. Every other component here binds ItemValue. + - `realTime` and `fillWithHistory` are bound to the same item, as + a bare shared connection with no itemAttribute. They read as + booleans, which is odd for a flow in m3/h; it is what the editor + wrote for a trend that works, so it is what is written here. + - `actionInternalServiced` leads the data block and takes no body. + + `scaleMax` is the only scale set; the minimum stays at the component + default of 0, which is right for a level in % and a flow in m3/h. + """ + cx, cy = x + w / 2.0, y + h / 2.0 + src = self.plain_conn(item) + bind = ('' + '' + '') + s = ['' % (cx, cy, h / 2.0, h / 2.0, w / 2.0, w / 2.0), + "%d" % self.hid(), + '' % self.named_colour("gainsboro"), + '' % self.comp_prop("itemName[0]"), + 'scaleMax[0]' + "%.1f" % scale_max, + '', + '', + '' + '' + '' + '1' + "%s%s" + "ItemName" + % (self.filt(), item, item_id(item)), + bind % ("realTime", self.filt(), src), + bind % ("fillWithHistory", self.filt(), src), + "", + ""] + self.parts.append("".join(s)) + + def button(self, x, y, w, h, label, item=None, write=None, display=None, + size=15, close=False): + """WRPS_Button: writes a value, opens a display, or both.""" + cx, cy = x + w / 2.0, y + h / 2.0 + acts = [] + if item is not None and write is not None: + acts.append('' + '' + '' + % (self.filt(str(write)), self.conn(item, direction=2))) + if close: + # closes the popup it lives on, rather than activating another + # display over the top of it + acts.append('' + '') + if display: + acts.append('' + '' + '' + "%s" + "%sdefault" + "" % (display, "FixedPopup" if display.startswith("WRPS_FP") + else "default")) + # Element is "] + self.parts.append("".join(s)) + + # --- kit components ------------------------------------------------- + def panel(self, x, y, w, h, header): + self.rect(x, y, w, h, C["SF"], C["BD"], 1.0) + self.rect(x, y, w, 34, C["SF"], C["BD2"], 1.0) + self.text(x + 12, y + 17, header, size=13, colour=C["T2"], font=VAL) + + def value_box(self, x, y, w, tag, item, units="", decimals=0, big=False, + editable=False): + """WRPS_Value. Every analog value on every display is this. + + `editable` swaps the read-only number for a numberField, so the + box an operator sets a setpoint in looks like every other box on + the screen rather than a foreign widget. + """ + h = 74 if big else 56 + self.rect(x, y, w, h, C["SF2"], C["BD2"], 1.0) + self.rect(x, y, 5, h, C["BD"], C["BD"], 1.0) # left rule + self.text(x + 14, y + 15, tag, size=12, colour=C["T2"], font=VAL) + if editable: + self.number_field(x + 12, y + 24, w - 70, 26, item) + else: + self.number(x + 14, y + (46 if big else 37), item, + size=30 if big else 21, decimals=decimals) + if units: + self.text(x + w - 12, y + (52 if big else 41), units, + size=12, colour=C["T2"], font=VAL, anchor="right") + return h + + def state_box(self, x, y, w, label, colour, bg): + self.rect(x, y, w, 34, bg, colour, 1.5) + self.rect(x + 9, y + 12, 10, 10, colour, colour, 1.0) + self.text(x + 26, y + 17, label, size=14, colour=colour, bold=True, font=VAL) + + def instrument(self, cx, cy, letters, loop, r=30): + self.ellipse(cx, cy, r, C["SF"], C["PRC"], 1.8) + self.text(cx, cy - 9, letters, size=13, colour=C["T1"], bold=True, font=VAL, anchor="centre") + self.text(cx, cy + 10, loop, size=13, colour=C["T1"], bold=True, font=VAL, anchor="centre") + + # --- output --------------------------------------------------------- + def xml(self): + when = datetime.datetime.now().strftime("%Y.%m.%d %H:%M:%S.000 AEST") + refs = "".join('%s' % (i, x) for i, x in self.refs) + vis = "".join( + '%d%s' + "%strue" + "%s" + % (i + 2, n, d, z) for i, (n, d, z) in enumerate([ + ("Overview", "Always shown", "10.0"), + ("Rough", "Shown when viewing a large area", "25.0"), + ("Standard", "Shown when using the default view setting", "100.0"), + ("Detail", "Shown only when viewing a small area", "400.0"), + ("Intricacies", "Shown only when viewing a very small area", "1000.0"), + ])) + head = ( + '\n' + '\n' + " %s\n" + ' \n' + ' \n' + " %d%d\n" + " 3\n" + ' \n' + ' \n' + ' \n' + ' \n' + ' ' + '\n' + ' ' + '\n' + " 700true\n" + " false\n" + " %s\n" + ' 1' + "Layer1\n" + " %d\n" + ) % (refs, self.w, self.h, C["BG"][0], C["BG"][1], C["BG"][2], + C["T1"][0], C["T1"][1], C["T1"][2], LAB, when, vis, len(self.parts)) + return head + "\n".join(" " + p for p in self.parts) + \ + "\n \n\n" + + +def esc(s): + return (s.replace("&", "&").replace("<", "<").replace(">", ">")) + + +# ===================================================================== +# WRPS_Overview - the single operator display +# ===================================================================== +PUMP_STATES = [(0, "OFF"), (1, "IDLE"), (2, "STARTING"), (3, "RUNNING"), + (4, "STOPPING"), (5, "TRIPPED"), (6, "LOCKED OUT")] +STN_STATES = [(0, "OFF", "T2"), (1, "IDLE", "T2"), (2, "PUMPING", "RUN"), + (3, "HIGH LEVEL", "ALM"), (4, "EMERGENCY - LSHH", "ALM"), + (5, "DRY RUN LOCKOUT", "ALM"), (6, "FAULT", "ALM")] +# ladder marks: % of the spill weir, label, is it an alarm level, and the +# setpoint behind it - None where the level is PHYSICAL and cannot be typed. +# The spill weir is concrete and the two float switches are bolted to the +# wall at a height; no operator moves those, so those three marks stay put +# and stay a plain string. The other five are numbers someone can change, +# and a mark drawn at a height that no longer matches its setpoint is a +# screen telling a lie, so those five follow their item. +LADDER = [(100, "SPILL WEIR", 1, None), + (91.7, "LSHH-102", 1, None), + (86.7, "HIGH ALARM", 1, ".SP.HIGH_ALARM"), + (83.3, "START P3", 0, ".SP.START_P3"), + (75, "START P2", 0, ".SP.START_P2"), + (66.7, "START DUTY", 0, ".SP.START_DUTY"), + (16.7, "STOP ALL", 0, ".SP.STOP_ALL"), + (10, "LSLL-103", 0, None)] + + +def station_header(d): + """The title bar, shared by every full-size display. + + Lifted out of overview() when WRPS_Tank was added. It is the same bar + on both screens by construction rather than by someone remembering to + copy a change across, which is the failure this file keeps having. + + DUTY stays on the tank screen even though no pump is drawn there: it + says which pump the levels are about to start, which is exactly what + someone editing those levels wants to know. + """ + d.rect(0, 0, W, 76, C["SF"], C["BD"], 2.0) + d.text(28, 40, "WATERLOO ROAD PUMP STATION", size=26, bold=True) + d.text(520, 44, "WW-101", size=14, colour=C["T2"], font=VAL) + + d.text(1180, 26, "STATION", size=11, colour=C["T2"], font=VAL) + for value, label, tone in STN_STATES: + p = d.equals(NS + ".STN.STATE", value, "STATE_%d" % value) + d.rect(1176, 38, 230, 30, C["SF2"], C["BD2"], 1.0, show_param=p) + d.text(1186, 53, label, size=15, bold=True, colour=C[tone], font=VAL, + show_param=p) + + d.text(1430, 26, "MODE", size=11, colour=C["T2"], font=VAL) + for value, label, tone in [(1, "AUTO", "RUN"), (2, "OFF", "T2")]: + p = d.equals(NS + ".SP.MODE", value, "MODE_%d" % value) + d.rect(1426, 38, 120, 30, C["SF2"], C["BD2"], 1.0, show_param=p) + d.text(1436, 53, label, size=15, bold=True, colour=C[tone], font=VAL, + show_param=p) + + d.text(1570, 26, "DUTY", size=11, colour=C["T2"], font=VAL) + d.rect(1566, 38, 130, 30, C["SF2"], C["BD2"], 1.0) + for n in (1, 2, 3): + p = d.equals(NS + ".STN.DUTY_PUMP", n, "DUTY_%d" % n) + d.text(1576, 53, "PU-30%d" % n, size=15, bold=True, font=VAL, show_param=p) + d.text(1576, 53, "NONE", size=15, colour=C["T2"], font=VAL, + show_param=d.equals(NS + ".STN.DUTY_PUMP", 0, "DUTY_0")) + + # exceptions only: an operator should see these, not hunt for a 0 + for item, label in [(".STN.HIGH_LEVEL", "HIGH LEVEL"), (".STN.SPILL_ACTIVE", "SPILLING")]: + d.rect(1726, 38, 170, 30, C["ALMBG"], C["ALM"], 1.5, show_item=NS + item) + d.text(1736, 53, label, size=13, bold=True, colour=C["ALM"], font=VAL, + show_item=NS + item) + + + +def draw_well(d, WX, WY, WW, WH, opens=None): + """The wet well and its level ladder - the one drawing both screens share. + + WRPS_Tank is the overview's well with the plant either side of it taken + away, so the well is drawn from here on both. Duplicating it would mean + the marks agreeing today and drifting the first time a percentage moves; + the ladder is the CONTRACT between what the PLC does and what the screen + claims, and it can only be in one place. + + `opens` makes the water itself the click target for another display. + """ + PER = WH / 100.0 + PER = WH / 100.0 + d.rect(WX, WY, WW, WH, C["SF"], C["PRC"], 3.0) + # the well is the way in to the setpoints: there is no SETPOINTS button, + # and clicking the thing the levels are ABOUT is a better target than one + d.data_bar(WX + 3, WY + 3, WW - 6, WH - 6, NS + ".STN.LEVEL", 0.0, 100.0, + opens=opens) + d.text(WX, WY - 14, "WET WELL WW-101 . 120 m2", size=13, bold=True) + # 0% is the well floor at WY+WH, 100% the spill weir at WY - the two ends + # the marks are interpolated between, and what a moving mark rides on + Y0, Y100 = WY + WH, WY + LBLY = 13 # label sits this far under its own line + for pct, label, alarm, item in LADDER: + y = WY + WH - pct * PER + tone = C["ALM"] if alarm else C["T2"] + if item is None: # physical: concrete and float switches + # LEFT wall, while everything adjustable is on the RIGHT. An + # adjustable mark can be driven to any height, so sooner or later + # it lands on a fixed one; the two kinds therefore never share a + # column. Outside the well was the other option and is not + # available - the FIT-201 trend runs straight across the 100% line. + d.line(WX, y, WX + WW, y, C["BD"], 1.0) + d.text(WX + 8, y + LBLY, "%g%% %s" % (pct, label), size=11, + colour=tone, font=VAL) + continue + # adjustable: line, live number and label all ride the same setpoint. + # The label keeps the right edge it has always had; the number sits + # immediately left of it, so the reading grows leftwards and the + # column stays flush against the wall of the well. + it = NS + item + tail = "%% %s" % label # "% START DUTY" - the number supplies 86.7 + numw = text_width("999.9", 11, VAL) + numx = WX + WW - 8 - text_width(tail, 11, VAL) - numw + d.line(WX, y, WX + WW, y, C["BD"], 1.0, + move=d.moves_y("rectangle", it, Y0, Y100)) + d.number(numx, y + LBLY, it, size=11, colour=tone, bold=False, + width=numw, move=d.moves_y("number", it, Y0 + LBLY, Y100 + LBLY)) + d.text(WX + WW - 8, y + LBLY, tail, size=11, colour=tone, font=VAL, + anchor="right", move=d.moves_y("text", it, Y0 + LBLY, Y100 + LBLY)) + # 34 below the floor, not 22: a mark driven to 0% puts its label at the + # floor + 13, and at 22 the two interleave. Found by the sweep in + # check_layout.py, which is the only thing that can see it. + d.text(WX, WY + WH + 34, "0 - 6.00 m . 100% = SPILL WEIR", size=11, + colour=C["T2"], font=VAL) + + +def overview(): + """The operator display. + + Laid out against a rendered preview rather than in a diff, one region + at a time. The rules that came out of that: + + - a reading belongs beside the instrument that produces it, not in + a rail on the far side of the screen + - nothing sits on a pipe, and the inlet main runs INTO the well + rather than stopping at the wall + - the ladder is computed from the percentages, so 10% is at a tenth + of the height, and every label sits the same distance below its + own line + - state is shown as a word and a colour, never as an enum number + """ + d = Display("WRPS Overview") + + station_header(d) + + # ---- process -------------------------------------------------------- + d.panel(24, 96, 1872, 812, "PROCESS") + + # inlet, with FIT-201 and its reading clear of everything else + d.instrument(88, 272, "FIT", "201", r=27) + d.line(88, 299, 88, 390, C["T2"], 1.6, True) + d.value_box(140, 240, 250, "FIT-201 INLET FLOW", NS + ".STN.INFLOW", "m3/h", 1) + d.micro_trend(140, 306, 250, 56, NS + ".STN.INFLOW", 1100) + d.line(60, 390, 470, 390, C["PRC"], 3.0) # runs INTO the well + d.text(140, 418, "DN600 INLET GRAVITY MAIN", size=12, colour=C["T2"]) + + WX, WY, WW, WH = 440, 330, 300, 500 + draw_well(d, WX, WY, WW, WH, opens="WRPS_Tank") + + # LIT-101 beside its own readings; leader drops and runs in underneath + d.instrument(88, 488, "LIT", "101", r=27) + d.line(88, 515, 88, 668, C["T2"], 1.6, True) + d.line(88, 668, WX, 668, C["T2"], 1.6, True) + d.value_box(140, 448, 280, "LIT-101 WET WELL LEVEL", NS + ".STN.LEVEL", "%", 1, + big=True) + d.micro_trend(140, 528, 280, 56, NS + ".STN.LEVEL", 100) + # 598, not 564: the level trend above it needs the 34 px + d.value_box(140, 598, 280, "LEVEL SETPOINT", NS + ".SP.LEVEL_SP", "%", 1, + editable=True) + + # overflow: plain text, not a bordered box - a box reads as a button + d.line(WX + WW, 344, WX + WW + 70, 344, C["PRC"], 3.0) + d.text(818, 349, "OVERFLOW TO ERS", size=11, colour=C["T2"], font=VAL) + + # pumps, left of the header so no reading sits on a pipe + CXP, R, BX, BW, HDR = 900, 34, 980, 210, 1300 + for i, cy in enumerate((450, 590, 730)): + n = i + 1 + run = "%s.PU30%d.RUNNING" % (NS, n) + trip = "%s.PU30%d.TRIPPED" % (NS, n) + d.line(WX + WW, cy, CXP - R, cy, C["PRC"], 3.0) + d.line(CXP + R, cy, HDR, cy, C["PRC"], 3.0) + d.ellipse(CXP, cy, R, C["SF2"], C["PRC"], 2.4) + d.ellipse(CXP, cy, R, C["RUNBG"], C["RUN"], 3.0, show_item=run) + d.ellipse(CXP, cy, R, C["ALMBG"], C["ALM"], 3.0, show_item=trip) + d.line(CXP - R, cy, CXP + R, cy, C["PRC"], 1.2) + d.line(CXP, cy - R, CXP, cy + R, C["PRC"], 1.2) + d.text(CXP - 32, cy + 58, "PU-30%d" % n, size=14, bold=True, font=VAL) + d.text(CXP - 32, cy + 76, "DUTY", size=11, bold=True, colour=C["AC"], font=VAL, + show_param=d.equals(NS + ".STN.DUTY_PUMP", n, "DUTYP_%d" % n)) + + d.rect(BX, cy - 64, BW, 52, C["SF2"], C["BD2"], 1.0) + d.rect(BX, cy - 64, 5, 52, C["RUN"], C["RUN"], 1.0) + d.text(BX + 14, cy - 46, "PU-30%d" % n, size=11, colour=C["T2"], font=VAL) + for value, label in PUMP_STATES: # one word, never an enum + p = d.equals("%s.PU30%d.STATE" % (NS, n), value, "P%dSTATE_%d" % (n, value)) + tone = C["RUN"] if value == 3 else (C["ALM"] if value in (5, 6) else C["T1"]) + d.text(BX + 14, cy - 24, label, size=14, bold=True, colour=tone, font=VAL, + show_param=p) + d.number(BX + BW - 70, cy - 30, "%s.PU30%d.RUN_HOURS" % (NS, n), size=12, width=44) + d.text(BX + BW - 10, cy - 24, "h", size=11, colour=C["T2"], font=VAL, anchor="right") + d.hit_area(CXP - R - 2, cy - R - 2, 2 * R + 4, 2 * R + 4, "WRPS_FP_PU30%d" % n) + + # speed and discharge trends sit ABOVE their boxes: below the speed box is + # the dashed leader down to the pumps, and below the discharge box is the + # rising main at y=590. Nothing else has to move to make room. + d.micro_trend(BX, 170, BW, 56, NS + ".STN.SPEED", 100) + d.value_box(BX, 240, BW, "VSD-301/2/3 SPEED", NS + ".STN.SPEED", "%", 1) + d.line(1085, 304, 1085, 386, C["T2"], 1.6, True) + + # discharge header and FIT-301 + d.line(HDR, 450, HDR, 730, C["PRC"], 3.0) + d.line(HDR, 590, 1820, 590, C["PRC"], 3.0) + d.text(1560, 614, "RISING MAIN . DN400", size=12, colour=C["T2"]) + d.instrument(1500, 512, "FIT", "301", r=27) + d.line(1500, 539, 1500, 590, C["T2"], 1.6, True) + d.micro_trend(1542, 418, 240, 56, NS + ".STN.DISCHARGE", 1400) + d.value_box(1542, 480, 240, "FIT-301 DISCHARGE", NS + ".STN.DISCHARGE", "m3/h", 1) + + # ---- simulation band ------------------------------------------------ + SY = 928 + d.rect(0, SY, W, H - SY, C["SF"], C["BD"], 1.0) + d.rect(0, SY, W, 4, C["AC"], C["AC"], 1.0) + d.text(28, SY + 30, "SIMULATION", size=14, bold=True, colour=C["AC"], font=VAL) + d.text(28, SY + 52, "NOT PLANT CONTROL", size=11, colour=C["AC"], font=VAL) + d.text(28, SY + 78, "ACTIVE:", size=11, colour=C["T2"], font=VAL) + + SCEN = [(0, "MANUAL", 206, ["inflow set by the", "box beside it"]), + (1, "DIURNAL", 612, ["dry weather day,", "144-396 m3/h over 24 h"]), + (2, "WET WEATHER", 770, ["storm: ramps to", "1080 m3/h, then decays"]), + (3, "DEMO REF", 928, ["held at 594 m3/h,", "starts at 4.00 m"])] + d.text(206, SY + 24, "SCENARIO - inflow the wet well sees", size=11, + colour=C["T2"], font=VAL) + for value, label, x, desc in SCEN: + p = d.equals(NS + ".SIM.SCENARIO", value, "SCEN_%d" % value) + d.rect(x - 4, SY + 34, 154, 54, C["ACBG"], C["AC"], 2.5, show_param=p) + d.button(x, SY + 38, 146, 46, label, NS + ".SIM.SCENARIO", value, size=13) + for k, line in enumerate(desc): + d.text(x, SY + 104 + k * 13, line, size=10, colour=C["T2"], font=VAL) + d.text(88, SY + 78, label, size=11, bold=True, colour=C["AC"], font=VAL, + show_param=p) + + # the manual inflow lives next to MANUAL: nothing else uses it + d.value_box(366, SY + 32, 230, "SIM INFLOW", NS + ".SIM.INFLOW", "m3/h", 1, + editable=True) + d.text(366, SY + 104, "type an inflow, used", size=10, colour=C["T2"], font=VAL) + d.text(366, SY + 117, "by MANUAL only", size=10, colour=C["T2"], font=VAL) + + d.text(1124, SY + 24, "TIME SCALE - simulated clock", size=11, colour=C["T2"], font=VAL) + for i, (s, desc) in enumerate([(1, "real time"), (10, "10x clock"), + (30, "30x clock"), (60, "60x clock")]): + x = 1124 + i * 96 + p = d.equals(NS + ".SIM.TIME_SCALE", s, "TS_%d" % s) + d.rect(x - 4, SY + 34, 92, 54, C["ACBG"], C["AC"], 2.5, show_param=p) + d.button(x, SY + 38, 84, 46, "%dx" % s, NS + ".SIM.TIME_SCALE", s, size=13) + d.text(x, SY + 104, desc, size=10, colour=C["T2"], font=VAL) + + d.button(1528, SY + 38, 180, 46, "RESET SCENARIO", NS + ".SIM.RESET", 1, size=13) + d.text(1528, SY + 104, "restart the sim clock,", size=10, colour=C["T2"], font=VAL) + d.text(1528, SY + 117, "level back to 3.50 m", size=10, colour=C["T2"], font=VAL) + return d + + +def tank(): + """WRPS_Tank - the well at full size, with the numbers that shape it. + + The overview screen with the plant either side of the well taken away: + no inlet, no rising main, no pumps. What replaces them is the setpoint + faceplate's own idea, at the size it deserves - the levels are marks on + the well and the entries sit beside it, so the number being typed and + the height it means are one glance apart. + + The faceplate draws a small well of its own to carry those marks. Here + that would be the same ladder twice at two scales, so the panel is rows + only and the marks are the real well's. + """ + d = Display("WRPS Tank") + station_header(d) + + # ---- the well ------------------------------------------------------- + d.panel(24, 96, 780, 812, "WET WELL") + # no `opens`: this IS the screen the overview's well opens + WX, WY, WW, WH = 440, 330, 300, 500 + draw_well(d, WX, WY, WW, WH) + + # LIT-101 beside its own readings; leader drops and runs in underneath + d.instrument(88, 488, "LIT", "101", r=27) + d.line(88, 515, 88, 668, C["T2"], 1.6, True) + d.line(88, 668, WX, 668, C["T2"], 1.6, True) + d.value_box(140, 448, 280, "LIT-101 WET WELL LEVEL", NS + ".STN.LEVEL", "%", 1, + big=True) + d.micro_trend(140, 528, 280, 56, NS + ".STN.LEVEL", 100) + # the overview's LEVEL SETPOINT box is deliberately NOT repeated here: + # it is a row in the panel opposite, and two entry fields writing one + # item on one screen is an invitation to wonder which one is real + d.button(48, 836, 200, 48, "OVERVIEW", display="WRPS_Overview") + + # ---- the setpoints -------------------------------------------------- + PX = 828 + d.panel(PX, 96, 1068, 812, "STATION SETPOINTS") + d.text(PX + 40, 152, "levels in % of the 6.00 m spill weir . type a value to change it", + size=12, colour=C["T2"], font=VAL) + + LX, FX, UX = PX + 40, PX + 652, PX + 808 + LEVELS = [(".SP.HIGH_ALARM", "HIGH LEVEL ALARM", "ALM", + "annunciates only - it starts no pump"), + (".SP.START_P3", "START PUMP 3", "T1", + "the third pump joins duty and lag at this level"), + (".SP.START_P2", "START PUMP 2", "T1", + "the second pump joins the duty pump at this level"), + (".SP.LEVEL_SP", "LEVEL CONTROL SETPOINT", "AC", + "the level the drive holds while pumping"), + (".SP.START_DUTY", "START DUTY", "T1", + "the duty pump starts at this level"), + (".SP.STOP_ALL", "STOP ALL", "T1", + "every pump stops at this level")] + for i, (item, label, tone, why) in enumerate(LEVELS): + ry = 186 + i * 68 + d.text(LX, ry, label, size=15, bold=True, colour=C[tone]) + d.text(LX, ry + 20, why, size=11, colour=C["T2"]) + d.number_field(FX, ry - 16, 140, 32, NS + item) + d.text(UX, ry, "%", size=12, colour=C["T2"], font=VAL) + + d.line(LX, 582, PX + 1028, 582, C["BD2"], 1.0) + d.text(LX, 608, "NOT A LEVEL", size=11, colour=C["T2"], font=VAL) + for i, (item, label, unit, why) in enumerate( + [(".SP.MIN_SPEED", "MINIMUM DRIVE SPEED", "%", + "38 Hz floor - below it the 22 m static lift gives no delivery"), + (".SP.SERVICE_HRS", "SERVICE INTERVAL", "h", + "run hours between services, counted per pump")]): + ry = 648 + i * 68 + d.text(LX, ry, label, size=15, bold=True) + d.text(LX, ry + 20, why, size=11, colour=C["T2"]) + d.number_field(FX, ry - 16, 140, 32, NS + item) + d.text(UX, ry, unit, size=12, colour=C["T2"], font=VAL) + + d.text(LX, 792, "a start level above 100% - the spill weir - is rejected by the PLC", + size=11, colour=C["T2"], font=VAL) + d.text(LX, 814, "writes go straight to the PLC, which clamps and rejects bad values", + size=11, colour=C["T2"], font=VAL) + return d + + +def fp_pump(n): + """One faceplate per pump. + + Generated three times rather than once, because a single display + cannot name the pump that opened it without binding an item through a + display parameter - a mechanism the AOG library uses but which I have + not verified. Three files cost nothing and cannot show PU-301's data + under PU-303's heading, which is what the old single faceplate did. + """ + tag = "PU-30%d" % n + P = "%s.PU30%d" % (NS, n) + d = Display("WRPS %s Faceplate" % tag, 760, 700) + d.rect(0, 0, 760, 700, C["SF"], C["BD"], 1.0) + d.rect(0, 70, 760, 4, C["AC"], C["AC"], 1.0) + d.text(24, 40, tag, size=26, bold=True) + d.text(150, 44, "TRANSFER PUMP %d . 75 kW . 120 L/s @ 32 m" % n, size=12, + colour=C["T2"], font=VAL) + d.rect(560, 20, 80, 30, C["SF2"], C["BD2"], 1.0, + show_param=d.equals(NS + ".STN.DUTY_PUMP", n, "ISDUTY_%d" % n)) + d.text(570, 35, "DUTY", size=12, bold=True, colour=C["AC"], font=VAL, + show_param="ISDUTY_%d" % n) + d.rect(652, 20, 88, 30, C["RUNBG"], C["RUN"], 1.5, show_item=P + ".AVAILABLE") + d.text(662, 35, "AVAILABLE", size=12, bold=True, colour=C["RUN"], font=VAL, + show_item=P + ".AVAILABLE") + + # state: the reason the faceplate was opened + d.rect(24, 96, 712, 78, C["SF2"], C["BD2"], 1.5) + d.rect(24, 96, 712, 78, C["RUNBG"], C["RUN"], 2.0, show_item=P + ".RUNNING") + d.rect(24, 96, 712, 78, C["ALMBG"], C["ALM"], 2.0, show_item=P + ".TRIPPED") + for value, label in PUMP_STATES: + p = d.equals(P + ".STATE", value, "FP%dSTATE_%d" % (n, value)) + tone = C["RUN"] if value == 3 else (C["ALM"] if value in (5, 6) else C["T1"]) + d.text(56, 130, label, size=26, bold=True, colour=tone, font=VAL, show_param=p) + d.text(56, 156, "pump state from the PLC", size=12, colour=C["T2"], font=VAL) + d.text(716, 122, "RUN HOURS", size=11, colour=C["T2"], font=VAL, anchor="right") + d.number(600, 152, P + ".RUN_HOURS", size=24, width=116) + + d.text(24, 200, "THIS PUMP", size=11, colour=C["T2"], font=VAL) + d.rect(24, 210, 228, 58, C["SF2"], C["BD2"], 1.0) + d.text(38, 229, "RUN COMMAND", size=11, colour=C["T2"], font=VAL) + d.text(38, 253, "ON", size=16, bold=True, colour=C["RUN"], font=VAL, + show_item=P + ".RUN_CMD") + d.rect(266, 210, 228, 58, C["SF2"], C["BD2"], 1.0) + d.text(280, 229, "TRIPPED", size=11, colour=C["T2"], font=VAL) + d.text(280, 253, "TRIPPED", size=16, bold=True, colour=C["ALM"], font=VAL, + show_item=P + ".TRIPPED") + d.value_box(508, 210, 228, "DRIVE SPEED . common", NS + ".STN.SPEED", "%", 1) + + d.text(24, 298, "STATION", size=11, colour=C["T2"], font=VAL) + d.rect(24, 308, 228, 52, C["SF2"], C["BD2"], 1.0) + d.text(38, 327, "STATION STATE", size=11, colour=C["T2"], font=VAL) + for value, label, tone in STN_STATES: + d.text(38, 350, label, size=14, bold=True, colour=C[tone], font=VAL, + show_param="STATE_%d" % value if False else + d.equals(NS + ".STN.STATE", value, "FP%dSTN_%d" % (n, value))) + d.rect(266, 308, 228, 52, C["SF2"], C["BD2"], 1.0) + d.text(280, 327, "DUTY PUMP", size=11, colour=C["T2"], font=VAL) + for k in (1, 2, 3): + d.text(280, 350, "PU-30%d" % k, size=14, bold=True, font=VAL, + show_param=d.equals(NS + ".STN.DUTY_PUMP", k, "FP%dDUTY_%d" % (n, k))) + d.value_box(508, 308, 228, "PUMPS RUNNING", NS + ".STN.PUMPS_RUNNING", "of 3", 0) + + # manual control only exists when the station is OFF, so say so + d.text(24, 400, "MANUAL CONTROL", size=11, colour=C["T2"], font=VAL) + auto = d.equals(NS + ".SP.MODE", 1, "FP%dAUTO" % n) + off = d.equals(NS + ".SP.MODE", 2, "FP%dOFF" % n) + d.rect(24, 410, 712, 72, C["WRNBG"], C["WRN"], 1.5, show_param=auto) + d.text(44, 438, "STATION IS IN AUTO - manual commands do nothing", size=14, + bold=True, colour=C["WRN"], font=VAL, show_param=auto) + d.text(44, 462, "Put the station in OFF to start or stop a pump by hand.", + size=12, colour=C["WRN"], font=VAL, show_param=auto) + d.rect(24, 410, 348, 72, C["SF"], C["BD2"], 1.0, show_param=off) + d.rect(388, 410, 348, 72, C["SF"], C["BD2"], 1.0, show_param=off) + d.button(36, 422, 324, 48, "START", NS + ".SP.CMD_WORD", 2) + d.button(400, 422, 324, 48, "STOP", NS + ".SP.CMD_WORD", 3) + + d.text(24, 514, "MAINTENANCE", size=11, colour=C["T2"], font=VAL) + d.button(24, 524, 228, 50, "LOCK OUT", NS + ".SP.CMD_WORD", 4) + d.button(266, 524, 228, 50, "RESET TRIP", NS + ".SP.CMD_WORD", 1) + d.button(508, 524, 228, 50, "RESET RUN HOURS", NS + ".SP.CMD_WORD", 5) + d.text(24, 592, "lock out and trip reset apply to %s . reset hours clears all three" % tag, + size=11, colour=C["T2"], font=VAL) + + d.text(24, 630, "LAST COMMAND ACKNOWLEDGED", size=11, colour=C["T2"], font=VAL) + d.number(24, 660, NS + ".STN.CMD_ACK", size=16, width=60) + d.button(596, 632, 140, 46, "CLOSE", close=True) + return d + + +def fp_setpoints(): + """Setpoints drawn as levels, because that is what they are. + + Eight identical boxes hid the one thing that matters about these + numbers: they are a stack of thresholds that has to stay in order. + Each one is now a mark on the well at its own height, against the + live level, with its entry field on the same line. + """ + d = Display("WRPS Setpoints", 900, 700) + d.rect(0, 0, 900, 700, C["SF"], C["BD"], 1.0) + d.rect(0, 70, 900, 4, C["AC"], C["AC"], 1.0) + d.text(24, 40, "STATION SETPOINTS", size=22, bold=True) + d.text(290, 44, "levels in % of the 6.00 m spill weir . type a value to change it", + size=12, colour=C["T2"], font=VAL) + + LX, LY, LW, LH = 60, 120, 120, 440 + PER = LH / 100.0 + d.text(24, 104, "WET WELL", size=11, colour=C["T2"], font=VAL) + d.rect(LX, LY, LW, LH, C["SF"], C["PRC"], 2.5) + d.data_bar(LX + 2, LY + 2, LW - 4, LH - 4, NS + ".STN.LEVEL", 0.0, 100.0) + # the spill weir is the one level on this screen nobody can type, so it is + # the one tick that stays put - and it moves to the RIGHT of the well, out + # of the left column the six adjustable ticks slide up and down + d.line(LX - 14, LY, LX + LW + 14, LY, C["ALM"], 1.5) + d.text(LX + LW + 20, LY + 4, "100 SPILL WEIR", size=10, colour=C["ALM"], font=VAL) + + # item, label, its live level for the mark, row y, tone + ROWS = [(".SP.HIGH_ALARM", "HIGH LEVEL ALARM", 86.7, 160, "ALM"), + (".SP.START_P3", "START PUMP 3", 83.3, 206, "T1"), + (".SP.START_P2", "START PUMP 2", 75.0, 252, "T1"), + (".SP.LEVEL_SP", "LEVEL CONTROL SETPOINT", 70.0, 298, "AC"), + (".SP.START_DUTY", "START DUTY", 66.7, 344, "T1"), + (".SP.STOP_ALL", "STOP ALL", 16.7, 390, "T1")] + # every mark here is adjustable - the fixed 100 above is the spill weir, + # and it is the only level on this screen nobody can type + Y0, Y100 = LY + LH, LY + TICKY = 4 # the tick number's offset from its line + numw = text_width("999.9", 10, VAL) + for item, label, pct, ry, tone in ROWS: + my = LY + LH - pct * PER + it = NS + item + colour = C[tone] if tone != "T1" else C["T2"] + # solid, not dashed: a dashed line here is 30 small rectangles and + # the dash convention belongs to instrument signals, not to a leader + d.line(LX - 14, my, 420, my, colour, 1.0, + move=d.moves_y("rectangle", it, Y0, Y100)) + # the tick was a hard-coded "86.7"; it is the setpoint, so it reads it + d.number(LX - 20 - numw, my + TICKY, it, size=10, colour=colour, bold=False, + width=numw, move=d.moves_y("number", it, Y0 + TICKY, Y100 + TICKY)) + # the ROW does not move - the entry fields are a list to work down, + # and a list that reorders itself as you type in it is unusable + d.text(440, ry + 16, label, size=13, bold=True, colour=C[tone]) + d.number_field(700, ry, 110, 28, it) + d.text(826, ry + 20, "%", size=11, colour=C["T2"], font=VAL) + d.text(440, 440, "a start level above 100% - the spill weir - is rejected by the PLC", + size=11, colour=C["T2"], font=VAL) + + d.line(440, 464, 860, 464, C["BD2"], 1.0) + d.text(440, 492, "NOT A LEVEL", size=11, colour=C["T2"], font=VAL) + d.text(440, 524, "MINIMUM DRIVE SPEED", size=13, bold=True) + d.number_field(700, 508, 110, 28, NS + ".SP.MIN_SPEED") + d.text(826, 528, "%", size=11, colour=C["T2"], font=VAL) + d.text(440, 552, "38 Hz floor - below it the 22 m static lift gives no delivery", + size=11, colour=C["T2"], font=VAL) + d.text(440, 588, "SERVICE INTERVAL", size=13, bold=True) + d.number_field(700, 572, 110, 28, NS + ".SP.SERVICE_HRS") + d.text(826, 592, "h", size=11, colour=C["T2"], font=VAL) + + d.text(24, 661, "writes go straight to the PLC, which clamps and rejects bad values", + size=11, colour=C["T2"], font=VAL) + d.button(700, 632, 140, 46, "CLOSE", close=True) + return d + + +ITEM_FILE = Path(__file__).resolve().parents[1] / "modbus_points" / "wrps_item_df.qli" + + +def known_items(): + """Every item the CI Server import actually creates.""" + import re + if not ITEM_FILE.is_file(): + return None + return set(re.findall(r'"(AID\.WRPS\.[A-Z0-9_.]+)"', ITEM_FILE.read_text(encoding="utf-8"))) + + +def report_unplaced(displays): + """Say plainly what is NOT on any screen. + + Kept honest by computing it from the built XML against the item + export, not from a list someone maintains by hand - a hand list is + exactly the thing that goes stale and lets a point vanish quietly. + """ + import re + bound = set() + for _, d in displays: + bound |= set(re.findall(r"(AID\.WRPS\.[A-Z0-9_.]+)", d.xml())) + everything = set(item_nsids()) + missing = sorted(everything - bound) + print("") + print("items on a display : %d of %d" % (len(bound & everything), len(everything))) + if missing: + print("NOT DISPLAYED ANYWHERE (%d):" % len(missing)) + for m in missing: + print(" %-22s %s" % (m.replace("AID.WRPS.", ""), UNPLACED_WHY.get(m, ""))) + if UNPLACED_BUTTONS: + print("BUTTONS NOT PLACED (%d):" % len(UNPLACED_BUTTONS)) + for b in UNPLACED_BUTTONS: + print(" %s" % b) + + +# Why each one is still off the screen, so the list is a decision record +# rather than a shrug. Emptied as they are placed. +UNPLACED_WHY = { + "AID.WRPS.STN.NET_ACCUM": "inflow minus discharge - no instrument to sit beside", + "AID.WRPS.STN.VOL_TO_SPILL": "derived from level - awaiting a home", + "AID.WRPS.STN.TIME_TO_SPILL": "derived from level - awaiting a home", + "AID.WRPS.STN.TIME_TO_LSHH": "derived from level - awaiting a home", + "AID.WRPS.STN.ALARM_WORD": "bitmask; needs decoding into named alarms, not a number", + "AID.WRPS.STN.IN_AUTO": "duplicated by SP.MODE, which the header already shows", + "AID.WRPS.SP.CMD_PARAM": "command plumbing - written with the command word, not read", +} + +UNPLACED_BUTTONS = [ + "AUTO / OFF - station mode, no device to sit beside", + "RESET TRIPS - station wide", + "RESET RUN HOURS - station wide (also on each pump faceplate)", + "SETPOINTS... - no button; the WELL opens WRPS_Tank, which carries them", +] + + +def check_ids(displays): + """Every item a display binds must have a MEASURED id. All at once. + + Reported as one list rather than failing on the first, because the + remedy is a single harvest that fixes all of them - being told about + them one build at a time would be its own small hell. + """ + import re + import sys + if not _ids: + load_ids() + used = set() + for _, d in displays: + used |= set(re.findall(r"([^<]+)", d.xml())) + missing = sorted(set(u for u in used if u not in _ids) | _missing) + if missing: + msg = ["BUILD FAILED: %d of %d bound items have no measured id." + % (len(missing), len(used)), + " These would render as 0 on a screen that looks healthy:"] + msg += [" " + m.replace("AID.WRPS.", "") for m in missing] + msg.append(HARVEST_ALL) + sys.exit(chr(10).join(msg)) + print("ids %d/%d bound items measured" % (len(used), len(used))) + + +def check_items(displays): + """Fail loudly if a display binds to an item that will not exist. + + Cheap to check, and the failure mode otherwise is a display full of + silently dead values that looks fine in the editor. + """ + import re + have = known_items() + if have is None: + print("WARN %s not found - item names not checked" % ITEM_FILE.name) + return + bad = {} + for name, d in displays: + used = set(re.findall(r"([^<]+)", d.xml())) + missing = sorted(used - have) + if missing: + bad[name] = missing + if bad: + import sys + msg = ["BUILD FAILED: displays bind to items that do not exist."] + for name, items in bad.items(): + msg.append(" %s:" % name) + msg += [" " + i for i in items] + msg.append(" Known items come from %s" % ITEM_FILE.name) + sys.exit(chr(10).join(msg)) + + +def harvest(path): + """Read itemName/itemId pairs out of a display CI View has saved. + + Ids from an earlier generation are DROPPED, not merged. A re-import + renumbers every item, so a file holding 24 fresh ids and 25 stale + ones is worse than one holding 24 - the stale ones bind silently to + nothing, and the derived-base check cannot tell which generation is + current. Anything the fresh harvest disagrees with goes. + """ + import csv + import re + import sys + txt = pathlib.Path(path).read_text(encoding="utf-8", errors="replace") + found = dict(re.findall(r"([^<]+)\s*([^<]+)", txt)) + if not found: + sys.exit("no itemId pairs in %s - has CI View saved it?" % path) + have = {} + if IDS_FILE.is_file(): + have = {r["item"]: r["itemId"] for r in csv.DictReader(IDS_FILE.open(encoding="utf-8"))} + changed = {k: v for k, v in found.items() if have.get(k) != v} + + have.update(found) + with IDS_FILE.open("w", newline="", encoding="utf-8") as fh: + w = csv.writer(fh); w.writerow(["item", "itemId"]) + for k in sorted(have): + w.writerow([k, have[k]]) + import datetime + import json + IDS_META.write_text(json.dumps({ + "harvested_from": str(path), + "harvested_at": datetime.datetime.now().isoformat(timespec="seconds"), + "item_file": ITEM_FILE.name, + "item_file_sha": item_file_sha(), + "measured": len(have), + "note": "Every .qli item import renumbers every item. Validate the " + "displays in CI Server's Editor Module, then re-harvest - " + "otherwise the screens go dead.", + }, indent=2) + "\n", encoding="utf-8") + + print("harvested %d ids from %s (%d new or changed) -> %s" + % (len(found), path, len(changed), IDS_FILE.name)) + print(" provenance -> %s" % IDS_META.name) + + +def verify(path): + """Check a CI View-saved display against the ids this build would emit. + + This is the check that makes a dead screen impossible to miss. CI View + resolves every connection by NAME when it saves, so the ids in a saved + file are CI Server's own truth. If they match what we generate, the + screens are live; if they do not, they are dead, and this says which + items and by how much - without anyone having to look at a display and + judge whether a 0 is a real 0. + """ + import re + import sys + txt = pathlib.Path(path).read_text(encoding="utf-8", errors="replace") + found = dict(re.findall(r"([^<]+)\s*([^<]+)", txt)) + if not found: + sys.exit("no itemId pairs in %s - has CI View saved it?" % path) + load_ids() + bad = {n: (v, _ids.get(n)) for n, v in found.items() if _ids.get(n) != v} + print("verified %d bindings in %s" % (len(found), pathlib.Path(path).name)) + if not bad: + print(" all match - the screens are bound to the live items") + return 0 + print(" %d MISMATCH(ES) - these values would read 0:" % len(bad)) + for n, (theirs, ours) in sorted(bad.items()): + print(" %-30s CI Server %s generated %s" + % (n.replace("AID.WRPS.", ""), theirs, ours)) + print(REHARVEST) + return 1 + + +def main(): + import sys + if len(sys.argv) > 2 and sys.argv[1] == "--harvest": + harvest(sys.argv[2]) + return + if len(sys.argv) > 2 and sys.argv[1] == "--verify": + sys.exit(verify(sys.argv[2])) + OUT.mkdir(exist_ok=True) + displays = [("WRPS_Overview", overview()), + ("WRPS_Tank", tank()), + ("WRPS_FP_PU301", fp_pump(1)), + ("WRPS_FP_PU302", fp_pump(2)), + ("WRPS_FP_PU303", fp_pump(3)), + ("WRPS_FP_Setpoints", fp_setpoints())] + check_items(displays) + check_ids(displays) + report_unplaced(displays) + for name, disp in displays: + path = OUT / (name + ".xml") + path.write_text(disp.xml(), encoding="utf-8") + print("OK %-22s %5d components, %3d shared objects, %6d bytes" + % (path.name, len(disp.parts), len(disp.refs), path.stat().st_size)) + + +if __name__ == "__main__": + main() diff --git a/04-scada/hmi/build_tagtest.py b/04-scada/hmi/build_tagtest.py new file mode 100644 index 0000000..fdc45eb --- /dev/null +++ b/04-scada/hmi/build_tagtest.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python3 +"""Build WRPS_TagTest.xml - a bare diagnostic display, no graphics. + +Why this exists +--------------- +Round 1 (shared vs inline ) came back with both +columns live for all 49 items, which ruled out the binding form, the derived +itemIds and the CI Server side. The remaining difference was : the +generated displays carried DecimalFormat patterns ("0", "0.0"), and CI View +writes as a DIGIT MASK ("99.99"). A one-digit-wide mask has nowhere +to render a four-digit level. + +Round 2, this build, tests the masks in point_format.py before they go +anywhere near the operator screens: + + * every item in the project, one row each, numbered - nothing else on + the screen, so nothing else can be blamed + * each item is bound TWICE, side by side: + PLAIN - no at all. The control: this is exactly what was + live in round 1, so it must stay live. + MASKED - the same binding plus from point_format.py, written + where CI View puts it (straight after , no ) + * so each row answers, independently: does the mask render the value, is + it wide enough for the real range, and does it round to the right + number of decimals + +Read it as: PLAIN live + MASKED blank -> that mask is too narrow or wrong. + both live, MASKED rounded -> the mask is good. + both blank -> that item, not the mask. + + python 04-scada/hmi/build_tagtest.py +""" + +import datetime +import re +from pathlib import Path + +import point_format as pf + +HERE = Path(__file__).resolve().parent +OUT = HERE / "out" / "WRPS_TagTest.xml" +ITEM_FILE = HERE.parents[1] / "04-scada" / "modbus_points" / "wrps_item_df.qli" +IDS_FILE = HERE / "item-ids.csv" + +W, H = 1920, 1080 + +# This display is the BOOTSTRAP: it binds every project item, so saving it +# once in CI View measures all 49 ids in a single pass. It is therefore +# the one display allowed to carry ids that are not measured yet - it has +# to be buildable before anything has been measured, or there is no way to +# measure anything. CI View resolves each connection by NAME on save, so +# a placeholder id is enough for the harvest to work. +# +# It used to carry its own copy of the id rule, with the base hardcoded at +# 136 - which silently went a whole generation stale when the items were +# re-imported. Ids now come from build_display, the single source, and +# anything unmeasured is an obvious placeholder rather than a plausible +# wrong number. +PLACEHOLDER = "1.1.0.0.0.1.1.%d" + +# ------------------------------------------------------------------ items + + +def items(): + """Project items in creation order, with their itemId. + + (index, name, representation, itemId, measured?) + """ + import build_display as bd + measured = bd.measured_ids() + reps = bd.item_reps() + + q = ITEM_FILE.read_text(encoding="utf-8") + order, seen = [], set() + for m in re.finditer(r'^"(AID\.WRPS\.[A-Z0-9_.]+)"', q, re.M): + if m.group(1) not in seen: + seen.add(m.group(1)) + order.append(m.group(1)) + + out = [] + for k, name in enumerate(order): + rep = reps.get(name, "Real") + iid = measured.get(name, PLACEHOLDER % (1 if rep == "Boolean" else 7)) + out.append((k, name, rep, iid, name in measured)) + return out + + +# ------------------------------------------------------------------ layout +ROWS_PER_COL = 25 +ROW_PITCH = 38 +TOP = 92 +COL_X = (40, 990) # left edge of each page column +W_IDX, W_NAME, W_REF = 34, 470, 150 + +LAB = "Segoe UI" +VAL = "Consolas" + + +def esc(s): + return (s.replace("&", "&").replace("<", "<").replace(">", ">")) + + +def half_w(text, size, font): + """Half the rendered width. Approximate - the editor normalises on save.""" + per = 0.60 if font == VAL else 0.52 + return round(len(text) * size * per / 2.0, 4) + + +class Doc: + def __init__(self): + self.refs = [] # (id, xml) in globalSection + self.parts = [] # component xml + self._next_ref = 10 + self._next_html = 100 + self._cache = {} + + def ref(self, key, xml): + if key in self._cache: + return self._cache[key] + rid = self._next_ref + self._next_ref += 1 + self.refs.append((rid, xml)) + self._cache[key] = rid + return rid + + def hid(self): + self._next_html += 1 + return self._next_html + + def paint(self, rgb): + return self.ref(("paint", rgb), + '\n' + ' \n' + ' ' % rgb) + + def filt(self, value="0.0"): + return self.ref(("filter", value), + '\n' + ' %s\n' + ' ' % value) + + # --- components ---------------------------------------------------- + def text(self, x, y, value, size=14, colour=(14, 22, 33), bold=False, + font=LAB): + """x is the LEFT edge; CI View stores centre + half extents.""" + hw = half_w(value, size, font) + hh = round(size * 0.62 / 2.0 + 2.0, 4) + self.parts.append( + ' \n' + ' %d\n' + ' %s\n' + ' \n' + ' \n' + ' \n' + ' true\n' + ' %s\n %s\n' + ' %s\n %s\n' + ' %s\n %s\n' + ' 0.0\n 0.0\n' + ' ' + % (self.hid(), esc(value), font, ' bold="true"' if bold else '', + size, self.paint(colour), round(x + hw, 4), float(y), + hh, hh, hw, hw)) + + def action(self, item, iid, filter_ref): + """The actionConnectTo block, byte-for-byte as CI View saved it.""" + return ('\n' + ' \n' + ' \n' + ' \n' + ' 1\n' + ' %s\n' + ' %s\n' + ' \n' + ' ' % (filter_ref, item, iid)) + + def number(self, x, y, item, iid, mask=None, size=14, + colour=(14, 22, 33)): + """A bound number. mask=None -> no (the control). + + Element order copies what CI View wrote when the FIT-201 INLET value + was formatted by hand: , , , ... , . + A masked number carries no . + """ + rid = self.ref(("data", item), + '\n %s\n ' + '' % self.action(item, iid, self.filt("0.0"))) + if mask: + head = ' %s\n' % mask + sizer = mask + else: + head = ' 0\n' + sizer = "0000000" + hw = half_w(sizer, size, VAL) + hh = round(size * 0.62 / 2.0 + 2.0, 4) + self.parts.append( + ' \n' + ' %d\n' + '%s' + ' \n' + ' \n' + ' \n' + ' true\n' + ' %s\n %s\n' + ' %s\n %s\n' + ' %s\n %s\n' + ' 0.0\n 0.0\n' + ' \n' + ' ' + % (self.hid(), head, VAL, size, self.paint(colour), + round(x + hw, 4), float(y), hh, hh, hw, hw, rid)) + + # --- serialise ------------------------------------------------------ + def xml(self, title): + when = datetime.datetime.now().strftime("%Y.%m.%d %H:%M:%S.0 AEST") + g = "\n".join(' \n %s\n ' + % (rid, x) for rid, x in self.refs) + vis = "".join( + ' \n' + ' %d\n %s\n' + ' %s\n' + ' true\n' + ' %s\n' + ' \n' + % (h, n, d, z) for h, n, d, z in ( + (2, "Overview", "Always shown", "10.0"), + (3, "Rough", "Shown when viewing a large area", "25.0"), + (4, "Standard", "Shown when using the default view setting", "100.0"), + (5, "Detailed", "Shown when zoomed in", "200.0"), + (6, "Fine", "Shown when zoomed in closely", "400.0"), + )) + return ( + '\n' + '\n' + ' \n%s\n \n' + ' \n' + ' \n' + ' %d\n %d\n' + ' 5\n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' 700\n' + ' true\n' + ' false\n' + '%s' + ' \n' + ' 1\n Layer1\n' + ' \n' + ' %d\n' + '%s\n' + ' \n' + '\n' + % (g, W, H, when, esc(title), vis, + len(self.parts) + 6, "\n".join(self.parts))) + + +# ------------------------------------------------------------------ build +def build(): + d = Doc() + rows = items() + + grey = (70, 84, 104) + d.text(40, 30, "WRPS TAG TEST 2 - format masks, every project item", + size=20, bold=True) + d.text(40, 56, "left number = PLAIN, no (this was live last time, " + "so it must stay live) | right number = the same binding " + "MASKED with the mask printed beside it | masked blank " + "while plain is live means that mask is wrong", + size=12, colour=grey) + + for k, name, rep, iid, measured in rows: + col, row = divmod(k, ROWS_PER_COL) + x = COL_X[col] + y = TOP + row * ROW_PITCH + short = name[len("AID.WRPS."):] + mask = pf.mask(name) + + d.text(x, y, "%02d" % (k + 1), size=13, colour=grey, font=VAL) + d.text(x + W_IDX, y, short, size=13, font=VAL) + d.text(x + W_IDX + 210, y, "%s%s" % (rep[0], "" if measured else " *"), + size=11, colour=grey, font=VAL) + + nx = x + W_IDX + 250 + d.number(nx, y, name, iid, mask=None) + d.text(nx + 120, y, mask.rjust(5), size=11, colour=grey, font=VAL) + d.number(nx + 190, y, name, iid, mask=mask) + + unmeasured = [r for r in rows if not r[4]] + d.text(40, 1050, + "* itemId NOT measured - placeholder. Save this display in CI View " + "and harvest it to measure all %d. " + "%d items, %d bindings, %d distinct masks." + % (len(rows), len(rows), len(rows) * 2, len(set(pf.MASKS.values()))), + size=12, colour=grey) + + OUT.parent.mkdir(exist_ok=True) + OUT.write_text(d.xml("Generated by build_tagtest.py"), encoding="utf-8") + print("wrote %s (%d items, %d numbers, %d shared objects)" + % (OUT, len(rows), len(rows) * 2, len(d.refs))) + if unmeasured: + print(" %d of %d ids are PLACEHOLDERS and will read 0: %s" + % (len(unmeasured), len(rows), + ", ".join(r[1].replace("AID.WRPS.", "") for r in unmeasured[:6]) + + (" ..." if len(unmeasured) > 6 else ""))) + print(" That is expected before a harvest - this display exists to " + "measure them.\n" + " Deploy it, open it in CI View, link one value, save, then:\n" + " python build_display.py --harvest ") + else: + print(" all %d ids measured" % len(rows)) + + +if __name__ == "__main__": + build() diff --git a/04-scada/hmi/check_layout.py b/04-scada/hmi/check_layout.py new file mode 100644 index 0000000..62d4d0c --- /dev/null +++ b/04-scada/hmi/check_layout.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +"""Overlap audit for the generated displays. + + python check_layout.py [display.xml ...] + +Reads the built XML and reports text that collides with other text, or +that escapes its panel. Written after a units relabel pushed the wet +well caption into the top ladder label and nobody noticed until the +screens were on a monitor - the generator places components by +arithmetic, so a collision is arithmetic too, and worth catching here +rather than in CI View. + +Text width is estimated, not measured: the value font is Consolas, which +is monospaced at ~0.55 em, and the label font is close enough at this +size. So the check is approximate by construction - it is a net for +gross collisions, not a typesetter. + +Some components no longer HAVE one position. A level mark bound to its +setpoint through `.y` sits wherever the operator last typed, so +checking it where the generator happened to draw it checks the one +arrangement that is guaranteed not to be the problem. Those are swept +across their whole input range instead - see `sweep`. +""" + +import re +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +CHAR_W = 0.55 # em, Consolas advance width +PAD = 2.0 # px of slack before two boxes count as colliding + + +def components(xml): + """(kind, x1, y1, x2, y2, value, size) for everything positioned.""" + out = [] + for kind, body in re.findall( + r'<(rectangle|text|number|ellipse) type="componentData">(.*?)', + xml, re.S): + def num(tag): + m = re.search(r"<%s>(-?[\d.]+)" % (tag, tag), body) + return float(m.group(1)) if m else None + + cx, cy = num("x"), num("y") + top, bot, left, right = num("top"), num("bottom"), num("left"), num("right") + if None in (cx, cy, top, bot, left, right): + continue + val = re.search(r"(.*?)", body) + size = re.search(r'size="(\d+)"', body) + out.append((kind, cx - left, cy - top, cx + right, cy + bot, + val.group(1) if val else "", int(size.group(1)) if size else 0, + " w * 1.5: + x1 = x2 - w + else: + x2 = x1 + w + return x1, y1, x2, y2 + + +def overlaps(a, b): + ax1, ay1, ax2, ay2 = text_box(a) + bx1, by1, bx2, by2 = text_box(b) + return not (ax2 - PAD <= bx1 or bx2 - PAD <= ax1 + or ay2 - PAD <= by1 or by2 - PAD <= ay1) + + +def line_boxes(xml): + """Process lines as thin boxes - the obstacles text must not sit on. + + THE GAP THIS CLOSES. The first version of this checker compared text + against text only, so it passed a screen whose top row of values was + sitting on the inlet pipe and whose data column ran across the + discharge manifold. It measured what was easy, not what was wrong. + """ + out = [] + for body in re.findall(r'(.*?)', xml, re.S): + pts = [float(v) for v in re.findall(r"<(?:x|y)>(-?[\d.]+)", body)] + if len(pts) < 2: + continue + cx, cy = pts[0], pts[1] + def num(tag): + m = re.search(r"<%s>(-?[\d.]+)" % (tag, tag), body) + return float(m.group(1)) if m else 0.0 + l, r_, t, b = num("left"), num("right"), num("top"), num("bottom") + out.append(("line", cx - l, cy - t, cx + r_, cy + b, "", 0, False, 0, 0)) + return out + + +def shared_data(xml): + """globalSection data objects by id - where a component's actions live.""" + return dict(re.findall( + r'(.*?)', + xml, re.S)) + + +def movers(xml): + """Components whose y is bound to an item, with the law that moves them. + + Yields (component, y_at_input_0, y_at_input_end, input_end, item). The + component tuple is the one `components()` produces, so it drops straight + into the same overlap test. + """ + shared = shared_data(xml) + out = [] + for kind, body in re.findall( + r'<(rectangle|text|number) type="componentData">(.*?)', xml, re.S): + ref = re.search(r'', body) + if not ref or ref.group(1) not in shared: + continue + m = re.search(r'name="%s\.y"/>.*?([\d.]+)' + r"(-?[\d.]+)" + r"(-?[\d.]+).*?" + r"([^<]+)" % kind, + shared[ref.group(1)], re.S) + if not m: + continue + end, y0, y1 = float(m.group(1)), float(m.group(2)), float(m.group(3)) + # a number carries no ; its mask is what it will be as wide as + val = re.search(r"(.*?)", body) + fmt = re.search(r"([^<]*)", body) + text = val.group(1) if val else (fmt.group(1) if fmt else "") + for c in components("<%s type=\"componentData\">%s" % (kind, body, kind)): + out.append((c[:5] + (text,) + c[6:], y0, y1, end, m.group(4))) + return out + + +def sweep(xml, statics, step=0.5): + """Drive every moving component across its range against what stays put. + + The invariant being enforced is that a mark which can be moved and a + mark which cannot never share a column - because a mark that moves will + eventually be driven to the height of one that does not. Checking the + positions the generator drew proves nothing about that; only the sweep + does. + + Moving-against-moving is deliberately NOT reported. Two setpoints set + close together really do put their marks close together, and that is the + screen telling the truth about the thresholds, not a layout fault. + """ + hits = {} + for c, y0, y1, end, item in movers(xml): + cy = (c[2] + c[4]) / 2.0 + v = 0.0 + while v <= end + 1e-9: + dy = (y0 + (y1 - y0) * v / end) - cy + moved = (c[0], c[1], c[2] + dy, c[3], c[4] + dy) + c[5:] + for s in statics: + if overlaps(moved, s): + key = (item, c[5] or c[0], s[5] or s[0]) + lo, hi = hits.get(key, (v, v)) + hits[key] = (min(lo, v), max(hi, v)) + v += step + return hits + + +def audit(path): + xml = path.read_text(encoding="utf-8") + comps = components(xml) + texts = [c for c in comps if c[0] == "text" and c[5].strip()] + lines = line_boxes(xml) + hits = [] + for i, a in enumerate(texts): + for b in texts[i + 1:]: + if overlaps(a, b) and not stacked(a, b): + hits.append((a[5], b[5])) + for a in texts: + for b in lines: + ax1, ay1, ax2, ay2 = text_box(a) + if not (ax2 <= b[1] or b[3] <= ax1 or ay2 <= b[2] or b[4] <= ay1): + hits.append((a[5], "" % (b[1], b[2]))) + # things that stay put, which is what a moving mark must never reach + moving_at = {(round(c[1], 1), round(c[2], 1)) for c, _, _, _, _ in movers(xml)} + statics = [t for t in texts if (round(t[1], 1), round(t[2], 1)) not in moving_at] + swept = sweep(xml, statics + lines) + + n = len(hits) + len(swept) + print("%-28s %3d components, %3d text, %2d moving" + % (path.name, len(comps), len(texts), len(moving_at)), end="") + if n: + print(" -> %d COLLISIONS" % n) + for a, b in hits: + print(" %-40s x %s" % (a[:40], b[:40])) + for (item, a, b), (lo, hi) in sorted(swept.items()): + print(" %-22s %-16s x %-22s at %g-%g%%" + % (item.replace("AID.WRPS.", ""), a[:16], b[:22], lo, hi)) + else: + print(" -> clear") + return n + + +def main(): + args = sys.argv[1:] + files = [Path(a) for a in args] if args else sorted((HERE / "out").glob("WRPS_*.xml")) + bad = sum(audit(f) for f in files if f.is_file()) + sys.exit(1 if bad else 0) + + +if __name__ == "__main__": + main() diff --git a/04-scada/hmi/component-kit.md b/04-scada/hmi/component-kit.md new file mode 100644 index 0000000..8e620e6 --- /dev/null +++ b/04-scada/hmi/component-kit.md @@ -0,0 +1,104 @@ +# The WRPS component kit + +The house style for this project's displays. AOG is the **reference** for how a +component is parameterised — it is not used directly, and only the components +this project needs exist. + +Applies to this display and every future display in the project. A new +requirement adds a component here; it never adds a second style. + +## Tokens + +Named like AOG's `AOG_C*` so they map onto CI Server global parameters. A restyle +is an edit to this table and nothing else. + +| Token | Value | Use | +|---|---|---| +| `WRPS_CBG` | `#EDF0F4` | display ground | +| `WRPS_CSF` | `#FFFFFF` | panel / surface | +| `WRPS_CSF2` | `#F5F8FB` | recessed surface, inside a value box | +| `WRPS_CBD` | `#C3CCD8` | border — heavier than a desktop design, for distance | +| `WRPS_CBD2` | `#DFE5EC` | hairline, internal dividers | +| `WRPS_CT1` | `#0E1621` | primary ink | +| `WRPS_CT2` | `#465468` | labels, secondary ink | +| `WRPS_CAC` | `#0B7DA8` | **interaction only** — selected, focus, faceplate rule | +| `WRPS_CAC2` | `#12A5D9` | accent bright — focus ring | +| `WRPS_CRUN` / bg | `#0F8A45` / `#E4F6EA` | running / on | +| `WRPS_CWRN` / bg | `#A85B00` / `#FFEFD2` | attention | +| `WRPS_CALM` / border | `#D32029` / `#96131A` | **alarm — always a filled block** | +| `WRPS_CPRC` | `#2B3F52` | pipe and vessel outline | +| `WRPS_CWTR` | `#9FCFE6` | liquid fill | + +**Colour policy.** The plant is drawn in ink on white. Cyan means *you can act on +this*; green, amber and red mean *the plant is telling you something*. Nothing +else is coloured, so an abnormal state is the only colour on the screen. + +**Abnormal is a filled block, never coloured text.** At distance, red text on +white reads as slightly darker grey; a filled red block with white text does not. +One fault propagates to every place it is relevant — banner, symbol, instrument +bubble, value box, faceplate badge — so it cannot be missed from any part of the +screen. + +**Type.** Labels **Segoe UI**, values and tag names **Consolas**. Both ship with +Windows, so nothing depends on a font that might not be installed — but Segoe is +the modern face and Tahoma is what dates a screen. Consolas is monospaced, so +digits keep their column as values update at 100 ms, and using it for tag names +gives the display its technical read. + +**Sized for a TV.** Nothing on the display is smaller than ~1.35% of screen +height (about 15 px at 1080p); the wet well level is the largest thing at 4.6%, +because it is the number a room actually watches. Ink is near-black on white and +borders are heavier than a desktop design would use — contrast is what survives a +bright room and a projector. + +**Why it does not look like AOG.** What dates AOG is the bevelled 3D artwork and +mid-grey ground, not the grey itself. This kit is flat, square-cornered, hairline +bordered, white on cool neutral. + +## Components + +Every one takes the AOG-shaped parameter set where it applies: `item`, +`setPointItem`, `tagName`, `format`, `showAlarmIndicator`, `selectable`, +`displayToActivate`. + +| Component | Purpose | Key parameters | States | +|---|---|---|---| +| `WRPS_Display` | The 16:9 shell: ground, title bar, body, simulation band | — | — | +| `WRPS_Title` | Display header — name, drawing ref, station states right | `title`, `subtitle` | — | +| `WRPS_Panel` | Every framed region. Panels never nest | `header` | — | +| `WRPS_Value` | **Every** analog value | `item`, `tagName`, `format`, `units`, `size`, `displayToActivate` | normal · large · warn · alarm | +| `WRPS_State` | **Every** discrete status | `item`, `onText`, `offText`, `inverse`, `severity` | off · on · warn · alarm | +| `WRPS_Button` | **Every** command and mode selection | `label`, `item`, `writeValue`, `pressed`, `variant` | normal · pressed · destructive | +| `WRPS_Pump` | Pump symbol, circle with impeller cross | `item`, `tagName`, `displayToActivate` | stopped · running · tripped | +| `WRPS_Valve` | Swing check and manual isolation, from the P&ID legend | `kind` | static | +| `WRPS_Instrument` | ISA bubble, function letters over loop number | `letters`, `loop`, `item` | static · with value | +| `WRPS_Vessel` | Wet well: outline, liquid fill, setpoint ladder at true heights | `levelItem`, `setpointItems`, `range` | — | +| `WRPS_Faceplate` | Popup display frame — cyan rule, kit components inside | `title`, `subtitle`, `item` | — | +| `WRPS_AlarmBanner` | Present **only** when something is abnormal; pushes the display down | `alarmList`, `count` | hidden · active · acknowledged | + +`WRPS_SimBand` is **not** another component: it is `WRPS_Button` and `WRPS_Value` +inside a fenced region with a cyan rule and a standing label, so the simulation +controls can never be mistaken for plant control. + +## Rules + +1. **No hand-drawn values or buttons.** If it shows a number it is `WRPS_Value`; + if it is clickable it is `WRPS_Button`. This is what stops displays drifting. +2. **Faceplates are separate displays** opened via `displayToActivate`, matching + how AOG works — not overlay groups on the parent display. +3. **Setpoint edits go through a numeric entry popup**, not an inline field. +4. **State is never colour alone.** `WRPS_State` carries a square marker and its + own text, so it survives a projector or a colour-blind operator. +5. **Simulation controls stay in the band.** They never appear beside plant + controls. +6. **An abnormal condition propagates everywhere it is relevant** — banner, plant + symbol, instrument bubble, value box, faceplate badge. One fault, five places. +7. **Blink is reserved for unacknowledged alarms**, and only on the tag name, not + whole regions. CI Server supports this natively (`blinkEnabled`, `blinkDelay`, + 700 ms in the reference screens). +8. **Green is only for genuinely-running equipment.** If normal operation is + colourful, red has competition and stops being an alarm. + +## Geometry + +16:9. Laid out on the editor's 4 px snap grid; 1920 × 1080 unless told otherwise. diff --git a/04-scada/hmi/item-ids.csv b/04-scada/hmi/item-ids.csv new file mode 100644 index 0000000..0a24cdd --- /dev/null +++ b/04-scada/hmi/item-ids.csv @@ -0,0 +1,50 @@ +item,itemId +AID.WRPS.PU301.AVAILABLE,1.1.291.0.0.1.1.1 +AID.WRPS.PU301.RUNNING,1.1.288.0.0.1.1.1 +AID.WRPS.PU301.RUN_CMD,1.1.285.0.0.1.1.1 +AID.WRPS.PU301.RUN_HOURS,1.1.308.0.0.1.1.7 +AID.WRPS.PU301.STATE,1.1.313.0.0.1.1.7 +AID.WRPS.PU301.TRIPPED,1.1.297.0.0.1.1.1 +AID.WRPS.PU302.AVAILABLE,1.1.292.0.0.1.1.1 +AID.WRPS.PU302.RUNNING,1.1.289.0.0.1.1.1 +AID.WRPS.PU302.RUN_CMD,1.1.286.0.0.1.1.1 +AID.WRPS.PU302.RUN_HOURS,1.1.309.0.0.1.1.7 +AID.WRPS.PU302.STATE,1.1.314.0.0.1.1.7 +AID.WRPS.PU302.TRIPPED,1.1.298.0.0.1.1.1 +AID.WRPS.PU303.AVAILABLE,1.1.293.0.0.1.1.1 +AID.WRPS.PU303.RUNNING,1.1.290.0.0.1.1.1 +AID.WRPS.PU303.RUN_CMD,1.1.287.0.0.1.1.1 +AID.WRPS.PU303.RUN_HOURS,1.1.310.0.0.1.1.7 +AID.WRPS.PU303.STATE,1.1.315.0.0.1.1.7 +AID.WRPS.PU303.TRIPPED,1.1.299.0.0.1.1.1 +AID.WRPS.SIM.INFLOW,1.1.330.0.0.1.1.7 +AID.WRPS.SIM.RESET,1.1.332.0.0.1.1.7 +AID.WRPS.SIM.SCENARIO,1.1.331.0.0.1.1.7 +AID.WRPS.SIM.TIME_SCALE,1.1.333.0.0.1.1.7 +AID.WRPS.SP.CMD_PARAM,1.1.321.0.0.1.1.7 +AID.WRPS.SP.CMD_WORD,1.1.320.0.0.1.1.7 +AID.WRPS.SP.HIGH_ALARM,1.1.327.0.0.1.1.7 +AID.WRPS.SP.LEVEL_SP,1.1.322.0.0.1.1.7 +AID.WRPS.SP.MIN_SPEED,1.1.328.0.0.1.1.7 +AID.WRPS.SP.MODE,1.1.319.0.0.1.1.7 +AID.WRPS.SP.SERVICE_HRS,1.1.329.0.0.1.1.7 +AID.WRPS.SP.START_DUTY,1.1.323.0.0.1.1.7 +AID.WRPS.SP.START_P2,1.1.324.0.0.1.1.7 +AID.WRPS.SP.START_P3,1.1.325.0.0.1.1.7 +AID.WRPS.SP.STOP_ALL,1.1.326.0.0.1.1.7 +AID.WRPS.STN.ALARM_WORD,1.1.317.0.0.1.1.7 +AID.WRPS.STN.CMD_ACK,1.1.318.0.0.1.1.7 +AID.WRPS.STN.DISCHARGE,1.1.302.0.0.1.1.7 +AID.WRPS.STN.DUTY_PUMP,1.1.316.0.0.1.1.7 +AID.WRPS.STN.HIGH_LEVEL,1.1.295.0.0.1.1.1 +AID.WRPS.STN.INFLOW,1.1.301.0.0.1.1.7 +AID.WRPS.STN.IN_AUTO,1.1.294.0.0.1.1.1 +AID.WRPS.STN.LEVEL,1.1.300.0.0.1.1.7 +AID.WRPS.STN.NET_ACCUM,1.1.307.0.0.1.1.7 +AID.WRPS.STN.PUMPS_RUNNING,1.1.303.0.0.1.1.7 +AID.WRPS.STN.SPEED,1.1.304.0.0.1.1.7 +AID.WRPS.STN.SPILL_ACTIVE,1.1.296.0.0.1.1.1 +AID.WRPS.STN.STATE,1.1.312.0.0.1.1.7 +AID.WRPS.STN.TIME_TO_LSHH,1.1.306.0.0.1.1.7 +AID.WRPS.STN.TIME_TO_SPILL,1.1.305.0.0.1.1.7 +AID.WRPS.STN.VOL_TO_SPILL,1.1.311.0.0.1.1.7 diff --git a/04-scada/hmi/item-ids.meta.json b/04-scada/hmi/item-ids.meta.json new file mode 100644 index 0000000..dc31868 --- /dev/null +++ b/04-scada/hmi/item-ids.meta.json @@ -0,0 +1,8 @@ +{ + "harvested_from": "", + "harvested_at": "2026-08-14T16:50:54", + "item_file": "wrps_item_df.qli", + "item_file_sha": "326d3bc8e525cebf0d3eba0df73b10b487f11b43b9459e2438e370f605a15bd1", + "measured": 49, + "note": "Every .qli item import renumbers every item. Validate the displays in CI Server's Editor Module, then re-harvest - otherwise the screens go dead." +} diff --git a/04-scada/hmi/point_format.py b/04-scada/hmi/point_format.py new file mode 100644 index 0000000..475af8e --- /dev/null +++ b/04-scada/hmi/point_format.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Display format mask for every project item. Shared by both generators. + +CI Server's `` is a **digit mask**, not a Java DecimalFormat pattern: + + 99.99 -> two integer digits, always two decimals + +That is what CI View wrote when the FIT-201 INLET value was set to 99.99 by +hand, and it matches the `VALUE_FORMAT` masks in the item export +(`"99999"` on our items, `"99.99"` on the reference analog IO). `9` is a +digit position; the count of positions after the point is the number of +decimals always shown. + +The first build emitted `0` and `0.0` - +DecimalFormat patterns, which read as masks **one digit wide**. Every value +wider than that had nowhere to render. That, not the missing ``, is +why the displays read 0: `WRPS_TagTest` carried no `` at all and all +98 bindings were live. + +So a mask must be **wide enough for the point's full range**, or the value +may not fit. The masks below are sized from the register map's engineering +ranges, and points of the same kind share a mask so the screens stay +consistent: + + level & speed in %, and their setpoints 999.9 + flows (m3/h, three pumps reach ~1300) 9999.9 + durations & hour counters (s / h) 99999 + volume (m3) 9999 + alarm bitmask (unsigned 16-bit) 99999 + enums, counts, modes, booleans 9 + +Decimals follow the point's real resolution, not a fixed two: level in % of +the 6.000 m spill weir resolves to 0.017% because the register is mm, so one +decimal is honest and two would not be; a flow in m3/h resolves to 0.1. +Integer registers get no decimal at all - a decimal place there would be +invented precision. + +**These masks must agree with `format_mask` in `04-scada/modbus_points/scada-points.csv`,** +which is what the item export carries into CI Server as `VALUE_FORMAT`. The +units and their gains are defined once, in `gen_scada_points.py`. +""" + +# item short name (after "AID.WRPS.") -> mask +MASKS = { + # --- digital status, one digit ------------------------------------ + "PU301.RUN_CMD": "9", + "PU302.RUN_CMD": "9", + "PU303.RUN_CMD": "9", + "PU301.RUNNING": "9", + "PU302.RUNNING": "9", + "PU303.RUNNING": "9", + "PU301.AVAILABLE": "9", + "PU302.AVAILABLE": "9", + "PU303.AVAILABLE": "9", + "STN.IN_AUTO": "9", + "STN.HIGH_LEVEL": "9", + "STN.SPILL_ACTIVE": "9", + "PU301.TRIPPED": "9", + "PU302.TRIPPED": "9", + "PU303.TRIPPED": "9", + + # --- analog measurements ------------------------------------------ + "STN.LEVEL": "999.9", # %, 100% = the 6.000 m spill weir + "STN.INFLOW": "9999.9", # m3/h, raw (L/s x10) x 0.36 + "STN.DISCHARGE": "9999.9", # m3/h, three pumps reach ~1300 + "STN.PUMPS_RUNNING": "9", # count 0-3 + "STN.SPEED": "999.9", # %, 100% = 50 Hz + "STN.TIME_TO_SPILL": "99999", # s, 32767 = drawing down + "STN.TIME_TO_LSHH": "99999", # s, 32767 = drawing down + "STN.NET_ACCUM": "9999.9", # m3/h, SIGNED + "PU301.RUN_HOURS": "99999", # h + "PU302.RUN_HOURS": "99999", + "PU303.RUN_HOURS": "99999", + "STN.VOL_TO_SPILL": "9999", # m3 + + # --- enums and words ---------------------------------------------- + "STN.STATE": "9", # enum 3.1 + "PU301.STATE": "9", # enum 3.2 + "PU302.STATE": "9", + "PU303.STATE": "9", + "STN.DUTY_PUMP": "9", # 0 = none, 1-3 + "STN.ALARM_WORD": "99999", # unsigned 16-bit bitmask + "STN.CMD_ACK": "99", + + # --- setpoints ------------------------------------------------------ + "SP.MODE": "9", # 1 = auto, 2 = off + "SP.CMD_WORD": "99", + "SP.CMD_PARAM": "9", # pump number + "SP.LEVEL_SP": "999.9", # %, entered in % of spill weir + "SP.START_DUTY": "999.9", # % + "SP.START_P2": "999.9", # % + "SP.START_P3": "999.9", # % + "SP.STOP_ALL": "999.9", # % + "SP.HIGH_ALARM": "999.9", # % + "SP.MIN_SPEED": "999.9", # %, 100% = 50 Hz + "SP.SERVICE_HRS": "99999", # h + + # --- simulation ------------------------------------------------------ + "SIM.INFLOW": "9999.9", # m3/h + "SIM.SCENARIO": "9", # 0-3 + "SIM.RESET": "9", # write 1, self-clearing + "SIM.TIME_SCALE": "999", # 1-120 +} + +PREFIX = "AID.WRPS." + + +def mask(item): + """Mask for an item, by full or short name. Unknown -> widest integer.""" + short = item[len(PREFIX):] if item.startswith(PREFIX) else item + return MASKS.get(short, "99999") + + +def decimals(item): + m = mask(item) + return len(m.split(".")[1]) if "." in m else 0 + + +def check(names): + """Fail loudly if an item has no mask - a new point must be given one.""" + missing = [n for n in names + if (n[len(PREFIX):] if n.startswith(PREFIX) else n) not in MASKS] + if missing: + import sys + sys.exit("no format mask for: %s\n add it to point_format.py" + % ", ".join(missing)) diff --git a/04-scada/hmi/render_preview.py b/04-scada/hmi/render_preview.py new file mode 100644 index 0000000..3daeabf --- /dev/null +++ b/04-scada/hmi/render_preview.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +"""Render built CI Server display XML back to a browser preview. + + python render_preview.py -> out/preview.html (all displays) + python render_preview.py WRPS_FP_Pump -> just that one + +WHY THIS EXISTS +--------------- +Every preview up to now was a drawing of what I *meant* to build, made +alongside the generator rather than from it. That is the same mistake as +a test that checks the intent instead of the artefact, and it has already +cost this project a round: a script that failed before writing its file, +while a checker with hard-coded coordinates cheerfully reported the file +was fine. + +This reads the XML that was actually generated and draws it. If a +component is in the file it appears here; if it is not, it does not. + +It also resolves what a static picture normally cannot: + + live values read over Modbus, formatted through the point's own + gain and display mask, so a number shows what the + operator would see rather than "9999.9" + visibility a component bound to a Boolean item is drawn only if + that item is true; one bound to a display parameter is + resolved through the genericFunctions equality that + writes the parameter. Without this, every state stack + renders at once and the preview is a smear. + +Approximate by construction: browser font metrics are not Java's, so +text width is estimated. It is a faithful reading of the geometry, not +a pixel-exact simulation of CI View. +""" + +import csv +import html +import importlib.util +import math +import re +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +OUT = HERE / "out" +POINTS = HERE.parent / "modbus_points" / "scada-points.csv" +QLI_GEN = HERE.parent / "modbus_points" / "gen_ciserver_qli.py" +PLC_HOST, PLC_PORT, PLC_UNIT = "192.168.153.192", 502, 1 + + +# ------------------------------------------------------------------ data +def leaf_to_iec(): + spec = importlib.util.spec_from_file_location("q", QLI_GEN) + q = importlib.util.module_from_spec(spec) + spec.loader.exec_module(q) + return {"AID.WRPS.%s.%s" % v: k for k, v in q.LEAF.items()} + + +def live_values(): + """{itemName: (raw, formatted_string)} - or {} if the PLC is unreachable.""" + rows = {r["iec_address"]: r for r in csv.DictReader(POINTS.open(newline="", encoding="utf-8"))} + names = leaf_to_iec() + try: + from pymodbus.client import ModbusTcpClient + c = ModbusTcpClient(PLC_HOST, port=PLC_PORT, timeout=3) + if not c.connect(): + return {} + hr = c.read_holding_registers(address=0, count=21, device_id=PLC_UNIT).registers + sp = c.read_holding_registers(address=1024, count=11, device_id=PLC_UNIT).registers + sim = c.read_holding_registers(address=1044, count=4, device_id=PLC_UNIT).registers + co = [int(b) for b in c.read_coils(address=0, count=15, device_id=PLC_UNIT).bits[:15]] + c.close() + except Exception: + return {} + + raw = {"%%QW%d" % i: v for i, v in enumerate(hr)} + raw.update({"%%MW%d" % i: v for i, v in enumerate(sp)}) + raw.update({"%%MW%d" % (20 + i): v for i, v in enumerate(sim)}) + raw.update({"%%QX0.%d" % i: co[i] for i in range(8)}) + raw.update({"%%QX1.%d" % i: co[8 + i] for i in range(7)}) + + out = {} + for name, iec in names.items(): + if iec not in raw or iec not in rows: + continue + r, v = rows[iec], raw[iec] + if r["data_type"] == "Boolean": + out[name] = (v, str(v)) + continue + if v > 32767 and iec != "%QW17": + v -= 65536 + dec = len(r["format_mask"].split(".")[1]) if "." in r["format_mask"] else 0 + out[name] = (v, "%.*f" % (dec, v * float(r["eng_gain"]))) + return out + + +# ------------------------------------------------------------------ parse +def num(body, tag, default=None): + m = re.search(r"<%s>(-?[\d.]+)" % (tag, tag), body) + return float(m.group(1)) if m else default + + +def attr(tag_text, name, default=None): + m = re.search(r'%s="(-?[\d.]+)"' % name, tag_text) + return float(m.group(1)) if m else default + + +class Doc: + def __init__(self, path): + self.xml = path.read_text(encoding="utf-8") + self.name = path.stem + self.w = int(num(self.xml, "width", 1920) or 1920) + self.h = int(num(self.xml, "height", 1080) or 1080) + self.paints, self.datas = {}, {} + for rid, body in re.findall(r'(.*?)', self.xml, re.S): + p = re.search(r'colorSet r="(\d+)" g="(\d+)" b="(\d+)"', body) + if p: + self.paints[rid] = "rgb(%s,%s,%s)" % p.groups() + else: + self.datas[rid] = body + # genericFunctions: parameter name -> (item, constant) + self.funcs = {} + for body in re.findall(r"(.*?)", + self.xml, re.S): + pname = re.search(r"([^<]+)", body) + const = num(body, "numberArgument2") + item = re.search(r"([^<]+)", body) + if pname and const is not None and item: + self.funcs[pname.group(1)] = (item.group(1), const) + + def paint(self, body, tag): + m = re.search(r'<%s ref="(\d+)"/>' % tag, body) + if m: + return self.paints.get(m.group(1), "none") + m = re.search(r'<%s type="paint">([^<]+)", body) + if m: + return m.group(1) + m = re.search(r'', body) + if m: + d = self.datas.get(m.group(1), "") + mm = re.search(r"([^<]+)", d) + if mm: + return mm.group(1) + return None + + def visible(self, body, live): + """Resolve a .visible binding. Unbound components are always drawn.""" + refs = re.findall(r'', body) + blocks = [self.datas.get(r, "") for r in refs] + [body] + for blk in blocks: + if ".visible" not in blk: + continue + p = re.search(r"([^<]+)", blk) + if p: + item, const = self.funcs.get(p.group(1), (None, None)) + if item is None or item not in live: + return None + return abs(live[item][0] - const) < 0.001 + i = re.search(r"([^<]+)", blk) + if i: + if i.group(1) not in live: + return None + return bool(live[i.group(1)][0]) + return True + + +def render(doc, live): + """SVG for one display, components in document order.""" + svg = ['' + % (doc.w, doc.h, doc.w, doc.h), + '' % (doc.w, doc.h)] + unknown = 0 + + pattern = (r'<(rectangle|ellipse|line|text|number|numberField|dataBar|button|microTrend)' + r'( type="componentData"[^>]*)>(.*?)') + for kind, head, body in re.findall(pattern, doc.xml, re.S): + vis = doc.visible(body, live) + if vis is False: + continue + if vis is None: + unknown += 1 + op = ' opacity="0.35"' if vis is None else "" + + # geometry: child elements, or attributes on the tag (numberField/dataBar/button) + cx = num(body, "x", attr(head, "x")) + cy = num(body, "y", attr(head, "y")) + if cx is None or cy is None: + continue + t = num(body, "top", attr(head, "top", 0)) or 0 + b = num(body, "bottom", attr(head, "bottom", 0)) or 0 + l = num(body, "left", attr(head, "left", 0)) or 0 + r = num(body, "right", attr(head, "right", 0)) or 0 + x, y, w, h = cx - l, cy - t, l + r, t + b + + if kind == "rectangle": + svg.append('' + % (x, y, w, h, doc.paint(body, "fillPaint"), doc.paint(body, "strokePaint"), + num(body, "width", 1) or attr(body, "width", 1) or 1, op)) + elif kind == "ellipse": + svg.append('' + % (cx, cy, l, t, doc.paint(body, "fillPaint"), doc.paint(body, "strokePaint"), op)) + elif kind == "line": + pts = re.findall(r"(-?[\d.]+)(-?[\d.]+)", body) + if len(pts) >= 2: + svg.append('' + % (" ".join("%s,%s" % p for p in pts), doc.paint(body, "strokePaint"), + float(re.search(r'stroke" width="([\d.]+)"', body).group(1)) if re.search(r'stroke" width="([\d.]+)"', body) else 2, op)) + else: + svg.append('' + % (x, y, x + w, y + h, doc.paint(body, "strokePaint"), op)) + elif kind == "text": + v = re.search(r"(.*?)", body, re.S) + f = re.search(r'name="([^"]+)" bold="(\w+)"[^>]*size="(\d+)"', body) + fam, bold, size = (f.group(1), f.group(2), int(f.group(3))) if f else ("Segoe UI", "false", 13) + svg.append('%s' + % (cx, cy + size * 0.35, fam, size, "700" if bold == "true" else "400", + doc.paint(body, "fillPaint"), op, html.escape(v.group(1) if v else ""))) + elif kind == "number": + item = doc.bound_item(body) + mask = re.search(r"([^<]*)", body) + shown = live.get(item, (None, mask.group(1) if mask else "?"))[1] + f = re.search(r'size="(\d+)"', body) + size = int(f.group(1)) if f else 20 + svg.append('%s' + % (cx, cy + size * 0.35, size, doc.paint(body, "fillPaint"), op, + html.escape(str(shown)))) + elif kind == "numberField": + item = doc.bound_item(body) + shown = live.get(item, (None, ""))[1] + svg.append('' + % (x, y, w, h, op)) + svg.append('%s' + % (x + 8, cy + 5, op, html.escape(str(shown)))) + elif kind == "dataBar": + item = doc.bound_item(body) + lo = num(body, "lowLimit", 0) or 0 + hi = num(body, "highLimit", 100) or 100 + val = live.get(item, (lo, ""))[0] + try: + pct = max(0.0, min(1.0, (float(live[item][1]) - lo) / (hi - lo))) + except Exception: + pct = 0.0 + svg.append('' + % (x, y, w, h, doc.paint(body, "background"), op)) + svg.append('' + % (x, y + h * (1 - pct), w, h * pct, doc.paint(body, "foreground"), op)) + elif kind == "microTrend": + # the trace itself is CI Server's; what the preview has to show is + # the FOOTPRINT, so a trend that lands on a pipe is visible here + # rather than after deployment + item = doc.bound_item(body) + hi = num(body, "value", 100) or 100 # scaleMax[0] + svg.append('' % (x, y, w, h, op)) + try: + pct = max(0.0, min(1.0, float(live[item][1]) / hi)) + except Exception: + pct = 0.5 + svg.append('' + % (" ".join("%.1f,%.1f" % (x + w * k / 24.0, + y + h * (1 - pct * (0.55 + 0.45 * math.sin(k / 2.4)))) + for k in range(25)), op)) + svg.append('%s' + % (x + 4, y + 11, op, html.escape((item or "").replace("AID.WRPS.", "")))) + elif kind == "button": + lab = re.search(r"", body) + fg = re.search(r'foregroundColor type="colorSet" r="(\d+)" g="(\d+)" b="(\d+)"', body) + bg = re.search(r'backgroundColor type="colorSet" r="(\d+)" g="(\d+)" b="(\d+)"', body) + svg.append('' + % (x, y, w, h, "rgb(%s,%s,%s)" % bg.groups() if bg else "#fff", op)) + svg.append('%s' + % (cx, cy + 5, "rgb(%s,%s,%s)" % fg.groups() if fg else "#0e1621", op, + html.escape(lab.group(1) if lab else ""))) + svg.append("") + return "\n".join(svg), unknown + + +def main(): + want = sys.argv[1:] + files = sorted(OUT.glob("WRPS_*.xml")) + if want: + files = [f for f in files if f.stem in want] + if not files: + sys.exit("nothing to render in %s" % OUT) + + live = live_values() + parts = ['WRPS displays — rendered from the built XML', + '
', + "

Rendered from the generated XML in out/

", + '

%s. Components are drawn only if the file contains them; ' + "values and visibility resolved against the live PLC.

" + % ("Live values read from the PLC" if live else + "PLC unreachable - numbers show their format mask, state-bound shapes are dimmed")] + for f in files: + doc = Doc(f) + svg, unknown = render(doc, live) + note = " %d component(s) whose visibility could not be resolved, shown dimmed" % unknown if unknown else "" + parts.append("

%s  %d x %d%s

" + % (doc.name, doc.w, doc.h, note)) + parts.append('
%s
' % svg) + print("rendered %-22s %d x %d" % (doc.name, doc.w, doc.h)) + parts.append("
") + dest = OUT / "preview.html" + dest.write_text("\n".join(parts), encoding="utf-8") + print("wrote %s" % dest) + + +if __name__ == "__main__": + main() diff --git a/04-scada/modbus_points/README.md b/04-scada/modbus_points/README.md new file mode 100644 index 0000000..a1f96ce --- /dev/null +++ b/04-scada/modbus_points/README.md @@ -0,0 +1,222 @@ +# modbus_points — the CI Server tag database + +Named for the protocol: CI Server configures other protocols differently, so a +folder called `points` would be ambiguous. + +Three kinds of file live here, and only the first two are generated: + +| | | +|---|---| +| **Generated** | `scada-points.csv` and the three `wrps_*.qli` | +| **Generators** | `gen_scada_points.py`, `gen_ciserver_qli.py` | +| **Hand-made** | `historian/` — no generator, and it has drifted. See its README. | + +Deployed with `dssqld`, not by file copy — see `../QUICKLOAD.md`. + +```bash +python gen_scada_points.py # register-map.csv -> scada-points.csv +python gen_ciserver_qli.py # scada-points.csv -> the two .qli files +``` + +| File | What | +|---|---| +| `scada-points.csv` | The point list, for reading and for configuring by hand | +| `historian/` | Collection groups and 49 item bindings — **hand-made**, see its README | +| `wrps_section_df.qli` | `@SECTION_DF` — the six `AID.WRPS.*` sections | +| `wrps_modbus_point_df.qli` | `@MODBUS_POINT_DF` — 49 Modbus point definitions, station `WRPS_PLC` | +| `wrps_item_df.qli` | `@ITEM_DF` — 49 items in those sections, bound to those points | + +**Import in that order.** CI Server derives its hierarchy from the dots in a name, +and a section must exist before an item can be created inside it. + +The chain is ST sources → `03-plc/register-map.csv` → `scada-points.csv` → `.qli`, +so a register change propagates by re-running the scripts. **Nothing here is +hand-edited**; if CI Server needs a field the generators do not emit, it goes in +the generator. + +## Item structure under AID.WRPS + +| Section | Points | Contents | +|---|---|---| +| `AID.WRPS.STN` | 16 | Level, inflow, discharge, pumps running, speed, times, volumes, station state, duty pump, alarm word, command ack, in-auto, high level, spill | +| `AID.WRPS.PU301/2/3` | 6 each | Run command, running, available, tripped, pump state, run hours | +| `AID.WRPS.SP` | 11 | Mode, command word/param, level setpoints, high alarm, min speed, service interval | +| `AID.WRPS.SIM` | 4 | Scenario, manual inflow, reset, time scale | + +Equipment-oriented rather than address-oriented, so an HMI face for a pump binds +to one folder, and the simulation controls sit apart from anything a real plant +would have. + +## NSIDs + +The NSID space is shared by sections and items. In your exports items occupy +3–166 and sections 1–170, so 171 is the first free id: + +| | NSIDs | +|---|---| +| Sections `STN`, `PU301`, `PU302`, `PU303`, `SP`, `SIM` | 171–176 | +| The 49 items | 177–225 | + +`AID` (169) and `AID.WRPS` (170) **already exist**, so the section file emits only +their six children, parented to 170. Pass `--include-root` to emit `AID` and +`AID.WRPS` too, for a system that lacks them. Override with `--nsid-base`, +`--parent-nsid`, or pin one with `--section-nsid STN=41`. + +## Address base — settled + +**`--address-base 1` is correct.** CI Server's `IO_ADDRESS` numbering is 1-based: +holding register 0 (`%QW0`, wet well level) is `RO:01`. + +Confirmed by import on 2026-08-14, cross-checking CI Server against a direct +pymodbus read. The decisive check was self-consistency between two independent +registers: `STN.LEVEL` 4140 mm and `STN.VOL_TO_SPILL` 223 m3 reconcile through the +plant geometry, (6.000 - 4.140) x 120 = 223.2 m3. An off-by-one mapping cannot +produce that agreement. + +## Engineering units — the SCADA side chooses them + +The PLC publishes mm, L/s × 10 and Hz × 10, and that does not change: it is the +contract in `03-plc/register-map.csv`. What the operator *reads* is a +presentation choice, made here, as a pure gain on the raw register: + + engineering value = raw register × gain (offset is always 0) + +| Item | Unit | Gain | Basis | +|---|---|---|---| +| `STN.LEVEL` | % | 1/60 | **100% = the 6.000 m spill weir**, register is mm | +| `STN.INFLOW`, `STN.DISCHARGE`, `STN.NET_ACCUM`, `SIM.INFLOW` | m³/h | 0.36 | L/s × 3.6, register is L/s × 10 | +| `STN.SPEED` | % | 0.2 | 100% = 50 Hz, the drive maximum | +| `SP.LEVEL_SP`, `START_DUTY`, `START_P2/P3`, `STOP_ALL`, `HIGH_ALARM` | % | 1/60 | same scale as `STN.LEVEL`; **entered in %** | +| `SP.MIN_SPEED` | % | 0.2 | same scale as `STN.SPEED`; **entered in %** | + +The gains live in one place — `UNITS` in `gen_scada_points.py`. Change a unit +there and it propagates to `scada-points.csv`, to both `.qli` files and to the +HMI masks. + +> [!IMPORTANT] +> **One item per I/O address — a register carries exactly one unit.** Publishing +> level in m *and* % as two Modbus points on the same `IO_ADDRESS` was tried on +> 2026-08-14 and CI Server R1.03 refused the item import: +> +> ``` +> EQP-E-DUP_ITEM, I/O address of item already defined +> DSSP-E-INSREC, Failed to insert a record in the dataset ITEM_DF +> ``` +> +> The point definitions are not the constraint; the *item* is. So a second unit +> for the same measurement needs a second **PLC** register publishing it, not a +> second view of one register — a register-map change, not a SCADA change. +> Level and speed are therefore published in % alone. + +**Setpoints are written in %.** A setpoint entered as 70.0% arrives at the PLC as +4200 mm. One decimal of % is a 6 mm step, so not every mm value is reachable — +66.7% writes 4002 mm, not 4000. That is the intended trade for a single scale +shared by the level reading and its setpoints. + +### Ranges + +Analog points use `Linear` with a **full-scale** mapping: raw −32768..32767 to +that range × the gain. That is exact and invents no plant range — which is why +the offset must stay zero on both ends. It does make some `PHYS` values large +(`STN_SPEED` spans ±6553.6%); that is the arithmetic of a full-scale map, not +a plant range. Display ranges, trend limits and alarm limits are deliberately +left at defaults for you to set per item — the generator does not guess at them. + +`HAS_SIGN` is 1 for signed registers and **0 for `STN_ALARM_WORD`**, which is +unsigned; see below. + +## The point list + +`gen_scada_points.py` reads `03-plc/register-map.csv` (the PLC-side list, itself +generated from the ST sources) and rewrites `scada-points.csv`. Re-run it after any +register change so the two sides cannot drift — that is the §3 invariant. Anything +changed by hand here is lost on the next run; if CI Server needs a field the script +does not emit, it goes in the generator. + +## Connection + +| | | +|---|---| +| Protocol | Modbus TCP, CI Server is the **client/master** | +| Host | `yau-sls-poc-lin001` — **`10.0.0.17`**, static | +| Port | `502` | +| Unit / slave id | `1` | + +> The PLC publishes 502 on `10.0.0.17` only, not `0.0.0.0`. CI Server reaches it +> over the LAN. This is the one place in the project where a literal IP is +> unavoidable — see `../../02-environment/`. + +## Poll groups + +| Group | FC | Range | Points | What | +|---|---|---|---|---| +| `PS_STATUS_BITS` | FC01 | 0–14 | 15 | Run commands, running, available, in-auto, alarms, trips | +| `PS_PUBLISHED` | FC03 | 0–20 | 19 | Every live measurement and station/pump state | +| `PS_SETPOINTS` | FC03/06 | 1024–1034 | 11 | Mode, command word/param, level setpoints, limits | +| `PS_SIM_CONTROL` | FC03/06 | 1044–1047 | 4 | Scenario, manual inflow, reset, time scale | + +Each group is contiguous, so each is one request. + +## Three things that will bite + +**1. Do not poll `%IW` / `%IX` (FC04 / FC02).** They are the *field* inputs. In the +simulation build nothing writes them — they read 0 forever, and an HMI built on +them shows a dead plant. Every live value is published in the `%QW` block instead. +The generator excludes them by default; `--include-field` emits them for a real +field deployment. + +**2. `%QW17` (holding register 17, alarm bitmask) is UNSIGNED.** Bit 15 does not +fit a signed INT, so a signed configuration turns the alarm word negative exactly +when the most severe alarm sets. The CSV marks it `Unsigned 16-bit`. + +**3. `%MW` is not holding register 0.** `%QW` occupies 0–1023 and `%MW` starts at +**1024**, so the level setpoint `%MW3` is holding register **1027**. The CSV +carries resolved addresses — use them, not the IEC names. + +## Driving the simulation from CI Server + +The four `PS_SIM_CONTROL` points exist so scenarios can be run from the HMI rather +than from a script: + +| Tag | HR | Values | +|---|---|---| +| `PS_SIM_SCENARIO_*` | 1045 | 0 manual · 1 diurnal dry weather · 2 wet weather · 3 demo reference | +| `PS_SIM_MANUAL_INFLOW` | 1044 | m³/h in CI Server (the register is L/s × 10), scenario 0 only | +| `PS_SIM_WRITE_1_TO_RESET_SCENARIO` | 1046 | write 1 to reset; self-clearing | +| `PS_SIM_TIME_SCALE_1_120` | 1047 | 1 while testing, 30–60 when presenting | + +Reset restores the scenario's initial level and volume. It does **not** clear run +hours or trip states — those live in the control logic, and need command word +`%MW1` = 1 (reset trips) or 5 (reset hours). See `03-plc/README.md`. + +These four registers only do anything in the **simulation build**. In a field +build they exist but nothing reads them. + +## Scaling + +`raw_to_eng` says how to convert, and `eng_gain` is the same thing as a number +for the generators to use. Points with no unit override keep the PLC's own +scaling — `value / 10` where it publishes ×10, `value` otherwise. The points +whose unit the SCADA side changes are listed under **Engineering units** above. +`format_mask` is the display mask that becomes `VALUE_FORMAT` on the item, and +it must stay in step with `../hmi/point_format.py`. + +## Status + +**CI Server reads live PLC values** (2026-08-14) — sections, points and items +imported, values verified against a direct Modbus read. That is Phase 4's +definition of done. + +**Units re-cut, 2026-08-14** — level in % of the spill weir, drive speed in % of +50 Hz, flows in m³/h, level and speed setpoints entered in %. **No new items and +no renumbering**: the same 49 items, same NSIDs, same `ID_NUMBER`s, so a +re-import of `wrps_modbus_point_df.qli` and `wrps_item_df.qli` updates them in +place and the HMI's harvested `item-ids.csv` stays valid. Verified against a live +read: raw 4217 → 70.3%, reconciling with `VOL_TO_SPILL` 214 m³ = (6.000 − 4.217) +× 120; raw 404 → 80.8% of 50 Hz. + +One thing left for you to set, deliberately not guessed at: + +- **Alarm and trend limits.** Every item imports with alarming off and limits at + 0. Which points alarm, at what thresholds and priority, is engineering judgement + about the plant, not something to derive from a register map. diff --git a/04-scada/modbus_points/gen_ciserver_qli.py b/04-scada/modbus_points/gen_ciserver_qli.py new file mode 100644 index 0000000..487c1c5 --- /dev/null +++ b/04-scada/modbus_points/gen_ciserver_qli.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python3 +"""Emit CI Server .qli imports for the WRPS points. + + python gen_scada_points.py # first: refresh scada-points.csv + python gen_ciserver_qli.py + +Writes, alongside this script: + + wrps_section_df.qli @SECTION_DF - the AID.WRPS.* sections + wrps_modbus_point_df.qli @MODBUS_POINT_DF - the Modbus point definitions + wrps_item_df.qli @ITEM_DF - the items in those sections + +Import them in that order: CI Server derives its hierarchy from the dots +in a name, and a section must exist before an item can be created in it. + +The chain is ST sources -> 03-plc/register-map.csv -> scada-points.csv -> +these files, so a register change propagates by re-running the three +scripts rather than by hand-editing anything. + +Field layouts and every constant here were copied from the user's own +exports in 99-reference/ciserver-qli-exports/ (section_df.qli, +modbus_point_df.qli, item_df.qli), not invented. + +NSIDs +----- +The NSID space is shared by sections and items. In the reference +exports items occupy 3-166 and sections 1-170, so 171 is the first free +id: sections take 171-176 and items 177 onward (--nsid-base). + +AID (169) and AID.WRPS (170) already exist, so this script emits only +their six children and hangs them off 170 (--parent-nsid). Pass +--include-root to emit AID and AID.WRPS as well, for a system that does +not have them yet. + +THE ONE THING TO VERIFY ON A SMALL TEST IMPORT +---------------------------------------------- +**Address base** (--address-base, default 1). The reference export maps +point AI_01 to IO_ADDRESS "RI:01". Whether CI Server's 01 means +protocol address 0 or 1 cannot be told from the file. Default here is +1-based, i.e. IO_ADDRESS = modbus address + 1, so holding register 0 +(%QW0, wet well level) becomes "RO:01". If the level shows up where the +inflow should be, it is 0-based: re-run with --address-base 0. +""" + +import argparse +import csv +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +POINTS = HERE / "scada-points.csv" + +STATION = "WRPS_PLC" +ROOT = "AID.WRPS" + +# IO_ADDRESS prefixes, from the reference export: +# RI = register input (FC04) RO = register output (FC03/06) +# DI = digital input (FC02) DO = digital output (FC01) +PREFIX = {"FC01": "DO", "FC02": "DI", "FC03": "RO", "FC03/FC06": "RO", "FC04": "RI"} + +# Section per poll group; PS_PUBLISHED and PS_STATUS_BITS are split by +# equipment so each pump gets its own folder. +GROUP_SECTION = {"PS_SETPOINTS": "SP", "PS_SIM_CONTROL": "SIM"} + +SECTION_ORDER = ["STN", "PU301", "PU302", "PU303", "SP", "SIM"] + +SECTION_DESC = { + "STN": "Waterloo Road PS - station wide measurements and status", + "PU301": "Pump PU-301", + "PU302": "Pump PU-302", + "PU303": "Pump PU-303", + "SP": "Operator setpoints and commands", + "SIM": "Simulation control - simulation build only", +} + +SECTION_FIELDS = [ + "NAME", "SECTION_PATH", "SECTION_NAME", "BLOCKED", "PARENT_BLOCKED", + "ALARM_INHIBIT", "PARENT_ALARM_INHIBIT", "OPC_VISIBLE", "PARENT_OPC_VISIBLE", "NSID", "PARENT_NSID", + "DESCRIPTION", "CREATED_BY", +] + +# Short, stable leaf names. Keyed on IEC address so they never drift +# with a description edit. +LEAF = { + "%QX0.0": ("PU301", "RUN_CMD"), "%QX0.1": ("PU302", "RUN_CMD"), + "%QX0.2": ("PU303", "RUN_CMD"), "%QX0.3": ("PU301", "RUNNING"), + "%QX0.4": ("PU302", "RUNNING"), "%QX0.5": ("PU303", "RUNNING"), + "%QX0.6": ("PU301", "AVAILABLE"), "%QX0.7": ("PU302", "AVAILABLE"), + "%QX1.0": ("PU303", "AVAILABLE"), "%QX1.1": ("STN", "IN_AUTO"), + "%QX1.2": ("STN", "HIGH_LEVEL"), "%QX1.3": ("STN", "SPILL_ACTIVE"), + "%QX1.4": ("PU301", "TRIPPED"), "%QX1.5": ("PU302", "TRIPPED"), + "%QX1.6": ("PU303", "TRIPPED"), + "%QW0": ("STN", "LEVEL"), "%QW1": ("STN", "INFLOW"), + "%QW2": ("STN", "DISCHARGE"), "%QW3": ("STN", "PUMPS_RUNNING"), + "%QW4": ("STN", "SPEED"), "%QW5": ("STN", "TIME_TO_SPILL"), + "%QW6": ("STN", "TIME_TO_LSHH"), "%QW7": ("STN", "NET_ACCUM"), + "%QW8": ("PU301", "RUN_HOURS"), "%QW9": ("PU302", "RUN_HOURS"), + "%QW10": ("PU303", "RUN_HOURS"), "%QW11": ("STN", "VOL_TO_SPILL"), + "%QW12": ("STN", "STATE"), "%QW13": ("PU301", "STATE"), + "%QW14": ("PU302", "STATE"), "%QW15": ("PU303", "STATE"), + "%QW16": ("STN", "DUTY_PUMP"), "%QW17": ("STN", "ALARM_WORD"), + "%QW20": ("STN", "CMD_ACK"), + "%MW0": ("SP", "MODE"), "%MW1": ("SP", "CMD_WORD"), + "%MW2": ("SP", "CMD_PARAM"), "%MW3": ("SP", "LEVEL_SP"), + "%MW4": ("SP", "START_DUTY"), "%MW5": ("SP", "START_P2"), + "%MW6": ("SP", "START_P3"), "%MW7": ("SP", "STOP_ALL"), + "%MW8": ("SP", "HIGH_ALARM"), "%MW9": ("SP", "MIN_SPEED"), + "%MW10": ("SP", "SERVICE_HRS"), + "%MW20": ("SIM", "INFLOW"), "%MW21": ("SIM", "SCENARIO"), + "%MW22": ("SIM", "RESET"), "%MW23": ("SIM", "TIME_SCALE"), +} + + +def q(v): + """Quote a .qli value the way the reference exports do.""" + return '"%s"' % ("" if v is None else str(v)) + + +def wrap(values): + """Split into the reference exports' line shape: 5, then 6 per line. + + Both reference files wrap this way, in the @FIELDS header and in + every record. Reproduced exactly rather than assuming the importer + tolerates a different grouping. + """ + out = [list(values[:5])] + rest = list(values[5:]) + for i in range(0, len(rest), 6): + out.append(rest[i:i + 6]) + return out + + +def record(rows): + """Format one record, backslash continuations between lines.""" + lines = [",".join(str(v) for v in chunk) for chunk in wrap(rows)] + return (",\\\n".join(lines)) + "\n" + + +def header(version, fields, tag): + lines = ["", "@LANGUAGE", "ENGLISH", "", "", "@VERSION", version, "", "", "!" + "=" * 130, "", "@FIELDS"] + lines += [",".join(chunk) for chunk in wrap(fields)] + lines += ["", tag] + return "\n".join(lines) + "\n" + + +POINT_FIELDS = [ + "NAME", "STATION", "POINT", "DESCRIPTION", "IO_ADDRESS", + "EXTERNAL_RELATION", "SCAN_TYPE", "CONV_TYPE", "DELTA_LIMIT", "MAX_INSENS", "OFFSET", + "PHYS_LOW", "PHYS_HIGH", "STEP", "INVERS", "HAS_SIGN", "OVERFL_DET", + "SWAP_BYTES", "SWAP_WORDS", "ELEC_LOW", "ELEC_HIGH", "TMO_NONE", "TMO_BOTH", + "AVE_UPD_INTERVAL", "NO_ZERO", "BURST_LIMIT", "BITS", "CHARS", "DIGITS", + "FLOAT_TYPE", "TIME_ZONE", "TIME_REPRES", "WLS_VAL_TYPE", +] + +ITEM_FIELDS = [ + "NAME", "NSID", "PARENT_NSID", "SECTION_PATH", "DESCRIPTION", + "DEADBAND", "LOW_LIMIT", "HIGH_LIMIT", "LOW_LOW_LIMIT", "HIGH_HIGH_LIMIT", "T_VALUE", + "P_VALUE", "I_VALUE", "TREND_UP_LIMIT", "TREND_LOW_LIMIT", "SCALE_HIGH_LIMIT", "SCALE_LOW_LIMIT", + "DUMMY2", "COMMENT_1", "COMMENT_2", "VALUE_FORMAT", "POSITIONED", "LONGITUDE", + "LATITUDE", "ALARMING", "DIAG", "NAME_IN_ITM_TAB", "STORAGE", "AUDIT_INFO", + "FO_ITEM", "BLOCKED", "PARENT_BLOCKED", "ALARM_INHIBIT", "PARENT_ALARM_INHIBIT", "OPC_VISIBLE", + "PARENT_OPC_VISIBLE", "OPC_READ", "OPC_WRITE", "OPC_ALARM_DETECTION", "HAS_SUB", "STRING_LENGTH", + "DELAY", "REPEAT", "ID_GROUP", "ID_NUMBER", "ITEM_REP", "ITEM_TYPE", + "ITEM_SPECIAL", "ACKN_TYPE", "ALARM_GROUP", "FRONT_END_NODE", "DISTR_TYPE", "INSTALL", + "UNIT", "TAG", "LIMIT_CLAMP", "OUT_OF_RANGE", "COL_GROUP", "STATION", + "POINT", "FO_GROUP", "ITEM_STAT_1", "ITEM_STAT_2", "ITEM_STAT_3", "ITEM_STAT_4", + "ITEM_STAT_5", "ITEM_STAT_6", "ALARM_STATE_1", "ALARM_STATE_2", "ALARM_STATE_3", "ALARM_STATE_4", + "ALARM_STATE_5", "ALARM_STATE_6", "PRIORITY_1", "PRIORITY_2", "PRIORITY_3", "PRIORITY_4", + "PRIORITY_5", "PRIORITY_6", "ALARM_TEXT_1", "ALARM_TEXT_2", "ALARM_TEXT_3", "ALARM_TEXT_4", + "ALARM_TEXT_5", "ALARM_TEXT_6", "ALARM_COLOR_1", "ALARM_COLOR_2", "ALARM_COLOR_3", "ALARM_COLOR_4", + "ALARM_COLOR_5", "ALARM_COLOR_6", "MNEMONIC_1", "MNEMONIC_2", "MNEMONIC_3", "MNEMONIC_4", + "MNEMONIC_5", "MNEMONIC_6", "AOI_1", "AOI_2", "AOI_3", "AOI_4", + "AOI_5", "AOI_6", "AOI_7", "AOI_8", "AOI_9", "AOI_10", + "AOI_11", "AOI_12", "AOI_13", "AOI_14", "AOI_15", "AOI_16", + "ENG_UNIT", "PROCESS_LIST", "POINT_NAME", "OPC_AE_STATION_NAME", "OPC_EVENT_SOURCE", "OPC_EVENT_SOURCE_NAME", + "CREATED_BY", "SHELVE_ENABLED_1", "SHELVE_ENABLED_2", "SHELVE_ENABLED_3", "SHELVE_ENABLED_4", "SHELVE_ENABLED_5", + "SHELVE_ENABLED_6", "AGG_INTERVAL", +] + + +def build_section(name, parent_path, leaf, nsid, parent_nsid, description): + """One @SECTION_DF record, shaped like the reference export. + + PARENT_OPC_VISIBLE is 1 for a root section and 0 for a child, which + is what the reference shows for AID (root) versus AID.WRPS (child). + """ + return record([ + q(name), q(parent_path), q(leaf), 0, 0, + 0, 0, 0, 1 if parent_path == "" else 0, nsid, parent_nsid, + q(description), q("unknown"), + ]) + + +def trimmed_span(gain, signed): + """The ELEC range to map from, trimmed so PHYS lands on exact decimals. + + CI Server stores the conversion as two endpoints, not as a gain, so + it recovers the gain by dividing. With a gain of 1/60 the full-scale + endpoint 32767/60 = 546.11666... has to be written rounded, and the + recovered gain is then slightly off: setpoint 4200 came back as + 70.00003333333336 rather than 70. + + So when the gain is 1/n, trim the span to the nearest multiple of n + inside it - 32760 instead of 32767, giving PHYS exactly +/-546 and a + gain CI Server recovers exactly. Nothing is lost: the trimmed 7 + counts are 0.4 mm of a level register that tops out at 7000. + + Gains that already terminate (0.1, 0.2, 0.36) keep the full span. + """ + lo, hi = (-32768, 32767) if signed else (0, 65535) + inv = 1.0 / gain + n = int(round(inv)) + if n > 1 and abs(inv - n) < 1e-9: + lo, hi = -((-lo) // n) * n, (hi // n) * n + return lo, hi + + +def build_point(row, io_addr): + digital = row["data_type"] == "Boolean" + writable = row["access"] == "Read/Write" + gain = float(row["eng_gain"]) + signed = row["data_type"] == "Signed 16-bit" + + if digital: + conv, bits = "Digital", 16 + elec_lo, elec_hi, phys_lo, phys_hi = 0, 100, 0, 100 + else: + conv, bits = "Linear", 16 + # Full-scale linear mapping. Exact, and invents no plant range; + # display ranges and alarm limits are set per item in CI Server. + # The engineering unit is carried entirely by the gain (see UNITS + # in gen_scada_points.py), so PHYS is ELEC scaled by it - which is + # why the offset must stay zero on both ends. + elec_lo, elec_hi = trimmed_span(gain, signed) + phys_lo, phys_hi = elec_lo * gain, elec_hi * gain + + external = "Input + Output" if writable else "Input" + scan = "MOD_SCAN" + + name = "%s:%s" % (STATION, row["point"]) + return record([ + q(name), q(STATION), q(row["point"]), q(row["description"][:60]), q(io_addr), + q(external), q(scan), q(conv), 0, 0, 0, + _num(phys_lo), _num(phys_hi), 1, 0, 1 if signed else 0, 0, + 0, 0, _num(elec_lo), _num(elec_hi), 0, 0, + 0, 0, 0, bits, 16, 4, + q("Intel"), q("Date+time GMT"), q("7 bytes IEC"), q("Float value"), + ]) + + +def _num(v): + """Whole numbers stay whole; fractions keep enough digits to be exact. + + Four decimals, not one: a gain of 0.001 puts PHYS_HIGH at 32.767, and + rounding that to 32.8 would bend the conversion by 0.1%. + """ + return int(v) if float(v) == int(float(v)) else round(float(v), 4) + + +def build_item(row, nsid, parent_nsid, id_number): + digital = row["data_type"] == "Boolean" + section = row["section"] + path = "%s.%s" % (ROOT, section) + name = "%s.%s" % (path, row["leaf"]) + + item_rep = "Boolean" if digital else "Real" + value_format = "" if digital else row["format_mask"] + eng_unit = row["eng_units"] + + f = {k: 0 for k in ITEM_FIELDS} + f.update({k: q("") for k in ITEM_FIELDS if k.startswith( + ("COMMENT", "ITEM_STAT", "ALARM_TEXT", "ALARM_COLOR", "MNEMONIC", "AOI"))}) + + f["NAME"] = q(name) + f["NSID"] = nsid + f["PARENT_NSID"] = parent_nsid + f["SECTION_PATH"] = q(path) + f["DESCRIPTION"] = q(row["description"][:60]) + f["VALUE_FORMAT"] = q(value_format) + f["NAME_IN_ITM_TAB"] = 1 + f["STRING_LENGTH"] = 1 + f["ID_GROUP"] = 1 + f["ID_NUMBER"] = id_number + f["ITEM_REP"] = q(item_rep) + f["ITEM_TYPE"] = q("") + f["ITEM_SPECIAL"] = q("") + f["ACKN_TYPE"] = q("") + f["ALARM_GROUP"] = q("") + f["FRONT_END_NODE"] = q("UNLICENCED") + f["DISTR_TYPE"] = q("Local Host") + f["INSTALL"] = q("WRPS") + f["UNIT"] = q(section) + f["TAG"] = q(row["leaf"]) + f["LIMIT_CLAMP"] = q("") + f["COL_GROUP"] = q("") + f["STATION"] = q(STATION) + f["POINT"] = q(row["point"]) + f["FO_GROUP"] = q("") + for i in range(1, 7): + f["ALARM_STATE_%d" % i] = q("Normal") + if digital: + f["ITEM_STAT_1"] = q("BOOLEAN 0") + f["ITEM_STAT_2"] = q("BOOLEAN 1") + f["ENG_UNIT"] = q(eng_unit) + f["PROCESS_LIST"] = q("") + f["POINT_NAME"] = q("%s:%s" % (STATION, row["point"])) + f["OPC_AE_STATION_NAME"] = q("") + f["OPC_EVENT_SOURCE"] = q("") + f["OPC_EVENT_SOURCE_NAME"] = q(":") + f["CREATED_BY"] = q("unknown") + for i in range(1, 7): + f["SHELVE_ENABLED_%d" % i] = 1 + f["AGG_INTERVAL"] = q("") + + return record([f[k] for k in ITEM_FIELDS]) + + +def main(): + ap = argparse.ArgumentParser(description="Emit CI Server .qli imports.") + ap.add_argument("--address-base", type=int, choices=(0, 1), default=1, + help="IO_ADDRESS numbering: 1 = protocol address + 1 (default)") + ap.add_argument("--nsid-base", type=int, default=171, + help="first free NSID; sections take six from here, items follow") + ap.add_argument("--parent-nsid", type=int, default=170, + help="NSID of AID.WRPS, which the six sections hang off") + ap.add_argument("--include-root", action="store_true", + help="also emit AID and AID.WRPS sections (they already exist here)") + ap.add_argument("--section-nsid", action="append", default=[], + metavar="SECTION=NSID", help="pin a section's NSID, e.g. STN=41") + ap.add_argument("--version", default="1.03.00", help="@VERSION written into both files") + args = ap.parse_args() + + if not POINTS.is_file(): + sys.exit("missing scada-points.csv - run gen_scada_points.py first") + + pinned = {} + for spec in args.section_nsid: + k, _, v = spec.partition("=") + pinned[k.strip().upper()] = int(v) + + rows = list(csv.DictReader(open(POINTS, newline="", encoding="utf-8"))) + prepared = [] + for r in rows: + iec = r["iec_address"] + if iec not in LEAF: + sys.exit("no leaf name defined for %s - add it to LEAF" % iec) + section, leaf = LEAF[iec] + r["section"] = section + r["leaf"] = leaf + r["point"] = ("%s_%s" % (section, leaf))[:24] + prepared.append(r) + + # NSIDs: one per section, then one per item, from --nsid-base. + next_id = args.nsid_base + section_nsid = {} + for s in SECTION_ORDER: + if s in pinned: + section_nsid[s] = pinned[s] + else: + section_nsid[s] = next_id + next_id += 1 + + section_out = [header(args.version, SECTION_FIELDS, "@SECTION_DF")] + if args.include_root: + root, _, wrps_leaf = ROOT.partition(".") + section_out.append(build_section(root, "", root, args.parent_nsid - 1, 0, + "Waterloo Road Pump Station demo")) + section_out.append(build_section(ROOT, root, wrps_leaf, args.parent_nsid, + args.parent_nsid - 1, + "Waterloo Road Pump Station")) + for s in SECTION_ORDER: + section_out.append(build_section("%s.%s" % (ROOT, s), ROOT, s, + section_nsid[s], args.parent_nsid, + SECTION_DESC[s])) + + point_out = [header(args.version, POINT_FIELDS, "@MODBUS_POINT_DF")] + item_out = [header(args.version, ITEM_FIELDS, "@ITEM_DF")] + + # NSID and ID_NUMBER follow register order, which is also the order the + # items were first imported in. Keep it stable: a re-import must update + # the existing items in place, not renumber them - the HMI's item ids in + # 05-scada/hmi/item-ids.csv are derived from that creation order. + for n, r in enumerate(prepared): + addr = int(r["modbus_address"]) + args.address_base + # Zero-padded to at least two digits, as the reference export does + # ("RI:01", not "RI:1"). Wider addresses keep their own width. + io_addr = "%s:%02d" % (PREFIX[r["function_code"]], addr) + point_out.append(build_point(r, io_addr)) + item_out.append(build_item(r, next_id + n, section_nsid[r["section"]], n + 1)) + + # The header already ends in a newline, so the first record follows it + # directly; records are then separated by one blank line, exactly as + # the reference exports are. + def assemble(parts): + return parts[0] + "\n".join(parts[1:]) + + (HERE / "wrps_section_df.qli").write_text(assemble(section_out), encoding="utf-8") + (HERE / "wrps_modbus_point_df.qli").write_text(assemble(point_out), encoding="utf-8") + (HERE / "wrps_item_df.qli").write_text(assemble(item_out), encoding="utf-8") + + n_sections = len(SECTION_ORDER) + (2 if args.include_root else 0) + print("OK wrps_section_df.qli (%d sections under %s)" % (n_sections, ROOT)) + print("OK wrps_modbus_point_df.qli (%d points, station %s)" % (len(prepared), STATION)) + print("OK wrps_item_df.qli (%d items under %s.*)" % (len(prepared), ROOT)) + print(" address base : %d (holding register 0 -> RO:%02d)" % (args.address_base, args.address_base)) + print(" section NSIDs: " + ", ".join("%s=%d" % (s, section_nsid[s]) for s in SECTION_ORDER)) + print(" item NSIDs : %d-%d" % (next_id, next_id + len(prepared) - 1)) + by_sec = {} + for r in prepared: + by_sec.setdefault(r["section"], 0) + by_sec[r["section"]] += 1 + print(" per section : " + ", ".join("%s=%d" % (s, by_sec.get(s, 0)) for s in SECTION_ORDER)) + + +if __name__ == "__main__": + main() diff --git a/04-scada/modbus_points/gen_scada_points.py b/04-scada/modbus_points/gen_scada_points.py new file mode 100644 index 0000000..532ca9c --- /dev/null +++ b/04-scada/modbus_points/gen_scada_points.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python3 +"""Derive the SCADA-side point list from the PLC-side register map. + + python gen_scada_points.py + +Reads ../../03-plc/register-map.csv (PLC side, generated by build.py) +Writes ./scada-points.csv (SCADA side, for CI Server config) + +CLAUDE.md section 3: the two sides are two views of the same points, and +the PLC side leads. This script makes the SCADA view reproducible +instead of hand-transcribed, so the two cannot drift apart silently. + +It is a *starting point* for CI Server configuration, not a CI Server +import file. CI Server's .qli item exports carry no Modbus addressing +(that lives in the front-end I/O configuration), so the mapping is +applied by hand in CI Server from this list. + +The four judgements this script encodes +--------------------------------------- +1. **Poll groups.** Four, not one per function code. The holding + registers split by purpose and address range: read-only published + data at 0-20, operator setpoints at 1024-1034, simulation control at + 1044-1047. Each group is then contiguous and polls as one request; + a single holding-register group would span 1048 mostly-empty + addresses. + +2. **%QW17 is UNSIGNED.** Bit 15 does not fit a signed INT. Configure + it as a 16-bit unsigned register or the alarm word goes negative + exactly when the most severe alarm is set. + +3. **Engineering units are chosen here, not in the PLC.** The PLC + publishes mm, L/s x10 and Hz x10 and keeps doing so; a unit on the + SCADA side is a gain on the raw register (see UNITS below). One + unit per register, though - CI Server rejects a second item on an + I/O address it already has. + +4. **%IW / %IX are excluded by default.** They are the *field* inputs. + In the simulation build nothing writes them, so they read 0 forever + and would show a dead plant on the HMI. Every live measurement is + published in the %QW block. Pass --include-field to emit them + anyway, for a real field deployment. +""" + +import argparse +import csv +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +SRC_MAP = ROOT / "03-plc" / "register-map.csv" +OUT = Path(__file__).resolve().parent / "scada-points.csv" + +# Modbus object -> (function code, poll group name) +# +# Holding registers are split into three groups rather than one. They +# share a function code but not a purpose or an address range: %QW is +# read-only published data at 0-1023, %MW is writable at 1024+. One +# group spanning both would be a single 1048-register request over a +# mostly empty address space. +GROUPS = { + "Coil": ("FC01", "PS_STATUS_BITS"), + "Discrete input": ("FC02", "PS_FIELD_BITS"), + "Input register": ("FC04", "PS_FIELD_ANALOG"), +} + +HR_PUBLISHED = ("FC03", "PS_PUBLISHED") # %QW, read-only +HR_SETPOINTS = ("FC03/FC06", "PS_SETPOINTS") # %MW0-10, operator writable +HR_SIM = ("FC03/FC06", "PS_SIM_CONTROL") # %MW20-23, simulation only + +FIELD_ONLY = {"Input register", "Discrete input"} +UNSIGNED = {"%QW17"} + +# --------------------------------------------------------------------------- +# Engineering units, SCADA side +# --------------------------------------------------------------------------- +# +# The PLC publishes mm, L/s x10 and Hz x10 - that is the contract in +# register-map.csv and it does not change. What the operator reads is a +# presentation choice, and CI Server already applies a linear conversion +# per Modbus point, so a unit here is nothing but a gain on the raw +# register: +# +# engineering value = raw register * gain (offset is always 0) +# +# Zero offset matters: it keeps the full-scale ELEC -> PHYS mapping in +# gen_ciserver_qli.py exact, and it means a second unit on the same +# register is just a second point with a different gain. +# +# ONE ITEM PER I/O ADDRESS. A register carries exactly one engineering +# unit, because CI Server will not accept two items on the same Modbus +# address - an import that tries it fails with +# +# EQP-E-DUP_ITEM, I/O address of item already defined +# DSSP-E-INSREC, Failed to insert a record in the dataset ITEM_DF +# +# (confirmed on R1.03, 2026-08-14, trying to publish level in m *and* %). +# So showing one measurement in two units needs two *PLC* registers, not +# two views of one. Level and speed are published in % alone. +# +# Gains chosen 2026-08-14: +# level % 100% = the 6.000 m spill weir, so a full bar means spilling +# and the reading reconciles with VOL_TO_SPILL. 1/60 per mm. +# flow m3/h = L/s x 3.6, and the register is L/s x10: 0.36. +# speed % 100% = 50 Hz, the drive's maximum. Register Hz x10: 0.2. +# +# iec address -> (units, gain, format mask) +UNITS = { + "%QW0": ("%", 1 / 60.0, "999.9"), # wet well level, was mm + "%QW1": ("m3/h", 0.36, "9999.9"), # inflow, was L/s + "%QW2": ("m3/h", 0.36, "9999.9"), # total discharge, was L/s + "%QW4": ("%", 0.2, "999.9"), # common drive speed, was Hz + "%QW7": ("m3/h", 0.36, "9999.9"), # net accumulation, signed + "%MW3": ("%", 1 / 60.0, "999.9"), # level setpoint, was mm + "%MW4": ("%", 1 / 60.0, "999.9"), # start duty + "%MW5": ("%", 1 / 60.0, "999.9"), # start pump 2 + "%MW6": ("%", 1 / 60.0, "999.9"), # start pump 3 + "%MW7": ("%", 1 / 60.0, "999.9"), # stop all + "%MW8": ("%", 1 / 60.0, "999.9"), # high level alarm + "%MW9": ("%", 0.2, "999.9"), # minimum drive speed, was Hz + "%MW20": ("m3/h", 0.36, "9999.9"), # SIM manual inflow, was L/s +} + +# Points that keep their PLC unit but not the default mask. Enums, modes +# and small counts are one or two digits wide; a five-digit mask on them +# is not wrong, only wide, and it would disagree with the HMI's masks in +# 04-scada/hmi/point_format.py. iec address -> mask +MASK_ONLY = { + "%QW3": "9", # pumps running, 0-3 + "%QW11": "9999", # volume remaining to spill, m3 + "%QW12": "9", # station state enum + "%QW13": "9", "%QW14": "9", "%QW15": "9", # pump state enums + "%QW16": "9", # duty pump, 0-3 + "%QW20": "99", # command acknowledge + "%MW0": "9", # station mode + "%MW1": "99", # command word + "%MW2": "9", # command parameter, pump number + "%MW21": "9", # SIM scenario 0-3 + "%MW22": "9", # SIM reset, write 1 + "%MW23": "999", # SIM time scale 1-120 +} + + +def eng_view(iec, scaling): + """(units override, gain, mask override) for a register. + + Falls back to the PLC's own unit and the raw scaling when the point + is not in UNITS - most points are already in the unit the operator + wants, and inventing a conversion for them would only add risk. + """ + if iec in UNITS: + return UNITS[iec] + gain = 0.1 if scaling == "x10" else 1.0 + default = "9999.9" if scaling == "x10" else "99999" + return None, gain, MASK_ONLY.get(iec, default) + + +def gain_expr(gain): + """The gain as the human-readable expression the CSV carries.""" + if gain == 1.0: + return "value" + if gain == 0.1: + return "value / 10" + if abs(gain - 1 / 60.0) < 1e-12: + return "value / 60" + return "value * %g" % gain + + +def tag_for(row, description): + """Tag name per CLAUDE.md section 8: PS__. + + Built from the cleaned description, so the "SIM ONLY:" marker does + not end up inside the tag name. + """ + equip = row["tag"].replace("-", "") + meas = description.split("(")[0].split(",")[0].strip() + meas = "".join(ch if ch.isalnum() else "_" for ch in meas) + meas = "_".join(p for p in meas.split("_") if p)[:28].upper() + return f"PS_{equip}_{meas}".upper() + + +def main(): + ap = argparse.ArgumentParser(description="Derive the SCADA point list.") + ap.add_argument( + "--include-field", + action="store_true", + help="also emit %%IW/%%IX field inputs (they read 0 in the simulation build)", + ) + args = ap.parse_args() + + if not SRC_MAP.is_file(): + sys.exit(f"missing {SRC_MAP} - run 04-plc/wrps-plc/build.py first") + + rows = list(csv.DictReader(open(SRC_MAP, newline="", encoding="utf-8"))) + out_rows, skipped = [], 0 + + for r in rows: + obj = r["modbus_object"] + if obj in FIELD_ONLY and not args.include_field: + skipped += 1 + continue + + iec = r["iec_address"] + sim_only = r["description"].startswith("SIM ONLY") + writable = r["access"] == "RW" + description = r["description"].replace("SIM ONLY: ", "") + + if obj == "Holding register": + if sim_only: + fc, group = HR_SIM + elif writable: + fc, group = HR_SETPOINTS + else: + fc, group = HR_PUBLISHED + else: + fc, group = GROUPS[obj] + + if obj in ("Coil", "Discrete input"): + data_type = "Boolean" + elif iec in UNSIGNED: + data_type = "Unsigned 16-bit" + else: + data_type = "Signed 16-bit" + + units_override, gain, mask = eng_view(iec, r["scaling"]) + + out_rows.append( + { + "scada_tag": tag_for(r, description), + "description": description, + "poll_group": group, + "function_code": fc, + "modbus_address": r["modbus_address"], + "data_type": data_type, + "access": "Read/Write" if writable else "Read", + "eng_units": units_override if units_override is not None else r["units"], + "raw_to_eng": gain_expr(gain), + "eng_gain": repr(gain), + "format_mask": mask, + "iec_address": iec, + "plc_tag": r["tag"], + "notes": "SIMULATION CONTROL - simulation build only" if sim_only else "", + } + ) + + with open(OUT, "w", newline="", encoding="utf-8") as fh: + w = csv.DictWriter(fh, fieldnames=list(out_rows[0].keys())) + w.writeheader() + w.writerows(out_rows) + + groups = {} + for r in out_rows: + groups.setdefault(r["poll_group"], []).append(int(r["modbus_address"])) + + print(f"OK {OUT} ({len(out_rows)} points)") + for g, addrs in groups.items(): + print(f" {g:<18} {len(addrs):>3} points, addresses {min(addrs)}-{max(addrs)}") + if skipped: + print( + f" skipped {skipped} %IW/%IX field points - they read 0 in the\n" + f" simulation build; pass --include-field for a real field deployment" + ) + + +if __name__ == "__main__": + main() diff --git a/04-scada/modbus_points/historian/README.md b/04-scada/modbus_points/historian/README.md new file mode 100644 index 0000000..dd8c0f0 --- /dev/null +++ b/04-scada/modbus_points/historian/README.md @@ -0,0 +1,62 @@ +# historian — collection groups and item bindings + +**Hand-made. There is no generator for this.** Everything else under +`modbus_points/` is generated from `03-plc/register-map.csv`; these two files are +not, and that is the reason they have drifted from the server. + +| File | Class | Holds | +|---|---|---| +| `his_group.qli` | `@HIS_GROUP_DF` | 3 collection groups | +| `item_his.qli` | `@ITEM_HIS_DF` | 49 item → group bindings | + +## The three groups + +| Group | Items | Collection | +|---|---|---| +| `WRPS_ONE_SEC` | 5 | 1 s scan — the fast analogues: level, inflow, discharge | +| `WRPS_ONE_MIN` | 4 | 1 min scan — slower trends | +| `WRPS_EVENT` | 40 | on change — states, commands, alarms, trips | + +Written 2026-08-21. + +## Why this matters more than it looks + +The demo this project exists to support is built on **historical grounding**. From +`01-design/00-origin/`: + +> "Are you sure? What did inflow actually do last time we had rain like this?" + +The answer to that question comes from the historian. Layer 1 of the demo — risk +and headroom — works from live values alone. The push-back that earns trust does +not. **Without these two files there is no historian, and the demo loses the layer +it was designed around.** + +## ⚠️ These files disagree with the server + +`ciserver-backup-2026-08/export_his_group.qli`, exported from CI Server around +2026-08-20, contains groups that `his_group.qli` does not define: + +| In the CI Server export | In `his_group.qli` | +|---|---| +| `WRPS_EVENT` | ✅ | +| `WRPS_ONE_SEC` | ✅ | +| `WRPS_THIRTY_SEC` | ❌ not defined here | +| `WRPS_ONE_MIN` | — not in the export | +| `FIVE_SECONDS` (binding `STN.LEVEL`, `STN.INFLOW`) | ❌ not a WRPS group at all | + +**Nobody currently knows which is right.** The export is dated a day before these +files, but contains groups they do not. Resolve it by exporting from `cicore1` and +comparing — see `../../QUICKLOAD.md`. + +Until then: **do not import these files onto a working server** without checking +what is already there. You may remove a group something depends on. + +## Worth doing: generate these too + +The points and items chain is generated end to end, which is what keeps the PLC +and SCADA from drifting apart. The historian stops short of that, is maintained by +hand, and has drifted — exactly the failure the generated chain exists to prevent. + +Extending `../gen_ciserver_qli.py` to emit `@HIS_GROUP_DF` and `@ITEM_HIS_DF` +would close the gap. The item list is already there; what it needs is a table +saying which item belongs to which group at what rate. Not done in this audit. diff --git a/04-scada/modbus_points/historian/his_group.qli b/04-scada/modbus_points/historian/his_group.qli new file mode 100644 index 0000000..4582631 --- /dev/null +++ b/04-scada/modbus_points/historian/his_group.qli @@ -0,0 +1,44 @@ + +@LANGUAGE +ENGLISH + + +@VERSION +1.03.00 + + +!================================================================================================================================== + +@FIELDS +NAME,DESCRIPTION,START_TIME,STOP_TIME,FIRST_ROLLOVER +LIFE_TIME,WARN_TIME,ROLLOVER_INT,FORCE_ROLLOVER,FILL_OLD,STORE_QUALITY +SAVE_TIME,SCAN_INTERVAL,CORRECT_DAYLIGHT,EXCLUDE_ARCHIVE,DATA_COMP,AGG_PERIOD_HOUR +AGG_PERIOD_SHIFT,AGG_PERIOD_DAY,AGG_PERIOD_WEEK,AGG_PERIOD_MONTH,AGG_PERIOD_YEAR,AGG_PERIOD_30MIN +AGG_CORRECT_DAY_DST,AGG_CORRECT_SHIFT_DST,NUMBER,NODE,NODE_NAME,NEXT_UNIT_SEQ +NEXT_DATA_SEQ,AVERAGING_INTERVAL,PRIORITY,SAMPLES_RECORD,TYPE,AVERAGING_METHOD +COL_STOR_TYPE,AGG_SHIFT_LENGTH,AGG_WEEK_START,AGG_SHIFT_START,AGG_DAY_START,AGG_COMP_NOT_ITEM + +@HIS_GROUP_DF +"WRPS_ONE_SEC","","","","21-08-2026 00:00:00",\ +"7 days","","1 hours",0,0,1,\ +0,1,0,0,0,1,\ +0,0,0,0,0,0,\ +0,0,50,1,"",1,\ +2,0,8,0,"Item","",\ +"Scan/Time","8 hours","Monday","00:00","00:00","" + +"WRPS_ONE_MIN","","","","21-08-2026 00:00:00",\ +"7 days","","6 hours",0,0,1,\ +0,60,0,0,0,1,\ +0,0,0,0,0,0,\ +0,0,51,1,"",1,\ +2,0,5,0,"Item","",\ +"Scan/Time","8 hours","Monday","00:00","00:00","" + +"WRPS_EVENT","","","","21-08-2026 00:00:00",\ +"7 days","","1 days",0,0,1,\ +0,0,0,0,0,1,\ +0,0,0,0,0,0,\ +0,0,52,1,"",1,\ +2,0,11,100,"Item","",\ +"Event/Item","8 hours","Monday","00:00","00:00","" diff --git a/04-scada/modbus_points/historian/item_his.qli b/04-scada/modbus_points/historian/item_his.qli new file mode 100644 index 0000000..7863479 --- /dev/null +++ b/04-scada/modbus_points/historian/item_his.qli @@ -0,0 +1,312 @@ + +@LANGUAGE +ENGLISH + + +@VERSION +1.03.00 + + +!================================================================================================================================== + +@FIELDS +NAME,ITEM_NAME,GROUP_NAME,ON_EACH_UPDATE,ON_FIRST_UPDATE +ON_OPTION_CHANGE,ON_PASS_PIT,ON_QUALITY_CHANGE,ON_STATUS_CHANGE,ON_VALUE_CHANGE,STORE_DEADBAND +STORE_HIGH_HIGH_LIMIT,STORE_HIGH_LIMIT,STORE_LOW_LIMIT,STORE_LOW_LOW_LIMIT,STORE_VALUE,STORE_AGG_MAX +STORE_AGG_MIN,STORE_AGG_AVG,STORE_AGG_INTG,STORE_AGG_STDV,STORE_AGG_CNT,STORE_AGG_DIFF_SUM +AGG_DIFF_SUM_RANGE,AGG_DIFF_SUM_PERC,AGG_INTG_PERIOD + +@ITEM_HIS_DF +"WRPS_ONE_SEC:AID.WRPS.STN.LEVEL","AID.WRPS.STN.LEVEL","WRPS_ONE_SEC",0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_ONE_SEC:AID.WRPS.STN.INFLOW","AID.WRPS.STN.INFLOW","WRPS_ONE_SEC",0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_ONE_SEC:AID.WRPS.STN.DISCHARGE","AID.WRPS.STN.DISCHARGE","WRPS_ONE_SEC",0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_ONE_SEC:AID.WRPS.STN.SPEED","AID.WRPS.STN.SPEED","WRPS_ONE_SEC",0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_ONE_SEC:AID.WRPS.STN.NET_ACCUM","AID.WRPS.STN.NET_ACCUM","WRPS_ONE_SEC",0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_ONE_MIN:AID.WRPS.STN.VOL_TO_SPILL","AID.WRPS.STN.VOL_TO_SPILL","WRPS_ONE_MIN",0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_ONE_MIN:AID.WRPS.STN.TIME_TO_SPILL","AID.WRPS.STN.TIME_TO_SPILL","WRPS_ONE_MIN",0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_ONE_MIN:AID.WRPS.STN.TIME_TO_LSHH","AID.WRPS.STN.TIME_TO_LSHH","WRPS_ONE_MIN",0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_ONE_MIN:AID.WRPS.STN.PUMPS_RUNNING","AID.WRPS.STN.PUMPS_RUNNING","WRPS_ONE_MIN",0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU301.RUN_CMD","AID.WRPS.PU301.RUN_CMD","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU302.RUN_CMD","AID.WRPS.PU302.RUN_CMD","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU303.RUN_CMD","AID.WRPS.PU303.RUN_CMD","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU301.RUNNING","AID.WRPS.PU301.RUNNING","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU302.RUNNING","AID.WRPS.PU302.RUNNING","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU303.RUNNING","AID.WRPS.PU303.RUNNING","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU301.AVAILABLE","AID.WRPS.PU301.AVAILABLE","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU302.AVAILABLE","AID.WRPS.PU302.AVAILABLE","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU303.AVAILABLE","AID.WRPS.PU303.AVAILABLE","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU301.TRIPPED","AID.WRPS.PU301.TRIPPED","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU302.TRIPPED","AID.WRPS.PU302.TRIPPED","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU303.TRIPPED","AID.WRPS.PU303.TRIPPED","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.STN.IN_AUTO","AID.WRPS.STN.IN_AUTO","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.STN.HIGH_LEVEL","AID.WRPS.STN.HIGH_LEVEL","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.STN.SPILL_ACTIVE","AID.WRPS.STN.SPILL_ACTIVE","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.STN.STATE","AID.WRPS.STN.STATE","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU301.STATE","AID.WRPS.PU301.STATE","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU302.STATE","AID.WRPS.PU302.STATE","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU303.STATE","AID.WRPS.PU303.STATE","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.STN.DUTY_PUMP","AID.WRPS.STN.DUTY_PUMP","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.STN.ALARM_WORD","AID.WRPS.STN.ALARM_WORD","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.STN.CMD_ACK","AID.WRPS.STN.CMD_ACK","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU301.RUN_HOURS","AID.WRPS.PU301.RUN_HOURS","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU302.RUN_HOURS","AID.WRPS.PU302.RUN_HOURS","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.PU303.RUN_HOURS","AID.WRPS.PU303.RUN_HOURS","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SP.MODE","AID.WRPS.SP.MODE","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SP.CMD_WORD","AID.WRPS.SP.CMD_WORD","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SP.CMD_PARAM","AID.WRPS.SP.CMD_PARAM","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SP.LEVEL_SP","AID.WRPS.SP.LEVEL_SP","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SP.START_DUTY","AID.WRPS.SP.START_DUTY","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SP.START_P2","AID.WRPS.SP.START_P2","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SP.START_P3","AID.WRPS.SP.START_P3","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SP.STOP_ALL","AID.WRPS.SP.STOP_ALL","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SP.HIGH_ALARM","AID.WRPS.SP.HIGH_ALARM","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SP.MIN_SPEED","AID.WRPS.SP.MIN_SPEED","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SP.SERVICE_HRS","AID.WRPS.SP.SERVICE_HRS","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SIM.INFLOW","AID.WRPS.SIM.INFLOW","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SIM.SCENARIO","AID.WRPS.SIM.SCENARIO","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SIM.RESET","AID.WRPS.SIM.RESET","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" + +"WRPS_EVENT:AID.WRPS.SIM.TIME_SCALE","AID.WRPS.SIM.TIME_SCALE","WRPS_EVENT",0,0,\ +0,0,0,0,1,0,\ +0,0,0,0,1,0,\ +0,0,0,0,0,0,\ +1000000,10,"Second" diff --git a/04-scada/modbus_points/scada-points.csv b/04-scada/modbus_points/scada-points.csv new file mode 100644 index 0000000..26d146a --- /dev/null +++ b/04-scada/modbus_points/scada-points.csv @@ -0,0 +1,50 @@ +scada_tag,description,poll_group,function_code,modbus_address,data_type,access,eng_units,raw_to_eng,eng_gain,format_mask,iec_address,plc_tag,notes +PS_PU301_RUN_COMMAND,Run command,PS_STATUS_BITS,FC01,0,Boolean,Read,,value,1.0,99999,%QX0.0,PU-301, +PS_PU302_RUN_COMMAND,Run command,PS_STATUS_BITS,FC01,1,Boolean,Read,,value,1.0,99999,%QX0.1,PU-302, +PS_PU303_RUN_COMMAND,Run command,PS_STATUS_BITS,FC01,2,Boolean,Read,,value,1.0,99999,%QX0.2,PU-303, +PS_PU301_RUNNING,Running,PS_STATUS_BITS,FC01,3,Boolean,Read,,value,1.0,99999,%QX0.3,PU-301, +PS_PU302_RUNNING,Running,PS_STATUS_BITS,FC01,4,Boolean,Read,,value,1.0,99999,%QX0.4,PU-302, +PS_PU303_RUNNING,Running,PS_STATUS_BITS,FC01,5,Boolean,Read,,value,1.0,99999,%QX0.5,PU-303, +PS_PU301_AVAILABLE,Available,PS_STATUS_BITS,FC01,6,Boolean,Read,,value,1.0,99999,%QX0.6,PU-301, +PS_PU302_AVAILABLE,Available,PS_STATUS_BITS,FC01,7,Boolean,Read,,value,1.0,99999,%QX0.7,PU-302, +PS_PU303_AVAILABLE,Available,PS_STATUS_BITS,FC01,8,Boolean,Read,,value,1.0,99999,%QX1.0,PU-303, +PS_STN_STATION_IN_AUTO,Station in auto,PS_STATUS_BITS,FC01,9,Boolean,Read,,value,1.0,99999,%QX1.1,STN, +PS_STN_HIGH_LEVEL_ALARM,High level alarm,PS_STATUS_BITS,FC01,10,Boolean,Read,,value,1.0,99999,%QX1.2,STN, +PS_STN_SPILL_ACTIVE,Spill active,PS_STATUS_BITS,FC01,11,Boolean,Read,,value,1.0,99999,%QX1.3,STN, +PS_PU301_TRIPPED,Tripped,PS_STATUS_BITS,FC01,12,Boolean,Read,,value,1.0,99999,%QX1.4,PU-301, +PS_PU302_TRIPPED,Tripped,PS_STATUS_BITS,FC01,13,Boolean,Read,,value,1.0,99999,%QX1.5,PU-302, +PS_PU303_TRIPPED,Tripped,PS_STATUS_BITS,FC01,14,Boolean,Read,,value,1.0,99999,%QX1.6,PU-303, +PS_STN_WET_WELL_LEVEL,Wet well level,PS_PUBLISHED,FC03,0,Signed 16-bit,Read,%,value / 60,0.016666666666666666,999.9,%QW0,STN, +PS_STN_INFLOW,Inflow,PS_PUBLISHED,FC03,1,Signed 16-bit,Read,m3/h,value * 0.36,0.36,9999.9,%QW1,STN, +PS_STN_TOTAL_DISCHARGE_FLOW,Total discharge flow,PS_PUBLISHED,FC03,2,Signed 16-bit,Read,m3/h,value * 0.36,0.36,9999.9,%QW2,STN, +PS_STN_PUMPS_RUNNING,Pumps running,PS_PUBLISHED,FC03,3,Signed 16-bit,Read,count,value,1.0,9,%QW3,STN, +PS_STN_COMMON_DRIVE_SPEED,Common drive speed,PS_PUBLISHED,FC03,4,Signed 16-bit,Read,%,value * 0.2,0.2,999.9,%QW4,STN, +PS_STN_TIME_TO_SPILL_WEIR,Time to spill weir (32767 = drawing down),PS_PUBLISHED,FC03,5,Signed 16-bit,Read,s,value,1.0,99999,%QW5,STN, +PS_STN_TIME_TO_LSHH,Time to LSHH (32767 = drawing down),PS_PUBLISHED,FC03,6,Signed 16-bit,Read,s,value,1.0,99999,%QW6,STN, +PS_STN_NET_ACCUMULATION,Net accumulation (signed),PS_PUBLISHED,FC03,7,Signed 16-bit,Read,m3/h,value * 0.36,0.36,9999.9,%QW7,STN, +PS_PU301_RUN_HOURS,Run hours,PS_PUBLISHED,FC03,8,Signed 16-bit,Read,h,value,1.0,99999,%QW8,PU-301, +PS_PU302_RUN_HOURS,Run hours,PS_PUBLISHED,FC03,9,Signed 16-bit,Read,h,value,1.0,99999,%QW9,PU-302, +PS_PU303_RUN_HOURS,Run hours,PS_PUBLISHED,FC03,10,Signed 16-bit,Read,h,value,1.0,99999,%QW10,PU-303, +PS_STN_VOLUME_REMAINING_TO_SPILL,Volume remaining to spill,PS_PUBLISHED,FC03,11,Signed 16-bit,Read,m3,value,1.0,9999,%QW11,STN, +PS_STN_STATION_STATE,Station state (enum 3.1),PS_PUBLISHED,FC03,12,Signed 16-bit,Read,,value,1.0,9,%QW12,STN, +PS_PU301_PUMP_STATE,Pump state (enum 3.2),PS_PUBLISHED,FC03,13,Signed 16-bit,Read,,value,1.0,9,%QW13,PU-301, +PS_PU302_PUMP_STATE,Pump state (enum 3.2),PS_PUBLISHED,FC03,14,Signed 16-bit,Read,,value,1.0,9,%QW14,PU-302, +PS_PU303_PUMP_STATE,Pump state (enum 3.2),PS_PUBLISHED,FC03,15,Signed 16-bit,Read,,value,1.0,9,%QW15,PU-303, +PS_STN_CURRENT_DUTY_PUMP,"Current duty pump (0 = none, 1-3)",PS_PUBLISHED,FC03,16,Signed 16-bit,Read,,value,1.0,9,%QW16,STN, +PS_STN_ALARM_BITMASK,Alarm bitmask (section 6) - READ AS UNSIGNED,PS_PUBLISHED,FC03,17,Unsigned 16-bit,Read,,value,1.0,99999,%QW17,STN, +PS_STN_COMMAND_ACKNOWLEDGE,Command acknowledge (echoes %MW1),PS_PUBLISHED,FC03,20,Signed 16-bit,Read,,value,1.0,99,%QW20,STN, +PS_STN_STATION_MODE_1_AUTO,"Station mode: 1 = auto, 2 = off",PS_SETPOINTS,FC03/FC06,1024,Signed 16-bit,Read/Write,,value,1.0,9,%MW0,STN, +PS_STN_COMMAND_WORD,Command word (section 3.3),PS_SETPOINTS,FC03/FC06,1025,Signed 16-bit,Read/Write,,value,1.0,99,%MW1,STN, +PS_STN_COMMAND_PARAMETER,Command parameter (pump number),PS_SETPOINTS,FC03/FC06,1026,Signed 16-bit,Read/Write,,value,1.0,9,%MW2,STN, +PS_STN_LEVEL_CONTROL_SETPOINT,Level control setpoint,PS_SETPOINTS,FC03/FC06,1027,Signed 16-bit,Read/Write,%,value / 60,0.016666666666666666,999.9,%MW3,STN, +PS_STN_START_DUTY_LEVEL,Start duty level,PS_SETPOINTS,FC03/FC06,1028,Signed 16-bit,Read/Write,%,value / 60,0.016666666666666666,999.9,%MW4,STN, +PS_STN_START_PUMP_2_LEVEL,Start pump 2 level,PS_SETPOINTS,FC03/FC06,1029,Signed 16-bit,Read/Write,%,value / 60,0.016666666666666666,999.9,%MW5,STN, +PS_STN_START_PUMP_3_LEVEL,Start pump 3 level,PS_SETPOINTS,FC03/FC06,1030,Signed 16-bit,Read/Write,%,value / 60,0.016666666666666666,999.9,%MW6,STN, +PS_STN_STOP_ALL_LEVEL,Stop all level,PS_SETPOINTS,FC03/FC06,1031,Signed 16-bit,Read/Write,%,value / 60,0.016666666666666666,999.9,%MW7,STN, +PS_STN_HIGH_LEVEL_ALARM,High level alarm,PS_SETPOINTS,FC03/FC06,1032,Signed 16-bit,Read/Write,%,value / 60,0.016666666666666666,999.9,%MW8,STN, +PS_STN_MINIMUM_DRIVE_SPEED,Minimum drive speed,PS_SETPOINTS,FC03/FC06,1033,Signed 16-bit,Read/Write,%,value * 0.2,0.2,999.9,%MW9,STN, +PS_STN_SERVICE_INTERVAL,Service interval,PS_SETPOINTS,FC03/FC06,1034,Signed 16-bit,Read/Write,h,value,1.0,99999,%MW10,STN, +PS_SIM_MANUAL_INFLOW,manual inflow (mode 0),PS_SIM_CONTROL,FC03/FC06,1044,Signed 16-bit,Read/Write,m3/h,value * 0.36,0.36,9999.9,%MW20,SIM,SIMULATION CONTROL - simulation build only +PS_SIM_SCENARIO_0_MAN_1_DIURNAL_2_W,scenario 0=man 1=diurnal 2=wet 3=ref,PS_SIM_CONTROL,FC03/FC06,1045,Signed 16-bit,Read/Write,,value,1.0,9,%MW21,SIM,SIMULATION CONTROL - simulation build only +PS_SIM_WRITE_1_TO_RESET_SCENARIO,"write 1 to reset scenario, self-clearing",PS_SIM_CONTROL,FC03/FC06,1046,Signed 16-bit,Read/Write,,value,1.0,9,%MW22,SIM,SIMULATION CONTROL - simulation build only +PS_SIM_TIME_SCALE_1_120,time scale 1-120,PS_SIM_CONTROL,FC03/FC06,1047,Signed 16-bit,Read/Write,x,value,1.0,999,%MW23,SIM,SIMULATION CONTROL - simulation build only diff --git a/04-scada/modbus_points/wrps_item_df.qli b/04-scada/modbus_points/wrps_item_df.qli new file mode 100644 index 0000000..898bdcc --- /dev/null +++ b/04-scada/modbus_points/wrps_item_df.qli @@ -0,0 +1,1162 @@ + +@LANGUAGE +ENGLISH + + +@VERSION +1.03.00 + + +!================================================================================================================================== + +@FIELDS +NAME,NSID,PARENT_NSID,SECTION_PATH,DESCRIPTION +DEADBAND,LOW_LIMIT,HIGH_LIMIT,LOW_LOW_LIMIT,HIGH_HIGH_LIMIT,T_VALUE +P_VALUE,I_VALUE,TREND_UP_LIMIT,TREND_LOW_LIMIT,SCALE_HIGH_LIMIT,SCALE_LOW_LIMIT +DUMMY2,COMMENT_1,COMMENT_2,VALUE_FORMAT,POSITIONED,LONGITUDE +LATITUDE,ALARMING,DIAG,NAME_IN_ITM_TAB,STORAGE,AUDIT_INFO +FO_ITEM,BLOCKED,PARENT_BLOCKED,ALARM_INHIBIT,PARENT_ALARM_INHIBIT,OPC_VISIBLE +PARENT_OPC_VISIBLE,OPC_READ,OPC_WRITE,OPC_ALARM_DETECTION,HAS_SUB,STRING_LENGTH +DELAY,REPEAT,ID_GROUP,ID_NUMBER,ITEM_REP,ITEM_TYPE +ITEM_SPECIAL,ACKN_TYPE,ALARM_GROUP,FRONT_END_NODE,DISTR_TYPE,INSTALL +UNIT,TAG,LIMIT_CLAMP,OUT_OF_RANGE,COL_GROUP,STATION +POINT,FO_GROUP,ITEM_STAT_1,ITEM_STAT_2,ITEM_STAT_3,ITEM_STAT_4 +ITEM_STAT_5,ITEM_STAT_6,ALARM_STATE_1,ALARM_STATE_2,ALARM_STATE_3,ALARM_STATE_4 +ALARM_STATE_5,ALARM_STATE_6,PRIORITY_1,PRIORITY_2,PRIORITY_3,PRIORITY_4 +PRIORITY_5,PRIORITY_6,ALARM_TEXT_1,ALARM_TEXT_2,ALARM_TEXT_3,ALARM_TEXT_4 +ALARM_TEXT_5,ALARM_TEXT_6,ALARM_COLOR_1,ALARM_COLOR_2,ALARM_COLOR_3,ALARM_COLOR_4 +ALARM_COLOR_5,ALARM_COLOR_6,MNEMONIC_1,MNEMONIC_2,MNEMONIC_3,MNEMONIC_4 +MNEMONIC_5,MNEMONIC_6,AOI_1,AOI_2,AOI_3,AOI_4 +AOI_5,AOI_6,AOI_7,AOI_8,AOI_9,AOI_10 +AOI_11,AOI_12,AOI_13,AOI_14,AOI_15,AOI_16 +ENG_UNIT,PROCESS_LIST,POINT_NAME,OPC_AE_STATION_NAME,OPC_EVENT_SOURCE,OPC_EVENT_SOURCE_NAME +CREATED_BY,SHELVE_ENABLED_1,SHELVE_ENABLED_2,SHELVE_ENABLED_3,SHELVE_ENABLED_4,SHELVE_ENABLED_5 +SHELVE_ENABLED_6,AGG_INTERVAL + +@ITEM_DF +"AID.WRPS.PU301.RUN_CMD",177,172,"AID.WRPS.PU301","Run command",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,1,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU301","RUN_CMD","",0,"","WRPS_PLC",\ +"PU301_RUN_CMD","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU301_RUN_CMD","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU302.RUN_CMD",178,173,"AID.WRPS.PU302","Run command",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,2,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU302","RUN_CMD","",0,"","WRPS_PLC",\ +"PU302_RUN_CMD","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU302_RUN_CMD","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU303.RUN_CMD",179,174,"AID.WRPS.PU303","Run command",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,3,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU303","RUN_CMD","",0,"","WRPS_PLC",\ +"PU303_RUN_CMD","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU303_RUN_CMD","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU301.RUNNING",180,172,"AID.WRPS.PU301","Running",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,4,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU301","RUNNING","",0,"","WRPS_PLC",\ +"PU301_RUNNING","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU301_RUNNING","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU302.RUNNING",181,173,"AID.WRPS.PU302","Running",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,5,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU302","RUNNING","",0,"","WRPS_PLC",\ +"PU302_RUNNING","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU302_RUNNING","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU303.RUNNING",182,174,"AID.WRPS.PU303","Running",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,6,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU303","RUNNING","",0,"","WRPS_PLC",\ +"PU303_RUNNING","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU303_RUNNING","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU301.AVAILABLE",183,172,"AID.WRPS.PU301","Available",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,7,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU301","AVAILABLE","",0,"","WRPS_PLC",\ +"PU301_AVAILABLE","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU301_AVAILABLE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU302.AVAILABLE",184,173,"AID.WRPS.PU302","Available",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,8,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU302","AVAILABLE","",0,"","WRPS_PLC",\ +"PU302_AVAILABLE","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU302_AVAILABLE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU303.AVAILABLE",185,174,"AID.WRPS.PU303","Available",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,9,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU303","AVAILABLE","",0,"","WRPS_PLC",\ +"PU303_AVAILABLE","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU303_AVAILABLE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.IN_AUTO",186,171,"AID.WRPS.STN","Station in auto",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,10,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","IN_AUTO","",0,"","WRPS_PLC",\ +"STN_IN_AUTO","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_IN_AUTO","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.HIGH_LEVEL",187,171,"AID.WRPS.STN","High level alarm",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,11,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","HIGH_LEVEL","",0,"","WRPS_PLC",\ +"STN_HIGH_LEVEL","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_HIGH_LEVEL","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.SPILL_ACTIVE",188,171,"AID.WRPS.STN","Spill active",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,12,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","SPILL_ACTIVE","",0,"","WRPS_PLC",\ +"STN_SPILL_ACTIVE","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_SPILL_ACTIVE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU301.TRIPPED",189,172,"AID.WRPS.PU301","Tripped",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,13,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU301","TRIPPED","",0,"","WRPS_PLC",\ +"PU301_TRIPPED","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU301_TRIPPED","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU302.TRIPPED",190,173,"AID.WRPS.PU302","Tripped",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,14,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU302","TRIPPED","",0,"","WRPS_PLC",\ +"PU302_TRIPPED","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU302_TRIPPED","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU303.TRIPPED",191,174,"AID.WRPS.PU303","Tripped",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,15,"Boolean","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU303","TRIPPED","",0,"","WRPS_PLC",\ +"PU303_TRIPPED","","BOOLEAN 0","BOOLEAN 1","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU303_TRIPPED","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.LEVEL",192,171,"AID.WRPS.STN","Wet well level",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,16,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","LEVEL","",0,"","WRPS_PLC",\ +"STN_LEVEL","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:STN_LEVEL","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.INFLOW",193,171,"AID.WRPS.STN","Inflow",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,17,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","INFLOW","",0,"","WRPS_PLC",\ +"STN_INFLOW","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"m3/h","","WRPS_PLC:STN_INFLOW","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.DISCHARGE",194,171,"AID.WRPS.STN","Total discharge flow",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,18,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","DISCHARGE","",0,"","WRPS_PLC",\ +"STN_DISCHARGE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"m3/h","","WRPS_PLC:STN_DISCHARGE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.PUMPS_RUNNING",195,171,"AID.WRPS.STN","Pumps running",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,19,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","PUMPS_RUNNING","",0,"","WRPS_PLC",\ +"STN_PUMPS_RUNNING","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"count","","WRPS_PLC:STN_PUMPS_RUNNING","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.SPEED",196,171,"AID.WRPS.STN","Common drive speed",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,20,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","SPEED","",0,"","WRPS_PLC",\ +"STN_SPEED","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:STN_SPEED","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.TIME_TO_SPILL",197,171,"AID.WRPS.STN","Time to spill weir (32767 = drawing down)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,21,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","TIME_TO_SPILL","",0,"","WRPS_PLC",\ +"STN_TIME_TO_SPILL","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"s","","WRPS_PLC:STN_TIME_TO_SPILL","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.TIME_TO_LSHH",198,171,"AID.WRPS.STN","Time to LSHH (32767 = drawing down)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,22,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","TIME_TO_LSHH","",0,"","WRPS_PLC",\ +"STN_TIME_TO_LSHH","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"s","","WRPS_PLC:STN_TIME_TO_LSHH","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.NET_ACCUM",199,171,"AID.WRPS.STN","Net accumulation (signed)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,23,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","NET_ACCUM","",0,"","WRPS_PLC",\ +"STN_NET_ACCUM","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"m3/h","","WRPS_PLC:STN_NET_ACCUM","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU301.RUN_HOURS",200,172,"AID.WRPS.PU301","Run hours",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,24,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU301","RUN_HOURS","",0,"","WRPS_PLC",\ +"PU301_RUN_HOURS","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"h","","WRPS_PLC:PU301_RUN_HOURS","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU302.RUN_HOURS",201,173,"AID.WRPS.PU302","Run hours",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,25,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU302","RUN_HOURS","",0,"","WRPS_PLC",\ +"PU302_RUN_HOURS","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"h","","WRPS_PLC:PU302_RUN_HOURS","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU303.RUN_HOURS",202,174,"AID.WRPS.PU303","Run hours",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,26,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU303","RUN_HOURS","",0,"","WRPS_PLC",\ +"PU303_RUN_HOURS","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"h","","WRPS_PLC:PU303_RUN_HOURS","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.VOL_TO_SPILL",203,171,"AID.WRPS.STN","Volume remaining to spill",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,27,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","VOL_TO_SPILL","",0,"","WRPS_PLC",\ +"STN_VOL_TO_SPILL","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"m3","","WRPS_PLC:STN_VOL_TO_SPILL","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.STATE",204,171,"AID.WRPS.STN","Station state (enum 3.1)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,28,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","STATE","",0,"","WRPS_PLC",\ +"STN_STATE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_STATE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU301.STATE",205,172,"AID.WRPS.PU301","Pump state (enum 3.2)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,29,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU301","STATE","",0,"","WRPS_PLC",\ +"PU301_STATE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU301_STATE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU302.STATE",206,173,"AID.WRPS.PU302","Pump state (enum 3.2)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,30,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU302","STATE","",0,"","WRPS_PLC",\ +"PU302_STATE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU302_STATE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.PU303.STATE",207,174,"AID.WRPS.PU303","Pump state (enum 3.2)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,31,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"PU303","STATE","",0,"","WRPS_PLC",\ +"PU303_STATE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:PU303_STATE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.DUTY_PUMP",208,171,"AID.WRPS.STN","Current duty pump (0 = none, 1-3)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,32,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","DUTY_PUMP","",0,"","WRPS_PLC",\ +"STN_DUTY_PUMP","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_DUTY_PUMP","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.ALARM_WORD",209,171,"AID.WRPS.STN","Alarm bitmask (section 6) - READ AS UNSIGNED",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,33,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","ALARM_WORD","",0,"","WRPS_PLC",\ +"STN_ALARM_WORD","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_ALARM_WORD","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.STN.CMD_ACK",210,171,"AID.WRPS.STN","Command acknowledge (echoes %MW1)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,34,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"STN","CMD_ACK","",0,"","WRPS_PLC",\ +"STN_CMD_ACK","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:STN_CMD_ACK","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.MODE",211,175,"AID.WRPS.SP","Station mode: 1 = auto, 2 = off",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,35,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SP","MODE","",0,"","WRPS_PLC",\ +"SP_MODE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:SP_MODE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.CMD_WORD",212,175,"AID.WRPS.SP","Command word (section 3.3)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,36,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SP","CMD_WORD","",0,"","WRPS_PLC",\ +"SP_CMD_WORD","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:SP_CMD_WORD","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.CMD_PARAM",213,175,"AID.WRPS.SP","Command parameter (pump number)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,37,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SP","CMD_PARAM","",0,"","WRPS_PLC",\ +"SP_CMD_PARAM","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:SP_CMD_PARAM","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.LEVEL_SP",214,175,"AID.WRPS.SP","Level control setpoint",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,38,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SP","LEVEL_SP","",0,"","WRPS_PLC",\ +"SP_LEVEL_SP","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_LEVEL_SP","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.START_DUTY",215,175,"AID.WRPS.SP","Start duty level",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,39,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SP","START_DUTY","",0,"","WRPS_PLC",\ +"SP_START_DUTY","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_START_DUTY","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.START_P2",216,175,"AID.WRPS.SP","Start pump 2 level",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,40,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SP","START_P2","",0,"","WRPS_PLC",\ +"SP_START_P2","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_START_P2","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.START_P3",217,175,"AID.WRPS.SP","Start pump 3 level",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,41,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SP","START_P3","",0,"","WRPS_PLC",\ +"SP_START_P3","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_START_P3","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.STOP_ALL",218,175,"AID.WRPS.SP","Stop all level",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,42,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SP","STOP_ALL","",0,"","WRPS_PLC",\ +"SP_STOP_ALL","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_STOP_ALL","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.HIGH_ALARM",219,175,"AID.WRPS.SP","High level alarm",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,43,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SP","HIGH_ALARM","",0,"","WRPS_PLC",\ +"SP_HIGH_ALARM","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_HIGH_ALARM","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.MIN_SPEED",220,175,"AID.WRPS.SP","Minimum drive speed",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,44,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SP","MIN_SPEED","",0,"","WRPS_PLC",\ +"SP_MIN_SPEED","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"%","","WRPS_PLC:SP_MIN_SPEED","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SP.SERVICE_HRS",221,175,"AID.WRPS.SP","Service interval",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","99999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,45,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SP","SERVICE_HRS","",0,"","WRPS_PLC",\ +"SP_SERVICE_HRS","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"h","","WRPS_PLC:SP_SERVICE_HRS","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SIM.INFLOW",222,176,"AID.WRPS.SIM","manual inflow (mode 0)",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9999.9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,46,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SIM","INFLOW","",0,"","WRPS_PLC",\ +"SIM_INFLOW","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"m3/h","","WRPS_PLC:SIM_INFLOW","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SIM.SCENARIO",223,176,"AID.WRPS.SIM","scenario 0=man 1=diurnal 2=wet 3=ref",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,47,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SIM","SCENARIO","",0,"","WRPS_PLC",\ +"SIM_SCENARIO","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:SIM_SCENARIO","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SIM.RESET",224,176,"AID.WRPS.SIM","write 1 to reset scenario, self-clearing",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","9",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,48,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SIM","RESET","",0,"","WRPS_PLC",\ +"SIM_RESET","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","WRPS_PLC:SIM_RESET","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" + +"AID.WRPS.SIM.TIME_SCALE",225,176,"AID.WRPS.SIM","time scale 1-120",\ +0,0,0,0,0,0,\ +0,0,0,0,0,0,\ +0,"","","999",0,0,\ +0,0,0,1,0,0,\ +0,0,0,0,0,0,\ +0,0,0,0,0,1,\ +0,0,1,49,"Real","",\ +"","","","UNLICENCED","Local Host","WRPS",\ +"SIM","TIME_SCALE","",0,"","WRPS_PLC",\ +"SIM_TIME_SCALE","","","","","",\ +"","","Normal","Normal","Normal","Normal",\ +"Normal","Normal",0,0,0,0,\ +0,0,"","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"","","","","","",\ +"x","","WRPS_PLC:SIM_TIME_SCALE","","",":",\ +"unknown",1,1,1,1,1,\ +1,"" diff --git a/04-scada/modbus_points/wrps_modbus_point_df.qli b/04-scada/modbus_points/wrps_modbus_point_df.qli new file mode 100644 index 0000000..b275192 --- /dev/null +++ b/04-scada/modbus_points/wrps_modbus_point_df.qli @@ -0,0 +1,362 @@ + +@LANGUAGE +ENGLISH + + +@VERSION +1.03.00 + + +!================================================================================================================================== + +@FIELDS +NAME,STATION,POINT,DESCRIPTION,IO_ADDRESS +EXTERNAL_RELATION,SCAN_TYPE,CONV_TYPE,DELTA_LIMIT,MAX_INSENS,OFFSET +PHYS_LOW,PHYS_HIGH,STEP,INVERS,HAS_SIGN,OVERFL_DET +SWAP_BYTES,SWAP_WORDS,ELEC_LOW,ELEC_HIGH,TMO_NONE,TMO_BOTH +AVE_UPD_INTERVAL,NO_ZERO,BURST_LIMIT,BITS,CHARS,DIGITS +FLOAT_TYPE,TIME_ZONE,TIME_REPRES,WLS_VAL_TYPE + +@MODBUS_POINT_DF +"WRPS_PLC:PU301_RUN_CMD","WRPS_PLC","PU301_RUN_CMD","Run command","DO:01",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU302_RUN_CMD","WRPS_PLC","PU302_RUN_CMD","Run command","DO:02",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU303_RUN_CMD","WRPS_PLC","PU303_RUN_CMD","Run command","DO:03",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU301_RUNNING","WRPS_PLC","PU301_RUNNING","Running","DO:04",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU302_RUNNING","WRPS_PLC","PU302_RUNNING","Running","DO:05",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU303_RUNNING","WRPS_PLC","PU303_RUNNING","Running","DO:06",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU301_AVAILABLE","WRPS_PLC","PU301_AVAILABLE","Available","DO:07",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU302_AVAILABLE","WRPS_PLC","PU302_AVAILABLE","Available","DO:08",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU303_AVAILABLE","WRPS_PLC","PU303_AVAILABLE","Available","DO:09",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_IN_AUTO","WRPS_PLC","STN_IN_AUTO","Station in auto","DO:10",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_HIGH_LEVEL","WRPS_PLC","STN_HIGH_LEVEL","High level alarm","DO:11",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_SPILL_ACTIVE","WRPS_PLC","STN_SPILL_ACTIVE","Spill active","DO:12",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU301_TRIPPED","WRPS_PLC","PU301_TRIPPED","Tripped","DO:13",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU302_TRIPPED","WRPS_PLC","PU302_TRIPPED","Tripped","DO:14",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU303_TRIPPED","WRPS_PLC","PU303_TRIPPED","Tripped","DO:15",\ +"Input","MOD_SCAN","Digital",0,0,0,\ +0,100,1,0,0,0,\ +0,0,0,100,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_LEVEL","WRPS_PLC","STN_LEVEL","Wet well level","RO:01",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-546,546,1,0,1,0,\ +0,0,-32760,32760,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_INFLOW","WRPS_PLC","STN_INFLOW","Inflow","RO:02",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-11796.48,11796.12,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_DISCHARGE","WRPS_PLC","STN_DISCHARGE","Total discharge flow","RO:03",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-11796.48,11796.12,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_PUMPS_RUNNING","WRPS_PLC","STN_PUMPS_RUNNING","Pumps running","RO:04",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_SPEED","WRPS_PLC","STN_SPEED","Common drive speed","RO:05",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-6553,6553,1,0,1,0,\ +0,0,-32765,32765,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_TIME_TO_SPILL","WRPS_PLC","STN_TIME_TO_SPILL","Time to spill weir (32767 = drawing down)","RO:06",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_TIME_TO_LSHH","WRPS_PLC","STN_TIME_TO_LSHH","Time to LSHH (32767 = drawing down)","RO:07",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_NET_ACCUM","WRPS_PLC","STN_NET_ACCUM","Net accumulation (signed)","RO:08",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-11796.48,11796.12,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU301_RUN_HOURS","WRPS_PLC","PU301_RUN_HOURS","Run hours","RO:09",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU302_RUN_HOURS","WRPS_PLC","PU302_RUN_HOURS","Run hours","RO:10",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU303_RUN_HOURS","WRPS_PLC","PU303_RUN_HOURS","Run hours","RO:11",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_VOL_TO_SPILL","WRPS_PLC","STN_VOL_TO_SPILL","Volume remaining to spill","RO:12",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_STATE","WRPS_PLC","STN_STATE","Station state (enum 3.1)","RO:13",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU301_STATE","WRPS_PLC","PU301_STATE","Pump state (enum 3.2)","RO:14",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU302_STATE","WRPS_PLC","PU302_STATE","Pump state (enum 3.2)","RO:15",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:PU303_STATE","WRPS_PLC","PU303_STATE","Pump state (enum 3.2)","RO:16",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_DUTY_PUMP","WRPS_PLC","STN_DUTY_PUMP","Current duty pump (0 = none, 1-3)","RO:17",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_ALARM_WORD","WRPS_PLC","STN_ALARM_WORD","Alarm bitmask (section 6) - READ AS UNSIGNED","RO:18",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +0,65535,1,0,0,0,\ +0,0,0,65535,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:STN_CMD_ACK","WRPS_PLC","STN_CMD_ACK","Command acknowledge (echoes %MW1)","RO:21",\ +"Input","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SP_MODE","WRPS_PLC","SP_MODE","Station mode: 1 = auto, 2 = off","RO:1025",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SP_CMD_WORD","WRPS_PLC","SP_CMD_WORD","Command word (section 3.3)","RO:1026",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SP_CMD_PARAM","WRPS_PLC","SP_CMD_PARAM","Command parameter (pump number)","RO:1027",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SP_LEVEL_SP","WRPS_PLC","SP_LEVEL_SP","Level control setpoint","RO:1028",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-546,546,1,0,1,0,\ +0,0,-32760,32760,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SP_START_DUTY","WRPS_PLC","SP_START_DUTY","Start duty level","RO:1029",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-546,546,1,0,1,0,\ +0,0,-32760,32760,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SP_START_P2","WRPS_PLC","SP_START_P2","Start pump 2 level","RO:1030",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-546,546,1,0,1,0,\ +0,0,-32760,32760,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SP_START_P3","WRPS_PLC","SP_START_P3","Start pump 3 level","RO:1031",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-546,546,1,0,1,0,\ +0,0,-32760,32760,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SP_STOP_ALL","WRPS_PLC","SP_STOP_ALL","Stop all level","RO:1032",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-546,546,1,0,1,0,\ +0,0,-32760,32760,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SP_HIGH_ALARM","WRPS_PLC","SP_HIGH_ALARM","High level alarm","RO:1033",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-546,546,1,0,1,0,\ +0,0,-32760,32760,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SP_MIN_SPEED","WRPS_PLC","SP_MIN_SPEED","Minimum drive speed","RO:1034",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-6553,6553,1,0,1,0,\ +0,0,-32765,32765,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SP_SERVICE_HRS","WRPS_PLC","SP_SERVICE_HRS","Service interval","RO:1035",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SIM_INFLOW","WRPS_PLC","SIM_INFLOW","manual inflow (mode 0)","RO:1045",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-11796.48,11796.12,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SIM_SCENARIO","WRPS_PLC","SIM_SCENARIO","scenario 0=man 1=diurnal 2=wet 3=ref","RO:1046",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SIM_RESET","WRPS_PLC","SIM_RESET","write 1 to reset scenario, self-clearing","RO:1047",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" + +"WRPS_PLC:SIM_TIME_SCALE","WRPS_PLC","SIM_TIME_SCALE","time scale 1-120","RO:1048",\ +"Input + Output","MOD_SCAN","Linear",0,0,0,\ +-32768,32767,1,0,1,0,\ +0,0,-32768,32767,0,0,\ +0,0,0,16,16,4,\ +"Intel","Date+time GMT","7 bytes IEC","Float value" diff --git a/04-scada/modbus_points/wrps_section_df.qli b/04-scada/modbus_points/wrps_section_df.qli new file mode 100644 index 0000000..83a7b66 --- /dev/null +++ b/04-scada/modbus_points/wrps_section_df.qli @@ -0,0 +1,40 @@ + +@LANGUAGE +ENGLISH + + +@VERSION +1.03.00 + + +!================================================================================================================================== + +@FIELDS +NAME,SECTION_PATH,SECTION_NAME,BLOCKED,PARENT_BLOCKED +ALARM_INHIBIT,PARENT_ALARM_INHIBIT,OPC_VISIBLE,PARENT_OPC_VISIBLE,NSID,PARENT_NSID +DESCRIPTION,CREATED_BY + +@SECTION_DF +"AID.WRPS.STN","AID.WRPS","STN",0,0,\ +0,0,0,0,171,170,\ +"Waterloo Road PS - station wide measurements and status","unknown" + +"AID.WRPS.PU301","AID.WRPS","PU301",0,0,\ +0,0,0,0,172,170,\ +"Pump PU-301","unknown" + +"AID.WRPS.PU302","AID.WRPS","PU302",0,0,\ +0,0,0,0,173,170,\ +"Pump PU-302","unknown" + +"AID.WRPS.PU303","AID.WRPS","PU303",0,0,\ +0,0,0,0,174,170,\ +"Pump PU-303","unknown" + +"AID.WRPS.SP","AID.WRPS","SP",0,0,\ +0,0,0,0,175,170,\ +"Operator setpoints and commands","unknown" + +"AID.WRPS.SIM","AID.WRPS","SIM",0,0,\ +0,0,0,0,176,170,\ +"Simulation control - simulation build only","unknown"