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>
16 KiB
Reference — Contracts
Note
Full topic contract, configuration schema, and child-registration filters for
rotatingMachine. Source of truth:src/commands/index.js,src/specificClass.jsconfigure(), and the schema atgeneralFunctions/src/configs/rotatingMachine.json.For an intuitive overview, return to the Home.
Topic contract
The registry lives in src/commands/index.js. Each descriptor maps a canonical msg.topic to its handler; aliases emit a one-time deprecation warning the first time they fire.
| Canonical topic | Aliases | Payload | Unit | Effect |
|---|---|---|---|---|
set.mode |
setMode |
string (auto / virtualControl / fysicalControl) |
— | Switch operational mode. Each mode has its own allow-list of actions and sources. |
cmd.startup |
— | any | — | Run the configured startup sequence (default [starting, warmingup, operational]). |
cmd.shutdown |
— | any | — | Run the shutdown sequence. If currently operational, executeSequence first ramps the setpoint to 0 (interruptible). |
cmd.estop |
emergencystop |
any | — | Run the emergencystop sequence (default [emergencystop, off]). Reachable from every state. |
set.setpoint |
execMovement |
{setpoint: number} |
control % (no units — convert has no percent measure) |
Move to a control-axis setpoint via state.moveTo. |
set.flow-setpoint |
flowMovement |
{setpoint: number} or bare number |
volumeFlowRate (default m3/h) |
Convert to canonical m³/s, then to control % via predictCtrl.y, then state.moveTo. |
data.simulate-measurement |
simulateMeasurement |
{asset: {type, unit}, value, position, childName?, childId?} |
type-specific | Inject a virtual sensor reading. The two virtual children (dashboard-sim-upstream / -downstream) auto-handle pressure; other types use the registering child's id. |
query.curves |
showWorkingCurves |
any | — | Reply on Port 0 with the current working curves (flow / power / efficiency). |
query.cog |
CoG |
any | — | Reply on Port 0 with the centre-of-gravity (CoG) point. |
child.register |
registerChild |
string (child node id) |
— | Register a measurement child with this machine. Port 2 wiring does this automatically in normal flows. |
execSequence |
— | {action: "startup" | "shutdown"} |
— | Legacy umbrella: demuxes payload.action to the canonical cmd.startup / cmd.shutdown handler. Marked _legacy: true; scheduled for removal. |
Mode / source / action allow-lists
A topic that survives the registry still passes through flowController.handle:
if (!host.isValidActionForMode(action, host.currentMode)) return;
if (!host.isValidSourceForMode(source, host.currentMode)) return;
Defaults from the schema:
| Mode | allowedActions |
allowedSources |
|---|---|---|
auto |
statuscheck, execmovement, execsequence, flowmovement, emergencystop, entermaintenance |
parent, GUI, fysical |
virtualControl |
statuscheck, execmovement, flowmovement, execsequence, emergencystop, exitmaintenance |
GUI, fysical |
fysicalControl |
statuscheck, emergencystop, entermaintenance, exitmaintenance |
fysical |
A rejected request logs at warn and short-circuits; nothing reaches the FSM.
Data model — getOutput() shape
Composed each tick by src/io/output.js buildOutput(). Delta-compressed: consumers see only the keys that changed.
Per-measurement keys
For every (type, variant, position) stored in MeasurementContainer, the flattened output emits:
<type>.<variant>.<position>.<childId>
Position labels are normalised to lowercase in the keys (atequipment, downstream, upstream, max, min). The trailing <childId> is:
<childId> |
When |
|---|---|
default |
The node's own predictions (flow / power / efficiency / Ncog). |
dashboard-sim-upstream / dashboard-sim-downstream |
The two auto-registered virtual pressure children. |
The real child's general.id |
When a registered measurement child wrote the value. |
Sample keys (operational pump, simulated pressure):
| Key | Type | Unit | Notes |
|---|---|---|---|
flow.predicted.downstream.default |
number | m³/h | Live predicted flow. |
flow.predicted.atequipment.default |
number | m³/h | Same number, equipment-side label. |
flow.predicted.max.default / .min.default |
number | m³/h | Curve envelope at the current fDimension. |
power.predicted.atequipment.default |
number | kW | Predicted shaft power. |
pressure.measured.upstream.dashboard-sim-upstream |
number | mbar | Last simulated suction pressure. |
pressure.measured.downstream.dashboard-sim-downstream |
number | mbar | Last simulated discharge pressure. |
temperature.measured.atequipment.dashboard-sim-upstream |
number | °C | Default 15°C until overwritten. |
atmPressure.measured.atequipment.dashboard-sim-upstream |
number | Pa | Default 101325 Pa until overwritten. |
Scalar keys
| Key | Type | Source | Notes |
|---|---|---|---|
state |
string | host.state.getCurrentState() |
One of the FSM states (idle, starting, warmingup, …). |
ctrl |
number | host.state.getCurrentPosition() |
Control-axis position 0..100. |
mode |
string | host.currentMode |
auto / virtualControl / fysicalControl. |
runtime |
number | host.state.getRunTimeHours() |
Cumulative hours in active states. |
moveTimeleft |
number | host.state.getMoveTimeLeft() |
Seconds remaining on the current move (0 when idle). |
maintenanceTime |
number | host.state.getMaintenanceTimeHours() |
Cumulative hours in maintenance. |
cog / NCog / NCogPercent |
number | host.cog etc. |
CoG metric on the η curve. NCog 0..1; NCogPercent is NCog * 100, rounded to 2 dp. |
effDistFromPeak |
number | host.absDistFromPeak |
Absolute η distance to peak. |
effRelDistFromPeak |
number | host.relDistFromPeak |
Normalised 0..1; undefined when η band collapses. |
predictionQuality |
string | host.predictionHealth.quality |
good / warming / degraded / invalid. |
predictionConfidence |
number | host.predictionHealth.confidence |
0..1, rounded to 3 dp. |
predictionPressureSource |
string | null | host.predictionHealth.pressureSource |
dashboard-sim or a real child id; null until pressure landed. |
predictionFlags |
array | host.predictionHealth.flags |
Reason codes (e.g. pressure_init_warming). |
pressureDriftLevel |
number | host.pressureDrift.level |
0..3. |
pressureDriftSource |
string | null | host.pressureDrift.source |
Source whose drift is worst. |
pressureDriftFlags |
array | host.pressureDrift.flags |
nominal when no drift detected. |
flowNrmse / flowLongTermNRMSD / flowImmediateLevel / flowLongTermLevel / flowDriftValid |
numbers / number / number / boolean | host.flowDrift |
Only present once flowDrift != null. |
powerNrmse / powerLongTermNRMSD / powerImmediateLevel / powerLongTermLevel / powerDriftValid |
same | host.powerDrift |
Same. |
Status badge
buildStatusBadge in io/output.js:
<mode>: <state-symbol> <ctrl%>% 💨<flow><unit> ⚡<power>kW
State symbols (per STATE_SYMBOLS map):
| State | Symbol | Fill |
|---|---|---|
off |
⬛ | red |
idle |
⏸️ | blue |
operational |
⏵️ | green |
starting |
⏯️ | yellow |
warmingup |
🔄 | green |
accelerating |
⏩ | yellow |
decelerating |
⏪ | yellow |
stopping |
⏹️ | yellow |
coolingdown |
❄️ | yellow |
maintenance |
🔧 | grey |
Pressure-not-initialised states (operational, warmingup, accelerating, decelerating) override the badge to a yellow ring '<mode>: pressure not initialized' until at least one pressure source has been written.
Configuration schema — editor form to config keys
Source of truth: generalFunctions/src/configs/rotatingMachine.json plus nodeClass.buildDomainConfig.
General (config.general)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Name | general.name |
derived: <softwareType>_<id> |
Re-derived in configure(). |
| (auto-assigned) | general.id |
null |
Node-RED node id. |
| Default unit | general.unit |
l/s (schema) / m3/h (nodeClass) |
buildDomainConfig resolves uiConfig.unit via convert and overrides to a valid flow unit. |
| Enable logging | general.logging.enabled |
true |
Master switch. |
| Log level | general.logging.logLevel |
info |
debug / info / warn / error. |
Functionality (config.functionality)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Position vs parent | functionality.positionVsParent |
atEquipment |
One of atEquipment / upstream / downstream. Used in the child-register payload that goes UP to MGC / pumpingStation. |
| (hidden) | functionality.softwareType |
rotatingmachine |
Constant. |
| (hidden) | functionality.role |
RotationalDeviceController |
Constant. |
| Distance offset | functionality.distance |
null |
Optional spatial offset; populated when hasDistance is enabled. |
| Distance unit | functionality.distanceUnit |
m |
|
| Distance description | functionality.distanceDescription |
"" |
Free-text. |
Asset (config.asset)
Resolved derived metadata (supplier / category / type / allowed units) lives in generalFunctions/datasets/assetData/rotatingmachine.json keyed by asset.model. The editor's asset menu reads from that registry.
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Asset UUID | asset.uuid |
null |
Globally-unique identifier. |
| Tag code | asset.tagCode |
null |
|
| Tag number | asset.tagNumber |
null |
Legacy column. |
| Geolocation | asset.geoLocation |
{x:0, y:0, z:0} |
|
| Model | asset.model |
null |
Required. Resolves curve + supplier / type / allowed units via the registry. |
| Deployment unit | asset.unit |
null |
Required. Must be a flow unit; soft-warned if not in the registry's recommended list for the model. |
| Curve units | asset.curveUnits |
{pressure:'mbar', flow:'m3/h', power:'kW', control:'%'} |
Carried for curve normalisation. |
| Accuracy | asset.accuracy |
null |
Optional sensor accuracy %. |
| (derived) | asset.machineCurve |
{nq:{}, np:{}} |
Loaded from loadModelCurve(model), then normalised. |
Warning
Legacy fields removed.
supplier,category, andassetTypeare no longer node config — the registry derives them from the model. Flows saved before the AssetResolver refactor will throw a startup error with a clear migration message. Re-open the node, re-select the model from the asset menu, and save.
State times (stateConfig.time)
Set on the state machine via nodeClass.buildDomainConfig from editor fields:
| Form field | Config key | Default (schema) | Notes |
|---|---|---|---|
| Startup Time | time.starting |
configured in s | Time spent in starting before transitioning to warmingup. |
| Warmup Time | time.warmingup |
configured in s | Time in warmingup — non-interruptible safety. |
| Shutdown Time | time.stopping |
configured in s | Time in stopping. |
| Cooldown Time | time.coolingdown |
configured in s | Time in coolingdown — non-interruptible safety. |
Movement (stateConfig.movement)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Reaction Speed | movement.speed |
configured in %/s | Controller ramp rate. E.g. 1 means 1%/s → setpoint 60 from idle reaches 60 in ~60 s. |
| Movement Mode | movement.mode |
staticspeed |
staticspeed (linear ramp) or dynspeed (cubic ease-in-out). Both yield the same total duration; only the curve differs. |
| (internal) | movement.maxSpeed |
from schema | Hard cap honoured by movementManager.getNormalizedSpeed. |
| (internal) | movement.interval |
from schema | Inner-loop tick of the move animation (ms). |
Sequences (config.sequences)
State-transition lists per sequence name. Defaults:
| Sequence | States |
|---|---|
startup |
[starting, warmingup, operational] |
shutdown |
[stopping, coolingdown, idle] |
emergencystop |
[emergencystop, off] |
boot |
[idle, starting, warmingup, operational] |
entermaintenance |
[stopping, coolingdown, idle, maintenance] |
exitmaintenance |
[off, idle] |
Custom sequences are accepted as long as every step is a known FSM state and the transitions between them are allowed by stateConfig.allowedTransitions.
Output (config.output)
| Form field | Config key | Default | Range | Notes |
|---|---|---|---|---|
| Process Output | output.process |
process |
process / json / csv |
Port-0 formatter. |
| Database Output | output.dbase |
influxdb |
influxdb / json / csv |
Port-1 formatter. |
Mode (config.mode)
| Form field | Config key | Default | Range | Notes |
|---|---|---|---|---|
| Mode | mode.current |
auto |
auto / virtualControl / fysicalControl |
The active operational mode. |
| (defaults) | mode.allowedActions.<mode> |
see Architecture | enforced by flowController.handle |
|
| (defaults) | mode.allowedSources.<mode> |
see Architecture | enforced by flowController.handle |
Unit policy
Source: src/specificClass.js lines 36–41.
| Quantity | Canonical (internal) | Output (rendered) | Curve (supplier) | Required-unit |
|---|---|---|---|---|
| Pressure | Pa |
mbar |
mbar |
✓ |
| Atmospheric pressure | Pa |
Pa |
— | ✓ |
| Flow | m3/s |
m3/h |
m3/h |
✓ |
| Power | W |
kW |
kW |
✓ |
| Temperature | K |
°C |
— | ✓ |
| Control | — | — | % |
— |
requireUnitForTypes means MeasurementContainer rejects writes that omit unit for these types.
Child registration
Source: src/measurement/childRegistrar.js registerMeasurementChild. The registrar reads asset.type and positionVsParent from the child's config and subscribes to <type>.measured.<position> on the child's measurement emitter.
| Software type | Filter | Wired to | Side-effect |
|---|---|---|---|
measurement |
asset.type='pressure', position=upstream |
pressureRouter.route('upstream', value, ctx) |
Stored as upstream pressure; refresh prediction + drift. pressureInitialization tracks readiness. |
measurement |
asset.type='pressure', position=downstream |
pressureRouter.route('downstream', value, ctx) |
Same on the discharge side. |
measurement |
asset.type='flow', position=* |
measurementHandlers.updateMeasuredFlow |
Stored; drift assessed against predicted. |
measurement |
asset.type='power', position=atEquipment |
measurementHandlers.updateMeasuredPower |
Stored; drift assessed against predicted. |
measurement |
asset.type='temperature', position=* |
measurementHandlers.updateMeasuredTemperature |
Stored; surfaced on Port 0. |
Virtual pressure children — auto-registered
At startup specificClass registers two measurement-typed children:
| Child id | Position | Default value | Use |
|---|---|---|---|
dashboard-sim-upstream |
upstream |
0 mbar | Receives data.simulate-measurement payloads with position upstream. |
dashboard-sim-downstream |
downstream |
0 mbar | Same for downstream. |
pressureSelector prefers a real registered child over the virtuals once one shows up — the virtuals keep listening so dashboards can still inject sim values during real-pressure outages.
Related pages
| Page | Why |
|---|---|
| Home | Intuitive overview |
| Reference — Architecture | Code map, FSM, prediction + drift pipeline |
| Reference — Examples | Shipped flows + debug recipes |
| Reference — Limitations | Known issues and open questions |
| EVOLV — Topic Conventions | Platform-wide topic rules |
| EVOLV — Telemetry | Port 0 / 1 / 2 InfluxDB layout |