Files
pumpingStation/wiki/Reference-Architecture.md
znetsixe 8507ee4e02 wiki: split per-node Home into Zone A (intuitive) + Reference-* siblings
New standard, pilot pass for pumpingStation. Sets the pattern the other
10 nodes will follow once we sign off on this one.

Zone A (wiki/Home.md, ~180 lines):
- one-sentence opener
- "at a glance" 5-row fact table
- "How it looks in Node-RED" — screenshot placeholder
- "What it models" — embeds the existing basin-model.drawio.svg
- "Try it" — 3-minute demo with curl-load command, click list,
  GIF placeholder
- "Typical wiring" — two placeholder screenshots (standalone +
  integrated), no mermaid (per user direction)
- "The five things you'll send" + sample Port-0 payload table
- "Need more?" footer linking to Reference-* siblings

Zone B (4 sibling pages):
- Reference-Contracts.md  — full topic contract + data model
  (AUTOGEN markers); config schema; child registration filters;
  unit policy
- Reference-Architecture.md — 3-tier code layout; safety FSM
  (stateDiagram-v2); tick lifecycle (sequenceDiagram); output ports
- Reference-Examples.md — 01-Basic / 02-Integration / 03-Dashboard
  walk-through with per-example screenshot + GIF placeholders;
  debug-recipes table
- Reference-Limitations.md — implemented vs schema-only modes;
  basin-shape constraint; net-flow source caveat; alias-removal map

Asset directory placeholders created:
- wiki/_partial-screenshots/pumpingStation/.gitkeep
- wiki/_partial-gifs/pumpingStation/.gitkeep
- wiki/_partial-flows/pumpingStation/.gitkeep

Abandoned per user direction (no longer linked, removed from source):
- wiki/README.md
- wiki/functional-description.md (377 lines retired)
- wiki/modes/*.md (5 files retired)

Diagrams kept in place (wiki/diagrams/*.drawio.svg) — referenced from
Home and Reference-Architecture.

package.json: wiki:contract + wiki:datamodel now target
Reference-Contracts.md instead of Home.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 09:19:48 +02:00

159 lines
6.3 KiB
Markdown

# Reference &mdash; Architecture
![code-ref](https://img.shields.io/badge/code--ref-b825ac1-blue)
> [!NOTE]
> Code structure for `pumpingStation`: the three-tier sandwich, the `src/` layout, the FSM, the lifecycle, and the output-port pipeline. Everything here is reproducible from `src/`. For an intuitive overview, return to [Home](Home).
---
## Three-tier code layout
```
nodes/pumpingStation/
|
+-- pumpingStation.js entry: RED.nodes.registerType('pumpingstation', NodeClass)
|
+-- src/
| nodeClass.js extends BaseNodeAdapter (Node-RED bridge)
| specificClass.js extends BaseDomain (orchestration only)
| |
| +-- commands/
| | index.js topic descriptors
| | handlers.js pure handler functions
| |
| +-- basin/
| | BasinGeometry.js basin shape, level <-> volume conversion
| | thresholdValidator.js derives + validates safety / control thresholds
| |
| +-- measurement/
| | flowAggregator.js net-flow + predicted-volume integrator
| | measurementRouter.js routes measurement-child events
| | calibration.js calibrate-to-known-level / volume helpers
| |
| +-- control/
| | index.js mode dispatcher (levelbased, manual, ...)
| |
| +-- safety/
| safetyController.js dry-run + high-volume + panic guards
```
### Tier responsibilities
| Tier | File | What it owns | Touches `RED.*` |
|:---|:---|:---|:---:|
| entry | `pumpingStation.js` | Type registration | Yes |
| nodeClass | `src/nodeClass.js` | Input routing, tick loop, output ports, status badge | Yes |
| specificClass | `src/specificClass.js` | Wire concern modules in `configure()`; run them in `tick()`; nothing more | No |
The specificClass is stitching, not implementation. All real work lives in `basin/`, `measurement/`, `control/`, `safety/`.
---
## State chart &mdash; safety controller
The pumpingStation does not have a per-mode FSM (control modes are stateless transfer functions). The state machine that matters is the **safety controller**, which can block or pass control commands.
```mermaid
stateDiagram-v2
[*] --> running
running --> blocked_dryrun: level < dryRunLevel
running --> blocked_highvolume: level >= highVolumeSafetyLevel
running --> blocked_panic: no-data panic timer expires
blocked_dryrun --> running: level recovers above hysteresis
blocked_highvolume --> running: level falls below hysteresis
blocked_panic --> running: data resumes
```
Each `blocked_*` state sets `safety.blocked = true` on Port 0 and prevents the control layer from emitting a non-zero demand. The hysteresis is mode-independent and lives in `src/safety/safetyController.js`.
### Safety-rules asymmetry
The `dryRunLevel` and `highVolumeSafetyLevel` rules differ in **which children they stop**:
![Dry-run vs high-volume safety asymmetry](diagrams/safety-rules.drawio.svg)
| Rule | What stops | Why |
|:---|:---|:---|
| Dry run | All children (pumps off) | Pumps cavitate without water; protect the equipment |
| High volume | Only outflow-side pumps | Spill is the lesser evil; some pumps may still serve safety functions |
---
## Lifecycle &mdash; one tick
```mermaid
sequenceDiagram
autonumber
participant tick as 1s tick
participant sc as specificClass.tick()
participant fa as flowAggregator
participant safe as safetyController
participant ctrl as control[mode]
participant out as Port 0 / 1
tick->>sc: tick()
sc->>fa: update predicted volume
fa->>fa: pick best net-flow source (measured / aggregated)
sc->>safe: evaluate
alt safety blocked
safe-->>sc: { blocked: true }
Note over sc: skip control layer
else safe to run
sc->>ctrl: strategies[mode].run(context)
ctrl-->>sc: demand 0..100
end
sc->>out: getOutput() &mdash; emit Port 0 + Port 1 deltas
```
Each tick is 1 Hz. The output pipeline (Port 0 + Port 1) is driven by `outputUtils.formatMsg` &mdash; only changed fields are sent.
---
## Output ports
| Port | Carries | Sample shape |
|:---|:---|:---|
| 0 (process) | Delta-compressed state snapshot consumed by downstream Node-RED logic | `{topic, payload: {level, volume, demand, direction, safety, etaSeconds}}` |
| 1 (telemetry) | InfluxDB line-protocol string with the same fields as Port 0 | `pumpingStation,id=PS1 level=1.62,volume=32.4 ...` |
| 2 (register / control) | `child.register` upward at init; internal control plumbing later | `{topic: 'child.register', payload: {ref, softwareType, config}}` |
See [EVOLV &mdash; Telemetry](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Telemetry) for the full InfluxDB layout.
---
## Tick timing and event sources
| Source | Where it fires | What it triggers |
|:---|:---|:---|
| `setInterval(1000)` | `BaseNodeAdapter` lifecycle | `specificClass.tick()` &mdash; the per-second integrator update |
| `measurement` emitter event | Child node's `emitter.emit(<type>.measured.<position>, ...)` | `measurementRouter` updates the basin balance |
| Inbound `msg.topic` | Node-RED input wire | `commandRegistry` dispatch to a handler |
| `child.register` from another node | Port 2 of a child | `_subscribeMeasurement` or `_subscribePredictedFlow` |
---
## Where to start reading
| If you're changing... | Read first |
|:---|:---|
| Basin geometry, level/volume conversion | `src/basin/BasinGeometry.js`, `src/basin/thresholdValidator.js` |
| Net-flow selection, predicted-volume integration | `src/measurement/flowAggregator.js` |
| Calibration commands | `src/measurement/calibration.js` |
| Control modes (level-based, manual, future modes) | `src/control/index.js` |
| Safety blocks | `src/safety/safetyController.js` |
| Topic dispatch | `src/commands/index.js` + `src/commands/handlers.js` |
| Adapter, ticking, output ports | `src/nodeClass.js` (and `BaseNodeAdapter` in `generalFunctions`) |
---
## Related pages
| Page | Why |
|:---|:---|
| [Home](Home) | Intuitive overview |
| [Reference &mdash; Contracts](Reference-Contracts) | Topic + config + child filters |
| [Reference &mdash; Examples](Reference-Examples) | Shipped example flows |
| [Reference &mdash; Limitations](Reference-Limitations) | Known limitations and open questions |
| [EVOLV &mdash; Architecture](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Architecture) | Platform-wide three-tier pattern |