---
title: "OCETS Reference Implementation Guide"
version: "1.1"
status: "Architecture Review Candidate"
release: "July 2026"
identifier: "OCETS-GUIDE-1.1"
---

# OCETS Reference Implementation Guide

**Document:** 2 of 5  
**Type:** Informative Implementation Guide  
**Version:** 1.1  
**Status:** Architecture Review Candidate  
**Compatibility:** OCETS 1.1

## 1. Purpose

This guide describes one practical implementation of the Open Canonical Energy Telemetry Standard using common open infrastructure components.

The normative standard defines the model. This guide shows how a deployment can implement that model using concrete tools. It may change more frequently than the standard as tools, devices, and operational practices evolve.

## 2. Scope

This guide covers:

- Telegraf;
- MQTT;
- VictoriaMetrics;
- Grafana;
- Modbus;
- Huawei devices;
- Janitza devices;
- templates;
- deployment;
- transformation scripts;
- operational validation.

Current or legacy implementation names are examples only. They are not OCETS compatibility exceptions and do not create alternative canonical names.

## 3. Reference Architecture

The reference implementation uses Registry-generated configuration and keeps Observations and Events logically distinct:

```text
Organization / Portfolio / Plant Registry -> Generator
                                              |
Field Device -> Modbus / Vendor Protocol -> Edge Telegraf
  -> MQTT -> Collector Telegraf -> VictoriaMetrics -> Grafana / API Consumers
                 |                    |
                 +-> Event channel    +-> Quality and provenance metadata
```

The implementation must preserve the canonical semantics defined by OCETS. Tool-specific names, register addresses, field aliases, hostnames, and transport topics are implementation details.

## 4. Canonical Storage Shape

In time-series storage, the measurement or series name should be the canonical metric name, for example `active_power`, `voltage`, or `state_of_charge`.

Recommended canonical labels:

- `plant`;
- `asset`;
- `device`;
- `measurement_point`;
- `role`;
- `energy_form`;
- `phase`, when applicable;
- `direction`, when applicable;
- `string`, when applicable;
- `mppt`, when applicable;
- `status`, for state telemetry where applicable.

Do not add a redundant label that repeats the metric name. Vendor, model, collector, source address, and protocol metadata may be retained as implementation labels, but canonical dashboards and alerts should not depend on them for semantic meaning.

Every stored value must also be resolvable to acquisition time, quality, provenance, and the Metric Catalogue version. Implementations may use fields, a companion metadata series, or an observation store; high-cardinality calculation, actor, model, or source references should not become TSDB labels.

Events should use a separate durable event stream or event store keyed by `event_id`. Re-delivery should upsert lifecycle state idempotently while preserving acknowledgement and transition history.

Example logical Observation:

```json
{
  "metric": "active_power",
  "catalogue_version": "1.1",
  "value": 42.5,
  "plant": "plant-example",
  "measurement_point": "mp-grid",
  "acquired_at": "2026-07-13T10:00:00Z",
  "quality": "GOOD",
  "provenance": {"origin": "device", "source_id": "meter-01"}
}
```

Example correlated Event after acknowledgement:

```json
{
  "event_id": "evt-01JZ8M8K7P",
  "event_type": "grid_failure",
  "severity": "critical",
  "source": {"kind": "measurement_point", "id": "mp-grid"},
  "begin_at": "2026-07-13T10:01:00Z",
  "state": "acknowledged",
  "correlation_id": "incident-4711",
  "quality": "GOOD",
  "provenance": {"origin": "device", "source_id": "meter-01"},
  "acknowledgement": {"actor": "operator-17", "at": "2026-07-13T10:02:00Z"}
}
```

## 5. Telegraf Layout

The recommended Telegraf layout separates stable infrastructure from generated device inputs.

### 5.1 Edge Layout

```text
/etc/telegraf/
  telegraf.conf
  telegraf.d/
    10-meter-grid.conf
    20-inverters.conf
    30-battery.conf
```

The main `telegraf.conf` should contain agent settings and outputs. Device inputs should be placed in generated or templated files under `telegraf.d/`.

### 5.2 Collector Layout

```text
/etc/telegraf/
  telegraf.conf
  telegraf.d/
    10-mqtt-telemetry.conf
    20-mqtt-health.conf
```

Unrelated consumers should remain in separate files with narrow topic subscriptions and clear parsing rules.

## 6. MQTT Transport

MQTT is used as the reference transport between edge collectors and central collectors.

Recommended topic pattern:

```text
telemetry/{plant}/{device}/{canonical_metric}
```

Payloads may use Influx line protocol when Telegraf is used on both sides.

The MQTT topic is not canonical identity. Canonical identity is carried by the metric name and registry-derived labels.

## 7. VictoriaMetrics Storage

VictoriaMetrics is the reference time-series storage backend.

The storage backend should receive values after:

- unit conversion;
- canonical metric naming;
- sign normalization;
- label normalization;
- enum normalization;
- validation.

The storage backend should not be used to repair unit inconsistencies or canonical naming errors. Those belong in ingestion and mapping.

## 8. Grafana Dashboards

Grafana dashboards should be generated or parameterized from registry data.

Dashboards should query canonical metric names rather than vendor-specific field names.

Recommended dashboard variables:

- plant;
- asset;
- device;
- measurement point;
- role;
- energy form;
- phase;
- direction.

## 9. Mapping Vendor Fields to Canonical Metrics

Vendor mappings should be explicit, reviewed, and tested with captured source data.

Each mapping should define:

- source field or register;
- source unit and scale factor;
- canonical metric name;
- canonical unit;
- target labels;
- role and Measurement Point;
- sign conversion rule;
- value type;
- enum mapping, if applicable.

Example mapping table:

| Source Field | Source Unit | Canonical Metric | Canonical Unit | Labels | Mapping Notes |
|---|---:|---|---:|---|---|
| `grid_p` | W | `active_power` | kW | `role=grid`, `measurement_point=grid_connection` | Divide by 1000; positive import, negative export. |
| `load_p` | W | `active_power` | kW | `role=load`, `measurement_point=site_load` | Divide by 1000; positive consumption. |
| `pv1_u` | V | `voltage` | V | `role=pv`, `measurement_point=pv_string_1`, `string=1` | Preserve value. |
| `pv1_i` | A | `current` | A | `role=pv`, `measurement_point=pv_string_1`, `string=1` | Preserve value. |
| `battery_soc` | % | `state_of_charge` | percent | `role=battery`, `measurement_point=battery_dc_bus` | Normalize percent range to 0..100. |
| `battery_p` | W | `active_power` | kW | `role=battery`, `measurement_point=battery_dc_bus` | Divide by 1000; positive charging, negative discharging. |

Avoid creating domain-specific electrical metric names when `active_power`, `voltage`, `current`, or another generic metric plus labels describes the measurement correctly.

## 10. Modbus Integration

Modbus register maps are implementation-specific and vendor-specific.

The reference implementation should map registers to canonical metrics during ingestion. Slave IDs and gateway addresses should not become canonical identity.

## 11. Huawei Integration

Huawei register maps belong in this guide or implementation-specific appendices.

Huawei source fields should be mapped into canonical metric names and canonical units before storage.

PV string and MPPT values should use `string` and `mppt` labels for subcomponents instead of multiplying metric names where possible.

## 12. Janitza Integration

Janitza meter values should be mapped into canonical electrical metrics for grid, load, or other Measurement Points.

Three-phase values should use the controlled `phase` values `l1`, `l2`, `l3`, and `total`.

## 13. Templates

Configuration should be generated from the Plant Registry.

Templates should produce:

- Telegraf input files;
- MQTT output configuration;
- collector subscriptions;
- VictoriaMetrics label rules;
- Grafana dashboard variables;
- alert labels;
- deployment inventory.

Humans should edit registry source data and templates, not repeated generated output.

## 14. Example Configuration Skeleton

```toml
[agent]
  omit_hostname = true

[[outputs.mqtt]]
  servers = ["tcp://broker.example:1883"]
  topic = 'telemetry/{{ .Tag "plant" }}/{{ .Tag "device" }}/{{ .Name }}'
  data_format = "influx"
```

This skeleton is illustrative. Production configuration must include credentials, TLS, buffering, retry behavior, and operational monitoring.

## 15. Transformation Processing

Transformation processors may be used to convert vendor-specific or flat field structures into canonical metric and label structures.

Example use cases:

- pivoting repeated fields into `string` or `mppt` labels;
- normalizing enum values;
- applying role-based power signs;
- splitting mixed measurements;
- adding registry-derived labels;
- dropping fields that cannot be mapped safely.

Transformation logic should be tested with captured input before deployment.

## 16. Deployment

The reference deployment should define:

- edge host provisioning;
- collector provisioning;
- secrets management;
- service units;
- configuration generation;
- validation commands;
- rollback procedures;
- monitoring checks.

## 17. Validation Ritual

After each configuration change:

```bash
telegraf --config /etc/telegraf/telegraf.conf \
  --config-directory /etc/telegraf/telegraf.d --test

grep -rnE '^\[\[(inputs|outputs)\.' \
  /etc/telegraf/telegraf.conf /etc/telegraf/telegraf.d/

systemctl reload telegraf || systemctl restart telegraf
systemctl is-active telegraf
```

The test command must include the configuration directory.

## 18. Best Practices

- Keep `host` out of canonical identity.
- Keep credentials out of generated files where possible.
- Use stable logical device IDs.
- Keep labels low-cardinality and controlled.
- Generate repeated configuration from templates.
- Monitor freshness and communication status.
- Treat missing data differently from measured zero.
- Validate generated configuration before reload.
- Document legacy-to-canonical migration separately from OCETS conformance.

## 19. Edge Collector Observability Profile

### 19.1 Collection Boundary

Use one stable registry Device for the logical edge collector. Emit profile metrics with `plant`, `device`, and `role=collector`. Keep hostname, IP address, network interface, filesystem path, Linux device name, and hardware serial number as implementation labels outside canonical identity.

Application-native instrumentation should provide collector, pipeline, freshness, buffer, mapping, validation, transport, and restart metrics. Operating-system collectors cannot reliably infer these application states.

### 19.2 Ubuntu and Raspberry Pi CM5

On Ubuntu, Telegraf inputs may collect CPU, memory, filesystem, disk I/O, network, processes, and selected systemd unit states. Limit collection to filesystems, interfaces, and services that affect telemetry acquisition or forwarding.

CM5-specific collectors may read:

- SoC and component temperatures from available Linux thermal zones;
- throttling or undervoltage flags exposed by the installed firmware and kernel tooling;
- fan speed where the carrier provides tachometer data;
- eMMC, NVMe, or SD health using tooling appropriate to the installed storage;
- RTC, external-power, and backup-power state where the carrier or UPS exposes them.

Linux thermal readings are operational indicators, not calibrated environmental measurements. Hardware-specific values should derive canonical `host_temperature`, `storage_status`, or power metrics only when their meaning is known. Raw sensor and kernel values may remain in the infrastructure-monitoring namespace.

Use the active Ubuntu time service as the source for `time_sync_status` and `timestamp_skew`. Current Ubuntu releases may use Chrony, while older or upgraded installations may use `systemd-timesyncd`. Detect the active service and do not run competing synchronization daemons.

### 19.3 Continuity Monitoring

Infrastructure monitoring should additionally alert on:

- out-of-memory events, sustained swapping, and unexpected reboots;
- read-only filesystems, I/O errors, inode exhaustion, and storage wear indicators;
- carrier loss, packet errors, upstream reachability, and persistent delivery retries;
- failed telemetry-related systemd units and unexpected collector restarts;
- certificate or credential expiry, authentication failures, and unexpected configuration changes.

These signals may explain canonical health state but are not themselves required OCETS series unless listed in the Canonical Metric Catalogue.

### 19.4 Initial Alert Policy

Deployments should tune alerts from observed baselines. The following are starting values:

| Condition | Warning | Critical |
|---|---:|---:|
| Data freshness | greater than 2 times expected sample interval | greater than 3 times expected sample interval |
| Buffer utilization | 70 percent | 90 percent |
| Storage utilization | 75 percent | 90 percent |
| Timestamp skew | 1 s | 5 s |
| CPU utilization | 80 percent for 15 min | 95 percent for 15 min |
| Available memory | less than 15 percent | less than 5 percent |
| CM5 host temperature | 75 degC | 82 degC |

Any known `failed`, `disconnected`, or `unsynchronized` state should alert immediately. Sustained increases in mapping, validation, transport, authentication, disk, or network errors should alert even when utilization remains below a threshold. Buffer age should alert when it exceeds the deployment's recovery or delivery objective.

## 20. Topology and Registry API

Generate topology views from versioned Registry Relationships. Do not maintain independent dashboard-only connection maps. Cache Registry API resources by `ETag` and persist the source `registry_revision` with generated configuration so an operator can reproduce the deployed state.

Large clients should consume `/snapshot` for bootstrap and `/changes` for incremental refresh. They should complete cursor pagination against one revision and restart traversal if that revision is no longer retained.

## 21. Security Baseline

Use separate credentials for field Devices, edge collectors, central collectors, Registry API clients, and human operators. Prefer short-lived service credentials or rotatable certificates, encrypted transport, tenant-scoped authorization, and a managed secret store. Registry YAML and generated configuration should reference secret identifiers rather than contain secret values.

Record Registry revisions, manual corrections, Event acknowledgements, access-policy changes, and credential rotations in an audit system. A network VPN may reduce exposure but does not replace authentication or authorization.
