Config workbench
Build a connector configuration and try it before you deploy it: describe the signals the
connector reads, configure it, and see what it publishes and what reaches Cumulocity. The three
steps share one state, so a change in one shows in the others. Copy or download the TOML when it
does what you want, and put it in /etc/tedge/plugins/ot/ on the gateway.
1. Signals
Section titled “1. Signals”What the connector reads: up to four signals, each with a behaviour and an optional outage, in
which every read of it fails. The point in step 2 with the same id reads a signal; the line
under each signal says how. Or start from an example, which sets the configuration too.
Point
temperaturepolls it every 1s ([connector] poll_interval).Point
setpointpolls it every 1s ([connector] poll_interval).Point
pressuresubscribes: the server samples it every 500ms and sends changes (point sampling_interval).
2. Configuration
Section titled “2. Configuration”Pick a protocol, or start from one of the
demo configs, which are wired to
the demo simulators. Loading a configuration gives it a signal per point (up to four), including
the points a device takes from a point library (points_from). Set the
reporting policy (report) and the grouping in measurement output (batch) on [connector] or
on a point; a point’s own table is merged key by key over [connector]’s, as on the gateway.
Select a point to open it.
[connector]
protocol = "opcua"
service_name = "tedge-dot-opcua"
poll_interval = "1s"
output = "measurements"
measurement = { group = "Process" }
batch = { group_by = "time" }
[mqtt]
host = "127.0.0.1"
port = 1883
[[device]]
name = "line1"
protocol_address = { endpoint = "opc.tcp://192.168.0.20:4840/" }
[[device.point]]
id = "temperature"
datatype = "float64"
access = "read"
unit = "°C"
subscribe = false
report = { deadband = 0.5 }
address = { node_id = "ns=2;s=temperature" }
[[device.point]]
id = "setpoint"
datatype = "float64"
access = "read"
unit = "°C"
subscribe = false
report = { on_change = true, max_interval = "2m" }
address = { node_id = "ns=2;s=setpoint" }
[[device.point]]
id = "pressure"
datatype = "float64"
access = "read"
unit = "bar"
sampling_interval = "500ms"
address = { node_id = "ns=2;s=pressure" }
The schema checks structure. For the full check (point libraries resolved, protocol rules), run tedge-dot describe -c opcua.toml on the gateway.
What is checked, and what the simulation reads
As you type, the configuration is validated against the contract’s
configuration schema, the same schema the
connectors are tested against. The protocol-specific tables (protocol_address, a point’s
address, [connection]) are checked for the types of the keys the
connector references describe; other keys are kept
as they are.
A schema checks structure, not everything the connector does: it cannot open the point
libraries a device names in points_from, for example. On the gateway,
tedge-dot describe -c <file> loads the configuration exactly as the service would and reports
what is wrong with it. Every config in demo/config/ and packaging/config/ passes this
builder in CI, and survives being loaded and written back unchanged.
The simulation reads the first device, with the points of the libraries it names in points_from collected first and its own points applied over them, as the connector does. A point is polled every poll_interval (its own, its
device’s or [connector]’s), except an OPC UA point the connector subscribes to (the default,
unless the point sets subscribe = false): the server samples it every sampling_interval and
sends only changes. The protocol and the addresses do not change how a signal is read otherwise.
3. Outcome
Section titled “3. Outcome”Package: both packages run this configuration, with the same outcome; tedge‑dot‑c is the default. The package picker chooses for your gateway.
What each point publishes
Section titled “What each point publishes”Each point’s readings, and what its effective report policy publishes. Choose a point, and
the run length for the whole outcome.
Effective report of temperature, merged from point:
report = { deadband = 0.5 }Polled every 1s, from [connector] poll_interval.
Change either in the configuration; a preset sets this point's own report and the signal's behaviour.
- Readings
- 601
- Published
- 36
- Saved
- 94%
- Heartbeats
- 0
- Reading
- Published
- Heartbeat
- Held, published later
Polled every 1s. A heartbeat on a polled point is its next poll after max_interval.
Published samples as a table
| Reading time | Value | Published at | Why |
|---|---|---|---|
| 0:00 | 19.64 | 0:00 | change |
| 0:02 | 20.29 | 0:02 | change |
| 0:04 | 19.71 | 0:04 | change |
| 0:07 | 20.26 | 0:07 | change |
| 0:18 | 19.70 | 0:18 | change |
| 0:26 | 20.43 | 0:26 | change |
| 0:37 | 19.82 | 0:37 | change |
| 0:40 | 20.53 | 0:40 | change |
| 0:42 | 19.81 | 0:42 | change |
| 0:45 | 20.50 | 0:45 | change |
| 0:54 | 19.98 | 0:54 | change |
| 0:59 | 20.52 | 0:59 | change |
| 2:41 | 21.11 | 2:41 | change |
| 3:16 | 20.47 | 3:16 | change |
| 3:21 | 21.22 | 3:21 | change |
| 3:35 | 20.68 | 3:35 | change |
| 3:36 | 21.28 | 3:36 | change |
| 3:39 | 20.69 | 3:39 | change |
| 3:40 | 21.46 | 3:40 | change |
| 3:42 | 20.75 | 3:42 | change |
| 3:43 | 21.30 | 3:43 | change |
| 3:55 | 20.77 | 3:55 | change |
| 3:57 | 21.31 | 3:57 | change |
| 5:19 | 21.83 | 5:19 | change |
| 5:34 | 21.32 | 5:34 | change |
| 5:36 | 21.84 | 5:36 | change |
| 6:47 | 22.35 | 6:47 | change |
| 6:58 | 21.83 | 6:58 | change |
| 7:00 | 22.37 | 7:00 | change |
| 7:25 | 21.83 | 7:25 | change |
| 7:35 | 22.40 | 7:35 | change |
| 8:39 | 22.95 | 8:39 | change |
| 8:41 | 22.19 | 8:41 | change |
| 8:42 | 22.76 | 8:42 | change |
| 8:50 | 22.18 | 8:50 | change |
| 8:51 | 22.89 | 8:51 | change |
How to read the chart
The workbench runs the reporting policy of the connector SDK in your browser. Its engine is a
port of the Rust SDK’s state machine, and the docs build runs it against every case in the
contract’s report-policy test vectors, the same
vectors the Rust and C SDKs run. So the chart shows what tedge-dot does, not an
approximation of it.
- A polled point is read every
poll_interval; its heartbeat is its next poll aftermax_interval. A subscribed OPC UA point (a monitored item) is sampled by the server everysampling_intervaland delivered only when it changed, with a source time a few milliseconds after the sampling time; its heartbeat is an on-demand read. - A reading held back by
min_intervalis published when the interval ends, with its own timestamp (the dashed line shows the wait). - During an outage every read fails. The change of quality is always published at once, in both directions; the line breaks where reads fail.
For the full rules, inheritance between connector, device and point, and per-connector notes, see Reducing data volume.
Batching: from readings to Cumulocity measurements
Section titled “Batching: from readings to Cumulocity measurements”With measurement output
(output = "measurements" in [connector]), the connector publishes the thin-edge measurements
itself, many readings per message. The batch policy decides which readings share a message and
which time it carries. The form here is [connector] batch of the configuration. The strip runs
the same samples through all three policies; the chart below it shows the configured one. Each
measurement has one cell per signal: a hollow cell is a signal missing from it. Select a
measurement to see the message the connector publishes and the measurement Cumulocity stores.
- Readings
- 1209
- Samples after report
- 49
- Measurements
- 47
- Series sent
- 48
- Signal missing
- 93
- Samples not sent
- 0
- Sample, coloured by its measurement
- Sample not sent
- Reading held back by
report - Read failed
- Measurement: a cell per signal, hollow when missing
7 measurements on te/device/line1///m/Process, 0:00 to 0:30, as a table
| # | time | temperature | setpoint | pressure |
|---|---|---|---|---|
| 0:00 | 19.64 | 20 | missing | |
| 0:00.001 | missing | missing | 2 | |
| 0:02 | 20.29 | missing | missing | |
| 0:04 | 19.71 | missing | missing | |
| 0:07 | 20.26 | missing | missing | |
| 0:18 | 19.7 | missing | missing | |
| 0:26 | 20.43 | missing | missing |
Measurement 1 in Cumulocity, on the child device line1:
{
"type": "Process",
"time": "2026-01-01T08:00:00.000Z",
"Process": {
"temperature": {
"value": 19.64,
"unit": "°C"
},
"setpoint": {
"value": 20,
"unit": "°C"
}
}
}Not in it: pressure. Cumulocity stores nothing for that series at this time.
From the connector, published at 0:00 on te/device/line1///m/Process:
{"time":"2026-01-01T08:00:00.000Z","Process":{"temperature":19.64,"setpoint":20}}Why a signal is missing from a measurement
A measurement carries only the series it has values for, and Cumulocity stores nothing for a missing one at that time. Which signals a measurement holds depends on the policy:
Under time and frame | Under sample | |
|---|---|---|
The report policy held a reading back (deadband, on_change) | Missing: nothing was published | Present: the value it holds is sent again |
| The signal is polled less often, or pushed only on change | Missing from the measurements in between | Present, with the value it holds |
| The signal’s reads fail (an outage) | Missing until it recovers | Missing until its next good reading |
| The signal has not been read yet | Missing | Missing |
So time and frame send each value once, when it was read, and a dashboard sees a series
only at its own times. sample sends every signal at every period, so each measurement is
complete, at the price of the readings between two samples (unless you aggregate them).
What to try with the examples
- Three polled points, filtered with
time: one poll is one measurement, buttemperature(deadband) andsetpoint(on change) are missing whenever their policy held the reading, andoutdooris polled every 5 s, so it is in one measurement out of five. Switch tosample: every measurement holds all three. - Pushed points, times ms apart with
time, thenframe: undertimeevery sample is its own measurement, because no two source times match. A 100 msframeputs the samples of one burst together and moves a time by a few milliseconds. - A signal drops out:
outletfails for a quarter of the run. Under every policy it is missing during the outage; undersamplewith an aggregate, its mean counts only the good time. - A spike between samples with
sample, then withaggregate = ["mean", "min", "max"]: sample-and-hold sends the value held at each sample time and misses the spike;pressure_maxkeeps it.
The batching engine is a port of the Rust SDK’s measurement batcher, and the docs build runs it
against every case in the contract’s
measurement-output test vectors, the same
vectors the Rust and C SDKs run. The thin-edge.io mapper turns each message into one Cumulocity
measurement of type Process (the message’s group) and takes the units from the connector’s
retained meta message.