Set up and test a DCM IoT device
Introduction
This tutorial walks you through setting up a test IoT device that communicates with the DCM (Device Control Management) backend over MQTT. By the end you will have:
- a device registered with Raytio,
- a catalog of purchasable items with prices, assigned to the device's slots,
- a device certificate issued from a CSR (certificate signing request) generated on the device — the private key never leaves the device,
- a local simulator connecting to the IoT platform over MQTT,
- the device requesting and applying its authoritative config via
config/request, - and verification that the backend is receiving telemetry and delivering config.
The tutorial uses a shower facility as the example: one device with three shower heads (slots), each selling 5, 10, and 15-minute shower sessions. This matches the simulator shipped in the DCM SDK. Replace the device key, item names, and prices with your own values throughout.
Steps that touch the Raytio backend can be performed either in the web client or via the API — use the toggle on each step to see your preferred flow. Your choice is remembered across steps. API requests use a bearer token; see the API documentation for authentication details.
Prerequisites
- A Raytio account with DCM administrator access to your tenant.
- The Raytio
dcm-sdkrepository checked out locally. uvinstalled (the SDK uses it for Python environment management).opensslavailable on the device (or test machine) for key and CSR generation.
You do not need AWS credentials or database access. Certificate issuance and device provisioning are handled by Raytio.
Identity reference
Before you begin, understand the identifiers that interact throughout this tutorial:
| Identifier | What it is | Where it appears |
|---|---|---|
| Device ID | UUID assigned by Raytio when the device is registered | MQTT topic segments, simulator config, API paths |
| Device key | Human-readable short code you choose (e.g. SHOWER-001) | Display, certificate CN, simulator logging |
| Topic prefix | dcm/{tenant_id}/{device_id} | All MQTT topics for the device |
| Certificate | X.509 certificate issued by Raytio from your CSR | Device connection (mTLS) |
MQTT topics must use the device ID (UUID) as the topic segment — not the device key. The backend extracts the tenant ID and device ID from the topic path. Topics using the device key will not match any device record and messages will be silently dropped. The config.json you download in Step 6 contains the correct values.
Step 1 — Register the device
- Web client
- API
In the web client, open the admin dashboard and navigate to Devices → All Devices, then create a new device:
| Field | Example value | Purpose |
|---|---|---|
| Device key | SHOWER-001 | Human-readable identity, unique within your tenant |
| Device name | Test Shower Unit - Poolside | Friendly label shown in the UI |
| Device type | SHOWER | Determines which config categories apply |
The device is created with a PROVISIONING status. Copy the device ID (UUID) from the device list — you will need it in later steps.
curl -X POST "https://api-dev-regional-tf-1.rayt.io/v2/dcm/devices" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"device_key": "SHOWER-001",
"device_name": "Test Shower Unit - Poolside",
"device_type_code": "SHOWER"
}'
The device is created with a PROVISIONING status. The response includes the generated device id (UUID) — you will need it in later steps.
Step 2 — Create the shower items and prices
Each shower duration a customer can buy is an item, with a price attached. Create three items and three prices:
| Item key | Item name | Item type | Amount | Currency | Billing |
|---|---|---|---|---|---|
SHOWER_5MIN | 5-Minute Shower | SERVICE | 2.00 | NZD | One-time |
SHOWER_10MIN | 10-Minute Shower | SERVICE | 3.50 | NZD | One-time |
SHOWER_15MIN | 15-Minute Shower | SERVICE | 5.00 | NZD | One-time |
Each shower session is a one-time charge, so the billing recurrence is ONCE.
- Web client
- API
- Navigate to Items and create the three items above, marking each as purchasable with item type
SERVICE. - Navigate to Prices and create one price per item with the amount, currency, and one-time billing shown above.
# Create an item (repeat for each duration)
curl -X POST "https://api-dev-regional-tf-1.rayt.io/db/v1/gpm_items" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{
"item_key": "SHOWER_5MIN",
"item_name": "5-Minute Shower",
"item_description": "A 5-minute shower session",
"item_type": "SERVICE",
"purchased": true
}'
# Create a price for the item (use the item id from the previous response)
curl -X POST "https://api-dev-regional-tf-1.rayt.io/db/v1/pcm_prices" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{
"item_id": "<item_id>",
"price_currency": "NZD",
"amount": 2.00,
"billing_recurrence": "ONCE"
}'
Step 3 — Create the device slots
Each slot represents a physical shower head. Create three slots:
| Slot number | Slot name | Status |
|---|---|---|
| 1 | Shower Head 1 | AVAILABLE |
| 2 | Shower Head 2 | AVAILABLE |
| 3 | Shower Head 3 | AVAILABLE |
Leave the slot's item field empty. A slot's item field represents the currently active session — it is set when a customer starts a session and cleared when the session ends. The list of items a slot can sell is configured separately in the next step.
- Web client
- API
Navigate to Devices → All Devices, select your device, and open the Slots tab. Create the three slots above.
curl -X POST "https://api-dev-regional-tf-1.rayt.io/v2/dcm/devices/<device_id>/slots" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slot_number": 1,
"slot_name": "Shower Head 1"
}'
Repeat for slot numbers 2 and 3.
Step 4 — Assign items to each slot
Each slot advertises the items it can sell through its slot item catalog. This is what allows one shower head to offer 5, 10, and 15-minute sessions — the customer picks the duration at the point of purchase.
Add all three shower items to each slot with a display order:
| Item | Display order |
|---|---|
| 5-Minute Shower | 1 |
| 10-Minute Shower | 2 |
| 15-Minute Shower | 3 |
Repeat for all three slots (nine catalog entries in total). Display order controls how the device presents the options — shortest and cheapest first.
Adding or changing slot items automatically queues a config sync, so a connected device receives the updated catalog without manual intervention.
- Web client
- API
Select a slot in the Slots tab and open its Slot Items tab. Add the three items with their display order, then repeat for the other two slots.
curl -X POST "https://api-dev-regional-tf-1.rayt.io/db/v1/dcm_device_slot_items" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{
"slot_id": "<slot_id>",
"item_id": "<item_id>",
"display_order": 1
}'
Repeat for each slot/item combination.
These two fields have different semantics and are easy to confuse:
- Slot item (single, nullable) — the item for the currently active session on the slot. Set when a purchase begins, cleared when the timer ends.
- Slot item catalog (list) — the static menu of items the slot is configured to sell. The device uses this list to present options to a customer, and the backend validates purchases against it.
Step 5 — Generate a key and CSR on the device
The device generates its own private key and a certificate signing request (CSR) locally. The private key never leaves the device — Raytio only ever sees the CSR.
On the device (or your test machine), run:
mkdir -p ~/dcm-shower-001 && cd ~/dcm-shower-001
openssl req -new -newkey rsa:2048 -nodes \
-keyout device.key \
-out device.csr \
-subj "/CN=SHOWER-001"
This produces two files:
| File | Purpose |
|---|---|
device.key | Private key — stays on the device, keep secret |
device.csr | Certificate signing request — uploaded to Raytio in the next step |
Step 6 — Issue the device certificate
- Web client
- API
In the web client, navigate to Devices → All Devices, select your device row, and choose the Issue Device Certificate action. Paste the contents of device.csr (or upload the file) and click Issue Certificate.
On success, the dialog offers two downloads:
| File | Contents |
|---|---|
device.pem.crt | The X.509 device certificate issued from your CSR |
config.json | Connection details: IoT endpoint, tenant ID, device ID, device key, topic prefix |
Download both and transfer them to the device, alongside the device.key you generated in Step 5.
curl -X POST "https://api-dev-regional-tf-1.rayt.io/v2/dcm/devices/<device_id>/certificates" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"csr\": \"$(awk '{printf "%s\\n", $0}' device.csr)\"}"
The response contains the certificate PEM plus everything the device needs to connect: endpoint, tenant_id, device_id, topic_prefix, and a root_ca_url for the Amazon root CA. Save the certificate PEM as device.pem.crt next to the device.key you generated in Step 5.
The issued certificate also appears in the device's Certificates tab, and the device status moves out of PROVISIONING.
Step 7 — Write the simulator config file
Create a YAML config file in the testbench/ directory of the dcm-sdk repository, using the values from config.json (or the certificate API response) and the paths to your local certificate and key files:
tenant_id: <tenant_id from config.json>
device_id: <device_id from config.json>
device_key: SHOWER-001
endpoint: <endpoint from config.json>
cert_path: /home/you/dcm-shower-001/device.pem.crt
key_path: /home/you/dcm-shower-001/device.key
heartbeat_interval: 30
device_id is the UUID the simulator uses as the MQTT topic segment. device_key is used for display and logging only.
You do not need to list slots in the config — the simulator discovers its slots and their item catalogs from the backend on startup.
Step 8 — Run the simulator
From the root of the dcm-sdk repository, run the test bench in cloud mode:
uv run python -m testbench run \
--mode cloud \
--config testbench/shower-001.yaml \
--duration-seconds 300
The --mode cloud flag connects to the IoT platform over MQTT using your certificate. On startup, the simulator will:
- Connect using the X.509 certificate and private key.
- Publish
DEVICE_ONLINEonevents/operational. - Subscribe to
config/set. - Publish
config/requestwithsections: ["device", "slots", "categories", "attributes", "attribute_values"]. - Wait for the
config/setresponse and apply the received config.
Watch for the config delivery confirmation in the output:
Requesting config from backend
Config received — active slots: slot_1, slot_2, slot_3 (config_source=server_config)
Each slot in the received config carries its items array — the catalog you configured in Step 4:
{
"slot_number": 1,
"item_id": null,
"items": [
{"item_id": "…", "item_key": "SHOWER_5MIN", "item_name": "5-Minute Shower", "display_order": 1},
{"item_id": "…", "item_key": "SHOWER_10MIN", "item_name": "10-Minute Shower", "display_order": 2},
{"item_id": "…", "item_key": "SHOWER_15MIN", "item_name": "15-Minute Shower", "display_order": 3}
]
}
If the output shows config_source=fallback_config, the backend did not respond to the config request within the timeout and the simulator fell back to locally generated slots. This means the end-to-end path is not working — see Troubleshooting below.
Step 9 — Verify the backend received messages
While the simulator is running (or after it has completed), confirm the backend processed the device messages.
- Web client
- API
Navigate to Devices → All Devices and select your device:
- The device row shows an updated last seen timestamp after the first heartbeat.
- The Events tab shows a
DEVICE_ONLINEevent, followed byHEARTBEATevents at the configured interval. - The Slots tab shows all three slots reporting
AVAILABLEwith no active item.
# Recent device events — expect DEVICE_ONLINE followed by HEARTBEAT events
curl "https://api-dev-regional-tf-1.rayt.io/v2/dcm/devices/<device_id>/events" \
-H "Authorization: Bearer $TOKEN"
# Current slot state — expect all three slots AVAILABLE with no active item
curl "https://api-dev-regional-tf-1.rayt.io/v2/dcm/devices/<device_id>/slots" \
-H "Authorization: Bearer $TOKEN"
Troubleshooting
Config set never arrives (config_source=fallback_config)
-
Device and tenant IDs match — Confirm
tenant_idanddevice_idin your simulator YAML exactly match the values in the downloadedconfig.json. A mismatch produces a topic the backend does not recognise, and messages are silently dropped. -
Slots and items configured — The config response includes an empty slot list if Steps 3 and 4 were skipped. Check the Slots tab and each slot's Slot Items tab.
-
Certificate is active — Check the device's Certificates tab in the web client and confirm the certificate status is
ACTIVE. If you re-issued a certificate, make sure the device is using the matchingdevice.pem.crt— a certificate and key from different issuance rounds will fail the TLS handshake.
For a complete walkthrough of config discovery, see Config Discovery — Troubleshooting.
The simulator cannot connect at all
- Endpoint — Confirm the
endpointvalue matchesconfig.jsonexactly. - Certificate and key paths — Confirm
cert_pathpoints to the downloadeddevice.pem.crtandkey_pathto thedevice.keygenerated in Step 5, and that both files are readable. - Key pair mismatch — The certificate was issued from the CSR created with your private key. If you regenerated the key after issuing the certificate, generate a new CSR and issue a new certificate (Steps 5 and 6).
The device connects but no events appear
- Topic uses the device UUID — The MQTT topic must use the device ID (UUID), not the device key. Check the simulator config file.
- Device registered in your tenant — Confirm the device appears under All Devices and its device ID matches your config.
If the connection succeeds and the configuration is correct but events still do not appear, contact your Raytio administrator or support — the remaining failure modes are on the backend side.
Summary
You have set up a complete end-to-end test path:
- Registered a device through the web client (or API).
- Created purchasable shower items with prices.
- Created three slots representing the shower heads.
- Assigned the item catalog to each slot, so one shower head can sell 5, 10, and 15-minute sessions.
- Generated a private key and CSR on the device, and had Raytio issue a certificate from the CSR — without the private key ever leaving the device.
- Configured the simulator from the downloaded
config.jsonand local certificate files. - Ran the test bench in cloud mode; the simulator connected, published online, and discovered its slot and item config from the backend.
- Confirmed message delivery and config discovery through the web client (or API).
For the conceptual background on how config discovery works, including all MQTT topic payloads, see Config Discovery. For the overall device control architecture, commands, and event flow, see Device Control System.