Message Envelopes and Correlation
ReferenceAudience: API consumerUse: build a command, locate the result in a reply, and recognize an event from its root shape
Every MQTT API message is a JSON object with the content type application/json. Commands and responses share an envelope. Events don't: each event has its own root shape and carries no requestId.
Commands
A command is a JSON object with these top-level members:
| Member | Type | Required | Meaning |
|---|---|---|---|
command | string | Yes | The command name, for example get_status |
requestId | string, 1 to 64 characters | Yes | An identifier you choose. The reader returns it unchanged in the reply. |
| The command's payload key | object | Required where the command defines one | The command's parameters. See Command Payload Keys. |
A command carries no apiVersion. The command, response, and event envelopes are defined in Common Objects.
Command Payload Keys
| Command | Payload key | Required |
|---|---|---|
get_version, get_status, get_current_region, get_eth, get_wifi, get_installed_certificates, get_operating_mode, get_post_filter, reboot | None | — |
get_endpoint_config | endpointDetails | No |
set_wifi | wifiConfig | Yes |
delete_wifi_profile | wifiProfileInfo | Yes |
config_endpoint | epConfig | Yes |
install_certificate | certDetails | Yes |
delete_certificate | certificateInfo | Yes |
config_events | eventConfiguration | Yes |
set_os | OSUpdateDetails | Yes |
set_operating_mode | operatingMode | Yes |
set_post_filter | postFilterPayload | Yes |
control_operation | ctrlOprPayload | Yes |
operation Values
Four commands take an operation member. Each uses its own set of values, with its own casing. Send them exactly as listed.
| Command | Member | Values |
|---|---|---|
config_endpoint | epConfig.operation | add, delete, update |
set_wifi | wifiConfig.operation | CREATE, MODIFY |
set_post_filter | postFilterPayload.operation | ADD, DELETE, MODIFY |
control_operation | ctrlOprPayload.operation | START, STOP |
What Happens to a Malformed Command
| Condition | What the sources state |
|---|---|
The command value is not a recognized command name | The reader drops the message without replying and without a response code. |
| The payload fails a command's validation | Some commands return code 2 or 23; see Codes by Command. |
| A top-level member that the command does not define | The contract does not close its messages, so a schema validator accepts it. What the reader does with it is not specified in the available sources. |
| More commands than the reader can queue | The reader's command queue holds 10 commands. Commands beyond that are not queued and can be lost. |
| A command larger than the reader accepts | Firmware reports a limit of 16,384 bytes on the command topic, not yet confirmed. The reader drops an oversized message and keeps the connection open. |
Responses
The reader answers each command it carries out with one response on the endpoint's response topic. A response contains:
| Member | Type | Meaning |
|---|---|---|
command | string | The command being answered |
requestId | string | The requestId from the request |
apiVersion | string | The version reported by the command handler. Optional only on delete_wifi_profile. |
response | object | code and description: the outcome of the command |
| The command's result key | object | The data the command returns, for commands that return data |
How to pair a response with its request and decide the outcome is specified in Correlation and Request/Response Pairing.
Result Keys
| Command | Result key |
|---|---|
get_version | readerVersion |
get_status | deviceStatus |
get_current_region | currentRegion |
get_eth | ethConfig |
get_wifi | wifiProfiles; with Wi-Fi disabled, a top-level wifiConfig |
get_endpoint_config | endpointResponse |
get_installed_certificates | installedCerts |
get_operating_mode | operatingMode |
get_post_filter | postFilterPayload |
| All other commands | None: the response object is the whole result |
Events
Events have no shared envelope and no member that names the event. Recognize each event by the members it requires, and by the topic it arrives on.
| Event | Identified by | Required members | Body | Topic slot |
|---|---|---|---|---|
heartBeatEVT | "eventName": "heartbeat" | timestamp, eventName, eventNumber | data | publishTopics[1] |
alerts | state, id, and priority | timestamp, type, state, id, priority | alertDetails | publishTopics[1] |
mqttConnEVT | connectionState | connectionState, deviceModel, deviceSerialNo, apiVersion, mqttVersion | None; all members are at the root | publishTopics[2] |
dataEVT | data containing tagData or barcodeData, and no eventName | timestamp | data | publishTopics[1] on a data endpoint. When inventory output goes to a management or control endpoint, its rfid topic; the slot is not specified in the available sources. |
Two members need care:
typehas two meanings. Onalerts, it is the alert category. OndataEVT, it is the operating-mode profile that was active. Don't dispatch ontypealone.timestampis not required onmqttConnEVT. TheDISCONNECTEDform is the Last Will, registered with the broker before the disconnect happens, so it carries no timestamp.heartBeatEVT,alerts, anddataEVTrequire one.
Dispatching Events
This order of checks is consistent with the required members above. It is a recommendation, not a rule the contract states:
- If
connectionStateis present, the message ismqttConnEVT. - If
eventNameis"heartbeat", it isheartBeatEVT. - If
state,id, andpriorityare present, it isalerts. - If
datais present, it isdataEVT.
Treat any other message as unrecognized, and log it.
Event Size
A dataEVT message is limited to 6,144 bytes. Firmware reports 10,240 bytes as the largest message the reader publishes, not yet confirmed. The reader drops an oversized message instead of reporting an error.
Related: 📕 Correlation and Request/Response Pairing · 📕 Topics and Channels · 📕 Common Objects · 📕 Response Codes
Correlation and Request/Response Pairing
Commands and their replies travel on a shared pair of topics, and MQTT itself does not pair them. The requestId in the JSON payload is the only thing that links a reply to its request.
A command and its reply each make two hops through the broker, and only the requestId pairs them:
Response Envelope
| Member | Type | Required | Meaning |
|---|---|---|---|
command | string | Yes | The command being answered, copied from the request |
requestId | string, 1 to 64 characters | Yes | The request's requestId, copied unchanged |
apiVersion | string: V1.0, V1.1, V1.2, V1.21, or V1.22 | Yes, except on delete_wifi_profile | The version reported by the command handler. See API Version Reporting. |
response.code | integer | Yes | The outcome. See Response Codes. |
response.description | string, at least 1 character | Yes | Human-readable text. Its wording can change between firmware builds. |
| Result key | object | No | The data a command returns. See Result Keys. |
Correlating Responses
- Choose a new
requestIdfor every request. It must be 1 to 64 characters. Treat it as opaque: the reader does not parse it. - Subscribe to the response topic before you publish. Responses are not retained, so a reply published before your subscription is active is lost.
- Match on
requestId, and checkcommand. Replies arrive out of order and interleaved with other traffic, including replies to other clients. Ignore replies whoserequestIdyou did not send. - Expect a reply to arrive more than once. The response channel uses QoS 1, so the broker can deliver the same reply twice. See Delivery and Retry Semantics.
requestId does not prevent a repeatThe reader keeps no request history. If the same command reaches it twice, with the same requestId or a different one, it carries out both copies. See Duplicates and Retries.
Deciding the Outcome
Read response, and decide in this order:
| Condition | Outcome | What to do |
|---|---|---|
code is 0 | Success | Read the result key, if the command has one |
code is absent and description is "Success" | Success | As above. See Success Without a Code. |
code is 1 | Accepted, not finished | Wait for the outcome. See Asynchronous Commands. |
Any other code | The command did not succeed | Look up the code in Response Codes |
Don't branch on description except to recognize "Success" when code is absent.
Success Without a Code
A successful reply to any command may omit response.code, for example "response": { "description": "Success" }. The contract requires code on every reply, but readers have been reported to omit it on success for control_operation, get_operating_mode, set_operating_mode, get_post_filter, and set_post_filter, and the device owners state that it can happen on any command. Error replies always carry a numeric code.
The firmware team also states the intent that every reply carry a code. Handle both forms, and don't depend on the code being absent.
MQTT Acknowledgments and Reader Responses
MQTT acknowledgments and reader responses answer different questions. Don't treat one as the other.
| Signal | Sent by | What it tells you |
|---|---|---|
| CONNACK | Broker | Whether the broker accepted your client's connection |
| SUBACK | Broker | Which QoS the broker granted for your subscription |
| PUBACK, for a QoS 1 publish | Broker | That the broker received your command. It says nothing about the reader. |
| Reader response | Reader | That the reader received the command, and the outcome in response |
mqttConnEVT | Reader, or the broker for the Last Will | That a reader endpoint connected, or lost its session without a clean disconnect |
MQTT 3.1.1 defines return codes for CONNACK and SUBACK. Those codes, and errors raised by your MQTT client library, are separate from the reader's response codes.
Asynchronous Commands
Most commands finish when they reply. Three reply with code 1, "Command payload is accepted", which means only that the reader took the request.
| Command | How you learn the outcome |
|---|---|
install_certificate | An alerts event with id: "FILE_DOWNLOAD". Read alertDetails.downloadInfo.status: DOWNLOAD SUCCESS, then SAVE SUCCESS. |
set_os | An alerts event with id: "FIRMWARE_UPDATE". Success is alertDetails.fwUpdateStatus.updateStatus equal to FW update success. Other updateStatus values are free text: don't parse them. |
reboot | No event reports the outcome. The reader replies, then drops its session. |
For install_certificate and set_os:
- Subscribe to the endpoint's event topic before you publish the command. Events are not retained, so an outcome published before your subscription is active is lost.
- Make sure the matching event category is enabled with
config_events:fileDownloadforinstall_certificate, andfirmwareUpdateforset_os. - Choose your own deadline. No timeout is specified.
For reboot, the next CONNECTED mqttConnEVT shows that a new session was established. On its own, it does not prove that a restart happened, because a network reconnect produces the same message. The upTime text in heartBeatEVT shows a restart to a person reading it; it is not machine-readable.
Timeouts
No timeout is specified for any command. Reply time depends on network and reader conditions, so set a deadline that suits your application. If the deadline passes, don't assume the command failed: read the reader's state before you send a state-changing command again.
Known Device Deviations
These are the places where readers are known to send a different form. Handle them as shown.
| Case | What to expect | How to handle it |
|---|---|---|
Success reply without code | "response": { "description": "Success" } | See Success Without a Code |
apiVersion and mqttVersion on mqttConnEVT | Numbers, such as 1.21 and 3.0999999, where command replies use strings such as V1.21. | Accept a number or a string. Don't compare 3.0999999 with 3.1 for equality. |
fwUpdateStatus.updateStatus on alerts | Free text during a firmware update | Test only for FW update success |
| Connection status in network-interface replies | Connected and Disconnected on the Ethernet linkStatus.status; CONNECTED and DISCONNECTED on every other status member | Compare case-insensitively |
apiVersion on delete_wifi_profile | Absent on success; the contract does not declare it for this reply | Treat apiVersion as optional on every reply |
Events on a MGMT_EVT endpoint | Published on the endpoint's subscribe topic | See Topic Slots |
API Version Reporting
apiVersion is reported by each command handler, not once for the reader. Two commands on the same firmware can report different values. The contract declares one set of values for all commands and does not say which command reports which value.
- Don't use
apiVersionfor feature detection, and don't compare two values to decide which is newer. - Log it with the
requestIdwhen you report an issue. - To identify the IoT Connector version, read
readerVersion.detailedVersions.iotcVersionfromget_version.
See API Version Support.
Related: 📕 Message Envelopes and Correlation · 📕 Response Codes · 📕 Delivery and Retry Semantics · 📙 Send Commands and Handle Responses