Reducing data volume on a metered link
A Modbus register polled every 2 seconds produces 43,200 samples a day, and each becomes a measurement in the cloud, even when the value never moves. On a cellular or satellite link you pay for every one of them.
What you’ll build: a reporting policy that sends a temperature only when it moves by more than its noise, a status bit only when it flips, a heartbeat that keeps the device available in Cumulocity, and a way to check what the connector actually applies.
Where the policy applies
Section titled “Where the policy applies”The report table is applied by the SDK runtime, in front of the sample topic, the same way
for every connector and in both the Rust and the C build. A reading the policy holds back never
reaches the broker, so every consumer (the measurement, alarm, event and parameter flows, and
any tool subscribed to the samples) sees the reduced stream. Without a report anywhere, every
reading is published.
Some readings are always published, whatever the policy: the first one after a start, a reload,
a reconnect, a write or a restored broker connection, and any reading whose quality changed.
A failing read is therefore reported at once, and so is the recovery.
Build the policy
Section titled “Build the policy”-
Keep the heartbeat. Cumulocity marks a device unavailable when it sends nothing within its required interval (often 60 minutes), and a device that publishes only on change can be quiet for hours. The packaged Modbus, OPC UA, CANopen and PROFIBUS configs already set a 30-minute heartbeat on the connector:
Viewpackaging/config/modbus.toml report = { max_interval = "30m" }packaging/config/modbus.tomlon GitHubA heartbeat is always a fresh reading (the next poll, or an on-demand read of a pushed OPC UA node), never an old value sent again. A CAN frame signal or an SNMP trap cannot be read on demand, so it gets no heartbeat.
-
Filter the analog values with a deadband. A temperature that moves by 0.1 °C of noise does not need to be sent. A deadband publishes a reading only once it is far enough from the last published value, so a slow drift still gets through when it adds up. Add a
min_intervalto cap the rate: a change that arrives too soon is held and published when the interval ends, with its own timestamp, so the last change of a burst is never lost.The demo library’s boiler temperature does both:
Viewdemo/points.d/modbus/demo-sim.toml [[point]]id = "temp_scaled"name = "Boiler temperature"description = "Measured boiler loop temperature, scaled to °C"datatype = "uint16"address = { table = "holding", address = 3, count = 1 }transform = { decimal_shift = -3 }unit = "°C"report = { deadband = 0.5, min_interval = "10s" }demo/points.d/modbus/demo-sim.tomlon GitHubFor a value whose scale varies a lot, such as a flow rate, a percent deadband fits better:
report = { deadband = "2%" }. -
Send states on change. A status word, a mode or a coil matters when it flips:
report = { on_change = true }. Booleans, strings and 64-bit integers compare exactly. -
Debounce what chatters. A float switch that bounces as it changes state:
report = { debounce = "2s" }publishes the new state once it has held for 2 seconds, and a short-lived value is never sent. -
Set defaults once.
reportcan be set on[connector], on a[[device]]and on a point (in a point library or in the configuration). The effective policy is merged key by key, the most specific level winning for each key it sets. A duration of"0"switches off an inherited setting, andon_change = falsewithdeadband = 0switches off change detection.
Fewer messages, or a trend instead of dropped readings
Section titled “Fewer messages, or a trend instead of dropped readings”A policy decides which readings are sent. With measurement output
(output = "measurements" in [connector]), the connector also decides how many messages
carry them: it publishes thin-edge measurements itself, one per device and poll for polled
points, so the mapper converts one message where it converted one per reading. The point’s
batch table chooses how readings are grouped: by exact time (the default), by a frame of
a few hundred milliseconds for pushed points, or by sample at a fixed rate.
When a policy would drop readings you still want to account for, a sample policy can publish
the time-weighted mean, minimum and maximum of each period instead:
[[device]]name = "line1"batch = { aggregate = ["mean", "min", "max"], every = "1m" }report = { max_interval = "0" } # switch off the heartbeat inherited from [connector]An aggregate uses every reading, so a point with aggregate whose merged report policy still
filters is refused when the configuration loads. The packaged configs set a heartbeat on
[connector], hence the max_interval = "0"; the aggregate’s own period keeps the device
available. The batching view of the config workbench
shows each policy on sample readings, and the
measurement output reference has the
rules.
Apply and check it
Section titled “Apply and check it”The service picks up a changed config file on its own within a few seconds, or at once with
sudo systemctl reload tedge-dot. The connector then publishes the policies it applies in its
retained capability descriptor, under reports:
tedge mqtt sub 'te/device/main/service/tedge-dot-modbus/ot/capabilities' # retained: shown at oncedefault is the [connector] table, devices the tables declared on devices, and points the
effective, merged policy of each point that differs from its device. When a point is quieter
than you expect, look there first. A sample’s seq counts published samples only, so a gap in
seq still means a sample was lost on the way, never that the policy held it back.