Skip to main content

MQTT Config Protocol

Device-side MQTT config pull aligned with the IOTMER control plane.

Prefix: {workspace_slug}/{device_key}/…

See also: MQTT topics. Control-plane normative spec: docs.iotmer.com.

Flow

config/meta (retained) → config/get → config/resp (chunked) → config/status

Topics

TopicDirectionRetainPurpose
…/config/#SubscribeCatch meta + resp
…/config/metaCloud → deviceYesLatest version + sha256 hint
…/config/getDevice → cloudNoPull request
…/config/respCloud → deviceNoChunked response
…/config/statusDevice → cloudNoApply ack

config/meta

{
"version": 12,
"sha256": "2b3d0b5a…",
"bytes": 1842,
"updated_at": "2026-04-17T10:00:00Z"
}

If applied version + sha256 match meta, the device may skip config/get. If it sends config/get anyway, the cloud always responds with the full config.

config/get

{
"rid": "c6d1d86b-9b8e-4f7a-9f63-3a2a7b1d2c3d",
"have": {"version": 11, "sha256": "aaaa…"},
"want": {
"chunk_bytes": 4096,
"max_total_bytes": 1048576,
"accept_encoding": ["gzip", "identity"]
}
}
FieldNotes
ridRequired. Correlates resp + status
haveOptional. What device already applied
want.chunk_bytesPreferred chunk size (server may clamp)
want.max_total_bytesMax decoded size device accepts
want.accept_encodinggzip and/or identity

config/resp

Correlated by rid. Ignore messages with a mismatched rid.

Success (chunked)

Every chunk includes:

FieldDescription
rid, ok: true, version, sha256Transfer metadata
encodinggzip+base64 or identity+base64
content_typeapplication/json
chunk_index, total_chunks, chunk_bytesChunking
data_b64Base64 payload for this chunk

Gzip path: decode each data_b64 → concatenate → gunzip → JSON bytes → verify sha256.

Identity path: decode each data_b64 → concatenate → JSON bytes → verify sha256.

Reassemble in chunk_index order (0 … total_chunks-1).

Error

{
"rid": "…",
"ok": false,
"error": {"code": "CONFIG_TOO_LARGE", "message": "…", "retryable": false}
}

SHA256

Hash is over the exact UTF-8 bytes of the canonical effective JSON:

  • Object keys sorted lexicographically at every level
  • No insignificant whitespace
  • Arrays keep order

Verify hash over received bytes after reassembly — do not re-serialize with a non-canonical printer.

Effective config shape:

{"capability_definitions":{},"config":{},"metadata":{}}

config/status

{
"rid": "…",
"applied": true,
"version": 12,
"sha256": "bbbb…",
"applied_at": "2026-04-17T10:22:31Z",
"error": null
}

On failure: applied: false, error: {code, message}.

Timeouts and recovery

A transfer that never receives a complete config/resp is reset automatically after CONFIG_IOTMER_CONFIG_TRANSFER_TIMEOUT_MS (default 30 s), so a lost response cannot permanently block further iotmer_config_request() calls. Applications can also reset earlier with iotmer_config_abort().

Chunks are placed by chunk_index (not arrival order); duplicate QoS 1 re-deliveries are ignored and every non-final chunk must decode to exactly chunk_bytes.

Memory

Size buffers for worst-case transfer implied by want.max_total_bytes. Gzip path needs space for both compressed and inflated data.

Example 04_config uses a 64 KiB split buffer — adjust for your flash/RAM budget.