Consumer half of the abort-token mechanism added in generalFunctions
state.js. executeSequence captures host.state.sequenceAbortToken at
entry, then re-checks before every state transition and after the
optional ramp-down. If MGC (or any external caller) bumps the token
mid-sequence, the loop bails out cleanly — no more barge-through where
a pre-empted shutdown advances through stopping → coolingdown after a
fresh demand has already engaged the pump.
Without this the MGC rendezvous planner can't reliably re-dispatch a
pump that's mid-shutdown: the new flowmovement claims the gate, but
the old shutdown's for-loop keeps running on microtasks and steps the
FSM into idle/off underneath it.
Also: wiki regen following the same visual-first 14-section template as
the other EVOLV nodes — Reference-{Architecture,Contracts,Examples,
Limitations}.md split with _Sidebar.md index.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.9 KiB
Reference — Examples
Note
Every example flow shipped under
nodes/rotatingMachine/examples/, plus how to load them, what they show, and the debug recipes that go with them. Live source:nodes/rotatingMachine/examples/.
Shipped examples
| File | Tier | Dependencies | What it shows |
|---|---|---|---|
01 - Basic Manual Control.json |
1 | EVOLV only | Single pump driven by inject buttons — mode switching, startup / shutdown / e-stop, control-% and flow-unit setpoints, simulated pressures, maintenance enter / leave. Debug taps on all three ports. |
02 - Integration with Machine Group.json |
2 | EVOLV only | Parent-child demo — one machineGroupControl with 2 rotatingMachine children. Auto-registration via Port 2 on deploy. Per-pump simulated pressures. |
03 - Dashboard Visualization.json |
3 | EVOLV + @flowfuse/node-red-dashboard |
FlowFuse charts: flow / power / pressure trends, status panel, per-pump controls. |
Three legacy files (basic.flow.json, integration.flow.json, edge.flow.json) are kept until the new Tier-2 has been fully Docker-validated; they predate the AssetResolver refactor and may need re-save in the editor before they deploy.
Loading a flow
Via the editor
- Open the Node-RED editor at
http://localhost:1880. - Menu → Import → drag the JSON file.
- Click Deploy.
(The numbered files contain spaces; in the editor's import dialog the filename is purely cosmetic.)
Via the Admin API
curl -X POST -H 'Content-Type: application/json' \
--data @"nodes/rotatingMachine/examples/01 - Basic Manual Control.json" \
http://localhost:1880/flows
Example 01 — Basic Manual Control
Single-pump flow with one of every input you'd ever send. Validated against a live Node-RED instance (2026-03-05).
Nodes on the tab
| Type | Purpose |
|---|---|
comment |
Tab header / driver-group labels |
inject × 9 |
Mode (auto / virtualControl), startup, shutdown, e-stop, setpoint = 30 / 60 / 100 %, simulated upstream + downstream pressures, simulate flow / power for drift |
rotatingMachine |
The unit under test |
debug × 3 |
Port 0 (process), Port 1 (telemetry), Port 2 (registration) |
What to do after deploy
- Click the two pressure simulations (upstream = 0 mbar, downstream = 1100 mbar). Once both land,
predictionPressureSourceflips fromnulltodashboard-simandpredictionFlagsdrops thepressure_init_warmingflag. - Click
set.mode = virtualControlso the GUI source is allowed. - Click
cmd.startup. Watch Port 0 in the debug pane:statewalksidle → starting → warmingup → operational.runtimestarts accumulating. - Click
set.setpoint = 60(control %).stategoesoperational → accelerating → operational;ctrlrises from 0 to 60 at the configuredReaction Speed.flow.predicted.downstream.defaultandpower.predicted.atequipment.defaultupdate at every position tick. - Click
set.flow-setpoint = {value: 80, unit: 'm3/h'}— same path, but the setpoint is a flow value; the node converts viapredictCtrlto a control %. - Click
cmd.shutdown. State:operational → decelerating → stopping → coolingdown → idle. The ramp-to-zero step is interruptible; the subsequent transitions are timed bytime.stoppingandtime.coolingdown.
Important
GIF needed. Demo recording of steps 1–6 + the status badge progression. Save as
wiki/_partial-gifs/rotatingMachine/01-basic-demo.gif, target ≤ 1 MB aftergifsicle -O3 --lossy=80.
Try the residue handler
After the pump reaches operational at 60 %:
- Send
set.setpoint = 20.stategoesoperational → decelerating → …. - While
decelerating, sendset.setpoint = 80. state.moveTosees the residue, transitions back tooperationalsynchronously, then ramps up to 80. No setpoint is lost.
This is the same mechanism the MGC planner relies on for fast retargets.
Try the sequence-abort token
After the pump reaches operational at 60 %, simulate the Scenario-5 race:
- Send
cmd.shutdown. The pump begins ramping to zero. - Within the ramp window, send
set.setpoint = 60. The new setpoint's residue-handler claims the FSM back tooperational. - Watch the log: instead of the shutdown's for-loop continuing through
stopping → coolingdown → idle, you'll seeSequence 'shutdown' interrupted during ramp-down by external abort; not entering shutdown loop.
Without the token (pre-2026-05-15), the pump would have ended at idle despite the new setpoint — with delayedMove = 60 sitting unused.
Example 02 — Integration with Machine Group
Important
Screenshot needed. Editor capture of
02 - Integration with Machine Group.json. Save aswiki/_partial-screenshots/rotatingMachine/02-integration.png. Replace this callout with the image link.
One MGC + two rotatingMachine children. Demonstrates:
- Auto-registration via Port 2 at deploy (each pump's
child.registerreaches the MGC; no manual wiring needed). - Independent per-pump controls (the injects still target each pump's input by id).
- Group-level aggregation: MGC's Port 0 sums the children's predicted flow + power into the group aggregate.
The MGC planner is exercised when MGC's set.demand fires (not in this example by default; add an inject if you want to see it).
Example 03 — Dashboard Visualization
Important
Screenshots needed. Two captures: the editor tab and the rendered dashboard. Save as
wiki/_partial-screenshots/rotatingMachine/03-dashboard-editor.pngand04-dashboard-rendered.png.
A single pump on a FlowFuse Dashboard 2.0 page with:
- Control buttons (mode, startup, shutdown, e-stop)
- A setpoint slider
- Live status (state badge, ctrl%, predicted flow / power / efficiency)
- Trend charts: flow, power, pressure, drift level
Required: @flowfuse/node-red-dashboard installed in the Node-RED instance.
Docker compose snippet
To bring up Node-RED + InfluxDB with EVOLV nodes pre-loaded:
# docker-compose.yml (extract)
services:
nodered:
build: ./docker/nodered
ports: ['1880:1880']
volumes:
- ./docker/nodered/data:/data/evolv
influxdb:
image: influxdb:2.7
ports: ['8086:8086']
Full file: EVOLV/docker-compose.yml.
Debug recipes
| Symptom | First thing to check | Where to look |
|---|---|---|
Editor throws legacy asset field(s) [supplier] on deploy |
Flow predates the AssetResolver refactor. Re-open the node, pick the model from the asset menu, save. The registry derives supplier / category / type. | src/nodeClass.js _rejectLegacyAssetFields. |
state stuck on idle after cmd.startup |
The action isn't allowed for this mode / source combination. Check flowController warn log for <source> is not allowed in mode <mode> or <action> is not allowed in mode <mode>. |
_setupState, isValidSourceForMode, isValidActionForMode. |
flow.predicted.* reads 0 or NaN |
Pressure hasn't initialised. predictionFlags will include pressure_init_warming. Inject pressure via data.simulate-measurement or wire real measurement children. |
getMeasuredPressure + pressureSelector. |
predictionQuality: 'invalid' from startup |
Curve normalisation failed — null predictors installed. Look for Curve normalization failed for model … in the log. The asset / model is unrecognised, the unit isn't a flow unit, or the registry entry is missing. |
_setupCurves. |
Drift level stays at 3 after startup |
Fewer than minSamplesForLongTerm = 10 paired samples have landed. Wait ~10 ticks; the level falls automatically. |
driftProfiles.minSamplesForLongTerm. |
cmd.estop and then the pump won't restart |
Allowed transitions out of emergencystop are idle / off / maintenance. Send cmd.shutdown to drop into idle, then cmd.startup. |
stateConfig.allowedTransitions.emergencystop. |
| Position bounces near the target | dynspeed (cubic ease-in-out) can overshoot at high speed. Try staticspeed (linear). Both modes have the same total duration. |
movement.mode. |
Pump still drifts to idle after a mid-shutdown re-engage |
Verify the submodule is at 394a972 or newer — the sequence-abort token in state.js + sequenceController.js is what closes that race. |
state.sequenceAbortToken. |
data.simulate-measurement payloads aren't reflected on Port 0 |
Payload shape: {asset: {type: 'pressure', unit: 'mbar'}, value: 1100, position: 'downstream', childId: 'dashboard-sim-downstream'}. Missing asset.type or position gets a Unsupported simulateMeasurement type: warn and is dropped. |
measurementHandlers.updateSimulatedMeasurement. |
| Per-pump Port 0 key names differ from what your dashboard expects | rotatingMachine uses <type>.<variant>.<position>.<childId> (e.g. flow.predicted.downstream.default). MGC uses <position>_<variant>_<type>. Don't mix them. |
io/output.js, MeasurementContainer.getFlattenedOutput. |
Never ship
enableLog: 'debug'in a demo — fills the container log within seconds and obscures real errors.
Related pages
| Page | Why |
|---|---|
| Home | Intuitive overview |
| Reference — Contracts | Topic + config + child filters |
| Reference — Architecture | Code map, FSM, prediction + drift pipeline |
| Reference — Limitations | Known issues and open questions |
| machineGroupControl — Examples | Group-control demo flows |
| EVOLV — Topology Patterns | Where rotatingMachine fits in a larger plant |