# rotatingMachine ![code-ref](https://img.shields.io/badge/code--ref-394a972-blue) ![s88](https://img.shields.io/badge/S88-Equipment_Module-86bbdd) ![status](https://img.shields.io/badge/status-trial--ready-brightgreen) 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. --- ## At a glance | Thing | Value | |:---|:---| | What it represents | One rotating asset on a curve — pump, blower, compressor | | S88 level | Equipment Module | | Use it when | You have a curve-modelled asset whose flow / power varies with header differential and you want predictions + drift | | Don't use it for | Passive non-return valves (`valve`), curveless assets (will silently emit zeros), groups (parent under `machineGroupControl`) | | Children it accepts | `measurement` (pressure / flow / power / temperature) | | Parents it talks to | `machineGroupControl`, `pumpingStation`, or any node that issues `flowmovement` / `execsequence` | --- ## How it fits ```mermaid flowchart LR parent[machineGroupControl /
pumpingStation]:::unit -->|flowmovement
execsequence| rm[rotatingMachine
Equipment]:::equip m_up[measurement
pressure upstream]:::ctrl -.measured.-> rm m_dn[measurement
pressure downstream]:::ctrl -.measured.-> rm sim[dashboard-sim-upstream /
dashboard-sim-downstream
(auto-registered virtual children)]:::ctrl -.measured.-> rm rm -->|child.register| parent rm -.->|flow.predicted.*
power.predicted.atequipment| parent classDef unit fill:#50a8d9,color:#000 classDef equip fill:#86bbdd,color:#000 classDef ctrl fill:#a9daee,color:#000 ``` S88 colours are anchored in `.claude/rules/node-red-flow-layout.md`. --- ## Try it — 3-minute demo Import the basic example flow, deploy, and drive a single pump through the full state machine. ```bash curl -X POST -H 'Content-Type: application/json' \ --data @nodes/rotatingMachine/examples/01\ -\ Basic\ Manual\ Control.json \ http://localhost:1880/flow ``` What to click after deploy (the inject buttons map one-to-one to topics in [Reference — Contracts](Reference-Contracts#topic-contract)): 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). 3. `cmd.startup` — FSM runs `idle → starting → warmingup → operational`. `runtime` starts accumulating. 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 %. 6. `cmd.shutdown` — `operational → decelerating → stopping → coolingdown → idle`. > [!IMPORTANT] > **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`. --- ## The seven things you'll send | Topic | Aliases | Payload | What it does | |:---|:---|:---|:---| | `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`. | | `data.simulate-measurement` | `simulateMeasurement` | `{asset: {type, unit}, value, position, childId?}` | Inject a virtual sensor reading (pressure / flow / power / temperature). | Plus two query topics for dashboards: | Topic | Aliases | Returns on the reply port | |:---|:---|:---| | `query.curves` | `showWorkingCurves` | The working curves (flow / power / efficiency) at the current operating point. | | `query.cog` | `CoG` | The centre-of-gravity (CoG) of the η curve. | --- ## What you'll see come out Sample Port 0 message (delta-compressed, while operational at ~60 % control): ```json { "topic": "rotatingMachine#pump_a", "payload": { "state": "operational", "ctrl": 60.0, "mode": "auto", "runtime": 0.024, "flow.predicted.downstream.default": 12.4, "flow.predicted.atequipment.default": 12.4, "power.predicted.atequipment.default": 18.2, "pressure.measured.upstream.dashboard-sim-upstream": 0, "pressure.measured.downstream.dashboard-sim-downstream": 1100, "predictionQuality": "good", "predictionConfidence": 0.92, "predictionPressureSource": "dashboard-sim", "predictionFlags": [], "cog": 0.62, "NCog": 0.71, "NCogPercent": 62, "effDistFromPeak": 0.04, "effRelDistFromPeak": 0.12 } } ``` Key shape: **`...`** — the inverse of MGC's key shape, because rotatingMachine emits per-measurement snapshots. The trailing `` 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). | --- ## The new bit — sequence-abort token 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. See [Architecture — FSM](Reference-Architecture#fsm) for the full mechanism. --- ## Need more? | Page | What you'll find | |:---|:---| | [Reference — Contracts](Reference-Contracts) | Full topic contract, config schema, child registration filters | | [Reference — Architecture](Reference-Architecture) | Code map, FSM, prediction pipeline, drift, lifecycle | | [Reference — Examples](Reference-Examples) | Shipped example flows + debug recipes | | [Reference — Limitations](Reference-Limitations) | When not to use, known limitations, open questions | [EVOLV master wiki](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Home) · [Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) · [Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions)