IoT Platforms and the Cloud
Managing many devices from one place: IoT platforms, device registries, device twins and shadows, digital twins, rules and data pipelines, and the trade-offs of cloud platforms versus self-hosting.
What an IoT platform does
An IoT platform is the software that connects a fleet of devices and lets people and other systems work with them as a whole: it accepts their connections, knows which devices exist and what they may do, keeps track of their state, routes their data to storage and applications, and helps update and manage them. It can be a cloud service, a product you install on your own servers, or a set of open-source parts you put together.
The data devices send about themselves and their measurements is telemetry, in the same sense as in observability. The other direction, from the platform to the device, carries settings, commands and firmware updates. Most of this page is about keeping those two directions in order for hundreds or millions of devices that are often offline.
Two running examples show the two ends of the scale. Lotte Kortenhoeve's home setup is a "platform" of one: Mosquitto and Home Assistant on a Raspberry Pi 5. The Kestrel Home pilot is a fleet of about 250 P1 dongles, ESP32-C3-based devices sold under Kestrel's name, that have been sending live power to Kestrel's own MQTT broker since September 2026, each with its own certificate (see Provisioning and Over-the-Air Updates).
The device registry
A device registry is the platform's list of every device it knows: a stable identifier per device, its credentials or certificate fingerprint, metadata such as model, firmware version, location or owner, and its status (active, disabled, retired). It answers the questions operations asks every day: which devices exist, which are on firmware 1.3, which have not connected for a week, which belong to this customer?
For the pilot, each dongle's registry entry links its factory serial
to a pilot home number, such as h0017 for Lotte's home;
the provisioning page shows a complete entry. Two design rules
matter more than the exact fields:
- The registry is the source of truth for access. A dongle that is not active in the registry cannot connect, and one that is retired stays blocked even if someone finds it in a drawer.
-
Keep personal data out of identifiers. The
registry stores the home number; the link from home number to
customer lives in Kestrel's customer systems, with their own
access rules. Topics, logs and dashboards see only
h0017.
Device twins and shadows
Devices are offline a lot: asleep to save power, on a bad connection, unplugged. A platform therefore cannot just send a setting and assume it arrived. The common answer is a JSON document per device, kept in the cloud, with two halves:
- Desired: the state the platform or an operator wants, written by the back end.
- Reported: the state the device says it is in, written by the device.
The difference between the two is what the device still has to do.
When a device connects, it reads the document, applies what differs
and reports back; when an application needs a device's state, it
reads the document instead of waking the device. Microsoft's Azure
IoT Hub calls this document a
device twin: per device, it holds
tags (metadata only the back end sees), desired properties and
reported properties. Amazon's AWS IoT Core calls it a
device shadow, with
desired, reported and a computed
delta section; a device can have one unnamed (classic)
shadow and several named shadows. An AWS-style shadow document looks
like this (illustrative):
{
"state": {
"desired": { "report_interval_s": 10, "led": "off" },
"reported": { "report_interval_s": 60, "led": "off", "firmware": "1.4.0" },
"delta": { "report_interval_s": 10 }
},
"version": 42
}
The pilot dongles send a 10-second average of the power by default. A dongle that has just been reset to its factory settings reports 60 seconds. The delta is the list of desired values the device has not reported yet:
import json
# Twin of a pilot dongle that was just reset to its factory settings.
desired = {"report_interval_s": 10, "led": "off"}
reported = {"report_interval_s": 60, "led": "off",
"firmware": "1.4.0", "wifi_rssi_dbm": -67}
def delta(desired, reported):
"""Desired values the device has not reported yet."""
return {key: value for key, value in desired.items()
if reported.get(key) != value}
print("delta:", json.dumps(delta(desired, reported), sort_keys=True))
# The dongle applies the setting and reports its new state.
reported["report_interval_s"] = 10
print("delta:", json.dumps(delta(desired, reported), sort_keys=True))
delta: {"report_interval_s": 10}
delta: {}
Values the back end does not set, such as the firmware version and the Wi-Fi signal strength, appear only under reported, and never in the delta. An empty delta means the device is where the platform wants it. A delta that stays non-empty for days is worth an alert: the device is offline, or it receives the setting but cannot apply it.
Device twin versus digital twin
The names are confusingly close. A device twin or shadow is a small
state document about one device's configuration and status. A
digital twin
is a model of a physical asset, process or network, kept in step
with live data so that people can simulate and predict: a model of a
transformer, a building or a whole grid. A digital twin may use the
data in many device twins, but a JSON document with
"led": "off" simulates nothing.
Messages to the device
A cloud-to-device message is a message the platform sends to one device, such as a command ("reboot", "send a status report now") or a notification. Platforms offer several ways to reach a device, and choosing the right one avoids most problems:
| Mechanism | Good for | If the device is offline |
|---|---|---|
| Desired state in the twin or shadow | Settings that should simply be true: report interval, LED, schedule | Applied when the device next connects |
| Queued message to the device | One-off actions that may wait a little | Kept for a limited time, then expired |
| Direct call that expects an answer | Interactive actions: "blink now" while a technician watches | Fails at once; the caller must handle that |
| Job across many devices | Firmware updates, certificate rotation, reboots in waves | Picked up when each device connects; progress tracked per device |
Prefer desired state over commands where you can. "The report interval should be 10 seconds" stays correct however late it arrives and however often it is applied; "lower your interval by 50 seconds" does damage if it is applied twice. Give every command an expiry, so a "reboot" queued during an outage does not reach a device three days later at the worst possible moment.
Rules and data pipelines
A rules engine is the part of a platform that evaluates every incoming message against a set of rules and acts on the matches: it filters, transforms and routes messages to storage, queues, functions or alerts, without code on the devices changing. In AWS IoT Core the rules are written in a SQL-like language over MQTT topics. A plausibility rule for the pilot could look like this (illustrative):
SELECT topic(3) AS home, watts, time
FROM 'kestrel/home/+/p1/power'
WHERE watts > 30000
The rule picks the home number out of the topic (the third level) and sends implausible values to a data-quality queue for a person to look at; a second rule sends every message to a time-series store. Azure IoT Hub does the same with message routing to other Azure services. In a self-hosted setup a rules engine can be a small service that subscribes to the broker, or the automations in Home Assistant.
From there on, IoT data is ordinary data engineering: a streaming pipeline into a store built for timestamps. What is special about device data (event time versus arrival time, late and missing values, retention and downsampling) is on IoT Data: Time Series and Streams.
The big cloud platforms
The two largest cloud providers both offer IoT services, built from the same parts but under different names. They change often, so the status below is dated.
AWS IoT Core is Amazon Web Services' managed service for connecting devices: an MQTT message broker with X.509 certificate authentication, a registry, device shadows and a rules engine. Around it sit AWS IoT Jobs for firmware updates, reboots and certificate rotation, fleet provisioning for giving devices their certificates at scale, and AWS IoT Greengrass v2, an open-source runtime for running cloud-managed software on edge devices.
Azure IoT Hub is Microsoft Azure's managed service for connecting devices: device-to-cloud and cloud-to-device messaging, device twins, direct methods and message routing, with the Device Provisioning Service (DPS) for enrolling devices. It speaks MQTT 3.1.1 on port 8883 and over WebSockets on port 443, but Microsoft's own documentation states that IoT Hub is not a full-featured MQTT broker: it does not support QoS 2, and retained messages are not kept. For a standards-based broker, Azure offers the MQTT broker in Event Grid (MQTT 3.1.1 and 5, also without QoS 2).
| Service | Status as of October 2026 |
|---|---|
| AWS IoT Core, Jobs, Greengrass v2 | Current |
| AWS IoT Greengrass V1 | End of support on 7 October 2026; move to v2 |
| AWS IoT Analytics | Ended on 15 December 2025 |
| AWS IoT Events | Ended on 20 May 2026 |
| AWS IoT FleetWise | Closed to new customers since 30 April 2026 |
| Azure IoT Hub, DPS, Event Grid MQTT broker | Current |
| Azure IoT Central | No new applications since 23 September 2026; existing ones run until 20 September 2029. Microsoft points to IoT Hub with DPS and Microsoft Fabric instead. |
| Google Cloud IoT Core | Discontinued in August 2023 |
Cloud platform or self-hosted?
The alternative to a cloud IoT service is running the parts yourself: an MQTT broker such as Mosquitto, a time-series database, your own registry and rules, or an open-source IoT platform that bundles them. Lotte's home runs this way, and so does the pilot: Kestrel runs its own broker. ADR-0011, still proposed, records the pilot's design (MQTT over TLS with a certificate per dongle) and the options it weighed for a larger rollout:
| Question | Cloud IoT service | Self-hosted |
|---|---|---|
| Getting started | Quick: certificates, registry and rules are built in | Quick for one broker; slow for registry, provisioning and jobs |
| Scale and availability | The provider's problem, within service limits | Yours: clustering, failover, upgrades, on-call |
| Cost | Per message, connection time and state operation; grows with the fleet | Servers and staff; mostly fixed |
| Lock-in | Twins, rules and jobs are provider-specific | Standard MQTT; you own the data model |
| Data location | Choose an EU region and check the contract | Wherever you run it |
| Service lifetime | The provider may retire a service | You decide, and you maintain it |
Cost depends mostly on how many messages the fleet sends, so work that out before comparing prices. The pilot's numbers, for the payload size alone (MQTT headers and TLS add more on the wire):
DONGLES = 250
DAYS = 30
PAYLOAD = '{"time":"2026-10-07T19:30:15+02:00","watts":1234}'
size = len(PAYLOAD.encode("utf-8")) # bytes; MQTT and TLS add overhead
print(f"payload: {size} bytes")
for interval_s in (1, 10, 60):
per_day = DONGLES * 86_400 // interval_s
per_month = per_day * DAYS
megabytes = per_month * size / 1_000_000
print(f"every {interval_s:2} s: {per_day:>10,} messages/day, "
f"{per_month:>11,}/month, {megabytes:8,.1f} MB/month")
payload: 49 bytes every 1 s: 21,600,000 messages/day, 648,000,000/month, 31,752.0 MB/month every 10 s: 2,160,000 messages/day, 64,800,000/month, 3,175.2 MB/month every 60 s: 360,000 messages/day, 10,800,000/month, 529.2 MB/month
At one message per dongle every 10 seconds the pilot sends 64.8 million messages a month; at every second it would be ten times as many. Put those numbers into each provider's pricing calculator, and do the same sum for a full rollout to all dynamic-tariff customers. For live power data the data residency question matters as much as the price: one-second or ten-second power use is personal data under the GDPR, wherever it is stored. The general trade-off is the one on vendor lock-in: the more of the provider's own features (twins, rules, jobs) you use, the more work it is to leave.