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 configuration request 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/requestconfig/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 (ACTIVE, INACTIVE, OFFLINE)
  • 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.

Slots

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

FieldDescription
slot_numberPrimary position identifier on the device (e.g. 1, 2, 3)
idUUID of the slot record; used as a stable fallback identifier
ProductThe catalogue item assigned to this slot
QuantityCurrent stock level
CapacityMaximum stock level
StatusAvailability: AVAILABLE, OUT_OF_STOCK, RESERVED

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.

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 the device publishes its acknowledgement referencing the same ID.

Commands progress through the following states:

StatusMeaning
PENDINGCreated, not yet published to MQTT
SENTPublished to the device's command topic
ACKNOWLEDGEDDevice confirmed receipt
COMPLETEDDevice confirmed successful execution
FAILEDDevice reported failure, or the command timed out

Commands are accessible through the REST API.

Events

An event is a message a device sends to report something that happened. Events are classified into two pipelines:

  • Operational events - health, status changes, errors, and diagnostics. Used for monitoring.
  • Usage events - completed billable interactions such as a successful vend or session. Forwarded 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
│ └── each Slot is assigned one Product (from GPM)
│ └── priced via the Pricing module (PCM)
├── has many Commands (cloud → device instructions)
└── has many Events (device → cloud messages)
EntityPurpose
DeviceRegistered physical unit
SlotProduct 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.

When a slot runs to zero quantity it transitions to OUT_OF_STOCK and will reject purchase commands until restocked.

MQTT Topics

Device firmware uses MQTT to communicate with the Raytio cloud. All topics are scoped to a specific tenant and device, which ensures isolation - a device only receives its own commands and can only publish on its own event topics.

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}commands, events, or config
{message_type}Specific message kind

Topic reference

TopicDirectionPurpose
dcm/{tenant_id}/{device_id}/commands/requestCloud → DeviceDeliver a command
dcm/{tenant_id}/{device_id}/commands/responseDevice → CloudAcknowledge a command
dcm/{tenant_id}/{device_id}/events/operationalDevice → CloudHealth and status events
dcm/{tenant_id}/{device_id}/events/usageDevice → CloudBillable usage events
dcm/{tenant_id}/{device_id}/config/requestDevice → CloudRequest authoritative configuration
dcm/{tenant_id}/{device_id}/config/setCloud → DeviceDeliver authoritative configuration
dcm/{tenant_id}/{device_id}/config/getCloud → DeviceRequest device to report current applied config
dcm/{tenant_id}/{device_id}/config/reportDevice → CloudDevice reports current applied config

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

Message format

All messages use JSON. Every message carries a correlation_id that links a command request to its response.

Command request (cloud → device):

{
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
"message_type": "HALT",
"device_id": "a1b2c3d4-0000-0000-0000-000000000001",
"slot_number": 3,
"issued_at": "2026-06-19T09:00:00Z"
}

Command response (device → cloud):

{
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
"message_type": "HALT_ACK",
"device_id": "a1b2c3d4-0000-0000-0000-000000000001",
"slot_number": 3,
"status": "ACCEPTED",
"responded_at": "2026-06-19T09:00:00.412Z"
}

Operational event (device → cloud):

{
"correlation_id": "7f3d9e1a-0000-0000-0000-000000000002",
"message_type": "SLOT_STATUS",
"device_id": "a1b2c3d4-0000-0000-0000-000000000001",
"slot_number": 3,
"status": "OUT_OF_STOCK",
"occurred_at": "2026-06-19T09:15:00Z"
}
note

The correlation_id in an event is a new UUID generated by the device for each event. It is not related to any pending command. For command responses, the correlation_id must exactly match the value from the originating command request.

Command Flow

Commands follow a request/response cycle. The cloud creates the command record, publishes it to the device's MQTT topic, and then 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 FAILED and no charge is applied.

Halt / set / release pattern

Remote purchases - such as buying a drink from a vending machine through an app - use a three-step sequence to prevent race conditions when multiple users might trigger the same slot simultaneously.

StepCommandEffect
1. HaltHALTReserves the slot. No other purchase can claim it. Slot status becomes RESERVED.
2. SetSETDevice dispenses the product. Payment is captured.
3. ReleaseRELEASEReservation lifted. Slot returns to AVAILABLE (or OUT_OF_STOCK if quantity reached zero).

Failure handling:

  • If HALT times out or fails, the cloud abandons the purchase. No charge is applied and no slot is reserved.
  • If SET fails after a successful HALT, the cloud sends RELEASE to free the slot and reverses any pending payment authorisation.
  • If RELEASE fails, the slot remains RESERVED. An operator must investigate and manually release it.

Event Processing

Events arrive from the device over MQTT and are classified by the cloud before being persisted.

Operational events

Event typeTrigger
DEVICE_ONLINEDevice connected to the MQTT broker
DEVICE_OFFLINEDevice disconnected (last-will message)
SLOT_STATUSSlot quantity changed or slot transitioned to OUT_OF_STOCK
DEVICE_ERRORDevice reported a hardware fault
MAINTENANCE_REQUESTDevice flagged a maintenance need (e.g. paper jam, door open)

Usage events

Event typeTrigger
VEND_SUCCESSProduct successfully dispensed from a slot
SESSION_COMPLETETime-limited session ended normally
SESSION_TIMEOUTSession ended because the configured maximum duration was reached

Usage events are not stored in dcm_device_events. They are written to the billing pipeline and are visible through the Billing module (PBM).

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)Each slot references a product item. The product defines what is in the slot - its name, description, 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)Usage 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