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

7.9 KiB

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

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 → for the full 34-row API table.

Output ports

Every EVOLV node emits on three ports:

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 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 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().

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

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.