A `rotatingMachine` models a single pump, compressor, or blower. It loads a supplier characteristic curve, takes upstream + downstream pressure measurements (real or simulated), predicts the resulting flow + power, drives a startup / shutdown state machine, and assesses prediction drift against measured flow / power. Used as a child of `machineGroupControl` when grouped, or directly under `pumpingStation` for a one-pump station.
1.`data.simulate-measurement` (upstream + downstream) — injects ~0 mbar suction and ~1100 mbar discharge so the predictor has something to work with.
2.`set.mode = virtualControl` — lets the GUI source drive the pump (parent path is for grouped use).
4.`set.setpoint = 60` (control %) — pump ramps from `0` to `60` at the configured `Reaction Speed`; state goes `operational → accelerating → operational`.
5.`set.flow-setpoint = {value: 80, unit: "m3/h"}` — same path, but the setpoint is a flow value; the node converts via `predictCtrl` to a control %.
> **GIF needed.** Demo recording of steps 1–6 with the live status panel. Save as `wiki/_partial-gifs/rotatingMachine/01-basic-demo.gif`, target ≤ 1 MB after `gifsicle -O3 --lossy=80`.
| `set.mode` | `setMode` | `"auto"` \| `"virtualControl"` \| `"fysicalControl"` | Switch between parent-controlled, GUI-controlled, and physical-source-only. Each mode has its own allow-list for actions and sources. |
| `cmd.startup` | — | any | Run the configured startup sequence (default `[starting, warmingup, operational]`). |
| `cmd.shutdown` | — | any | Run the configured shutdown sequence (default `[stopping, coolingdown, idle]`). `operational` triggers a ramp-to-zero first. |
| `cmd.estop` | `emergencystop` | any | Hard cut: runs the `emergencystop` sequence (default `[emergencystop, off]`). Reachable from every state. |
| `set.setpoint` | `execMovement` | `{setpoint: number}` (control %) | Move to a control-% setpoint. |
| `set.flow-setpoint` | `flowMovement` | `{setpoint: number}` (flow, unit per `units`) | Move to a flow setpoint. Converted to canonical m³/s, then to control % via `predictCtrl`. |
Key shape: **`<type>.<variant>.<position>.<childId>`** — the inverse of MGC's key shape, because rotatingMachine emits per-measurement snapshots. The trailing `<childId>` is the registering child's id (`dashboard-sim-upstream`, `dashboard-sim-downstream`, or `default` for own predictions). Position labels are normalised to lowercase in keys.
| Field | Meaning |
|:---|:---|
| `state` | Current FSM state. See [Architecture — FSM](Reference-Architecture#fsm). |
| `ctrl` | Control-axis position (`0..100`). |
| `mode` | One of `auto` / `virtualControl` / `fysicalControl`. |
| `runtime` | Accumulated hours in active states (operational and movement variants). |
| `flow.predicted.{downstream,atequipment}.default` | Predicted flow at the current operating point (canonical m³/s; renders to `m3/h`). |
| `power.predicted.atequipment.default` | Predicted shaft power (canonical W; renders to `kW`). |
| `predictionQuality` | `good` / `warming` / `degraded` / `invalid` — derived by `predictionHealth` from drift + pressure availability. |
| `predictionPressureSource` | `dashboard-sim` (virtual children active) or a real-child id (real children preferred). |
| `predictionFlags` | Reason codes when health < `good` (e.g. `pressure_init_warming`, `flow_high_drift`). |
| `cog` / `NCog` / `NCogPercent` | Centre-of-gravity metric on the η curve. `NCog` is normalised 0..1. |
| `effDistFromPeak` / `effRelDistFromPeak` | Distance from the η peak (absolute and 0..1 relative). |
When a parent MGC sends a new demand, it calls `abortMovement` to interrupt any in-flight `accelerating` / `decelerating` movement. Before 2026-05-15 that abort only stopped the moveTo — an in-flight `executeSequence('shutdown')` for-loop would keep transitioning the FSM through `stopping → coolingdown → idle`, fighting the new dispatch's residue-handler.
The pump now carries a monotonic `sequenceAbortToken` on its state object. External aborts (the kind MGC fires) advance it; sequence-internal aborts (e.g. shutdown's own pre-empt of its ramp-down step) do not. `executeSequence` captures the token at entry and bails out before its next transition if the counter has advanced.
Net effect: a mid-decel re-engage takes the pump cleanly back to operational, without the orphaned shutdown completing in the background. `warmingup` and `coolingdown` remain protected at the stateManager layer — safety guarantees are unchanged.