83 KiB
Software Specification
WAGO Modbus TCP Simulator — Autonomous FAT Testing Framework
| Project | wago-modbustcp-simulator-py |
| Version | 1.4 (Draft for review) |
| Date | 2026-10-03 |
| Status | Framework phase; module addressing implemented (FR-4.3.11 v1.5 per-type numbering); service/container deployment implemented (§12); plain-text request tracing implemented (NFR-6.4); clean CLI error handling implemented (NFR-6.5 CLI part); web UI §10 in implementation (M1–M5a done: auth, overview, manipulation, diagnostics, configuration, channel names, import/export, split overview; M6 scenario view open); long-duration simulation (§8) implemented 2026-10-04 incl. PlantEnv (FR-8.2.14–16), datasets, degradation, invariants, snapshots; bench mode (§9), runtime device configuration (§11 — coupler/module/channel-name subset covered by web UI M5/M5a), multi-vendor device profiles (§13) and additional fieldbus protocols PROFINET/COMLI (§14) 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) |
1. Introduction
1.1 Purpose
This document specifies a software system that simulates a WAGO 750-362 Modbus TCP fieldbus coupler on a standard PC, to be used as a test bench during Factory Acceptance Tests (FAT) of PLC programs.
During FAT the PLC under test is normally wired to a real WAGO I/O node. This simulator replaces the physical node entirely: the PLC (or any Modbus TCP master/SCADA) connects over TCP/IP to the simulator, which responds exactly like a 750-362 coupler — same function codes, same register map, same exception behavior, same timing characteristics.
1.2 Problem being solved
FAT of PLC programs requires either:
- Physical I/O hardware (expensive, must be wired per project, error-prone, single-purpose), or
- A generic Modbus slave (does not implement the WAGO-specific register map, digital-channel addressing, watchdog, statistics, or module-diagnostics behavior; PLC programs validated against it may fail against a real node).
The simulator provides the exact WAGO behavior plus scriptable, autonomous scenario execution, so a complete FAT round — including fault cases — can run unattended and produce a machine-verifiable result.
1.3 Scope
In scope (v1.0 framework):
- Modbus TCP server (slave) with the 750-362 function-code set.
- WAGO 750-362 register map: process image (words + bits), configuration, diagnostic, constant, and firmware-info registers.
- Virtual I/O node: a configurable list of WAGO 750/753 I/O modules forming the process image.
- Scenario engine: scripted macros that observe input states and act on outputs (the automation core).
- Deterministic simulation clock and scheduler.
- CLI runner with configuration files.
Out of scope (v1.0): HTTP/WBM web interface, FTP, SNMP, UDP variant of Modbus, DHCP/BootP provisioning, real-time behavior beyond response latency simulation, and module parameterization (mailbox traffic) beyond process data.
1.4 Definitions
| Term | Meaning |
|---|---|
| PII | Process Input Image — data the coupler presents to the master (from input modules) |
| PIO | Process Output Image — data the master writes (to output modules) |
| MBAP | Modbus Application Protocol header (7-byte TCP header) |
| FC | Modbus function code |
| FAT | Factory Acceptance Test |
| Scenario | A scripted, timed sequence of state changes and assertions |
| Macro | A reusable scripted action (e.g., "pulse output 3 for 500 ms") |
| Node | One simulated fieldbus coupler + its I/O module configuration |
| LTS | Long-Time Simulation — continuous (“soak”) mode running hours to weeks of simulated time |
| Dataset | A CSV time-series file driving designated input aliases in continuous mode |
| Wear counter | Engine-maintained per-alias statistic: runtime hours, edge counts, load-hours |
| Degradation model | Time/cycle-dependent transformation of a plant parameter (e.g., pump capacity) |
| Invariant | A condition that must hold every tick throughout a continuous run |
| Episode | One aggregated violation period of an invariant (bounded reporting) |
2. System Overview
flowchart LR
subgraph DUT["Devices under test"]
PLC["PLC 1..n\n(Modbus TCP masters)"]
end
subgraph SIM["Simulator (this system)"]
MB["Modbus TCP Server\n(asyncio, ≤ 15 connections)"]
DS["Datastore\n(WAGO word & bit maps)"]
NODE["Virtual WAGO Node\n(module list → process image)"]
SE["Scenario Engine\n(states → actions)"]
CLK["Deterministic Clock\n+ Scheduler"]
LOG["Event Log / Traces"]
end
PLC -- "MBAP frames (FC1..FC23)" --> MB
MB <--> DS
DS <--> NODE
SE -- "read inputs / write outputs" --> NODE
SE <--> CLK
NODE -- change events --> SE
LOG -.records.- MB
LOG -.records.- SE
The system is a pure slave/server: it never initiates Modbus traffic. Autonomy comes from the scenario engine, which drives the field side of the virtual node (as if sensors moved and actuators did something), while the PLC drives the master side through normal Modbus writes.
3. Functional Requirements — Modbus Server
3.1 Transport & connections
- FR-3.1.1: The server shall listen on TCP port 502 (configurable) and speak Modbus TCP with the standard MBAP header.
- FR-3.1.2: The server shall support up to 15 concurrent Modbus TCP connections (per 750-362 spec, §7.3). Connection number 16 shall be refused.
- FR-3.1.3: Idle connections shall be closed after the timeout configured in register
0x1030(default 600 × 100 ms). Outputs remain in last state (§11.2.5.5). - FR-3.1.4: Protocol identifier other than 0 in MBAP shall increment the
BadProtocolstatistic and be ignored. - FR-3.1.5: Unit identifier shall be ignored (accepted, echoed back), as on the real device.
3.2 Function codes
The simulator shall implement exactly the 750-362 function set (§11.2.2) and reject all others with exception 0x01 (Illegal Function):
| FC | Function | Limits (R/W) |
|---|---|---|
| 01 | Read Coils | 2000 / — |
| 02 | Read Discrete Inputs | 2000 / — |
| 03 | Read Holding Registers | 125 / — |
| 04 | Read Input Registers | 125 / — |
| 05 | Write Single Coil | — / 1 |
| 06 | Write Single Register | — / 1 |
| 11 | Get Comm Event Count | — |
| 15 | Write Multiple Coils | — / 800 |
| 16 | Write Multiple Registers | — / 100 |
| 22 | Mask Write Register | — / 1 |
| 23 | Read/Write Multiple Registers | 125 / 100 |
Exceeding limits → exception 0x03 (Illegal Data Value) and increment TooManyRegisters / TooManyBits.
Exception codes (§11.2.3): 0x01 Illegal Function, 0x02 Illegal Data Address, 0x03 Illegal Data Value, 0x04 Slave Device Failure, 0x05 Acknowledge, 0x06 Server Busy, 0x08 Memory Parity Error, 0x0A/0x0B Gateway errors.
3.3 Register map (WAGO 750-362)
3.3.1 Word (register) access — reading (FC3/FC4)
| Modbus address (hex) | Area | Behavior |
|---|---|---|
0x0000–0x00FF |
Physical input area (1), %IW0–255 | First 256 words of input process image |
0x0100–0x01FF |
— | Exception 0x02 |
0x0200–0x02FF |
Physical output area (1) | Read-back mirror of %QW0–255 (outputs written at 0x0000 are readable here, +0x0200 offset) |
0x0300–0x0FFF |
— | Exception 0x02 |
0x1000–0x2FFF |
Configuration registers | See §3.5 |
0x3000–0x5FFF |
— | Exception 0x02 |
0x6000–0x62FB |
Physical input area (2), %IW256–1020 | Additional 764 words |
0x62FC–0x6FFF |
— | Exception 0x02 |
0x7000–0x72FB |
Physical output area (2), %QW256–1020 | Read-back mirror, +0x01000 offset from write area (2) |
0x72FC–0xFFFF |
— | Exception 0x02 |
3.3.2 Word access — writing (FC6/FC16)
| Modbus address (hex) | Area |
|---|---|
0x0000–0x00FF |
Physical output area (1), %QW0–255 |
0x0100–0x01FF |
Exception 0x02 |
0x0200–0x02FF |
Physical output area (1) (mirror accepted for writing) |
0x0300–0x0FFF |
Exception 0x02 |
0x1000–0x2FFF |
Configuration registers |
0x3000–0x5FFF |
Exception 0x02 |
0x6000–0x62FB |
Physical output area (2), %QW256–1020 |
0x62FC–0x6FFF |
Exception 0x02 |
0x7000–0x72FB |
Physical output area (2) (mirror) |
0x72FC–0xFFFF |
Exception 0x02 |
3.3.3 Bit (coil) access — reading (FC1/FC2) & writing (FC5/FC15)
| Modbus address (hex) | Area |
|---|---|
0x0000–0x01FF |
Physical input area (1) — first 512 digital inputs (read) |
0x0200–0x03FF |
Physical output area (1) — first 512 digital outputs (read mirror + write) |
0x0800–0x85F7 |
Physical input area (2) — digital inputs 513–2039 |
0x9000–0x95F7 |
Physical output area (2) — digital outputs 513–2039 |
| all others | Exception 0x02 |
Digital channel addressing rule (§11.2.4): bit addresses are channel numbers — digital input no. 47 has Modbus address 46 — independent of the physical module layout. Word-oriented modules are laid out first (in physical order, from word 0), then bit-oriented modules are byte-packed in physical order.
3.3.4 Constant registers (FC3/FC4 read-only)
| Addr | Value | Purpose |
|---|---|---|
0x2000 |
0x0000 |
Zero |
0x2001 |
0xFFFF |
Ones |
0x2002 |
0x1234 |
Byte-order check (Intel format expected) |
0x2003 |
0xAAAAl |
Bit mask 1 |
0x2004 |
0x5555 |
Bit mask 2 |
0x2005 |
0x7FFF |
Max positive |
0x2006 |
0x8000 |
Max negative |
0x2007 |
0x3FFF |
Half positive |
0x2008 |
0x4000 |
Half negative |
3.3.5 Firmware-info registers (read-only)
0x2010 revision, 0x2011 series code (0x02EE = 750), 0x2012 coupler code (0x0166 = 362), 0x2013/0x2014 major/minor, 0x2020 description (16 words), 0x2021 compile time, 0x2022 compile date, 0x2023 firmware loader info, 0x2030–0x2033 connected-module descriptions, 0x2035 process-image settings, 0x2036 diagnostics, 0x2040 software reset (write 0x55AA/0xAA55), 0x2043 factory settings.
3.4 Watchdog (§11.2.5.2/3)
The Modbus watchdog shall be simulated:
0x1000timeout value ×100 ms (default0x0064), writable only when WD inactive.0x1001/0x1002coding masks (bit n−1 of the mask = FC n; default0xFFFF).0x1003trigger (write nonzero, alternating values),0x1005stop (0xAAAAthen0x5555),0x1008simple stop (0xAA55/0x55AA),0x1007restart (write1).0x1006status:0inactive /1active /2expired.0x1009close-socket-on-timeout,0x100Atype Standard/Alternative,0x100Bsave WD params (remanent).- On timeout: all analog outputs → 0, all digital outputs → OFF; in Standard mode further process-data access is answered with exception
0x04; WD registers remain accessible;0x1009 = 1additionally closes TCP connections. - Watchdog events shall be visible to the scenario engine (assertions: "WD triggered at t seconds").
3.5 Configuration & diagnostic registers
0x1022–0x1025: image sizes (analog in/out bits, digital in/out bits) computed from the configured module list.0x1029(9 words) Modbus TCP statistics: SlaveDeviceFailure, BadProtocol, BadLength, BadFunction, BadAddress, BadData, TooManyRegisters, TooManyBits, ModTcpMessageCounter. Reset by writing0xAA55/0x55AA.0x102Aactive TCP connections.0x1030Modbus TCP idle timeout (×100 ms).0x1031MAC-ID (3 words, configurable to emulate a specific device).0x1037response delay (ms) — the simulator shall actually delay responses, to emulate link latency.0x1038TOS/DSCP (stored, echoed).0x1050module diagnosis (3 words): module, channel, diagnosis bits (underrange … collective failure) — scriptable fault injection.0x1020/0x1021LED error code/argument — scenario-settable.0x102BKBUS reset (write): re-reads the module configuration; process image rebuilt.
3.6 Fault injection
FR-3.6.1: The scenario engine shall be able to simulate fieldbus faults:
- KBUS/local-bus failure →
SlaveDeviceFailurestatistic increments and process-data reads may return exception0x04. - Response-delay injection via
0x1037. - Connection refusal beyond 15 concurrent connections.
- Illegal-address probing (read/write the exception areas) → correct
0x02responses (this validates master error handling).
3.7 Process data semantics
- Word mapping of digital channels follows the Intel format (§7.3.3): channel 1 = bit 0 of the low byte (
D0), high byteD1on top. - Digital word mapping:
Bit15 … Bit0=Byte D1 D0, bit 0 = lowest channel number. - Analog modules contribute 16-bit data words (control/status bytes not accessible via Modbus).
- A module list defines the image; the framework computes addresses and exposes them in the log and via module-description registers (
0x2030+).
4. Functional Requirements — Scenario & Macro Engine
4.1 Concept
A scenario is a scripted, time-ordered description of the world around the PLC: sensor inputs change over time, actuators respond, and the script asserts on PLC outputs. This is what makes FAT autonomous — no operator needs to toggle signals.
- State-based: rules keyed on input/output conditions (
when input 5 turns ON …). - Time-based: scheduled events (
after 30 s …). - Output actions:
set,reset,pulse,toggle,ramp,hold_valueon simulated field outputs (what the PLC sees as its inputs). - Input observations: the engine reads the process output image (what the PLC wrote) with edge detection (rising/falling).
- Assertions: expected PLC behavior (output X ON within 2 s after input Y) → FAT verdicts.
4.2 Scenario DSL (YAML)
name: pump_station_fat
description: "Full FAT round for pump station PLC program"
node: config/pump_node.yaml # module configuration
timing: { tick: 10ms } # simulation tick
inputs: # named aliases → digital/analog inputs
start_button: { type: di, module: 0, channel: 0 } # DI0.0
level_sensor: { type: ai, module: 4, channel: 0, scale: 0.1 } # AI4.0, mm → counts
em_stop_ok: { type: di, module: 0, channel: 3 } # DI0.3
outputs: # named aliases → PLC outputs (read-only for script)
pump_relay: { type: do, module: 2, channel: 0 } # DO2.0
valve_open: { type: do, module: 2, channel: 1 } # DO2.1
speed_setpoint: { type: ao, module: 5, channel: 0, scale: 0.1 } # AO5.0
macros:
pulse_start: # reusable macro
- set: start_button = ON
- wait: 250ms
- set: start_button = OFF
states: # optional state machine
idle:
on: { em_stop_ok: ON }
goto: running
running:
entry: [ ramp: level_sensor, to: 8500, over: 30s ]
on: { pump_relay: RISING }
assert: { valve_open: ON, within: 2s }
goto: full
...
sequence: # time-ordered script (or fully state-driven)
- macro: pulse_start
- assert: { pump_relay: ON, within: 2s }
- wait: 5s
- set: level_sensor = 4200
- assert: { speed_setpoint: ">= 1000", within: 3s }
- report: PASS
4.3 Requirements
- FR-4.3.1: Scenarios shall be defined in YAML (no programming required for standard FAT sequences).
- FR-4.3.2: The engine shall support state machines (states, entry actions, transitions on input/output edges or conditions, guards).
- FR-4.3.3: The engine shall support macros (reusable action groups, parameterizable).
- FR-4.3.4: Actions on simulated inputs:
set,reset,pulse,toggle,ramp(linear over time),noise(± band),sin,hold. - FR-4.3.5: Assertions on PLC outputs with timeout → PASS/FAIL/ERROR verdict per check, with timestamps; final scenario verdict.
- FR-4.3.6: Assertions may compare against expected values, ranges, edges, and statistics registers (e.g., "watchdog expired").
- FR-4.3.7: All scenario events and all Modbus transactions shall be logged (CSV/JSONL) for the FAT report.
- FR-4.3.8: Python action plugins shall be supported for actions beyond the built-in set (registered callables), for complex plant-model physics.
- FR-4.3.9: The engine shall run deterministically: simulation time decoupled from wall-clock; results reproducible.
- FR-4.3.10: A scenario may load multiple nodes (multi-coupler FAT benches).
- FR-4.3.11: Module-based channel addressing — aliases may be addressed
as module type + module number + channel number, all components
explicitly configurable:
{ type: di, module: 2, channel: 1 }selects digital input channel 1 (0-based) on DI-module 2 (0-based number among the modules of the same type, i.e. the third DI module in the node), i.e.DI2.1. Channel numbers are 0-based. The channel kind must match the module (DOon a DI-only module is a validation error). This is an alternative to addressing by physical slot; both pool addressing ({ type: di, channel: 3 }= 3rd DI channel across the whole node) and module addressing are supported and may be mixed, but an alias uses exactly one. Implemented in v1.1 (channel_by_module,Alias.module,alias_location). Changed in v1.5 (2026-10-04, breaking): module numbers previously counted physical slots (all modules in one sequence); they now number within the module's type —DI0/DO0/AI0/AO0start each kind's sequence (e.g. a node[DI_8, DI_16, DO_8, DO_16, AI_2, AO_2]numbers its DI modulesDI0,DI1regardless of the DI/DO interleaving). This matches how channels are named on real WAGO benches and makes rearranging modules less disruptive to existing configs. Scenarios, channel names, and analog ranges use the per-type codes.
4.4 Example FAT flow
- PLC program boots, connects to simulator (Modbus TCP client), reads input image.
- Scenario
pump_station_fatstarts inidlestate;em_stop_okturns ON. - Scenario pulses
start_button; PLC sees input, starts pump relay (writes coil). - Engine detects rising edge on
pump_relay, assertsvalve_openwithin 2 s. - Scenario ramps
level_sensorto 8500 counts; PLC adjustsspeed_setpoint; assertions verify. - Scenario injects fault (KBUS error / watchdog timeout); PLC must react (e.g., switch to safe state) — asserted.
- Report generated: verdicts, timing, Modbus trace.
5. Architecture
5.1 Package layout
wago-modbustcp-simulator-py/
├── docs/ # WAGO manuals + this spec
│ └── spec/
│ ├── software-spec.md # this document
│ └── register-map.md # extracted WAGO tables
├── src/
│ └── wago_sim/
│ ├── __init__.py
│ ├── mbap.py # MBAP frame codec
│ ├── pdu.py # PDU codec (request/response/exception)
│ ├── server.py # asyncio Modbus TCP server
│ ├── datastore.py # WAGO word/coil datastores
│ │ # (holds process images + register bank)
│ ├── registers.py # config/diagnostic/constant/firmware regs
│ ├── modules.py # virtual WAGO I/O modules (module catalog)
│ ├── node.py # node = modules → process image mapping
│ ├── watchdog.py # Modbus watchdog simulation
│ ├── clock.py # deterministic clock + scheduler
│ ├── scenario/
│ │ ├── __init__.py
│ │ ├── engine.py # interpreter for scenario DSL
│ │ ├── dsl.py # YAML schema + validation
│ │ │ ├── actions.py # set/pulse/ramp/... + plugin registry
│ │ │ ├── assertions.py # condition matcher
│ │ │ ├── statemachine.py
│ │ └── report.py # verdicts, CSV/JSONL log
│ ├── stats.py # Modbus TCP statistics counters
│ ├── logging_setup.py
│ └── cli.py # entry point (argparse)
├── config/
│ └── pump_node.yaml # example node configuration
├── scenarios/
│ └── pump_station_fat.yaml # example scenario
├── tests/
│ ├── test_mbap.py
│ ├── test_pdu.py
│ ├── test_server.py
│ ├── test_datastore.py
│ ├── test_registers.py
│ � ├── test_watchdog.py
│ ├── test_scenario_engine.py
│ └── conftest.py
├── pyproject.toml
└── README.md
5.2 Design decisions
| Decision | Rationale |
|---|---|
| Pure-Python, asyncio TCP server | No external Modbus library needed; full control over WAGO-specific behavior (register-map exceptions, statistics, watchdog); testable |
| Deterministic clock | Reproducible FAT runs; scenario times independent of host speed |
| YAML DSL + plugin boundary | Test engineers write scenarios, not code; complex physics via Python plugins |
| Datastore split: process image vs. register bank | Mirrors real device: process image vs. internal registers; the readback-mirror rule (+0x0200) is dataflow, not copy-on-read |
| Statistics as first-class objects | FAT assertions can check communication health (0x1029) |
5.3 Threading & concurrency model
- asyncio event loop owns the Modbus server and the scheduler tick.
- Scenario engine runs in the same loop (coroutines), so image updates and Modbus reads see consistent data without locks.
- Response delay (
0x1037) implemented as asyncio sleep before response send.
5.4 Data model
erDiagram
NodeConfig ||--|{ ModuleConfig : modules
NodeConfig ||--|| ProcessImage : builds
ModuleConfig ||--|{ Channel : channels
ProcessImage ||--|| InputImage : inputs
ProcessImage ||--|| OutputImage : outputs
OutputImage ||--|| InputMirror : "read-back at +0x200/+0x1000"
Scenario ||--|{ State : states
Scenario ||--|{ SequenceStep : sequence
State ||--|{ Action : entry
State ||--|{ Transition : on
SequenceStep ||--|{ Action : actions
Scenario ||--|{ Alias : "inputs/outputs aliases"
Scenario ||--|{ Macro : macros
Macro ||--|{ Action : steps
5.5 State chart — server session
stateDiagram-v2
[*] --> Listening
Listening --> Connected: TCP accept (≤15)
Connected --> RequestParsing: MBAP complete
RequestParsing --> Dispatching: PDU decoded
Dispatching --> Responding: result
Dispatching --> ExceptionResponse: FC/addr/val error
Responding --> Connected
ExceptionResponse --> Connected
RequestParsing --> Connected: malformed (count BadLength)
Connected --> Closed: idle timeout / WD 0x1009=1 / client close
6. Non-Functional Requirements
- NFR-6.1 Performance: sustain ≥ 500 requests/s with ≤ 10 ms added latency (typical FAT bench load).
- NFR-6.2 Portability: Python 3.11+ on Windows/Linux.
- NFR-6.3 Determinism: two runs of a scenario with identical inputs and timing produce identical verdict logs (except wall-clock stamps).
- NFR-6.4 Observability: every Modbus transaction is observable in
verbose mode (
-v/WAGO_SIM_VERBOSE) as plain text — decodedREQ/RSP/EXClines with transaction id, unit, function name, values, and the WAGO 750-362 register-map meaning of each address (process areas with %IW/%QW mapping per Tables 119/120, read-back mirrors, configuration/constant registers, illegal areas); malformed frames are traced asRAWlines with PDU hex. Implemented (src/wago_sim/trace.py). - NFR-6.5 Robustness: malformed frames never crash the server; counted in
statistics. Invalid operator input — unknown commands or options,
non-numeric or out-of-range port numbers (CLI or
WAGO_SIM_PORT), missing/unparsable node or scenario files, unwritable report paths — yields a one-linewago-sim: error: …on stderr and exit code 2, never a Python traceback; missing report directories are auto-created; unexpected internal errors keep the traceback (logged) for debugging. CLI part implemented (tested intests/test_cli.py). - NFR-6.6 Configurability: port, bind address, node config, firmware identity registers, MAC-ID configurable without code changes.
7. Acceptance Criteria (framework phase)
- A Modbus TCP master (e.g.,
pymodbusclient in tests) can connect, read constants0x2000–0x2008, and gets exactly0x0000, 0xFFFF, 0x1234, 0xAAAA, 0x5555, 0x0166, ...per spec — byte order verified. - Reads/writes in all mapped/unmapped areas produce values/exceptions per §3.3 tables.
- Digital channel addressing: a configured 16-channel DI module + 8-channel DO module yields channel-number bit addressing (DI 47 → bit address 46).
- Output readback mirrors at +
0x0200(word) and +0x0200/+0x1000(bit/word area 2) work. - A YAML scenario can set a digital input, detect a PLC output rising edge, and produce a PASS/FAIL report file.
- Watchdog: configure
0x1000, trigger via0x1003, let it expire, outputs clear,0x1006= 2, process access → exception0x04, restart via0x1007. - Statistics counters increment correctly on bad frames/addresses; reset via
0x1029write0xAA55.
8. Feature Request — Long-Duration (Continuous) Simulation
Status: implemented 2026-10-04 (roadmap phase 3 complete). Extends the scenario engine (§4) with a
continuousmode for endurance (“soak”) testing: the facility runs for hours, days or weeks of simulated time against evolving inputs and an aging plant.
8.1 Motivation
FAT rounds (§4) validate functional behavior over minutes. A facility must additionally be proven over long time:
- Endurance/soak: memory leaks, counter rollovers, watchdog stability over days.
- Trend & historian verification: SCADA trends, averaging, alarm shelving.
- Cycle behavior: daily/weekly demand profiles, shift patterns, weekend idle.
- Maintenance logic: wear-based alarms, degradation detection, service recovery.
- 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, and both run in parallel on the same sim-time axis:
- Dataset playback (optional) — designated inputs are driven from time-series files (e.g., CSV exported from a plant historian, or synthesized profiles).
- 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. Thesequencebecomes optional. The run continues until all active drivers are exhausted: every dataset finished (or looped to itsrepeatlimit), all scheduled steps executed, and — if a top-leveluntil: <duration>is given — the duration elapsed. With no datasets,until:is the primary run-length control. A continuous scenario with neither datasets, nor scheduled steps, noruntil: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 viatime_column) plus one column per alias. Time is elapsed seconds (default) or ISO-8601 absolute timestamps (time_format: iso8601). Analog values are numeric; digital values acceptON/OFF,1/0,true/false. Lines starting with#are comments. - FR-8.2.4: Values pass through the alias
scale/offset(engineering units → raw counts), identical toset:semantics. - FR-8.2.5: Between samples, analog aliases use per-dataset
interpolation: linear | hold; digital aliases always usehold(never interpolated). - FR-8.2.6: Datasets shall be streamed with bounded memory (one-row lookahead); files covering months of samples must be playable without loading them into RAM.
- FR-8.2.7:
repeat: nloops a dataset seamlessly and monotonically (no time jump at the seam); identical loops produce identical values (deterministic).until: <duration>limits total playback. - FR-8.2.8:
time_factor: k(default 1.0) — dataset time advances k× faster than sim-time, enabling a 28-day soak in a fraction of the wall-clock time. - FR-8.2.9: Multiple datasets share one sim-time axis; each has an optional
offset:start shift. The engine merges them; an alias’s value is that of its owning dataset. - FR-8.2.10: Pacing: in server mode (real PLC connected) sim-time follows wall-clock for real-time soak testing; headless runs are unpaced (as fast as deterministic processing allows).
- 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.
With the plant environment (FR-8.2.14), the decision source sees this
pipeline as one atomic
envtick:observe()reads the state as of the start of the tick,apply()queues the decided outputs, and the engine then advances the pipeline once — outputs written byapply()land in the output image before the next tick'sobserve(), so every state the decision source sees is pipeline-consistent. - 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-leveluntil:bounding the run. All §8.3–8.4 machinery (degradation, invariants, rate limits, snapshots) applies identically, whether or not any CSV streaming is configured. - FR-8.2.14: Plant environment abstraction (requested 2026-10-04,
reworked same day to environment-first):
PlantEnvis the primary interface for continuous plant simulation, standalone-first — it requires only a virtual node + datastore (no scenario), with the §8.3/8.4 machinery (degradation, invariants) as optional attachments. Per deterministic tick (the engine or the runner advances the tick clock, never wall-clock arithmetic):state = env.observe()returns the state of ALL modules — DI/DO/AI/AO channels, engineering units, per-type I/O codes, configured names, analog display ranges (FR-10.2.14), plus active aliases and parameters — →env.model(state, dt)(the caller-supplied plant physics) →apply()writes the new field state. The environment owns no policy; plant models are supplied by subclassing (FR-8.2.17). - FR-8.2.15: Action semantics:
apply()primarily writes the input side (DI/AI) — the simulated field: what the sensors show, driven by plant physics reacting to the PLC's output commands. Writing outputs (DO/AO) is allowed as explicit forcing via aforce=Truekeyword orenv.force(...)— for headless runs without a PLC and for bench fault setup; forced outputs go through the datastore's internal write paths so mirrors/statistics/watchdog stay consistent (FR-9.2.6). Exactly one decision source may own an alias per tick (ownership conflicts are validation errors, extending FR-8.2.2). - FR-8.2.16: Environment API surface (concrete):
env.observe() -> PlantState(all channels with names and analog display ranges, aliases, parameters, sim-time);env.apply(values, force=False)(keys: I/O code or alias name; DI/AI default, DO/AO require force; unknown keys are errors);env.model(state, dt_s)— override point for plant physics, returning new field values;env.is_done(state) -> bool— optional end criterion, default False (runs until manually stopped);env.reset()— deterministic fresh episode;env.tick_ms/env.sim_time_ms(read-only clock);env.parameters/env.invariantsattachments.PlantState.channel(key)returns the full channel record by I/O code or by configured application name (FR-10.2.11);by_code()returns values by I/O code;channel_range(key)returns an analog channel's display range (min, max) — always defined for AI/AO (dense store, FR-10.2.14). - FR-8.2.17: Environment authoring & runners: environments are easy
to create and call — a subclass of
PlantEnvoverridingmodel()(and optionallyis_done()) in any importable module (--env myenvs.pump_station.PumpStationEnv), or a module-level factory function. Two runners: (a)wago-sim serve --env ...ticks the environment on a wall-clock-paced periodic task (10 ms tick alongside the Modbus server, real-time behavior for a live PLC) and runs until manually stopped (SIGINT/SIGTERM — the existing graceful-shutdown path); (b)wago-sim sim --node ... --env ...runs headless unpaced (as fast as determinism allows) untilis_done()or SIGINT. The scenario-continuous mode (FR-8.2.1-13) remains a scripted layer on top of the same engine ticks.
8.3 Requirements — degradation models (plant logic over time)
- FR-8.3.1: The engine maintains wear counters per alias, updated every tick: runtime hours (integral of truth), edge counts (rising/falling), and load-hours (integral of the analog value). Counters are deterministic state.
- FR-8.3.2: A degradation model computes a named plant parameter (e.g.
pump_capacity, range 0…1) from wear counters and sim-time. Built-in shapes:linear(per runtime hour viarate:, or per start cycle withper: cycles),exponential,staircase(staircase:level table). A model is configured withobserves:naming the alias whose wear counts feed it (NB: the key isobserves— a YAMLonkey would be parsed as boolean True by YAML 1.1). - FR-8.3.3: Plant-model plugins read degraded parameters via
engine.parameter("pump_capacity")and shall use them in the loop physics (e.g., actual pump flow = nominal flow × capacity), so the PLC must compensate (e.g., speed creep) — the behavior under test. - FR-8.3.4:
maintenance_at:threshold crossing fires configured actions: fault injection (module diagnosis, register0x1050), assertions (maintenance alarm within a time bound), and logging. Amaintenance_resetrestores the parameter to 1.0 and clears injected diagnoses — verifying the PLC’s alarm clearing and performance recovery. - FR-8.3.5:
floor:/ceiling:clamp the parameter so the simulated plant stays physically plausible. - FR-8.3.6: Models must be pure functions of (sim-time, wear counters, configuration) — no wall-clock — keeping runs reproducible.
- FR-8.3.7: Degradation state and wear counters are included in snapshots (§8.4) and the final report.
- FR-8.3.8: New DSL actions introduced:
inject_diagnosis/clear_diagnosis(module-diagnosis fault injection) andmaintenance_reset.
8.4 Requirements — continuous assertions & monitoring
- FR-8.4.1:
invariants:— conditions evaluated every tick (analog bounds, expected digital states). Violations are recorded as episodes (start, end, extreme value, tick count) so a 30-day run yields a bounded report, not a per-tick flood. - FR-8.4.2:
rate_limits:— maximum rising/falling edges per sliding hour window per alias (e.g., pump starts ≤ 6/h), verifying anti-cycling logic. - FR-8.4.3:
report.snapshot_every: <duration>— periodic dumps of all alias values, wear counters, and degradation parameters (CSV/JSONL) for trend/historian verification. - FR-8.4.4: Continuous-mode verdict: PASS iff the dataset schedule completed, no invariant episode occurred, no rate limit was exceeded, and no assertion failed; otherwise FAIL/ERROR accordingly.
- FR-8.4.5: Report and log sizes shall be bounded independent of run length (episode aggregation + ring-buffered recent events).
- FR-8.4.6: A limited
sequence:may still be used in continuous mode; steps may be scheduled viaat: <sim duration>(e.g., a service visit at day 12). Scheduled steps and datasets interleave on the same sim-time axis.
8.5 Example — 28-day pump-station soak
Scenario (YAML):
name: pump_station_soak_28d
description: "28-day soak: demand/operator playback, closed-loop plant with pump degradation"
mode: continuous
timing: { tick: 100ms }
inputs:
start_button: { type: di, channel: 1 } # exogenous (operator, CSV)
em_stop_ok: { type: di, channel: 2 } # exogenous (CSV)
inflow_valve: { type: di, channel: 3 } # exogenous (CSV)
inflow_rate: { type: ai, channel: 2, scale: 0.01 } # exogenous (CSV), m³/h → counts
level_sensor: { type: ai, channel: 1, scale: 0.1 } # endogenous (plant model computes)
outputs:
pump_relay: { type: do, channel: 1 }
valve_open: { type: do, channel: 2 }
speed_setpoint: { type: ao, channel: 1, scale: 0.1 }
maintenance_alarm: { type: do, channel: 3 }
datasets:
demand_week: # exogenous demand profile
file: data/demand_week.csv
aliases: [inflow_rate]
interpolation: linear
repeat: 4 # 7-day profile × 4 = 28 sim-days
operator_week: # operator actions
file: data/operator_week.csv
aliases: [start_button, em_stop_ok, inflow_valve]
interpolation: hold
repeat: 4
degradation:
pump_capacity:
observes: pump_relay # wear counts from this alias
model: linear
rate: 0.0004 # -0.04 %/h runtime → ~10 % after ~10.4 sim-days
from: 1.0
maintenance_at: 0.90 # threshold crossing injects diagnosis
diagnosis: { module: 4, channel: 1, value: 0x0001 }
# the PLC raises maintenance_alarm; the scenario asserts it:
sequence: # scheduled events (sim-time)
- at: 12d
maintenance_reset: true # service visit: capacity restored
- at: 12d
assert: { label: "maintenance alarm raised",
conditions: { maintenance_alarm: ON }, within: 60s }
plugins: [plugins.pump_plant.pump_plant] # loop: d(level)/dt = inflow − pump_flow(speed, capacity)
invariants:
- { alias: level_sensor, condition: '>= 200', label: "tank level floor" }
- { alias: level_sensor, condition: '<= 9000', label: "tank level brim" }
rate_limits:
- { alias: speed_setpoint, max_per_hour: 3000 } # ramp-rate bound
report:
snapshot_every: 1h
aggregate: episodes
Datasets (CSV):
# data/demand_week.csv (elapsed seconds, inflow in m³/h)
time,inflow_rate
0,12.0
3600,12.4
7200,13.1
...
604800,12.0 # week boundary — loops seamlessly with repeat: 4
# data/operator_week.csv
time,start_button,em_stop_ok,inflow_valve
0,OFF,ON,OFF
28800,ON,ON,ON # Mon 08:00: shift start, valve opened
28810,OFF,ON,ON
432000,OFF,OFF,ON # Sat 00:00: weekend e-stop drill
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:
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 — OPTIONAL driver in continuous mode
├── degradation.py # WearCounters, DegradationModel registry, maintenance actions
├── invariants.py # invariant & rate-limit evaluation, episode aggregation
├── plantenv.py # PlantEnv: gym-style observe/apply/reset cycle over the
│ # process images — the decision-source interface
│ # (FR-8.2.14–16); plugins become thin adapters
└── engine.py # mode: continuous, parameter() accessor, snapshot
# scheduler; fixed tick pipeline per FR-8.2.12:
# datasets -> edges -> plugins/env -> degradation ->
# invariants -> scheduled steps -> snapshots
erDiagram
ContinuousScenario ||--o{ Dataset : "datasets (optional)"
ContinuousScenario ||--|| PlantEnv : "environment (FR-8.2.14)"
PlantEnv ||--o{ DecisionSource : "plugins / external agents"
Dataset ||--|{ ColumnMapping : "alias columns"
ContinuousScenario ||--|{ PlantPlugin : "signal logic (always runs)"
ContinuousScenario ||--|{ DegradationModel : degradation
DegradationModel ||--|{ WearCounter : counts
WearCounter }o--|| Alias : observes
ContinuousScenario ||--|{ Invariant : invariants
Invariant ||--o{ Episode : violations
ContinuousScenario ||--|{ RateLimit : rate_limits
ContinuousScenario ||--o{ Snapshot : "periodic dumps"
PlantPlugin }o--|| DegradationModel : "reads parameters"
PlantPlugin }o--o{ Alias : "reads dataset-driven + computes logic-driven"
PlantEnv }o--|| ProcessImage : "observe/apply via datastore"
8.7 Non-functional requirements (long-duration)
- NFR-8.7.1: Memory O(1) in dataset length and run duration (streaming + bounded caches).
- NFR-8.7.2: A 28-sim-day soak with two datasets completes headless in ≤ 1 hour wall-clock on a standard PC (budget to be confirmed after implementation).
- NFR-8.7.3: Determinism incl. degradation: identical config + datasets produce identical reports (except wall-clock stamps).
- NFR-8.7.4: Robustness: host sleep/hibernate or GC pauses never distort sim-time or dataset position (sim-time accumulates by tick, never from wall-clock deltas — except when explicitly paced per FR-8.2.10).
- NFR-8.7.5: Report and log artifacts stay bounded (FR-8.4.5 mechanism), enabling unattended multi-day runs.
8.8 Acceptance criteria (long-duration simulation)
- A ≥ 100,000-row CSV streams with constant memory; alias values at arbitrary ticks match the expected interpolation exactly.
linearinterpolation for analog andholdfor digital verified against hand-computed samples; digital aliases are never interpolated.repeat: 4of a 7-day dataset produces four identical per-loop value sequences (seamless seam, deterministic).time_factor: 10consumes a 7-day dataset in 16.8 h of sim-time.- An alias-ownership conflict (dataset + ramp on one alias) is rejected at validation time.
- Degradation:
pump_capacitydeclines per the configured formula; crossingmaintenance_atinjects the configured diagnosis and the maintenance-alarm assertion passes;maintenance_resetrestores capacity to 1.0 and clears the diagnosis. - An invariant breach is recorded as one episode (start, end, peak) regardless of duration; a rate-limit breach fails the run.
- A full 28-sim-day soak completes headless with a bounded report and a correct PASS/FAIL/ERROR verdict.
- 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). - 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.
- A continuous scenario with no datasets, no scheduled steps, and no
until:is rejected at validation time (FR-8.2.1). - PlantEnv cycle (FR-8.2.14):
observe()returns every input channel (values, names, codes, ranges, sim-time);apply()lands the decided outputs in the output image before the nextobserve()— verified by a deterministic two-tick test where the decision source copies an input to an output and the second observation contains it. - Plugin parity (FR-8.2.15): the same plant logic runs unchanged as
a classic
plugins:entry and as anenv.observe/applydecision source — identical outputs over a 1000-tick headless run (regression gate for the adapter). - External agent: a decision source stepping the env over a socket (non-Python agent) produces identical results to the in-process variant on the same scenario (FR-8.2.15).
9. Feature Request — Bench Mode (Run Without a Scenario)
Status: requested 2026-10-03, planned (roadmap phase 2), partially available today.
wago-sim serve --node <config>already runs the Modbus TCP server with a static process image and no scenario engine; this section formalizes the mode and its supporting tooling.
9.1 Motivation
Early commissioning and interactive troubleshooting do not follow a script: an engineer wants the virtual node up, connected to the PLC/SCADA, and then manually observe and tweak values — the way one would probe a real node with a screwdriver and a multimeter. Requiring a scenario file for this is friction; a syntax error in an unrelated YAML file should not block a bench session.
Bench mode is also the foundation for manual FAT exploration and for the web UI (§10), which needs a long-lived node whose I/O can be manipulated at runtime without a scenario driving it.
9.2 Requirements
- FR-9.2.1:
wago-sim serve --node <config>shall start the Modbus TCP server with no scenario attached: the process image starts at defined defaults (all-zero), the watchdog/statistics behave normally, and the server runs until terminated. (Available today.) - FR-9.2.2:
--set alias=valueCLI options (repeatable) shall seed input values at startup, e.g.--set DI0.1=ON --set AI4.0=1234, addressing channels by the module notation of FR-4.3.11. Optional in v1: a small startup-commands file for larger value sets. - FR-9.2.3: An interactive console monitor (
--monitor): periodically prints a compact table of all I/O channels (module, type, address, value) and highlights changes, so a bench session is observable in a terminal without any UI. - FR-9.2.4: Runtime manipulation via the console monitor: simple commands
(
set DI0.1 ON,pulse DO2.0 500ms,watch 0x1029,reset stats) — the same operations the web UI (§10) offers, over stdio. - FR-9.2.5:
serveshall be combinable with a scenario later (--scenariobecomes optional); running with neither scenario nor monitor prints a hint that the node is passive and values can only be changed by a master or via the web UI. - FR-9.2.6: The console monitor and web UI (§10) must never violate device semantics — manipulations go through the same datastore paths as Modbus writes, so read-back mirrors, watchdog kicks, and statistics stay consistent.
9.3 Acceptance criteria
wago-sim serve --node config/pump_node.yamlstarts with no scenario file present and serves a static image; a pymodbus client can read and write process data.--set DI0.1=ONseeds the corresponding input bit; a master reading bit address 0 sees ON.- The console monitor shows the full channel table and flags value changes within one refresh interval.
- Console
set/pulse/watchcommands affect the image immediately and are visible to a connected master in the same way Modbus writes are.
10. Feature Request — Web UI (I/O Overview & Manipulation)
Status: in implementation (2026-10-03). The implementation decisions below are binding; milestones M1–M4 are implemented and verified (M5 configuration and M6 scenario view remain). A browser front-end overlooking and manipulating the simulated node's I/O values and states at runtime.
10.1 Motivation
Terminal monitors (§9) suffice for single engineers, but FAT benches are collaborative: commissioning engineers, PLC programmers, and test witnesses need a shared, visual view of the virtual I/O — the equivalent of watching the LEDs on a real WAGO node, plus the ability to force signals the way WAGO's own WBM does on real devices. A web UI keeps the simulator usable from any machine on the bench network without installing tools.
10.2 Requirements
- FR-10.2.1: The simulator shall serve a web UI (default port 8080, configurable) alongside the Modbus TCP server, with no external backend dependencies beyond Python (FastAPI/Starlette + static files, or an equivalent embedded approach — implementation choice, to be decided).
- FR-10.2.2: The UI shall present the node overview: module list with
article numbers, per-module channel tables (type, module, channel,
addressing
DI0.1per FR-4.3.11, Modbus address, current value/state), derived from the same process image the Modbus server serves. On the node graphic (requested 2026-10-04, refined same day): each module shows its kind+slot designation (DI0,DO2,AI4, …) in the module's lower left corner, below the channel indicators, and the channel indicators carry only the 0-based channel number: below each DI/DO LED, and on analog modules outside the indicator stack, above the overflow and below the underflow LED (FR-10.2.14) so the limit LEDs stay adjacent to the bar. DI/DO channels lay out in vertical columns of eight — channels 0–7 in the left column, 8–15 in the next — and modules are drawn as thin, tall bezels mirroring the proportions of real WAGO DIN-rail housings (narrow width, vertically stacked LEDs). - FR-10.2.3: Values shall update live (WebSocket push or short-polling), including changes made by the PLC/master over Modbus, by scenarios, by the console monitor, or by other UI clients — one shared source of truth.
- FR-10.2.4: Manipulation: clicking a DI/DO toggles it; an AI/AO gets a
numeric input (engineering units, using alias scale/offset when
defined);
pulse(with duration) andramp(target + duration) actions on DI/DO/AI. All writes go through the datastore's Modbus-equivalent paths (FR-9.2.6) so mirrors, watchdog, and statistics stay consistent. Clicking an analog bar indicator (requested 2026-10-04) sets the value proportionally to the click position within the channel's display range (FR-10.2.14) — AI = field side, AO = master-side force; exact values remain enterable via the channel list. - FR-10.2.5: Diagnostics view: watchdog status (0x1006), statistics block (0x1029, with reset button), module diagnosis (0x1050), LED error registers (0x1020/0x1021) — mirroring WBM pages of the real device — with controls to inject/clear faults for FAT fault cases.
- FR-10.2.6: A scenario/run view: which scenario (if any) is loaded, current step, verdicts so far, and basic controls (start/abort). Headless result reports (JSONL/CSV) remain downloadable via the CLI.
- FR-10.2.7: Multi-client consistency: several simultaneous browser clients see the same state; forced values propagate to all clients and to the Modbus side within one refresh interval.
- FR-10.2.8: Access control for shared benches: optional read-only mode and (v2) token-based login, so witnesses can observe without accident rights.
- 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.
- FR-10.2.11: Channel names (requested 2026-10-03): every module
channel can carry an application-specific name (e.g.
start_button,tank_level). Names live in the node configuration — not in the module catalog (catalog articles are shared per type) — persisted alongside the module list, and are re-applied after process-image rebuilds (§11). Unnamed channels fall back to the generic channel label (e.g.DI 1). Names surface everywhere the UI lists a channel: node overview, the channel list, the configuration editor, and tooltips. - FR-10.2.12: Import/export of module configuration (requested 2026-10-03): the complete module configuration — module list with slot order, per-channel names, and coupler settings — can be downloaded as a single JSON file and imported back. Import validates the payload (known articles, ≤ 64 modules, name shapes) and applies it atomically: module rebuild + channel names + coupler settings in one step, with the same persistence behavior as SAVE CONFIG (§11). Export/import makes FAT benches shareable and reproducible between engineers and benches.
- FR-10.2.13: Split overview (requested 2026-10-03): the overview
page is divided in two — the realistic node graphic (DIN rail with
LEDs, FR-10.2.2) on top, and below it a scrollable channel list:
one row per channel with the I/O code (
DI0.0), the configured channel name (if any), and a control for the value — a switch for DI/DO and a numeric field for AI/AO — which both displays the live value and manipulates it (same semantics as FR-10.2.4: DI = field side, DO/AO = master-side force through internal paths). The list updates live like the rail (FR-10.2.3) and is the primary working surface for benches with many channels. - FR-10.2.14: Analog display ranges (requested 2026-10-04): the
analog bar indicators (rail + list) scale a channel's raw value
against a display range. The default range is 0–4095 counts
(100 % at 4095, matching the WAGO 750 analog modules' 12-bit
scaling); per-channel
min/maxcan be configured in the node configuration — used by both AI (generated) and AO (used) values. Values within the range fill the bar 0–100 % from the bottom. Underflow/overflow are indicated: the bar fills in the limit color (red) and a small red LED above the bar lights for overflow, below the bar for underflow, clearing when the value returns to range. Signed values (2026-10-04): range bounds may be negative —-65534 <= min < max <= 65535(the raw 16-bit image holds 0–65535; a negative display bound lets signed plant quantities map onto the bar). Simulated AI values and AO forces accept negative numbers within the same window; values are stored in the image as their 16-bit two's-complement representation. Dense runtime store (2026-10-04):node.channel_rangesis always complete — every analog channel of the current layout has an entry (unconfigured channels carry the materialized default), codes that no longer match an analog channel are pruned on rebuild, so envs and API consumers can read ranges without default-fallback guesswork. Persistence (node YAML, config export) stays lean: only explicit non-default entries are written, so defaults survive a future DEFAULT_RANGE change.
10.3 Implementation decisions (binding, 2026-10-03)
- Framework: FastAPI (Starlette) + Uvicorn, embedded in-process on the same asyncio event loop as the Modbus TCP server — a UI write and a Modbus request never race on the datastore.
- Front-end: single-page vanilla HTML/CSS/JS (no build step) under
src/wago_sim/webui/static/; realistic node graphics (DIN-rail strip, module bezels, glowing per-channel LEDs for DI/DO, bar/numeric indicators for AI/AO) drawn with inline SVG/CSS. - Authentication (user request 2026-10-03): session-cookie login with
in-memory token store; default credentials
admin/simu, overridable via env (WAGO_SIM_WEB_USER/WAGO_SIM_WEB_PASSWORD); optional read-only mode viaWAGO_SIM_WEB_READONLY. No third-party auth/session dependencies (hashlib.scrypthashing; sessions need not survive restarts on a bench). - Backend data access (user request 2026-10-03): the API never uses
Modbus TCP. It reads via the internal memory handler
(
WagoDatastoregetters,VirtualNodechannel model,ConfigRegisterBank,ModbusWatchdog,ModbusStatistics) and writes via internal paths chosen by role: simulated-field writes go throughset_digital_input_channel()/set_input_word()(as the scenario engine does); master-side forcing goes through the datastore's internalwrite_bits()/write_words()dispatch (mirrors, watchdog kicks, and statistics stay consistent, FR-9.2.6 semantics) — but always as direct in-process calls, never via TCP. - Live updates: WebSocket push (
/api/ws) — datastoreadd_listenerfor output changes plus a 10 Hz snapshot-difference sweep for input-side changes. - Wiring:
servestarts the web UI by default (--no-webto disable;--web-port, envWAGO_SIM_WEB_PORT; default port 8080).
10.4 Architecture sketch (as-built)
src/wago_sim/
├── webui/
│ ├── __init__.py
│ ├── app.py # build_webui(app, ...) -> FastAPI; API + static
│ ├── auth.py # login/logout, scrypt hash, session dependency
│ ├── state.py # snapshot builder + change detection
│ ├── api.py # REST + WebSocket routes
│ └── static/ # single-page front-end (no build step)
10.5 API surface (as-built)
| Method/Route | Purpose | FR |
|---|---|---|
POST /api/auth/login, POST /api/auth/logout |
session cookie (HttpOnly) |
auth |
GET /api/node |
module list, channel tables with DI0.1-style and Modbus addresses |
10.2.2 |
GET /api/values |
full channel-value snapshot | 10.2.3 |
WS /api/ws |
live value/state deltas | 10.2.3, 10.2.7 |
POST /api/channels/{kind}/{module}/{channel} |
set/toggle/pulse/ramp a channel (field side) | 10.2.4 |
POST /api/force |
master-side force at a Modbus address (internal dispatch) | 10.2.4 |
GET /api/diagnostics |
watchdog status, statistics, module diagnosis, LED errors | 10.2.5 |
POST /api/diagnostics/reset-stats |
statistics reset (0x1029 semantics) | 10.2.5 |
POST /api/diagnostics/inject-fault, POST /api/diagnostics/clear-fault |
LED / module-diagnosis injection | 10.2.5 |
GET/PUT /api/config/coupler |
watchdog settings, response delay, idle timeout, MAC-ID | §11 subset |
GET/PUT /api/config/modules |
module list CRUD + process-image rebuild | §11 FR-11.2.1–11.2.4 |
GET /api/config/export · POST /api/config/import |
full configuration (modules + channel names + coupler) as JSON | 10.2.12 |
GET/PUT /api/config/channel-names |
read/write per-channel names | 10.2.11 |
10.6 Milestones
| # | Deliverable |
|---|---|
| M1 | WebUI skeleton, in-process FastAPI, CLI/env wiring, authentication (admin/simu), GET /api/node |
| M2 | Node overview page — DIN-rail graphics, LEDs, analog indicators, live values (WS) |
| M3 | Manipulation — toggle/pulse/ramp, AI engineering units, master-side force |
| M4 | Diagnostics — watchdog/statistics/diagnosis view, reset, fault injection |
| M5 | Configuration — coupler forms; module CRUD with image rebuild and persistence |
| M5a | Channel names (FR-10.2.11) + configuration import/export (FR-10.2.12) + split overview with channel list (FR-10.2.13) |
| M6 | (deferred) Scenario run view (FR-10.2.6) + alias-ownership rejection (FR-10.2.9) |
10.7 Acceptance criteria
- Opening the UI shows the node's module list and channel table exactly
as configured, with
DI0.1-style addresses and Modbus addresses. - Toggling a DO in the browser is immediately visible to a connected Modbus master (and vice versa: a master write appears in the UI within one refresh interval).
- Forcing an AI value uses engineering units and the configured scale.
- The diagnostics page shows live watchdog status and statistics; the reset button clears the counters (register 0x1029 semantics).
- With a running scenario, writes to scenario-owned aliases are rejected with a visible message; the scenario continues unaffected.
- Two simultaneous browsers stay consistent with each other and with the Modbus image.
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):
- 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).
- 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 (
0x102Bor WBM SAVE CONFIG): word modules first in physical order, then bit modules byte-packed (§7.3.2.1); image sizes updated in0x1022–0x1025; module descriptions updated in0x2030+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.1stays 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.
- FR-11.2.6: Module reordering (requested 2026-10-04, implemented same day): the web UI's I/O configuration pane supports rearranging modules two ways — drag-and-drop of module rows (HTML5 DnD) and move up/down arrow controls per slot; both are one gesture each and trigger the same process-image rebuild + persistence as any module change (FR-11.2.1–11.2.3). Because I/O codes number modules per type (FR-4.3.11 v1.5), moving a module past a module of a different kind does not renumber the other kind's channels — only the relative order within the same type affects addressing.
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
serverestart) 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 registers0x1000–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
<SR>[.<SO>]–<ER>[.<EO>]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
0x2043write, 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
- Removing a module and issuing SAVE CONFIG (or
0x102B) rebuilds the image:0x1022–0x1025and0x2030+reflect the change and a pymodbus master reading process data sees the new layout. - 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. - Aliases orphaned by a module removal are reported as errors, not silently misaddressed.
- Reconfiguration during a running scenario is rejected; in bench mode it succeeds and is logged.
- 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.
- Watchdog fields edited on the web settings page behave identically to register writes (same validation, same statistics).
- Device state survives a simulator restart; factory reset restores delivery defaults.
12. Deployment — Service / Containerized Operation
Status: implemented 2026-10-03 (v1.3). The simulator can run as a long-lived service: docker/compose container, or a systemd unit on bare metal/VM. Verified: image builds, container answers Modbus requests, and
docker stop(SIGTERM) shuts down gracefully in < 1 s.
12.1 Motivation
A FAT bench simulator is infrastructure: it must start on boot, survive restarts, log to the platform's log pipeline, and stop cleanly so a connected PLC sees a controlled fieldbus-node failure rather than a
hung socket — the exact condition watchdog/FAT fault cases test.
12.2 Requirements (all implemented)
- FR-12.2.1: Environment-variable configuration:
WAGO_SIM_NODE,WAGO_SIM_HOST,WAGO_SIM_PORTconfigureservewithout CLI arguments (containerCMD ["wago-sim", "serve"]needs zero arguments). Explicit CLI arguments override environment values. - FR-12.2.2: Graceful shutdown: on SIGINT/SIGTERM (docker stop,
systemctl stop) the server closes all client connections and the
listening socket via
server.stop()before exiting — no forced kill, no lingering sockets. Verified:docker stopcompletes in 0.34 s. - FR-12.2.3: Container image (
Dockerfile):python:3.12-slim, non-rootwago-simuser, unbuffered stdout logging, baked-in example config for out-of-the-box startup, TCP health check, ~197 MB image. - FR-12.2.4: Compose service (
docker-compose.yml): port mapping (5020→502 default; 502 needs privileges on some hosts), read-only mounts for config/scenarios/plugins, writableout/for reports, resource limits, log rotation,restart: unless-stopped. - FR-12.2.5: systemd unit (
deploy/wago-sim.service): dedicated service user, env-var configuration,Restart=on-failure, hardened (NoNewPrivileges, ProtectSystem, CAP_NET_BIND_SERVICE for port 502 without root). - FR-12.2.6: Logs to stdout/stderr only (docker json-file / journald collect them); single-line format, no ANSI colors.
12.3 Acceptance criteria (all verified)
docker build -t wago-sim .succeeds; image ~197 MB.docker run -p <host>:502 -e WAGO_SIM_NODE=... wago-simserves Modbus (constants0x2000–0x2008read correctly from the container).docker stopcompletes in < 1 s with clean shutdown logs.docker compose up -dstarts a healthy service;downtears it down.- CLI tests cover env-var configuration and override precedence.
13. Feature Request — Multi-Vendor Device Profiles
Status: requested 2026-10-03, not yet planned into a roadmap phase. Simulate additional Modbus TCP bus couplers — other WAGO 750 couplers and other brands' Modbus TCP I/O heads — reusing the existing protocol stack. This section captures the requirements; it does not commit to a schedule.
13.1 Motivation
FAT benches test more than one controller and fieldbus family. Today
the simulator is single-device: the 750-362 register map, limits,
watchdog, and statistics are hard-coded across the protocol stack
(pdu.py limits, datastore.py address dispatch, server.py
connection limit and process-area predicate, registers.py,
watchdog.py, stats.py, trace.py annotations). The transport layer
(mbap.py), PDU codecs, server skeleton, scenario engine, DSL,
plugins, and reporting are protocol-generic and directly reusable.
A second Modbus TCP device multiplies the value of the bench without a protocol rewrite: the deltas between Modbus TCP devices are — address maps, configuration areas, watchdog presence/semantics, statistics blocks, connection limits, and module catalogs with image-layout rules. These are exactly the facts the device manual provides.
13.2 Requirements
- FR-13.2.1: Device profile abstraction: extract the per-device facts into a profile concept covering: supported function codes and per-FC limits, max concurrent connections, idle-timeout behavior, process-image address map (reads and writes), read-back mirror rules, configuration/diagnostic register areas and semantics, watchdog (presence, register layout, modes), statistics block layout, and identity/description registers. The 750-362 becomes the first profile; its behavior must be unchanged after the refactor (the existing test suite is the regression gate).
- FR-13.2.2: Scenario-engine independence: the scenario engine,
DSL, plugins, and reports must remain device-agnostic — they interact
with a node only through the channel model (
ModuleType/Channel/ChannelLocation) and the datastore's set/read surface. Scenarios must not require changes when a profile is added. - FR-13.2.3: Per-vendor module catalogs: catalogs keyed by brand; each entry defines word/bit widths and channels. A node config selects brand + module list; validation must reject cross-brand module mixes within one node (e.g., a WAGO 750-430 in an ABB S800 station). New catalogs are data-only additions (no engine changes).
- FR-13.2.4: Process-image layout rules per brand: layout becomes a profile parameter (word-first + byte-packed bit modules is the WAGO §7.3.2.1 rule; other brands may pack differently — e.g., byte-per-channel images). The channel model and alias addressing (FR-4.3.11 module-based and pool-based schemes) must keep working on top of any layout.
- FR-13.2.5: Config registers / watchdog / statistics as pluggable
blocks: implementations following the existing
registers.py/watchdog.py/stats.pypattern, one set per profile. Devices lacking a Modbus watchdog or statistics block expose nothing (or an illegal-address exception) there — that is a device fact, not a simulator limitation. - FR-13.2.6: Trace annotations per profile:
trace.pyaddress annotations (currently 750-362 Tables 119–122) must come from the active profile so verbose tracing stays meaningful for every device. - FR-13.2.7: Profile selection: node config gains a
device:key (e.g.device: wago-750-362, the default for backward compatibility); CLI/WAGO_SIM_NODEpaths unchanged.wago-sim validatemust report unknown devices and cross-brand module mixes as clean errors.
13.3 Candidate devices (informative, non-binding)
- WAGO 750 Modbus TCP family beyond the 362 (352/363/860…): same register-map family — profile effort is mainly constants and identity registers.
- Other vendors' Modbus TCP I/O heads / controllers (e.g., Phoenix Contact Inline, Beckhoff BK/BC series): a profile written against the vendor manual; effort dominated by the address map and configuration-register semantics.
13.4 Acceptance criteria (draft)
- The 750-362 profile passes the full existing test suite unchanged after the profile extraction.
- A second profile (from §13.3) serves a PLC master that reads/writes its documented process image; identity/description registers match the vendor manual; the same scenario DSL drives its channels without modification.
- Scenarios written for one device run unchanged against another device with equivalent channel configuration.
wago-sim validatereports unknown device keys and cross-brand module mixes with clean one-line errors.
14. Feature Request — Additional Fieldbus Protocols (PROFINET, COMLI)
Status: requested 2026-10-03, not yet planned into a roadmap phase. Extend the simulator beyond Modbus TCP, motivated by ABB AC800M/800xA systems and their distributed I/O: PROFINET IO (AC800M PN controllers with S800 I/O via CI840-class interface modules) and COMLI (ABB's protocol between AC800M controllers and S800/S900 I/O via CI51x/CI522 communication interfaces, TCP-encapsulated).
14.1 Motivation
An AC800M FAT bench talks to its distributed I/O over the protocol the controller is actually configured for. Simulating the I/O side of those protocols lets the same scenario engine, datasets (§8), bench mode (§9), and reporting drive FAT benches for ABB controllers, not only Modbus TCP masters.
The architecture separates cleanly along the same seams the multi-vendor profiles (§13) expose: the scenario engine couples to a node only through the channel model and the datastore's set/read surface; a new protocol is a new transport + protocol layer binding to the same process-image core.
14.2 Requirements
- FR-14.2.1: Protocol abstraction: a protocol binding defines how the field-side process image is exposed to a master: wire protocol, address/identifier model, cyclic vs. transactional access, and the device's diagnostics/alarm model. The simulator core (process image, scenario engine, plugins, reports) stays protocol-independent.
- FR-14.2.2: PROFINET IO device simulation (candidate; scope to be defined against AC800M PN-controller configuration): simulate the device side of PROFINET IO — DCP identify/boot, cyclic RT I/O data exchange for a configured module list, and enough record/identification data for the IO controller to bring the device into data exchange. Scope note: a full PROFINET stack is a major undertaking; a realistic first target is an Ethernet-level RT simulation of one S800-style station profile with a fixed module list, driven by the existing deterministic clock and scenario engine.
- FR-14.2.3: COMLI device simulation (candidate; scope to be defined against AC800M/CI51x/CI522 documentation): simulate the I/O-side endpoint of ABB COMLI over TCP as used between AC800M and S800/S900 I/O through CI communication interfaces — the register/point addressing model, cyclic polling, and the diagnostics responses the CI interface expects. COMLI's request/response register semantics map naturally onto the datastore's set/read surface.
- FR-14.2.4: Shared scenario engine: scenarios, aliases, macros, assertions, plugins, and datasets (§8) work unchanged against protocol bindings; only the channel/identifier model of the target I/O system is expressed in the node config (module list with brand-specific article numbers). A scenario must not name wire-level addresses; alias resolution stays per-device via FR-4.3.11/FR-13.2.4.
- FR-14.2.5: Trace and diagnostics parity: per-protocol verbose tracing equivalent to the Modbus plain-text trace (NFR-6.4): PROFINET RT frames and COMLI requests decoded in readable form with device-level annotations; protocol error counters analogous to the Modbus statistics block.
- FR-14.2.6: Fault-injection parity (per protocol): for PROFINET — frame loss/corruption, device pull/plug, subslot mismatch, communication/watchdog timeouts (the PN equivalent of the Modbus watchdog §3.4); for COMLI — request timeouts, malformed messages, CI-interface failure simulation. These must be scriptable from scenarios like the Modbus fault paths (§3.6).
- FR-14.2.7: Deployment parity: protocol servers run under the same service/container model (§12): env-var configuration, graceful shutdown; one container may host multiple protocol bindings for a node (a station reachable via different protocols in different bench setups).
14.3 Scoping questions (answer before planning)
- PROFINET: full device stack vs. targeted RT simulation of one station profile? Which controller-side tooling (e.g., ABB Compact Builder) must accept the simulated device?
- COMLI: exact CI51x/CI522 variants in scope; is TCP encapsulation sufficient, or is the serial CI variant also needed?
- Are both protocols needed, or is one the realistic FAT target? (COMLI is far smaller in scope; PROFINET conformance is the larger effort by an order of magnitude.)
- Which I/O module families per protocol (S800 modules via PROFINET/COMLI interfaces; WAGO 750 modules over Modbus as today)?
14.4 Acceptance criteria (draft)
- An AC800M controller (or a protocol-level test master representing one) establishes data exchange with the simulated I/O station and reads/writes the documented process data.
- The existing scenario DSL drives input changes and observes output reactions through the new protocol binding, unchanged.
- Verbose tracing shows per-protocol decoded traffic with device annotations; fault-injection steps behave like the Modbus equivalents.
- The 750-362 Modbus behavior is unaffected (regression suite green).
15. 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); 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 |
| ? | Multi-vendor device profiles (§13) and additional fieldbus protocols PROFINET/COMLI (§14) — not yet scheduled; phase assignment after §13.3/§14.3 scoping |
16. 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 - MODBUS Application Protocol Specification V1.1b3 / MODBUS TCP specification (modbus.org)
- ABB CI51x/CI522 communication interface and S800/S900 I/O documentation (COMLI)
- PROFINET specification (PI, profibus.com) — if PROFINET scope is confirmed