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
| Topic | Direction | Retain | Purpose |
|---|---|---|---|
…/config/# | Subscribe | — | Catch meta + resp |
…/config/meta | Cloud → device | Yes | Latest version + sha256 hint |
…/config/get | Device → cloud | No | Pull request |
…/config/resp | Cloud → device | No | Chunked response |
…/config/status | Device → cloud | No | Apply 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"]
}
}
| Field | Notes |
|---|---|
rid | Required. Correlates resp + status |
have | Optional. What device already applied |
want.chunk_bytes | Preferred chunk size (server may clamp) |
want.max_total_bytes | Max decoded size device accepts |
want.accept_encoding | gzip and/or identity |
config/resp
Correlated by rid. Ignore messages with a mismatched rid.
Success (chunked)
Every chunk includes:
| Field | Description |
|---|---|
rid, ok: true, version, sha256 | Transfer metadata |
encoding | gzip+base64 or identity+base64 |
content_type | application/json |
chunk_index, total_chunks, chunk_bytes | Chunking |
data_b64 | Base64 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.