Skip to main content

Device Control System (DCM)

The Device Control Module (DCM) connects IoT devices deployed in the field to the Raytio platform. Examples include vending machines, showers, and pool tables, but any device type can be modelled. It provides remote command dispatch, real-time event capture, and a configurable slot-and-product model that adapts to different device types without requiring code changes.

Tenant administrators use DCM to register and configure devices. API integrators use the REST API to trigger purchases and query device state. Device firmware developers use MQTT to send events and receive commands, and to fetch authoritative configuration at startup.

Architecture​

Devices communicate with the cloud over MQTT. The Raytio database is the authoritative source of truth for device state; the cloud backend keeps the two sides in sync.

Devices subscribe to their command and configuration topics and publish on their event and status topics. The cloud backend translates between MQTT messages and database records. Administrators and integrators interact exclusively through the REST API — no direct MQTT access is needed.

Device firmware and the REST API

Device firmware does not call the REST API directly. Firmware fetches its authoritative configuration over MQTT using the config/request → config/set flow. Management clients (admin UI, integration tools) read and write slots and device configuration through the REST API. See Config Discovery for the full contract.

Core Concepts​

Devices​

A device is a registered physical unit. Each device has:

  • A unique device ID (UUID) assigned at registration
  • A device type (e.g. VENDING_MACHINE, SHOWER, POOL_TABLE) that determines its capabilities and attribute configuration
  • A status reflecting its current operational state
  • A linked location indicating where it is physically installed
  • A linked party (organisation or person) that owns or operates it

Devices are registered and managed through the REST API.

Device status​

StatusMeaning
PROVISIONINGDevice registered; certificate not yet issued
ONLINEDevice is connected and communicating with the cloud
OFFLINEDevice is not reachable
MAINTENANCEDevice is undergoing maintenance; connectivity events are ignored
DECOMMISSIONEDDevice has been permanently removed from service

ONLINE and OFFLINE are set automatically when the device connects or disconnects — firmware does not set its own status.

Device identity​

Each device has three identifiers that serve different purposes:

IdentifierFormatUsed for
Device IDUUID (e.g. a1b2c3d4-…)MQTT topic segments, API paths
Thing namedcm-{tenant short}-{device_key} (e.g. dcm-t1000000-SHOW-001)MQTT client ID and device shadow
Device keyHuman-readable short code (e.g. SHOWER-001)Display in the web client and event payloads

The thing name is created by Raytio when the device certificate is issued. It is included in the config.json download and in the config/set payload under device.iot_thing_name. Firmware must be configured with the exact thing name from config.json.

Slots​

A slot is a logical position or channel on a device — the unit that holds a product or service offering.

FieldDescription
slot_numberPrimary position identifier on the device (e.g. 1, 2, 3)
slot_idUUID of the slot record; stable fallback identifier
Item catalogThe list of items (items[]) the slot is configured to sell
item_idThe item for the currently active session (nullable; cleared on end)
quantityCurrent stock level
capacityMaximum stock level
StatusAvailability — see slot status table below

For example, a vending machine typically has one slot per product row, a shower can have multiple slots (one per shower head or bay), and a pool table might have a single slot representing the session itself.

Slot status​

StatusMeaning
AVAILABLESlot is ready for use
IN_USESlot is currently occupied by an active session
RESERVEDSlot is reserved and not available for general use
EMPTYSlot has no product
DISABLEDSlot has been taken out of service

Slots are managed through the REST API. When slot data is included in MQTT payloads, slot_number is the primary identifier and slot_id (the UUID) is the fallback.

Firmware discovers its current slot assignments by sending a config/request with slots in the sections list. The backend responds on config/set with the authoritative slot list for that device. See Config Discovery.

Commands​

A command is an instruction sent from the cloud to a device. Commands follow a request/response pattern over MQTT: the cloud publishes the command with a correlation ID and action, and the device publishes its acknowledgement referencing the same correlation ID.

Command status​

StatusMeaning
PENDINGCreated, not yet published to MQTT
SENTPublished to the device's command topic
ACKNOWLEDGEDDevice confirmed receipt
COMPLETEDDevice confirmed successful execution
TIMED_OUTNo device response received within the timeout
FAILEDDevice reported failure

TIMED_OUT and FAILED are distinct: a timeout means the device did not reply at all; a failure means the device replied with an error.

Commands are accessible through the REST API.

Command types​

Command typeDescription
GET_STATUSRequest current device status
SET_STATUSUpdate device operational status
HALT_STATUSTemporarily suspend device operations (halt/release pattern)
RELEASE_HALTResume device from halted state
SELECT_TIMERStart a timed session on a slot
SELECT_PRODUCTInitiate product dispensing from a slot
CANCEL_TIMERCancel an active timer session
SET_PRICESUpdate pricing configuration on the device
GET_PRICESRetrieve current pricing from the device
GET_LOGSRetrieve operational logs from the device
DISPENSETrigger product dispensing

Events​

An event is a message a device sends to report something that happened. Events are classified by type:

  • Operational events — health, status changes, errors, and diagnostics. These appear on the device's Events tab in the web client.
  • Billable events — completed interactions such as a successful dispense or session. Written to the Billing module (PBM) for metered charging.

Events are available through the REST API.

Data Model​

The four core entities and their relationships:

Device
├── has many Slots
│ ├── items[] — item catalog: many items a slot can sell
│ └── item_id — the item for the current active session (nullable)
├── has many Commands (cloud → device instructions)
└── has many Events (device → cloud messages)
EntityPurpose
DeviceRegistered physical unit
SlotService position on a device
CommandInstruction sent to a device
EventMessage received from a device

Commands and events are both linked to a device but represent opposite directions: commands flow cloud-to-device, events flow device-to-cloud.

MQTT Topics​

Device firmware uses MQTT to communicate with the Raytio cloud. All topics for a device are scoped to its tenant and device ID.

Topic naming convention​

dcm/{tenant_id}/{device_id}/{category}/{message_type}
SegmentDescription
dcmFixed module prefix
{tenant_id}UUID of the tenant that owns the device
{device_id}UUID of this device
{category}command, events, status, config, or logs
{message_type}Specific message kind

Topic reference​

TopicDirectionPurpose
dcm/{tenant_id}/{device_id}/command/{ACTION}Cloud → DeviceDeliver a command (action is the command type code)
dcm/{tenant_id}/{device_id}/command/{ACTION}/responseDevice → CloudAcknowledge or complete a command
dcm/{tenant_id}/{device_id}/eventsDevice → CloudAll device events (operational and billable)
dcm/{tenant_id}/{device_id}/status/reportDevice → CloudDevice reports current shadow and slot state
dcm/{tenant_id}/{device_id}/status/getCloud → DeviceRequest device to send a status report
dcm/{tenant_id}/{device_id}/logsDevice → CloudDevice publishes a log batch
dcm/{tenant_id}/{device_id}/logs/getCloud → DeviceRequest device to send its log buffer
dcm/{tenant_id}/{device_id}/config/requestDevice → CloudDevice requests authoritative configuration
dcm/{tenant_id}/{device_id}/config/setCloud → DeviceCloud delivers authoritative configuration
dcm/{tenant_id}/{device_id}/config/getCloud → DeviceRequest device to report its currently applied config
dcm/{tenant_id}/{device_id}/config/reportDevice → CloudDevice reports currently applied config
$aws/things/{thing_name}/shadow/updateDevice → CloudDevice updates its AWS IoT classic shadow
$aws/things/{thing_name}/shadow/update/deltaCloud → DeviceShadow service delivers desired-state delta

For details on the config topics, request sections, and payload shapes, see Config Discovery.

Message format​

All messages use JSON. Every command carries a correlation_id that links it to its response, and an action that names the command type.

Command (cloud → device):

{
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
"action": "SELECT_TIMER",
"payload": {
"slot_number": 1,
"duration_seconds": 300
}
}

Command response (device → cloud):

{
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "COMPLETED",
"success": true
}

Event (device → cloud):

{
"event_type": "HEARTBEAT",
"event_severity": "INFO",
"device_key": "SHOWER-001"
}
note

The correlation_id in a command response must exactly match the value from the originating command. Events do not reference command correlation IDs.

Command Flow​

Commands follow a request/response cycle. The cloud creates the command record, publishes it to the device's MQTT command topic, and waits for a response keyed by correlation_id. The command record is updated at each stage.

If the device does not respond within the configured timeout, the command moves to TIMED_OUT.

Halt / set / release pattern​

Remote purchases use a three-step sequence to prevent race conditions when multiple users might trigger the same slot simultaneously.

StepActionEffect
1. HaltHALT_STATUSPauses local UI on the device; slot cannot be claimed locally
2. Actione.g. DISPENSE / SELECT_TIMER / SELECT_PRODUCTDevice executes the purchase
3. ReleaseRELEASE_HALTReleases the pause; device resumes normal operation

The halt and release commands use derivative correlation IDs: the HALT_STATUS command uses {id}:halt and the RELEASE_HALT uses {id}:release, where {id} is the main command's correlation ID.

Failure handling:

  • If HALT_STATUS times out or fails, the cloud abandons the purchase — no charge is applied.
  • If the action fails after a successful HALT_STATUS, the cloud sends RELEASE_HALT to free the device.
  • If RELEASE_HALT fails, the device remains halted and an operator must investigate.

Event Processing​

Events arrive from the device over MQTT on the …/events topic and are classified by the cloud before being persisted.

Operational events​

Published on dcm/{tenant_id}/{device_id}/events with event_type set to one of:

Event typeTrigger
CONNECTEDDevice connected to the cloud (set automatically)
DISCONNECTEDDevice disconnected from the cloud (set automatically)
CONNECTIVITYDevice-reported connectivity change
HEARTBEATPeriodic connectivity confirmation from the device
STATUS_CHANGEDevice operational status changed
TIMER_STARTA timed session has begun
TIMER_ENDA timed session has ended normally
TIMER_CANCELA timed session was cancelled
ERRORAn error condition occurred on the device
STOCK_LOWProduct stock in a slot is running low

Billable events​

Event typeTrigger
PAYMENTPayment received for a session or vend
SESSION_COMPLETETime-limited session ended normally
DISPENSEProduct successfully dispensed from a slot

Billable events are written to the billing pipeline and are visible through the Billing module (PBM). The backend validates the item_id in a billable event against the slot's configured item catalog before accepting it.

Event payload​

All events share a common payload structure:

{
"event_type": "HEARTBEAT",
"event_severity": "INFO",
"device_key": "SHOWER-001"
}

Additional fields depend on the event type. Billable events include slot_id, item_id, and quantity.

Status Reports and the Device Shadow​

The device publishes its current state on dcm/{tenant_id}/{device_id}/status/report. The backend stores the JSON body as the device's shadow_state and updates last_seen_at.

{
"shadow_state": {
"device_key": "SHOWER-001",
"halted": false
},
"slots": [
{
"slot_number": 1,
"slot_status_code": "AVAILABLE",
"quantity": 0
}
]
}

The cloud also syncs device state to the AWS IoT classic shadow via $aws/things/{thing_name}/shadow/update. The shadow delta topic (…/shadow/update/delta) delivers desired-state changes back to the device.

Logs / GET_LOGS flow​

The cloud sends a GET_LOGS command to request the device's in-memory log buffer. The device publishes its log entries on dcm/{tenant_id}/{device_id}/logs. Log batches appear on the device's Events tab in the web client, and the pending GET_LOGS command is marked as COMPLETED.

The device can also publish log batches proactively on dcm/{tenant_id}/{device_id}/logs without a preceding logs/get request.

Connectivity and Presence​

ONLINE and OFFLINE device statuses are set automatically when the device connects or disconnects — the device does not publish these itself. A CONNECTED event is recorded when the device connects; a DISCONNECTED event is recorded when it disconnects.

Devices in MAINTENANCE, PROVISIONING, or DECOMMISSIONED status are not affected by connectivity changes.

Extensible Attributes​

Different device types need different configuration fields. DCM handles this through the Extensible Fields framework, which stores device-type-specific attributes without requiring schema changes.

For a complete explanation of how device-type categories, attribute definitions, and attribute values are stored and delivered to firmware, see Config Discovery.

ModuleRelationship
Products (GPM)Slot item catalogs reference GPM items. Each item defines a name and category.
Pricing (PCM)Session and vend prices are resolved through the pricing module at purchase time, based on the active price list for the device's tenant.
Party Relationships (PRM)Devices are linked to the party (organisation or person) that owns or operates them.
Billing (PBM)Billable events from devices feed into the billing pipeline.
Foundation (FND)Device types, command types, event types, and slot statuses are defined as lookup values. The Extensible Fields framework provides device-type categories and attributes.

Further reading​