Bumps: - nodes/generalFunctions 024db55 -> 75d16c6 (FSM abort recovery + schema sync) - nodes/rotatingMachine 07af7ce -> 17b8887 (interruptible sequences, dual-curve tests, rewritten README) Wiki: - wiki/manuals/nodes/rotatingMachine.md — new user manual covering inputs, outputs, state machine, supported curves, and troubleshooting. - wiki/sessions/2026-04-13-rotatingMachine-trial-ready.md — session note with findings, fixes, test additions, and dual-curve E2E results. - wiki/index.md — link both and bump updated date. Status: rotatingMachine is now trial-ready. 91/91 unit tests green, live Docker E2E verifies shutdown/emergency-stop during ramps and prediction behaviour across both shipped pump curves (hidrostal-H05K-S03R, hidrostal-C5-D03R-SHN1). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
12 KiB
title, node, updated, status
| title | node | updated | status |
|---|---|---|---|
| rotatingMachine — User Manual | rotatingMachine | 2026-04-13 | trial-ready |
rotatingMachine — User Manual
The rotatingMachine node models a single pump, compressor, or blower. It runs an S88-style state machine, predicts flow and power from a supplier curve, and publishes process and telemetry data every second. It is the atomic control module beneath machineGroupControl and pumpingStation.
This manual is the operator-facing reference. For architecture and the 3-tier code layout see Node Architecture; for curve theory see 3D Pump Curves.
At a glance
| Item | Value |
|---|---|
| Node category | EVOLV |
| Inputs | 1 (message-driven) |
| Outputs | 3 — process / dbase / parent |
| Tick period | 1 s |
| State machine | 10 states (S88) |
| Predictions | curve-backed (nq flow, np power, reversed nq for ctrl) |
| Canonical units | Pa, m³/s, W, K |
Editor configuration
| Field | Default | Meaning |
|---|---|---|
| Reaction Speed | 1 |
Ramp rate in controller-position units per second. 1 = 1 %/s. |
| Startup Time | 0 |
Seconds in the starting state. |
| Warmup Time | 0 |
Seconds in the protected warmingup state. |
| Shutdown Time | 0 |
Seconds in the stopping state. |
| Cooldown Time | 0 |
Seconds in the protected coolingdown state. |
| Movement Mode | staticspeed |
staticspeed = linear ramp; dynspeed = ease-in/out. |
| Process Output | process |
Port 0 payload format: process (delta-compressed) / json / csv. |
| Database Output | influxdb |
Port 1 payload format: influxdb line protocol / json / csv. |
| Asset (menu) | — | Supplier, category, model (must match a curve file in generalFunctions/datasets), output flow unit, curve units. |
| Logger (menu) | info, enabled |
Log level and toggle. |
| Position (menu) | atEquipment |
upstream / atEquipment / downstream relative to parent. Icon and optional distance offset. |
Tip. With
Reaction Speed = 1andSet 60%from idle, the controller takes ~60 s to reach 60 %. ScaleReaction Speedup to emulate a faster actuator (e.g.20gives 1 second per 20 % = 3 s to reach 60 %).
Input topics
Every command enters on the single input port. msg.topic selects the handler; msg.payload carries the arguments.
setMode
{ "topic": "setMode", "payload": "virtualControl" }
Valid values: auto, virtualControl, fysicalControl. The current mode gates which source may issue which action (mode/action/source policy lives in generalFunctions/src/configs/rotatingMachine.json).
execSequence
{ "topic": "execSequence",
"payload": { "source": "GUI", "action": "execSequence", "parameter": "startup" } }
parameter values: startup, shutdown, entermaintenance, exitmaintenance. Case is normalized.
If a shutdown is issued while the machine is mid-ramp (accelerating / decelerating), the active movement is aborted and the shutdown proceeds as soon as the FSM has returned to operational.
execMovement
{ "topic": "execMovement",
"payload": { "source": "GUI", "action": "execMovement", "setpoint": 60 } }
setpoint is expressed in controller units (0–100 %).
flowMovement
{ "topic": "flowMovement",
"payload": { "source": "parent", "action": "flowMovement", "setpoint": 150 } }
setpoint is expressed in the configured output flow unit (e.g. m³/h). The node converts flow → controller-% via the reversed nq curve and then drives execMovement.
emergencystop
{ "topic": "emergencystop",
"payload": { "source": "GUI", "action": "emergencystop" } }
Aborts any active movement, runs the emergencystop → off transition. Allowed from every active state. Case-insensitive.
simulateMeasurement
Inject a dashboard-side measurement without wiring a sensor child. Useful for validation, smoke tests, demo flows.
{ "topic": "simulateMeasurement",
"payload": { "type": "pressure", "position": "upstream", "value": 200, "unit": "mbar" } }
type: pressure / flow / temperature / power. unit is required and must be convertible to the canonical unit for the type.
Diagnostics
showWorkingCurves— snapshot of current curve slices + computed metrics; reply on port 0.CoG— current centre-of-gravity (peak efficiency point) indicators; reply on port 0.
registerChild
Internal. Sensor children (typically measurement nodes) send this to bind themselves to the machine. The machine also emits one on port 2 shortly after deploy so a parent group/station can register it.
Output ports
Port 0 — process data
Delta-compressed payload. Only changed fields are emitted each tick. Keys use a 4-segment format:
<type>.<variant>.<position>.<childId>
Examples:
| Key | Meaning |
|---|---|
flow.predicted.downstream.default |
predicted flow at discharge |
flow.predicted.atequipment.default |
predicted flow at equipment |
power.predicted.atequipment.default |
predicted electrical power draw |
pressure.measured.downstream.dashboard-sim-downstream |
simulated discharge pressure |
pressure.measured.upstream.<childId> |
real upstream sensor reading |
state |
current FSM state |
mode |
current mode |
ctrl |
current controller position (0–100 %) |
NCog / cog |
normalized / absolute centre-of-gravity |
runtime |
cumulative operational hours |
Consumers must cache and merge deltas. The example flow 01 - Basic Manual Control.json includes a function node that does exactly this — reuse its logic in your own flows.
Port 1 — dbase (InfluxDB)
InfluxDB line-protocol payload formatted for the telemetry bucket. Tags are low-cardinality fields (node name, machine type); measurements are numeric values. See the InfluxDB Schema Design page for the full tag/field contract.
Port 2 — parent
{ topic: "registerChild", payload: <this-node-id>, positionVsParent } — emitted once ~180 ms after deploy so a downstream parent group can discover this machine. Subsequent commands and data flow through the parent's input port.
State machine
┌────────────────────────────┐
│ operational │◄────┐
└────┬──────────┬────────┬────┘ │
│ │ │ │
execMovement │ │ │ │
execMovement │ │ │ │
▼ ▼ ▼ ▼ │
accelerating decelerating │ emergencystop ─► off
│ │ │
└─── (abort)─┘ │
│ │
┌────▼──────────▼────┐
│ stopping │
└────────┬─────────────┘
│
coolingdown
│
idle
│
starting
│
warmingup
│
(operational)
Protected states (cannot be aborted by a new command): warmingup, coolingdown.
Interruptible states: accelerating, decelerating. A shutdown or emergencystop issued during a ramp aborts the ramp and drives the FSM correctly to idle / off.
Active states (contribute to runtime): operational, starting, warmingup, accelerating, decelerating.
Predictions and pressure
Flow and power are curve-backed. The curve set is indexed by the differential pressure across the machine:
- Best: both upstream and downstream pressures present → real Δp.
- Degraded: only one side present → falls back to that side with a warn.
- Minimum: no pressure →
fDimension = 0; flow and power predictions use the lowest curve slice and will look unrealistic.
Pressure sources are resolved in priority order real sensor child > virtual dashboard child > aggregated fallback. Real-child values always win.
Predictions are only emitted while the FSM is in an active state (operational, starting, warmingup, accelerating, decelerating). In idle, stopping, coolingdown, off, maintenance the outputs are clamped to zero.
Supported curves and verification
| Model | Pressure envelope | Flow envelope | Power envelope |
|---|---|---|---|
hidrostal-H05K-S03R |
700 – 3900 mbar (33 slices) | 9.5 – 227 m³/h | 8.2 – 65.1 kW |
hidrostal-C5-D03R-SHN1 |
400 – 2900 mbar (26 slices) | 6.4 – 52.5 m³/h | 0.55 – 31.5 kW |
Both curves are covered by unit tests (test/integration/curve-prediction.integration.test.js) and a live E2E benchmark (test/e2e/curve-prediction-benchmark.py) that sweeps each pump through its own pressure × controller envelope. Last green run: 2026-04-13 — 12/12 samples per curve inside envelope, ctrl-monotonic, inverse-pressure monotonic.
Pressure out of envelope is not clamped. If a measured pressure falls below the curve's minimum slice, the node extrapolates and may produce implausibly large flow values (e.g. H05K at 400 mbar, ctrl 20 % → flow ≈ 30 000 m³/h; real envelope max is 227). Use realistic sensor ranges on your pressure
measurementchildren.
Example flows
In the editor: Import ▸ Examples ▸ EVOLV ▸ rotatingMachine.
01 - Basic Manual Control.json— single machine, inject-only. Good for smoke-testing a node installation.02 - Integration with Machine Group.json—machineGroupControlwith two pumps as children. Good for verifying registration and parent orchestration.03 - Dashboard Visualization.json— FlowFuse dashboard with live charts. Depends on@flowfuse/node-red-dashboard.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Editor says pressure not initialized, status ring is yellow |
No pressure child wired yet and no simulated pressure injected. | Inject a simulateMeasurement of type pressure (both sides preferred) or wire a measurement child. |
Predictions are enormous at ctrl = 0 % |
At near-zero controller position with high backpressure, the intercept of the curve gives a nominally-nonzero flow. This is a curve-data artefact, not a runtime bug. | Confirm the curve with Rene / supplier data. For a conservative prediction use a lower Reaction Speed or constrain setpoint ≥ 10 %. |
| "Transition aborted" / "Movement aborted" in logs | Expected during shutdown / emergencystop issued during a ramp — the fix path intentionally aborts the active move. |
None — informational only. |
Status bar shows pressure not initialized even after inject |
simulateMeasurement payload missing unit or with a non-convertible value. |
Include unit (e.g. "mbar") and a finite number in value. |
| Shutdown does nothing and no error | Machine is in warmingup or coolingdown (protected). |
Wait for the phase to complete (≤ configured seconds) and retry. |
Running it locally
git clone --recurse-submodules https://gitea.wbd-rd.nl/RnD/EVOLV.git
cd EVOLV
docker compose up -d
# Node-RED: http://localhost:1880 InfluxDB: :8086 Grafana: :3000
Then in Node-RED: Import ▸ Examples ▸ EVOLV ▸ rotatingMachine ▸ 01 - Basic Manual Control.
Testing
cd nodes/rotatingMachine
npm test
Unit tests (79) cover construction, mode gating, sequences, interruptible movement, emergency stop, shutdown, efficiency/CoG, pressure initialization, output formatting, listener cleanup. See also examples/README.md for the flow-level test matrix.
Production status
See the project memory entry node_rotatingMachine.md for the latest benchmarks and wishlist. Trial-ready as of 2026-04-13 following the interruptibility + schema-sync fixes documented in session 2026-04-13.