The tank gauge (basin level) and 270° arc gauge (fill %) now live on
the trend pages alongside the basin metrics chart — not on the control
page. Each trend page (10 min / 1 hour) gets its own pair of gauges.
Layout per trend page Basin group:
- Chart (width 8): Basin fill % + Level + Net flow series
- Tank gauge (width 2): 0–3 m with color zones at stop/start levels
- Arc gauge (width 2): 0–100% fill with red/orange/green zones
Deployed via partial (nodes-only) deploy so the basin wasn't reset.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Basin Status group on the Control page now has two visual gauges:
1. gauge-tank (vertical tank with fill gradient) for basin level 0–3 m.
Color zones: red < 0.6 m (below stopLevel) → orange → blue 1.2–2.5 m
(normal operating range) → orange → red > 2.8 m (overflow zone).
2. gauge-34 (270° arc) for fill percentage 0–100%.
Color zones: red < 10% → orange → green 30–80% → orange → red > 95%.
Both gauges are fed from the PS dispatcher's numeric outputs (fillPctNum
and levelNum) which also feed the basin trend charts — same data, two
visual forms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PS control page now shows 7 fields instead of 5:
- Direction (filling/draining/steady)
- Basin level (m)
- Basin volume (m³)
- Fill level (%)
- Net flow (m³/h, signed)
- Time to full/empty (countdown in min or s)
- Inflow (m³/h)
Two new trend pages per time window (short 10 min / long 1 hour):
- Basin chart: 3 series (Basin fill %, Basin level m, Net flow m³/h)
on both Trends 10 min and Trends 1 hour pages.
PS formatter now extracts direction, netFlow, seconds from the delta-
compressed port 0 cache and computes fillPct from vol/maxVol. Dispatcher
sends 10 outputs (7 text + 3 trend numerics to both short+long basin
charts).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Architecture change: demo is now driven by a sinusoidal inflow into the
pumping station basin, rather than a random demand generator. The basin
fills from the sinus, and PS's levelbased control should start/stop
pumps via MGC when level crosses start/stop thresholds.
Changes:
- Demo Drivers tab: sinus generator (period 120s, base 0.005 + amp 0.03
m³/s) replaces the random demand. Sends q_in to PS via link channel.
- PS config: levelbased mode, 10 m³ basin, startLevel 1.2 m / stopLevel
0.6 m. Volume-based safeties on, time-based off.
- MGC scaling = normalized (was absolute) so PS's percent-based level
control maps correctly.
- Dashboard mode toggle now drives PS mode (levelbased ↔ manual) instead
of per-pump setMode. Slider sends Qd to PS (only effective in manual).
- PS code (committed separately): _controlLevelBased now calls
_applyMachineGroupLevelControl + new Qd topic + forwardDemandToChildren.
KNOWN ISSUE: Basin fills correctly (visible on dashboard), but pumps
don't start when level exceeds startLevel. Likely cause: _pickVariant
for 'level' in _controlLevelBased may not be resolving the predicted
level correctly, or the safetyController is interfering despite
time-threshold being 0. Needs source-level tracing of the PS tick →
_safetyController → _controlLogic → _controlLevelBased path with
logging enabled. To be debugged in the next session.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Charts rendered blank because the helper was missing 15+ required
FlowFuse properties. The critical three:
- interpolation: "linear" (no line drawn without it)
- yAxisProperty: "payload" + yAxisPropertyType: "msg" (chart didn't
know which msg field to plot)
- xAxisPropertyType: "timestamp" (chart didn't know the x source)
Also: width/height must be numbers not strings, colors/textColor/
gridColor arrays must be present, and stackSeries/bins/xAxisFormat/
xAxisFormatType all need explicit values.
Fixed the ui_chart helper to include every property from the working
rotatingMachine/examples/03-Dashboard.json charts. Added the full
required-property template + gotcha list to the flow-layout rule set
(Section 4) so this class of bug is caught by reference on the next
chart build.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FlowFuse ui-chart with xAxisType=time may need an explicit timestamp
on each msg for the time axis to render. Added Date.now() as
msg.timestamp on the per-pump dispatcher flow/power outputs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FlowFuse ui-text only supports {{msg.payload}} — not nested paths
like {{msg.payload.state}}. Every ui-text was showing [object Object]
because the formatter sent a fat object as msg.payload and the format
template tried to access sub-fields.
Fix: per-pump (and per-MGC, per-PS) "dispatcher" function on the
Dashboard UI tab. The dispatcher receives the fat object via one
link-in, then returns 7-9 plain-string outputs — one per ui-text
widget — each with msg.payload set to the formatted string value.
Outputs 8+9 carry numeric values (flowNum/powerNum) tagged with
msg.topic for the trend charts, wired directly to both short-term
and long-term chart nodes.
Pattern documented as the recommended approach in the rule set:
"FlowFuse ui-text receives plain strings only — use a dispatcher
function to split a fat object into per-widget outputs."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dashboard was a single page — 30+ widgets + tiny charts competing for
space. Trends were invisible or very small (width/height both "0"
meant "inherit from group" which gave near-zero chart area).
Split into 3 dashboard pages:
1. Control — Process Demand, Station Controls, MGC/Basin status,
per-pump panels (unchanged, just moved off trend groups)
2. Trends — 10 min — rolling 10-minute flow + power charts with
width=12 (full group), height=8 (tall charts), 300 max points
3. Trends — 1 hour — same layout with 60-minute window, 1800 points
All 3 pages auto-nav via the FlowFuse sidebar. Same data feed: the
per-pump trend_split function now wires to 4 charts (2 outputs × 2
pages) instead of 2.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The demo was a single 96-node tab with everything wired directly. Now
4 tabs wired only through named link-out / link-in pairs, and a
permanent rule set for future Claude sessions to follow.
Tabs (by concern, not by data flow):
🏭 Process Plant only EVOLV nodes (3 pumps + MGC + PS + 6 measurements)
+ per-node output formatters
📊 Dashboard UI only ui-* widgets, button/setpoint wrappers, trend
splitters
🎛️ Demo Drivers random demand generator + state holder. Removable
in production
⚙️ Setup & Init one-shot deploy-time injects (mode, scaling,
auto-startup, random-on)
Cross-tab wiring uses a fixed named-channel contract (cmd:demand,
cmd:mode, cmd:setpoint-A, evt:pump-A, etc.) — multiple emitters can
target a single link-in for fan-in, e.g. both the slider and the random
generator feed cmd:demand.
Bug fixes folded in:
1. Trend chart was empty / scrambled. Root cause: the trend-feeder
function had ONE output that wired to BOTH flow and power charts,
so each chart received both flow and power msgs and the legend
garbled. Now: 2 outputs (flow → flow chart, power → power chart),
one msg per output.
2. Every ui-text and ui-chart fell on the (0, 0) corner of the editor
canvas. Root cause: the helper functions accepted x/y parameters
but never assigned them on the returned node dict — Node-RED
defaulted every widget to (0, 0) and they piled on top of each
other. The dashboard render was unaffected (it lays out by group/
order), but the editor was unreadable. Fixed both helpers and added
a verification step ("no node should be at (0, 0)") to the rule set.
Spacing convention (now codified):
- 6 lanes per tab at x = [120, 380, 640, 900, 1160, 1420]
- 80 px standard row pitch, 30-40 px for tight ui-text stacks
- 200 px gap between sections, with a comment header per section
New rule set: .claude/rules/node-red-flow-layout.md
- Tab boundaries by concern
- Link-channel naming convention (cmd:/evt:/setup: prefixes)
- Spacing constants
- Trend-split chart pattern
- Inject node payload typing pitfall (per-prop v/vt)
- Dashboard widget rules (every ui-* needs x/y!)
- Do/don't checklist
- Link-out/link-in JSON cheat sheet
- 5-step layout verification before declaring a flow done
CLAUDE.md updated to point at the new rule set.
Verified end-to-end on Dockerized Node-RED 2026-04-13: 168 nodes across
4 tabs, all wired via 22 link-out / 19 link-in pairs, no nodes at
(0, 0), pumps reach operational ~5 s after deploy, MGC distributes
random demand, trends populate per pump.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New top-level examples/ folder for end-to-end demos that show how multiple
EVOLV nodes work together (complementing the per-node example flows under
nodes/<name>/examples/). Future end-to-end demos will live as siblings.
First demo: pumpingstation-3pumps-dashboard
- 1 pumpingStation (basin model, manual mode for the demo so it observes
rather than auto-shutting pumps; safety guards disabled — see README)
- 1 machineGroupControl (optimalcontrol mode, absolute scaling)
- 3 rotatingMachine pumps (hidrostal-H05K-S03R curve)
- 6 measurement nodes (per pump: upstream + downstream pressure mbar,
simulator mode for continuous activity)
- Process demand input via dashboard slider (0-300 m3/h) AND auto random
generator (3s tick, [40, 240] m3/h) — both feed PS q_in + MGC Qd
- Auto/Manual mode toggle (broadcasts setMode to all 3 pumps)
- Station-wide Start / Stop / Emergency-Stop buttons
- Per-pump setpoint slider, individual buttons, full status text
- Two trend charts (flow per pump, power per pump)
- FlowFuse dashboard at /dashboard/pumping-station-demo
build_flow.py is the source of truth — it generates flow.json
deterministically and is the right place to extend the demo.
Bumps:
nodes/generalFunctions 43f6906 -> 29b78a3
Fix: childRegistrationUtils now aliases the production
softwareType values (rotatingmachine, machinegroupcontrol) to the
dispatch keys parent nodes check for (machine, machinegroup). Without
this, MGC <-> rotatingMachine and pumpingStation <-> MGC wiring
silently never matched in production even though tests passed.
Demo confirms: MGC reports '3 machine(s) connected'.
Verified end-to-end on Dockerized Node-RED 2026-04-13: pumps reach
operational ~5s after deploy, MGC distributes random demand across them,
basin tracks net flow direction, all dashboard widgets update each second.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>