Skip to main content

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:

AudienceAccess methodWhat they can do
Management clients (admin UI, integration tools)REST APICreate, update, and delete slots and device configuration
Device firmwareMQTT config/requestconfig/set flowRead-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}
TopicDirectionPurpose
dcm/{tenant_id}/{device_id}/config/requestDevice → CloudDevice requests authoritative config from the cloud
dcm/{tenant_id}/{device_id}/config/setCloud → DeviceCloud delivers authoritative config to the device
dcm/{tenant_id}/{device_id}/config/getCloud → DeviceCloud asks the device to report its current applied config
dcm/{tenant_id}/{device_id}/config/reportDevice → CloudDevice reports its current applied config

config/request and config/set form the firmware startup pair: the device asks and the cloud answers.

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 sections array controls which parts of the config are included in the response.

Request payload

{
"correlation_id": "d3f1a2b4-0000-0000-0000-000000000001",
"device_id": "a1b2c3d4-0000-0000-0000-000000000001",
"sections": [
"device",
"slots",
"categories",
"attributes",
"attribute_values"
]
}
FieldTypeRequiredDescription
correlation_idUUIDYesUnique identifier for this request; echoed in the config/set response
device_idUUIDYesUUID of this device (must match the topic segment)
sectionsarray of stringsYesWhich parts of the config to include in the response

sections values

SectionWhat the response includes
deviceDevice record fields: device key, device name, device type, status, and stored attribute values
slotsSlot list for the device with slot_number, slot_id, product details, quantity, capacity, and slot status
categoriesCategory definitions assigned to this device type
attributesAttribute definitions for those categories
attribute_valuesActual attribute values stored for this device

A device that only needs to refresh its slot list after a restocking event can send "sections": ["slots"]. A device that needs a full re-initialisation sends all five sections.

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",
"device_id": "a1b2c3d4-0000-0000-0000-000000000001",
"device": {
"device_key": "VEND-001",
"device_name": "Lobby Vending Machine",
"device_type_code": "VENDING_MACHINE",
"device_status_code": "ACTIVE"
},
"slots": [
{
"slot_number": 1,
"slot_id": "b2c3d4e5-0000-0000-0000-000000000001",
"product_id": "f6a7b8c9-0000-0000-0000-000000000001",
"product_name": "Cola 375ml",
"quantity": 12,
"capacity": 24,
"slot_status_code": "AVAILABLE"
},
{
"slot_number": 2,
"slot_id": "c3d4e5f6-0000-0000-0000-000000000002",
"product_id": "g7b8c9d0-0000-0000-0000-000000000002",
"product_name": "Water 600ml",
"quantity": 0,
"capacity": 18,
"slot_status_code": "OUT_OF_STOCK"
}
],
"categories": [
{
"category_id": "e4f5a6b7-0000-0000-0000-000000000001",
"category_code": "VENDING_CONFIG",
"category_name": "Vending Machine Configuration"
}
],
"attributes": [
{
"attr_id": "f5a6b7c8-0000-0000-0000-000000000001",
"category_id": "e4f5a6b7-0000-0000-0000-000000000001",
"attr_code": "TEMPERATURE_ZONE",
"attr_name": "Temperature Zone",
"data_type": "LOOKUP",
"is_required": true
},
{
"attr_id": "g6b7c8d9-0000-0000-0000-000000000002",
"category_id": "e4f5a6b7-0000-0000-0000-000000000001",
"attr_code": "COMPRESSOR_ENABLED",
"attr_name": "Compressor Enabled",
"data_type": "BOOLEAN",
"is_required": false
}
],
"attribute_values": [
{
"attr_id": "f5a6b7c8-0000-0000-0000-000000000001",
"attr_code": "TEMPERATURE_ZONE",
"value_text": null,
"value_number": null,
"value_boolean": null,
"value_lookup": "CHILLED"
},
{
"attr_id": "g6b7c8d9-0000-0000-0000-000000000002",
"attr_code": "COMPRESSOR_ENABLED",
"value_text": null,
"value_number": null,
"value_boolean": true,
"value_lookup": null
}
]
}

Slot payload fields

FieldDescription
slot_numberPrimary slot identifier. Integer position on the device (1, 2, 3, …). Use this to index into slot-specific operations.
slot_idFallback/secondary identifier. UUID of the slot record in the database. Stable across slot reconfigurations.

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 typeValue column
TEXTvalue_text
NUMBERvalue_number
BOOLEANvalue_boolean
LOOKUPvalue_lookup (stores the lookup value code)
DATEvalue_date

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:

  1. The device connects using its X.509 certificate. The certificate restricts publish/subscribe to dcm/{tenant_id}/{device_id}/*.
  2. The device publishes DEVICE_ONLINE so the backend records connectivity state.
  3. The device subscribes to config/set before publishing config/request, to avoid missing the response.
  4. The cloud receives the config/request, reads device, slot, and attribute data, and publishes the config/set response.
  5. The device applies the received config - slot assignments, attribute values, device name - and is ready for commands.

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. Check that the MQTT message is routed to the backend

Verify the IoT Rule that routes messages from dcm/+/+/# to the backend is active. If the rule is disabled or missing, config/request messages are silently dropped.

3. Check backend logs for errors

Look for error messages in the backend logs. Common causes:

  • JSON parse error in the request payload
  • Missing sections field (required)
  • device_id in the payload does not match a registered device
  • Backend connectivity timeout

4. Check that a config sync job was created

If no config sync job was created for the device, the backend failed before it could process the request.

5. 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.

Further reading