Skip to content
AI-driventedge-dot is developed largely by AI coding agents, and it is alpha software. Check what it does before you rely on it, and report what you find.

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.

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.

Start from an example (it replaces the signals and the configuration):Replace the signals and the configuration with a ready-made case.
  • Point temperature polls it every 1s ([connector] poll_interval).

  • Point setpoint polls it every 1s ([connector] poll_interval).

  • Point pressure subscribes: the server samples it every 500ms and sends changes (point sampling_interval).

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.

Paste TOMLLoad your own configuration. The signals follow: one per point.
[connector]
reportThe reporting policy of every point that does not set its own: which readings are published. — reporting policy
See what it publishes in the outcome below.
batchHow measurement output groups readings into measurements, for every point that does not set its own. — grouping in measurement output
See how each policy groups readings in the batching view below.
[mqtt] 127.0.0.1:1883
[connection] shared connection settings

shared connection settings

[[device]] line1
Device settings type, default_mode, max_inflight, points_from, protocol_address
Point libraries, comma-separated (e.g. demo-sim)

protocol_address

Points

temperaturefloat64 · °C · polled · own report

address

reportThis point's reporting policy, merged key by key over its device's and [connector]'s. — this point's reporting policy, over [connector]'s
See what it publishes in the outcome below.
batchThis point's grouping in measurement output, merged over its device's and [connector]'s. — this point's grouping, over [connector]'s
See how each policy groups readings in the batching view below.
setpointfloat64 · °C · polled · own report

address

reportThis point's reporting policy, merged key by key over its device's and [connector]'s. — this point's reporting policy, over [connector]'s
See what it publishes in the outcome below.
batchThis point's grouping in measurement output, merged over its device's and [connector]'s. — this point's grouping, over [connector]'s
See how each policy groups readings in the batching view below.
pressurefloat64 · bar · subscribed 500ms

address

reportThis point's reporting policy, merged key by key over its device's and [connector]'s. — this point's reporting policy, over [connector]'s
See what it publishes in the outcome below.
batchThis point's grouping in measurement output, merged over its device's and [connector]'s. — this point's grouping, over [connector]'s
See how each policy groups readings in the batching view below.
Valid against the contract schema
[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.

Package: both packages run this configuration, with the same outcome; tedge‑dot‑c is the default. The package picker chooses for your gateway.

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
182022240:002:004:006:008:0010:00

Polled every 1s. A heartbeat on a polled point is its next poll after max_interval.

Published samples as a table
Reading timeValuePublished atWhy
0:0019.640:00change
0:0220.290:02change
0:0419.710:04change
0:0720.260:07change
0:1819.700:18change
0:2620.430:26change
0:3719.820:37change
0:4020.530:40change
0:4219.810:42change
0:4520.500:45change
0:5419.980:54change
0:5920.520:59change
2:4121.112:41change
3:1620.473:16change
3:2121.223:21change
3:3520.683:35change
3:3621.283:36change
3:3920.693:39change
3:4021.463:40change
3:4220.753:42change
3:4321.303:43change
3:5520.773:55change
3:5721.313:57change
5:1921.835:19change
5:3421.325:34change
5:3621.845:36change
6:4722.356:47change
6:5821.836:58change
7:0022.377:00change
7:2521.837:25change
7:3522.407:35change
8:3922.958:39change
8:4122.198:41change
8:4222.768:42change
8:5022.188:50change
8:5122.898:51change
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 after max_interval. A subscribed OPC UA point (a monitored item) is sampled by the server every sampling_interval and 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_interval is 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.

batch on [connector]time: readings with the same timestamp share a measurement. frame: readings within a window. sample: every value at a fixed period.
Readings with the same timestamp share a measurement. Nothing is lost, no time is moved. A signal that published nothing at that time is not in the measurement.
[connector]
output = "measurements"
batch = { group_by = "time" }
The same samples, three ways to publish them (0:00 to 0:30). Each mark is a measurement with one cell per signal, in the order of the configured points: a hollow cell is a signal missing from that measurement.
samplestimeframe 500mssample 5s0:000:050:100:150:200:250:30
Readings
1209
Samples after report
49
Measurements
47
Series sent
48
Signal missing
93
Samples not sent
0
temperaturesetpointpressuremeasurementstemperature = 19.64 at 0:00, in measurement 1setpoint = 20 at 0:00, in measurement 1pressure = 2 at 0:00.001, in measurement 2temperature = 20.29 at 0:02, in measurement 3temperature = 19.71 at 0:04, in measurement 4temperature = 20.26 at 0:07, in measurement 5temperature = 19.7 at 0:18, in measurement 6temperature = 20.43 at 0:26, in measurement 7Measurement 1 at 0:00: temperature, setpoint; missing pressureMeasurement 2 at 0:00.001: pressure; missing temperature, setpointMeasurement 3 at 0:02: temperature; missing setpoint, pressureMeasurement 4 at 0:04: temperature; missing setpoint, pressureMeasurement 5 at 0:07: temperature; missing setpoint, pressureMeasurement 6 at 0:18: temperature; missing setpoint, pressureMeasurement 7 at 0:26: temperature; missing setpoint, pressure0:000:050:100:150:200:250:30
  • 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
#timetemperaturesetpointpressure
0:0019.6420missing
0:00.001missingmissing2
0:0220.29missingmissing
0:0419.71missingmissing
0:0720.26missingmissing
0:1819.7missingmissing
0:2620.43missingmissing

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 frameUnder sample
The report policy held a reading back (deadband, on_change)Missing: nothing was publishedPresent: the value it holds is sent again
The signal is polled less often, or pushed only on changeMissing from the measurements in betweenPresent, with the value it holds
The signal’s reads fail (an outage)Missing until it recoversMissing until its next good reading
The signal has not been read yetMissingMissing

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, but temperature (deadband) and setpoint (on change) are missing whenever their policy held the reading, and outdoor is polled every 5 s, so it is in one measurement out of five. Switch to sample: every measurement holds all three.
  • Pushed points, times ms apart with time, then frame: under time every sample is its own measurement, because no two source times match. A 100 ms frame puts the samples of one burst together and moves a time by a few milliseconds.
  • A signal drops out: outlet fails for a quarter of the run. Under every policy it is missing during the outage; under sample with an aggregate, its mean counts only the good time.
  • A spike between samples with sample, then with aggregate = ["mean", "min", "max"]: sample-and-hold sends the value held at each sample time and misses the spike; pressure_max keeps 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.