2026-10-05 07:13:04 +02:00
2026-10-04 22:48:10 +02:00
🚧 WIP
2026-10-04 20:59:22 +02:00
🚧 WIP
2026-10-03 18:54:37 +02:00
2026-10-04 22:48:10 +02:00
2026-10-04 22:48:10 +02:00
2026-10-04 13:05:47 +02:00
2026-10-04 14:50:57 +02:00
2026-10-04 22:48:10 +02:00
2026-10-04 22:48:10 +02:00
🚧 WIP
2026-10-03 18:54:37 +02:00
2026-10-03 09:46:11 +02:00
2026-10-03 09:46:11 +02:00
🚧 WIP
2026-10-03 18:54:37 +02:00
🚧 WIP
2026-10-03 18:54:37 +02:00
🚧 Web UI
2026-10-03 23:48:16 +02:00
2026-10-04 22:48:10 +02:00
2026-10-05 07:13:04 +02:00
2026-10-05 07:13:04 +02:00
2026-10-05 07:13:04 +02:00

wago-modbustcp-simulator-py

WAGO 750-362 Modbus TCP bus coupler simulator for autonomous PLC FAT testing.

The simulator replaces a physical WAGO I/O node during Factory Acceptance Tests: the PLC under test connects via Modbus TCP and talks to a virtual node whose I/O behavior is driven by scripted scenarios.

Features

  • Faithful WAGO 750-362 register map (manual §11.2.4): process image words/bits with the exact mapped/unmapped areas and exception behavior, read-back mirrors (+0x0200 / +0x1000), configuration registers 0x1000–0x2FFF, constants 0x2000–0x2008 (byte-order check 0x1234).
  • Function codes FC1, FC2, FC3, FC4, FC5, FC6, FC11, FC15, FC16, FC22, FC23 with the device limits (2000/800 bits, 125/100 registers).
  • Modbus watchdog (0x1000–0x100B), statistics block (0x1029), response-delay injection (0x1037), module diagnosis (0x1050).
  • Virtual node built from a YAML module list (WAGO 750 catalog subset, extensible).
  • Scenario engine: YAML scenarios with aliases, macros, ramps, pulses, waits, and PASS/FAIL assertions with timeouts; state machines and Python action plugins for complex plant models.
  • Two I/O addressing schemes for aliases: module-based — { type: di, module: 2, channel: 1 } = DI2.1 (module numbered 0-based within its type — DI0, DO0, AI0, AO0 start each kind — and channel 0-based; v1.5) — and pool-based — { type: di, channel: 3 } (1-based index across the node's DI pool). Both may be mixed.
  • Bench mode (requested, planned — spec §9): run the simulator without a scenario (wago-sim serve --node ... already works); planned additions: --set DI0.1=ON startup seeding, an interactive console monitor with live I/O table, and runtime set/pulse/watch commands.
  • Web UI (requested, planned — spec §10): browser front-end to overlook and manipulate the virtual node's I/O — live channel tables 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 CSV datasets (historian exports, demand/operator profiles, seamless repeat, time_factor acceleration), while degradation models age plant parameters (e.g., pump capacity) from wear counters — exercising the PLC's compensation, maintenance-alarm and service-recovery logic. Continuous invariants and rate_limits produce bounded, episode-based reports for unattended runs.
  • ≤ 15 concurrent Modbus TCP connections, idle-timeout handling.
  • Service / container deployment — run as infrastructure: Dockerfile (non-root, health check, ~197 MB), docker-compose service (restart policy, log rotation, read-only config mounts), systemd unit example, env-var configuration (WAGO_SIM_NODE/HOST/PORT), and graceful SIGTERM shutdown (docker stop completes in < 1 s).

Install (dev)

pip install -e .[dev]

Usage

Run the Modbus TCP server against a node configuration:

wago-sim serve --node config/pump_node.yaml --port 502

Validate a scenario:

wago-sim validate --scenario scenarios/pump_station_fat.yaml

Run a scenario headless and write reports:

wago-sim run --node config/pump_node.yaml \
  --scenario scenarios/pump_station_fat.yaml \
  --report-jsonl out/report.jsonl --report-csv out/report.csv

Deployment (service / container)

The simulator runs as a long-lived service. Configuration comes from environment variables — WAGO_SIM_NODE, WAGO_SIM_HOST, WAGO_SIM_PORT — so no CLI arguments are needed in the container command.

Docker:

docker build -t wago-sim .
docker run -d --name wago-sim -p 5020:502 \
  -v ./config:/app/config:ro -v ./scenarios:/app/scenarios:ro \
  -v ./plugins:/app/plugins:ro -v ./out:/app/out \
  -e WAGO_SIM_NODE=config/pump_node.yaml \
  wago-sim

Compose (restart policy, health check, log rotation included):

docker compose up -d     # start
docker compose logs -f wago-sim
docker compose down     # graceful SIGTERM shutdown

Bare metal / VM (systemd; unit file in deploy/wago-sim.service):

sudo cp deploy/wago-sim.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now wago-sim
journalctl -u wago-sim -f

Notes:

  • Host port 5020 by default; use 502 if your bench expects it and your host allows binding privileged ports (the container runs non-root; the systemd unit grants CAP_NET_BIND_SERVICE to a dedicated user).
  • docker stop sends SIGTERM; the server closes all client connections and the listening socket cleanly (< 1 s) — useful for testing the PLC's fieldbus-failure reaction deliberately.
  • Reports from headless run invocations go to the mounted out/.

Verbose mode (request tracing)

Run with -v / --verbose (or WAGO_SIM_VERBOSE=1 for containers) to trace every Modbus transaction in plain text — with the WAGO 750-362 register-map meaning of every address annotated:

$ wago-sim serve --node config/pump_node.yaml -v

REQ  txn=0x0001 unit=1 FC03 Read Holding Registers
     addr=0x2002 count=1 [config/constant regs 0x2002]
RSP  txn=0x0001 unit=1 FC03 -> 1 regs: [0x1234]
REQ  txn=0x0002 unit=1 FC06 Write Single Register
     addr=0x0000 value=0x1234 [output area 1 (%QW0)]
RSP  txn=0x0002 unit=1 FC06 -> echo
EXC  txn=0x0006 unit=1 FC03 -> code=0x02 Illegal Data Address
RAW  txn=0x0007 unit=1 PDU[3]: 07 00 00

Traces go to the standard log at DEBUG level, so they appear in the terminal, in docker logs, and in journalctl alike. Read vs write mappings are annotated correctly per the manual's Tables 119/120 (e.g. word address 0x0000 reads as %IW0 but writes to %QW0).

Web UI

serve starts a browser UI alongside the Modbus server (spec §10):

wago-sim serve --node config/pump_node.yaml --port 5020
# Web UI on http://127.0.0.1:8080 — default login: admin / simu
  • Overview: realistic DIN-rail view of the coupler + modules with per-channel LEDs for digital I/O and bar/numeric indicators for analog values; updates live via WebSocket — including changes made by the PLC over Modbus.
  • Manipulation: click a DI/DO LED to toggle it (field side), or force DO/AO master-side; AI values can be set directly. All writes go through the simulator's internal data paths — never via Modbus TCP — while keeping read-back mirrors and statistics consistent.
  • Diagnostics: watchdog status, statistics with reset, and LED/module fault injection for FAT fault cases.

Configuration options: --no-web, --web-port <n> or env WAGO_SIM_WEB_PORT (default 8080); credentials via WAGO_SIM_WEB_USER / WAGO_SIM_WEB_PASSWORD (defaults admin / simu); WAGO_SIM_WEB_READONLY=1 for observation-only benches (all mutating endpoints answer 403). Plain HTTP — keep the port inside the bench network.

Plant environments (continuous simulation)

A plant environment simulates the physical world around the distributed I/O: per deterministic tick it reads what the PLC commanded (DO/AO), runs your plant physics, and writes what the field now shows (DI/AI) — the sensors the PLC reads back. Environments run until manually stopped (Ctrl+C / docker stop), optionally ending sooner via an is_done() criterion.

Attach one to a live Modbus server (wall-clock-paced 10 ms ticks):

wago-sim serve --node config/pump_node.yaml --port 5020 \
               --env envs.pump_station_env:PumpStationEnv

or run headless (unpaced, for plant-model development):

wago-sim sim --node config/pump_node.yaml \
             --env envs.pump_station_env:PumpStationEnv

Authoring one is a small subclass — override model():

# myenvs/tank.py
from wago_sim.scenario.plantenv import PlantEnv

class TankEnv(PlantEnv):
    def model(self, state, dt_s):
        pump_on = bool(state.by_code("DO0.0"))   # PLC command
        level   = float(state.by_code("AI0.0"))  # current field
        return {"AI0.0": level + (-5.0 if pump_on else 1.0) * dt_s}

Env = TankEnv        # `--env myenvs.tank` picks this up

API surface (spec FR-8.2.14–17): observe() returns every DI/DO/AI/AO channel (I/O codes, names, values, analog display ranges) plus aliases and degradation parameters; apply() writes DI/AI (the field); force() writes DO/AO master-side (headless runs, fault setup); is_done() optional end; reset() deterministic replay. state.channel(key) returns the full channel record by I/O code or configured name; state.channel_range(key) gives an analog channel's (min, max) directly. Reference forms: pkg.mod:ClassName, pkg.mod:factory, or pkg.mod with Env/make_env. The shipped example is envs/pump_station_env.py (tank + pump physics with a low-level switch). Scenario-continuous mode (§8 YAML: datasets, degradation, invariants) remains available for scripted soak runs on the same engine ticks.

Documentation

  • docs/spec/software-spec.md — the software specification (requirements, architecture, scenario DSL, acceptance criteria, and the long-duration simulation feature request in §8).
  • docs/m07500362_xxxxxxxx_0en.pdf — WAGO 750-362 reference manual.

Testing

pytest

TODO

Work remaining, tracked against the software specification (docs/spec/software-spec.md). Completed items are checked and grouped first; open items follow, grouped by spec phase, with the spec section or requirement ID for each item in parentheses.

Completed — framework phase (spec §3 + §4 core, 141 tests passing)

  • MBAP + PDU codecs: function codes FC1, FC2, FC3, FC4, FC5, FC6, FC11, FC15, FC16, FC22, FC23 with the 750-362 device limits (2000/800 bits, 125/100 registers) and exception codes (§3.2).
  • WAGO register map (§3.3): word/bit process-image areas with the exact mapped/unmapped ranges, illegal-address exceptions, output read-back mirrors (+0x0200 / +0x1000), digital channel-number bit addressing, constants 0x2000–0x2008 (incl. byte-order check 0x2002 = 0x1234).
  • Configuration/diagnostic/firmware registers (§3.5): image sizes 0x1022–0x1025, statistics 0x1029, connections 0x102A, idle timeout 0x1030, MAC-ID 0x1031, response delay 0x1037, module diagnosis 0x1050, LED error 0x1020/0x1021.
  • Modbus watchdog (§3.4): registers 0x1000–0x100B, Standard and Alternative modes, trigger/masks/stop sequences, expiry clears outputs, address-aware 0x04 lock in Standard mode, optional TCP close on timeout.
  • Fault-injection paths (§3.6): response-delay injection, connection refusal beyond 15, illegal-address probing responses, diagnosis/LED registers scriptable.
  • Virtual node built from a YAML module list (13-module WAGO 750 catalog subset; complex-first, bit-byte-packed image layout per manual §7.3.2.1).
  • Scenario DSL (§4): named aliases with scale/offset, macros with parameters (inline expansion, blocking waits inside macros), sequence steps set / reset / toggle / pulse / ramp / wait / assert / report, rising-edge detection on all aliases.
  • Two I/O addressing schemes for scenario aliases (FR-4.3.11): module-based ({ type: di, module: 2, channel: 1 } = DI2.1, module slot and channel 0-based) and pool-based (channel: = 1-based index within the node's kind pool); kind/module/channel validation with clear errors; both verified by tests.
  • Assertions with within: timeouts → PASS/FAIL verdicts with sim-time stamps (FR-4.3.5).
  • Python action/plant-model plugins: per-tick models and step actions (FR-4.3.8), e.g. plugins/pump_plant.py.
  • Deterministic tick-driven engine: sim-time decoupled from wall-clock (FR-4.3.9 core).
  • Service / container deployment (spec §12): env-var config (WAGO_SIM_NODE/HOST/PORT, CLI args override), graceful SIGINT/SIGTERM shutdown via server.stop(), Dockerfile (python:3.12-slim, non-root user, TCP health check), docker-compose service (restart policy, resource limits, log rotation, read-only config mounts), systemd unit example with hardening; verified end-to-end (build, Modbus round-trip, docker stop in 0.34 s); CLI env tests added.
  • CLI: wago-sim serve / run / validate.
  • Reports: console verdict summary, JSONL and CSV export (--report-jsonl / --report-csv).
  • asyncio Modbus TCP server: ≤ 15 concurrent connections, idle connection reaper, response-delay task.
  • Test suite: 178 tests incl. real-socket server tests, pymodbus interop, CLI env-config and bad-argument tests, and plain-text trace tests; spec §7 acceptance criteria 1–7 covered.
  • Clean CLI error handling: invalid arguments (unknown command/ options, non-numeric or out-of-range --port, bad WAGO_SIM_PORT), missing/unparsable node and scenario files, and unwritable report paths all produce a one-line wago-sim: error: … on stderr with exit code 2 — never a Python traceback; missing report directories are auto-created; internal errors keep the full traceback for debugging (NFR-6.4 operational goal).

Requested — multi-vendor & multi-protocol (spec §13 + §14, not yet scheduled)

  • Multi-vendor device profiles (spec §13): extract the per-device facts (function codes/limits, connection limit, address maps, mirrors, config/watchdog/statistics blocks, trace annotations) into a device profile; other WAGO 750 Modbus couplers and other brands' Modbus TCP I/O heads become data + a profile module (FR-13.2.1–13.2.7); scenario engine, DSL, plugins and reports stay device-agnostic; device: key in node config with the 750-362 as default; validate rejects unknown devices and cross-brand module mixes cleanly.
  • Additional fieldbus protocols (spec §14): protocol bindings for ABB AC800M-style benches — PROFINET IO device simulation (RT cyclic data exchange, DCP, record data; FR-14.2.2) and COMLI over TCP as used between AC800M and S800/S900 I/O via CI51x/CI522 (FR-14.2.3) — reusing the same process image, scenario engine and reporting (FR-14.2.1, FR-14.2.4); per- protocol tracing, diagnostics and fault injection (FR-14.2.5– 14.2.6); scoping questions to answer before planning (§14.3).

Web UI (spec §10) — in implementation

  • Spec §10.3–10.6 updated with binding implementation decisions: FastAPI/Uvicorn in-process on the Modbus server's asyncio loop; vanilla single-page front-end (no build step) with realistic DIN-rail graphics and per-channel LEDs; session-cookie auth with default credentials admin/simu (env-overridable, read-only mode); backend API uses only internal memory handlers (WagoDatastore, VirtualNode, registers, watchdog, statistics) — never Modbus TCP; WebSocket live updates; milestones M1–M6.
  • M1: webui package (src/wago_sim/webui/: app.py, auth.py, state.py, api.py, static SPA); session auth with scrypt hashing, lockout after 5 failed logins, read-only mode (WAGO_SIM_WEB_READONLY); GET /api/node with DI0.1-style and Modbus addresses; CLI wiring --web/--no-web, --web-port, env WAGO_SIM_WEB_USER/PASSWORD/READONLY/ NO_WEB/WEB_PORT; login page + SPA shell.
  • M2: node overview page — DIN-rail strip with coupler block (MS/NS/BF/SF LEDs), module bezels with article numbers, the kind+slot designation (DI0, DO2, AI4) in the module's lower left corner below the indicators, per-channel LEDs for DI/DO with the channel number beneath, bar+numeric indicators for AI/AO with the channel number outside the overflow/underflow limit LEDs, live values via WebSocket (/api/ws, 10 Hz delta push), GET /api/values snapshot (FR-10.2.2).
  • M3: manipulation — DI/DO toggle (field side via set_digital_input_channel, master side via internal write_bits/write_words dispatch so mirrors/statistics stay consistent), AI value entry (raw counts), AO force; unknown-channel → 404, kind-mismatch → 400, read-only → 403.
  • M4: diagnostics — watchdog status card (0x1006/0x100A), statistics table (0x1029 words) with reset, LED-error (0x1020/0x1021) and module-diagnosis (0x1050) fault injection and clear; SF LED on the coupler reflects injected LED errors.
  • M5: configuration — coupler settings forms (watchdog time/masks/type/close-on-timeout via 0x1000–0x100B, response delay 0x1037, idle timeout 0x1030) with the device's own validation surfaced (writes refused while the watchdog runs → field error); module list editor over the 13-article catalog with SAVE CONFIG → live process-image rebuild (rebuild_node: datastore survives, values clear like a KBUS reset, 0x1022–0x1025 and 0x2030+ updated) + persistence to the node-config YAML via a comment-preserving custom emitter. Modules re-orderable by drag-and-drop or ▲/▼ arrows (FR-11.2.6); with per-type I/O codes (FR-4.3.11 v1.5) cross-kind moves don't renumber other kinds' channels.
  • M5a: channel names for every module channel (FR-10.2.11) — node-config persisted (channel_names: mapping, I/O code → name), survive module saves and rebuilds, shown in the overview list/tooltips and a per-channel name editor; configuration import/export as JSON (FR-10.2.12) — modules, channel names, coupler settings in one file, atomic validated import with stale-name-code dropping; split overview (FR-10.2.13) — DIN-rail graphic on top, live scrollable channel list below with I/O code, configured name, and a switch (DI/DO) / numeric field (AI/AO) for display + manipulation.
  • M5b: analog display ranges (FR-10.2.14) — bar indicators fill from the bottom against the channel's display range, default 0–4095 counts (100 % at 4095, WAGO 750 12-bit scaling), with per-channel min/max configurable in the node config (channel_ranges:) and editable on the Configuration page; values outside the range fill the bar red with a red LED above the bar (overflow) or below it (underflow); ranges carried through module saves, export/import, and rebuilds; also fixes the bar never filling (it set width instead of height on a vertical bar) and the invisible numeric label (clipped by overflow: hidden). Signed support (2026-10-04): range bounds and simulated values may be negative (-65534–65535; stored as 16-bit two's complement), for signed plant quantities. DI/DO channels render in vertical columns of eight (0–7 left, 8–15 next) on thin, tall module bezels mirroring real DIN-rail housings. Dense store (2026-10-04): node.channel_ranges always carries every analog channel of the current layout (unconfigured = the materialized default, stale codes pruned on rebuild), so environments read min/max without fallbacks; the YAML and the config export stay lean (non-default entries only).
  • M6: scenario run view + alias-ownership rejection (FR-10.2.6, FR-10.2.9) — deferred.
  • M7: plant environment control (FR-10.2.15, FR-10.2.17) — the web UI loads/unloads environments at runtime (--env becomes the startup default, not the only option): an environment card shows ref, state (running/paused/done/none), sim-time and tick ms with start (with hot reload of edited source, sys.modules invalidation), stop (ticker cancelled, field freezes), pause/resume (instance kept — pause is a deliberate FAT fault: "plant stuck"), reset (deterministic fresh episode) and restart. Exactly one env at a time; is_done() ends the ticker (state done); SAVE CONFIG (module-list rebuild) discards the env like a KBUS reset discards values; starting while loaded is refused; readonly mode blocks every env mutation. API: GET /api/env + POST /api/env/{start,stop,pause,resume,reset,restart}.
  • M8: environment file authoring (FR-10.2.16, planned) — create/modify/delete environment source files (e.g. envs/*.py) through the web UI (editor pane + upload/download). Security note: UI-written Python is RCE by design — same trust boundary as the bench network; disabled in readonly mode; delete refused while the env is loaded; the on-disk directory must be explicit in configuration.
  • Tests: 46 web-UI tests (tests/test_webui.py) via httpx ASGITransport (in-process, no socket) + live verification: browser SAVE CONFIG rebuilds the node and persists YAML; Modbus master reads the module description (0x2030) correctly after the loader-overlap register fix; channel-name save + list sync; export/import round-trip with a Modbus read after the rebuild; bar states verified for in-range (97.7 % at 4000), overflow, custom-range, and underflow. Full suite: 224 passing.

Framework gaps (spec §4 — Scenario & Macro Engine)

  • State machines: states: with entry actions, transitions on input/output edges or conditions, and guards (FR-4.3.2) — currently only the time-ordered sequence: is implemented.
  • Actions noise, sin, hold on simulated inputs (FR-4.3.4) — set/reset/pulse/toggle/ramp are done.
  • wait_edge step is accepted but not yet blocking — implement edge-wait semantics in the engine loop.
  • Assertions on statistics registers, e.g. "watchdog expired", "BadAddress counter > 0" (FR-4.3.6).
  • Determinism guard: seed/record plugin RNG state so identical runs produce identical reports (FR-4.3.9, NFR-6.3) — needed once noise/sin or randomizing plugins exist.
  • Multi-node scenarios — several couplers in one scenario (FR-4.3.10).

Phase 2 — Module catalog expansion & bench mode (spec §9)

  • Analog module variants, counter, pulse-width, SSI, incremental encoder, serial interface, DALI/DSI modules with their process-data layouts (spec §12.2 of the WAGO manual).
  • Mailbox parameterization traffic beyond process data.
  • Bench mode tooling (§9): --set DI0.1=ON startup seeding, interactive console monitor (--monitor) with live channel table and change highlighting (FR-9.2.2–9.2.3).
  • Console runtime manipulation commands: set, pulse, watch, reset stats (FR-9.2.4).
  • serve --scenario optional combination + passive-node hint (FR-9.2.5). (Plain serve without a scenario already works — see Completed above.)

Phase 3 — Long-duration (continuous) simulation (spec §8) — IMPLEMENTED 2026-10-04

The soak-testing feature specified in §8 is implemented: continuous mode with the fixed tick pipeline, optional streaming CSV datasets, degradation models, invariants/rate limits with bounded episode reporting, snapshots, and the gym-style PlantEnv. 30 new tests (tests/test_continuous.py); the shipped example scenarios/pump_soak.example.yaml demonstrates the full DSL. Datasets are optional: signal logic runs in parallel with CSV streaming, or entirely without it.

  • Plant environment scenario/plantenv.py (FR-8.2.14–17, reworked 2026-10-04 to environment-first): standalone PlantEnv — observe() returns ALL modules (DI/DO/AI/AO channels, per-type codes, names, parameters); apply() writes the FIELD side (DI/AI) by default; force() writes DO/AO master-side (mirrors/ statistics/watchdog stay consistent); model()/is_done() override hooks (subclass authoring, FR-8.2.17); reset() deterministic. Runners: serve --env ... wall-clock-paced alongside Modbus, runs until stopped; sim --env ... headless unpaced until is_done()/Ctrl+C. Example: envs/pump_station_env.py. Verified live: PLC coil write → env physics → level registers drain/refill over Modbus.
  • scenario/datasets.py — streaming CSV playback for designated 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 parameters such as pump_capacity (FR-8.3.1–8.3.2).
  • Degradation hooks in plant plugins: engine.parameter() accessor used in loop physics, e.g. actual flow = nominal × capacity (FR-8.3.3).
  • maintenance_at thresholds: diagnosis injection via register 0x1050, maintenance-alarm assertions, maintenance_reset action restoring capacity and clearing diagnoses (FR-8.3.4–8.3.5, FR-8.3.8).
  • scenario/invariants.py — per-tick invariants with bounded episode aggregation and rate_limits over sliding one-hour windows (FR-8.4.1–8.4.2).
  • snapshot_every periodic dumps of aliases, wear counters, and degradation parameters (FR-8.4.3).
  • Continuous-mode verdict rules and bounded reporting for unattended multi-day runs (FR-8.4.4–8.4.5).
  • Scheduled sequence steps via at: <duration> interleaved with datasets on the sim-time axis (FR-8.4.6).
  • 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, logic-only run, mixed dataset+logic tick-order run).

Phase 4 — Reporting (spec §11)

  • FAT report generator (HTML/PDF) from scenario results and Modbus traces (console/JSONL/CSV export is done — see Completed above).
  • Scenario comparison (run-vs-run diff) and template library per project type.

Phase 5 — Multi-node orchestration & protocol variants (spec §11)

  • Several couplers in one FAT bench (ties into FR-4.3.10).
  • Modbus UDP variant.
  • WBM (web-based management) simulation pages.
  • Web UI (§10): browser front-end for overlooking and manipulating I/O — node/module/channel tables with DI0.1-style and Modbus addresses (FR-10.2.2), live value updates from all sources (FR-10.2.3), toggle/force/pulse/ramp manipulation through Modbus-consistent paths (FR-10.2.4), diagnostics views with fault injection and statistics reset (FR-10.2.5), scenario/run 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)

  • Headless scenario execution in CI pipelines.
  • JUnit XML export of verdicts for test dashboards.

Operational hardening (spec §6 — NFRs)

  • Plain-text request/response tracing in verbose mode (-v / WAGO_SIM_VERBOSE): every transaction logged as readable REQ/RSP/EXC/RAW lines with WAGO register-map annotations (input/output areas, %IW/%QW mapping, read-back mirrors, config/constant registers, illegal areas) per Tables 119/120 — src/wago_sim/trace.py + tests. Covers NFR-6.4's readability goal; PDU hex is included in RAW lines for malformed frames.
  • Performance budget validation: ≥ 500 requests/s, ≤ 10 ms added latency (NFR-6.1).
S
Description
No description provided
Readme
10 MiB
Languages
Python 88.9%
JavaScript 6.6%
CSS 2.7%
HTML 1.4%
Dockerfile 0.4%