This commit is contained in:
Jonas Bengtsson
2026-10-03 18:54:37 +02:00
parent ba8df075c3
commit 1aa7aa8767
13 changed files with 1167 additions and 30 deletions
+102 -5
View File
@@ -52,6 +52,11 @@ a virtual node whose I/O behavior is driven by scripted scenarios.
Continuous `invariants` and `rate_limits` produce bounded, episode-based
reports for unattended runs.
- ≤ 15 concurrent Modbus TCP connections, idle-timeout handling.
- **Service / container deployment** — run as infrastructure:
Dockerfile (non-root, health check, ~197 MB), docker-compose service
(restart policy, log rotation, read-only config mounts), systemd unit
example, env-var configuration (`WAGO_SIM_NODE/HOST/PORT`), and
graceful SIGTERM shutdown (`docker stop` completes in < 1 s).
## Install (dev)
@@ -81,6 +86,76 @@ wago-sim run --node config/pump_node.yaml \
--report-jsonl out/report.jsonl --report-csv out/report.csv
```
## Deployment (service / container)
The simulator runs as a long-lived service. Configuration comes from
environment variables — `WAGO_SIM_NODE`, `WAGO_SIM_HOST`, `WAGO_SIM_PORT` —
so no CLI arguments are needed in the container command.
Docker:
```bash
docker build -t wago-sim .
docker run -d --name wago-sim -p 5020:502 \
-v ./config:/app/config:ro -v ./scenarios:/app/scenarios:ro \
-v ./plugins:/app/plugins:ro -v ./out:/app/out \
-e WAGO_SIM_NODE=config/pump_node.yaml \
wago-sim
```
Compose (restart policy, health check, log rotation included):
```bash
docker compose up -d # start
```
```bash
docker compose logs -f wago-sim
docker compose down # graceful SIGTERM shutdown
```
Bare metal / VM (systemd; unit file in `deploy/wago-sim.service`):
```bash
sudo cp deploy/wago-sim.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now wago-sim
journalctl -u wago-sim -f
```
Notes:
- Host port 5020 by default; use 502 if your bench expects it and your
host allows binding privileged ports (the container runs non-root;
the systemd unit grants `CAP_NET_BIND_SERVICE` to a dedicated user).
- `docker stop` sends SIGTERM; the server closes all client connections
and the listening socket cleanly (< 1 s) — useful for testing the
PLC's fieldbus-failure reaction deliberately.
- Reports from headless `run` invocations go to the mounted `out/`.
## Verbose mode (request tracing)
Run with `-v` / `--verbose` (or `WAGO_SIM_VERBOSE=1` for containers) to
trace every Modbus transaction in plain text — with the WAGO 750-362
register-map meaning of every address annotated:
```
$ wago-sim serve --node config/pump_node.yaml -v
REQ txn=0x0001 unit=1 FC03 Read Holding Registers
addr=0x2002 count=1 [config/constant regs 0x2002]
RSP txn=0x0001 unit=1 FC03 -> 1 regs: [0x1234]
REQ txn=0x0002 unit=1 FC06 Write Single Register
addr=0x0000 value=0x1234 [output area 1 (%QW0)]
RSP txn=0x0002 unit=1 FC06 -> echo
EXC txn=0x0006 unit=1 FC03 -> code=0x02 Illegal Data Address
RAW txn=0x0007 unit=1 PDU[3]: 07 00 00
```
Traces go to the standard log at DEBUG level, so they appear in the
terminal, in `docker logs`, and in `journalctl` alike. Read vs write
mappings are annotated correctly per the manual's Tables 119/120
(e.g. word address `0x0000` reads as `%IW0` but writes to `%QW0`).
## Documentation
- `docs/spec/software-spec.md` — the software specification
@@ -101,7 +176,7 @@ Work remaining, tracked against the software specification
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)
### Completed — framework phase (spec §3 + §4 core, 141 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
@@ -141,13 +216,30 @@ requirement ID for each item in parentheses.
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] Service / container deployment (spec §12): env-var config
(`WAGO_SIM_NODE/HOST/PORT`, CLI args override), graceful
SIGINT/SIGTERM shutdown via `server.stop()`, Dockerfile
(python:3.12-slim, non-root user, TCP health check),
docker-compose service (restart policy, resource limits, log
rotation, read-only config mounts), systemd unit example with
hardening; verified end-to-end (build, Modbus round-trip,
`docker stop` in 0.34 s); CLI env tests added.
- [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.
- [x] Test suite: 178 tests incl. real-socket server tests, `pymodbus`
interop, CLI env-config and bad-argument tests, and plain-text
trace tests; spec §7 acceptance criteria 1–7 covered.
- [x] Clean CLI error handling: invalid arguments (unknown command/
options, non-numeric or out-of-range `--port`, bad
`WAGO_SIM_PORT`), missing/unparsable node and scenario files, and
unwritable report paths all produce a one-line
`wago-sim: error: …` on stderr with exit code 2 — never a Python
traceback; missing report directories are auto-created;
internal errors keep the full traceback for debugging (NFR-6.4
operational goal).
### Framework gaps (spec §4 — Scenario & Macro Engine)
@@ -271,7 +363,12 @@ streaming, or entirely without it.
### Operational hardening (spec §6 — NFRs)
- [ ] Per-transaction Modbus trace log with sim-time + wall-time stamps
and PDU hex (NFR-6.4).
- [x] Plain-text request/response tracing in verbose mode (`-v` /
`WAGO_SIM_VERBOSE`): every transaction logged as readable
`REQ`/`RSP`/`EXC`/`RAW` lines with WAGO register-map annotations
(input/output areas, %IW/%QW mapping, read-back mirrors,
config/constant registers, illegal areas) per Tables 119/120 —
`src/wago_sim/trace.py` + tests. Covers NFR-6.4's readability goal;
PDU hex is included in `RAW` lines for malformed frames.
- [ ] Performance budget validation: ≥ 500 requests/s, ≤ 10 ms added
latency (NFR-6.1).