Files
EVOLV/wiki/Architecture.md
znetsixe 2ccc8aea9e wiki: master EVOLV wiki refactor — 7 new pages + corrected Home
Complete redesign of the platform-level wiki. Previous Home.md had a
broken Mermaid diagram (showed pumpingStation → valveGroupControl as a
parent/child edge, which isn't in any configure() declaration). Audit
of all 12 specificClass.js configure() calls drives the new ground-truth
hierarchy.

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 21:47:57 +02:00

171 lines
7.9 KiB
Markdown

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