Some checks failed
CI / lint-and-test (push) Has been cancelled
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>
141 lines
9.1 KiB
Markdown
141 lines
9.1 KiB
Markdown
# Pumping Station — 3 Pumps with Dashboard
|
||
|
||
A complete end-to-end EVOLV stack: a wet-well basin model, a `machineGroupControl` orchestrating three `rotatingMachine` pumps (each with upstream/downstream pressure measurements), process-demand input from either a dashboard slider or an auto random generator, individual + auto control modes, and a FlowFuse dashboard with status, gauges, and trend charts.
|
||
|
||
This is the canonical "make sure everything works together" demo for the platform. Use it after any cross-node refactor to confirm the architecture still hangs together end-to-end.
|
||
|
||
## Quick start
|
||
|
||
```bash
|
||
cd /mnt/d/gitea/EVOLV
|
||
docker compose up -d
|
||
# Wait for http://localhost:1880/nodes to return 200, then:
|
||
curl -s -X POST http://localhost:1880/flows \
|
||
-H "Content-Type: application/json" \
|
||
-H "Node-RED-Deployment-Type: full" \
|
||
--data-binary @examples/pumpingstation-3pumps-dashboard/flow.json
|
||
```
|
||
|
||
Or open Node-RED at <http://localhost:1880>, **Import → drop the `flow.json`**, click **Deploy**.
|
||
|
||
Then open the dashboard:
|
||
|
||
- <http://localhost:1880/dashboard/pumping-station-demo>
|
||
|
||
## Tabs
|
||
|
||
The flow is split across four tabs by **concern**:
|
||
|
||
| Tab | Lives here | Why |
|
||
|---|---|---|
|
||
| 🏭 **Process Plant** | EVOLV nodes (3 pumps + MGC + PS + 6 measurements) and per-node output formatters | The "real plant" layer. Lift this tab into production unchanged. |
|
||
| 📊 **Dashboard UI** | All `ui-*` widgets, button/setpoint wrappers, trend-split functions | Display + operator inputs only. No business logic. |
|
||
| 🎛️ **Demo Drivers** | Random demand generator, random-toggle state | Demo-only stimulus. In production, delete this tab and feed `cmd:demand` from your real demand source. |
|
||
| ⚙️ **Setup & Init** | One-shot `once: true` injects (MGC scaling/mode, pumps mode, auto-startup, random-on) | Runs at deploy time only. Disable for production runtimes. |
|
||
|
||
Cross-tab wiring uses **named link-out / link-in pairs**, never direct cross-tab wires. The channel names form the contract:
|
||
|
||
| Channel | Direction | What it carries |
|
||
|---|---|---|
|
||
| `cmd:demand` | UI / drivers → process | numeric demand in m³/h |
|
||
| `cmd:randomToggle` | UI → drivers | `'on'` / `'off'` |
|
||
| `cmd:mode` | UI / setup → process | `'auto'` / `'virtualControl'` setMode broadcast |
|
||
| `cmd:station-startup` / `cmd:station-shutdown` / `cmd:station-estop` | UI / setup → process | station-wide command, fanned to all 3 pumps |
|
||
| `cmd:setpoint-A` / `-B` / `-C` | UI → process | per-pump setpoint slider value |
|
||
| `cmd:pump-A-seq` / `-B-seq` / `-C-seq` | UI → process | per-pump start/stop |
|
||
| `evt:pump-A` / `-B` / `-C` | process → UI | formatted per-pump status |
|
||
| `evt:mgc` | process → UI | MGC totals (flow / power / efficiency) |
|
||
| `evt:ps` | process → UI | basin state + level + volume + flows |
|
||
| `setup:to-mgc` | setup → process | MGC scaling/mode init |
|
||
|
||
See `.claude/rules/node-red-flow-layout.md` for the full layout rule set this demo follows.
|
||
|
||
## What the flow contains
|
||
|
||
| Layer | Node(s) | Role |
|
||
|---|---|---|
|
||
| Top | `pumpingStation` "Pumping Station" | Wet-well basin model. Tracks inflow (`q_in`), outflow (from machine-group child predictions), basin level/volume. PS is in `manual` control mode for the demo so it observes without taking control. |
|
||
| Mid | `machineGroupControl` "MGC — Pump Group" | Distributes Qd flow demand across the 3 pumps via `optimalcontrol` (BEP-driven). Scaling: `absolute` (Qd is in m³/h directly). |
|
||
| Low | `rotatingMachine` × 3 — Pump A / B / C | Hidrostal H05K-S03R curve. `auto` mode by default so MGC's `parent` commands are accepted. Manual setpoint slider overrides per-pump when each is in `virtualControl`. |
|
||
| Sensors | `measurement` × 6 | Per pump: upstream + downstream pressure (mbar). Simulator mode — each ticks a random-walk value continuously. Registered as children of their pump. |
|
||
| Demand | inject `demand_rand_tick` + function `demand_rand_fn` + `ui-slider` | Random generator (3 s tick, [40, 240] m³/h) AND a manual slider. Both feed a router that fans out to PS (`q_in` in m³/s) and MGC (`Qd` in m³/h). |
|
||
| Glue | `setMode` fanouts + station-wide buttons | Mode toggle broadcasts `setMode` to all 3 pumps. Station-wide Start / Stop / Emergency-Stop buttons fan out to all 3. |
|
||
| Dashboard | FlowFuse `ui-page` + 6 groups | Process Demand · Pumping Station · Pump A · Pump B · Pump C · Trends. |
|
||
|
||
## Dashboard map
|
||
|
||
The page (`/dashboard/pumping-station-demo`) is laid out top-to-bottom:
|
||
|
||
1. **Process Demand**
|
||
- Slider 0–300 m³/h (`manualDemand` topic)
|
||
- Random demand toggle (auto cycles every 3 s)
|
||
- Live "current demand" text
|
||
2. **Pumping Station**
|
||
- Auto/Manual mode toggle (drives all pumps' `setMode` simultaneously)
|
||
- Station-wide buttons: Start all · Stop all · Emergency stop
|
||
- Basin state, level (m), volume (m³), inflow / pumped-out flow (m³/h)
|
||
3. **Pump A / B / C** (one group each)
|
||
- Setpoint slider 0–100 % (only effective when that pump is in `virtualControl`)
|
||
- Per-pump Startup + Shutdown buttons
|
||
- Live state, mode, controller %, flow, power, upstream/downstream pressure
|
||
4. **Trends**
|
||
- Flow per pump chart (m³/h)
|
||
- Power per pump chart (kW)
|
||
|
||
## Control model
|
||
|
||
- **AUTO** — the default. `setMode auto` → MGC's `optimalcontrol` decides which pumps run and at what flow. Operator drives only the **Process Demand** slider (or leaves the random generator on); the per-pump setpoint sliders are ignored.
|
||
- **MANUAL** — flip the Auto/Manual switch. All 3 pumps go to `virtualControl`. MGC commands are now ignored. Per-pump setpoint sliders / Start / Stop are the only inputs that affect the pumps.
|
||
|
||
The Emergency Stop button always works regardless of mode and uses the new interruptible-movement path so it stops a pump mid-ramp.
|
||
|
||
## Notable design choices
|
||
|
||
- **PS is in `manual` control mode** (`controlMode: "manual"`). The default `levelbased` mode would auto-shut all pumps as soon as basin level dips below `stopLevel` (1 m default), which masks the demo. Manual = observation only.
|
||
- **PS safety guards (dry-run / overfill) disabled.** With no real inflow the basin will frequently look "empty" — that's expected for a demo, not a fault. In production you'd configure a real `q_in` source and leave safeties on.
|
||
- **MGC scaling = `absolute`, mode = `optimalcontrol`.** Set via inject at deploy. Demand in m³/h, BEP-driven distribution.
|
||
- **demand_router gates Qd ≤ 0.** A demand of 0 would shut every running pump (via MGC.turnOffAllMachines). Use the explicit Stop All button to actually take pumps down.
|
||
- **Auto-startup on deploy.** All three pumps fire `execSequence startup` 4 s after deploy so the dashboard shows activity immediately.
|
||
- **Auto-enable random demand** 5 s after deploy so the trends fill in without operator action.
|
||
- **Verbose logging is OFF.** All EVOLV nodes are at `warn`. Crank the per-node `logLevel` to `info` or `debug` if you're diagnosing a flow.
|
||
|
||
## Things to try
|
||
|
||
- Drag the **Process Demand slider** with random off — watch MGC distribute that target across pumps and the basin start filling/draining accordingly.
|
||
- Flip to **Manual** mode and use the per-pump setpoint sliders — note that MGC stops driving them.
|
||
- Hit **Emergency Stop** while a pump is ramping — confirms the interruptible-movement fix shipped in `rotatingMachine` v1.0.3.
|
||
- Watch the **Trends** chart over a few minutes — flow distribution shifts as MGC re-balances around the BEP.
|
||
|
||
## Verification (last green run, 2026-04-13)
|
||
|
||
Deployed via `POST /flows` to a Dockerized Node-RED, observed for ~15 s after auto-startup:
|
||
|
||
- All 3 measurement nodes per pump tick (6 total): pressure values stream every second.
|
||
- Each pump reaches `operational` ~5 s after the auto-startup inject (3 s starting + 1 s warmup + 1 s for setpoint=0 settle).
|
||
- MGC reports `3 machine(s) connected` with mode `optimalcontrol`.
|
||
- Pumping Station shows non-zero basin volume + tracks net flow direction (⬆ / ⬇ / ⏸).
|
||
- Random demand cycles between ~40 and ~240 m³/h every 3 s.
|
||
- Per-pump status text + trend chart update on every tick.
|
||
|
||
## Regenerating `flow.json`
|
||
|
||
`flow.json` is generated from `build_flow.py`. Edit the Python (cleaner diff) and regenerate:
|
||
|
||
```bash
|
||
cd examples/pumpingstation-3pumps-dashboard
|
||
python3 build_flow.py > flow.json
|
||
```
|
||
|
||
The `build_flow.py` is the source of truth — keep it in sync if you tweak the demo.
|
||
|
||
## Wishlist (not in this demo, build separately)
|
||
|
||
- **Pump failure + MGC re-routing** — kill pump 2 mid-run, watch MGC redistribute. Would demonstrate fault-tolerance.
|
||
- **Energy-optimal vs equal-flow control** — same demand profile run through `optimalcontrol` and `prioritycontrol` modes side-by-side, energy comparison chart.
|
||
- **Schedule-driven demand** — diurnal flow pattern (low at night, peak at 7 am), MGC auto-tuning over 24 simulated hours.
|
||
- **PS with real `q_in` source + safeties on** — show the basin auto-shut behaviour as a feature, not a bug.
|
||
- **Real flow sensor per pump** (vs. relying on rotatingMachine's predicted flow) — would let the demo also show measurement-vs-prediction drift indicators.
|
||
- **Reactor or settler downstream** — close the loop on a real wastewater scenario.
|
||
|
||
See the parent `examples/README.md` for the full follow-up catalogue.
|