From ba8df075c37bb0178d8802902521c0323caf9156 Mon Sep 17 00:00:00 2001 From: Jonas Bengtsson Date: Sat, 3 Oct 2026 12:05:38 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20Add=20feature=20requests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 46 ++++++- docs/spec/software-spec.md | 249 ++++++++++++++++++++++++++++++++++--- 2 files changed, 269 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 9dbd55a..1cc3592 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,13 @@ a virtual node whose I/O behavior is driven by scripted scenarios. with `DI0.1`-style addresses, toggle/force/pulse/ramp actions, watchdog/statistics/diagnostics views with fault injection, and multi-client consistency for collaborative FAT benches. +- **Runtime device configuration** *(requested, planned — spec §11)*: + WBM/DIP-switch parity — module "plug/unplug" at runtime with + process-image rebuild (SAVE CONFIG / KBUS `0x102B` semantics), + address-selection DIP-switch emulation (host ID 1–254, 0 = stored + config, 255 = DHCP), WBM-equivalent pages (Port/Ethernet, Modbus + settings, Modbus Mapping, I/O Config), and device-state persistence + with factory reset. - **Long-duration (continuous) simulation** *(requested, planned — see `docs/spec/software-spec.md` §8)*: endurance/soak testing over days or weeks of simulated time. Designated inputs are driven from streaming @@ -176,13 +183,27 @@ requirement ID for each item in parentheses. ### Phase 3 — Long-duration (continuous) simulation (spec §8) The soak-testing feature requested 2026-10-03 — specified in §8, -not yet implemented: +not yet implemented. Datasets are **optional**: signal logic +(plugins, scheduled steps, degradation) runs in parallel with CSV +streaming, or entirely without it. - [ ] `scenario/datasets.py` — streaming CSV playback for designated - inputs: header-driven time column, `interpolation: linear|hold`, - `repeat` with seamless seams, `time_factor` acceleration, - `until` limits, bounded memory, ERROR verdict with file/line on - malformed data (FR-8.2.1–8.2.11). + inputs (OPTIONAL driver): header-driven time column, + `interpolation: linear|hold`, `repeat` with seamless seams, + `time_factor` acceleration, `until` limits, bounded memory, ERROR + verdict with file/line on malformed data (FR-8.2.1–8.2.11). +- [ ] Parallel logic pipeline — fixed per-tick order: datasets → edge + detection → plant plugins → degradation → invariants/rate + limits → scheduled steps → snapshots; plugins may read + dataset-driven and logic-driven aliases in the same tick + (FR-8.2.12). +- [ ] Logic-only continuous runs — no `datasets:` needed: demand + profiles generated inside plugins, top-level `until:` bounds the + run; degradation/invariants/rate limits/snapshots apply + identically (FR-8.2.13). +- [ ] Continuous-mode run-end + validation rules: run ends when all + drivers exhausted; scenario with no datasets, no scheduled steps + and no `until:` rejected (FR-8.2.1). - [ ] `scenario/degradation.py` — wear counters (runtime hours, start cycles, load-hours) and degradation models (`linear`, `exponential`, `staircase`, plugin shapes) computing plant @@ -206,7 +227,8 @@ not yet implemented: - [ ] Acceptance tests per spec §8.8 (streaming 100k-row CSV, interpolation checks, repeat determinism, time_factor math, alias-ownership validation, degradation/recovery round-trip, - episode recording, full 28-day soak). + episode recording, full 28-day soak, logic-only run, mixed + dataset+logic tick-order run). ### Phase 4 — Reporting (spec §11) @@ -229,6 +251,18 @@ not yet implemented: view (FR-10.2.6), multi-client consistency (FR-10.2.7), optional read-only/access control (FR-10.2.8), alias-ownership protection during scenario runs (FR-10.2.9). +- [ ] **Runtime device configuration (§11)**: module hot-configuration + (plug/unplug at slot positions) with process-image rebuild per + KBUS/SAVE-CONFIG semantics and updated `0x1022–0x1025` / + `0x2030+` registers (FR-11.2.1–11.2.2); alias revalidation after + rebuilds with position-stable module addressing (FR-11.2.3); + rejection during running scenarios (FR-11.2.4); + address-selection DIP-switch emulation with the manual's exact + coding (1–254 host ID, 0 = stored config, 255 = DHCP, + restart-to-apply) (FR-11.3.1–11.3.4); WBM-equivalent pages — + Port/Ethernet, Modbus settings, Modbus Mapping, I/O Config with + SAVE CONFIG (FR-11.4.1–11.4.4); device-state persistence and + factory reset via `0x2043` (FR-11.5.1–11.5.2). ### Phase 6 — CI integration (spec §11) diff --git a/docs/spec/software-spec.md b/docs/spec/software-spec.md index a2bffea..94df265 100644 --- a/docs/spec/software-spec.md +++ b/docs/spec/software-spec.md @@ -5,9 +5,9 @@ | | | |---|---| | Project | `wago-modbustcp-simulator-py` | -| Version | 1.1 (Draft for review) | +| Version | 1.2 (Draft for review) | | Date | 2026-10-03 | -| Status | Framework phase; module addressing implemented (FR-4.3.11); long-duration simulation (§8), bench mode (§9) and web UI (§10) requested | +| Status | Framework phase; module addressing implemented (FR-4.3.11); long-duration simulation (§8), bench mode (§9), web UI (§10) and runtime device configuration (§11) requested | | Target device simulated | WAGO 750-362 / 750-362/0000-0001 FC Modbus TCP (G4) bus coupler | | Reference documents | `docs/m07500362_xxxxxxxx_0en.pdf` (750-362 Modbus TCP manual), Modbus TCP specification (modbus.org) | @@ -471,24 +471,40 @@ additionally be proven **over long time**: - **Clock edge cases**: midnight rollovers and (via absolute-timestamp datasets) DST transitions. Long runs cannot be hand-scripted step by step. Two mechanisms replace the -scripted `sequence`: +scripted `sequence`, and both run **in parallel on the same sim-time axis**: -1. **Dataset playback** — designated inputs are driven from time-series files - (e.g., CSV exported from a plant historian, or synthesized profiles). -2. **Degradation models** — plant-model parameters (e.g., pump capacity) - degrade as a function of runtime and cycles, exercising the PLC’s +1. **Dataset playback** *(optional)* — designated inputs are driven from + time-series files (e.g., CSV exported from a plant historian, or + synthesized profiles). +2. **Signal logic** — plant-model plugins (FR-4.3.8) compute endogenous + signals and closed-loop physics in Python, scheduled sequence steps + (`at:`) perform discrete actions, and degradation models age plant + parameters (e.g., pump capacity) from wear, exercising the PLC’s compensation and maintenance logic. +Datasets are **not required**: a continuous run may be driven entirely by +logic (plugin-generated demand profiles, scheduled events, degradation), +entirely by datasets, or by any mix — some aliases fed from CSV while +others are computed by logic, in the same run. The signal logic works +unchanged whether or not CSV streaming is used. + ### 8.2 Requirements — continuous mode & CSV dataset playback - FR-8.2.1: A scenario may declare `mode: continuous`. The `sequence` becomes - optional; the run continues until the dataset schedule (plus any `until:` - duration) is exhausted. -- FR-8.2.2: Input aliases (di/ai) may be driven from **datasets**. Output - aliases must not appear in datasets (validation error). An endogenous process - variable (e.g., tank level) may instead be computed by a plant-model plugin - (FR-4.3.8, closed loop). **Exactly one driver** may own an alias (dataset, - plugin-declared variable, ramp, pulse, or step) — conflicts are validation errors. + optional. The run continues until **all active drivers are exhausted**: + every dataset finished (or looped to its `repeat` limit), all scheduled + steps executed, and — if a top-level `until: ` is given — the + duration elapsed. With no datasets, `until:` is the primary run-length + control. A continuous scenario with neither datasets, nor scheduled + steps, nor `until:` is a validation error (it would run forever). +- FR-8.2.2: Input aliases (di/ai) may be driven from **datasets**, which are + entirely **optional**. Output aliases must not appear in datasets + (validation error). Aliases not owned by a dataset are **logic-driven**: + computed by a plant-model plugin (FR-4.3.8, closed loop), set by + scheduled steps, or left at their current value. An endogenous process + variable (e.g., tank level) is computed by the plant model. **Exactly one + driver** may own an alias (dataset, plugin-declared variable, ramp, + pulse, or step) — conflicts are validation errors. - FR-8.2.3: CSV format: a header row with the time column (default `time`, configurable via `time_column`) plus one column per alias. Time is elapsed seconds (default) or ISO-8601 absolute timestamps (`time_format: iso8601`). @@ -516,6 +532,24 @@ scripted `sequence`: - FR-8.2.11: Malformed data (unknown column, unparsable time or value, non-monotonic timestamps) ends the run with verdict **ERROR**, citing file and line number. +- FR-8.2.12: **Parallel logic execution**: datasets and signal logic run in + the same deterministic tick pipeline, in a fixed order per tick: + (1) datasets apply their sampled/interpolated values to their aliases, + (2) alias edges are detected, + (3) plant-model plugins run — they may read dataset-driven AND + logic-driven aliases and compute endogenous variables in the same tick, + (4) degradation models update, + (5) invariants and rate limits evaluate, + (6) scheduled steps due at this tick execute, + (7) snapshots are taken if due. + This order is part of the specification and shall be covered by tests. +- FR-8.2.13: **Logic-only continuous runs**: with no `datasets:` block at + all, continuous mode shall work unchanged — demand profiles and other + exogenous signals may be generated *inside* plant-model plugins (e.g., a + daily sinusoid or a state-based profile computed in Python), with + top-level `until:` bounding the run. All §8.3–8.4 machinery (degradation, + invariants, rate limits, snapshots) applies identically, whether or not + any CSV streaming is configured. ### 8.3 Requirements — degradation models (plant logic over time) @@ -653,20 +687,50 @@ time,start_button,em_stop_ok,inflow_valve 440000,OFF,ON,ON ``` +**Variant without datasets** (logic-only, same soak): drop the +`datasets:` block entirely, keep everything else, and let the plant +plugin *also* generate the demand signal each tick: + +```yaml +name: pump_station_soak_28d_logic +mode: continuous +until: 28d # no datasets: until bounds the run +inputs: ... # same aliases — none dataset-owned +outputs: ... +plugins: [plugins.pump_plant.pump_plant] + # plugin computes, per tick (FR-8.2.12 step 3): + # inflow_rate = 12 + 3*sin(2*pi*t/24h) + # d(level)/dt = inflow - pump_flow(speed, capacity) +degradation: ... # identical to the dataset version +invariants: ... +rate_limits: ... +report: { snapshot_every: 1h } +``` + +Both variants test the same PLC behavior; the CSV version replays recorded +demand, the logic version synthesizes it. Mixing is equally valid — e.g. +`inflow_rate` from a dataset while `start_button` is toggled by scheduled +`at:` steps, with the plant plugin reading both (FR-8.2.12). + ### 8.6 Architecture impact (planned modules) ``` src/wago_sim/scenario/ -├── datasets.py # CsvDataset: streaming reader, interpolation, repeat, time_factor +├── datasets.py # CsvDataset: streaming reader, interpolation, repeat, +│ # time_factor — OPTIONAL driver in continuous mode ├── degradation.py # WearCounters, DegradationModel registry, maintenance actions ├── invariants.py # invariant & rate-limit evaluation, episode aggregation -└── engine.py # mode: continuous, parameter() accessor, snapshot scheduler +└── engine.py # mode: continuous, parameter() accessor, snapshot + # scheduler; fixed tick pipeline per FR-8.2.12: + # datasets -> edges -> plugins -> degradation -> + # invariants -> scheduled steps -> snapshots ``` ```mermaid erDiagram - ContinuousScenario ||--|{ Dataset : datasets + ContinuousScenario ||--o{ Dataset : "datasets (optional)" Dataset ||--|{ ColumnMapping : "alias columns" + ContinuousScenario ||--|{ PlantPlugin : "signal logic (always runs)" ContinuousScenario ||--|{ DegradationModel : degradation DegradationModel ||--|{ WearCounter : counts WearCounter }o--|| Alias : observes @@ -675,6 +739,7 @@ erDiagram ContinuousScenario ||--|{ RateLimit : rate_limits ContinuousScenario ||--o{ Snapshot : "periodic dumps" PlantPlugin }o--|| DegradationModel : "reads parameters" + PlantPlugin }o--o{ Alias : "reads dataset-driven + computes logic-driven" ``` ### 8.7 Non-functional requirements (long-duration) @@ -711,6 +776,15 @@ erDiagram regardless of duration; a rate-limit breach fails the run. 8. A full 28-sim-day soak completes headless with a bounded report and a correct PASS/FAIL/ERROR verdict. +9. A continuous run with **no datasets** (plugin-generated demand, + top-level `until:`) completes with degradation, invariants, rate limits + and snapshots all active, and a correct verdict (FR-8.2.13). +10. A **mixed run** — one alias driven by a dataset, another computed by a + plugin in the same run — is supported; the plugin observes the dataset + value applied earlier in the same tick (FR-8.2.12 order), verified by a + tick-order test. +11. A continuous scenario with no datasets, no scheduled steps, and no + `until:` is rejected at validation time (FR-8.2.1). --- @@ -824,6 +898,10 @@ Terminal monitors (§9) suffice for single engineers, but FAT benches are - FR-10.2.9: The UI must not disturb deterministic scenario runs: when a scenario owns an alias, UI writes to it are rejected with a clear message (alias-ownership rules of §8 FR-8.2.2 apply). +- FR-10.2.10: The UI's **configuration** pages (Ethernet/port settings, + Modbus watchdog settings, I/O Config with SAVE CONFIG, Modbus Mapping + overview, address-selection DIP switch) are specified in §11; §10 covers + value/state oversight and manipulation. ### 10.3 Architecture sketch (planned) @@ -856,19 +934,150 @@ server, so a UI write and a Modbus read never race on the datastore. --- -## 11. Roadmap (post-framework) +## 11. Feature Request — Runtime Device Configuration (WBM/DIP-Switch Parity) + +> **Status: requested 2026-10-03, planned (roadmap phase 5), not yet +> implemented.** Mirrors the real 750-362's configuration mechanisms so +> the FAT bench can commission the node the way a real one is +> commissioned. + +### 11.1 Motivation + +On the real coupler the *node* is configured through two mechanisms +(manual §4.4.2, §8.3, §9.11–9.18): + +1. **Web-Based Management (WBM)** pages: Ethernet/port settings (IP + address, subnet, gateway, DHCP/BootP/EEPROM source, base IP), Modbus + settings (connection idle timeout, Modbus watchdog type/timeout/trigger + masks, keepalive), Modbus Mapping (per-module register/coil address map, + read-only), and I/O Config (connected module list + **SAVE CONFIG**, + which re-reads the terminal bus). +2. **Address-selection DIP switch** (8-pole): binary host ID 1–254 = last + IP octet (base network 192.168.1.0/24 in delivery state); 0 = use the + stored WBM port configuration; 255 = DHCP. Changing it requires a + restart to take effect. + +The I/O modules themselves are **not** configured via WBM or DIP switch — +they are physically plugged/unplugged, and the coupler re-reads the +terminal bus (SAVE CONFIG / KBUS reset, register `0x102B`), rebuilding +the process image. The simulator equivalent of "plugging" a module is a +**runtime module-list change with image rebuild**. + +Today the simulator freezes all of this at startup: module list from +YAML, watchdog only via Modbus registers, no DIP switch, no WBM pages. +This feature request closes that gap. + +### 11.2 Requirements — runtime module configuration ("plug/unplug") + +- FR-11.2.1: The node's module list shall be **mutable at runtime** via + the web UI (§10), the console monitor (§9), and a programmatic API: + insert/remove modules at slot positions, replicating physical + plug/unplug on the local bus. +- FR-11.2.2: After every change the process image is **rebuilt exactly + like the real device after a KBUS reset** (`0x102B` or WBM SAVE + CONFIG): word modules first in physical order, then bit modules + byte-packed (§7.3.2.1); image sizes updated in `0x1022–0x1025`; + module descriptions updated in `0x2030+` so a polling master sees the + new configuration. +- FR-11.2.3: Scenario aliases are **revalidated** after a rebuild. + Module-based addresses (FR-4.3.11) are position-stable — `DI2.1` stays + on module 2 when earlier slots change — while pool-based indexes may + shift; aliases orphaned by a removal are reported as configuration + errors. This difference shall be documented and covered by tests. +- FR-11.2.4: While a scenario is running, module reconfiguration is + rejected with a clear message (consistent with FR-10.2.9) — deterministic + runs must not be disturbed. In bench mode (§9) it is always allowed. +- FR-11.2.5: A reconfiguration event is logged (sim-time, change + description, new image sizes) and visible in the web UI's I/O Config + page, mirroring the WBM SAVE CONFIG flow. + +### 11.3 Requirements — address-selection DIP switch emulation + +- FR-11.3.1: An 8-pole address-selection switch shall be emulated per node + with the manual's exact coding (§4.4.2/§8.3.1): DIP 1 = LSB … DIP 8 = + MSB; **1–254** = host ID appended to the base network (default + 192.168.1.0/24, changeable); **0** = use the stored port configuration + (WBM port settings); **255** = DHCP (virtual: an address from the + configured DHCP pool is reported). +- FR-11.3.2: The switch shall be visual and operable in the web UI + (toggle poles) and settable in the console/config; the **derived virtual + IP address** is displayed and introspectable via the API. +- FR-11.3.3: **Apply semantics match the device**: a switch change marks + the node "restart required"; on apply (explicit restart command or + `serve` restart) the simulator rebinds its listen address to the + derived IP when running in dedicated-IP mode, or updates the virtual + IP model in shared-IP mode. Outputs and connections behave like a + device restart. +- FR-11.3.4: The stored port configuration (base network, static IP for + switch position 0, DHCP pool) is part of the device state (FR-11.5.1). + +### 11.4 Requirements — WBM-equivalent configuration pages (web UI) + +- FR-11.4.1: **Port/Ethernet page**: virtual IP settings — configuration + source (static/EEPROM, DHCP, BootP), IP/subnet/gateway, base network for + DIP addressing — persisted as device state and reported alongside the + DIP-derived address. +- FR-11.4.2: **Modbus settings page**: connection idle timeout + (`0x1030`), Modbus watchdog fields (type Standard/Alternative, timeout, + trigger masks FC1–16/FC17–32, close-socket-on-timeout) — the same + fields the real WBM exposes (manual §9.16), mapped 1:1 onto registers + `0x1000–0x100B`. Writes obey the register rules (e.g., timeout not + changeable while the watchdog is active). +- FR-11.4.3: **Modbus Mapping page**: per-module address map generated + from the node layout — word addresses (input RO, output WO, read-back + RW) and bit addresses (input, output, read-back) in the WBM's + `[.]`–`[.]` notation (manual §9.17), plus the general + area overview of §3.3. Read-only; auto-updates after FR-11.2.2 rebuilds. +- FR-11.4.4: **I/O Config page**: module list (position, article, type, + channel count) + **SAVE CONFIG** button = re-read/rebuild per + FR-11.2.2, mirroring WBM §9.18. + +### 11.5 Requirements — persistence & reset + +- FR-11.5.1: Device state (module list changes, DIP switch position, + port configuration, watchdog settings written as remanent per `0x100B`) + persists to a device-state file and is reloaded on start — the + simulator's equivalent of the device's EEPROM. +- FR-11.5.2: **Factory reset** (register `0x2043` write, CLI flag, web UI + button): restores delivery state — module list from the node YAML, DIP + switch to 0, watchdog defaults, cleared statistics — and persists that + as the new device state. + +### 11.6 Acceptance criteria + +1. Removing a module and issuing SAVE CONFIG (or `0x102B`) rebuilds the + image: `0x1022–0x1025` and `0x2030+` reflect the change and a pymodbus + master reading process data sees the new layout. +2. Inserting a module before existing ones leaves module-addressed + aliases (`DI2.1`) pointing at the same module, while pool-indexed + aliases shift by the inserted channel count — asserted by tests. +3. Aliases orphaned by a module removal are reported as errors, not + silently misaddressed. +4. Reconfiguration during a running scenario is rejected; in bench mode + it succeeds and is logged. +5. DIP switch 00001001 (33) derives virtual IP 192.168.1.33; switch 0 + shows the stored port configuration; 255 reports a DHCP-pool address; + changing the switch shows "restart required" until applied. +6. Watchdog fields edited on the web settings page behave identically to + register writes (same validation, same statistics). +7. Device state survives a simulator restart; factory reset restores + delivery defaults. + +--- + +## 12. Roadmap (post-framework) | Phase | Feature | |---|---| | 2 | Module catalog expansion (analog, counter, SSI, DALI/DSI, serial modules), mailbox parameters; **bench mode tooling (§9)**: `--set` seeding, console monitor, runtime manipulation commands | | 3 | **Long-duration simulation (§8)**: CSV dataset playback, degradation models, invariants, soak-test reporting | | 4 | FAT report generator (HTML/PDF), scenario comparison, template library per project type | -| 5 | Multi-node orchestration (several couplers in one FAT bench), UDP variant, WBM simulation; **web UI (§10)** | +| 5 | Multi-node orchestration (several couplers in one FAT bench), UDP variant, WBM simulation; **web UI (§10)**; **runtime device configuration (§11)**: module hot-configuration with image rebuild, DIP-switch emulation, WBM-equivalent pages, device-state persistence | | 6 | CI integration: run scenarios headless, export JUnit XML | --- -## 12. References +## 13. References - WAGO 750-362 manual `m07500362_xxxxxxxx_0en.pdf`, sections 7.2–7.3 (process image), 11.2 (Modbus), 12.2 (module data architecture) - WAGO I/O System 750 system description `m075xxxxx_xxxxxxxx_0en.pdf`