wiki: master EVOLV wiki refactor — 7 new pages + corrected Home

Complete redesign of the platform-level wiki. Previous Home.md had a
broken Mermaid diagram (showed pumpingStation → valveGroupControl as a
parent/child edge, which isn't in any configure() declaration). Audit
of all 12 specificClass.js configure() calls drives the new ground-truth
hierarchy.

New pages:
- Home.md (rewritten — accurate mermaid, full node + concept index)
- Architecture.md (3-tier code structure, generalFunctions API surface,
  child-registration sequence)
- Topology-Patterns.md (5 verified plant configurations + worked example)
- Topic-Conventions.md (set./cmd./evt./data./child. + unit policy + S88
  palette + measurement key shape + status badge + HealthStatus)
- Telemetry.md (Port 0/1/2 contracts + InfluxDB line-protocol layout +
  FlowFuse charts + Grafana provisioning)
- Getting-Started.md (clone, install, Docker vs local, first example)
- Glossary.md (S88, EVOLV runtime, WWTP, pumps, control, project terms)
- _Sidebar.md (gitea wiki navigation)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-05-11 21:47:57 +02:00
parent 9ab9f6b3e3
commit 2ccc8aea9e
8 changed files with 1226 additions and 48 deletions

170
wiki/Architecture.md Normal file
View File

@@ -0,0 +1,170 @@
# Architecture
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`**
How every EVOLV node is structured, and what the shared `generalFunctions` library provides.
## The 3-tier node pattern
```mermaid
flowchart LR
rt["Node-RED runtime"]:::neutral
subgraph node["Custom node (one folder under nodes/)"]
entry["<NodeName>.js<br/>(entry — registers node type with RED)"]:::tier1
nc["src/<NodeName>NodeClass.js<br/>(nodeClass — Node-RED adapter)"]:::tier2
sc["src/<NodeName>SpecificClass.js<br/>(specificClass — pure domain logic)"]:::tier3
end
rt -->|RED.nodes.registerType| entry
entry -->|new| nc
nc -->|new + configure()| sc
classDef neutral fill:#dddddd,color:#000
classDef tier1 fill:#a9daee,color:#000
classDef tier2 fill:#86bbdd,color:#000
classDef tier3 fill:#50a8d9,color:#000
```
| Tier | Owns | Touches RED.* API? | Tested by |
|---|---|---|---|
| entry (`<NodeName>.js`) | Type registration, HTTP admin endpoints | yes | smoke tests |
| nodeClass (`src/...NodeClass.js`, extends `BaseNodeAdapter`) | msg routing, tick loop, output port wiring, status badge updates | yes | integration tests |
| specificClass (`src/...SpecificClass.js`, extends `BaseDomain`) | All business logic; emits via `this.emitter`; calls `this.measurements` / `this.router` | **no** — must be free of RED imports | unit tests |
**Rule:** never import Node-RED APIs in the specificClass. The specificClass is unit-testable by `new SpecificClass(config)`. If you find `RED.*` calls outside the entry/nodeClass tiers, that's a bug.
## generalFunctions — what it provides
The `nodes/generalFunctions` submodule is a plain-JS library every node depends on. Public exports (top-level `require('generalFunctions')`):
| Export | Role |
|---|---|
| `BaseDomain` | Base class for every specificClass. Owns `measurements`, `router`, `emitter`, `logger`, `unitPolicy`. |
| `BaseNodeAdapter` | Base class for every nodeClass. Wires `commandRegistry` to `node.on('input')`, owns tick loop. |
| `ChildRouter` | Declarative child-registration matcher. `router.onRegister(softwareType, handler)`, `router.onMeasurement(...)`. |
| `commandRegistry` | Topic → handler descriptor map. Owns alias resolution + unit coercion. |
| `UnitPolicy` | Per-node canonical + output units. Coerces incoming `msg.unit` to canonical. |
| `MeasurementContainer` | Chainable storage: `type(t).variant(v).position(p).value(x, ts, unit)`. Key shape: `<type>.<variant>.<position>.<childId>`. |
| `statusBadge` | Composer for `node.status({fill,shape,text})` updates. |
| `HealthStatus` | Standardised `{ level: 0..3, flags: [], message, source }` shape. |
| `LatestWinsGate` | Mutex with supersede semantics — keeps only the freshest in-flight call. |
| `logger` | Structured logger (use this; never `console.log`). |
| `configManager` | Loads JSON schemas from `src/configs/<node>.json`. |
| `MenuManager` | Dynamic editor dropdowns (asset lists). |
| `outputUtils` | Delta-compressed Port-0 + InfluxDB-line-protocol Port-1 formatting. |
See [generalFunctions Home →](https://gitea.wbd-rd.nl/RnD/generalFunctions/wiki/Home) for the full 34-row API table.
## Output ports
Every EVOLV node emits on three ports:
```mermaid
flowchart LR
sc[specificClass]:::tier3
p0[(Port 0<br/>process data)]:::p0
p1[(Port 1<br/>InfluxDB line)]:::p1
p2[(Port 2<br/>registration / control)]:::p2
sc --> p0
sc --> p1
sc --> p2
p0 -.-> dn1[downstream Node-RED nodes<br/>dashboards, function nodes]
p1 -.-> influx[(InfluxDB)]
p2 -.-> parent[parent EVOLV node<br/>via child.register]
classDef tier3 fill:#50a8d9,color:#000
classDef p0 fill:#86bbdd
classDef p1 fill:#a9daee
classDef p2 fill:#dddddd
```
| Port | Carries | Format | Cardinality |
|---|---|---|---|
| **0** Process | Delta-compressed measurement / state snapshot for downstream Node-RED logic. | `msg.payload` = object of changed keys only. | One msg per tick when something changed. |
| **1** Telemetry | InfluxDB line-protocol strings: `measurement,tag=val field=val ts`. | `msg.payload` = `string` (or array of strings). | One msg per tick when something changed; all numeric outputs. |
| **2** Registration / control | `child.register` upward on adapter init; control replies. | `{topic, payload: nodeRef}` | At init time + on demand. |
See [Telemetry](Telemetry) for the full Port-1 schema and InfluxDB conventions.
## Topic conventions
| Prefix | Direction | Used for |
|---|---|---|
| `set.` | inbound | Set a configurable value (mode, setpoint). Idempotent. |
| `cmd.` | inbound | Trigger an action (startup, shutdown, calibrate). Has side-effects. |
| `data.` | inbound or outbound | Carries measurement data between child ↔ parent. |
| `evt.` | outbound | Signal that something happened (state change, alarm). |
| `child.` | inbound (on parent) | Child node registers itself with this parent. |
See [Topic-Conventions](Topic-Conventions) for the full list, payload shapes, alias deprecation map.
## Child registration
When a node is configured with `parent` = some other node's id, on `init()` the nodeClass emits a `child.register` message on Port 2 toward the parent. The parent's `commandRegistry` routes it into `ChildRouter`, which fires the matching `onRegister(softwareType, handler)` declared in `configure()`.
```mermaid
sequenceDiagram
participant childNc as Child nodeClass
participant parentReg as Parent commandRegistry
participant parentRouter as Parent ChildRouter
participant parentSc as Parent specificClass
childNc->>parentReg: msg{topic: child.register, softwareType, ref}
parentReg->>parentRouter: dispatch(child.register, ref)
parentRouter->>parentRouter: match softwareType
parentRouter->>parentSc: invoke registered handler
parentSc->>parentSc: store ref, wire emitter.on(...)
```
A child is anything the parent's `configure()` declares via `router.onRegister(<softwareType>, handler)`. Examples:
| Parent | Accepts children with softwareType |
|---|---|
| pumpingStation | `measurement`, `machine`, `machinegroup`, `pumpingstation` |
| machineGroupControl | `machine`, `measurement` |
| valveGroupControl | `valve`, `machine`, `machinegroup`, `pumpingstation`, `valvegroupcontrol` (last 4 as flow sources) |
| reactor | `measurement`, `reactor` |
| settler | `measurement`, `reactor`, `machine` |
| monster | `measurement` |
| diffuser | `measurement` |
| rotatingMachine | `measurement` |
| valve | `measurement` |
| dashboardAPI | any (used for Grafana provisioning) |
## Where business logic lives
```mermaid
flowchart TB
subgraph node["A node's src/ folder"]
sc["specificClass.js<br/>orchestration only"]
subgraph concerns["Concern subdirs (per-node)"]
c1[basin/ or curves/ or kinetics/<br/>physics / math]
c2[state/<br/>FSM transitions]
c3[dispatch/ or safety/<br/>action / guard logic]
c4[commands/<br/>topic → handler descriptors]
c5[io/<br/>output composition]
end
end
sc --> c1
sc --> c2
sc --> c3
sc --> c4
sc --> c5
```
specificClass should be **stitching only** — instantiate concern modules in `configure()`, call them in `tick()` or in router handlers. Concerns are individually testable.
## Reading order for newcomers
1. `.claude/refactor/CONTRACTS.md` — every API shape this wiki abstracts over.
2. `.claude/refactor/CONVENTIONS.md` — code style, file size, naming.
3. `.claude/refactor/MODULE_SPLIT.md` — concern layout per node.
4. One node's `wiki/Home.md` (pumpingStation is the most mature pilot — start there).
5. The corresponding `src/` folder, top-down: specificClass → concern modules.
## Related pages
- [Topology-Patterns](Topology-Patterns) — typical plant configurations
- [Topic-Conventions](Topic-Conventions) — naming and units
- [Telemetry](Telemetry) — Port-1 InfluxDB schema
- [Getting-Started](Getting-Started) — hands-on first run

172
wiki/Getting-Started.md Normal file
View File

@@ -0,0 +1,172 @@
# Getting Started
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`**
How to clone, install, and run EVOLV locally; how to deploy the example flows; and where to go next.
## Prerequisites
| Tool | Version | Why |
|---|---|---|
| Node.js | ≥ 18 LTS | Node-RED 4 requires 18+ |
| npm | ≥ 9 | Comes with Node.js |
| git | ≥ 2.35 | Submodule support |
| Docker + compose v2 | optional | For the local Node-RED + InfluxDB stack |
| WSL2 (on Windows) | optional | Recommended for native docker performance |
## Clone and install
```bash
git clone --recurse-submodules https://gitea.wbd-rd.nl/RnD/EVOLV.git
cd EVOLV
npm install
```
If you cloned without `--recurse-submodules`, run:
```bash
git submodule update --init --recursive
```
There are 12 submodules — one per node (`generalFunctions` plus 11 active nodes). Each lives under `nodes/<name>/`.
## Verify the platform builds
```bash
npm run test:platform # expect 823 / 0
```
This runs the full unit test suite across all 12 submodules. ~3 minutes on a workstation (reactor's mathjs initialisation dominates).
## Option A — Run via Docker (recommended)
The repo ships a `docker-compose.yml` that brings up Node-RED + InfluxDB pre-loaded with the EVOLV nodes:
```bash
docker compose up -d
```
When healthy:
| Service | URL |
|---|---|
| Node-RED editor | http://localhost:1880 |
| FlowFuse dashboard (if widgets installed) | http://localhost:1880/dashboard |
| InfluxDB UI | http://localhost:8086 |
Watch the container logs while you click around:
```bash
docker compose logs -f nodered
```
**WSL2 note:** use the native `docker` from Ubuntu, not `docker.exe` from Windows Docker Desktop. systemd `docker.service` should be enabled and your user in the `docker` group. Compose v2 plugin lives at `~/.docker/cli-plugins/docker-compose`.
## Option B — Run via local Node-RED
If you already have Node-RED installed in `~/.node-red`:
```bash
# in EVOLV/
ln -s "$PWD" ~/.node-red/nodes/EVOLV
```
Or add to your `~/.node-red/settings.js`:
```js
module.exports = {
// ...
nodesDir: ['/path/to/EVOLV/nodes'],
}
```
Then start Node-RED:
```bash
node-red
```
## Your first flow
Each node ships with example flows under `nodes/<name>/examples/`. The recommended starting point is **rotatingMachine — Basic Manual Control**:
```bash
# Copy the example into your Node-RED user dir
cp nodes/rotatingMachine/examples/01-Basic-Manual-Control.json ~/.node-red/
```
In the editor:
1. Menu → **Import** → select the file → **Import**.
2. Hit **Deploy**.
3. Open the dashboard at http://localhost:1880/dashboard.
4. Click the **startup** button. Watch the state machine progress: `idle → starting → warmingup → operational`.
5. Drag the demand slider. The flow + power predictions update in real time.
## What to read next
```mermaid
flowchart TB
start[You are here]:::neutral
arch[Architecture<br/>3-tier code structure]:::tier1
topo[Topology-Patterns<br/>typical plant configs]:::tier1
conv[Topic-Conventions<br/>naming + units]:::tier1
tele[Telemetry<br/>Port 0/1/2 + InfluxDB]:::tier1
node[Pick a node's wiki<br/>per-repo Home.md]:::tier3
start --> arch
start --> topo
arch --> node
topo --> node
node --> conv
node --> tele
classDef neutral fill:#dddddd
classDef tier1 fill:#a9daee,color:#000
classDef tier3 fill:#50a8d9,color:#000
```
| Path | Why |
|---|---|
| [Architecture](Architecture) | Internalise the 3-tier (entry → nodeClass → specificClass) pattern. |
| [Topology-Patterns](Topology-Patterns) | See typical plant configs end-to-end with verified edges. |
| Pick a node | The most mature is [pumpingStation](https://gitea.wbd-rd.nl/RnD/pumpingStation/wiki/Home) (refactor pilot). |
| [Topic-Conventions](Topic-Conventions) | Reference for naming when you start wiring your own flows. |
| [Telemetry](Telemetry) | If you're plumbing InfluxDB or Grafana. |
## Quick command reference
```bash
# run all tests
npm run test:platform
# run one node's tests
cd nodes/rotatingMachine && node --test test/basic/*.test.js
# regenerate a node's wiki AUTOGEN blocks
cd nodes/rotatingMachine && npm run wiki:all
# rebuild docker stack
docker compose build && docker compose up -d
# update all submodules to their development tips
git submodule update --remote --recursive
# pack EVOLV as an npm tarball
npm pack
```
## Where to ask for help
| Channel | Use it for |
|---|---|
| Per-node wiki on Gitea | Operator-level questions for one node. |
| `.claude/refactor/OPEN_QUESTIONS.md` | Live decisions log — issues being worked on. |
| Gitea repo issues per submodule | File a bug against a specific node. |
| R&D team Slack / Teams | Anything urgent or strategic. |
## Related pages
- [Home](Home) — top-level navigation
- [Architecture](Architecture) — how a node is built
- [Topology-Patterns](Topology-Patterns) — plant configurations

132
wiki/Glossary.md Normal file
View File

@@ -0,0 +1,132 @@
# Glossary
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`**
Terms and abbreviations used across the EVOLV codebase, wikis, and dashboards.
## S88 (ISA-88 batch control)
```mermaid
flowchart TB
enterprise["Enterprise"]:::neutral
site["Site"]:::neutral
area["Area"]:::area
pc["Process Cell"]:::pc
unit["Unit"]:::unit
em["Equipment Module"]:::equip
cm["Control Module"]:::ctrl
enterprise --> site --> area --> pc --> unit --> em --> cm
classDef neutral fill:#dddddd
classDef area fill:#0f52a5,color:#fff
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
```
| Term | Meaning in EVOLV |
|---|---|
| **Area** | Plant section. *Reserved*, no node implements it yet. |
| **Process Cell (PC)** | Self-contained sub-process. `pumpingStation` is the only PC-level node. |
| **Unit (UN)** | One major piece of equipment or a coordinator over equipment. `MGC`, `VGC`, `reactor`, `settler`, `monster`. |
| **Equipment Module (EM)** | A single piece of equipment. `rotatingMachine`, `valve`, `diffuser`. |
| **Control Module (CM)** | Single sensor / actuator. `measurement`. |
| **softwareType** | The node's S88 type — used by `ChildRouter` to match `child.register` calls. |
## EVOLV runtime concepts
| Term | Meaning |
|---|---|
| **BaseDomain** | Base class for every specificClass. Owns `measurements`, `router`, `emitter`, `logger`. |
| **BaseNodeAdapter** | Base class for every nodeClass. Bridges Node-RED ↔ specificClass via `commandRegistry`. |
| **specificClass** | Pure-JS domain logic. No `RED.*` imports. Unit-testable in isolation. |
| **nodeClass** | Node-RED adapter — owns input routing, tick loop, port wiring, status badge. |
| **ChildRouter** | Declarative matcher for `child.register` events. `router.onRegister(swType, handler)`. |
| **commandRegistry** | Inbound-msg dispatcher. Topic → handler descriptor map; resolves aliases and coerces units. |
| **UnitPolicy** | Per-node declaration of canonical (internal) and output units. |
| **MeasurementContainer** | Chainable measurement store. Keys: `<type>.<variant>.<position>.<childId>`. |
| **statusBadge** | Composes `node.status({fill,shape,text})` from a HealthStatus. |
| **HealthStatus** | `{level: 0..3, flags, message, source}` — standardised health summary. |
| **LatestWinsGate** | Mutex with supersede semantics — superseded calls resolve with `{superseded: true}`. |
| **outputUtils** | Single point for Port-0 delta compression + Port-1 line-protocol formatting. |
| **tick** | The 1 Hz update loop. Domain runs `tick()` for time-based concerns (integrators, FSM timers). |
| **getOutput()** | Domain method returning the current snapshot — fed to `outputUtils` for diff/format. |
| **getFlattenedOutput()** | Returns measurements with dot-flattened keys (4-segment: `type.variant.position.childId`). |
## Topic prefixes
(See [Topic-Conventions](Topic-Conventions) for the full table.)
| Prefix | Direction | Idempotent? |
|---|---|---|
| `set.` | inbound | yes |
| `cmd.` | inbound | no — has side-effects |
| `data.` | bidirectional | n/a |
| `evt.` | outbound | n/a |
| `child.` | inbound (parent receives) | yes — id-keyed |
## Wastewater treatment terms
| Term | Meaning |
|---|---|
| **WWTP** | Wastewater Treatment Plant. |
| **Influent / Effluent** | Inlet / outlet stream of a process unit. |
| **Activated Sludge** | Biological process where bacteria consume organic matter under aeration. Modelled by ASM (ASM1, ASM3, …). |
| **MLSS** | Mixed Liquor Suspended Solids — biomass concentration in the reactor. |
| **RAS** | Return Activated Sludge — settled sludge pumped back from settler to reactor. |
| **WAS** | Waste Activated Sludge — excess sludge removed from the system. |
| **TSS** | Total Suspended Solids. |
| **COD / BOD** | Chemical / Biological Oxygen Demand — organic load metrics. |
| **NH₄ / NO₃** | Ammonium / Nitrate — N species, key for nitrification/denitrification. |
| **DO** | Dissolved Oxygen, mg/L. Setpoint typically ~2 mg/L in aerated zones. |
| **K_La** | Volumetric mass-transfer coefficient (oxygen transfer rate / driving force). |
| **OTR** | Oxygen Transfer Rate — diffuser's output to the reactor. |
| **HRT / SRT** | Hydraulic / Sludge Retention Time. |
| **F/M ratio** | Food-to-microorganism ratio. |
| **Composite sample** | A sample built up over time, often proportional to flow — what `monster` simulates. |
## Pump / hydraulics terms
| Term | Meaning |
|---|---|
| **BEP** | Best Efficiency Point — operating point where the pump consumes the least energy per unit flow. |
| **NPSH** | Net Positive Suction Head — pressure margin to avoid cavitation. |
| **Affinity laws** | Q ∝ N, H ∝ N², P ∝ N³ — how flow/head/power scale with pump speed. |
| **Characteristic curve** | Q ↔ H (or Q ↔ P, Q ↔ η) graph supplied by the pump manufacturer. |
| **NCog** | Normalised cost-of-going metric used by MGC for switching stability. |
| **Wet-well basin** | The buffer volume on a lift station — what `pumpingStation` models. |
| **Setpoint** | The commanded value (e.g., flow setpoint = 12 m³/h). |
| **Demand** | The integrated requirement (e.g., the parent's "need this much flow now"). |
## Control / signal-processing terms
| Term | Meaning |
|---|---|
| **PID** | Proportional-Integral-Derivative controller. |
| **Anti-windup** | Prevents integral term from growing unboundedly when actuator saturates. |
| **Hysteresis** | Switching threshold with a deadband (e.g., start at 80%, stop at 30%). |
| **Schmitt trigger** | Hysteresis-based binary switch (used for `stopLevel` in `pumpingStation`). |
| **Smoothing** | Filtering noise (moving average, exponential, Savitzky-Golay). |
| **Outlier detection** | Identifying readings that fall outside expected variance. |
| **FSM** | Finite State Machine — discrete states + transitions. |
## Project terms
| Term | Meaning |
|---|---|
| **Tier 14** | Refactor phases (see [Home](Home) project status). |
| **Wave A / B / C** | Sub-batches of submodule pointer bumps during the refactor. |
| **OPEN_QUESTIONS.md** | Live decisions log at `.claude/refactor/OPEN_QUESTIONS.md`. |
| **CONTRACTS.md** | API shapes at `.claude/refactor/CONTRACTS.md`. |
| **MODULE_SPLIT.md** | Per-node concern layout at `.claude/refactor/MODULE_SPLIT.md`. |
| **WIKI_TEMPLATE.md** | 14-section per-node wiki template at `.claude/refactor/WIKI_TEMPLATE.md`. |
| **AUTOGEN block** | Sections of a wiki regenerated by `npm run wiki:all` — do not hand-edit between markers. |
## Related pages
- [Home](Home)
- [Architecture](Architecture)
- [Topic-Conventions](Topic-Conventions)
- [Topology-Patterns](Topology-Patterns)

View File

@@ -1,8 +1,9 @@
# EVOLV — Wastewater treatment plant automation # EVOLV — Wastewater Treatment Plant Automation
> **Reflects code as of `afc304b` · regenerated `2026-05-11` via `npm run wiki:home`** > **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`**
> Source of truth: `nodes/<name>/src/specificClass.js` `configure()` declarations. Edges below were verified against `router.onRegister(...)` calls and emitter subscriptions.
EVOLV is a Node-RED node library for wastewater plant automation, developed by the R&D team at Waterschap Brabantse Delta. Nodes follow the ISA-88 (S88) batch control standard. The library exposes 11 active nodes spanning four S88 levels — from Process Cell down to Control Module — plus one utility node for dashboard integration. EVOLV is a Node-RED node library for wastewater plant automation, developed by Waterschap Brabantse Delta's R&D team. Nodes follow ISA-88 (S88). The library exposes **11 active nodes** across four S88 levels plus **1 utility node** for Grafana dashboard provisioning, all built on a shared `generalFunctions` library.
## Platform overview ## Platform overview
@@ -24,64 +25,85 @@ flowchart TB
diff[diffuser]:::equip diff[diffuser]:::equip
end end
subgraph CM["Control Module"] subgraph CM["Control Module"]
meas[measurement]:::ctrl meas["measurement<br/><i>registers with any process node</i>"]:::ctrl
end end
subgraph UT["Utility"] subgraph UT["Utility"]
dash[dashboardAPI]:::neutral dash["dashboardAPI<br/><i>any node → Grafana dashboard</i>"]:::util
end end
ps --> mgc
ps --> vgc ps -->|owns| mgc
mgc --> rm ps -.->|direct child, no group| rm
vgc --> v mgc -->|load-shares| rm
reactor --> diff vgc -->|positions| v
meas -.data.-> rm settler -->|return pump| rm
meas -.data.-> v
meas -.data.-> reactor reactor ==stateChange==> settler
meas -.data.-> settler diff -. OTR data .-> reactor
classDef pc fill:#0c99d9,color:#fff classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000 classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000 classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000 classDef ctrl fill:#a9daee,color:#000
classDef neutral fill:#dddddd,color:#000 classDef util fill:#dddddd,color:#000
``` ```
S88 colours: Process Cell `#0c99d9`, Unit `#50a8d9`, Equipment `#86bbdd`, Control Module `#a9daee`. Solid arrow = parent/child relationship. Dashed arrow = data flow (`measurement` feeds many node types). Source of truth: `.claude/rules/node-red-flow-layout.md` §14. **Edges in this diagram are ground-truth** — every solid arrow is a `router.onRegister(softwareType, …)` declaration in the parent's `configure()`. Dashed arrows are emitter subscriptions (not child registrations). For full data-flow including `measurement` fan-out to every process node and `valveGroupControl`'s flow-source registrations, see **[Topology-Patterns](Topology-Patterns)**.
## Live nodes ## Live nodes
| S88 | Node | One-liner | Wiki | | S88 level | Node | One-liner | Per-node wiki |
|---|---|---|---| |---|---|---|---|
| Process Cell | **pumpingStation** | Manages a wet-well basin, hands demand to one or more group controllers. | [](https://gitea.wbd-rd.nl/RnD/pumpingStation/wiki/Home) | | 🟦 Process Cell | **pumpingStation** | Wet-well basin model; dispatches demand to one or more pump groups. | [Home ](https://gitea.wbd-rd.nl/RnD/pumpingStation/wiki/Home) |
| Unit | **machineGroupControl** | Load-sharing across a group of rotatingMachines. | [](https://gitea.wbd-rd.nl/RnD/machineGroupControl/wiki/Home) | | 🔷 Unit | **machineGroupControl** | Load-sharing across a group of `rotatingMachine` children. | [Home ](https://gitea.wbd-rd.nl/RnD/machineGroupControl/wiki/Home) |
| Unit | **valveGroupControl** | Coordinated valve control across a group of valves. | [](https://gitea.wbd-rd.nl/RnD/valveGroupControl/wiki/Home) | | 🔷 Unit | **valveGroupControl** | Coordinated position control across a group of `valve` children; can register pump/PS/MGC nodes as flow sources. | [Home ](https://gitea.wbd-rd.nl/RnD/valveGroupControl/wiki/Home) |
| Unit | **reactor** | Bioreactor — couples diffuser + measurements + ASM kinetics. | [](https://gitea.wbd-rd.nl/RnD/reactor/wiki/Home) | | 🔷 Unit | **reactor** | Bioreactor — ASM kinetics (CSTR/PFR engines); pairs with diffuser + downstream settler. | [Home ](https://gitea.wbd-rd.nl/RnD/reactor/wiki/Home) |
| Unit | **settler** | Settler / clarifier modelling. | [](https://gitea.wbd-rd.nl/RnD/settler/wiki/Home) | | 🔷 Unit | **settler** | Secondary clarifier; subscribes to upstream reactor stateChange, drives a return-pump. | [Home ](https://gitea.wbd-rd.nl/RnD/settler/wiki/Home) |
| Unit | **monster** | Composite-sample sensor surrogate / multi-parameter monitor. | [](https://gitea.wbd-rd.nl/RnD/monster/wiki/Home) | | 🔷 Unit | **monster** | Composite-sample sensor surrogate / proportional sampling program. | [Home ](https://gitea.wbd-rd.nl/RnD/monster/wiki/Home) |
| Equipment | **rotatingMachine** | Single pump / compressor — curves, state machine, prediction. | [](https://gitea.wbd-rd.nl/RnD/rotatingMachine/wiki/Home) | | 🟦 Equipment | **rotatingMachine** | Single pump / compressor — characteristic curves, prediction, FSM. | [Home ](https://gitea.wbd-rd.nl/RnD/rotatingMachine/wiki/Home) |
| Equipment | **valve** | Single valve actuator with FSM. | [](https://gitea.wbd-rd.nl/RnD/valve/wiki/Home) | | 🟦 Equipment | **valve** | Single valve actuator with FSM (shared with rotatingMachine state model). | [Home ](https://gitea.wbd-rd.nl/RnD/valve/wiki/Home) |
| Equipment | **diffuser** | Aeration diffuser, gas-side modelling. | [](https://gitea.wbd-rd.nl/RnD/diffuser/wiki/Home) | | 🟦 Equipment | **diffuser** | Aeration diffuser; gas-side modelling, OTR emission to reactor. | [Home ](https://gitea.wbd-rd.nl/RnD/diffuser/wiki/Home) |
| Control Module | **measurement** | Sensor signal-conditioning, scaling, calibration. | [](https://gitea.wbd-rd.nl/RnD/measurement/wiki/Home) | | 🔹 Control Module | **measurement** | Sensor signal-conditioning, scaling, smoothing, outlier detection, analog/digital/MQTT. | [Home ](https://gitea.wbd-rd.nl/RnD/measurement/wiki/Home) |
| Utility | **dashboardAPI** | Bridge between EVOLV nodes and Grafana dashboard upserts. | [](https://gitea.wbd-rd.nl/RnD/dashboardAPI/wiki/Home) | | Utility | **dashboardAPI** | Receives `child.register` for any process node → provisions Grafana dashboard via HTTP. | [Home ](https://gitea.wbd-rd.nl/RnD/dashboardAPI/wiki/Home) |
| — | **generalFunctions** | Shared library — `BaseDomain`, `BaseNodeAdapter`, `ChildRouter`, `commandRegistry`, `UnitPolicy`, `MeasurementContainer`, `statusBadge`, `HealthStatus`, `logger`, `configManager`. **Not a Node-RED node.** | [Home →](https://gitea.wbd-rd.nl/RnD/generalFunctions/wiki/Home) |
Plus the shared library `generalFunctions` — not a Node-RED node itself; provides `BaseDomain`, `BaseNodeAdapter`, `ChildRouter`, `UnitPolicy`, `MeasurementContainer`, command registry, logger, and config manager. ## Start here
## Standards & conventions | You want to… | Read |
|---|---|
| Stand up a local dev environment + run an example flow | [Getting-Started](Getting-Started) |
| Understand the codebase layout, BaseDomain/adapter pattern, output ports | [Architecture](Architecture) |
| See typical plant configurations and how nodes wire together | [Topology-Patterns](Topology-Patterns) |
| Know what topic names to use, units, S88 colours | [Topic-Conventions](Topic-Conventions) |
| Understand what Port 0 / Port 1 / Port 2 carry, InfluxDB layout | [Telemetry](Telemetry) |
| Decode S88 / EVOLV jargon | [Glossary](Glossary) |
| Document | What it covers | Where | ## Domain concepts
|---|---|---|
| Node architecture (3-tier) | entry → nodeClass → specificClass | `.claude/rules/node-architecture.md` |
| Flow layout (Node-RED tabs) | Tab boundaries, lanes, S88 colours, link channels | `.claude/rules/node-red-flow-layout.md` |
| Telemetry (Port 0/1/2) | InfluxDB line protocol, cardinality, FlowFuse compatibility | `.claude/rules/telemetry.md` |
| generalFunctions stability | What's safe to change in the shared lib | `.claude/rules/general-functions.md` |
| repo-mem MCP usage | When to use `repo_search` / `repo_record_fix` instead of grep | `.claude/rules/repo-mem.md` |
| Refactor goals + tiers | Why the refactor exists, sequencing | `.claude/refactor/README.md` |
| Code conventions | Style, file size, comments, naming, imports, tests | `.claude/refactor/CONVENTIONS.md` |
| Contracts | `BaseNodeAdapter`, `BaseDomain`, commands registry, child router, unit policy, status badge, output ports | `.claude/refactor/CONTRACTS.md` |
| Module split | Per-node `src/` layout for the 4 core nodes + generic template | `.claude/refactor/MODULE_SPLIT.md` |
| Wiki per-node template | The 14-section page shape | `.claude/refactor/WIKI_TEMPLATE.md` |
| Wiki home + archive | This page's template | `.claude/refactor/WIKI_HOME_TEMPLATE.md` |
## Refactor status Evergreen technical references (not affected by refactors):
| Page | Topic |
|---|---|
| [ASM models](concepts/asm-models) | Activated Sludge Models — biological process kinetics |
| [PID control theory](concepts/pid-control-theory) | Loop tuning, anti-windup, controller forms |
| [Pump affinity laws](concepts/pump-affinity-laws) | Speed/flow/head/power scaling |
| [Settling models](concepts/settling-models) | Takács / Vesilind / discrete settling |
| [Signal processing — sensors](concepts/signal-processing-sensors) | Smoothing, outlier rejection |
| [InfluxDB schema design](concepts/influxdb-schema-design) | Cardinality, tags vs fields |
| [Wastewater compliance NL](concepts/wastewater-compliance-nl) | Dutch regulatory context |
| [OT security — IEC 62443](concepts/ot-security-iec62443) | OT cybersecurity baseline |
## Operations findings
Algorithm-level proofs and behavioural notes that are still valid:
| Page | Topic |
|---|---|
| [BEP gravitation proof](findings/bep-gravitation-proof) | Best-efficiency-point convergence |
| [Curve non-convexity](findings/curve-non-convexity) | When pump curves break local optima |
| [NCog behaviour](findings/ncog-behavior) | NCog control metric notes |
| [Pump switching stability](findings/pump-switching-stability) | Hysteresis design for multi-pump groups |
## Project status
| Tier | What | Status | | Tier | What | Status |
|---|---|---| |---|---|---|
@@ -90,12 +112,14 @@ Plus the shared library `generalFunctions` — not a Node-RED node itself; provi
| 3 | Convert measurement, MGC, rotatingMachine | ✅ done | | 3 | Convert measurement, MGC, rotatingMachine | ✅ done |
| 4 | Convert valve, VGC, reactor, settler, monster, diffuser, dashboardAPI | ✅ done | | 4 | Convert valve, VGC, reactor, settler, monster, diffuser, dashboardAPI | ✅ done |
| 5 | Canonical topic names + alias deprecation map | ✅ done | | 5 | Canonical topic names + alias deprecation map | ✅ done |
| 6 | Promote `development``main` | ⏳ pending Docker E2E sign-off + human review | | 6 | Promote `development``main` | ⏳ pending Docker E2E + human review |
| 8.5 | Remove deprecated paths in `generalFunctions` | ✅ done | | 8.5 | Remove deprecated paths in `generalFunctions` | ✅ done |
| 9 | Wiki cleanup — visual-first template + per-node Home pages | 🟡 in progress (per-node rewrites landing 2026-05-11; parent-repo wiki audit this wave) | | 9 | Wiki refactor — visual-first per-node + master pages | ✅ landed 2026-05-11 |
| 10 | Test-suite refactor across all nodes | in progress | | 10 | Test-suite refactor across all nodes | 🟡 in progress |
| — | pumpingStation Docker E2E (P2.14) | ⏳ pending | | — | pumpingStation Docker E2E (P2.14) | ⏳ pending |
823 platform tests pass · 0 failures · 12 submodules + parent on `development`.
## Archive ## Archive
Pre-refactor pages live under `Archive/`. See [Archive index](Archive). Pre-refactor planning pages have been moved to the [Archive](Archive). The current Home and supporting pages are the canonical references.

202
wiki/Telemetry.md Normal file
View File

@@ -0,0 +1,202 @@
# Telemetry
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`**
What every EVOLV node emits on each of its three output ports, the InfluxDB line-protocol layout, and how the data reaches Grafana/FlowFuse.
## Three-port model
```mermaid
flowchart LR
sc[specificClass<br/>tick or event]:::tier3
nc[nodeClass<br/>outputUtils.formatMsg]:::tier2
p0[(Port 0<br/>process)]:::p0
p1[(Port 1<br/>InfluxDB line)]:::p1
p2[(Port 2<br/>registration)]:::p2
sc -->|getOutput| nc
nc --> p0
nc --> p1
nc --> p2
p0 -. delta-compressed payload .-> dl[Downstream<br/>Node-RED logic]:::neutral
p1 -. line protocol .-> influx[(InfluxDB)]:::ext
p2 -. child.register .-> parent[Parent EVOLV node]:::neutral
classDef tier3 fill:#50a8d9,color:#000
classDef tier2 fill:#86bbdd,color:#000
classDef p0 fill:#86bbdd
classDef p1 fill:#a9daee
classDef p2 fill:#dddddd
classDef neutral fill:#dddddd
classDef ext fill:#fff2cc
```
## Port 0 — Process data (delta-compressed)
**Purpose:** feeds downstream Node-RED logic — dashboards, control functions, alarms.
**Shape:** `msg.payload` is an object containing **only keys that changed** since the last tick. Consumers cache + merge.
**Why delta-compressed:** at 1 Hz ticks with 50 fields per node, full snapshots flood downstream nodes and dashboards. Delta payloads typically carry 05 fields per tick.
**Example (rotatingMachine, 1 tick):**
```json
{
"topic": "rotatingMachine#pump-A",
"payload": {
"flow.predicted.downstream.default": 12.4,
"predictionConfidence": 0.87
}
}
```
Most other fields (state, pressure, mode, …) didn't change this tick — omitted.
**Trigger:** `outputUtils.checkForChanges()` compares the current `getOutput()` against the previous snapshot.
## Port 1 — Telemetry (InfluxDB line protocol)
**Purpose:** time-series storage in InfluxDB for trending, regulatory reporting, ML training.
**Shape:** `msg.payload` is a **string** (or array of strings) in InfluxDB line protocol:
```
<measurement>,<tag-set> <field-set> <timestamp-ns>
```
**Example:**
```
rotatingMachine,id=pump-A,softwareType=rotatingMachine flow_predicted_downstream=12.4,power_measured_atequipment=18.2 1714752000000000000
```
**Conventions:**
| Element | Rule |
|---|---|
| measurement (table) | The node's `softwareType` (lowercase). |
| tag-set | Low-cardinality identity: `id`, `softwareType`, location-style tags. **Never** raw measurement values. |
| field-set | Numeric values only. Keys flatten `<type>_<variant>_<position>` (underscore, not dot — InfluxDB constraint). |
| timestamp | Nanoseconds. Set by `outputUtils` from the node's clock. |
See [InfluxDB schema design](concepts/influxdb-schema-design) for the cardinality discipline.
## Port 2 — Registration / control
**Purpose:** upward `child.register` at startup; later, internal control msgs (the registry-driven command replies).
**Shape:**
```json
{
"topic": "child.register",
"payload": {
"ref": <node reference>,
"softwareType": "machine",
"config": { ... }
}
}
```
**Trigger:** the nodeClass adapter emits `child.register` on `init` if a `parent` is configured. The parent's `commandRegistry` dispatches into `ChildRouter.onRegister(...)`.
## The output composition pipeline
```mermaid
sequenceDiagram
participant tick as Tick (1 Hz)
participant sc as specificClass
participant mc as MeasurementContainer
participant ou as outputUtils
participant ports as Ports 0 / 1
tick->>sc: tick()
sc->>sc: concern modules update mc + state
sc->>ou: getOutput() snapshot
ou->>ou: diff vs last
alt no change
ou-->>sc: skip
else change
ou->>ports: Port 0 — JSON delta
ou->>ports: Port 1 — line protocol
end
```
`outputUtils` is the single place the platform serialises state. Never write directly to `node.send` from specificClass — go through `outputUtils.formatMsg`.
## InfluxDB layout
Recommended schema for EVOLV's data:
| InfluxDB element | Maps to |
|---|---|
| Database / bucket | One per plant (or per environment: `evolv_dev`, `evolv_prod`). |
| Measurement (table) | Node softwareType (`rotatingMachine`, `pumpingStation`, …). |
| Tags | `id` (instance id), `softwareType`, `area`, `processCell`, `unit` (for hierarchical drill-down). |
| Fields | Numeric series — every key from `getOutput()` that has a numeric value, flattened with `_`. |
| Retention | Hot bucket: 7 days @ 1 s. Cold bucket: 1 year @ 1 min downsample. |
**Cardinality discipline:** keep tag sets stable. Don't put `state` (string) as a tag — emit it as a field with code (`state_code=2`). High-cardinality tags fragment the index.
## FlowFuse dashboard wiring
If you use FlowFuse `ui-chart` widgets, Port 0 is the natural source — the delta-compressed JSON maps cleanly to `msg.topic` (series label) + `msg.payload` (y-value).
Layout rule for charts (from `.claude/rules/node-red-flow-layout.md` §4):
- One chart per metric type (one for flow, one for power, one for level).
- A trend-feeder function splits Port-0 deltas into per-series outputs, each output wired to one chart.
- The chart's `category: "topic"` + `categoryType: "msg"` plots one series per unique `msg.topic`.
```mermaid
flowchart LR
p0[(Port 0)]:::p0
split[trend-feeder<br/>function (N outputs)]:::tier2
chart1[ui-chart: flow]:::neutral
chart2[ui-chart: power]:::neutral
p0 --> split
split --> chart1
split --> chart2
classDef p0 fill:#86bbdd
classDef tier2 fill:#86bbdd,color:#000
classDef neutral fill:#dddddd
```
## Grafana dashboard provisioning
`dashboardAPI` consumes registrations and emits Grafana dashboard JSON via HTTP. Wiring:
```mermaid
flowchart LR
evolv[EVOLV node<br/>any softwareType]:::tier3
dash[dashboardAPI]:::util
grafana[(Grafana HTTP API<br/>POST /api/dashboards/db)]:::ext
evolv -->|child.register| dash
dash -->|composed JSON| grafana
classDef tier3 fill:#50a8d9,color:#000
classDef util fill:#dddddd
classDef ext fill:#fff2cc
```
dashboardAPI looks up a template per softwareType (in `nodes/dashboardAPI/src/config/templates/`), substitutes the node's id + tags, and POSTs an upsert. Bearer-token auth is supported.
## Common debug recipes
| Symptom | First check |
|---|---|
| InfluxDB rows missing for a node | Confirm Port 1 is wired to an `influxdb out` node. Tap Port 1 with a debug node to verify line-protocol output. |
| Dashboard widgets stuck on `n/a` | Confirm Port 0 is reaching the trend-feeder. Many widgets need `msg.topic` set for series labelling. |
| `child.register` not arriving at parent | Tap Port 2 with debug. Confirm parent's `commandRegistry` accepts `child.register` (or the legacy `registerChild` alias). |
| Too many InfluxDB writes (high write-rate) | Check that `outputUtils.checkForChanges()` is firing. Likely you wired a tick-driven debug node bypassing the delta filter. |
| Grafana dashboard not created on plant boot | Inspect dashboardAPI's HTTP response. Check the bearer token + base URL in its config. |
## Related pages
- [Architecture](Architecture) — output port wiring in the 3-tier code
- [Topic-Conventions](Topic-Conventions) — what topics map to what fields
- [InfluxDB schema design](concepts/influxdb-schema-design) — cardinality discipline

185
wiki/Topic-Conventions.md Normal file
View File

@@ -0,0 +1,185 @@
# Topic Conventions
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`**
Naming rules, unit policy, and S88 colour palette. Source of truth: `.claude/refactor/CONTRACTS.md` §1.
## Topic prefixes
Every topic is `<prefix>.<verb>` lowercase. Five prefixes only.
```mermaid
flowchart LR
ui[UI / parent / driver]:::neutral
node[Node]:::tier3
child[Child]:::tier1
ui -->|set.x / cmd.x| node
node -->|evt.x| ui
child -->|data.x| node
node -->|data.x| child
child -->|child.register| node
classDef neutral fill:#dddddd
classDef tier3 fill:#50a8d9,color:#000
classDef tier1 fill:#a9daee,color:#000
```
| Prefix | Direction | Semantics | Examples |
|---|---|---|---|
| `set.` | inbound | Set a configurable value. **Idempotent**, no side-effects beyond storing the value. | `set.mode`, `set.demand`, `set.position` |
| `cmd.` | inbound | Trigger an action. **Has side-effects** (state transitions, motor commands). | `cmd.startup`, `cmd.shutdown`, `cmd.calibrate`, `cmd.estop` |
| `data.` | bidirectional | Carries measurement / process data. Used by `measurement → parent` and emitters. | `data.pressure`, `data.flow`, `data.temperature` |
| `evt.` | outbound | Announces something happened. Consumer-driven. | `evt.state-change`, `evt.alarm`, `evt.health` |
| `child.` | inbound (parent) | Child node lifecycle. | `child.register` (with legacy alias `registerChild`) |
**Anti-patterns to avoid:**
- ❌ A topic that does two things (`setStartup` to both set a flag *and* trigger startup). Split into `set.` + `cmd.`.
- ❌ Reusing a `cmd.` topic for both inbound trigger and outbound ack — make a paired `evt.<verb>-complete`.
- ❌ Per-node prefixes (`pump.set.demand`). The prefix is the *kind*, not the *target*.
## Alias deprecation
Legacy topic names (pre-refactor) are still accepted as aliases. The current alias map per node lives in `src/commands/index.js`. Common aliases:
| Canonical | Legacy aliases |
|---|---|
| `set.mode` | `setMode` |
| `set.demand` | `Qd`, `setDemand` |
| `cmd.startup` | `execSequence` with `payload.action='startup'` |
| `cmd.shutdown` | `execSequence` with `payload.action='shutdown'` |
| `child.register` | `registerChild` |
| `data.pressure` | `pressure` |
| `data.flow` | `flow` |
Aliases are logged at debug level on use. Plan is to remove them in a future major version. Update integrations to canonical names.
## Unit policy
Every node declares canonical + output units via `UnitPolicy.declare({canonical, output})`. The command registry coerces incoming `msg.unit` to the canonical unit before the handler runs. Outputs are emitted in the declared output unit (often human-friendly).
```mermaid
flowchart LR
ui[UI message<br/>e.g. 50 m³/h]:::neutral
coerce[unit coercion<br/>m³/h → m³/s]:::tier1
sc[specificClass<br/>canonical m³/s]:::tier3
out[output<br/>renders back to m³/h]:::tier2
ui --> coerce --> sc --> out
classDef neutral fill:#dddddd
classDef tier1 fill:#a9daee,color:#000
classDef tier3 fill:#50a8d9,color:#000
classDef tier2 fill:#86bbdd,color:#000
```
| Quantity | Canonical (internal) | Common output |
|---|---|---|
| Flow | `m3/s` | `m3/h`, `l/s`, `gpm` |
| Pressure | `Pa` | `bar`, `mbar`, `kPa` |
| Power | `W` | `kW`, `MW` |
| Temperature | `K` | `degC`, `degF` |
| Level | `m` | `m`, `cm` |
| Volume | `m3` | `m3`, `l` |
**Rule:** anywhere in `specificClass`, treat values as canonical. Conversion happens at the boundary (input coercion + output formatting).
## S88 colour palette
```mermaid
flowchart TB
A[Area<br/>#0f52a5]:::area
PC[Process Cell<br/>#0c99d9]:::pc
UN[Unit<br/>#50a8d9]:::unit
EM[Equipment Module<br/>#86bbdd]:::equip
CM[Control Module<br/>#a9daee]:::ctrl
UT[Utility / neutral<br/>#dddddd]:::neutral
A --> PC --> UN --> EM --> CM
UT -.- A
classDef area fill:#0f52a5,color:#fff
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
classDef neutral fill:#dddddd,color:#000
```
| Hex | S88 level | Used by |
|---|---|---|
| `#0f52a5` | Area | (reserved — not in use yet) |
| `#0c99d9` | Process Cell | pumpingStation |
| `#50a8d9` | Unit | machineGroupControl, valveGroupControl, reactor, settler, monster |
| `#86bbdd` | Equipment Module | rotatingMachine, valve, diffuser |
| `#a9daee` | Control Module | measurement |
| `#dddddd` | Utility / neutral | dashboardAPI, helper function nodes |
**Rule:** every Mermaid diagram in this wiki, every Node-RED node's editor colour, and every dashboard grouping uses this palette. Source of truth: `.claude/rules/node-red-flow-layout.md` §14.
**Known outliers** (pending cleanup, tracked in OPEN_QUESTIONS.md):
- `settler` editor colour is `#e4a363` (orange) — should be `#50a8d9`.
- `monster` editor colour is `#4f8582` (teal) — should be `#50a8d9`.
- `diffuser` editor colour was missing pre-refactor; now `#86bbdd`.
- `dashboardAPI` registers under category `'wbd typical'` instead of `'EVOLV'`.
## Measurement key shape
The `MeasurementContainer` stores values under composite keys:
```
<type>.<variant>.<position>.<childId>
```
| Segment | Examples |
|---|---|
| `type` | `flow`, `pressure`, `power`, `temperature`, `level` |
| `variant` | `measured`, `predicted`, `setpoint`, `min`, `max` |
| `position` | `upstream`, `downstream`, `atequipment`, `inlet`, `outlet` (always lowercase in keys) |
| `childId` | The registering child's id, OR `default` for internal computations |
Examples:
- `flow.measured.downstream.dashboard-sim-downstream` — externally measured downstream flow.
- `flow.predicted.downstream.default` — node's own prediction.
- `power.measured.atequipment.default` — measured power at the equipment.
- `pressure.measured.upstream.<childId>` — pressure from a specific measurement child.
**Gotcha:** `position` is **always lowercase in keys**. The configuration form may use mixed case (`atEquipment`); the container normalises.
## Status badge
`statusBadge.compose(state)` returns `{fill, shape, text}` for `node.status(...)`:
| level | shape | fill | meaning |
|---|---|---|---|
| `info` | dot | blue | normal operation |
| `success` | dot | green | success / running optimally |
| `warning` | ring | yellow | degraded, attention needed |
| `error` | ring | red | fault, operator action required |
| `pending` | dot | grey | initialising / no data yet |
Composer reads from `HealthStatus.level` (03) — kept centralised so all nodes show consistent badges.
## HealthStatus shape
```json
{
"level": 0,
"flags": ["pressure_init_warming"],
"message": "warmup phase",
"source": "rotatingMachine#pump-A"
}
```
| Field | Range | Meaning |
|---|---|---|
| `level` | 0..3 | 0 = healthy, 1 = degraded, 2 = warning, 3 = error |
| `flags` | string[] | Machine-readable reason codes. |
| `message` | string | Human-readable summary (one line). |
| `source` | string | `<nodeType>#<id>` — for routing UI / alarm correlation. |
## Related pages
- [Architecture](Architecture) — generalFunctions API surface
- [Telemetry](Telemetry) — Port-1 InfluxDB schema (where these conventions appear in stored data)
- [Topology-Patterns](Topology-Patterns) — what topics flow where

248
wiki/Topology-Patterns.md Normal file
View File

@@ -0,0 +1,248 @@
# Topology Patterns
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`**
Typical plant configurations and how nodes wire together. Each pattern is **verified** against the corresponding nodes' `configure()` declarations.
## Pattern 1 — Pumping station with grouped pumps
The canonical wet-well lift station. One basin, one demand controller (`pumpingStation`), one load-sharing coordinator (`machineGroupControl`), N pumps. Level + flow measurements feed the basin model.
```mermaid
flowchart TB
subgraph PC["Process Cell"]
ps[pumpingStation]:::pc
end
subgraph UN["Unit"]
mgc[machineGroupControl]:::unit
end
subgraph EM["Equipment"]
rmA[rotatingMachine A]:::equip
rmB[rotatingMachine B]:::equip
rmC[rotatingMachine C]:::equip
end
subgraph CM["Control Module"]
ml[measurement: level]:::ctrl
mfin[measurement: inflow]:::ctrl
mpA[measurement: pressure A]:::ctrl
mpB[measurement: pressure B]:::ctrl
mpC[measurement: pressure C]:::ctrl
end
ps --> mgc
mgc --> rmA
mgc --> rmB
mgc --> rmC
ml -. data .-> ps
mfin -. data .-> ps
mpA -. data .-> rmA
mpB -. data .-> rmB
mpC -. data .-> rmC
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
```
**Data flow:**
- `pumpingStation` computes basin volume + level dynamics from inflow/outflow measurements.
- `pumpingStation` emits a demand setpoint downstream to `machineGroupControl` on its Port 0 or via `set.demand`.
- `machineGroupControl` solves a per-pump operating point using each pump's characteristic curve + measured pressure, sends `set.setpoint` to each `rotatingMachine`.
- Each `rotatingMachine` runs its own FSM (idle/warmingup/operational/coolingdown/emergencystop) and predicts flow/power from pressure + speed.
**Notes:**
- For a single-pump station, `pumpingStation` can register `rotatingMachine` directly (skip the MGC) — `pumpingStation`'s `configure()` accepts `machine` as a child softwareType.
- For two stations in series, the downstream PS can register the upstream PS as a `pumpingstation` softwareType source.
## Pattern 2 — Reactor / settler train with aeration
Biological treatment line. Reactor runs ASM kinetics, diffuser drives O₂ transfer, settler clarifies effluent and returns sludge via a return pump.
```mermaid
flowchart TB
subgraph UN["Unit"]
reactor[reactor]:::unit
settler[settler]:::unit
end
subgraph EM["Equipment"]
diff[diffuser]:::equip
rp[rotatingMachine<br/>return pump]:::equip
end
subgraph CM["Control Module"]
mt[measurement: temperature]:::ctrl
mdo[measurement: dissolved O₂]:::ctrl
mts[measurement: TSS]:::ctrl
end
reactor ==stateChange==> settler
diff -. OTR data .-> reactor
settler -->|return pump child| rp
mt -. data .-> reactor
mdo -. data .-> reactor
mts -. data .-> settler
mdo -. data .-> diff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
```
**Data flow:**
- `reactor.configure()` registers `measurement` (temperature, DO) and upstream `reactor` (for chained tanks).
- `diffuser` emits `data.otr` on its emitter; reactor subscribes via `emitter.on('otr', …)`**not** a child registration, just a data subscription.
- `reactor` emits `stateChange` after every kinetics step. `settler._connectReactor` subscribes via `emitter.on('stateChange', …)` and pulls effluent composition.
- `settler.configure()` accepts `reactor` (the upstream), `machine` (return pump), and `measurement` children.
**Notes:**
- Reactor supports two kinetics engines: CSTR (continuous-stirred tank) and PFR (plug-flow). Set via `config.reactor_type`.
- DO setpoint feedback (DO measurement → diffuser airflow) is not wired automatically — connect via a small control function or use a `valveGroupControl` upstream of an airflow valve.
## Pattern 3 — Valve group on a distribution manifold
Multi-valve flow distribution. VGC computes per-valve K_v shares to satisfy a target distribution while respecting upstream flow availability.
```mermaid
flowchart TB
subgraph PC["Process Cell"]
ps[pumpingStation<br/>upstream flow source]:::pc
end
subgraph UN["Unit"]
vgc[valveGroupControl]:::unit
end
subgraph EM["Equipment"]
vA[valve A]:::equip
vB[valve B]:::equip
vC[valve C]:::equip
end
ps -. flow source .-> vgc
vgc --> vA
vgc --> vB
vgc --> vC
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
```
**Important detail:** `valveGroupControl.configure()` registers four extra softwareTypes — `machine`, `machinegroup`, `pumpingstation`, `valvegroupcontrol`**not as S88 children** but as **flow sources**. VGC uses them to read upstream flow availability when computing per-valve splits. The arrow above is `child.register` from pumpingStation to vgc; the semantic relationship is "VGC knows about this upstream flow producer", not "VGC controls pumpingStation".
## Pattern 4 — Composite sampling
`monster` runs a proportional sampling program — accumulates samples in a bucket based on integrated flow. Used as a virtual sensor for downstream lab analysis.
```mermaid
flowchart TB
subgraph UN["Unit"]
monster[monster]:::unit
end
subgraph CM["Control Module"]
mflow[measurement: flow<br/>assetType MUST be 'flow']:::ctrl
mq[measurement: any quality<br/>e.g. NH₄, COD]:::ctrl
end
mflow -. data .-> monster
mq -. data .-> monster
classDef unit fill:#50a8d9,color:#000
classDef ctrl fill:#a9daee,color:#000
```
**Gotchas:**
- `measurement.config.asset.type` MUST be `"flow"` exactly — `"flow-electromagnetic"` or any sub-type is silently ignored by monster's child router.
- `monster.config.constraints.flowmeter` exists in the schema but is **not forwarded** by `buildDomainConfig` — toggling proportional-vs-time mode has no effect at runtime. (Tracked in OPEN_QUESTIONS.md.)
## Pattern 5 — Dashboard provisioning
`dashboardAPI` doesn't operate on data — it generates Grafana dashboards. Any node can register with `dashboardAPI` via `child.register`; dashboardAPI then composes a dashboard JSON from the node's softwareType + measurements and POSTs to Grafana's HTTP API.
```mermaid
flowchart LR
subgraph EVOLV["EVOLV process nodes"]
ps[pumpingStation]:::pc
mgc[machineGroupControl]:::unit
rm[rotatingMachine]:::equip
end
subgraph UT["Utility"]
dash[dashboardAPI]:::util
end
grafana[(Grafana<br/>HTTP API)]:::ext
ps -. child.register .-> dash
mgc -. child.register .-> dash
rm -. child.register .-> dash
dash -->|POST /api/dashboards/db| grafana
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef util fill:#dddddd,color:#000
classDef ext fill:#fff2cc,color:#000
```
**Notes:**
- `dashboardAPI` is the one node in the platform that doesn't extend `BaseDomain` (it's a passive HTTP bridge — see OPEN_QUESTIONS.md for the deferral decision).
- The `meta` field of dashboardAPI's outbound msg carries `{nodeId, softwareType, uid, title}` for correlating responses.
## Putting it all together — example plant
A small WWTP combining all patterns:
```mermaid
flowchart TB
subgraph PC["Process Cell"]
ps1[pumpingStation<br/>inlet lift]:::pc
ps2[pumpingStation<br/>RAS pumping]:::pc
end
subgraph UN["Unit"]
mgc1[MGC inlet]:::unit
mgc2[MGC RAS]:::unit
vgc[VGC effluent split]:::unit
r1[reactor aerobic]:::unit
s1[settler]:::unit
mon[monster<br/>composite sampler]:::unit
end
subgraph EM["Equipment"]
rm1[pump A]:::equip
rm2[pump B]:::equip
rm3[RAS pump]:::equip
d1[diffuser]:::equip
v1[valve 1]:::equip
v2[valve 2]:::equip
end
ps1 --> mgc1
mgc1 --> rm1
mgc1 --> rm2
ps2 --> mgc2
mgc2 --> rm3
r1 ==stateChange==> s1
s1 -->|return pump| rm3
d1 -. OTR .-> r1
ps2 -. flow source .-> vgc
vgc --> v1
vgc --> v2
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
```
This is the kind of diagram each `wiki/Home.md` per node should be able to fit into — every edge is reproducible from a `configure()` declaration.
## Anti-patterns
-`pumpingStation → valveGroupControl` as a parent/child edge. PS does not register VGC. VGC registers PS as a *flow source*; the edge goes the other way semantically.
- ❌ A `diffuser → reactor` child registration. Diffuser emits OTR via its emitter; reactor subscribes. No `child.register` handshake.
-`measurement` parented under `dashboardAPI`. dashboardAPI accepts any node for Grafana provisioning, but `measurement` registers with the **process** node it's monitoring, not with dashboardAPI.
## Related pages
- [Home](Home) — top-level node map
- [Architecture](Architecture) — 3-tier code structure + generalFunctions API
- [Topic-Conventions](Topic-Conventions) — what topics flow between nodes

45
wiki/_Sidebar.md Normal file
View File

@@ -0,0 +1,45 @@
### EVOLV Wiki
**Start here**
- [Home](Home)
- [Getting Started](Getting-Started)
**Reference**
- [Architecture](Architecture)
- [Topology Patterns](Topology-Patterns)
- [Topic Conventions](Topic-Conventions)
- [Telemetry](Telemetry)
- [Glossary](Glossary)
**Per-node wikis**
- [pumpingStation](https://gitea.wbd-rd.nl/RnD/pumpingStation/wiki/Home)
- [machineGroupControl](https://gitea.wbd-rd.nl/RnD/machineGroupControl/wiki/Home)
- [valveGroupControl](https://gitea.wbd-rd.nl/RnD/valveGroupControl/wiki/Home)
- [reactor](https://gitea.wbd-rd.nl/RnD/reactor/wiki/Home)
- [settler](https://gitea.wbd-rd.nl/RnD/settler/wiki/Home)
- [monster](https://gitea.wbd-rd.nl/RnD/monster/wiki/Home)
- [rotatingMachine](https://gitea.wbd-rd.nl/RnD/rotatingMachine/wiki/Home)
- [valve](https://gitea.wbd-rd.nl/RnD/valve/wiki/Home)
- [diffuser](https://gitea.wbd-rd.nl/RnD/diffuser/wiki/Home)
- [measurement](https://gitea.wbd-rd.nl/RnD/measurement/wiki/Home)
- [dashboardAPI](https://gitea.wbd-rd.nl/RnD/dashboardAPI/wiki/Home)
- [generalFunctions](https://gitea.wbd-rd.nl/RnD/generalFunctions/wiki/Home)
**Concepts** (domain knowledge)
- [ASM models](Concept-ASM-Models)
- [PID control theory](Concept-PID-Control-Theory)
- [Pump affinity laws](Concept-Pump-Affinity-Laws)
- [Settling models](Concept-Settling-Models)
- [Signal processing](Concept-Signal-Processing-Sensors)
- [InfluxDB schema](Concept-InfluxDB-Schema-Design)
- [Compliance NL](Concept-Wastewater-Compliance-NL)
- [OT security](Concept-OT-Security-IEC62443)
**Findings** (algorithm proofs)
- [BEP gravitation](Finding-BEP-Gravitation-Proof)
- [Curve non-convexity](Finding-Curve-Non-Convexity)
- [NCog behaviour](Finding-NCog-Behavior)
- [Pump switching stability](Finding-Pump-Switching-Stability)
**Archive**
- [Archive index](Archive)