277 lines
13 KiB
Markdown
277 lines
13 KiB
Markdown
# 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 slot and
|
||
channel both 0-based) — 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.
|
||
|
||
## Install (dev)
|
||
|
||
```bash
|
||
pip install -e .[dev]
|
||
```
|
||
|
||
## Usage
|
||
|
||
Run the Modbus TCP server against a node configuration:
|
||
|
||
```bash
|
||
wago-sim serve --node config/pump_node.yaml --port 502
|
||
```
|
||
|
||
Validate a scenario:
|
||
|
||
```bash
|
||
wago-sim validate --scenario scenarios/pump_station_fat.yaml
|
||
```
|
||
|
||
Run a scenario headless and write reports:
|
||
|
||
```bash
|
||
wago-sim run --node config/pump_node.yaml \
|
||
--scenario scenarios/pump_station_fat.yaml \
|
||
--report-jsonl out/report.jsonl --report-csv out/report.csv
|
||
```
|
||
|
||
## 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
|
||
|
||
```bash
|
||
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, 124 tests passing)
|
||
|
||
- [x] 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).
|
||
- [x] 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`).
|
||
- [x] 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`.
|
||
- [x] 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.
|
||
- [x] Fault-injection paths (§3.6): response-delay injection,
|
||
connection refusal beyond 15, illegal-address probing responses,
|
||
diagnosis/LED registers scriptable.
|
||
- [x] 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).
|
||
- [x] 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.
|
||
- [x] 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.
|
||
- [x] Assertions with `within:` timeouts → PASS/FAIL verdicts with
|
||
sim-time stamps (FR-4.3.5).
|
||
- [x] Python action/plant-model plugins: per-tick models and step
|
||
actions (FR-4.3.8), e.g. `plugins/pump_plant.py`.
|
||
- [x] Deterministic tick-driven engine: sim-time decoupled from
|
||
wall-clock (FR-4.3.9 core).
|
||
- [x] CLI: `wago-sim serve / run / validate`.
|
||
- [x] Reports: console verdict summary, JSONL and CSV export
|
||
(`--report-jsonl` / `--report-csv`).
|
||
- [x] asyncio Modbus TCP server: ≤ 15 concurrent connections, idle
|
||
connection reaper, response-delay task.
|
||
- [x] Test suite: 124 tests incl. real-socket server tests and
|
||
`pymodbus` interop; spec §7 acceptance criteria 1–7 covered.
|
||
|
||
### 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)
|
||
The soak-testing feature requested 2026-10-03 — specified in §8,
|
||
not yet implemented. Datasets are **optional**: signal logic
|
||
(plugins, scheduled steps, degradation) runs in parallel with CSV
|
||
streaming, or entirely without it.
|
||
|
||
- [ ] `scenario/datasets.py` — streaming CSV playback for designated
|
||
inputs (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)
|
||
|
||
- [ ] Per-transaction Modbus trace log with sim-time + wall-time stamps
|
||
and PDU hex (NFR-6.4).
|
||
- [ ] Performance budget validation: ≥ 500 requests/s, ≤ 10 ms added
|
||
latency (NFR-6.1). |