From 55279ca78fcc09c4d22d806e7f1cff804ca36053 Mon Sep 17 00:00:00 2001 From: Clio Liu Date: Wed, 2 Sep 2026 16:48:30 +1000 Subject: [PATCH] docs(plc): as-built copy, versions, and a getting-started guide Adds what the folder was missing for someone picking it up cold. as-built/ the STruC++ output copied out of the live container - the C++ the PLC is actually executing, plus program.st and the Modbus buffer config. The running image was made with docker commit and exists in no registry, so this is the only other copy of the compiled form. Verified while copying: the deployed program.st matches build/wrps.st generated from src/. Identical POU structure, ZERO differences in non-declaration lines. src/ is genuinely canonical and the running PLC agrees with it. VERSIONS.md every version read from the running system, not from documentation: runtime v4.1.10, STruC++ 0.6.2, Editor 4.2.11, Debian 12, g++ 12.2, pymodbus 3.11.2, CI Server R1.03. Plus which plugins are enabled - ethercat is on for no reason - and a v3-vs-v4 table, since most OpenPLC guidance online is for v3 and the %MW HR1024 change silently produces wrong data. GETTING-STARTED.md three questions answered in a page: how to modify the program, how to stand up a new PLC container from scratch, how to move this one. Includes the compose file, the Editor steps that are not generated and are always missed, and a symptom-to-document table. --- 03-plc/GETTING-STARTED.md | 159 + 03-plc/README.md | 9 +- 03-plc/VERSIONS.md | 73 + 03-plc/as-built/README.md | 75 + 03-plc/as-built/c_blocks.h | 1 + 03-plc/as-built/conf/ethercat.json | 0 03-plc/as-built/conf/modbus_slave.json | 24 + 03-plc/as-built/configuration.cpp | 472 +++ 03-plc/as-built/debug-map.json | 3372 +++++++++++++++++ 03-plc/as-built/defines.h | 3 + 03-plc/as-built/generated.hpp | 910 +++++ 03-plc/as-built/generated_debug.cpp | 522 +++ 03-plc/as-built/pou_CONTROL.cpp | 432 +++ 03-plc/as-built/pou_FB_DUTY_SELECT.cpp | 76 + 03-plc/as-built/pou_FB_HEADROOM.cpp | 56 + 03-plc/as-built/pou_FB_LEVEL_CTRL.cpp | 43 + 03-plc/as-built/pou_FB_PUMP.cpp | 82 + 03-plc/as-built/pou_IO_MUX.cpp | 157 + 03-plc/as-built/pou_SIMULATION.cpp | 160 + 03-plc/as-built/program.st | 1687 +++++++++ 03-plc/as-built/program.st.map.json | 39 + .../include/debug_dispatch.hpp | 483 +++ .../strucpp_runtime/include/debug_table.hpp | 132 + .../strucpp_runtime/include/iec_array.hpp | 379 ++ .../strucpp_runtime/include/iec_char.hpp | 251 ++ .../strucpp_runtime/include/iec_date.hpp | 89 + .../strucpp_runtime/include/iec_dt.hpp | 146 + .../strucpp_runtime/include/iec_enum.hpp | 277 ++ .../strucpp_runtime/include/iec_fault.hpp | 63 + .../strucpp_runtime/include/iec_global.hpp | 111 + .../strucpp_runtime/include/iec_located.hpp | 223 ++ .../strucpp_runtime/include/iec_memory.hpp | 94 + .../strucpp_runtime/include/iec_pointer.hpp | 541 +++ .../strucpp_runtime/include/iec_ptr.hpp | 261 ++ .../strucpp_runtime/include/iec_retain.hpp | 83 + .../strucpp_runtime/include/iec_std_lib.hpp | 1501 ++++++++ .../strucpp_runtime/include/iec_string.hpp | 1202 ++++++ .../strucpp_runtime/include/iec_struct.hpp | 115 + .../strucpp_runtime/include/iec_subrange.hpp | 390 ++ .../strucpp_runtime/include/iec_time.hpp | 122 + .../strucpp_runtime/include/iec_tod.hpp | 116 + .../strucpp_runtime/include/iec_traits.hpp | 616 +++ .../strucpp_runtime/include/iec_types.hpp | 270 ++ .../strucpp_runtime/include/iec_var.hpp | 540 +++ .../strucpp_runtime/include/iec_wstring.hpp | 555 +++ 45 files changed, 16911 insertions(+), 1 deletion(-) create mode 100644 03-plc/GETTING-STARTED.md create mode 100644 03-plc/VERSIONS.md create mode 100644 03-plc/as-built/README.md create mode 100644 03-plc/as-built/c_blocks.h create mode 100644 03-plc/as-built/conf/ethercat.json create mode 100644 03-plc/as-built/conf/modbus_slave.json create mode 100644 03-plc/as-built/configuration.cpp create mode 100644 03-plc/as-built/debug-map.json create mode 100644 03-plc/as-built/defines.h create mode 100644 03-plc/as-built/generated.hpp create mode 100644 03-plc/as-built/generated_debug.cpp create mode 100644 03-plc/as-built/pou_CONTROL.cpp create mode 100644 03-plc/as-built/pou_FB_DUTY_SELECT.cpp create mode 100644 03-plc/as-built/pou_FB_HEADROOM.cpp create mode 100644 03-plc/as-built/pou_FB_LEVEL_CTRL.cpp create mode 100644 03-plc/as-built/pou_FB_PUMP.cpp create mode 100644 03-plc/as-built/pou_IO_MUX.cpp create mode 100644 03-plc/as-built/pou_SIMULATION.cpp create mode 100644 03-plc/as-built/program.st create mode 100644 03-plc/as-built/program.st.map.json create mode 100644 03-plc/as-built/strucpp_runtime/include/debug_dispatch.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/debug_table.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_array.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_char.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_date.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_dt.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_enum.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_fault.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_global.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_located.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_memory.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_pointer.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_ptr.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_retain.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_std_lib.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_string.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_struct.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_subrange.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_time.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_tod.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_traits.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_types.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_var.hpp create mode 100644 03-plc/as-built/strucpp_runtime/include/iec_wstring.hpp 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