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:
170
wiki/Architecture.md
Normal file
170
wiki/Architecture.md
Normal 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
172
wiki/Getting-Started.md
Normal 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
132
wiki/Glossary.md
Normal 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 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)
|
||||
120
wiki/Home.md
120
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/<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
|
||||
|
||||
@@ -24,64 +25,85 @@ flowchart TB
|
||||
diff[diffuser]:::equip
|
||||
end
|
||||
subgraph CM["Control Module"]
|
||||
meas[measurement]:::ctrl
|
||||
meas["measurement<br/><i>registers with any process node</i>"]:::ctrl
|
||||
end
|
||||
subgraph UT["Utility"]
|
||||
dash[dashboardAPI]:::neutral
|
||||
dash["dashboardAPI<br/><i>any node → Grafana dashboard</i>"]:::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.
|
||||
|
||||
202
wiki/Telemetry.md
Normal file
202
wiki/Telemetry.md
Normal 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 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:
|
||||
|
||||
```
|
||||
<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
185
wiki/Topic-Conventions.md
Normal 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` (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 | `<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
248
wiki/Topology-Patterns.md
Normal 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
45
wiki/_Sidebar.md
Normal 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)
|
||||
Reference in New Issue
Block a user