Aligns the code with the 5-threshold convention used throughout the
wiki (basin model + per-mode transfer-function diagrams):
heightInlet → inflowLevel
heightOutlet → outflowLevel
heightOverflow → overflowLevel
stopLevel → minLevel
maxFlowLevel → maxLevel
minFlowLevel → removed (collapsed into startLevel; they were
always supposed to hold the same value)
minVolIn → minVolAtInflow
minVolOut → minVolAtOutflow
maxVolOverflow → maxVolAtOverflow
startLevel → unchanged
Config schema (generalFunctions/src/configs/pumpingStation.json) is
updated in a parallel commit in that submodule.
Also:
- Stripped the ~150-line ASCII basin diagram from initBasinProperties
JSDoc; it now points at wiki/functional-description.md#basin-model.
- Trimmed the top-of-class JSDoc — the config-sections breakdown was
drifting from the schema anyway; wiki is now the source of truth.
- Tidied inline comments in _controlLevelBased, _scaleLevelToFlowPercent.
- Editor order reshuffled to match the bottom→top basin order:
minLevel, startLevel, maxLevel.
Breaking change for saved flows: existing pumpingStation nodes in
production flows reference the old field names and will need to be
re-entered in the editor. No compat shim — node is RnD/trial.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two hard rules for the safety controller, matching sewer PS design:
1. BELOW stopLevel (dry-run): pumps CANNOT start.
All downstream equipment shut down. safetyControllerActive=true
blocks _controlLogic so level control can't restart pumps.
Only manual override or emergency can change this.
2. ABOVE overflow level (overfill): pumps CANNOT stop.
Only UPSTREAM equipment is shut down (stop more water coming in).
Machine groups (downstream pumps) are NOT shut down — they must
keep draining. safetyControllerActive is NOT set, so _controlLogic
continues commanding pumps at the demand dictated by the level
curve (which is >100% near overflow = all pumps at maximum).
Only manual override or emergency stop can shut pumps during
an overfill event.
Previously the overfill branch called turnOffAllMachines() on machine
groups AND set safetyControllerActive=true, which shut down the pumps
and blocked level control from restarting them — exactly backwards
for a sewer pumping station where the sewage keeps coming.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously PS only sent demand to MGC when level > startLevel AND
direction === 'filling'. Between startLevel and stopLevel (the 'dead
zone'), pumps kept running at their last commanded setpoint with no
updates. Basin drained uncontrolled until hitting stopLevel.
Fix: send percControl on every tick when level > stopLevel. The
_scaleLevelToFlowPercent math naturally gives:
- Positive % above startLevel (pumps ramp up)
- 0% at exactly startLevel (pumps at minimum)
- Negative % below startLevel → clamped to 0 → MGC scales to 0
→ pumps ramp down gracefully
This creates smooth visible ramp-up and ramp-down as the basin fills
and drains, instead of a sudden jump at startLevel and stuck ctrl in
the dead zone.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three bugs in registerChild caused multi-counted outflow in _updatePredictedVolume:
1. machinegroup registered twice (line 66 + line 70 both called
_registerPredictedFlowChild). Fixed: only register in the
machinegroup branch.
2. Individual machines registered alongside their machinegroup parent.
Each pump's predicted flow is already included in MGC's aggregated
total — subscribing to both triple-counts. Fixed: only register
individual machines when no machinegroup is present (direct-wired
pumps without MGC).
3. _registerPredictedFlowChild subscribed to BOTH flow.predicted.downstream
AND flow.predicted.atequipment events. These carry the same total flow
on two event names — the handler wrote the value twice per tick.
Fixed: subscribe to ONE event per child (downstream for outflow,
upstream for inflow).
These are generalizable patterns:
- When a group aggregator exists, subscribe to IT, not its children.
- One event per measurement type per child — pick the most specific.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two additions to pumpingStation:
1. _controlLevelBased now calls _applyMachineGroupLevelControl in
addition to _applyMachineLevelControl when the basin is filling
above startLevel. Previously only direct-child machines received
the level-based percent-control signal; in a hierarchical topology
(PS → MGC → pumps) the machines sit under MGC and PS.machines is
empty, so the level control never reached them.
2. New 'Qd' input topic + forwardDemandToChildren() method. When PS
is in 'manual' mode (matching the pattern from rotatingMachine's
virtualControl), operator demand from a dashboard slider is forwarded
to all child machine groups and direct machines. When PS is in any
other mode (levelbased, flowbased, etc.), the Qd msg is silently
dropped with a debug log so the automatic control isn't overridden.
No breaking changes — existing flows that don't send 'Qd' are unaffected,
and _controlLevelBased's additional call to machineGroupLevelControl
is a no-op when no machine groups are registered.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevents demo code from executing when module is required by Node-RED,
which caused crashes due to missing measurement data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix method name mismatch in tick() that called non-existent _calcTimeRemaining
instead of _calcRemainingTime. Add 27 unit tests for specificClass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded position strings with POSITIONS.* constants.
Prefix unused variables with _ to resolve no-unused-vars warnings.
Fix no-prototype-builtins where applicable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>