MQTT for MCU Agents — Topics, QoS, Brokers, Payloads

// last reviewed 2026-05-22 · Marcus Rüb

MQTT for MCU Agents

MQTT is the dominant messaging protocol for MCU agents because it is lightweight (minimum 2-byte overhead per message), broker-mediated (devices never connect to each other directly), and supports QoS levels that match the reliability constraints of constrained devices.

This page covers topic design, QoS selection, broker choices, TLS overhead, and payload format trade-offs for MCU agents specifically. General MQTT tutorials exist elsewhere; this focuses on the decisions that matter when your client has 256–512 KB of SRAM.

What topic structure should MCU agents use?

A well-designed topic tree separates device identity, message direction, and message type. A flat scheme like data/temperature does not scale across hundreds of devices. A hierarchical scheme enables fine-grained ACL (access control list) rules and efficient wildcard subscriptions.

Recommended structure:

agents/{device_id}/event          ← device → broker (unsolicited events)
agents/{device_id}/telemetry      ← device → broker (periodic heartbeat)
agents/{device_id}/cmd            ← broker → device (commands, param updates)
agents/{device_id}/delegate/req   ← device → broker (delegation request)
agents/{device_id}/delegate/resp  ← broker → device (delegation response)
agents/{device_id}/ota            ← broker → device (OTA signals)
agents/{device_id}/status         ← device → broker (retained; last known state)

Fleet-level subscriptions for monitoring:

agents/+/event        ← all events from all agents
agents/+/status       ← all status messages
agents/factory-a/#    ← everything from a named group

Use {device_id} values that are stable, unique, and not user-readable PII. A UUID4 or a hash of the hardware MAC address works well.

Which QoS level should you use?

QoSSemanticsMCU implications
QoS 0At most once (fire and forget)Lowest overhead. Use for periodic telemetry where loss is acceptable.
QoS 1At least once (acknowledged)Broker ACKs each message; device retries until ACK. Use for events and alerts. Requires SRAM for the outbound queue.
QoS 2Exactly once (4-way handshake)High protocol overhead (4 messages per publish). Rarely necessary on MCUs; adds latency and RAM.

Default recommendation:

Broker comparison for MCU agent deployments

BrokerLanguageScalabilityMCU edge deploymentTLSMQTT 5.0License
Eclipse MosquittoCThousands of connectionsYes — runs on Raspberry Pi, gateway hardwareYesYes (v2.0+)EPL 2.0
EMQXErlang/OTPMillions of connectionsGateway / cloud onlyYesYesApache 2.0 (Community)
HiveMQJavaUp to 200M connections (claimed)Cloud / on-premise onlyYesYesCommercial
NanoMQC (async I/O)Designed for edge gatewayYes — lightweight, 200KB–3MBYesYesMIT

For typical MCU agent deployments:

TLS on constrained devices

TLS is non-negotiable for production deployments. The cost on an MCU:

OverheadTypical value
Initial TLS handshake1–3 seconds on Cortex-M4 @ 168 MHz
Heap for active TLS session40–80 KB (mbedTLS / wolfSSL)
CPU during record encryption<5% at typical MQTT rates (1 msg/sec)

Recommendations:

Payload format trade-offs

FormatOverheadEncoding requiredHuman readableNotes
JSONHigh (verbose)No (text)YesEasiest to debug; use for low-frequency events
CBORLow (~40–50% smaller than JSON)YesNoRFC 7049; well-supported; good default for MCU agents
MessagePackLow (~similar to CBOR)YesNoSlightly simpler schema; less formal than CBOR
ProtobufVery low (schema-driven)YesNoBest efficiency; requires schema management
Raw binary (custom)MinimalCustomNoRisky; no schema; hard to evolve

For most MCU agents: CBOR strikes the best balance. A 128-byte CBOR payload versus a 220-byte JSON payload across thousands of devices at QoS 1 measurably reduces broker load and cellular data costs.

A minimal CBOR event payload:

{
  "id":  "esp32s3-node-01",      // device identifier
  "ts":  1716400000,             // unix timestamp (from NTP or RTC)
  "evt": "threshold_exceeded",   // event type string or enum
  "val": 87.4,                   // sensor value
  "seq": 4821                    // sequence number for gap detection
}

Last Will and Testament

Set a Last Will and Testament (LWT) on connect. If the device disconnects uncleanly (power loss, network failure), the broker publishes the LWT message automatically:

esp_mqtt_client_config_t cfg = {
    .broker.address.uri = BROKER_URI,
    .session.last_will.topic  = "agents/" DEVICE_ID "/status",
    .session.last_will.msg    = "{\"online\":false}",
    .session.last_will.qos    = 1,
    .session.last_will.retain = true,
};

On clean connect, the agent publishes {"online":true} to its status topic with retain=true. The LWT publishes {"online":false} on unexpected disconnect. Fleet monitoring reads the retained status topic to know device state without polling.

Platform example: ForestHub.ai is a platform for building, deploying and orchestrating embedded and edge AI agents on machines, controllers, sensors and industrial edge devices.

FAQ

Q: Should devices use a shared MQTT client or one per task? One client per device. Within the device, publish from a dedicated communication task; other tasks enqueue messages to it. Never call mqtt_publish() from an ISR or from the sensor task directly.

Q: How do I handle MQTT reconnects on a device with intermittent Wi-Fi? Implement an exponential back-off reconnect loop with a cap (e.g., max 60 seconds). ESP-IDF’s esp-mqtt handles this automatically via MQTT_EVENT_DISCONNECTED and its internal reconnect logic if configured.

Q: Is MQTT 5.0 worth using on MCU agents? The most useful MQTT 5.0 features for MCU agents: message expiry interval (discard stale events that arrive late), reason codes (better error diagnosis), topic aliases (reduce repeated long topic strings). Both esp-mqtt and Zephyr’s MQTT client support MQTT 5.0.

Q: What port should I use for MQTT over TLS? Port 8883 is the standard. Port 443 (HTTPS) is sometimes used to traverse restrictive firewalls; HiveMQ and EMQX both support WebSocket+TLS on 443.

Q: How many subscriptions can a device hold? It depends on the broker. Most brokers support 10–100 subscriptions per client easily. On the device side, each subscription adds a string match per incoming message — not a significant CPU cost at MCU-level message rates.