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>
7.2 KiB
Telemetry
Reflects code as of
9ab9f6b· regenerated2026-05-11
What every EVOLV node emits on each of its three output ports, the InfluxDB line-protocol layout, and how the data reaches Grafana/FlowFuse.
Three-port model
flowchart LR
sc[specificClass<br/>tick or event]:::tier3
nc[nodeClass<br/>outputUtils.formatMsg]:::tier2
p0[(Port 0<br/>process)]:::p0
p1[(Port 1<br/>InfluxDB line)]:::p1
p2[(Port 2<br/>registration)]:::p2
sc -->|getOutput| nc
nc --> p0
nc --> p1
nc --> p2
p0 -. delta-compressed payload .-> dl[Downstream<br/>Node-RED logic]:::neutral
p1 -. line protocol .-> influx[(InfluxDB)]:::ext
p2 -. child.register .-> parent[Parent EVOLV node]:::neutral
classDef tier3 fill:#50a8d9,color:#000
classDef tier2 fill:#86bbdd,color:#000
classDef p0 fill:#86bbdd
classDef p1 fill:#a9daee
classDef p2 fill:#dddddd
classDef neutral fill:#dddddd
classDef ext fill:#fff2cc
Port 0 — Process data (delta-compressed)
Purpose: feeds downstream Node-RED logic — dashboards, control functions, alarms.
Shape: msg.payload is an object containing only keys that changed since the last tick. Consumers cache + merge.
Why delta-compressed: at 1 Hz ticks with 50 fields per node, full snapshots flood downstream nodes and dashboards. Delta payloads typically carry 0–5 fields per tick.
Example (rotatingMachine, 1 tick):
{
"topic": "rotatingMachine#pump-A",
"payload": {
"flow.predicted.downstream.default": 12.4,
"predictionConfidence": 0.87
}
}
Most other fields (state, pressure, mode, …) didn't change this tick — omitted.
Trigger: outputUtils.checkForChanges() compares the current getOutput() against the previous snapshot.
Port 1 — Telemetry (InfluxDB line protocol)
Purpose: time-series storage in InfluxDB for trending, regulatory reporting, ML training.
Shape: msg.payload is a string (or array of strings) in InfluxDB line protocol:
<measurement>,<tag-set> <field-set> <timestamp-ns>
Example:
rotatingMachine,id=pump-A,softwareType=rotatingMachine flow_predicted_downstream=12.4,power_measured_atequipment=18.2 1714752000000000000
Conventions:
| Element | Rule |
|---|---|
| measurement (table) | The node's softwareType (lowercase). |
| tag-set | Low-cardinality identity: id, softwareType, location-style tags. Never raw measurement values. |
| field-set | Numeric values only. Keys flatten <type>_<variant>_<position> (underscore, not dot — InfluxDB constraint). |
| timestamp | Nanoseconds. Set by outputUtils from the node's clock. |
See InfluxDB schema design for the cardinality discipline.
Port 2 — Registration / control
Purpose: upward child.register at startup; later, internal control msgs (the registry-driven command replies).
Shape:
{
"topic": "child.register",
"payload": {
"ref": <node reference>,
"softwareType": "machine",
"config": { ... }
}
}
Trigger: the nodeClass adapter emits child.register on init if a parent is configured. The parent's commandRegistry dispatches into ChildRouter.onRegister(...).
The output composition pipeline
sequenceDiagram
participant tick as Tick (1 Hz)
participant sc as specificClass
participant mc as MeasurementContainer
participant ou as outputUtils
participant ports as Ports 0 / 1
tick->>sc: tick()
sc->>sc: concern modules update mc + state
sc->>ou: getOutput() snapshot
ou->>ou: diff vs last
alt no change
ou-->>sc: skip
else change
ou->>ports: Port 0 — JSON delta
ou->>ports: Port 1 — line protocol
end
outputUtils is the single place the platform serialises state. Never write directly to node.send from specificClass — go through outputUtils.formatMsg.
InfluxDB layout
Recommended schema for EVOLV's data:
| InfluxDB element | Maps to |
|---|---|
| Database / bucket | One per plant (or per environment: evolv_dev, evolv_prod). |
| Measurement (table) | Node softwareType (rotatingMachine, pumpingStation, …). |
| Tags | id (instance id), softwareType, area, processCell, unit (for hierarchical drill-down). |
| Fields | Numeric series — every key from getOutput() that has a numeric value, flattened with _. |
| Retention | Hot bucket: 7 days @ 1 s. Cold bucket: 1 year @ 1 min downsample. |
Cardinality discipline: keep tag sets stable. Don't put state (string) as a tag — emit it as a field with code (state_code=2). High-cardinality tags fragment the index.
FlowFuse dashboard wiring
If you use FlowFuse ui-chart widgets, Port 0 is the natural source — the delta-compressed JSON maps cleanly to msg.topic (series label) + msg.payload (y-value).
Layout rule for charts (from .claude/rules/node-red-flow-layout.md §4):
- One chart per metric type (one for flow, one for power, one for level).
- A trend-feeder function splits Port-0 deltas into per-series outputs, each output wired to one chart.
- The chart's
category: "topic"+categoryType: "msg"plots one series per uniquemsg.topic.
flowchart LR
p0[(Port 0)]:::p0
split[trend-feeder<br/>function (N outputs)]:::tier2
chart1[ui-chart: flow]:::neutral
chart2[ui-chart: power]:::neutral
p0 --> split
split --> chart1
split --> chart2
classDef p0 fill:#86bbdd
classDef tier2 fill:#86bbdd,color:#000
classDef neutral fill:#dddddd
Grafana dashboard provisioning
dashboardAPI consumes registrations and emits Grafana dashboard JSON via HTTP. Wiring:
flowchart LR
evolv[EVOLV node<br/>any softwareType]:::tier3
dash[dashboardAPI]:::util
grafana[(Grafana HTTP API<br/>POST /api/dashboards/db)]:::ext
evolv -->|child.register| dash
dash -->|composed JSON| grafana
classDef tier3 fill:#50a8d9,color:#000
classDef util fill:#dddddd
classDef ext fill:#fff2cc
dashboardAPI looks up a template per softwareType (in nodes/dashboardAPI/src/config/templates/), substitutes the node's id + tags, and POSTs an upsert. Bearer-token auth is supported.
Common debug recipes
| Symptom | First check |
|---|---|
| InfluxDB rows missing for a node | Confirm Port 1 is wired to an influxdb out node. Tap Port 1 with a debug node to verify line-protocol output. |
Dashboard widgets stuck on n/a |
Confirm Port 0 is reaching the trend-feeder. Many widgets need msg.topic set for series labelling. |
child.register not arriving at parent |
Tap Port 2 with debug. Confirm parent's commandRegistry accepts child.register (or the legacy registerChild alias). |
| Too many InfluxDB writes (high write-rate) | Check that outputUtils.checkForChanges() is firing. Likely you wired a tick-driven debug node bypassing the delta filter. |
| Grafana dashboard not created on plant boot | Inspect dashboardAPI's HTTP response. Check the bearer token + base URL in its config. |
Related pages
- Architecture — output port wiring in the 3-tier code
- Topic-Conventions — what topics map to what fields
- InfluxDB schema design — cardinality discipline