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>
This commit is contained in:
202
wiki/Telemetry.md
Normal file
202
wiki/Telemetry.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# 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<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):**
|
||||
|
||||
```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:
|
||||
|
||||
```
|
||||
<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](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": <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
|
||||
|
||||
```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<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:
|
||||
|
||||
```mermaid
|
||||
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](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
|
||||
Reference in New Issue
Block a user