Files
pumpingStation/wiki/Home.md
znetsixe ed22f01932 P9.3 + examples: fresh 3-tier flows + pilot wiki Home.md
examples/ (new — was empty except standalone-demo.js):
  01-Basic.json         14 nodes, inject + dashboard, no parent
  02-Integration.json   32 nodes, 2 tabs, measurement + MGC + 2 pumps,
                        link-out/link-in channels per node-red-flow-layout.md
  03-Dashboard.json     63 nodes, 3 tabs (process + UI + setup),
                        FlowFuse charts + sliders, trend-split pattern
  README.md             load instructions
  tools/build-examples.js  regenerator

All canonical topic names only (set.*, cmd.*, data.*, child.*). No
legacy aliases. Every ui-* widget has x/y. Every chart has the full
mandatory key set from node-red-flow-layout.md §4.

wiki/Home.md (new) — pilot page for the 14-section visual-first template.
Sections 5 (topic-contract) + 9 (data-model) are auto-generated via the
new npm run wiki:* scripts; everything else hand-written following
.claude/refactor/WIKI_TEMPLATE.md.

package.json — added wiki:contract / wiki:datamodel / wiki:all scripts
wired to ../generalFunctions/scripts/wikiGen.js.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:50:45 +02:00

14 KiB
Raw Blame History

pumpingStation

Reflects code as of d2384b1 · regenerated <YYYY-MM-DD> via npm run wiki:all If this banner is stale, the page may be out of date. Treat as informative, not authoritative.

1. What this node is

pumpingStation is an S88 Process Cell that owns a wet-well basin and orchestrates the pumps that drain it. It tracks measured + predicted volume, evaluates safety interlocks (dry-run, overfill), and dispatches a control strategy that hands a demand setpoint to one or more downstream machine groups or individual pumps.

2. Position in the platform

flowchart LR
    ps[pumpingStation<br/>Process Cell]:::pc
    meas_lvl[measurement<br/>type=level<br/>position=atequipment]:::ctrl
    meas_in[measurement<br/>type=flow<br/>position=upstream]:::ctrl
    mgc[machineGroupControl<br/>Unit]:::unit

    meas_lvl -.data.-> ps
    meas_in -.data.-> ps
    ps -->|set.demand| mgc
    mgc -.evt.flow-predicted.-> ps
    mgc -->|child.register| ps
    classDef pc fill:#0c99d9,color:#fff
    classDef unit fill:#50a8d9,color:#000
    classDef ctrl fill:#a9daee,color:#000

S88 colours: Process Cell #0c99d9, Unit #50a8d9, Control Module #a9daee. Source of truth: .claude/rules/node-red-flow-layout.md.

3. Capability matrix

Capability Status Notes
Predicts basin volume from net flow Integrator seeded from basin.minVol; recomputes level.
Accepts measured level / volume / pressure Routed via measurementRouter on child registration.
Level-based control strategy Linear or log ramp between minLevel and maxLevel.
Flow-based control strategy PID against flowSetpoint.
Manual demand passthrough set.demand only honoured in manual mode.
Dry-run safety interlock Stops downstream pumps when volume < minVol while draining.
Overfill safety interlock Stops upstream equipment when volume crosses overfill threshold.
Cascaded children (sub-stations) ⚠️ Accepted via pumpingstation softwareType but not exercised in production.

4. Code map

flowchart TB
    subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"]
        nc["buildDomainConfig()<br/>static DomainClass, commands<br/>static tickInterval = 1000ms"]
    end
    subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
        sc["PumpingStation.configure()<br/>declares ChildRouter rules<br/>tick() → safety → control"]
    end
    subgraph concerns["src/ concern modules"]
        basin["basin/<br/>BasinGeometry + thresholdValidator"]
        measurement["measurement/<br/>flowAggregator + router + calibration"]
        control["control/<br/>levelbased / flowbased / manual"]
        safety["safety/<br/>SafetyController"]
        commands["commands/<br/>topic registry + handlers"]
    end
    nc --> sc
    sc --> basin
    sc --> measurement
    sc --> control
    sc --> safety
    nc --> commands
Module Owns Read first if you're changing…
basin/ Geometry, volume↔level conversion, threshold ordering Capacity, level↔volume math, fill %.
measurement/ Net-flow aggregation, predicted-volume integrator, calibration Predicted volume / time-to-full.
control/ Control strategy dispatch (levelbased, flowbased, manual) Demand calculation, mode behaviour.
safety/ Dry-run + overfill rules, pump-shutdown side-effects Safety envelope, alarm reactions.
commands/ Input-topic registry and handlers New input topics, payload validation.

5. Topic contract

Auto-generated from src/commands/index.js. Do NOT hand-edit between the markers. Re-run npm run wiki:contract.

Canonical topic Aliases Payload Effect
set.mode changemode string Replaces the named state value with the supplied payload.
child.register registerChild string Parent/child plumbing — registers or unregisters a child node.
cmd.calibrate.volume calibratePredictedVolume any Triggers an action / sequence — not idempotent.
cmd.calibrate.level calibratePredictedLevel any Triggers an action / sequence — not idempotent.
set.inflow q_in any Replaces the named state value with the supplied payload.
set.demand Qd any Replaces the named state value with the supplied payload.

6. Child registration

Mirrors the ChildRouter declarations in specificClass.js → configure().

flowchart LR
    subgraph kids["accepted children (softwareType)"]
        m["measurement"]:::ctrl
        mach["machine<br/>(rotatingMachine)"]:::equip
        mgc["machinegroup"]:::unit
        sub["pumpingstation<br/>(sub-station)"]:::pc
    end
    m -->|"&lt;type&gt;.measured.&lt;position&gt;"| route1[_subscribeMeasurement<br/>routes to measurementRouter]
    mach -->|flow.predicted.&lt;in or out&gt;| route2[_subscribePredictedFlow<br/>+ flowAggregator]
    mgc -->|flow.predicted.&lt;in or out&gt;| route2
    sub -->|flow.predicted.&lt;in or out&gt;| route2
    route1 --> tick[tick]
    route2 --> tick
    classDef ctrl fill:#a9daee,color:#000
    classDef equip fill:#86bbdd,color:#000
    classDef unit fill:#50a8d9,color:#000
    classDef pc fill:#0c99d9,color:#fff
softwareType onRegister side-effect Subscribed events
measurement _subscribeMeasurement(child) — registers in MeasurementContainer. <type>.measured.<position> for any type (pressure, level, flow, …).
machine Stored in this.machines[id]. Skipped when a machineGroup parent is present to avoid double-counting. `flow.predicted.<in
machinegroup Stored in this.machineGroups[id]. `flow.predicted.<in
pumpingstation Stored in this.stations[id]. `flow.predicted.<in

7. Lifecycle — what one tick does

sequenceDiagram
    participant child as measurement / pump child
    participant ps as pumpingStation
    participant fa as flowAggregator
    participant sf as safetyController
    participant ctl as control strategy
    participant out as Port-0 output

    child->>ps: data event (measured.level / flow.predicted.out)
    ps->>ps: ChildRouter dispatches to handler
    Note over ps: every 1000 ms (static tickInterval)
    ps->>fa: tick() — net flow, ETA, predicted volume
    ps->>sf: evaluate({direction, secondsRemaining})
    alt safety blocked
        sf-->>ps: blocked=true, reason
        Note over ctl: skipped this tick
    else safety clear
        ps->>ctl: dispatch(mode, ctx, controlState)
        ctl-->>ps: percControl updated
    end
    ps->>ps: notifyOutputChanged()
    ps->>out: msg{topic, payload (delta-compressed)}

8. Data model — getOutput()

What lands on Port 0. Built in getOutput(), then delta-compressed by outputUtils.formatMsg.

Key Type Unit Sample
direction string "steady"
flowSource null null
heightBasin number m 1
inflowLevel number m 2
maxVol number m3 1
maxVolAtOverflow number m3 2.5
minHeightBasedOn string "outlet"
minVol number m3 0.2
minVolAtInflow number m3 2
minVolAtOutflow number m3 0.2
outflowLevel number m 0.2
overflowLevel number m 2.5
percControl number % 0
surfaceArea number m2 1
timeleft null s null
volEmptyBasin number m3 1
volume.predicted.atequipment.wikigen-pumpingstation-id number m3 0.2

The <nodeId> segment of the MeasurementContainer key is the Node-RED node id assigned at deploy time; auto-gen substitutes a placeholder stub.

9. Configuration — editor form ↔ config keys

flowchart TB
    subgraph editor["Node-RED editor form"]
        f1[Basin: volume / height]
        f2[Levels: inflow / outflow / overflow]
        f3[Control mode]
        f4[Level setpoints: min / start / max]
        f5[Safety: dry-run % / overfill %]
    end
    subgraph config["Domain config slice"]
        c1[basin.volume<br/>basin.height]
        c2[basin.inflowLevel<br/>basin.outflowLevel<br/>basin.overflowLevel]
        c3[control.mode]
        c4[control.levelbased.minLevel<br/>control.levelbased.startLevel<br/>control.levelbased.maxLevel]
        c5[safety.dryRunThresholdPercent<br/>safety.overfillThresholdPercent]
    end
    f1 --> c1
    f2 --> c2
    f3 --> c3
    f4 --> c4
    f5 --> c5
Form field Config key Default Range Where used
basinVolume basin.volume 1 > 0 (m³) BasinGeometry
basinHeight basin.height 1 > 0 (m) BasinGeometry
inflowLevel basin.inflowLevel 2 ≥ 0 (m) threshold validator, control
outflowLevel basin.outflowLevel 0.2 ≥ 0 (m) dead-volume floor
overflowLevel basin.overflowLevel 2.5 > 0 (m) overfill safety
controlMode control.mode levelbased enum control/dispatch
minLevel control.levelbased.minLevel 1 ≥ 0 (m) levelBased.run
startLevel control.levelbased.startLevel 1 ≥ minLevel ramp foot
maxLevel control.levelbased.maxLevel 4 ≤ overflowLevel ramp top
enableDryRunProtection safety.enableDryRunProtection true bool SafetyController
dryRunThresholdPercent safety.dryRunThresholdPercent 2 0100 % dry-run trip
enableOverfillProtection safety.enableOverfillProtection true bool overfill safety
overfillThresholdPercent safety.overfillThresholdPercent 98 0100 % overfill trip

10. State chart

Two orthogonal state vectors: control mode (operator-driven) and safety state (data-driven). The diagram shows them together — most transitions are independent.

stateDiagram-v2
    state ControlMode {
        [*] --> none
        none --> levelbased: set.mode
        levelbased --> flowbased: set.mode
        flowbased --> manual: set.mode
        manual --> levelbased: set.mode
        levelbased --> none: set.mode
    }
    state SafetyState {
        [*] --> nominal
        nominal --> dryRun: vol < minVol AND draining
        nominal --> overfill: vol > overfillThreshold AND filling
        dryRun --> nominal: vol ≥ minVol
        overfill --> nominal: vol ≤ overfillThreshold
    }

While the safety state is dryRun, control dispatch is skipped entirely. While overfill, control still runs (pumps must keep draining) but upstream equipment is shut down.

11. Examples

Example flows live under examples/ in the repo. The structured tier-1/2/3 flows for this node are still in progress; until they land, the standalone simulator demo is the only runnable artefact.

Tier File What it shows Status
Basic examples/01-Basic.flow.json Inject + dashboard, single basin, no parent TBD
Integration examples/02-Integration.flow.json pumpingStation + MGC + 2 pumps + measurement children TBD
Dashboard examples/03-Dashboard.flow.json Live FlowFuse charts (level, net flow, ETA) TBD
Headless examples/standalone-demo.js Node.js-only simulator, no Node-RED in repo

12. Debug recipes

Symptom First thing to check Where to look
Status badge stuck on ❔ 0.0% Did any volume / level measurement register? Watch Port 2 + first-child event. Editor debug tap on Port 2 + _subscribeMeasurement log line.
direction always steady Net flow inside general.flowThreshold dead-band (default 0.0001 m³/s). flowAggregator.deriveDirection.
set.demand ignored Mode isn't manual. Check set.mode history. handlers.setDemand debug log.
Predicted volume drifts off measured Calibration needed — fire cmd.calibrate.volume with a known reading. measurement/calibration.js.
Pumps don't stop on dry-run safety.enableDryRunProtection must be true AND the orchestrator must see direction='draining'. SafetyController.evaluate.
Threshold-ordering warnings on startup validateThresholdOrdering printed inflowLevel < overflowLevel style violations. basin/thresholdValidator.js.

Never ship enableLog: 'debug' in a demo — fills the container log within seconds and obscures real errors. Use only for live debugging.

13. When you would NOT use this node

  • Use pumpingStation for a wet-well basin that needs orchestrated drainage. For a single pump with no basin model, use rotatingMachine directly.
  • Don't use pumpingStation to schedule a fixed pump rota — its modes are reactive (level / flow / manual). Use an external scheduler if you need a calendar-driven schedule.
  • Skip pumpingStation if you don't need predicted volume / time-to-full. A bare machineGroupControl is lighter when the upstream basin is modelled elsewhere.

14. Known limitations / current issues

# Issue Tracked in
1 Cascaded pumpingstation children accepted but not exercised in production — semantics of nested stations are not test-covered. TBD
2 pressureBased, percentageBased, powerBased, and hybrid are in the config enum but not implemented as control strategies. control/index.js — only levelbased / flowbased / manual dispatched.
3 Predicted-volume integrator can drift over long horizons without a measured-level calibration source. cmd.calibrate.volume is operator-triggered, not automatic.
4 Tier 1/2/3 example flows not yet written — current examples/ only contains the standalone simulator. P2.14 (Docker E2E) + P9 wiki cleanup.