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>
This commit is contained in:
158
wiki/Reference-Architecture.md
Normal file
158
wiki/Reference-Architecture.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Reference — Architecture
|
||||
|
||||

|
||||
|
||||
> [!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 — 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**:
|
||||
|
||||

|
||||
|
||||
| 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 — 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() — emit Port 0 + Port 1 deltas
|
||||
```
|
||||
|
||||
Each tick is 1 Hz. The output pipeline (Port 0 + Port 1) is driven by `outputUtils.formatMsg` — 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 — 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()` — 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 — Contracts](Reference-Contracts) | Topic + config + child filters |
|
||||
| [Reference — Examples](Reference-Examples) | Shipped example flows |
|
||||
| [Reference — Limitations](Reference-Limitations) | Known limitations and open questions |
|
||||
| [EVOLV — Architecture](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Architecture) | Platform-wide three-tier pattern |
|
||||
Reference in New Issue
Block a user