diff --git a/03-plc/GETTING-STARTED.md b/03-plc/GETTING-STARTED.md new file mode 100644 index 0000000..0e3557f --- /dev/null +++ b/03-plc/GETTING-STARTED.md @@ -0,0 +1,159 @@ +# Getting started — three things you might need to do + +Short answers. Detail is in `DEPLOY.md`, `VERSIONS.md` and `README.md`. + +Before anything else, know the two hard facts about this setup: + +1. **The ST → C++ compiler (STruC++) ships only inside the OpenPLC Editor GUI.** + There is no CLI, no library, no container image of it. Any change to the PLC + *logic* goes through the Editor. Nothing else will do it. +2. **The running PLC's image was made with `docker commit`** and holds the compiled + program in its writable layer. It is not in a registry and cannot be re-pulled. + +--- + +## 1. I need to modify the PLC program + +**What you need:** OpenPLC Editor **v4.2.11**, on a machine with a graphical +desktop that can reach `10.0.0.17:8443` (LAN or WireGuard VPN). + +> ⚠️ **The Editor is not currently installed anywhere.** It lived on the retired +> `dev-ubuntu` host. Installing it is step one — see `DEPLOY.md` §0. + +**Then:** + +```bash +# 1. edit the ST - this is the only place you edit +vi src/30_prog_control.st + +# 2. rebuild. This also regenerates register-map.csv +python build.py --mode sim + +# 3. generate the Editor project +python gen_project.py --mode sim --out editor-project +``` + +Copy `editor-project/project.json` and `editor-project/pous/` into the Editor +project folder — **with the Editor closed** — then open it, Build, and Start PLC. +`DEPLOY.md` §B is the full procedure; every warning in it was earned. + +**Then verify:** + +```bash +python ../05-tests/verify_modbus.py --host 10.0.0.17 --port 502 --unit 1 +``` + +### Three things to get right + +- **Never hand-edit `register-map.csv`.** It is generated. Change the `REGISTERS` + table in `build.py` and rebuild. It is the contract with CI Server — if it moves, + the SCADA point list in `04-scada/modbus/` must be regenerated too. +- **Never edit the Editor project directly.** `gen_project.py` overwrites it. Fixes + go into `src/`. +- **Never edit `as-built/`.** That is a snapshot of what is deployed, not source. + +### If you add or move a register + +The whole chain has to be re-run, in order: + +``` +src/*.st → build.py → register-map.csv + ↓ + 04-scada/modbus/gen_scada_points.py + ↓ + gen_ciserver_qli.py → re-import into CI Server +``` + +Skip a step and the PLC and SCADA disagree silently — the reads still succeed, +they are just wrong. + +--- + +## 2. I need to create a new PLC container from scratch + +For a **new demo**, not for recovering this one. + +**What you need:** a Docker host, and the OpenPLC Editor to compile a program. + +```yaml +# ~/myplc-compose.yml +services: + myplc-runtime: + image: ghcr.io/autonomy-logic/openplc-runtime:latest + container_name: myplc-runtime + restart: unless-stopped + cap_add: [SYS_NICE, SYS_RESOURCE] # required - the runtime needs + # real-time scheduling or it breaks + ports: + - ":502:502" # Modbus TCP, for SCADA + - ":8443:8443" # REST API, for the Editor + volumes: + - myplc-data:/var/run/runtime + logging: + driver: json-file + options: { max-size: "10m", max-file: "3" } +volumes: + myplc-data: +``` + +```bash +docker compose -f ~/myplc-compose.yml up -d +``` + +**Bind the ports to the host's LAN address, never `0.0.0.0`.** Modbus has no +authentication or encryption, and 8443 is the control channel with only JWT in +front of it. On a host with a public IP, the bind address is the security control. + +### Then, in the Editor — the part that is not obvious + +Three steps that are configured through GUI dialogs and are not generated. The +reference copies of what they produce are in `editor-devices/`. + +1. **Device → Configuration** — device `OpenPLC Runtime V4`, IP = the host's LAN + address, port `8443`, user `admin`. +2. **Device → Servers** — add a Modbus TCP server, **Enable Server** on, + `networkInterface` `0.0.0.0`, port `502`. + > **This is the step everyone misses.** It is what makes the Editor ship + > `conf/modbus_slave.json`, which enables the runtime's `modbus_slave` plugin. + > Without it the plugin stays off and **nothing ever binds port 502**, however + > the container is configured. `0.0.0.0` here is correct — it is inside the + > container; Docker's publish is what restricts exposure. +3. **Do not touch Device → Remote Devices.** That is the Modbus *master* — it makes + the PLC poll someone else, and its IO groups claim `%IW` addresses. Your SCADA + needs no entry in the PLC project at all; it is a client and simply connects. + +Then Build → the Editor uploads → **Start PLC**. Port 502 only answers while a +program is running. + +### What to copy from this project + +The method transfers even when the plant does not: + +| Take | Why | +|---|---| +| `build.py`'s `REGISTERS` table + `modbus_address()` | Generating the register map from one table is what stops the PLC and SCADA drifting. Change the tags, keep the machinery. | +| `gen_project.py` | The Editor has no flat-`.st` import, and it will not let you type located addresses by hand. This writes them into `project.json`. | +| The `IO_MUX` pattern | One POU owns every located variable; control logic reads globals only. It is what lets the same control code run against a simulation or a real field. | +| `05-tests/verify_modbus.py` | Proves the map against the live PLC before you touch SCADA. | + +--- + +## 3. I need to move this PLC to a different host + +The compiled program is in the container's **writable layer**, so `docker commit`, +never `docker pull`. Full procedure with the real checksums from the 2026-08-19 +move: `../02-environment/MIGRATION.md`. + +--- + +## Where to look when something is wrong + +| Symptom | Look at | +|---|---| +| Port 502 refuses connections | `README.md` — the two conditions. A program must be **running**, and the Editor project must define a Modbus **Server**. | +| SCADA reads plausible but wrong values | `%MW` starts at **HR 1024**, not 0. `register-map.csv` has resolved addresses. | +| The alarm word goes negative | `%QW17` must be read as **unsigned** 16-bit. Bit 15 does not fit a signed INT. | +| Every measurement reads 0 | You are polling `%IW`/`%IX` (FC04/FC02). In the simulation build nothing writes those. Live values are in the `%QW` block. | +| Editor build fails on `VAR_EXTERNAL … has no matching VAR_GLOBAL` | The Editor was open while files were copied in and wrote its cached `project.json` back. `DEPLOY.md` §B2. | +| Console says "Compile only mode — skipping upload" | Compile-only is on; nothing was uploaded. `DEPLOY.md` §B4. | +| Setpoints reverted to unfamiliar values | The runtime restarted and `IO_MUX` re-seeded the defaults. Live tuning is not stored in this repo. `README.md`. | diff --git a/03-plc/README.md b/03-plc/README.md index 8ae2997..b45f357 100644 --- a/03-plc/README.md +++ b/03-plc/README.md @@ -8,14 +8,20 @@ POUs, from one source tree. how it reaches the runtime. ``` +GETTING-STARTED.md start here - modify the program / build a new PLC / move it +VERSIONS.md every version, read from the running system src/*.st the program - concatenated in lexical order build.py builds a flat .st AND generates register-map.csv gen_project.py generates the OpenPLC Editor v4 project (the deploy artefact) register-map.csv GENERATED - the PLC half of the Modbus contract editor-devices/ the GUI-configured half of the Editor project (reference copy) -DEPLOY.md how a program actually reaches the runtime +as-built/ what is ACTUALLY running, copied out of the container +DEPLOY.md how a program reaches the runtime, and every gotcha hit ``` +**New here?** Read `GETTING-STARTED.md` first — it answers "how do I change the +program", "how do I stand up a new PLC" and "how do I move this one" in a page. + ## Build ```bash @@ -223,6 +229,7 @@ changes controller tuning and run-hour accumulation. | Register map | 69 points, **reproduces byte-identically** from `build.py` | | Modbus contract | Verified against the live PLC — addresses, segmentation and signedness all as documented | | Field build | Compiled and verified 2026-08-14 (65/65 points, 11/11 RW). Superseded by the sim build now running | +| Source integrity | **`src/` matches what is deployed.** The container's `program.st` was compared against `build/wrps.st` on 2026-09-02: identical POU structure, **zero** differences in non-declaration lines. See `as-built/README.md`. | ### ⚠️ Two things that are not done diff --git a/03-plc/VERSIONS.md b/03-plc/VERSIONS.md new file mode 100644 index 0000000..24d5045 --- /dev/null +++ b/03-plc/VERSIONS.md @@ -0,0 +1,73 @@ +# Software and versions + +Everything below was **read from the running system on 2026-09-02**, not taken +from documentation. Where a version matters — where a different one behaves +differently — that is said explicitly. + +## The PLC + +| Component | Version | Where it runs | How to check | +|---|---|---|---| +| **OpenPLC Runtime** | **v4.1.10** | `openplc-runtime` container on `yau-sls-poc-lin001` | `docker exec openplc-runtime cat /workdir/VERSION` | +| Docker image | `openplc-runtime-migrated:v4.1.10` | local to the host, **not in any registry** | `docker images` | +| Base OS in container | Debian 12 (bookworm) | | `docker exec openplc-runtime cat /etc/os-release` | +| Python (runtime + plugins) | 3.11.2 | | `docker exec openplc-runtime python3 --version` | +| g++ | 12.2.0 | used to build the PLC program | `docker exec openplc-runtime g++ --version` | +| GNU Make | 4.3 | drives `scripts/Makefile.strucpp` | | +| pymodbus (in the slave plugin) | 3.11.2 | | `docker exec openplc-runtime ./venvs/modbus_slave/bin/pip list` | + +### Runtime plugins + +`/workdir/plugins.conf` — the third field is enabled (1) / disabled (0): + +| Plugin | Enabled | Note | +|---|---|---| +| `modbus_slave` | **1** | This is the one that matters. It is what makes the PLC a Modbus **server** on 502. | +| `modbus_master` | 0 | Correct — the PLC must not poll anyone. | +| `opcua` | 0 | | +| `s7comm` | 0 | | +| `ethercat` | **1** | ⚠️ Enabled for no reason. It came from an `ethercat.json` the Editor project emits by default (the file is 0 bytes). Harmless so far, but it is a plugin running with nothing to do. | + +## The toolchain + +| Component | Version | Notes | +|---|---|---| +| **OpenPLC Editor** | **v4.2.11** (AppImage) | Needs `--no-sandbox` (Electron) and `libfuse2t64`. ⚠️ **Not currently installed anywhere** — see `DEPLOY.md` §0. | +| **STruC++** | **v0.6.2** | The ST → C++ compiler. **Ships only inside the Editor AppImage**; there is no standalone binary. This is the single reason the Editor is unavoidable. | + +## The SCADA + +| Component | Version | Where | +|---|---|---| +| **Yokogawa CI Server** | R1.03 | `yau-poc-cicore1` (`10.0.0.21`) | + +## Versions that actually matter + +**OpenPLC Runtime v3 → v4 is a breaking change, and most guidance you'll find +online is for v3.** + +| | v3 | **v4 (what we run)** | +|---|---|---| +| ST compiler | MatIEC | **STruC++** | +| Accepts a flat `.st` upload | yes | **no** — `scripts/compile.sh` explicitly rejects MatIEC files (`Config0.c`, `glueVars.c`) | +| Web UI on the runtime | yes | **no** — REST API only, port 8443, JWT | +| Editor project format | one file | **a folder** — `project.json` + one file per POU | +| `%MW` Modbus mapping | starts at HR 0 | **starts at HR 1024** (`%QW` occupies 0–1023) | + +That last row is the one that silently produces wrong data rather than an error. +See `register-map.csv`, which carries resolved addresses. + +**The Modbus buffer layout is configuration, not a constant.** From +`core/generated/conf/modbus_slave.json` in the running container: + +```json +"holding_registers": { "qw_count": 1024, "mw_count": 1024, ... } +``` + +`qw_count: 1024` is *why* `%MW0` is holding register 1024. Change that file and +every SCADA address moves. + +**CI Server R1.03 vs R1.05.** One R1.03 behaviour is load-bearing here: an item +import is rejected if two items share an `IO_ADDRESS` (`EQP-E-DUP_ITEM`). The SCADA +design works around it by publishing one unit per register. If you move to R1.05, +re-check that before assuming the workaround is still needed. diff --git a/03-plc/as-built/README.md b/03-plc/as-built/README.md new file mode 100644 index 0000000..07d1b91 --- /dev/null +++ b/03-plc/as-built/README.md @@ -0,0 +1,75 @@ +# as-built — what is actually running + +**Copied out of the live `openplc-runtime` container on 2026-09-02**, from +`/workdir/core/generated/`. This is the compiled artefact set the running PLC was +built from — the ground truth, as opposed to `../src/`, which is the intent. + +> [!WARNING] +> **Do not edit anything in this folder and do not build from it.** It is a +> snapshot for reference and recovery. The editable source is `../src/`. + +## Why keep it + +The running PLC is a **`docker commit` image**. The compiled program lives in the +container's writable layer, not in a volume and not in any registry. If that image +is lost, and the OpenPLC Editor is still unavailable (`../DEPLOY.md` §0), this +folder is the only remaining copy of the compiled form of the program. + +It is also the proof of what is deployed: you can read the C++ the PLC is actually +executing, rather than inferring it from the ST. + +## Contents + +| File | What | +|---|---| +| `program.st` | The ST as the Editor serialised and uploaded it | +| `generated.hpp` | STruC++ declarations for every POU and global | +| `pou_CONTROL.cpp` etc. | One C++ file per POU — the compiled logic | +| `configuration.cpp` | The task configuration: `plc_task`, 100 ms, three program instances | +| `generated_debug.cpp`, `debug-map.json` | Variable maps for the Editor's debugger | +| `program.st.map.json` | Line map from the generated C++ back to `program.st` | +| `strucpp_runtime/include/` | The STruC++ runtime headers the build needs | +| `conf/modbus_slave.json` | **The Modbus buffer layout** — `qw_count: 1024` is why `%MW0` is HR 1024 | +| `conf/ethercat.json` | 0 bytes. Emitted by the Editor by default; it is what leaves the EtherCAT plugin enabled for no reason. | +| `c_blocks.h`, `defines.h` | Small generated headers | + +Built **2026-08-14**. Loaded as `libplc_1786668930820554523.so`. + +## Verified against `../src/` on 2026-09-02 + +`program.st` was compared with `build/wrps.st` generated from `../src/`, after +stripping comments and normalising whitespace: + +``` +POU structure identical - FB_PUMP, FB_DUTY_SELECT, FB_LEVEL_CTRL, + FB_HEADROOM, CONTROL, IO_MUX, SIMULATION, + CONFIGURATION Config0, three task instances +Non-declaration lines 0 differences, in either direction +Declaration lines differ in formatting only - the Editor requires one + declaration per line, and holds VAR_GLOBAL in + project.json rather than in the ST +``` + +**The deployed program is this repo's source.** `../src/` is canonical, and the +running PLC agrees with it. + +## Using this for an emergency change + +If the Editor is unavailable and something must change *now*, the container can +rebuild from these files without any external toolchain — g++ 12.2 and Make are +inside it: + +```bash +# on yau-sls-poc-lin001 +docker exec -it openplc-runtime bash +# edit core/generated/pou_*.cpp +./scripts/compile.sh # rebuilds build/new_libplc.so +``` + +> [!CAUTION] +> **This is break-glass, not a workflow.** You would be editing generated C++, +> which immediately diverges from `../src/`, and the next proper deployment +> silently discards your change. The PLC also stops while it reloads. If you do +> it, record what you changed and port it back into `../src/` the same day. + +Prefer restoring the Editor. See `../DEPLOY.md` §0. diff --git a/03-plc/as-built/c_blocks.h b/03-plc/as-built/c_blocks.h new file mode 100644 index 0000000..fab17ac --- /dev/null +++ b/03-plc/as-built/c_blocks.h @@ -0,0 +1 @@ +// Empty file diff --git a/03-plc/as-built/conf/ethercat.json b/03-plc/as-built/conf/ethercat.json new file mode 100644 index 0000000..e69de29 diff --git a/03-plc/as-built/conf/modbus_slave.json b/03-plc/as-built/conf/modbus_slave.json new file mode 100644 index 0000000..a155ebc --- /dev/null +++ b/03-plc/as-built/conf/modbus_slave.json @@ -0,0 +1,24 @@ +{ + "network_configuration": { + "host": "0.0.0.0", + "port": 502 + }, + "buffer_mapping": { + "holding_registers": { + "qw_count": 1024, + "mw_count": 1024, + "md_count": 1024, + "ml_count": 1024 + }, + "coils": { + "qx_bits": 8192, + "mx_bits": 0 + }, + "discrete_inputs": { + "ix_bits": 8192 + }, + "input_registers": { + "iw_count": 1024 + } + } +} \ No newline at end of file diff --git a/03-plc/as-built/configuration.cpp b/03-plc/as-built/configuration.cpp new file mode 100644 index 0000000..9b3cdac --- /dev/null +++ b/03-plc/as-built/configuration.cpp @@ -0,0 +1,472 @@ +// Generated by STruC++ - IEC 61131-3 Structured Text to C++ Compiler +// Do not edit this file manually. + +#include "generated.hpp" + +namespace strucpp { + +// Library: iec-standard-fb +TON::TON() { + // Initialize variables +} + +void TON::operator()() { + CURRENT_TIME = TIME(); + if (((((STATE == 0)) & (NOT(PREV_IN))) & (IN))) { + STATE = 1; + Q = false; + START_TIME = CURRENT_TIME; + } else { + if ((NOT(IN))) { + ET = 0LL; + Q = false; + STATE = 0; + } else if ((STATE == 1)) { + if (((START_TIME + PT) <= CURRENT_TIME)) { + STATE = 2; + Q = true; + ET = PT; + } else { + ET = CURRENT_TIME - START_TIME; + } + } + } + PREV_IN = IN; +} + + +// Library: oscat-basic +INTEGRATE::INTEGRATE() + : E(true), K(1.0) +{ + // Initialize variables +} + +void INTEGRATE::operator()() { + TX = T_PLC_MS(); + if (!INIT) { + INIT = true; + X_LAST = X; + } else if (E) { + Y = (X + X_LAST) * 0.0005 * TO_REAL(TX - LAST) * K + Y; + X_LAST = X; + } + LAST = TX; +} + +SPEED::SPEED() { + // Initialize variables +} + +void SPEED::operator()() { + YMS = MS + KMH * 0.27777777777778 + KN * 0.5144444 + MH * 0.44704; + YKMH = YMS * 3.6; + YKN = YMS * 1.94384466037535; + YMH = YMS * 2.2369362920544; +} + +IEC_INT CEIL(IEC_REAL X) { + IEC_INT CEIL_result; + CEIL_result = TO_INT(X); + if (CEIL_result < X) { + CEIL_result = CEIL_result + 1; + } + return CEIL_result; +} + +IEC_REAL EXPN(IEC_REAL X, IEC_INT N) { + IEC_REAL EXPN_result; + IEC_BOOL SIGN; + SIGN = ((static_cast(N) >> 15) & 1); + N = ABS(N); + if (((static_cast(N) >> 0) & 1)) { + EXPN_result = X; + } else { + EXPN_result = 1.0; + } + N = SHR(N, 1); + while (N > 0) { + X = X * X; + if (((static_cast(N) >> 0) & 1)) { + EXPN_result = EXPN_result * X; + } + N = SHR(N, 1); + } + if (SIGN) { + EXPN_result = 1.0 / EXPN_result; + } + return EXPN_result; +} + +IEC_REAL OFFSET(IEC_REAL X, IEC_BOOL O1, IEC_BOOL O2, IEC_BOOL O3, IEC_BOOL O4, IEC_BOOL D, IEC_REAL OFFSET_1, IEC_REAL OFFSET_2, IEC_REAL OFFSET_3, IEC_REAL OFFSET_4, IEC_REAL DEFAULT) { + IEC_REAL OFFSET_result; + if (D) { + OFFSET_result = DEFAULT; + } else { + OFFSET_result = X; + } + if (O1) { + OFFSET_result = OFFSET_result + OFFSET_1; + } + if (O2) { + OFFSET_result = OFFSET_result + OFFSET_2; + } + if (O3) { + OFFSET_result = OFFSET_result + OFFSET_3; + } + if (O4) { + OFFSET_result = OFFSET_result + OFFSET_4; + } + return OFFSET_result; +} + +IEC_REAL RND(IEC_REAL X, IEC_INT N) { + IEC_REAL RND_result; + IEC_REAL M; + if (X == 0.0) { + RND_result = 0.0; + } else { + M = EXPN(10.0, N - CEIL(LOG(ABS(X)))); + RND_result = TO_REAL(TO_DINT(X * M)) / M; + } + return RND_result; +} + +IEC_DWORD T_PLC_MS() { + IEC_DWORD T_PLC_MS_result; + IEC_BOOL DEBUG = 0; + IEC_INT N = 0; + IEC_DWORD OFFSET = 0; + IEC_TIME TX; + TX = TIME(); + T_PLC_MS_result = TO_DWORD(TIME_TO_MS(TX)); + if (DEBUG) { + T_PLC_MS_result = ((SHL(T_PLC_MS_result, N)) | (SHL(static_cast(1), N) - 1)) + OFFSET; + } + return T_PLC_MS_result; +} + + +// ============================================================================= +// Located Variables Descriptor Array +// ============================================================================= + +LocatedVar locatedVars[69] = { + { LocatedArea::Input, LocatedSize::Word, 0, 0, {0, 0, 0}, nullptr }, // IW_LIT101 AT %IW0 + { LocatedArea::Input, LocatedSize::Word, 1, 0, {0, 0, 0}, nullptr }, // IW_FIT201 AT %IW1 + { LocatedArea::Input, LocatedSize::Word, 2, 0, {0, 0, 0}, nullptr }, // IW_FIT301 AT %IW2 + { LocatedArea::Input, LocatedSize::Word, 3, 0, {0, 0, 0}, nullptr }, // IW_PIT302 AT %IW3 + { LocatedArea::Input, LocatedSize::Word, 4, 0, {0, 0, 0}, nullptr }, // IW_PIT311 AT %IW4 + { LocatedArea::Input, LocatedSize::Word, 5, 0, {0, 0, 0}, nullptr }, // IW_PIT321 AT %IW5 + { LocatedArea::Input, LocatedSize::Word, 6, 0, {0, 0, 0}, nullptr }, // IW_PIT331 AT %IW6 + { LocatedArea::Input, LocatedSize::Word, 7, 0, {0, 0, 0}, nullptr }, // IW_VE314 AT %IW7 + { LocatedArea::Input, LocatedSize::Word, 8, 0, {0, 0, 0}, nullptr }, // IW_VE324 AT %IW8 + { LocatedArea::Input, LocatedSize::Word, 9, 0, {0, 0, 0}, nullptr }, // IW_VE334 AT %IW9 + { LocatedArea::Input, LocatedSize::Bit, 0, 0, {0, 0, 0}, nullptr }, // IX_LSHH102 AT %IX0.0 + { LocatedArea::Input, LocatedSize::Bit, 0, 1, {0, 0, 0}, nullptr }, // IX_LSLL103 AT %IX0.1 + { LocatedArea::Input, LocatedSize::Bit, 0, 2, {0, 0, 0}, nullptr }, // IX_LSH104 AT %IX0.2 + { LocatedArea::Input, LocatedSize::Bit, 0, 3, {0, 0, 0}, nullptr }, // IX_TE312 AT %IX0.3 + { LocatedArea::Input, LocatedSize::Bit, 0, 4, {0, 0, 0}, nullptr }, // IX_TE322 AT %IX0.4 + { LocatedArea::Input, LocatedSize::Bit, 0, 5, {0, 0, 0}, nullptr }, // IX_TE332 AT %IX0.5 + { LocatedArea::Input, LocatedSize::Bit, 0, 6, {0, 0, 0}, nullptr }, // IX_MSE313 AT %IX0.6 + { LocatedArea::Input, LocatedSize::Bit, 0, 7, {0, 0, 0}, nullptr }, // IX_MSE323 AT %IX0.7 + { LocatedArea::Input, LocatedSize::Bit, 1, 0, {0, 0, 0}, nullptr }, // IX_MSE333 AT %IX1.0 + { LocatedArea::Input, LocatedSize::Bit, 1, 1, {0, 0, 0}, nullptr }, // IX_XA502 AT %IX1.1 + { LocatedArea::Output, LocatedSize::Bit, 0, 0, {0, 0, 0}, nullptr }, // QX_RUNCMD1 AT %QX0.0 + { LocatedArea::Output, LocatedSize::Bit, 0, 1, {0, 0, 0}, nullptr }, // QX_RUNCMD2 AT %QX0.1 + { LocatedArea::Output, LocatedSize::Bit, 0, 2, {0, 0, 0}, nullptr }, // QX_RUNCMD3 AT %QX0.2 + { LocatedArea::Output, LocatedSize::Bit, 0, 3, {0, 0, 0}, nullptr }, // QX_RUNNING1 AT %QX0.3 + { LocatedArea::Output, LocatedSize::Bit, 0, 4, {0, 0, 0}, nullptr }, // QX_RUNNING2 AT %QX0.4 + { LocatedArea::Output, LocatedSize::Bit, 0, 5, {0, 0, 0}, nullptr }, // QX_RUNNING3 AT %QX0.5 + { LocatedArea::Output, LocatedSize::Bit, 0, 6, {0, 0, 0}, nullptr }, // QX_AVAIL1 AT %QX0.6 + { LocatedArea::Output, LocatedSize::Bit, 0, 7, {0, 0, 0}, nullptr }, // QX_AVAIL2 AT %QX0.7 + { LocatedArea::Output, LocatedSize::Bit, 1, 0, {0, 0, 0}, nullptr }, // QX_AVAIL3 AT %QX1.0 + { LocatedArea::Output, LocatedSize::Bit, 1, 1, {0, 0, 0}, nullptr }, // QX_INAUTO AT %QX1.1 + { LocatedArea::Output, LocatedSize::Bit, 1, 2, {0, 0, 0}, nullptr }, // QX_HIGHLEVEL AT %QX1.2 + { LocatedArea::Output, LocatedSize::Bit, 1, 3, {0, 0, 0}, nullptr }, // QX_SPILLACTIVE AT %QX1.3 + { LocatedArea::Output, LocatedSize::Bit, 1, 4, {0, 0, 0}, nullptr }, // QX_TRIPPED1 AT %QX1.4 + { LocatedArea::Output, LocatedSize::Bit, 1, 5, {0, 0, 0}, nullptr }, // QX_TRIPPED2 AT %QX1.5 + { LocatedArea::Output, LocatedSize::Bit, 1, 6, {0, 0, 0}, nullptr }, // QX_TRIPPED3 AT %QX1.6 + { LocatedArea::Output, LocatedSize::Word, 0, 0, {0, 0, 0}, nullptr }, // QW_LEVEL AT %QW0 + { LocatedArea::Output, LocatedSize::Word, 1, 0, {0, 0, 0}, nullptr }, // QW_INFLOW AT %QW1 + { LocatedArea::Output, LocatedSize::Word, 2, 0, {0, 0, 0}, nullptr }, // QW_DISCHARGE AT %QW2 + { LocatedArea::Output, LocatedSize::Word, 3, 0, {0, 0, 0}, nullptr }, // QW_PUMPSRUNNING AT %QW3 + { LocatedArea::Output, LocatedSize::Word, 4, 0, {0, 0, 0}, nullptr }, // QW_SPEED AT %QW4 + { LocatedArea::Output, LocatedSize::Word, 5, 0, {0, 0, 0}, nullptr }, // QW_TIMETOSPILL AT %QW5 + { LocatedArea::Output, LocatedSize::Word, 6, 0, {0, 0, 0}, nullptr }, // QW_TIMETOLSHH AT %QW6 + { LocatedArea::Output, LocatedSize::Word, 7, 0, {0, 0, 0}, nullptr }, // QW_NETACCUM AT %QW7 + { LocatedArea::Output, LocatedSize::Word, 8, 0, {0, 0, 0}, nullptr }, // QW_RUNHOURS1 AT %QW8 + { LocatedArea::Output, LocatedSize::Word, 9, 0, {0, 0, 0}, nullptr }, // QW_RUNHOURS2 AT %QW9 + { LocatedArea::Output, LocatedSize::Word, 10, 0, {0, 0, 0}, nullptr }, // QW_RUNHOURS3 AT %QW10 + { LocatedArea::Output, LocatedSize::Word, 11, 0, {0, 0, 0}, nullptr }, // QW_VOLTOSPILL AT %QW11 + { LocatedArea::Output, LocatedSize::Word, 12, 0, {0, 0, 0}, nullptr }, // QW_STATIONSTATE AT %QW12 + { LocatedArea::Output, LocatedSize::Word, 13, 0, {0, 0, 0}, nullptr }, // QW_PUMPSTATE1 AT %QW13 + { LocatedArea::Output, LocatedSize::Word, 14, 0, {0, 0, 0}, nullptr }, // QW_PUMPSTATE2 AT %QW14 + { LocatedArea::Output, LocatedSize::Word, 15, 0, {0, 0, 0}, nullptr }, // QW_PUMPSTATE3 AT %QW15 + { LocatedArea::Output, LocatedSize::Word, 16, 0, {0, 0, 0}, nullptr }, // QW_DUTYPUMP AT %QW16 + { LocatedArea::Output, LocatedSize::Word, 17, 0, {0, 0, 0}, nullptr }, // QW_ALARMWORD AT %QW17 + { LocatedArea::Output, LocatedSize::Word, 20, 0, {0, 0, 0}, nullptr }, // QW_CMDACK AT %QW20 + { LocatedArea::Memory, LocatedSize::Word, 0, 0, {0, 0, 0}, nullptr }, // MW_MODE AT %MW0 + { LocatedArea::Memory, LocatedSize::Word, 1, 0, {0, 0, 0}, nullptr }, // MW_CMDWORD AT %MW1 + { LocatedArea::Memory, LocatedSize::Word, 2, 0, {0, 0, 0}, nullptr }, // MW_CMDPARAM AT %MW2 + { LocatedArea::Memory, LocatedSize::Word, 3, 0, {0, 0, 0}, nullptr }, // MW_SPLEVEL AT %MW3 + { LocatedArea::Memory, LocatedSize::Word, 4, 0, {0, 0, 0}, nullptr }, // MW_STARTDUTY AT %MW4 + { LocatedArea::Memory, LocatedSize::Word, 5, 0, {0, 0, 0}, nullptr }, // MW_STARTP2 AT %MW5 + { LocatedArea::Memory, LocatedSize::Word, 6, 0, {0, 0, 0}, nullptr }, // MW_STARTP3 AT %MW6 + { LocatedArea::Memory, LocatedSize::Word, 7, 0, {0, 0, 0}, nullptr }, // MW_STOPALL AT %MW7 + { LocatedArea::Memory, LocatedSize::Word, 8, 0, {0, 0, 0}, nullptr }, // MW_HIGHALARM AT %MW8 + { LocatedArea::Memory, LocatedSize::Word, 9, 0, {0, 0, 0}, nullptr }, // MW_MINSPEED AT %MW9 + { LocatedArea::Memory, LocatedSize::Word, 10, 0, {0, 0, 0}, nullptr }, // MW_SERVICEHRS AT %MW10 + { LocatedArea::Memory, LocatedSize::Word, 20, 0, {0, 0, 0}, nullptr }, // MW_SIMINFLOW AT %MW20 + { LocatedArea::Memory, LocatedSize::Word, 21, 0, {0, 0, 0}, nullptr }, // MW_SIMMODE AT %MW21 + { LocatedArea::Memory, LocatedSize::Word, 22, 0, {0, 0, 0}, nullptr }, // MW_SIMRESET AT %MW22 + { LocatedArea::Memory, LocatedSize::Word, 23, 0, {0, 0, 0}, nullptr } // MW_SIMTIMESCALE AT %MW23 +}; + +#ifdef STRUCPP_THREADED +// Canonical storage pointers of the located CONFIGURATION VAR_GLOBALs. +// Populated in the configuration constructor (like locatedVars[] above). +void *locatedGlobals[69] = { + nullptr, // IW_LIT101 AT %IW0 + nullptr, // IW_FIT201 AT %IW1 + nullptr, // IW_FIT301 AT %IW2 + nullptr, // IW_PIT302 AT %IW3 + nullptr, // IW_PIT311 AT %IW4 + nullptr, // IW_PIT321 AT %IW5 + nullptr, // IW_PIT331 AT %IW6 + nullptr, // IW_VE314 AT %IW7 + nullptr, // IW_VE324 AT %IW8 + nullptr, // IW_VE334 AT %IW9 + nullptr, // IX_LSHH102 AT %IX0.0 + nullptr, // IX_LSLL103 AT %IX0.1 + nullptr, // IX_LSH104 AT %IX0.2 + nullptr, // IX_TE312 AT %IX0.3 + nullptr, // IX_TE322 AT %IX0.4 + nullptr, // IX_TE332 AT %IX0.5 + nullptr, // IX_MSE313 AT %IX0.6 + nullptr, // IX_MSE323 AT %IX0.7 + nullptr, // IX_MSE333 AT %IX1.0 + nullptr, // IX_XA502 AT %IX1.1 + nullptr, // QX_RUNCMD1 AT %QX0.0 + nullptr, // QX_RUNCMD2 AT %QX0.1 + nullptr, // QX_RUNCMD3 AT %QX0.2 + nullptr, // QX_RUNNING1 AT %QX0.3 + nullptr, // QX_RUNNING2 AT %QX0.4 + nullptr, // QX_RUNNING3 AT %QX0.5 + nullptr, // QX_AVAIL1 AT %QX0.6 + nullptr, // QX_AVAIL2 AT %QX0.7 + nullptr, // QX_AVAIL3 AT %QX1.0 + nullptr, // QX_INAUTO AT %QX1.1 + nullptr, // QX_HIGHLEVEL AT %QX1.2 + nullptr, // QX_SPILLACTIVE AT %QX1.3 + nullptr, // QX_TRIPPED1 AT %QX1.4 + nullptr, // QX_TRIPPED2 AT %QX1.5 + nullptr, // QX_TRIPPED3 AT %QX1.6 + nullptr, // QW_LEVEL AT %QW0 + nullptr, // QW_INFLOW AT %QW1 + nullptr, // QW_DISCHARGE AT %QW2 + nullptr, // QW_PUMPSRUNNING AT %QW3 + nullptr, // QW_SPEED AT %QW4 + nullptr, // QW_TIMETOSPILL AT %QW5 + nullptr, // QW_TIMETOLSHH AT %QW6 + nullptr, // QW_NETACCUM AT %QW7 + nullptr, // QW_RUNHOURS1 AT %QW8 + nullptr, // QW_RUNHOURS2 AT %QW9 + nullptr, // QW_RUNHOURS3 AT %QW10 + nullptr, // QW_VOLTOSPILL AT %QW11 + nullptr, // QW_STATIONSTATE AT %QW12 + nullptr, // QW_PUMPSTATE1 AT %QW13 + nullptr, // QW_PUMPSTATE2 AT %QW14 + nullptr, // QW_PUMPSTATE3 AT %QW15 + nullptr, // QW_DUTYPUMP AT %QW16 + nullptr, // QW_ALARMWORD AT %QW17 + nullptr, // QW_CMDACK AT %QW20 + nullptr, // MW_MODE AT %MW0 + nullptr, // MW_CMDWORD AT %MW1 + nullptr, // MW_CMDPARAM AT %MW2 + nullptr, // MW_SPLEVEL AT %MW3 + nullptr, // MW_STARTDUTY AT %MW4 + nullptr, // MW_STARTP2 AT %MW5 + nullptr, // MW_STARTP3 AT %MW6 + nullptr, // MW_STOPALL AT %MW7 + nullptr, // MW_HIGHALARM AT %MW8 + nullptr, // MW_MINSPEED AT %MW9 + nullptr, // MW_SERVICEHRS AT %MW10 + nullptr, // MW_SIMINFLOW AT %MW20 + nullptr, // MW_SIMMODE AT %MW21 + nullptr, // MW_SIMRESET AT %MW22 + nullptr // MW_SIMTIMESCALE AT %MW23 +}; + +// C linkage: a host runtime is built once and loads many .so files, so it +// cannot reach namespaced C++ symbols by mangled name portably. +extern "C" void *const *strucpp_get_located_globals(void) { return locatedGlobals; } +extern "C" uint32_t strucpp_get_located_global_count(void) { return locatedGlobalsCount; } +#endif // STRUCPP_THREADED + +Configuration_CONFIG0::Configuration_CONFIG0() + : INST_SIM(&G_SIMCMD_INFLOW, &G_SIMCMD_MODE, &G_SIMCMD_RESET, &G_SIMCMD_TIMESCALE, &G_O_RUNCMD, &G_O_SPEED_X10, &G_SIMACTIVE, &G_SIM_CLEARRESET, &G_SIM_LEVEL_MM, &G_SIM_INFLOW_X10, &G_SIM_DISCH_X10, &G_SIM_MANIFOLDP, &G_SIM_PUMPP, &G_SIM_VIB_X10, &G_SIM_LSHH, &G_SIM_LSLL_WET, &G_SIM_SPILL, &G_SIM_THERMALOK, &G_SIM_SEALLEAK, &G_SIM_MAINSOK),INST_MUX(&IW_LIT101, &IW_FIT201, &IW_FIT301, &IW_PIT302, &IW_PIT311, &IW_PIT321, &IW_PIT331, &IW_VE314, &IW_VE324, &IW_VE334, &IX_LSHH102, &IX_LSLL103, &IX_LSH104, &IX_TE312, &IX_TE322, &IX_TE332, &IX_MSE313, &IX_MSE323, &IX_MSE333, &IX_XA502, &QX_RUNCMD1, &QX_RUNCMD2, &QX_RUNCMD3, &QX_RUNNING1, &QX_RUNNING2, &QX_RUNNING3, &QX_AVAIL1, &QX_AVAIL2, &QX_AVAIL3, &QX_INAUTO, &QX_HIGHLEVEL, &QX_SPILLACTIVE, &QX_TRIPPED1, &QX_TRIPPED2, &QX_TRIPPED3, &QW_LEVEL, &QW_INFLOW, &QW_DISCHARGE, &QW_PUMPSRUNNING, &QW_SPEED, &QW_TIMETOSPILL, &QW_TIMETOLSHH, &QW_NETACCUM, &QW_RUNHOURS1, &QW_RUNHOURS2, &QW_RUNHOURS3, &QW_VOLTOSPILL, &QW_STATIONSTATE, &QW_PUMPSTATE1, &QW_PUMPSTATE2, &QW_PUMPSTATE3, &QW_DUTYPUMP, &QW_ALARMWORD, &QW_CMDACK, &MW_MODE, &MW_CMDWORD, &MW_CMDPARAM, &MW_SPLEVEL, &MW_STARTDUTY, &MW_STARTP2, &MW_STARTP3, &MW_STOPALL, &MW_HIGHALARM, &MW_MINSPEED, &MW_SERVICEHRS, &MW_SIMINFLOW, &MW_SIMMODE, &MW_SIMRESET, &MW_SIMTIMESCALE, &G_LEVELRAW_MM, &G_LEVEL_MM, &G_LEVEL_M, &G_INFLOW_LPS, &G_DISCH_LPS, &G_MANIFOLDP_KPA, &G_PUMPP_KPA, &G_VIB_MMS, &G_LSHH, &G_LSLL_WET, &G_SPILLDETECTED, &G_THERMALOK, &G_SEALLEAK, &G_MAINSOK, &G_CMD_MODE, &G_CMD_WORD, &G_CMD_PARAM, &G_SP_LEVEL, &G_SP_STARTDUTY, &G_SP_STARTP2, &G_SP_STARTP3, &G_SP_STOPALL, &G_SP_HIGHALARM, &G_SP_MINSPEED, &G_SP_SERVICEHRS, &G_O_RUNCMD, &G_O_RUNNING, &G_O_AVAILABLE, &G_O_TRIPPED, &G_O_INAUTO, &G_O_HIGHLEVEL, &G_O_SPILLACTIVE, &G_O_LEVEL_MM, &G_O_INFLOW_X10, &G_O_DISCH_X10, &G_O_PUMPSRUN, &G_O_SPEED_X10, &G_O_TIMETOSPILL, &G_O_TIMETOLSHH, &G_O_NETACCUM, &G_O_RUNHOURS, &G_O_VOLTOSPILL, &G_O_STATIONSTATE, &G_O_PUMPSTATE, &G_O_DUTYPUMP, &G_O_ALARMWORD, &G_O_CMDACK, &DEF_MODE, &DEF_SP_LEVEL, &DEF_START_DUTY, &DEF_START_P2, &DEF_START_P3, &DEF_STOP_ALL, &DEF_HIGH_ALARM, &DEF_MIN_SPEED, &DEF_SERVICE_HRS, &G_SIMACTIVE, &G_SIM_CLEARRESET, &G_SIMCMD_INFLOW, &G_SIMCMD_MODE, &G_SIMCMD_RESET, &G_SIMCMD_TIMESCALE, &G_SIM_LEVEL_MM, &G_SIM_INFLOW_X10, &G_SIM_DISCH_X10, &G_SIM_MANIFOLDP, &G_SIM_PUMPP, &G_SIM_VIB_X10, &G_SIM_LSHH, &G_SIM_LSLL_WET, &G_SIM_SPILL, &G_SIM_THERMALOK, &G_SIM_SEALLEAK, &G_SIM_MAINSOK),INST_CTL(&G_LEVELRAW_MM, &G_LEVEL_MM, &G_LEVEL_M, &G_INFLOW_LPS, &G_DISCH_LPS, &G_PUMPP_KPA, &G_VIB_MMS, &G_LSHH, &G_LSLL_WET, &G_SPILLDETECTED, &G_THERMALOK, &G_SEALLEAK, &G_MAINSOK, &G_CMD_MODE, &G_CMD_WORD, &G_CMD_PARAM, &G_SP_LEVEL, &G_SP_STARTDUTY, &G_SP_STARTP2, &G_SP_STARTP3, &G_SP_STOPALL, &G_SP_HIGHALARM, &G_SP_MINSPEED, &G_SP_SERVICEHRS, &G_O_RUNCMD, &G_O_RUNNING, &G_O_AVAILABLE, &G_O_TRIPPED, &G_O_INAUTO, &G_O_HIGHLEVEL, &G_O_SPILLACTIVE, &G_O_LEVEL_MM, &G_O_INFLOW_X10, &G_O_DISCH_X10, &G_O_PUMPSRUN, &G_O_SPEED_X10, &G_O_TIMETOSPILL, &G_O_TIMETOLSHH, &G_O_NETACCUM, &G_O_RUNHOURS, &G_O_VOLTOSPILL, &G_O_STATIONSTATE, &G_O_PUMPSTATE, &G_O_DUTYPUMP, &G_O_ALARMWORD, &G_O_CMDACK) +{ + // Wire up tasks and resources + task_programs_storage[0] = &INST_SIM; + task_programs_storage[1] = &INST_MUX; + task_programs_storage[2] = &INST_CTL; + tasks_storage[0] = TaskInstance("PLC_TASK", 100000000LL, 0, &task_programs_storage[0], 3); + resources_storage[0] = ResourceInstance("RES0", "PLC", &tasks_storage[0], 1); + // Initialize located variable pointers + locatedVars[0].pointer = IW_LIT101.value.raw_ptr(); + locatedVars[1].pointer = IW_FIT201.value.raw_ptr(); + locatedVars[2].pointer = IW_FIT301.value.raw_ptr(); + locatedVars[3].pointer = IW_PIT302.value.raw_ptr(); + locatedVars[4].pointer = IW_PIT311.value.raw_ptr(); + locatedVars[5].pointer = IW_PIT321.value.raw_ptr(); + locatedVars[6].pointer = IW_PIT331.value.raw_ptr(); + locatedVars[7].pointer = IW_VE314.value.raw_ptr(); + locatedVars[8].pointer = IW_VE324.value.raw_ptr(); + locatedVars[9].pointer = IW_VE334.value.raw_ptr(); + locatedVars[10].pointer = IX_LSHH102.value.raw_ptr(); + locatedVars[11].pointer = IX_LSLL103.value.raw_ptr(); + locatedVars[12].pointer = IX_LSH104.value.raw_ptr(); + locatedVars[13].pointer = IX_TE312.value.raw_ptr(); + locatedVars[14].pointer = IX_TE322.value.raw_ptr(); + locatedVars[15].pointer = IX_TE332.value.raw_ptr(); + locatedVars[16].pointer = IX_MSE313.value.raw_ptr(); + locatedVars[17].pointer = IX_MSE323.value.raw_ptr(); + locatedVars[18].pointer = IX_MSE333.value.raw_ptr(); + locatedVars[19].pointer = IX_XA502.value.raw_ptr(); + locatedVars[20].pointer = QX_RUNCMD1.value.raw_ptr(); + locatedVars[21].pointer = QX_RUNCMD2.value.raw_ptr(); + locatedVars[22].pointer = QX_RUNCMD3.value.raw_ptr(); + locatedVars[23].pointer = QX_RUNNING1.value.raw_ptr(); + locatedVars[24].pointer = QX_RUNNING2.value.raw_ptr(); + locatedVars[25].pointer = QX_RUNNING3.value.raw_ptr(); + locatedVars[26].pointer = QX_AVAIL1.value.raw_ptr(); + locatedVars[27].pointer = QX_AVAIL2.value.raw_ptr(); + locatedVars[28].pointer = QX_AVAIL3.value.raw_ptr(); + locatedVars[29].pointer = QX_INAUTO.value.raw_ptr(); + locatedVars[30].pointer = QX_HIGHLEVEL.value.raw_ptr(); + locatedVars[31].pointer = QX_SPILLACTIVE.value.raw_ptr(); + locatedVars[32].pointer = QX_TRIPPED1.value.raw_ptr(); + locatedVars[33].pointer = QX_TRIPPED2.value.raw_ptr(); + locatedVars[34].pointer = QX_TRIPPED3.value.raw_ptr(); + locatedVars[35].pointer = QW_LEVEL.value.raw_ptr(); + locatedVars[36].pointer = QW_INFLOW.value.raw_ptr(); + locatedVars[37].pointer = QW_DISCHARGE.value.raw_ptr(); + locatedVars[38].pointer = QW_PUMPSRUNNING.value.raw_ptr(); + locatedVars[39].pointer = QW_SPEED.value.raw_ptr(); + locatedVars[40].pointer = QW_TIMETOSPILL.value.raw_ptr(); + locatedVars[41].pointer = QW_TIMETOLSHH.value.raw_ptr(); + locatedVars[42].pointer = QW_NETACCUM.value.raw_ptr(); + locatedVars[43].pointer = QW_RUNHOURS1.value.raw_ptr(); + locatedVars[44].pointer = QW_RUNHOURS2.value.raw_ptr(); + locatedVars[45].pointer = QW_RUNHOURS3.value.raw_ptr(); + locatedVars[46].pointer = QW_VOLTOSPILL.value.raw_ptr(); + locatedVars[47].pointer = QW_STATIONSTATE.value.raw_ptr(); + locatedVars[48].pointer = QW_PUMPSTATE1.value.raw_ptr(); + locatedVars[49].pointer = QW_PUMPSTATE2.value.raw_ptr(); + locatedVars[50].pointer = QW_PUMPSTATE3.value.raw_ptr(); + locatedVars[51].pointer = QW_DUTYPUMP.value.raw_ptr(); + locatedVars[52].pointer = QW_ALARMWORD.value.raw_ptr(); + locatedVars[53].pointer = QW_CMDACK.value.raw_ptr(); + locatedVars[54].pointer = MW_MODE.value.raw_ptr(); + locatedVars[55].pointer = MW_CMDWORD.value.raw_ptr(); + locatedVars[56].pointer = MW_CMDPARAM.value.raw_ptr(); + locatedVars[57].pointer = MW_SPLEVEL.value.raw_ptr(); + locatedVars[58].pointer = MW_STARTDUTY.value.raw_ptr(); + locatedVars[59].pointer = MW_STARTP2.value.raw_ptr(); + locatedVars[60].pointer = MW_STARTP3.value.raw_ptr(); + locatedVars[61].pointer = MW_STOPALL.value.raw_ptr(); + locatedVars[62].pointer = MW_HIGHALARM.value.raw_ptr(); + locatedVars[63].pointer = MW_MINSPEED.value.raw_ptr(); + locatedVars[64].pointer = MW_SERVICEHRS.value.raw_ptr(); + locatedVars[65].pointer = MW_SIMINFLOW.value.raw_ptr(); + locatedVars[66].pointer = MW_SIMMODE.value.raw_ptr(); + locatedVars[67].pointer = MW_SIMRESET.value.raw_ptr(); + locatedVars[68].pointer = MW_SIMTIMESCALE.value.raw_ptr(); +#ifdef STRUCPP_THREADED + // Initialize located-global pointers + locatedGlobals[0] = IW_LIT101.value.raw_ptr(); + locatedGlobals[1] = IW_FIT201.value.raw_ptr(); + locatedGlobals[2] = IW_FIT301.value.raw_ptr(); + locatedGlobals[3] = IW_PIT302.value.raw_ptr(); + locatedGlobals[4] = IW_PIT311.value.raw_ptr(); + locatedGlobals[5] = IW_PIT321.value.raw_ptr(); + locatedGlobals[6] = IW_PIT331.value.raw_ptr(); + locatedGlobals[7] = IW_VE314.value.raw_ptr(); + locatedGlobals[8] = IW_VE324.value.raw_ptr(); + locatedGlobals[9] = IW_VE334.value.raw_ptr(); + locatedGlobals[10] = IX_LSHH102.value.raw_ptr(); + locatedGlobals[11] = IX_LSLL103.value.raw_ptr(); + locatedGlobals[12] = IX_LSH104.value.raw_ptr(); + locatedGlobals[13] = IX_TE312.value.raw_ptr(); + locatedGlobals[14] = IX_TE322.value.raw_ptr(); + locatedGlobals[15] = IX_TE332.value.raw_ptr(); + locatedGlobals[16] = IX_MSE313.value.raw_ptr(); + locatedGlobals[17] = IX_MSE323.value.raw_ptr(); + locatedGlobals[18] = IX_MSE333.value.raw_ptr(); + locatedGlobals[19] = IX_XA502.value.raw_ptr(); + locatedGlobals[20] = QX_RUNCMD1.value.raw_ptr(); + locatedGlobals[21] = QX_RUNCMD2.value.raw_ptr(); + locatedGlobals[22] = QX_RUNCMD3.value.raw_ptr(); + locatedGlobals[23] = QX_RUNNING1.value.raw_ptr(); + locatedGlobals[24] = QX_RUNNING2.value.raw_ptr(); + locatedGlobals[25] = QX_RUNNING3.value.raw_ptr(); + locatedGlobals[26] = QX_AVAIL1.value.raw_ptr(); + locatedGlobals[27] = QX_AVAIL2.value.raw_ptr(); + locatedGlobals[28] = QX_AVAIL3.value.raw_ptr(); + locatedGlobals[29] = QX_INAUTO.value.raw_ptr(); + locatedGlobals[30] = QX_HIGHLEVEL.value.raw_ptr(); + locatedGlobals[31] = QX_SPILLACTIVE.value.raw_ptr(); + locatedGlobals[32] = QX_TRIPPED1.value.raw_ptr(); + locatedGlobals[33] = QX_TRIPPED2.value.raw_ptr(); + locatedGlobals[34] = QX_TRIPPED3.value.raw_ptr(); + locatedGlobals[35] = QW_LEVEL.value.raw_ptr(); + locatedGlobals[36] = QW_INFLOW.value.raw_ptr(); + locatedGlobals[37] = QW_DISCHARGE.value.raw_ptr(); + locatedGlobals[38] = QW_PUMPSRUNNING.value.raw_ptr(); + locatedGlobals[39] = QW_SPEED.value.raw_ptr(); + locatedGlobals[40] = QW_TIMETOSPILL.value.raw_ptr(); + locatedGlobals[41] = QW_TIMETOLSHH.value.raw_ptr(); + locatedGlobals[42] = QW_NETACCUM.value.raw_ptr(); + locatedGlobals[43] = QW_RUNHOURS1.value.raw_ptr(); + locatedGlobals[44] = QW_RUNHOURS2.value.raw_ptr(); + locatedGlobals[45] = QW_RUNHOURS3.value.raw_ptr(); + locatedGlobals[46] = QW_VOLTOSPILL.value.raw_ptr(); + locatedGlobals[47] = QW_STATIONSTATE.value.raw_ptr(); + locatedGlobals[48] = QW_PUMPSTATE1.value.raw_ptr(); + locatedGlobals[49] = QW_PUMPSTATE2.value.raw_ptr(); + locatedGlobals[50] = QW_PUMPSTATE3.value.raw_ptr(); + locatedGlobals[51] = QW_DUTYPUMP.value.raw_ptr(); + locatedGlobals[52] = QW_ALARMWORD.value.raw_ptr(); + locatedGlobals[53] = QW_CMDACK.value.raw_ptr(); + locatedGlobals[54] = MW_MODE.value.raw_ptr(); + locatedGlobals[55] = MW_CMDWORD.value.raw_ptr(); + locatedGlobals[56] = MW_CMDPARAM.value.raw_ptr(); + locatedGlobals[57] = MW_SPLEVEL.value.raw_ptr(); + locatedGlobals[58] = MW_STARTDUTY.value.raw_ptr(); + locatedGlobals[59] = MW_STARTP2.value.raw_ptr(); + locatedGlobals[60] = MW_STARTP3.value.raw_ptr(); + locatedGlobals[61] = MW_STOPALL.value.raw_ptr(); + locatedGlobals[62] = MW_HIGHALARM.value.raw_ptr(); + locatedGlobals[63] = MW_MINSPEED.value.raw_ptr(); + locatedGlobals[64] = MW_SERVICEHRS.value.raw_ptr(); + locatedGlobals[65] = MW_SIMINFLOW.value.raw_ptr(); + locatedGlobals[66] = MW_SIMMODE.value.raw_ptr(); + locatedGlobals[67] = MW_SIMRESET.value.raw_ptr(); + locatedGlobals[68] = MW_SIMTIMESCALE.value.raw_ptr(); +#endif +} + +const char* Configuration_CONFIG0::get_name() const { + return "CONFIG0"; +} + +ResourceInstance* Configuration_CONFIG0::get_resources() { + return resources_storage; +} + +size_t Configuration_CONFIG0::get_resource_count() const { + return 1; +} + +} // namespace strucpp \ No newline at end of file diff --git a/03-plc/as-built/debug-map.json b/03-plc/as-built/debug-map.json new file mode 100644 index 0000000..ef39555 --- /dev/null +++ b/03-plc/as-built/debug-map.json @@ -0,0 +1,3372 @@ +{ + "version": 2, + "md5": "5f0b241a708123a8744659c85e96e24c", + "typeTags": { + "BOOL": 0, + "SINT": 1, + "USINT": 2, + "INT": 3, + "UINT": 4, + "DINT": 5, + "UDINT": 6, + "LINT": 7, + "ULINT": 8, + "REAL": 9, + "LREAL": 10, + "BYTE": 11, + "WORD": 12, + "DWORD": 13, + "LWORD": 14, + "TIME": 15, + "DATE": 16, + "TOD": 17, + "DT": 18, + "STRING": 19, + "WSTRING": 20 + }, + "arrays": [ + { + "index": 0, + "count": 177 + }, + { + "index": 1, + "count": 46 + }, + { + "index": 2, + "count": 1 + }, + { + "index": 3, + "count": 251 + } + ], + "leaves": [ + { + "arrayIdx": 0, + "elemIdx": 0, + "path": "CFG_AREA_M2", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 1, + "path": "CFG_SPILL_M", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 2, + "path": "CFG_LSHH_M", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 3, + "path": "CFG_MIN_HZ", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 4, + "path": "CFG_MAX_HZ", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 5, + "path": "DEF_MODE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 6, + "path": "DEF_SP_LEVEL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 7, + "path": "DEF_START_DUTY", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 8, + "path": "DEF_START_P2", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 9, + "path": "DEF_START_P3", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 10, + "path": "DEF_STOP_ALL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 11, + "path": "DEF_HIGH_ALARM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 12, + "path": "DEF_MIN_SPEED", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 13, + "path": "DEF_SERVICE_HRS", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 14, + "path": "CFG_NO_TIME", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 15, + "path": "IW_LIT101", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 16, + "path": "IW_FIT201", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 17, + "path": "IW_FIT301", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 18, + "path": "IW_PIT302", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 19, + "path": "IW_PIT311", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 20, + "path": "IW_PIT321", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 21, + "path": "IW_PIT331", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 22, + "path": "IW_VE314", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 23, + "path": "IW_VE324", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 24, + "path": "IW_VE334", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 25, + "path": "IX_LSHH102", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 26, + "path": "IX_LSLL103", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 27, + "path": "IX_LSH104", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 28, + "path": "IX_TE312", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 29, + "path": "IX_TE322", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 30, + "path": "IX_TE332", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 31, + "path": "IX_MSE313", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 32, + "path": "IX_MSE323", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 33, + "path": "IX_MSE333", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 34, + "path": "IX_XA502", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 35, + "path": "QX_RUNCMD1", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 36, + "path": "QX_RUNCMD2", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 37, + "path": "QX_RUNCMD3", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 38, + "path": "QX_RUNNING1", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 39, + "path": "QX_RUNNING2", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 40, + "path": "QX_RUNNING3", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 41, + "path": "QX_AVAIL1", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 42, + "path": "QX_AVAIL2", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 43, + "path": "QX_AVAIL3", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 44, + "path": "QX_INAUTO", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 45, + "path": "QX_HIGHLEVEL", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 46, + "path": "QX_SPILLACTIVE", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 47, + "path": "QX_TRIPPED1", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 48, + "path": "QX_TRIPPED2", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 49, + "path": "QX_TRIPPED3", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 50, + "path": "QW_LEVEL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 51, + "path": "QW_INFLOW", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 52, + "path": "QW_DISCHARGE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 53, + "path": "QW_PUMPSRUNNING", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 54, + "path": "QW_SPEED", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 55, + "path": "QW_TIMETOSPILL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 56, + "path": "QW_TIMETOLSHH", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 57, + "path": "QW_NETACCUM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 58, + "path": "QW_RUNHOURS1", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 59, + "path": "QW_RUNHOURS2", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 60, + "path": "QW_RUNHOURS3", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 61, + "path": "QW_VOLTOSPILL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 62, + "path": "QW_STATIONSTATE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 63, + "path": "QW_PUMPSTATE1", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 64, + "path": "QW_PUMPSTATE2", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 65, + "path": "QW_PUMPSTATE3", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 66, + "path": "QW_DUTYPUMP", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 67, + "path": "QW_ALARMWORD", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 68, + "path": "QW_CMDACK", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 69, + "path": "MW_MODE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 70, + "path": "MW_CMDWORD", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 71, + "path": "MW_CMDPARAM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 72, + "path": "MW_SPLEVEL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 73, + "path": "MW_STARTDUTY", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 74, + "path": "MW_STARTP2", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 75, + "path": "MW_STARTP3", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 76, + "path": "MW_STOPALL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 77, + "path": "MW_HIGHALARM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 78, + "path": "MW_MINSPEED", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 79, + "path": "MW_SERVICEHRS", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 80, + "path": "MW_SIMINFLOW", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 81, + "path": "MW_SIMMODE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 82, + "path": "MW_SIMRESET", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 83, + "path": "MW_SIMTIMESCALE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 84, + "path": "G_LEVELRAW_MM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 85, + "path": "G_LEVEL_MM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 86, + "path": "G_LEVEL_M", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 87, + "path": "G_INFLOW_LPS", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 88, + "path": "G_DISCH_LPS", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 89, + "path": "G_MANIFOLDP_KPA", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 90, + "path": "G_PUMPP_KPA[1]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 91, + "path": "G_PUMPP_KPA[2]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 92, + "path": "G_PUMPP_KPA[3]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 93, + "path": "G_VIB_MMS[1]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 94, + "path": "G_VIB_MMS[2]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 95, + "path": "G_VIB_MMS[3]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 0, + "elemIdx": 96, + "path": "G_LSHH", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 97, + "path": "G_LSLL_WET", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 98, + "path": "G_SPILLDETECTED", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 99, + "path": "G_THERMALOK[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 100, + "path": "G_THERMALOK[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 101, + "path": "G_THERMALOK[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 102, + "path": "G_SEALLEAK[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 103, + "path": "G_SEALLEAK[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 104, + "path": "G_SEALLEAK[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 105, + "path": "G_MAINSOK", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 106, + "path": "G_CMD_MODE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 107, + "path": "G_CMD_WORD", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 108, + "path": "G_CMD_PARAM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 109, + "path": "G_SP_LEVEL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 110, + "path": "G_SP_STARTDUTY", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 111, + "path": "G_SP_STARTP2", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 112, + "path": "G_SP_STARTP3", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 113, + "path": "G_SP_STOPALL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 114, + "path": "G_SP_HIGHALARM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 115, + "path": "G_SP_MINSPEED", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 116, + "path": "G_SP_SERVICEHRS", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 117, + "path": "G_O_RUNCMD[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 118, + "path": "G_O_RUNCMD[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 119, + "path": "G_O_RUNCMD[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 120, + "path": "G_O_RUNNING[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 121, + "path": "G_O_RUNNING[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 122, + "path": "G_O_RUNNING[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 123, + "path": "G_O_AVAILABLE[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 124, + "path": "G_O_AVAILABLE[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 125, + "path": "G_O_AVAILABLE[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 126, + "path": "G_O_TRIPPED[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 127, + "path": "G_O_TRIPPED[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 128, + "path": "G_O_TRIPPED[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 129, + "path": "G_O_INAUTO", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 130, + "path": "G_O_HIGHLEVEL", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 131, + "path": "G_O_SPILLACTIVE", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 132, + "path": "G_O_LEVEL_MM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 133, + "path": "G_O_INFLOW_X10", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 134, + "path": "G_O_DISCH_X10", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 135, + "path": "G_O_PUMPSRUN", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 136, + "path": "G_O_SPEED_X10", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 137, + "path": "G_O_TIMETOSPILL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 138, + "path": "G_O_TIMETOLSHH", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 139, + "path": "G_O_NETACCUM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 140, + "path": "G_O_RUNHOURS[1]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 141, + "path": "G_O_RUNHOURS[2]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 142, + "path": "G_O_RUNHOURS[3]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 143, + "path": "G_O_VOLTOSPILL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 144, + "path": "G_O_STATIONSTATE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 145, + "path": "G_O_PUMPSTATE[1]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 146, + "path": "G_O_PUMPSTATE[2]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 147, + "path": "G_O_PUMPSTATE[3]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 148, + "path": "G_O_DUTYPUMP", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 149, + "path": "G_O_ALARMWORD", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 150, + "path": "G_O_CMDACK", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 151, + "path": "G_SIMACTIVE", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 152, + "path": "G_SIM_CLEARRESET", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 153, + "path": "G_SIMCMD_INFLOW", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 154, + "path": "G_SIMCMD_MODE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 155, + "path": "G_SIMCMD_RESET", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 156, + "path": "G_SIMCMD_TIMESCALE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 157, + "path": "G_SIM_LEVEL_MM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 158, + "path": "G_SIM_INFLOW_X10", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 159, + "path": "G_SIM_DISCH_X10", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 160, + "path": "G_SIM_MANIFOLDP", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 161, + "path": "G_SIM_PUMPP[1]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 162, + "path": "G_SIM_PUMPP[2]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 163, + "path": "G_SIM_PUMPP[3]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 164, + "path": "G_SIM_VIB_X10[1]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 165, + "path": "G_SIM_VIB_X10[2]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 166, + "path": "G_SIM_VIB_X10[3]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 0, + "elemIdx": 167, + "path": "G_SIM_LSHH", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 168, + "path": "G_SIM_LSLL_WET", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 169, + "path": "G_SIM_SPILL", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 170, + "path": "G_SIM_THERMALOK[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 171, + "path": "G_SIM_THERMALOK[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 172, + "path": "G_SIM_THERMALOK[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 173, + "path": "G_SIM_SEALLEAK[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 174, + "path": "G_SIM_SEALLEAK[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 175, + "path": "G_SIM_SEALLEAK[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 0, + "elemIdx": 176, + "path": "G_SIM_MAINSOK", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 1, + "elemIdx": 0, + "path": "INST_SIM.SCAN_S", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 1, + "path": "INST_SIM.AREA_M2", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 2, + "path": "INST_SIM.SPILL_M", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 3, + "path": "INST_SIM.LSHH_M", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 4, + "path": "INST_SIM.LSLL_M", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 5, + "path": "INST_SIM.START_DLY_S", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 6, + "path": "INST_SIM.HZ_LO", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 7, + "path": "INST_SIM.HZ_HI", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 8, + "path": "INST_SIM.Q_LO", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 9, + "path": "INST_SIM.Q_HI", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 10, + "path": "INST_SIM.P_IDLE", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 11, + "path": "INST_SIM.P_BASE", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 12, + "path": "INST_SIM.P_PER_LPS", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 13, + "path": "INST_SIM.VIB_IDLE", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 14, + "path": "INST_SIM.VIB_BASE", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 15, + "path": "INST_SIM.VIB_PER_LPS", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 16, + "path": "INST_SIM.INIT", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 1, + "elemIdx": 17, + "path": "INST_SIM.VOLUME_M3", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 18, + "path": "INST_SIM.LEVEL_M", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 19, + "path": "INST_SIM.INFLOW_LPS", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 20, + "path": "INST_SIM.SUMFLOW_LPS", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 21, + "path": "INST_SIM.SIMCLOCK_S", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 22, + "path": "INST_SIM.STARTDLY_S[1]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 23, + "path": "INST_SIM.STARTDLY_S[2]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 24, + "path": "INST_SIM.STARTDLY_S[3]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 25, + "path": "INST_SIM.DELIVERING[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 1, + "elemIdx": 26, + "path": "INST_SIM.DELIVERING[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 1, + "elemIdx": 27, + "path": "INST_SIM.DELIVERING[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 1, + "elemIdx": 28, + "path": "INST_SIM.FLOW_LPS[1]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 29, + "path": "INST_SIM.FLOW_LPS[2]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 30, + "path": "INST_SIM.FLOW_LPS[3]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 31, + "path": "INST_SIM.PRESS_KPA[1]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 32, + "path": "INST_SIM.PRESS_KPA[2]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 33, + "path": "INST_SIM.PRESS_KPA[3]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 34, + "path": "INST_SIM.VIB_MMS[1]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 35, + "path": "INST_SIM.VIB_MMS[2]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 36, + "path": "INST_SIM.VIB_MMS[3]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 37, + "path": "INST_SIM.TIMESCALE", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 38, + "path": "INST_SIM.DT_S", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 39, + "path": "INST_SIM.SPEED_HZ", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 40, + "path": "INST_SIM.UNITQ_LPS", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 41, + "path": "INST_SIM.DERATE", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 42, + "path": "INST_SIM.NDELIVERING", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 1, + "elemIdx": 43, + "path": "INST_SIM.I", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 1, + "elemIdx": 44, + "path": "INST_SIM.RND", + "type": "DINT", + "size": 4 + }, + { + "arrayIdx": 1, + "elemIdx": 45, + "path": "INST_SIM.NOISE", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 2, + "elemIdx": 0, + "path": "INST_MUX.SEEDED", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 0, + "path": "INST_CTL.SPILL_MM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 1, + "path": "INST_CTL.LEVEL_MAX_MM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 2, + "path": "INST_CTL.HARD_MIN_HZ", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 3, + "path": "INST_CTL.HARD_MAX_HZ", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 4, + "path": "INST_CTL.PUMP1.RUNREQUEST", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 5, + "path": "INST_CTL.PUMP1.SPEEDREF", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 6, + "path": "INST_CTL.PUMP1.THERMALOK", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 7, + "path": "INST_CTL.PUMP1.SEALLEAK", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 8, + "path": "INST_CTL.PUMP1.VIBRATION", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 9, + "path": "INST_CTL.PUMP1.DISCHPRESSURE", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 10, + "path": "INST_CTL.PUMP1.RESETTRIP", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 11, + "path": "INST_CTL.PUMP1.LOCKOUT", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 12, + "path": "INST_CTL.PUMP1.MINOFFBYPASS", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 13, + "path": "INST_CTL.PUMP1.SERVICEINTERVAL", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 14, + "path": "INST_CTL.PUMP1.RESETHOURS", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 15, + "path": "INST_CTL.PUMP1.RUNCMD", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 16, + "path": "INST_CTL.PUMP1.RUNNING", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 17, + "path": "INST_CTL.PUMP1.AVAILABLE", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 18, + "path": "INST_CTL.PUMP1.TRIPPED", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 19, + "path": "INST_CTL.PUMP1.STATE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 20, + "path": "INST_CTL.PUMP1.RUNHOURS", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 21, + "path": "INST_CTL.PUMP1.SERVICEDUE", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 22, + "path": "INST_CTL.PUMP1.VIBALARM", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 23, + "path": "INST_CTL.PUMP1.SEALALARM", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 24, + "path": "INST_CTL.PUMP1.SCAN_S", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 25, + "path": "INST_CTL.PUMP1.VIB_ALARM", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 26, + "path": "INST_CTL.PUMP1.VIB_TRIP", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 27, + "path": "INST_CTL.PUMP1.NOFLOW_KPA", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 28, + "path": "INST_CTL.PUMP1.MINRUNTMR.IN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 29, + "path": "INST_CTL.PUMP1.MINRUNTMR.PT", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 30, + "path": "INST_CTL.PUMP1.MINRUNTMR.Q", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 31, + "path": "INST_CTL.PUMP1.MINRUNTMR.ET", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 32, + "path": "INST_CTL.PUMP1.MINOFFTMR.IN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 33, + "path": "INST_CTL.PUMP1.MINOFFTMR.PT", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 34, + "path": "INST_CTL.PUMP1.MINOFFTMR.Q", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 35, + "path": "INST_CTL.PUMP1.MINOFFTMR.ET", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 36, + "path": "INST_CTL.PUMP1.NOFLOWTMR.IN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 37, + "path": "INST_CTL.PUMP1.NOFLOWTMR.PT", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 38, + "path": "INST_CTL.PUMP1.NOFLOWTMR.Q", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 39, + "path": "INST_CTL.PUMP1.NOFLOWTMR.ET", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 40, + "path": "INST_CTL.PUMP1.HASRUN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 41, + "path": "INST_CTL.PUMP1.STARTOK", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 42, + "path": "INST_CTL.PUMP2.RUNREQUEST", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 43, + "path": "INST_CTL.PUMP2.SPEEDREF", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 44, + "path": "INST_CTL.PUMP2.THERMALOK", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 45, + "path": "INST_CTL.PUMP2.SEALLEAK", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 46, + "path": "INST_CTL.PUMP2.VIBRATION", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 47, + "path": "INST_CTL.PUMP2.DISCHPRESSURE", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 48, + "path": "INST_CTL.PUMP2.RESETTRIP", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 49, + "path": "INST_CTL.PUMP2.LOCKOUT", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 50, + "path": "INST_CTL.PUMP2.MINOFFBYPASS", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 51, + "path": "INST_CTL.PUMP2.SERVICEINTERVAL", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 52, + "path": "INST_CTL.PUMP2.RESETHOURS", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 53, + "path": "INST_CTL.PUMP2.RUNCMD", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 54, + "path": "INST_CTL.PUMP2.RUNNING", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 55, + "path": "INST_CTL.PUMP2.AVAILABLE", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 56, + "path": "INST_CTL.PUMP2.TRIPPED", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 57, + "path": "INST_CTL.PUMP2.STATE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 58, + "path": "INST_CTL.PUMP2.RUNHOURS", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 59, + "path": "INST_CTL.PUMP2.SERVICEDUE", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 60, + "path": "INST_CTL.PUMP2.VIBALARM", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 61, + "path": "INST_CTL.PUMP2.SEALALARM", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 62, + "path": "INST_CTL.PUMP2.SCAN_S", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 63, + "path": "INST_CTL.PUMP2.VIB_ALARM", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 64, + "path": "INST_CTL.PUMP2.VIB_TRIP", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 65, + "path": "INST_CTL.PUMP2.NOFLOW_KPA", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 66, + "path": "INST_CTL.PUMP2.MINRUNTMR.IN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 67, + "path": "INST_CTL.PUMP2.MINRUNTMR.PT", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 68, + "path": "INST_CTL.PUMP2.MINRUNTMR.Q", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 69, + "path": "INST_CTL.PUMP2.MINRUNTMR.ET", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 70, + "path": "INST_CTL.PUMP2.MINOFFTMR.IN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 71, + "path": "INST_CTL.PUMP2.MINOFFTMR.PT", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 72, + "path": "INST_CTL.PUMP2.MINOFFTMR.Q", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 73, + "path": "INST_CTL.PUMP2.MINOFFTMR.ET", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 74, + "path": "INST_CTL.PUMP2.NOFLOWTMR.IN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 75, + "path": "INST_CTL.PUMP2.NOFLOWTMR.PT", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 76, + "path": "INST_CTL.PUMP2.NOFLOWTMR.Q", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 77, + "path": "INST_CTL.PUMP2.NOFLOWTMR.ET", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 78, + "path": "INST_CTL.PUMP2.HASRUN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 79, + "path": "INST_CTL.PUMP2.STARTOK", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 80, + "path": "INST_CTL.PUMP3.RUNREQUEST", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 81, + "path": "INST_CTL.PUMP3.SPEEDREF", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 82, + "path": "INST_CTL.PUMP3.THERMALOK", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 83, + "path": "INST_CTL.PUMP3.SEALLEAK", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 84, + "path": "INST_CTL.PUMP3.VIBRATION", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 85, + "path": "INST_CTL.PUMP3.DISCHPRESSURE", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 86, + "path": "INST_CTL.PUMP3.RESETTRIP", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 87, + "path": "INST_CTL.PUMP3.LOCKOUT", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 88, + "path": "INST_CTL.PUMP3.MINOFFBYPASS", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 89, + "path": "INST_CTL.PUMP3.SERVICEINTERVAL", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 90, + "path": "INST_CTL.PUMP3.RESETHOURS", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 91, + "path": "INST_CTL.PUMP3.RUNCMD", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 92, + "path": "INST_CTL.PUMP3.RUNNING", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 93, + "path": "INST_CTL.PUMP3.AVAILABLE", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 94, + "path": "INST_CTL.PUMP3.TRIPPED", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 95, + "path": "INST_CTL.PUMP3.STATE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 96, + "path": "INST_CTL.PUMP3.RUNHOURS", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 97, + "path": "INST_CTL.PUMP3.SERVICEDUE", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 98, + "path": "INST_CTL.PUMP3.VIBALARM", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 99, + "path": "INST_CTL.PUMP3.SEALALARM", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 100, + "path": "INST_CTL.PUMP3.SCAN_S", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 101, + "path": "INST_CTL.PUMP3.VIB_ALARM", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 102, + "path": "INST_CTL.PUMP3.VIB_TRIP", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 103, + "path": "INST_CTL.PUMP3.NOFLOW_KPA", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 104, + "path": "INST_CTL.PUMP3.MINRUNTMR.IN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 105, + "path": "INST_CTL.PUMP3.MINRUNTMR.PT", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 106, + "path": "INST_CTL.PUMP3.MINRUNTMR.Q", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 107, + "path": "INST_CTL.PUMP3.MINRUNTMR.ET", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 108, + "path": "INST_CTL.PUMP3.MINOFFTMR.IN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 109, + "path": "INST_CTL.PUMP3.MINOFFTMR.PT", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 110, + "path": "INST_CTL.PUMP3.MINOFFTMR.Q", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 111, + "path": "INST_CTL.PUMP3.MINOFFTMR.ET", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 112, + "path": "INST_CTL.PUMP3.NOFLOWTMR.IN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 113, + "path": "INST_CTL.PUMP3.NOFLOWTMR.PT", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 114, + "path": "INST_CTL.PUMP3.NOFLOWTMR.Q", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 115, + "path": "INST_CTL.PUMP3.NOFLOWTMR.ET", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 116, + "path": "INST_CTL.PUMP3.HASRUN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 117, + "path": "INST_CTL.PUMP3.STARTOK", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 118, + "path": "INST_CTL.DUTY.AVAILABLE[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 119, + "path": "INST_CTL.DUTY.AVAILABLE[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 120, + "path": "INST_CTL.DUTY.AVAILABLE[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 121, + "path": "INST_CTL.DUTY.RUNHOURS[1]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 122, + "path": "INST_CTL.DUTY.RUNHOURS[2]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 123, + "path": "INST_CTL.DUTY.RUNHOURS[3]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 124, + "path": "INST_CTL.DUTY.SERVICEDUE[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 125, + "path": "INST_CTL.DUTY.SERVICEDUE[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 126, + "path": "INST_CTL.DUTY.SERVICEDUE[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 127, + "path": "INST_CTL.DUTY.RUNNINGNOW[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 128, + "path": "INST_CTL.DUTY.RUNNINGNOW[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 129, + "path": "INST_CTL.DUTY.RUNNINGNOW[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 130, + "path": "INST_CTL.DUTY.PUMPSREQUIRED", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 131, + "path": "INST_CTL.DUTY.RUNREQUEST[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 132, + "path": "INST_CTL.DUTY.RUNREQUEST[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 133, + "path": "INST_CTL.DUTY.RUNREQUEST[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 134, + "path": "INST_CTL.DUTY.DUTYPUMP", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 135, + "path": "INST_CTL.DUTY.SERVICE_PENALTY", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 136, + "path": "INST_CTL.DUTY.RANK[1]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 137, + "path": "INST_CTL.DUTY.RANK[2]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 138, + "path": "INST_CTL.DUTY.RANK[3]", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 139, + "path": "INST_CTL.DUTY.USED[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 140, + "path": "INST_CTL.DUTY.USED[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 141, + "path": "INST_CTL.DUTY.USED[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 142, + "path": "INST_CTL.DUTY.SEL[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 143, + "path": "INST_CTL.DUTY.SEL[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 144, + "path": "INST_CTL.DUTY.SEL[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 145, + "path": "INST_CTL.DUTY.I", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 146, + "path": "INST_CTL.DUTY.K", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 147, + "path": "INST_CTL.DUTY.BEST", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 148, + "path": "INST_CTL.DUTY.BESTKEY", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 149, + "path": "INST_CTL.DUTY.KEY", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 150, + "path": "INST_CTL.DUTY.NRANKED", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 151, + "path": "INST_CTL.DUTY.SLOTS", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 152, + "path": "INST_CTL.DUTY.CNT", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 153, + "path": "INST_CTL.LVLCTL.LEVEL", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 154, + "path": "INST_CTL.LVLCTL.SETPOINT", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 155, + "path": "INST_CTL.LVLCTL.ENABLE", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 156, + "path": "INST_CTL.LVLCTL.MINSPEED", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 157, + "path": "INST_CTL.LVLCTL.MAXSPEED", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 158, + "path": "INST_CTL.LVLCTL.SPEED", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 159, + "path": "INST_CTL.LVLCTL.SCAN_S", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 160, + "path": "INST_CTL.LVLCTL.KP", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 161, + "path": "INST_CTL.LVLCTL.TI", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 162, + "path": "INST_CTL.LVLCTL.INTEG", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 163, + "path": "INST_CTL.LVLCTL.ERR", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 164, + "path": "INST_CTL.LVLCTL.RAW", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 165, + "path": "INST_CTL.LVLCTL.INTEGRATE", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 166, + "path": "INST_CTL.HEAD.LEVEL", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 167, + "path": "INST_CTL.HEAD.INFLOW", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 168, + "path": "INST_CTL.HEAD.TOTALDISCHARGE", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 169, + "path": "INST_CTL.HEAD.INFLOWFILT", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 170, + "path": "INST_CTL.HEAD.NETINFLOW", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 171, + "path": "INST_CTL.HEAD.VOLTOSPILL", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 172, + "path": "INST_CTL.HEAD.VOLTOLSHH", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 173, + "path": "INST_CTL.HEAD.TIMETOSPILL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 174, + "path": "INST_CTL.HEAD.TIMETOLSHH", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 175, + "path": "INST_CTL.HEAD.SCAN_S", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 176, + "path": "INST_CTL.HEAD.TAU_S", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 177, + "path": "INST_CTL.HEAD.AREA_M2", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 178, + "path": "INST_CTL.HEAD.SPILL_M", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 179, + "path": "INST_CTL.HEAD.LSHH_M", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 180, + "path": "INST_CTL.HEAD.MIN_NET", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 181, + "path": "INST_CTL.HEAD.NO_TIME", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 182, + "path": "INST_CTL.HEAD.MAX_TIME", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 183, + "path": "INST_CTL.HEAD.PRIMED", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 184, + "path": "INST_CTL.HEAD.T", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 185, + "path": "INST_CTL.V_MODE", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 186, + "path": "INST_CTL.V_SPLEVEL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 187, + "path": "INST_CTL.V_STARTDUTY", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 188, + "path": "INST_CTL.V_STARTP2", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 189, + "path": "INST_CTL.V_STARTP3", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 190, + "path": "INST_CTL.V_STOPALL", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 191, + "path": "INST_CTL.V_HIGHALARM", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 192, + "path": "INST_CTL.V_MINSPEED", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 193, + "path": "INST_CTL.V_SERVICEHRS", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 194, + "path": "INST_CTL.SPREJECTED", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 195, + "path": "INST_CTL.SPOK", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 196, + "path": "INST_CTL.CMDBUSY", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 197, + "path": "INST_CTL.RESETTRIP[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 198, + "path": "INST_CTL.RESETTRIP[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 199, + "path": "INST_CTL.RESETTRIP[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 200, + "path": "INST_CTL.RESETHOURS[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 201, + "path": "INST_CTL.RESETHOURS[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 202, + "path": "INST_CTL.RESETHOURS[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 203, + "path": "INST_CTL.LOCKOUT[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 204, + "path": "INST_CTL.LOCKOUT[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 205, + "path": "INST_CTL.LOCKOUT[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 206, + "path": "INST_CTL.ACKALARMS", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 207, + "path": "INST_CTL.P", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 208, + "path": "INST_CTL.PUMPSREQUIRED", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 209, + "path": "INST_CTL.PUMPSALLOWED", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 210, + "path": "INST_CTL.STAGGERTMR.IN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 211, + "path": "INST_CTL.STAGGERTMR.PT", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 212, + "path": "INST_CTL.STAGGERTMR.Q", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 213, + "path": "INST_CTL.STAGGERTMR.ET", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 214, + "path": "INST_CTL.STAGGERARM", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 215, + "path": "INST_CTL.DRYRUN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 216, + "path": "INST_CTL.DRYLOCKOUT", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 217, + "path": "INST_CTL.LEVELRANGEFAULT", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 218, + "path": "INST_CTL.LEVELFROZEN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 219, + "path": "INST_CTL.LEVELFAULT", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 220, + "path": "INST_CTL.LEVELREF", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 221, + "path": "INST_CTL.LEVELMOVED", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 222, + "path": "INST_CTL.FROZENTMR.IN", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 223, + "path": "INST_CTL.FROZENTMR.PT", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 224, + "path": "INST_CTL.FROZENTMR.Q", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 225, + "path": "INST_CTL.FROZENTMR.ET", + "type": "TIME", + "size": 8 + }, + { + "arrayIdx": 3, + "elemIdx": 226, + "path": "INST_CTL.ANYRUNNING", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 227, + "path": "INST_CTL.AVAIL[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 228, + "path": "INST_CTL.AVAIL[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 229, + "path": "INST_CTL.AVAIL[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 230, + "path": "INST_CTL.HOURS[1]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 231, + "path": "INST_CTL.HOURS[2]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 232, + "path": "INST_CTL.HOURS[3]", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 233, + "path": "INST_CTL.SVCDUE[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 234, + "path": "INST_CTL.SVCDUE[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 235, + "path": "INST_CTL.SVCDUE[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 236, + "path": "INST_CTL.RUNNOW[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 237, + "path": "INST_CTL.RUNNOW[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 238, + "path": "INST_CTL.RUNNOW[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 239, + "path": "INST_CTL.REQ[1]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 240, + "path": "INST_CTL.REQ[2]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 241, + "path": "INST_CTL.REQ[3]", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 242, + "path": "INST_CTL.SPEED", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 243, + "path": "INST_CTL.MINSPEEDHZ", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 244, + "path": "INST_CTL.SPLEVEL_M", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 245, + "path": "INST_CTL.HIGHLEVEL", + "type": "BOOL", + "size": 1 + }, + { + "arrayIdx": 3, + "elemIdx": 246, + "path": "INST_CTL.PUMPSRUN", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 247, + "path": "INST_CTL.ALARM", + "type": "DINT", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 248, + "path": "INST_CTL.I", + "type": "INT", + "size": 2 + }, + { + "arrayIdx": 3, + "elemIdx": 249, + "path": "INST_CTL.R", + "type": "REAL", + "size": 4 + }, + { + "arrayIdx": 3, + "elemIdx": 250, + "path": "INST_CTL.PRIMED", + "type": "BOOL", + "size": 1 + } + ] +} \ No newline at end of file diff --git a/03-plc/as-built/defines.h b/03-plc/as-built/defines.h new file mode 100644 index 0000000..47b4f5d --- /dev/null +++ b/03-plc/as-built/defines.h @@ -0,0 +1,3 @@ +#pragma once +// Program MD5 +#define PROGRAM_MD5 "5f0b241a708123a8744659c85e96e24c" diff --git a/03-plc/as-built/generated.hpp b/03-plc/as-built/generated.hpp new file mode 100644 index 0000000..90261d0 --- /dev/null +++ b/03-plc/as-built/generated.hpp @@ -0,0 +1,910 @@ +#pragma once + +// Generated by STruC++ - IEC 61131-3 Structured Text to C++ Compiler +// Do not edit this file manually. + +#include "iec_types.hpp" +#include "iec_var.hpp" +#include "iec_global.hpp" +#include "iec_array.hpp" +#include "iec_located.hpp" +#include "iec_std_lib.hpp" +#include "iec_enum.hpp" +#include "iec_memory.hpp" +#include "iec_pointer.hpp" +#include "iec_string.hpp" +#include "iec_wstring.hpp" +#include +#include +#include + +// Avoid clashes between ST identifiers and C stdlib macros +#ifdef TMP_MAX +#undef TMP_MAX +#endif +#ifdef EOF +#undef EOF +#endif +#ifdef BUFSIZ +#undef BUFSIZ +#endif +#ifdef FOPEN_MAX +#undef FOPEN_MAX +#endif +#ifdef FILENAME_MAX +#undef FILENAME_MAX +#endif +#ifdef RAND_MAX +#undef RAND_MAX +#endif +#ifdef EXIT_SUCCESS +#undef EXIT_SUCCESS +#endif +#ifdef EXIT_FAILURE +#undef EXIT_FAILURE +#endif + +#undef OVERFLOW + +// Global constants +constexpr size_t STRING_LENGTH = 254; +constexpr size_t LIST_LENGTH = 254; + +namespace strucpp { + +// Library: iec-standard-fb +class TON; +class TON { +public: + // Inputs + IEC_BOOL IN; + IEC_TIME PT; + // Outputs + IEC_BOOL Q; + IEC_TIME ET; + // Local variables + IEC_SINT STATE; + IEC_BOOL PREV_IN; + IEC_TIME CURRENT_TIME; + IEC_TIME START_TIME; + + // Implicit IEC 61131-3 ENO pin (mirrors EN) + IEC_BOOL ENO = true; + + // Constructor + TON(); + + // Execute function block + void operator()(); + + virtual ~TON() = default; +}; + + +// Library: oscat-basic +class INTEGRATE; +class SPEED; +class INTEGRATE { +public: + // Inputs + IEC_BOOL E; + IEC_REAL X; + IEC_REAL K; + // In-Out + IEC_REAL Y; + // Local variables + IEC_REAL X_LAST; + IEC_BOOL INIT; + IEC_DWORD LAST; + IEC_DWORD TX; + + // Implicit IEC 61131-3 ENO pin (mirrors EN) + IEC_BOOL ENO = true; + + // Constructor + INTEGRATE(); + + // Execute function block + void operator()(); + + virtual ~INTEGRATE() = default; +}; + +class SPEED { +public: + // Inputs + IEC_REAL MS; + IEC_REAL KMH; + IEC_REAL KN; + IEC_REAL MH; + // Outputs + IEC_REAL YMS; + IEC_REAL YKMH; + IEC_REAL YKN; + IEC_REAL YMH; + + // Implicit IEC 61131-3 ENO pin (mirrors EN) + IEC_BOOL ENO = true; + + // Constructor + SPEED(); + + // Execute function block + void operator()(); + + virtual ~SPEED() = default; +}; + +IEC_INT CEIL(IEC_REAL X); +IEC_REAL EXPN(IEC_REAL X, IEC_INT N); +IEC_REAL OFFSET(IEC_REAL X, IEC_BOOL O1, IEC_BOOL O2, IEC_BOOL O3, IEC_BOOL O4, IEC_BOOL D, IEC_REAL OFFSET_1, IEC_REAL OFFSET_2, IEC_REAL OFFSET_3, IEC_REAL OFFSET_4, IEC_REAL DEFAULT); +IEC_REAL RND(IEC_REAL X, IEC_INT N); +IEC_DWORD T_PLC_MS(); + +class FB_DUTY_SELECT; +class FB_HEADROOM; +class FB_LEVEL_CTRL; +class FB_PUMP; +class Program_CONTROL; +class Program_SIMULATION; +class Program_IO_MUX; +class Configuration_CONFIG0; + +// Configuration VAR_GLOBAL storage — file-scope so every POU (program or nested function block) reaches the one canonical GlobalVar (value + mutex). +inline GlobalVar CFG_AREA_M2{120.0}; +inline GlobalVar CFG_SPILL_M{6.000}; +inline GlobalVar CFG_LSHH_M{5.500}; +inline GlobalVar CFG_MIN_HZ{38.0}; +inline GlobalVar CFG_MAX_HZ{50.0}; +inline GlobalVar DEF_MODE{1}; +inline GlobalVar DEF_SP_LEVEL{4200}; +inline GlobalVar DEF_START_DUTY{4000}; +inline GlobalVar DEF_START_P2{4500}; +inline GlobalVar DEF_START_P3{5000}; +inline GlobalVar DEF_STOP_ALL{1000}; +inline GlobalVar DEF_HIGH_ALARM{5200}; +inline GlobalVar DEF_MIN_SPEED{380}; +inline GlobalVar DEF_SERVICE_HRS{4000}; +inline GlobalVar CFG_NO_TIME{32767}; +inline GlobalVar IW_LIT101{0}; +inline GlobalVar IW_FIT201{0}; +inline GlobalVar IW_FIT301{0}; +inline GlobalVar IW_PIT302{0}; +inline GlobalVar IW_PIT311{0}; +inline GlobalVar IW_PIT321{0}; +inline GlobalVar IW_PIT331{0}; +inline GlobalVar IW_VE314{0}; +inline GlobalVar IW_VE324{0}; +inline GlobalVar IW_VE334{0}; +inline GlobalVar IX_LSHH102{false}; +inline GlobalVar IX_LSLL103{false}; +inline GlobalVar IX_LSH104{false}; +inline GlobalVar IX_TE312{false}; +inline GlobalVar IX_TE322{false}; +inline GlobalVar IX_TE332{false}; +inline GlobalVar IX_MSE313{false}; +inline GlobalVar IX_MSE323{false}; +inline GlobalVar IX_MSE333{false}; +inline GlobalVar IX_XA502{false}; +inline GlobalVar QX_RUNCMD1{false}; +inline GlobalVar QX_RUNCMD2{false}; +inline GlobalVar QX_RUNCMD3{false}; +inline GlobalVar QX_RUNNING1{false}; +inline GlobalVar QX_RUNNING2{false}; +inline GlobalVar QX_RUNNING3{false}; +inline GlobalVar QX_AVAIL1{false}; +inline GlobalVar QX_AVAIL2{false}; +inline GlobalVar QX_AVAIL3{false}; +inline GlobalVar QX_INAUTO{false}; +inline GlobalVar QX_HIGHLEVEL{false}; +inline GlobalVar QX_SPILLACTIVE{false}; +inline GlobalVar QX_TRIPPED1{false}; +inline GlobalVar QX_TRIPPED2{false}; +inline GlobalVar QX_TRIPPED3{false}; +inline GlobalVar QW_LEVEL{0}; +inline GlobalVar QW_INFLOW{0}; +inline GlobalVar QW_DISCHARGE{0}; +inline GlobalVar QW_PUMPSRUNNING{0}; +inline GlobalVar QW_SPEED{0}; +inline GlobalVar QW_TIMETOSPILL{0}; +inline GlobalVar QW_TIMETOLSHH{0}; +inline GlobalVar QW_NETACCUM{0}; +inline GlobalVar QW_RUNHOURS1{0}; +inline GlobalVar QW_RUNHOURS2{0}; +inline GlobalVar QW_RUNHOURS3{0}; +inline GlobalVar QW_VOLTOSPILL{0}; +inline GlobalVar QW_STATIONSTATE{0}; +inline GlobalVar QW_PUMPSTATE1{0}; +inline GlobalVar QW_PUMPSTATE2{0}; +inline GlobalVar QW_PUMPSTATE3{0}; +inline GlobalVar QW_DUTYPUMP{0}; +inline GlobalVar QW_ALARMWORD{0}; +inline GlobalVar QW_CMDACK{0}; +inline GlobalVar MW_MODE{0}; +inline GlobalVar MW_CMDWORD{0}; +inline GlobalVar MW_CMDPARAM{0}; +inline GlobalVar MW_SPLEVEL{0}; +inline GlobalVar MW_STARTDUTY{0}; +inline GlobalVar MW_STARTP2{0}; +inline GlobalVar MW_STARTP3{0}; +inline GlobalVar MW_STOPALL{0}; +inline GlobalVar MW_HIGHALARM{0}; +inline GlobalVar MW_MINSPEED{0}; +inline GlobalVar MW_SERVICEHRS{0}; +inline GlobalVar MW_SIMINFLOW{0}; +inline GlobalVar MW_SIMMODE{0}; +inline GlobalVar MW_SIMRESET{0}; +inline GlobalVar MW_SIMTIMESCALE{0}; +inline GlobalVar G_LEVELRAW_MM{0}; +inline GlobalVar G_LEVEL_MM{0}; +inline GlobalVar G_LEVEL_M{0.0}; +inline GlobalVar G_INFLOW_LPS{0.0}; +inline GlobalVar G_DISCH_LPS{0.0}; +inline GlobalVar G_MANIFOLDP_KPA{0.0}; +inline GlobalVar> G_PUMPP_KPA{}; +inline GlobalVar> G_VIB_MMS{}; +inline GlobalVar G_LSHH{false}; +inline GlobalVar G_LSLL_WET{false}; +inline GlobalVar G_SPILLDETECTED{false}; +inline GlobalVar> G_THERMALOK{}; +inline GlobalVar> G_SEALLEAK{}; +inline GlobalVar G_MAINSOK{false}; +inline GlobalVar G_CMD_MODE{0}; +inline GlobalVar G_CMD_WORD{0}; +inline GlobalVar G_CMD_PARAM{0}; +inline GlobalVar G_SP_LEVEL{0}; +inline GlobalVar G_SP_STARTDUTY{0}; +inline GlobalVar G_SP_STARTP2{0}; +inline GlobalVar G_SP_STARTP3{0}; +inline GlobalVar G_SP_STOPALL{0}; +inline GlobalVar G_SP_HIGHALARM{0}; +inline GlobalVar G_SP_MINSPEED{0}; +inline GlobalVar G_SP_SERVICEHRS{0}; +inline GlobalVar> G_O_RUNCMD{}; +inline GlobalVar> G_O_RUNNING{}; +inline GlobalVar> G_O_AVAILABLE{}; +inline GlobalVar> G_O_TRIPPED{}; +inline GlobalVar G_O_INAUTO{false}; +inline GlobalVar G_O_HIGHLEVEL{false}; +inline GlobalVar G_O_SPILLACTIVE{false}; +inline GlobalVar G_O_LEVEL_MM{0}; +inline GlobalVar G_O_INFLOW_X10{0}; +inline GlobalVar G_O_DISCH_X10{0}; +inline GlobalVar G_O_PUMPSRUN{0}; +inline GlobalVar G_O_SPEED_X10{0}; +inline GlobalVar G_O_TIMETOSPILL{0}; +inline GlobalVar G_O_TIMETOLSHH{0}; +inline GlobalVar G_O_NETACCUM{0}; +inline GlobalVar> G_O_RUNHOURS{}; +inline GlobalVar G_O_VOLTOSPILL{0}; +inline GlobalVar G_O_STATIONSTATE{0}; +inline GlobalVar> G_O_PUMPSTATE{}; +inline GlobalVar G_O_DUTYPUMP{0}; +inline GlobalVar G_O_ALARMWORD{0}; +inline GlobalVar G_O_CMDACK{0}; +inline GlobalVar G_SIMACTIVE{false}; +inline GlobalVar G_SIM_CLEARRESET{false}; +inline GlobalVar G_SIMCMD_INFLOW{0}; +inline GlobalVar G_SIMCMD_MODE{0}; +inline GlobalVar G_SIMCMD_RESET{0}; +inline GlobalVar G_SIMCMD_TIMESCALE{0}; +inline GlobalVar G_SIM_LEVEL_MM{0}; +inline GlobalVar G_SIM_INFLOW_X10{0}; +inline GlobalVar G_SIM_DISCH_X10{0}; +inline GlobalVar G_SIM_MANIFOLDP{0}; +inline GlobalVar> G_SIM_PUMPP{}; +inline GlobalVar> G_SIM_VIB_X10{}; +inline GlobalVar G_SIM_LSHH{false}; +inline GlobalVar G_SIM_LSLL_WET{false}; +inline GlobalVar G_SIM_SPILL{false}; +inline GlobalVar> G_SIM_THERMALOK{}; +inline GlobalVar> G_SIM_SEALLEAK{}; +inline GlobalVar G_SIM_MAINSOK{false}; + +class FB_DUTY_SELECT { +public: + // Inputs + Array1D AVAILABLE; + Array1D RUNHOURS; + Array1D SERVICEDUE; + Array1D RUNNINGNOW; + IEC_INT PUMPSREQUIRED; + // Outputs + Array1D RUNREQUEST; + IEC_INT DUTYPUMP; + // Local variables + IEC_REAL SERVICE_PENALTY; + Array1D RANK; + Array1D USED; + Array1D SEL; + IEC_INT I; + IEC_INT K; + IEC_INT BEST; + IEC_REAL BESTKEY; + IEC_REAL KEY; + IEC_INT NRANKED; + IEC_INT SLOTS; + IEC_INT CNT; + + // Implicit IEC 61131-3 ENO pin (mirrors EN) + IEC_BOOL ENO = true; + + // Constructor + FB_DUTY_SELECT(); + + // Execute function block + void operator()(); + + virtual ~FB_DUTY_SELECT() = default; +}; + +class FB_HEADROOM { +public: + // Inputs + IEC_REAL LEVEL; + IEC_REAL INFLOW; + IEC_REAL TOTALDISCHARGE; + // Outputs + IEC_REAL INFLOWFILT; + IEC_REAL NETINFLOW; + IEC_REAL VOLTOSPILL; + IEC_REAL VOLTOLSHH; + IEC_INT TIMETOSPILL; + IEC_INT TIMETOLSHH; + // Local variables + IEC_REAL SCAN_S; + IEC_REAL TAU_S; + IEC_REAL AREA_M2; + IEC_REAL SPILL_M; + IEC_REAL LSHH_M; + IEC_REAL MIN_NET; + IEC_INT NO_TIME; + IEC_REAL MAX_TIME; + IEC_BOOL PRIMED; + IEC_REAL T; + + // Implicit IEC 61131-3 ENO pin (mirrors EN) + IEC_BOOL ENO = true; + + // Constructor + FB_HEADROOM(); + + // Execute function block + void operator()(); + + virtual ~FB_HEADROOM() = default; +}; + +class FB_LEVEL_CTRL { +public: + // Inputs + IEC_REAL LEVEL; + IEC_REAL SETPOINT; + IEC_BOOL ENABLE; + IEC_REAL MINSPEED; + IEC_REAL MAXSPEED; + // Outputs + IEC_REAL SPEED; + // Local variables + IEC_REAL SCAN_S; + IEC_REAL KP; + IEC_REAL TI; + IEC_REAL INTEG; + IEC_REAL ERR; + IEC_REAL RAW; + IEC_BOOL INTEGRATE; + + // Implicit IEC 61131-3 ENO pin (mirrors EN) + IEC_BOOL ENO = true; + + // Constructor + FB_LEVEL_CTRL(); + + // Execute function block + void operator()(); + + virtual ~FB_LEVEL_CTRL() = default; +}; + +class FB_PUMP { +public: + // Inputs + IEC_BOOL RUNREQUEST; + IEC_REAL SPEEDREF; + IEC_BOOL THERMALOK; + IEC_BOOL SEALLEAK; + IEC_REAL VIBRATION; + IEC_REAL DISCHPRESSURE; + IEC_BOOL RESETTRIP; + IEC_BOOL LOCKOUT; + IEC_BOOL MINOFFBYPASS; + IEC_REAL SERVICEINTERVAL; + IEC_BOOL RESETHOURS; + // Outputs + IEC_BOOL RUNCMD; + IEC_BOOL RUNNING; + IEC_BOOL AVAILABLE; + IEC_BOOL TRIPPED; + IEC_INT STATE; + IEC_REAL RUNHOURS; + IEC_BOOL SERVICEDUE; + IEC_BOOL VIBALARM; + IEC_BOOL SEALALARM; + // Local variables + IEC_REAL SCAN_S; + IEC_REAL VIB_ALARM; + IEC_REAL VIB_TRIP; + IEC_REAL NOFLOW_KPA; + TON MINRUNTMR; + TON MINOFFTMR; + TON NOFLOWTMR; + IEC_BOOL HASRUN; + IEC_BOOL STARTOK; + + // Implicit IEC 61131-3 ENO pin (mirrors EN) + IEC_BOOL ENO = true; + + // Constructor + FB_PUMP(); + + // Execute function block + void operator()(); + + virtual ~FB_PUMP() = default; +}; + +class Program_CONTROL : public ProgramBase { +public: + // Local variables + IEC_INT SPILL_MM; + IEC_INT LEVEL_MAX_MM; + IEC_REAL HARD_MIN_HZ; + IEC_REAL HARD_MAX_HZ; + FB_PUMP PUMP1; + FB_PUMP PUMP2; + FB_PUMP PUMP3; + FB_DUTY_SELECT DUTY; + FB_LEVEL_CTRL LVLCTL; + FB_HEADROOM HEAD; + IEC_INT V_MODE; + IEC_INT V_SPLEVEL; + IEC_INT V_STARTDUTY; + IEC_INT V_STARTP2; + IEC_INT V_STARTP3; + IEC_INT V_STOPALL; + IEC_INT V_HIGHALARM; + IEC_INT V_MINSPEED; + IEC_INT V_SERVICEHRS; + IEC_BOOL SPREJECTED; + IEC_BOOL SPOK; + IEC_BOOL CMDBUSY; + Array1D RESETTRIP; + Array1D RESETHOURS; + Array1D LOCKOUT; + IEC_BOOL ACKALARMS; + IEC_INT P; + IEC_INT PUMPSREQUIRED; + IEC_INT PUMPSALLOWED; + TON STAGGERTMR; + IEC_BOOL STAGGERARM; + IEC_BOOL DRYRUN; + IEC_BOOL DRYLOCKOUT; + IEC_BOOL LEVELRANGEFAULT; + IEC_BOOL LEVELFROZEN; + IEC_BOOL LEVELFAULT; + IEC_INT LEVELREF; + IEC_BOOL LEVELMOVED; + TON FROZENTMR; + IEC_BOOL ANYRUNNING; + Array1D AVAIL; + Array1D HOURS; + Array1D SVCDUE; + Array1D RUNNOW; + Array1D REQ; + IEC_REAL SPEED; + IEC_REAL MINSPEEDHZ; + IEC_REAL SPLEVEL_M; + IEC_BOOL HIGHLEVEL; + IEC_INT PUMPSRUN; + IEC_DINT ALARM; + IEC_INT I; + IEC_REAL R; + IEC_BOOL PRIMED; + // External variables (pointers to shared globals) + GlobalVar* G_LEVELRAW_MM = nullptr; + GlobalVar* G_LEVEL_MM = nullptr; + GlobalVar* G_LEVEL_M = nullptr; + GlobalVar* G_INFLOW_LPS = nullptr; + GlobalVar* G_DISCH_LPS = nullptr; + GlobalVar>* G_PUMPP_KPA = nullptr; + GlobalVar>* G_VIB_MMS = nullptr; + GlobalVar* G_LSHH = nullptr; + GlobalVar* G_LSLL_WET = nullptr; + GlobalVar* G_SPILLDETECTED = nullptr; + GlobalVar>* G_THERMALOK = nullptr; + GlobalVar>* G_SEALLEAK = nullptr; + GlobalVar* G_MAINSOK = nullptr; + GlobalVar* G_CMD_MODE = nullptr; + GlobalVar* G_CMD_WORD = nullptr; + GlobalVar* G_CMD_PARAM = nullptr; + GlobalVar* G_SP_LEVEL = nullptr; + GlobalVar* G_SP_STARTDUTY = nullptr; + GlobalVar* G_SP_STARTP2 = nullptr; + GlobalVar* G_SP_STARTP3 = nullptr; + GlobalVar* G_SP_STOPALL = nullptr; + GlobalVar* G_SP_HIGHALARM = nullptr; + GlobalVar* G_SP_MINSPEED = nullptr; + GlobalVar* G_SP_SERVICEHRS = nullptr; + GlobalVar>* G_O_RUNCMD = nullptr; + GlobalVar>* G_O_RUNNING = nullptr; + GlobalVar>* G_O_AVAILABLE = nullptr; + GlobalVar>* G_O_TRIPPED = nullptr; + GlobalVar* G_O_INAUTO = nullptr; + GlobalVar* G_O_HIGHLEVEL = nullptr; + GlobalVar* G_O_SPILLACTIVE = nullptr; + GlobalVar* G_O_LEVEL_MM = nullptr; + GlobalVar* G_O_INFLOW_X10 = nullptr; + GlobalVar* G_O_DISCH_X10 = nullptr; + GlobalVar* G_O_PUMPSRUN = nullptr; + GlobalVar* G_O_SPEED_X10 = nullptr; + GlobalVar* G_O_TIMETOSPILL = nullptr; + GlobalVar* G_O_TIMETOLSHH = nullptr; + GlobalVar* G_O_NETACCUM = nullptr; + GlobalVar>* G_O_RUNHOURS = nullptr; + GlobalVar* G_O_VOLTOSPILL = nullptr; + GlobalVar* G_O_STATIONSTATE = nullptr; + GlobalVar>* G_O_PUMPSTATE = nullptr; + GlobalVar* G_O_DUTYPUMP = nullptr; + GlobalVar* G_O_ALARMWORD = nullptr; + GlobalVar* G_O_CMDACK = nullptr; + + // Implicit IEC 61131-3 ENO pin (mirrors EN) + IEC_BOOL ENO = true; + + // Constructor + explicit Program_CONTROL(GlobalVar* G_LEVELRAW_MM_ref, GlobalVar* G_LEVEL_MM_ref, GlobalVar* G_LEVEL_M_ref, GlobalVar* G_INFLOW_LPS_ref, GlobalVar* G_DISCH_LPS_ref, GlobalVar>* G_PUMPP_KPA_ref, GlobalVar>* G_VIB_MMS_ref, GlobalVar* G_LSHH_ref, GlobalVar* G_LSLL_WET_ref, GlobalVar* G_SPILLDETECTED_ref, GlobalVar>* G_THERMALOK_ref, GlobalVar>* G_SEALLEAK_ref, GlobalVar* G_MAINSOK_ref, GlobalVar* G_CMD_MODE_ref, GlobalVar* G_CMD_WORD_ref, GlobalVar* G_CMD_PARAM_ref, GlobalVar* G_SP_LEVEL_ref, GlobalVar* G_SP_STARTDUTY_ref, GlobalVar* G_SP_STARTP2_ref, GlobalVar* G_SP_STARTP3_ref, GlobalVar* G_SP_STOPALL_ref, GlobalVar* G_SP_HIGHALARM_ref, GlobalVar* G_SP_MINSPEED_ref, GlobalVar* G_SP_SERVICEHRS_ref, GlobalVar>* G_O_RUNCMD_ref, GlobalVar>* G_O_RUNNING_ref, GlobalVar>* G_O_AVAILABLE_ref, GlobalVar>* G_O_TRIPPED_ref, GlobalVar* G_O_INAUTO_ref, GlobalVar* G_O_HIGHLEVEL_ref, GlobalVar* G_O_SPILLACTIVE_ref, GlobalVar* G_O_LEVEL_MM_ref, GlobalVar* G_O_INFLOW_X10_ref, GlobalVar* G_O_DISCH_X10_ref, GlobalVar* G_O_PUMPSRUN_ref, GlobalVar* G_O_SPEED_X10_ref, GlobalVar* G_O_TIMETOSPILL_ref, GlobalVar* G_O_TIMETOLSHH_ref, GlobalVar* G_O_NETACCUM_ref, GlobalVar>* G_O_RUNHOURS_ref, GlobalVar* G_O_VOLTOSPILL_ref, GlobalVar* G_O_STATIONSTATE_ref, GlobalVar>* G_O_PUMPSTATE_ref, GlobalVar* G_O_DUTYPUMP_ref, GlobalVar* G_O_ALARMWORD_ref, GlobalVar* G_O_CMDACK_ref); + + // Run program + void run() override; +}; + +class Program_SIMULATION : public ProgramBase { +public: + // Local variables + IEC_REAL SCAN_S; + IEC_REAL AREA_M2; + IEC_REAL SPILL_M; + IEC_REAL LSHH_M; + IEC_REAL LSLL_M; + IEC_REAL START_DLY_S; + IEC_REAL HZ_LO; + IEC_REAL HZ_HI; + IEC_REAL Q_LO; + IEC_REAL Q_HI; + IEC_REAL P_IDLE; + IEC_REAL P_BASE; + IEC_REAL P_PER_LPS; + IEC_REAL VIB_IDLE; + IEC_REAL VIB_BASE; + IEC_REAL VIB_PER_LPS; + IEC_BOOL INIT; + IEC_REAL VOLUME_M3; + IEC_REAL LEVEL_M; + IEC_REAL INFLOW_LPS; + IEC_REAL SUMFLOW_LPS; + IEC_REAL SIMCLOCK_S; + Array1D STARTDLY_S; + Array1D DELIVERING; + Array1D FLOW_LPS; + Array1D PRESS_KPA; + Array1D VIB_MMS; + IEC_REAL TIMESCALE; + IEC_REAL DT_S; + IEC_REAL SPEED_HZ; + IEC_REAL UNITQ_LPS; + IEC_REAL DERATE; + IEC_INT NDELIVERING; + IEC_INT I; + IEC_DINT RND; + IEC_REAL NOISE; + // External variables (pointers to shared globals) + GlobalVar* G_SIMCMD_INFLOW = nullptr; + GlobalVar* G_SIMCMD_MODE = nullptr; + GlobalVar* G_SIMCMD_RESET = nullptr; + GlobalVar* G_SIMCMD_TIMESCALE = nullptr; + GlobalVar>* G_O_RUNCMD = nullptr; + GlobalVar* G_O_SPEED_X10 = nullptr; + GlobalVar* G_SIMACTIVE = nullptr; + GlobalVar* G_SIM_CLEARRESET = nullptr; + GlobalVar* G_SIM_LEVEL_MM = nullptr; + GlobalVar* G_SIM_INFLOW_X10 = nullptr; + GlobalVar* G_SIM_DISCH_X10 = nullptr; + GlobalVar* G_SIM_MANIFOLDP = nullptr; + GlobalVar>* G_SIM_PUMPP = nullptr; + GlobalVar>* G_SIM_VIB_X10 = nullptr; + GlobalVar* G_SIM_LSHH = nullptr; + GlobalVar* G_SIM_LSLL_WET = nullptr; + GlobalVar* G_SIM_SPILL = nullptr; + GlobalVar>* G_SIM_THERMALOK = nullptr; + GlobalVar>* G_SIM_SEALLEAK = nullptr; + GlobalVar* G_SIM_MAINSOK = nullptr; + + // Implicit IEC 61131-3 ENO pin (mirrors EN) + IEC_BOOL ENO = true; + + // Constructor + explicit Program_SIMULATION(GlobalVar* G_SIMCMD_INFLOW_ref, GlobalVar* G_SIMCMD_MODE_ref, GlobalVar* G_SIMCMD_RESET_ref, GlobalVar* G_SIMCMD_TIMESCALE_ref, GlobalVar>* G_O_RUNCMD_ref, GlobalVar* G_O_SPEED_X10_ref, GlobalVar* G_SIMACTIVE_ref, GlobalVar* G_SIM_CLEARRESET_ref, GlobalVar* G_SIM_LEVEL_MM_ref, GlobalVar* G_SIM_INFLOW_X10_ref, GlobalVar* G_SIM_DISCH_X10_ref, GlobalVar* G_SIM_MANIFOLDP_ref, GlobalVar>* G_SIM_PUMPP_ref, GlobalVar>* G_SIM_VIB_X10_ref, GlobalVar* G_SIM_LSHH_ref, GlobalVar* G_SIM_LSLL_WET_ref, GlobalVar* G_SIM_SPILL_ref, GlobalVar>* G_SIM_THERMALOK_ref, GlobalVar>* G_SIM_SEALLEAK_ref, GlobalVar* G_SIM_MAINSOK_ref); + + // Run program + void run() override; +}; + +class Program_IO_MUX : public ProgramBase { +public: + // Local variables + IEC_BOOL SEEDED; + // External variables (pointers to shared globals) + GlobalVar* IW_LIT101 = nullptr; + GlobalVar* IW_FIT201 = nullptr; + GlobalVar* IW_FIT301 = nullptr; + GlobalVar* IW_PIT302 = nullptr; + GlobalVar* IW_PIT311 = nullptr; + GlobalVar* IW_PIT321 = nullptr; + GlobalVar* IW_PIT331 = nullptr; + GlobalVar* IW_VE314 = nullptr; + GlobalVar* IW_VE324 = nullptr; + GlobalVar* IW_VE334 = nullptr; + GlobalVar* IX_LSHH102 = nullptr; + GlobalVar* IX_LSLL103 = nullptr; + GlobalVar* IX_LSH104 = nullptr; + GlobalVar* IX_TE312 = nullptr; + GlobalVar* IX_TE322 = nullptr; + GlobalVar* IX_TE332 = nullptr; + GlobalVar* IX_MSE313 = nullptr; + GlobalVar* IX_MSE323 = nullptr; + GlobalVar* IX_MSE333 = nullptr; + GlobalVar* IX_XA502 = nullptr; + GlobalVar* QX_RUNCMD1 = nullptr; + GlobalVar* QX_RUNCMD2 = nullptr; + GlobalVar* QX_RUNCMD3 = nullptr; + GlobalVar* QX_RUNNING1 = nullptr; + GlobalVar* QX_RUNNING2 = nullptr; + GlobalVar* QX_RUNNING3 = nullptr; + GlobalVar* QX_AVAIL1 = nullptr; + GlobalVar* QX_AVAIL2 = nullptr; + GlobalVar* QX_AVAIL3 = nullptr; + GlobalVar* QX_INAUTO = nullptr; + GlobalVar* QX_HIGHLEVEL = nullptr; + GlobalVar* QX_SPILLACTIVE = nullptr; + GlobalVar* QX_TRIPPED1 = nullptr; + GlobalVar* QX_TRIPPED2 = nullptr; + GlobalVar* QX_TRIPPED3 = nullptr; + GlobalVar* QW_LEVEL = nullptr; + GlobalVar* QW_INFLOW = nullptr; + GlobalVar* QW_DISCHARGE = nullptr; + GlobalVar* QW_PUMPSRUNNING = nullptr; + GlobalVar* QW_SPEED = nullptr; + GlobalVar* QW_TIMETOSPILL = nullptr; + GlobalVar* QW_TIMETOLSHH = nullptr; + GlobalVar* QW_NETACCUM = nullptr; + GlobalVar* QW_RUNHOURS1 = nullptr; + GlobalVar* QW_RUNHOURS2 = nullptr; + GlobalVar* QW_RUNHOURS3 = nullptr; + GlobalVar* QW_VOLTOSPILL = nullptr; + GlobalVar* QW_STATIONSTATE = nullptr; + GlobalVar* QW_PUMPSTATE1 = nullptr; + GlobalVar* QW_PUMPSTATE2 = nullptr; + GlobalVar* QW_PUMPSTATE3 = nullptr; + GlobalVar* QW_DUTYPUMP = nullptr; + GlobalVar* QW_ALARMWORD = nullptr; + GlobalVar* QW_CMDACK = nullptr; + GlobalVar* MW_MODE = nullptr; + GlobalVar* MW_CMDWORD = nullptr; + GlobalVar* MW_CMDPARAM = nullptr; + GlobalVar* MW_SPLEVEL = nullptr; + GlobalVar* MW_STARTDUTY = nullptr; + GlobalVar* MW_STARTP2 = nullptr; + GlobalVar* MW_STARTP3 = nullptr; + GlobalVar* MW_STOPALL = nullptr; + GlobalVar* MW_HIGHALARM = nullptr; + GlobalVar* MW_MINSPEED = nullptr; + GlobalVar* MW_SERVICEHRS = nullptr; + GlobalVar* MW_SIMINFLOW = nullptr; + GlobalVar* MW_SIMMODE = nullptr; + GlobalVar* MW_SIMRESET = nullptr; + GlobalVar* MW_SIMTIMESCALE = nullptr; + GlobalVar* G_LEVELRAW_MM = nullptr; + GlobalVar* G_LEVEL_MM = nullptr; + GlobalVar* G_LEVEL_M = nullptr; + GlobalVar* G_INFLOW_LPS = nullptr; + GlobalVar* G_DISCH_LPS = nullptr; + GlobalVar* G_MANIFOLDP_KPA = nullptr; + GlobalVar>* G_PUMPP_KPA = nullptr; + GlobalVar>* G_VIB_MMS = nullptr; + GlobalVar* G_LSHH = nullptr; + GlobalVar* G_LSLL_WET = nullptr; + GlobalVar* G_SPILLDETECTED = nullptr; + GlobalVar>* G_THERMALOK = nullptr; + GlobalVar>* G_SEALLEAK = nullptr; + GlobalVar* G_MAINSOK = nullptr; + GlobalVar* G_CMD_MODE = nullptr; + GlobalVar* G_CMD_WORD = nullptr; + GlobalVar* G_CMD_PARAM = nullptr; + GlobalVar* G_SP_LEVEL = nullptr; + GlobalVar* G_SP_STARTDUTY = nullptr; + GlobalVar* G_SP_STARTP2 = nullptr; + GlobalVar* G_SP_STARTP3 = nullptr; + GlobalVar* G_SP_STOPALL = nullptr; + GlobalVar* G_SP_HIGHALARM = nullptr; + GlobalVar* G_SP_MINSPEED = nullptr; + GlobalVar* G_SP_SERVICEHRS = nullptr; + GlobalVar>* G_O_RUNCMD = nullptr; + GlobalVar>* G_O_RUNNING = nullptr; + GlobalVar>* G_O_AVAILABLE = nullptr; + GlobalVar>* G_O_TRIPPED = nullptr; + GlobalVar* G_O_INAUTO = nullptr; + GlobalVar* G_O_HIGHLEVEL = nullptr; + GlobalVar* G_O_SPILLACTIVE = nullptr; + GlobalVar* G_O_LEVEL_MM = nullptr; + GlobalVar* G_O_INFLOW_X10 = nullptr; + GlobalVar* G_O_DISCH_X10 = nullptr; + GlobalVar* G_O_PUMPSRUN = nullptr; + GlobalVar* G_O_SPEED_X10 = nullptr; + GlobalVar* G_O_TIMETOSPILL = nullptr; + GlobalVar* G_O_TIMETOLSHH = nullptr; + GlobalVar* G_O_NETACCUM = nullptr; + GlobalVar>* G_O_RUNHOURS = nullptr; + GlobalVar* G_O_VOLTOSPILL = nullptr; + GlobalVar* G_O_STATIONSTATE = nullptr; + GlobalVar>* G_O_PUMPSTATE = nullptr; + GlobalVar* G_O_DUTYPUMP = nullptr; + GlobalVar* G_O_ALARMWORD = nullptr; + GlobalVar* G_O_CMDACK = nullptr; + GlobalVar* DEF_MODE = nullptr; + GlobalVar* DEF_SP_LEVEL = nullptr; + GlobalVar* DEF_START_DUTY = nullptr; + GlobalVar* DEF_START_P2 = nullptr; + GlobalVar* DEF_START_P3 = nullptr; + GlobalVar* DEF_STOP_ALL = nullptr; + GlobalVar* DEF_HIGH_ALARM = nullptr; + GlobalVar* DEF_MIN_SPEED = nullptr; + GlobalVar* DEF_SERVICE_HRS = nullptr; + GlobalVar* G_SIMACTIVE = nullptr; + GlobalVar* G_SIM_CLEARRESET = nullptr; + GlobalVar* G_SIMCMD_INFLOW = nullptr; + GlobalVar* G_SIMCMD_MODE = nullptr; + GlobalVar* G_SIMCMD_RESET = nullptr; + GlobalVar* G_SIMCMD_TIMESCALE = nullptr; + GlobalVar* G_SIM_LEVEL_MM = nullptr; + GlobalVar* G_SIM_INFLOW_X10 = nullptr; + GlobalVar* G_SIM_DISCH_X10 = nullptr; + GlobalVar* G_SIM_MANIFOLDP = nullptr; + GlobalVar>* G_SIM_PUMPP = nullptr; + GlobalVar>* G_SIM_VIB_X10 = nullptr; + GlobalVar* G_SIM_LSHH = nullptr; + GlobalVar* G_SIM_LSLL_WET = nullptr; + GlobalVar* G_SIM_SPILL = nullptr; + GlobalVar>* G_SIM_THERMALOK = nullptr; + GlobalVar>* G_SIM_SEALLEAK = nullptr; + GlobalVar* G_SIM_MAINSOK = nullptr; + + // Implicit IEC 61131-3 ENO pin (mirrors EN) + IEC_BOOL ENO = true; + + // Constructor + explicit Program_IO_MUX(GlobalVar* IW_LIT101_ref, GlobalVar* IW_FIT201_ref, GlobalVar* IW_FIT301_ref, GlobalVar* IW_PIT302_ref, GlobalVar* IW_PIT311_ref, GlobalVar* IW_PIT321_ref, GlobalVar* IW_PIT331_ref, GlobalVar* IW_VE314_ref, GlobalVar* IW_VE324_ref, GlobalVar* IW_VE334_ref, GlobalVar* IX_LSHH102_ref, GlobalVar* IX_LSLL103_ref, GlobalVar* IX_LSH104_ref, GlobalVar* IX_TE312_ref, GlobalVar* IX_TE322_ref, GlobalVar* IX_TE332_ref, GlobalVar* IX_MSE313_ref, GlobalVar* IX_MSE323_ref, GlobalVar* IX_MSE333_ref, GlobalVar* IX_XA502_ref, GlobalVar* QX_RUNCMD1_ref, GlobalVar* QX_RUNCMD2_ref, GlobalVar* QX_RUNCMD3_ref, GlobalVar* QX_RUNNING1_ref, GlobalVar* QX_RUNNING2_ref, GlobalVar* QX_RUNNING3_ref, GlobalVar* QX_AVAIL1_ref, GlobalVar* QX_AVAIL2_ref, GlobalVar* QX_AVAIL3_ref, GlobalVar* QX_INAUTO_ref, GlobalVar* QX_HIGHLEVEL_ref, GlobalVar* QX_SPILLACTIVE_ref, GlobalVar* QX_TRIPPED1_ref, GlobalVar* QX_TRIPPED2_ref, GlobalVar* QX_TRIPPED3_ref, GlobalVar* QW_LEVEL_ref, GlobalVar* QW_INFLOW_ref, GlobalVar* QW_DISCHARGE_ref, GlobalVar* QW_PUMPSRUNNING_ref, GlobalVar* QW_SPEED_ref, GlobalVar* QW_TIMETOSPILL_ref, GlobalVar* QW_TIMETOLSHH_ref, GlobalVar* QW_NETACCUM_ref, GlobalVar* QW_RUNHOURS1_ref, GlobalVar* QW_RUNHOURS2_ref, GlobalVar* QW_RUNHOURS3_ref, GlobalVar* QW_VOLTOSPILL_ref, GlobalVar* QW_STATIONSTATE_ref, GlobalVar* QW_PUMPSTATE1_ref, GlobalVar* QW_PUMPSTATE2_ref, GlobalVar* QW_PUMPSTATE3_ref, GlobalVar* QW_DUTYPUMP_ref, GlobalVar* QW_ALARMWORD_ref, GlobalVar* QW_CMDACK_ref, GlobalVar* MW_MODE_ref, GlobalVar* MW_CMDWORD_ref, GlobalVar* MW_CMDPARAM_ref, GlobalVar* MW_SPLEVEL_ref, GlobalVar* MW_STARTDUTY_ref, GlobalVar* MW_STARTP2_ref, GlobalVar* MW_STARTP3_ref, GlobalVar* MW_STOPALL_ref, GlobalVar* MW_HIGHALARM_ref, GlobalVar* MW_MINSPEED_ref, GlobalVar* MW_SERVICEHRS_ref, GlobalVar* MW_SIMINFLOW_ref, GlobalVar* MW_SIMMODE_ref, GlobalVar* MW_SIMRESET_ref, GlobalVar* MW_SIMTIMESCALE_ref, GlobalVar* G_LEVELRAW_MM_ref, GlobalVar* G_LEVEL_MM_ref, GlobalVar* G_LEVEL_M_ref, GlobalVar* G_INFLOW_LPS_ref, GlobalVar* G_DISCH_LPS_ref, GlobalVar* G_MANIFOLDP_KPA_ref, GlobalVar>* G_PUMPP_KPA_ref, GlobalVar>* G_VIB_MMS_ref, GlobalVar* G_LSHH_ref, GlobalVar* G_LSLL_WET_ref, GlobalVar* G_SPILLDETECTED_ref, GlobalVar>* G_THERMALOK_ref, GlobalVar>* G_SEALLEAK_ref, GlobalVar* G_MAINSOK_ref, GlobalVar* G_CMD_MODE_ref, GlobalVar* G_CMD_WORD_ref, GlobalVar* G_CMD_PARAM_ref, GlobalVar* G_SP_LEVEL_ref, GlobalVar* G_SP_STARTDUTY_ref, GlobalVar* G_SP_STARTP2_ref, GlobalVar* G_SP_STARTP3_ref, GlobalVar* G_SP_STOPALL_ref, GlobalVar* G_SP_HIGHALARM_ref, GlobalVar* G_SP_MINSPEED_ref, GlobalVar* G_SP_SERVICEHRS_ref, GlobalVar>* G_O_RUNCMD_ref, GlobalVar>* G_O_RUNNING_ref, GlobalVar>* G_O_AVAILABLE_ref, GlobalVar>* G_O_TRIPPED_ref, GlobalVar* G_O_INAUTO_ref, GlobalVar* G_O_HIGHLEVEL_ref, GlobalVar* G_O_SPILLACTIVE_ref, GlobalVar* G_O_LEVEL_MM_ref, GlobalVar* G_O_INFLOW_X10_ref, GlobalVar* G_O_DISCH_X10_ref, GlobalVar* G_O_PUMPSRUN_ref, GlobalVar* G_O_SPEED_X10_ref, GlobalVar* G_O_TIMETOSPILL_ref, GlobalVar* G_O_TIMETOLSHH_ref, GlobalVar* G_O_NETACCUM_ref, GlobalVar>* G_O_RUNHOURS_ref, GlobalVar* G_O_VOLTOSPILL_ref, GlobalVar* G_O_STATIONSTATE_ref, GlobalVar>* G_O_PUMPSTATE_ref, GlobalVar* G_O_DUTYPUMP_ref, GlobalVar* G_O_ALARMWORD_ref, GlobalVar* G_O_CMDACK_ref, GlobalVar* DEF_MODE_ref, GlobalVar* DEF_SP_LEVEL_ref, GlobalVar* DEF_START_DUTY_ref, GlobalVar* DEF_START_P2_ref, GlobalVar* DEF_START_P3_ref, GlobalVar* DEF_STOP_ALL_ref, GlobalVar* DEF_HIGH_ALARM_ref, GlobalVar* DEF_MIN_SPEED_ref, GlobalVar* DEF_SERVICE_HRS_ref, GlobalVar* G_SIMACTIVE_ref, GlobalVar* G_SIM_CLEARRESET_ref, GlobalVar* G_SIMCMD_INFLOW_ref, GlobalVar* G_SIMCMD_MODE_ref, GlobalVar* G_SIMCMD_RESET_ref, GlobalVar* G_SIMCMD_TIMESCALE_ref, GlobalVar* G_SIM_LEVEL_MM_ref, GlobalVar* G_SIM_INFLOW_X10_ref, GlobalVar* G_SIM_DISCH_X10_ref, GlobalVar* G_SIM_MANIFOLDP_ref, GlobalVar>* G_SIM_PUMPP_ref, GlobalVar>* G_SIM_VIB_X10_ref, GlobalVar* G_SIM_LSHH_ref, GlobalVar* G_SIM_LSLL_WET_ref, GlobalVar* G_SIM_SPILL_ref, GlobalVar>* G_SIM_THERMALOK_ref, GlobalVar>* G_SIM_SEALLEAK_ref, GlobalVar* G_SIM_MAINSOK_ref); + + // Run program + void run() override; +}; + +class Configuration_CONFIG0 : public ConfigurationInstance { +public: + // Program instances + Program_SIMULATION INST_SIM; + Program_IO_MUX INST_MUX; + Program_CONTROL INST_CTL; + + // Task storage + TaskInstance tasks_storage[1]; + ProgramBase* task_programs_storage[3]; + // Resource storage + ResourceInstance resources_storage[1]; + + // Constructor + Configuration_CONFIG0(); + + // ConfigurationInstance interface + const char* get_name() const override; + ResourceInstance* get_resources() override; + size_t get_resource_count() const override; +}; + +// ============================================================================= +// Located Variables Descriptor Array +// ============================================================================= + +/** + * Located variable descriptors for runtime I/O binding. + * The runtime iterates this array to bind variables to I/O image tables. + */ + +// Forward: IW_LIT101 AT %IW0 in configuration +// Forward: IW_FIT201 AT %IW1 in configuration +// Forward: IW_FIT301 AT %IW2 in configuration +// Forward: IW_PIT302 AT %IW3 in configuration +// Forward: IW_PIT311 AT %IW4 in configuration +// Forward: IW_PIT321 AT %IW5 in configuration +// Forward: IW_PIT331 AT %IW6 in configuration +// Forward: IW_VE314 AT %IW7 in configuration +// Forward: IW_VE324 AT %IW8 in configuration +// Forward: IW_VE334 AT %IW9 in configuration +// Forward: IX_LSHH102 AT %IX0.0 in configuration +// Forward: IX_LSLL103 AT %IX0.1 in configuration +// Forward: IX_LSH104 AT %IX0.2 in configuration +// Forward: IX_TE312 AT %IX0.3 in configuration +// Forward: IX_TE322 AT %IX0.4 in configuration +// Forward: IX_TE332 AT %IX0.5 in configuration +// Forward: IX_MSE313 AT %IX0.6 in configuration +// Forward: IX_MSE323 AT %IX0.7 in configuration +// Forward: IX_MSE333 AT %IX1.0 in configuration +// Forward: IX_XA502 AT %IX1.1 in configuration +// Forward: QX_RUNCMD1 AT %QX0.0 in configuration +// Forward: QX_RUNCMD2 AT %QX0.1 in configuration +// Forward: QX_RUNCMD3 AT %QX0.2 in configuration +// Forward: QX_RUNNING1 AT %QX0.3 in configuration +// Forward: QX_RUNNING2 AT %QX0.4 in configuration +// Forward: QX_RUNNING3 AT %QX0.5 in configuration +// Forward: QX_AVAIL1 AT %QX0.6 in configuration +// Forward: QX_AVAIL2 AT %QX0.7 in configuration +// Forward: QX_AVAIL3 AT %QX1.0 in configuration +// Forward: QX_INAUTO AT %QX1.1 in configuration +// Forward: QX_HIGHLEVEL AT %QX1.2 in configuration +// Forward: QX_SPILLACTIVE AT %QX1.3 in configuration +// Forward: QX_TRIPPED1 AT %QX1.4 in configuration +// Forward: QX_TRIPPED2 AT %QX1.5 in configuration +// Forward: QX_TRIPPED3 AT %QX1.6 in configuration +// Forward: QW_LEVEL AT %QW0 in configuration +// Forward: QW_INFLOW AT %QW1 in configuration +// Forward: QW_DISCHARGE AT %QW2 in configuration +// Forward: QW_PUMPSRUNNING AT %QW3 in configuration +// Forward: QW_SPEED AT %QW4 in configuration +// Forward: QW_TIMETOSPILL AT %QW5 in configuration +// Forward: QW_TIMETOLSHH AT %QW6 in configuration +// Forward: QW_NETACCUM AT %QW7 in configuration +// Forward: QW_RUNHOURS1 AT %QW8 in configuration +// Forward: QW_RUNHOURS2 AT %QW9 in configuration +// Forward: QW_RUNHOURS3 AT %QW10 in configuration +// Forward: QW_VOLTOSPILL AT %QW11 in configuration +// Forward: QW_STATIONSTATE AT %QW12 in configuration +// Forward: QW_PUMPSTATE1 AT %QW13 in configuration +// Forward: QW_PUMPSTATE2 AT %QW14 in configuration +// Forward: QW_PUMPSTATE3 AT %QW15 in configuration +// Forward: QW_DUTYPUMP AT %QW16 in configuration +// Forward: QW_ALARMWORD AT %QW17 in configuration +// Forward: QW_CMDACK AT %QW20 in configuration +// Forward: MW_MODE AT %MW0 in configuration +// Forward: MW_CMDWORD AT %MW1 in configuration +// Forward: MW_CMDPARAM AT %MW2 in configuration +// Forward: MW_SPLEVEL AT %MW3 in configuration +// Forward: MW_STARTDUTY AT %MW4 in configuration +// Forward: MW_STARTP2 AT %MW5 in configuration +// Forward: MW_STARTP3 AT %MW6 in configuration +// Forward: MW_STOPALL AT %MW7 in configuration +// Forward: MW_HIGHALARM AT %MW8 in configuration +// Forward: MW_MINSPEED AT %MW9 in configuration +// Forward: MW_SERVICEHRS AT %MW10 in configuration +// Forward: MW_SIMINFLOW AT %MW20 in configuration +// Forward: MW_SIMMODE AT %MW21 in configuration +// Forward: MW_SIMRESET AT %MW22 in configuration +// Forward: MW_SIMTIMESCALE AT %MW23 in configuration + +extern LocatedVar locatedVars[69]; +constexpr uint32_t locatedVarsCount = 69; + +#ifdef STRUCPP_THREADED +extern void *locatedGlobals[69]; +constexpr uint32_t locatedGlobalsCount = 69; +#endif + +} // namespace strucpp \ No newline at end of file diff --git a/03-plc/as-built/generated_debug.cpp b/03-plc/as-built/generated_debug.cpp new file mode 100644 index 0000000..2f920cd --- /dev/null +++ b/03-plc/as-built/generated_debug.cpp @@ -0,0 +1,522 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by STruC++ debug-table-gen - Do not edit by hand. +// +// Per-project debugger pointer tables consumed by +// strucpp::debug::handle_*() in debug_dispatch.hpp. + +#include "generated.hpp" +#include "debug_table.hpp" + +// The sketch/runtime must define this global with external linkage: +// strucpp::Configuration_CONFIG0 g_config; +// The debug table below reaches into it via compile-time +// address-of expressions — so it must be a real object, not a +// static-local or a pointer. +extern ::strucpp::Configuration_CONFIG0 g_config; + +namespace strucpp { namespace debug { + +const Entry debug_arr_0[177] STRUCPP_DEBUG_FLASH = { + { (void*)&CFG_AREA_M2.value, TAG_REAL, 0 }, // CFG_AREA_M2 + { (void*)&CFG_SPILL_M.value, TAG_REAL, 0 }, // CFG_SPILL_M + { (void*)&CFG_LSHH_M.value, TAG_REAL, 0 }, // CFG_LSHH_M + { (void*)&CFG_MIN_HZ.value, TAG_REAL, 0 }, // CFG_MIN_HZ + { (void*)&CFG_MAX_HZ.value, TAG_REAL, 0 }, // CFG_MAX_HZ + { (void*)&DEF_MODE.value, TAG_INT, 0 }, // DEF_MODE + { (void*)&DEF_SP_LEVEL.value, TAG_INT, 0 }, // DEF_SP_LEVEL + { (void*)&DEF_START_DUTY.value, TAG_INT, 0 }, // DEF_START_DUTY + { (void*)&DEF_START_P2.value, TAG_INT, 0 }, // DEF_START_P2 + { (void*)&DEF_START_P3.value, TAG_INT, 0 }, // DEF_START_P3 + { (void*)&DEF_STOP_ALL.value, TAG_INT, 0 }, // DEF_STOP_ALL + { (void*)&DEF_HIGH_ALARM.value, TAG_INT, 0 }, // DEF_HIGH_ALARM + { (void*)&DEF_MIN_SPEED.value, TAG_INT, 0 }, // DEF_MIN_SPEED + { (void*)&DEF_SERVICE_HRS.value, TAG_INT, 0 }, // DEF_SERVICE_HRS + { (void*)&CFG_NO_TIME.value, TAG_INT, 0 }, // CFG_NO_TIME + { (void*)&IW_LIT101.value, TAG_INT, 0 }, // IW_LIT101 + { (void*)&IW_FIT201.value, TAG_INT, 0 }, // IW_FIT201 + { (void*)&IW_FIT301.value, TAG_INT, 0 }, // IW_FIT301 + { (void*)&IW_PIT302.value, TAG_INT, 0 }, // IW_PIT302 + { (void*)&IW_PIT311.value, TAG_INT, 0 }, // IW_PIT311 + { (void*)&IW_PIT321.value, TAG_INT, 0 }, // IW_PIT321 + { (void*)&IW_PIT331.value, TAG_INT, 0 }, // IW_PIT331 + { (void*)&IW_VE314.value, TAG_INT, 0 }, // IW_VE314 + { (void*)&IW_VE324.value, TAG_INT, 0 }, // IW_VE324 + { (void*)&IW_VE334.value, TAG_INT, 0 }, // IW_VE334 + { (void*)&IX_LSHH102.value, TAG_BOOL, 0 }, // IX_LSHH102 + { (void*)&IX_LSLL103.value, TAG_BOOL, 0 }, // IX_LSLL103 + { (void*)&IX_LSH104.value, TAG_BOOL, 0 }, // IX_LSH104 + { (void*)&IX_TE312.value, TAG_BOOL, 0 }, // IX_TE312 + { (void*)&IX_TE322.value, TAG_BOOL, 0 }, // IX_TE322 + { (void*)&IX_TE332.value, TAG_BOOL, 0 }, // IX_TE332 + { (void*)&IX_MSE313.value, TAG_BOOL, 0 }, // IX_MSE313 + { (void*)&IX_MSE323.value, TAG_BOOL, 0 }, // IX_MSE323 + { (void*)&IX_MSE333.value, TAG_BOOL, 0 }, // IX_MSE333 + { (void*)&IX_XA502.value, TAG_BOOL, 0 }, // IX_XA502 + { (void*)&QX_RUNCMD1.value, TAG_BOOL, 0 }, // QX_RUNCMD1 + { (void*)&QX_RUNCMD2.value, TAG_BOOL, 0 }, // QX_RUNCMD2 + { (void*)&QX_RUNCMD3.value, TAG_BOOL, 0 }, // QX_RUNCMD3 + { (void*)&QX_RUNNING1.value, TAG_BOOL, 0 }, // QX_RUNNING1 + { (void*)&QX_RUNNING2.value, TAG_BOOL, 0 }, // QX_RUNNING2 + { (void*)&QX_RUNNING3.value, TAG_BOOL, 0 }, // QX_RUNNING3 + { (void*)&QX_AVAIL1.value, TAG_BOOL, 0 }, // QX_AVAIL1 + { (void*)&QX_AVAIL2.value, TAG_BOOL, 0 }, // QX_AVAIL2 + { (void*)&QX_AVAIL3.value, TAG_BOOL, 0 }, // QX_AVAIL3 + { (void*)&QX_INAUTO.value, TAG_BOOL, 0 }, // QX_INAUTO + { (void*)&QX_HIGHLEVEL.value, TAG_BOOL, 0 }, // QX_HIGHLEVEL + { (void*)&QX_SPILLACTIVE.value, TAG_BOOL, 0 }, // QX_SPILLACTIVE + { (void*)&QX_TRIPPED1.value, TAG_BOOL, 0 }, // QX_TRIPPED1 + { (void*)&QX_TRIPPED2.value, TAG_BOOL, 0 }, // QX_TRIPPED2 + { (void*)&QX_TRIPPED3.value, TAG_BOOL, 0 }, // QX_TRIPPED3 + { (void*)&QW_LEVEL.value, TAG_INT, 0 }, // QW_LEVEL + { (void*)&QW_INFLOW.value, TAG_INT, 0 }, // QW_INFLOW + { (void*)&QW_DISCHARGE.value, TAG_INT, 0 }, // QW_DISCHARGE + { (void*)&QW_PUMPSRUNNING.value, TAG_INT, 0 }, // QW_PUMPSRUNNING + { (void*)&QW_SPEED.value, TAG_INT, 0 }, // QW_SPEED + { (void*)&QW_TIMETOSPILL.value, TAG_INT, 0 }, // QW_TIMETOSPILL + { (void*)&QW_TIMETOLSHH.value, TAG_INT, 0 }, // QW_TIMETOLSHH + { (void*)&QW_NETACCUM.value, TAG_INT, 0 }, // QW_NETACCUM + { (void*)&QW_RUNHOURS1.value, TAG_INT, 0 }, // QW_RUNHOURS1 + { (void*)&QW_RUNHOURS2.value, TAG_INT, 0 }, // QW_RUNHOURS2 + { (void*)&QW_RUNHOURS3.value, TAG_INT, 0 }, // QW_RUNHOURS3 + { (void*)&QW_VOLTOSPILL.value, TAG_INT, 0 }, // QW_VOLTOSPILL + { (void*)&QW_STATIONSTATE.value, TAG_INT, 0 }, // QW_STATIONSTATE + { (void*)&QW_PUMPSTATE1.value, TAG_INT, 0 }, // QW_PUMPSTATE1 + { (void*)&QW_PUMPSTATE2.value, TAG_INT, 0 }, // QW_PUMPSTATE2 + { (void*)&QW_PUMPSTATE3.value, TAG_INT, 0 }, // QW_PUMPSTATE3 + { (void*)&QW_DUTYPUMP.value, TAG_INT, 0 }, // QW_DUTYPUMP + { (void*)&QW_ALARMWORD.value, TAG_INT, 0 }, // QW_ALARMWORD + { (void*)&QW_CMDACK.value, TAG_INT, 0 }, // QW_CMDACK + { (void*)&MW_MODE.value, TAG_INT, 0 }, // MW_MODE + { (void*)&MW_CMDWORD.value, TAG_INT, 0 }, // MW_CMDWORD + { (void*)&MW_CMDPARAM.value, TAG_INT, 0 }, // MW_CMDPARAM + { (void*)&MW_SPLEVEL.value, TAG_INT, 0 }, // MW_SPLEVEL + { (void*)&MW_STARTDUTY.value, TAG_INT, 0 }, // MW_STARTDUTY + { (void*)&MW_STARTP2.value, TAG_INT, 0 }, // MW_STARTP2 + { (void*)&MW_STARTP3.value, TAG_INT, 0 }, // MW_STARTP3 + { (void*)&MW_STOPALL.value, TAG_INT, 0 }, // MW_STOPALL + { (void*)&MW_HIGHALARM.value, TAG_INT, 0 }, // MW_HIGHALARM + { (void*)&MW_MINSPEED.value, TAG_INT, 0 }, // MW_MINSPEED + { (void*)&MW_SERVICEHRS.value, TAG_INT, 0 }, // MW_SERVICEHRS + { (void*)&MW_SIMINFLOW.value, TAG_INT, 0 }, // MW_SIMINFLOW + { (void*)&MW_SIMMODE.value, TAG_INT, 0 }, // MW_SIMMODE + { (void*)&MW_SIMRESET.value, TAG_INT, 0 }, // MW_SIMRESET + { (void*)&MW_SIMTIMESCALE.value, TAG_INT, 0 }, // MW_SIMTIMESCALE + { (void*)&G_LEVELRAW_MM.value, TAG_INT, 0 }, // G_LEVELRAW_MM + { (void*)&G_LEVEL_MM.value, TAG_INT, 0 }, // G_LEVEL_MM + { (void*)&G_LEVEL_M.value, TAG_REAL, 0 }, // G_LEVEL_M + { (void*)&G_INFLOW_LPS.value, TAG_REAL, 0 }, // G_INFLOW_LPS + { (void*)&G_DISCH_LPS.value, TAG_REAL, 0 }, // G_DISCH_LPS + { (void*)&G_MANIFOLDP_KPA.value, TAG_REAL, 0 }, // G_MANIFOLDP_KPA + { (void*)&G_PUMPP_KPA.value[1], TAG_REAL, 0 }, // G_PUMPP_KPA[1] + { (void*)&G_PUMPP_KPA.value[2], TAG_REAL, 0 }, // G_PUMPP_KPA[2] + { (void*)&G_PUMPP_KPA.value[3], TAG_REAL, 0 }, // G_PUMPP_KPA[3] + { (void*)&G_VIB_MMS.value[1], TAG_REAL, 0 }, // G_VIB_MMS[1] + { (void*)&G_VIB_MMS.value[2], TAG_REAL, 0 }, // G_VIB_MMS[2] + { (void*)&G_VIB_MMS.value[3], TAG_REAL, 0 }, // G_VIB_MMS[3] + { (void*)&G_LSHH.value, TAG_BOOL, 0 }, // G_LSHH + { (void*)&G_LSLL_WET.value, TAG_BOOL, 0 }, // G_LSLL_WET + { (void*)&G_SPILLDETECTED.value, TAG_BOOL, 0 }, // G_SPILLDETECTED + { (void*)&G_THERMALOK.value[1], TAG_BOOL, 0 }, // G_THERMALOK[1] + { (void*)&G_THERMALOK.value[2], TAG_BOOL, 0 }, // G_THERMALOK[2] + { (void*)&G_THERMALOK.value[3], TAG_BOOL, 0 }, // G_THERMALOK[3] + { (void*)&G_SEALLEAK.value[1], TAG_BOOL, 0 }, // G_SEALLEAK[1] + { (void*)&G_SEALLEAK.value[2], TAG_BOOL, 0 }, // G_SEALLEAK[2] + { (void*)&G_SEALLEAK.value[3], TAG_BOOL, 0 }, // G_SEALLEAK[3] + { (void*)&G_MAINSOK.value, TAG_BOOL, 0 }, // G_MAINSOK + { (void*)&G_CMD_MODE.value, TAG_INT, 0 }, // G_CMD_MODE + { (void*)&G_CMD_WORD.value, TAG_INT, 0 }, // G_CMD_WORD + { (void*)&G_CMD_PARAM.value, TAG_INT, 0 }, // G_CMD_PARAM + { (void*)&G_SP_LEVEL.value, TAG_INT, 0 }, // G_SP_LEVEL + { (void*)&G_SP_STARTDUTY.value, TAG_INT, 0 }, // G_SP_STARTDUTY + { (void*)&G_SP_STARTP2.value, TAG_INT, 0 }, // G_SP_STARTP2 + { (void*)&G_SP_STARTP3.value, TAG_INT, 0 }, // G_SP_STARTP3 + { (void*)&G_SP_STOPALL.value, TAG_INT, 0 }, // G_SP_STOPALL + { (void*)&G_SP_HIGHALARM.value, TAG_INT, 0 }, // G_SP_HIGHALARM + { (void*)&G_SP_MINSPEED.value, TAG_INT, 0 }, // G_SP_MINSPEED + { (void*)&G_SP_SERVICEHRS.value, TAG_INT, 0 }, // G_SP_SERVICEHRS + { (void*)&G_O_RUNCMD.value[1], TAG_BOOL, 0 }, // G_O_RUNCMD[1] + { (void*)&G_O_RUNCMD.value[2], TAG_BOOL, 0 }, // G_O_RUNCMD[2] + { (void*)&G_O_RUNCMD.value[3], TAG_BOOL, 0 }, // G_O_RUNCMD[3] + { (void*)&G_O_RUNNING.value[1], TAG_BOOL, 0 }, // G_O_RUNNING[1] + { (void*)&G_O_RUNNING.value[2], TAG_BOOL, 0 }, // G_O_RUNNING[2] + { (void*)&G_O_RUNNING.value[3], TAG_BOOL, 0 }, // G_O_RUNNING[3] + { (void*)&G_O_AVAILABLE.value[1], TAG_BOOL, 0 }, // G_O_AVAILABLE[1] + { (void*)&G_O_AVAILABLE.value[2], TAG_BOOL, 0 }, // G_O_AVAILABLE[2] + { (void*)&G_O_AVAILABLE.value[3], TAG_BOOL, 0 }, // G_O_AVAILABLE[3] + { (void*)&G_O_TRIPPED.value[1], TAG_BOOL, 0 }, // G_O_TRIPPED[1] + { (void*)&G_O_TRIPPED.value[2], TAG_BOOL, 0 }, // G_O_TRIPPED[2] + { (void*)&G_O_TRIPPED.value[3], TAG_BOOL, 0 }, // G_O_TRIPPED[3] + { (void*)&G_O_INAUTO.value, TAG_BOOL, 0 }, // G_O_INAUTO + { (void*)&G_O_HIGHLEVEL.value, TAG_BOOL, 0 }, // G_O_HIGHLEVEL + { (void*)&G_O_SPILLACTIVE.value, TAG_BOOL, 0 }, // G_O_SPILLACTIVE + { (void*)&G_O_LEVEL_MM.value, TAG_INT, 0 }, // G_O_LEVEL_MM + { (void*)&G_O_INFLOW_X10.value, TAG_INT, 0 }, // G_O_INFLOW_X10 + { (void*)&G_O_DISCH_X10.value, TAG_INT, 0 }, // G_O_DISCH_X10 + { (void*)&G_O_PUMPSRUN.value, TAG_INT, 0 }, // G_O_PUMPSRUN + { (void*)&G_O_SPEED_X10.value, TAG_INT, 0 }, // G_O_SPEED_X10 + { (void*)&G_O_TIMETOSPILL.value, TAG_INT, 0 }, // G_O_TIMETOSPILL + { (void*)&G_O_TIMETOLSHH.value, TAG_INT, 0 }, // G_O_TIMETOLSHH + { (void*)&G_O_NETACCUM.value, TAG_INT, 0 }, // G_O_NETACCUM + { (void*)&G_O_RUNHOURS.value[1], TAG_INT, 0 }, // G_O_RUNHOURS[1] + { (void*)&G_O_RUNHOURS.value[2], TAG_INT, 0 }, // G_O_RUNHOURS[2] + { (void*)&G_O_RUNHOURS.value[3], TAG_INT, 0 }, // G_O_RUNHOURS[3] + { (void*)&G_O_VOLTOSPILL.value, TAG_INT, 0 }, // G_O_VOLTOSPILL + { (void*)&G_O_STATIONSTATE.value, TAG_INT, 0 }, // G_O_STATIONSTATE + { (void*)&G_O_PUMPSTATE.value[1], TAG_INT, 0 }, // G_O_PUMPSTATE[1] + { (void*)&G_O_PUMPSTATE.value[2], TAG_INT, 0 }, // G_O_PUMPSTATE[2] + { (void*)&G_O_PUMPSTATE.value[3], TAG_INT, 0 }, // G_O_PUMPSTATE[3] + { (void*)&G_O_DUTYPUMP.value, TAG_INT, 0 }, // G_O_DUTYPUMP + { (void*)&G_O_ALARMWORD.value, TAG_INT, 0 }, // G_O_ALARMWORD + { (void*)&G_O_CMDACK.value, TAG_INT, 0 }, // G_O_CMDACK + { (void*)&G_SIMACTIVE.value, TAG_BOOL, 0 }, // G_SIMACTIVE + { (void*)&G_SIM_CLEARRESET.value, TAG_BOOL, 0 }, // G_SIM_CLEARRESET + { (void*)&G_SIMCMD_INFLOW.value, TAG_INT, 0 }, // G_SIMCMD_INFLOW + { (void*)&G_SIMCMD_MODE.value, TAG_INT, 0 }, // G_SIMCMD_MODE + { (void*)&G_SIMCMD_RESET.value, TAG_INT, 0 }, // G_SIMCMD_RESET + { (void*)&G_SIMCMD_TIMESCALE.value, TAG_INT, 0 }, // G_SIMCMD_TIMESCALE + { (void*)&G_SIM_LEVEL_MM.value, TAG_INT, 0 }, // G_SIM_LEVEL_MM + { (void*)&G_SIM_INFLOW_X10.value, TAG_INT, 0 }, // G_SIM_INFLOW_X10 + { (void*)&G_SIM_DISCH_X10.value, TAG_INT, 0 }, // G_SIM_DISCH_X10 + { (void*)&G_SIM_MANIFOLDP.value, TAG_INT, 0 }, // G_SIM_MANIFOLDP + { (void*)&G_SIM_PUMPP.value[1], TAG_INT, 0 }, // G_SIM_PUMPP[1] + { (void*)&G_SIM_PUMPP.value[2], TAG_INT, 0 }, // G_SIM_PUMPP[2] + { (void*)&G_SIM_PUMPP.value[3], TAG_INT, 0 }, // G_SIM_PUMPP[3] + { (void*)&G_SIM_VIB_X10.value[1], TAG_INT, 0 }, // G_SIM_VIB_X10[1] + { (void*)&G_SIM_VIB_X10.value[2], TAG_INT, 0 }, // G_SIM_VIB_X10[2] + { (void*)&G_SIM_VIB_X10.value[3], TAG_INT, 0 }, // G_SIM_VIB_X10[3] + { (void*)&G_SIM_LSHH.value, TAG_BOOL, 0 }, // G_SIM_LSHH + { (void*)&G_SIM_LSLL_WET.value, TAG_BOOL, 0 }, // G_SIM_LSLL_WET + { (void*)&G_SIM_SPILL.value, TAG_BOOL, 0 }, // G_SIM_SPILL + { (void*)&G_SIM_THERMALOK.value[1], TAG_BOOL, 0 }, // G_SIM_THERMALOK[1] + { (void*)&G_SIM_THERMALOK.value[2], TAG_BOOL, 0 }, // G_SIM_THERMALOK[2] + { (void*)&G_SIM_THERMALOK.value[3], TAG_BOOL, 0 }, // G_SIM_THERMALOK[3] + { (void*)&G_SIM_SEALLEAK.value[1], TAG_BOOL, 0 }, // G_SIM_SEALLEAK[1] + { (void*)&G_SIM_SEALLEAK.value[2], TAG_BOOL, 0 }, // G_SIM_SEALLEAK[2] + { (void*)&G_SIM_SEALLEAK.value[3], TAG_BOOL, 0 }, // G_SIM_SEALLEAK[3] + { (void*)&G_SIM_MAINSOK.value, TAG_BOOL, 0 }, // G_SIM_MAINSOK +}; + +const Entry debug_arr_1[46] STRUCPP_DEBUG_FLASH = { + { (void*)&g_config.INST_SIM.SCAN_S, TAG_REAL, 0 }, // INST_SIM.SCAN_S + { (void*)&g_config.INST_SIM.AREA_M2, TAG_REAL, 0 }, // INST_SIM.AREA_M2 + { (void*)&g_config.INST_SIM.SPILL_M, TAG_REAL, 0 }, // INST_SIM.SPILL_M + { (void*)&g_config.INST_SIM.LSHH_M, TAG_REAL, 0 }, // INST_SIM.LSHH_M + { (void*)&g_config.INST_SIM.LSLL_M, TAG_REAL, 0 }, // INST_SIM.LSLL_M + { (void*)&g_config.INST_SIM.START_DLY_S, TAG_REAL, 0 }, // INST_SIM.START_DLY_S + { (void*)&g_config.INST_SIM.HZ_LO, TAG_REAL, 0 }, // INST_SIM.HZ_LO + { (void*)&g_config.INST_SIM.HZ_HI, TAG_REAL, 0 }, // INST_SIM.HZ_HI + { (void*)&g_config.INST_SIM.Q_LO, TAG_REAL, 0 }, // INST_SIM.Q_LO + { (void*)&g_config.INST_SIM.Q_HI, TAG_REAL, 0 }, // INST_SIM.Q_HI + { (void*)&g_config.INST_SIM.P_IDLE, TAG_REAL, 0 }, // INST_SIM.P_IDLE + { (void*)&g_config.INST_SIM.P_BASE, TAG_REAL, 0 }, // INST_SIM.P_BASE + { (void*)&g_config.INST_SIM.P_PER_LPS, TAG_REAL, 0 }, // INST_SIM.P_PER_LPS + { (void*)&g_config.INST_SIM.VIB_IDLE, TAG_REAL, 0 }, // INST_SIM.VIB_IDLE + { (void*)&g_config.INST_SIM.VIB_BASE, TAG_REAL, 0 }, // INST_SIM.VIB_BASE + { (void*)&g_config.INST_SIM.VIB_PER_LPS, TAG_REAL, 0 }, // INST_SIM.VIB_PER_LPS + { (void*)&g_config.INST_SIM.INIT, TAG_BOOL, 0 }, // INST_SIM.INIT + { (void*)&g_config.INST_SIM.VOLUME_M3, TAG_REAL, 0 }, // INST_SIM.VOLUME_M3 + { (void*)&g_config.INST_SIM.LEVEL_M, TAG_REAL, 0 }, // INST_SIM.LEVEL_M + { (void*)&g_config.INST_SIM.INFLOW_LPS, TAG_REAL, 0 }, // INST_SIM.INFLOW_LPS + { (void*)&g_config.INST_SIM.SUMFLOW_LPS, TAG_REAL, 0 }, // INST_SIM.SUMFLOW_LPS + { (void*)&g_config.INST_SIM.SIMCLOCK_S, TAG_REAL, 0 }, // INST_SIM.SIMCLOCK_S + { (void*)&g_config.INST_SIM.STARTDLY_S[1], TAG_REAL, 0 }, // INST_SIM.STARTDLY_S[1] + { (void*)&g_config.INST_SIM.STARTDLY_S[2], TAG_REAL, 0 }, // INST_SIM.STARTDLY_S[2] + { (void*)&g_config.INST_SIM.STARTDLY_S[3], TAG_REAL, 0 }, // INST_SIM.STARTDLY_S[3] + { (void*)&g_config.INST_SIM.DELIVERING[1], TAG_BOOL, 0 }, // INST_SIM.DELIVERING[1] + { (void*)&g_config.INST_SIM.DELIVERING[2], TAG_BOOL, 0 }, // INST_SIM.DELIVERING[2] + { (void*)&g_config.INST_SIM.DELIVERING[3], TAG_BOOL, 0 }, // INST_SIM.DELIVERING[3] + { (void*)&g_config.INST_SIM.FLOW_LPS[1], TAG_REAL, 0 }, // INST_SIM.FLOW_LPS[1] + { (void*)&g_config.INST_SIM.FLOW_LPS[2], TAG_REAL, 0 }, // INST_SIM.FLOW_LPS[2] + { (void*)&g_config.INST_SIM.FLOW_LPS[3], TAG_REAL, 0 }, // INST_SIM.FLOW_LPS[3] + { (void*)&g_config.INST_SIM.PRESS_KPA[1], TAG_REAL, 0 }, // INST_SIM.PRESS_KPA[1] + { (void*)&g_config.INST_SIM.PRESS_KPA[2], TAG_REAL, 0 }, // INST_SIM.PRESS_KPA[2] + { (void*)&g_config.INST_SIM.PRESS_KPA[3], TAG_REAL, 0 }, // INST_SIM.PRESS_KPA[3] + { (void*)&g_config.INST_SIM.VIB_MMS[1], TAG_REAL, 0 }, // INST_SIM.VIB_MMS[1] + { (void*)&g_config.INST_SIM.VIB_MMS[2], TAG_REAL, 0 }, // INST_SIM.VIB_MMS[2] + { (void*)&g_config.INST_SIM.VIB_MMS[3], TAG_REAL, 0 }, // INST_SIM.VIB_MMS[3] + { (void*)&g_config.INST_SIM.TIMESCALE, TAG_REAL, 0 }, // INST_SIM.TIMESCALE + { (void*)&g_config.INST_SIM.DT_S, TAG_REAL, 0 }, // INST_SIM.DT_S + { (void*)&g_config.INST_SIM.SPEED_HZ, TAG_REAL, 0 }, // INST_SIM.SPEED_HZ + { (void*)&g_config.INST_SIM.UNITQ_LPS, TAG_REAL, 0 }, // INST_SIM.UNITQ_LPS + { (void*)&g_config.INST_SIM.DERATE, TAG_REAL, 0 }, // INST_SIM.DERATE + { (void*)&g_config.INST_SIM.NDELIVERING, TAG_INT, 0 }, // INST_SIM.NDELIVERING + { (void*)&g_config.INST_SIM.I, TAG_INT, 0 }, // INST_SIM.I + { (void*)&g_config.INST_SIM.RND, TAG_DINT, 0 }, // INST_SIM.RND + { (void*)&g_config.INST_SIM.NOISE, TAG_REAL, 0 }, // INST_SIM.NOISE +}; + +const Entry debug_arr_2[1] STRUCPP_DEBUG_FLASH = { + { (void*)&g_config.INST_MUX.SEEDED, TAG_BOOL, 0 }, // INST_MUX.SEEDED +}; + +const Entry debug_arr_3[251] STRUCPP_DEBUG_FLASH = { + { (void*)&g_config.INST_CTL.SPILL_MM, TAG_INT, 0 }, // INST_CTL.SPILL_MM + { (void*)&g_config.INST_CTL.LEVEL_MAX_MM, TAG_INT, 0 }, // INST_CTL.LEVEL_MAX_MM + { (void*)&g_config.INST_CTL.HARD_MIN_HZ, TAG_REAL, 0 }, // INST_CTL.HARD_MIN_HZ + { (void*)&g_config.INST_CTL.HARD_MAX_HZ, TAG_REAL, 0 }, // INST_CTL.HARD_MAX_HZ + { (void*)&g_config.INST_CTL.PUMP1.RUNREQUEST, TAG_BOOL, 0 }, // INST_CTL.PUMP1.RUNREQUEST + { (void*)&g_config.INST_CTL.PUMP1.SPEEDREF, TAG_REAL, 0 }, // INST_CTL.PUMP1.SPEEDREF + { (void*)&g_config.INST_CTL.PUMP1.THERMALOK, TAG_BOOL, 0 }, // INST_CTL.PUMP1.THERMALOK + { (void*)&g_config.INST_CTL.PUMP1.SEALLEAK, TAG_BOOL, 0 }, // INST_CTL.PUMP1.SEALLEAK + { (void*)&g_config.INST_CTL.PUMP1.VIBRATION, TAG_REAL, 0 }, // INST_CTL.PUMP1.VIBRATION + { (void*)&g_config.INST_CTL.PUMP1.DISCHPRESSURE, TAG_REAL, 0 }, // INST_CTL.PUMP1.DISCHPRESSURE + { (void*)&g_config.INST_CTL.PUMP1.RESETTRIP, TAG_BOOL, 0 }, // INST_CTL.PUMP1.RESETTRIP + { (void*)&g_config.INST_CTL.PUMP1.LOCKOUT, TAG_BOOL, 0 }, // INST_CTL.PUMP1.LOCKOUT + { (void*)&g_config.INST_CTL.PUMP1.MINOFFBYPASS, TAG_BOOL, 0 }, // INST_CTL.PUMP1.MINOFFBYPASS + { (void*)&g_config.INST_CTL.PUMP1.SERVICEINTERVAL, TAG_REAL, 0 }, // INST_CTL.PUMP1.SERVICEINTERVAL + { (void*)&g_config.INST_CTL.PUMP1.RESETHOURS, TAG_BOOL, 0 }, // INST_CTL.PUMP1.RESETHOURS + { (void*)&g_config.INST_CTL.PUMP1.RUNCMD, TAG_BOOL, 0 }, // INST_CTL.PUMP1.RUNCMD + { (void*)&g_config.INST_CTL.PUMP1.RUNNING, TAG_BOOL, 0 }, // INST_CTL.PUMP1.RUNNING + { (void*)&g_config.INST_CTL.PUMP1.AVAILABLE, TAG_BOOL, 0 }, // INST_CTL.PUMP1.AVAILABLE + { (void*)&g_config.INST_CTL.PUMP1.TRIPPED, TAG_BOOL, 0 }, // INST_CTL.PUMP1.TRIPPED + { (void*)&g_config.INST_CTL.PUMP1.STATE, TAG_INT, 0 }, // INST_CTL.PUMP1.STATE + { (void*)&g_config.INST_CTL.PUMP1.RUNHOURS, TAG_REAL, 0 }, // INST_CTL.PUMP1.RUNHOURS + { (void*)&g_config.INST_CTL.PUMP1.SERVICEDUE, TAG_BOOL, 0 }, // INST_CTL.PUMP1.SERVICEDUE + { (void*)&g_config.INST_CTL.PUMP1.VIBALARM, TAG_BOOL, 0 }, // INST_CTL.PUMP1.VIBALARM + { (void*)&g_config.INST_CTL.PUMP1.SEALALARM, TAG_BOOL, 0 }, // INST_CTL.PUMP1.SEALALARM + { (void*)&g_config.INST_CTL.PUMP1.SCAN_S, TAG_REAL, 0 }, // INST_CTL.PUMP1.SCAN_S + { (void*)&g_config.INST_CTL.PUMP1.VIB_ALARM, TAG_REAL, 0 }, // INST_CTL.PUMP1.VIB_ALARM + { (void*)&g_config.INST_CTL.PUMP1.VIB_TRIP, TAG_REAL, 0 }, // INST_CTL.PUMP1.VIB_TRIP + { (void*)&g_config.INST_CTL.PUMP1.NOFLOW_KPA, TAG_REAL, 0 }, // INST_CTL.PUMP1.NOFLOW_KPA + { (void*)&g_config.INST_CTL.PUMP1.MINRUNTMR.IN, TAG_BOOL, 0 }, // INST_CTL.PUMP1.MINRUNTMR.IN + { (void*)&g_config.INST_CTL.PUMP1.MINRUNTMR.PT, TAG_TIME, 0 }, // INST_CTL.PUMP1.MINRUNTMR.PT + { (void*)&g_config.INST_CTL.PUMP1.MINRUNTMR.Q, TAG_BOOL, 0 }, // INST_CTL.PUMP1.MINRUNTMR.Q + { (void*)&g_config.INST_CTL.PUMP1.MINRUNTMR.ET, TAG_TIME, 0 }, // INST_CTL.PUMP1.MINRUNTMR.ET + { (void*)&g_config.INST_CTL.PUMP1.MINOFFTMR.IN, TAG_BOOL, 0 }, // INST_CTL.PUMP1.MINOFFTMR.IN + { (void*)&g_config.INST_CTL.PUMP1.MINOFFTMR.PT, TAG_TIME, 0 }, // INST_CTL.PUMP1.MINOFFTMR.PT + { (void*)&g_config.INST_CTL.PUMP1.MINOFFTMR.Q, TAG_BOOL, 0 }, // INST_CTL.PUMP1.MINOFFTMR.Q + { (void*)&g_config.INST_CTL.PUMP1.MINOFFTMR.ET, TAG_TIME, 0 }, // INST_CTL.PUMP1.MINOFFTMR.ET + { (void*)&g_config.INST_CTL.PUMP1.NOFLOWTMR.IN, TAG_BOOL, 0 }, // INST_CTL.PUMP1.NOFLOWTMR.IN + { (void*)&g_config.INST_CTL.PUMP1.NOFLOWTMR.PT, TAG_TIME, 0 }, // INST_CTL.PUMP1.NOFLOWTMR.PT + { (void*)&g_config.INST_CTL.PUMP1.NOFLOWTMR.Q, TAG_BOOL, 0 }, // INST_CTL.PUMP1.NOFLOWTMR.Q + { (void*)&g_config.INST_CTL.PUMP1.NOFLOWTMR.ET, TAG_TIME, 0 }, // INST_CTL.PUMP1.NOFLOWTMR.ET + { (void*)&g_config.INST_CTL.PUMP1.HASRUN, TAG_BOOL, 0 }, // INST_CTL.PUMP1.HASRUN + { (void*)&g_config.INST_CTL.PUMP1.STARTOK, TAG_BOOL, 0 }, // INST_CTL.PUMP1.STARTOK + { (void*)&g_config.INST_CTL.PUMP2.RUNREQUEST, TAG_BOOL, 0 }, // INST_CTL.PUMP2.RUNREQUEST + { (void*)&g_config.INST_CTL.PUMP2.SPEEDREF, TAG_REAL, 0 }, // INST_CTL.PUMP2.SPEEDREF + { (void*)&g_config.INST_CTL.PUMP2.THERMALOK, TAG_BOOL, 0 }, // INST_CTL.PUMP2.THERMALOK + { (void*)&g_config.INST_CTL.PUMP2.SEALLEAK, TAG_BOOL, 0 }, // INST_CTL.PUMP2.SEALLEAK + { (void*)&g_config.INST_CTL.PUMP2.VIBRATION, TAG_REAL, 0 }, // INST_CTL.PUMP2.VIBRATION + { (void*)&g_config.INST_CTL.PUMP2.DISCHPRESSURE, TAG_REAL, 0 }, // INST_CTL.PUMP2.DISCHPRESSURE + { (void*)&g_config.INST_CTL.PUMP2.RESETTRIP, TAG_BOOL, 0 }, // INST_CTL.PUMP2.RESETTRIP + { (void*)&g_config.INST_CTL.PUMP2.LOCKOUT, TAG_BOOL, 0 }, // INST_CTL.PUMP2.LOCKOUT + { (void*)&g_config.INST_CTL.PUMP2.MINOFFBYPASS, TAG_BOOL, 0 }, // INST_CTL.PUMP2.MINOFFBYPASS + { (void*)&g_config.INST_CTL.PUMP2.SERVICEINTERVAL, TAG_REAL, 0 }, // INST_CTL.PUMP2.SERVICEINTERVAL + { (void*)&g_config.INST_CTL.PUMP2.RESETHOURS, TAG_BOOL, 0 }, // INST_CTL.PUMP2.RESETHOURS + { (void*)&g_config.INST_CTL.PUMP2.RUNCMD, TAG_BOOL, 0 }, // INST_CTL.PUMP2.RUNCMD + { (void*)&g_config.INST_CTL.PUMP2.RUNNING, TAG_BOOL, 0 }, // INST_CTL.PUMP2.RUNNING + { (void*)&g_config.INST_CTL.PUMP2.AVAILABLE, TAG_BOOL, 0 }, // INST_CTL.PUMP2.AVAILABLE + { (void*)&g_config.INST_CTL.PUMP2.TRIPPED, TAG_BOOL, 0 }, // INST_CTL.PUMP2.TRIPPED + { (void*)&g_config.INST_CTL.PUMP2.STATE, TAG_INT, 0 }, // INST_CTL.PUMP2.STATE + { (void*)&g_config.INST_CTL.PUMP2.RUNHOURS, TAG_REAL, 0 }, // INST_CTL.PUMP2.RUNHOURS + { (void*)&g_config.INST_CTL.PUMP2.SERVICEDUE, TAG_BOOL, 0 }, // INST_CTL.PUMP2.SERVICEDUE + { (void*)&g_config.INST_CTL.PUMP2.VIBALARM, TAG_BOOL, 0 }, // INST_CTL.PUMP2.VIBALARM + { (void*)&g_config.INST_CTL.PUMP2.SEALALARM, TAG_BOOL, 0 }, // INST_CTL.PUMP2.SEALALARM + { (void*)&g_config.INST_CTL.PUMP2.SCAN_S, TAG_REAL, 0 }, // INST_CTL.PUMP2.SCAN_S + { (void*)&g_config.INST_CTL.PUMP2.VIB_ALARM, TAG_REAL, 0 }, // INST_CTL.PUMP2.VIB_ALARM + { (void*)&g_config.INST_CTL.PUMP2.VIB_TRIP, TAG_REAL, 0 }, // INST_CTL.PUMP2.VIB_TRIP + { (void*)&g_config.INST_CTL.PUMP2.NOFLOW_KPA, TAG_REAL, 0 }, // INST_CTL.PUMP2.NOFLOW_KPA + { (void*)&g_config.INST_CTL.PUMP2.MINRUNTMR.IN, TAG_BOOL, 0 }, // INST_CTL.PUMP2.MINRUNTMR.IN + { (void*)&g_config.INST_CTL.PUMP2.MINRUNTMR.PT, TAG_TIME, 0 }, // INST_CTL.PUMP2.MINRUNTMR.PT + { (void*)&g_config.INST_CTL.PUMP2.MINRUNTMR.Q, TAG_BOOL, 0 }, // INST_CTL.PUMP2.MINRUNTMR.Q + { (void*)&g_config.INST_CTL.PUMP2.MINRUNTMR.ET, TAG_TIME, 0 }, // INST_CTL.PUMP2.MINRUNTMR.ET + { (void*)&g_config.INST_CTL.PUMP2.MINOFFTMR.IN, TAG_BOOL, 0 }, // INST_CTL.PUMP2.MINOFFTMR.IN + { (void*)&g_config.INST_CTL.PUMP2.MINOFFTMR.PT, TAG_TIME, 0 }, // INST_CTL.PUMP2.MINOFFTMR.PT + { (void*)&g_config.INST_CTL.PUMP2.MINOFFTMR.Q, TAG_BOOL, 0 }, // INST_CTL.PUMP2.MINOFFTMR.Q + { (void*)&g_config.INST_CTL.PUMP2.MINOFFTMR.ET, TAG_TIME, 0 }, // INST_CTL.PUMP2.MINOFFTMR.ET + { (void*)&g_config.INST_CTL.PUMP2.NOFLOWTMR.IN, TAG_BOOL, 0 }, // INST_CTL.PUMP2.NOFLOWTMR.IN + { (void*)&g_config.INST_CTL.PUMP2.NOFLOWTMR.PT, TAG_TIME, 0 }, // INST_CTL.PUMP2.NOFLOWTMR.PT + { (void*)&g_config.INST_CTL.PUMP2.NOFLOWTMR.Q, TAG_BOOL, 0 }, // INST_CTL.PUMP2.NOFLOWTMR.Q + { (void*)&g_config.INST_CTL.PUMP2.NOFLOWTMR.ET, TAG_TIME, 0 }, // INST_CTL.PUMP2.NOFLOWTMR.ET + { (void*)&g_config.INST_CTL.PUMP2.HASRUN, TAG_BOOL, 0 }, // INST_CTL.PUMP2.HASRUN + { (void*)&g_config.INST_CTL.PUMP2.STARTOK, TAG_BOOL, 0 }, // INST_CTL.PUMP2.STARTOK + { (void*)&g_config.INST_CTL.PUMP3.RUNREQUEST, TAG_BOOL, 0 }, // INST_CTL.PUMP3.RUNREQUEST + { (void*)&g_config.INST_CTL.PUMP3.SPEEDREF, TAG_REAL, 0 }, // INST_CTL.PUMP3.SPEEDREF + { (void*)&g_config.INST_CTL.PUMP3.THERMALOK, TAG_BOOL, 0 }, // INST_CTL.PUMP3.THERMALOK + { (void*)&g_config.INST_CTL.PUMP3.SEALLEAK, TAG_BOOL, 0 }, // INST_CTL.PUMP3.SEALLEAK + { (void*)&g_config.INST_CTL.PUMP3.VIBRATION, TAG_REAL, 0 }, // INST_CTL.PUMP3.VIBRATION + { (void*)&g_config.INST_CTL.PUMP3.DISCHPRESSURE, TAG_REAL, 0 }, // INST_CTL.PUMP3.DISCHPRESSURE + { (void*)&g_config.INST_CTL.PUMP3.RESETTRIP, TAG_BOOL, 0 }, // INST_CTL.PUMP3.RESETTRIP + { (void*)&g_config.INST_CTL.PUMP3.LOCKOUT, TAG_BOOL, 0 }, // INST_CTL.PUMP3.LOCKOUT + { (void*)&g_config.INST_CTL.PUMP3.MINOFFBYPASS, TAG_BOOL, 0 }, // INST_CTL.PUMP3.MINOFFBYPASS + { (void*)&g_config.INST_CTL.PUMP3.SERVICEINTERVAL, TAG_REAL, 0 }, // INST_CTL.PUMP3.SERVICEINTERVAL + { (void*)&g_config.INST_CTL.PUMP3.RESETHOURS, TAG_BOOL, 0 }, // INST_CTL.PUMP3.RESETHOURS + { (void*)&g_config.INST_CTL.PUMP3.RUNCMD, TAG_BOOL, 0 }, // INST_CTL.PUMP3.RUNCMD + { (void*)&g_config.INST_CTL.PUMP3.RUNNING, TAG_BOOL, 0 }, // INST_CTL.PUMP3.RUNNING + { (void*)&g_config.INST_CTL.PUMP3.AVAILABLE, TAG_BOOL, 0 }, // INST_CTL.PUMP3.AVAILABLE + { (void*)&g_config.INST_CTL.PUMP3.TRIPPED, TAG_BOOL, 0 }, // INST_CTL.PUMP3.TRIPPED + { (void*)&g_config.INST_CTL.PUMP3.STATE, TAG_INT, 0 }, // INST_CTL.PUMP3.STATE + { (void*)&g_config.INST_CTL.PUMP3.RUNHOURS, TAG_REAL, 0 }, // INST_CTL.PUMP3.RUNHOURS + { (void*)&g_config.INST_CTL.PUMP3.SERVICEDUE, TAG_BOOL, 0 }, // INST_CTL.PUMP3.SERVICEDUE + { (void*)&g_config.INST_CTL.PUMP3.VIBALARM, TAG_BOOL, 0 }, // INST_CTL.PUMP3.VIBALARM + { (void*)&g_config.INST_CTL.PUMP3.SEALALARM, TAG_BOOL, 0 }, // INST_CTL.PUMP3.SEALALARM + { (void*)&g_config.INST_CTL.PUMP3.SCAN_S, TAG_REAL, 0 }, // INST_CTL.PUMP3.SCAN_S + { (void*)&g_config.INST_CTL.PUMP3.VIB_ALARM, TAG_REAL, 0 }, // INST_CTL.PUMP3.VIB_ALARM + { (void*)&g_config.INST_CTL.PUMP3.VIB_TRIP, TAG_REAL, 0 }, // INST_CTL.PUMP3.VIB_TRIP + { (void*)&g_config.INST_CTL.PUMP3.NOFLOW_KPA, TAG_REAL, 0 }, // INST_CTL.PUMP3.NOFLOW_KPA + { (void*)&g_config.INST_CTL.PUMP3.MINRUNTMR.IN, TAG_BOOL, 0 }, // INST_CTL.PUMP3.MINRUNTMR.IN + { (void*)&g_config.INST_CTL.PUMP3.MINRUNTMR.PT, TAG_TIME, 0 }, // INST_CTL.PUMP3.MINRUNTMR.PT + { (void*)&g_config.INST_CTL.PUMP3.MINRUNTMR.Q, TAG_BOOL, 0 }, // INST_CTL.PUMP3.MINRUNTMR.Q + { (void*)&g_config.INST_CTL.PUMP3.MINRUNTMR.ET, TAG_TIME, 0 }, // INST_CTL.PUMP3.MINRUNTMR.ET + { (void*)&g_config.INST_CTL.PUMP3.MINOFFTMR.IN, TAG_BOOL, 0 }, // INST_CTL.PUMP3.MINOFFTMR.IN + { (void*)&g_config.INST_CTL.PUMP3.MINOFFTMR.PT, TAG_TIME, 0 }, // INST_CTL.PUMP3.MINOFFTMR.PT + { (void*)&g_config.INST_CTL.PUMP3.MINOFFTMR.Q, TAG_BOOL, 0 }, // INST_CTL.PUMP3.MINOFFTMR.Q + { (void*)&g_config.INST_CTL.PUMP3.MINOFFTMR.ET, TAG_TIME, 0 }, // INST_CTL.PUMP3.MINOFFTMR.ET + { (void*)&g_config.INST_CTL.PUMP3.NOFLOWTMR.IN, TAG_BOOL, 0 }, // INST_CTL.PUMP3.NOFLOWTMR.IN + { (void*)&g_config.INST_CTL.PUMP3.NOFLOWTMR.PT, TAG_TIME, 0 }, // INST_CTL.PUMP3.NOFLOWTMR.PT + { (void*)&g_config.INST_CTL.PUMP3.NOFLOWTMR.Q, TAG_BOOL, 0 }, // INST_CTL.PUMP3.NOFLOWTMR.Q + { (void*)&g_config.INST_CTL.PUMP3.NOFLOWTMR.ET, TAG_TIME, 0 }, // INST_CTL.PUMP3.NOFLOWTMR.ET + { (void*)&g_config.INST_CTL.PUMP3.HASRUN, TAG_BOOL, 0 }, // INST_CTL.PUMP3.HASRUN + { (void*)&g_config.INST_CTL.PUMP3.STARTOK, TAG_BOOL, 0 }, // INST_CTL.PUMP3.STARTOK + { (void*)&g_config.INST_CTL.DUTY.AVAILABLE[1], TAG_BOOL, 0 }, // INST_CTL.DUTY.AVAILABLE[1] + { (void*)&g_config.INST_CTL.DUTY.AVAILABLE[2], TAG_BOOL, 0 }, // INST_CTL.DUTY.AVAILABLE[2] + { (void*)&g_config.INST_CTL.DUTY.AVAILABLE[3], TAG_BOOL, 0 }, // INST_CTL.DUTY.AVAILABLE[3] + { (void*)&g_config.INST_CTL.DUTY.RUNHOURS[1], TAG_REAL, 0 }, // INST_CTL.DUTY.RUNHOURS[1] + { (void*)&g_config.INST_CTL.DUTY.RUNHOURS[2], TAG_REAL, 0 }, // INST_CTL.DUTY.RUNHOURS[2] + { (void*)&g_config.INST_CTL.DUTY.RUNHOURS[3], TAG_REAL, 0 }, // INST_CTL.DUTY.RUNHOURS[3] + { (void*)&g_config.INST_CTL.DUTY.SERVICEDUE[1], TAG_BOOL, 0 }, // INST_CTL.DUTY.SERVICEDUE[1] + { (void*)&g_config.INST_CTL.DUTY.SERVICEDUE[2], TAG_BOOL, 0 }, // INST_CTL.DUTY.SERVICEDUE[2] + { (void*)&g_config.INST_CTL.DUTY.SERVICEDUE[3], TAG_BOOL, 0 }, // INST_CTL.DUTY.SERVICEDUE[3] + { (void*)&g_config.INST_CTL.DUTY.RUNNINGNOW[1], TAG_BOOL, 0 }, // INST_CTL.DUTY.RUNNINGNOW[1] + { (void*)&g_config.INST_CTL.DUTY.RUNNINGNOW[2], TAG_BOOL, 0 }, // INST_CTL.DUTY.RUNNINGNOW[2] + { (void*)&g_config.INST_CTL.DUTY.RUNNINGNOW[3], TAG_BOOL, 0 }, // INST_CTL.DUTY.RUNNINGNOW[3] + { (void*)&g_config.INST_CTL.DUTY.PUMPSREQUIRED, TAG_INT, 0 }, // INST_CTL.DUTY.PUMPSREQUIRED + { (void*)&g_config.INST_CTL.DUTY.RUNREQUEST[1], TAG_BOOL, 0 }, // INST_CTL.DUTY.RUNREQUEST[1] + { (void*)&g_config.INST_CTL.DUTY.RUNREQUEST[2], TAG_BOOL, 0 }, // INST_CTL.DUTY.RUNREQUEST[2] + { (void*)&g_config.INST_CTL.DUTY.RUNREQUEST[3], TAG_BOOL, 0 }, // INST_CTL.DUTY.RUNREQUEST[3] + { (void*)&g_config.INST_CTL.DUTY.DUTYPUMP, TAG_INT, 0 }, // INST_CTL.DUTY.DUTYPUMP + { (void*)&g_config.INST_CTL.DUTY.SERVICE_PENALTY, TAG_REAL, 0 }, // INST_CTL.DUTY.SERVICE_PENALTY + { (void*)&g_config.INST_CTL.DUTY.RANK[1], TAG_INT, 0 }, // INST_CTL.DUTY.RANK[1] + { (void*)&g_config.INST_CTL.DUTY.RANK[2], TAG_INT, 0 }, // INST_CTL.DUTY.RANK[2] + { (void*)&g_config.INST_CTL.DUTY.RANK[3], TAG_INT, 0 }, // INST_CTL.DUTY.RANK[3] + { (void*)&g_config.INST_CTL.DUTY.USED[1], TAG_BOOL, 0 }, // INST_CTL.DUTY.USED[1] + { (void*)&g_config.INST_CTL.DUTY.USED[2], TAG_BOOL, 0 }, // INST_CTL.DUTY.USED[2] + { (void*)&g_config.INST_CTL.DUTY.USED[3], TAG_BOOL, 0 }, // INST_CTL.DUTY.USED[3] + { (void*)&g_config.INST_CTL.DUTY.SEL[1], TAG_BOOL, 0 }, // INST_CTL.DUTY.SEL[1] + { (void*)&g_config.INST_CTL.DUTY.SEL[2], TAG_BOOL, 0 }, // INST_CTL.DUTY.SEL[2] + { (void*)&g_config.INST_CTL.DUTY.SEL[3], TAG_BOOL, 0 }, // INST_CTL.DUTY.SEL[3] + { (void*)&g_config.INST_CTL.DUTY.I, TAG_INT, 0 }, // INST_CTL.DUTY.I + { (void*)&g_config.INST_CTL.DUTY.K, TAG_INT, 0 }, // INST_CTL.DUTY.K + { (void*)&g_config.INST_CTL.DUTY.BEST, TAG_INT, 0 }, // INST_CTL.DUTY.BEST + { (void*)&g_config.INST_CTL.DUTY.BESTKEY, TAG_REAL, 0 }, // INST_CTL.DUTY.BESTKEY + { (void*)&g_config.INST_CTL.DUTY.KEY, TAG_REAL, 0 }, // INST_CTL.DUTY.KEY + { (void*)&g_config.INST_CTL.DUTY.NRANKED, TAG_INT, 0 }, // INST_CTL.DUTY.NRANKED + { (void*)&g_config.INST_CTL.DUTY.SLOTS, TAG_INT, 0 }, // INST_CTL.DUTY.SLOTS + { (void*)&g_config.INST_CTL.DUTY.CNT, TAG_INT, 0 }, // INST_CTL.DUTY.CNT + { (void*)&g_config.INST_CTL.LVLCTL.LEVEL, TAG_REAL, 0 }, // INST_CTL.LVLCTL.LEVEL + { (void*)&g_config.INST_CTL.LVLCTL.SETPOINT, TAG_REAL, 0 }, // INST_CTL.LVLCTL.SETPOINT + { (void*)&g_config.INST_CTL.LVLCTL.ENABLE, TAG_BOOL, 0 }, // INST_CTL.LVLCTL.ENABLE + { (void*)&g_config.INST_CTL.LVLCTL.MINSPEED, TAG_REAL, 0 }, // INST_CTL.LVLCTL.MINSPEED + { (void*)&g_config.INST_CTL.LVLCTL.MAXSPEED, TAG_REAL, 0 }, // INST_CTL.LVLCTL.MAXSPEED + { (void*)&g_config.INST_CTL.LVLCTL.SPEED, TAG_REAL, 0 }, // INST_CTL.LVLCTL.SPEED + { (void*)&g_config.INST_CTL.LVLCTL.SCAN_S, TAG_REAL, 0 }, // INST_CTL.LVLCTL.SCAN_S + { (void*)&g_config.INST_CTL.LVLCTL.KP, TAG_REAL, 0 }, // INST_CTL.LVLCTL.KP + { (void*)&g_config.INST_CTL.LVLCTL.TI, TAG_REAL, 0 }, // INST_CTL.LVLCTL.TI + { (void*)&g_config.INST_CTL.LVLCTL.INTEG, TAG_REAL, 0 }, // INST_CTL.LVLCTL.INTEG + { (void*)&g_config.INST_CTL.LVLCTL.ERR, TAG_REAL, 0 }, // INST_CTL.LVLCTL.ERR + { (void*)&g_config.INST_CTL.LVLCTL.RAW, TAG_REAL, 0 }, // INST_CTL.LVLCTL.RAW + { (void*)&g_config.INST_CTL.LVLCTL.INTEGRATE, TAG_BOOL, 0 }, // INST_CTL.LVLCTL.INTEGRATE + { (void*)&g_config.INST_CTL.HEAD.LEVEL, TAG_REAL, 0 }, // INST_CTL.HEAD.LEVEL + { (void*)&g_config.INST_CTL.HEAD.INFLOW, TAG_REAL, 0 }, // INST_CTL.HEAD.INFLOW + { (void*)&g_config.INST_CTL.HEAD.TOTALDISCHARGE, TAG_REAL, 0 }, // INST_CTL.HEAD.TOTALDISCHARGE + { (void*)&g_config.INST_CTL.HEAD.INFLOWFILT, TAG_REAL, 0 }, // INST_CTL.HEAD.INFLOWFILT + { (void*)&g_config.INST_CTL.HEAD.NETINFLOW, TAG_REAL, 0 }, // INST_CTL.HEAD.NETINFLOW + { (void*)&g_config.INST_CTL.HEAD.VOLTOSPILL, TAG_REAL, 0 }, // INST_CTL.HEAD.VOLTOSPILL + { (void*)&g_config.INST_CTL.HEAD.VOLTOLSHH, TAG_REAL, 0 }, // INST_CTL.HEAD.VOLTOLSHH + { (void*)&g_config.INST_CTL.HEAD.TIMETOSPILL, TAG_INT, 0 }, // INST_CTL.HEAD.TIMETOSPILL + { (void*)&g_config.INST_CTL.HEAD.TIMETOLSHH, TAG_INT, 0 }, // INST_CTL.HEAD.TIMETOLSHH + { (void*)&g_config.INST_CTL.HEAD.SCAN_S, TAG_REAL, 0 }, // INST_CTL.HEAD.SCAN_S + { (void*)&g_config.INST_CTL.HEAD.TAU_S, TAG_REAL, 0 }, // INST_CTL.HEAD.TAU_S + { (void*)&g_config.INST_CTL.HEAD.AREA_M2, TAG_REAL, 0 }, // INST_CTL.HEAD.AREA_M2 + { (void*)&g_config.INST_CTL.HEAD.SPILL_M, TAG_REAL, 0 }, // INST_CTL.HEAD.SPILL_M + { (void*)&g_config.INST_CTL.HEAD.LSHH_M, TAG_REAL, 0 }, // INST_CTL.HEAD.LSHH_M + { (void*)&g_config.INST_CTL.HEAD.MIN_NET, TAG_REAL, 0 }, // INST_CTL.HEAD.MIN_NET + { (void*)&g_config.INST_CTL.HEAD.NO_TIME, TAG_INT, 0 }, // INST_CTL.HEAD.NO_TIME + { (void*)&g_config.INST_CTL.HEAD.MAX_TIME, TAG_REAL, 0 }, // INST_CTL.HEAD.MAX_TIME + { (void*)&g_config.INST_CTL.HEAD.PRIMED, TAG_BOOL, 0 }, // INST_CTL.HEAD.PRIMED + { (void*)&g_config.INST_CTL.HEAD.T, TAG_REAL, 0 }, // INST_CTL.HEAD.T + { (void*)&g_config.INST_CTL.V_MODE, TAG_INT, 0 }, // INST_CTL.V_MODE + { (void*)&g_config.INST_CTL.V_SPLEVEL, TAG_INT, 0 }, // INST_CTL.V_SPLEVEL + { (void*)&g_config.INST_CTL.V_STARTDUTY, TAG_INT, 0 }, // INST_CTL.V_STARTDUTY + { (void*)&g_config.INST_CTL.V_STARTP2, TAG_INT, 0 }, // INST_CTL.V_STARTP2 + { (void*)&g_config.INST_CTL.V_STARTP3, TAG_INT, 0 }, // INST_CTL.V_STARTP3 + { (void*)&g_config.INST_CTL.V_STOPALL, TAG_INT, 0 }, // INST_CTL.V_STOPALL + { (void*)&g_config.INST_CTL.V_HIGHALARM, TAG_INT, 0 }, // INST_CTL.V_HIGHALARM + { (void*)&g_config.INST_CTL.V_MINSPEED, TAG_INT, 0 }, // INST_CTL.V_MINSPEED + { (void*)&g_config.INST_CTL.V_SERVICEHRS, TAG_INT, 0 }, // INST_CTL.V_SERVICEHRS + { (void*)&g_config.INST_CTL.SPREJECTED, TAG_BOOL, 0 }, // INST_CTL.SPREJECTED + { (void*)&g_config.INST_CTL.SPOK, TAG_BOOL, 0 }, // INST_CTL.SPOK + { (void*)&g_config.INST_CTL.CMDBUSY, TAG_BOOL, 0 }, // INST_CTL.CMDBUSY + { (void*)&g_config.INST_CTL.RESETTRIP[1], TAG_BOOL, 0 }, // INST_CTL.RESETTRIP[1] + { (void*)&g_config.INST_CTL.RESETTRIP[2], TAG_BOOL, 0 }, // INST_CTL.RESETTRIP[2] + { (void*)&g_config.INST_CTL.RESETTRIP[3], TAG_BOOL, 0 }, // INST_CTL.RESETTRIP[3] + { (void*)&g_config.INST_CTL.RESETHOURS[1], TAG_BOOL, 0 }, // INST_CTL.RESETHOURS[1] + { (void*)&g_config.INST_CTL.RESETHOURS[2], TAG_BOOL, 0 }, // INST_CTL.RESETHOURS[2] + { (void*)&g_config.INST_CTL.RESETHOURS[3], TAG_BOOL, 0 }, // INST_CTL.RESETHOURS[3] + { (void*)&g_config.INST_CTL.LOCKOUT[1], TAG_BOOL, 0 }, // INST_CTL.LOCKOUT[1] + { (void*)&g_config.INST_CTL.LOCKOUT[2], TAG_BOOL, 0 }, // INST_CTL.LOCKOUT[2] + { (void*)&g_config.INST_CTL.LOCKOUT[3], TAG_BOOL, 0 }, // INST_CTL.LOCKOUT[3] + { (void*)&g_config.INST_CTL.ACKALARMS, TAG_BOOL, 0 }, // INST_CTL.ACKALARMS + { (void*)&g_config.INST_CTL.P, TAG_INT, 0 }, // INST_CTL.P + { (void*)&g_config.INST_CTL.PUMPSREQUIRED, TAG_INT, 0 }, // INST_CTL.PUMPSREQUIRED + { (void*)&g_config.INST_CTL.PUMPSALLOWED, TAG_INT, 0 }, // INST_CTL.PUMPSALLOWED + { (void*)&g_config.INST_CTL.STAGGERTMR.IN, TAG_BOOL, 0 }, // INST_CTL.STAGGERTMR.IN + { (void*)&g_config.INST_CTL.STAGGERTMR.PT, TAG_TIME, 0 }, // INST_CTL.STAGGERTMR.PT + { (void*)&g_config.INST_CTL.STAGGERTMR.Q, TAG_BOOL, 0 }, // INST_CTL.STAGGERTMR.Q + { (void*)&g_config.INST_CTL.STAGGERTMR.ET, TAG_TIME, 0 }, // INST_CTL.STAGGERTMR.ET + { (void*)&g_config.INST_CTL.STAGGERARM, TAG_BOOL, 0 }, // INST_CTL.STAGGERARM + { (void*)&g_config.INST_CTL.DRYRUN, TAG_BOOL, 0 }, // INST_CTL.DRYRUN + { (void*)&g_config.INST_CTL.DRYLOCKOUT, TAG_BOOL, 0 }, // INST_CTL.DRYLOCKOUT + { (void*)&g_config.INST_CTL.LEVELRANGEFAULT, TAG_BOOL, 0 }, // INST_CTL.LEVELRANGEFAULT + { (void*)&g_config.INST_CTL.LEVELFROZEN, TAG_BOOL, 0 }, // INST_CTL.LEVELFROZEN + { (void*)&g_config.INST_CTL.LEVELFAULT, TAG_BOOL, 0 }, // INST_CTL.LEVELFAULT + { (void*)&g_config.INST_CTL.LEVELREF, TAG_INT, 0 }, // INST_CTL.LEVELREF + { (void*)&g_config.INST_CTL.LEVELMOVED, TAG_BOOL, 0 }, // INST_CTL.LEVELMOVED + { (void*)&g_config.INST_CTL.FROZENTMR.IN, TAG_BOOL, 0 }, // INST_CTL.FROZENTMR.IN + { (void*)&g_config.INST_CTL.FROZENTMR.PT, TAG_TIME, 0 }, // INST_CTL.FROZENTMR.PT + { (void*)&g_config.INST_CTL.FROZENTMR.Q, TAG_BOOL, 0 }, // INST_CTL.FROZENTMR.Q + { (void*)&g_config.INST_CTL.FROZENTMR.ET, TAG_TIME, 0 }, // INST_CTL.FROZENTMR.ET + { (void*)&g_config.INST_CTL.ANYRUNNING, TAG_BOOL, 0 }, // INST_CTL.ANYRUNNING + { (void*)&g_config.INST_CTL.AVAIL[1], TAG_BOOL, 0 }, // INST_CTL.AVAIL[1] + { (void*)&g_config.INST_CTL.AVAIL[2], TAG_BOOL, 0 }, // INST_CTL.AVAIL[2] + { (void*)&g_config.INST_CTL.AVAIL[3], TAG_BOOL, 0 }, // INST_CTL.AVAIL[3] + { (void*)&g_config.INST_CTL.HOURS[1], TAG_REAL, 0 }, // INST_CTL.HOURS[1] + { (void*)&g_config.INST_CTL.HOURS[2], TAG_REAL, 0 }, // INST_CTL.HOURS[2] + { (void*)&g_config.INST_CTL.HOURS[3], TAG_REAL, 0 }, // INST_CTL.HOURS[3] + { (void*)&g_config.INST_CTL.SVCDUE[1], TAG_BOOL, 0 }, // INST_CTL.SVCDUE[1] + { (void*)&g_config.INST_CTL.SVCDUE[2], TAG_BOOL, 0 }, // INST_CTL.SVCDUE[2] + { (void*)&g_config.INST_CTL.SVCDUE[3], TAG_BOOL, 0 }, // INST_CTL.SVCDUE[3] + { (void*)&g_config.INST_CTL.RUNNOW[1], TAG_BOOL, 0 }, // INST_CTL.RUNNOW[1] + { (void*)&g_config.INST_CTL.RUNNOW[2], TAG_BOOL, 0 }, // INST_CTL.RUNNOW[2] + { (void*)&g_config.INST_CTL.RUNNOW[3], TAG_BOOL, 0 }, // INST_CTL.RUNNOW[3] + { (void*)&g_config.INST_CTL.REQ[1], TAG_BOOL, 0 }, // INST_CTL.REQ[1] + { (void*)&g_config.INST_CTL.REQ[2], TAG_BOOL, 0 }, // INST_CTL.REQ[2] + { (void*)&g_config.INST_CTL.REQ[3], TAG_BOOL, 0 }, // INST_CTL.REQ[3] + { (void*)&g_config.INST_CTL.SPEED, TAG_REAL, 0 }, // INST_CTL.SPEED + { (void*)&g_config.INST_CTL.MINSPEEDHZ, TAG_REAL, 0 }, // INST_CTL.MINSPEEDHZ + { (void*)&g_config.INST_CTL.SPLEVEL_M, TAG_REAL, 0 }, // INST_CTL.SPLEVEL_M + { (void*)&g_config.INST_CTL.HIGHLEVEL, TAG_BOOL, 0 }, // INST_CTL.HIGHLEVEL + { (void*)&g_config.INST_CTL.PUMPSRUN, TAG_INT, 0 }, // INST_CTL.PUMPSRUN + { (void*)&g_config.INST_CTL.ALARM, TAG_DINT, 0 }, // INST_CTL.ALARM + { (void*)&g_config.INST_CTL.I, TAG_INT, 0 }, // INST_CTL.I + { (void*)&g_config.INST_CTL.R, TAG_REAL, 0 }, // INST_CTL.R + { (void*)&g_config.INST_CTL.PRIMED, TAG_BOOL, 0 }, // INST_CTL.PRIMED +}; + +const Entry* const debug_arrays[4] STRUCPP_DEBUG_FLASH = { + debug_arr_0, + debug_arr_1, + debug_arr_2, + debug_arr_3, +}; + +const uint16_t debug_array_counts[4] STRUCPP_DEBUG_FLASH = { + 177, + 46, + 1, + 251, +}; + +const uint8_t debug_array_count = 4; + +} } // namespace strucpp::debug diff --git a/03-plc/as-built/pou_CONTROL.cpp b/03-plc/as-built/pou_CONTROL.cpp new file mode 100644 index 0000000..e4e403e --- /dev/null +++ b/03-plc/as-built/pou_CONTROL.cpp @@ -0,0 +1,432 @@ +// Generated by STruC++ - IEC 61131-3 Structured Text to C++ Compiler +// Do not edit this file manually. + +#include "generated.hpp" + +namespace strucpp { + +Program_CONTROL::Program_CONTROL(GlobalVar* G_LEVELRAW_MM_ref, GlobalVar* G_LEVEL_MM_ref, GlobalVar* G_LEVEL_M_ref, GlobalVar* G_INFLOW_LPS_ref, GlobalVar* G_DISCH_LPS_ref, GlobalVar>* G_PUMPP_KPA_ref, GlobalVar>* G_VIB_MMS_ref, GlobalVar* G_LSHH_ref, GlobalVar* G_LSLL_WET_ref, GlobalVar* G_SPILLDETECTED_ref, GlobalVar>* G_THERMALOK_ref, GlobalVar>* G_SEALLEAK_ref, GlobalVar* G_MAINSOK_ref, GlobalVar* G_CMD_MODE_ref, GlobalVar* G_CMD_WORD_ref, GlobalVar* G_CMD_PARAM_ref, GlobalVar* G_SP_LEVEL_ref, GlobalVar* G_SP_STARTDUTY_ref, GlobalVar* G_SP_STARTP2_ref, GlobalVar* G_SP_STARTP3_ref, GlobalVar* G_SP_STOPALL_ref, GlobalVar* G_SP_HIGHALARM_ref, GlobalVar* G_SP_MINSPEED_ref, GlobalVar* G_SP_SERVICEHRS_ref, GlobalVar>* G_O_RUNCMD_ref, GlobalVar>* G_O_RUNNING_ref, GlobalVar>* G_O_AVAILABLE_ref, GlobalVar>* G_O_TRIPPED_ref, GlobalVar* G_O_INAUTO_ref, GlobalVar* G_O_HIGHLEVEL_ref, GlobalVar* G_O_SPILLACTIVE_ref, GlobalVar* G_O_LEVEL_MM_ref, GlobalVar* G_O_INFLOW_X10_ref, GlobalVar* G_O_DISCH_X10_ref, GlobalVar* G_O_PUMPSRUN_ref, GlobalVar* G_O_SPEED_X10_ref, GlobalVar* G_O_TIMETOSPILL_ref, GlobalVar* G_O_TIMETOLSHH_ref, GlobalVar* G_O_NETACCUM_ref, GlobalVar>* G_O_RUNHOURS_ref, GlobalVar* G_O_VOLTOSPILL_ref, GlobalVar* G_O_STATIONSTATE_ref, GlobalVar>* G_O_PUMPSTATE_ref, GlobalVar* G_O_DUTYPUMP_ref, GlobalVar* G_O_ALARMWORD_ref, GlobalVar* G_O_CMDACK_ref) + : SPILL_MM(6000), LEVEL_MAX_MM(7000), HARD_MIN_HZ(38.0), HARD_MAX_HZ(50.0), V_MODE(1), V_SPLEVEL(4200), V_STARTDUTY(4000), V_STARTP2(4500), V_STARTP3(5000), V_STOPALL(1000), V_HIGHALARM(5200), V_MINSPEED(380), V_SERVICEHRS(4000), SPREJECTED(false), SPOK(false), CMDBUSY(false), ACKALARMS(false), P(0), PUMPSREQUIRED(0), PUMPSALLOWED(0), STAGGERARM(false), DRYRUN(false), DRYLOCKOUT(false), LEVELRANGEFAULT(false), LEVELFROZEN(false), LEVELFAULT(false), LEVELREF(0), LEVELMOVED(false), ANYRUNNING(false), SPEED(0.0), MINSPEEDHZ(0.0), SPLEVEL_M(0.0), HIGHLEVEL(false), PUMPSRUN(0), ALARM(0), I(0), R(0.0), PRIMED(false), G_LEVELRAW_MM(G_LEVELRAW_MM_ref), G_LEVEL_MM(G_LEVEL_MM_ref), G_LEVEL_M(G_LEVEL_M_ref), G_INFLOW_LPS(G_INFLOW_LPS_ref), G_DISCH_LPS(G_DISCH_LPS_ref), G_PUMPP_KPA(G_PUMPP_KPA_ref), G_VIB_MMS(G_VIB_MMS_ref), G_LSHH(G_LSHH_ref), G_LSLL_WET(G_LSLL_WET_ref), G_SPILLDETECTED(G_SPILLDETECTED_ref), G_THERMALOK(G_THERMALOK_ref), G_SEALLEAK(G_SEALLEAK_ref), G_MAINSOK(G_MAINSOK_ref), G_CMD_MODE(G_CMD_MODE_ref), G_CMD_WORD(G_CMD_WORD_ref), G_CMD_PARAM(G_CMD_PARAM_ref), G_SP_LEVEL(G_SP_LEVEL_ref), G_SP_STARTDUTY(G_SP_STARTDUTY_ref), G_SP_STARTP2(G_SP_STARTP2_ref), G_SP_STARTP3(G_SP_STARTP3_ref), G_SP_STOPALL(G_SP_STOPALL_ref), G_SP_HIGHALARM(G_SP_HIGHALARM_ref), G_SP_MINSPEED(G_SP_MINSPEED_ref), G_SP_SERVICEHRS(G_SP_SERVICEHRS_ref), G_O_RUNCMD(G_O_RUNCMD_ref), G_O_RUNNING(G_O_RUNNING_ref), G_O_AVAILABLE(G_O_AVAILABLE_ref), G_O_TRIPPED(G_O_TRIPPED_ref), G_O_INAUTO(G_O_INAUTO_ref), G_O_HIGHLEVEL(G_O_HIGHLEVEL_ref), G_O_SPILLACTIVE(G_O_SPILLACTIVE_ref), G_O_LEVEL_MM(G_O_LEVEL_MM_ref), G_O_INFLOW_X10(G_O_INFLOW_X10_ref), G_O_DISCH_X10(G_O_DISCH_X10_ref), G_O_PUMPSRUN(G_O_PUMPSRUN_ref), G_O_SPEED_X10(G_O_SPEED_X10_ref), G_O_TIMETOSPILL(G_O_TIMETOSPILL_ref), G_O_TIMETOLSHH(G_O_TIMETOLSHH_ref), G_O_NETACCUM(G_O_NETACCUM_ref), G_O_RUNHOURS(G_O_RUNHOURS_ref), G_O_VOLTOSPILL(G_O_VOLTOSPILL_ref), G_O_STATIONSTATE(G_O_STATIONSTATE_ref), G_O_PUMPSTATE(G_O_PUMPSTATE_ref), G_O_DUTYPUMP(G_O_DUTYPUMP_ref), G_O_ALARMWORD(G_O_ALARMWORD_ref), G_O_CMDACK(G_O_CMDACK_ref) +{ +} + +void Program_CONTROL::run() { + if (((G_CMD_MODE->read() == 1)) | ((G_CMD_MODE->read() == 2))) { + V_MODE = G_CMD_MODE->read(); + } else { + SPREJECTED = true; + } + SPOK = true; + if (((G_SP_STOPALL->read() < 0)) | ((G_SP_STOPALL->read() >= G_SP_STARTDUTY->read()))) { + SPOK = false; + } + if (((G_SP_STARTDUTY->read() >= G_SP_STARTP2->read())) | ((G_SP_STARTDUTY->read() >= SPILL_MM))) { + SPOK = false; + } + if (((G_SP_STARTP2->read() >= G_SP_STARTP3->read())) | ((G_SP_STARTP2->read() >= SPILL_MM))) { + SPOK = false; + } + if ((G_SP_STARTP3->read() >= SPILL_MM)) { + SPOK = false; + } + if (((G_SP_LEVEL->read() <= G_SP_STOPALL->read())) | ((G_SP_LEVEL->read() >= SPILL_MM))) { + SPOK = false; + } + if (((G_SP_HIGHALARM->read() <= 0)) | ((G_SP_HIGHALARM->read() > SPILL_MM))) { + SPOK = false; + } + if (SPOK) { + V_SPLEVEL = G_SP_LEVEL->read(); + V_STARTDUTY = G_SP_STARTDUTY->read(); + V_STARTP2 = G_SP_STARTP2->read(); + V_STARTP3 = G_SP_STARTP3->read(); + V_STOPALL = G_SP_STOPALL->read(); + V_HIGHALARM = G_SP_HIGHALARM->read(); + } else { + SPREJECTED = true; + } + if (((G_SP_MINSPEED->read() >= 380)) & ((G_SP_MINSPEED->read() <= 500))) { + V_MINSPEED = G_SP_MINSPEED->read(); + } else { + SPREJECTED = true; + } + if (G_SP_SERVICEHRS->read() > 0) { + V_SERVICEHRS = G_SP_SERVICEHRS->read(); + } else { + SPREJECTED = true; + } + MINSPEEDHZ = TO_REAL(V_MINSPEED) / 10.0; + if (MINSPEEDHZ < HARD_MIN_HZ) { + MINSPEEDHZ = HARD_MIN_HZ; + } + SPLEVEL_M = TO_REAL(V_SPLEVEL) / 1000.0; + for (I = 1; I <= 3; I++) { + RESETTRIP.at(I) = false; + RESETHOURS.at(I) = false; + } + ACKALARMS = false; + if (((G_CMD_WORD->read() != 0)) & (!CMDBUSY)) { + CMDBUSY = true; + P = G_CMD_PARAM->read(); + switch (G_CMD_WORD->read()) { + case 1: + for (I = 1; I <= 3; I++) { + RESETTRIP.at(I) = true; + } + if (G_LEVEL_MM->read() > V_STOPALL) { + DRYLOCKOUT = false; + } + break; + case 2: + if (((P >= 1)) & ((P <= 3))) { + RESETTRIP.at(P) = true; + } + break; + case 3: + if (((P >= 1)) & ((P <= 3))) { + LOCKOUT.at(P) = true; + } + break; + case 4: + if (((P >= 1)) & ((P <= 3))) { + LOCKOUT.at(P) = false; + } + break; + case 5: + if (((P >= 1)) & ((P <= 3))) { + RESETHOURS.at(P) = true; + } + break; + case 6: + ACKALARMS = true; + SPREJECTED = false; + break; + } + G_O_CMDACK->write(G_CMD_WORD->read()); + } else if (G_CMD_WORD->read() == 0) { + CMDBUSY = false; + G_O_CMDACK->write(0); + } + LEVELRANGEFAULT = ((G_LEVELRAW_MM->read() < 0)) | ((G_LEVELRAW_MM->read() > LEVEL_MAX_MM)); + if (!PRIMED) { + LEVELREF = G_LEVELRAW_MM->read(); + PRIMED = true; + } + if (ABS(G_LEVELRAW_MM->read() - LEVELREF) > 1) { + LEVELREF = G_LEVELRAW_MM->read(); + LEVELMOVED = true; + } else { + LEVELMOVED = false; + } + ANYRUNNING = ((PUMP1.RUNNING) | (PUMP2.RUNNING)) | (PUMP3.RUNNING); + FROZENTMR.IN = (ANYRUNNING) & (!LEVELMOVED); + FROZENTMR.PT = 600000000000LL; + FROZENTMR(); + FROZENTMR.ENO = true; + LEVELFROZEN = FROZENTMR.Q; + LEVELFAULT = (LEVELRANGEFAULT) | (LEVELFROZEN); + if (!LEVELFAULT) { + if (G_LEVEL_MM->read() >= V_STARTP3) { + PUMPSREQUIRED = 3; + } else if (G_LEVEL_MM->read() >= V_STARTP2) { + PUMPSREQUIRED = 2; + } else if (G_LEVEL_MM->read() >= V_STARTDUTY) { + PUMPSREQUIRED = 1; + } else if (G_LEVEL_MM->read() <= V_STOPALL) { + PUMPSREQUIRED = 0; + } + } else { + if (G_LSHH->read()) { + PUMPSREQUIRED = 3; + } + } + if (G_LSHH->read()) { + PUMPSREQUIRED = 3; + } + DRYRUN = !G_LSLL_WET->read(); + if (DRYRUN) { + PUMPSREQUIRED = 0; + DRYLOCKOUT = true; + } + if (DRYLOCKOUT) { + PUMPSREQUIRED = 0; + } + if (V_MODE == 2) { + PUMPSREQUIRED = 0; + } + if (G_LSHH->read()) { + PUMPSALLOWED = PUMPSREQUIRED; + STAGGERARM = false; + } else { + STAGGERTMR.IN = STAGGERARM; + STAGGERTMR.PT = 30000000000LL; + STAGGERTMR(); + STAGGERTMR.ENO = true; + if (PUMPSALLOWED < PUMPSREQUIRED) { + if (PUMPSALLOWED == 0) { + PUMPSALLOWED = 1; + STAGGERARM = false; + } else if (STAGGERTMR.Q) { + PUMPSALLOWED = PUMPSALLOWED + 1; + STAGGERARM = false; + } else { + STAGGERARM = true; + } + } else { + if (PUMPSALLOWED > PUMPSREQUIRED) { + PUMPSALLOWED = PUMPSREQUIRED; + } + STAGGERARM = false; + } + } + AVAIL.at(1) = PUMP1.AVAILABLE; + AVAIL.at(2) = PUMP2.AVAILABLE; + AVAIL.at(3) = PUMP3.AVAILABLE; + HOURS.at(1) = PUMP1.RUNHOURS; + HOURS.at(2) = PUMP2.RUNHOURS; + HOURS.at(3) = PUMP3.RUNHOURS; + SVCDUE.at(1) = PUMP1.SERVICEDUE; + SVCDUE.at(2) = PUMP2.SERVICEDUE; + SVCDUE.at(3) = PUMP3.SERVICEDUE; + RUNNOW.at(1) = PUMP1.RUNNING; + RUNNOW.at(2) = PUMP2.RUNNING; + RUNNOW.at(3) = PUMP3.RUNNING; + DUTY.AVAILABLE = AVAIL; + DUTY.RUNHOURS = HOURS; + DUTY.SERVICEDUE = SVCDUE; + DUTY.RUNNINGNOW = RUNNOW; + DUTY.PUMPSREQUIRED = PUMPSALLOWED; + DUTY(); + DUTY.ENO = true; + REQ.at(1) = DUTY.RUNREQUEST.at(1); + REQ.at(2) = DUTY.RUNREQUEST.at(2); + REQ.at(3) = DUTY.RUNREQUEST.at(3); + LVLCTL.LEVEL = G_LEVEL_M->read(); + LVLCTL.SETPOINT = SPLEVEL_M; + LVLCTL.ENABLE = (PUMPSALLOWED > 0); + LVLCTL.MINSPEED = MINSPEEDHZ; + LVLCTL.MAXSPEED = HARD_MAX_HZ; + LVLCTL(); + LVLCTL.ENO = true; + SPEED = LVLCTL.SPEED; + if (G_LSHH->read()) { + SPEED = HARD_MAX_HZ; + } + PUMP1.RUNREQUEST = REQ.at(1); + PUMP1.SPEEDREF = SPEED; + PUMP1.THERMALOK = G_THERMALOK->with_lock([&](auto* __glk){ return (*__glk).at(1); }); + PUMP1.SEALLEAK = G_SEALLEAK->with_lock([&](auto* __glk){ return (*__glk).at(1); }); + PUMP1.VIBRATION = G_VIB_MMS->with_lock([&](auto* __glk){ return (*__glk).at(1); }); + PUMP1.DISCHPRESSURE = G_PUMPP_KPA->with_lock([&](auto* __glk){ return (*__glk).at(1); }); + PUMP1.RESETTRIP = RESETTRIP.at(1); + PUMP1.LOCKOUT = LOCKOUT.at(1); + PUMP1.MINOFFBYPASS = G_LSHH->read(); + PUMP1.SERVICEINTERVAL = TO_REAL(V_SERVICEHRS); + PUMP1.RESETHOURS = RESETHOURS.at(1); + PUMP1(); + PUMP1.ENO = true; + PUMP2.RUNREQUEST = REQ.at(2); + PUMP2.SPEEDREF = SPEED; + PUMP2.THERMALOK = G_THERMALOK->with_lock([&](auto* __glk){ return (*__glk).at(2); }); + PUMP2.SEALLEAK = G_SEALLEAK->with_lock([&](auto* __glk){ return (*__glk).at(2); }); + PUMP2.VIBRATION = G_VIB_MMS->with_lock([&](auto* __glk){ return (*__glk).at(2); }); + PUMP2.DISCHPRESSURE = G_PUMPP_KPA->with_lock([&](auto* __glk){ return (*__glk).at(2); }); + PUMP2.RESETTRIP = RESETTRIP.at(2); + PUMP2.LOCKOUT = LOCKOUT.at(2); + PUMP2.MINOFFBYPASS = G_LSHH->read(); + PUMP2.SERVICEINTERVAL = TO_REAL(V_SERVICEHRS); + PUMP2.RESETHOURS = RESETHOURS.at(2); + PUMP2(); + PUMP2.ENO = true; + PUMP3.RUNREQUEST = REQ.at(3); + PUMP3.SPEEDREF = SPEED; + PUMP3.THERMALOK = G_THERMALOK->with_lock([&](auto* __glk){ return (*__glk).at(3); }); + PUMP3.SEALLEAK = G_SEALLEAK->with_lock([&](auto* __glk){ return (*__glk).at(3); }); + PUMP3.VIBRATION = G_VIB_MMS->with_lock([&](auto* __glk){ return (*__glk).at(3); }); + PUMP3.DISCHPRESSURE = G_PUMPP_KPA->with_lock([&](auto* __glk){ return (*__glk).at(3); }); + PUMP3.RESETTRIP = RESETTRIP.at(3); + PUMP3.LOCKOUT = LOCKOUT.at(3); + PUMP3.MINOFFBYPASS = G_LSHH->read(); + PUMP3.SERVICEINTERVAL = TO_REAL(V_SERVICEHRS); + PUMP3.RESETHOURS = RESETHOURS.at(3); + PUMP3(); + PUMP3.ENO = true; + HEAD.LEVEL = G_LEVEL_M->read(); + HEAD.INFLOW = G_INFLOW_LPS->read(); + HEAD.TOTALDISCHARGE = G_DISCH_LPS->read(); + HEAD(); + HEAD.ENO = true; + PUMPSRUN = 0; + if (PUMP1.RUNNING) { + PUMPSRUN = PUMPSRUN + 1; + } + if (PUMP2.RUNNING) { + PUMPSRUN = PUMPSRUN + 1; + } + if (PUMP3.RUNNING) { + PUMPSRUN = PUMPSRUN + 1; + } + HIGHLEVEL = G_LEVEL_MM->read() >= V_HIGHALARM; + auto __gwv_0 = PUMP1.RUNCMD; + G_O_RUNCMD->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_0; }); + auto __gwv_1 = PUMP2.RUNCMD; + G_O_RUNCMD->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_1; }); + auto __gwv_2 = PUMP3.RUNCMD; + G_O_RUNCMD->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_2; }); + auto __gwv_3 = PUMP1.RUNNING; + G_O_RUNNING->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_3; }); + auto __gwv_4 = PUMP2.RUNNING; + G_O_RUNNING->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_4; }); + auto __gwv_5 = PUMP3.RUNNING; + G_O_RUNNING->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_5; }); + auto __gwv_6 = PUMP1.AVAILABLE; + G_O_AVAILABLE->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_6; }); + auto __gwv_7 = PUMP2.AVAILABLE; + G_O_AVAILABLE->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_7; }); + auto __gwv_8 = PUMP3.AVAILABLE; + G_O_AVAILABLE->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_8; }); + auto __gwv_9 = PUMP1.TRIPPED; + G_O_TRIPPED->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_9; }); + auto __gwv_10 = PUMP2.TRIPPED; + G_O_TRIPPED->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_10; }); + auto __gwv_11 = PUMP3.TRIPPED; + G_O_TRIPPED->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_11; }); + auto __gwv_12 = PUMP1.STATE; + G_O_PUMPSTATE->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_12; }); + auto __gwv_13 = PUMP2.STATE; + G_O_PUMPSTATE->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_13; }); + auto __gwv_14 = PUMP3.STATE; + G_O_PUMPSTATE->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_14; }); + G_O_INAUTO->write((V_MODE == 1)); + G_O_HIGHLEVEL->write(HIGHLEVEL); + G_O_SPILLACTIVE->write(G_SPILLDETECTED->read()); + G_O_LEVEL_MM->write(G_LEVEL_MM->read()); + G_O_PUMPSRUN->write(PUMPSRUN); + G_O_DUTYPUMP->write(DUTY.DUTYPUMP); + R = G_INFLOW_LPS->read() * 10.0; + if (R > 32767.0) { + R = 32767.0; + } else if (R < -32768.0) { + R = -32768.0; + } + G_O_INFLOW_X10->write(TO_INT(R)); + R = G_DISCH_LPS->read() * 10.0; + if (R > 32767.0) { + R = 32767.0; + } else if (R < -32768.0) { + R = -32768.0; + } + G_O_DISCH_X10->write(TO_INT(R)); + R = SPEED * 10.0; + if (R > 32767.0) { + R = 32767.0; + } else if (R < 0.0) { + R = 0.0; + } + G_O_SPEED_X10->write(TO_INT(R)); + R = HEAD.NETINFLOW * 10.0; + if (R > 32767.0) { + R = 32767.0; + } else if (R < -32768.0) { + R = -32768.0; + } + G_O_NETACCUM->write(TO_INT(R)); + R = HEAD.VOLTOSPILL; + if (R > 32767.0) { + R = 32767.0; + } else if (R < 0.0) { + R = 0.0; + } + G_O_VOLTOSPILL->write(TO_INT(R)); + G_O_TIMETOSPILL->write(HEAD.TIMETOSPILL); + G_O_TIMETOLSHH->write(HEAD.TIMETOLSHH); + R = PUMP1.RUNHOURS; + if (R > 32767.0) { + R = 32767.0; + } + auto __gwv_15 = TO_INT(R); + G_O_RUNHOURS->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_15; }); + R = PUMP2.RUNHOURS; + if (R > 32767.0) { + R = 32767.0; + } + auto __gwv_16 = TO_INT(R); + G_O_RUNHOURS->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_16; }); + R = PUMP3.RUNHOURS; + if (R > 32767.0) { + R = 32767.0; + } + auto __gwv_17 = TO_INT(R); + G_O_RUNHOURS->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_17; }); + if (V_MODE == 2) { + G_O_STATIONSTATE->write(0); + } else if (G_LSHH->read()) { + G_O_STATIONSTATE->write(4); + } else if (DRYLOCKOUT) { + G_O_STATIONSTATE->write(5); + } else if (LEVELFAULT) { + G_O_STATIONSTATE->write(6); + } else if (HIGHLEVEL) { + G_O_STATIONSTATE->write(3); + } else if (PUMPSRUN > 0) { + G_O_STATIONSTATE->write(2); + } else { + G_O_STATIONSTATE->write(1); + } + ALARM = 0; + if (HIGHLEVEL) { + ALARM = ALARM + 1; + } + if (G_LSHH->read()) { + ALARM = ALARM + 2; + } + if ((DRYRUN) | (DRYLOCKOUT)) { + ALARM = ALARM + 4; + } + if (G_SPILLDETECTED->read()) { + ALARM = ALARM + 8; + } + if (PUMP1.TRIPPED) { + ALARM = ALARM + 16; + } + if (PUMP2.TRIPPED) { + ALARM = ALARM + 32; + } + if (PUMP3.TRIPPED) { + ALARM = ALARM + 64; + } + if (PUMP1.SEALALARM) { + ALARM = ALARM + 128; + } + if (PUMP2.SEALALARM) { + ALARM = ALARM + 256; + } + if (PUMP3.SEALALARM) { + ALARM = ALARM + 512; + } + if (PUMP1.VIBALARM) { + ALARM = ALARM + 1024; + } + if (PUMP2.VIBALARM) { + ALARM = ALARM + 2048; + } + if (PUMP3.VIBALARM) { + ALARM = ALARM + 4096; + } + if (LEVELFAULT) { + ALARM = ALARM + 8192; + } + if (!G_MAINSOK->read()) { + ALARM = ALARM + 16384; + } + if (SPREJECTED) { + ALARM = ALARM + 32768; + } + if (ALARM >= 32768) { + G_O_ALARMWORD->write(TO_INT(ALARM - 65536)); + } else { + G_O_ALARMWORD->write(TO_INT(ALARM)); + } +} + +} // namespace strucpp \ No newline at end of file diff --git a/03-plc/as-built/pou_FB_DUTY_SELECT.cpp b/03-plc/as-built/pou_FB_DUTY_SELECT.cpp new file mode 100644 index 0000000..8f48b59 --- /dev/null +++ b/03-plc/as-built/pou_FB_DUTY_SELECT.cpp @@ -0,0 +1,76 @@ +// Generated by STruC++ - IEC 61131-3 Structured Text to C++ Compiler +// Do not edit this file manually. + +#include "generated.hpp" + +namespace strucpp { + +FB_DUTY_SELECT::FB_DUTY_SELECT() + : SERVICE_PENALTY(1000000.0) +{ + // Initialize variables +} + +void FB_DUTY_SELECT::operator()() { + for (I = 1; I <= 3; I++) { + USED.at(I) = false; + SEL.at(I) = false; + RANK.at(I) = 0; + } + NRANKED = 0; + for (K = 1; K <= 3; K++) { + BEST = 0; + BESTKEY = 0.0; + for (I = 1; I <= 3; I++) { + if ((AVAILABLE.at(I)) & (!USED.at(I))) { + KEY = RUNHOURS.at(I); + if (SERVICEDUE.at(I)) { + KEY = KEY + SERVICE_PENALTY; + } + if (((BEST == 0)) | ((KEY < BESTKEY))) { + BEST = I; + BESTKEY = KEY; + } + } + } + if (BEST > 0) { + NRANKED = NRANKED + 1; + RANK.at(NRANKED) = BEST; + USED.at(BEST) = true; + } + } + SLOTS = PUMPSREQUIRED; + if (SLOTS < 0) { + SLOTS = 0; + } + if (SLOTS > 3) { + SLOTS = 3; + } + CNT = 0; + for (K = 1; K <= NRANKED; K++) { + I = RANK.at(K); + if ((RUNNINGNOW.at(I)) & ((CNT < SLOTS))) { + SEL.at(I) = true; + CNT = CNT + 1; + } + } + for (K = 1; K <= NRANKED; K++) { + I = RANK.at(K); + if (((!SEL.at(I))) & ((CNT < SLOTS))) { + SEL.at(I) = true; + CNT = CNT + 1; + } + } + DUTYPUMP = 0; + for (K = 1; K <= NRANKED; K++) { + I = RANK.at(K); + if ((SEL.at(I)) & ((DUTYPUMP == 0))) { + DUTYPUMP = I; + } + } + for (I = 1; I <= 3; I++) { + RUNREQUEST.at(I) = SEL.at(I); + } +} + +} // namespace strucpp \ No newline at end of file diff --git a/03-plc/as-built/pou_FB_HEADROOM.cpp b/03-plc/as-built/pou_FB_HEADROOM.cpp new file mode 100644 index 0000000..7c6bc5a --- /dev/null +++ b/03-plc/as-built/pou_FB_HEADROOM.cpp @@ -0,0 +1,56 @@ +// Generated by STruC++ - IEC 61131-3 Structured Text to C++ Compiler +// Do not edit this file manually. + +#include "generated.hpp" + +namespace strucpp { + +FB_HEADROOM::FB_HEADROOM() + : SCAN_S(0.1), TAU_S(30.0), AREA_M2(120.0), SPILL_M(6.0), LSHH_M(5.5), MIN_NET(0.5), NO_TIME(32767), MAX_TIME(32767.0), PRIMED(false) +{ + // Initialize variables +} + +void FB_HEADROOM::operator()() { + if (!PRIMED) { + INFLOWFILT = INFLOW; + PRIMED = true; + } else { + INFLOWFILT = INFLOWFILT + (INFLOW - INFLOWFILT) * SCAN_S / TAU_S; + } + NETINFLOW = INFLOWFILT - TOTALDISCHARGE; + VOLTOSPILL = (SPILL_M - LEVEL) * AREA_M2; + VOLTOLSHH = (LSHH_M - LEVEL) * AREA_M2; + if (VOLTOSPILL < 0.0) { + VOLTOSPILL = 0.0; + } + if (VOLTOLSHH < 0.0) { + VOLTOLSHH = 0.0; + } + if (NETINFLOW <= MIN_NET) { + TIMETOSPILL = NO_TIME; + } else { + T = VOLTOSPILL * 1000.0 / NETINFLOW; + if (T >= MAX_TIME) { + TIMETOSPILL = NO_TIME; + } else if (T < 0.0) { + TIMETOSPILL = 0; + } else { + TIMETOSPILL = TO_INT(T); + } + } + if (NETINFLOW <= MIN_NET) { + TIMETOLSHH = NO_TIME; + } else { + T = VOLTOLSHH * 1000.0 / NETINFLOW; + if (T >= MAX_TIME) { + TIMETOLSHH = NO_TIME; + } else if (T < 0.0) { + TIMETOLSHH = 0; + } else { + TIMETOLSHH = TO_INT(T); + } + } +} + +} // namespace strucpp \ No newline at end of file diff --git a/03-plc/as-built/pou_FB_LEVEL_CTRL.cpp b/03-plc/as-built/pou_FB_LEVEL_CTRL.cpp new file mode 100644 index 0000000..0b2719e --- /dev/null +++ b/03-plc/as-built/pou_FB_LEVEL_CTRL.cpp @@ -0,0 +1,43 @@ +// Generated by STruC++ - IEC 61131-3 Structured Text to C++ Compiler +// Do not edit this file manually. + +#include "generated.hpp" + +namespace strucpp { + +FB_LEVEL_CTRL::FB_LEVEL_CTRL() + : SCAN_S(0.1), KP(12.0), TI(120.0), INTEG(38.0) +{ + // Initialize variables +} + +void FB_LEVEL_CTRL::operator()() { + if (!ENABLE) { + INTEG = MINSPEED; + SPEED = MINSPEED; + } else { + ERR = LEVEL - SETPOINT; + RAW = KP * ERR + INTEG; + INTEGRATE = false; + if (((RAW > MINSPEED)) & ((RAW < MAXSPEED))) { + INTEGRATE = true; + } else if (((RAW >= MAXSPEED)) & ((ERR < 0.0))) { + INTEGRATE = true; + } else if (((RAW <= MINSPEED)) & ((ERR > 0.0))) { + INTEGRATE = true; + } + if (INTEGRATE) { + INTEG = INTEG + (KP / TI) * ERR * SCAN_S; + } + RAW = KP * ERR + INTEG; + if (RAW > MAXSPEED) { + SPEED = MAXSPEED; + } else if (RAW < MINSPEED) { + SPEED = MINSPEED; + } else { + SPEED = RAW; + } + } +} + +} // namespace strucpp \ No newline at end of file diff --git a/03-plc/as-built/pou_FB_PUMP.cpp b/03-plc/as-built/pou_FB_PUMP.cpp new file mode 100644 index 0000000..ccf6689 --- /dev/null +++ b/03-plc/as-built/pou_FB_PUMP.cpp @@ -0,0 +1,82 @@ +// Generated by STruC++ - IEC 61131-3 Structured Text to C++ Compiler +// Do not edit this file manually. + +#include "generated.hpp" + +namespace strucpp { + +FB_PUMP::FB_PUMP() + : SCAN_S(0.1), VIB_ALARM(7.1), VIB_TRIP(11.0), NOFLOW_KPA(150.0) +{ + // Initialize variables +} + +void FB_PUMP::operator()() { + if (RESETTRIP) { + TRIPPED = false; + } + if (!THERMALOK) { + TRIPPED = true; + } + if (VIBRATION > VIB_TRIP) { + TRIPPED = true; + } + NOFLOWTMR.IN = RUNCMD; + NOFLOWTMR.PT = 20000000000LL; + NOFLOWTMR(); + NOFLOWTMR.ENO = true; + if ((NOFLOWTMR.Q) & ((DISCHPRESSURE < NOFLOW_KPA))) { + TRIPPED = true; + } + VIBALARM = VIBRATION > VIB_ALARM; + SEALALARM = SEALLEAK; + AVAILABLE = ((THERMALOK) & (!TRIPPED)) & (!LOCKOUT); + MINRUNTMR.IN = RUNCMD; + MINRUNTMR.PT = 300000000000LL; + MINRUNTMR(); + MINRUNTMR.ENO = true; + MINOFFTMR.IN = ((!RUNCMD)) & (HASRUN); + MINOFFTMR.PT = 300000000000LL; + MINOFFTMR(); + MINOFFTMR.ENO = true; + STARTOK = (((!HASRUN)) | (MINOFFTMR.Q)) | (MINOFFBYPASS); + if (((TRIPPED) | (LOCKOUT)) | (!THERMALOK)) { + RUNCMD = false; + } else if (RUNCMD) { + if (((!RUNREQUEST)) & (MINRUNTMR.Q)) { + RUNCMD = false; + } + } else { + if ((RUNREQUEST) & (STARTOK)) { + RUNCMD = true; + HASRUN = true; + } + } + RUNNING = RUNCMD; + if (RESETHOURS) { + RUNHOURS = 0.0; + } + if (RUNNING) { + RUNHOURS = RUNHOURS + SCAN_S / 3600.0; + } + SERVICEDUE = RUNHOURS >= SERVICEINTERVAL; + if (TRIPPED) { + STATE = 6; + } else if (LOCKOUT) { + STATE = 7; + } else if (!THERMALOK) { + STATE = 0; + } else if ((RUNNING) & (!RUNREQUEST)) { + STATE = 4; + } else if ((RUNCMD) & (!NOFLOWTMR.Q)) { + STATE = 2; + } else if (RUNNING) { + STATE = 3; + } else if ((HASRUN) & (!MINOFFTMR.Q)) { + STATE = 5; + } else { + STATE = 1; + } +} + +} // namespace strucpp \ No newline at end of file diff --git a/03-plc/as-built/pou_IO_MUX.cpp b/03-plc/as-built/pou_IO_MUX.cpp new file mode 100644 index 0000000..66775fb --- /dev/null +++ b/03-plc/as-built/pou_IO_MUX.cpp @@ -0,0 +1,157 @@ +// Generated by STruC++ - IEC 61131-3 Structured Text to C++ Compiler +// Do not edit this file manually. + +#include "generated.hpp" + +namespace strucpp { + +Program_IO_MUX::Program_IO_MUX(GlobalVar* IW_LIT101_ref, GlobalVar* IW_FIT201_ref, GlobalVar* IW_FIT301_ref, GlobalVar* IW_PIT302_ref, GlobalVar* IW_PIT311_ref, GlobalVar* IW_PIT321_ref, GlobalVar* IW_PIT331_ref, GlobalVar* IW_VE314_ref, GlobalVar* IW_VE324_ref, GlobalVar* IW_VE334_ref, GlobalVar* IX_LSHH102_ref, GlobalVar* IX_LSLL103_ref, GlobalVar* IX_LSH104_ref, GlobalVar* IX_TE312_ref, GlobalVar* IX_TE322_ref, GlobalVar* IX_TE332_ref, GlobalVar* IX_MSE313_ref, GlobalVar* IX_MSE323_ref, GlobalVar* IX_MSE333_ref, GlobalVar* IX_XA502_ref, GlobalVar* QX_RUNCMD1_ref, GlobalVar* QX_RUNCMD2_ref, GlobalVar* QX_RUNCMD3_ref, GlobalVar* QX_RUNNING1_ref, GlobalVar* QX_RUNNING2_ref, GlobalVar* QX_RUNNING3_ref, GlobalVar* QX_AVAIL1_ref, GlobalVar* QX_AVAIL2_ref, GlobalVar* QX_AVAIL3_ref, GlobalVar* QX_INAUTO_ref, GlobalVar* QX_HIGHLEVEL_ref, GlobalVar* QX_SPILLACTIVE_ref, GlobalVar* QX_TRIPPED1_ref, GlobalVar* QX_TRIPPED2_ref, GlobalVar* QX_TRIPPED3_ref, GlobalVar* QW_LEVEL_ref, GlobalVar* QW_INFLOW_ref, GlobalVar* QW_DISCHARGE_ref, GlobalVar* QW_PUMPSRUNNING_ref, GlobalVar* QW_SPEED_ref, GlobalVar* QW_TIMETOSPILL_ref, GlobalVar* QW_TIMETOLSHH_ref, GlobalVar* QW_NETACCUM_ref, GlobalVar* QW_RUNHOURS1_ref, GlobalVar* QW_RUNHOURS2_ref, GlobalVar* QW_RUNHOURS3_ref, GlobalVar* QW_VOLTOSPILL_ref, GlobalVar* QW_STATIONSTATE_ref, GlobalVar* QW_PUMPSTATE1_ref, GlobalVar* QW_PUMPSTATE2_ref, GlobalVar* QW_PUMPSTATE3_ref, GlobalVar* QW_DUTYPUMP_ref, GlobalVar* QW_ALARMWORD_ref, GlobalVar* QW_CMDACK_ref, GlobalVar* MW_MODE_ref, GlobalVar* MW_CMDWORD_ref, GlobalVar* MW_CMDPARAM_ref, GlobalVar* MW_SPLEVEL_ref, GlobalVar* MW_STARTDUTY_ref, GlobalVar* MW_STARTP2_ref, GlobalVar* MW_STARTP3_ref, GlobalVar* MW_STOPALL_ref, GlobalVar* MW_HIGHALARM_ref, GlobalVar* MW_MINSPEED_ref, GlobalVar* MW_SERVICEHRS_ref, GlobalVar* MW_SIMINFLOW_ref, GlobalVar* MW_SIMMODE_ref, GlobalVar* MW_SIMRESET_ref, GlobalVar* MW_SIMTIMESCALE_ref, GlobalVar* G_LEVELRAW_MM_ref, GlobalVar* G_LEVEL_MM_ref, GlobalVar* G_LEVEL_M_ref, GlobalVar* G_INFLOW_LPS_ref, GlobalVar* G_DISCH_LPS_ref, GlobalVar* G_MANIFOLDP_KPA_ref, GlobalVar>* G_PUMPP_KPA_ref, GlobalVar>* G_VIB_MMS_ref, GlobalVar* G_LSHH_ref, GlobalVar* G_LSLL_WET_ref, GlobalVar* G_SPILLDETECTED_ref, GlobalVar>* G_THERMALOK_ref, GlobalVar>* G_SEALLEAK_ref, GlobalVar* G_MAINSOK_ref, GlobalVar* G_CMD_MODE_ref, GlobalVar* G_CMD_WORD_ref, GlobalVar* G_CMD_PARAM_ref, GlobalVar* G_SP_LEVEL_ref, GlobalVar* G_SP_STARTDUTY_ref, GlobalVar* G_SP_STARTP2_ref, GlobalVar* G_SP_STARTP3_ref, GlobalVar* G_SP_STOPALL_ref, GlobalVar* G_SP_HIGHALARM_ref, GlobalVar* G_SP_MINSPEED_ref, GlobalVar* G_SP_SERVICEHRS_ref, GlobalVar>* G_O_RUNCMD_ref, GlobalVar>* G_O_RUNNING_ref, GlobalVar>* G_O_AVAILABLE_ref, GlobalVar>* G_O_TRIPPED_ref, GlobalVar* G_O_INAUTO_ref, GlobalVar* G_O_HIGHLEVEL_ref, GlobalVar* G_O_SPILLACTIVE_ref, GlobalVar* G_O_LEVEL_MM_ref, GlobalVar* G_O_INFLOW_X10_ref, GlobalVar* G_O_DISCH_X10_ref, GlobalVar* G_O_PUMPSRUN_ref, GlobalVar* G_O_SPEED_X10_ref, GlobalVar* G_O_TIMETOSPILL_ref, GlobalVar* G_O_TIMETOLSHH_ref, GlobalVar* G_O_NETACCUM_ref, GlobalVar>* G_O_RUNHOURS_ref, GlobalVar* G_O_VOLTOSPILL_ref, GlobalVar* G_O_STATIONSTATE_ref, GlobalVar>* G_O_PUMPSTATE_ref, GlobalVar* G_O_DUTYPUMP_ref, GlobalVar* G_O_ALARMWORD_ref, GlobalVar* G_O_CMDACK_ref, GlobalVar* DEF_MODE_ref, GlobalVar* DEF_SP_LEVEL_ref, GlobalVar* DEF_START_DUTY_ref, GlobalVar* DEF_START_P2_ref, GlobalVar* DEF_START_P3_ref, GlobalVar* DEF_STOP_ALL_ref, GlobalVar* DEF_HIGH_ALARM_ref, GlobalVar* DEF_MIN_SPEED_ref, GlobalVar* DEF_SERVICE_HRS_ref, GlobalVar* G_SIMACTIVE_ref, GlobalVar* G_SIM_CLEARRESET_ref, GlobalVar* G_SIMCMD_INFLOW_ref, GlobalVar* G_SIMCMD_MODE_ref, GlobalVar* G_SIMCMD_RESET_ref, GlobalVar* G_SIMCMD_TIMESCALE_ref, GlobalVar* G_SIM_LEVEL_MM_ref, GlobalVar* G_SIM_INFLOW_X10_ref, GlobalVar* G_SIM_DISCH_X10_ref, GlobalVar* G_SIM_MANIFOLDP_ref, GlobalVar>* G_SIM_PUMPP_ref, GlobalVar>* G_SIM_VIB_X10_ref, GlobalVar* G_SIM_LSHH_ref, GlobalVar* G_SIM_LSLL_WET_ref, GlobalVar* G_SIM_SPILL_ref, GlobalVar>* G_SIM_THERMALOK_ref, GlobalVar>* G_SIM_SEALLEAK_ref, GlobalVar* G_SIM_MAINSOK_ref) + : SEEDED(false), IW_LIT101(IW_LIT101_ref), IW_FIT201(IW_FIT201_ref), IW_FIT301(IW_FIT301_ref), IW_PIT302(IW_PIT302_ref), IW_PIT311(IW_PIT311_ref), IW_PIT321(IW_PIT321_ref), IW_PIT331(IW_PIT331_ref), IW_VE314(IW_VE314_ref), IW_VE324(IW_VE324_ref), IW_VE334(IW_VE334_ref), IX_LSHH102(IX_LSHH102_ref), IX_LSLL103(IX_LSLL103_ref), IX_LSH104(IX_LSH104_ref), IX_TE312(IX_TE312_ref), IX_TE322(IX_TE322_ref), IX_TE332(IX_TE332_ref), IX_MSE313(IX_MSE313_ref), IX_MSE323(IX_MSE323_ref), IX_MSE333(IX_MSE333_ref), IX_XA502(IX_XA502_ref), QX_RUNCMD1(QX_RUNCMD1_ref), QX_RUNCMD2(QX_RUNCMD2_ref), QX_RUNCMD3(QX_RUNCMD3_ref), QX_RUNNING1(QX_RUNNING1_ref), QX_RUNNING2(QX_RUNNING2_ref), QX_RUNNING3(QX_RUNNING3_ref), QX_AVAIL1(QX_AVAIL1_ref), QX_AVAIL2(QX_AVAIL2_ref), QX_AVAIL3(QX_AVAIL3_ref), QX_INAUTO(QX_INAUTO_ref), QX_HIGHLEVEL(QX_HIGHLEVEL_ref), QX_SPILLACTIVE(QX_SPILLACTIVE_ref), QX_TRIPPED1(QX_TRIPPED1_ref), QX_TRIPPED2(QX_TRIPPED2_ref), QX_TRIPPED3(QX_TRIPPED3_ref), QW_LEVEL(QW_LEVEL_ref), QW_INFLOW(QW_INFLOW_ref), QW_DISCHARGE(QW_DISCHARGE_ref), QW_PUMPSRUNNING(QW_PUMPSRUNNING_ref), QW_SPEED(QW_SPEED_ref), QW_TIMETOSPILL(QW_TIMETOSPILL_ref), QW_TIMETOLSHH(QW_TIMETOLSHH_ref), QW_NETACCUM(QW_NETACCUM_ref), QW_RUNHOURS1(QW_RUNHOURS1_ref), QW_RUNHOURS2(QW_RUNHOURS2_ref), QW_RUNHOURS3(QW_RUNHOURS3_ref), QW_VOLTOSPILL(QW_VOLTOSPILL_ref), QW_STATIONSTATE(QW_STATIONSTATE_ref), QW_PUMPSTATE1(QW_PUMPSTATE1_ref), QW_PUMPSTATE2(QW_PUMPSTATE2_ref), QW_PUMPSTATE3(QW_PUMPSTATE3_ref), QW_DUTYPUMP(QW_DUTYPUMP_ref), QW_ALARMWORD(QW_ALARMWORD_ref), QW_CMDACK(QW_CMDACK_ref), MW_MODE(MW_MODE_ref), MW_CMDWORD(MW_CMDWORD_ref), MW_CMDPARAM(MW_CMDPARAM_ref), MW_SPLEVEL(MW_SPLEVEL_ref), MW_STARTDUTY(MW_STARTDUTY_ref), MW_STARTP2(MW_STARTP2_ref), MW_STARTP3(MW_STARTP3_ref), MW_STOPALL(MW_STOPALL_ref), MW_HIGHALARM(MW_HIGHALARM_ref), MW_MINSPEED(MW_MINSPEED_ref), MW_SERVICEHRS(MW_SERVICEHRS_ref), MW_SIMINFLOW(MW_SIMINFLOW_ref), MW_SIMMODE(MW_SIMMODE_ref), MW_SIMRESET(MW_SIMRESET_ref), MW_SIMTIMESCALE(MW_SIMTIMESCALE_ref), G_LEVELRAW_MM(G_LEVELRAW_MM_ref), G_LEVEL_MM(G_LEVEL_MM_ref), G_LEVEL_M(G_LEVEL_M_ref), G_INFLOW_LPS(G_INFLOW_LPS_ref), G_DISCH_LPS(G_DISCH_LPS_ref), G_MANIFOLDP_KPA(G_MANIFOLDP_KPA_ref), G_PUMPP_KPA(G_PUMPP_KPA_ref), G_VIB_MMS(G_VIB_MMS_ref), G_LSHH(G_LSHH_ref), G_LSLL_WET(G_LSLL_WET_ref), G_SPILLDETECTED(G_SPILLDETECTED_ref), G_THERMALOK(G_THERMALOK_ref), G_SEALLEAK(G_SEALLEAK_ref), G_MAINSOK(G_MAINSOK_ref), G_CMD_MODE(G_CMD_MODE_ref), G_CMD_WORD(G_CMD_WORD_ref), G_CMD_PARAM(G_CMD_PARAM_ref), G_SP_LEVEL(G_SP_LEVEL_ref), G_SP_STARTDUTY(G_SP_STARTDUTY_ref), G_SP_STARTP2(G_SP_STARTP2_ref), G_SP_STARTP3(G_SP_STARTP3_ref), G_SP_STOPALL(G_SP_STOPALL_ref), G_SP_HIGHALARM(G_SP_HIGHALARM_ref), G_SP_MINSPEED(G_SP_MINSPEED_ref), G_SP_SERVICEHRS(G_SP_SERVICEHRS_ref), G_O_RUNCMD(G_O_RUNCMD_ref), G_O_RUNNING(G_O_RUNNING_ref), G_O_AVAILABLE(G_O_AVAILABLE_ref), G_O_TRIPPED(G_O_TRIPPED_ref), G_O_INAUTO(G_O_INAUTO_ref), G_O_HIGHLEVEL(G_O_HIGHLEVEL_ref), G_O_SPILLACTIVE(G_O_SPILLACTIVE_ref), G_O_LEVEL_MM(G_O_LEVEL_MM_ref), G_O_INFLOW_X10(G_O_INFLOW_X10_ref), G_O_DISCH_X10(G_O_DISCH_X10_ref), G_O_PUMPSRUN(G_O_PUMPSRUN_ref), G_O_SPEED_X10(G_O_SPEED_X10_ref), G_O_TIMETOSPILL(G_O_TIMETOSPILL_ref), G_O_TIMETOLSHH(G_O_TIMETOLSHH_ref), G_O_NETACCUM(G_O_NETACCUM_ref), G_O_RUNHOURS(G_O_RUNHOURS_ref), G_O_VOLTOSPILL(G_O_VOLTOSPILL_ref), G_O_STATIONSTATE(G_O_STATIONSTATE_ref), G_O_PUMPSTATE(G_O_PUMPSTATE_ref), G_O_DUTYPUMP(G_O_DUTYPUMP_ref), G_O_ALARMWORD(G_O_ALARMWORD_ref), G_O_CMDACK(G_O_CMDACK_ref), DEF_MODE(DEF_MODE_ref), DEF_SP_LEVEL(DEF_SP_LEVEL_ref), DEF_START_DUTY(DEF_START_DUTY_ref), DEF_START_P2(DEF_START_P2_ref), DEF_START_P3(DEF_START_P3_ref), DEF_STOP_ALL(DEF_STOP_ALL_ref), DEF_HIGH_ALARM(DEF_HIGH_ALARM_ref), DEF_MIN_SPEED(DEF_MIN_SPEED_ref), DEF_SERVICE_HRS(DEF_SERVICE_HRS_ref), G_SIMACTIVE(G_SIMACTIVE_ref), G_SIM_CLEARRESET(G_SIM_CLEARRESET_ref), G_SIMCMD_INFLOW(G_SIMCMD_INFLOW_ref), G_SIMCMD_MODE(G_SIMCMD_MODE_ref), G_SIMCMD_RESET(G_SIMCMD_RESET_ref), G_SIMCMD_TIMESCALE(G_SIMCMD_TIMESCALE_ref), G_SIM_LEVEL_MM(G_SIM_LEVEL_MM_ref), G_SIM_INFLOW_X10(G_SIM_INFLOW_X10_ref), G_SIM_DISCH_X10(G_SIM_DISCH_X10_ref), G_SIM_MANIFOLDP(G_SIM_MANIFOLDP_ref), G_SIM_PUMPP(G_SIM_PUMPP_ref), G_SIM_VIB_X10(G_SIM_VIB_X10_ref), G_SIM_LSHH(G_SIM_LSHH_ref), G_SIM_LSLL_WET(G_SIM_LSLL_WET_ref), G_SIM_SPILL(G_SIM_SPILL_ref), G_SIM_THERMALOK(G_SIM_THERMALOK_ref), G_SIM_SEALLEAK(G_SIM_SEALLEAK_ref), G_SIM_MAINSOK(G_SIM_MAINSOK_ref) +{ +} + +void Program_IO_MUX::run() { + if (!SEEDED) { + SEEDED = true; + MW_MODE->write(DEF_MODE->read()); + MW_CMDWORD->write(0); + MW_CMDPARAM->write(0); + MW_SPLEVEL->write(DEF_SP_LEVEL->read()); + MW_STARTDUTY->write(DEF_START_DUTY->read()); + MW_STARTP2->write(DEF_START_P2->read()); + MW_STARTP3->write(DEF_START_P3->read()); + MW_STOPALL->write(DEF_STOP_ALL->read()); + MW_HIGHALARM->write(DEF_HIGH_ALARM->read()); + MW_MINSPEED->write(DEF_MIN_SPEED->read()); + MW_SERVICEHRS->write(DEF_SERVICE_HRS->read()); + MW_SIMINFLOW->write(750); + MW_SIMMODE->write(0); + MW_SIMRESET->write(0); + MW_SIMTIMESCALE->write(1); + } + QX_RUNCMD1->write(G_O_RUNCMD->with_lock([&](auto* __glk){ return (*__glk).at(1); })); + QX_RUNCMD2->write(G_O_RUNCMD->with_lock([&](auto* __glk){ return (*__glk).at(2); })); + QX_RUNCMD3->write(G_O_RUNCMD->with_lock([&](auto* __glk){ return (*__glk).at(3); })); + QX_RUNNING1->write(G_O_RUNNING->with_lock([&](auto* __glk){ return (*__glk).at(1); })); + QX_RUNNING2->write(G_O_RUNNING->with_lock([&](auto* __glk){ return (*__glk).at(2); })); + QX_RUNNING3->write(G_O_RUNNING->with_lock([&](auto* __glk){ return (*__glk).at(3); })); + QX_AVAIL1->write(G_O_AVAILABLE->with_lock([&](auto* __glk){ return (*__glk).at(1); })); + QX_AVAIL2->write(G_O_AVAILABLE->with_lock([&](auto* __glk){ return (*__glk).at(2); })); + QX_AVAIL3->write(G_O_AVAILABLE->with_lock([&](auto* __glk){ return (*__glk).at(3); })); + QX_INAUTO->write(G_O_INAUTO->read()); + QX_HIGHLEVEL->write(G_O_HIGHLEVEL->read()); + QX_SPILLACTIVE->write(G_O_SPILLACTIVE->read()); + QX_TRIPPED1->write(G_O_TRIPPED->with_lock([&](auto* __glk){ return (*__glk).at(1); })); + QX_TRIPPED2->write(G_O_TRIPPED->with_lock([&](auto* __glk){ return (*__glk).at(2); })); + QX_TRIPPED3->write(G_O_TRIPPED->with_lock([&](auto* __glk){ return (*__glk).at(3); })); + QW_LEVEL->write(G_O_LEVEL_MM->read()); + QW_INFLOW->write(G_O_INFLOW_X10->read()); + QW_DISCHARGE->write(G_O_DISCH_X10->read()); + QW_PUMPSRUNNING->write(G_O_PUMPSRUN->read()); + QW_SPEED->write(G_O_SPEED_X10->read()); + QW_TIMETOSPILL->write(G_O_TIMETOSPILL->read()); + QW_TIMETOLSHH->write(G_O_TIMETOLSHH->read()); + QW_NETACCUM->write(G_O_NETACCUM->read()); + QW_RUNHOURS1->write(G_O_RUNHOURS->with_lock([&](auto* __glk){ return (*__glk).at(1); })); + QW_RUNHOURS2->write(G_O_RUNHOURS->with_lock([&](auto* __glk){ return (*__glk).at(2); })); + QW_RUNHOURS3->write(G_O_RUNHOURS->with_lock([&](auto* __glk){ return (*__glk).at(3); })); + QW_VOLTOSPILL->write(G_O_VOLTOSPILL->read()); + QW_STATIONSTATE->write(G_O_STATIONSTATE->read()); + QW_PUMPSTATE1->write(G_O_PUMPSTATE->with_lock([&](auto* __glk){ return (*__glk).at(1); })); + QW_PUMPSTATE2->write(G_O_PUMPSTATE->with_lock([&](auto* __glk){ return (*__glk).at(2); })); + QW_PUMPSTATE3->write(G_O_PUMPSTATE->with_lock([&](auto* __glk){ return (*__glk).at(3); })); + QW_DUTYPUMP->write(G_O_DUTYPUMP->read()); + QW_ALARMWORD->write(G_O_ALARMWORD->read()); + QW_CMDACK->write(G_O_CMDACK->read()); + G_CMD_MODE->write(MW_MODE->read()); + G_CMD_WORD->write(MW_CMDWORD->read()); + G_CMD_PARAM->write(MW_CMDPARAM->read()); + G_SP_LEVEL->write(MW_SPLEVEL->read()); + G_SP_STARTDUTY->write(MW_STARTDUTY->read()); + G_SP_STARTP2->write(MW_STARTP2->read()); + G_SP_STARTP3->write(MW_STARTP3->read()); + G_SP_STOPALL->write(MW_STOPALL->read()); + G_SP_HIGHALARM->write(MW_HIGHALARM->read()); + G_SP_MINSPEED->write(MW_MINSPEED->read()); + G_SP_SERVICEHRS->write(MW_SERVICEHRS->read()); + G_SIMCMD_INFLOW->write(MW_SIMINFLOW->read()); + G_SIMCMD_MODE->write(MW_SIMMODE->read()); + G_SIMCMD_RESET->write(MW_SIMRESET->read()); + G_SIMCMD_TIMESCALE->write(MW_SIMTIMESCALE->read()); + if (G_SIM_CLEARRESET->read()) { + MW_SIMRESET->write(0); + } + if (G_SIMACTIVE->read()) { + G_LEVELRAW_MM->write(G_SIM_LEVEL_MM->read()); + G_LEVEL_MM->write(G_SIM_LEVEL_MM->read()); + G_LEVEL_M->write(TO_REAL(G_SIM_LEVEL_MM->read()) / 1000.0); + G_INFLOW_LPS->write(TO_REAL(G_SIM_INFLOW_X10->read()) / 10.0); + G_DISCH_LPS->write(TO_REAL(G_SIM_DISCH_X10->read()) / 10.0); + G_MANIFOLDP_KPA->write(TO_REAL(G_SIM_MANIFOLDP->read())); + auto __gwv_22 = TO_REAL(G_SIM_PUMPP->with_lock([&](auto* __glk){ return (*__glk).at(1); })); + G_PUMPP_KPA->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_22; }); + auto __gwv_23 = TO_REAL(G_SIM_PUMPP->with_lock([&](auto* __glk){ return (*__glk).at(2); })); + G_PUMPP_KPA->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_23; }); + auto __gwv_24 = TO_REAL(G_SIM_PUMPP->with_lock([&](auto* __glk){ return (*__glk).at(3); })); + G_PUMPP_KPA->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_24; }); + auto __gwv_25 = TO_REAL(G_SIM_VIB_X10->with_lock([&](auto* __glk){ return (*__glk).at(1); })) / 10.0; + G_VIB_MMS->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_25; }); + auto __gwv_26 = TO_REAL(G_SIM_VIB_X10->with_lock([&](auto* __glk){ return (*__glk).at(2); })) / 10.0; + G_VIB_MMS->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_26; }); + auto __gwv_27 = TO_REAL(G_SIM_VIB_X10->with_lock([&](auto* __glk){ return (*__glk).at(3); })) / 10.0; + G_VIB_MMS->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_27; }); + G_LSHH->write(G_SIM_LSHH->read()); + G_LSLL_WET->write(G_SIM_LSLL_WET->read()); + G_SPILLDETECTED->write(G_SIM_SPILL->read()); + auto __gwv_28 = G_SIM_THERMALOK->with_lock([&](auto* __glk){ return (*__glk).at(1); }); + G_THERMALOK->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_28; }); + auto __gwv_29 = G_SIM_THERMALOK->with_lock([&](auto* __glk){ return (*__glk).at(2); }); + G_THERMALOK->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_29; }); + auto __gwv_30 = G_SIM_THERMALOK->with_lock([&](auto* __glk){ return (*__glk).at(3); }); + G_THERMALOK->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_30; }); + auto __gwv_31 = G_SIM_SEALLEAK->with_lock([&](auto* __glk){ return (*__glk).at(1); }); + G_SEALLEAK->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_31; }); + auto __gwv_32 = G_SIM_SEALLEAK->with_lock([&](auto* __glk){ return (*__glk).at(2); }); + G_SEALLEAK->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_32; }); + auto __gwv_33 = G_SIM_SEALLEAK->with_lock([&](auto* __glk){ return (*__glk).at(3); }); + G_SEALLEAK->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_33; }); + G_MAINSOK->write(G_SIM_MAINSOK->read()); + } else { + G_LEVELRAW_MM->write(IW_LIT101->read()); + G_LEVEL_MM->write(IW_LIT101->read()); + G_LEVEL_M->write(TO_REAL(IW_LIT101->read()) / 1000.0); + G_INFLOW_LPS->write(TO_REAL(IW_FIT201->read()) / 10.0); + G_DISCH_LPS->write(TO_REAL(IW_FIT301->read()) / 10.0); + G_MANIFOLDP_KPA->write(TO_REAL(IW_PIT302->read())); + auto __gwv_34 = TO_REAL(IW_PIT311->read()); + G_PUMPP_KPA->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_34; }); + auto __gwv_35 = TO_REAL(IW_PIT321->read()); + G_PUMPP_KPA->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_35; }); + auto __gwv_36 = TO_REAL(IW_PIT331->read()); + G_PUMPP_KPA->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_36; }); + auto __gwv_37 = TO_REAL(IW_VE314->read()) / 10.0; + G_VIB_MMS->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_37; }); + auto __gwv_38 = TO_REAL(IW_VE324->read()) / 10.0; + G_VIB_MMS->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_38; }); + auto __gwv_39 = TO_REAL(IW_VE334->read()) / 10.0; + G_VIB_MMS->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_39; }); + G_LSHH->write(IX_LSHH102->read()); + G_LSLL_WET->write(IX_LSLL103->read()); + G_SPILLDETECTED->write(IX_LSH104->read()); + auto __gwv_40 = IX_TE312->read(); + G_THERMALOK->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_40; }); + auto __gwv_41 = IX_TE322->read(); + G_THERMALOK->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_41; }); + auto __gwv_42 = IX_TE332->read(); + G_THERMALOK->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_42; }); + auto __gwv_43 = IX_MSE313->read(); + G_SEALLEAK->with_lock([&](auto* __glk){ (*__glk).at(1) = __gwv_43; }); + auto __gwv_44 = IX_MSE323->read(); + G_SEALLEAK->with_lock([&](auto* __glk){ (*__glk).at(2) = __gwv_44; }); + auto __gwv_45 = IX_MSE333->read(); + G_SEALLEAK->with_lock([&](auto* __glk){ (*__glk).at(3) = __gwv_45; }); + G_MAINSOK->write(IX_XA502->read()); + } +} + +} // namespace strucpp \ No newline at end of file diff --git a/03-plc/as-built/pou_SIMULATION.cpp b/03-plc/as-built/pou_SIMULATION.cpp new file mode 100644 index 0000000..a8ced20 --- /dev/null +++ b/03-plc/as-built/pou_SIMULATION.cpp @@ -0,0 +1,160 @@ +// Generated by STruC++ - IEC 61131-3 Structured Text to C++ Compiler +// Do not edit this file manually. + +#include "generated.hpp" + +namespace strucpp { + +Program_SIMULATION::Program_SIMULATION(GlobalVar* G_SIMCMD_INFLOW_ref, GlobalVar* G_SIMCMD_MODE_ref, GlobalVar* G_SIMCMD_RESET_ref, GlobalVar* G_SIMCMD_TIMESCALE_ref, GlobalVar>* G_O_RUNCMD_ref, GlobalVar* G_O_SPEED_X10_ref, GlobalVar* G_SIMACTIVE_ref, GlobalVar* G_SIM_CLEARRESET_ref, GlobalVar* G_SIM_LEVEL_MM_ref, GlobalVar* G_SIM_INFLOW_X10_ref, GlobalVar* G_SIM_DISCH_X10_ref, GlobalVar* G_SIM_MANIFOLDP_ref, GlobalVar>* G_SIM_PUMPP_ref, GlobalVar>* G_SIM_VIB_X10_ref, GlobalVar* G_SIM_LSHH_ref, GlobalVar* G_SIM_LSLL_WET_ref, GlobalVar* G_SIM_SPILL_ref, GlobalVar>* G_SIM_THERMALOK_ref, GlobalVar>* G_SIM_SEALLEAK_ref, GlobalVar* G_SIM_MAINSOK_ref) + : SCAN_S(0.1), AREA_M2(120.0), SPILL_M(6.0), LSHH_M(5.5), LSLL_M(0.30), START_DLY_S(3.0), HZ_LO(38.0), HZ_HI(50.0), Q_LO(65.0), Q_HI(120.0), P_IDLE(80.0), P_BASE(220.0), P_PER_LPS(1.4), VIB_IDLE(0.2), VIB_BASE(1.5), VIB_PER_LPS(0.02), INIT(false), VOLUME_M3(0.0), LEVEL_M(0.0), INFLOW_LPS(0.0), SUMFLOW_LPS(0.0), SIMCLOCK_S(0.0), TIMESCALE(0.0), DT_S(0.0), SPEED_HZ(0.0), UNITQ_LPS(0.0), DERATE(0.0), NDELIVERING(0), I(0), RND(12345), NOISE(0.0), G_SIMCMD_INFLOW(G_SIMCMD_INFLOW_ref), G_SIMCMD_MODE(G_SIMCMD_MODE_ref), G_SIMCMD_RESET(G_SIMCMD_RESET_ref), G_SIMCMD_TIMESCALE(G_SIMCMD_TIMESCALE_ref), G_O_RUNCMD(G_O_RUNCMD_ref), G_O_SPEED_X10(G_O_SPEED_X10_ref), G_SIMACTIVE(G_SIMACTIVE_ref), G_SIM_CLEARRESET(G_SIM_CLEARRESET_ref), G_SIM_LEVEL_MM(G_SIM_LEVEL_MM_ref), G_SIM_INFLOW_X10(G_SIM_INFLOW_X10_ref), G_SIM_DISCH_X10(G_SIM_DISCH_X10_ref), G_SIM_MANIFOLDP(G_SIM_MANIFOLDP_ref), G_SIM_PUMPP(G_SIM_PUMPP_ref), G_SIM_VIB_X10(G_SIM_VIB_X10_ref), G_SIM_LSHH(G_SIM_LSHH_ref), G_SIM_LSLL_WET(G_SIM_LSLL_WET_ref), G_SIM_SPILL(G_SIM_SPILL_ref), G_SIM_THERMALOK(G_SIM_THERMALOK_ref), G_SIM_SEALLEAK(G_SIM_SEALLEAK_ref), G_SIM_MAINSOK(G_SIM_MAINSOK_ref) +{ +} + +void Program_SIMULATION::run() { + G_SIM_CLEARRESET->write(false); + if (((!INIT)) | ((G_SIMCMD_RESET->read() == 1))) { + INIT = true; + G_SIM_CLEARRESET->write(true); + SIMCLOCK_S = 0.0; + if (G_SIMCMD_MODE->read() == 3) { + LEVEL_M = 4.0; + } else { + LEVEL_M = 3.5; + } + VOLUME_M3 = LEVEL_M * AREA_M2; + for (I = 1; I <= 3; I++) { + STARTDLY_S.at(I) = 0.0; + DELIVERING.at(I) = false; + FLOW_LPS.at(I) = 0.0; + PRESS_KPA.at(I) = P_IDLE; + VIB_MMS.at(I) = VIB_IDLE; + } + } + if (((G_SIMCMD_TIMESCALE->read() >= 1)) & ((G_SIMCMD_TIMESCALE->read() <= 120))) { + TIMESCALE = TO_REAL(G_SIMCMD_TIMESCALE->read()); + } else { + TIMESCALE = 1.0; + } + DT_S = SCAN_S * TIMESCALE; + SIMCLOCK_S = SIMCLOCK_S + DT_S; + switch (G_SIMCMD_MODE->read()) { + case 1: + INFLOW_LPS = 75.0 + 35.0 * SIN(6.283185 * SIMCLOCK_S / 86400.0); + break; + case 2: + if (SIMCLOCK_S < 1200.0) { + INFLOW_LPS = 75.0 + (300.0 - 75.0) * SIMCLOCK_S / 1200.0; + } else if (SIMCLOCK_S < 3600.0) { + INFLOW_LPS = 300.0; + } else if (SIMCLOCK_S < 9000.0) { + INFLOW_LPS = 300.0 - (300.0 - 75.0) * (SIMCLOCK_S - 3600.0) / 5400.0; + } else { + INFLOW_LPS = 75.0; + } + break; + case 3: + INFLOW_LPS = 165.0; + break; + default: + INFLOW_LPS = TO_REAL(G_SIMCMD_INFLOW->read()) / 10.0; + break; + } + if (INFLOW_LPS < 0.0) { + INFLOW_LPS = 0.0; + } + SPEED_HZ = TO_REAL(G_O_SPEED_X10->read()) / 10.0; + if (SPEED_HZ < HZ_LO) { + UNITQ_LPS = 0.0; + } else { + if (SPEED_HZ > HZ_HI) { + SPEED_HZ = HZ_HI; + } + UNITQ_LPS = Q_LO + (SPEED_HZ - HZ_LO) * (Q_HI - Q_LO) / (HZ_HI - HZ_LO); + } + NDELIVERING = 0; + for (I = 1; I <= 3; I++) { + if (G_O_RUNCMD->with_lock([&](auto* __glk){ return (*__glk).at(I); })) { + if (STARTDLY_S.at(I) < START_DLY_S) { + STARTDLY_S.at(I) = STARTDLY_S.at(I) + SCAN_S; + } + DELIVERING.at(I) = ((STARTDLY_S.at(I) >= START_DLY_S)) & ((UNITQ_LPS > 0.0)); + } else { + STARTDLY_S.at(I) = 0.0; + DELIVERING.at(I) = false; + } + if (DELIVERING.at(I)) { + NDELIVERING = NDELIVERING + 1; + } + } + switch (NDELIVERING) { + case 1: + DERATE = 1.0; + break; + case 2: + DERATE = 0.94; + break; + case 3: + DERATE = 0.88; + break; + default: + DERATE = 1.0; + break; + } + SUMFLOW_LPS = 0.0; + for (I = 1; I <= 3; I++) { + if (DELIVERING.at(I)) { + FLOW_LPS.at(I) = UNITQ_LPS * DERATE; + PRESS_KPA.at(I) = P_BASE + P_PER_LPS * FLOW_LPS.at(I); + VIB_MMS.at(I) = VIB_BASE + VIB_PER_LPS * FLOW_LPS.at(I); + SUMFLOW_LPS = SUMFLOW_LPS + FLOW_LPS.at(I); + } else { + FLOW_LPS.at(I) = 0.0; + PRESS_KPA.at(I) = P_IDLE; + if (G_O_RUNCMD->with_lock([&](auto* __glk){ return (*__glk).at(I); })) { + VIB_MMS.at(I) = VIB_BASE; + } else { + VIB_MMS.at(I) = VIB_IDLE; + } + } + } + VOLUME_M3 = VOLUME_M3 + (INFLOW_LPS - SUMFLOW_LPS) * DT_S / 1000.0; + if (VOLUME_M3 < 0.0) { + VOLUME_M3 = 0.0; + } + if (VOLUME_M3 > SPILL_M * AREA_M2) { + VOLUME_M3 = SPILL_M * AREA_M2; + } + LEVEL_M = VOLUME_M3 / AREA_M2; + RND = (RND * 75 + 74) % 65537; + NOISE = (TO_REAL(RND) / 65536.0 - 0.5) * 0.01; + G_SIM_LEVEL_MM->write(TO_INT(LEVEL_M * 1000.0 * (1.0 + NOISE))); + RND = (RND * 75 + 74) % 65537; + NOISE = (TO_REAL(RND) / 65536.0 - 0.5) * 0.04; + G_SIM_INFLOW_X10->write(TO_INT(INFLOW_LPS * 10.0 * (1.0 + NOISE))); + RND = (RND * 75 + 74) % 65537; + NOISE = (TO_REAL(RND) / 65536.0 - 0.5) * 0.04; + G_SIM_DISCH_X10->write(TO_INT(SUMFLOW_LPS * 10.0 * (1.0 + NOISE))); + for (I = 1; I <= 3; I++) { + auto __gwv_18 = TO_INT(PRESS_KPA.at(I)); + G_SIM_PUMPP->with_lock([&](auto* __glk){ (*__glk).at(I) = __gwv_18; }); + auto __gwv_19 = TO_INT(VIB_MMS.at(I) * 10.0); + G_SIM_VIB_X10->with_lock([&](auto* __glk){ (*__glk).at(I) = __gwv_19; }); + } + G_SIM_MANIFOLDP->write(TO_INT(P_IDLE)); + if (NDELIVERING > 0) { + G_SIM_MANIFOLDP->write(TO_INT(P_BASE + P_PER_LPS * UNITQ_LPS * DERATE)); + } + G_SIM_LSHH->write(LEVEL_M >= LSHH_M); + G_SIM_LSLL_WET->write(LEVEL_M > LSLL_M); + G_SIM_SPILL->write(LEVEL_M >= (SPILL_M - 0.001)); + for (I = 1; I <= 3; I++) { + auto __gwv_20 = true; + G_SIM_THERMALOK->with_lock([&](auto* __glk){ (*__glk).at(I) = __gwv_20; }); + auto __gwv_21 = false; + G_SIM_SEALLEAK->with_lock([&](auto* __glk){ (*__glk).at(I) = __gwv_21; }); + } + G_SIM_MAINSOK->write(true); + G_SIMACTIVE->write(true); +} + +} // namespace strucpp \ No newline at end of file diff --git a/03-plc/as-built/program.st b/03-plc/as-built/program.st new file mode 100644 index 0000000..afc3ea2 --- /dev/null +++ b/03-plc/as-built/program.st @@ -0,0 +1,1687 @@ +FUNCTION_BLOCK FB_DUTY_SELECT + VAR_INPUT + Available : ARRAY [1..3] OF BOOL; + RunHours : ARRAY [1..3] OF REAL; + ServiceDue : ARRAY [1..3] OF BOOL; + RunningNow : ARRAY [1..3] OF BOOL; + PumpsRequired : INT; + END_VAR + VAR_OUTPUT + RunRequest : ARRAY [1..3] OF BOOL; + DutyPump : INT; + END_VAR + VAR + SERVICE_PENALTY : REAL := 1000000.0; + Rank : ARRAY [1..3] OF INT; + Used : ARRAY [1..3] OF BOOL; + Sel : ARRAY [1..3] OF BOOL; + i : INT; + k : INT; + best : INT; + bestKey : REAL; + key : REAL; + nRanked : INT; + slots : INT; + cnt : INT; + END_VAR + + (* --- 1. Reset working state --------------------------------------- *) + FOR i := 1 TO 3 DO + Used[i] := FALSE; + Sel[i] := FALSE; + Rank[i] := 0; + END_FOR; + nRanked := 0; + + (* --- 2. Build the ranked list by repeated selection. + Scanning i ascending with a strict "<" test means an equal key + never displaces an earlier pump, which is rule 4. ------------ *) + FOR k := 1 TO 3 DO + best := 0; + bestKey := 0.0; + FOR i := 1 TO 3 DO + IF Available[i] AND NOT Used[i] THEN + key := RunHours[i]; + IF ServiceDue[i] THEN + key := key + SERVICE_PENALTY; + END_IF; + IF (best = 0) OR (key < bestKey) THEN + best := i; + bestKey := key; + END_IF; + END_IF; + END_FOR; + IF best > 0 THEN + nRanked := nRanked + 1; + Rank[nRanked] := best; + Used[best] := TRUE; + END_IF; + END_FOR; + + (* --- 3. Clamp the demand ------------------------------------------ *) + slots := PumpsRequired; + IF slots < 0 THEN + slots := 0; + END_IF; + IF slots > 3 THEN + slots := 3; + END_IF; + + (* --- 4. Pass A: units already running keep their slots. + Walking in rank order means that if the demand has dropped it + is the worst-ranked running unit that loses its slot. -------- *) + cnt := 0; + FOR k := 1 TO nRanked DO + i := Rank[k]; + IF RunningNow[i] AND (cnt < slots) THEN + Sel[i] := TRUE; + cnt := cnt + 1; + END_IF; + END_FOR; + + (* --- 5. Pass B: fill the slots that remain, by rank --------------- *) + FOR k := 1 TO nRanked DO + i := Rank[k]; + IF (NOT Sel[i]) AND (cnt < slots) THEN + Sel[i] := TRUE; + cnt := cnt + 1; + END_IF; + END_FOR; + + (* --- 6. Publish. DutyPump is the best-ranked selected unit. ------- *) + DutyPump := 0; + FOR k := 1 TO nRanked DO + i := Rank[k]; + IF Sel[i] AND (DutyPump = 0) THEN + DutyPump := i; + END_IF; + END_FOR; + + FOR i := 1 TO 3 DO + RunRequest[i] := Sel[i]; + END_FOR; +END_FUNCTION_BLOCK + +FUNCTION_BLOCK FB_HEADROOM + VAR_INPUT + Level : REAL; + Inflow : REAL; + TotalDischarge : REAL; + END_VAR + VAR_OUTPUT + InflowFilt : REAL; + NetInflow : REAL; + VolToSpill : REAL; + VolToLSHH : REAL; + TimeToSpill : INT; + TimeToLSHH : INT; + END_VAR + VAR + SCAN_S : REAL := 0.1; + TAU_S : REAL := 30.0; + AREA_M2 : REAL := 120.0; + SPILL_M : REAL := 6.000; + LSHH_M : REAL := 5.500; + MIN_NET : REAL := 0.5; + NO_TIME : INT := 32767; + MAX_TIME : REAL := 32767.0; + Primed : BOOL := FALSE; + t : REAL; + END_VAR + + (* --- Inflow filter. First-order lag, 30 s. + + Primed on the first scan rather than ramping from zero: without + this the reference figure in test 14 would take ~2 minutes to + settle and would not reproduce exactly on demand. ---------------- *) + IF NOT Primed THEN + InflowFilt := Inflow; + Primed := TRUE; + ELSE + InflowFilt := InflowFilt + (Inflow - InflowFilt) * SCAN_S / TAU_S; + END_IF; + + NetInflow := InflowFilt - TotalDischarge; + + VolToSpill := (SPILL_M - Level) * AREA_M2; + VolToLSHH := (LSHH_M - Level) * AREA_M2; + IF VolToSpill < 0.0 THEN + VolToSpill := 0.0; + END_IF; + IF VolToLSHH < 0.0 THEN + VolToLSHH := 0.0; + END_IF; + + (* --- Time to spill weir ------------------------------------------- *) + IF NetInflow <= MIN_NET THEN + TimeToSpill := NO_TIME; + ELSE + t := VolToSpill * 1000.0 / NetInflow; + IF t >= MAX_TIME THEN + TimeToSpill := NO_TIME; + ELSIF t < 0.0 THEN + TimeToSpill := 0; + ELSE + TimeToSpill := REAL_TO_INT(t); + END_IF; + END_IF; + + (* --- Time to LSHH -------------------------------------------------- *) + IF NetInflow <= MIN_NET THEN + TimeToLSHH := NO_TIME; + ELSE + t := VolToLSHH * 1000.0 / NetInflow; + IF t >= MAX_TIME THEN + TimeToLSHH := NO_TIME; + ELSIF t < 0.0 THEN + TimeToLSHH := 0; + ELSE + TimeToLSHH := REAL_TO_INT(t); + END_IF; + END_IF; +END_FUNCTION_BLOCK + +FUNCTION_BLOCK FB_LEVEL_CTRL + VAR_INPUT + Level : REAL; + Setpoint : REAL; + Enable : BOOL; + MinSpeed : REAL; + MaxSpeed : REAL; + END_VAR + VAR_OUTPUT + Speed : REAL; + END_VAR + VAR + SCAN_S : REAL := 0.1; + KP : REAL := 12.0; + TI : REAL := 120.0; + Integ : REAL := 38.0; + Err : REAL; + Raw : REAL; + Integrate : BOOL; + END_VAR + + IF NOT Enable THEN + (* Hold at minimum and reset the integrator, so that a restart does + not inherit stale integral action. *) + Integ := MinSpeed; + Speed := MinSpeed; + ELSE + Err := Level - Setpoint; + Raw := KP * Err + Integ; + + (* Anti-windup: freeze the integrator whenever the output is + clamped, except when the error would drive it back into range. *) + Integrate := FALSE; + IF (Raw > MinSpeed) AND (Raw < MaxSpeed) THEN + Integrate := TRUE; + ELSIF (Raw >= MaxSpeed) AND (Err < 0.0) THEN + Integrate := TRUE; + ELSIF (Raw <= MinSpeed) AND (Err > 0.0) THEN + Integrate := TRUE; + END_IF; + + IF Integrate THEN + Integ := Integ + (KP / TI) * Err * SCAN_S; + END_IF; + + Raw := KP * Err + Integ; + + IF Raw > MaxSpeed THEN + Speed := MaxSpeed; + ELSIF Raw < MinSpeed THEN + Speed := MinSpeed; + ELSE + Speed := Raw; + END_IF; + END_IF; +END_FUNCTION_BLOCK + +FUNCTION_BLOCK FB_PUMP + VAR_INPUT + RunRequest : BOOL; + SpeedRef : REAL; + ThermalOK : BOOL; + SealLeak : BOOL; + Vibration : REAL; + DischPressure : REAL; + ResetTrip : BOOL; + Lockout : BOOL; + MinOffBypass : BOOL; + ServiceInterval : REAL; + ResetHours : BOOL; + END_VAR + VAR_OUTPUT + RunCmd : BOOL; + Running : BOOL; + Available : BOOL; + Tripped : BOOL; + State : INT; + RunHours : REAL; + ServiceDue : BOOL; + VibAlarm : BOOL; + SealAlarm : BOOL; + END_VAR + VAR + SCAN_S : REAL := 0.1; + VIB_ALARM : REAL := 7.1; + VIB_TRIP : REAL := 11.0; + NOFLOW_KPA : REAL := 150.0; + MinRunTmr : TON; + MinOffTmr : TON; + NoFlowTmr : TON; + HasRun : BOOL; + StartOK : BOOL; + END_VAR + + (* --- 1. Trip reset. Runs first so that a reset issued while the + initiating condition is still present re-trips immediately + rather than latching clear. --------------------------------- *) + IF ResetTrip THEN + Tripped := FALSE; + END_IF; + + (* --- 2. Trip conditions. All latch; they clear only on reset. ---- *) + IF NOT ThermalOK THEN + Tripped := TRUE; (* TE-31x, section 5 *) + END_IF; + + IF Vibration > VIB_TRIP THEN + Tripped := TRUE; (* VE-31x > 11.0 mm/s *) + END_IF; + + (* No-flow: 20 s after RunCmd goes true, low discharge pressure trips + the unit. Monitored continuously once the window has elapsed, not + sampled once, so a loss of flow while running is also caught. *) + NoFlowTmr(IN := RunCmd, PT := T#20s); + IF NoFlowTmr.Q AND (DischPressure < NOFLOW_KPA) THEN + Tripped := TRUE; + END_IF; + + (* --- 3. Alarms that do not affect availability, section 4.1 ------- *) + VibAlarm := Vibration > VIB_ALARM; + SealAlarm := SealLeak; + + (* --- 4. Availability. A seal leak is deliberately absent here: + it raises an alarm only, per WRPS-PRO-001 5.5. --------------- *) + Available := ThermalOK AND NOT Tripped AND NOT Lockout; + + (* --- 5. Minimum run / minimum off timers. + MinOff is gated on HasRun so that a cold start is not blocked + for 5 minutes after a runtime restart. ---------------------- *) + MinRunTmr(IN := RunCmd, PT := T#5m); + MinOffTmr(IN := (NOT RunCmd) AND HasRun, PT := T#5m); + + StartOK := (NOT HasRun) OR MinOffTmr.Q OR MinOffBypass; + + (* --- 6. Run command ---------------------------------------------- *) + IF Tripped OR Lockout OR NOT ThermalOK THEN + RunCmd := FALSE; + ELSIF RunCmd THEN + (* running: honour minimum run before accepting a stop *) + IF (NOT RunRequest) AND MinRunTmr.Q THEN + RunCmd := FALSE; + END_IF; + ELSE + IF RunRequest AND StartOK THEN + RunCmd := TRUE; + HasRun := TRUE; + END_IF; + END_IF; + + Running := RunCmd; + + (* --- 7. Run hours. Scan-time increments while running only. ------ *) + IF ResetHours THEN + RunHours := 0.0; + END_IF; + IF Running THEN + RunHours := RunHours + SCAN_S / 3600.0; + END_IF; + ServiceDue := RunHours >= ServiceInterval; + + (* --- 8. Published state, section 3.2 ------------------------------ *) + IF Tripped THEN + State := 6; (* Tripped *) + ELSIF Lockout THEN + State := 7; (* Maintenance lockout *) + ELSIF NOT ThermalOK THEN + State := 0; (* Unavailable *) + ELSIF Running AND NOT RunRequest THEN + State := 4; (* Min-run inhibit *) + ELSIF RunCmd AND NOT NoFlowTmr.Q THEN + State := 2; (* Start delay *) + ELSIF Running THEN + State := 3; (* Running *) + ELSIF HasRun AND NOT MinOffTmr.Q THEN + State := 5; (* Min-off inhibit *) + ELSE + State := 1; (* Available, stopped *) + END_IF; +END_FUNCTION_BLOCK + +PROGRAM CONTROL + VAR_EXTERNAL + g_LevelRaw_mm : INT; + g_Level_mm : INT; + g_Level_m : REAL; + g_Inflow_Lps : REAL; + g_Disch_Lps : REAL; + g_PumpP_kPa : ARRAY [1..3] OF REAL; + g_Vib_mms : ARRAY [1..3] OF REAL; + g_LSHH : BOOL; + g_LSLL_Wet : BOOL; + g_SpillDetected : BOOL; + g_ThermalOK : ARRAY [1..3] OF BOOL; + g_SealLeak : ARRAY [1..3] OF BOOL; + g_MainsOK : BOOL; + g_cmd_Mode : INT; + g_cmd_Word : INT; + g_cmd_Param : INT; + g_sp_Level : INT; + g_sp_StartDuty : INT; + g_sp_StartP2 : INT; + g_sp_StartP3 : INT; + g_sp_StopAll : INT; + g_sp_HighAlarm : INT; + g_sp_MinSpeed : INT; + g_sp_ServiceHrs : INT; + g_o_RunCmd : ARRAY [1..3] OF BOOL; + g_o_Running : ARRAY [1..3] OF BOOL; + g_o_Available : ARRAY [1..3] OF BOOL; + g_o_Tripped : ARRAY [1..3] OF BOOL; + g_o_InAuto : BOOL; + g_o_HighLevel : BOOL; + g_o_SpillActive : BOOL; + g_o_Level_mm : INT; + g_o_Inflow_x10 : INT; + g_o_Disch_x10 : INT; + g_o_PumpsRun : INT; + g_o_Speed_x10 : INT; + g_o_TimeToSpill : INT; + g_o_TimeToLSHH : INT; + g_o_NetAccum : INT; + g_o_RunHours : ARRAY [1..3] OF INT; + g_o_VolToSpill : INT; + g_o_StationState : INT; + g_o_PumpState : ARRAY [1..3] OF INT; + g_o_DutyPump : INT; + g_o_AlarmWord : INT; + g_o_CmdAck : INT; + END_VAR + VAR + SPILL_MM : INT := 6000; + LEVEL_MAX_MM : INT := 7000; + HARD_MIN_HZ : REAL := 38.0; + HARD_MAX_HZ : REAL := 50.0; + Pump1 : FB_PUMP; + Pump2 : FB_PUMP; + Pump3 : FB_PUMP; + Duty : FB_DUTY_SELECT; + LvlCtl : FB_LEVEL_CTRL; + Head : FB_HEADROOM; + v_Mode : INT := 1; + v_SpLevel : INT := 4200; + v_StartDuty : INT := 4000; + v_StartP2 : INT := 4500; + v_StartP3 : INT := 5000; + v_StopAll : INT := 1000; + v_HighAlarm : INT := 5200; + v_MinSpeed : INT := 380; + v_ServiceHrs : INT := 4000; + SpRejected : BOOL; + SpOK : BOOL; + CmdBusy : BOOL; + ResetTrip : ARRAY [1..3] OF BOOL; + ResetHours : ARRAY [1..3] OF BOOL; + Lockout : ARRAY [1..3] OF BOOL; + AckAlarms : BOOL; + p : INT; + PumpsRequired : INT; + PumpsAllowed : INT; + StaggerTmr : TON; + StaggerArm : BOOL; + DryRun : BOOL; + DryLockout : BOOL; + LevelRangeFault : BOOL; + LevelFrozen : BOOL; + LevelFault : BOOL; + LevelRef : INT; + LevelMoved : BOOL; + FrozenTmr : TON; + AnyRunning : BOOL; + Avail : ARRAY [1..3] OF BOOL; + Hours : ARRAY [1..3] OF REAL; + SvcDue : ARRAY [1..3] OF BOOL; + RunNow : ARRAY [1..3] OF BOOL; + Req : ARRAY [1..3] OF BOOL; + Speed : REAL; + MinSpeedHz : REAL; + SpLevel_m : REAL; + HighLevel : BOOL; + PumpsRun : INT; + Alarm : DINT; + i : INT; + r : REAL; + Primed : BOOL := FALSE; + END_VAR + + (* ===================================================================== + Step 1 - read and clamp setpoints, section 2.3 + + Every setpoint is validated as a set, not individually: the start + levels only make sense in order. A rejected write holds the last + good value and raises bit 15 rather than acting on it. + ===================================================================== *) + + (* Mode *) + IF (g_cmd_Mode = 1) OR (g_cmd_Mode = 2) THEN + v_Mode := g_cmd_Mode; + ELSE + SpRejected := TRUE; + END_IF; + + (* Level setpoints. A start level at or above the spill weir must + never be accepted, section 2.3. *) + SpOK := TRUE; + IF (g_sp_StopAll < 0) OR (g_sp_StopAll >= g_sp_StartDuty) THEN + SpOK := FALSE; + END_IF; + IF (g_sp_StartDuty >= g_sp_StartP2) OR (g_sp_StartDuty >= SPILL_MM) THEN + SpOK := FALSE; + END_IF; + IF (g_sp_StartP2 >= g_sp_StartP3) OR (g_sp_StartP2 >= SPILL_MM) THEN + SpOK := FALSE; + END_IF; + IF (g_sp_StartP3 >= SPILL_MM) THEN + SpOK := FALSE; + END_IF; + IF (g_sp_Level <= g_sp_StopAll) OR (g_sp_Level >= SPILL_MM) THEN + SpOK := FALSE; + END_IF; + IF (g_sp_HighAlarm <= 0) OR (g_sp_HighAlarm > SPILL_MM) THEN + SpOK := FALSE; + END_IF; + + IF SpOK THEN + v_SpLevel := g_sp_Level; + v_StartDuty := g_sp_StartDuty; + v_StartP2 := g_sp_StartP2; + v_StartP3 := g_sp_StartP3; + v_StopAll := g_sp_StopAll; + v_HighAlarm := g_sp_HighAlarm; + ELSE + SpRejected := TRUE; + END_IF; + + (* Minimum drive speed, Hz x 10, bounded by the hard physical limits *) + IF (g_sp_MinSpeed >= 380) AND (g_sp_MinSpeed <= 500) THEN + v_MinSpeed := g_sp_MinSpeed; + ELSE + SpRejected := TRUE; + END_IF; + + (* Service interval *) + IF g_sp_ServiceHrs > 0 THEN + v_ServiceHrs := g_sp_ServiceHrs; + ELSE + SpRejected := TRUE; + END_IF; + + MinSpeedHz := INT_TO_REAL(v_MinSpeed) / 10.0; + IF MinSpeedHz < HARD_MIN_HZ THEN + MinSpeedHz := HARD_MIN_HZ; + END_IF; + SpLevel_m := INT_TO_REAL(v_SpLevel) / 1000.0; + + + (* ===================================================================== + Step 2 - command word and acknowledge, section 3.3 + + Executes on the rising edge of a non-zero %MW1, echoes the value to + %QW20, then takes no further action until %MW1 returns to 0. + ===================================================================== *) + + (* one-shot pulses, consumed by the FB_PUMP calls later this scan *) + FOR i := 1 TO 3 DO + ResetTrip[i] := FALSE; + ResetHours[i] := FALSE; + END_FOR; + AckAlarms := FALSE; + + IF (g_cmd_Word <> 0) AND NOT CmdBusy THEN + CmdBusy := TRUE; + p := g_cmd_Param; + + CASE g_cmd_Word OF + 1: (* reset all trips *) + FOR i := 1 TO 3 DO + ResetTrip[i] := TRUE; + END_FOR; + (* the dry run lockout is manual-reset and only clears once + the level has actually recovered, section 5 *) + IF g_Level_mm > v_StopAll THEN + DryLockout := FALSE; + END_IF; + + 2: (* reset trip on pump in %MW2 *) + IF (p >= 1) AND (p <= 3) THEN + ResetTrip[p] := TRUE; + END_IF; + + 3: (* lock out pump in %MW2 *) + IF (p >= 1) AND (p <= 3) THEN + Lockout[p] := TRUE; + END_IF; + + 4: (* release lockout on pump in %MW2 *) + IF (p >= 1) AND (p <= 3) THEN + Lockout[p] := FALSE; + END_IF; + + 5: (* reset run hours on pump in %MW2 - service done *) + IF (p >= 1) AND (p <= 3) THEN + ResetHours[p] := TRUE; + END_IF; + + 6: (* acknowledge alarms *) + AckAlarms := TRUE; + SpRejected := FALSE; + END_CASE; + + g_o_CmdAck := g_cmd_Word; + + ELSIF g_cmd_Word = 0 THEN + CmdBusy := FALSE; + g_o_CmdAck := 0; + END_IF; + + + (* ===================================================================== + Level signal integrity, section 5 + + A frozen transmitter reading a plausible value is the failure that + actually causes spills, and a range check alone cannot see it. + ===================================================================== *) + + LevelRangeFault := (g_LevelRaw_mm < 0) OR (g_LevelRaw_mm > LEVEL_MAX_MM); + + IF NOT Primed THEN + LevelRef := g_LevelRaw_mm; + Primed := TRUE; + END_IF; + + IF ABS(g_LevelRaw_mm - LevelRef) > 1 THEN + LevelRef := g_LevelRaw_mm; + LevelMoved := TRUE; + ELSE + LevelMoved := FALSE; + END_IF; + + AnyRunning := Pump1.Running OR Pump2.Running OR Pump3.Running; + FrozenTmr(IN := AnyRunning AND NOT LevelMoved, PT := T#10m); + LevelFrozen := FrozenTmr.Q; + + LevelFault := LevelRangeFault OR LevelFrozen; + + + (* ===================================================================== + Step 3 - determine PumpsRequired from level + + The band between StopAll and StartDuty holds the previous value. + That hysteresis is the whole point; it is never recomputed from + scratch. + ===================================================================== *) + + IF NOT LevelFault THEN + IF g_Level_mm >= v_StartP3 THEN + PumpsRequired := 3; + ELSIF g_Level_mm >= v_StartP2 THEN + PumpsRequired := 2; + ELSIF g_Level_mm >= v_StartDuty THEN + PumpsRequired := 1; + ELSIF g_Level_mm <= v_StopAll THEN + PumpsRequired := 0; + END_IF; + (* otherwise: hold *) + ELSE + (* Fall back to discrete level control, section 5. LSHH and LSLL + are independent instruments and remain trustworthy. *) + IF g_LSHH THEN + PumpsRequired := 3; + END_IF; + (* otherwise: hold, and let the LSLL override below stop the + station if the well is actually dry *) + END_IF; + + + (* ===================================================================== + Step 4 - LSHH override. Start all available, bypass min-off. + ===================================================================== *) + IF g_LSHH THEN + PumpsRequired := 3; + END_IF; + + + (* ===================================================================== + Step 5 - LSLL override. Fail-safe: the instrument reads TRUE when + wet, so a broken wire reads dry and stops the station. + ===================================================================== *) + DryRun := NOT g_LSLL_Wet; + IF DryRun THEN + PumpsRequired := 0; + DryLockout := TRUE; (* latched, manual reset via command 1 *) + END_IF; + IF DryLockout THEN + PumpsRequired := 0; + END_IF; + + + (* ===================================================================== + Step 6 - station mode off + ===================================================================== *) + IF v_Mode = 2 THEN + PumpsRequired := 0; + END_IF; + + + (* ===================================================================== + Step 11 (applied here, before selection) - stagger starts + + Held second and third starts by 30 s each, to limit inrush and the + hydraulic transient. Applied before FB_DUTY_SELECT because it + limits how many units may start, which is an input to selection, + not a correction applied afterwards. Stops are never staggered. + + LSHH bypasses the stagger as well as the min-off timers, so that + the emergency response is immediate. + ===================================================================== *) + IF g_LSHH THEN + PumpsAllowed := PumpsRequired; + StaggerArm := FALSE; + ELSE + StaggerTmr(IN := StaggerArm, PT := T#30s); + IF PumpsAllowed < PumpsRequired THEN + IF PumpsAllowed = 0 THEN + PumpsAllowed := 1; (* first unit starts at once *) + StaggerArm := FALSE; + ELSIF StaggerTmr.Q THEN + PumpsAllowed := PumpsAllowed + 1; + StaggerArm := FALSE; + ELSE + StaggerArm := TRUE; + END_IF; + ELSE + IF PumpsAllowed > PumpsRequired THEN + PumpsAllowed := PumpsRequired; + END_IF; + StaggerArm := FALSE; + END_IF; + END_IF; + + + (* ===================================================================== + Step 7 - duty selection + ===================================================================== *) + Avail[1] := Pump1.Available; Avail[2] := Pump2.Available; Avail[3] := Pump3.Available; + Hours[1] := Pump1.RunHours; Hours[2] := Pump2.RunHours; Hours[3] := Pump3.RunHours; + SvcDue[1] := Pump1.ServiceDue; SvcDue[2] := Pump2.ServiceDue; SvcDue[3] := Pump3.ServiceDue; + RunNow[1] := Pump1.Running; RunNow[2] := Pump2.Running; RunNow[3] := Pump3.Running; + + Duty(Available := Avail, + RunHours := Hours, + ServiceDue := SvcDue, + RunningNow := RunNow, + PumpsRequired := PumpsAllowed); + + Req[1] := Duty.RunRequest[1]; + Req[2] := Duty.RunRequest[2]; + Req[3] := Duty.RunRequest[3]; + + + (* ===================================================================== + Step 8 - level control. On LSHH force 50.0 Hz. + ===================================================================== *) + LvlCtl(Level := g_Level_m, + Setpoint := SpLevel_m, + Enable := (PumpsAllowed > 0), + MinSpeed := MinSpeedHz, + MaxSpeed := HARD_MAX_HZ); + + Speed := LvlCtl.Speed; + IF g_LSHH THEN + Speed := HARD_MAX_HZ; + END_IF; + + + (* ===================================================================== + Step 9 - the pumps + ===================================================================== *) + Pump1(RunRequest := Req[1], + SpeedRef := Speed, + ThermalOK := g_ThermalOK[1], + SealLeak := g_SealLeak[1], + Vibration := g_Vib_mms[1], + DischPressure := g_PumpP_kPa[1], + ResetTrip := ResetTrip[1], + Lockout := Lockout[1], + MinOffBypass := g_LSHH, + ServiceInterval := INT_TO_REAL(v_ServiceHrs), + ResetHours := ResetHours[1]); + + Pump2(RunRequest := Req[2], + SpeedRef := Speed, + ThermalOK := g_ThermalOK[2], + SealLeak := g_SealLeak[2], + Vibration := g_Vib_mms[2], + DischPressure := g_PumpP_kPa[2], + ResetTrip := ResetTrip[2], + Lockout := Lockout[2], + MinOffBypass := g_LSHH, + ServiceInterval := INT_TO_REAL(v_ServiceHrs), + ResetHours := ResetHours[2]); + + Pump3(RunRequest := Req[3], + SpeedRef := Speed, + ThermalOK := g_ThermalOK[3], + SealLeak := g_SealLeak[3], + Vibration := g_Vib_mms[3], + DischPressure := g_PumpP_kPa[3], + ResetTrip := ResetTrip[3], + Lockout := Lockout[3], + MinOffBypass := g_LSHH, + ServiceInterval := INT_TO_REAL(v_ServiceHrs), + ResetHours := ResetHours[3]); + + + (* ===================================================================== + Step 10 - headroom + ===================================================================== *) + Head(Level := g_Level_m, + Inflow := g_Inflow_Lps, + TotalDischarge := g_Disch_Lps); + + + (* ===================================================================== + Step 12 - publish + ===================================================================== *) + + PumpsRun := 0; + IF Pump1.Running THEN PumpsRun := PumpsRun + 1; END_IF; + IF Pump2.Running THEN PumpsRun := PumpsRun + 1; END_IF; + IF Pump3.Running THEN PumpsRun := PumpsRun + 1; END_IF; + + HighLevel := g_Level_mm >= v_HighAlarm; + + g_o_RunCmd[1] := Pump1.RunCmd; + g_o_RunCmd[2] := Pump2.RunCmd; + g_o_RunCmd[3] := Pump3.RunCmd; + g_o_Running[1] := Pump1.Running; + g_o_Running[2] := Pump2.Running; + g_o_Running[3] := Pump3.Running; + g_o_Available[1] := Pump1.Available; + g_o_Available[2] := Pump2.Available; + g_o_Available[3] := Pump3.Available; + g_o_Tripped[1] := Pump1.Tripped; + g_o_Tripped[2] := Pump2.Tripped; + g_o_Tripped[3] := Pump3.Tripped; + + g_o_PumpState[1] := Pump1.State; + g_o_PumpState[2] := Pump2.State; + g_o_PumpState[3] := Pump3.State; + + g_o_InAuto := (v_Mode = 1); + g_o_HighLevel := HighLevel; + g_o_SpillActive := g_SpillDetected; + + g_o_Level_mm := g_Level_mm; + g_o_PumpsRun := PumpsRun; + g_o_DutyPump := Duty.DutyPump; + + (* scaled analogues, clamped into 16-bit signed range *) + r := g_Inflow_Lps * 10.0; + IF r > 32767.0 THEN r := 32767.0; ELSIF r < -32768.0 THEN r := -32768.0; END_IF; + g_o_Inflow_x10 := REAL_TO_INT(r); + + r := g_Disch_Lps * 10.0; + IF r > 32767.0 THEN r := 32767.0; ELSIF r < -32768.0 THEN r := -32768.0; END_IF; + g_o_Disch_x10 := REAL_TO_INT(r); + + r := Speed * 10.0; + IF r > 32767.0 THEN r := 32767.0; ELSIF r < 0.0 THEN r := 0.0; END_IF; + g_o_Speed_x10 := REAL_TO_INT(r); + + r := Head.NetInflow * 10.0; + IF r > 32767.0 THEN r := 32767.0; ELSIF r < -32768.0 THEN r := -32768.0; END_IF; + g_o_NetAccum := REAL_TO_INT(r); + + r := Head.VolToSpill; + IF r > 32767.0 THEN r := 32767.0; ELSIF r < 0.0 THEN r := 0.0; END_IF; + g_o_VolToSpill := REAL_TO_INT(r); + + g_o_TimeToSpill := Head.TimeToSpill; + g_o_TimeToLSHH := Head.TimeToLSHH; + + r := Pump1.RunHours; + IF r > 32767.0 THEN r := 32767.0; END_IF; + g_o_RunHours[1] := REAL_TO_INT(r); + r := Pump2.RunHours; + IF r > 32767.0 THEN r := 32767.0; END_IF; + g_o_RunHours[2] := REAL_TO_INT(r); + r := Pump3.RunHours; + IF r > 32767.0 THEN r := 32767.0; END_IF; + g_o_RunHours[3] := REAL_TO_INT(r); + + (* --- station state, section 3.1 ---------------------------------- *) + IF v_Mode = 2 THEN + g_o_StationState := 0; (* Off *) + ELSIF g_LSHH THEN + g_o_StationState := 4; (* Emergency *) + ELSIF DryLockout THEN + g_o_StationState := 5; (* Dry run lockout *) + ELSIF LevelFault THEN + g_o_StationState := 6; (* Fault *) + ELSIF HighLevel THEN + g_o_StationState := 3; (* High level *) + ELSIF PumpsRun > 0 THEN + g_o_StationState := 2; (* Pumping *) + ELSE + g_o_StationState := 1; (* Idle *) + END_IF; + + (* --- alarm bitmask, section 6. + + Accumulated in a DINT because bit 15 does not fit a signed INT. + Values at or above 32768 are folded into the negative half of the + 16-bit word; CI Server must read %QW17 as UNSIGNED. ------------- *) + Alarm := 0; + IF HighLevel THEN Alarm := Alarm + 1; END_IF; (* bit0 *) + IF g_LSHH THEN Alarm := Alarm + 2; END_IF; (* bit1 *) + IF DryRun OR DryLockout THEN Alarm := Alarm + 4; END_IF; (* bit2 *) + IF g_SpillDetected THEN Alarm := Alarm + 8; END_IF; (* bit3 *) + IF Pump1.Tripped THEN Alarm := Alarm + 16; END_IF; (* bit4 *) + IF Pump2.Tripped THEN Alarm := Alarm + 32; END_IF; (* bit5 *) + IF Pump3.Tripped THEN Alarm := Alarm + 64; END_IF; (* bit6 *) + IF Pump1.SealAlarm THEN Alarm := Alarm + 128; END_IF; (* bit7 *) + IF Pump2.SealAlarm THEN Alarm := Alarm + 256; END_IF; (* bit8 *) + IF Pump3.SealAlarm THEN Alarm := Alarm + 512; END_IF; (* bit9 *) + IF Pump1.VibAlarm THEN Alarm := Alarm + 1024; END_IF; (* bit10 *) + IF Pump2.VibAlarm THEN Alarm := Alarm + 2048; END_IF; (* bit11 *) + IF Pump3.VibAlarm THEN Alarm := Alarm + 4096; END_IF; (* bit12 *) + IF LevelFault THEN Alarm := Alarm + 8192; END_IF; (* bit13 *) + IF NOT g_MainsOK THEN Alarm := Alarm + 16384; END_IF; (* bit14 *) + IF SpRejected THEN Alarm := Alarm + 32768; END_IF; (* bit15 *) + + IF Alarm >= 32768 THEN + g_o_AlarmWord := DINT_TO_INT(Alarm - 65536); + ELSE + g_o_AlarmWord := DINT_TO_INT(Alarm); + END_IF; +END_PROGRAM + +PROGRAM SIMULATION + VAR_EXTERNAL + g_simcmd_Inflow : INT; + g_simcmd_Mode : INT; + g_simcmd_Reset : INT; + g_simcmd_TimeScale : INT; + g_o_RunCmd : ARRAY [1..3] OF BOOL; + g_o_Speed_x10 : INT; + g_SimActive : BOOL; + g_sim_ClearReset : BOOL; + g_sim_Level_mm : INT; + g_sim_Inflow_x10 : INT; + g_sim_Disch_x10 : INT; + g_sim_ManifoldP : INT; + g_sim_PumpP : ARRAY [1..3] OF INT; + g_sim_Vib_x10 : ARRAY [1..3] OF INT; + g_sim_LSHH : BOOL; + g_sim_LSLL_Wet : BOOL; + g_sim_Spill : BOOL; + g_sim_ThermalOK : ARRAY [1..3] OF BOOL; + g_sim_SealLeak : ARRAY [1..3] OF BOOL; + g_sim_MainsOK : BOOL; + END_VAR + VAR + SCAN_S : REAL := 0.1; + AREA_M2 : REAL := 120.0; + SPILL_M : REAL := 6.0; + LSHH_M : REAL := 5.5; + LSLL_M : REAL := 0.30; + START_DLY_S : REAL := 3.0; + HZ_LO : REAL := 38.0; + HZ_HI : REAL := 50.0; + Q_LO : REAL := 65.0; + Q_HI : REAL := 120.0; + P_IDLE : REAL := 80.0; + P_BASE : REAL := 220.0; + P_PER_LPS : REAL := 1.4; + VIB_IDLE : REAL := 0.2; + VIB_BASE : REAL := 1.5; + VIB_PER_LPS : REAL := 0.02; + Init : BOOL := FALSE; + Volume_m3 : REAL; + Level_m : REAL; + Inflow_Lps : REAL; + SumFlow_Lps : REAL; + SimClock_s : REAL; + StartDly_s : ARRAY [1..3] OF REAL; + Delivering : ARRAY [1..3] OF BOOL; + Flow_Lps : ARRAY [1..3] OF REAL; + Press_kPa : ARRAY [1..3] OF REAL; + Vib_mms : ARRAY [1..3] OF REAL; + TimeScale : REAL; + dt_s : REAL; + Speed_Hz : REAL; + UnitQ_Lps : REAL; + Derate : REAL; + nDelivering : INT; + i : INT; + Rnd : DINT := 12345; + Noise : REAL; + END_VAR + + (* --------------------------------------------------------------------- + Reset / first scan. + + %MW22 = 1 restores the initial conditions of the selected mode + (section 8.2). The acknowledgement is a global; IO_MUX clears %MW22, + because this program may not touch located variables. + --------------------------------------------------------------------- *) + g_sim_ClearReset := FALSE; + + IF (NOT Init) OR (g_simcmd_Reset = 1) THEN + Init := TRUE; + g_sim_ClearReset := TRUE; + + SimClock_s := 0.0; + + IF g_simcmd_Mode = 3 THEN + Level_m := 4.000; (* section 7.3 reference condition *) + ELSE + Level_m := 3.500; (* below start duty, station idle *) + END_IF; + + Volume_m3 := Level_m * AREA_M2; + + FOR i := 1 TO 3 DO + StartDly_s[i] := 0.0; + Delivering[i] := FALSE; + Flow_Lps[i] := 0.0; + Press_kPa[i] := P_IDLE; + Vib_mms[i] := VIB_IDLE; + END_FOR; + END_IF; + + + (* --------------------------------------------------------------------- + Time base. %MW23 = 1..120, anything outside that is treated as 1. + --------------------------------------------------------------------- *) + IF (g_simcmd_TimeScale >= 1) AND (g_simcmd_TimeScale <= 120) THEN + TimeScale := INT_TO_REAL(g_simcmd_TimeScale); + ELSE + TimeScale := 1.0; + END_IF; + + dt_s := SCAN_S * TimeScale; + SimClock_s := SimClock_s + dt_s; + + + (* --------------------------------------------------------------------- + Inflow generator, section 8.2. %MW21 selects the mode. + --------------------------------------------------------------------- *) + CASE g_simcmd_Mode OF + 1: (* diurnal dry weather: 40-110 L/s over a 24 h simulated period *) + Inflow_Lps := 75.0 + 35.0 * SIN(6.283185 * SimClock_s / 86400.0); + + 2: (* wet weather: ramp 20 min to 300, hold 40 min, decay over 90 min *) + IF SimClock_s < 1200.0 THEN + Inflow_Lps := 75.0 + (300.0 - 75.0) * SimClock_s / 1200.0; + ELSIF SimClock_s < 3600.0 THEN + Inflow_Lps := 300.0; + ELSIF SimClock_s < 9000.0 THEN + Inflow_Lps := 300.0 - (300.0 - 75.0) * (SimClock_s - 3600.0) / 5400.0; + ELSE + Inflow_Lps := 75.0; + END_IF; + + 3: (* demo reference, section 7.3: held at exactly 165 L/s *) + Inflow_Lps := 165.0; + + ELSE (* 0 and anything unrecognised: manual, %MW20 in L/s x 10 *) + Inflow_Lps := INT_TO_REAL(g_simcmd_Inflow) / 10.0; + END_CASE; + + IF Inflow_Lps < 0.0 THEN + Inflow_Lps := 0.0; + END_IF; + + + (* --------------------------------------------------------------------- + Per-pump flow model, section 8.2. + + Speed comes from CONTROL's published common drive speed, which is + this scan's value because SIMULATION runs after CONTROL. + --------------------------------------------------------------------- *) + Speed_Hz := INT_TO_REAL(g_o_Speed_x10) / 10.0; + + IF Speed_Hz < HZ_LO THEN + UnitQ_Lps := 0.0; (* static lift cutoff *) + ELSE + IF Speed_Hz > HZ_HI THEN + Speed_Hz := HZ_HI; + END_IF; + UnitQ_Lps := Q_LO + (Speed_Hz - HZ_LO) * (Q_HI - Q_LO) / (HZ_HI - HZ_LO); + END_IF; + + (* Start delay on REAL time - see the header comment. *) + nDelivering := 0; + FOR i := 1 TO 3 DO + IF g_o_RunCmd[i] THEN + IF StartDly_s[i] < START_DLY_S THEN + StartDly_s[i] := StartDly_s[i] + SCAN_S; + END_IF; + Delivering[i] := (StartDly_s[i] >= START_DLY_S) AND (UnitQ_Lps > 0.0); + ELSE + StartDly_s[i] := 0.0; + Delivering[i] := FALSE; + END_IF; + + IF Delivering[i] THEN + nDelivering := nDelivering + 1; + END_IF; + END_FOR; + + (* Parallel derating: 3 units give ~360 L/s, not a naive 3 x 120. *) + CASE nDelivering OF + 1: Derate := 1.00; + 2: Derate := 0.94; + 3: Derate := 0.88; + ELSE Derate := 1.00; + END_CASE; + + SumFlow_Lps := 0.0; + FOR i := 1 TO 3 DO + IF Delivering[i] THEN + Flow_Lps[i] := UnitQ_Lps * Derate; + Press_kPa[i] := P_BASE + P_PER_LPS * Flow_Lps[i]; + Vib_mms[i] := VIB_BASE + VIB_PER_LPS * Flow_Lps[i]; + SumFlow_Lps := SumFlow_Lps + Flow_Lps[i]; + ELSE + Flow_Lps[i] := 0.0; + Press_kPa[i] := P_IDLE; + IF g_o_RunCmd[i] THEN + Vib_mms[i] := VIB_BASE; (* spinning up, no flow yet *) + ELSE + Vib_mms[i] := VIB_IDLE; + END_IF; + END_IF; + END_FOR; + + + (* --------------------------------------------------------------------- + Wet well integration, section 8.2. + + On reaching the weir the level holds at 6.00 m and the excess is + discarded - the station is seen to spill rather than running the + level off scale. + --------------------------------------------------------------------- *) + Volume_m3 := Volume_m3 + (Inflow_Lps - SumFlow_Lps) * dt_s / 1000.0; + + IF Volume_m3 < 0.0 THEN + Volume_m3 := 0.0; + END_IF; + + IF Volume_m3 > SPILL_M * AREA_M2 THEN + Volume_m3 := SPILL_M * AREA_M2; + END_IF; + + Level_m := Volume_m3 / AREA_M2; + + + (* --------------------------------------------------------------------- + Publish, section 8.3: small noise so the trends are not perfectly + smooth and FB_HEADROOM's inflow filter has something to filter. + LCG kept small enough that the DINT multiply cannot overflow. + --------------------------------------------------------------------- *) + Rnd := (Rnd * 75 + 74) MOD 65537; + Noise := (DINT_TO_REAL(Rnd) / 65536.0 - 0.5) * 0.01; (* +/-0.5% *) + g_sim_Level_mm := REAL_TO_INT(Level_m * 1000.0 * (1.0 + Noise)); + + Rnd := (Rnd * 75 + 74) MOD 65537; + Noise := (DINT_TO_REAL(Rnd) / 65536.0 - 0.5) * 0.04; (* +/-2% *) + g_sim_Inflow_x10 := REAL_TO_INT(Inflow_Lps * 10.0 * (1.0 + Noise)); + + Rnd := (Rnd * 75 + 74) MOD 65537; + Noise := (DINT_TO_REAL(Rnd) / 65536.0 - 0.5) * 0.04; + g_sim_Disch_x10 := REAL_TO_INT(SumFlow_Lps * 10.0 * (1.0 + Noise)); + + FOR i := 1 TO 3 DO + g_sim_PumpP[i] := REAL_TO_INT(Press_kPa[i]); + g_sim_Vib_x10[i] := REAL_TO_INT(Vib_mms[i] * 10.0); + END_FOR; + + (* Manifold: the highest delivering unit's pressure, idle if none. *) + g_sim_ManifoldP := REAL_TO_INT(P_IDLE); + IF nDelivering > 0 THEN + g_sim_ManifoldP := REAL_TO_INT(P_BASE + P_PER_LPS * UnitQ_Lps * Derate); + END_IF; + + (* --- level switches. Sense conventions are section 2.1's. --------- *) + g_sim_LSHH := Level_m >= LSHH_M; + g_sim_LSLL_Wet := Level_m > LSLL_M; (* FALSE = dry *) + g_sim_Spill := Level_m >= (SPILL_M - 0.001); + + (* --- plant health. Healthy unless a fault is injected; injection is + not modelled yet, so these are constant. ---------------------- *) + FOR i := 1 TO 3 DO + g_sim_ThermalOK[i] := TRUE; + g_sim_SealLeak[i] := FALSE; + END_FOR; + g_sim_MainsOK := TRUE; + + (* Tell IO_MUX to take its process image from here, not from %IW/%IX. *) + g_SimActive := TRUE; +END_PROGRAM + +PROGRAM IO_MUX + VAR_EXTERNAL + IW_LIT101 : INT; + IW_FIT201 : INT; + IW_FIT301 : INT; + IW_PIT302 : INT; + IW_PIT311 : INT; + IW_PIT321 : INT; + IW_PIT331 : INT; + IW_VE314 : INT; + IW_VE324 : INT; + IW_VE334 : INT; + IX_LSHH102 : BOOL; + IX_LSLL103 : BOOL; + IX_LSH104 : BOOL; + IX_TE312 : BOOL; + IX_TE322 : BOOL; + IX_TE332 : BOOL; + IX_MSE313 : BOOL; + IX_MSE323 : BOOL; + IX_MSE333 : BOOL; + IX_XA502 : BOOL; + QX_RunCmd1 : BOOL; + QX_RunCmd2 : BOOL; + QX_RunCmd3 : BOOL; + QX_Running1 : BOOL; + QX_Running2 : BOOL; + QX_Running3 : BOOL; + QX_Avail1 : BOOL; + QX_Avail2 : BOOL; + QX_Avail3 : BOOL; + QX_InAuto : BOOL; + QX_HighLevel : BOOL; + QX_SpillActive : BOOL; + QX_Tripped1 : BOOL; + QX_Tripped2 : BOOL; + QX_Tripped3 : BOOL; + QW_Level : INT; + QW_Inflow : INT; + QW_Discharge : INT; + QW_PumpsRunning : INT; + QW_Speed : INT; + QW_TimeToSpill : INT; + QW_TimeToLSHH : INT; + QW_NetAccum : INT; + QW_RunHours1 : INT; + QW_RunHours2 : INT; + QW_RunHours3 : INT; + QW_VolToSpill : INT; + QW_StationState : INT; + QW_PumpState1 : INT; + QW_PumpState2 : INT; + QW_PumpState3 : INT; + QW_DutyPump : INT; + QW_AlarmWord : INT; + QW_CmdAck : INT; + MW_Mode : INT; + MW_CmdWord : INT; + MW_CmdParam : INT; + MW_SpLevel : INT; + MW_StartDuty : INT; + MW_StartP2 : INT; + MW_StartP3 : INT; + MW_StopAll : INT; + MW_HighAlarm : INT; + MW_MinSpeed : INT; + MW_ServiceHrs : INT; + MW_SimInflow : INT; + MW_SimMode : INT; + MW_SimReset : INT; + MW_SimTimeScale : INT; + g_LevelRaw_mm : INT; + g_Level_mm : INT; + g_Level_m : REAL; + g_Inflow_Lps : REAL; + g_Disch_Lps : REAL; + g_ManifoldP_kPa : REAL; + g_PumpP_kPa : ARRAY [1..3] OF REAL; + g_Vib_mms : ARRAY [1..3] OF REAL; + g_LSHH : BOOL; + g_LSLL_Wet : BOOL; + g_SpillDetected : BOOL; + g_ThermalOK : ARRAY [1..3] OF BOOL; + g_SealLeak : ARRAY [1..3] OF BOOL; + g_MainsOK : BOOL; + g_cmd_Mode : INT; + g_cmd_Word : INT; + g_cmd_Param : INT; + g_sp_Level : INT; + g_sp_StartDuty : INT; + g_sp_StartP2 : INT; + g_sp_StartP3 : INT; + g_sp_StopAll : INT; + g_sp_HighAlarm : INT; + g_sp_MinSpeed : INT; + g_sp_ServiceHrs : INT; + g_o_RunCmd : ARRAY [1..3] OF BOOL; + g_o_Running : ARRAY [1..3] OF BOOL; + g_o_Available : ARRAY [1..3] OF BOOL; + g_o_Tripped : ARRAY [1..3] OF BOOL; + g_o_InAuto : BOOL; + g_o_HighLevel : BOOL; + g_o_SpillActive : BOOL; + g_o_Level_mm : INT; + g_o_Inflow_x10 : INT; + g_o_Disch_x10 : INT; + g_o_PumpsRun : INT; + g_o_Speed_x10 : INT; + g_o_TimeToSpill : INT; + g_o_TimeToLSHH : INT; + g_o_NetAccum : INT; + g_o_RunHours : ARRAY [1..3] OF INT; + g_o_VolToSpill : INT; + g_o_StationState : INT; + g_o_PumpState : ARRAY [1..3] OF INT; + g_o_DutyPump : INT; + g_o_AlarmWord : INT; + g_o_CmdAck : INT; + DEF_MODE : INT; + DEF_SP_LEVEL : INT; + DEF_START_DUTY : INT; + DEF_START_P2 : INT; + DEF_START_P3 : INT; + DEF_STOP_ALL : INT; + DEF_HIGH_ALARM : INT; + DEF_MIN_SPEED : INT; + DEF_SERVICE_HRS : INT; + g_SimActive : BOOL; + g_sim_ClearReset : BOOL; + g_simcmd_Inflow : INT; + g_simcmd_Mode : INT; + g_simcmd_Reset : INT; + g_simcmd_TimeScale : INT; + g_sim_Level_mm : INT; + g_sim_Inflow_x10 : INT; + g_sim_Disch_x10 : INT; + g_sim_ManifoldP : INT; + g_sim_PumpP : ARRAY [1..3] OF INT; + g_sim_Vib_x10 : ARRAY [1..3] OF INT; + g_sim_LSHH : BOOL; + g_sim_LSLL_Wet : BOOL; + g_sim_Spill : BOOL; + g_sim_ThermalOK : ARRAY [1..3] OF BOOL; + g_sim_SealLeak : ARRAY [1..3] OF BOOL; + g_sim_MainsOK : BOOL; + END_VAR + VAR + Seeded : BOOL := FALSE; + END_VAR + + (* --------------------------------------------------------------------- + Seed the %MW setpoint defaults once, at first scan. + + Section 9 forbids relying on retained variables, so after a runtime + restart every %MW reads 0. Writing the defaults once here means + CI Server sees real values rather than zeros, and CONTROL's + validation does not reject an all-zero image on every scan. + + This is a one-shot write, not a per-scan overwrite: everything + CI Server writes afterwards survives, per section 2. + --------------------------------------------------------------------- *) + IF NOT Seeded THEN + Seeded := TRUE; + MW_Mode := DEF_MODE; + MW_CmdWord := 0; + MW_CmdParam := 0; + MW_SpLevel := DEF_SP_LEVEL; + MW_StartDuty := DEF_START_DUTY; + MW_StartP2 := DEF_START_P2; + MW_StartP3 := DEF_START_P3; + MW_StopAll := DEF_STOP_ALL; + MW_HighAlarm := DEF_HIGH_ALARM; + MW_MinSpeed := DEF_MIN_SPEED; + MW_ServiceHrs := DEF_SERVICE_HRS; + + (* Simulation defaults, section 8.2: manual inflow at the dry + weather average, time scale 1. Unused in the field build. *) + MW_SimInflow := 750; + MW_SimMode := 0; + MW_SimReset := 0; + MW_SimTimeScale := 1; + END_IF; + + + (* --------------------------------------------------------------------- + Publish the previous scan's results to %QW / %QX + --------------------------------------------------------------------- *) + QX_RunCmd1 := g_o_RunCmd[1]; + QX_RunCmd2 := g_o_RunCmd[2]; + QX_RunCmd3 := g_o_RunCmd[3]; + QX_Running1 := g_o_Running[1]; + QX_Running2 := g_o_Running[2]; + QX_Running3 := g_o_Running[3]; + QX_Avail1 := g_o_Available[1]; + QX_Avail2 := g_o_Available[2]; + QX_Avail3 := g_o_Available[3]; + QX_InAuto := g_o_InAuto; + QX_HighLevel := g_o_HighLevel; + QX_SpillActive := g_o_SpillActive; + QX_Tripped1 := g_o_Tripped[1]; + QX_Tripped2 := g_o_Tripped[2]; + QX_Tripped3 := g_o_Tripped[3]; + + QW_Level := g_o_Level_mm; + QW_Inflow := g_o_Inflow_x10; + QW_Discharge := g_o_Disch_x10; + QW_PumpsRunning := g_o_PumpsRun; + QW_Speed := g_o_Speed_x10; + QW_TimeToSpill := g_o_TimeToSpill; + QW_TimeToLSHH := g_o_TimeToLSHH; + QW_NetAccum := g_o_NetAccum; + QW_RunHours1 := g_o_RunHours[1]; + QW_RunHours2 := g_o_RunHours[2]; + QW_RunHours3 := g_o_RunHours[3]; + QW_VolToSpill := g_o_VolToSpill; + QW_StationState := g_o_StationState; + QW_PumpState1 := g_o_PumpState[1]; + QW_PumpState2 := g_o_PumpState[2]; + QW_PumpState3 := g_o_PumpState[3]; + QW_DutyPump := g_o_DutyPump; + QW_AlarmWord := g_o_AlarmWord; + QW_CmdAck := g_o_CmdAck; + + + (* --------------------------------------------------------------------- + Commands and setpoints in + --------------------------------------------------------------------- *) + g_cmd_Mode := MW_Mode; + g_cmd_Word := MW_CmdWord; + g_cmd_Param := MW_CmdParam; + g_sp_Level := MW_SpLevel; + g_sp_StartDuty := MW_StartDuty; + g_sp_StartP2 := MW_StartP2; + g_sp_StartP3 := MW_StartP3; + g_sp_StopAll := MW_StopAll; + g_sp_HighAlarm := MW_HighAlarm; + g_sp_MinSpeed := MW_MinSpeed; + g_sp_ServiceHrs := MW_ServiceHrs; + + + (* --------------------------------------------------------------------- + Simulation control out, and the reset acknowledgement. + + SIMULATION may not touch located variables, so clearing %MW22 after + a reset happens here. + --------------------------------------------------------------------- *) + g_simcmd_Inflow := MW_SimInflow; + g_simcmd_Mode := MW_SimMode; + g_simcmd_Reset := MW_SimReset; + g_simcmd_TimeScale := MW_SimTimeScale; + + IF g_sim_ClearReset THEN + MW_SimReset := 0; + END_IF; + + + (* --------------------------------------------------------------------- + THE MUX, section 8.1. + + Field build: g_SimActive is FALSE (SIMULATION is not compiled + in and nothing ever sets it), so the located + inputs are used. + Simulation build: SIMULATION sets it TRUE every scan and the + process image comes from g_sim_* instead. + + CONTROL sees identical globals either way and cannot tell which + source it is running on - that is the point of section 8.1. + --------------------------------------------------------------------- *) + IF g_SimActive THEN + + g_LevelRaw_mm := g_sim_Level_mm; + g_Level_mm := g_sim_Level_mm; + g_Level_m := INT_TO_REAL(g_sim_Level_mm) / 1000.0; + g_Inflow_Lps := INT_TO_REAL(g_sim_Inflow_x10) / 10.0; + g_Disch_Lps := INT_TO_REAL(g_sim_Disch_x10) / 10.0; + g_ManifoldP_kPa := INT_TO_REAL(g_sim_ManifoldP); + + g_PumpP_kPa[1] := INT_TO_REAL(g_sim_PumpP[1]); + g_PumpP_kPa[2] := INT_TO_REAL(g_sim_PumpP[2]); + g_PumpP_kPa[3] := INT_TO_REAL(g_sim_PumpP[3]); + + g_Vib_mms[1] := INT_TO_REAL(g_sim_Vib_x10[1]) / 10.0; + g_Vib_mms[2] := INT_TO_REAL(g_sim_Vib_x10[2]) / 10.0; + g_Vib_mms[3] := INT_TO_REAL(g_sim_Vib_x10[3]) / 10.0; + + g_LSHH := g_sim_LSHH; + g_LSLL_Wet := g_sim_LSLL_Wet; + g_SpillDetected := g_sim_Spill; + + g_ThermalOK[1] := g_sim_ThermalOK[1]; + g_ThermalOK[2] := g_sim_ThermalOK[2]; + g_ThermalOK[3] := g_sim_ThermalOK[3]; + + g_SealLeak[1] := g_sim_SealLeak[1]; + g_SealLeak[2] := g_sim_SealLeak[2]; + g_SealLeak[3] := g_sim_SealLeak[3]; + + g_MainsOK := g_sim_MainsOK; + + ELSE + + (* --------------------------------------------------------------------- + FIELD SOURCE - analogue inputs, scaled to engineering units + --------------------------------------------------------------------- *) + g_LevelRaw_mm := IW_LIT101; + g_Level_mm := IW_LIT101; + g_Level_m := INT_TO_REAL(IW_LIT101) / 1000.0; + g_Inflow_Lps := INT_TO_REAL(IW_FIT201) / 10.0; + g_Disch_Lps := INT_TO_REAL(IW_FIT301) / 10.0; + g_ManifoldP_kPa := INT_TO_REAL(IW_PIT302); + + g_PumpP_kPa[1] := INT_TO_REAL(IW_PIT311); + g_PumpP_kPa[2] := INT_TO_REAL(IW_PIT321); + g_PumpP_kPa[3] := INT_TO_REAL(IW_PIT331); + + g_Vib_mms[1] := INT_TO_REAL(IW_VE314) / 10.0; + g_Vib_mms[2] := INT_TO_REAL(IW_VE324) / 10.0; + g_Vib_mms[3] := INT_TO_REAL(IW_VE334) / 10.0; + + (* --- discrete inputs. Sense conventions are section 2.1's, and are + applied here so that CONTROL never has to know them. --------- *) + g_LSHH := IX_LSHH102; (* TRUE = wet *) + g_LSLL_Wet := IX_LSLL103; (* TRUE = wet; FALSE = dry, so a + broken wire stops the station *) + g_SpillDetected := IX_LSH104; + + g_ThermalOK[1] := IX_TE312; (* TRUE = healthy *) + g_ThermalOK[2] := IX_TE322; + g_ThermalOK[3] := IX_TE332; + + g_SealLeak[1] := IX_MSE313; (* TRUE = leak *) + g_SealLeak[2] := IX_MSE323; + g_SealLeak[3] := IX_MSE333; + + g_MainsOK := IX_XA502; (* TRUE = healthy *) + + END_IF; +END_PROGRAM + + +CONFIGURATION Config0 + VAR_GLOBAL + CFG_AREA_M2 : REAL := 120.0; + CFG_SPILL_M : REAL := 6.000; + CFG_LSHH_M : REAL := 5.500; + CFG_MIN_HZ : REAL := 38.0; + CFG_MAX_HZ : REAL := 50.0; + DEF_MODE : INT := 1; + DEF_SP_LEVEL : INT := 4200; + DEF_START_DUTY : INT := 4000; + DEF_START_P2 : INT := 4500; + DEF_START_P3 : INT := 5000; + DEF_STOP_ALL : INT := 1000; + DEF_HIGH_ALARM : INT := 5200; + DEF_MIN_SPEED : INT := 380; + DEF_SERVICE_HRS : INT := 4000; + CFG_NO_TIME : INT := 32767; + IW_LIT101 AT %IW0 : INT; + IW_FIT201 AT %IW1 : INT; + IW_FIT301 AT %IW2 : INT; + IW_PIT302 AT %IW3 : INT; + IW_PIT311 AT %IW4 : INT; + IW_PIT321 AT %IW5 : INT; + IW_PIT331 AT %IW6 : INT; + IW_VE314 AT %IW7 : INT; + IW_VE324 AT %IW8 : INT; + IW_VE334 AT %IW9 : INT; + IX_LSHH102 AT %IX0.0 : BOOL; + IX_LSLL103 AT %IX0.1 : BOOL; + IX_LSH104 AT %IX0.2 : BOOL; + IX_TE312 AT %IX0.3 : BOOL; + IX_TE322 AT %IX0.4 : BOOL; + IX_TE332 AT %IX0.5 : BOOL; + IX_MSE313 AT %IX0.6 : BOOL; + IX_MSE323 AT %IX0.7 : BOOL; + IX_MSE333 AT %IX1.0 : BOOL; + IX_XA502 AT %IX1.1 : BOOL; + QX_RunCmd1 AT %QX0.0 : BOOL; + QX_RunCmd2 AT %QX0.1 : BOOL; + QX_RunCmd3 AT %QX0.2 : BOOL; + QX_Running1 AT %QX0.3 : BOOL; + QX_Running2 AT %QX0.4 : BOOL; + QX_Running3 AT %QX0.5 : BOOL; + QX_Avail1 AT %QX0.6 : BOOL; + QX_Avail2 AT %QX0.7 : BOOL; + QX_Avail3 AT %QX1.0 : BOOL; + QX_InAuto AT %QX1.1 : BOOL; + QX_HighLevel AT %QX1.2 : BOOL; + QX_SpillActive AT %QX1.3 : BOOL; + QX_Tripped1 AT %QX1.4 : BOOL; + QX_Tripped2 AT %QX1.5 : BOOL; + QX_Tripped3 AT %QX1.6 : BOOL; + QW_Level AT %QW0 : INT; + QW_Inflow AT %QW1 : INT; + QW_Discharge AT %QW2 : INT; + QW_PumpsRunning AT %QW3 : INT; + QW_Speed AT %QW4 : INT; + QW_TimeToSpill AT %QW5 : INT; + QW_TimeToLSHH AT %QW6 : INT; + QW_NetAccum AT %QW7 : INT; + QW_RunHours1 AT %QW8 : INT; + QW_RunHours2 AT %QW9 : INT; + QW_RunHours3 AT %QW10 : INT; + QW_VolToSpill AT %QW11 : INT; + QW_StationState AT %QW12 : INT; + QW_PumpState1 AT %QW13 : INT; + QW_PumpState2 AT %QW14 : INT; + QW_PumpState3 AT %QW15 : INT; + QW_DutyPump AT %QW16 : INT; + QW_AlarmWord AT %QW17 : INT; + QW_CmdAck AT %QW20 : INT; + MW_Mode AT %MW0 : INT; + MW_CmdWord AT %MW1 : INT; + MW_CmdParam AT %MW2 : INT; + MW_SpLevel AT %MW3 : INT; + MW_StartDuty AT %MW4 : INT; + MW_StartP2 AT %MW5 : INT; + MW_StartP3 AT %MW6 : INT; + MW_StopAll AT %MW7 : INT; + MW_HighAlarm AT %MW8 : INT; + MW_MinSpeed AT %MW9 : INT; + MW_ServiceHrs AT %MW10 : INT; + MW_SimInflow AT %MW20 : INT; + MW_SimMode AT %MW21 : INT; + MW_SimReset AT %MW22 : INT; + MW_SimTimeScale AT %MW23 : INT; + g_LevelRaw_mm : INT; + g_Level_mm : INT; + g_Level_m : REAL; + g_Inflow_Lps : REAL; + g_Disch_Lps : REAL; + g_ManifoldP_kPa : REAL; + g_PumpP_kPa : ARRAY [1..3] OF REAL; + g_Vib_mms : ARRAY [1..3] OF REAL; + g_LSHH : BOOL; + g_LSLL_Wet : BOOL; + g_SpillDetected : BOOL; + g_ThermalOK : ARRAY [1..3] OF BOOL; + g_SealLeak : ARRAY [1..3] OF BOOL; + g_MainsOK : BOOL; + g_cmd_Mode : INT; + g_cmd_Word : INT; + g_cmd_Param : INT; + g_sp_Level : INT; + g_sp_StartDuty : INT; + g_sp_StartP2 : INT; + g_sp_StartP3 : INT; + g_sp_StopAll : INT; + g_sp_HighAlarm : INT; + g_sp_MinSpeed : INT; + g_sp_ServiceHrs : INT; + g_o_RunCmd : ARRAY [1..3] OF BOOL; + g_o_Running : ARRAY [1..3] OF BOOL; + g_o_Available : ARRAY [1..3] OF BOOL; + g_o_Tripped : ARRAY [1..3] OF BOOL; + g_o_InAuto : BOOL; + g_o_HighLevel : BOOL; + g_o_SpillActive : BOOL; + g_o_Level_mm : INT; + g_o_Inflow_x10 : INT; + g_o_Disch_x10 : INT; + g_o_PumpsRun : INT; + g_o_Speed_x10 : INT; + g_o_TimeToSpill : INT; + g_o_TimeToLSHH : INT; + g_o_NetAccum : INT; + g_o_RunHours : ARRAY [1..3] OF INT; + g_o_VolToSpill : INT; + g_o_StationState : INT; + g_o_PumpState : ARRAY [1..3] OF INT; + g_o_DutyPump : INT; + g_o_AlarmWord : INT; + g_o_CmdAck : INT; + g_SimActive : BOOL; + g_sim_ClearReset : BOOL; + g_simcmd_Inflow : INT; + g_simcmd_Mode : INT; + g_simcmd_Reset : INT; + g_simcmd_TimeScale : INT; + g_sim_Level_mm : INT; + g_sim_Inflow_x10 : INT; + g_sim_Disch_x10 : INT; + g_sim_ManifoldP : INT; + g_sim_PumpP : ARRAY [1..3] OF INT; + g_sim_Vib_x10 : ARRAY [1..3] OF INT; + g_sim_LSHH : BOOL; + g_sim_LSLL_Wet : BOOL; + g_sim_Spill : BOOL; + g_sim_ThermalOK : ARRAY [1..3] OF BOOL; + g_sim_SealLeak : ARRAY [1..3] OF BOOL; + g_sim_MainsOK : BOOL; + END_VAR + + RESOURCE Res0 ON PLC + TASK plc_task(INTERVAL := T#100ms,PRIORITY := 0); + PROGRAM inst_sim WITH plc_task : SIMULATION; + PROGRAM inst_mux WITH plc_task : IO_MUX; + PROGRAM inst_ctl WITH plc_task : CONTROL; + END_RESOURCE +END_CONFIGURATION diff --git a/03-plc/as-built/program.st.map.json b/03-plc/as-built/program.st.map.json new file mode 100644 index 0000000..13da15d --- /dev/null +++ b/03-plc/as-built/program.st.map.json @@ -0,0 +1,39 @@ +{ + "pouOffsets": { + "FB_DUTY_SELECT": { + "kind": "FUNCTION_BLOCK", + "startLine": 1, + "endLine": 103 + }, + "FB_HEADROOM": { + "kind": "FUNCTION_BLOCK", + "startLine": 105, + "endLine": 182 + }, + "FB_LEVEL_CTRL": { + "kind": "FUNCTION_BLOCK", + "startLine": 184, + "endLine": 239 + }, + "FB_PUMP": { + "kind": "FUNCTION_BLOCK", + "startLine": 241, + "endLine": 362 + }, + "CONTROL": { + "kind": "PROGRAM", + "startLine": 364, + "endLine": 921 + }, + "SIMULATION": { + "kind": "PROGRAM", + "startLine": 923, + "endLine": 1186 + }, + "IO_MUX": { + "kind": "PROGRAM", + "startLine": 1188, + "endLine": 1525 + } + } +} \ No newline at end of file diff --git a/03-plc/as-built/strucpp_runtime/include/debug_dispatch.hpp b/03-plc/as-built/strucpp_runtime/include/debug_dispatch.hpp new file mode 100644 index 0000000..aa11124 --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/debug_dispatch.hpp @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - Debugger Dispatch + * + * Per-entry force/unforce/read operations for the OpenPLC debugger protocol. + * + * Each leaf variable in a compiled project (including array elements, struct + * fields, and FB internals) is registered in a compile-time Entry table with + * {void* ptr, uint8_t tag}. The pointer is to the leaf's own IECVar; the + * tag indexes this file's type_ops table, which holds templated function + * pointers that know how to force/unforce/read that concrete T. + * + * The table itself is emitted per-project by STruC++ into generated_debug.cpp. + * This header provides the shared, project-agnostic dispatch logic. + */ + +#pragma once + +// `debug_table.hpp` is the AVR-clean header generated_debug.cpp also +// includes — it carries the Entry / TypeTag / STRUCPP_DEBUG_FLASH bits +// shared between the table emitter and the dispatch helpers. Importing +// it here (rather than redefining) keeps the ABI definitions in exactly +// one place. See debug_table.hpp's preamble for why +// `` no longer lives in the same TU as user variable +// references. +#include "debug_table.hpp" + +#include "iec_types.hpp" +#include "iec_traits.hpp" +#include "iec_var.hpp" +#include "iec_string.hpp" +#include "iec_wstring.hpp" +#include +#include +#include +#include + +#ifdef __AVR__ +// `read_entry` and friends use `pgm_read_word_far` / `pgm_read_byte` / +// `pgm_get_far_address`, which live here. Only the runtime translation +// unit (arduino_runtime_glue.cpp / runtime_v4_entry.cpp) ever needs +// this dispatch header; `generated_debug.cpp` consumes only +// `debug_table.hpp` so it never sees the AVR register-macro contamination +// `` brings in transitively. +#include +#endif + +namespace strucpp { namespace debug { + +// --------------------------------------------------------------------------- +// Status codes used by the protocol helpers below. +// Match the values the MatIEC-era ModbusSlave expected (0x7E / 0x81 / 0x82) +// so wire-format parsers on the editor don't need to change. +// --------------------------------------------------------------------------- +constexpr uint8_t STATUS_OK = 0x7E; +constexpr uint8_t STATUS_OUT_OF_BOUNDS = 0x81; +constexpr uint8_t STATUS_DATA_TOO_LARGE = 0x82; + +// --------------------------------------------------------------------------- +// Templated per-type helpers. One instantiation per IEC elementary type; +// type_ops[] below wires them into a runtime-indexable table. +// --------------------------------------------------------------------------- +template +inline void force_impl(void* p, const uint8_t* bytes) noexcept { + T v; + std::memcpy(&v, bytes, sizeof(T)); + static_cast*>(p)->force(v); +} + +// Specialization: memcpy-into-bool is technically UB for non-{0,1} byte +// values, and some AVR GCC versions have optimizer behavior around bool +// that can surprise. Normalize explicitly. +template <> +inline void force_impl(void* p, const uint8_t* bytes) noexcept { + const bool v = bytes[0] != 0; + static_cast*>(p)->force(v); +} + +template +inline void unforce_impl(void* p) noexcept { + static_cast*>(p)->unforce(); +} + +// Soft write — updates the underlying value_ via IECVar::set(). Respects +// existing forces (set() is a no-op while forced_ is true), so a force in +// place stays authoritative until the user explicitly unforces. +// +// Distinct from force_impl: that one pins the variable indefinitely; this +// one writes a value the program can overwrite on the next scan cycle. +// Used by external clients (OPC-UA, future BACnet, etc.) that want +// regular write semantics rather than debugger-style forcing. +template +inline void write_impl(void* p, const uint8_t* bytes) noexcept { + T v; + std::memcpy(&v, bytes, sizeof(T)); + static_cast*>(p)->set(v); +} + +// memcpy-into-bool is technically UB for non-{0,1} byte values; normalize +// explicitly. Same reasoning as force_impl. +template <> +inline void write_impl(void* p, const uint8_t* bytes) noexcept { + const bool v = bytes[0] != 0; + static_cast*>(p)->set(v); +} + +template +inline void read_impl(const void* p, uint8_t* dest) noexcept { + T v = static_cast*>(p)->get(); + std::memcpy(dest, &v, sizeof(T)); +} + +// STRING / WSTRING live in `IECStringVar<254>` / `IECWStringVar<254>`, +// the force-aware wrappers around `IECString<254>` / `IECWString<254>`. +// Both wrappers carry their own length, capped at 254 bytes / 254 wide +// code units of storage. +// +// Wire format (matches the editor decoder in +// `src/frontend/utils/variable-sizes.ts` — `len8-utf8` / `len8-utf16le`): +// +// STRING: [ uint8 length ][ DEBUG_STRING_CAP bytes UTF-8 payload ] +// ^ 1 byte ^ 126 bytes (always — content past the +// declared length is unused but the +// wire width is fixed) +// +// WSTRING: [ uint8 length ][ DEBUG_STRING_CAP * 2 bytes UTF-16LE ] +// ^ 1 byte ^ 252 bytes (126 little-endian code units) +// +// The length prefix is a uint8 because that's what the wire reserves +// (`DEBUG_STRING_CAP = 126` in the editor); a string longer than 126 +// is truncated at the boundary on the way out. The editor reads +// exactly the prefix and uses it to decode `min(length, CAP)` content +// units; the remaining bytes in the fixed window are ignored. +// +// We zero-fill the unused tail of the window on every read so stale +// bus contents from a previous read can't leak into the editor — which +// would otherwise show garbage after the legitimate content if a +// reader misuses the cap. +// +// All four ops are force-aware (read sees the forced value when active; +// write/set is a no-op on a forced variable per `IECStringVar::set`'s +// own guard; force/unforce manipulate the force state directly). +constexpr uint8_t DEBUG_STRING_CAP = 126; // chars / code units +constexpr uint8_t DEBUG_STRING_WIDTH = 1 + DEBUG_STRING_CAP; // 127 bytes on the wire +constexpr uint8_t DEBUG_WSTRING_WIDTH = 1 + DEBUG_STRING_CAP * 2; // 253 bytes on the wire + +// --- STRING (IECStringVar<254>) --------------------------------------- + +inline void read_string(const void* p, uint8_t* dest) noexcept { + const auto* var = static_cast*>(p); + const std::size_t actual_len = var->length(); + const uint8_t wire_len = static_cast( + actual_len < DEBUG_STRING_CAP ? actual_len : DEBUG_STRING_CAP); + dest[0] = wire_len; + if (wire_len > 0) { + std::memcpy(dest + 1, var->c_str(), wire_len); + } + if (wire_len < DEBUG_STRING_CAP) { + std::memset(dest + 1 + wire_len, 0, DEBUG_STRING_CAP - wire_len); + } +} + +inline void write_string(void* p, const uint8_t* bytes) noexcept { + auto* var = static_cast*>(p); + const uint8_t wire_len = bytes[0] < DEBUG_STRING_CAP ? bytes[0] : DEBUG_STRING_CAP; + var->set(IECString<254>(reinterpret_cast(bytes + 1), wire_len)); +} + +inline void force_string(void* p, const uint8_t* bytes) noexcept { + auto* var = static_cast*>(p); + const uint8_t wire_len = bytes[0] < DEBUG_STRING_CAP ? bytes[0] : DEBUG_STRING_CAP; + var->force(IECString<254>(reinterpret_cast(bytes + 1), wire_len)); +} + +inline void unforce_string(void* p) noexcept { + static_cast*>(p)->unforce(); +} + +// --- WSTRING (IECWStringVar<254>) ------------------------------------- + +inline void read_wstring(const void* p, uint8_t* dest) noexcept { + const auto* var = static_cast*>(p); + const std::size_t actual_len = var->length(); + const uint8_t wire_len = static_cast( + actual_len < DEBUG_STRING_CAP ? actual_len : DEBUG_STRING_CAP); + dest[0] = wire_len; + const char16_t* src = var->c_str(); + for (uint8_t i = 0; i < wire_len; ++i) { + // Little-endian 16-bit code unit — explicit byte split so the + // wire format is host-endianness-independent (AVR is LE in + // practice but ARM-BE targets, however rare, would otherwise + // serialise the wrong way around). + dest[1 + i * 2] = static_cast(src[i] & 0xFF); + dest[1 + i * 2 + 1] = static_cast((src[i] >> 8) & 0xFF); + } + const std::size_t used = 1 + static_cast(wire_len) * 2; + if (used < DEBUG_WSTRING_WIDTH) { + std::memset(dest + used, 0, DEBUG_WSTRING_WIDTH - used); + } +} + +inline void write_wstring(void* p, const uint8_t* bytes) noexcept { + auto* var = static_cast*>(p); + const uint8_t wire_len = bytes[0] < DEBUG_STRING_CAP ? bytes[0] : DEBUG_STRING_CAP; + char16_t buf[DEBUG_STRING_CAP]; + for (uint8_t i = 0; i < wire_len; ++i) { + buf[i] = static_cast(bytes[1 + i * 2]) + | static_cast(static_cast(bytes[1 + i * 2 + 1]) << 8); + } + var->set(IECWString<254>(buf, wire_len)); +} + +inline void force_wstring(void* p, const uint8_t* bytes) noexcept { + auto* var = static_cast*>(p); + const uint8_t wire_len = bytes[0] < DEBUG_STRING_CAP ? bytes[0] : DEBUG_STRING_CAP; + char16_t buf[DEBUG_STRING_CAP]; + for (uint8_t i = 0; i < wire_len; ++i) { + buf[i] = static_cast(bytes[1 + i * 2]) + | static_cast(static_cast(bytes[1 + i * 2 + 1]) << 8); + } + var->force(IECWString<254>(buf, wire_len)); +} + +inline void unforce_wstring(void* p) noexcept { + static_cast*>(p)->unforce(); +} + +// --------------------------------------------------------------------------- +// Dispatch table entry. The `size` field is the byte width consumed/produced +// by force/read (for strings: reserved, handled specially). +// --------------------------------------------------------------------------- +struct TypeOps { + void (*force) (void*, const uint8_t*); + void (*unforce)(void*); + void (*read) (const void*, uint8_t*); + void (*write) (void*, const uint8_t*); + uint8_t size; +}; + +// --------------------------------------------------------------------------- +// type_ops[]: one row per TypeTag, in tag order. +// Kept inline so it's flash-resident with no separate .cpp required. +// --------------------------------------------------------------------------- +inline constexpr TypeOps type_ops[TAG__COUNT] = { + /*BOOL */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(BOOL_t) }, + /*SINT */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(SINT_t) }, + /*USINT */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(USINT_t) }, + /*INT */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(INT_t) }, + /*UINT */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(UINT_t) }, + /*DINT */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(DINT_t) }, + /*UDINT */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(UDINT_t) }, + /*LINT */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(LINT_t) }, + /*ULINT */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(ULINT_t) }, + /*REAL */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(REAL_t) }, + /*LREAL */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(LREAL_t) }, + /*BYTE */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(BYTE_t) }, + /*WORD */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(WORD_t) }, + /*DWORD */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(DWORD_t) }, + /*LWORD */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(LWORD_t) }, + /*TIME */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(TIME_t) }, + /*DATE */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(DATE_t) }, + /*TOD */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(TOD_t) }, + /*DT */ { &force_impl, &unforce_impl, &read_impl, &write_impl, sizeof(DT_t) }, + /*STRING */ { &force_string, &unforce_string, &read_string, &write_string, DEBUG_STRING_WIDTH }, + /*WSTRING */ { &force_wstring, &unforce_wstring, &read_wstring, &write_wstring, DEBUG_WSTRING_WIDTH }, +}; + +// --------------------------------------------------------------------------- +// Per-project tables are declared in `debug_table.hpp` (which we +// include above). They live there — not here — because the table-emit +// translation unit (`generated_debug.cpp`) needs the `extern` +// declarations to force external linkage on its `const` definitions, +// and pulling `debug_dispatch.hpp` into generated_debug.cpp drags +// `` → `` into a TU that names user +// variables. See debug_table.hpp's preamble for the rationale. +// +// On AVR these tables are in PROGMEM; the accessors below use +// pgm_read_*_far() when the chip exposes RAMPZ (Mega2560, ATmega32U4, +// ATmega1280, etc.) and fall back to near pgm_read_word() on the +// atmega328p / atmega168 family (Uno, Nano, Pro Mini), whose entire +// flash always fits in 16 bits. +// --------------------------------------------------------------------------- + +// --------------------------------------------------------------------------- +// read_entry(): fetches Entry for (array_idx, elem_idx). +// On AVR uses PROGMEM reads (far when the chip has RAMPZ, near otherwise); +// elsewhere a plain array access. Returns {nullptr, 0} on out-of-bounds so +// callers can cheaply check. +// +// `defined(RAMPZ)` is the same predicate avr-libc's uses to +// gate declarations of `pgm_read_*_far` and `pgm_get_far_address`. Chips +// without RAMPZ (atmega328p / atmega168 family — Uno, Nano, Pro Mini) lack +// the ELPM instruction and the avr-libc headers don't expose the _far +// variants, so referencing them is a hard compile error. Chips with RAMPZ +// (atmega2560 — Mega, atmega1280, atmega32u4 — Micro / Leonardo, etc.) +// keep the far-addressing path since their tables may live above 64 KB +// (Mega) or because the same code is benign-but-correct when flash is +// ≤64 KB (32u4: ELPM with RAMPZ=0 behaves as LPM). +// --------------------------------------------------------------------------- +inline Entry read_entry(uint8_t arr, uint16_t elem) noexcept { + Entry out{nullptr, 0, 0}; + if (arr >= debug_array_count) return out; + +#if defined(__AVR__) && defined(RAMPZ) + // Fetch elem count (uint16_t in PROGMEM) first + uint32_t counts_base = pgm_get_far_address(debug_array_counts); + uint16_t count = pgm_read_word_far(counts_base + arr * sizeof(uint16_t)); + if (elem >= count) return out; + + // Fetch Entry* (pointer-to-PROGMEM, 16-bit on AVR but stored in far flash) + uint32_t arrays_base = pgm_get_far_address(debug_arrays); + // pointers in PROGMEM are 16-bit near pointers on AVR (entry arrays live + // in their own PROGMEM regions which near pointers can still reach, since + // each array < 32 KB. But debug_arrays itself can be far.) + uintptr_t table_ptr = pgm_read_word_far(arrays_base + arr * sizeof(void*)); + + // Read the 4-byte Entry. We assume the array is in the lower 64 KB; if + // it's past, we would need pgm_read_word_far on the element too. For + // Phase 4a we accept the <64 KB constraint per entry array. + const uint8_t* entry_addr = reinterpret_cast(table_ptr) + elem * sizeof(Entry); + uintptr_t ptr_val = pgm_read_word(entry_addr); + uint8_t tag_val = pgm_read_byte(entry_addr + sizeof(void*)); + out.ptr = reinterpret_cast(ptr_val); + out.tag = tag_val; +#elif defined(__AVR__) + // AVR without RAMPZ — flash is ≤64 KB on these chips, so every PROGMEM + // address fits in a 16-bit pointer and near accessors are sufficient. + uint16_t count = pgm_read_word(&debug_array_counts[arr]); + if (elem >= count) return out; + + const Entry* table = reinterpret_cast(pgm_read_word(&debug_arrays[arr])); + const uint8_t* entry_addr = reinterpret_cast(table) + elem * sizeof(Entry); + uintptr_t ptr_val = pgm_read_word(entry_addr); + uint8_t tag_val = pgm_read_byte(entry_addr + sizeof(void*)); + out.ptr = reinterpret_cast(ptr_val); + out.tag = tag_val; +#else + uint16_t count = debug_array_counts[arr]; + if (elem >= count) return out; + out = debug_arrays[arr][elem]; +#endif + return out; +} + +// --------------------------------------------------------------------------- +// Per-entry operations. These are what ModbusSlave / Runtime v4 call. +// --------------------------------------------------------------------------- + +/** Set (force or unforce) a variable. Returns STATUS_* code. */ +inline uint8_t handle_set(uint8_t arr, uint16_t elem, bool forcing, + const uint8_t* bytes, uint16_t len) noexcept { + Entry e = read_entry(arr, elem); + if (!e.ptr || e.tag >= TAG__COUNT) return STATUS_OUT_OF_BOUNDS; + + if (forcing) { + uint8_t expected = type_ops[e.tag].size; + // size == 0 is the string stub — Phase 4a rejects for now + if (expected == 0) return STATUS_DATA_TOO_LARGE; + if (len < expected) return STATUS_DATA_TOO_LARGE; + type_ops[e.tag].force(e.ptr, bytes); + } else { + type_ops[e.tag].unforce(e.ptr); + } + return STATUS_OK; +} + +/** Read one variable into `dest`. Writes type_ops[tag].size bytes. + * Returns bytes written, or 0 on out-of-bounds. */ +inline uint16_t handle_read(uint8_t arr, uint16_t elem, uint8_t* dest) noexcept { + Entry e = read_entry(arr, elem); + if (!e.ptr || e.tag >= TAG__COUNT) return 0; + uint8_t n = type_ops[e.tag].size; + if (n == 0) return 0; // string stub + type_ops[e.tag].read(e.ptr, dest); + return n; +} + +/** Soft write (non-forcing). Updates the underlying value via + * IECVar::set(). If the variable is currently forced, the write is + * silently ignored — forcing remains authoritative until unforced. + * This matches OPC-UA / BACnet write semantics: the next scan cycle + * may overwrite the written value, unlike force which pins it. + * Returns STATUS_* code. */ +inline uint8_t handle_write(uint8_t arr, uint16_t elem, + const uint8_t* bytes, uint16_t len) noexcept { + Entry e = read_entry(arr, elem); + if (!e.ptr || e.tag >= TAG__COUNT) return STATUS_OUT_OF_BOUNDS; + uint8_t expected = type_ops[e.tag].size; + if (expected == 0) return STATUS_DATA_TOO_LARGE; // string stub + if (len < expected) return STATUS_DATA_TOO_LARGE; + type_ops[e.tag].write(e.ptr, bytes); + return STATUS_OK; +} + +/** Variable size for (arr, elem) — 0 if unknown/out-of-bounds. */ +inline uint16_t handle_size(uint8_t arr, uint16_t elem) noexcept { + Entry e = read_entry(arr, elem); + if (!e.ptr || e.tag >= TAG__COUNT) return 0; + return type_ops[e.tag].size; +} + +/** Total number of arrays. */ +inline uint8_t handle_array_count() noexcept { + return debug_array_count; +} + +/** Element count for a given array — 0 if `arr` out-of-bounds. + * AVR branch mirrors `read_entry` above: RAMPZ-equipped chips use far + * accessors, others fall back to near reads. */ +inline uint16_t handle_elem_count(uint8_t arr) noexcept { + if (arr >= debug_array_count) return 0; +#if defined(__AVR__) && defined(RAMPZ) + uint32_t counts_base = pgm_get_far_address(debug_array_counts); + return pgm_read_word_far(counts_base + arr * sizeof(uint16_t)); +#elif defined(__AVR__) + return pgm_read_word(&debug_array_counts[arr]); +#else + return debug_array_counts[arr]; +#endif +} + +} } // namespace strucpp::debug + +// --------------------------------------------------------------------------- +// C-linkage shims for the OpenPLC Runtime v4 .so interface. +// +// The runtime dlopen()s a libplc_.so and dlsym()s these symbols to +// speak the debug protocol without needing the C++ strucpp::debug namespace. +// +// Usage: in the .so's packaging step (Phase 5), compile ONE .cpp with +// +// #define STRUCPP_V4_DEBUG_EXPORTS_DEFINE +// #include "debug_dispatch.hpp" +// +// The symbols use `attribute((used, visibility("default")))` so they're +// retained even under LTO and appear in the dynamic symbol table. +// +// Embedded targets (Arduino) should NOT define the macro — the Flash cost +// of these extra symbols is unnecessary there (the ModbusSlave calls +// handle_* directly via C++ linkage). +// --------------------------------------------------------------------------- +#ifdef STRUCPP_V4_DEBUG_EXPORTS_DEFINE +#define STRUCPP_V4_EXPORT __attribute__((used, visibility("default"))) + +extern "C" { + +STRUCPP_V4_EXPORT uint8_t strucpp_debug_array_count(void) { + return strucpp::debug::handle_array_count(); +} + +STRUCPP_V4_EXPORT uint16_t strucpp_debug_elem_count(uint8_t arr) { + return strucpp::debug::handle_elem_count(arr); +} + +STRUCPP_V4_EXPORT uint16_t strucpp_debug_size(uint8_t arr, uint16_t elem) { + return strucpp::debug::handle_size(arr, elem); +} + +STRUCPP_V4_EXPORT uint8_t strucpp_debug_set(uint8_t arr, uint16_t elem, + bool forcing, + const uint8_t *bytes, + uint16_t len) { + return strucpp::debug::handle_set(arr, elem, forcing, bytes, len); +} + +STRUCPP_V4_EXPORT uint16_t strucpp_debug_read(uint8_t arr, uint16_t elem, + uint8_t *dest) { + return strucpp::debug::handle_read(arr, elem, dest); +} + +STRUCPP_V4_EXPORT uint8_t strucpp_debug_write(uint8_t arr, uint16_t elem, + const uint8_t *bytes, + uint16_t len) { + return strucpp::debug::handle_write(arr, elem, bytes, len); +} + +} // extern "C" + +#undef STRUCPP_V4_EXPORT +#endif // STRUCPP_V4_DEBUG_EXPORTS_DEFINE diff --git a/03-plc/as-built/strucpp_runtime/include/debug_table.hpp b/03-plc/as-built/strucpp_runtime/include/debug_table.hpp new file mode 100644 index 0000000..88efbec --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/debug_table.hpp @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - Debug Table Types + * + * Minimal header included by the per-project `generated_debug.cpp` to declare + * the debug Entry table. Carries exactly three things: + * + * 1. `STRUCPP_DEBUG_FLASH` — placement attribute applied to the table + * arrays (`PROGMEM` on AVR, no-op elsewhere). + * 2. `TypeTag` enum — the ABI between strucpp's code generator and the + * runtime's per-type dispatch. + * 3. `Entry` struct — the per-leaf record shape. + * + * Crucially, this header does NOT include `` — and that's the + * entire reason it exists. On AVR targets, `` transitively pulls + * `` which defines `SP`, `SREG`, `OCR0A`, `TCNT0`, `DDRA`, etc. as + * preprocessor macros expanding to register-pointer casts. IEC 61131-3 + * function blocks routinely use those identifiers as field names (PID's + * `SP` setpoint, for example), so the moment a TU that names a user variable + * sees `` the preprocessor mangles `g_config.INSTANCE0.SP` into + * `g_config.INSTANCE0.(*(volatile uint16_t *)(0x3D))` and the C++ parser + * dies at the `(`. + * + * The dispatch helpers (`read_entry`, `handle_set`, …) genuinely need the + * AVR-specific accessors `pgm_read_word_far` / `pgm_read_byte` / etc., so + * they stay in `debug_dispatch.hpp`. But the runtime translation unit + * that #includes that header doesn't name user variables — `generated_debug.cpp` + * is the only TU that does, and it doesn't need the dispatch helpers. By + * having `generated_debug.cpp` include THIS file instead of + * `debug_dispatch.hpp`, AVR's register macros never enter the user-variable + * TU and identifier collisions become structurally impossible. + * + * `debug_dispatch.hpp` includes this file too, so the `TypeTag` enum and + * `Entry` struct stay defined exactly once — the dispatch side and the + * table-emit side cannot drift. + */ + +#pragma once + +#include + +// --------------------------------------------------------------------------- +// Flash-placement attribute for per-project pointer tables. +// +// `PROGMEM` (avr-libc) ultimately expands to `__attribute__((__progmem__))`, +// which is a plain GCC section attribute that the avr-gcc driver understands +// natively — `` is not required to USE the attribute, only +// to call the `pgm_read_*` accessors. Defining it directly here keeps this +// header AVR-clean. +// --------------------------------------------------------------------------- +#ifdef __AVR__ +#define STRUCPP_DEBUG_FLASH __attribute__((__progmem__)) +#else +#define STRUCPP_DEBUG_FLASH +#endif + +namespace strucpp { namespace debug { + +// --------------------------------------------------------------------------- +// Type tags. Order is ABI — matches the indices generated into +// `generated_debug.cpp`'s `Entry{.tag}` fields, and must match `type_ops[]` +// in `debug_dispatch.hpp`. When extending: append only, never reorder. +// --------------------------------------------------------------------------- +enum TypeTag : uint8_t { + TAG_BOOL = 0, + TAG_SINT = 1, + TAG_USINT = 2, + TAG_INT = 3, + TAG_UINT = 4, + TAG_DINT = 5, + TAG_UDINT = 6, + TAG_LINT = 7, + TAG_ULINT = 8, + TAG_REAL = 9, + TAG_LREAL = 10, + TAG_BYTE = 11, + TAG_WORD = 12, + TAG_DWORD = 13, + TAG_LWORD = 14, + TAG_TIME = 15, + TAG_DATE = 16, + TAG_TOD = 17, + TAG_DT = 18, + TAG_STRING = 19, + TAG_WSTRING = 20, + TAG__COUNT +}; + +// --------------------------------------------------------------------------- +// Debug entry: one per leaf variable. Layout is ABI; see notes in +// debug_dispatch.hpp's runtime dispatch for the per-platform size: +// 4 bytes on 16-bit-pointer AVR, 16 bytes on 64-bit platforms (pad absorbs +// alignment). +// --------------------------------------------------------------------------- +struct Entry { + void* ptr; + uint8_t tag; + uint8_t _pad; +}; + +// --------------------------------------------------------------------------- +// Per-project tables — DECLARED here, DEFINED by generated_debug.cpp. +// +// These MUST be declared `extern` here even though generated_debug.cpp's +// definitions are themselves `const`. C++ rule: a namespace-scope `const` +// variable gets INTERNAL linkage by default — which would hide the +// definitions from every other translation unit (debug_dispatch.hpp's +// `read_entry` / `handle_*` accessors, the runtime entry, the simulator's +// ModbusSlave) and the linker would fail with `undefined reference to +// strucpp::debug::debug_arrays`. Putting the `extern` declaration +// FIRST in the TU forces external linkage for the subsequent `const` +// definitions. +// +// The decls live here (not in debug_dispatch.hpp) because +// `generated_debug.cpp` includes only this header — pulling in +// `debug_dispatch.hpp` from the table-emit TU drags `` +// → `` into user-variable land, which is the whole reason +// this header exists. Consumers that need the accessors (read_entry, +// handle_set, …) include `debug_dispatch.hpp` separately and get the +// extern declarations transitively through this header. +// +// `STRUCPP_DEBUG_FLASH` placement is part of the declared signature so +// the linker sees matching `__attribute__((__progmem__))` on both sides. +// --------------------------------------------------------------------------- +extern const Entry* const debug_arrays[] STRUCPP_DEBUG_FLASH; +extern const uint16_t debug_array_counts[] STRUCPP_DEBUG_FLASH; +extern const uint8_t debug_array_count; + +} } // namespace strucpp::debug diff --git a/03-plc/as-built/strucpp_runtime/include/iec_array.hpp b/03-plc/as-built/strucpp_runtime/include/iec_array.hpp new file mode 100644 index 0000000..c6e72e0 --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_array.hpp @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC Array Types + * + * This header provides IEC 61131-3 array types as C++ templates. + * Arrays use 1-based indexing (IEC convention) and support element-level forcing. + * Unlike MatIEC, individual array elements can be forced for debugging. + */ + +#pragma once + +#include +#include +#include +#include "iec_fault.hpp" +#if STRUCPP_HAS_EXCEPTIONS +#include +#endif +#include "iec_var.hpp" + +namespace strucpp { + +// Array bounds specification +template +struct ArrayBounds { + static constexpr int64_t lower = Lower; + static constexpr int64_t upper = Upper; + static constexpr size_t size = static_cast(Upper - Lower + 1); + + static constexpr bool in_bounds(int64_t index) noexcept { + return index >= Lower && index <= Upper; + } +}; + +// Single-dimensional array +// T is stored directly — caller controls wrapping (IECVar for elementary, +// bare StructName for composites whose fields already contain IECVar leaves). +template +class IEC_ARRAY_1D { +public: + using element_type = T; + using bounds_type = Bounds; + using var_type = T; + static constexpr size_t size = Bounds::size; + +private: + std::array data_; + + // Convert IEC 1-based index to 0-based internal index + static constexpr size_t to_internal_index(int64_t index) noexcept { + return static_cast(index - Bounds::lower); + } + +public: + // Default constructor - initializes all elements to default + IEC_ARRAY_1D() noexcept : data_{} {} + + // Initializer list constructor + template + IEC_ARRAY_1D(std::initializer_list init) noexcept : data_{} { + size_t i = 0; + for (const auto& val : init) { + if (i >= size) break; + data_[i] = val; + ++i; + } + } + + // Element access (1-based IEC indexing) - no bounds checking. + // constexpr so &arr[i] is a constant expression — required by AVR + // PROGMEM placement of the generated debug-pointer table. + // generated_debug.cpp emits entries like + // { (void*)&g_config.foo.MY_ARRAY[i], TAG_DINT, 0 } + // which would otherwise need dynamic initialization and avr-gcc + // rejects `dynamic initialization put into program memory area`. + constexpr var_type& operator[](int64_t index) noexcept { + return data_[to_internal_index(index)]; + } + + constexpr const var_type& operator[](int64_t index) const noexcept { + return data_[to_internal_index(index)]; + } + + // Bounds-checked access - throws std::out_of_range on invalid index + var_type& at(int64_t index) { + if (!Bounds::in_bounds(index)) { +#if STRUCPP_HAS_EXCEPTIONS + throw std::out_of_range("Array index out of bounds"); +#else + iec_runtime_fault(IecFault::ArrayBounds); +#endif + } + return data_[to_internal_index(index)]; + } + + const var_type& at(int64_t index) const { + if (!Bounds::in_bounds(index)) { +#if STRUCPP_HAS_EXCEPTIONS + throw std::out_of_range("Array index out of bounds"); +#else + iec_runtime_fault(IecFault::ArrayBounds); +#endif + } + return data_[to_internal_index(index)]; + } + + // Iterators for range-based for loops + auto begin() noexcept { return data_.begin(); } + auto end() noexcept { return data_.end(); } + auto begin() const noexcept { return data_.begin(); } + auto end() const noexcept { return data_.end(); } + auto cbegin() const noexcept { return data_.cbegin(); } + auto cend() const noexcept { return data_.cend(); } + + // Size information + static constexpr size_t length() noexcept { return size; } + static constexpr int64_t lower_bound(int = 1) noexcept { return Bounds::lower; } + static constexpr int64_t upper_bound(int = 1) noexcept { return Bounds::upper; } + + // Raw data access (for interop) + var_type* data() noexcept { return data_.data(); } + const var_type* data() const noexcept { return data_.data(); } +}; + +// Multi-dimensional array (2D) +template +class IEC_ARRAY_2D { +public: + using element_type = T; + using var_type = T; + static constexpr size_t rows = Bounds1::size; + static constexpr size_t cols = Bounds2::size; + static constexpr size_t total_size = rows * cols; + +private: + std::array data_; + + // Convert 2D IEC indices to linear internal index + static constexpr size_t to_linear_index(int64_t i, int64_t j) noexcept { + return static_cast((i - Bounds1::lower) * cols + (j - Bounds2::lower)); + } + +public: + IEC_ARRAY_2D() noexcept : data_{} {} + + // Flat (row-major) initializer-list constructor — mirrors IEC_ARRAY_1D. + // ST aggregate inits for a 2D array (e.g. `ARRAY[1..20,0..1] OF REAL := [..]`) + // codegen to a flat brace list; fill row-major, ignoring any overflow. + template + IEC_ARRAY_2D(std::initializer_list init) noexcept : data_{} { + size_t i = 0; + for (const auto& val : init) { + if (i >= total_size) break; + data_[i] = val; + ++i; + } + } + + // Element access (1-based IEC indexing) - no bounds checking. + // constexpr so &arr(i, j) is a constant expression — see the + // matching note on IEC_ARRAY_1D::operator[] above. + constexpr var_type& operator()(int64_t i, int64_t j) noexcept { + return data_[to_linear_index(i, j)]; + } + + constexpr const var_type& operator()(int64_t i, int64_t j) const noexcept { + return data_[to_linear_index(i, j)]; + } + + // Bounds-checked access - throws std::out_of_range on invalid index + var_type& at(int64_t i, int64_t j) { + if (!Bounds1::in_bounds(i) || !Bounds2::in_bounds(j)) { +#if STRUCPP_HAS_EXCEPTIONS + throw std::out_of_range("Array index out of bounds"); +#else + iec_runtime_fault(IecFault::ArrayBounds); +#endif + } + return data_[to_linear_index(i, j)]; + } + + const var_type& at(int64_t i, int64_t j) const { + if (!Bounds1::in_bounds(i) || !Bounds2::in_bounds(j)) { +#if STRUCPP_HAS_EXCEPTIONS + throw std::out_of_range("Array index out of bounds"); +#else + iec_runtime_fault(IecFault::ArrayBounds); +#endif + } + return data_[to_linear_index(i, j)]; + } + + // Size information + static constexpr size_t dim1_size() noexcept { return rows; } + static constexpr size_t dim2_size() noexcept { return cols; } + static constexpr int64_t dim1_lower() noexcept { return Bounds1::lower; } + static constexpr int64_t dim1_upper() noexcept { return Bounds1::upper; } + static constexpr int64_t dim2_lower() noexcept { return Bounds2::lower; } + static constexpr int64_t dim2_upper() noexcept { return Bounds2::upper; } + + // Raw data access + var_type* data() noexcept { return data_.data(); } + const var_type* data() const noexcept { return data_.data(); } + + // Iterators (linear traversal) + auto begin() noexcept { return data_.begin(); } + auto end() noexcept { return data_.end(); } + auto begin() const noexcept { return data_.begin(); } + auto end() const noexcept { return data_.end(); } +}; + +// Multi-dimensional array (3D) +template +class IEC_ARRAY_3D { +public: + using element_type = T; + using var_type = T; + static constexpr size_t dim1 = Bounds1::size; + static constexpr size_t dim2 = Bounds2::size; + static constexpr size_t dim3 = Bounds3::size; + static constexpr size_t total_size = dim1 * dim2 * dim3; + +private: + std::array data_; + + static constexpr size_t to_linear_index(int64_t i, int64_t j, int64_t k) noexcept { + return static_cast( + (i - Bounds1::lower) * dim2 * dim3 + + (j - Bounds2::lower) * dim3 + + (k - Bounds3::lower) + ); + } + +public: + IEC_ARRAY_3D() noexcept : data_{} {} + + // constexpr so &arr(i, j, k) is a constant expression — see the + // matching note on IEC_ARRAY_1D::operator[] above. + constexpr var_type& operator()(int64_t i, int64_t j, int64_t k) noexcept { + return data_[to_linear_index(i, j, k)]; + } + + constexpr const var_type& operator()(int64_t i, int64_t j, int64_t k) const noexcept { + return data_[to_linear_index(i, j, k)]; + } + + static constexpr size_t size1() noexcept { return dim1; } + static constexpr size_t size2() noexcept { return dim2; } + static constexpr size_t size3() noexcept { return dim3; } + + var_type* data() noexcept { return data_.data(); } + const var_type* data() const noexcept { return data_.data(); } +}; + +// Convenience type aliases +// Array1D - e.g., Array1D for ARRAY[1..10] OF INT +template +using Array1D = IEC_ARRAY_1D>; + +// Array2D - e.g., Array2D for ARRAY[1..3, 1..4] OF REAL +template +using Array2D = IEC_ARRAY_2D, ArrayBounds>; + +// Array3D +template +using Array3D = IEC_ARRAY_3D, ArrayBounds, ArrayBounds>; + +// ============================================================================= +// Variable-Length Array Views (Phase 3.4) +// Type-erased array views for ARRAY[*] parameters in VAR_IN_OUT +// ============================================================================= + +// 1D ArrayView - type-erased wrapper for ARRAY[*] OF T +template +class ArrayView1D { + T* data_; + int64_t lower_; + int64_t upper_; + +public: + // Construct from any IEC_ARRAY_1D with matching element type + template + ArrayView1D(IEC_ARRAY_1D& arr) + : data_(&arr[Bounds::lower]) + , lower_(Bounds::lower) + , upper_(Bounds::upper) {} + + T& operator[](int64_t index) noexcept { + return data_[index - lower_]; + } + + const T& operator[](int64_t index) const noexcept { + return data_[index - lower_]; + } + + // Bounds-checked access — the codegen emits .at() for all subscripts so an + // out-of-range index raises a clean fault instead of corrupting memory. + T& at(int64_t index) { + if (index < lower_ || index > upper_) { +#if STRUCPP_HAS_EXCEPTIONS + throw std::out_of_range("Array index out of bounds"); +#else + iec_runtime_fault(IecFault::ArrayBounds); +#endif + } + return data_[index - lower_]; + } + + const T& at(int64_t index) const { + if (index < lower_ || index > upper_) { +#if STRUCPP_HAS_EXCEPTIONS + throw std::out_of_range("Array index out of bounds"); +#else + iec_runtime_fault(IecFault::ArrayBounds); +#endif + } + return data_[index - lower_]; + } + + int64_t lower_bound(int = 1) const noexcept { return lower_; } + int64_t upper_bound(int = 1) const noexcept { return upper_; } + int64_t length() const noexcept { return upper_ - lower_ + 1; } +}; + +// 2D ArrayView - type-erased wrapper for ARRAY[*, *] OF T +template +class ArrayView2D { + T* data_; + int64_t lower1_, upper1_; + int64_t lower2_, upper2_; + int64_t dim2_; + +public: + template + ArrayView2D(IEC_ARRAY_2D& arr) + : data_(arr.data()) + , lower1_(Bounds1::lower), upper1_(Bounds1::upper) + , lower2_(Bounds2::lower), upper2_(Bounds2::upper) + , dim2_(Bounds2::upper - Bounds2::lower + 1) {} + + T& operator()(int64_t i, int64_t j) noexcept { + return data_[(i - lower1_) * dim2_ + (j - lower2_)]; + } + + const T& operator()(int64_t i, int64_t j) const noexcept { + return data_[(i - lower1_) * dim2_ + (j - lower2_)]; + } + + // Bounds-checked access — codegen emits .at(i, j) for 2D subscripts. + T& at(int64_t i, int64_t j) { + if (i < lower1_ || i > upper1_ || j < lower2_ || j > upper2_) { +#if STRUCPP_HAS_EXCEPTIONS + throw std::out_of_range("Array index out of bounds"); +#else + iec_runtime_fault(IecFault::ArrayBounds); +#endif + } + return data_[(i - lower1_) * dim2_ + (j - lower2_)]; + } + + const T& at(int64_t i, int64_t j) const { + if (i < lower1_ || i > upper1_ || j < lower2_ || j > upper2_) { +#if STRUCPP_HAS_EXCEPTIONS + throw std::out_of_range("Array index out of bounds"); +#else + iec_runtime_fault(IecFault::ArrayBounds); +#endif + } + return data_[(i - lower1_) * dim2_ + (j - lower2_)]; + } + + int64_t lower_bound(int dim) const noexcept { return dim == 1 ? lower1_ : lower2_; } + int64_t upper_bound(int dim) const noexcept { return dim == 1 ? upper1_ : upper2_; } +}; + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_char.hpp b/03-plc/as-built/strucpp_runtime/include/iec_char.hpp new file mode 100644 index 0000000..cf26bb0 --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_char.hpp @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC Character Types + * + * This header provides the IEC 61131-3 CHAR and WCHAR types with character-specific + * functions and conversions. CHAR is a single-byte character, WCHAR is a wide character + * (UTF-16). + */ + +#pragma once + +#include +#include "iec_types.hpp" + +namespace strucpp { + +template +class IECCharVar { +public: + using value_type = CharType; + + constexpr IECCharVar() noexcept : value_{}, forced_{false}, forced_value_{} {} + constexpr explicit IECCharVar(CharType v) noexcept : value_{v}, forced_{false}, forced_value_{} {} + IECCharVar(const IECCharVar&) = default; + IECCharVar(IECCharVar&&) = default; + IECCharVar& operator=(const IECCharVar&) = default; + IECCharVar& operator=(IECCharVar&&) = default; + + constexpr CharType get() const noexcept { + return forced_ ? forced_value_ : value_; + } + + void set(CharType v) noexcept { + value_ = v; + } + + constexpr CharType get_underlying() const noexcept { + return value_; + } + + void force(CharType v) noexcept { + forced_ = true; + forced_value_ = v; + } + + void unforce() noexcept { + forced_ = false; + } + + constexpr bool is_forced() const noexcept { + return forced_; + } + + constexpr CharType get_forced_value() const noexcept { + return forced_value_; + } + + constexpr operator CharType() const noexcept { + return get(); + } + + IECCharVar& operator=(CharType v) noexcept { + set(v); + return *this; + } + + constexpr bool operator==(const IECCharVar& other) const noexcept { + return get() == other.get(); + } + + constexpr bool operator!=(const IECCharVar& other) const noexcept { + return get() != other.get(); + } + + constexpr bool operator<(const IECCharVar& other) const noexcept { + return get() < other.get(); + } + + constexpr bool operator<=(const IECCharVar& other) const noexcept { + return get() <= other.get(); + } + + constexpr bool operator>(const IECCharVar& other) const noexcept { + return get() > other.get(); + } + + constexpr bool operator>=(const IECCharVar& other) const noexcept { + return get() >= other.get(); + } + + constexpr bool operator==(CharType c) const noexcept { + return get() == c; + } + + constexpr bool operator!=(CharType c) const noexcept { + return get() != c; + } + +private: + CharType value_; + bool forced_; + CharType forced_value_; +}; + +using IEC_CHAR_Var = IECCharVar; +using IEC_WCHAR_Var = IECCharVar; + +inline constexpr CHAR_t CHAR_FROM_INT(int32_t code) noexcept { + return static_cast(code & 0xFF); +} + +inline constexpr WCHAR_t WCHAR_FROM_INT(int32_t code) noexcept { + return static_cast(code & 0xFFFF); +} + +inline constexpr int32_t CHAR_TO_INT(CHAR_t c) noexcept { + return static_cast(static_cast(c)); +} + +inline constexpr int32_t WCHAR_TO_INT(WCHAR_t c) noexcept { + return static_cast(c); +} + +inline constexpr WCHAR_t CHAR_TO_WCHAR(CHAR_t c) noexcept { + return static_cast(static_cast(c)); +} + +inline constexpr CHAR_t WCHAR_TO_CHAR(WCHAR_t c) noexcept { + return static_cast(c & 0xFF); +} + +inline constexpr bool IS_ALPHA(CHAR_t c) noexcept { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} + +inline constexpr bool IS_DIGIT(CHAR_t c) noexcept { + return c >= '0' && c <= '9'; +} + +inline constexpr bool IS_ALNUM(CHAR_t c) noexcept { + return IS_ALPHA(c) || IS_DIGIT(c); +} + +inline constexpr bool IS_SPACE(CHAR_t c) noexcept { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; +} + +inline constexpr bool IS_UPPER(CHAR_t c) noexcept { + return c >= 'A' && c <= 'Z'; +} + +inline constexpr bool IS_LOWER(CHAR_t c) noexcept { + return c >= 'a' && c <= 'z'; +} + +inline constexpr bool IS_XDIGIT(CHAR_t c) noexcept { + return IS_DIGIT(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +inline constexpr CHAR_t TO_UPPER(CHAR_t c) noexcept { + return IS_LOWER(c) ? static_cast(c - 'a' + 'A') : c; +} + +inline constexpr CHAR_t TO_LOWER(CHAR_t c) noexcept { + return IS_UPPER(c) ? static_cast(c - 'A' + 'a') : c; +} + +inline constexpr bool IS_WALPHA(WCHAR_t c) noexcept { + return (c >= u'A' && c <= u'Z') || (c >= u'a' && c <= u'z'); +} + +inline constexpr bool IS_WDIGIT(WCHAR_t c) noexcept { + return c >= u'0' && c <= u'9'; +} + +inline constexpr bool IS_WALNUM(WCHAR_t c) noexcept { + return IS_WALPHA(c) || IS_WDIGIT(c); +} + +inline constexpr bool IS_WSPACE(WCHAR_t c) noexcept { + return c == u' ' || c == u'\t' || c == u'\n' || c == u'\r' || c == u'\f' || c == u'\v'; +} + +inline constexpr bool IS_WUPPER(WCHAR_t c) noexcept { + return c >= u'A' && c <= u'Z'; +} + +inline constexpr bool IS_WLOWER(WCHAR_t c) noexcept { + return c >= u'a' && c <= u'z'; +} + +inline constexpr WCHAR_t TO_WUPPER(WCHAR_t c) noexcept { + return IS_WLOWER(c) ? static_cast(c - u'a' + u'A') : c; +} + +inline constexpr WCHAR_t TO_WLOWER(WCHAR_t c) noexcept { + return IS_WUPPER(c) ? static_cast(c - u'A' + u'a') : c; +} + +inline constexpr bool GT_CHAR(CHAR_t a, CHAR_t b) noexcept { + return a > b; +} + +inline constexpr bool GE_CHAR(CHAR_t a, CHAR_t b) noexcept { + return a >= b; +} + +inline constexpr bool EQ_CHAR(CHAR_t a, CHAR_t b) noexcept { + return a == b; +} + +inline constexpr bool LE_CHAR(CHAR_t a, CHAR_t b) noexcept { + return a <= b; +} + +inline constexpr bool LT_CHAR(CHAR_t a, CHAR_t b) noexcept { + return a < b; +} + +inline constexpr bool NE_CHAR(CHAR_t a, CHAR_t b) noexcept { + return a != b; +} + +inline constexpr bool GT_WCHAR(WCHAR_t a, WCHAR_t b) noexcept { + return a > b; +} + +inline constexpr bool GE_WCHAR(WCHAR_t a, WCHAR_t b) noexcept { + return a >= b; +} + +inline constexpr bool EQ_WCHAR(WCHAR_t a, WCHAR_t b) noexcept { + return a == b; +} + +inline constexpr bool LE_WCHAR(WCHAR_t a, WCHAR_t b) noexcept { + return a <= b; +} + +inline constexpr bool LT_WCHAR(WCHAR_t a, WCHAR_t b) noexcept { + return a < b; +} + +inline constexpr bool NE_WCHAR(WCHAR_t a, WCHAR_t b) noexcept { + return a != b; +} + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_date.hpp b/03-plc/as-built/strucpp_runtime/include/iec_date.hpp new file mode 100644 index 0000000..76d23dc --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_date.hpp @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC DATE Standard Functions + * + * IEC 61131-3 standard functions on the DATE (and LDATE) calendar + * types. DATE / LDATE are stored as signed days since the Unix epoch + * (1970-01-01) in `IECVar` — the same generic per-variable + * wrapper used for every other elementary type. Codegen emits DATE + * variables as `IEC_DATE` (the `IECVar` alias) and the + * functions below take/return `IEC_DATE` so they're directly callable + * from generated POU code. + * + * Scope: only the standard arithmetic / comparison / round-trip + * functions. Calendar component accessors (YEAR, MONTH, DAY, + * DAY_OF_WEEK, DAY_OF_YEAR, …) are intentionally NOT here — those are + * OSCAT-style extensions and user libraries (OSCAT, codesys-v23 + * stdlib imports, …) ship their own implementations. Providing them + * here would create overload ambiguity when a project imports such a + * library. + * + * Historical note: an earlier `DateValue` + `IECDateVar` value- + * class design lived here. Codegen never adopted it (DATE variables + * were always declared as `IEC_DATE`), so the parallel API was dead + * from generated code's perspective. Removed in favour of a single + * IECVar-based surface. See `iec_time.hpp` for the matching note on + * the TIME family. + */ + +#pragma once + +#include +#include "iec_types.hpp" +#include "iec_var.hpp" +#include "iec_traits.hpp" + +namespace strucpp { + +// --------------------------------------------------------------------------- +// Construction helpers +// --------------------------------------------------------------------------- +// `DATE_FROM_YMD(2024, 3, 15)` converts a Gregorian (year, month, day) +// triple into a DATE by way of the Julian Day Number. Branch-free +// integer arithmetic. +inline IEC_DATE DATE_FROM_YMD(int year, int month, int day) noexcept { + const int a = (14 - month) / 12; + const int y = year + 4800 - a; + const int m = month + 12 * a - 3; + const int jdn = day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045; + constexpr int UNIX_EPOCH_JDN = 2440588; + return IEC_DATE(static_cast(jdn - UNIX_EPOCH_JDN)); +} + +inline IEC_DATE DATE_FROM_DAYS(int64_t days) noexcept { + return IEC_DATE(static_cast(days)); +} + +inline int64_t DATE_TO_DAYS(IEC_DATE d) noexcept { + return iec_unwrap(d); +} + +// --------------------------------------------------------------------------- +// Arithmetic +// --------------------------------------------------------------------------- +inline IEC_DATE ADD_DATE(IEC_DATE d, int64_t days) noexcept { + return IEC_DATE(iec_unwrap(d) + static_cast(days)); +} + +inline IEC_DATE SUB_DATE(IEC_DATE d, int64_t days) noexcept { + return IEC_DATE(iec_unwrap(d) - static_cast(days)); +} + +inline int64_t DIFF_DATE(IEC_DATE a, IEC_DATE b) noexcept { + return iec_unwrap(a) - iec_unwrap(b); +} + +// --------------------------------------------------------------------------- +// Comparison +// --------------------------------------------------------------------------- +inline bool GT_DATE(IEC_DATE a, IEC_DATE b) noexcept { return iec_unwrap(a) > iec_unwrap(b); } +inline bool GE_DATE(IEC_DATE a, IEC_DATE b) noexcept { return iec_unwrap(a) >= iec_unwrap(b); } +inline bool EQ_DATE(IEC_DATE a, IEC_DATE b) noexcept { return iec_unwrap(a) == iec_unwrap(b); } +inline bool NE_DATE(IEC_DATE a, IEC_DATE b) noexcept { return iec_unwrap(a) != iec_unwrap(b); } +inline bool LE_DATE(IEC_DATE a, IEC_DATE b) noexcept { return iec_unwrap(a) <= iec_unwrap(b); } +inline bool LT_DATE(IEC_DATE a, IEC_DATE b) noexcept { return iec_unwrap(a) < iec_unwrap(b); } + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_dt.hpp b/03-plc/as-built/strucpp_runtime/include/iec_dt.hpp new file mode 100644 index 0000000..0a8ab5e --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_dt.hpp @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC DATE_AND_TIME Standard Functions + * + * IEC 61131-3 standard functions on the DATE_AND_TIME (DT) and LDT + * combined types. DT / LDT are stored as signed nanoseconds since + * the Unix epoch (1970-01-01 00:00:00) in `IECVar` — the same + * generic per-variable wrapper used everywhere. Codegen emits DT + * variables as `IEC_DT` (the `IECVar` alias) and the functions + * below take/return `IEC_DT` so they're directly callable from + * generated POU code. + * + * Scope: only the standard arithmetic / comparison / split-join + * functions. Calendar/clock component accessors (DT_YEAR, + * DT_MONTH, DT_DAY, DT_HOUR, …) are intentionally NOT here — those + * are OSCAT-style extensions and user libraries (OSCAT, codesys-v23 + * stdlib imports) ship their own implementations. Providing them + * here would create overload ambiguity when a project imports such + * a library. + * + * Historical note: an earlier `DateTimeValue` + `IECDtVar` + * value-class design lived here. Codegen never adopted it; the + * parallel API was dead from generated code's perspective. Removed + * in favour of a single IECVar-based surface. See `iec_time.hpp` + * for the matching note on the TIME family. + */ + +#pragma once + +#include +#include "iec_types.hpp" +#include "iec_var.hpp" +#include "iec_date.hpp" +#include "iec_tod.hpp" +#include "iec_traits.hpp" + +namespace strucpp { + +// --------------------------------------------------------------------------- +// Nanosecond unit constant +// --------------------------------------------------------------------------- +// Duplicated from iec_time.hpp on purpose: clients sometimes include +// iec_dt.hpp without iec_time.hpp, and the `DT_FROM_*` helpers below +// need the unit factor. C++ tolerates redeclaration of inline +// constexpr at namespace scope as long as the value matches. +inline constexpr int64_t DT_NS_PER_DAY = 24LL * 60LL * 60LL * 1000000000LL; + +// --------------------------------------------------------------------------- +// Construction helpers +// --------------------------------------------------------------------------- +inline IEC_DT DT_FROM_COMPONENTS( + int year, int month, int day, + int hour, int minute, int second, + int millisecond = 0, int microsecond = 0, int nanosecond = 0) noexcept { + const int a = (14 - month) / 12; + const int y = year + 4800 - a; + const int m = month + 12 * a - 3; + const int jdn = day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045; + constexpr int UNIX_EPOCH_JDN = 2440588; + const int64_t days = jdn - UNIX_EPOCH_JDN; + + const int64_t ns = days * DT_NS_PER_DAY + + static_cast(hour) * 3600LL * 1000000000LL + + static_cast(minute) * 60LL * 1000000000LL + + static_cast(second) * 1000000000LL + + static_cast(millisecond) * 1000000LL + + static_cast(microsecond) * 1000LL + + nanosecond; + return IEC_DT(static_cast(ns)); +} + +inline IEC_DT DT_FROM_NS(int64_t ns) noexcept { + return IEC_DT(static_cast(ns)); +} + +inline IEC_DT DT_FROM_SECONDS(int64_t s) noexcept { + return IEC_DT(static_cast(s * 1000000000LL)); +} + +// IEC 61131-3 `CONCAT_DATE_TOD`: combine a calendar date and a +// time-of-day into a single DT value. Stored as +// `date_days * NS_PER_DAY + tod_nanoseconds`. +inline IEC_DT CONCAT_DATE_TOD(IEC_DATE date, IEC_TOD tod) noexcept { + return IEC_DT(static_cast(iec_unwrap(date) * DT_NS_PER_DAY + iec_unwrap(tod))); +} + +// IEC 61131-3 `DT_TO_DATE` and `DT_TO_TOD`: split a DT back into its +// date and time-of-day parts. Handles negative pre-epoch values by +// keeping `tod_ns` in the canonical [0, 24h) range and spilling the +// borrow into the day count. +inline IEC_DATE DATE_OF_DT(IEC_DT dt) noexcept { + const DT_t ns = iec_unwrap(dt); + DT_t days = ns / DT_NS_PER_DAY; + DT_t tod_ns = ns % DT_NS_PER_DAY; + if (tod_ns < 0) days -= 1; + return IEC_DATE(static_cast(days)); +} + +inline IEC_TOD TOD_OF_DT(IEC_DT dt) noexcept { + const DT_t ns = iec_unwrap(dt); + DT_t tod_ns = ns % DT_NS_PER_DAY; + if (tod_ns < 0) tod_ns += DT_NS_PER_DAY; + return IEC_TOD(static_cast(tod_ns)); +} + +inline int64_t DT_TO_NS(IEC_DT dt) noexcept { + return iec_unwrap(dt); +} + +inline int64_t DT_TO_MS(IEC_DT dt) noexcept { + return iec_unwrap(dt) / 1000000LL; +} + +inline int64_t DT_TO_SECONDS(IEC_DT dt) noexcept { + return iec_unwrap(dt) / 1000000000LL; +} + +// --------------------------------------------------------------------------- +// Arithmetic +// --------------------------------------------------------------------------- +inline IEC_DT ADD_DT(IEC_DT dt, int64_t ns) noexcept { + return IEC_DT(iec_unwrap(dt) + static_cast(ns)); +} + +inline IEC_DT SUB_DT(IEC_DT dt, int64_t ns) noexcept { + return IEC_DT(iec_unwrap(dt) - static_cast(ns)); +} + +inline int64_t DIFF_DT(IEC_DT a, IEC_DT b) noexcept { + return iec_unwrap(a) - iec_unwrap(b); +} + +// --------------------------------------------------------------------------- +// Comparison +// --------------------------------------------------------------------------- +inline bool GT_DT(IEC_DT a, IEC_DT b) noexcept { return iec_unwrap(a) > iec_unwrap(b); } +inline bool GE_DT(IEC_DT a, IEC_DT b) noexcept { return iec_unwrap(a) >= iec_unwrap(b); } +inline bool EQ_DT(IEC_DT a, IEC_DT b) noexcept { return iec_unwrap(a) == iec_unwrap(b); } +inline bool NE_DT(IEC_DT a, IEC_DT b) noexcept { return iec_unwrap(a) != iec_unwrap(b); } +inline bool LE_DT(IEC_DT a, IEC_DT b) noexcept { return iec_unwrap(a) <= iec_unwrap(b); } +inline bool LT_DT(IEC_DT a, IEC_DT b) noexcept { return iec_unwrap(a) < iec_unwrap(b); } + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_enum.hpp b/03-plc/as-built/strucpp_runtime/include/iec_enum.hpp new file mode 100644 index 0000000..cd7dd3f --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_enum.hpp @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC Enumeration Support + * + * This header provides infrastructure for IEC 61131-3 enumeration types. + * Enumerations in IEC 61131-3 are strongly typed, similar to C++ enum class. + * Actual enum definitions are generated by the compiler based on user TYPE declarations. + */ + +#pragma once + +#include +#ifndef __AVR__ +#include +#endif +#include +#include "iec_var.hpp" + +namespace strucpp { + +/** + * Value wrapper for IEC enumerations. + * Provides a type-safe wrapper around C++ enum class types. + * + * @tparam EnumType The underlying C++ enum class type + */ +template +class IEC_ENUM_Value { +private: + EnumType value_; + +public: + using enum_type = EnumType; + + // Default constructor - initializes to first enum value (0) + constexpr IEC_ENUM_Value() noexcept : value_{} {} + + // Constructor from enum value + constexpr IEC_ENUM_Value(EnumType val) noexcept : value_(val) {} + + // Implicit conversion to underlying enum + constexpr operator EnumType() const noexcept { return value_; } + + // Get the underlying enum value + constexpr EnumType get() const noexcept { return value_; } + + // Assignment from enum value + IEC_ENUM_Value& operator=(EnumType val) noexcept { + value_ = val; + return *this; + } + + // Comparison operators + constexpr bool operator==(const IEC_ENUM_Value& other) const noexcept { + return value_ == other.value_; + } + + constexpr bool operator!=(const IEC_ENUM_Value& other) const noexcept { + return value_ != other.value_; + } + + constexpr bool operator==(EnumType val) const noexcept { + return value_ == val; + } + + constexpr bool operator!=(EnumType val) const noexcept { + return value_ != val; + } + + // Ordering operators (for enums with numeric values) + constexpr bool operator<(const IEC_ENUM_Value& other) const noexcept { + return static_cast(value_) < static_cast(other.value_); + } + + constexpr bool operator<=(const IEC_ENUM_Value& other) const noexcept { + return static_cast(value_) <= static_cast(other.value_); + } + + constexpr bool operator>(const IEC_ENUM_Value& other) const noexcept { + return static_cast(value_) > static_cast(other.value_); + } + + constexpr bool operator>=(const IEC_ENUM_Value& other) const noexcept { + return static_cast(value_) >= static_cast(other.value_); + } + + // Convert to integer (for serialization, debugging) + constexpr int to_int() const noexcept { + return static_cast(value_); + } +}; + +/** + * IEC enumeration variable with forcing support. + * Wraps IEC_ENUM_Value in IECVar for debugging capabilities. + * + * @tparam EnumType The underlying C++ enum class type + */ +template +class IEC_ENUM_Var { +public: + using value_type = IEC_ENUM_Value; + using enum_type = EnumType; + +private: + value_type value_; + bool forced_; + value_type forced_value_; + +public: + IEC_ENUM_Var() noexcept : value_{}, forced_{false}, forced_value_{} {} + + // Non-explicit so raw `EnumType` values can implicitly convert at call + // sites — matches the implicit `IECVar(T v)` constructor for elementary + // types. Without this, codegen would have to wrap every raw enum + // literal at the call site, since enum-typed variables are now + // declared as `IEC_` (= IEC_ENUM_Var). + IEC_ENUM_Var(EnumType val) noexcept + : value_{val}, forced_{false}, forced_value_{} {} + + IEC_ENUM_Var(value_type val) noexcept + : value_{val}, forced_{false}, forced_value_{} {} + + IEC_ENUM_Var(const IEC_ENUM_Var&) = default; + IEC_ENUM_Var(IEC_ENUM_Var&&) = default; + IEC_ENUM_Var& operator=(const IEC_ENUM_Var&) = default; + IEC_ENUM_Var& operator=(IEC_ENUM_Var&&) = default; + + // Get current value (returns forced value if forced) + value_type get() const noexcept { + return forced_ ? forced_value_ : value_; + } + + // Set value (ignored if forced) + void set(value_type v) noexcept { + value_ = v; + } + + void set(EnumType v) noexcept { + value_ = v; + } + + // Get underlying value (ignoring forcing) + value_type get_underlying() const noexcept { + return value_; + } + + // Force to a specific value + void force(value_type v) noexcept { + forced_ = true; + forced_value_ = v; + } + + void force(EnumType v) noexcept { + forced_ = true; + forced_value_ = v; + } + + // Remove forcing + void unforce() noexcept { + forced_ = false; + } + + // Check if forced + bool is_forced() const noexcept { + return forced_; + } + + // Get forced value + value_type get_forced_value() const noexcept { + return forced_value_; + } + + // Implicit conversion to value_type + operator value_type() const noexcept { + return get(); + } + + // Implicit conversion to enum_type + operator EnumType() const noexcept { + return get().get(); + } + + // Assignment operators + IEC_ENUM_Var& operator=(value_type v) noexcept { + set(v); + return *this; + } + + IEC_ENUM_Var& operator=(EnumType v) noexcept { + set(v); + return *this; + } + + // Comparison operators + bool operator==(const IEC_ENUM_Var& other) const noexcept { + return get() == other.get(); + } + + bool operator!=(const IEC_ENUM_Var& other) const noexcept { + return get() != other.get(); + } + + bool operator==(EnumType val) const noexcept { + return get() == val; + } + + bool operator!=(EnumType val) const noexcept { + return get() != val; + } +}; + +/** + * Convenience alias for IEC enumeration with forcing support. + * Usage: IEC_ENUM myVar; + */ +template +using IEC_ENUM = IEC_ENUM_Var; + +#ifndef __AVR__ +// Stream output for IEC_ENUM_Var — outputs underlying integer value +template +inline std::ostream& operator<<(std::ostream& os, const IEC_ENUM_Var& v) { + return os << static_cast::type>( + static_cast(v)); +} +#endif + +/* + * Example generated enumeration: + * + * ST Source: + * TYPE TrafficLight : (RED, YELLOW, GREEN); END_TYPE + * + * Generated C++: + * enum class TrafficLight : int16_t { + * RED = 0, + * YELLOW = 1, + * GREEN = 2 + * }; + * using TrafficLight_Value = IEC_ENUM_Value; + * using TrafficLight_Var = IEC_ENUM_Var; + * + * Usage: + * TrafficLight_Var light; + * light = TrafficLight::RED; + * + * if (light == TrafficLight::RED) { + * // Handle red light + * } + * + * // Force for debugging + * light.force(TrafficLight::GREEN); + * light = TrafficLight::RED; // Ignored + * assert(light == TrafficLight::GREEN); + */ + +/* + * Example typed enumeration (IEC v3): + * + * ST Source: + * TYPE Status : INT (IDLE := 0, RUNNING := 1, ERROR := -1); END_TYPE + * + * Generated C++: + * enum class Status : int16_t { + * IDLE = 0, + * RUNNING = 1, + * ERROR = -1 + * }; + * using Status_Value = IEC_ENUM_Value; + * using Status_Var = IEC_ENUM_Var; + */ + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_fault.hpp b/03-plc/as-built/strucpp_runtime/include/iec_fault.hpp new file mode 100644 index 0000000..627db62 --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_fault.hpp @@ -0,0 +1,63 @@ +/** + * iec_fault.hpp — unrecoverable-fault handling for the STruC++ runtime. + * + * STruC++ runs on two very different classes of target: + * + * - Hosted (Linux/Windows OpenPLC runtime, the REPL, the test harness): + * compiled WITH C++ exceptions. Runtime faults `throw`, and the host + * catches them — e.g. to stop the offending POU while the rest of the + * program keeps scanning, or to print a diagnostic and exit cleanly. + * + * - Freestanding (microcontroller firmware: AVR, SAMD, RP2040, STM32, …): + * compiled with `-fno-exceptions`. There is nothing to catch a throw and + * the C++ exception runtime is dead weight (~10-15 KB of unwinder), so + * instead of throwing, the runtime calls `iec_runtime_fault()`. + * + * The selection is keyed off whether the *compiler* has exceptions enabled + * (`STRUCPP_HAS_EXCEPTIONS`), NOT off any particular chip macro — so every + * `-fno-exceptions` target uniformly takes the fault path with no per-board + * flags. + */ +#pragma once + +#include + +// Are C++ exceptions available in this translation unit? GCC/Clang define +// __cpp_exceptions / __EXCEPTIONS with -fexceptions (the hosted default) and +// leave them undefined with -fno-exceptions; MSVC uses _CPPUNWIND. +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) +# define STRUCPP_HAS_EXCEPTIONS 1 +#else +# define STRUCPP_HAS_EXCEPTIONS 0 +#endif + +namespace strucpp { + +/** + * Reason an unrecoverable runtime fault was raised. Passed to + * `iec_runtime_fault()` so a platform hook can react per-cause (distinct LED + * blink code, alarm tone, log line, …). + */ +enum class IecFault : uint8_t { + NullReference = 0, ///< Dereferenced a NULL IEC pointer/reference. + ArrayBounds = 1, ///< IEC array index out of bounds. + BadLocation = 2, ///< Invalid located-variable area/size character. +}; + +/** + * Unrecoverable-fault handler for targets compiled WITHOUT exceptions — the + * freestanding analogue of `throw`. It MUST NOT return. + * + * Only *declared* here. A weak default definition (provided by the firmware + * glue) halts the CPU; a platform/VPP HAL may supply a strong definition to + * blink a fault LED keyed off `reason`, sound an alarm, reboot, etc. + * + * On hosted (exception) builds this is never referenced — those code paths + * `throw` instead — so no definition is required there. + * + * @param reason what went wrong (for platform-specific signalling) + * @param context optional human-readable context; may be nullptr + */ +[[noreturn]] void iec_runtime_fault(IecFault reason, const char* context = nullptr) noexcept; + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_global.hpp b/03-plc/as-built/strucpp_runtime/include/iec_global.hpp new file mode 100644 index 0000000..68b5e16 --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_global.hpp @@ -0,0 +1,111 @@ +// ============================================================================ +// WARNING: KEEP THIS HEADER C++14-CLEAN (see iec_var.hpp for the full rationale). +// ---------------------------------------------------------------------------- +// This header is included by strucpp's emitted code, which on some Arduino cores +// (mbed-based: Nano RP2040/33 BLE, Opta, GIGA, Portenta, Edge) is compiled under +// `-std=gnu++14`. Do NOT use C++17/20 features unguarded (no `if constexpr`, no +// `std::trait_v`, no inline variables, no //). +// `auto` return-type deduction and generic/`decltype` trailing returns are C++14 +// and OK. The include + all locks are `#ifdef STRUCPP_THREADED`, so the +// baremetal (Arduino) build never pulls them in. Codegen emits with_lock() +// lambdas with CONCRETE parameter types (not `auto*`) to stay portable. +// ============================================================================ + +/** + * @file iec_global.hpp + * @brief Shared-global wrapper (value + per-global mutex) for VAR_GLOBAL / + * VAR_EXTERNAL. + * + * A CONFIGURATION `VAR_GLOBAL` is emitted as a `GlobalVar` MEMBER of the + * configuration — one object bundling the canonical storage (`value`, the real + * IEC type, e.g. `IEC_BOOL` or a function-block type) with that global's own + * mutex. A PROGRAM's `VAR_EXTERNAL` reference is emitted as a plain + * `GlobalVar*` pointing at that single canonical object. There is exactly one + * mutex per global (it lives on the canonical); locking "through the external + * pointer" locks the shared canonical. + * + * - NON-THREADED (baremetal / single task): the mutex and locks compile out; + * accesses go straight to `value`. Zero overhead. + * + * - STRUCPP_THREADED (openplc-runtime v4): every access (read / write / field + * / FB call) is serialized on the global's own mutex — fine-grained, so a + * lock is held for exactly one access and never nested with another global's + * lock (deadlock-free). Contract: per-access validity (no torn read/write); + * conflicts resolve last-writer-in-time. Data-agnostic — scalars, structs, + * arrays, and FB instances all work, and different-field writes from + * different tasks all survive because they mutate the one shared object. + * + * read() returns the real IEC value type, so generated bodies and constrained + * std-lib templates (NOT, ADD, …) deduce the operand correctly — the wrapper is + * never the deduced operand. Forcing is preserved via the canonical's + * get()/set(); located globals additionally honor the image forced-slot bitmap. + */ +#ifndef STRUCPP_IEC_GLOBAL_HPP +#define STRUCPP_IEC_GLOBAL_HPP + +#include "iec_var.hpp" + +#ifdef STRUCPP_THREADED +#include +#endif + +namespace strucpp { + +template +class GlobalVar { + public: + /** Canonical storage — the real IEC value/instance. Public so the located + * binding and debug exports can reach it (e.g. `g.value.raw_ptr()`), and so + * with_lock() can hand out a pointer to it. */ + V value; + + GlobalVar() = default; + /** Forward an initial value to the underlying IEC type. */ + template + explicit GlobalVar(T init) : value(init) {} + + // Non-copyable / non-movable: the mutex is; and a canonical global is a + // fixed configuration member that nothing copies. + GlobalVar(const GlobalVar&) = delete; + GlobalVar& operator=(const GlobalVar&) = delete; + + /** Scalar read: returns the real IEC value type (forcing-aware), + * deduction-friendly. Only instantiated for scalar globals (codegen uses + * with_lock() for structs / arrays / FB instances). */ + auto read() const { +#ifdef STRUCPP_THREADED + std::lock_guard lg(mtx_); +#endif + return value.get(); + } + + /** Scalar write (forcing-aware via set()). */ + template + void write(T v) { +#ifdef STRUCPP_THREADED + std::lock_guard lg(mtx_); +#endif + value.set(v); + } + + /** Locked direct access to the canonical, for field / array-element + * reads-writes and function-block calls: `f` receives `V*` (a pointer to + * `value`) and runs under the global's lock. Returns whatever `f` returns + * (a field's real type on reads → deduction-friendly). */ + template + auto with_lock(F&& f) -> decltype(f(static_cast(nullptr))) { +#ifdef STRUCPP_THREADED + std::lock_guard lg(mtx_); +#endif + return f(&value); + } + +#ifdef STRUCPP_THREADED + private: + mutable std::mutex mtx_; +#endif +}; + +} // namespace strucpp + +#endif // STRUCPP_IEC_GLOBAL_HPP diff --git a/03-plc/as-built/strucpp_runtime/include/iec_located.hpp b/03-plc/as-built/strucpp_runtime/include/iec_located.hpp new file mode 100644 index 0000000..2199d2c --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_located.hpp @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - Located Variables Support + * + * This header defines the types and structures needed for located variables + * (AT %IX0.0, %QX0.0, %MW100, etc.) that bind to runtime I/O image tables. + * + * Located variables provide the interface between IEC programs and physical + * I/O in PLC systems. The compiler generates a descriptor array that the + * runtime uses to bind variables to I/O addresses. + */ + +#pragma once + +#include +#include +#include "iec_fault.hpp" +#if STRUCPP_HAS_EXCEPTIONS +#include +#endif + +namespace strucpp { + +// ============================================================================= +// Located Variable Area Types +// ============================================================================= + +/** + * Memory area for located variables. + * Corresponds to the first letter after % in IEC addresses: + * - I = Input (read from physical inputs) + * - Q = Output (write to physical outputs) + * - M = Memory (internal markers/flags) + */ +enum class LocatedArea : uint8_t { + Input = 0, // %I - Input area + Output = 1, // %Q - Output area + Memory = 2 // %M - Memory/Marker area +}; + +// ============================================================================= +// Located Variable Size Types +// ============================================================================= + +/** + * Data size for located variables. + * Corresponds to the size specifier in IEC addresses: + * - X = Bit (1 bit) + * - B = Byte (8 bits) + * - W = Word (16 bits) + * - D = Double Word (32 bits) + * - L = Long Word (64 bits) + */ +enum class LocatedSize : uint8_t { + Bit = 0, // %?X - Single bit + Byte = 1, // %?B - 8 bits + Word = 2, // %?W - 16 bits + DWord = 3, // %?D - 32 bits + LWord = 4 // %?L - 64 bits +}; + +// ============================================================================= +// Located Variable Descriptor +// ============================================================================= + +/** + * Descriptor for a located variable. + * + * This structure is generated by the compiler for each located variable + * in the program. The runtime uses this descriptor array to: + * 1. Bind variables to I/O image tables at startup + * 2. Copy values between I/O images and variables during scan cycle + * 3. Support variable forcing for debugging + * + * Memory Layout (16 bytes, aligned): + * - area: 1 byte + * - size: 1 byte + * - byte_index: 2 bytes (major address component) + * - bit_index: 1 byte (minor address component, 0-7 for bits) + * - reserved: 3 bytes (padding for alignment) + * - pointer: 8 bytes (pointer to variable storage) + * + * Address Format: + * %. + * + * Examples: + * %IX0.5 -> area=Input, size=Bit, byte_index=0, bit_index=5 + * %QW10 -> area=Output, size=Word, byte_index=10, bit_index=0 + * %MD100 -> area=Memory, size=DWord, byte_index=100, bit_index=0 + */ +struct LocatedVar { + LocatedArea area; ///< Memory area (I, Q, or M) + LocatedSize size; ///< Data size (X, B, W, D, or L) + uint16_t byte_index; ///< Byte offset in the I/O image + uint8_t bit_index; ///< Bit offset within byte (0-7, only for X size) + uint8_t _reserved[3]; ///< Padding for alignment + void* pointer; ///< Pointer to the variable's raw storage + + /** + * Check if this descriptor represents a bit-addressed variable. + */ + constexpr bool is_bit() const noexcept { + return size == LocatedSize::Bit; + } + + /** + * Get the size in bytes for this variable. + * Returns 0 for bit-addressed variables (handled specially). + */ + constexpr size_t byte_size() const noexcept { + switch (size) { + case LocatedSize::Bit: return 0; // Special handling + case LocatedSize::Byte: return 1; + case LocatedSize::Word: return 2; + case LocatedSize::DWord: return 4; + case LocatedSize::LWord: return 8; + default: return 0; + } + } +}; + +// Verify expected layout (size varies by platform: 16 bytes on 64-bit, 8 on 32-bit, 6 on AVR) +#if INTPTR_MAX == INT64_MAX +static_assert(sizeof(LocatedVar) == 16, "LocatedVar should be 16 bytes on 64-bit"); +static_assert(alignof(LocatedVar) == 8, "LocatedVar should be 8-byte aligned on 64-bit"); +#endif + +// ============================================================================= +// Helper Functions for Address Parsing +// ============================================================================= + +/** + * Parse an area character to LocatedArea enum. + * @param c The area character ('I', 'Q', or 'M') + * @return The corresponding LocatedArea value + * @throws std::invalid_argument if character is invalid + */ +inline LocatedArea parse_area(char c) { + switch (c) { + case 'I': case 'i': return LocatedArea::Input; + case 'Q': case 'q': return LocatedArea::Output; + case 'M': case 'm': return LocatedArea::Memory; +#if STRUCPP_HAS_EXCEPTIONS + default: throw std::invalid_argument("Invalid area character"); +#else + default: iec_runtime_fault(IecFault::BadLocation, "Invalid area character"); +#endif + } +} + +/** + * Parse a size character to LocatedSize enum. + * @param c The size character ('X', 'B', 'W', 'D', or 'L') + * @return The corresponding LocatedSize value + * @throws std::invalid_argument if character is invalid + */ +inline LocatedSize parse_size(char c) { + switch (c) { + case 'X': case 'x': return LocatedSize::Bit; + case 'B': case 'b': return LocatedSize::Byte; + case 'W': case 'w': return LocatedSize::Word; + case 'D': case 'd': return LocatedSize::DWord; + case 'L': case 'l': return LocatedSize::LWord; +#if STRUCPP_HAS_EXCEPTIONS + default: throw std::invalid_argument("Invalid size character"); +#else + default: iec_runtime_fault(IecFault::BadLocation, "Invalid size character"); +#endif + } +} + +/** + * Get the area character for a LocatedArea enum. + * @param area The LocatedArea value + * @return The corresponding character ('I', 'Q', or 'M') + */ +constexpr char area_to_char(LocatedArea area) noexcept { + switch (area) { + case LocatedArea::Input: return 'I'; + case LocatedArea::Output: return 'Q'; + case LocatedArea::Memory: return 'M'; + default: return '?'; + } +} + +/** + * Get the size character for a LocatedSize enum. + * @param size The LocatedSize value + * @return The corresponding character ('X', 'B', 'W', 'D', or 'L') + */ +constexpr char size_to_char(LocatedSize size) noexcept { + switch (size) { + case LocatedSize::Bit: return 'X'; + case LocatedSize::Byte: return 'B'; + case LocatedSize::Word: return 'W'; + case LocatedSize::DWord: return 'D'; + case LocatedSize::LWord: return 'L'; + default: return '?'; + } +} + +// ============================================================================= +// Located Variable Table Marker +// ============================================================================= + +/** + * End marker for the located variable descriptor array. + * The compiler generates this as the last entry in the array. + * Runtime scans the array until it finds this marker (pointer == nullptr). + */ +constexpr LocatedVar LOCATED_VAR_END = { + LocatedArea::Input, // Doesn't matter + LocatedSize::Bit, // Doesn't matter + 0, // byte_index + 0, // bit_index + {0, 0, 0}, // reserved + nullptr // End marker +}; + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_memory.hpp b/03-plc/as-built/strucpp_runtime/include/iec_memory.hpp new file mode 100644 index 0000000..caea52b --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_memory.hpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC Dynamic Memory Allocation + * + * Implements CODESYS-compatible __NEW/__DELETE operators for dynamic memory. + * Uses malloc/free with placement new for allocation to match IEC semantics: + * - Returns nullptr (0) on failure (no exceptions) + * - __DELETE sets pointer to nullptr after freeing + * - Array allocation constructs each element + */ + +#pragma once + +#include +#include +#include +#include "iec_ptr.hpp" + +namespace strucpp { + +/** + * Allocate and construct a single object of type T. + * Returns nullptr on allocation failure. + */ +template +T* iec_new() { + void* mem = std::malloc(sizeof(T)); + if (!mem) return nullptr; + return new (mem) T(); // Placement new for construction +} + +/** + * Allocate and construct an array of count elements of type T. + * Returns nullptr on allocation failure. + */ +template +T* iec_new_array(std::size_t count) { + if (count == 0) return nullptr; + void* mem = std::malloc(sizeof(T) * count); + if (!mem) return nullptr; + T* arr = static_cast(mem); + for (std::size_t i = 0; i < count; ++i) { + new (&arr[i]) T(); // Construct each element + } + return arr; +} + +/** + * Deallocate a single object. Sets pointer to nullptr after freeing. + * Safe to call with nullptr (no-op). + */ +template +void iec_delete(T*& ptr) { + if (ptr) { + ptr->~T(); // Call destructor + std::free(ptr); + ptr = nullptr; + } +} + +/** + * Deallocate via IEC_Ptr. Extracts raw pointer, frees, and resets to nullptr. + */ +template +void iec_delete(IEC_Ptr& ptr) { + T* raw = ptr.get(); + if (raw) { + raw->~T(); + std::free(raw); + ptr = nullptr; + } +} + +/** + * Deallocate an array of objects. Sets pointer to nullptr after freeing. + * Note: For simplicity, this uses the same iec_delete since we don't + * track array sizes. The destructor for individual elements is trivial + * for IEC elementary types. + */ +template +void iec_delete_array(T*& ptr, std::size_t count) { + if (ptr) { + for (std::size_t i = 0; i < count; ++i) { + ptr[i].~T(); + } + std::free(ptr); + ptr = nullptr; + } +} + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_pointer.hpp b/03-plc/as-built/strucpp_runtime/include/iec_pointer.hpp new file mode 100644 index 0000000..78e50c2 --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_pointer.hpp @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC Reference Types (REF_TO, REFERENCE_TO) + * + * This header provides IEC 61131-3 reference types: + * - REF_TO: Explicit-dereference reference (nullable pointer) + * - REFERENCE_TO: Implicit-dereference reference (CODESYS compatibility) + * - REF() operator to get reference to a variable + * - NULL value for uninitialized REF_TO + * - NullReferenceException for null dereference errors + * + * Design decisions: + * - References are NOT forceable (unlike IECVar) + * - Writes through references respect target's forcing state + * - Null dereference throws NullReferenceException + * - REFERENCE_TO cannot be NULL (must always be bound) + * + * Example ST code (REF_TO): + * VAR + * V1, V2 : INT; + * rV : REF_TO INT; + * END_VAR + * + * rV := REF(V2); // Get reference to V2 + * rV^ := 12; // Assign 12 to V2 via reference + * V1 := rV^; // Read V2 via reference + * + * IF rV <> NULL THEN // Null check + * rV^ := 42; + * END_IF; + * + * Example ST code (REFERENCE_TO): + * VAR + * target : INT := 10; + * ref : REFERENCE_TO INT := target; // Must be initialized + * END_VAR + * + * ref := 42; // Implicit write to target + * x := ref; // Implicit read from target + * ref REF= other; // Rebind to different variable + */ + +#pragma once + +#include +#include "iec_fault.hpp" +#if STRUCPP_HAS_EXCEPTIONS +#include +#include +#endif +#include "iec_var.hpp" + +namespace strucpp { + +// ============================================================================= +// Null Reference Exception +// ============================================================================= + +#if STRUCPP_HAS_EXCEPTIONS +/** + * Exception thrown when dereferencing a NULL reference. + * The runtime catches this and stops execution of the affected POU. + */ +class NullReferenceException : public std::runtime_error { +public: + NullReferenceException() + : std::runtime_error("Null reference dereference") {} + + explicit NullReferenceException(const char* context) + : std::runtime_error(std::string("Null reference dereference in ") + context) {} + + explicit NullReferenceException(const std::string& context) + : std::runtime_error("Null reference dereference in " + context) {} +}; +#endif + +// ============================================================================= +// IEC NULL Constant +// ============================================================================= + +/** + * IEC NULL pointer constant. + * Used to indicate no address / uninitialized pointer. + */ +constexpr std::nullptr_t IEC_NULL = nullptr; + +// ============================================================================= +// REF_TO Type (Explicit Dereference) +// ============================================================================= + +/** + * REF_TO pointer type for IEC 61131-3. + * Wraps a pointer to an IECVar with null checking. + * + * Note: References themselves are NOT forceable. + * Writes through references respect the target's forcing state. + * + * @tparam T The underlying value type (e.g., INT_t, REAL_t) + */ +template +class IEC_REF_TO { +public: + using value_type = T; + using pointer_type = IECVar*; + +private: + pointer_type ptr_; + +public: + /** + * Default constructor - initializes to NULL + */ + IEC_REF_TO() noexcept : ptr_(nullptr) {} + + /** + * Constructor from pointer to IECVar + */ + explicit IEC_REF_TO(pointer_type p) noexcept : ptr_(p) {} + + /** + * Constructor from nullptr (NULL) + */ + IEC_REF_TO(std::nullptr_t) noexcept : ptr_(nullptr) {} + + /** + * Constructor from an integer address (e.g. a __XWORD temp produced by + * REF_LINK()). The address is reinterpreted as the wrapped IECVar*; + * it must originate from REF_LINK()/ADR() of a matching variable. + */ + explicit IEC_REF_TO(std::uintptr_t addr) noexcept + : ptr_(reinterpret_cast(addr)) {} + + // Copy and move constructors/assignment - default is fine + IEC_REF_TO(const IEC_REF_TO&) = default; + IEC_REF_TO(IEC_REF_TO&&) = default; + IEC_REF_TO& operator=(const IEC_REF_TO&) = default; + IEC_REF_TO& operator=(IEC_REF_TO&&) = default; + + /** + * Get the pointer (for internal use) + */ + pointer_type get() const noexcept { return ptr_; } + + /** + * Set the pointer + */ + void set(pointer_type p) noexcept { ptr_ = p; } + + /** + * Check if pointer is NULL + */ + bool is_null() const noexcept { return ptr_ == nullptr; } + + /** + * Dereference - throws NullReferenceException if NULL + * Used by generated code for ^ operator and DREF() function + */ + IECVar& deref() { + if (ptr_ == nullptr) { +#if STRUCPP_HAS_EXCEPTIONS + throw NullReferenceException(); +#else + iec_runtime_fault(IecFault::NullReference); +#endif + } + return *ptr_; + } + + const IECVar& deref() const { + if (ptr_ == nullptr) { +#if STRUCPP_HAS_EXCEPTIONS + throw NullReferenceException(); +#else + iec_runtime_fault(IecFault::NullReference); +#endif + } + return *ptr_; + } + + /** + * Dereference with context for better error messages + */ + IECVar& deref(const char* context) { + if (ptr_ == nullptr) { +#if STRUCPP_HAS_EXCEPTIONS + throw NullReferenceException(context); +#else + iec_runtime_fault(IecFault::NullReference, context); +#endif + } + return *ptr_; + } + + const IECVar& deref(const char* context) const { + if (ptr_ == nullptr) { +#if STRUCPP_HAS_EXCEPTIONS + throw NullReferenceException(context); +#else + iec_runtime_fault(IecFault::NullReference, context); +#endif + } + return *ptr_; + } + + // ========================================================================= + // Operators + // ========================================================================= + + /** + * Assignment from pointer + */ + IEC_REF_TO& operator=(pointer_type p) noexcept { + set(p); + return *this; + } + + /** + * Assignment from nullptr (NULL) + */ + IEC_REF_TO& operator=(std::nullptr_t) noexcept { + set(nullptr); + return *this; + } + + /** + * Assignment from an integer address (e.g. `ref := _TMP` where the temp is + * a __XWORD produced by REF_LINK()). The address is reinterpreted as the + * wrapped IECVar*. + */ + IEC_REF_TO& operator=(std::uintptr_t addr) noexcept { + set(reinterpret_cast(addr)); + return *this; + } + + /** + * Dereference operator (*) - same as deref() + */ + IECVar& operator*() { + return deref(); + } + + const IECVar& operator*() const { + return deref(); + } + + /** + * Arrow operator for accessing IECVar methods + * Also throws NullReferenceException if NULL + */ + pointer_type operator->() { + if (ptr_ == nullptr) { +#if STRUCPP_HAS_EXCEPTIONS + throw NullReferenceException(); +#else + iec_runtime_fault(IecFault::NullReference); +#endif + } + return ptr_; + } + + const pointer_type operator->() const { + if (ptr_ == nullptr) { +#if STRUCPP_HAS_EXCEPTIONS + throw NullReferenceException(); +#else + iec_runtime_fault(IecFault::NullReference); +#endif + } + return ptr_; + } + + /** + * Comparison with nullptr (NULL) + */ + bool operator==(std::nullptr_t) const noexcept { + return is_null(); + } + + bool operator!=(std::nullptr_t) const noexcept { + return !is_null(); + } + + /** + * Comparison with another pointer + */ + bool operator==(const IEC_REF_TO& other) const noexcept { + return ptr_ == other.ptr_; + } + + bool operator!=(const IEC_REF_TO& other) const noexcept { + return ptr_ != other.ptr_; + } + + /** + * Implicit conversion to bool (for null checks) + * Returns true if pointer is not NULL + */ + explicit operator bool() const noexcept { + return !is_null(); + } +}; + +// ============================================================================= +// REFERENCE_TO Type (Implicit Dereference - CODESYS Compatibility) +// ============================================================================= + +/** + * REFERENCE_TO type with implicit dereferencing (CODESYS compatibility). + * Unlike REF_TO, this type automatically dereferences on value access. + * + * Conceptually always bound; default-constructs unbound (nullptr) only so + * the variable can be declared before being bound. Must be bound (via the + * REF= operator / bind()) before any read or write — accessing it unbound + * is undefined behaviour, mirroring CODESYS. + * + * @tparam T The underlying value type (e.g., INT_t, REAL_t) + */ +template +class IEC_REFERENCE_TO { +public: + using value_type = T; + using pointer_type = IECVar*; + +private: + pointer_type ptr_; + +public: + /** + * Default constructor - unbound (ptr_ == nullptr). + * + * IEC 61131-3 REFERENCE TO is conceptually "always bound", but a + * variable of this type must still be *declarable* before it is bound + * (e.g. an uninitialized FB member or local that is bound later via the + * REF= operator inside the POU body). The generated code default- + * constructs such members, so a usable default ctor is required. + * + * As with CODESYS, reading or writing an unbound REFERENCE TO is + * undefined behaviour — generated code is expected to bind it (REF=) + * before first access. (Use REF_TO if you need explicit NULL checks.) + */ + IEC_REFERENCE_TO() noexcept : ptr_(nullptr) {} + + /** + * Constructor - initialize bound to a variable (REFERENCE TO X := target) + */ + explicit IEC_REFERENCE_TO(IECVar& var) noexcept : ptr_(&var) {} + + // Copy/move - default is fine + IEC_REFERENCE_TO(const IEC_REFERENCE_TO&) = default; + IEC_REFERENCE_TO(IEC_REFERENCE_TO&&) = default; + IEC_REFERENCE_TO& operator=(const IEC_REFERENCE_TO&) = default; + IEC_REFERENCE_TO& operator=(IEC_REFERENCE_TO&&) = default; + + /** + * Bind to a new variable (REF= operator) + */ + void bind(IECVar& var) noexcept { ptr_ = &var; } + + /** + * Implicit value access (get) - reads from target + */ + T get() const noexcept { return ptr_->get(); } + + /** + * Implicit value assignment (set) - writes to target + * Respects target's forcing state + */ + void set(const T& value) noexcept { ptr_->set(value); } + + /** + * Get underlying IECVar reference + */ + IECVar& target() noexcept { return *ptr_; } + const IECVar& target() const noexcept { return *ptr_; } + + // ========================================================================= + // Operators + // ========================================================================= + + /** + * Assignment operator writes through to target + */ + IEC_REFERENCE_TO& operator=(const T& value) noexcept { + set(value); + return *this; + } + + /** + * Implicit conversion to value type for reading + */ + operator T() const noexcept { return get(); } + + // Arithmetic compound assignment operators (write through to target) + IEC_REFERENCE_TO& operator+=(const T& v) noexcept { + set(get() + v); + return *this; + } + + IEC_REFERENCE_TO& operator-=(const T& v) noexcept { + set(get() - v); + return *this; + } + + IEC_REFERENCE_TO& operator*=(const T& v) noexcept { + set(get() * v); + return *this; + } + + IEC_REFERENCE_TO& operator/=(const T& v) noexcept { + set(get() / v); + return *this; + } +}; + +// ============================================================================= +// REF() Function - Get Reference to Variable +// ============================================================================= + +/** + * REF() operator - Get reference to an IECVar + * Returns a REF_TO that can be assigned to a reference variable. + * + * Usage: + * IEC_INT myVar; + * REF_TO myRef = REF(myVar); + */ +template +inline IEC_REF_TO REF(IECVar& var) noexcept { + return IEC_REF_TO(&var); +} + +/** + * REF() for references (reference to reference) + * Allows creating a reference to a REF_TO variable. + * + * Usage: + * REF_TO ref1; + * REF_TO> ref2 = REF(ref1); + */ +template +inline IEC_REF_TO> REF(IEC_REF_TO& ref) noexcept { + return IEC_REF_TO>(&ref); +} + +// Note: REF() for array elements and struct fields works automatically +// because they return IECVar& from operator[] and member access. + +// ============================================================================= +// Type Aliases +// ============================================================================= + +/** + * Convenience alias for REF_TO types + * Usage: REF_TO myRef; + */ +template +using REF_TO = IEC_REF_TO; + +/** + * Convenience alias for REFERENCE_TO types + * Usage: REFERENCE_TO myRef{target}; + */ +template +using REFERENCE_TO = IEC_REFERENCE_TO; + +/* + * Example usage (generated code for REF_TO): + * + * ST Source: + * VAR + * V1, V2 : INT; + * rV : REF_TO INT; + * END_VAR + * + * rV := REF(V2); + * rV^ := 12; + * V1 := rV^; + * + * IF rV <> NULL THEN + * rV^ := 42; + * END_IF; + * + * Generated C++: + * IEC_INT V1, V2; + * REF_TO rV; + * + * rV = REF(V2); + * rV.deref().set(12); + * V1 = rV.deref().get(); + * + * if (rV != IEC_NULL) { + * rV.deref().set(42); + * } + */ + +/* + * Example usage (generated code for REFERENCE_TO): + * + * ST Source: + * VAR + * target : INT := 10; + * ref : REFERENCE_TO INT := target; + * END_VAR + * + * ref := 42; // Implicit write + * x := ref; // Implicit read + * ref REF= other; // Rebind + * + * Generated C++: + * IEC_INT target{10}; + * REFERENCE_TO ref{target}; + * + * ref.set(42); + * x = ref.get(); + * ref.bind(other); + */ + +/* + * Example usage (reference to array element): + * + * ST Source: + * VAR + * arr : ARRAY[1..10] OF INT; + * elem_ref : REF_TO INT; + * END_VAR + * + * elem_ref := REF(arr[5]); + * elem_ref^ := 100; + * + * Generated C++: + * Array1D arr; + * REF_TO elem_ref; + * + * elem_ref = REF(arr[5]); // arr[5] returns IECVar& + * elem_ref.deref().set(100); + */ + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_ptr.hpp b/03-plc/as-built/strucpp_runtime/include/iec_ptr.hpp new file mode 100644 index 0000000..8a2945f --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_ptr.hpp @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC Pointer Type + * + * Provides IEC_Ptr, a smart pointer wrapper that handles the type-punning + * semantics of IEC 61131-3 POINTER TO. In CODESYS, POINTER TO BYTE is commonly + * used as a universal memory pointer (like void*) and ADR() can return any + * address that is freely assigned to any pointer type. + * + * IEC_Ptr stores a void* internally and provides: + * - Construction/assignment from any pointer type (cross-type OK) + * - Construction/assignment from other IEC_Ptr (cross-type OK) + * - Dereference as T& / T* + * - Pointer arithmetic in units of sizeof(T) + * - Conversion to integral types (for CODESYS-style address math) + * - Comparison operators (same-type and cross-type) + */ + +#pragma once + +#include +#include +#include + +#include "iec_fault.hpp" // STRUCPP_HAS_EXCEPTIONS, IecFault, iec_runtime_fault +#if STRUCPP_HAS_EXCEPTIONS +#include +#endif + +namespace strucpp { + +// Forward declare IECVar for arithmetic overloads +template class IECVar; + +template +class IEC_Ptr { + void* ptr_; +public: + using element_type = T; + + // Constructors + IEC_Ptr() noexcept : ptr_(nullptr) {} + IEC_Ptr(std::nullptr_t) noexcept : ptr_(nullptr) {} + + // Construct from any raw pointer + template + IEC_Ptr(U* p) noexcept : ptr_(const_cast(static_cast(p))) {} + + // Construct from another IEC_Ptr (cross-type) + template + IEC_Ptr(IEC_Ptr other) noexcept : ptr_(other.get_void()) {} + + // Construct from an integer address (e.g. __XWORD/ULINT from ADR()). + // `IEC_XWORD` implicitly converts to its underlying integer, which + // converts to uintptr_t here — the typed pointer round-trips the address. + explicit IEC_Ptr(std::uintptr_t addr) noexcept + : ptr_(reinterpret_cast(addr)) {} + + // Assignment from any raw pointer + template + IEC_Ptr& operator=(U* p) noexcept { + ptr_ = const_cast(static_cast(p)); + return *this; + } + + // Assignment from another IEC_Ptr (cross-type) + template + IEC_Ptr& operator=(IEC_Ptr other) noexcept { + ptr_ = other.get_void(); + return *this; + } + + IEC_Ptr& operator=(std::nullptr_t) noexcept { + ptr_ = nullptr; + return *this; + } + + // Assignment from an integer address (e.g. `ptr := _TMP` where the temp is + // __XWORD/ULINT from ADR()). The address is reinterpreted to the typed + // pointer; the source integer carries a pointer-width value. + IEC_Ptr& operator=(std::uintptr_t addr) noexcept { + ptr_ = reinterpret_cast(addr); + return *this; + } + + // Null-fault helper: a dereference of an unassigned (NULL) POINTER TO + // raises a clean fault instead of an undefined access — throw on exception + // targets (the runtime catches it and stops just the faulting task), or + // iec_runtime_fault(IecFault::NullReference) on -fno-exceptions MCU targets + // (where there is no MMU to trap a null deref, so the unchecked access would + // silently read/write address 0). Mirrors REF_TO's NullReferenceException. + void __fault_if_null() const { + if (!ptr_) { +#if STRUCPP_HAS_EXCEPTIONS + throw std::runtime_error("Null pointer dereference"); +#else + iec_runtime_fault(IecFault::NullReference); +#endif + } + } + + // Dereference (null-checked) + T& operator*() const { __fault_if_null(); return *static_cast(ptr_); } + T* operator->() const { __fault_if_null(); return static_cast(ptr_); } + + // Subscript (pointer[n]) — null-checked base (the offset itself is + // unbounded raw pointer arithmetic, as in C). + T& operator[](std::ptrdiff_t n) const { + __fault_if_null(); + return *(static_cast(ptr_) + n); + } + + // Bounds-checked accessor used by codegen's subscript path. A POINTER TO has + // no bounds metadata, so this matches operator[]: null-checked base with + // unbounded raw pointer arithmetic (as in C). + T& at(std::ptrdiff_t n) const { + __fault_if_null(); + return *(static_cast(ptr_) + n); + } + + // Raw access + T* get() const noexcept { return static_cast(ptr_); } + void* get_void() const noexcept { return ptr_; } + + // Pointer arithmetic (in units of sizeof(T)) + // Use templates to directly match any arithmetic type, avoiding + // ambiguity with the integral conversion operator. + template, int> = 0> + IEC_Ptr operator+(N n) const noexcept { + return IEC_Ptr(static_cast(ptr_) + static_cast(n)); + } + + template, int> = 0> + IEC_Ptr operator-(N n) const noexcept { + return IEC_Ptr(static_cast(ptr_) - static_cast(n)); + } + + // Arithmetic with IECVar-wrapped types (unwrap the value) + template + IEC_Ptr operator+(const IECVar& n) const noexcept { + return IEC_Ptr(static_cast(ptr_) + static_cast(n.get())); + } + + template + IEC_Ptr operator-(const IECVar& n) const noexcept { + return IEC_Ptr(static_cast(ptr_) - static_cast(n.get())); + } + + template, int> = 0> + IEC_Ptr& operator+=(N n) noexcept { + ptr_ = static_cast(ptr_) + static_cast(n); + return *this; + } + + template, int> = 0> + IEC_Ptr& operator-=(N n) noexcept { + ptr_ = static_cast(ptr_) - static_cast(n); + return *this; + } + + // Pre/post increment/decrement + IEC_Ptr& operator++() noexcept { + ptr_ = static_cast(ptr_) + 1; + return *this; + } + + IEC_Ptr operator++(int) noexcept { + IEC_Ptr tmp = *this; + ptr_ = static_cast(ptr_) + 1; + return tmp; + } + + IEC_Ptr& operator--() noexcept { + ptr_ = static_cast(ptr_) - 1; + return *this; + } + + IEC_Ptr operator--(int) noexcept { + IEC_Ptr tmp = *this; + ptr_ = static_cast(ptr_) - 1; + return tmp; + } + + // Conversion to integral types (for CODESYS pointer-to-DWORD patterns) + // This enables: DWORD_VAR := PT; (store address as integer) + // Must NOT be implicit to avoid ambiguity with operator+ etc. + // Use to_addr() for explicit conversion instead. + uintptr_t to_addr() const noexcept { + return reinterpret_cast(ptr_); + } + + // Conversion to bool (for null checks) + explicit operator bool() const noexcept { return ptr_ != nullptr; } + + // Same-type comparison + bool operator==(IEC_Ptr other) const noexcept { return ptr_ == other.ptr_; } + bool operator!=(IEC_Ptr other) const noexcept { return ptr_ != other.ptr_; } + bool operator<(IEC_Ptr other) const noexcept { return ptr_ < other.ptr_; } + bool operator>(IEC_Ptr other) const noexcept { return ptr_ > other.ptr_; } + bool operator<=(IEC_Ptr other) const noexcept { return ptr_ <= other.ptr_; } + bool operator>=(IEC_Ptr other) const noexcept { return ptr_ >= other.ptr_; } + + // Cross-type IEC_Ptr comparison + template + bool operator==(IEC_Ptr other) const noexcept { return ptr_ == other.get_void(); } + template + bool operator!=(IEC_Ptr other) const noexcept { return ptr_ != other.get_void(); } + template + bool operator<(IEC_Ptr other) const noexcept { return ptr_ < other.get_void(); } + template + bool operator>(IEC_Ptr other) const noexcept { return ptr_ > other.get_void(); } + template + bool operator<=(IEC_Ptr other) const noexcept { return ptr_ <= other.get_void(); } + template + bool operator>=(IEC_Ptr other) const noexcept { return ptr_ >= other.get_void(); } + + // Comparison with nullptr + bool operator==(std::nullptr_t) const noexcept { return ptr_ == nullptr; } + bool operator!=(std::nullptr_t) const noexcept { return ptr_ != nullptr; } + + // Comparison with IECVar (for CODESYS: PT < DWORD_END) + template, int> = 0> + bool operator<(IECVar other) const noexcept { + return reinterpret_cast(ptr_) < static_cast(other.get()); + } + template, int> = 0> + bool operator>(IECVar other) const noexcept { + return reinterpret_cast(ptr_) > static_cast(other.get()); + } + template, int> = 0> + bool operator<=(IECVar other) const noexcept { + return reinterpret_cast(ptr_) <= static_cast(other.get()); + } + template, int> = 0> + bool operator>=(IECVar other) const noexcept { + return reinterpret_cast(ptr_) >= static_cast(other.get()); + } + template, int> = 0> + bool operator==(IECVar other) const noexcept { + return reinterpret_cast(ptr_) == static_cast(other.get()); + } + template, int> = 0> + bool operator!=(IECVar other) const noexcept { + return reinterpret_cast(ptr_) != static_cast(other.get()); + } +}; + +// Reverse comparison: nullptr == IEC_Ptr +template +bool operator==(std::nullptr_t, IEC_Ptr p) noexcept { return p == nullptr; } +template +bool operator!=(std::nullptr_t, IEC_Ptr p) noexcept { return p != nullptr; } + +// Arithmetic: n + ptr (reverse order) +template +IEC_Ptr operator+(std::ptrdiff_t n, IEC_Ptr p) noexcept { return p + n; } + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_retain.hpp b/03-plc/as-built/strucpp_runtime/include/iec_retain.hpp new file mode 100644 index 0000000..c8c636d --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_retain.hpp @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - Retain Variable Support + * + * This header defines the types and structures needed for retain variables + * that preserve their values across power cycles. + * + * The compiler generates a descriptor array for each program/function block + * containing retain variables. The runtime uses this array to save/restore + * values to non-volatile storage. + */ + +#pragma once + +#include +#include + +namespace strucpp { + +// ============================================================================= +// Retain Variable Descriptor +// ============================================================================= + +/** + * Metadata for a retain variable. + * Used by runtime to save/restore values across power cycles. + * + * The descriptor uses offset instead of pointer to allow: + * - constexpr initialization (compile-time constant) + * - Correct behavior with multiple instances of the same class + * - Simple serialization (offset + size = memory region) + */ +struct RetainVarInfo { + const char* name; ///< Variable name (for diagnostics/debugging) + size_t offset; ///< Offset from object base (use with offsetof) + size_t size; ///< Size in bytes for serialization +}; + +// ============================================================================= +// Retain Storage Interface (for Phase 6 implementation) +// ============================================================================= + +/** + * Abstract interface for retain variable persistence. + * Implemented by the runtime (Phase 6) to provide actual storage. + * + * Example implementations: + * - File-based storage for development/testing + * - NVRAM for embedded systems + * - Battery-backed RAM for industrial PLCs + */ +class RetainStorage { +public: + virtual ~RetainStorage() = default; + + /** + * Save retain variables to persistent storage. + * + * @param instance Pointer to the program/FB instance + * @param vars Array of retain variable descriptors + * @param count Number of retain variables + */ + virtual void save(const void* instance, + const RetainVarInfo* vars, + size_t count) = 0; + + /** + * Restore retain variables from persistent storage. + * + * @param instance Pointer to the program/FB instance + * @param vars Array of retain variable descriptors + * @param count Number of retain variables + * @return true if restore was successful, false if no saved data + */ + virtual bool restore(void* instance, + const RetainVarInfo* vars, + size_t count) = 0; +}; + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_std_lib.hpp b/03-plc/as-built/strucpp_runtime/include/iec_std_lib.hpp new file mode 100644 index 0000000..18be8a6 --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_std_lib.hpp @@ -0,0 +1,1501 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC Standard Library + * + * This header provides the standard IEC 61131-3 functions and utilities. + * Functions are implemented as C++ templates with IEC type constraints + * for type safety and compliance with IEC 61131-3 type system. + * + * Type constraints follow IEC 61131-3 ANY type hierarchy: + * - ANY_NUM: Numeric types (integers + reals) + * - ANY_INT: Integer types (signed + unsigned) + * - ANY_REAL: Floating point types (REAL, LREAL) + * - ANY_BIT: Bit string types (BOOL, BYTE, WORD, DWORD, LWORD) + * - ANY_ELEMENTARY: All elementary types + * - ANY_MAGNITUDE: Numeric + time types + */ + +#pragma once + +#include "iec_var.hpp" +#include "iec_traits.hpp" +#include "iec_retain.hpp" +#include "iec_ptr.hpp" +#include "iec_string.hpp" +#include "iec_wstring.hpp" +// IEC 61131-3 temporal types — pulled in here so the standard +// library entry point exposes every standard function (`ADD_TIME`, +// `ADD_DATE`, `ADD_DT`, `ADD_TOD`, `CONCAT_DATE_TOD`, etc.) without +// the caller having to chase the right per-type header. `codegen.ts` +// emits a single `#include "iec_std_lib.hpp"` into every +// generated.hpp, so the only way a generated POU using TIME or +// calendar arithmetic can resolve those symbols is through this +// transitive chain. Header guards make each include idempotent. +#include "iec_time.hpp" +#include "iec_date.hpp" +#include "iec_dt.hpp" +#include "iec_tod.hpp" +#include +#include +#include +#include +#include +#include +#include + +// Undefine AVR `` macros that collide with common IEC +// identifiers. `` above pulls in `` → ``, +// and AVR-libc's `time.h` defines a handful of all-caps duration / +// epoch constants that would silently rewrite a user's similarly- +// named variable into a numeric literal before the C++ parser sees +// it. The collisions surface as cryptic `expected unqualified-id +// before numeric constant` errors on lines like +// `IEC_TIME ONE_HOUR;`. +// +// Same pattern as the `#undef OVERFLOW` codegen emits into every +// `generated.hpp` to neutralise ``'s SVID error code. +// Header guards make each include idempotent, so doing the undef +// right after the time-family includes is fine: any later include +// of `` directly would re-define the macros, but no +// strucpp-side header does that. +#undef ONE_HOUR +#undef ONE_DEGREE +#undef ONE_DAY +#undef UNIX_OFFSET +#undef NTP_OFFSET + +namespace strucpp { + +// ============================================================================= +// Base Classes for Runtime +// ============================================================================= + +// Forward declaration for retain support +struct RetainVarInfo; + +/** + * Base class for all program instances. + * Provides the interface for the runtime scheduler. + */ +struct ProgramBase { + virtual ~ProgramBase() = default; + + /** Execute one cycle of the program */ + virtual void run() = 0; + + /** + * Get the array of retain variable descriptors. + * Override in generated code if the program has RETAIN variables. + * @return Pointer to static array, or nullptr if no retain variables + */ + virtual const RetainVarInfo* getRetainVars() const { return nullptr; } + + /** + * Get the number of retain variables. + * Override in generated code if the program has RETAIN variables. + * @return Count of retain variables + */ + virtual size_t getRetainCount() const { return 0; } + + // ------------------------------------------------------------------------- + // Threaded-runtime hooks (appended at the end of the vtable so run() stays + // at slot 1 -- a runtime that predates these still works, it just never + // calls them). No-ops by default; generated code overrides them ONLY when + // compiled with STRUCPP_THREADED. The OpenPLC threaded runtime calls + // sync_in() before run() and sync_out() after, around a per-task private + // working copy of this program's VAR_EXTERNAL globals (so task bodies run + // without holding a global lock). located_range() reports this program's + // contiguous slice of the global locatedVars[] table so the runtime can + // copy its located I/O in/out scoped to the owning task. + // ------------------------------------------------------------------------- + + /** Copy this program's VAR_EXTERNAL globals from canonical storage into + * its private working copies. Called by the runtime before run(). */ + virtual void sync_in() {} + + /** Commit this program's changed VAR_EXTERNAL globals from its working + * copies back to canonical storage. Called by the runtime after run(). */ + virtual void sync_out() {} + + /** Report this program's slice [offset, offset+count) of the project-wide + * locatedVars[] table. count == 0 means the program has no located I/O. */ + virtual void located_range(uint32_t* offset, uint32_t* count) const { + *offset = 0; + *count = 0; + } +}; + +/** + * Task instance descriptor. + * Describes a task's scheduling properties and associated program instances. + */ +struct TaskInstance { + const char* name; ///< Task name + int64_t interval_ns; ///< Execution interval in nanoseconds (0 = event-driven) + int32_t priority; ///< Task priority (higher = more important) + ProgramBase** programs; ///< Array of program instances for this task + size_t program_count; ///< Number of programs in this task + + TaskInstance() noexcept + : name(nullptr), interval_ns(0), priority(0), programs(nullptr), program_count(0) {} + + TaskInstance(const char* n, int64_t interval, int32_t prio, + ProgramBase** progs, size_t count) noexcept + : name(n), interval_ns(interval), priority(prio), programs(progs), program_count(count) {} +}; + +/** + * Resource instance descriptor. + * Describes a resource (processor) and its associated tasks. + */ +struct ResourceInstance { + const char* name; ///< Resource name + const char* processor; ///< Processor type (from ON clause) + TaskInstance* tasks; ///< Array of tasks in this resource + size_t task_count; ///< Number of tasks + + ResourceInstance() noexcept + : name(nullptr), processor(nullptr), tasks(nullptr), task_count(0) {} + + ResourceInstance(const char* n, const char* proc, + TaskInstance* t, size_t count) noexcept + : name(n), processor(proc), tasks(t), task_count(count) {} +}; + +/** + * Base class for configuration instances. + * Provides the interface for the runtime to access project structure. + */ +struct ConfigurationInstance { + virtual ~ConfigurationInstance() = default; + + /** Get configuration name */ + virtual const char* get_name() const = 0; + + /** Get array of resources */ + virtual ResourceInstance* get_resources() = 0; + + /** Get number of resources */ + virtual size_t get_resource_count() const = 0; +}; + +// ============================================================================= +// Numeric Functions (ANY_NUM -> ANY_NUM, or ANY_REAL -> ANY_REAL) +// ============================================================================= + +/** + * ABS - Absolute value + * Input: ANY_NUM, Output: ANY_NUM (same type) + */ +template = 0> +inline T ABS(T value) noexcept { + auto v = iec_unwrap(value); + if constexpr (std::is_floating_point_v) { + return T(std::abs(v)); + } else if constexpr (std::is_signed_v) { + return T(v < 0 ? -v : v); + } else { + return value; + } +} + +/** + * SQRT - Square root + * Input: ANY_REAL, Output: ANY_REAL (same type) + */ +template = 0> +inline T SQRT(T value) noexcept { + return T(std::sqrt(static_cast(iec_unwrap(value)))); +} + +/** + * LN - Natural logarithm + * Input: ANY_REAL, Output: ANY_REAL (same type) + */ +template = 0> +inline T LN(T value) noexcept { + return T(std::log(static_cast(iec_unwrap(value)))); +} + +/** + * LOG - Base-10 logarithm + * Input: ANY_REAL, Output: ANY_REAL (same type) + */ +template = 0> +inline T LOG(T value) noexcept { + return T(std::log10(static_cast(iec_unwrap(value)))); +} + +/** + * EXP - Exponential (e^x) + * Input: ANY_REAL, Output: ANY_REAL (same type) + */ +template = 0> +inline T EXP(T value) noexcept { + return T(std::exp(static_cast(iec_unwrap(value)))); +} + +/** + * EXPT - Exponentiation (base^exponent) + * Input: ANY_REAL, Output: ANY_REAL (same type) + */ +template = 0> +inline T EXPT(T base, T exponent) noexcept { + return T(std::pow(static_cast(iec_unwrap(base)), static_cast(iec_unwrap(exponent)))); +} + +// Mixed-type EXPT: allows e.g. EXPT(INT, REAL) → returns LREAL +template, std::decay_t>>> +inline IEC_LREAL EXPT(T1 base, T2 exponent) noexcept { + return IEC_LREAL(std::pow(static_cast(iec_unwrap(base)), static_cast(iec_unwrap(exponent)))); +} + +// Same-type EXPT for non-REAL types (CODESYS extension: EXPT(INT, INT)) +template || is_any_bit_v) && !is_any_real_v, int> = 0> +inline IEC_LREAL EXPT(T base, T exponent) noexcept { + return IEC_LREAL(std::pow(static_cast(iec_unwrap(base)), static_cast(iec_unwrap(exponent)))); +} + +// ============================================================================= +// Trigonometric Functions (ANY_REAL -> ANY_REAL) +// ============================================================================= + +/** + * SIN - Sine + * Input: ANY_REAL (radians), Output: ANY_REAL + */ +template = 0> +inline T SIN(T value) noexcept { + return T(std::sin(static_cast(iec_unwrap(value)))); +} + +/** + * COS - Cosine + * Input: ANY_REAL (radians), Output: ANY_REAL + */ +template = 0> +inline T COS(T value) noexcept { + return T(std::cos(static_cast(iec_unwrap(value)))); +} + +/** + * TAN - Tangent + * Input: ANY_REAL (radians), Output: ANY_REAL + */ +template = 0> +inline T TAN(T value) noexcept { + return T(std::tan(static_cast(iec_unwrap(value)))); +} + +/** + * ASIN - Arc sine + * Input: ANY_REAL, Output: ANY_REAL (radians) + */ +template = 0> +inline T ASIN(T value) noexcept { + return T(std::asin(static_cast(iec_unwrap(value)))); +} + +/** + * ACOS - Arc cosine + * Input: ANY_REAL, Output: ANY_REAL (radians) + */ +template = 0> +inline T ACOS(T value) noexcept { + return T(std::acos(static_cast(iec_unwrap(value)))); +} + +/** + * ATAN - Arc tangent + * Input: ANY_REAL, Output: ANY_REAL (radians) + */ +template = 0> +inline T ATAN(T value) noexcept { + return T(std::atan(static_cast(iec_unwrap(value)))); +} + +/** + * ATAN2 - Arc tangent of y/x (two-argument form) + * Input: ANY_REAL, Output: ANY_REAL (radians between -PI and PI) + */ +template = 0> +inline T ATAN2(T y, T x) noexcept { + return T(std::atan2(static_cast(iec_unwrap(y)), static_cast(iec_unwrap(x)))); +} + +/** + * TRUNC - Truncate toward zero + * Input: ANY_REAL, Output: ANY_REAL (integer part) + */ +template = 0> +inline T TRUNC(T value) noexcept { + return T(std::trunc(static_cast(iec_unwrap(value)))); +} + +/** + * ROUND - Round to nearest integer + * Input: ANY_REAL, Output: ANY_REAL + * Rounds half away from zero (banker's rounding not used) + */ +template = 0> +inline T ROUND(T value) noexcept { + return T(std::round(static_cast(iec_unwrap(value)))); +} + +// ============================================================================= +// Selection Functions (ANY_ELEMENTARY for comparisons) +// ============================================================================= + +/** + * SEL - Binary selection + * Input: BOOL selector, ANY values, Output: ANY (same type as inputs) + * Returns in1 if g is FALSE, in0 if g is TRUE + */ +template +inline T SEL(IEC_BOOL g, T in0, T in1) noexcept { + return iec_unwrap(g) ? in1 : in0; +} + +/** + * MAX - Maximum of two values + * Input: ANY_ELEMENTARY, Output: ANY_ELEMENTARY (same type) + */ +template = 0> +inline T MAX(T a, T b) noexcept { + return iec_unwrap(a) > iec_unwrap(b) ? a : b; +} + +/** + * MIN - Minimum of two values + * Input: ANY_ELEMENTARY, Output: ANY_ELEMENTARY (same type) + */ +template = 0> +inline T MIN(T a, T b) noexcept { + return iec_unwrap(a) < iec_unwrap(b) ? a : b; +} + +/** + * LIMIT - Limit value to range [mn, mx] + * Input: ANY_ELEMENTARY, Output: ANY_ELEMENTARY (same type) + */ +template = 0> +inline T LIMIT(T mn, T in, T mx) noexcept { + if (iec_unwrap(in) < iec_unwrap(mn)) return mn; + if (iec_unwrap(in) > iec_unwrap(mx)) return mx; + return in; +} + +// Mixed-type MIN/MAX/LIMIT/SEL overloads (OSCAT mixes e.g. INT with DINT) +template, std::decay_t>, int> = 0> +inline auto MAX(T a, U b) noexcept { + using CT = std::common_type_t; + auto va = static_cast(iec_unwrap(a)); + auto vb = static_cast(iec_unwrap(b)); + return va > vb ? va : vb; +} + +template, std::decay_t>, int> = 0> +inline auto MIN(T a, U b) noexcept { + using CT = std::common_type_t; + auto va = static_cast(iec_unwrap(a)); + auto vb = static_cast(iec_unwrap(b)); + return va < vb ? va : vb; +} + +template +inline auto LIMIT(T1 mn, T2 in, T3 mx) noexcept + -> std::enable_if_t< + !(std::is_same_v, std::decay_t> && + std::is_same_v, std::decay_t>), + std::common_type_t> { + using CT = std::common_type_t; + auto vmn = static_cast(iec_unwrap(mn)); + auto vin = static_cast(iec_unwrap(in)); + auto vmx = static_cast(iec_unwrap(mx)); + if (vin < vmn) return vmn; + if (vin > vmx) return vmx; + return vin; +} + +template, std::decay_t>, int> = 0> +inline auto SEL(IEC_BOOL g, T in0, U in1) noexcept { + using CT = std::common_type_t; + return iec_unwrap(g) ? static_cast(iec_unwrap(in1)) : static_cast(iec_unwrap(in0)); +} + +/** + * MUX - Multiplexer (extensible — IEC 61131-3 minArgs=3, K + at least + * two inputs). Returns the input selected by the zero-based `k`: + * + * MUX(0, A, B, C, D) == A + * MUX(2, A, B, C, D) == C + * + * The single-input terminator `MUX(k, in0)` exists only to anchor + * the variadic recursion; callers should not invoke it directly + * (the IEC contract requires K + ≥2 inputs). When `k` is out of + * range we fall through to the last input, matching the editor's + * legacy 2-input behaviour and consistent with how CODESYS clamps + * over-range selectors. + */ +template +inline T MUX([[maybe_unused]] IEC_INT k, T in0) noexcept { + return in0; +} + +template +inline T MUX(IEC_INT k, T in0, T in1, Args... rest) noexcept { + if (iec_unwrap(k) == 0) return in0; + return MUX(IEC_INT(iec_unwrap(k) - 1), in1, rest...); +} + +// ============================================================================= +// Comparison Functions (ANY_ELEMENTARY -> BOOL) +// ============================================================================= + +// Comparison operators take two arguments deduced independently — that way +// `LE(real_var, 0.0)` (where the literal is double / IEC_LREAL) and +// `EQ(my_int, 0)` (where the literal is int / IEC_INT) both type-check +// without forcing the caller to wrap every literal in a cast. Each side +// only has to land on an IEC elementary type after `iec_unwrap`; the +// comparison itself uses C++'s usual arithmetic conversions to find a +// common type. +// +// CONVERSION SEMANTICS — read this before writing cross-sign tests: +// Mixing signed and unsigned operands follows C++'s usual arithmetic +// conversions, not an IEC rule. +// +// - When the unsigned operand has *lower* integer rank than `int` +// (IEC_USINT, IEC_UINT — uint8/uint16), both sides are promoted to +// `int` and the compare happens in signed land. No wrap. +// +// - When the unsigned operand has rank >= `int` (IEC_UDINT, IEC_ULINT — +// uint32/uint64) the signed operand converts to the unsigned type +// and a negative value wraps to a large unsigned. So +// `EQ(IEC_UDINT(0xFFFFFFFFu), -1)` is TRUE because -1 becomes +// 0xFFFFFFFF before the compare. +// +// STruC++ does not insert extra guards: IEC 61131-3 doesn't define +// cross-sign-class comparison, and we want the generated C++ to behave +// predictably under standard rules. If a project needs sign-strict +// comparisons, cast both sides to the same type before calling +// EQ/NE/LT/LE/GT/GE. +template +using enable_if_two_elementary = std::enable_if_t< + is_any_elementary_v>> && + is_any_elementary_v>>, + int>; + +/** + * GT - Greater than + * Input: ANY_ELEMENTARY, Output: BOOL + */ +template = 0> +inline IEC_BOOL GT(A a, B b) noexcept { + return IEC_BOOL(iec_unwrap(a) > iec_unwrap(b)); +} + +/** + * GE - Greater than or equal + * Input: ANY_ELEMENTARY, Output: BOOL + */ +template = 0> +inline IEC_BOOL GE(A a, B b) noexcept { + return IEC_BOOL(iec_unwrap(a) >= iec_unwrap(b)); +} + +/** + * EQ - Equal + * Input: ANY_ELEMENTARY, Output: BOOL + */ +template = 0> +inline IEC_BOOL EQ(A a, B b) noexcept { + return IEC_BOOL(iec_unwrap(a) == iec_unwrap(b)); +} + +/** + * LE - Less than or equal + * Input: ANY_ELEMENTARY, Output: BOOL + */ +template = 0> +inline IEC_BOOL LE(A a, B b) noexcept { + return IEC_BOOL(iec_unwrap(a) <= iec_unwrap(b)); +} + +/** + * LT - Less than + * Input: ANY_ELEMENTARY, Output: BOOL + */ +template = 0> +inline IEC_BOOL LT(A a, B b) noexcept { + return IEC_BOOL(iec_unwrap(a) < iec_unwrap(b)); +} + +/** + * NE - Not equal + * Input: ANY_ELEMENTARY, Output: BOOL + */ +template = 0> +inline IEC_BOOL NE(A a, B b) noexcept { + return IEC_BOOL(iec_unwrap(a) != iec_unwrap(b)); +} + +// --------------------------------------------------------------------------- +// Variadic chain forms for GT / GE / EQ / LE / LT / NE +// --------------------------------------------------------------------------- +// +// IEC 61131-3 defines the comparison functions as extensible: +// `GT(a, b, c)` means `(a > b) AND (b > c)`. These overloads +// implement the chain semantic and live alongside the binary forms +// above so the codegen can emit the same C++ name for any arity. +// +// Same heterogeneous-type signature as the binary forms — every +// adjacent pair goes through `iec_unwrap` independently so mixed +// IECVar / underlying types compare cleanly. + +template = 0> +inline IEC_BOOL GT(A a, B b, C c, Rest... rest) noexcept { + if (!(iec_unwrap(a) > iec_unwrap(b))) return IEC_BOOL(false); + return GT(b, c, rest...); +} + +template = 0> +inline IEC_BOOL GE(A a, B b, C c, Rest... rest) noexcept { + if (!(iec_unwrap(a) >= iec_unwrap(b))) return IEC_BOOL(false); + return GE(b, c, rest...); +} + +template = 0> +inline IEC_BOOL EQ(A a, B b, C c, Rest... rest) noexcept { + if (!(iec_unwrap(a) == iec_unwrap(b))) return IEC_BOOL(false); + return EQ(b, c, rest...); +} + +template = 0> +inline IEC_BOOL LE(A a, B b, C c, Rest... rest) noexcept { + if (!(iec_unwrap(a) <= iec_unwrap(b))) return IEC_BOOL(false); + return LE(b, c, rest...); +} + +template = 0> +inline IEC_BOOL LT(A a, B b, C c, Rest... rest) noexcept { + if (!(iec_unwrap(a) < iec_unwrap(b))) return IEC_BOOL(false); + return LT(b, c, rest...); +} + +template = 0> +inline IEC_BOOL NE(A a, B b, C c, Rest... rest) noexcept { + if (!(iec_unwrap(a) != iec_unwrap(b))) return IEC_BOOL(false); + return NE(b, c, rest...); +} + +// ============================================================================= +// Bit Shift Functions (ANY_BIT -> ANY_BIT) +// ============================================================================= + +/** + * SHL - Shift left + * Input: ANY_BIT, ANY_INT (shift count), Output: ANY_BIT + */ +template = 0> +inline T SHL(T in, IEC_INT n) noexcept { + auto shift = iec_unwrap(n); + if (shift <= 0) return shift == 0 ? in : T(0); + return T(iec_unwrap(in) << shift); +} + +// Mixed-type shift count overloads (OSCAT uses various integer types for shift amount) +template = 0, + std::enable_if_t, IEC_INT>, int> = 0> +inline T SHL(T in, N n) noexcept { + auto shift = static_cast(iec_unwrap(n)); + if (shift <= 0) return shift == 0 ? in : T(0); + return T(iec_unwrap(in) << shift); +} + +/** + * SHR - Shift right + * Input: ANY_BIT, ANY_INT (shift count), Output: ANY_BIT + */ +template = 0> +inline T SHR(T in, IEC_INT n) noexcept { + auto shift = iec_unwrap(n); + if (shift <= 0) return shift == 0 ? in : T(0); + return T(iec_unwrap(in) >> shift); +} + +// Mixed-type shift count overloads +template = 0, + std::enable_if_t, IEC_INT>, int> = 0> +inline T SHR(T in, N n) noexcept { + auto shift = static_cast(iec_unwrap(n)); + if (shift <= 0) return shift == 0 ? in : T(0); + return T(iec_unwrap(in) >> shift); +} + +// SHL/SHR for signed integer types (CODESYS extension, used by OSCAT) +// IEC standard restricts to ANY_BIT, but CODESYS allows ANY_INT +template && !is_any_bit_v, int> = 0> +inline T SHL(T in, N n) noexcept { + auto shift = static_cast(iec_unwrap(n)); + if (shift <= 0) return shift == 0 ? in : T(0); + using UT = std::make_unsigned_t>; + return T(static_cast>( + static_cast(iec_unwrap(in)) << shift)); +} + +template && !is_any_bit_v, int> = 0> +inline T SHR(T in, N n) noexcept { + auto shift = static_cast(iec_unwrap(n)); + if (shift <= 0) return shift == 0 ? in : T(0); + return T(iec_unwrap(in) >> shift); +} + +/** + * ROL - Rotate left + * Input: ANY_BIT, ANY_INT (shift count), Output: ANY_BIT + */ +template = 0> +inline T ROL(T in, IEC_INT n) noexcept { + constexpr int bits = sizeof(iec_underlying_type_t) * 8; + auto v = iec_unwrap(in); + auto shift = iec_unwrap(n) % bits; + if (shift < 0) shift += bits; // IEC 61131-3: negative N reverses direction + if (shift == 0) return in; + return T((v << shift) | (v >> (bits - shift))); +} + +// Mixed-type rotate overloads +template = 0, + std::enable_if_t, IEC_INT>, int> = 0> +inline T ROL(T in, N n) noexcept { + constexpr int bits = sizeof(iec_underlying_type_t) * 8; + auto v = iec_unwrap(in); + auto shift = static_cast(iec_unwrap(n)) % bits; + if (shift < 0) shift += bits; // IEC 61131-3: negative N reverses direction + if (shift == 0) return in; + return T((v << shift) | (v >> (bits - shift))); +} + +/** + * ROR - Rotate right + * Input: ANY_BIT, ANY_INT (shift count), Output: ANY_BIT + */ +template = 0> +inline T ROR(T in, IEC_INT n) noexcept { + constexpr int bits = sizeof(iec_underlying_type_t) * 8; + auto v = iec_unwrap(in); + auto shift = iec_unwrap(n) % bits; + if (shift < 0) shift += bits; // IEC 61131-3: negative N reverses direction + if (shift == 0) return in; + return T((v >> shift) | (v << (bits - shift))); +} + +// Mixed-type rotate overloads +template = 0, + std::enable_if_t, IEC_INT>, int> = 0> +inline T ROR(T in, N n) noexcept { + constexpr int bits = sizeof(iec_underlying_type_t) * 8; + auto v = iec_unwrap(in); + auto shift = static_cast(iec_unwrap(n)) % bits; + if (shift < 0) shift += bits; // IEC 61131-3: negative N reverses direction + if (shift == 0) return in; + return T((v >> shift) | (v << (bits - shift))); +} + +// ============================================================================= +// Type Conversion Functions +// ============================================================================= + +/** + * Helper: round-then-cast for REAL→integer conversions per IEC 61131-3 + */ +template +inline ToVal iec_convert_value(FromVal value) noexcept { + // IEC 61131-3: REAL/LREAL to integer types use rounding (nearest) + if constexpr (std::is_floating_point_v && std::is_integral_v) { + return static_cast(std::round(static_cast(value))); + } else { + return static_cast(value); + } +} + +/** + * Generic type conversion (IECVar → IECVar) + */ +template +inline auto CONVERT(From value) noexcept + -> std::enable_if_t, To> { + return To(iec_convert_value(iec_unwrap(value))); +} + +/** + * Generic type conversion (arithmetic → IECVar) + */ +template +inline auto CONVERT(From value) noexcept + -> std::enable_if_t, To> { + return To(iec_convert_value(value)); +} + +// Specific conversion functions (aliases for clarity) +template inline IEC_BOOL TO_BOOL(T v) noexcept { return CONVERT(v); } +template inline IEC_SINT TO_SINT(T v) noexcept { return CONVERT(v); } +template inline IEC_INT TO_INT(T v) noexcept { return CONVERT(v); } +template inline IEC_DINT TO_DINT(T v) noexcept { return CONVERT(v); } +template inline IEC_LINT TO_LINT(T v) noexcept { return CONVERT(v); } +template inline IEC_USINT TO_USINT(T v) noexcept { return CONVERT(v); } +template inline IEC_UINT TO_UINT(T v) noexcept { return CONVERT(v); } +template inline IEC_UDINT TO_UDINT(T v) noexcept { return CONVERT(v); } +template inline IEC_ULINT TO_ULINT(T v) noexcept { return CONVERT(v); } +template inline IEC_REAL TO_REAL(T v) noexcept { return CONVERT(v); } +template inline IEC_LREAL TO_LREAL(T v) noexcept { return CONVERT(v); } +template inline IEC_BYTE TO_BYTE(T v) noexcept { return CONVERT(v); } +template inline IEC_WORD TO_WORD(T v) noexcept { return CONVERT(v); } +template inline IEC_DWORD TO_DWORD(T v) noexcept { return CONVERT(v); } +template inline IEC_LWORD TO_LWORD(T v) noexcept { return CONVERT(v); } + +// Time/Date conversion functions +// All time types are int64_t aliases, so IEC_TIME/IEC_DATE/IEC_TOD/IEC_DT +// are all IECVar. We use a single template for each target type. +// OSCAT calls TO_TIME with integer values (ms) — we convert ms → ns. +// For TIME→TIME (same underlying type), the static_cast is identity and +// the multiply still applies, but this matches CODESYS behavior where +// integer values passed to TO_TIME are treated as milliseconds. + +template inline IEC_TIME TO_TIME(T v) noexcept { + // If the input is already an IECVar (TIME/DATE/DT/TOD), this + // treats the raw nanosecond value as milliseconds — but in practice + // OSCAT only calls TO_TIME on integer types, not on TIME values. + return IEC_TIME(static_cast(iec_unwrap(v)) * 1000000); +} + +template inline IEC_DATE TO_DATE(T v) noexcept { + return IEC_DATE(static_cast(iec_unwrap(v))); +} + +template inline IEC_DT TO_DT(T v) noexcept { + return IEC_DT(static_cast(iec_unwrap(v))); +} + +template inline IEC_TOD TO_TOD(T v) noexcept { + return IEC_TOD(static_cast(iec_unwrap(v))); +} + +// --------------------------------------------------------------------------- +// STRING -> TIME / TOD / DATE / DT parsing +// +// The frontend lowers STRING_TO_TIME / STRING_TO_TOD / STRING_TO_DATE / +// STRING_TO_DT to TO_TIME / TO_TOD / TO_DATE / TO_DT. The numeric overloads +// above treat their argument as a raw count; the string overloads below PARSE +// the textual IEC literal (used by e.g. OSCAT's TIMER_EVENT_DECODE). Formats, +// each with an optional `PREFIX#`: +// TIME : [T#] ((d|h|m|s|ms|us|ns))+ -> nanoseconds +// TOD : [TOD#] HH:MM[:SS[.fff]] -> ns since midnight +// DATE : [D#] YYYY-MM-DD -> days since 1970-01-01 +// DT : [DT#] YYYY-MM-DD-HH:MM[:SS[.fff]] -> ns since the Unix epoch +// Lenient and exception-free (AVR-safe); unparseable input yields 0. +namespace iec_strparse { + +// Skip a leading `IDENT#` literal prefix (e.g. "T#", "TOD#") if present. +inline const char* skip_literal_prefix(const char* s) noexcept { + for (const char* p = s; *p; ++p) { + if (*p == '#') return p + 1; + const char c = *p; + const bool idish = c == '_' || (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + if (!idish) break; + } + return s; +} + +inline int64_t parse_time_ns(const char* s) noexcept { + s = skip_literal_prefix(s); + int64_t total = 0; + while (*s) { + char* end = nullptr; + const double val = std::strtod(s, &end); + if (end == s) { ++s; continue; } + s = end; + const char u0 = (*s >= 'A' && *s <= 'Z') ? static_cast(*s + 32) : *s; + const char u1 = + (s[0] && s[1] >= 'A' && s[1] <= 'Z') ? static_cast(s[1] + 32) : s[1]; + int64_t mult = 1000000LL; // default unit: milliseconds + if (u0 == 'm' && u1 == 's') { mult = 1000000LL; s += 2; } + else if (u0 == 'u' && u1 == 's') { mult = 1000LL; s += 2; } + else if (u0 == 'n' && u1 == 's') { mult = 1LL; s += 2; } + else if (u0 == 'd') { mult = 86400000000000LL; s += 1; } + else if (u0 == 'h') { mult = 3600000000000LL; s += 1; } + else if (u0 == 'm') { mult = 60000000000LL; s += 1; } + else if (u0 == 's') { mult = 1000000000LL; s += 1; } + total += static_cast(val * static_cast(mult)); + } + return total; +} + +inline void read_int(const char*& s, long long& out) noexcept { + char* end = nullptr; + out = std::strtoll(s, &end, 10); + if (end != s) s = end; +} + +inline int64_t parse_tod_ns(const char* s) noexcept { + s = skip_literal_prefix(s); + long long hh = 0, mm = 0, ss = 0; + double frac = 0; + read_int(s, hh); + if (*s == ':') { ++s; read_int(s, mm); } + if (*s == ':') { ++s; read_int(s, ss); } + if (*s == '.') { char* e = nullptr; frac = std::strtod(s, &e); if (e != s) s = e; } + return hh * 3600000000000LL + mm * 60000000000LL + ss * 1000000000LL + + static_cast(frac * 1e9); +} + +// Days from 1970-01-01 for a proleptic-Gregorian date (Hinnant's algorithm). +inline int64_t days_from_civil(long long y, long long m, long long d) noexcept { + y -= (m <= 2); + const long long era = (y >= 0 ? y : y - 399) / 400; + const long long yoe = y - era * 400; + const long long doy = (153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1; + const long long doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + doe - 719468; +} + +inline int64_t parse_date_days(const char* s) noexcept { + s = skip_literal_prefix(s); + long long y = 0, mo = 0, d = 0; + read_int(s, y); + if (*s == '-') { ++s; read_int(s, mo); } + if (*s == '-') { ++s; read_int(s, d); } + return days_from_civil(y, mo ? mo : 1, d ? d : 1); +} + +inline int64_t parse_dt_ns(const char* s) noexcept { + s = skip_literal_prefix(s); + long long y = 0, mo = 0, d = 0, hh = 0, mm = 0, ss = 0; + read_int(s, y); + if (*s == '-') { ++s; read_int(s, mo); } + if (*s == '-') { ++s; read_int(s, d); } + if (*s == '-') { ++s; read_int(s, hh); } + if (*s == ':') { ++s; read_int(s, mm); } + if (*s == ':') { ++s; read_int(s, ss); } + return days_from_civil(y, mo ? mo : 1, d ? d : 1) * 86400000000000LL + + hh * 3600000000000LL + mm * 60000000000LL + ss * 1000000000LL; +} + +} // namespace iec_strparse + +// String overloads (more specialized than the numeric TO_* templates, so they +// win overload resolution for STRING arguments). +template inline IEC_TIME TO_TIME(const IECString& s) noexcept { return IEC_TIME(iec_strparse::parse_time_ns(s.c_str())); } +template inline IEC_TIME TO_TIME(const IECStringVar& s) noexcept { return IEC_TIME(iec_strparse::parse_time_ns(s.get().c_str())); } +template inline IEC_TOD TO_TOD(const IECString& s) noexcept { return IEC_TOD(iec_strparse::parse_tod_ns(s.c_str())); } +template inline IEC_TOD TO_TOD(const IECStringVar& s) noexcept { return IEC_TOD(iec_strparse::parse_tod_ns(s.get().c_str())); } +template inline IEC_DATE TO_DATE(const IECString& s) noexcept { return IEC_DATE(iec_strparse::parse_date_days(s.c_str())); } +template inline IEC_DATE TO_DATE(const IECStringVar& s) noexcept { return IEC_DATE(iec_strparse::parse_date_days(s.get().c_str())); } +template inline IEC_DT TO_DT(const IECString& s) noexcept { return IEC_DT(iec_strparse::parse_dt_ns(s.c_str())); } +template inline IEC_DT TO_DT(const IECStringVar& s) noexcept { return IEC_DT(iec_strparse::parse_dt_ns(s.get().c_str())); } + +// ============================================================================= +// String / Wide String Conversion +// ============================================================================= +// +// STRING ↔ WSTRING per IEC 61131-3 §6.5.4.6: codepoint-by-codepoint +// transcoding. Anything outside the BMP would require surrogate +// handling that the runtime does not implement; OpenPLC programs in +// practice deal in 7-bit ASCII or simple Latin-1, so a lossy narrow +// (truncate the high byte) is documented behaviour rather than a +// surprise. Callers that need full Unicode round-tripping should keep +// data in WSTRING throughout. + +template +inline IECWString STRING_TO_WSTRING(const IECString& src) noexcept { + IECWString result; + const size_t n = src.length(); + for (size_t i = 0; i < n; ++i) { + // Treat each STRING byte as a codepoint in the U+0000–U+00FF + // range. Multi-byte UTF-8 sequences pass through byte-for-byte + // and end up as Latin-1 — wrong for non-ASCII, but the IEC + // standard doesn't define UTF-8/UTF-16 transcoding either. + result.append(static_cast(static_cast(src[i]))); + } + return result; +} + +// Overload for the per-variable wrapper (handles auto-unwrap). +template +inline IECWString STRING_TO_WSTRING(const IECStringVar& src) noexcept { + return STRING_TO_WSTRING(iec_unwrap(src)); +} + +template +inline IECString WSTRING_TO_STRING(const IECWString& src) noexcept { + IECString result; + const size_t n = src.length(); + for (size_t i = 0; i < n; ++i) { + // Truncate to the low byte. Codepoints > U+00FF lose + // information; surrogate pairs (rare in IEC programs) collapse + // to garbage. Document as "ASCII / Latin-1 only" round-trip. + result.append(static_cast(src[i] & 0xFF)); + } + return result; +} + +template +inline IECString WSTRING_TO_STRING(const IECWStringVar& src) noexcept { + return WSTRING_TO_STRING(iec_unwrap(src)); +} + +// `*_TO_*` resolution in the frontend collapses STRING_TO_WSTRING / +// WSTRING_TO_STRING to plain TO_WSTRING / TO_STRING calls (cppName is +// `TO_${toType}`), so provide the matching aliases. Templated on the +// source type so they bind to either the bare class or the *Var +// wrapper without relying on conversions. + +template +inline auto TO_WSTRING(const T& src) noexcept -> decltype(STRING_TO_WSTRING(src)) { + return STRING_TO_WSTRING(src); +} + +template +inline auto TO_STRING(const T& src) noexcept -> decltype(WSTRING_TO_STRING(src)) { + return WSTRING_TO_STRING(src); +} + +// ============================================================================= +// WSTRING → Numeric Conversions +// ============================================================================= +// +// IEC 61131-3: WSTRING_TO_INT / WSTRING_TO_REAL / etc. all route through +// WSTRING_TO_STRING (lossy narrow-to-ASCII; same caveat the standard +// transcoding helpers document) and then reuse the STRING parsers +// already defined in iec_string.hpp. This keeps the parsing semantics +// (strtoul / strtol / strtod) byte-identical between the STRING and +// WSTRING surfaces, and the narrow conversion is correct for the +// numeric ASCII / Latin-1 subset users actually write into STRING +// literals. + +template +inline IEC_BOOL TO_BOOL(const IECWString& s) noexcept { + return TO_BOOL(WSTRING_TO_STRING(s)); +} +template +inline IEC_BOOL TO_BOOL(const IECWStringVar& s) noexcept { + return TO_BOOL(s.get()); +} + +template +inline IEC_SINT TO_SINT(const IECWString& s) noexcept { + return TO_SINT(WSTRING_TO_STRING(s)); +} +template +inline IEC_SINT TO_SINT(const IECWStringVar& s) noexcept { + return TO_SINT(s.get()); +} + +template +inline IEC_INT TO_INT(const IECWString& s) noexcept { + return TO_INT(WSTRING_TO_STRING(s)); +} +template +inline IEC_INT TO_INT(const IECWStringVar& s) noexcept { + return TO_INT(s.get()); +} + +template +inline IEC_DINT TO_DINT(const IECWString& s) noexcept { + return TO_DINT(WSTRING_TO_STRING(s)); +} +template +inline IEC_DINT TO_DINT(const IECWStringVar& s) noexcept { + return TO_DINT(s.get()); +} + +template +inline IEC_LINT TO_LINT(const IECWString& s) noexcept { + return TO_LINT(WSTRING_TO_STRING(s)); +} +template +inline IEC_LINT TO_LINT(const IECWStringVar& s) noexcept { + return TO_LINT(s.get()); +} + +template +inline IEC_USINT TO_USINT(const IECWString& s) noexcept { + return TO_USINT(WSTRING_TO_STRING(s)); +} +template +inline IEC_USINT TO_USINT(const IECWStringVar& s) noexcept { + return TO_USINT(s.get()); +} + +template +inline IEC_UINT TO_UINT(const IECWString& s) noexcept { + return TO_UINT(WSTRING_TO_STRING(s)); +} +template +inline IEC_UINT TO_UINT(const IECWStringVar& s) noexcept { + return TO_UINT(s.get()); +} + +template +inline IEC_UDINT TO_UDINT(const IECWString& s) noexcept { + return TO_UDINT(WSTRING_TO_STRING(s)); +} +template +inline IEC_UDINT TO_UDINT(const IECWStringVar& s) noexcept { + return TO_UDINT(s.get()); +} + +template +inline IEC_ULINT TO_ULINT(const IECWString& s) noexcept { + return TO_ULINT(WSTRING_TO_STRING(s)); +} +template +inline IEC_ULINT TO_ULINT(const IECWStringVar& s) noexcept { + return TO_ULINT(s.get()); +} + +template +inline IEC_REAL TO_REAL(const IECWString& s) noexcept { + return TO_REAL(WSTRING_TO_STRING(s)); +} +template +inline IEC_REAL TO_REAL(const IECWStringVar& s) noexcept { + return TO_REAL(s.get()); +} + +template +inline IEC_LREAL TO_LREAL(const IECWString& s) noexcept { + return TO_LREAL(WSTRING_TO_STRING(s)); +} +template +inline IEC_LREAL TO_LREAL(const IECWStringVar& s) noexcept { + return TO_LREAL(s.get()); +} + +template +inline IEC_BYTE TO_BYTE(const IECWString& s) noexcept { + return TO_BYTE(WSTRING_TO_STRING(s)); +} +template +inline IEC_BYTE TO_BYTE(const IECWStringVar& s) noexcept { + return TO_BYTE(s.get()); +} + +template +inline IEC_WORD TO_WORD(const IECWString& s) noexcept { + return TO_WORD(WSTRING_TO_STRING(s)); +} +template +inline IEC_WORD TO_WORD(const IECWStringVar& s) noexcept { + return TO_WORD(s.get()); +} + +template +inline IEC_DWORD TO_DWORD(const IECWString& s) noexcept { + return TO_DWORD(WSTRING_TO_STRING(s)); +} +template +inline IEC_DWORD TO_DWORD(const IECWStringVar& s) noexcept { + return TO_DWORD(s.get()); +} + +template +inline IEC_LWORD TO_LWORD(const IECWString& s) noexcept { + return TO_LWORD(WSTRING_TO_STRING(s)); +} +template +inline IEC_LWORD TO_LWORD(const IECWStringVar& s) noexcept { + return TO_LWORD(s.get()); +} + +// ============================================================================= +// Time Utilities +// ============================================================================= + +/** + * Create a TIME value from milliseconds + */ +inline IEC_TIME TIME_FROM_MS(int64_t ms) noexcept { + return IEC_TIME(ms * 1000000); // Convert to nanoseconds +} + +/** + * Create a TIME value from seconds + */ +inline IEC_TIME TIME_FROM_S(double s) noexcept { + return IEC_TIME(static_cast(s * 1000000000.0)); +} + +/** + * Get milliseconds from a TIME value + */ +inline int64_t TIME_TO_MS(IEC_TIME t) noexcept { + return iec_unwrap(t) / 1000000; +} + +/** + * Get seconds from a TIME value + */ +inline double TIME_TO_S(IEC_TIME t) noexcept { + return static_cast(iec_unwrap(t)) / 1000000000.0; +} + +// ============================================================================= +// Variadic Arithmetic Functions (ANY_NUM -> ANY_NUM) +// ============================================================================= + +/** + * NEG - Negation (unary minus) + * Input: ANY_NUM, Output: ANY_NUM (same type) + */ +template = 0> +inline T NEG(T value) noexcept { + return T(-iec_unwrap(value)); +} + +/** + * ADD - Addition (variadic) + * Input: ANY_NUM, Output: ANY_NUM (same type) + * Adds two or more values together + */ +template = 0> +inline T ADD(T a, T b) noexcept { + return T(iec_unwrap(a) + iec_unwrap(b)); +} + +template = 0> +inline T ADD(T first, T second, Args... rest) noexcept { + return ADD(T(iec_unwrap(first) + iec_unwrap(second)), rest...); +} + +/** + * MUL - Multiplication (variadic) + * Input: ANY_NUM, Output: ANY_NUM (same type) + * Multiplies two or more values together + */ +template = 0> +inline T MUL(T a, T b) noexcept { + return T(iec_unwrap(a) * iec_unwrap(b)); +} + +template = 0> +inline T MUL(T first, T second, Args... rest) noexcept { + return MUL(T(iec_unwrap(first) * iec_unwrap(second)), rest...); +} + +/** + * SUB - Subtraction + * Input: ANY_NUM, Output: ANY_NUM (same type) + * Subtracts second value from first + */ +template = 0> +inline T SUB(T a, T b) noexcept { + return T(iec_unwrap(a) - iec_unwrap(b)); +} + +/** + * DIV - Division + * Input: ANY_NUM, Output: ANY_NUM (same type) + * Divides first value by second + */ +template = 0> +inline T DIV(T a, T b) noexcept { + return T(iec_unwrap(a) / iec_unwrap(b)); +} + +/** + * MOD - Modulo + * Input: ANY_NUM, Output: ANY_NUM (same type) + * Returns remainder of division + */ +template = 0> +inline T MOD(T a, T b) noexcept { + if constexpr (std::is_floating_point_v>) { + return T(std::fmod(static_cast(iec_unwrap(a)), static_cast(iec_unwrap(b)))); + } else { + return T(iec_unwrap(a) % iec_unwrap(b)); + } +} + +// ============================================================================= +// Variadic Bitwise Functions (ANY_BIT -> ANY_BIT) +// ============================================================================= + +/** + * NOT - Bitwise NOT (one's complement) + * Input: ANY_BIT, Output: ANY_BIT (same type) + * + * BOOL needs logical negation, not bitwise: `~bool(true)` integer-promotes + * to `~1 == -2`, and converting back via `bool(-2)` is `true` (any non-zero + * is true), so the bitwise path returns `true` for both inputs. The + * IEC_BOOL specialization handles wrapped booleans, but expressions like + * `NOT(a == b)` instantiate the primary template with `T = bool` (raw) + * because IECVar's comparison operators return plain `bool`. Add a + * raw-bool specialization that uses `!` so NOT(comparison) works. + */ +template = 0> +inline T NOT(T value) noexcept { + return T(~iec_unwrap(value)); +} + +template<> +inline bool NOT(bool value) noexcept { + return !value; +} + +template<> +inline IEC_BOOL NOT(IEC_BOOL value) noexcept { + return IEC_BOOL(!iec_unwrap(value)); +} + +/** + * AND - Bitwise AND (variadic) + * Input: ANY_BIT, Output: ANY_BIT (same type) + */ +template = 0> +inline T AND(T a, T b) noexcept { + return T(iec_unwrap(a) & iec_unwrap(b)); +} + +template = 0> +inline T AND(T first, T second, Args... rest) noexcept { + return AND(T(iec_unwrap(first) & iec_unwrap(second)), rest...); +} + +/** + * OR - Bitwise OR (variadic) + * Input: ANY_BIT, Output: ANY_BIT (same type) + */ +template = 0> +inline T OR(T a, T b) noexcept { + return T(iec_unwrap(a) | iec_unwrap(b)); +} + +template = 0> +inline T OR(T first, T second, Args... rest) noexcept { + return OR(T(iec_unwrap(first) | iec_unwrap(second)), rest...); +} + +/** + * XOR - Bitwise XOR (variadic) + * Input: ANY_BIT, Output: ANY_BIT (same type) + */ +template = 0> +inline T XOR(T a, T b) noexcept { + return T(iec_unwrap(a) ^ iec_unwrap(b)); +} + +template = 0> +inline T XOR(T first, T second, Args... rest) noexcept { + return XOR(T(iec_unwrap(first) ^ iec_unwrap(second)), rest...); +} + +// ============================================================================= +// Variadic Selection Functions (ANY_ELEMENTARY) +// ============================================================================= + +/** + * MAX - Maximum (variadic) + * Input: ANY_ELEMENTARY, Output: ANY_ELEMENTARY (same type) + * Returns the maximum of two or more values + */ +template = 0> +inline T MAX(T first, T second, Args... rest) noexcept { + T current_max = iec_unwrap(first) > iec_unwrap(second) ? first : second; + if constexpr (sizeof...(rest) > 0) { + return MAX(current_max, rest...); + } else { + return current_max; + } +} + +/** + * MIN - Minimum (variadic) + * Input: ANY_ELEMENTARY, Output: ANY_ELEMENTARY (same type) + * Returns the minimum of two or more values + */ +template = 0> +inline T MIN(T first, T second, Args... rest) noexcept { + T current_min = iec_unwrap(first) < iec_unwrap(second) ? first : second; + if constexpr (sizeof...(rest) > 0) { + return MIN(current_min, rest...); + } else { + return current_min; + } +} + +/** + * MOVE - Copy value (identity function) + * Input: ANY, Output: ANY (same type) + * Used for explicit value copying in ST + */ +template +inline T MOVE(T value) noexcept { + return value; +} + +// ============================================================================= +// Scan-Cycle Time (CODESYS/MatIEC-compatible) +// ============================================================================= + +/** + * Global scan-cycle time in nanoseconds. + * Advanced by the runtime before each scan cycle. + * - REPL advances by common_ticktime each cycle. + * - OpenPLC runtime advances before each task execution. + * - Test runner resets to 0 before each test case. + * + * All calls to TIME() within the same cycle return the same value, + * matching CODESYS behavior. + */ +#ifdef STRUCPP_THREADED +// Threaded runtime (OpenPLC v4): each IEC task runs on its own thread and must +// observe an IEC TIME() value that is STABLE for the duration of its scan and +// equal to the time at which the dispatcher released it. thread_local gives +// every worker its own TIME() base; the runtime stamps it via +// strucpp_set_current_time() at each dispatch, so a slow/overrunning task keeps +// reading its own snapshot while the dispatcher's master clock advances freely +// for the other tasks. Gated on STRUCPP_THREADED because single-threaded +// targets (Arduino/bare-metal) may have no TLS runtime — there it stays a plain +// global, which is correct for a one-thread scan loop. +inline thread_local int64_t __CURRENT_TIME_NS = 0; +#else +inline int64_t __CURRENT_TIME_NS = 0; +#endif + +/** + * Returns the current scan-cycle time. + * CODESYS-compatible: TIME() returns the same value for the entire cycle. + */ +inline IEC_TIME TIME() { + return IEC_TIME(static_cast(__CURRENT_TIME_NS)); +} + +/** + * Wall-clock date-and-time override slot, in nanoseconds since the + * Unix epoch. + * + * Platform integrations that *can* deliver real wall-clock time + * (VPP packages with a DS3231 RTC chip wired up, Wi-Fi targets that + * pull NTP, etc.) populate this before each scan and CURRENT_DT() + * returns it verbatim. Targets without that capability leave it at 0 + * and CURRENT_DT() falls back to a meaningful-but-not-wall-clock + * value — see the function comment for the full priority order. + */ +inline int64_t __CURRENT_DT_NS = 0; + +/** + * CURRENT_DT() — wall-clock date-and-time. + * + * Returns the current absolute time as IEC_DT (nanoseconds since the + * Unix epoch). Distinct from TIME() which returns the scan-cycle's + * monotonic elapsed time, not a date. + * + * Used by the Additional Function Blocks library's RTC FB, which under + * MatIEC consumed a `__CURRENT_TIME` global the runtime injected before + * each scan. STruC++ exposes the same capability through this regular + * function so RTC's body can call it without compiler-specific pragmas. + * + * Resolution priority (highest first): + * 1. `__CURRENT_DT_NS` when non-zero — the platform integration + * delivered a real wall-clock value (RTC chip, NTP, host syscall + * wired by an OpenPLC v4 runtime, etc.). Honoured on every + * target. + * 2. std::chrono::system_clock on hosted targets — covers REPL, test + * runner, and any g++ build that didn't populate + * `__CURRENT_DT_NS`. Inherits CLOCK_REALTIME's quirks (can step + * backwards if the system clock is corrected); code needing + * strict monotonicity should use TIME() instead. + * 3. `__CURRENT_TIME_NS` (time since program start) on bare-metal + * targets where std::chrono::system_clock isn't available. + * avr-gcc's libstdc++ ships `` but omits `system_clock`, + * so we can't reach for it on Arduino / AVR. Returning uptime + * keeps the IEC_DT value monotonically advancing — programs that + * diff two CURRENT_DT() readings still see meaningful elapsed + * time, just expressed in seconds-since-boot rather than seconds- + * since-1970. + * + * VPP packages targeting hardware with an RTC override (1) by writing + * `__CURRENT_DT_NS` from their platform glue. Nothing else in the + * runtime needs to change to enable that path. + */ +inline IEC_DT CURRENT_DT() { + if (__CURRENT_DT_NS != 0) { + return IEC_DT(static_cast(__CURRENT_DT_NS)); + } +#ifdef __AVR__ + // No system_clock on avr-gcc. `__CURRENT_TIME_NS` advances + // monotonically as the runtime drives the scan cycle, giving us + // time-since-boot — meaningful for diffing timestamps even when + // no RTC is wired up. + return IEC_DT(static_cast(__CURRENT_TIME_NS)); +#else + using namespace std::chrono; + auto now = system_clock::now(); + auto ns = duration_cast(now.time_since_epoch()).count(); + return IEC_DT(static_cast(ns)); +#endif +} + +// ============================================================================= +// CODESYS System Functions +// ============================================================================= + +/** + * ADR(variable) - Returns the memory address of a variable. + * CODESYS extension. Maps to address-of in C++, returning uintptr_t + * for compatibility with pointer arithmetic. + */ +template +inline IEC_ULINT ADR(T& var) { + return static_cast(reinterpret_cast(&var)); +} + +/** + * IEC_SIZEOF(var) - Returns the logical IEC type size in bytes. + * For IECVar types, returns sizeof(T) (the underlying type), + * not sizeof(IECVar) which includes the forcing wrapper overhead. + * Matches CODESYS SIZEOF behavior: SIZEOF(INT) = 2, SIZEOF(DINT) = 4, etc. + */ +template +inline IEC_UDINT IEC_SIZEOF(const IECVar&) noexcept { + return static_cast(sizeof(T)); +} +template +inline IEC_UDINT IEC_SIZEOF(const T&) noexcept { + return static_cast(sizeof(T)); +} + +/** + * MEMCPY(dest, src, n) - Copies n bytes from src to dest. + * CODESYS extension. Accepts uintptr_t addresses from ADR() for + * pointer arithmetic compatibility. + */ +inline IEC_ULINT MEMCPY(IEC_ULINT dest, IEC_ULINT src, std::size_t n) { + std::memcpy(reinterpret_cast(static_cast(dest)), + reinterpret_cast(static_cast(src)), n); + return dest; +} + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_string.hpp b/03-plc/as-built/strucpp_runtime/include/iec_string.hpp new file mode 100644 index 0000000..400ad5e --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_string.hpp @@ -0,0 +1,1202 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +// +// ============================================================================ +// WARNING: KEEP THIS HEADER C++14-CLEAN. +// ---------------------------------------------------------------------------- +// strucpp targets C++17 and its EMITTED code is compiled as C++17 — but this +// header is part of the C/C++ Function Block include chain, which is NOT. +// OpenPLC Editor's Arduino flow emits `c_blocks_code.cpp`, including +// `iec_var.hpp` + `iec_string.hpp` (which transitively pull in `iec_traits.hpp` +// and `iec_types.hpp`). That translation unit is compiled under whatever +// `-std=` the Arduino core picks, and every mbed-based core — Nano RP2040 +// Connect, Nano 33 BLE, Opta, GIGA, Portenta, Edge — hard-codes `-std=gnu++14`. +// So any C++17/20 construct reachable from here breaks the user's C/C++ POU +// build, even though the rest of strucpp is happily on C++17. +// +// In this header (and anything it includes) do NOT use C++17/20 features +// unguarded. In particular: +// * `std::trait_v` -> `std::trait::value` +// * `if constexpr` -> SFINAE / tag dispatch +// * inline variables / `inline constexpr` +// * `auto` non-type template params -> typed NTTPs +// * C++17/20 library headers (, , , +// , ...) -> include ONLY behind `#if __cplusplus >= ...` +// (see the guarded block in iec_types.hpp for the pattern). +// +// Boundary introduced in commit be85d8a. If you change which headers +// `c_blocks_code.cpp` pulls in, update this set of warnings accordingly. +// ============================================================================ +/** + * STruC++ Runtime - IEC String Types + * + * This header provides the IEC 61131-3 STRING type as a fixed-length string template. + * STRING[n] represents a string with maximum length n (default 254 per IEC standard). + * The implementation avoids dynamic memory allocation for real-time safety. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "iec_types.hpp" +#include "iec_var.hpp" +#include "iec_traits.hpp" + +namespace strucpp { + +// Forward declaration for cross-type assignment +template class IECStringVar; + +template +class IECString { +public: + static constexpr size_t max_length = MaxLen; + using value_type = CHAR_t; + using size_type = size_t; + + constexpr IECString() noexcept : length_(0) { + data_[0] = '\0'; + } + + IECString(const char* str) noexcept : length_(0) { + if (str) { + size_t len = std::strlen(str); + length_ = static_cast(len < MaxLen ? len : MaxLen); + std::memcpy(data_, str, length_); + } + data_[length_] = '\0'; + } + + IECString(const char* str, size_t len) noexcept { + length_ = static_cast(len < MaxLen ? len : MaxLen); + std::memcpy(data_, str, length_); + data_[length_] = '\0'; + } + + template + IECString(const IECString& other) noexcept { + length_ = static_cast(other.length() < MaxLen ? other.length() : MaxLen); + std::memcpy(data_, other.c_str(), length_); + data_[length_] = '\0'; + } + + IECString(const IECString&) = default; + IECString(IECString&&) = default; + IECString& operator=(const IECString&) = default; + IECString& operator=(IECString&&) = default; + + IECString& operator=(const char* str) noexcept { + if (str) { + size_t len = std::strlen(str); + length_ = static_cast(len < MaxLen ? len : MaxLen); + std::memcpy(data_, str, length_); + } else { + length_ = 0; + } + data_[length_] = '\0'; + return *this; + } + + template + IECString& operator=(const IECString& other) noexcept { + length_ = static_cast(other.length() < MaxLen ? other.length() : MaxLen); + std::memcpy(data_, other.c_str(), length_); + data_[length_] = '\0'; + return *this; + } + + // Cross-type assignment from IECStringVar (defined after IECStringVar class) + template + inline IECString& operator=(const IECStringVar& other) noexcept; + + constexpr size_t length() const noexcept { return length_; } + constexpr size_t size() const noexcept { return length_; } + constexpr size_t capacity() const noexcept { return MaxLen; } + constexpr bool empty() const noexcept { return length_ == 0; } + + const char* c_str() const noexcept { return data_; } + const char* data() const noexcept { return data_; } + char* data() noexcept { return data_; } + + char operator[](size_t index) const noexcept { + return index < length_ ? data_[index] : '\0'; + } + + char& operator[](size_t index) noexcept { + return data_[index < length_ ? index : length_]; + } + + char at(size_t index) const noexcept { + return index < length_ ? data_[index] : '\0'; + } + + void clear() noexcept { + length_ = 0; + data_[0] = '\0'; + } + + void resize(size_t new_len) noexcept { + if (new_len > MaxLen) new_len = MaxLen; + if (new_len > length_) { + std::memset(data_ + length_, ' ', new_len - length_); + } + length_ = static_cast(new_len); + data_[length_] = '\0'; + } + + template + IECString& append(const IECString& other) noexcept { + size_t copy_len = other.length(); + if (length_ + copy_len > MaxLen) { + copy_len = MaxLen - length_; + } + std::memcpy(data_ + length_, other.c_str(), copy_len); + length_ += static_cast(copy_len); + data_[length_] = '\0'; + return *this; + } + + IECString& append(const char* str) noexcept { + if (str) { + size_t str_len = std::strlen(str); + size_t copy_len = str_len; + if (length_ + copy_len > MaxLen) { + copy_len = MaxLen - length_; + } + std::memcpy(data_ + length_, str, copy_len); + length_ += static_cast(copy_len); + data_[length_] = '\0'; + } + return *this; + } + + IECString& append(char c) noexcept { + if (length_ < MaxLen) { + data_[length_++] = c; + data_[length_] = '\0'; + } + return *this; + } + + template + IECString operator+(const IECString& other) const noexcept { + IECString result(*this); + result.append(other); + return result; + } + + IECString operator+(const char* str) const noexcept { + IECString result(*this); + result.append(str); + return result; + } + + template + IECString& operator+=(const IECString& other) noexcept { + return append(other); + } + + IECString& operator+=(const char* str) noexcept { + return append(str); + } + + IECString& operator+=(char c) noexcept { + return append(c); + } + + template + bool operator==(const IECString& other) const noexcept { + if (length_ != other.length()) return false; + return std::memcmp(data_, other.c_str(), length_) == 0; + } + + bool operator==(const char* str) const noexcept { + if (!str) return length_ == 0; + return std::strcmp(data_, str) == 0; + } + + template + bool operator!=(const IECString& other) const noexcept { + return !(*this == other); + } + + bool operator!=(const char* str) const noexcept { + return !(*this == str); + } + + template + bool operator<(const IECString& other) const noexcept { + return std::strcmp(data_, other.c_str()) < 0; + } + + template + bool operator<=(const IECString& other) const noexcept { + return std::strcmp(data_, other.c_str()) <= 0; + } + + template + bool operator>(const IECString& other) const noexcept { + return std::strcmp(data_, other.c_str()) > 0; + } + + template + bool operator>=(const IECString& other) const noexcept { + return std::strcmp(data_, other.c_str()) >= 0; + } + + template + int compare(const IECString& other) const noexcept { + return std::strcmp(data_, other.c_str()); + } + + int compare(const char* str) const noexcept { + return std::strcmp(data_, str ? str : ""); + } + + template + size_t find(const IECString& substr, size_t pos = 0) const noexcept { + if (pos >= length_ || substr.length() == 0) return npos; + const char* found = std::strstr(data_ + pos, substr.c_str()); + return found ? static_cast(found - data_) : npos; + } + + size_t find(const char* substr, size_t pos = 0) const noexcept { + if (pos >= length_ || !substr || !*substr) return npos; + const char* found = std::strstr(data_ + pos, substr); + return found ? static_cast(found - data_) : npos; + } + + size_t find(char c, size_t pos = 0) const noexcept { + for (size_t i = pos; i < length_; ++i) { + if (data_[i] == c) return i; + } + return npos; + } + + IECString substr(size_t pos, size_t len = npos) const noexcept { + if (pos >= length_) return IECString(); + if (len == npos || pos + len > length_) { + len = length_ - pos; + } + return IECString(data_ + pos, len); + } + + void replace(size_t pos, size_t len, const char* str) noexcept { + if (pos >= length_) return; + if (pos + len > length_) len = length_ - pos; + + size_t str_len = str ? std::strlen(str) : 0; + size_t new_len = length_ - len + str_len; + if (new_len > MaxLen) { + str_len = MaxLen - (length_ - len); + new_len = MaxLen; + } + + if (str_len != len) { + std::memmove(data_ + pos + str_len, data_ + pos + len, length_ - pos - len); + } + if (str_len > 0 && str) { + std::memcpy(data_ + pos, str, str_len); + } + length_ = static_cast(new_len); + data_[length_] = '\0'; + } + + void insert(size_t pos, const char* str) noexcept { + if (pos > length_) pos = length_; + if (!str) return; + + size_t str_len = std::strlen(str); + if (length_ + str_len > MaxLen) { + str_len = MaxLen - length_; + } + + std::memmove(data_ + pos + str_len, data_ + pos, length_ - pos); + std::memcpy(data_ + pos, str, str_len); + length_ += static_cast(str_len); + data_[length_] = '\0'; + } + + void erase(size_t pos, size_t len = npos) noexcept { + if (pos >= length_) return; + if (len == npos || pos + len > length_) { + len = length_ - pos; + } + std::memmove(data_ + pos, data_ + pos + len, length_ - pos - len); + length_ -= static_cast(len); + data_[length_] = '\0'; + } + + static constexpr size_t npos = static_cast(-1); + +private: + char data_[MaxLen + 1]; + uint16_t length_; +}; + +using STRING = IECString<254>; + +template +class IECStringVar { +public: + using value_type = IECString; + + IECStringVar() noexcept : value_{}, forced_{false}, forced_value_{} {} + IECStringVar(const value_type& v) noexcept : value_{v}, forced_{false}, forced_value_{} {} + IECStringVar(const char* str) noexcept : value_{str}, forced_{false}, forced_value_{} {} + IECStringVar(const IECStringVar&) = default; + IECStringVar(IECStringVar&&) = default; + IECStringVar& operator=(const IECStringVar&) = default; + IECStringVar& operator=(IECStringVar&&) = default; + + // Cross-size converting constructor (IEC 61131-3: STRING types are interoperable) + template = 0> + IECStringVar(const IECStringVar& other) noexcept + : value_(other.get().c_str()), forced_{false}, forced_value_{} {} + + // Converting constructor from IECString of any size + template = 0> + IECStringVar(const IECString& other) noexcept + : value_(other.c_str()), forced_{false}, forced_value_{} {} + + // Converting constructor from IECVar> (struct field access returns this type) + template + IECStringVar(const IECVar>& other) noexcept + : value_(static_cast>(other).c_str()), forced_{false}, forced_value_{} {} + + // Cross-size assignment (IEC 61131-3: STRING types are interoperable, truncation on overflow) + template + IECStringVar& operator=(const IECStringVar& other) noexcept { + value_ = IECString(other.get().c_str()); + return *this; + } + + // Assignment from IECVar> (struct field access) + template + IECStringVar& operator=(const IECVar>& other) noexcept { + value_ = IECString(static_cast>(other).c_str()); + return *this; + } + + // Assignment from IECString of different size + template = 0> + IECStringVar& operator=(const IECString& other) noexcept { + value_ = IECString(other.c_str()); + return *this; + } + + value_type get() const noexcept { + return forced_ ? forced_value_ : value_; + } + + void set(const value_type& v) noexcept { + value_ = v; + } + + void set(const char* str) noexcept { + value_ = str; + } + + value_type get_underlying() const noexcept { + return value_; + } + + void force(const value_type& v) noexcept { + forced_ = true; + forced_value_ = v; + } + + void force(const char* str) noexcept { + forced_ = true; + forced_value_ = str; + } + + void unforce() noexcept { + forced_ = false; + } + + bool is_forced() const noexcept { + return forced_; + } + + value_type get_forced_value() const noexcept { + return forced_value_; + } + + operator value_type() const noexcept { + return get(); + } + + IECStringVar& operator=(const value_type& v) noexcept { + set(v); + return *this; + } + + IECStringVar& operator=(const char* str) noexcept { + set(str); + return *this; + } + + // Read-through proxies into the inner IECString. Without these, + // user code in C/C++ POU bodies has to write `name.get().c_str()` + // / `name.get().length()` for every read — `IECStringVar` would + // otherwise expose nothing but `get()` / `set()` / `operator=` to + // its consumers. Routed through the force-aware value (forced + // value when forcing is active, underlying value otherwise) so + // every read respects force semantics the same way `IECVar`'s + // `operator T()` already does for numeric pins. Returning the + // pointer to the persistent member (not to `get()`'s temporary) + // keeps `c_str()`'s buffer stable for the lifetime of `*this`. + // + // NOTE: only methods that didn't previously exist on + // `IECStringVar` are added here. Comparison (`operator==` etc.) + // is intentionally NOT proxied — the free-function overloads at + // the bottom of this file (`operator==(IECStringVar, IECString)` + // and converse) already handle every cross-class compare path, + // and adding member operators would risk overload ambiguity at + // ST call sites that previously bound to the free functions. + constexpr size_t length() const noexcept { + return (forced_ ? forced_value_ : value_).length(); + } + const char* c_str() const noexcept { + return (forced_ ? forced_value_ : value_).c_str(); + } + char operator[](size_t index) const noexcept { + return (forced_ ? forced_value_ : value_)[index]; + } + +private: + value_type value_; + bool forced_; + value_type forced_value_; +}; + +using STRING_VAR = IECStringVar<254>; + +// Deferred definition: IECString::operator=(const IECStringVar&) +// Template deduction doesn't consider user-defined conversions, so we need +// this explicit assignment to handle: rawStringField = stringVar +template +template +inline IECString& IECString::operator=(const IECStringVar& other) noexcept { + auto val = other.get(); + length_ = static_cast(val.length() < MaxLen ? val.length() : MaxLen); + std::memcpy(data_, val.c_str(), length_); + data_[length_] = '\0'; + return *this; +} + +// Comparison operators between IECString and IECStringVar +// (template deduction doesn't use implicit conversions) +template +inline bool operator==(const IECString& a, const IECStringVar& b) noexcept { + return a == b.get(); +} + +template +inline bool operator==(const IECStringVar& a, const IECString& b) noexcept { + return a.get() == b; +} + +template +inline bool operator==(const IECStringVar& a, const IECStringVar& b) noexcept { + return a.get() == b.get(); +} + +template +inline bool operator!=(const IECString& a, const IECStringVar& b) noexcept { + return !(a == b); +} + +template +inline bool operator!=(const IECStringVar& a, const IECString& b) noexcept { + return !(a == b); +} + +template +inline bool operator!=(const IECStringVar& a, const IECStringVar& b) noexcept { + return !(a == b); +} + +// Comparison operators for IECVar> (struct field access returns this type) +// Template deduction won't chain through IECVar::operator T() + IECString comparison +template +inline bool operator==(const IECVar>& a, const IECStringVar& b) noexcept { + return static_cast>(a) == b.get(); +} +template +inline bool operator==(const IECStringVar& a, const IECVar>& b) noexcept { + return a.get() == static_cast>(b); +} +template +inline bool operator!=(const IECVar>& a, const IECStringVar& b) noexcept { + return !(a == b); +} +template +inline bool operator!=(const IECStringVar& a, const IECVar>& b) noexcept { + return !(a == b); +} + +// Comparison with const char* +template +inline bool operator==(const IECStringVar& a, const char* b) noexcept { + return a.get() == b; +} + +template +inline bool operator==(const char* a, const IECStringVar& b) noexcept { + return b.get() == a; +} + +template +inline bool operator!=(const IECStringVar& a, const char* b) noexcept { + return !(a == b); +} + +template +inline bool operator!=(const char* a, const IECStringVar& b) noexcept { + return !(a == b); +} + +// Ordering operators for IECStringVar +template +inline bool operator<(const IECStringVar& a, const IECStringVar& b) noexcept { + return a.get() < b.get(); +} + +template +inline bool operator<(const IECString& a, const IECStringVar& b) noexcept { + return a < b.get(); +} + +template +inline bool operator<(const IECStringVar& a, const IECString& b) noexcept { + return a.get() < b; +} + +template +inline bool operator>(const IECStringVar& a, const IECStringVar& b) noexcept { + return b < a; +} + +template +inline bool operator>(const IECString& a, const IECStringVar& b) noexcept { + return b < a; +} + +template +inline bool operator>(const IECStringVar& a, const IECString& b) noexcept { + return b < a; +} + +// Non-template alias for codegen: IEC_STRING = IECStringVar<254> +// For parameterized STRING(N), codegen emits IECStringVar directly +using IEC_STRING = IECStringVar<254>; + +template +inline size_t LEN(const IECString& s) noexcept { + return s.length(); +} + +// IECStringVar overload: template deduction doesn't go through implicit conversions +template +inline size_t LEN(const IECStringVar& s) noexcept { + return s.get().length(); +} + +template +inline IECString LEFT(const IECString& s, size_t len) noexcept { + return s.substr(0, len); +} + +template +inline IECString RIGHT(const IECString& s, size_t len) noexcept { + if (len >= s.length()) return s; + return s.substr(s.length() - len, len); +} + +template +inline IECString MID(const IECString& s, size_t len, size_t pos) noexcept { + if (pos == 0) return IECString(); + return s.substr(pos - 1, len); +} + +template +inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)> +CONCAT(const IECString& s1, const IECString& s2) noexcept { + constexpr size_t ResultLen = MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2; + IECString result(s1); + result.append(s2); + return result; +} + +// Variadic CONCAT for 3+ arguments (IEC 61131-3 extensible function) +template +inline auto +CONCAT(const IECString& s1, const IECString& s2, const Args&... rest) noexcept { + return CONCAT(CONCAT(s1, s2), rest...); +} + +template +inline IECString INSERT(const IECString& s1, const IECString& s2, size_t pos) noexcept { + IECString result(s1); + if (pos == 0) pos = 1; + result.insert(pos - 1, s2.c_str()); + return result; +} + +template +inline IECString DELETE_STR(const IECString& s, size_t len, size_t pos) noexcept { + IECString result(s); + if (pos == 0) pos = 1; + result.erase(pos - 1, len); + return result; +} + +template +inline IECString REPLACE(const IECString& s1, const IECString& s2, size_t len, size_t pos) noexcept { + IECString result(s1); + if (pos == 0) pos = 1; + result.replace(pos - 1, len, s2.c_str()); + return result; +} + +// const char* overloads for string functions (codegen may emit string literals) +template +inline IECString REPLACE(const IECString& s1, const char* s2, size_t len, size_t pos) noexcept { + return REPLACE(s1, IECString(s2), len, pos); +} + +template +inline IECString REPLACE(const IECStringVar& s1, const char* s2, size_t len, size_t pos) noexcept { + return REPLACE(s1.get(), IECString(s2), len, pos); +} + +template +inline IECString INSERT(const IECString& s1, const char* s2, size_t pos) noexcept { + return INSERT(s1, IECString(s2), pos); +} + +template +inline IECString INSERT(const IECStringVar& s1, const char* s2, size_t pos) noexcept { + return INSERT(s1.get(), IECString(s2), pos); +} + +template +inline size_t FIND(const IECString& s1, const char* s2) noexcept { + return FIND(s1, IECString(s2)); +} + +template +inline size_t FIND(const IECStringVar& s1, const char* s2) noexcept { + return FIND(s1.get(), IECString(s2)); +} + +template +inline size_t FIND(const IECString& s1, const IECString& s2) noexcept { + size_t pos = s1.find(s2); + return pos == IECString::npos ? 0 : pos + 1; +} + +// ============================================================================= +// IECStringVar overloads — template deduction doesn't use implicit conversions, +// so we need explicit overloads that forward to the IECString versions via .get() +// ============================================================================= + +template +inline IECString LEFT(const IECStringVar& s, size_t len) noexcept { + return LEFT(s.get(), len); +} + +template +inline IECString RIGHT(const IECStringVar& s, size_t len) noexcept { + return RIGHT(s.get(), len); +} + +template +inline IECString MID(const IECStringVar& s, size_t len, size_t pos) noexcept { + return MID(s.get(), len, pos); +} + +template +inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)> +CONCAT(const IECStringVar& s1, const IECStringVar& s2) noexcept { + return CONCAT(s1.get(), s2.get()); +} + +template +inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)> +CONCAT(const IECStringVar& s1, const IECString& s2) noexcept { + return CONCAT(s1.get(), s2); +} + +template +inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)> +CONCAT(const IECString& s1, const IECStringVar& s2) noexcept { + return CONCAT(s1, s2.get()); +} + +template +inline IECString INSERT(const IECStringVar& s1, const IECStringVar& s2, size_t pos) noexcept { + return INSERT(s1.get(), s2.get(), pos); +} + +template +inline IECString INSERT(const IECStringVar& s1, const IECString& s2, size_t pos) noexcept { + return INSERT(s1.get(), s2, pos); +} + +template +inline IECString INSERT(const IECString& s1, const IECStringVar& s2, size_t pos) noexcept { + return INSERT(s1, s2.get(), pos); +} + +template +inline IECString DELETE_STR(const IECStringVar& s, size_t len, size_t pos) noexcept { + return DELETE_STR(s.get(), len, pos); +} + +template +inline IECString REPLACE(const IECStringVar& s1, const IECStringVar& s2, size_t len, size_t pos) noexcept { + return REPLACE(s1.get(), s2.get(), len, pos); +} + +template +inline IECString REPLACE(const IECStringVar& s1, const IECString& s2, size_t len, size_t pos) noexcept { + return REPLACE(s1.get(), s2, len, pos); +} + +template +inline IECString REPLACE(const IECString& s1, const IECStringVar& s2, size_t len, size_t pos) noexcept { + return REPLACE(s1, s2.get(), len, pos); +} + +template +inline size_t FIND(const IECStringVar& s1, const IECStringVar& s2) noexcept { + return FIND(s1.get(), s2.get()); +} + +template +inline size_t FIND(const IECStringVar& s1, const IECString& s2) noexcept { + return FIND(s1.get(), s2); +} + +template +inline size_t FIND(const IECString& s1, const IECStringVar& s2) noexcept { + return FIND(s1, s2.get()); +} + +// ============================================================================= +// IECVar> overloads — struct field access returns this type. +// Template deduction won't chain through operator T(). +// ============================================================================= + +template +inline size_t LEN(const IECVar>& s) noexcept { + return static_cast>(s).length(); +} + +template +inline IECString LEFT(const IECVar>& s, size_t len) noexcept { + return LEFT(static_cast>(s), len); +} + +template +inline IECString RIGHT(const IECVar>& s, size_t len) noexcept { + return RIGHT(static_cast>(s), len); +} + +template +inline IECString MID(const IECVar>& s, size_t len, size_t pos) noexcept { + return MID(static_cast>(s), len, pos); +} + +template +inline IECString DELETE_STR(const IECVar>& s, size_t len, size_t pos) noexcept { + return DELETE_STR(static_cast>(s), len, pos); +} + +template +inline size_t FIND(const IECVar>& s1, const IECStringVar& s2) noexcept { + return FIND(static_cast>(s1), s2.get()); +} + +template +inline size_t FIND(const IECVar>& s1, const IECString& s2) noexcept { + return FIND(static_cast>(s1), s2); +} + +template +inline size_t FIND(const IECStringVar& s1, const IECVar>& s2) noexcept { + return FIND(s1.get(), static_cast>(s2)); +} + +template +inline IECString REPLACE(const IECVar>& s1, const IECStringVar& s2, size_t len, size_t pos) noexcept { + return REPLACE(static_cast>(s1), IECString(s2.get().c_str()), len, pos); +} + +template +inline IECString INSERT(const IECVar>& s1, const IECStringVar& s2, size_t pos) noexcept { + return INSERT(static_cast>(s1), IECString(s2.get().c_str()), pos); +} + +// Cross-size REPLACE/INSERT: arguments may have different string sizes +template = 0> +inline IECString REPLACE(const IECStringVar& s1, const IECStringVar& s2, size_t len, size_t pos) noexcept { + return REPLACE(s1.get(), IECString(s2.get().c_str()), len, pos); +} + +template = 0> +inline IECString INSERT(const IECStringVar& s1, const IECStringVar& s2, size_t pos) noexcept { + return INSERT(s1.get(), IECString(s2.get().c_str()), pos); +} + +// CONCAT overloads for IECVar> +template +inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)> +CONCAT(const IECVar>& s1, const IECString& s2) noexcept { + return CONCAT(static_cast>(s1), s2); +} + +template +inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)> +CONCAT(const IECString& s1, const IECVar>& s2) noexcept { + return CONCAT(s1, static_cast>(s2)); +} + +template +inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)> +CONCAT(const IECVar>& s1, const IECStringVar& s2) noexcept { + return CONCAT(static_cast>(s1), s2.get()); +} + +template +inline IECString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)> +CONCAT(const IECStringVar& s1, const IECVar>& s2) noexcept { + return CONCAT(s1.get(), static_cast>(s2)); +} + +template +inline bool GT_STRING(const IECString& s1, const IECString& s2) noexcept { + return s1 > s2; +} + +template +inline bool GE_STRING(const IECString& s1, const IECString& s2) noexcept { + return s1 >= s2; +} + +template +inline bool EQ_STRING(const IECString& s1, const IECString& s2) noexcept { + return s1 == s2; +} + +template +inline bool LE_STRING(const IECString& s1, const IECString& s2) noexcept { + return s1 <= s2; +} + +template +inline bool LT_STRING(const IECString& s1, const IECString& s2) noexcept { + return s1 < s2; +} + +template +inline bool NE_STRING(const IECString& s1, const IECString& s2) noexcept { + return s1 != s2; +} + +// ============================================================================= +// TO_STRING Conversions +// ============================================================================= + +// Numeric → STRING (uses snprintf for real-time safety, no std::to_string) +// +// Split into two SFINAE'd overloads (signed vs unsigned) rather than a single +// function with `if constexpr`, because user C/C++ POU code in +// `c_blocks_code.cpp` compiles under whatever -std= the platform's Arduino +// core picked (gnu++14 on mbed cores). `if constexpr` is C++17-only and +// would make this header unusable from those compilation units. Tag +// dispatch via `enable_if_t` works back to C++11. +template()))>::value + && std::is_unsigned()))>::value, int> = 0> +inline IECString<254> TO_STRING(T v) noexcept { + char buf[32]; + std::snprintf(buf, sizeof(buf), "%llu", static_cast(iec_unwrap(v))); + return IECString<254>(buf); +} + +template()))>::value + && !std::is_unsigned()))>::value, int> = 0> +inline IECString<254> TO_STRING(T v) noexcept { + char buf[32]; + std::snprintf(buf, sizeof(buf), "%lld", static_cast(iec_unwrap(v))); + return IECString<254>(buf); +} + +template()))>::value, int> = 0> +inline IECString<254> TO_STRING(T v) noexcept { + char buf[64]; + std::snprintf(buf, sizeof(buf), "%g", static_cast(iec_unwrap(v))); + return IECString<254>(buf); +} + +// BOOL → STRING +inline IECString<254> TO_STRING(IECVar v) noexcept { + return IECString<254>(iec_unwrap(v) ? "TRUE" : "FALSE"); +} + +// IECString → STRING (identity / cross-size copy) +template +inline IECString<254> TO_STRING(const IECString& v) noexcept { + return IECString<254>(v.c_str()); +} + +template +inline IECString<254> TO_STRING(const IECStringVar& v) noexcept { + return IECString<254>(v.get().c_str()); +} + +// ============================================================================= +// OSCAT-Compatible String Functions +// ============================================================================= + +// CODE - Return ASCII code of first character +template +inline int32_t CODE(const IECString& s) noexcept { + return s.length() > 0 ? static_cast(static_cast(s[0])) : 0; +} + +template +inline int32_t CODE(const IECStringVar& s) noexcept { + return CODE(s.get()); +} + +// CHR - Return string from ASCII code +inline IECString<254> CHR(int32_t code) noexcept { + char buf[2] = { static_cast(code), '\0' }; + return IECString<254>(buf); +} + +// TRIM - Remove leading and trailing whitespace +template +inline IECString TRIM(const IECString& s) noexcept { + const char* start = s.c_str(); + const char* end = start + s.length(); + while (start < end && (*start == ' ' || *start == '\t' || *start == '\r' || *start == '\n')) ++start; + while (end > start && (*(end-1) == ' ' || *(end-1) == '\t' || *(end-1) == '\r' || *(end-1) == '\n')) --end; + return IECString(start, static_cast(end - start)); +} + +template +inline IECString TRIM(const IECStringVar& s) noexcept { + return TRIM(s.get()); +} + +// LOWERCASE / TOUPPER - Case conversion (OSCAT uses these names) +template +inline IECString LOWERCASE(const IECString& s) noexcept { + IECString result(s); + for (size_t i = 0; i < result.length(); ++i) { + char c = result[i]; + if (c >= 'A' && c <= 'Z') result[i] = c + ('a' - 'A'); + } + return result; +} + +template +inline IECString LOWERCASE(const IECStringVar& s) noexcept { + return LOWERCASE(s.get()); +} + +template +inline IECString UPPERCASE(const IECString& s) noexcept { + IECString result(s); + for (size_t i = 0; i < result.length(); ++i) { + char c = result[i]; + if (c >= 'a' && c <= 'z') result[i] = c - ('a' - 'A'); + } + return result; +} + +template +inline IECString UPPERCASE(const IECStringVar& s) noexcept { + return UPPERCASE(s.get()); +} + +// CONCAT with const char* overloads (codegen may mix string literals with IECString) +template +inline IECString CONCAT(const IECString& s1, const char* s2) noexcept { + IECString result(s1); + result.append(s2); + return result; +} + +template +inline IECString CONCAT(const char* s1, const IECString& s2) noexcept { + IECString result(s1); + result.append(s2); + return result; +} + +template +inline IECString CONCAT(const IECStringVar& s1, const char* s2) noexcept { + return CONCAT(s1.get(), s2); +} + +template +inline IECString CONCAT(const char* s1, const IECStringVar& s2) noexcept { + return CONCAT(s1, s2.get()); +} + +// ============================================================================= +// String-to-Numeric Conversions +// IEC 61131-3: STRING_TO_INT, STRING_TO_REAL, etc. +// Placed here because they need both IECString and IEC_INT/IEC_REAL types. +// ============================================================================= + +template +inline IEC_INT TO_INT(const IECString& s) noexcept { + return IEC_INT(static_cast(std::strtol(s.c_str(), nullptr, 10))); +} +template +inline IEC_INT TO_INT(const IECStringVar& s) noexcept { + return TO_INT(s.get()); +} +template +inline IEC_REAL TO_REAL(const IECString& s) noexcept { + return IEC_REAL(static_cast(std::strtod(s.c_str(), nullptr))); +} +template +inline IEC_REAL TO_REAL(const IECStringVar& s) noexcept { + return TO_REAL(s.get()); +} +template +inline IEC_LREAL TO_LREAL(const IECString& s) noexcept { + return IEC_LREAL(static_cast(std::strtod(s.c_str(), nullptr))); +} +template +inline IEC_LREAL TO_LREAL(const IECStringVar& s) noexcept { + return TO_LREAL(s.get()); +} +template +inline IEC_DINT TO_DINT(const IECString& s) noexcept { + return IEC_DINT(static_cast(std::strtol(s.c_str(), nullptr, 10))); +} +template +inline IEC_DINT TO_DINT(const IECStringVar& s) noexcept { + return TO_DINT(s.get()); +} + +template +inline IEC_SINT TO_SINT(const IECString& s) noexcept { + return IEC_SINT(static_cast(std::strtol(s.c_str(), nullptr, 10))); +} +template +inline IEC_SINT TO_SINT(const IECStringVar& s) noexcept { + return TO_SINT(s.get()); +} + +template +inline IEC_LINT TO_LINT(const IECString& s) noexcept { + return IEC_LINT(static_cast(std::strtoll(s.c_str(), nullptr, 10))); +} +template +inline IEC_LINT TO_LINT(const IECStringVar& s) noexcept { + return TO_LINT(s.get()); +} + +template +inline IEC_USINT TO_USINT(const IECString& s) noexcept { + return IEC_USINT(static_cast(std::strtoul(s.c_str(), nullptr, 10))); +} +template +inline IEC_USINT TO_USINT(const IECStringVar& s) noexcept { + return TO_USINT(s.get()); +} + +template +inline IEC_UINT TO_UINT(const IECString& s) noexcept { + return IEC_UINT(static_cast(std::strtoul(s.c_str(), nullptr, 10))); +} +template +inline IEC_UINT TO_UINT(const IECStringVar& s) noexcept { + return TO_UINT(s.get()); +} + +template +inline IEC_UDINT TO_UDINT(const IECString& s) noexcept { + return IEC_UDINT(static_cast(std::strtoul(s.c_str(), nullptr, 10))); +} +template +inline IEC_UDINT TO_UDINT(const IECStringVar& s) noexcept { + return TO_UDINT(s.get()); +} + +template +inline IEC_ULINT TO_ULINT(const IECString& s) noexcept { + return IEC_ULINT(static_cast(std::strtoull(s.c_str(), nullptr, 10))); +} +template +inline IEC_ULINT TO_ULINT(const IECStringVar& s) noexcept { + return TO_ULINT(s.get()); +} + +template +inline IEC_BYTE TO_BYTE(const IECString& s) noexcept { + return IEC_BYTE(static_cast(std::strtoul(s.c_str(), nullptr, 10))); +} +template +inline IEC_BYTE TO_BYTE(const IECStringVar& s) noexcept { + return TO_BYTE(s.get()); +} + +template +inline IEC_WORD TO_WORD(const IECString& s) noexcept { + return IEC_WORD(static_cast(std::strtoul(s.c_str(), nullptr, 10))); +} +template +inline IEC_WORD TO_WORD(const IECStringVar& s) noexcept { + return TO_WORD(s.get()); +} + +template +inline IEC_DWORD TO_DWORD(const IECString& s) noexcept { + return IEC_DWORD(static_cast(std::strtoul(s.c_str(), nullptr, 10))); +} +template +inline IEC_DWORD TO_DWORD(const IECStringVar& s) noexcept { + return TO_DWORD(s.get()); +} + +template +inline IEC_LWORD TO_LWORD(const IECString& s) noexcept { + return IEC_LWORD(static_cast(std::strtoull(s.c_str(), nullptr, 10))); +} +template +inline IEC_LWORD TO_LWORD(const IECStringVar& s) noexcept { + return TO_LWORD(s.get()); +} + +template +inline IEC_BOOL TO_BOOL(const IECString& s) noexcept { + // "TRUE" or "1" → true, everything else → false + return IEC_BOOL(s.length() > 0 && (s[0] == 'T' || s[0] == 't' || s[0] == '1')); +} +template +inline IEC_BOOL TO_BOOL(const IECStringVar& s) noexcept { + return TO_BOOL(s.get()); +} + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_struct.hpp b/03-plc/as-built/strucpp_runtime/include/iec_struct.hpp new file mode 100644 index 0000000..660a38a --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_struct.hpp @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC Structure Support + * + * This header provides infrastructure for IEC 61131-3 STRUCT types. + * Actual struct definitions are generated by the compiler based on user TYPE declarations. + * This file provides the base class and conventions for generated structures. + * + * Structure fields use IECVar wrappers to support individual field forcing. + */ + +#pragma once + +#include "iec_var.hpp" + +namespace strucpp { + +/** + * Base class for generated IEC structures. + * Provides a common base for RTTI and potential reflection support. + * Generated structures inherit from this class. + */ +class IEC_STRUCT_Base { +public: + virtual ~IEC_STRUCT_Base() = default; + + // Optional: type name for debugging/reflection + // Subclasses can override to return their type name + virtual const char* type_name() const noexcept { return "STRUCT"; } +}; + +/* + * Example generated structure: + * + * ST Source: + * TYPE Point : STRUCT + * x : REAL; + * y : REAL; + * END_STRUCT; + * END_TYPE + * + * Generated C++: + * struct Point : public IEC_STRUCT_Base { + * IECVar x; + * IECVar y; + * + * Point() noexcept : x{}, y{} {} + * + * const char* type_name() const noexcept override { return "Point"; } + * }; + * + * Usage: + * Point p; + * p.x = 10.5f; + * p.y = 20.5f; + * + * // Force individual field + * p.x.force(100.0f); + * p.x = 0.0f; // Ignored while forced + * assert(p.x.get() == 100.0f); + */ + +/* + * Example nested structure: + * + * ST Source: + * TYPE Rectangle : STRUCT + * topLeft : Point; + * bottomRight : Point; + * END_STRUCT; + * END_TYPE + * + * Generated C++: + * struct Rectangle : public IEC_STRUCT_Base { + * Point topLeft; + * Point bottomRight; + * + * Rectangle() noexcept : topLeft{}, bottomRight{} {} + * + * const char* type_name() const noexcept override { return "Rectangle"; } + * }; + * + * Usage: + * Rectangle rect; + * rect.topLeft.x = 0.0f; + * rect.topLeft.y = 0.0f; + * rect.bottomRight.x = 100.0f; + * rect.bottomRight.y = 50.0f; + */ + +/* + * Example structure with array: + * + * ST Source: + * TYPE Polygon : STRUCT + * numPoints : INT; + * points : ARRAY[1..10] OF Point; + * END_STRUCT; + * END_TYPE + * + * Generated C++: + * struct Polygon : public IEC_STRUCT_Base { + * IECVar numPoints; + * Array1D points; + * + * Polygon() noexcept : numPoints{}, points{} {} + * + * const char* type_name() const noexcept override { return "Polygon"; } + * }; + */ + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_subrange.hpp b/03-plc/as-built/strucpp_runtime/include/iec_subrange.hpp new file mode 100644 index 0000000..360414a --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_subrange.hpp @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +// +// ============================================================================ +// WARNING: KEEP THIS HEADER C++14-CLEAN (defensive). +// ---------------------------------------------------------------------------- +// This header is NOT currently reachable from the C/C++ Function Block +// translation unit (`c_blocks_code.cpp` includes iec_var.hpp + iec_string.hpp, +// which pull in iec_traits.hpp + iec_types.hpp; iec_traits.hpp only +// forward-declares the subrange specs rather than including this file). It was +// nonetheless C++14-bridged in commit be85d8a and is adjacent to that chain — +// a future change (e.g. a subrange-typed pin on a C/C++ POU, or iec_traits.hpp +// switching from a forward declaration to a real `#include`) would pull it in. +// +// strucpp targets C++17, but the C/C++ FB TU is compiled under the Arduino +// core's `-std` — gnu++14 on every mbed-based core (Nano RP2040 Connect, Nano +// 33 BLE, Opta, GIGA, Portenta, Edge). To stay safe if/when this header enters +// that chain, keep it C++14-clean. Do NOT use C++17/20 features unguarded: +// * `std::trait_v` -> `std::trait::value` +// * `if constexpr` -> SFINAE / tag dispatch +// * inline variables / `inline constexpr` +// * `auto` non-type template params -> typed NTTPs +// * C++17/20 library headers -> include ONLY behind `#if __cplusplus >= ...` +// (see the guarded block in iec_types.hpp for the pattern). +// +// If iec_subrange.hpp becomes part of the C/C++ FB include chain, promote this +// to the same banner the reachable headers carry. +// ============================================================================ +/** + * STruC++ Runtime - IEC Subrange Types + * + * This header provides IEC 61131-3 subrange types with compile-time bounds. + * Subranges restrict a base type to a specific range of values. + * Bounds checking can be enabled/disabled via IEC_RANGE_CHECK macro. + */ + +#pragma once + +#include +#include "iec_var.hpp" + +namespace strucpp { + +/** + * Subrange value type with compile-time bounds. + * Restricts a base type to values within [Lower, Upper]. + * + * @tparam BaseType The underlying numeric type (e.g., int16_t, int32_t) + * @tparam Lower The minimum allowed value (inclusive) + * @tparam Upper The maximum allowed value (inclusive) + */ +template +class IEC_SUBRANGE_Value { +public: + using base_type = BaseType; + static constexpr BaseType lower_bound = Lower; + static constexpr BaseType upper_bound = Upper; + +private: + BaseType value_; + + // Check if value is within range + static constexpr bool in_range(BaseType val) noexcept { + return val >= static_cast(Lower) && + val <= static_cast(Upper); + } + + // Clamp value to range (for when range checking is disabled) + static constexpr BaseType clamp(BaseType val) noexcept { + if (val < static_cast(Lower)) return static_cast(Lower); + if (val > static_cast(Upper)) return static_cast(Upper); + return val; + } + +public: + // Default constructor - initializes to lower bound + constexpr IEC_SUBRANGE_Value() noexcept : value_(static_cast(Lower)) {} + + // Constructor from base type value + constexpr IEC_SUBRANGE_Value(BaseType val) noexcept : value_(val) { + #ifdef IEC_RANGE_CHECK + // In debug builds, clamp out-of-range values + // (Could also throw or assert, but we avoid exceptions for real-time) + if (!in_range(val)) { + value_ = clamp(val); + } + #endif + } + + // Implicit conversion to base type + constexpr operator BaseType() const noexcept { return value_; } + + // Get the underlying value + constexpr BaseType get() const noexcept { return value_; } + + // Assignment with optional range check + IEC_SUBRANGE_Value& operator=(BaseType val) noexcept { + #ifdef IEC_RANGE_CHECK + value_ = in_range(val) ? val : clamp(val); + #else + value_ = val; + #endif + return *this; + } + + // Comparison operators + constexpr bool operator==(const IEC_SUBRANGE_Value& other) const noexcept { + return value_ == other.value_; + } + + constexpr bool operator!=(const IEC_SUBRANGE_Value& other) const noexcept { + return value_ != other.value_; + } + + constexpr bool operator<(const IEC_SUBRANGE_Value& other) const noexcept { + return value_ < other.value_; + } + + constexpr bool operator<=(const IEC_SUBRANGE_Value& other) const noexcept { + return value_ <= other.value_; + } + + constexpr bool operator>(const IEC_SUBRANGE_Value& other) const noexcept { + return value_ > other.value_; + } + + constexpr bool operator>=(const IEC_SUBRANGE_Value& other) const noexcept { + return value_ >= other.value_; + } + + // Comparison with base type + constexpr bool operator==(BaseType val) const noexcept { + return value_ == val; + } + + constexpr bool operator!=(BaseType val) const noexcept { + return value_ != val; + } + + // Arithmetic operators (result is base type, not subrange) + // This follows IEC semantics where arithmetic can exceed subrange bounds + constexpr BaseType operator+(const IEC_SUBRANGE_Value& other) const noexcept { + return value_ + other.value_; + } + + constexpr BaseType operator-(const IEC_SUBRANGE_Value& other) const noexcept { + return value_ - other.value_; + } + + constexpr BaseType operator*(const IEC_SUBRANGE_Value& other) const noexcept { + return value_ * other.value_; + } + + constexpr BaseType operator/(const IEC_SUBRANGE_Value& other) const noexcept { + return value_ / other.value_; + } + + constexpr BaseType operator%(const IEC_SUBRANGE_Value& other) const noexcept { + return value_ % other.value_; + } + + // Arithmetic with base type + constexpr BaseType operator+(BaseType val) const noexcept { + return value_ + val; + } + + constexpr BaseType operator-(BaseType val) const noexcept { + return value_ - val; + } + + constexpr BaseType operator*(BaseType val) const noexcept { + return value_ * val; + } + + constexpr BaseType operator/(BaseType val) const noexcept { + return value_ / val; + } + + // Compound assignment (with range check) + IEC_SUBRANGE_Value& operator+=(BaseType val) noexcept { + return *this = value_ + val; + } + + IEC_SUBRANGE_Value& operator-=(BaseType val) noexcept { + return *this = value_ - val; + } + + IEC_SUBRANGE_Value& operator*=(BaseType val) noexcept { + return *this = value_ * val; + } + + IEC_SUBRANGE_Value& operator/=(BaseType val) noexcept { + return *this = value_ / val; + } + + // Increment/decrement + IEC_SUBRANGE_Value& operator++() noexcept { + return *this = value_ + 1; + } + + IEC_SUBRANGE_Value operator++(int) noexcept { + IEC_SUBRANGE_Value tmp = *this; + ++(*this); + return tmp; + } + + IEC_SUBRANGE_Value& operator--() noexcept { + return *this = value_ - 1; + } + + IEC_SUBRANGE_Value operator--(int) noexcept { + IEC_SUBRANGE_Value tmp = *this; + --(*this); + return tmp; + } + + // Unary operators + constexpr BaseType operator-() const noexcept { + return -value_; + } + + constexpr BaseType operator+() const noexcept { + return +value_; + } +}; + +/** + * Subrange variable with forcing support. + * Wraps IEC_SUBRANGE_Value in a forceable variable. + * + * @tparam BaseType The underlying numeric type + * @tparam Lower The minimum allowed value + * @tparam Upper The maximum allowed value + */ +template +class IEC_SUBRANGE_Var { +public: + using value_type = IEC_SUBRANGE_Value; + using base_type = BaseType; + static constexpr BaseType lower_bound = Lower; + static constexpr BaseType upper_bound = Upper; + +private: + value_type value_; + bool forced_; + value_type forced_value_; + +public: + IEC_SUBRANGE_Var() noexcept : value_{}, forced_{false}, forced_value_{} {} + + explicit IEC_SUBRANGE_Var(BaseType val) noexcept + : value_{val}, forced_{false}, forced_value_{} {} + + explicit IEC_SUBRANGE_Var(value_type val) noexcept + : value_{val}, forced_{false}, forced_value_{} {} + + IEC_SUBRANGE_Var(const IEC_SUBRANGE_Var&) = default; + IEC_SUBRANGE_Var(IEC_SUBRANGE_Var&&) = default; + IEC_SUBRANGE_Var& operator=(const IEC_SUBRANGE_Var&) = default; + IEC_SUBRANGE_Var& operator=(IEC_SUBRANGE_Var&&) = default; + + // Get current value (returns forced value if forced) + value_type get() const noexcept { + return forced_ ? forced_value_ : value_; + } + + // Set value (ignored if forced) + void set(value_type v) noexcept { + value_ = v; + } + + void set(BaseType v) noexcept { + value_ = v; + } + + // Get underlying value (ignoring forcing) + value_type get_underlying() const noexcept { + return value_; + } + + // Force to a specific value + void force(value_type v) noexcept { + forced_ = true; + forced_value_ = v; + } + + void force(BaseType v) noexcept { + forced_ = true; + forced_value_ = v; + } + + // Remove forcing + void unforce() noexcept { + forced_ = false; + } + + // Check if forced + bool is_forced() const noexcept { + return forced_; + } + + // Get forced value + value_type get_forced_value() const noexcept { + return forced_value_; + } + + // Implicit conversion to value_type + operator value_type() const noexcept { + return get(); + } + + // Implicit conversion to base_type + operator BaseType() const noexcept { + return get().get(); + } + + // Assignment operators + IEC_SUBRANGE_Var& operator=(value_type v) noexcept { + set(v); + return *this; + } + + IEC_SUBRANGE_Var& operator=(BaseType v) noexcept { + set(v); + return *this; + } + + // Comparison operators + bool operator==(const IEC_SUBRANGE_Var& other) const noexcept { + return get() == other.get(); + } + + bool operator!=(const IEC_SUBRANGE_Var& other) const noexcept { + return get() != other.get(); + } + + bool operator==(BaseType val) const noexcept { + return get() == val; + } + + bool operator!=(BaseType val) const noexcept { + return get() != val; + } +}; + +/** + * Convenience alias for subrange with forcing support. + * Usage: IEC_SUBRANGE percentage; + */ +template +using IEC_SUBRANGE = IEC_SUBRANGE_Var; + +/* + * Example subrange type: + * + * ST Source: + * TYPE Percentage : INT (0..100); END_TYPE + * + * Generated C++: + * using Percentage_Value = IEC_SUBRANGE_Value; + * using Percentage_Var = IEC_SUBRANGE_Var; + * // Or simply: + * using Percentage = IEC_SUBRANGE; + * + * Usage: + * Percentage_Var pct; + * pct = 50; + * assert(pct == 50); + * + * pct = 100; + * assert(pct == 100); + * + * // With IEC_RANGE_CHECK defined: + * pct = 150; // Clamped to 100 + * assert(pct == 100); + */ + +/* + * Example subrange for array index: + * + * ST Source: + * TYPE ArrayIndex : INT (1..10); END_TYPE + * + * Generated C++: + * using ArrayIndex = IEC_SUBRANGE; + */ + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_time.hpp b/03-plc/as-built/strucpp_runtime/include/iec_time.hpp new file mode 100644 index 0000000..fc3bfee --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_time.hpp @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC TIME Standard Functions + * + * IEC 61131-3 standard functions on the TIME (and LTIME) duration types. + * TIME / LTIME are stored as signed nanoseconds in `IECVar` (the + * generic per-variable wrapper). Codegen emits TIME variables as + * `IEC_TIME` (the `IECVar` alias) and time literals as raw + * nanosecond `int64_t` values that the IECVar `operator=(T)` assigns + * directly, so every function here takes/returns `IEC_TIME` for + * symmetry — no separate value class wraps the IEC variable form. + * + * Historical note: an earlier `TimeValue` + `IECTimeVar` value- + * class design lived here. Codegen never adopted it (TIME variables + * were always declared as `IEC_TIME`), so the parallel API was dead + * from generated code's perspective. Removed in favour of a single + * IECVar-based surface. + */ + +#pragma once + +#include +#include "iec_types.hpp" +#include "iec_var.hpp" +#include "iec_traits.hpp" + +namespace strucpp { + +// --------------------------------------------------------------------------- +// Nanosecond conversion constants +// --------------------------------------------------------------------------- +// Storage unit for both TIME and LTIME is nanoseconds. Conversion helpers +// below scale the underlying `int64_t` by these factors; user code can also +// import them for arithmetic that mixes literal scalars with TIME values +// (e.g. `MUL_TIME(t, 60)` for a 1-minute multiplier without recomputing the +// constant). +inline constexpr int64_t NS_PER_US = 1000LL; +inline constexpr int64_t NS_PER_MS = 1000000LL; +inline constexpr int64_t NS_PER_S = 1000000000LL; +inline constexpr int64_t NS_PER_M = 60LL * NS_PER_S; +inline constexpr int64_t NS_PER_H = 60LL * NS_PER_M; +inline constexpr int64_t NS_PER_D = 24LL * NS_PER_H; + +// --------------------------------------------------------------------------- +// Conversion: TIME → integer count of the requested unit (truncated) +// --------------------------------------------------------------------------- +// The millisecond / second variants live in `iec_std_lib.hpp` next to the +// generic numeric conversions. Other units stay here so the per-type +// surface is self-contained. +// +// Functions are inline but not constexpr: `IECVar`'s default constructor +// is non-constexpr (storage has a runtime `forced_` flag and forced-value +// slot the debugger flips through `force()`), so passing one by value to +// a constexpr context isn't allowed. Inline gives us identical codegen +// without the literal-type constraint. +inline int64_t TIME_TO_NS(IEC_TIME t) noexcept { + return iec_unwrap(t); +} + +inline int64_t TIME_TO_US(IEC_TIME t) noexcept { + return iec_unwrap(t) / NS_PER_US; +} + +inline int64_t TIME_TO_M(IEC_TIME t) noexcept { + return iec_unwrap(t) / NS_PER_M; +} + +inline int64_t TIME_TO_H(IEC_TIME t) noexcept { + return iec_unwrap(t) / NS_PER_H; +} + +inline int64_t TIME_TO_D(IEC_TIME t) noexcept { + return iec_unwrap(t) / NS_PER_D; +} + +// --------------------------------------------------------------------------- +// Arithmetic +// --------------------------------------------------------------------------- +inline IEC_TIME ABS_TIME(IEC_TIME t) noexcept { + const TIME_t ns = iec_unwrap(t); + return IEC_TIME(ns >= 0 ? ns : -ns); +} + +inline IEC_TIME ADD_TIME(IEC_TIME a, IEC_TIME b) noexcept { + return IEC_TIME(iec_unwrap(a) + iec_unwrap(b)); +} + +inline IEC_TIME SUB_TIME(IEC_TIME a, IEC_TIME b) noexcept { + return IEC_TIME(iec_unwrap(a) - iec_unwrap(b)); +} + +template +inline IEC_TIME MUL_TIME(IEC_TIME t, S scalar) noexcept { + return IEC_TIME(static_cast(iec_unwrap(t) * scalar)); +} + +template +inline IEC_TIME DIV_TIME(IEC_TIME t, S scalar) noexcept { + return IEC_TIME(static_cast(iec_unwrap(t) / scalar)); +} + +// DIVTIME(a, b) returns the integer count of `b`-durations that fit in `a` +// (i.e. `floor(a / b)`). Different return type from `DIV_TIME(t, scalar)` +// because the operands carry units that cancel — the result is unitless. +inline int64_t DIVTIME(IEC_TIME a, IEC_TIME b) noexcept { + return iec_unwrap(a) / iec_unwrap(b); +} + +// --------------------------------------------------------------------------- +// Comparison +// --------------------------------------------------------------------------- +inline bool GT_TIME(IEC_TIME a, IEC_TIME b) noexcept { return iec_unwrap(a) > iec_unwrap(b); } +inline bool GE_TIME(IEC_TIME a, IEC_TIME b) noexcept { return iec_unwrap(a) >= iec_unwrap(b); } +inline bool EQ_TIME(IEC_TIME a, IEC_TIME b) noexcept { return iec_unwrap(a) == iec_unwrap(b); } +inline bool NE_TIME(IEC_TIME a, IEC_TIME b) noexcept { return iec_unwrap(a) != iec_unwrap(b); } +inline bool LE_TIME(IEC_TIME a, IEC_TIME b) noexcept { return iec_unwrap(a) <= iec_unwrap(b); } +inline bool LT_TIME(IEC_TIME a, IEC_TIME b) noexcept { return iec_unwrap(a) < iec_unwrap(b); } + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_tod.hpp b/03-plc/as-built/strucpp_runtime/include/iec_tod.hpp new file mode 100644 index 0000000..1372111 --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_tod.hpp @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC TIME_OF_DAY Standard Functions + * + * IEC 61131-3 standard functions on the TIME_OF_DAY (TOD) and LTOD + * types. TOD / LTOD are stored as signed nanoseconds since midnight + * in `IECVar`, normalised into the [0, 24h) range by the + * `TOD_NORMALIZE` helper used by the construction + arithmetic + * functions below. Codegen emits TOD variables as `IEC_TOD` (the + * `IECVar` alias) and the functions take/return `IEC_TOD` so + * they're directly callable from generated POU code. + * + * Scope: only the standard arithmetic / comparison / round-trip + * functions. Calendar/clock component accessors (HOUR, MINUTE, + * SECOND, …) are intentionally NOT here — those are OSCAT-style + * extensions and user libraries (OSCAT, codesys-v23 stdlib imports) + * ship their own implementations. Providing them here would create + * overload ambiguity when a project imports such a library. + * + * Historical note: an earlier `TimeOfDayValue` + `IECTodVar` + * value-class design lived here. Codegen never adopted it; the + * parallel API was dead from generated code's perspective. Removed + * in favour of a single IECVar-based surface. See `iec_time.hpp` + * for the matching note on the TIME family. + */ + +#pragma once + +#include +#include "iec_types.hpp" +#include "iec_var.hpp" +#include "iec_traits.hpp" + +namespace strucpp { + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- +// Mirror of iec_time.hpp's NS_PER_X but scoped here too so iec_tod.hpp is +// self-contained. Duplicate inline-constexpr declarations at namespace +// scope are legal as long as the value matches; clients including both +// headers see one definition. +inline constexpr int64_t TOD_NS_PER_DAY = 24LL * 60LL * 60LL * 1000000000LL; + +// Normalise a signed nanosecond count into the canonical [0, 24h) TOD +// range. Negative inputs roll over from "before midnight today" to +// "before midnight yesterday"; values ≥ 24h wrap to the next day. +inline TOD_t TOD_NORMALIZE(int64_t ns) noexcept { + TOD_t result = static_cast(ns % TOD_NS_PER_DAY); + if (result < 0) result += static_cast(TOD_NS_PER_DAY); + return result; +} + +// --------------------------------------------------------------------------- +// Construction helpers +// --------------------------------------------------------------------------- +inline IEC_TOD TOD_FROM_HMS(int hour, int minute, int second, + int millisecond = 0, int microsecond = 0, + int nanosecond = 0) noexcept { + const int64_t ns = static_cast(hour) * 3600LL * 1000000000LL + + static_cast(minute) * 60LL * 1000000000LL + + static_cast(second) * 1000000000LL + + static_cast(millisecond) * 1000000LL + + static_cast(microsecond) * 1000LL + + nanosecond; + return IEC_TOD(TOD_NORMALIZE(ns)); +} + +inline IEC_TOD TOD_FROM_NS(int64_t ns) noexcept { + return IEC_TOD(TOD_NORMALIZE(ns)); +} + +inline IEC_TOD TOD_FROM_MS(int64_t ms) noexcept { + return IEC_TOD(TOD_NORMALIZE(ms * 1000000LL)); +} + +inline int64_t TOD_TO_NS(IEC_TOD tod) noexcept { + return iec_unwrap(tod); +} + +inline int64_t TOD_TO_MS(IEC_TOD tod) noexcept { + return iec_unwrap(tod) / 1000000LL; +} + +// --------------------------------------------------------------------------- +// Arithmetic — results normalised back into the [0, 24h) range. +// `DIFF_TOD` returns a raw (signed) difference in (-24h, +24h); +// callers decide whether to treat negatives as "tomorrow" or +// "yesterday". +// --------------------------------------------------------------------------- +inline IEC_TOD ADD_TOD(IEC_TOD tod, int64_t ns) noexcept { + return IEC_TOD(TOD_NORMALIZE(iec_unwrap(tod) + ns)); +} + +inline IEC_TOD SUB_TOD(IEC_TOD tod, int64_t ns) noexcept { + return IEC_TOD(TOD_NORMALIZE(iec_unwrap(tod) - ns)); +} + +inline int64_t DIFF_TOD(IEC_TOD a, IEC_TOD b) noexcept { + return iec_unwrap(a) - iec_unwrap(b); +} + +// --------------------------------------------------------------------------- +// Comparison +// --------------------------------------------------------------------------- +inline bool GT_TOD(IEC_TOD a, IEC_TOD b) noexcept { return iec_unwrap(a) > iec_unwrap(b); } +inline bool GE_TOD(IEC_TOD a, IEC_TOD b) noexcept { return iec_unwrap(a) >= iec_unwrap(b); } +inline bool EQ_TOD(IEC_TOD a, IEC_TOD b) noexcept { return iec_unwrap(a) == iec_unwrap(b); } +inline bool NE_TOD(IEC_TOD a, IEC_TOD b) noexcept { return iec_unwrap(a) != iec_unwrap(b); } +inline bool LE_TOD(IEC_TOD a, IEC_TOD b) noexcept { return iec_unwrap(a) <= iec_unwrap(b); } +inline bool LT_TOD(IEC_TOD a, IEC_TOD b) noexcept { return iec_unwrap(a) < iec_unwrap(b); } + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_traits.hpp b/03-plc/as-built/strucpp_runtime/include/iec_traits.hpp new file mode 100644 index 0000000..4897f31 --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_traits.hpp @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +// +// ============================================================================ +// WARNING: KEEP THIS HEADER C++14-CLEAN. +// ---------------------------------------------------------------------------- +// strucpp targets C++17 and its EMITTED code is compiled as C++17 — but this +// header is part of the C/C++ Function Block include chain, which is NOT. +// OpenPLC Editor's Arduino flow emits `c_blocks_code.cpp`, including +// `iec_var.hpp` + `iec_string.hpp` (which transitively pull in `iec_traits.hpp` +// and `iec_types.hpp`). That translation unit is compiled under whatever +// `-std=` the Arduino core picks, and every mbed-based core — Nano RP2040 +// Connect, Nano 33 BLE, Opta, GIGA, Portenta, Edge — hard-codes `-std=gnu++14`. +// So any C++17/20 construct reachable from here breaks the user's C/C++ POU +// build, even though the rest of strucpp is happily on C++17. +// +// In this header (and anything it includes) do NOT use C++17/20 features +// unguarded. In particular: +// * `std::trait_v` -> `std::trait::value` +// * `if constexpr` -> SFINAE / tag dispatch +// * inline variables / `inline constexpr` +// * `auto` non-type template params -> typed NTTPs +// * C++17/20 library headers (, , , +// , ...) -> include ONLY behind `#if __cplusplus >= ...` +// (see the guarded block in iec_types.hpp for the pattern). +// +// Boundary introduced in commit be85d8a. If you change which headers +// `c_blocks_code.cpp` pulls in, update this set of warnings accordingly. +// ============================================================================ +/** + * STruC++ Runtime - IEC Type Traits + * + * This header provides C++ type traits and concepts for IEC 61131-3 type categories. + * These traits enable compile-time type checking and generic programming for + * standard library functions. + */ + +#pragma once + +#include +#include +#include +#include "iec_types.hpp" + +namespace strucpp { + +// Forward declarations +template class IECVar; + +// Forward declarations for composite types +template struct ArrayBounds; +template class IEC_ARRAY_1D; +template class IEC_ARRAY_2D; +template class IEC_ARRAY_3D; +class IEC_STRUCT_Base; +template class IEC_ENUM_Value; +template class IEC_ENUM_Var; +// Forward declarations. `auto` template parameters are C++17; we declare +// these subrange templates with `BaseType`-typed bounds so they remain +// compilable under the platform's gnu++14 default (mbed Arduino cores). +// Numeric bounds in IEC subrange types are always the same type as the +// base, so requiring `Lower`/`Upper` to be `BaseType` is no semantic loss. +template class IEC_SUBRANGE_Value; +template class IEC_SUBRANGE_Var; +template class IEC_REF_TO; + +// ============================================================================= +// Primary Type Traits (default to false) +// ============================================================================= + +/** Check if T is an IEC type */ +template struct is_iec_type : std::false_type {}; + +/** Check if T is ANY_BOOL (just BOOL) */ +template struct is_any_bool : std::false_type {}; + +/** Check if T is ANY_SINT (signed integers) */ +template struct is_any_sint : std::false_type {}; + +/** Check if T is ANY_UINT (unsigned integers) */ +template struct is_any_uint : std::false_type {}; + +/** Check if T is ANY_INT (all integers) */ +template struct is_any_int : std::false_type {}; + +/** Check if T is ANY_REAL (floating point) */ +template struct is_any_real : std::false_type {}; + +/** Check if T is ANY_NUM (numeric: integers + reals) */ +template struct is_any_num : std::false_type {}; + +/** Check if T is ANY_BIT (bit strings including BOOL) */ +template struct is_any_bit : std::false_type {}; + +/** Check if T is ANY_STRING (strings and characters) */ +template struct is_any_string : std::false_type {}; + +/** Check if T is ANY_DATE (date/time types) */ +template struct is_any_date : std::false_type {}; + +/** Check if T is ANY_TIME (duration types: TIME, LTIME) */ +template struct is_any_time : std::false_type {}; + +/** Check if T is ANY_MAGNITUDE (numeric + time) */ +template struct is_any_magnitude : std::false_type {}; + +/** Check if T is ANY_ELEMENTARY (all elementary types) */ +template struct is_any_elementary : std::false_type {}; + +// ============================================================================= +// Specializations for Raw C++ Types +// ============================================================================= +// Note: IEC types share underlying C++ types, so we specialize on the actual +// C++ types. The categorization reflects the primary IEC use case. + +// Boolean (BOOL_t = bool) +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_bool : std::true_type {}; +template<> struct is_any_bit : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// 8-bit unsigned (BYTE_t/USINT_t = uint8_t) - categorized as bit string +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_bit : std::true_type {}; +template<> struct is_any_uint : std::true_type {}; +template<> struct is_any_int : std::true_type {}; +template<> struct is_any_num : std::true_type {}; +template<> struct is_any_magnitude : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// 8-bit signed (SINT_t = int8_t) +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_sint : std::true_type {}; +template<> struct is_any_int : std::true_type {}; +template<> struct is_any_num : std::true_type {}; +template<> struct is_any_magnitude : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// 16-bit unsigned (WORD_t/UINT_t = uint16_t) - categorized as bit string +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_bit : std::true_type {}; +template<> struct is_any_uint : std::true_type {}; +template<> struct is_any_int : std::true_type {}; +template<> struct is_any_num : std::true_type {}; +template<> struct is_any_magnitude : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// 16-bit signed (INT_t = int16_t) +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_sint : std::true_type {}; +template<> struct is_any_int : std::true_type {}; +template<> struct is_any_num : std::true_type {}; +template<> struct is_any_magnitude : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// 32-bit unsigned (DWORD_t/UDINT_t = uint32_t) - categorized as bit string +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_bit : std::true_type {}; +template<> struct is_any_uint : std::true_type {}; +template<> struct is_any_int : std::true_type {}; +template<> struct is_any_num : std::true_type {}; +template<> struct is_any_magnitude : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// 32-bit signed (DINT_t = int32_t) +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_sint : std::true_type {}; +template<> struct is_any_int : std::true_type {}; +template<> struct is_any_num : std::true_type {}; +template<> struct is_any_magnitude : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// 64-bit unsigned (LWORD_t/ULINT_t = uint64_t) - categorized as bit string +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_bit : std::true_type {}; +template<> struct is_any_uint : std::true_type {}; +template<> struct is_any_int : std::true_type {}; +template<> struct is_any_num : std::true_type {}; +template<> struct is_any_magnitude : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// 64-bit signed (LINT_t/TIME_t/DATE_t/TOD_t/DT_t/etc = int64_t) +// Note: This type is used for both integers and date/time types +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_sint : std::true_type {}; +template<> struct is_any_int : std::true_type {}; +template<> struct is_any_num : std::true_type {}; +template<> struct is_any_time : std::true_type {}; +template<> struct is_any_date : std::true_type {}; +template<> struct is_any_magnitude : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// Single precision float (REAL_t = float) +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_real : std::true_type {}; +template<> struct is_any_num : std::true_type {}; +template<> struct is_any_magnitude : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// Double precision float (LREAL_t = double) +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_real : std::true_type {}; +template<> struct is_any_num : std::true_type {}; +template<> struct is_any_magnitude : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// Character (CHAR_t = char) +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_string : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// Wide character (WCHAR_t = char16_t) +template<> struct is_iec_type : std::true_type {}; +template<> struct is_any_string : std::true_type {}; +template<> struct is_any_elementary : std::true_type {}; + +// ============================================================================= +// Specializations for IECVar Wrapped Types +// ============================================================================= + +template +struct is_iec_type> : is_iec_type {}; + +template +struct is_any_bool> : is_any_bool {}; + +template +struct is_any_sint> : is_any_sint {}; + +template +struct is_any_uint> : is_any_uint {}; + +template +struct is_any_int> : is_any_int {}; + +template +struct is_any_real> : is_any_real {}; + +template +struct is_any_num> : is_any_num {}; + +template +struct is_any_bit> : is_any_bit {}; + +template +struct is_any_string> : is_any_string {}; + +template +struct is_any_date> : is_any_date {}; + +template +struct is_any_time> : is_any_time {}; + +template +struct is_any_magnitude> : is_any_magnitude {}; + +template +struct is_any_elementary> : is_any_elementary {}; + +// ============================================================================= +// Helper Variable Templates (C++17) +// ============================================================================= + +template +constexpr bool is_iec_type_v = is_iec_type::value; + +template +constexpr bool is_any_bool_v = is_any_bool::value; + +template +constexpr bool is_any_sint_v = is_any_sint::value; + +template +constexpr bool is_any_uint_v = is_any_uint::value; + +template +constexpr bool is_any_int_v = is_any_int::value; + +template +constexpr bool is_any_real_v = is_any_real::value; + +template +constexpr bool is_any_num_v = is_any_num::value; + +template +constexpr bool is_any_bit_v = is_any_bit::value; + +template +constexpr bool is_any_string_v = is_any_string::value; + +template +constexpr bool is_any_date_v = is_any_date::value; + +template +constexpr bool is_any_time_v = is_any_time::value; + +template +constexpr bool is_any_magnitude_v = is_any_magnitude::value; + +template +constexpr bool is_any_elementary_v = is_any_elementary::value; + +// ============================================================================= +// Type Size Traits +// ============================================================================= + +/** Get the size in bits for an IEC type */ +template struct iec_bit_size; + +// Note: Using actual C++ types to avoid duplicate specializations +template<> struct iec_bit_size : std::integral_constant {}; +template<> struct iec_bit_size : std::integral_constant {}; +template<> struct iec_bit_size : std::integral_constant {}; +template<> struct iec_bit_size : std::integral_constant {}; +template<> struct iec_bit_size : std::integral_constant {}; +template<> struct iec_bit_size : std::integral_constant {}; +template<> struct iec_bit_size : std::integral_constant {}; +template<> struct iec_bit_size : std::integral_constant {}; +template<> struct iec_bit_size : std::integral_constant {}; +template<> struct iec_bit_size : std::integral_constant {}; +template<> struct iec_bit_size : std::integral_constant {}; +template<> struct iec_bit_size : std::integral_constant {}; +template<> struct iec_bit_size : std::integral_constant {}; + +// IECVar wrapper +template +struct iec_bit_size> : iec_bit_size {}; + +template +constexpr size_t iec_bit_size_v = iec_bit_size::value; + +// ============================================================================= +// Underlying Type Traits +// ============================================================================= + +/** Get the underlying C++ type for an IEC type */ +template struct iec_underlying_type { using type = T; }; + +// IECVar wrapper - extract the underlying type +template +struct iec_underlying_type> { using type = T; }; + +template +using iec_underlying_type_t = typename iec_underlying_type::type; + +/** Extract the raw value from an IECVar or pass through a raw value unchanged */ +template +constexpr T iec_unwrap(T v) noexcept { return v; } + +template +constexpr T iec_unwrap(const IECVar& v) noexcept { return v.get(); } + +// ============================================================================= +// Type Limits +// ============================================================================= + +/** Get the minimum and maximum values for an IEC type */ +template struct iec_limits; + +// Note: Using actual C++ types to avoid duplicate specializations +template<> struct iec_limits { + static constexpr int8_t min() { return -128; } + static constexpr int8_t max() { return 127; } +}; + +template<> struct iec_limits { + static constexpr int16_t min() { return -32768; } + static constexpr int16_t max() { return 32767; } +}; + +template<> struct iec_limits { + static constexpr int32_t min() { return -2147483648; } + static constexpr int32_t max() { return 2147483647; } +}; + +template<> struct iec_limits { + static constexpr int64_t min() { return INT64_MIN; } + static constexpr int64_t max() { return INT64_MAX; } +}; + +template<> struct iec_limits { + static constexpr uint8_t min() { return 0; } + static constexpr uint8_t max() { return 255; } +}; + +template<> struct iec_limits { + static constexpr uint16_t min() { return 0; } + static constexpr uint16_t max() { return 65535; } +}; + +template<> struct iec_limits { + static constexpr uint32_t min() { return 0; } + static constexpr uint32_t max() { return 4294967295U; } +}; + +template<> struct iec_limits { + static constexpr uint64_t min() { return 0; } + static constexpr uint64_t max() { return UINT64_MAX; } +}; + +// IECVar wrapper +template +struct iec_limits> : iec_limits {}; + +// ============================================================================= +// Composite Type Traits +// ============================================================================= + +/** Check if T is an IEC array type */ +template struct is_iec_array : std::false_type {}; + +template +struct is_iec_array> : std::true_type {}; + +template +struct is_iec_array> : std::true_type {}; + +template +struct is_iec_array> : std::true_type {}; + +template +constexpr bool is_iec_array_v = is_iec_array::value; + +/** Check if T is an IEC struct type */ +// Uses std::is_base_of to detect types derived from IEC_STRUCT_Base +// Note: Generated structs inherit from IEC_STRUCT_Base +template +struct is_iec_struct : std::integral_constant::value> {}; + +template +constexpr bool is_iec_struct_v = is_iec_struct::value; + +/** Check if T is an IEC enumeration type */ +template struct is_iec_enum : std::false_type {}; + +template +struct is_iec_enum> : std::true_type {}; + +template +struct is_iec_enum> : std::true_type {}; + +template +constexpr bool is_iec_enum_v = is_iec_enum::value; + +/** Check if T is an IEC subrange type */ +template struct is_iec_subrange : std::false_type {}; + +template +struct is_iec_subrange> : std::true_type {}; + +template +struct is_iec_subrange> : std::true_type {}; + +template +constexpr bool is_iec_subrange_v = is_iec_subrange::value; + +/** Check if T is an IEC pointer type (REF_TO) */ +template struct is_iec_pointer : std::false_type {}; + +template +struct is_iec_pointer> : std::true_type {}; + +template +constexpr bool is_iec_pointer_v = is_iec_pointer::value; + +/** Check if T is ANY_DERIVED (composite types: arrays, structs, enums, subranges, pointers) */ +template +struct is_any_derived : std::integral_constant::value || + is_iec_struct::value || + is_iec_enum::value || + is_iec_subrange::value || + is_iec_pointer::value +> {}; + +template +constexpr bool is_any_derived_v = is_any_derived::value; + +// ============================================================================= +// C++17 SFINAE Helpers +// ============================================================================= + +template +using enable_if_any_int = std::enable_if_t, int>; + +template +using enable_if_any_real = std::enable_if_t, int>; + +template +using enable_if_any_num = std::enable_if_t, int>; + +template +using enable_if_any_bit = std::enable_if_t, int>; + +template +using enable_if_any_string = std::enable_if_t, int>; + +template +using enable_if_any_date = std::enable_if_t, int>; + +template +using enable_if_any_time = std::enable_if_t, int>; + +template +using enable_if_any_magnitude = std::enable_if_t, int>; + +template +using enable_if_any_elementary = std::enable_if_t, int>; + +template +using enable_if_iec_array = std::enable_if_t, int>; + +template +using enable_if_iec_struct = std::enable_if_t, int>; + +template +using enable_if_iec_enum = std::enable_if_t, int>; + +template +using enable_if_iec_subrange = std::enable_if_t, int>; + +template +using enable_if_any_derived = std::enable_if_t, int>; + +template +using enable_if_iec_pointer = std::enable_if_t, int>; + +// ============================================================================= +// C++20 Concepts (when available) +// ============================================================================= + +#if __cplusplus >= 202002L + +#include + +/** Concept for IEC types */ +template +concept IECType = is_iec_type_v; + +/** Concept for ANY_BOOL types */ +template +concept AnyBool = is_any_bool_v; + +/** Concept for ANY_SINT types (signed integers) */ +template +concept AnySInt = is_any_sint_v; + +/** Concept for ANY_UINT types (unsigned integers) */ +template +concept AnyUInt = is_any_uint_v; + +/** Concept for ANY_INT types (all integers) */ +template +concept AnyInt = is_any_int_v; + +/** Concept for ANY_REAL types */ +template +concept AnyReal = is_any_real_v; + +/** Concept for ANY_NUM types */ +template +concept AnyNum = is_any_num_v; + +/** Concept for ANY_BIT types */ +template +concept AnyBit = is_any_bit_v; + +/** Concept for ANY_STRING types */ +template +concept AnyString = is_any_string_v; + +/** Concept for ANY_DATE types */ +template +concept AnyDate = is_any_date_v; + +/** Concept for ANY_TIME types (durations) */ +template +concept AnyTime = is_any_time_v; + +/** Concept for ANY_MAGNITUDE types */ +template +concept AnyMagnitude = is_any_magnitude_v; + +/** Concept for ANY_ELEMENTARY types */ +template +concept AnyElementary = is_any_elementary_v; + +/** Concept for IEC array types */ +template +concept IECArray = is_iec_array_v; + +/** Concept for IEC struct types */ +template +concept IECStruct = is_iec_struct_v; + +/** Concept for IEC enum types */ +template +concept IECEnum = is_iec_enum_v; + +/** Concept for IEC subrange types */ +template +concept IECSubrange = is_iec_subrange_v; + +/** Concept for ANY_DERIVED types (composite types) */ +template +concept AnyDerived = is_any_derived_v; + +/** Concept for IEC pointer types (REF_TO) */ +template +concept IECPointer = is_iec_pointer_v; + +#endif // C++20 + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_types.hpp b/03-plc/as-built/strucpp_runtime/include/iec_types.hpp new file mode 100644 index 0000000..e0effb4 --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_types.hpp @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +// +// ============================================================================ +// WARNING: KEEP THIS HEADER C++14-CLEAN. +// ---------------------------------------------------------------------------- +// strucpp targets C++17 and its EMITTED code is compiled as C++17 — but this +// header is part of the C/C++ Function Block include chain, which is NOT. +// OpenPLC Editor's Arduino flow emits `c_blocks_code.cpp`, including +// `iec_var.hpp` + `iec_string.hpp` (which transitively pull in `iec_traits.hpp` +// and `iec_types.hpp`). That translation unit is compiled under whatever +// `-std=` the Arduino core picks, and every mbed-based core — Nano RP2040 +// Connect, Nano 33 BLE, Opta, GIGA, Portenta, Edge — hard-codes `-std=gnu++14`. +// So any C++17/20 construct reachable from here breaks the user's C/C++ POU +// build, even though the rest of strucpp is happily on C++17. +// +// In this header (and anything it includes) do NOT use C++17/20 features +// unguarded. In particular: +// * `std::trait_v` -> `std::trait::value` +// * `if constexpr` -> SFINAE / tag dispatch +// * inline variables / `inline constexpr` +// * `auto` non-type template params -> typed NTTPs +// * C++17/20 library headers (, , , +// , ...) -> include ONLY behind `#if __cplusplus >= ...` +// (see the guarded block further down in this file). +// +// Boundary introduced in commit be85d8a. If you change which headers +// `c_blocks_code.cpp` pulls in, update this set of warnings accordingly. +// ============================================================================ +/** + * STruC++ Runtime - IEC Type Definitions + * + * This header defines the C++ type aliases for IEC 61131-3 data types. + * These types are used by generated code and provide the foundation + * for the STruC++ runtime library. + */ + +#pragma once + +#include +#include + +namespace strucpp { + +// ============================================================================= +// Elementary Types - Bit Strings +// ============================================================================= + +/** IEC BOOL - Boolean value (TRUE/FALSE) */ +using BOOL_t = bool; + +/** IEC BYTE - 8-bit bit string */ +using BYTE_t = uint8_t; + +/** IEC WORD - 16-bit bit string */ +using WORD_t = uint16_t; + +/** IEC DWORD - 32-bit bit string */ +using DWORD_t = uint32_t; + +/** IEC LWORD - 64-bit bit string (IEC v3) */ +using LWORD_t = uint64_t; + +/** + * CODESYS __XWORD - unsigned integer sized to the target pointer width. + * Used for ADR()/REF() results and generic pointer-sized values, so an + * address round-trips without truncation and without wasting space on + * narrow targets (2 bytes on AVR, 8 on 64-bit hosts). `__SIZEOF_POINTER__` + * is provided by GCC/Clang/avr-gcc. + */ +#if __SIZEOF_POINTER__ <= 2 +using XWORD_t = uint16_t; +#elif __SIZEOF_POINTER__ <= 4 +using XWORD_t = uint32_t; +#else +using XWORD_t = uint64_t; +#endif + +// ============================================================================= +// Elementary Types - Signed Integers +// ============================================================================= + +/** IEC SINT - Short integer (8-bit signed) */ +using SINT_t = int8_t; + +/** IEC INT - Integer (16-bit signed) */ +using INT_t = int16_t; + +/** IEC DINT - Double integer (32-bit signed) */ +using DINT_t = int32_t; + +/** IEC LINT - Long integer (64-bit signed) */ +using LINT_t = int64_t; + +// ============================================================================= +// Elementary Types - Unsigned Integers +// ============================================================================= + +/** IEC USINT - Unsigned short integer (8-bit) */ +using USINT_t = uint8_t; + +/** IEC UINT - Unsigned integer (16-bit) */ +using UINT_t = uint16_t; + +/** IEC UDINT - Unsigned double integer (32-bit) */ +using UDINT_t = uint32_t; + +/** IEC ULINT - Unsigned long integer (64-bit) */ +using ULINT_t = uint64_t; + +// ============================================================================= +// Elementary Types - Real Numbers +// ============================================================================= + +/** IEC REAL - Single precision floating point (32-bit IEEE 754) */ +using REAL_t = float; + +/** IEC LREAL - Double precision floating point (64-bit IEEE 754) */ +using LREAL_t = double; + +// ============================================================================= +// Elementary Types - Time and Date +// ============================================================================= + +/** IEC TIME - Duration in nanoseconds */ +using TIME_t = int64_t; + +/** IEC DATE - Calendar date (days since epoch) */ +using DATE_t = int64_t; + +/** IEC TIME_OF_DAY - Time of day in nanoseconds since midnight */ +using TOD_t = int64_t; + +/** IEC DATE_AND_TIME - Combined date and time */ +using DT_t = int64_t; + +// IEC v3 Long variants (extended precision/range) + +/** IEC LTIME - Long duration in nanoseconds (IEC v3) */ +using LTIME_t = int64_t; + +/** IEC LDATE - Long calendar date (days since epoch, IEC v3) */ +using LDATE_t = int64_t; + +/** IEC LTOD - Long time of day in nanoseconds since midnight (IEC v3) */ +using LTOD_t = int64_t; + +/** IEC LDT - Long combined date and time (IEC v3) */ +using LDT_t = int64_t; + +// ============================================================================= +// Platform Pointer-Width Integer +// ============================================================================= + +/** Platform-width integer for pointer-to-integer conversions (CODESYS compat). + * On 64-bit platforms this is uint64_t; on 32-bit platforms uint32_t. + * Use this instead of DWORD for storing pointer addresses portably. */ +#if UINTPTR_MAX > UINT32_MAX +using PTR_INT_t = uint64_t; +#else +using PTR_INT_t = uint32_t; +#endif + +// ============================================================================= +// Elementary Types - Characters +// ============================================================================= + +/** IEC CHAR - Single-byte character */ +using CHAR_t = char; + +/** IEC WCHAR - Wide character (UTF-16) */ +using WCHAR_t = char16_t; + +// ============================================================================= +// Type Category Tags +// ============================================================================= + +/** Tag for ANY_BIT types */ +struct AnyBitTag {}; + +/** Tag for ANY_INT types */ +struct AnyIntTag {}; + +/** Tag for ANY_REAL types */ +struct AnyRealTag {}; + +/** Tag for ANY_NUM types (ANY_INT | ANY_REAL) */ +struct AnyNumTag {}; + +/** Tag for ANY_DATE types */ +struct AnyDateTag {}; + +/** Tag for ANY_STRING types */ +struct AnyStringTag {}; + +// ============================================================================= +// Type Traits +// ============================================================================= + +/** + * Type trait to get the category tag for an IEC type. + */ +template +struct IECTypeCategory; + +// Note: Some IEC types share the same underlying C++ type, so we only define +// one specialization per unique C++ type. The type category is determined by +// the primary use case of that underlying type. + +// Boolean type +template<> struct IECTypeCategory { using type = AnyBitTag; }; + +// 8-bit types (BYTE_t/USINT_t are both uint8_t, SINT_t is int8_t) +template<> struct IECTypeCategory { using type = AnyBitTag; }; +template<> struct IECTypeCategory { using type = AnyIntTag; }; + +// 16-bit types (WORD_t/UINT_t are both uint16_t, INT_t is int16_t) +template<> struct IECTypeCategory { using type = AnyBitTag; }; +template<> struct IECTypeCategory { using type = AnyIntTag; }; + +// 32-bit types (DWORD_t/UDINT_t are both uint32_t, DINT_t is int32_t) +template<> struct IECTypeCategory { using type = AnyBitTag; }; +template<> struct IECTypeCategory { using type = AnyIntTag; }; + +// 64-bit types (LWORD_t/ULINT_t are both uint64_t) +// Note: LINT_t, TIME_t, DATE_t, TOD_t, DT_t, LTIME_t, LDATE_t, LTOD_t, LDT_t are all int64_t +template<> struct IECTypeCategory { using type = AnyBitTag; }; +template<> struct IECTypeCategory { using type = AnyIntTag; }; + +// Real types +template<> struct IECTypeCategory { using type = AnyRealTag; }; +template<> struct IECTypeCategory { using type = AnyRealTag; }; + +// Character types +template<> struct IECTypeCategory { using type = AnyStringTag; }; +template<> struct IECTypeCategory { using type = AnyStringTag; }; + +// ============================================================================= +// C++20 Concepts (when available) +// ============================================================================= + +#if __cplusplus >= 202002L + +#include + +/** Concept for ANY_BIT types */ +template +concept IECAnyBit = std::is_same_v::type, AnyBitTag>; + +/** Concept for ANY_INT types */ +template +concept IECAnyInt = std::is_same_v::type, AnyIntTag>; + +/** Concept for ANY_REAL types */ +template +concept IECAnyReal = std::is_same_v::type, AnyRealTag>; + +/** Concept for ANY_NUM types */ +template +concept IECAnyNum = IECAnyInt || IECAnyReal; + +/** Concept for ANY_DATE types */ +template +concept IECAnyDate = std::is_same_v::type, AnyDateTag>; + +#endif // C++20 + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_var.hpp b/03-plc/as-built/strucpp_runtime/include/iec_var.hpp new file mode 100644 index 0000000..0fdbf39 --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_var.hpp @@ -0,0 +1,540 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +// +// ============================================================================ +// WARNING: KEEP THIS HEADER C++14-CLEAN. +// ---------------------------------------------------------------------------- +// strucpp targets C++17 and its EMITTED code is compiled as C++17 — but this +// header is part of the C/C++ Function Block include chain, which is NOT. +// OpenPLC Editor's Arduino flow emits `c_blocks_code.cpp`, including +// `iec_var.hpp` + `iec_string.hpp` (which transitively pull in `iec_traits.hpp` +// and `iec_types.hpp`). That translation unit is compiled under whatever +// `-std=` the Arduino core picks, and every mbed-based core — Nano RP2040 +// Connect, Nano 33 BLE, Opta, GIGA, Portenta, Edge — hard-codes `-std=gnu++14`. +// So any C++17/20 construct reachable from here breaks the user's C/C++ POU +// build, even though the rest of strucpp is happily on C++17. +// +// In this header (and anything it includes) do NOT use C++17/20 features +// unguarded. In particular: +// * `std::trait_v` -> `std::trait::value` +// * `if constexpr` -> SFINAE / tag dispatch +// * inline variables / `inline constexpr` +// * `auto` non-type template params -> typed NTTPs +// * C++17/20 library headers (, , , +// , ...) -> include ONLY behind `#if __cplusplus >= ...` +// (see the guarded block in iec_types.hpp for the pattern). +// +// Boundary introduced in commit be85d8a. If you change which headers +// `c_blocks_code.cpp` pulls in, update this set of warnings accordingly. +// ============================================================================ +/** + * STruC++ Runtime - IEC Variable Wrapper + * + * This header defines the IECVar template class that wraps IEC types + * with support for variable forcing (a key OpenPLC feature). + * + * Located variables (AT %IX0.0, etc.) use this same wrapper, and the + * raw_ptr() method provides access to the underlying storage for + * runtime binding to I/O image tables. + */ + +#pragma once + +#include "iec_types.hpp" +#include +#include + +namespace strucpp { + +// Forward declaration for pointer-to-integer assignment +template class IEC_Ptr; + +// ============================================================================= +// IEC Variable Wrapper +// ============================================================================= + +/** + * Template wrapper for IEC variables with forcing support. + * + * This class wraps any IEC type and provides: + * - Normal get/set operations + * - Variable forcing (override value for debugging/testing) + * - Implicit conversion for natural syntax + * - Arithmetic operators for numeric types + * + * @tparam T The underlying C++ type (e.g., int16_t for INT) + */ +template +class IECVar { +public: + using value_type = T; + + // ========================================================================= + // Constructors + // ========================================================================= + + /** Default constructor - initializes to zero/false */ + IECVar() noexcept : value_{}, forced_{false}, forced_value_{} {} + + /** Construct with initial value (non-explicit to allow IEC_INT val = 10 syntax) */ + IECVar(T v) noexcept : value_{v}, forced_{false}, forced_value_{} {} + + /** Cross-type converting constructor: IECVar → IECVar etc. + * Enables implicit widening when struct fields (now IECVar-wrapped) are passed + * to functions expecting a wider IECVar type. Without this, C++ would need + * two user-defined conversions (IECVar→U→T→IECVar) which is disallowed. */ + template::value && !std::is_same::value, int> = 0> + IECVar(const IECVar& other) noexcept + : value_{static_cast(other.get())}, forced_{false}, forced_value_{} {} + + /** Copy constructor — fresh IECVar starts unforced regardless of source. */ + IECVar(const IECVar& other) noexcept + : value_{other.get()}, forced_{false}, forced_value_{} {} + + /** Move constructor — same semantics as copy. */ + IECVar(IECVar&& other) noexcept + : value_{other.get()}, forced_{false}, forced_value_{} {} + + /** + * Copy assignment. + * + * Assigning FROM another IECVar must go through `set()` so forcing + * state is preserved on the destination. A memberwise copy would + * clobber `forced_` / `forced_value_`, silently unforcing variables + * that the debugger is holding — precisely what generated PLC code + * does every scan cycle with `BLINK := TOF0.Q`. + */ + IECVar& operator=(const IECVar& other) noexcept { + set(other.get()); + return *this; + } + + /** Move assignment — same semantics as copy. */ + IECVar& operator=(IECVar&& other) noexcept { + set(other.get()); + return *this; + } + + // ========================================================================= + // Value Access + // ========================================================================= + + /** + * Get the current value. + * Returns the forced value if forcing is active, otherwise the normal value. + */ + T get() const noexcept { + return forced_ ? forced_value_ : value_; + } + + /** + * Set the value. + * If forcing is active, the set is ignored to ensure drivers reading + * the raw storage always see the forced value for output variables. + */ + void set(T v) noexcept { + if (!forced_) { + value_ = v; + } + } + + /** + * Get the underlying value (ignoring forcing). + * Useful for debugging to see what the program would have set. + */ + T get_underlying() const noexcept { + return value_; + } + + // ========================================================================= + // Forcing Support + // ========================================================================= + + /** + * Force the variable to a specific value. + * While forced, get() will return the forced value regardless of set() calls. + * Also updates the raw storage so drivers reading via raw_ptr() see the forced value. + */ + void force(T v) noexcept { + forced_ = true; + forced_value_ = v; + value_ = v; // Update raw value so external readers (plugins) see forced value + } + + /** + * Remove forcing and return to normal operation. + */ + void unforce() noexcept { + forced_ = false; + } + + /** + * Check if the variable is currently forced. + */ + bool is_forced() const noexcept { + return forced_; + } + + /** + * Get the forced value (only valid if is_forced() is true). + */ + T get_forced_value() const noexcept { + return forced_value_; + } + + // ========================================================================= + // Raw Pointer Access (for Located Variables) + // ========================================================================= + + /** + * Get a pointer to the underlying raw storage. + * Used by the runtime to bind located variables to I/O image tables. + * Plugins and drivers read/write through this pointer. + * + * For inputs: drivers write to this pointer, get() returns forced value when forced + * For outputs: force() updates this storage, so drivers always read the forced value + */ + T* raw_ptr() noexcept { return &value_; } + + /** + * Get a const pointer to the underlying raw storage. + */ + const T* raw_ptr() const noexcept { return &value_; } + + // ========================================================================= + // Implicit Conversions + // ========================================================================= + + /** Implicit conversion to underlying type for natural syntax */ + operator T() const noexcept { + return get(); + } + + /** Assignment from raw value */ + IECVar& operator=(T v) noexcept { + set(v); + return *this; + } + + /** Cross-type assignment: IECVar → IECVar etc. + * Resolves ambiguity when assigning between different IECVar specializations + * by providing a direct match (template is preferred over two indirect paths + * that each require one user-defined conversion). */ + template::value && !std::is_same::value, int> = 0> + IECVar& operator=(const IECVar& other) noexcept { + set(static_cast(other.get())); + return *this; + } + + /** Assignment from IEC_Ptr (CODESYS: DWORD_VAR := PT stores address as integer). + * WARNING: On 64-bit platforms, assigning to types narrower than pointer width + * (e.g., DWORD) truncates the address. Use ULINT, LWORD, or PTR_INT_t for + * portable pointer-to-integer storage. */ + template::value, int> = 0> + IECVar& operator=(const IEC_Ptr& ptr) noexcept { + set(static_cast(ptr.to_addr())); + return *this; + } + + /** Assignment from a raw pointer — stores the address as an integer. + * Used by the ADR(x) lowering `_TMP : __XWORD := &(x)`. Integral targets + * only; routed through uintptr_t so it is pointer-width-correct per + * target (no truncation when T is __XWORD/XWORD_t). */ + template::value, int> = 0> + IECVar& operator=(U* p) noexcept { + set(static_cast(reinterpret_cast(p))); + return *this; + } + + // ========================================================================= + // Container Access Forwarding (for array/struct types) + // ========================================================================= + + /** Forward operator-> to underlying type (struct/FB member access) */ + template::value, int> = 0> + T* operator->() noexcept { return &value_; } + + template::value, int> = 0> + const T* operator->() const noexcept { return &value_; } + + /** Forward operator[] to underlying type (1D array access) */ + template + auto operator[](Index i) noexcept -> decltype(std::declval()[i]) { + return value_[i]; + } + + template + auto operator[](Index i) const noexcept -> decltype(std::declval()[i]) { + return value_[i]; + } + + /** Forward operator() to underlying type (2D+ array access) */ + template + auto operator()(Args... args) noexcept -> decltype(std::declval()(args...)) { + return value_(args...); + } + + template + auto operator()(Args... args) const noexcept -> decltype(std::declval()(args...)) { + return value_(args...); + } + + // ========================================================================= + // Arithmetic Operators + // ========================================================================= + + IECVar& operator+=(T v) noexcept { + set(get() + v); + return *this; + } + + IECVar& operator-=(T v) noexcept { + set(get() - v); + return *this; + } + + IECVar& operator*=(T v) noexcept { + set(get() * v); + return *this; + } + + IECVar& operator/=(T v) noexcept { + set(get() / v); + return *this; + } + + IECVar& operator%=(T v) noexcept { + set(get() % v); + return *this; + } + + // Prefix increment + IECVar& operator++() noexcept { + set(get() + 1); + return *this; + } + + // Postfix increment + IECVar operator++(int) noexcept { + IECVar tmp = *this; + ++(*this); + return tmp; + } + + // Prefix decrement + IECVar& operator--() noexcept { + set(get() - 1); + return *this; + } + + // Postfix decrement + IECVar operator--(int) noexcept { + IECVar tmp = *this; + --(*this); + return tmp; + } + + // ========================================================================= + // Bitwise Operators (for bit string types) + // ========================================================================= + + IECVar& operator&=(T v) noexcept { + set(get() & v); + return *this; + } + + IECVar& operator|=(T v) noexcept { + set(get() | v); + return *this; + } + + IECVar& operator^=(T v) noexcept { + set(get() ^ v); + return *this; + } + +private: + T value_; ///< The actual value + bool forced_; ///< Whether forcing is active + T forced_value_; ///< The forced value (when forced_ is true) +}; + +// ============================================================================= +// Binary Operators +// ============================================================================= + +template +inline IECVar operator+(const IECVar& a, const IECVar& b) noexcept { + return IECVar(a.get() + b.get()); +} + +template +inline IECVar operator-(const IECVar& a, const IECVar& b) noexcept { + return IECVar(a.get() - b.get()); +} + +template +inline IECVar operator*(const IECVar& a, const IECVar& b) noexcept { + return IECVar(a.get() * b.get()); +} + +template +inline IECVar operator/(const IECVar& a, const IECVar& b) noexcept { + return IECVar(a.get() / b.get()); +} + +template::value>> +inline IECVar operator%(const IECVar& a, const IECVar& b) noexcept { + return IECVar(a.get() % b.get()); +} + +// Mixed-type arithmetic operators (IECVar op T) and (T op IECVar) +template inline IECVar operator+(const IECVar& a, T b) noexcept { return IECVar(a.get() + b); } +template inline IECVar operator+(T a, const IECVar& b) noexcept { return IECVar(a + b.get()); } +template inline IECVar operator-(const IECVar& a, T b) noexcept { return IECVar(a.get() - b); } +template inline IECVar operator-(T a, const IECVar& b) noexcept { return IECVar(a - b.get()); } +template inline IECVar operator*(const IECVar& a, T b) noexcept { return IECVar(a.get() * b); } +template inline IECVar operator*(T a, const IECVar& b) noexcept { return IECVar(a * b.get()); } +template inline IECVar operator/(const IECVar& a, T b) noexcept { return IECVar(a.get() / b); } +template inline IECVar operator/(T a, const IECVar& b) noexcept { return IECVar(a / b.get()); } +template::value>> inline IECVar operator%(const IECVar& a, T b) noexcept { return IECVar(a.get() % b); } +template::value>> inline IECVar operator%(T a, const IECVar& b) noexcept { return IECVar(a % b.get()); } + +// ============================================================================= +// Comparison Operators +// ============================================================================= + +template +inline bool operator==(const IECVar& a, const IECVar& b) noexcept { + return a.get() == b.get(); +} + +template +inline bool operator!=(const IECVar& a, const IECVar& b) noexcept { + return a.get() != b.get(); +} + +template +inline bool operator<(const IECVar& a, const IECVar& b) noexcept { + return a.get() < b.get(); +} + +template +inline bool operator>(const IECVar& a, const IECVar& b) noexcept { + return a.get() > b.get(); +} + +template +inline bool operator<=(const IECVar& a, const IECVar& b) noexcept { + return a.get() <= b.get(); +} + +template +inline bool operator>=(const IECVar& a, const IECVar& b) noexcept { + return a.get() >= b.get(); +} + +// Mixed-type comparison operators +template inline bool operator==(const IECVar& a, T b) noexcept { return a.get() == b; } +template inline bool operator==(T a, const IECVar& b) noexcept { return a == b.get(); } +template inline bool operator!=(const IECVar& a, T b) noexcept { return a.get() != b; } +template inline bool operator!=(T a, const IECVar& b) noexcept { return a != b.get(); } +template inline bool operator<(const IECVar& a, T b) noexcept { return a.get() < b; } +template inline bool operator<(T a, const IECVar& b) noexcept { return a < b.get(); } +template inline bool operator>(const IECVar& a, T b) noexcept { return a.get() > b; } +template inline bool operator>(T a, const IECVar& b) noexcept { return a > b.get(); } +template inline bool operator<=(const IECVar& a, T b) noexcept { return a.get() <= b; } +template inline bool operator<=(T a, const IECVar& b) noexcept { return a <= b.get(); } +template inline bool operator>=(const IECVar& a, T b) noexcept { return a.get() >= b; } +template inline bool operator>=(T a, const IECVar& b) noexcept { return a >= b.get(); } + +// ============================================================================= +// Bitwise Operators +// ============================================================================= + +template +inline IECVar operator&(const IECVar& a, const IECVar& b) noexcept { + return IECVar(a.get() & b.get()); +} + +template +inline IECVar operator|(const IECVar& a, const IECVar& b) noexcept { + return IECVar(a.get() | b.get()); +} + +template +inline IECVar operator^(const IECVar& a, const IECVar& b) noexcept { + return IECVar(a.get() ^ b.get()); +} + +// Mixed-type bitwise operators +template inline IECVar operator&(const IECVar& a, T b) noexcept { return IECVar(a.get() & b); } +template inline IECVar operator&(T a, const IECVar& b) noexcept { return IECVar(a & b.get()); } +template inline IECVar operator|(const IECVar& a, T b) noexcept { return IECVar(a.get() | b); } +template inline IECVar operator|(T a, const IECVar& b) noexcept { return IECVar(a | b.get()); } +template inline IECVar operator^(const IECVar& a, T b) noexcept { return IECVar(a.get() ^ b); } +template inline IECVar operator^(T a, const IECVar& b) noexcept { return IECVar(a ^ b.get()); } + +template +inline IECVar operator~(const IECVar& a) noexcept { + return IECVar(~a.get()); +} + +// ============================================================================= +// IEC Type Aliases with Forcing Support +// ============================================================================= + +// Boolean +using IEC_BOOL = IECVar; + +// Bit strings +using IEC_BYTE = IECVar; +using IEC_WORD = IECVar; +using IEC_DWORD = IECVar; +using IEC_LWORD = IECVar; +// CODESYS __XWORD — pointer-width unsigned (see XWORD_t in iec_types.hpp). +using IEC_XWORD = IECVar; + +// Signed integers +using IEC_SINT = IECVar; +using IEC_INT = IECVar; +using IEC_DINT = IECVar; +using IEC_LINT = IECVar; + +// Unsigned integers +using IEC_USINT = IECVar; +using IEC_UINT = IECVar; +using IEC_UDINT = IECVar; +using IEC_ULINT = IECVar; + +// Real numbers +using IEC_REAL = IECVar; +using IEC_LREAL = IECVar; + +// Time types +using IEC_TIME = IECVar; +using IEC_DATE = IECVar; +using IEC_TOD = IECVar; +using IEC_DT = IECVar; + +// IEC v3 Long time types +using IEC_LTIME = IECVar; +using IEC_LDATE = IECVar; +using IEC_LTOD = IECVar; +using IEC_LDT = IECVar; + +// Character types +using IEC_CHAR = IECVar; +using IEC_WCHAR = IECVar; + +// Aliases for compatibility +using IEC_TIME_OF_DAY = IEC_TOD; +using IEC_DATE_AND_TIME = IEC_DT; +using IEC_LONG_TIME_OF_DAY = IEC_LTOD; +using IEC_LONG_DATE_AND_TIME = IEC_LDT; + +} // namespace strucpp diff --git a/03-plc/as-built/strucpp_runtime/include/iec_wstring.hpp b/03-plc/as-built/strucpp_runtime/include/iec_wstring.hpp new file mode 100644 index 0000000..c9ad242 --- /dev/null +++ b/03-plc/as-built/strucpp_runtime/include/iec_wstring.hpp @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception +// Copyright (C) 2025 Autonomy / OpenPLC Project +// This file is part of the STruC++ Runtime Library and is covered by the +// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details. +/** + * STruC++ Runtime - IEC Wide String Types + * + * This header provides the IEC 61131-3 WSTRING type as a fixed-length wide string template. + * WSTRING[n] represents a wide string with maximum length n (default 254 per IEC standard). + * Uses char16_t (UTF-16) for wide character storage. + * The implementation avoids dynamic memory allocation for real-time safety. + */ + +#pragma once + +#include +#include +#include +#include "iec_types.hpp" + +namespace strucpp { + +template +class IECWString { +public: + static constexpr size_t max_length = MaxLen; + using value_type = WCHAR_t; + using size_type = size_t; + + constexpr IECWString() noexcept : length_(0) { + data_[0] = u'\0'; + } + + IECWString(const char16_t* str) noexcept : length_(0) { + if (str) { + size_t len = 0; + while (str[len] != u'\0' && len < MaxLen) ++len; + length_ = static_cast(len); + for (size_t i = 0; i < length_; ++i) { + data_[i] = str[i]; + } + } + data_[length_] = u'\0'; + } + + IECWString(const char16_t* str, size_t len) noexcept { + length_ = static_cast(len < MaxLen ? len : MaxLen); + for (size_t i = 0; i < length_; ++i) { + data_[i] = str[i]; + } + data_[length_] = u'\0'; + } + + template + IECWString(const IECWString& other) noexcept { + length_ = static_cast(other.length() < MaxLen ? other.length() : MaxLen); + for (size_t i = 0; i < length_; ++i) { + data_[i] = other[i]; + } + data_[length_] = u'\0'; + } + + IECWString(const IECWString&) = default; + IECWString(IECWString&&) = default; + IECWString& operator=(const IECWString&) = default; + IECWString& operator=(IECWString&&) = default; + + IECWString& operator=(const char16_t* str) noexcept { + if (str) { + size_t len = 0; + while (str[len] != u'\0' && len < MaxLen) ++len; + length_ = static_cast(len); + for (size_t i = 0; i < length_; ++i) { + data_[i] = str[i]; + } + } else { + length_ = 0; + } + data_[length_] = u'\0'; + return *this; + } + + template + IECWString& operator=(const IECWString& other) noexcept { + length_ = static_cast(other.length() < MaxLen ? other.length() : MaxLen); + for (size_t i = 0; i < length_; ++i) { + data_[i] = other[i]; + } + data_[length_] = u'\0'; + return *this; + } + + constexpr size_t length() const noexcept { return length_; } + constexpr size_t size() const noexcept { return length_; } + constexpr size_t capacity() const noexcept { return MaxLen; } + constexpr bool empty() const noexcept { return length_ == 0; } + + const char16_t* c_str() const noexcept { return data_; } + const char16_t* data() const noexcept { return data_; } + char16_t* data() noexcept { return data_; } + + char16_t operator[](size_t index) const noexcept { + return index < length_ ? data_[index] : u'\0'; + } + + char16_t& operator[](size_t index) noexcept { + return data_[index < length_ ? index : length_]; + } + + char16_t at(size_t index) const noexcept { + return index < length_ ? data_[index] : u'\0'; + } + + void clear() noexcept { + length_ = 0; + data_[0] = u'\0'; + } + + void resize(size_t new_len) noexcept { + if (new_len > MaxLen) new_len = MaxLen; + if (new_len > length_) { + for (size_t i = length_; i < new_len; ++i) { + data_[i] = u' '; + } + } + length_ = static_cast(new_len); + data_[length_] = u'\0'; + } + + template + IECWString& append(const IECWString& other) noexcept { + size_t copy_len = other.length(); + if (length_ + copy_len > MaxLen) { + copy_len = MaxLen - length_; + } + for (size_t i = 0; i < copy_len; ++i) { + data_[length_ + i] = other[i]; + } + length_ += static_cast(copy_len); + data_[length_] = u'\0'; + return *this; + } + + IECWString& append(const char16_t* str) noexcept { + if (str) { + size_t str_len = 0; + while (str[str_len] != u'\0') ++str_len; + size_t copy_len = str_len; + if (length_ + copy_len > MaxLen) { + copy_len = MaxLen - length_; + } + for (size_t i = 0; i < copy_len; ++i) { + data_[length_ + i] = str[i]; + } + length_ += static_cast(copy_len); + data_[length_] = u'\0'; + } + return *this; + } + + IECWString& append(char16_t c) noexcept { + if (length_ < MaxLen) { + data_[length_++] = c; + data_[length_] = u'\0'; + } + return *this; + } + + template + IECWString operator+(const IECWString& other) const noexcept { + IECWString result(*this); + result.append(other); + return result; + } + + IECWString operator+(const char16_t* str) const noexcept { + IECWString result(*this); + result.append(str); + return result; + } + + template + IECWString& operator+=(const IECWString& other) noexcept { + return append(other); + } + + IECWString& operator+=(const char16_t* str) noexcept { + return append(str); + } + + IECWString& operator+=(char16_t c) noexcept { + return append(c); + } + + template + bool operator==(const IECWString& other) const noexcept { + if (length_ != other.length()) return false; + for (size_t i = 0; i < length_; ++i) { + if (data_[i] != other[i]) return false; + } + return true; + } + + bool operator==(const char16_t* str) const noexcept { + if (!str) return length_ == 0; + size_t i = 0; + while (i < length_ && str[i] != u'\0') { + if (data_[i] != str[i]) return false; + ++i; + } + return i == length_ && str[i] == u'\0'; + } + + template + bool operator!=(const IECWString& other) const noexcept { + return !(*this == other); + } + + bool operator!=(const char16_t* str) const noexcept { + return !(*this == str); + } + + template + bool operator<(const IECWString& other) const noexcept { + size_t min_len = length_ < other.length() ? length_ : other.length(); + for (size_t i = 0; i < min_len; ++i) { + if (data_[i] < other[i]) return true; + if (data_[i] > other[i]) return false; + } + return length_ < other.length(); + } + + template + bool operator<=(const IECWString& other) const noexcept { + return !(other < *this); + } + + template + bool operator>(const IECWString& other) const noexcept { + return other < *this; + } + + template + bool operator>=(const IECWString& other) const noexcept { + return !(*this < other); + } + + template + int compare(const IECWString& other) const noexcept { + size_t min_len = length_ < other.length() ? length_ : other.length(); + for (size_t i = 0; i < min_len; ++i) { + if (data_[i] < other[i]) return -1; + if (data_[i] > other[i]) return 1; + } + if (length_ < other.length()) return -1; + if (length_ > other.length()) return 1; + return 0; + } + + template + size_t find(const IECWString& substr, size_t pos = 0) const noexcept { + if (pos >= length_ || substr.length() == 0) return npos; + if (substr.length() > length_ - pos) return npos; + + for (size_t i = pos; i <= length_ - substr.length(); ++i) { + bool found = true; + for (size_t j = 0; j < substr.length(); ++j) { + if (data_[i + j] != substr[j]) { + found = false; + break; + } + } + if (found) return i; + } + return npos; + } + + size_t find(char16_t c, size_t pos = 0) const noexcept { + for (size_t i = pos; i < length_; ++i) { + if (data_[i] == c) return i; + } + return npos; + } + + IECWString substr(size_t pos, size_t len = npos) const noexcept { + if (pos >= length_) return IECWString(); + if (len == npos || pos + len > length_) { + len = length_ - pos; + } + return IECWString(data_ + pos, len); + } + + void replace(size_t pos, size_t len, const char16_t* str) noexcept { + if (pos >= length_) return; + if (pos + len > length_) len = length_ - pos; + + size_t str_len = 0; + if (str) { + while (str[str_len] != u'\0') ++str_len; + } + + size_t new_len = length_ - len + str_len; + if (new_len > MaxLen) { + str_len = MaxLen - (length_ - len); + new_len = MaxLen; + } + + if (str_len != len) { + for (size_t i = 0; i < length_ - pos - len; ++i) { + data_[pos + str_len + i] = data_[pos + len + i]; + } + } + if (str_len > 0 && str) { + for (size_t i = 0; i < str_len; ++i) { + data_[pos + i] = str[i]; + } + } + length_ = static_cast(new_len); + data_[length_] = u'\0'; + } + + void insert(size_t pos, const char16_t* str) noexcept { + if (pos > length_) pos = length_; + if (!str) return; + + size_t str_len = 0; + while (str[str_len] != u'\0') ++str_len; + + if (length_ + str_len > MaxLen) { + str_len = MaxLen - length_; + } + + for (size_t i = length_ - pos; i > 0; --i) { + data_[pos + str_len + i - 1] = data_[pos + i - 1]; + } + for (size_t i = 0; i < str_len; ++i) { + data_[pos + i] = str[i]; + } + length_ += static_cast(str_len); + data_[length_] = u'\0'; + } + + void erase(size_t pos, size_t len = npos) noexcept { + if (pos >= length_) return; + if (len == npos || pos + len > length_) { + len = length_ - pos; + } + for (size_t i = 0; i < length_ - pos - len; ++i) { + data_[pos + i] = data_[pos + len + i]; + } + length_ -= static_cast(len); + data_[length_] = u'\0'; + } + + static constexpr size_t npos = static_cast(-1); + +private: + char16_t data_[MaxLen + 1]; + uint16_t length_; +}; + +using WSTRING = IECWString<254>; + +template +class IECWStringVar { +public: + using value_type = IECWString; + + IECWStringVar() noexcept : value_{}, forced_{false}, forced_value_{} {} + IECWStringVar(const value_type& v) noexcept : value_{v}, forced_{false}, forced_value_{} {} + IECWStringVar(const char16_t* str) noexcept : value_{str}, forced_{false}, forced_value_{} {} + IECWStringVar(const IECWStringVar&) = default; + IECWStringVar(IECWStringVar&&) = default; + IECWStringVar& operator=(const IECWStringVar&) = default; + IECWStringVar& operator=(IECWStringVar&&) = default; + + // Cross-size assignment (IEC 61131-3: WSTRING types are interoperable, truncation on overflow) + template + IECWStringVar& operator=(const IECWStringVar& other) noexcept { + value_ = IECWString(other.get().c_str()); + return *this; + } + + value_type get() const noexcept { + return forced_ ? forced_value_ : value_; + } + + void set(const value_type& v) noexcept { + value_ = v; + } + + void set(const char16_t* str) noexcept { + value_ = str; + } + + value_type get_underlying() const noexcept { + return value_; + } + + void force(const value_type& v) noexcept { + forced_ = true; + forced_value_ = v; + } + + void force(const char16_t* str) noexcept { + forced_ = true; + forced_value_ = str; + } + + void unforce() noexcept { + forced_ = false; + } + + bool is_forced() const noexcept { + return forced_; + } + + value_type get_forced_value() const noexcept { + return forced_value_; + } + + operator value_type() const noexcept { + return get(); + } + + IECWStringVar& operator=(const value_type& v) noexcept { + set(v); + return *this; + } + + IECWStringVar& operator=(const char16_t* str) noexcept { + set(str); + return *this; + } + + // Read-through proxies into the inner IECWString. Mirror of the + // IECStringVar proxy set — see `iec_string.hpp` for the design + // rationale, including why comparison operators are intentionally + // NOT proxied (free-function `==` / `!=` overloads already cover + // every cross-class compare path, and adding member operators + // would risk overload ambiguity at ST call sites). + constexpr size_t length() const noexcept { + return (forced_ ? forced_value_ : value_).length(); + } + const char16_t* c_str() const noexcept { + return (forced_ ? forced_value_ : value_).c_str(); + } + char16_t operator[](size_t index) const noexcept { + return (forced_ ? forced_value_ : value_)[index]; + } + +private: + value_type value_; + bool forced_; + value_type forced_value_; +}; + +using WSTRING_VAR = IECWStringVar<254>; + +// Non-template alias for codegen: IEC_WSTRING = IECWStringVar<254> +// For parameterized WSTRING(N), codegen emits IECWStringVar directly +using IEC_WSTRING = IECWStringVar<254>; + +template +inline size_t WLEN(const IECWString& s) noexcept { + return s.length(); +} + +template +inline IECWString WLEFT(const IECWString& s, size_t len) noexcept { + return s.substr(0, len); +} + +template +inline IECWString WRIGHT(const IECWString& s, size_t len) noexcept { + if (len >= s.length()) return s; + return s.substr(s.length() - len, len); +} + +template +inline IECWString WMID(const IECWString& s, size_t pos, size_t len) noexcept { + if (pos == 0) return IECWString(); + return s.substr(pos - 1, len); +} + +template +inline IECWString<(MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2)> +WCONCAT(const IECWString& s1, const IECWString& s2) noexcept { + constexpr size_t ResultLen = MaxLen1 > MaxLen2 ? MaxLen1 : MaxLen2; + IECWString result(s1); + result.append(s2); + return result; +} + +template +inline IECWString WINSERT(const IECWString& s1, const IECWString& s2, size_t pos) noexcept { + IECWString result(s1); + if (pos == 0) pos = 1; + result.insert(pos - 1, s2.c_str()); + return result; +} + +template +inline IECWString WDELETE(const IECWString& s, size_t len, size_t pos) noexcept { + IECWString result(s); + if (pos == 0) pos = 1; + result.erase(pos - 1, len); + return result; +} + +template +inline IECWString WREPLACE(const IECWString& s1, const IECWString& s2, size_t len, size_t pos) noexcept { + IECWString result(s1); + if (pos == 0) pos = 1; + result.replace(pos - 1, len, s2.c_str()); + return result; +} + +template +inline size_t WFIND(const IECWString& s1, const IECWString& s2) noexcept { + size_t pos = s1.find(s2); + return pos == IECWString::npos ? 0 : pos + 1; +} + +template +inline bool GT_WSTRING(const IECWString& s1, const IECWString& s2) noexcept { + return s1 > s2; +} + +template +inline bool GE_WSTRING(const IECWString& s1, const IECWString& s2) noexcept { + return s1 >= s2; +} + +template +inline bool EQ_WSTRING(const IECWString& s1, const IECWString& s2) noexcept { + return s1 == s2; +} + +template +inline bool LE_WSTRING(const IECWString& s1, const IECWString& s2) noexcept { + return s1 <= s2; +} + +template +inline bool LT_WSTRING(const IECWString& s1, const IECWString& s2) noexcept { + return s1 < s2; +} + +template +inline bool NE_WSTRING(const IECWString& s1, const IECWString& s2) noexcept { + return s1 != s2; +} + +} // namespace strucpp