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

7.2 KiB
Raw Blame History

Telemetry

Reflects code as of 9ab9f6b · regenerated 2026-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 05 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 unique msg.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.