Reference-Contracts.md now carries a concrete `msg = { topic, payload, ... }`
example for each of the 7 input topics (plus the built-in query.units) and for
all three output ports (Port 0 process, Port 1 InfluxDB, Port 2 registration),
plus the emitter-event shape. Shapes verified against commandRegistry unit
normalisation and the process/influxdb formatters. Examples sit outside the
AUTOGEN markers so `npm run wiki:all` stays a no-op on them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
13 KiB
Reference — Contracts
Note
Full topic contract, configuration schema, and child-registration filters for
pumpingStation. The topic-contract and data-model sections are regenerated bynpm run wiki:all— do not hand-edit between theBEGIN AUTOGEN/END AUTOGENmarkers. Source of truth for everything on this page: the node'ssrc/commands/index.js,src/specificClass.jsconfigure(), and the schema atgeneralFunctions/src/configs/pumpingStation.json.For an intuitive overview, return to the Home.
Topic contract
The Unit column reflects each descriptor's units: { measure, default } declaration. The default unit is what the commandRegistry coerces incoming msg.unit values to before the handler runs.
| Canonical topic | Aliases | Payload | Unit | Effect |
|---|---|---|---|---|
set.mode |
changemode |
string |
— | Switch the station between auto / manual control modes. |
child.register |
registerChild |
string |
— | Register a child node (machine group, measurement, …) with this station. |
cmd.calibrate.volume |
calibratePredictedVolume |
any |
volume (default m3) |
Calibrate the predicted-volume integrator to a known basin volume. |
cmd.calibrate.level |
calibratePredictedLevel |
any |
length (default m) |
Calibrate the predicted-volume integrator to a known basin level. |
set.inflow |
q_in |
any |
volumeFlowRate (default m3/h) |
Push a measured inflow value into the basin balance. |
set.outflow |
q_out |
any |
volumeFlowRate (default m3/h) |
Push a measured outflow value into the basin balance. |
set.demand |
Qd |
any |
volumeFlowRate (default m3/h) |
Operator outflow demand setpoint for the station. |
Input message examples
One worked msg per accepted topic. Send these into Port 0. For unit-bearing
topics the commandRegistry converts msg.unit (or a { value, unit } payload) to
the default unit before the handler runs — so the unit is optional and any
compatible unit is accepted.
// 1. set.mode — switch control strategy
msg = { topic: 'set.mode', payload: 'manual' }; // manual | levelbased | flowbased | none
// 2. child.register — register a child (usually arrives on Port 2 from the child;
// this is the manual form). payload = the child node's Node-RED id.
msg = { topic: 'child.register', payload: 'a1b2c3d4.ef567', positionVsParent: 'upstream' };
// positionVsParent: upstream | downstream | atequipment (or in | out for predicted-flow children)
// 3. cmd.calibrate.volume — seed the predicted-volume integrator (default m³)
msg = { topic: 'cmd.calibrate.volume', payload: 12.5 }; // 12.5 m³
msg = { topic: 'cmd.calibrate.volume', payload: 12500, unit: 'L' }; // 12 500 L → auto-converted to 12.5 m³
// 4. cmd.calibrate.level — seed the predicted level (default m)
msg = { topic: 'cmd.calibrate.level', payload: 1.8 }; // 1.8 m
// 5. set.inflow — push a measured inflow (default m³/h)
msg = { topic: 'set.inflow', payload: 45 }; // 45 m³/h
msg = { topic: 'set.inflow', payload: 12.5, unit: 'L/s' }; // 12.5 L/s → 45 m³/h
msg = { topic: 'set.inflow', payload: { value: 45, unit: 'm3/h' }, timestamp: 1716998400000 };
// 6. set.outflow — push a measured/forced outflow (default m³/h)
msg = { topic: 'set.outflow', payload: 30 }; // 30 m³/h drawn from the basin
// 7. set.demand — operator outflow setpoint (default m³/h); ignored unless mode === 'manual'
msg = { topic: 'set.demand', payload: 120 }; // 120 m³/h
// Built-in (every EVOLV node): query.units — ask which units each topic accepts.
// Replies on Port 0 with { topic:'query.units', payload:{ node, units } }.
msg = { topic: 'query.units', payload: null };
Deprecated aliases behave identically and log a one-time warning, e.g.
{ topic: 'q_in', payload: 45 }≡set.inflow,{ topic: 'Qd', payload: 120 }≡set.demand.
Data model — getOutput() shape
Keys composed each tick by specificClass.getOutput() and emitted via outputUtils.formatMsg on Port 0. Delta-compressed: consumers see only the keys that changed.
| Key | Type | Unit | Sample |
|---|---|---|---|
direction |
string | — | "steady" |
dryRunLevel |
number | — | 0.20400000000000001 |
dryRunSafetyVol |
number | — | 2.55 |
flowSource |
null | — | null |
heightBasin |
number | m | 4 |
highVolumeSafetyLevel |
number | — | 3.7239999999999998 |
highVolumeSafetyVol |
number | — | 46.55 |
inflowLevel |
number | m | 1.5 |
inletPipeDiameter |
number | — | 0.4 |
manualDemand |
null | — | null |
maxVol |
number | m3 | 50 |
maxVolAtOverflow |
number | m3 | 47.5 |
minHeightBasedOn |
string | — | "outlet" |
minVol |
number | m3 | 2.5 |
minVolAtInflow |
number | m3 | 18.75 |
minVolAtOutflow |
number | m3 | 2.5 |
mode |
string | — | "levelbased" |
outflowLevel |
number | m | 0.2 |
outletPipeDiameter |
number | — | 0.4 |
overflowLevel |
number | m | 3.8 |
percControl |
number | % | 0 |
predictedOverflowRate |
number | — | 0 |
predictedOverflowVolume |
number | — | 0 |
predictedUnderflowVolume |
number | — | 0 |
surfaceArea |
number | m2 | 12.5 |
timeleft |
null | s | null |
volEmptyBasin |
number | m3 | 50 |
volume.predicted.atequipment.wikigen-pumpingstation-id |
number | m3 | 2.5 |
Sample values come from a stub instantiation in wikiGen — in a live deployment the volume key is shaped volume.<variant>.<position>.<childId> per the standard Measurement Key Shape.
Note
Two control-state keys carry the live operating mode rather than a measurement:
mode— string, the active control strategy (levelbased/manual/flowbased/none). Echoes the most recentset.modeinput.manualDemand— number (m³/h) ornull. The operator outflow setpoint last accepted viaset.demand;nulloutsidemanualmode.
Output message examples
The node emits on three ports every tick (outputUtils.formatMsg). Port 0 / Port 1
fire only when at least one field changed (delta-compression); Port 2 fires once at
startup. topic is the station's configured name (here "PS-Influent-01").
// Port 0 — process data. payload = only the keys that changed this tick.
msg = {
topic: 'PS-Influent-01',
payload: {
mode: 'levelbased',
direction: 'filling',
percControl: 25,
'level.predicted.atequipment.default': 3.25, // m
'volume.predicted.atequipment.default': 32.5, // m³
timeleft: 400, // s, or null when steady
manualDemand: null // m³/h, or null outside manual mode
}
};
// Port 1 — InfluxDB telemetry. Same changed fields, wrapped for the InfluxDB node.
msg = {
topic: 'PS-Influent-01',
payload: {
measurement: 'PS-Influent-01',
fields: { percControl: 25, 'volume.predicted.atequipment.default': 32.5 },
tags: { id: 'a1b2c3d4.ef567', softwareType: 'pumpingstation', type: 'pumpingStation' },
timestamp: '2026-05-29T10:00:00.000Z' // Date
}
};
// Port 2 — registration handshake, sent once at startup to the upstream parent.
msg = {
topic: 'child.register',
payload: 'a1b2c3d4.ef567', // this node's id
positionVsParent: 'atEquipment',
distance: null
};
Child-facing events are not Port messages — they fire on
source.measurements.emitteras<type>.<variant>.<position>, e.g. eventvolume.predicted.atequipmentwith payload{ value: 32.5, unit: 'm3', timestamp }. Parents subscribe by event name.
Configuration schema — editor form to config keys
Source of truth: generalFunctions/src/configs/pumpingStation.json.
Basin geometry (config.basin)
| Form field | Config key | Default | Unit | Notes |
|---|---|---|---|---|
| Basin Volume | basin.volume |
1 |
m3 | Total geometric storage from floor to rim |
| Basin Height | basin.height |
1 |
m | Floor-to-rim wall height |
| Inlet Elevation | basin.inflowLevel |
2 |
m | Bottom of incoming pipe, from floor |
| Outlet Elevation | basin.outflowLevel |
0.2 |
m | Top of pump-suction pipe, from floor |
| Inlet Pipe Diameter | basin.inletPipeDiameter |
0.4 |
m | For future hydraulic upgrades |
| Outlet Pipe Diameter | basin.outletPipeDiameter |
0.4 |
m | For future hydraulic upgrades |
| Overflow Level | basin.overflowLevel |
2.5 |
m | Physical overflow weir crest |
Safety thresholds (config.safety)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| High-Volume Safety % | safety.highVolumeSafetyThresholdPercent |
98 |
Trigger high-volume safety at this fill % |
| Dry-Run Safety Level | safety.dryRunLevel |
0.2 |
Below this level all pumps stop |
| Enable High-Volume Safety | safety.enableHighVolumeSafety |
true |
Master switch |
Warning
Earlier versions used
enableOverfillProtectionandoverfillThresholdPercent. Those names are deprecated. The current canonical names areenableHighVolumeSafetyandhighVolumeSafetyThresholdPercent. See.claude/refactor/OPEN_QUESTIONS.mdfor the alias-removal timeline.
Control mode (config.control)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Mode | control.mode |
"levelbased" |
One of levelbased, manual, flowbased, pressureBased, percentageBased, powerBased, hybrid*. Asterisked modes are placeholders in code. |
| Level Curve Type | control.levelbased.curveType |
"linear" |
linear or log |
| Log Curve Factor | control.levelbased.logCurveFactor |
0.5 |
Slope tuning for log curve |
| Min Level | control.levelbased.minLevel |
0.3 |
Demand hard-zero below this |
| Start Level | control.levelbased.startLevel |
0.5 |
Falling-ramp returns to 0 % here |
| Stop Level | control.levelbased.stopLevel |
0.4 |
Schmitt-trigger lower bound for pump-count keep-alive |
| Max Level | control.levelbased.maxLevel |
2.3 |
Demand saturates at 100 % here |
| Enable Shifted Ramp | control.levelbased.enableShiftedRamp |
true |
Hysteresis-armed shift between rising / falling ramps |
| Manual Flow Setpoint | control.manual.flowSetpoint |
0 |
Honoured in manual mode |
General (config.general)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Time-left full / empty threshold | general.timeleftToFullOrEmptyThresholdSeconds |
120 |
ETA below this triggers warning state |
| Flow dead-band | general.flowThreshold |
1e-4 m³/s |
Net-flow below this is treated as steady |
Child registration
Source: nodes/pumpingStation/src/specificClass.js configure(), lines 107–116.
| Software type | Filter | Wired to | Side-effect |
|---|---|---|---|
measurement |
any | _subscribeMeasurement |
Subscribes to the measurement's emitter; updates basin balance |
machine |
only if no machinegroup parent is present |
direct dispatch | Bypassed when an MGC is the predicted-flow source |
machinegroup |
any | _subscribePredictedFlow |
Reads aggregated predicted flow from the MGC |
pumpingstation |
any | _subscribePredictedFlow |
Cascaded PS — reads predicted outflow of upstream station |
The router only subscribes to the highest-level aggregator for predicted flow. If an MGC is present, direct machine children are not double-counted.
Unit policy
Source: nodes/pumpingStation/src/specificClass.js lines 21–30.
| Quantity | Canonical (internal) | Output (rendered) |
|---|---|---|
| Flow | m3/s |
m3/s (also netFlowRate) |
| Level | m |
m |
| Volume | m3 |
m3 |
| Pressure | Pa |
(not surfaced) |
| Power | W |
(not surfaced) |
| Temperature | K |
(not surfaced) |
overflowVolume and underflowVolume are explicitly listed in the policy output so the MeasurementContainer keeps the integrator's m3 unit on those streams (FlowAggregator writes spill / underflow per tick).
Related pages
| Page | Why |
|---|---|
| Home | Intuitive overview |
| Reference — Architecture | Code map, state chart, lifecycle |
| Reference — Examples | Shipped example flows |
| Reference — Limitations | Known limitations and open questions |
| EVOLV — Topic Conventions | Platform-wide topic rules |
| EVOLV — Telemetry | Port 0 / 1 / 2 InfluxDB layout |