Skip to main content

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"
}
FieldUpdated byDescription
desiredCloud (operators, automation)Configuration the device should apply
desiredVersionAuto-incrementedMonotonic counter, incremented on each desired update
reportedDevice (via MQTT)Current device state as reported by firmware
reportedVersionAuto-incrementedMonotonic counter, incremented on each reported update
tagsCloud onlyMetadata labels, not visible to the device
lastActivityAuto-updatedTimestamp 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

  1. The MQTT Provider receives the message
  2. The reported properties are stored in the database (atomic update)
  3. reportedVersion is incremented
  4. lastActivity is set to the current time
  5. The properties are forwarded to the message queue as a ProviderClientPropertiesChanged message 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 the location tag
  • Remove a key: {"location": null} — deletes the location tag
  • 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:

QueryTopic
Get desiredbt.provider.mqtt.query.get-desired.*{deviceId}
Get reportedbt.provider.mqtt.query.get-reported.*{deviceId}
Get device databt.provider.mqtt.query.get-device-data.*{deviceId}

Next Steps