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>
170 lines
9.9 KiB
Markdown
170 lines
9.9 KiB
Markdown
# 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
|
|
|
|
1. Open the Node-RED editor at `http://localhost:1880`.
|
|
2. Menu → Import → drag the JSON file.
|
|
3. Click Deploy.
|
|
|
|
(The numbered files contain spaces; in the editor's import dialog the filename is purely cosmetic.)
|
|
|
|
### Via the Admin API
|
|
|
|
```bash
|
|
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
|
|
|
|
1. Click the two pressure simulations (upstream = 0 mbar, downstream = 1100 mbar). Once both land, `predictionPressureSource` flips from `null` to `dashboard-sim` and `predictionFlags` drops the `pressure_init_warming` flag.
|
|
2. Click `set.mode = virtualControl` so the GUI source is allowed.
|
|
3. Click `cmd.startup`. Watch Port 0 in the debug pane: `state` walks `idle → starting → warmingup → operational`. `runtime` starts accumulating.
|
|
4. Click `set.setpoint = 60` (control %). `state` goes `operational → accelerating → operational`; `ctrl` rises from 0 to 60 at the configured `Reaction Speed`. `flow.predicted.downstream.default` and `power.predicted.atequipment.default` update at every position tick.
|
|
5. Click `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. Click `cmd.shutdown`. State: `operational → decelerating → stopping → coolingdown → idle`. The ramp-to-zero step is interruptible; the subsequent transitions are timed by `time.stopping` and `time.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 after `gifsicle -O3 --lossy=80`.
|
|
|
|
### Try the residue handler
|
|
|
|
After the pump reaches `operational` at 60 %:
|
|
|
|
1. Send `set.setpoint = 20`. `state` goes `operational → decelerating → …`.
|
|
2. While `decelerating`, send `set.setpoint = 80`.
|
|
3. `state.moveTo` sees the residue, transitions back to `operational` synchronously, 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:
|
|
|
|
1. Send `cmd.shutdown`. The pump begins ramping to zero.
|
|
2. *Within the ramp window*, send `set.setpoint = 60`. The new setpoint's residue-handler claims the FSM back to `operational`.
|
|
3. Watch the log: instead of the shutdown's for-loop continuing through `stopping → coolingdown → idle`, you'll see `Sequence '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 as `wiki/_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.register` reaches 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.png` and `04-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:
|
|
|
|
```yaml
|
|
# 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](https://gitea.wbd-rd.nl/RnD/EVOLV/src/branch/development/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](Home) | Intuitive overview |
|
|
| [Reference — Contracts](Reference-Contracts) | Topic + config + child filters |
|
|
| [Reference — Architecture](Reference-Architecture) | Code map, FSM, prediction + drift pipeline |
|
|
| [Reference — Limitations](Reference-Limitations) | Known issues and open questions |
|
|
| [machineGroupControl — Examples](https://gitea.wbd-rd.nl/RnD/machineGroupControl/wiki/Reference-Examples) | Group-control demo flows |
|
|
| [EVOLV — Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) | Where rotatingMachine fits in a larger plant |
|