DCM Device Config Discovery
When a DCM device starts up, it needs to know which product slots it has, what their properties are, and any device-type-specific configuration values. This information is delivered to the device over MQTT - firmware does not call any HTTP endpoint directly.
This page explains the full config discovery contract: MQTT topics, request payload, response payload, extensible attribute storage, and what to check when config responses do not arrive.
Who reads and writes config
Config access is split between two audiences with different protocols:
| Audience | Access method | What they can do |
|---|---|---|
| Management clients (admin UI, integration tools) | REST API | Create, update, and delete slots and device configuration |
| Device firmware | MQTT config/request → config/set flow | Read-only: request the authoritative config the cloud holds for this device |
Firmware never writes config back. If a device needs to report its currently applied config, it publishes on config/report. If the cloud needs to audit what a device has applied, it sends config/get.
MQTT config topics
All config topics follow the standard DCM topic convention:
dcm/{tenant_id}/{device_id}/config/{message_type}
| Topic | Direction | Purpose |
|---|---|---|
dcm/{tenant_id}/{device_id}/config/request | Device → Cloud | Device requests authoritative config from the cloud |
dcm/{tenant_id}/{device_id}/config/set | Cloud → Device | Cloud delivers authoritative config to the device |
dcm/{tenant_id}/{device_id}/config/get | Cloud → Device | Cloud asks the device to report its current applied config |
dcm/{tenant_id}/{device_id}/config/report | Device → Cloud | Device reports its current applied config |
config/request and config/set form the firmware startup pair: the device asks and the cloud answers synchronously.
config/get and config/report form the cloud audit pair: the cloud asks and the device answers. These are used by operators and tools to inspect what config a device currently has loaded, without any database lookup.
config/request — device asks for authoritative config
The device publishes on config/request to ask the cloud for the latest configuration. The optional sections array controls which parts of the config are included in the response.
Request payload
{
"correlation_id": "d3f1a2b4-0000-0000-0000-000000000001",
"sections": [
"device",
"slots",
"categories",
"attributes",
"attribute_values"
]
}
| Field | Type | Required | Description |
|---|---|---|---|
correlation_id | UUID | No | Unique identifier echoed back in the config/set response if provided |
sections | array of strings | No | Which parts of the config to include. Missing or empty means all sections |
The backend identifies the device from the MQTT topic path — device_id is not needed in the payload.
sections values
| Section | What the response includes |
|---|---|
device | Device record fields: device key, device name, device type, status, and IoT endpoint details |
slots | Slot list for the device with slot identifiers, item catalog, quantity, capacity, and slot status |
categories | Category definitions assigned to this device type |
attributes | Attribute definitions for those categories |
attribute_values | Actual attribute values stored for this device |
A device that only needs to refresh its slot list can send "sections": ["slots"]. A device that needs a full re-initialisation sends all five sections, or omits sections entirely.
config/set — cloud delivers authoritative config
The cloud publishes config/set in response to a config/request. The payload mirrors the sections the device requested and includes only the data for those sections.
Response payload
{
"correlation_id": "d3f1a2b4-0000-0000-0000-000000000001",
"tenant_id": "t1000000-0000-0000-0000-000000000001",
"device_id": "a1b2c3d4-0000-0000-0000-000000000001",
"requested_sections": [
"device",
"slots",
"categories",
"attributes",
"attribute_values"
],
"generated_at": "2026-06-19T09:00:00.123456+00:00",
"device": {
"device_id": "a1b2c3d4-0000-0000-0000-000000000001",
"device_key": "SHOWER-001",
"device_name": "Poolside Shower Unit 1",
"device_type_code": "SHOWER",
"device_status_code": "ONLINE",
"iot_thing_name": "dcm-t1000000-SHOW-001",
"iot_endpoint_name": "abc123.iot.ap-southeast-2.amazonaws.com",
"firmware_version": null,
"metadata": null
},
"slots": [
{
"slot_id": "b2c3d4e5-0000-0000-0000-000000000001",
"slot_number": 1,
"slot_name": "Shower Head 1",
"slot_status_code": "AVAILABLE",
"item_id": null,
"quantity": "0",
"slot_uom": null,
"capacity": null,
"slot_state": null,
"metadata": null,
"items": [
{
"item_id": "f6a7b8c9-0000-0000-0000-000000000001",
"item_key": "SHW-SVC-5MIN",
"item_name": "5-Minute Shower",
"display_order": 10
},
{
"item_id": "f6a7b8c9-0000-0000-0000-000000000002",
"item_key": "SHW-SVC-10MIN",
"item_name": "10-Minute Shower",
"display_order": 20
},
{
"item_id": "f6a7b8c9-0000-0000-0000-000000000003",
"item_key": "SHW-SVC-15MIN",
"item_name": "15-Minute Shower",
"display_order": 30
}
]
}
],
"categories": [
{
"category_id": "e4f5a6b7-0000-0000-0000-000000000001",
"category_code": "SHOWER_CONFIG",
"category_name": "Shower Configuration",
"category_description": null,
"parent_category_id": null,
"entity_type": "DCM_DEVICE",
"applies_to_type": "SHOWER",
"display_order": 10,
"metadata": null
}
],
"attributes": [
{
"attr_id": "f5a6b7c8-0000-0000-0000-000000000001",
"category_id": "e4f5a6b7-0000-0000-0000-000000000001",
"attr_code": "HEARTBEAT_INTERVAL",
"attr_name": "Heartbeat Interval (seconds)",
"data_type": "NUMBER",
"lookup_code": null,
"default_value": "60",
"is_required": true,
"is_searchable": false,
"is_filterable": false,
"validation_rule": null,
"uom_code": null,
"display_order": 10,
"display_group": null,
"metadata": null
}
],
"assignments": [
{
"assignment_id": "a1b2c3d4-0000-0000-0000-0000000000a1",
"entity_type": "DCM_DEVICE",
"entity_id": "a1b2c3d4-0000-0000-0000-000000000001",
"category_id": "e4f5a6b7-0000-0000-0000-000000000001",
"is_primary": true,
"metadata": null
}
],
"attribute_values": [
{
"value_id": "v1a2b3c4-0000-0000-0000-000000000001",
"entity_type": "DCM_DEVICE",
"entity_id": "a1b2c3d4-0000-0000-0000-000000000001",
"attr_id": "f5a6b7c8-0000-0000-0000-000000000001",
"value_text": null,
"value_number": 30.0,
"value_date": null,
"value_boolean": null,
"uom_code": null,
"metadata": null
}
]
}
Top-level envelope fields
| Field | Description |
|---|---|
correlation_id | Echoed from the request, if provided |
tenant_id | UUID of the owning tenant |
device_id | UUID of the device |
requested_sections | Sorted list of sections included in this response |
generated_at | ISO 8601 timestamp of when the payload was built |
Device payload fields
| Field | Description |
|---|---|
device_id | UUID of this device |
device_key | Human-readable identifier (e.g. SHOWER-001) |
device_name | Friendly display name |
device_type_code | Device type lookup code (e.g. SHOWER, VENDING_MACHINE) |
device_status_code | Current operational status |
iot_thing_name | AWS IoT Thing name used as the MQTT client ID |
iot_endpoint_name | AWS IoT Core endpoint hostname for this device |
firmware_version | Firmware version string, if recorded |
metadata | Arbitrary JSON metadata |
Firmware can compare device.iot_thing_name in this payload against the thing name it was configured with to verify they match.
Slot payload fields
| Field | Description |
|---|---|
slot_id | UUID of the slot record |
slot_number | Primary slot identifier. Integer position on the device (1, 2, 3, …) |
slot_name | Friendly name for the slot (e.g. Shower Head 1) |
slot_status_code | Current availability status (AVAILABLE, IN_USE, RESERVED, EMPTY, DISABLED) |
item_id | The item for the currently active session — null when the slot is idle |
quantity | Current stock level (decimal string) |
slot_uom | Unit of measure for quantity, if applicable |
capacity | Maximum stock level |
slot_state | Arbitrary JSON state blob maintained by the device |
metadata | Arbitrary JSON metadata |
items | Item catalog — the list of items this slot is configured to sell. Each entry has item_id, item_key, item_name, and display_order |
config/get and config/report — cloud audits device state
The cloud uses this pair to inspect what config a device currently has applied. This is distinct from config/request/config/set, which delivers what the cloud thinks the device should have.
config/get (cloud → device):
{
"correlation_id": "e4a2b3c4-0000-0000-0000-000000000003",
"sections": ["slots", "attribute_values"]
}
The device responds on config/report with its currently applied values in the same sections format as config/set.
config/report (device → cloud):
{
"correlation_id": "e4a2b3c4-0000-0000-0000-000000000003",
"device_id": "a1b2c3d4-0000-0000-0000-000000000001",
"slots": [ ... ],
"attribute_values": [ ... ]
}
Extensible attribute storage
Device-type configuration uses the Extensible Fields framework. The framework stores categories, attribute definitions, and attribute values separately, linked by device type and device instance. This lets each device type have its own set of configuration fields without requiring schema changes.
Category and attribute hierarchy
Category (scoped to device type)
└── Attribute definitions
└── Category assignments (links device type → category)
└── Attribute values (links device instance → attribute → typed value)
Attributes use typed value columns. Only the column matching the attribute's data_type is populated; the rest are null.
| Attribute data type | Value column |
|---|---|
TEXT | value_text |
NUMBER | value_number (serialized as a float) |
BOOLEAN | value_boolean |
LOOKUP | value_text (stores the lookup value code) |
DATE | value_date (ISO 8601 string) |
End-to-end device startup flow
The following sequence describes a device connecting to the cloud for the first time and fetching its full configuration.
Key points in this flow:
- The device connects using its X.509 certificate and private key. Connectivity state is driven by AWS IoT presence, not by a message the device publishes.
- The device subscribes to
config/setbefore publishingconfig/request, to avoid missing the response. - The cloud receives the
config/request, reads device, slot, and attribute data, and publishes theconfig/setresponse synchronously. - The device applies the received config — slot assignments, attribute values, device name — then publishes a status report and starts its heartbeat.
Troubleshooting missing config responses
If the device publishes config/request but never receives config/set, work through this checklist:
1. Confirm the device is subscribed to config/set before publishing config/request
The device must subscribe to dcm/{tenant_id}/{device_id}/config/set before publishing the request. If the subscription is established after the response is published, the message is lost (QoS 0) or queued to the next session (QoS 1). Subscribe first.
2. Verify the device is registered and active in the web client
Open Devices → All Devices and confirm the device is listed and its status is not PROVISIONING or DECOMMISSIONED. A device that has not completed provisioning cannot receive config.
3. Confirm slots and items are configured
Open the device's Slots tab and confirm each slot has its item catalog set up. The config/set response includes an empty slot list if no slots are configured.
4. Confirm the device UUID matches
The backend extracts tenant_id and device_id from the MQTT topic path. If the topic segment uses a device key or any identifier other than the device's UUID, the backend will not find the device record and will drop the message silently.
If none of these checks resolve the issue — the topic is correct, the IDs match, and the device and slots are configured — no error is sent back on the config topics. Re-check the IDs, then contact Raytio support.
Further reading
- Device Control System - architecture, commands, events, and the full MQTT topic reference
- Lookups and Reference Data - how device type codes and attribute data types are defined