Functional Overview

Plant-level process flow
Real water flows left to right through real equipment. EVOLV models a subset of that equipment as Node-RED nodes. The coloured nodes below are modelled by EVOLV; the grey ones are upstream / downstream of the EVOLV-modelled section.
Plant step to EVOLV node mapping
| Plant step |
What happens physically |
EVOLV node(s) |
| Inlet lift station |
Wet-well buffer; pumps raise water to plant elevation |
pumpingStation + machineGroupControl + rotatingMachine |
| Aerobic reactor |
Bacteria consume NH4, COD under aeration; O2 supplied by diffusers |
reactor + diffuser + measurement |
| Secondary settler |
Biomass settles by gravity; clean water overflows; sludge returns or is wasted |
settler + rotatingMachine (RAS pump) |
| Flow distribution |
Multi-valve manifold for effluent split, airflow distribution, RAS proportioning |
valveGroupControl + valve |
| Composite sampling |
Flow-proportional sample buildup for lab analysis |
monster + measurement |
| Telemetry to UI |
Time-series storage + dashboard provisioning |
dashboardAPI (Grafana) + Port 1 to InfluxDB |
pumpingStation — wet-well lift station
Physical view
A wet-well buffer at low elevation. Inflow arrives by gravity from the upstream sewer; pumps lift water to plant elevation. Level is the controlled variable.
Process variables
| Variable |
Typical range |
Unit |
Source |
| Inflow Q_in |
0 to 2000 |
m3/h |
gravity sewer; measured by inlet flow meter |
| Outflow Q_out |
0 to 2000 |
m3/h |
sum of running pump flows |
| Level |
0 to 5 |
m |
level sensor (radar, ultrasonic, hydrostatic) |
| Volume |
0 to V_max |
m3 |
basin geometry × level |
| Predicted ETA full / empty |
seconds |
s |
(V_max − V) / (Q_in − Q_out) |
Control objective
| Goal |
Mechanism |
| Keep level in operating band |
Schmitt-trigger hysteresis: start pumps at startLevel, stop at stopLevel |
| Avoid rapid pump cycling |
stopLevel strictly below startLevel (deadband) |
| Avoid overflow |
High-level alarm; safety controller can override |
| Avoid running dry |
Low-level cutoff stops all pumps |
| Stay near pump BEP |
Demand is shared by machineGroupControl using each pump's curve |
EVOLV implementation
pumpingStation.configure() builds a BasinGeometry from config.basin plus a FlowAggregator that integrates volume from inflow / outflow measurements per tick.
- Level + inflow + outflow arrive as
measurement children with positions inflow, outflow, atequipment for the level.
- The
control strategy module picks one of several modes (level-based, flow-based, time-based). The level-based strategy implements the Schmitt trigger and shifted-ramp behaviour.
SafetyController guards against panic / dry-run / overfill conditions and can block dispatch.
rotatingMachine — single pump or compressor
Physical view
A centrifugal pump (or compressor) characterised by its supplier curves. Speed sets where on the curve the machine operates. The operating point is the intersection of pump curve and system curve.
Process variables
| Variable |
Typical range |
Unit |
| Flow Q |
1 to 1000 |
m3/h |
| Head H (or differential pressure) |
1 to 100 |
m (or bar) |
| Power P |
1 to 500 |
kW |
| Efficiency η |
0.3 to 0.85 |
— |
| Speed N |
0 to 100 |
% of rated |
Physical state machine (water-side)
Control objective
| Goal |
Mechanism |
| Deliver commanded flow / speed |
Internal FSM ramps speed up / down within configured rates |
| Predict outputs before sensors react |
Q + P + η predicted from speed + measured pressure differential via characteristic curves |
| Detect drift |
drift/ module compares predicted vs measured; fires HealthStatus levels 0..3 |
| Protect during warmup / cooldown |
Some transitions are non-interruptible (configurable per machine) |
EVOLV implementation
curves/ module loads supplier characteristic curves (Q-H, Q-P, Q-η); supports multi-dataset assets.
prediction/ module interpolates curve values at current operating point.
state/ module owns the FSM with configurable warmup / cooldown / ramp times.
drift/ module assesses prediction quality and emits HealthStatus.
machineGroupControl — multi-pump load sharing
Physical view
Multiple pumps share a common discharge header. The group operating point is where the sum of pump curves intersects the system curve. The load-sharing problem is: which pumps run at what speed to deliver demand at the lowest combined power.
Process variables
| Variable |
Source |
| Group demand Q_d |
Inbound from parent (pumpingStation, UI, scheduler) |
| Per-pump setpoint |
Computed each tick |
| Pressure (downstream) |
Measurement child, position downstream |
| Group totals (flow, power, efficiency) |
Sum / weighted average of per-pump predictions |
Control objective
| Goal |
Mechanism |
| Deliver Q_d at lowest total power |
GroupOperatingPoint solver picks per-pump shares |
| Avoid frequent pump start / stop |
Hysteresis on pump count + NCog switching metric |
| Stay near per-pump BEP |
Penalise operating points far from BEP in the solver |
| Settle latest demand if upstream fires faster than children absorb |
DemandDispatcher (LatestWinsGate) keeps only the freshest dispatch in flight |
EVOLV implementation
dispatch/DemandDispatcher wraps a LatestWinsGate so a rapid stream of demands collapses to the most recent.
efficiency/groupEfficiency computes mean group efficiency at the current shares.
totals/TotalsCalculator aggregates flow / power across active machines.
valveGroupControl — multi-valve flow distribution
Physical view
Multiple valves on a distribution manifold. Each valve has a flow coefficient K_v that varies with position. The group must split a total available flow between branches.
Process variables
| Variable |
Typical range |
Unit |
| Valve position |
0 to 100 |
% |
| K_v at full open |
1 to 1000 |
m3/h / sqrt(bar) |
| Differential pressure across valve |
0.1 to 5 |
bar |
| Per-branch flow split |
percentage of total |
% |
Control objective
| Goal |
Mechanism |
| Achieve target per-branch flow split |
Solve per-valve K_v from inverse characteristic |
| Respect upstream availability |
Read total flow from registered flow source (pumpingStation, MGC, etc.) |
| Honour valve position limits |
Clamp K_v to physical valve range |
Note on softwareType registration
valveGroupControl.configure() registers five softwareTypes — valve, machine, machinegroup, pumpingstation, valvegroupcontrol. Only valve is a true S88 child. The other four are flow-source registrations: VGC reads their flow output to know how much it has to distribute.
reactor — bioreactor (ASM kinetics)
Physical view
A tank where bacteria consume substrate under aeration. Continuous-flow operation (CSTR or PFR). The state of the reactor is described by 13 ASM state variables (soluble + particulate fractions of organic matter, ammonia, nitrate, biomass, alkalinity, oxygen).
Process variables
| Variable |
Typical range |
Unit |
| Volume V |
100 to 10000 |
m3 |
| Hydraulic retention time HRT |
4 to 24 |
h |
| Sludge retention time SRT |
5 to 30 |
d |
| MLSS |
2000 to 5000 |
mg/L |
| Temperature |
5 to 30 |
degC |
| DO setpoint |
1 to 3 |
mg/L |
| NH4 effluent target |
< 1 |
mg/L |
| NO3 effluent |
0 to 15 |
mg/L |
Control objective
| Goal |
Mechanism |
| Maintain DO setpoint |
DO measurement → diffuser airflow loop (closed externally) |
| Achieve effluent quality |
Manage HRT via reactor inflow, SRT via WAS rate |
| Operate stably across temperature |
Kinetics are temperature-corrected via Arrhenius factors |
Engine choice
| Engine |
When to use |
| CSTR |
Single well-mixed tank or short reactor |
| PFR |
Long, narrow plug-flow reactor; discretised into grid cells along the flow path |
Set via config.reactor_type.
EVOLV implementation
kinetics/baseEngine.js — common state vector + boundary-condition handling.
kinetics/cstr.js — single-zone integrator.
kinetics/pfr.js — multi-zone PFR with a grid.
- Diffuser fires
data.otr on its emitter; reactor subscribes and treats OTR as an O2 source term.
- Downstream
settler subscribes to reactor stateChange; the settler reads effluent composition each tick.
settler — secondary clarifier
Physical view
A wide, shallow tank where biomass settles by gravity. A sludge blanket forms at the bottom; clean water overflows the rim at the top. A return-pump sucks settled sludge back to the reactor; a smaller waste stream removes excess (WAS).
Process variables
| Variable |
Typical range |
Unit |
| Surface area |
50 to 2000 |
m2 |
| Depth |
3 to 5 |
m |
| Surface loading rate (SLR) |
0.5 to 2 |
m/h |
| RAS flow |
50 to 150 |
% of inflow |
| WAS flow |
1 to 5 |
% of inflow |
| Effluent TSS |
< 30 |
mg/L |
| Underflow TSS |
6000 to 12000 |
mg/L |
Control objective
| Goal |
Mechanism |
| Keep sludge blanket below overflow weir |
RAS pump rate adjusted to inflow |
| Maintain reactor MLSS target |
WAS rate set as fraction of inflow |
| Avoid blanket carryover |
Limit SLR; alarm on rising blanket level |
EVOLV implementation
settler._connectReactor attaches emitter.on('stateChange', ...) to the upstream reactor, pulling effluent composition each tick.
settler._connectMachine registers the RAS pump (a rotatingMachine) as a child.
- Settling-velocity model (Takács or Vesilind) is in the settler's
src/; see Settling Models.
diffuser — aeration device
Physical view
A perforated panel or membrane at the bottom of the reactor that releases fine bubbles. Bubbles rise through the water column; oxygen dissolves into the water across the gas-liquid interface.
Process variables
| Variable |
Typical range |
Unit |
| Airflow per diffuser |
1 to 10 |
Nm3/h |
| Header pressure |
0.3 to 0.7 |
bar |
| Water depth (above diffuser) |
4 to 6 |
m |
| K_La (volumetric mass-transfer coefficient) |
1 to 20 |
1/h |
| Alpha factor (wastewater vs clean water) |
0.5 to 0.9 |
— |
| OTR (oxygen transfer rate) |
1 to 5 |
kg-O2/h per diffuser |
Control objective
| Goal |
Mechanism |
| Deliver enough OTR to meet reactor DO setpoint |
Modulate airflow via upstream blower / valve |
| Distribute air evenly across panels |
Manifold sizing; sometimes a valveGroupControl on the air side |
| Avoid over-aeration (energy waste) |
DO feedback loop |
EVOLV implementation
diffuser reads headerPressure, water depth, airflow as inputs.
- Computes OTR from K_La (configurable, supplier-specific), alpha factor, water properties.
- Emits
data.otr on its emitter. Reactor subscribes via emitter.on('otr', ...) — this is not a child-register handshake.
valve — single valve actuator
Physical view
A control valve in a pipe. Position 0..100% maps to K_v via the valve's characteristic curve (linear, equal-percentage, or quick-opening). Flow through the valve obeys Q = K_v * sqrt(dP).
Process variables
| Variable |
Typical range |
Unit |
| Position |
0 to 100 |
% |
| K_v at full open |
1 to 1000 |
m3/h / sqrt(bar) |
| Differential pressure |
0.1 to 5 |
bar |
| Stroke time (close to open) |
10 to 60 |
s |
Physical state machine
valve shares the rotatingMachine state model. States: off, idle, warmingup, operational, accelerating (opening / closing), decelerating, coolingdown, emergencystop, maintenance. warmingup and coolingdown are protected (cannot be aborted).
Control objective
| Goal |
Mechanism |
| Reach commanded position |
Move at the configured reactionSpeed rate |
| Avoid water hammer |
Limit how fast position changes |
| Provide flow feedback to upstream |
Computed Q from current K_v and measured dP |
EVOLV implementation
valve registers measurement children for position / pressure feedback.
- Inherits the shared FSM (
generalFunctions state config) with rotatingMachine.
- Characteristic curve is supplier-specific and loaded similarly to pump curves.
measurement — sensor signal conditioning
Physical view
A field sensor (level meter, flow meter, pressure transducer, DO probe, ...) outputs a raw signal. EVOLV's measurement node wraps that signal: scales it from instrument units (mA, mV, raw counts) to engineering units, smooths it, rejects outliers, and publishes the result to a parent process node.
Three input modes
| Mode |
Source |
When to use |
| analog |
4-20 mA, 0-10 V, raw scaled value |
Direct PLC / IO-card analog input |
| digital |
Boolean (on / off, ok / fault) |
Limit switches, status contacts |
| MQTT |
External MQTT broker topic |
Field bus, sensor with its own gateway |
Process variables (examples)
| Type |
Typical range |
Unit |
Example sensor |
| level |
0 to 5 |
m |
radar level meter |
| flow |
0 to 2000 |
m3/h |
electromagnetic flowmeter |
| pressure |
0 to 10 |
bar |
piezo pressure transmitter |
| temperature |
5 to 40 |
degC |
PT100 RTD |
| DO |
0 to 10 |
mg/L |
optical dissolved-O2 probe |
| NH4 |
0 to 50 |
mg/L |
ion-selective electrode |
| TSS |
0 to 5000 |
mg/L |
optical turbidity sensor |
Control objective
| Goal |
Mechanism |
| Deliver trustworthy values to parent |
Pipeline: scaling → smoothing → outlier → publish |
| Survive sensor faults |
Outlier rejection + sticky-last-good behaviour |
| Calibrate against reference |
cmd.calibrate triggers a calibration cycle |
monster — composite sampling
Physical view
A virtual composite sampler: imagine a small bucket beside the pipe. Every time a unit volume of water flows past, a unit dose of that water is added to the bucket. After a sampling period (e.g. 24 h) the bucket contains a flow-proportional composite of every concentration over that period.
Process variables
| Variable |
Typical range |
Unit |
| Sampling period |
1 to 24 |
h |
| Bucket volume target |
2 to 10 |
L |
| Sample doses per period |
24 to 96 |
— |
| Output composite concentration |
as configured per parameter |
mg/L, NTU, … |
Control objective
| Goal |
Mechanism |
| Build a representative composite sample over the period |
Flow-proportional dosing |
| Produce reportable averages |
Each tick, accumulate flow-weighted concentration |
| Reset for next period |
At end of period, emit composite and reset bucket |
Gotchas
| Gotcha |
Detail |
assetType must be "flow" exactly |
Sub-types like "flow-electromagnetic" are silently ignored by monster's child router |
constraints.flowmeter not forwarded |
Toggling proportional-vs-time mode has no runtime effect in current code. Tracked in .claude/refactor/OPEN_QUESTIONS.md |
dashboardAPI — Grafana provisioning
Physical view
There is none. dashboardAPI does not model any piece of water-treatment equipment. It is a utility that auto-generates Grafana dashboards from a node's softwareType + measurements, so operators see the right panels per equipment without hand-building dashboard JSON.
Operational role
| Trigger |
Effect |
Any EVOLV node sends child.register to dashboardAPI |
dashboardAPI composes a dashboard JSON from the template for that softwareType and POSTs an upsert to Grafana |
| Telemetry arrives in InfluxDB (Port 1 of process nodes) |
Grafana panels query InfluxDB and render the trends |
dashboardAPI is the one node in the platform that does not extend BaseDomain (it is a passive HTTP bridge). See .claude/refactor/OPEN_QUESTIONS.md.
Where it all fits
If you imagine a wastewater plant from inlet to outlet, every EVOLV node is a piece of equipment you would see on a P&ID. The software's job is to model that equipment well enough that:
- Operators can run the plant without watching the water directly (predictions + telemetry).
- New plants can be commissioned by composing standard nodes (no bespoke control code).
- Anomalies surface as
HealthStatus flags before they become operator problems.
See Topology Patterns for how to assemble these nodes into a working plant.
Related pages