diff --git a/wiki/Architecture.md b/wiki/Architecture.md new file mode 100644 index 0000000..769dfbb --- /dev/null +++ b/wiki/Architecture.md @@ -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[".js
(entry — registers node type with RED)"]:::tier1 + nc["src/NodeClass.js
(nodeClass — Node-RED adapter)"]:::tier2 + sc["src/SpecificClass.js
(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 (`.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: `...`. | +| `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/.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
process data)]:::p0 + p1[(Port 1
InfluxDB line)]:::p1 + p2[(Port 2
registration / control)]:::p2 + sc --> p0 + sc --> p1 + sc --> p2 + + p0 -.-> dn1[downstream Node-RED nodes
dashboards, function nodes] + p1 -.-> influx[(InfluxDB)] + p2 -.-> parent[parent EVOLV node
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(, 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
orchestration only"] + subgraph concerns["Concern subdirs (per-node)"] + c1[basin/ or curves/ or kinetics/
physics / math] + c2[state/
FSM transitions] + c3[dispatch/ or safety/
action / guard logic] + c4[commands/
topic → handler descriptors] + c5[io/
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 diff --git a/wiki/Getting-Started.md b/wiki/Getting-Started.md new file mode 100644 index 0000000..cbc47ec --- /dev/null +++ b/wiki/Getting-Started.md @@ -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//`. + +## 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//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
3-tier code structure]:::tier1 + topo[Topology-Patterns
typical plant configs]:::tier1 + conv[Topic-Conventions
naming + units]:::tier1 + tele[Telemetry
Port 0/1/2 + InfluxDB]:::tier1 + node[Pick a node's wiki
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 diff --git a/wiki/Glossary.md b/wiki/Glossary.md new file mode 100644 index 0000000..d3bf2c6 --- /dev/null +++ b/wiki/Glossary.md @@ -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: `...`. | +| **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 1–4** | 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) diff --git a/wiki/Home.md b/wiki/Home.md index 03dbfd7..3af0eff 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -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//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 @@ -24,64 +25,85 @@ flowchart TB diff[diffuser]:::equip end subgraph CM["Control Module"] - meas[measurement]:::ctrl + meas["measurement
registers with any process node"]:::ctrl end subgraph UT["Utility"] - dash[dashboardAPI]:::neutral + dash["dashboardAPI
any node → Grafana dashboard"]:::util end - ps --> mgc - ps --> vgc - mgc --> rm - vgc --> v - reactor --> diff - meas -.data.-> rm - meas -.data.-> v - meas -.data.-> reactor - meas -.data.-> settler + + ps -->|owns| mgc + ps -.->|direct child, no group| rm + mgc -->|load-shares| rm + vgc -->|positions| v + settler -->|return pump| rm + + reactor ==stateChange==> settler + diff -. OTR data .-> reactor + 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 + 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 -| 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) | -| Unit | **machineGroupControl** | Load-sharing across a group of rotatingMachines. | [→](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 | **reactor** | Bioreactor — couples diffuser + measurements + ASM kinetics. | [→](https://gitea.wbd-rd.nl/RnD/reactor/wiki/Home) | -| Unit | **settler** | Settler / clarifier modelling. | [→](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) | -| Equipment | **rotatingMachine** | Single pump / compressor — curves, state machine, prediction. | [→](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 | **diffuser** | Aeration diffuser, gas-side modelling. | [→](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) | -| Utility | **dashboardAPI** | Bridge between EVOLV nodes and Grafana dashboard upserts. | [→](https://gitea.wbd-rd.nl/RnD/dashboardAPI/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 `rotatingMachine` children. | [Home →](https://gitea.wbd-rd.nl/RnD/machineGroupControl/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 — ASM kinetics (CSTR/PFR engines); pairs with diffuser + downstream settler. | [Home →](https://gitea.wbd-rd.nl/RnD/reactor/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 / proportional sampling program. | [Home →](https://gitea.wbd-rd.nl/RnD/monster/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 (shared with rotatingMachine state model). | [Home →](https://gitea.wbd-rd.nl/RnD/valve/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, smoothing, outlier detection, analog/digital/MQTT. | [Home →](https://gitea.wbd-rd.nl/RnD/measurement/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 | -|---|---|---| -| 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` | +## Domain concepts -## 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 | |---|---|---| @@ -90,12 +112,14 @@ Plus the shared library `generalFunctions` — not a Node-RED node itself; provi | 3 | Convert measurement, MGC, rotatingMachine | ✅ done | | 4 | Convert valve, VGC, reactor, settler, monster, diffuser, dashboardAPI | ✅ 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 | -| 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) | -| 10 | Test-suite refactor across all nodes | ⏳ in progress | +| 9 | Wiki refactor — visual-first per-node + master pages | ✅ landed 2026-05-11 | +| 10 | Test-suite refactor across all nodes | 🟡 in progress | | — | pumpingStation Docker E2E (P2.14) | ⏳ pending | +823 platform tests pass · 0 failures · 12 submodules + parent on `development`. + ## 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. diff --git a/wiki/Telemetry.md b/wiki/Telemetry.md new file mode 100644 index 0000000..5224b04 --- /dev/null +++ b/wiki/Telemetry.md @@ -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
tick or event]:::tier3 + nc[nodeClass
outputUtils.formatMsg]:::tier2 + p0[(Port 0
process)]:::p0 + p1[(Port 1
InfluxDB line)]:::p1 + p2[(Port 2
registration)]:::p2 + + sc -->|getOutput| nc + nc --> p0 + nc --> p1 + nc --> p2 + + p0 -. delta-compressed payload .-> dl[Downstream
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 0–5 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: + +``` +, +``` + +**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 `__` (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": , + "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
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
any softwareType]:::tier3 + dash[dashboardAPI]:::util + grafana[(Grafana HTTP API
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 diff --git a/wiki/Topic-Conventions.md b/wiki/Topic-Conventions.md new file mode 100644 index 0000000..eafd1ae --- /dev/null +++ b/wiki/Topic-Conventions.md @@ -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 `.` 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.-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
e.g. 50 m³/h]:::neutral + coerce[unit coercion
m³/h → m³/s]:::tier1 + sc[specificClass
canonical m³/s]:::tier3 + out[output
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
#0f52a5]:::area + PC[Process Cell
#0c99d9]:::pc + UN[Unit
#50a8d9]:::unit + EM[Equipment Module
#86bbdd]:::equip + CM[Control Module
#a9daee]:::ctrl + UT[Utility / neutral
#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: + +``` +... +``` + +| 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.` — 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` (0–3) — 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 | `#` — 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 diff --git a/wiki/Topology-Patterns.md b/wiki/Topology-Patterns.md new file mode 100644 index 0000000..fe1275a --- /dev/null +++ b/wiki/Topology-Patterns.md @@ -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
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
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
assetType MUST be 'flow']:::ctrl + mq[measurement: any quality
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
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
inlet lift]:::pc + ps2[pumpingStation
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
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 diff --git a/wiki/_Sidebar.md b/wiki/_Sidebar.md new file mode 100644 index 0000000..2805844 --- /dev/null +++ b/wiki/_Sidebar.md @@ -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)