Topic Conventions
Note
Every
msg.topicin 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.
The six prefixes
flowchart LR
ui["UI / parent / driver / function node"]
node[Node]
child["Child node"]
ext["external consumers"]
ui -- "set. / cmd. / query." --> node
node -. "evt." .-> ui
node -. "evt." .-> ext
child -- "data." --> node
node -- "data." --> child
child <-->|child.register| node
class ui,ext neutral
class node tier3
class child tier1
classDef neutral fill:#dddddd
classDef tier3 fill:#50a8d9,color:#000
classDef tier1 fill:#a9daee,color:#000
Inbound (the node accepts on its input)
| Prefix | Idempotent | Meaning | Examples |
|---|---|---|---|
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 |
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 |
Outbound (the node emits)
| Prefix | Meaning | Where it appears |
|---|---|---|
evt.<noun> |
Event. A fact about something that just happened. Fire-and-forget — 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— writeset.demandand 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
setStartupthat both sets a flag and triggers startup should be split intoset.<noun>andcmd.<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.
{
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 |
|---|---|
set.mode |
setMode, changemode |
set.demand |
Qd, setDemand |
cmd.startup |
execSequence (with payload.action='startup') |
cmd.shutdown |
execSequence (with payload.action='shutdown') |
child.register |
registerChild |
data.pressure |
pressure |
data.flow |
flow |
Important
Update integrations to canonical names before Phase 7 ships. Aliases work today; they will be removed next major release.
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:
{
topic: 'set.demand',
units: { measure: 'volumeFlowRate', default: 'm3/h' },
payloadSchema: { type: 'number' },
handler: handlers.setDemand,
}
flowchart LR
in["Inbound msg — payload=50, unit='m3/h'"]
parse["Extract value+unit — 3 payload shapes accepted"]
convert["convert(value).from(unit).to(default)"]
handler["Handler receives msg.payload = canonical number, msg.unit = units.default"]
in --> parse --> convert --> handler
class in,handler neutral
class parse,convert step
classDef neutral fill:#dddddd
classDef step fill:#a9daee,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 — 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):
settlereditor colour is#e4a363(orange) — should be#50a8d9.monstereditor colour is#4f8582(teal) — should be#50a8d9.diffuserregisters 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
positionis 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(...).
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 — attention needed |
red |
ring |
Fault — operator action required |
grey |
dot |
Initialising / no data yet |
Important
Badges live in the domain, not the adapter.
nodeClasscallsthis.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.
{
"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> — 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 |
|---|---|---|
| Flow | m3/s |
m3/h, l/s, gpm |
| Pressure | Pa |
bar, mbar, kPa |
| Power | W |
kW, MW |
| Temperature | K |
degC, degF |
| Level | m |
m, cm |
| Volume | m3 |
m3, l |
Tip
Inside
specificClass, treat values as canonical. Conversion happens at the boundary: input coercion by the commands registry; output formatting byoutputUtils.
Dual access form
UnitPolicy exposes each accessor as both a method and a frozen property bag.
policy.canonical('flow') // 'm3/s' (method form)
policy.canonical.flow // 'm3/s' (property form — preferred in hot paths)
policy.output.pressure // 'mbar'
Related pages
| Page | Why |
|---|---|
| Architecture | Where these conventions are implemented in code |
| Telemetry | What these keys look like in InfluxDB |
| Topology Patterns | Which topics flow between which nodes |
| Glossary | Domain terms used here |
EVOLV Wiki
Start here
Reference
Per-node wikis
- pumpingStation
- machineGroupControl
- valveGroupControl
- reactor
- settler
- monster
- rotatingMachine
- valve
- diffuser
- measurement
- dashboardAPI
- generalFunctions
Domain concepts
- ASM Models
- PID Control Theory
- Pump Affinity Laws
- Settling Models
- Signal Processing — Sensors
- InfluxDB Schema Design
- Wastewater Compliance NL
- OT Security IEC 62443
Operations findings
Node-RED / FlowFuse manuals
- Manual Index
- Runtime — Node.js
- Function Node Patterns
- Messages and Editor Structure
- FlowFuse ui-chart
- FlowFuse ui-button
- FlowFuse ui-gauge
- FlowFuse ui-text
- FlowFuse ui-template
- FlowFuse ui-config
- Dashboard Layout
- Widgets Catalog
Archive