Files
EVOLV/wiki/Topology-Patterns.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

9.3 KiB

Topology Patterns

Reflects code as of 9ab9f6b · regenerated 2026-05-11

Typical plant configurations and how nodes wire together. Each pattern is verified against the corresponding nodes' configure() declarations.

Pattern 1 — Pumping station with grouped pumps

The canonical wet-well lift station. One basin, one demand controller (pumpingStation), one load-sharing coordinator (machineGroupControl), N pumps. Level + flow measurements feed the basin model.

flowchart TB
    subgraph PC["Process Cell"]
        ps[pumpingStation]:::pc
    end
    subgraph UN["Unit"]
        mgc[machineGroupControl]:::unit
    end
    subgraph EM["Equipment"]
        rmA[rotatingMachine A]:::equip
        rmB[rotatingMachine B]:::equip
        rmC[rotatingMachine C]:::equip
    end
    subgraph CM["Control Module"]
        ml[measurement: level]:::ctrl
        mfin[measurement: inflow]:::ctrl
        mpA[measurement: pressure A]:::ctrl
        mpB[measurement: pressure B]:::ctrl
        mpC[measurement: pressure C]:::ctrl
    end

    ps --> mgc
    mgc --> rmA
    mgc --> rmB
    mgc --> rmC

    ml -. data .-> ps
    mfin -. data .-> ps
    mpA -. data .-> rmA
    mpB -. data .-> rmB
    mpC -. data .-> rmC

    classDef pc fill:#0c99d9,color:#fff
    classDef unit fill:#50a8d9,color:#000
    classDef equip fill:#86bbdd,color:#000
    classDef ctrl fill:#a9daee,color:#000

Data flow:

  • pumpingStation computes basin volume + level dynamics from inflow/outflow measurements.
  • pumpingStation emits a demand setpoint downstream to machineGroupControl on its Port 0 or via set.demand.
  • machineGroupControl solves a per-pump operating point using each pump's characteristic curve + measured pressure, sends set.setpoint to each rotatingMachine.
  • Each rotatingMachine runs its own FSM (idle/warmingup/operational/coolingdown/emergencystop) and predicts flow/power from pressure + speed.

Notes:

  • For a single-pump station, pumpingStation can register rotatingMachine directly (skip the MGC) — pumpingStation's configure() accepts machine as a child softwareType.
  • For two stations in series, the downstream PS can register the upstream PS as a pumpingstation softwareType source.

Pattern 2 — Reactor / settler train with aeration

Biological treatment line. Reactor runs ASM kinetics, diffuser drives O₂ transfer, settler clarifies effluent and returns sludge via a return pump.

flowchart TB
    subgraph UN["Unit"]
        reactor[reactor]:::unit
        settler[settler]:::unit
    end
    subgraph EM["Equipment"]
        diff[diffuser]:::equip
        rp[rotatingMachine<br/>return pump]:::equip
    end
    subgraph CM["Control Module"]
        mt[measurement: temperature]:::ctrl
        mdo[measurement: dissolved O₂]:::ctrl
        mts[measurement: TSS]:::ctrl
    end

    reactor ==stateChange==> settler
    diff -. OTR data .-> reactor
    settler -->|return pump child| rp

    mt -. data .-> reactor
    mdo -. data .-> reactor
    mts -. data .-> settler
    mdo -. data .-> diff

    classDef unit fill:#50a8d9,color:#000
    classDef equip fill:#86bbdd,color:#000
    classDef ctrl fill:#a9daee,color:#000

Data flow:

  • reactor.configure() registers measurement (temperature, DO) and upstream reactor (for chained tanks).
  • diffuser emits data.otr on its emitter; reactor subscribes via emitter.on('otr', …)not a child registration, just a data subscription.
  • reactor emits stateChange after every kinetics step. settler._connectReactor subscribes via emitter.on('stateChange', …) and pulls effluent composition.
  • settler.configure() accepts reactor (the upstream), machine (return pump), and measurement children.

Notes:

  • Reactor supports two kinetics engines: CSTR (continuous-stirred tank) and PFR (plug-flow). Set via config.reactor_type.
  • DO setpoint feedback (DO measurement → diffuser airflow) is not wired automatically — connect via a small control function or use a valveGroupControl upstream of an airflow valve.

Pattern 3 — Valve group on a distribution manifold

Multi-valve flow distribution. VGC computes per-valve K_v shares to satisfy a target distribution while respecting upstream flow availability.

flowchart TB
    subgraph PC["Process Cell"]
        ps[pumpingStation<br/>upstream flow source]:::pc
    end
    subgraph UN["Unit"]
        vgc[valveGroupControl]:::unit
    end
    subgraph EM["Equipment"]
        vA[valve A]:::equip
        vB[valve B]:::equip
        vC[valve C]:::equip
    end

    ps -. flow source .-> vgc
    vgc --> vA
    vgc --> vB
    vgc --> vC

    classDef pc fill:#0c99d9,color:#fff
    classDef unit fill:#50a8d9,color:#000
    classDef equip fill:#86bbdd,color:#000

Important detail: valveGroupControl.configure() registers four extra softwareTypes — machine, machinegroup, pumpingstation, valvegroupcontrolnot as S88 children but as flow sources. VGC uses them to read upstream flow availability when computing per-valve splits. The arrow above is child.register from pumpingStation to vgc; the semantic relationship is "VGC knows about this upstream flow producer", not "VGC controls pumpingStation".

Pattern 4 — Composite sampling

monster runs a proportional sampling program — accumulates samples in a bucket based on integrated flow. Used as a virtual sensor for downstream lab analysis.

flowchart TB
    subgraph UN["Unit"]
        monster[monster]:::unit
    end
    subgraph CM["Control Module"]
        mflow[measurement: flow<br/>assetType MUST be 'flow']:::ctrl
        mq[measurement: any quality<br/>e.g. NH₄, COD]:::ctrl
    end

    mflow -. data .-> monster
    mq -. data .-> monster

    classDef unit fill:#50a8d9,color:#000
    classDef ctrl fill:#a9daee,color:#000

Gotchas:

  • measurement.config.asset.type MUST be "flow" exactly — "flow-electromagnetic" or any sub-type is silently ignored by monster's child router.
  • monster.config.constraints.flowmeter exists in the schema but is not forwarded by buildDomainConfig — toggling proportional-vs-time mode has no effect at runtime. (Tracked in OPEN_QUESTIONS.md.)

Pattern 5 — Dashboard provisioning

dashboardAPI doesn't operate on data — it generates Grafana dashboards. Any node can register with dashboardAPI via child.register; dashboardAPI then composes a dashboard JSON from the node's softwareType + measurements and POSTs to Grafana's HTTP API.

flowchart LR
    subgraph EVOLV["EVOLV process nodes"]
        ps[pumpingStation]:::pc
        mgc[machineGroupControl]:::unit
        rm[rotatingMachine]:::equip
    end
    subgraph UT["Utility"]
        dash[dashboardAPI]:::util
    end
    grafana[(Grafana<br/>HTTP API)]:::ext

    ps -. child.register .-> dash
    mgc -. child.register .-> dash
    rm -. child.register .-> dash
    dash -->|POST /api/dashboards/db| grafana

    classDef pc fill:#0c99d9,color:#fff
    classDef unit fill:#50a8d9,color:#000
    classDef equip fill:#86bbdd,color:#000
    classDef util fill:#dddddd,color:#000
    classDef ext fill:#fff2cc,color:#000

Notes:

  • dashboardAPI is the one node in the platform that doesn't extend BaseDomain (it's a passive HTTP bridge — see OPEN_QUESTIONS.md for the deferral decision).
  • The meta field of dashboardAPI's outbound msg carries {nodeId, softwareType, uid, title} for correlating responses.

Putting it all together — example plant

A small WWTP combining all patterns:

flowchart TB
    subgraph PC["Process Cell"]
        ps1[pumpingStation<br/>inlet lift]:::pc
        ps2[pumpingStation<br/>RAS pumping]:::pc
    end
    subgraph UN["Unit"]
        mgc1[MGC inlet]:::unit
        mgc2[MGC RAS]:::unit
        vgc[VGC effluent split]:::unit
        r1[reactor aerobic]:::unit
        s1[settler]:::unit
        mon[monster<br/>composite sampler]:::unit
    end
    subgraph EM["Equipment"]
        rm1[pump A]:::equip
        rm2[pump B]:::equip
        rm3[RAS pump]:::equip
        d1[diffuser]:::equip
        v1[valve 1]:::equip
        v2[valve 2]:::equip
    end

    ps1 --> mgc1
    mgc1 --> rm1
    mgc1 --> rm2
    ps2 --> mgc2
    mgc2 --> rm3

    r1 ==stateChange==> s1
    s1 -->|return pump| rm3
    d1 -. OTR .-> r1

    ps2 -. flow source .-> vgc
    vgc --> v1
    vgc --> v2

    classDef pc fill:#0c99d9,color:#fff
    classDef unit fill:#50a8d9,color:#000
    classDef equip fill:#86bbdd,color:#000

This is the kind of diagram each wiki/Home.md per node should be able to fit into — every edge is reproducible from a configure() declaration.

Anti-patterns

  • pumpingStation → valveGroupControl as a parent/child edge. PS does not register VGC. VGC registers PS as a flow source; the edge goes the other way semantically.
  • A diffuser → reactor child registration. Diffuser emits OTR via its emitter; reactor subscribes. No child.register handshake.
  • measurement parented under dashboardAPI. dashboardAPI accepts any node for Grafana provisioning, but measurement registers with the process node it's monitoring, not with dashboardAPI.