Files
pumpingStation/wiki/Reference-Contracts.md
znetsixe e47de87adb feat(commands): unit shorthand + collapse duplicated value/unit parsing; wiki sync
- 5 descriptors -> unit: shorthand (cmd.calibrate.volume/level, set.inflow/
  outflow/demand).
- setInflow/setOutflow: drop the hand-rolled scalar-vs-object parsing — the
  registry now normalises every shape to a number in the descriptor unit; the
  handlers become guarded one-liners (matching setDemand).
- Regenerate wiki topic-contract + command-envelope note (msg.origin).

143/143 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 18:41:32 +02:00

14 KiB
Raw Blame History

Reference — Contracts

code-ref autogen

Note

Full topic contract, configuration schema, and child-registration filters for pumpingStation. The topic-contract and data-model sections are regenerated by npm run wiki:all — do not hand-edit between the BEGIN AUTOGEN / END AUTOGEN markers. Source of truth for everything on this page: the node's src/commands/index.js, src/specificClass.js configure(), and the schema at generalFunctions/src/configs/pumpingStation.json.

For an intuitive overview, return to the Home.


Topic contract

The Unit column reflects each descriptor's declared unit (via the unit: 'm3/h' shorthand or the legacy units: { measure, default }; the measure is derived from the unit). The default unit is what the commandRegistry coerces incoming values to before the handler runs.

Command envelope (all EVOLV nodes). Every command shares one envelope on top of msg.topic:

  • Value + unit — send msg.payload as a number (with optional sibling msg.unit) or as { value, unit }. The registry always converts the value to the descriptor's unit before the handler; numeric strings are converted too. A missing unit assumes the descriptor default.
  • msg.origin — the control authority that issued the command: parent (automation/parent controller, the default), GUI (SCADA/HMI operator), or fysical (physical buttons). On nodes with a control mode, the mode's allowedSources decides which origins are accepted; releasing control is done by changing the mode.
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 recent set.mode input.
  • manualDemand — number (m³/h) or null. The operator outflow setpoint last accepted via set.demand; null outside manual mode.

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.emitter as <type>.<variant>.<position>, e.g. event volume.predicted.atequipment with 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 enableOverfillProtection and overfillThresholdPercent. Those names are deprecated. The current canonical names are enableHighVolumeSafety and highVolumeSafetyThresholdPercent. See .claude/refactor/OPEN_QUESTIONS.md for 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 107116.

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 2130.

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).


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