📝 Update spec and README

This commit is contained in:
Jonas Bengtsson
2026-10-04 14:15:26 +02:00
parent 59679b96ee
commit c30b444ef1
2 changed files with 84 additions and 10 deletions
+72 -9
View File
@@ -567,6 +567,12 @@ unchanged whether or not CSV streaming is used.
(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 `env` tick: `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 by `apply()` land in
the output image before the next tick's `observe()`, 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
@@ -574,6 +580,44 @@ unchanged whether or not CSV streaming is used.
top-level `until:` bounding the run. All §8.3–8.4 machinery (degradation,
invariants, rate limits, snapshots) applies identically, whether or not
any CSV streaming is configured.
- FR-8.2.14: **Plant environment abstraction** (requested 2026-10-04): the
continuous engine exposes a gymnasium-style **environment object** —
`PlantEnv` — as the single programmatic interface to the simulated plant.
Per deterministic tick (the engine's fixed tick clock, FR-4.3.9 — never
wall-clock), the cycle is:
**`state = env.observe()`** → the caller (plant logic, an external
agent, or the scenario) decides → **`env.apply(actions)`** writes the
decided output values → the engine advances exactly one tick
(edges, datasets, degradation, invariants — FR-8.2.12 order) → repeat.
`observe()` returns the state of the **generic inputs** — the WAGO
input process image in engineering units (all DI/DO/AI/AO channel
values, per-type module/channel addressing, configurable channel
names, plus the active aliases) — and `apply(actions)` maps decided
values onto the **output process image** through the datastore's
internal Modbus write paths, so mirrors, statistics, and watchdog
behavior stay consistent (FR-9.2.6 semantics). The environment owns
no policy: the plant model (physics, closed-loop control) is supplied
by the caller, exactly like `gym.Env` vs. its agents.
- FR-8.2.15: **Environment integration**: `PlantEnv` is the interface the
per-tick plugins (FR-4.3.8) are migrated onto: a plugin becomes
`env.observe()` → decide → `env.apply()` per tick, and a scenario may
alternatively register an external `step(env)` agent — including a
non-Python one (e.g., a trained RL policy) communicating over a
socket/pipe — without touching the engine loop. The existing
`plugins:` mechanism keeps working unchanged (thin adapter); the
engine always owns the tick clock, and exactly one decision source
may drive an output alias per tick (ownership conflicts are validation
errors, extending FR-8.2.2).
- FR-8.2.16: **Environment API surface** (concrete):
`env.observe() -> PlantState` (channel values, names, I/O codes,
ranges, sim-time, wear/parameters); `env.apply(actions) -> None`
(actions keyed by I/O code or alias name; unknown keys are errors);
`env.reset(config) -> PlantState` (fresh episode, deterministic
seed); `env.tick_ms`, `env.sim_time_ms` (read-only clock); and the
full §8.3/§8.4 machinery (parameters, degradation, invariants,
snapshots) reachable as `env.parameters`, `env.invariants`. The same
object is served over the scenario-run HTTP API in server mode (§9),
so external agents can run against a live PLC-connected bench.
### 8.3 Requirements — degradation models (plant logic over time)
@@ -744,15 +788,20 @@ src/wago_sim/scenario/
│ # 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 -> degradation ->
# datasets -> edges -> plugins/env -> degradation ->
# invariants -> scheduled steps -> snapshots
```
```mermaid
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
@@ -764,6 +813,7 @@ erDiagram
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)
@@ -809,6 +859,18 @@ erDiagram
tick-order test.
11. A continuous scenario with no datasets, no scheduled steps, and no
`until:` is rejected at validation time (FR-8.2.1).
12. **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 next `observe()` — verified by a
deterministic two-tick test where the decision source copies an input
to an output and the second observation contains it.
13. **Plugin parity** (FR-8.2.15): the same plant logic runs unchanged as
a classic `plugins:` entry and as an `env.observe/apply` decision
source — identical outputs over a 1000-tick headless run (regression
gate for the adapter).
14. **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).
---
@@ -1135,14 +1197,15 @@ This feature request closes that gap.
- 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): the web UI's
I/O configuration pane supports rearranging modules — move up/down
controls per module slot. Reordering triggers the same process-image
rebuild as any module change (FR-11.2.1–11.2.3) and persists the new
order. 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.
- 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