Device Twins
Device twins maintain a synchronized view of device state between the cloud and the device. The twin has two halves — desired (what the cloud wants) and reported (what the device says it is) — plus tags for cloud-side metadata.
Twin Structure
{
"deviceId": "temp-sensor-001",
"desired": {
"reporting_interval": 30,
"alert_threshold": 85.0
},
"desiredVersion": 3,
"reported": {
"firmware_version": "2.1.0",
"reporting_interval": 30,
"battery_level": 87
},
"reportedVersion": 12,
"tags": {
"location": "building-a",
"floor": 3
},
"lastActivity": "2026-02-25T10:30:00Z"
}
| Field | Updated by | Description |
|---|---|---|
desired | Cloud (operators, automation) | Configuration the device should apply |
desiredVersion | Auto-incremented | Monotonic counter, incremented on each desired update |
reported | Device (via MQTT) | Current device state as reported by firmware |
reportedVersion | Auto-incremented | Monotonic counter, incremented on each reported update |
tags | Cloud only | Metadata labels, not visible to the device |
lastActivity | Auto-updated | Timestamp of the last reported property update |
Reported Properties (Device → Cloud)
The device publishes its state to the reported properties topic:
devices/{deviceId}/twin/reported (QoS 1)
Payload: A JSON object with the properties to report. This is a full replacement — the entire reported state is overwritten, not merged.
{
"firmware_version": "2.1.0",
"reporting_interval": 30,
"battery_level": 87
}
What Happens on Publish
- The MQTT Provider receives the message
- The reported properties are stored in the database (atomic update)
reportedVersionis incrementedlastActivityis set to the current time- The properties are forwarded to the message queue as a
ProviderClientPropertiesChangedmessage for downstream processing (ingress, time-series storage, flows)
When to Report
Devices should publish reported properties when:
- Startup — Report initial state (firmware version, hardware capabilities)
- After applying desired changes — Confirm the new configuration is active
- State changes — Battery level, connectivity status, error codes
- Periodic heartbeat — Some deployments use a reporting interval
Desired Properties (Cloud → Device)
Operators or automation set desired properties through the platform API. The MQTT Provider then pushes them to the device via MQTT.
Setting Desired Properties
Desired properties are set via the Beacon Tower API (not via MQTT):
curl -X PUT https://api.beacontower.ai/assets/temp-sensor-001/properties/desired \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reporting_interval": 30,
"alert_threshold": 85.0
}'
This triggers the MQTT Provider to publish the desired state to the device.
Receiving Desired Properties
The device subscribes to:
devices/{deviceId}/twin/desired (QoS 1)
When desired properties are updated, the device receives the full desired state:
{
"reporting_interval": 30,
"alert_threshold": 85.0
}
Desired-Reported Reconciliation Pattern
A well-behaved device follows this pattern:
1. Cloud sets desired: {"reporting_interval": 30}
2. Device receives desired on twin/desired topic
3. Device applies the change (updates its interval timer)
4. Device publishes to twin/reported: {"reporting_interval": 30, ...}
5. Operator sees reported matches desired → change confirmed
If the device cannot apply a desired change, it should report its actual state so operators can see the discrepancy.
Tags (Cloud Only)
Tags are cloud-side metadata attached to the device twin. They are not visible to the device over MQTT — tags are managed entirely through the platform API.
# Set tags via API
curl -X PATCH https://api.beacontower.ai/providers/{providerId}/clients/{deviceId}/tags \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"location": "building-a",
"floor": 3
}'
Tags use JSON Merge Patch semantics (RFC 7396):
- Set a key:
{"location": "building-b"}— updates thelocationtag - Remove a key:
{"location": null}— deletes thelocationtag - Unmentioned keys are preserved
Tags are useful for organizing devices, filtering queries, and driving automation rules without modifying the device's own state.
Versioning
Both desiredVersion and reportedVersion are monotonically increasing integers. They enable:
- Conflict detection — Compare versions to detect concurrent updates
- Staleness checks — Verify the device has seen the latest desired state
- Audit trail — Track how many times state has changed
Versions are managed server-side and cannot be set by the device.
Querying Twin State
Via Platform API
# Get reported properties
GET /assets/{assetId}/properties/reported
# Get desired properties
GET /assets/{assetId}/properties/desired
Via Internal Messaging
Internal services query twin state via request-reply messaging:
| Query | Topic |
|---|---|
| Get desired | bt.provider.mqtt.query.get-desired.*{deviceId} |
| Get reported | bt.provider.mqtt.query.get-reported.*{deviceId} |
| Get device data | bt.provider.mqtt.query.get-device-data.*{deviceId} |
Next Steps
- MQTT Protocol Reference — Topic structure and payload formats
- Direct Methods — Invoke commands on connected devices
- Assets and Bindings — Desired/reported properties through the asset API