Files
EVOLV/examples/pumpingstation-3pumps-dashboard/README.md
znetsixe 0d7af6bfff
Some checks failed
CI / lint-and-test (push) Has been cancelled
refactor(examples): split pumpingstation demo across 4 concern-based tabs + add layout rule set
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>
2026-04-13 16:13:27 +02:00

141 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 0300 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 0100 % (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.