wiki: crisp overhaul — no decoration emoji, all 9 master pages refactored

Source-tree mirror of EVOLV.wiki.git refactor (27a42ee on wiki.git):

- 7 master pages rewritten with clean design (Home, Architecture,
  Topology-Patterns, Topic-Conventions, Telemetry, Getting-Started,
  Glossary). Tables and Mermaid for visuals, gitea alert callouts for
  warnings, shields badges for metadata only. No emoji as decoration.
- Archive.md becomes a removal-changelog pointing readers to git
  history and to the successor pages.
- _Sidebar.md updated to navigate the new flat-name layout.
- Concept / finding / manual pages: uniform mini-header (badges +
  "reference page" callout) added without rewriting domain content.
- Every internal link now uses the flat naming that resolves on the
  live gitea wiki (Concept-ASM-Models, Finding-BEP-..., etc.).

On wiki.git: 29 Archive-* pages hard-deleted (the git history
preserves them; Archive.md documents the removal).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-05-11 22:24:51 +02:00
parent 2ccc8aea9e
commit 5ae8788fd7
33 changed files with 1491 additions and 729 deletions

View File

@@ -1,170 +1,335 @@
# Architecture # Architecture
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`** ![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue)
![source](https://img.shields.io/badge/source-CONTRACTS.md-orange)
How every EVOLV node is structured, and what the shared `generalFunctions` library provides. > [!NOTE]
> Every EVOLV node is a three-tier sandwich: the entry registers the type with Node-RED; `nodeClass` (extends `BaseNodeAdapter`) bridges runtime to domain; `specificClass` (extends `BaseDomain`) holds pure-JS domain logic with zero `RED.*` imports. Everything shared &mdash; `BaseDomain`, `BaseNodeAdapter`, `ChildRouter`, the commands registry, `UnitPolicy`, `MeasurementContainer`, `statusBadge`, `HealthStatus`, `LatestWinsGate`, `logger`, `configManager` &mdash; lives in `generalFunctions`. Source of truth: `.claude/refactor/CONTRACTS.md`.
## The 3-tier node pattern ---
```mermaid ## The three-tier pattern
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 Node-RED runtime
classDef tier2 fill:#86bbdd,color:#000 |
classDef tier3 fill:#50a8d9,color:#000 +-- entry: nodes/<name>/<NodeName>.js
| RED.nodes.registerType('<nodeName>', NodeClass)
| HTTP admin endpoints (if any)
|
| +-- nodeClass: src/<...>NodeClass.js
| | extends BaseNodeAdapter (generalFunctions)
| | static DomainClass = SpecificClass
| | static commands = [...descriptors]
| | static tickInterval = null | <ms>
| | buildDomainConfig(uiConfig, nodeId)
| |
| | +-- specificClass: src/<...>SpecificClass.js
| | | extends BaseDomain (generalFunctions)
| | | static name = '<softwareType>'
| | | static unitPolicy = UnitPolicy.declare(...)
| | | configure() <- wire routers + concern modules
| | | tick() <- opt-in time-based math
| | | getOutput() <- Port 0 / 1 snapshot
| | | getStatusBadge() <- node.status badge
``` ```
| Tier | Owns | Touches RED.* API? | Tested by | ### Tier responsibilities
|---|---|---|---|
| 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. | Tier | File path | Extends | Touches `RED.*` | Unit-testable |
|:---|:---|:---|:---|:---|
| entry | `nodes/<name>/<NodeName>.js` | (top-level) | Yes | No (smoke only) |
| nodeClass | `src/<...>NodeClass.js` | `BaseNodeAdapter` | Yes | Integration only |
| specificClass | `src/<...>SpecificClass.js` | `BaseDomain` | No (never) | Yes |
## generalFunctions — what it provides > [!CAUTION]
> The specificClass must never import Node-RED APIs. If you find `RED.*` calls outside the entry or nodeClass tier, that is a bug. Pure-JS in the specificClass is what makes unit tests possible without spinning up a Node-RED runtime.
The `nodes/generalFunctions` submodule is a plain-JS library every node depends on. Public exports (top-level `require('generalFunctions')`): Source: `.claude/refactor/CONTRACTS.md` §2 and §3.
| 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. ## generalFunctions &mdash; what the library provides
The `nodes/generalFunctions` submodule is a plain-JS library every node depends on. Public exports from `require('generalFunctions')`:
```
generalFunctions
|
+-- Bases
| BaseDomain extend in specificClass.js
| BaseNodeAdapter extend in nodeClass.js
|
+-- Wiring
| ChildRouter onRegister / onMeasurement / onPrediction
| commandRegistry topic -> descriptor map
|
+-- Data
| UnitPolicy canonical + output unit declaration
| MeasurementContainer chainable type/variant/position/childId store
| convert unit conversion (m3/s <-> m3/h, ...)
|
+-- Concurrency
| LatestWinsGate supersede-semantics mutex
|
+-- Health and status
| statusBadge node.status({fill, shape, text}) composer
| HealthStatus {level, flags, message, source}
|
+-- Utilities
logger structured (never console.log)
configManager loads configs/<name>.json
MenuManager dynamic editor dropdowns
outputUtils delta-compressed Port 0 / 1 formatting
```
### API one-liners
| Export | Contract |
|:---|:---|
| `BaseDomain` | Owns `emitter`, `config`, `logger`, `measurements`, `child`. Calls subclass `configure()` then `_init?()`. |
| `BaseNodeAdapter` | Builds merged config, instantiates `DomainClass`, emits Port-2 register, wires output strategy, wires status loop, wires input dispatcher. |
| `ChildRouter` | `.onRegister(swType, handler)` · `.onMeasurement(swType, filter, handler)` · `.onPrediction(swType, filter, handler)`. |
| `commandRegistry` | Topic + alias map to handler. Coerces `msg.unit` to descriptor `units.default`. Logs one-time deprecation per alias. |
| `UnitPolicy` | `.canonical(t)` · `.output(t)` · `.curve(t)` · `.resolve()` · `.convert()` · `.containerOptions()`. Dual access: method form or frozen property bag (`policy.canonical.flow`). |
| `MeasurementContainer` | `.type(t).variant(v).position(p).value(x, ts, unit)`. Keys: `<type>.<variant>.<position>.<childId>`. |
| `statusBadge` | `.compose([..])` · `.error(msg)` · `.idle(label)`. Returns `{fill, shape, text}`. |
| `HealthStatus` | `{level: 0..3, flags: string[], message: string, source: string \| null}`. Lower level = healthier. |
| `LatestWinsGate` | `.fire(v)` (no-wait) · `.fireAndWait(v)` (per-call result; superseded calls resolve with sentinel `{superseded: true}`). |
| `logger` | `.info` · `.warn` · `.error` · `.debug`. Named after `config.general.name`. |
| `configManager` | `buildConfig(uiConfig, baseConfig)` &mdash; validates, merges, applies defaults. |
| `outputUtils` | `formatMsg(snapshot, 'process' \| 'influxdb')` &mdash; delta-compressed; only changed fields are emitted. |
The full 34-row API surface is on the [generalFunctions wiki Home](https://gitea.wbd-rd.nl/RnD/generalFunctions/wiki/Home) under "API surface".
---
## Output ports ## Output ports
Every EVOLV node emits on three ports: Every node emits on three ports. Source: `.claude/refactor/CONTRACTS.md` §10.
```mermaid ```mermaid
flowchart LR flowchart LR
sc[specificClass]:::tier3 sc["specificClass &mdash; tick() or 'output-changed'"]
p0[(Port 0<br/>process data)]:::p0 ou["outputUtils.formatMsg &mdash; delta-compress"]
p1[(Port 1<br/>InfluxDB line)]:::p1 p0[("Port 0 &mdash; process")]
p2[(Port 2<br/>registration / control)]:::p2 p1[("Port 1 &mdash; InfluxDB line")]
sc --> p0 p2[("Port 2 &mdash; register / control")]
sc --> p1 dl["downstream Node-RED &mdash; dashboards, functions"]
sc --> p2 influx[("InfluxDB")]
parent["parent EVOLV node"]
p0 -.-> dn1[downstream Node-RED nodes<br/>dashboards, function nodes] sc -- getOutput() --> ou
p1 -.-> influx[(InfluxDB)] ou --> p0 --> dl
p2 -.-> parent[parent EVOLV node<br/>via child.register] ou --> p1 --> influx
sc -. child.register .-> p2 --> parent
class sc tier3
class ou tier2
class p0 p0c
class p1 p1c
class p2 p2c
class dl,parent dn
class influx ext
classDef tier3 fill:#50a8d9,color:#000 classDef tier3 fill:#50a8d9,color:#000
classDef p0 fill:#86bbdd classDef tier2 fill:#86bbdd,color:#000
classDef p1 fill:#a9daee classDef p0c fill:#0c99d9,color:#fff
classDef p2 fill:#dddddd classDef p1c fill:#50a8d9,color:#000
classDef p2c fill:#a9daee,color:#000
classDef dn fill:#dddddd,color:#000
classDef ext fill:#fff2cc,color:#000
``` ```
| Port | Carries | Format | Cardinality | | Port | Direction | Carries | When |
|---|---|---|---| |:---|:---|:---|:---|
| **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. | | 0 | out | Process data, formatted via `outputUtils.formatMsg(..., 'process')`. Object containing only keys that changed since last tick. | `'output-changed'` fires on the emitter, or every tick if tick-driven |
| **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. | | 1 | out | InfluxDB line-protocol string via `outputUtils.formatMsg(..., 'influxdb')`. Numeric fields only. | Same trigger as Port 0 |
| **2** Registration / control | `child.register` upward on adapter init; control replies. | `{topic, payload: nodeRef}` | At init time + on demand. | | 2 | out | `child.register` upward at init plus internal control plumbing. | Once on init (after 100ms delay); on demand |
| in | in | Commands by `msg.topic`, dispatched through the `commands/` registry. | Any time another node sends a msg |
See [Telemetry](Telemetry) for the full Port-1 schema and InfluxDB conventions. See [Telemetry](Telemetry) for the line-protocol layout and downstream wiring.
## Topic conventions ---
| Prefix | Direction | Used for | ## Lifecycle &mdash; what BaseNodeAdapter does
|---|---|---|
| `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. In order, in the constructor. Source: `.claude/refactor/CONTRACTS.md` §2.
## 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 ```mermaid
sequenceDiagram sequenceDiagram
participant childNc as Child nodeClass autonumber
participant parentReg as Parent commandRegistry participant rt as Node-RED runtime
participant parentRouter as Parent ChildRouter participant nc as nodeClass (BaseNodeAdapter)
participant parentSc as Parent specificClass participant sc as specificClass (BaseDomain)
participant outs as Output pipeline
childNc->>parentReg: msg{topic: child.register, softwareType, ref} rt->>nc: new nodeClass(uiConfig)
parentReg->>parentRouter: dispatch(child.register, ref) nc->>nc: configManager.buildConfig(uiConfig)
parentRouter->>parentRouter: match softwareType nc->>nc: this.buildDomainConfig(uiConfig)
parentRouter->>parentSc: invoke registered handler nc->>sc: new DomainClass(mergedConfig)
parentSc->>parentSc: store ref, wire emitter.on(...) sc->>sc: configure() &mdash; wire ChildRouter + concerns
sc->>sc: _init?() &mdash; optional post-configure hook
Note over nc: 100ms delay
nc->>nc: emit Port 2 child.register
nc->>outs: subscribe to 'output-changed' OR start tick(N ms)
nc->>nc: start status loop (every 1000ms)
nc->>nc: attach input handler (commands dispatcher)
rt->>nc: msg.topic dispatch -> handler
nc->>sc: handler.call(source, msg, ctx)
sc->>sc: emit 'output-changed' if state shifted
outs->>rt: Port 0 + Port 1 send (delta-compressed)
``` ```
A child is anything the parent's `configure()` declares via `router.onRegister(<softwareType>, handler)`. Examples: ### Two output strategies
| Parent | Accepts children with softwareType | | Strategy | When to pick | What domain does | What adapter does |
|---|---| |:---|:---|:---|:---|
| pumpingStation | `measurement`, `machine`, `machinegroup`, `pumpingstation` | | Event-driven (default) | Domain reacts to incoming events and has no genuinely time-driven math | Fire `this.emitter.emit('output-changed')` when public output state shifts | Subscribes to `'output-changed'`; on each fire, calls `getOutput()` and pushes the delta-compressed msg |
| machineGroupControl | `machine`, `measurement` | | Tick-driven (opt-in) | Domain has time-driven math &mdash; integrators, simulators, time-based thresholds | Implement `tick()`. Fire `'output-changed'` from inside it when output state shifts | Calls `tick()` every `static tickInterval` ms; listens to `'output-changed'` the same way as event-driven |
| valveGroupControl | `valve`, `machine`, `machinegroup`, `pumpingstation`, `valvegroupcontrol` (last 4 as flow sources) |
| reactor | `measurement`, `reactor` | Both strategies funnel into the same `'output-changed'` &rarr; `getOutput()` &rarr; `formatMsg` &rarr; `node.send` pipeline.
| settler | `measurement`, `reactor`, `machine` |
| monster | `measurement` | ---
| diffuser | `measurement` |
| rotatingMachine | `measurement` | ## The commands registry
| valve | `measurement` |
| dashboardAPI | any (used for Grafana provisioning) | Each node has `src/commands/index.js` exporting an array of descriptors. The base adapter builds a `Map<topic | alias, descriptor>` at construction. Dispatch is one lookup. Source: `.claude/refactor/CONTRACTS.md` §4.
```js
module.exports = [
{
topic: 'set.demand',
aliases: ['setDemand', 'Qd'], // legacy names
units: { measure: 'volumeFlowRate', default: 'm3/h' },
payloadSchema: { type: 'number' },
description: 'Operator demand setpoint.',
handler: handlers.setDemand,
},
{
topic: 'cmd.calibrate',
payloadSchema: { type: 'none' }, // trigger-only
description: 'Trigger a one-shot calibration.',
handler: handlers.calibrate,
},
];
```
| Descriptor field | What it does |
|:---|:---|
| `topic` | Canonical name. See [Topic Conventions](Topic-Conventions). |
| `aliases` | Pre-refactor legacy names. First use of each fires a one-time deprecation warning. |
| `units` | `{measure, default}` &mdash; pre-dispatch unit normalisation. Handler always sees `default` unit. |
| `payloadSchema` | `{type: 'string' \| 'number' \| 'boolean' \| 'object' \| 'any' \| 'none'}` &mdash; type-check before handler. |
| `description` | Free-text. Surfaced by `.list()` and `wikiGen` topic-contract autogen. |
| `handler` | `(source, msg, ctx) => ...` &mdash; pure function on the domain. |
---
## Child registration &mdash; declarative routing
The `ChildRouter` declares which child softwareTypes the parent accepts and what to do with each. Source: `.claude/refactor/CONTRACTS.md` §5.
```js
configure() {
this.router
.onRegister('machine', (child) => this.machines[child.id] = child)
.onRegister('measurement', (child) => this._subscribeMeasurement(child))
.onMeasurement('measurement', { type: 'pressure', position: 'upstream' },
(data, child) => this._onPressure('upstream', data));
}
```
```mermaid
sequenceDiagram
autonumber
participant child as Child nodeClass
participant reg as Parent commandRegistry
participant rt as Parent ChildRouter
participant sc as Parent specificClass
child->>reg: msg{topic: child.register, payload: {ref, softwareType}}
reg->>rt: dispatchRegister(child, softwareType)
rt->>rt: match softwareType in onRegister handlers
rt->>sc: invoke handler(child)
sc->>sc: store ref, wire emitter.on(<topic>, ...)
```
### Who accepts what
Verified against each node's `configure()` in source.
| Parent | Accepted softwareTypes | Use |
|:---|:---|:---|
| pumpingStation | `measurement`, `machine`, `machinegroup`, `pumpingstation` | Basin sensors + pumps + groups + cascaded PS |
| machineGroupControl | `machine`, `measurement` | Pumps + pressure sensors |
| valveGroupControl | `valve`, `machine`, `machinegroup`, `pumpingstation`, `valvegroupcontrol` | Valves + four flow-source softwareTypes (peer-level, not S88 children) |
| reactor | `measurement`, `reactor` | Sensors + upstream reactor in a chain |
| settler | `measurement`, `reactor`, `machine` | Sensors + upstream reactor + return pump |
| monster | `measurement` | Flow + quality sensors |
| diffuser | `measurement` | DO + airflow sensors |
| rotatingMachine | `measurement` | Pressure / flow / power sensors |
| valve | `measurement` | Position / pressure sensors |
| dashboardAPI | any softwareType | Triggers Grafana dashboard generation per node |
---
## Where business logic lives ## Where business logic lives
```mermaid Each node's `src/` follows the same shape (concern modules).
flowchart TB
subgraph node["A node's src/ folder"] ```
sc["specificClass.js<br/>orchestration only"] nodes/<name>/
subgraph concerns["Concern subdirs (per-node)"] |
c1[basin/ or curves/ or kinetics/<br/>physics / math] +-- <NodeName>.js entry
c2[state/<br/>FSM transitions] +-- <NodeName>.html editor form
c3[dispatch/ or safety/<br/>action / guard logic] +-- package.json
c4[commands/<br/>topic → handler descriptors] |
c5[io/<br/>output composition] +-- src/
end | <Name>NodeClass.js nodeClass (adapter)
end | <Name>SpecificClass.js specificClass (orchestrator)
sc --> c1 | |
sc --> c2 | +-- commands/
sc --> c3 | | index.js topic descriptors
sc --> c4 | | handlers.js pure handler functions
sc --> c5 | |
| +-- state/ FSM (if stateful)
| +-- <concern1>/ e.g. basin/, kinetics/, curves/
| +-- <concern2>/ e.g. safety/, dispatch/
| |
| +-- io/
| output.js getOutput() composition
| statusBadge.js getStatusBadge()
|
+-- test/
| basic/ · integration/ · edge/
|
+-- examples/
01-Basic.json · 02-Integration.json · 03-Dashboard.json
``` ```
specificClass should be **stitching only** — instantiate concern modules in `configure()`, call them in `tick()` or in router handlers. Concerns are individually testable. > [!IMPORTANT]
> The specificClass is stitching, not implementation. It instantiates concern modules in `configure()` and calls them in `tick()` or in router handlers. Concerns are individually testable; specificClass tests verify wiring, not math. Source: `.claude/refactor/MODULE_SPLIT.md`.
---
## Reading order for newcomers ## Reading order for newcomers
1. `.claude/refactor/CONTRACTS.md` — every API shape this wiki abstracts over. | # | Read | Why |
2. `.claude/refactor/CONVENTIONS.md` — code style, file size, naming. |:---|:---|:---|
3. `.claude/refactor/MODULE_SPLIT.md` — concern layout per node. | 1 | `.claude/refactor/CONTRACTS.md` | Every API shape this page summarises |
4. One node's `wiki/Home.md` (pumpingStation is the most mature pilot — start there). | 2 | `.claude/refactor/CONVENTIONS.md` | Code style, file size, naming, imports, tests |
5. The corresponding `src/` folder, top-down: specificClass → concern modules. | 3 | `.claude/refactor/MODULE_SPLIT.md` | Concern layout per node |
| 4 | [pumpingStation wiki](https://gitea.wbd-rd.nl/RnD/pumpingStation/wiki/Home) | The refactor pilot &mdash; most mature node |
| 5 | The corresponding `src/` folder | Top-down: specificClass &rarr; concern modules &rarr; handlers |
---
## Related pages ## Related pages
- [Topology-Patterns](Topology-Patterns) — typical plant configurations | Page | Why |
- [Topic-Conventions](Topic-Conventions) — naming and units |:---|:---|
- [Telemetry](Telemetry) — Port-1 InfluxDB schema | [Topology Patterns](Topology-Patterns) | See the contracts above in action across a realistic plant |
- [Getting-Started](Getting-Started) — hands-on first run | [Topic Conventions](Topic-Conventions) | Full reference for `set.` / `cmd.` / `data.` / `query.` / `child.` / `evt.` |
| [Telemetry](Telemetry) | Port 0 / 1 / 2 InfluxDB schema details |
| [Getting Started](Getting-Started) | First hands-on with the contracts |

View File

@@ -1,28 +1,64 @@
# Archive — pre-refactor wiki pages # Archive
Pages kept for historical reference. **Do not update them.** Corrections go on the current page; if you find a meaningful inaccuracy in the archived page, leave it and add a note to the *current* page explaining what changed. > [!NOTE]
> Pre-refactor wiki content has been removed from the live wiki. The git history of `EVOLV.wiki.git` preserves every prior page if you need to consult it. This page lists what was removed and when, plus where to look for the current content.
| Page | Era | Archived on | ---
|---|---|---|
| [Wiki schema (SCHEMA.md)](Archive/SCHEMA.md) | Pre-refactor wiki maintenance schema (Obsidian-style) | 2026-05-11 |
| [Wiki index (index.md)](Archive/index.md) | Pre-refactor wiki index (2026-04-13) | 2026-05-11 |
| [Wiki log (log.md)](Archive/log.md) | Pre-refactor session log, single Apr-07 entry | 2026-05-11 |
| [Metrics dashboard (metrics.md)](Archive/metrics.md) | Pre-refactor test counts (Apr-07 snapshot, 43 tests) | 2026-05-11 |
| [Project overview (overview.md)](Archive/overview.md) | Pre-refactor node inventory ("needs review" for most nodes) | 2026-05-11 |
| [Knowledge graph (knowledge-graph.yaml)](Archive/knowledge-graph.yaml) | Apr-07 test / metrics / bug snapshot — superseded by 823 platform tests | 2026-05-11 |
| [Architecture: Node architecture](Archive/architecture-node-architecture.md) | Pre-refactor 3-tier diagram with old `_loadConfig`/`_setupSpecificClass` internals | 2026-05-11 |
| [Architecture: Platform overview](Archive/architecture-platform-overview.md) | Pre-refactor edge/site/central layering vision | 2026-05-11 |
| [Architecture: Stack review](Archive/architecture-stack-review.md) | Pre-refactor full stack analysis (references `temp/*.pdf`, `tagcodering`) | 2026-05-11 |
| [Architecture: 3D pump curves](Archive/architecture-3d-pump-curves.md) | Pre-refactor predict_class / spline internals description | 2026-05-11 |
| [Architecture: Group optimization](Archive/architecture-group-optimization.md) | Pre-refactor BEP-Gravitation algorithm walkthrough | 2026-05-11 |
| [Architecture: Deployment blueprint](Archive/architecture-deployment-blueprint.md) | Pre-refactor Docker topology + rollout order | 2026-05-11 |
| [Concepts: generalFunctions API](Archive/concepts-generalfunctions-api.md) | Pre-refactor API reference — missing BaseDomain / BaseNodeAdapter / ChildRouter | 2026-05-11 |
| [Concepts: Sources readme](Archive/concepts-sources-readme.md) | Empty placeholder for future PDFs (never populated) | 2026-05-11 |
| [Findings: Open issues 2026-03](Archive/findings-open-issues-2026-03.md) | Issues 15 all resolved by the refactor (diffuser, monster, dashboardAPI, etc.) | 2026-05-11 |
| [Session: 2026-04-07 production hardening](Archive/sessions-2026-04-07-production-hardening.md) | rotatingMachine + MGC hardening session log | 2026-05-11 |
| [Session: 2026-04-13 rotatingMachine trial-ready](Archive/sessions-2026-04-13-rotatingMachine-trial-ready.md) | FSM interruptibility, config schema sync, UX polish session log | 2026-05-11 |
| [Session: 2026-04-13 measurement digital mode](Archive/sessions-2026-04-13-measurement-digital-mode.md) | Dispatcher bug fix, digital/MQTT mode addition session log | 2026-05-11 |
| [Manual: rotatingMachine (pre-refactor)](Archive/manuals-nodes-rotatingMachine.md) | Per-repo wiki on gitea.wbd-rd.nl/RnD/rotatingMachine is the current page | 2026-05-11 |
| [Manual: measurement (pre-refactor)](Archive/manuals-nodes-measurement.md) | Per-repo wiki on gitea.wbd-rd.nl/RnD/measurement is the current page | 2026-05-11 |
Each archived page carries the standard banner at its top (see `.claude/refactor/WIKI_TEMPLATE.md` → Archive banner). ## What was removed (2026-05-11)
The 2026-05-11 wiki refactor removed nine pre-refactor pages from the live `EVOLV.wiki.git`:
| Removed page | Era | Successor |
|:---|:---|:---|
| `Architecture-Configuration-Model-and-Tagcodering` | Pre-refactor planning doc | [Architecture](Architecture) |
| `Architecture-Container-Topology` | Pre-refactor Docker / container planning | [Architecture](Architecture), [Getting Started](Getting-Started) |
| `Architecture-Deployment-Blueprint` | Pre-refactor rollout plan | [Architecture](Architecture), [Getting Started](Getting-Started) |
| `Architecture-Deployment-Controls-Checklist` | Pre-refactor go / no-go checklist | (none &mdash; superseded by per-node `wiki/Home.md`) |
| `Architecture-Platform-Overview` | Pre-refactor edge / site / central layering | [Home](Home), [Architecture](Architecture) |
| `Architecture-Security-and-Access-Boundaries` | Pre-refactor security model | [OT Security IEC 62443](Concept-OT-Security-IEC62443) |
| `Architecture-Security-and-Regulatory-Mapping` | Pre-refactor IEC 62443 mapping | [OT Security IEC 62443](Concept-OT-Security-IEC62443) |
| `Architecture-Telemetry-and-Smart-Storage` | Pre-refactor telemetry blueprint | [Telemetry](Telemetry), [InfluxDB Schema Design](Concept-InfluxDB-Schema-Design) |
| `AI-assisted coding.-` | Pre-refactor coding-with-AI usage note | (none) |
Plus, in the source tree under `EVOLV/wiki/`, the audit moved a further 20 stale pages to `wiki/Archive/<name>.md` (these were never published to the live wiki, only existed in the source repo).
---
## How to recover an archived page
The content has not been lost &mdash; git keeps it.
```bash
git clone https://gitea.wbd-rd.nl/RnD/EVOLV.wiki.git
cd EVOLV.wiki
git log --all --diff-filter=D --name-only | grep '<page-name>'
git show <commit>:<path>
```
Or use Gitea's web UI:
1. Open https://gitea.wbd-rd.nl/RnD/EVOLV/commits/branch/main
2. Click into the 2026-05-11 refactor commit
3. Scroll to the deleted files
---
## Where to look instead
| For ... | See |
|:---|:---|
| Top-level navigation | [Home](Home) |
| Code architecture (BaseDomain / three-tier / generalFunctions) | [Architecture](Architecture) |
| Typical plant configurations | [Topology Patterns](Topology-Patterns) |
| Topic naming, units, S88 colours | [Topic Conventions](Topic-Conventions) |
| Port 0 / 1 / 2, InfluxDB schema, Grafana | [Telemetry](Telemetry) |
| Per-node operator reference | The node's own wiki on `gitea.wbd-rd.nl/RnD/<node>/wiki` |
---
## Related pages
| Page | Why |
|:---|:---|
| [Home](Home) | Current top-level navigation |

View File

@@ -1,20 +1,26 @@
# Getting Started # Getting Started
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`** ![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue)
![platform-tests](https://img.shields.io/badge/platform_tests-823%2F823-brightgreen)
How to clone, install, and run EVOLV locally; how to deploy the example flows; and where to go next. > [!NOTE]
> Clone the repo with `--recurse-submodules`, install dependencies, then either spin up the Docker stack (recommended) or symlink into a local Node-RED. Import an example flow, deploy, watch the state machine run. Total time is around ten minutes.
---
## Prerequisites ## Prerequisites
| Tool | Version | Why | | Tool | Required version | Why |
|---|---|---| |:---|:---|:---|
| Node.js | 18 LTS | Node-RED 4 requires 18+ | | Node.js | 18 LTS or newer | Node-RED 4 requires 18+ |
| npm | ≥ 9 | Comes with Node.js | | npm | 9 or newer | Bundled with Node.js |
| git | 2.35 | Submodule support | | git | 2.35 or newer | Submodule support |
| Docker + compose v2 | optional | For the local Node-RED + InfluxDB stack | | Docker + compose v2 | Optional | Local Node-RED + InfluxDB stack |
| WSL2 (on Windows) | optional | Recommended for native docker performance | | WSL2 (Windows) | Optional | Recommended for native Docker performance |
## Clone and install ---
## Step 1 &mdash; Clone and install
```bash ```bash
git clone --recurse-submodules https://gitea.wbd-rd.nl/RnD/EVOLV.git git clone --recurse-submodules https://gitea.wbd-rd.nl/RnD/EVOLV.git
@@ -22,62 +28,70 @@ cd EVOLV
npm install npm install
``` ```
If you cloned without `--recurse-submodules`, run: If you forgot the recurse flag:
```bash ```bash
git submodule update --init --recursive git submodule update --init --recursive
``` ```
There are 12 submodules — one per node (`generalFunctions` plus 11 active nodes). Each lives under `nodes/<name>/`. There are 12 submodules &mdash; `generalFunctions` plus 11 active nodes. Each lives under `nodes/<name>/`.
## Verify the platform builds ---
## Step 2 &mdash; Verify the platform builds
```bash ```bash
npm run test:platform # expect 823 / 0 npm run test:platform
``` ```
This runs the full unit test suite across all 12 submodules. ~3 minutes on a workstation (reactor's mathjs initialisation dominates). Expect: `823 / 0` (823 passing, 0 failing) across all 12 submodules. Around three minutes on a workstation; reactor's mathjs initialisation dominates.
## Option A — Run via Docker (recommended) > [!TIP]
> A failure here usually means a submodule isn't on its `development` tip or `npm install` didn't complete. Run `git submodule update --init --recursive` and try again.
The repo ships a `docker-compose.yml` that brings up Node-RED + InfluxDB pre-loaded with the EVOLV nodes: ---
## Step 3 &mdash; Pick a run mode
### Option A &mdash; Docker (recommended)
```bash ```bash
docker compose up -d docker compose up -d
``` ```
When healthy: Brings up Node-RED + InfluxDB pre-loaded with EVOLV nodes.
| Service | URL | | Service | URL |
|---|---| |:---|:---|
| Node-RED editor | http://localhost:1880 | | Node-RED editor | http://localhost:1880 |
| FlowFuse dashboard (if widgets installed) | http://localhost:1880/dashboard | | FlowFuse dashboard | http://localhost:1880/dashboard |
| InfluxDB UI | http://localhost:8086 | | InfluxDB UI | http://localhost:8086 |
Watch the container logs while you click around: Watch logs:
```bash ```bash
docker compose logs -f nodered 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`. > [!WARNING]
> WSL2 users: use the native Ubuntu `docker`, not `docker.exe` from Windows Docker Desktop. The compose v2 plugin lives at `~/.docker/cli-plugins/docker-compose`.
## Option B — Run via local Node-RED ### Option B &mdash; Local Node-RED
If you already have Node-RED installed in `~/.node-red`: If you already have Node-RED installed:
```bash ```bash
# in EVOLV/ # from EVOLV/
ln -s "$PWD" ~/.node-red/nodes/EVOLV ln -s "$PWD" ~/.node-red/nodes/EVOLV
node-red
``` ```
Or add to your `~/.node-red/settings.js`: Or via `~/.node-red/settings.js`:
```js ```js
module.exports = { module.exports = {
// ...
nodesDir: ['/path/to/EVOLV/nodes'], nodesDir: ['/path/to/EVOLV/nodes'],
} };
``` ```
Then start Node-RED: Then start Node-RED:
@@ -86,33 +100,40 @@ Then start Node-RED:
node-red node-red
``` ```
## Your first flow ---
Each node ships with example flows under `nodes/<name>/examples/`. The recommended starting point is **rotatingMachine — Basic Manual Control**: ## Step 4 &mdash; Run your first flow
Every node ships example flows under `nodes/<name>/examples/`. The recommended start is the rotatingMachine "Basic Manual Control" example.
```bash ```bash
# Copy the example into your Node-RED user dir
cp nodes/rotatingMachine/examples/01-Basic-Manual-Control.json ~/.node-red/ cp nodes/rotatingMachine/examples/01-Basic-Manual-Control.json ~/.node-red/
``` ```
In the editor: In the Node-RED editor:
1. Menu **Import** → select the file **Import**. 1. Menu &rarr; Import &rarr; pick the file &rarr; Import.
2. Hit **Deploy**. 2. Click Deploy.
3. Open the dashboard at http://localhost:1880/dashboard. 3. Open http://localhost:1880/dashboard.
4. Click the **startup** button. Watch the state machine progress: `idle → starting → warmingup → operational`. 4. Click the startup button.
5. Drag the demand slider. The flow + power predictions update in real time. 5. Watch the state machine progress: `idle` &rarr; `starting` &rarr; `warmingup` &rarr; `operational`.
6. Drag the demand slider. Flow + power predictions update in real time.
## What to read next > [!TIP]
> Inject pressure for meaningful predictions. Without pressure data, `fDimension=0` produces unrealistic flow/power values. The example flow injects a simulated pressure profile.
---
## Step 5 &mdash; What to read next
```mermaid ```mermaid
flowchart TB flowchart LR
start[You are here]:::neutral start["You are here"]
arch[Architecture<br/>3-tier code structure]:::tier1 arch["Architecture &mdash; 3-tier code"]
topo[Topology-Patterns<br/>typical plant configs]:::tier1 topo["Topology Patterns &mdash; plant configs"]
conv[Topic-Conventions<br/>naming + units]:::tier1 node["Pick a node &mdash; per-repo wiki"]
tele[Telemetry<br/>Port 0/1/2 + InfluxDB]:::tier1 conv["Topic Conventions &mdash; naming + units"]
node[Pick a node's wiki<br/>per-repo Home.md]:::tier3 tele["Telemetry &mdash; Port 0/1/2 + InfluxDB"]
start --> arch start --> arch
start --> topo start --> topo
@@ -121,52 +142,65 @@ flowchart TB
node --> conv node --> conv
node --> tele node --> tele
class start neutral
class arch,topo,conv,tele step
class node domain
classDef neutral fill:#dddddd classDef neutral fill:#dddddd
classDef tier1 fill:#a9daee,color:#000 classDef step fill:#a9daee,color:#000
classDef tier3 fill:#50a8d9,color:#000 classDef domain fill:#50a8d9,color:#000
``` ```
| Path | Why | | Path | Why |
|---|---| |:---|:---|
| [Architecture](Architecture) | Internalise the 3-tier (entry nodeClass specificClass) pattern. | | [Architecture](Architecture) | Internalise the three-tier (entry &rarr; nodeClass &rarr; specificClass) pattern |
| [Topology-Patterns](Topology-Patterns) | See typical plant configs end-to-end with verified edges. | | [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). | | Pick a node | 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. | | [Topic Conventions](Topic-Conventions) | Reference for naming when you wire your own flows |
| [Telemetry](Telemetry) | If you're plumbing InfluxDB or Grafana. | | [Telemetry](Telemetry) | If you are plumbing InfluxDB or Grafana |
---
## Quick command reference ## Quick command reference
```bash ```bash
# run all tests # All tests
npm run test:platform npm run test:platform
# run one node's tests # One node's tests
cd nodes/rotatingMachine && node --test test/basic/*.test.js cd nodes/rotatingMachine && node --test test/basic/*.test.js
# regenerate a node's wiki AUTOGEN blocks # Regenerate a node's wiki AUTOGEN blocks
cd nodes/rotatingMachine && npm run wiki:all cd nodes/rotatingMachine && npm run wiki:all
# rebuild docker stack # Rebuild docker stack
docker compose build && docker compose up -d docker compose build && docker compose up -d
# update all submodules to their development tips # Fetch all submodules to their development tips
git submodule update --remote --recursive git submodule update --remote --recursive
# pack EVOLV as an npm tarball # Pack EVOLV as an npm tarball
npm pack npm pack
``` ```
---
## Where to ask for help ## Where to ask for help
| Channel | Use it for | | Channel | Use it for |
|---|---| |:---|:---|
| Per-node wiki on Gitea | Operator-level questions for one node. | | Per-node wiki on Gitea | Operator-level questions for one node |
| `.claude/refactor/OPEN_QUESTIONS.md` | Live decisions log issues being worked on. | | `.claude/refactor/OPEN_QUESTIONS.md` | Live decisions log &mdash; issues being worked on |
| Gitea repo issues per submodule | File a bug against a specific node. | | Gitea repo issues per submodule | File a bug against a specific node |
| R&D team Slack / Teams | Anything urgent or strategic. | | R&D team Slack / Teams | Anything urgent or strategic |
---
## Related pages ## Related pages
- [Home](Home) — top-level navigation | Page | Why |
- [Architecture](Architecture) — how a node is built |:---|:---|
- [Topology-Patterns](Topology-Patterns) — plant configurations | [Home](Home) | Top-level navigation |
| [Architecture](Architecture) | How a node is built |
| [Topology Patterns](Topology-Patterns) | Plant configurations |
| [Glossary](Glossary) | Decode S88 / EVOLV jargon |

View File

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

View File

@@ -1,34 +1,41 @@
# EVOLV — Wastewater Treatment Plant Automation # EVOLV — Wastewater Treatment Plant Automation
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`** ![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue)
> Source of truth: `nodes/<name>/src/specificClass.js` `configure()` declarations. Edges below were verified against `router.onRegister(...)` calls and emitter subscriptions. ![platform-tests](https://img.shields.io/badge/platform_tests-823%2F823-brightgreen)
![branch](https://img.shields.io/badge/branch-development-orange)
![nodes](https://img.shields.io/badge/active_nodes-11_%2B_library-blue)
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. > [!NOTE]
> EVOLV is a Node-RED node library for wastewater plant automation, built by Waterschap Brabantse Delta R&D. It exposes 11 active nodes across four ISA-88 (S88) levels plus one Grafana-provisioning utility, all on a shared `generalFunctions` library. Every node follows the same three-tier code shape: entry registers the node type; `nodeClass` (extends `BaseNodeAdapter`) bridges to the Node-RED runtime; `specificClass` (extends `BaseDomain`) holds pure-JS domain logic. Source of truth for everything claimed on this page: each node's `src/specificClass.js` `configure()` plus `.claude/refactor/CONTRACTS.md`.
---
## Platform overview ## Platform overview
Every solid arrow is a real `router.onRegister(<softwareType>, …)` call in the parent's `configure()`. Dashed arrows are emitter subscriptions (no child-register handshake). Thick `==>` arrows are Unit-to-Unit `stateChange` subscriptions. Verified node by node against current source.
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph PC["Process Cell"] subgraph PC["Process Cell"]
ps[pumpingStation]:::pc ps[pumpingStation]:::pc
end end
subgraph UN["Unit"] subgraph UN["Unit"]
mgc[machineGroupControl]:::unit mgc[machineGroupControl]
vgc[valveGroupControl]:::unit vgc[valveGroupControl]
reactor[reactor]:::unit reactor[reactor]
settler[settler]:::unit settler[settler]
monster[monster]:::unit monster[monster]
end end
subgraph EM["Equipment"] subgraph EM["Equipment Module"]
rm[rotatingMachine]:::equip rm[rotatingMachine]
v[valve]:::equip v[valve]
diff[diffuser]:::equip diff[diffuser]
end end
subgraph CM["Control Module"] subgraph CM["Control Module"]
meas["measurement<br/><i>registers with any process node</i>"]:::ctrl meas["measurement &mdash; registers with any process node"]
end end
subgraph UT["Utility"] subgraph UT["Utility"]
dash["dashboardAPI<br/><i>any node Grafana dashboard</i>"]:::util dash["dashboardAPI &mdash; any node &rarr; Grafana"]
end end
ps -->|owns| mgc ps -->|owns| mgc
@@ -40,86 +47,149 @@ flowchart TB
reactor ==stateChange==> settler reactor ==stateChange==> settler
diff -. OTR data .-> reactor diff -. OTR data .-> reactor
classDef pc fill:#0c99d9,color:#fff class ps pc
classDef unit fill:#50a8d9,color:#000 class mgc,vgc,reactor,settler,monster unit
classDef equip fill:#86bbdd,color:#000 class rm,v,diff equip
classDef ctrl fill:#a9daee,color:#000 class meas ctrl
classDef util fill:#dddddd,color:#000 class dash util
classDef pc fill:#0c99d9,color:#fff,stroke:#075a82,stroke-width:2px
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef equip fill:#86bbdd,color:#000,stroke:#5a90b2,stroke-width:2px
classDef ctrl fill:#a9daee,color:#000,stroke:#76b7d4,stroke-width:2px
classDef util fill:#dddddd,color:#000,stroke:#a8a8a8,stroke-width:2px
``` ```
**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)**. ### Edge legend
| Arrow | Meaning | Implementation |
|---|---|---|
| `A --> B` (solid) | A owns B as an S88 child via `child.register` | `router.onRegister('<swType>', ...)` |
| `A -.-> B` (dashed) | A subscribes to B's emitter events; no handshake | `B.emitter.on('<event>', ...)` |
| `A ==> B` (thick) | Unit-to-Unit `stateChange` subscription | `settler._connectReactor` pattern |
### S88 colours
| Hex | Level | Used by |
|:---|:---|:---|
| `#0f52a5` | Area | Reserved &mdash; no node yet |
| `#0c99d9` | Process Cell | pumpingStation |
| `#50a8d9` | Unit | MGC, VGC, reactor, settler, monster |
| `#86bbdd` | Equipment Module | rotatingMachine, valve, diffuser |
| `#a9daee` | Control Module | measurement |
| `#dddddd` | Utility / neutral | dashboardAPI, helper functions |
Source: `.claude/rules/node-red-flow-layout.md` §14.
For the full data-flow map &mdash; every `measurement -> parent` edge, VGC's flow-source registrations, dashboardAPI's provisioning calls, and the worked plant example &mdash; see [Topology Patterns](Topology-Patterns).
---
## Live nodes ## Live nodes
| S88 level | Node | One-liner | Per-node wiki | | S88 level | Node | Role | Per-node wiki |
|---|---|---|---| |:---|:---|:---|:---|
| 🟦 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) | | Process Cell | pumpingStation | Wet-well basin model; dispatches demand to pump groups. | [Open](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 | machineGroupControl | Load-sharing across a group of `rotatingMachine` children. | [Open](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 | valveGroupControl | Coordinated position control over `valve` children. Registers upstream pumps / PS / MGC as flow sources. | [Open](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 | reactor | Bioreactor &mdash; ASM kinetics (CSTR / PFR). Pairs with diffuser and downstream settler. | [Open](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 | settler | Secondary clarifier; subscribes to upstream reactor `stateChange`, drives a return pump. | [Open](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) | | Unit | monster | Composite-sample sensor surrogate; proportional sampling program. | [Open](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 | rotatingMachine | Single pump or compressor &mdash; characteristic curves, prediction, full FSM. | [Open](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 | valve | Single valve actuator &mdash; shared FSM with rotatingMachine. | [Open](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) | | Equipment | diffuser | Aeration diffuser; gas-side modelling, OTR emission. | [Open](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) | | Control Module | measurement | Sensor signal conditioning &mdash; scaling, smoothing, outlier detection; analog / digital / MQTT modes. | [Open](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) | | Utility | dashboardAPI | Receives `child.register` from any node; provisions a Grafana dashboard via HTTP. | [Open](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) | | Library | generalFunctions | `BaseDomain`, `BaseNodeAdapter`, `ChildRouter`, commandRegistry, `UnitPolicy`, `MeasurementContainer`, `statusBadge`, `HealthStatus`, `LatestWinsGate`, logger, configManager. Not a Node-RED node. | [Open](https://gitea.wbd-rd.nl/RnD/generalFunctions/wiki/Home) |
---
## Start here ## Start here
| You want to | Read | | If you want to&hellip; | Read |
|---|---| |:---|:---|
| Stand up a local dev environment + run an example flow | [Getting-Started](Getting-Started) | | Stand up a local dev environment and run an example flow | [Getting Started](Getting-Started) |
| Understand the codebase layout, BaseDomain/adapter pattern, output ports | [Architecture](Architecture) | | Understand the codebase layout, BaseDomain / adapter pattern, output ports | [Architecture](Architecture) |
| See typical plant configurations and how nodes wire together | [Topology-Patterns](Topology-Patterns) | | 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) | | Know what topic names to use, units, S88 colours, measurement keys | [Topic Conventions](Topic-Conventions) |
| Understand what Port 0 / Port 1 / Port 2 carry, InfluxDB layout | [Telemetry](Telemetry) | | Understand what Port 0 / Port 1 / Port 2 carry, InfluxDB layout, Grafana | [Telemetry](Telemetry) |
| Decode S88 / EVOLV jargon | [Glossary](Glossary) | | Decode S88 / EVOLV / WWTP jargon | [Glossary](Glossary) |
## Domain concepts ---
Evergreen technical references (not affected by refactors): ## Domain concepts (evergreen)
Domain knowledge that doesn't change when code is refactored.
| Page | Topic | | Page | Topic |
|---|---| |:---|:---|
| [ASM models](concepts/asm-models) | Activated Sludge Models biological process kinetics | | [ASM Models](Concept-ASM-Models) | Activated Sludge Models &mdash; biological process kinetics |
| [PID control theory](concepts/pid-control-theory) | Loop tuning, anti-windup, controller forms | | [PID Control Theory](Concept-PID-Control-Theory) | Loop tuning, anti-windup, controller forms |
| [Pump affinity laws](concepts/pump-affinity-laws) | Speed/flow/head/power scaling | | [Pump Affinity Laws](Concept-Pump-Affinity-Laws) | Speed / flow / head / power scaling |
| [Settling models](concepts/settling-models) | Takács / Vesilind / discrete settling | | [Settling Models](Concept-Settling-Models) | Tak&aacute;cs / Vesilind / discrete settling |
| [Signal processing — sensors](concepts/signal-processing-sensors) | Smoothing, outlier rejection | | [Signal Processing &mdash; Sensors](Concept-Signal-Processing-Sensors) | Smoothing, outlier rejection |
| [InfluxDB schema design](concepts/influxdb-schema-design) | Cardinality, tags vs fields | | [InfluxDB Schema Design](Concept-InfluxDB-Schema-Design) | Cardinality, tags vs fields |
| [Wastewater compliance NL](concepts/wastewater-compliance-nl) | Dutch regulatory context | | [Wastewater Compliance NL](Concept-Wastewater-Compliance-NL) | Dutch regulatory context |
| [OT security IEC 62443](concepts/ot-security-iec62443) | OT cybersecurity baseline | | [OT Security &mdash; IEC 62443](Concept-OT-Security-IEC62443) | OT cybersecurity baseline |
## Operations findings ## Operations findings (algorithm proofs)
Algorithm-level proofs and behavioural notes that are still valid:
| Page | Topic | | Page | Topic |
|---|---| |:---|:---|
| [BEP gravitation proof](findings/bep-gravitation-proof) | Best-efficiency-point convergence | | [BEP Gravitation Proof](Finding-BEP-Gravitation-Proof) | Best-Efficiency-Point convergence |
| [Curve non-convexity](findings/curve-non-convexity) | When pump curves break local optima | | [Curve Non-Convexity](Finding-Curve-Non-Convexity) | When pump curves break local optima |
| [NCog behaviour](findings/ncog-behavior) | NCog control metric notes | | [NCog Behaviour](Finding-NCog-Behavior) | NCog control metric notes |
| [Pump switching stability](findings/pump-switching-stability) | Hysteresis design for multi-pump groups | | [Pump Switching Stability](Finding-Pump-Switching-Stability) | Hysteresis design for multi-pump groups |
## Project status ## Node-RED / FlowFuse manuals
| Tier | What | Status | | Page | Topic |
|---|---|---| |:---|:---|
| 1 | Add infra in `generalFunctions` (additive only) | ✅ done | | [Manual Index](Manual-NodeRED-INDEX) | Top of the Node-RED reference set |
| 2 | Pilot: pumpingStation end-to-end on new infra | ✅ done | | [Runtime &mdash; Node.js](Manual-NodeRED-Runtime-Node-Js) | `send`, `done`, multi-output arrays |
| 3 | Convert measurement, MGC, rotatingMachine | ✅ done | | [Function Node Patterns](Manual-NodeRED-Function-Node-Patterns) | Return / send patterns |
| 4 | Convert valve, VGC, reactor, settler, monster, diffuser, dashboardAPI | ✅ done | | [Messages and Editor Structure](Manual-NodeRED-Messages-And-Editor-Structure) | Msg shape + HTML / editor / runtime split |
| 5 | Canonical topic names + alias deprecation map | ✅ done | | [FlowFuse ui-chart](Manual-NodeRED-Flowfuse-Ui-Chart-Manual) | Data contract, runtime controls |
| 6 | Promote `development``main` | ⏳ pending Docker E2E + human review | | [FlowFuse ui-button](Manual-NodeRED-Flowfuse-Ui-Button-Manual) | Button reference |
| 8.5 | Remove deprecated paths in `generalFunctions` | ✅ done | | [FlowFuse ui-gauge](Manual-NodeRED-Flowfuse-Ui-Gauge-Manual) | Gauge reference |
| 9 | Wiki refactor — visual-first per-node + master pages | ✅ landed 2026-05-11 | | [FlowFuse ui-text](Manual-NodeRED-Flowfuse-Ui-Text-Manual) | Text reference |
| 10 | Test-suite refactor across all nodes | 🟡 in progress | | [FlowFuse ui-template](Manual-NodeRED-Flowfuse-Ui-Template-Manual) | Template reference |
| — | pumpingStation Docker E2E (P2.14) | ⏳ pending | | [FlowFuse ui-config](Manual-NodeRED-Flowfuse-Ui-Config-Manual) | Config reference |
| [Dashboard Layout](Manual-NodeRED-Flowfuse-Dashboard-Layout-Manual) | Compact layout guidance |
| [Widgets Catalog](Manual-NodeRED-Flowfuse-Widgets-Catalog) | All widgets at a glance |
823 platform tests pass · 0 failures · 12 submodules + parent on `development`. ---
## Refactor status
| Tier | Scope | Status |
|:---:|:---|:---|
| 1 | Add infra in `generalFunctions` (additive only) | Done |
| 2 | Pilot: pumpingStation on new infra | Done |
| 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` &rarr; `main` | Pending Docker E2E + human review |
| 8.5 | Remove deprecated paths in `generalFunctions` | Done |
| 9 | Visual-first wiki refactor &mdash; per-node + master | Done 2026-05-11 |
| 10 | Test-suite refactor across all nodes | In progress |
> [!IMPORTANT]
> Active branch is `development` everywhere &mdash; 12 submodules plus parent EVOLV. `main` will receive the merged refactor only after Docker E2E sign-off (pumpingStation P2.14 pending).
---
## Archive ## Archive
Pre-refactor planning pages have been moved to the [Archive](Archive). The current Home and supporting pages are the canonical references. Pre-refactor wiki content has been removed from the live wiki. The git history of `EVOLV.wiki.git` preserves every prior page if you need to consult it. See [Archive](Archive) for the changelog of what was removed and when.
---
## Need help?
| Channel | Use it for |
|:---|:---|
| Per-node wiki on Gitea | Operator-level questions for one node |
| `.claude/refactor/OPEN_QUESTIONS.md` | Live decisions log &mdash; issues being worked on |
| Submodule issues on Gitea | File a bug against a specific node |
| R&D team Slack / Teams | Anything urgent or strategic |

View File

@@ -1,46 +1,68 @@
# Telemetry # Telemetry
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`** ![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue)
![source](https://img.shields.io/badge/source-CONTRACTS.md_%C2%A710-orange)
What every EVOLV node emits on each of its three output ports, the InfluxDB line-protocol layout, and how the data reaches Grafana/FlowFuse. > [!NOTE]
> Every node sends on three output ports: Port 0 (process data), Port 1 (InfluxDB line protocol), Port 2 (registration / control plumbing). All output is formatted by `outputUtils.formatMsg` with delta compression: only changed fields are sent each tick. InfluxDB cardinality discipline: tags = identity (low cardinality), fields = numbers. Source of truth: `.claude/refactor/CONTRACTS.md` §10.
---
## Three-port model ## Three-port model
```mermaid ```mermaid
flowchart LR flowchart LR
sc[specificClass<br/>tick or event]:::tier3 sc["specificClass &mdash; 'output-changed' or tick()"]
nc[nodeClass<br/>outputUtils.formatMsg]:::tier2 ou["outputUtils.formatMsg &mdash; delta-compress"]
p0[(Port 0<br/>process)]:::p0 p0[("Port 0 &mdash; process")]
p1[(Port 1<br/>InfluxDB line)]:::p1 p1[("Port 1 &mdash; InfluxDB line")]
p2[(Port 2<br/>registration)]:::p2 p2[("Port 2 &mdash; register / control")]
dl["Downstream Node-RED &mdash; dashboards, functions"]
influx[("InfluxDB")]
parent["Parent EVOLV node"]
sc -->|getOutput| nc sc -- getOutput() --> ou
nc --> p0 ou --> p0 --> dl
nc --> p1 ou --> p1 --> influx
nc --> p2 sc -. child.register .-> p2 --> parent
p0 -. delta-compressed payload .-> dl[Downstream<br/>Node-RED logic]:::neutral class sc tier3
p1 -. line protocol .-> influx[(InfluxDB)]:::ext class ou tier2
p2 -. child.register .-> parent[Parent EVOLV node]:::neutral class p0 p0c
class p1 p1c
class p2 p2c
class dl,parent dn
class influx ext
classDef tier3 fill:#50a8d9,color:#000 classDef tier3 fill:#50a8d9,color:#000
classDef tier2 fill:#86bbdd,color:#000 classDef tier2 fill:#86bbdd,color:#000
classDef p0 fill:#86bbdd classDef p0c fill:#0c99d9,color:#fff
classDef p1 fill:#a9daee classDef p1c fill:#50a8d9,color:#000
classDef p2 fill:#dddddd classDef p2c fill:#a9daee,color:#000
classDef neutral fill:#dddddd classDef dn fill:#dddddd,color:#000
classDef ext fill:#fff2cc classDef ext fill:#fff2cc,color:#000
``` ```
## Port 0 — Process data (delta-compressed) ### Port summary
**Purpose:** feeds downstream Node-RED logic — dashboards, control functions, alarms. | Port | Carries | Format | Configured by | Trigger |
|:---|:---|:---|:---|:---|
| 0 (process) | Snapshot of changed measurement / state keys | JSON delta object | `outputUtils.formatMsg(..., 'process')` via `config.output.process` | `'output-changed'` on the emitter, or each tick |
| 1 (telemetry) | Numeric fields only &mdash; time-series | InfluxDB line protocol | `outputUtils.formatMsg(..., 'influxdb')` via `config.output.dbase` | Same trigger as Port 0 |
| 2 (register) | `child.register` plus internal control msgs | Plain object | `BaseNodeAdapter` init | Once 100ms after init; on demand |
**Shape:** `msg.payload` is an object containing **only keys that changed** since the last tick. Consumers cache + merge. ---
**Why delta-compressed:** at 1 Hz ticks with 50 fields per node, full snapshots flood downstream nodes and dashboards. Delta payloads typically carry 05 fields per tick. ## Port 0 &mdash; Process data (delta-compressed)
**Example (rotatingMachine, 1 tick):** Purpose: feed downstream Node-RED logic such as dashboards, alarms, control function nodes.
Shape: `msg.payload` contains only keys that changed since the last tick. Consumers must cache and merge.
> [!IMPORTANT]
> Why delta compression: at 1 Hz with 50+ fields per node, full snapshots flood downstream nodes and dashboards. A typical delta carries 0&ndash;5 fields per tick.
Example output, one rotatingMachine tick:
```json ```json
{ {
@@ -52,151 +74,192 @@ flowchart LR
} }
``` ```
Most other fields (state, pressure, mode, ) didn't change this tick omitted. Most other fields (state, pressure, mode, ...) didn't change this tick and are omitted.
**Trigger:** `outputUtils.checkForChanges()` compares the current `getOutput()` against the previous snapshot. Trigger: `outputUtils.checkForChanges()` compares the current `getOutput()` against the previous snapshot. No change means no send.
## Port 1 — Telemetry (InfluxDB line protocol) ---
**Purpose:** time-series storage in InfluxDB for trending, regulatory reporting, ML training. ## Port 1 &mdash; InfluxDB line protocol
**Shape:** `msg.payload` is a **string** (or array of strings) in InfluxDB line protocol: Purpose: time-series storage 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> <measurement>,<tag-set> <field-set> <timestamp-ns>
``` ```
**Example:** Example, rotatingMachine telemetry:
``` ```
rotatingMachine,id=pump-A,softwareType=rotatingMachine flow_predicted_downstream=12.4,power_measured_atequipment=18.2 1714752000000000000 rotatingMachine,id=pump-A,softwareType=rotatingMachine flow_predicted_downstream=12.4,power_measured_atequipment=18.2 1714752000000000000
``` ```
**Conventions:** ### Conventions
| Element | Rule | | Element | Rule |
|---|---| |:---|:---|
| measurement (table) | The node's `softwareType` (lowercase). | | Measurement (table name) | The node's `softwareType`, lowercase |
| tag-set | Low-cardinality identity: `id`, `softwareType`, location-style tags. **Never** raw measurement values. | | 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). | | Field set | Numeric values only. Keys flatten `<type>_<variant>_<position>` (underscore, not dot &mdash; InfluxDB constraint) |
| timestamp | Nanoseconds. Set by `outputUtils` from the node's clock. | | Timestamp | Nanoseconds. Set by `outputUtils` from the node's clock |
See [InfluxDB schema design](concepts/influxdb-schema-design) for the cardinality discipline. See [InfluxDB Schema Design](Concept-InfluxDB-Schema-Design) for cardinality discipline.
## Port 2 — Registration / control ---
**Purpose:** upward `child.register` at startup; later, internal control msgs (the registry-driven command replies). ## Port 2 &mdash; Registration / control
**Shape:** Purpose: upward `child.register` at startup; later, internal control msgs.
Shape:
```json ```json
{ {
"topic": "child.register", "topic": "child.register",
"payload": { "payload": {
"ref": <node reference>, "ref": "<node reference>",
"softwareType": "machine", "softwareType": "machine",
"config": { ... } "config": { }
} }
} }
``` ```
**Trigger:** the nodeClass adapter emits `child.register` on `init` if a `parent` is configured. The parent's `commandRegistry` dispatches into `ChildRouter.onRegister(...)`. Trigger: the nodeClass adapter emits `child.register` on init if a parent is configured. Parent's `commandRegistry` dispatches into `ChildRouter.onRegister(...)`.
The legacy alias `registerChild` is still accepted; it logs a one-time deprecation warning on first use.
---
## The output composition pipeline ## The output composition pipeline
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant tick as Tick (1 Hz) autonumber
participant tick as Tick (1 Hz) or event source
participant sc as specificClass participant sc as specificClass
participant mc as MeasurementContainer participant mc as MeasurementContainer
participant ou as outputUtils participant ou as outputUtils
participant ports as Ports 0 / 1 participant ports as Ports 0 / 1
tick->>sc: tick() tick->>sc: tick() OR emit('output-changed')
sc->>sc: concern modules update mc + state sc->>sc: concern modules update mc + state
sc->>ou: getOutput() snapshot sc->>ou: getOutput() snapshot
ou->>ou: diff vs last ou->>ou: diff vs last snapshot
alt no change alt no change
ou-->>sc: skip ou-->>sc: skip
else change else change
ou->>ports: Port 0 JSON delta ou->>ports: Port 0 &mdash; JSON delta
ou->>ports: Port 1 line protocol ou->>ports: Port 1 &mdash; line protocol
end end
``` ```
`outputUtils` is the single place the platform serialises state. Never write directly to `node.send` from specificClass — go through `outputUtils.formatMsg`. > [!CAUTION]
> Never write directly to `node.send` from specificClass. Go through `outputUtils.formatMsg`. Direct sends bypass delta compression and flood downstream nodes.
## InfluxDB layout ---
Recommended schema for EVOLV's data: ## InfluxDB schema layout (recommended)
| InfluxDB element | Maps to | | InfluxDB element | Maps to |
|---|---| |:---|:---|
| Database / bucket | One per plant (or per environment: `evolv_dev`, `evolv_prod`). | | Database / bucket | One per plant, or per environment: `evolv_dev`, `evolv_prod` |
| Measurement (table) | Node softwareType (`rotatingMachine`, `pumpingStation`, …). | | Measurement (table) | Node `softwareType` |
| Tags | `id` (instance id), `softwareType`, `area`, `processCell`, `unit` (for hierarchical drill-down). | | Tags | `id`, `softwareType`, `area`, `processCell`, `unit` (for hierarchical drill-down) |
| Fields | Numeric series — every key from `getOutput()` that has a numeric value, flattened with `_`. | | Fields | Numeric series &mdash; every numeric key from `getOutput()`, flattened with `_` |
| Retention | Hot bucket: 7 days @ 1 s. Cold bucket: 1 year @ 1 min downsample. | | Retention &mdash; hot | 7 days at 1 s |
| Retention &mdash; cold | 1 year at 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. > [!WARNING]
> Cardinality discipline. Keep tag sets stable. Do not put `state` (string) as a tag &mdash; emit it as a field with a code (`state_code=2`). High-cardinality tags fragment InfluxDB's index and degrade query performance dramatically.
See [InfluxDB Schema Design](Concept-InfluxDB-Schema-Design) for full guidance.
---
## FlowFuse dashboard wiring ## 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). Port 0 is the natural source for FlowFuse `ui-chart` widgets &mdash; the delta-compressed JSON maps cleanly to `msg.topic` (series label) plus `msg.payload` (y-value).
Layout rule (from `.claude/rules/node-red-flow-layout.md` §4):
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). - 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. - A trend-feeder function splits Port-0 deltas into per-series outputs.
- The chart's `category: "topic"` + `categoryType: "msg"` plots one series per unique `msg.topic`. - Each output wires to one chart. The chart's `category: "topic"` and `categoryType: "msg"` plot one series per unique `msg.topic`.
```mermaid ```mermaid
flowchart LR flowchart LR
p0[(Port 0)]:::p0 p0[("Port 0")]
split[trend-feeder<br/>function (N outputs)]:::tier2 split["trend-feeder &mdash; function (N outputs)"]
chart1[ui-chart: flow]:::neutral chart1["ui-chart: flow"]
chart2[ui-chart: power]:::neutral chart2["ui-chart: power"]
p0 --> split p0 --> split
split --> chart1 split --> chart1
split --> chart2 split --> chart2
classDef p0 fill:#86bbdd class p0 p0c
class split tier2
class chart1,chart2 neutral
classDef p0c fill:#0c99d9,color:#fff
classDef tier2 fill:#86bbdd,color:#000 classDef tier2 fill:#86bbdd,color:#000
classDef neutral fill:#dddddd classDef neutral fill:#dddddd
``` ```
See [FlowFuse ui-chart manual](Manual-NodeRED-Flowfuse-Ui-Chart-Manual) for the required chart properties.
---
## Grafana dashboard provisioning ## Grafana dashboard provisioning
`dashboardAPI` consumes registrations and emits Grafana dashboard JSON via HTTP. Wiring: `dashboardAPI` consumes registrations and emits Grafana dashboard JSON via HTTP.
```mermaid ```mermaid
flowchart LR flowchart LR
evolv[EVOLV node<br/>any softwareType]:::tier3 evolv["EVOLV node (any softwareType)"]
dash[dashboardAPI]:::util dash[dashboardAPI]
grafana[(Grafana HTTP API<br/>POST /api/dashboards/db)]:::ext grafana[("Grafana HTTP API &mdash; POST /api/dashboards/db")]
evolv -->|child.register| dash evolv -- child.register --> dash
dash -->|composed JSON| grafana dash -- composed JSON --> grafana
class evolv tier3
class dash util
class grafana ext
classDef tier3 fill:#50a8d9,color:#000 classDef tier3 fill:#50a8d9,color:#000
classDef util fill:#dddddd classDef util fill:#dddddd
classDef ext fill:#fff2cc classDef ext fill:#fff2cc,color:#000
``` ```
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. 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 via `config.grafanaConnector.bearerToken`.
## Common debug recipes ---
| Symptom | First check | ## Debug recipes
|---|---|
| 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. | | Symptom | First thing to check |
| 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). | | InfluxDB rows missing for a node | Port 1 wired to an `influxdb out` node? Tap Port 1 with a debug node to verify line-protocol output |
| 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. | | Dashboard widgets stuck on `n/a` | Port 0 reaching the trend-feeder? Many widgets need `msg.topic` set for series labelling |
| Grafana dashboard not created on plant boot | Inspect dashboardAPI's HTTP response. Check the bearer token + base URL in its config. | | `child.register` not arriving | Tap Port 2 with debug. Confirm parent's `commandRegistry` accepts `child.register` (or `registerChild` alias) |
| Too many InfluxDB writes | Likely a tick-driven debug node bypassed the delta filter. Confirm `outputUtils.checkForChanges()` is firing |
| Grafana dashboard not created on boot | Inspect dashboardAPI's HTTP response. Check bearer token + base URL in its config |
| High cardinality alarm in InfluxDB | A string value is being written as a tag (probably `state` or similar). Move it to a field |
> [!CAUTION]
> Never ship `enableLog: 'debug'` in a demo. Fills the container log within seconds and obscures real errors. Use only for live debugging.
---
## Related pages ## Related pages
- [Architecture](Architecture) — output port wiring in the 3-tier code | Page | Why |
- [Topic-Conventions](Topic-Conventions) — what topics map to what fields |:---|:---|
- [InfluxDB schema design](concepts/influxdb-schema-design) — cardinality discipline | [Architecture](Architecture) | Output port wiring in the three-tier model |
| [Topic Conventions](Topic-Conventions) | What topics map to what fields |
| [Topology Patterns](Topology-Patterns) | Typical telemetry flows |
| [InfluxDB Schema Design](Concept-InfluxDB-Schema-Design) | Cardinality discipline |
| [FlowFuse ui-chart manual](Manual-NodeRED-Flowfuse-Ui-Chart-Manual) | Required chart properties |

View File

@@ -1,80 +1,265 @@
# Topic Conventions # Topic Conventions
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`** ![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue)
![source](https://img.shields.io/badge/source-CONTRACTS.md_%C2%A71-orange)
Naming rules, unit policy, and S88 colour palette. Source of truth: `.claude/refactor/CONTRACTS.md` §1. > [!NOTE]
> Every `msg.topic` in EVOLV uses one of six prefixes. The prefix tells you the kind (setter vs trigger vs data vs query vs lifecycle vs event), not the target. Units are coerced before handlers run. S88 colours are mandatory in every diagram. Source of truth: `.claude/refactor/CONTRACTS.md` §1.
## Topic prefixes ---
Every topic is `<prefix>.<verb>` lowercase. Five prefixes only. ## The six prefixes
```mermaid ```mermaid
flowchart LR flowchart LR
ui[UI / parent / driver]:::neutral ui["UI / parent / driver / function node"]
node[Node]:::tier3 node[Node]
child[Child]:::tier1 child["Child node"]
ext["external consumers"]
ui -->|set.x / cmd.x| node ui -- "set. / cmd. / query." --> node
node -->|evt.x| ui node -. "evt." .-> ui
child -->|data.x| node node -. "evt." .-> ext
node -->|data.x| child child -- "data." --> node
child -->|child.register| node node -- "data." --> child
child <-->|child.register| node
class ui,ext neutral
class node tier3
class child tier1
classDef neutral fill:#dddddd classDef neutral fill:#dddddd
classDef tier3 fill:#50a8d9,color:#000 classDef tier3 fill:#50a8d9,color:#000
classDef tier1 fill:#a9daee,color:#000 classDef tier1 fill:#a9daee,color:#000
``` ```
| Prefix | Direction | Semantics | Examples | ### Inbound (the node accepts on its input)
|---|---|---|---|
| `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:** | Prefix | Idempotent | Meaning | Examples |
- ❌ 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`. | `set.<noun>` | Yes | Setter. Replaces a state value with the supplied payload. Repeating with the same payload does nothing extra. | `set.mode`, `set.scaling`, `set.demand`, `set.inflow` |
- ❌ Per-node prefixes (`pump.set.demand`). The prefix is the *kind*, not the *target*. | `cmd.<verb>` | No | Imperative action. Triggers a transition or sequence. Repeating triggers it again (or is rejected). | `cmd.startup`, `cmd.shutdown`, `cmd.estop`, `cmd.calibrate` |
| `data.<noun>` | n/a (values flow) | Bulk data input. Sensor readings, measurement values, raw streams. The node consumes them. | `data.measurement`, `data.flow`, `data.pressure` |
| `query.<noun>` | Yes (read-only) | Synchronous query. The node responds on the same msg (or a sibling output). For dashboards / debug. | `query.curves`, `query.cog`, `query.snapshot` |
| `child.<verb>` | n/a (plumbing) | Parent / child plumbing. Routed via Port 2. | `child.register`, `child.unregister` |
## Alias deprecation ### Outbound (the node emits)
Legacy topic names (pre-refactor) are still accepted as aliases. The current alias map per node lives in `src/commands/index.js`. Common aliases: | Prefix | Meaning | Where it appears |
|:---|:---|:---|
| `evt.<noun>` | Event. A fact about something that just happened. Fire-and-forget &mdash; no consumer required. | `msg.topic` on Port 0; also fired on `this.emitter` for sibling modules |
The default measurement output (delta-compressed payload from `outputUtils.formatMsg`) keeps `msg.topic = config.general.name` per existing convention. `evt.*` is for additional event-shaped emissions, not the per-tick measurement stream.
> [!TIP]
> The prefix is the kind, never the target. Don't write `pump.set.demand` &mdash; write `set.demand` and let routing handle which pump. The prefix system says explicitly what the message does; the target is identified by node id, not by topic.
> [!CAUTION]
> One topic, two effects is a bug magnet. A topic like `setStartup` that both sets a flag and triggers startup should be split into `set.<noun>` and `cmd.<verb>`.
---
## Aliases and deprecation
Each `commands/index.js` declares the canonical name as `topic` and lists pre-refactor names in `aliases`. First use of each alias logs a one-time deprecation warning. Aliases are removed in Phase 7 after one release cycle.
```js
{
topic: 'set.mode',
aliases: ['setMode', 'changemode'],
payloadSchema: { type: 'string' },
description: 'Switch the node between auto and manual control modes.',
handler: handlers.setMode,
}
```
### Common alias map
| Canonical | Legacy aliases | | Canonical | Legacy aliases |
|---|---| |:---|:---|
| `set.mode` | `setMode` | | `set.mode` | `setMode`, `changemode` |
| `set.demand` | `Qd`, `setDemand` | | `set.demand` | `Qd`, `setDemand` |
| `cmd.startup` | `execSequence` with `payload.action='startup'` | | `cmd.startup` | `execSequence` (with `payload.action='startup'`) |
| `cmd.shutdown` | `execSequence` with `payload.action='shutdown'` | | `cmd.shutdown` | `execSequence` (with `payload.action='shutdown'`) |
| `child.register` | `registerChild` | | `child.register` | `registerChild` |
| `data.pressure` | `pressure` | | `data.pressure` | `pressure` |
| `data.flow` | `flow` | | `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. > [!IMPORTANT]
> Update integrations to canonical names before Phase 7 ships. Aliases work today; they will be removed next major release.
## 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). ## Payload schemas
`payloadSchema.type` accepts six values. Source: `.claude/refactor/CONTRACTS.md` §4.
| Type | Meaning |
|:---|:---|
| `'string'` | `typeof payload === 'string'` |
| `'number'` | `typeof payload === 'number'` |
| `'boolean'` | `typeof payload === 'boolean'` |
| `'object'` | Non-null object. Optional `properties: { key: 'typeName' }` enforces per-key `typeof` (missing keys allowed) |
| `'any'` | Anything passes. Use when handler accepts heterogeneous payloads |
| `'none'` | Trigger-only. Handler invoked regardless of payload. If `msg.payload` is anything but `undefined` / `null`, registry logs a `warn` and still invokes the handler |
---
## Unit coercion (pre-dispatch)
A descriptor for a numeric setter or data topic may declare:
```js
{
topic: 'set.demand',
units: { measure: 'volumeFlowRate', default: 'm3/h' },
payloadSchema: { type: 'number' },
handler: handlers.setDemand,
}
```
```mermaid ```mermaid
flowchart LR flowchart LR
ui[UI message<br/>e.g. 50 m³/h]:::neutral in["Inbound msg &mdash; payload=50, unit='m3/h'"]
coerce[unit coercion<br/>m³/h → m³/s]:::tier1 parse["Extract value+unit &mdash; 3 payload shapes accepted"]
sc[specificClass<br/>canonical m³/s]:::tier3 convert["convert(value).from(unit).to(default)"]
out[output<br/>renders back to m³/h]:::tier2 handler["Handler receives msg.payload = canonical number, msg.unit = units.default"]
ui --> coerce --> sc --> out in --> parse --> convert --> handler
class in,handler neutral
class parse,convert step
classDef neutral fill:#dddddd classDef neutral fill:#dddddd
classDef tier1 fill:#a9daee,color:#000 classDef step fill:#a9daee,color:#000
classDef tier3 fill:#50a8d9,color:#000
classDef tier2 fill:#86bbdd,color:#000
``` ```
### Three accepted payload shapes
| Shape | Example |
|:---|:---|
| Plain number | `msg.payload = 50; msg.unit = 'l/s'` |
| Object with explicit unit | `msg.payload = { value: 50, unit: 'l/s' }` |
| Object without unit (falls back to `msg.unit`) | `msg.payload = { value: 50 }; msg.unit = 'l/s'` |
### Behaviour on unit mismatch
| Situation | What the registry does |
|:---|:---|
| No unit supplied | Silently assume `units.default` |
| Unit recognised + correct measure | Convert and rewrite payload |
| Unit recognised, wrong measure | Log `warn` with accepted-unit list; fall through |
| Unit unrecognised | Log `warn` with accepted-unit list; fall through |
The handler always sees a plain number in `units.default`. Source: `.claude/refactor/CONTRACTS.md` §4 ("Determine the unit-of-record").
---
## S88 colour palette
Every Mermaid diagram, every Node-RED node editor colour, every FlowFuse dashboard group uses this palette. Source: `.claude/rules/node-red-flow-layout.md` §14.
| Hex | S88 level | Used by |
|:---|:---|:---|
| `#0f52a5` | Area | Reserved &mdash; not used yet |
| `#0c99d9` | Process Cell | pumpingStation |
| `#50a8d9` | Unit | MGC, VGC, reactor, settler, monster |
| `#86bbdd` | Equipment Module | rotatingMachine, valve, diffuser |
| `#a9daee` | Control Module | measurement |
| `#dddddd` | Utility / neutral | dashboardAPI, helper functions |
> [!WARNING]
> Known palette outliers (pending cleanup, tracked in `.claude/refactor/OPEN_QUESTIONS.md`):
> - `settler` editor colour is `#e4a363` (orange) &mdash; should be `#50a8d9`.
> - `monster` editor colour is `#4f8582` (teal) &mdash; should be `#50a8d9`.
> - `diffuser` registers under category `'wbd typical'` instead of `'EVOLV'`.
>
> Wiki diagrams use the correct S88 colour regardless of the editor mismatch.
---
## Measurement key shape
`MeasurementContainer` stores values under composite keys:
```
<type>.<variant>.<position>.<childId>
| | | |
| | | +-- child id (or 'default' for internal computations)
| | +------------- 'upstream' / 'downstream' / 'atequipment' / ... (always lowercase in keys)
| +------------------------ 'measured' / 'predicted' / 'setpoint' / 'min' / 'max'
+-------------------------------- 'flow' / 'pressure' / 'power' / 'temperature' / 'level'
```
### Examples
| Key | Meaning |
|:---|:---|
| `flow.measured.downstream.dashboard-sim-downstream` | Externally measured downstream flow |
| `flow.predicted.downstream.default` | The node's own prediction |
| `power.measured.atequipment.default` | Measured power at the equipment |
| `pressure.measured.upstream.<childId>` | Pressure from a specific measurement child |
> [!WARNING]
> `position` is always lowercase in keys. The configuration form may use mixed case (`atEquipment`); the container normalises. Don't rely on the casing the form shows you.
---
## Status badge
`statusBadge.compose(state)` returns `{fill, shape, text}` for `node.status(...)`.
```js
const { statusBadge } = require('generalFunctions');
statusBadge.compose(['OK', `flow=${flow.toFixed(1)} m3/h`])
statusBadge.error(message)
statusBadge.idle(label)
```
| `fill` | `shape` | Meaning |
|:---|:---|:---|
| `blue` | `dot` | Normal / running |
| `green` | `dot` | Success / running optimally |
| `yellow` | `ring` | Degraded &mdash; attention needed |
| `red` | `ring` | Fault &mdash; operator action required |
| `grey` | `dot` | Initialising / no data yet |
> [!IMPORTANT]
> Badges live in the domain, not the adapter. `nodeClass` calls `this.source.getStatusBadge()` once per second; the domain owns the shape. Source: `.claude/refactor/CONTRACTS.md` §7.
---
## HealthStatus
A standardised shape for nodes that compute prediction quality, drift, or general health. Source: `.claude/refactor/CONTRACTS.md` §9.
```json
{
"level": 1,
"flags": ["pressure_init_warming"],
"message": "warmup phase",
"source": "rotatingMachine#pump-A"
}
```
| Field | Type | Meaning |
|:---|:---|:---|
| `level` | `0 \| 1 \| 2 \| 3` | 0 = fine, 3 = unusable |
| `flags` | `string[]` | Machine-readable tags (e.g. `no_pressure_input`) |
| `message` | `string` | Single-line human summary |
| `source` | `string \| null` | `<nodeType>#<id>` &mdash; for routing UI / alarm correlation |
Helpers compose multiple sub-statuses (flow drift + power drift + pressure init) into one node-level status.
---
## Canonical units (used in code)
Every node declares its `UnitPolicy`: what canonical (internal) unit it uses and what output unit to render to. Source: `.claude/refactor/CONTRACTS.md` §6.
| Quantity | Canonical (internal) | Common output | | Quantity | Canonical (internal) | Common output |
|---|---|---| |:---|:---|:---|
| Flow | `m3/s` | `m3/h`, `l/s`, `gpm` | | Flow | `m3/s` | `m3/h`, `l/s`, `gpm` |
| Pressure | `Pa` | `bar`, `mbar`, `kPa` | | Pressure | `Pa` | `bar`, `mbar`, `kPa` |
| Power | `W` | `kW`, `MW` | | Power | `W` | `kW`, `MW` |
@@ -82,104 +267,26 @@ flowchart LR
| Level | `m` | `m`, `cm` | | Level | `m` | `m`, `cm` |
| Volume | `m3` | `m3`, `l` | | Volume | `m3` | `m3`, `l` |
**Rule:** anywhere in `specificClass`, treat values as canonical. Conversion happens at the boundary (input coercion + output formatting). > [!TIP]
> Inside `specificClass`, treat values as canonical. Conversion happens at the boundary: input coercion by the commands registry; output formatting by `outputUtils`.
## S88 colour palette ### Dual access form
```mermaid `UnitPolicy` exposes each accessor as both a method and a frozen property bag.
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 ```js
UT -.- A policy.canonical('flow') // 'm3/s' (method form)
policy.canonical.flow // 'm3/s' (property form &mdash; preferred in hot paths)
classDef area fill:#0f52a5,color:#fff policy.output.pressure // 'mbar'
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
classDef neutral fill:#dddddd,color:#000
``` ```
| Hex | S88 level | Used by | ---
|---|---|---|
| `#0f52a5` | Area | (reserved — not in use yet) |
| `#0c99d9` | Process Cell | pumpingStation |
| `#50a8d9` | Unit | machineGroupControl, valveGroupControl, reactor, settler, monster |
| `#86bbdd` | Equipment Module | rotatingMachine, valve, diffuser |
| `#a9daee` | Control Module | measurement |
| `#dddddd` | Utility / neutral | dashboardAPI, helper function nodes |
**Rule:** every Mermaid diagram in this wiki, every Node-RED node's editor colour, and every dashboard grouping uses this palette. Source of truth: `.claude/rules/node-red-flow-layout.md` §14.
**Known outliers** (pending cleanup, tracked in OPEN_QUESTIONS.md):
- `settler` editor colour is `#e4a363` (orange) — should be `#50a8d9`.
- `monster` editor colour is `#4f8582` (teal) — should be `#50a8d9`.
- `diffuser` editor colour was missing pre-refactor; now `#86bbdd`.
- `dashboardAPI` registers under category `'wbd typical'` instead of `'EVOLV'`.
## Measurement key shape
The `MeasurementContainer` stores values under composite keys:
```
<type>.<variant>.<position>.<childId>
```
| Segment | Examples |
|---|---|
| `type` | `flow`, `pressure`, `power`, `temperature`, `level` |
| `variant` | `measured`, `predicted`, `setpoint`, `min`, `max` |
| `position` | `upstream`, `downstream`, `atequipment`, `inlet`, `outlet` (always lowercase in keys) |
| `childId` | The registering child's id, OR `default` for internal computations |
Examples:
- `flow.measured.downstream.dashboard-sim-downstream` — externally measured downstream flow.
- `flow.predicted.downstream.default` — node's own prediction.
- `power.measured.atequipment.default` — measured power at the equipment.
- `pressure.measured.upstream.<childId>` — pressure from a specific measurement child.
**Gotcha:** `position` is **always lowercase in keys**. The configuration form may use mixed case (`atEquipment`); the container normalises.
## Status badge
`statusBadge.compose(state)` returns `{fill, shape, text}` for `node.status(...)`:
| level | shape | fill | meaning |
|---|---|---|---|
| `info` | dot | blue | normal operation |
| `success` | dot | green | success / running optimally |
| `warning` | ring | yellow | degraded, attention needed |
| `error` | ring | red | fault, operator action required |
| `pending` | dot | grey | initialising / no data yet |
Composer reads from `HealthStatus.level` (03) — kept centralised so all nodes show consistent badges.
## HealthStatus shape
```json
{
"level": 0,
"flags": ["pressure_init_warming"],
"message": "warmup phase",
"source": "rotatingMachine#pump-A"
}
```
| Field | Range | Meaning |
|---|---|---|
| `level` | 0..3 | 0 = healthy, 1 = degraded, 2 = warning, 3 = error |
| `flags` | string[] | Machine-readable reason codes. |
| `message` | string | Human-readable summary (one line). |
| `source` | string | `<nodeType>#<id>` — for routing UI / alarm correlation. |
## Related pages ## Related pages
- [Architecture](Architecture) — generalFunctions API surface | Page | Why |
- [Telemetry](Telemetry) — Port-1 InfluxDB schema (where these conventions appear in stored data) |:---|:---|
- [Topology-Patterns](Topology-Patterns) — what topics flow where | [Architecture](Architecture) | Where these conventions are implemented in code |
| [Telemetry](Telemetry) | What these keys look like in InfluxDB |
| [Topology Patterns](Topology-Patterns) | Which topics flow between which nodes |
| [Glossary](Glossary) | Domain terms used here |

View File

@@ -1,32 +1,49 @@
# Topology Patterns # Topology Patterns
> **Reflects code as of `9ab9f6b` · regenerated `2026-05-11`** ![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue)
![verified](https://img.shields.io/badge/edges-verified_against_configure()-brightgreen)
Typical plant configurations and how nodes wire together. Each pattern is **verified** against the corresponding nodes' `configure()` declarations. > [!NOTE]
> Five canonical plant configurations and one worked example that combines them. Every edge in every diagram was checked against the parent's `configure()` declaration in source. Use these as templates when wiring your own plant.
## 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. ## Pattern index
| Pattern | When to use it |
|:---|:---|
| [1. Pumping station with grouped pumps](#1-pumping-station-with-grouped-pumps) | Lift station, single basin, N pumps load-shared |
| [2. Reactor + diffuser + settler train](#2-reactor--diffuser--settler-train) | Biological treatment line |
| [3. Valve group on a distribution manifold](#3-valve-group-on-a-distribution-manifold) | Multi-valve flow split with upstream flow context |
| [4. Composite sampling](#4-composite-sampling) | Flow-proportional grab samples for lab analysis |
| [5. Dashboard provisioning](#5-dashboard-provisioning) | Auto-generated Grafana dashboards |
| [Worked example &mdash; small WWTP](#worked-example--small-wwtp) | All five patterns combined |
---
## 1. Pumping station with grouped pumps
The canonical wet-well lift station: one basin model, one demand controller (`pumpingStation`), one load-sharing coordinator (`machineGroupControl`), N pumps (`rotatingMachine` &times; N), measurements for level + flow + per-pump pressure.
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph PC["Process Cell"] subgraph PC["Process Cell"]
ps[pumpingStation]:::pc ps[pumpingStation]
end end
subgraph UN["Unit"] subgraph UN["Unit"]
mgc[machineGroupControl]:::unit mgc[machineGroupControl]
end end
subgraph EM["Equipment"] subgraph EM["Equipment Module"]
rmA[rotatingMachine A]:::equip rmA[rotatingMachine A]
rmB[rotatingMachine B]:::equip rmB[rotatingMachine B]
rmC[rotatingMachine C]:::equip rmC[rotatingMachine C]
end end
subgraph CM["Control Module"] subgraph CM["Control Module"]
ml[measurement: level]:::ctrl ml["measurement &mdash; level"]
mfin[measurement: inflow]:::ctrl mfin["measurement &mdash; inflow"]
mpA[measurement: pressure A]:::ctrl mpA["measurement &mdash; pressure A"]
mpB[measurement: pressure B]:::ctrl mpB["measurement &mdash; pressure B"]
mpC[measurement: pressure C]:::ctrl mpC["measurement &mdash; pressure C"]
end end
ps --> mgc ps --> mgc
@@ -40,82 +57,102 @@ flowchart TB
mpB -. data .-> rmB mpB -. data .-> rmB
mpC -. data .-> rmC mpC -. data .-> rmC
classDef pc fill:#0c99d9,color:#fff class ps pc
classDef unit fill:#50a8d9,color:#000 class mgc unit
classDef equip fill:#86bbdd,color:#000 class rmA,rmB,rmC equip
classDef ctrl fill:#a9daee,color:#000 class ml,mfin,mpA,mpB,mpC ctrl
classDef pc fill:#0c99d9,color:#fff,stroke:#075a82,stroke-width:2px
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef equip fill:#86bbdd,color:#000,stroke:#5a90b2,stroke-width:2px
classDef ctrl fill:#a9daee,color:#000,stroke:#76b7d4,stroke-width:2px
``` ```
**Data flow:** ### 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:** | Stage | What happens |
- 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. | Basin integration | `pumpingStation` integrates basin volume from inflow / outflow rates |
| Demand computation | `pumpingStation` computes a demand setpoint and dispatches it to `machineGroupControl` |
| Per-pump operating point | `machineGroupControl` solves a per-pump operating point using each pump's characteristic curve plus measured upstream pressure |
| Pump dispatch | Each `rotatingMachine` runs its own FSM (`idle` &rarr; `warmingup` &rarr; `operational` &rarr; `coolingdown` &rarr; `emergencystop`, plus `accelerating` / `decelerating`) and predicts flow + power from speed + pressure |
## Pattern 2 — Reactor / settler train with aeration ### Variants
Biological treatment line. Reactor runs ASM kinetics, diffuser drives O₂ transfer, settler clarifies effluent and returns sludge via a return pump. | Variant | How to wire |
|:---|:---|
| Single pump (no MGC) | `pumpingStation.configure()` accepts `machine` directly &mdash; skip the MGC and parent the `rotatingMachine` under `pumpingStation` |
| Cascaded stations | `pumpingStation.configure()` accepts `pumpingstation` as a child &mdash; downstream PS registers upstream PS to read its predicted outflow |
---
## 2. Reactor + diffuser + settler train
Biological treatment line. `reactor` runs ASM kinetics (CSTR or PFR engine, set via `config.reactor_type`). `diffuser` injects OTR. `settler` clarifies the effluent and drives a return pump.
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph UN["Unit"] subgraph UN["Unit"]
reactor[reactor]:::unit reactor[reactor]
settler[settler]:::unit settler[settler]
end end
subgraph EM["Equipment"] subgraph EM["Equipment Module"]
diff[diffuser]:::equip diff[diffuser]
rp[rotatingMachine<br/>return pump]:::equip rp["rotatingMachine &mdash; return pump"]
end end
subgraph CM["Control Module"] subgraph CM["Control Module"]
mt[measurement: temperature]:::ctrl mt["measurement &mdash; temperature"]
mdo[measurement: dissolved O₂]:::ctrl mdo["measurement &mdash; dissolved O2"]
mts[measurement: TSS]:::ctrl mts["measurement &mdash; TSS"]
end end
reactor ==stateChange==> settler reactor ==stateChange==> settler
diff -. OTR data .-> reactor diff -. OTR data .-> reactor
settler -->|return pump child| rp settler -->|return pump| rp
mt -. data .-> reactor mt -. data .-> reactor
mdo -. data .-> reactor mdo -. data .-> reactor
mts -. data .-> settler mts -. data .-> settler
mdo -. data .-> diff mdo -. data .-> diff
classDef unit fill:#50a8d9,color:#000 class reactor,settler unit
classDef equip fill:#86bbdd,color:#000 class diff,rp equip
classDef ctrl fill:#a9daee,color:#000 class mt,mdo,mts ctrl
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef equip fill:#86bbdd,color:#000,stroke:#5a90b2,stroke-width:2px
classDef ctrl fill:#a9daee,color:#000,stroke:#76b7d4,stroke-width:2px
``` ```
**Data flow:** ### Two non-standard wirings
- `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:** > [!IMPORTANT]
- Reactor supports two kinetics engines: CSTR (continuous-stirred tank) and PFR (plug-flow). Set via `config.reactor_type`. > `diffuser` &rarr; `reactor` is data-only. Diffuser fires `data.otr` on its emitter; reactor subscribes via `emitter.on('otr', ...)`. There is no `child.register` handshake between them. See `nodes/reactor/src/specificClass.js` `configure()`.
- 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 > [!IMPORTANT]
> `reactor` &rarr; `settler` is a `stateChange` subscription, not a parent / child edge. Settler's `_connectReactor` attaches `emitter.on('stateChange', ...)` to pull effluent composition from the upstream reactor. The `reactor` softwareType is registered as a child of settler even though the reactor is semantically upstream.
Multi-valve flow distribution. VGC computes per-valve K_v shares to satisfy a target distribution while respecting upstream flow availability. > [!CAUTION]
> DO setpoint feedback is not automatic. A measured-DO &rarr; diffuser-airflow loop must be closed externally (a function node) or via a `valveGroupControl` upstream of an airflow valve.
---
## 3. Valve group on a distribution manifold
Multi-valve flow distribution. `valveGroupControl` computes per-valve K_v shares to satisfy a target split while respecting upstream flow availability.
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph PC["Process Cell"] subgraph PC["Process Cell"]
ps[pumpingStation<br/>upstream flow source]:::pc ps["pumpingStation &mdash; upstream flow source"]
end end
subgraph UN["Unit"] subgraph UN["Unit"]
vgc[valveGroupControl]:::unit vgc[valveGroupControl]
end end
subgraph EM["Equipment"] subgraph EM["Equipment Module"]
vA[valve A]:::equip vA[valve A]
vB[valve B]:::equip vB[valve B]
vC[valve C]:::equip vC[valve C]
end end
ps -. flow source .-> vgc ps -. flow source .-> vgc
@@ -123,95 +160,125 @@ flowchart TB
vgc --> vB vgc --> vB
vgc --> vC vgc --> vC
classDef pc fill:#0c99d9,color:#fff class ps pc
classDef unit fill:#50a8d9,color:#000 class vgc unit
classDef equip fill:#86bbdd,color:#000 class vA,vB,vC equip
classDef pc fill:#0c99d9,color:#fff,stroke:#075a82,stroke-width:2px
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef equip fill:#86bbdd,color:#000,stroke:#5a90b2,stroke-width:2px
``` ```
**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". > [!IMPORTANT]
> VGC's child types are unusual. `valveGroupControl.configure()` registers five softwareTypes:
> - `valve` &mdash; actual S88 child relationship (VGC controls these)
> - `machine`, `machinegroup`, `pumpingstation`, `valvegroupcontrol` &mdash; flow sources. VGC reads upstream flow availability when computing splits. Semantic is "VGC knows about this flow producer", not "VGC controls it".
>
> See `nodes/valveGroupControl/src/specificClass.js` lines 13&ndash;49.
## 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. ## 4. Composite sampling
Virtual sensor for downstream lab analysis. `monster` accumulates samples in a bucket based on integrated flow &mdash; a flow-proportional grab sample.
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph UN["Unit"] subgraph UN["Unit"]
monster[monster]:::unit monster[monster]
end end
subgraph CM["Control Module"] subgraph CM["Control Module"]
mflow[measurement: flow<br/>assetType MUST be 'flow']:::ctrl mflow["measurement &mdash; flow (assetType MUST be 'flow')"]
mq[measurement: any quality<br/>e.g. NH, COD]:::ctrl mq["measurement &mdash; any quality (e.g. NH4, COD)"]
end end
mflow -. data .-> monster mflow -. data .-> monster
mq -. data .-> monster mq -. data .-> monster
classDef unit fill:#50a8d9,color:#000 class monster unit
classDef ctrl fill:#a9daee,color:#000 class mflow,mq ctrl
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef ctrl fill:#a9daee,color:#000,stroke:#76b7d4,stroke-width:2px
``` ```
**Gotchas:** > [!WARNING]
- `measurement.config.asset.type` MUST be `"flow"` exactly — `"flow-electromagnetic"` or any sub-type is silently ignored by monster's child router. > Two gotchas:
- `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.) > 1. `measurement.config.asset.type` must be exactly `"flow"`. A value like `"flow-electromagnetic"` is silently ignored by monster's child router.
> 2. `monster.config.constraints.flowmeter` exists in the schema but is not forwarded by `buildDomainConfig`. Toggling proportional-vs-time mode has no runtime effect. Tracked in `.claude/refactor/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. ## 5. Dashboard provisioning
`dashboardAPI` doesn't operate on data &mdash; it generates Grafana dashboards. Any node registers via `child.register`; dashboardAPI composes a dashboard JSON from softwareType plus measurements and POSTs to Grafana's HTTP API.
```mermaid ```mermaid
flowchart LR flowchart LR
subgraph EVOLV["EVOLV process nodes"] subgraph EVOLV["EVOLV process nodes (any softwareType)"]
ps[pumpingStation]:::pc direction TB
mgc[machineGroupControl]:::unit ps[pumpingStation]
rm[rotatingMachine]:::equip mgc[machineGroupControl]
rm[rotatingMachine]
end end
subgraph UT["Utility"] subgraph UT["Utility"]
dash[dashboardAPI]:::util dash[dashboardAPI]
end end
grafana[(Grafana<br/>HTTP API)]:::ext grafana[("Grafana HTTP API")]
ps -. child.register .-> dash ps -. child.register .-> dash
mgc -. child.register .-> dash mgc -. child.register .-> dash
rm -. child.register .-> dash rm -. child.register .-> dash
dash -->|POST /api/dashboards/db| grafana dash ==>|POST /api/dashboards/db| grafana
classDef pc fill:#0c99d9,color:#fff class ps pc
classDef unit fill:#50a8d9,color:#000 class mgc unit
classDef equip fill:#86bbdd,color:#000 class rm equip
classDef util fill:#dddddd,color:#000 class dash util
classDef ext fill:#fff2cc,color:#000 class grafana ext
classDef pc fill:#0c99d9,color:#fff,stroke:#075a82,stroke-width:2px
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef equip fill:#86bbdd,color:#000,stroke:#5a90b2,stroke-width:2px
classDef util fill:#dddddd,color:#000,stroke:#a8a8a8,stroke-width:2px
classDef ext fill:#fff2cc,color:#000,stroke:#aa8400,stroke-width:2px
``` ```
**Notes:** | Behaviour | Detail |
- `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. | What it accepts | Any softwareType on `child.register` |
| What it emits | One HTTP POST per registered child, payload from `nodes/dashboardAPI/src/config/templates/<softwareType>.json` |
| Auth | Bearer token in `config.grafanaConnector.bearerToken` (when set) |
| `meta` envelope | `{nodeId, softwareType, uid, title}` for correlating responses |
| Architecture variance | The one node in the platform that does not extend `BaseDomain`. Documented in `.claude/refactor/OPEN_QUESTIONS.md` |
## Putting it all together — example plant ---
A small WWTP combining all patterns: ## Worked example &mdash; small WWTP
All five patterns combined.
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph PC["Process Cell"] subgraph PC["Process Cell"]
ps1[pumpingStation<br/>inlet lift]:::pc ps1["pumpingStation &mdash; inlet lift"]
ps2[pumpingStation<br/>RAS pumping]:::pc ps2["pumpingStation &mdash; RAS pumping"]
end end
subgraph UN["Unit"] subgraph UN["Unit"]
mgc1[MGC inlet]:::unit mgc1["MGC inlet"]
mgc2[MGC RAS]:::unit mgc2["MGC RAS"]
vgc[VGC effluent split]:::unit vgc["VGC effluent split"]
r1[reactor aerobic]:::unit r1["reactor aerobic"]
s1[settler]:::unit s1["settler"]
mon[monster<br/>composite sampler]:::unit mon["monster &mdash; composite sampler"]
end end
subgraph EM["Equipment"] subgraph EM["Equipment Module"]
rm1[pump A]:::equip rm1["pump A"]
rm2[pump B]:::equip rm2["pump B"]
rm3[RAS pump]:::equip rm3["RAS pump"]
d1[diffuser]:::equip d1["diffuser"]
v1[valve 1]:::equip v1["valve 1"]
v2[valve 2]:::equip v2["valve 2"]
end end
ps1 --> mgc1 ps1 --> mgc1
@@ -228,21 +295,45 @@ flowchart TB
vgc --> v1 vgc --> v1
vgc --> v2 vgc --> v2
classDef pc fill:#0c99d9,color:#fff class ps1,ps2 pc
classDef unit fill:#50a8d9,color:#000 class mgc1,mgc2,vgc,r1,s1,mon unit
classDef equip fill:#86bbdd,color:#000 class rm1,rm2,rm3,d1,v1,v2 equip
classDef pc fill:#0c99d9,color:#fff,stroke:#075a82,stroke-width:2px
classDef unit fill:#50a8d9,color:#000,stroke:#2c7ba8,stroke-width:2px
classDef equip fill:#86bbdd,color:#000,stroke:#5a90b2,stroke-width:2px
``` ```
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. | Sub-pattern | Recognise it |
|:---|:---|
| Pumping station with grouped pumps (&times;2) | `ps1 -> mgc1 -> {rm1, rm2}` and `ps2 -> mgc2 -> rm3` |
| Reactor + settler train | `r1 ==stateChange==> s1` plus `d1 -. OTR .-> r1` |
| Valve group on flow source | `ps2 -. flow source .-> vgc -> {v1, v2}` |
| Settler return pump | `s1 -> rm3` |
| Composite sampling | `mon` (would be wired to inflow + quality measurements not drawn) |
Every edge here is reproducible from one of the patterns above.
---
## Anti-patterns ## 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. > [!CAUTION]
- ❌ A `diffuser → reactor` child registration. Diffuser emits OTR via its emitter; reactor subscribes. No `child.register` handshake. > `pumpingStation` &rarr; `valveGroupControl` as a parent / child edge. PS does not register VGC. VGC registers PS as a flow source &mdash; the edge goes the other way semantically.
-`measurement` parented under `dashboardAPI`. dashboardAPI accepts any node for Grafana provisioning, but `measurement` registers with the **process** node it's monitoring, not with dashboardAPI.
> [!CAUTION]
> `diffuser` &rarr; `reactor` as a child registration. Diffuser emits OTR via its emitter; reactor subscribes via `emitter.on`. No `child.register` handshake.
> [!CAUTION]
> `measurement` parented under `dashboardAPI`. dashboardAPI accepts any node for Grafana provisioning, but `measurement` should register with the process node it is monitoring, not with dashboardAPI.
---
## Related pages ## Related pages
- [Home](Home) — top-level node map | Page | Why |
- [Architecture](Architecture) — 3-tier code structure + generalFunctions API |:---|:---|
- [Topic-Conventions](Topic-Conventions) — what topics flow between nodes | [Home](Home) | Top-level node map |
| [Architecture](Architecture) | Three-tier code + generalFunctions API |
| [Topic Conventions](Topic-Conventions) | What topics flow on each edge |
| [Telemetry](Telemetry) | Port 0 / 1 / 2 InfluxDB layout |

View File

@@ -1,10 +1,12 @@
### EVOLV Wiki ### EVOLV Wiki
**Start here** **Start here**
- [Home](Home) - [Home](Home)
- [Getting Started](Getting-Started) - [Getting Started](Getting-Started)
**Reference** **Reference**
- [Architecture](Architecture) - [Architecture](Architecture)
- [Topology Patterns](Topology-Patterns) - [Topology Patterns](Topology-Patterns)
- [Topic Conventions](Topic-Conventions) - [Topic Conventions](Topic-Conventions)
@@ -12,6 +14,7 @@
- [Glossary](Glossary) - [Glossary](Glossary)
**Per-node wikis** **Per-node wikis**
- [pumpingStation](https://gitea.wbd-rd.nl/RnD/pumpingStation/wiki/Home) - [pumpingStation](https://gitea.wbd-rd.nl/RnD/pumpingStation/wiki/Home)
- [machineGroupControl](https://gitea.wbd-rd.nl/RnD/machineGroupControl/wiki/Home) - [machineGroupControl](https://gitea.wbd-rd.nl/RnD/machineGroupControl/wiki/Home)
- [valveGroupControl](https://gitea.wbd-rd.nl/RnD/valveGroupControl/wiki/Home) - [valveGroupControl](https://gitea.wbd-rd.nl/RnD/valveGroupControl/wiki/Home)
@@ -25,21 +28,39 @@
- [dashboardAPI](https://gitea.wbd-rd.nl/RnD/dashboardAPI/wiki/Home) - [dashboardAPI](https://gitea.wbd-rd.nl/RnD/dashboardAPI/wiki/Home)
- [generalFunctions](https://gitea.wbd-rd.nl/RnD/generalFunctions/wiki/Home) - [generalFunctions](https://gitea.wbd-rd.nl/RnD/generalFunctions/wiki/Home)
**Concepts** (domain knowledge) **Domain concepts**
- [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) - [ASM Models](Concept-ASM-Models)
- [BEP gravitation](Finding-BEP-Gravitation-Proof) - [PID Control Theory](Concept-PID-Control-Theory)
- [Curve non-convexity](Finding-Curve-Non-Convexity) - [Pump Affinity Laws](Concept-Pump-Affinity-Laws)
- [NCog behaviour](Finding-NCog-Behavior) - [Settling Models](Concept-Settling-Models)
- [Pump switching stability](Finding-Pump-Switching-Stability) - [Signal Processing — Sensors](Concept-Signal-Processing-Sensors)
- [InfluxDB Schema Design](Concept-InfluxDB-Schema-Design)
- [Wastewater Compliance NL](Concept-Wastewater-Compliance-NL)
- [OT Security IEC 62443](Concept-OT-Security-IEC62443)
**Operations findings**
- [BEP Gravitation Proof](Finding-BEP-Gravitation-Proof)
- [Curve Non-Convexity](Finding-Curve-Non-Convexity)
- [NCog Behaviour](Finding-NCog-Behavior)
- [Pump Switching Stability](Finding-Pump-Switching-Stability)
**Node-RED / FlowFuse manuals**
- [Manual Index](Manual-NodeRED-INDEX)
- [Runtime — Node.js](Manual-NodeRED-Runtime-Node-Js)
- [Function Node Patterns](Manual-NodeRED-Function-Node-Patterns)
- [Messages and Editor Structure](Manual-NodeRED-Messages-And-Editor-Structure)
- [FlowFuse ui-chart](Manual-NodeRED-Flowfuse-Ui-Chart-Manual)
- [FlowFuse ui-button](Manual-NodeRED-Flowfuse-Ui-Button-Manual)
- [FlowFuse ui-gauge](Manual-NodeRED-Flowfuse-Ui-Gauge-Manual)
- [FlowFuse ui-text](Manual-NodeRED-Flowfuse-Ui-Text-Manual)
- [FlowFuse ui-template](Manual-NodeRED-Flowfuse-Ui-Template-Manual)
- [FlowFuse ui-config](Manual-NodeRED-Flowfuse-Ui-Config-Manual)
- [Dashboard Layout](Manual-NodeRED-Flowfuse-Dashboard-Layout-Manual)
- [Widgets Catalog](Manual-NodeRED-Flowfuse-Widgets-Catalog)
**Archive** **Archive**
- [Archive index](Archive)
- [Archive](Archive)

View File

@@ -1,5 +1,11 @@
# Activated Sludge Models (ASM1, ASM2d, ASM3) # Activated Sludge Models (ASM1, ASM2d, ASM3)
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-domain_concept-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
> **Used by**: `biological-process-engineer` agent, `reactor` node, `monster` node > **Used by**: `biological-process-engineer` agent, `reactor` node, `monster` node
> **Validation**: Verified against IWA publications, WaterTAP documentation, and peer-reviewed literature > **Validation**: Verified against IWA publications, WaterTAP documentation, and peer-reviewed literature

View File

@@ -1,5 +1,11 @@
# InfluxDB Time-Series Best Practices # InfluxDB Time-Series Best Practices
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-domain_concept-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
> **Used by**: `telemetry-database` agent, `dashboardAPI` node > **Used by**: `telemetry-database` agent, `dashboardAPI` node
> **Validation**: Verified against InfluxDB official documentation (v1, v2, v3) > **Validation**: Verified against InfluxDB official documentation (v1, v2, v3)

View File

@@ -1,5 +1,11 @@
# OT Security Standards — IEC 62443 & NIST SP 800-82 # OT Security Standards — IEC 62443 & NIST SP 800-82
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-domain_concept-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
> **Used by**: `ot-security-integration` agent > **Used by**: `ot-security-integration` agent
> **Validation**: Verified against IEC 62443 series, NIST SP 800-82, Dragos, and Rockwell Automation publications > **Validation**: Verified against IEC 62443 series, NIST SP 800-82, Dragos, and Rockwell Automation publications

View File

@@ -1,5 +1,11 @@
# PID Control for Process Applications # PID Control for Process Applications
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-domain_concept-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
> **Used by**: `mechanical-process-engineer` agent, `node-red-runtime` agent, `generalFunctions/src/pid/` > **Used by**: `mechanical-process-engineer` agent, `node-red-runtime` agent, `generalFunctions/src/pid/`
> **Validation**: Verified against Astrom & Hagglund (ISA, 2006) and MATLAB/Simulink documentation > **Validation**: Verified against Astrom & Hagglund (ISA, 2006) and MATLAB/Simulink documentation

View File

@@ -1,5 +1,11 @@
# Pump Affinity Laws & Curve Theory # Pump Affinity Laws & Curve Theory
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-domain_concept-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
> **Used by**: `mechanical-process-engineer` agent, `rotatingMachine` node, `pumpingStation` node > **Used by**: `mechanical-process-engineer` agent, `rotatingMachine` node, `pumpingStation` node
> **Validation**: Verified against Engineering Toolbox, Hydraulic Institute standards, and ScienceDirect > **Validation**: Verified against Engineering Toolbox, Hydraulic Institute standards, and ScienceDirect

View File

@@ -1,5 +1,11 @@
# Sludge Settling & Clarifier Models # Sludge Settling & Clarifier Models
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-domain_concept-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
> **Used by**: `biological-process-engineer` agent, `settler` node > **Used by**: `biological-process-engineer` agent, `settler` node
> **Validation**: Verified against Takacs et al. (1991), Vesilind (1968), and Burger-Diehl framework publications > **Validation**: Verified against Takacs et al. (1991), Vesilind (1968), and Burger-Diehl framework publications

View File

@@ -1,5 +1,11 @@
# Sensor Signal Conditioning & Data Quality # Sensor Signal Conditioning & Data Quality
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-domain_concept-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
> **Used by**: `instrumentation-measurement` agent, `measurement` node > **Used by**: `instrumentation-measurement` agent, `measurement` node
> **Validation**: Verified against IEC 61298, sensor manufacturer literature, and signal processing references > **Validation**: Verified against IEC 61298, sensor manufacturer literature, and signal processing references

View File

@@ -1,5 +1,11 @@
# Dutch Wastewater Regulations & Compliance # Dutch Wastewater Regulations & Compliance
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-domain_concept-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
> **Used by**: `commissioning-compliance` agent, `biological-process-engineer` agent > **Used by**: `commissioning-compliance` agent, `biological-process-engineer` agent
> **Validation**: Verified against EU Directive 91/271/EEC, Activiteitenbesluit milieubeheer, and Dutch water authority publications > **Validation**: Verified against EU Directive 91/271/EEC, Activiteitenbesluit milieubeheer, and Dutch water authority publications

View File

@@ -7,6 +7,11 @@ tags: [machineGroupControl, optimization, BEP, brute-force]
sources: [nodes/machineGroupControl/test/integration/distribution-power-table.integration.test.js] sources: [nodes/machineGroupControl/test/integration/distribution-power-table.integration.test.js]
--- ---
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-algorithm_finding-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
# BEP-Gravitation vs Brute-Force Global Optimum # BEP-Gravitation vs Brute-Force Global Optimum
## Claim ## Claim

View File

@@ -7,6 +7,11 @@ tags: [curves, interpolation, C5, non-convex]
sources: [nodes/generalFunctions/datasets/assetData/curves/hidrostal-C5-D03R-SHN1.json] sources: [nodes/generalFunctions/datasets/assetData/curves/hidrostal-C5-D03R-SHN1.json]
--- ---
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-algorithm_finding-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
# Pump Curve Non-Convexity from Sparse Data # Pump Curve Non-Convexity from Sparse Data
## Finding ## Finding

View File

@@ -7,6 +7,11 @@ tags: [rotatingMachine, NCog, BEP, efficiency]
sources: [nodes/rotatingMachine/src/specificClass.js] sources: [nodes/rotatingMachine/src/specificClass.js]
--- ---
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-algorithm_finding-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
# NCog — Normalized Center of Gravity # NCog — Normalized Center of Gravity
## What It Is ## What It Is

View File

@@ -7,6 +7,11 @@ tags: [machineGroupControl, stability, switching]
sources: [nodes/machineGroupControl/test/integration/ncog-distribution.integration.test.js] sources: [nodes/machineGroupControl/test/integration/ncog-distribution.integration.test.js]
--- ---
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-algorithm_finding-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
# Pump Switching Stability # Pump Switching Stability
## Concern ## Concern

View File

@@ -1,5 +1,11 @@
# Node-RED Manual Index # Node-RED Manual Index
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-manual_(third-party)-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
This folder summarizes official Node-RED docs that are relevant to EVOLV node development. This folder summarizes official Node-RED docs that are relevant to EVOLV node development.
## Official Sources ## Official Sources

View File

@@ -1,5 +1,11 @@
# FlowFuse Dashboard Layout Notes (EVOLV Reference) # FlowFuse Dashboard Layout Notes (EVOLV Reference)
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-manual_(third-party)-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
Primary sources: Primary sources:
- https://dashboard.flowfuse.com/ - https://dashboard.flowfuse.com/
- https://dashboard.flowfuse.com/nodes/widgets/ui-chart.html - https://dashboard.flowfuse.com/nodes/widgets/ui-chart.html

View File

@@ -1,4 +1,10 @@
# FlowFuse `ui-button` Manual (EVOLV Reference) # FlowFuse `ui-button` Manual (EVOLV Reference)
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-manual_(third-party)-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
Source: https://dashboard.flowfuse.com/nodes/widgets/ui-button.html Source: https://dashboard.flowfuse.com/nodes/widgets/ui-button.html

View File

@@ -1,5 +1,11 @@
# FlowFuse `ui-chart` Manual (EVOLV Reference) # FlowFuse `ui-chart` Manual (EVOLV Reference)
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-manual_(third-party)-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
Source: https://dashboard.flowfuse.com/nodes/widgets/ui-chart.html Source: https://dashboard.flowfuse.com/nodes/widgets/ui-chart.html
## Chart Types ## Chart Types

View File

@@ -1,4 +1,10 @@
# FlowFuse Config Nodes Manual (EVOLV Reference) # FlowFuse Config Nodes Manual (EVOLV Reference)
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-manual_(third-party)-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
Sources: Sources:
- https://dashboard.flowfuse.com/nodes/config/ui-base.html - https://dashboard.flowfuse.com/nodes/config/ui-base.html

View File

@@ -1,4 +1,10 @@
# FlowFuse `ui-gauge` Manual (EVOLV Reference) # FlowFuse `ui-gauge` Manual (EVOLV Reference)
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-manual_(third-party)-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
Source: https://dashboard.flowfuse.com/nodes/widgets/ui-gauge.html Source: https://dashboard.flowfuse.com/nodes/widgets/ui-gauge.html

View File

@@ -1,4 +1,10 @@
# FlowFuse `ui-template` Manual (EVOLV Reference) # FlowFuse `ui-template` Manual (EVOLV Reference)
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-manual_(third-party)-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
Source: https://dashboard.flowfuse.com/nodes/widgets/ui-template.html Source: https://dashboard.flowfuse.com/nodes/widgets/ui-template.html

View File

@@ -1,4 +1,10 @@
# FlowFuse `ui-text` Manual (EVOLV Reference) # FlowFuse `ui-text` Manual (EVOLV Reference)
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-manual_(third-party)-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
Source: https://dashboard.flowfuse.com/nodes/widgets/ui-text.html Source: https://dashboard.flowfuse.com/nodes/widgets/ui-text.html

View File

@@ -1,4 +1,10 @@
# FlowFuse Dashboard 2.0 — Widget Catalog # FlowFuse Dashboard 2.0 — Widget Catalog
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-manual_(third-party)-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
Source: https://dashboard.flowfuse.com/ Source: https://dashboard.flowfuse.com/

View File

@@ -1,5 +1,11 @@
# Node-RED Function Node Patterns (EVOLV Summary) # Node-RED Function Node Patterns (EVOLV Summary)
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-manual_(third-party)-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
Based on: https://nodered.org/docs/user-guide/writing-functions Based on: https://nodered.org/docs/user-guide/writing-functions
## Return Semantics ## Return Semantics

View File

@@ -1,5 +1,11 @@
# Node-RED Messages and Editor/Runtime Structure (EVOLV Summary) # Node-RED Messages and Editor/Runtime Structure (EVOLV Summary)
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-manual_(third-party)-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
Sources: Sources:
- Messages: https://nodered.org/docs/user-guide/messages - Messages: https://nodered.org/docs/user-guide/messages
- Edit dialog and node definition: https://nodered.org/docs/creating-nodes/edit-dialog - Edit dialog and node definition: https://nodered.org/docs/creating-nodes/edit-dialog

View File

@@ -1,5 +1,11 @@
# Node-RED Runtime Node JS Manual (EVOLV Summary) # Node-RED Runtime Node JS Manual (EVOLV Summary)
![code-ref](https://img.shields.io/badge/code--ref-9ab9f6b-blue) ![type](https://img.shields.io/badge/type-manual_(third-party)-orange)
> [!NOTE]
> Reference page. Maintained for context; not regenerated by code. See [Home](Home) for current top-level navigation.
Based on: https://nodered.org/docs/creating-nodes/node-js Based on: https://nodered.org/docs/creating-nodes/node-js
## Input Handler Contract ## Input Handler Contract