Source-tree mirror of EVOLV.wiki.git refactor (27a42ee on wiki.git): - 7 master pages rewritten with clean design (Home, Architecture, Topology-Patterns, Topic-Conventions, Telemetry, Getting-Started, Glossary). Tables and Mermaid for visuals, gitea alert callouts for warnings, shields badges for metadata only. No emoji as decoration. - Archive.md becomes a removal-changelog pointing readers to git history and to the successor pages. - _Sidebar.md updated to navigate the new flat-name layout. - Concept / finding / manual pages: uniform mini-header (badges + "reference page" callout) added without rewriting domain content. - Every internal link now uses the flat naming that resolves on the live gitea wiki (Concept-ASM-Models, Finding-BEP-..., etc.). On wiki.git: 29 Archive-* pages hard-deleted (the git history preserves them; Archive.md documents the removal). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.2 KiB
Telemetry
Note
Every node sends on three output ports: Port 0 (process data), Port 1 (InfluxDB line protocol), Port 2 (registration / control plumbing). All output is formatted by
outputUtils.formatMsgwith delta compression: only changed fields are sent each tick. InfluxDB cardinality discipline: tags = identity (low cardinality), fields = numbers. Source of truth:.claude/refactor/CONTRACTS.md§10.
Three-port model
flowchart LR
sc["specificClass — 'output-changed' or tick()"]
ou["outputUtils.formatMsg — delta-compress"]
p0[("Port 0 — process")]
p1[("Port 1 — InfluxDB line")]
p2[("Port 2 — register / control")]
dl["Downstream Node-RED — dashboards, functions"]
influx[("InfluxDB")]
parent["Parent EVOLV node"]
sc -- getOutput() --> ou
ou --> p0 --> dl
ou --> p1 --> influx
sc -. child.register .-> p2 --> parent
class sc tier3
class ou tier2
class p0 p0c
class p1 p1c
class p2 p2c
class dl,parent dn
class influx ext
classDef tier3 fill:#50a8d9,color:#000
classDef tier2 fill:#86bbdd,color:#000
classDef p0c fill:#0c99d9,color:#fff
classDef p1c fill:#50a8d9,color:#000
classDef p2c fill:#a9daee,color:#000
classDef dn fill:#dddddd,color:#000
classDef ext fill:#fff2cc,color:#000
Port summary
| Port | Carries | Format | Configured by | Trigger |
|---|---|---|---|---|
| 0 (process) | Snapshot of changed measurement / state keys | JSON delta object | outputUtils.formatMsg(..., 'process') via config.output.process |
'output-changed' on the emitter, or each tick |
| 1 (telemetry) | Numeric fields only — time-series | InfluxDB line protocol | outputUtils.formatMsg(..., 'influxdb') via config.output.dbase |
Same trigger as Port 0 |
| 2 (register) | child.register plus internal control msgs |
Plain object | BaseNodeAdapter init |
Once 100ms after init; on demand |
Port 0 — Process data (delta-compressed)
Purpose: feed downstream Node-RED logic such as dashboards, alarms, control function nodes.
Shape: msg.payload contains only keys that changed since the last tick. Consumers must cache and merge.
Important
Why delta compression: at 1 Hz with 50+ fields per node, full snapshots flood downstream nodes and dashboards. A typical delta carries 0–5 fields per tick.
Example output, one rotatingMachine 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 and are omitted.
Trigger: outputUtils.checkForChanges() compares the current getOutput() against the previous snapshot. No change means no send.
Port 1 — InfluxDB line protocol
Purpose: time-series storage 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 telemetry:
rotatingMachine,id=pump-A,softwareType=rotatingMachine flow_predicted_downstream=12.4,power_measured_atequipment=18.2 1714752000000000000
Conventions
| Element | Rule |
|---|---|
| Measurement (table name) | 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 cardinality discipline.
Port 2 — Registration / control
Purpose: upward child.register at startup; later, internal control msgs.
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. Parent's commandRegistry dispatches into ChildRouter.onRegister(...).
The legacy alias registerChild is still accepted; it logs a one-time deprecation warning on first use.
The output composition pipeline
sequenceDiagram
autonumber
participant tick as Tick (1 Hz) or event source
participant sc as specificClass
participant mc as MeasurementContainer
participant ou as outputUtils
participant ports as Ports 0 / 1
tick->>sc: tick() OR emit('output-changed')
sc->>sc: concern modules update mc + state
sc->>ou: getOutput() snapshot
ou->>ou: diff vs last snapshot
alt no change
ou-->>sc: skip
else change
ou->>ports: Port 0 — JSON delta
ou->>ports: Port 1 — line protocol
end
Caution
Never write directly to
node.sendfrom specificClass. Go throughoutputUtils.formatMsg. Direct sends bypass delta compression and flood downstream nodes.
InfluxDB schema layout (recommended)
| InfluxDB element | Maps to |
|---|---|
| Database / bucket | One per plant, or per environment: evolv_dev, evolv_prod |
| Measurement (table) | Node softwareType |
| Tags | id, softwareType, area, processCell, unit (for hierarchical drill-down) |
| Fields | Numeric series — every numeric key from getOutput(), flattened with _ |
| Retention — hot | 7 days at 1 s |
| Retention — cold | 1 year at 1 min downsample |
Warning
Cardinality discipline. Keep tag sets stable. Do not put
state(string) as a tag — emit it as a field with a code (state_code=2). High-cardinality tags fragment InfluxDB's index and degrade query performance dramatically.
See InfluxDB Schema Design for full guidance.
FlowFuse dashboard wiring
Port 0 is the natural source for FlowFuse ui-chart widgets — the delta-compressed JSON maps cleanly to msg.topic (series label) plus msg.payload (y-value).
Layout rule (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 wires to one chart. The chart's
category: "topic"andcategoryType: "msg"plot one series per uniquemsg.topic.
flowchart LR
p0[("Port 0")]
split["trend-feeder — function (N outputs)"]
chart1["ui-chart: flow"]
chart2["ui-chart: power"]
p0 --> split
split --> chart1
split --> chart2
class p0 p0c
class split tier2
class chart1,chart2 neutral
classDef p0c fill:#0c99d9,color:#fff
classDef tier2 fill:#86bbdd,color:#000
classDef neutral fill:#dddddd
See FlowFuse ui-chart manual for the required chart properties.
Grafana dashboard provisioning
dashboardAPI consumes registrations and emits Grafana dashboard JSON via HTTP.
flowchart LR
evolv["EVOLV node (any softwareType)"]
dash[dashboardAPI]
grafana[("Grafana HTTP API — POST /api/dashboards/db")]
evolv -- child.register --> dash
dash -- composed JSON --> grafana
class evolv tier3
class dash util
class grafana ext
classDef tier3 fill:#50a8d9,color:#000
classDef util fill:#dddddd
classDef ext fill:#fff2cc,color:#000
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 via config.grafanaConnector.bearerToken.
Debug recipes
| Symptom | First thing to check |
|---|---|
| InfluxDB rows missing for a node | Port 1 wired to an influxdb out node? Tap Port 1 with a debug node to verify line-protocol output |
Dashboard widgets stuck on n/a |
Port 0 reaching the trend-feeder? Many widgets need msg.topic set for series labelling |
child.register not arriving |
Tap Port 2 with debug. Confirm parent's commandRegistry accepts child.register (or registerChild alias) |
| Too many InfluxDB writes | Likely a tick-driven debug node bypassed the delta filter. Confirm outputUtils.checkForChanges() is firing |
| Grafana dashboard not created on boot | Inspect dashboardAPI's HTTP response. Check bearer token + base URL in its config |
| High cardinality alarm in InfluxDB | A string value is being written as a tag (probably state or similar). Move it to a field |
Caution
Never ship
enableLog: 'debug'in a demo. Fills the container log within seconds and obscures real errors. Use only for live debugging.
Related pages
| Page | Why |
|---|---|
| Architecture | Output port wiring in the three-tier model |
| Topic Conventions | What topics map to what fields |
| Topology Patterns | Typical telemetry flows |
| InfluxDB Schema Design | Cardinality discipline |
| FlowFuse ui-chart manual | Required chart properties |