Editor + schema defaults
- pumpingStation.html: drag-in defaults now reflect a realistic basin
(volume=50 m³, height=4 m, inflowLevel=1.5, outflowLevel=0.2,
overflowLevel=3.8, startLevel=1, stopLevel=0.5, minLevel=0.3,
maxLevel=3.8). Old defaults left every level field null.
Visual bug fix
- src/editor/mode-preview.js: the level-based ramp curve in the editor
was being drawn with foot=startLevel via buildPath(start, start, max).
The runtime in control/levelBased.js has always used inflowLevel as
the ramp foot. Pass buildPath(start, upFoot, max) where upFoot falls
back to start when inflowLevel is missing, matching the runtime.
Manual mode observability
- src/specificClass.js: store last forwarded demand on this._manualDemand;
surface as `mode` and `manualDemand` in getOutput(); call
notifyOutputChanged() on forwardDemandToChildren and on changeMode so
Port 0/1 emit even with no children registered. Status badge compacted
to `mode | dir% | net m³/h` + `Qd=X m³/h` in manual mode.
Examples cleanup
- Drop stale 02-Integration.json, 03-Dashboard.json, basic-dashboard.flow.json,
standalone-demo.js.
- 01-Basic.json: numbered driver groups (1. Control mode … 4. Calibration),
Debug-outputs group, fixed typos and HOW-TO-USE; Port 1 debug now active.
- New 02-Dashboard.json: FlowFuse Dashboard 2.0 with Controls (7 buttons),
Status (7 ui-text rows), Trends (4 ui-charts: level / volume / volume% /
flow in-out-net), Raw output (ui-template dumping every Port 0 field).
Fan-out function pattern-matches the 4-segment measurement keys by
prefix instead of hardcoding childId, converts flow m³/s → m³/h, and
caches last-known values so deltas never blank a row.
- examples/README.md realigned to the two-file set.
Wiki
- Home.md: 5 image placeholders replaced with the provided screenshots
(01-node-and-editor, 02-basic-flow, 03-wiring-standalone,
04-wiring-integrated) and the demo GIF (01-basic-demo).
- Reference-Examples.md: shipped-files table reduced to 01-Basic +
02-Dashboard, Example-01 section uses the screenshot + GIF, Example-02
rewritten as Dashboard (kept screenshot/GIF callouts open for those
captures), Example-03/Integration sections + their debug-recipes row
removed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Splits pumpingStation/src/ into focused concern modules. specificClass.js
will be slimmed to an orchestrator in P2.9 (integration); for now both
the inlined logic AND the new modules coexist so tests stay green
throughout.
src/basin/ BasinGeometry + thresholdValidator (pure)
src/measurement/ flowAggregator + measurementRouter + calibration
src/control/ levelBased + flowBased(stub) + manual + index dispatcher
src/safety/ safetyController split into dryRun + overfill rules
src/commands/ registry array + handlers (canonical names from start)
src/editor.js 260 lines of SVG basin-diagram redraw, was inline in .html
examples/standalone-demo.js was if(require.main===module) at bottom of specificClass.js
CONTRACT.md canonical inputs + outputs + emitted events
Modified:
src/specificClass.js removed the 170-line standalone demo block
pumpingStation.html oneditprepare/oneditsave delegate to editor.{init,save}
pumpingStation.js added admin endpoint serving src/editor.js
102 basic tests pass (60 new + 42 existing).
specificClass.js itself is unchanged in behaviour — integration is P2.9.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Levelbased control now distinguishes startLevel (rising-edge engage,
ramp foot) from stopLevel (falling-edge disengage). _stopHystRunning
flag flips TRUE crossing startLevel up, FALSE crossing stopLevel down.
While engaged AND level inside [stopLevel, startLevel] (basin draining
through the dead band), emit a configurable keep-alive percControl
(default 1 %) so MGC keeps a single pump running for a full drain
stroke instead of oscillating at startLevel.
Hard turn-off the moment level <= stopLevel — independent of ramp
scaling. Manual-mode demand=0 now also issues explicit turnOff to
keep parity with the new MGC handleInput semantics where demand<=0
means "off".
Editor preview shades the new hysteresis band; admin endpoint
exposes runtime engaged state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bounds (new src/editor/bounds.js):
- Sets HTML5 min/max on every level + percent input each redraw,
derived from the current values of related inputs so the spinner
stops at the basin hierarchy:
0 < outflowLevel < dryRunLevel < startLevel ≤ inflowLevel
≤ shiftLevel ≤ maxLevel ≤ overflowLevel ≤ basinHeight
- dryRunPercent capped so dryRunLevel ≤ startLevel given current outflow.
- shiftArmPercent ∈ [1, 100]; highVolumeSafety% ∈ [1, 100].
Validation:
- New visible ribbon above the basin diagram (#ps-basin-validation)
listing every hierarchy violation. The in-SVG warning text is now a
small reminder ("⚠ N ordering issues").
- basin-diagram.js owns hierarchy issues; mode-preview.js trimmed to
only own shift-specific issues (shift > start, shift ≤ max,
shiftArmPercent range, shiftLevel required-when-enabled).
- oneditsave blocks Deploy on the union of _psBasinValidationIssues
and _psModeValidationIssues with a RED.notify listing all problems.
Layout polish:
- Side panel widened to 220 px with minmax(0, 1fr) first column so long
labels can no longer push the rows past the panel edge.
- Basin SVG max-width 380 → 360, gap between side panel and SVG bumped
14 → 28 px. Tank shifted right (x=145 width=110) so the inlet
"bottom of pipe" sub-label is no longer clipped on the left edge.
- "0 m (datum)" moved below the tank (y=395, centred) so it can't
collide with "Outlet / top of pipe" when outflowLevel is near floor.
- Zone labels shortened (Spare / Sewage + buffer / Buffer / Dead vol)
and only show when the bracketing thresholds are ≥ 28 px apart, so
they never sit on a threshold label.
- Mode preview axis labels under the chart removed — line colour +
side-panel labels + hover-couple already identify each line. Stub
<text> elements left hidden to keep the redraw loop simple. Arm-%
line + label trimmed in 10 px on the right so they're not clipped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runtime (specificClass.js):
- Replace the "shift left both ramp ends" geometry with a true
hold-then-ramp hysteresis driven by output %, not level:
• Up-curve % crosses shiftArmPercent on the way up → ARM.
• Filling→draining transition while armed → capture the up-curve %
at that moment as _shiftHoldValue.
• Draining + level ≥ shiftLevel → output stays at _shiftHoldValue
(horizontal hold, matching the dashed segment in the SVG).
• Draining + level in [start, shift] → output ramps holdValue → 0 %
along the same curve shape (linear or log) as the up curve.
• Draining + level < startLevel → 0 % AND disarm.
• Returning to filling clears holdValue, stays armed; next drain
transition captures a fresh hold so bouncing fills rearm cleanly.
• Disarm only when level ≤ startLevel.
- New _curveShape(x) helper for shared linear/log shaping.
- Removed legacy _levelBasedRampStart / _levelBasedRampTop /
_updateShiftArmed in favour of the inline state machine.
Adapter (nodeClass.js):
- Pipe shiftArmPercent through to control.levelbased.
Editor (pumpingStation.html + src/editor/):
- Add shiftArmPercent input row (% with unit) to the mode side panel
(only shown when shifted ramp is enabled). Default 95 %.
- Add the horizontal arming-% line + label inside the mode SVG —
this is the "% Threshold triggering shifted ramp down" line from
the original drawing that had been missing.
- Redraw the shifted-down curve to match the SVG geometry literally:
100 % flat from maxLevel → shiftLevel, then ramp shiftLevel →
startLevel down to 0 %, OFF below startLevel. Preview shows the
worst-case envelope (hold = 100 %); runtime hold is captured live.
- Validation extended: 0 < shiftArmPercent ≤ 100; ordering rules
preserved (start < shift ≤ max etc.).
- Auto-default shiftArmPercent to 95 when shift is enabled and the
current value is missing or out of range.
Dashboard example (examples/basic-dashboard.flow.json):
- Parser now reads `level.predicted.atequipment.default` etc. The
MeasurementContainer flatten format includes the implicit 'default'
childId; consumers must include it. Comment in the parser points
at the documenting source in generalFunctions.
Tests:
- test/basic: replace old level-armed-shift tests with two new ones
that exercise the hold-then-ramp arming, capture, hold, ramp-down,
disarm, and the bounce case (filling→draining→filling→draining
captures a fresh hold each time).
- test/integration/shifted-ramp-end-to-end.test.js: new file. Drives
Q_IN/Q_OUT through the full runtime tick with a controllable clock,
asserting the same hysteresis path the dashboard exercises.
- test/integration/basic-dashboard-flow.test.js: fixture keys updated
to the .default-suffixed form so they match the real flatten output.
56/56 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runtime (specificClass.js):
- Replace direction-based hysteresis with level-armed _shiftArmed state.
Arms when level rises past shiftLevel; disarms when level drops below
startLevel. While armed, ramp foot moves to startLevel and ramp top
to shiftLevel — both ends shift left, then saturate at 100 % up to
maxLevel.
- _scaleLevelToFlowPercent now takes (rampStartLevel, rampTopLevel) so
the saturation point follows the shift state.
- New setManualOutflow mirroring setManualInflow.
Adapter (nodeClass.js):
- Pipe enableShiftedRamp / shiftLevel through to control.levelbased.
- New q_out topic handler.
Editor (pumpingStation.html + new src/editor/ modules):
- Split monolithic <script> into modules: index.js (helpers),
basin-diagram.js, mode-preview.js, hover-couple.js, oneditprepare.js,
oneditsave.js — served via /pumpingStation/editor/:file.
- Mode preview redrawn per the SVG diagrams: OFF tier below 0 %, 0 %
flat from start→inlet, ramp inlet→max, optional shifted-down curve
start→shift with 100 % saturation past shift.
- Mode preview gains zone bands (dryRun / safetyLow / safe / safetyHigh /
overflow), level markers (dryRun derived, start, inlet, max, shift,
overflow), validation ribbon that blocks save on bad ordering.
- Auto-default shiftLevel to 0.9 × maxLevel on enable so the marker is
always visible.
- All level inputs moved to a side panel left of each diagram, color-
coded to match line strokes; hover-couple highlights the paired SVG
line on input focus / mouseover.
- Removed UI for non-static parameters: minHeightBasedOn,
pipelineLength, maxDischargeHead, staticHead, defaultFluid,
maxInflowRate, temperatureReferenceDegC,
timeleftToFullOrEmptyThresholdSeconds, inletPipeDiameter,
outletPipeDiameter, minLevel (now derived = dryRunLevel).
- foreignObject inputs in basin SVG removed (single source of truth in
side panel).
Dashboard example (examples/basic-dashboard.flow.json):
- Add manual Q_OUT slider + q_out builder mirroring the existing q_in
trio so the basin can be exercised end-to-end without a connected
rotating-machine downstream.
Tests (test/basic/specificClass.test.js):
- Replace direction-shift test with two new cases covering shift-disabled
hold-zone behaviour and shift-armed/disarmed transitions through
shiftLevel and startLevel boundaries. 53/53 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three user-facing fixes:
1. Outlet was getting pushed below the tank floor by the top-down
nudge because its ideal y is already near the bottom. Now
outflowLevel is PINNED at its proportional y (like basinHeight
is pinned at the rim) and a second bottom-up pass pushes
non-pinned items upward from the outlet anchor. Result: outlet
stays near the tank floor, dryRunLevel sits right above it, the
rest of the stack stays readable. Two anchors, two passes.
2. Zone labels mirrored from the wiki basin-model drawio:
- "Spare volume before spilling" (overflowLevel ↔ maxLevel)
- "Sewage + tank buffer" (maxLevel ↔ startLevel)
- "Tank buffer" (startLevel ↔ minLevel)
- "Tank buffer" (minLevel ↔ dryRunLevel)
- "Dead volume" (outflowLevel ↔ floor)
Each sits at the midpoint of its pair of nudged thresholds and
hides when the gap between them is too small to read (< 14 px).
3. basinVolume moved into the SVG as a pinned input above the tank
rim (alongside basinHeight), replacing the separate form row.
One editor, one diagram — the total volume belongs with the
geometry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reverts the tank-bigger approach from last commit. Instead of
scaling the tank and keeping strict proportionality, the dashed
threshold lines are now nudged apart directly so each gets a
guaranteed 36-px vertical gap. Inputs and labels align with the
lines (no more leader lines needed).
Trade-off: the diagram is now an ordered schematic, not a strictly
to-scale rendering. Values are still shown next to each line via
the input boxes, and the value ordering is preserved. For an editor
where the goal is entering parameters, readability wins over scale
fidelity.
Sizing reverted:
viewBox 620 → 430
tank h 520 → 340
botY 560 → 380
Behavior:
GAP 30 → 36 (more visible space between dashed lines)
placeItem takes a single y now (line + input + label + unit
share it); leader-line mechanism kept as hidden
plumbing in case we switch back to proportional later
Dead-volume band now anchors to the (possibly-nudged) outflow line
instead of the proportional y so it still visually meets the line
cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tank height 340 → 520 px (viewBox 480 → 620). Lines that were
cramped in the bottom metre now have ~50 % more room, so:
- The Outlet arrow no longer visually crowds the minLevel line
- Dashed threshold lines (dryRunLevel, minLevel, outflowLevel)
have visible breathing room between them for typical wastewater
values where they sit in the bottom 1 m
- Input-stack GAP bumped 26 → 30 px to match the extra vertical
real estate
Pure layout change — same proportional mapping, same nudging
algorithm, just more canvas. Floor/datum label and ordering-
warning ribbon positions shifted accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When real wastewater values cluster near the basin floor (minLevel,
dryRunLevel, outflowLevel are often within a few cm of each other),
the threshold inputs were stacking on top of each other. Now:
- Threshold LINE stays at its proportional y on the tank (visual
truth: that's where the level actually is).
- Input BOX / label / unit are positioned in a nudged right-column
stack with a minimum 26-px gap so they never overlap.
- A dashed grey leader line connects each line to its input when
they had to be pulled apart, so the association stays visible.
- Items are sorted by ideal y top-down and nudged downward once;
basinHeight is pinned at the rim and acts as the anchor.
Also: viewBox extended 430 → 480 so the bottom-of-stack items have
room below the tank when the bottom cluster is tight. Warning ribbon
moved to y=460 accordingly.
No schema change; purely UI layout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the static parameters-diagram-above-form-rows layout with a
single interactive SVG where every threshold input sits directly on
the tank at its proportional y-position. Typing a value repositions
the corresponding line + input + label live.
What moved into the diagram (via <foreignObject> holding real
<input> elements with their existing node-input-* IDs so Node-RED
save/restore is untouched):
basinHeight — top of tank (fixed at rim by definition)
overflowLevel — weir crest (red, dashed)
maxLevel — 100 % demand line (orange, dashed)
startLevel — ramp-start line (green, dashed)
minLevel — MGC-shutdown line (purple, dashed)
inflowLevel — Inlet arrow + input on left
outflowLevel — Outlet arrow + input on right
dryRunLevel — read-only, computed from outflow × (1+dryRunPct/100)
Also in the diagram:
- Dead-volume band fills the area below outflowLevel dynamically
- Warning ribbon appears below the tank if ordering invariants break
(mirrors specificClass._validateThresholdOrdering)
- All positions scale against the user's basinHeight; if empty, a
default 5 m scale is used just to keep the diagram readable
What stayed as regular form rows:
- Basin Volume (m³) — not a height, can't be placed on a y-axis
- minLevel / startLevel / maxLevel were in the Control Strategy >
Level-based section; removed from there and moved into the diagram
(the level-based subsection now contains a one-line pointer)
- Safety % inputs (dryRun, overfill) stay in the Safety section with
their derived-level readouts, now synced with the diagram
No schema changes, no field additions, no behaviour changes in the
runtime. Pure editor-UX.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
~3 KB inline SVG showing the 5 threshold lines + inlet/outlet pipe
arrows + floor datum, using the same names as the editor fields
(basinHeight, overflowLevel, maxLevel, startLevel, minLevel,
dryRunLevel). No new inputs — purely a visual reminder of what
each field refers to, so operators don't have to alt-tab to the
wiki to figure out which pipe edge to measure.
Wrapped in <details open> so users can collapse it once they
know the layout — no forced scroll through the diagram on every
edit session.
Matches the vocabulary in wiki/diagrams/basin-model.drawio.svg
(inlet = bottom of pipe, outlet = top of pipe, 0 m datum at
basin floor, dryRunLevel derived from %).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
### P1 — match diagram naming (labels only, no schema change)
- "Inlet Elevation" → "Inlet (bottom of pipe, m)"
- "Outlet Elevation" → "Outlet (top of pipe, m)"
- "Overflow Level" → "Overflow (weir crest, m)"
- "Basin Bottom (m Refheight)" → "Basin floor above datum (m)"
- Added a one-line banner at the top of Basin Geometry:
"All heights measured from the basin floor (0 m)."
These map directly to the clarifications added to basin-model.drawio.svg
so editor and diagram speak the same vocabulary.
### P3 — live derived safety levels next to the % fields
Low/High Volume Threshold fields now show the resulting trip level
live as the operator types:
Low Volume Threshold (%) [ 2 ] → dryRunLevel ≈ 0.21 m
High Volume Threshold (%) [ 98 ] → overfillLevel ≈ 4.41 m
Recomputed on every input/change of outflowLevel, inflowLevel,
overflowLevel, minHeightBasedOn, or either %. Pure UI feedback —
no schema change, no save-side change, same formulas as
specificClass._validateThresholdOrdering().
Also includes the user's latest basin-model.drawio.svg update
(inlet=bottom/outlet=top labels + datum annotation).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>