Skip to content

MQTT Topic Structure

Django DeviceHub uses a structured topic hierarchy for MQTT communication. This page documents the topic format, standard channels, wildcard subscriptions, and how to customize topics.

Topic Format

The default topic template is:

{prefix}/{type_name}/{device_id}/{message_type}

Example topics for a WeatherStation device with prefix "myproject":

myproject/weatherstation/STATION-001/data
myproject/weatherstation/STATION-001/status
myproject/weatherstation/STATION-001/cmd

Segments

Segment Source Example
prefix DJANGO_DEVICEHUB["TOPIC_PREFIX"] setting myproject
type_name DeviceType class name, lowercased weatherstation
device_id device.device_id field STATION-001
message_type Channel: data, status, or cmd data

Standard Channels

data

Direction: Device -> Server

Devices publish sensor readings to the data channel. The MessageRouter parses the payload, creates a Reading record, and fires the device_data_received signal.

myproject/weatherstation/STATION-001/data

Payload (flat format):

{
    "temperature": 25.3,
    "humidity": 80.1,
    "timestamp": "2025-01-15T12:00:00+00:00"
}

Payload (readings list format):

{
    "readings": [
        {"type": "temperature", "value": 25.3},
        {"type": "humidity", "value": 80.1}
    ]
}

status

Direction: Device -> Server

Devices publish status updates (heartbeat, battery level, firmware version) to the status channel.

myproject/weatherstation/STATION-001/status

Payload:

{
    "status": "online",
    "battery_level": 85.5,
    "firmware_version": "2.1.0"
}

Valid status values: online, offline, error, maintenance, low_battery, provisioning.

cmd

Direction: Server -> Device

The server publishes commands to the cmd channel. Devices subscribe to this channel to receive instructions.

myproject/weatherstation/STATION-001/cmd

Payload:

{
    "command": "set_interval",
    "payload": {"interval": 60}
}

Commands without payload:

{
    "command": "reboot"
}

Wildcard Subscriptions

The iot_listen command subscribes to topics using MQTT + wildcards for the device_id segment:

myproject/weatherstation/+/data
myproject/weatherstation/+/status

This subscribes to data and status messages from all devices of a given type. The + wildcard matches exactly one topic level.

Generating subscribe topics

topics = WeatherStation.get_subscribe_topics()
# ['myproject/weatherstation/+/data', 'myproject/weatherstation/+/status']

Shared Subscriptions

When USE_SHARED_SUBSCRIPTIONS is enabled (the default), subscriptions use the MQTT 5.0 shared subscription format:

$share/django-devicehub/myproject/weatherstation/+/data

This distributes messages across multiple iot_listen instances, enabling horizontal scaling. Each message is delivered to exactly one listener in the share group.

The share group name is configurable via SHARE_GROUP (default: "django-devicehub").

Topic Utilities

The django_devicehub.utils.topic module provides helper functions for working with topics:

build_topic(type_name, device_id, message_type="data", template=None)

Build a topic string:

from django_devicehub.utils.topic import build_topic

topic = build_topic("weatherstation", "STATION-001", "data")
# "myproject/weatherstation/STATION-001/data"

topic = build_topic("weatherstation", "STATION-001", "cmd")
# "myproject/weatherstation/STATION-001/cmd"

parse_topic(topic)

Parse a topic string into its components:

from django_devicehub.utils.topic import parse_topic

result = parse_topic("myproject/weatherstation/STATION-001/data")
# ("weatherstation", "STATION-001", "data")

result = parse_topic("unrelated/topic")
# None

Returns a 3-tuple (type_name, device_id, message_type) or None if the topic does not match the expected pattern. Only data, status, and cmd are recognized as valid message types.

build_subscribe_pattern(type_name, message_type="+")

Build a subscription pattern with a + wildcard for device_id:

from django_devicehub.utils.topic import build_subscribe_pattern

pattern = build_subscribe_pattern("weatherstation")
# "myproject/weatherstation/+/+"

pattern = build_subscribe_pattern("weatherstation", "data")
# "myproject/weatherstation/+/data"

Generating Topics from DeviceType

The DeviceType class provides methods for topic generation:

get_topic(device_id, message_type="data")

Generate the full topic for a specific device:

topic = WeatherStation.get_topic("STATION-001", "data")
# "myproject/weatherstation/STATION-001/data"

topic = WeatherStation.get_topic("STATION-001", "cmd")
# "myproject/weatherstation/STATION-001/cmd"

get_subscribe_topics()

Get the MQTT topic patterns for subscribing to all devices:

topics = WeatherStation.get_subscribe_topics()
# ["myproject/weatherstation/+/data", "myproject/weatherstation/+/status"]

Custom Topic Templates

Override the topic template in the DeviceType Meta:

class LoRaNode(DeviceType):
    class Meta:
        topic_template = "lora/{type_name}/{device_id}/{message_type}"

This produces topics like:

lora/loranode/NODE-001/data
lora/loranode/NODE-001/status

Note that the {prefix} placeholder is still resolved from TOPIC_PREFIX, even if you do not include it in your template.

The template must contain {type_name}, {device_id}, and {message_type} placeholders. The {prefix} placeholder is optional.

ACL Rules

When using the built-in broker auth endpoints, the ACL (BrokerACLView) enforces these rules:

Action Allowed Topics
Publish (pub) {prefix}/{type}/{device_id}/data, {prefix}/{type}/{device_id}/status
Subscribe (sub) {prefix}/{type}/{device_id}/data, {prefix}/{type}/{device_id}/status, {prefix}/{type}/{device_id}/cmd

A device can only access its own topics. It cannot publish to or subscribe to topics belonging to other devices or other device types.