# 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 ```mermaid flowchart LR sc[specificClass
tick or event]:::tier3 nc[nodeClass
outputUtils.formatMsg]:::tier2 p0[(Port 0
process)]:::p0 p1[(Port 1
InfluxDB line)]:::p1 p2[(Port 2
registration)]:::p2 sc -->|getOutput| nc nc --> p0 nc --> p1 nc --> p2 p0 -. delta-compressed payload .-> dl[Downstream
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):** ```json { "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: ``` , ``` **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 `__` (underscore, not dot — InfluxDB constraint). | | timestamp | Nanoseconds. Set by `outputUtils` from the node's clock. | See [InfluxDB schema design](concepts/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:** ```json { "topic": "child.register", "payload": { "ref": , "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 ```mermaid 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`. ```mermaid flowchart LR p0[(Port 0)]:::p0 split[trend-feeder
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: ```mermaid flowchart LR evolv[EVOLV node
any softwareType]:::tier3 dash[dashboardAPI]:::util grafana[(Grafana HTTP API
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](Architecture) — output port wiring in the 3-tier code - [Topic-Conventions](Topic-Conventions) — what topics map to what fields - [InfluxDB schema design](concepts/influxdb-schema-design) — cardinality discipline