Skip to main content

set_os

ReferenceAudience: API consumerUse: initiate an over-the-air firmware update on the reader

Overview​

set_os initiates an over-the-air (OTA) firmware update — it points the reader at a firmware .DAT image at a URL and triggers the download-and-flash cycle. It is a state-changing command on the device-management (MGMT) endpoint, and it is both asynchronous and session-severing: the device accepts the payload, downloads and flashes the firmware, and reboots — dropping off the network for the duration. The authoritative confirmation of the outcome is an out-of-band get_version read-back.

set_os reflashes and reboots the reader. The reply code 1 is an accept (returned for success and failure alike), and the reader rejects every other command with code 4 while the update runs. See the danger callout in Notes and Caveats; confirm the result with get_version.

Details​

PropertyValue
Interaction PatternAsynchronous Request → Accept → Reboot → Verify (completion confirmed out-of-band)
State ChangeSTATE-CHANGING (downloads and flashes firmware; reboots the reader)
ReversibilityIRREVERSIBLE (no single-step undo; re-flash a prior build with another set_os to roll back)
Required Request Fieldscommand, requestId, OSUpdateDetails (with url and authenticationType)
Applies ToEndpoint: Management (MGMT). Reader: RFD40 series, RFD90 series
API VersionV1.1 (API Version Reporting)
Relatedget_version, install_certificate, reboot, get_status

When to Use​

Use set_os to update the reader's firmware — during commissioning, a fleet migration, or a security/feature rollout.

  • Host the image — place the firmware .DAT at a URL the reader can reach.
  • Choose authentication — NONE for an open/internal server, or CERTIFICATE (with a CA reference or inline PEM) plus a verificationType for a secured HTTPS download.
  • Check preconditions — ensure adequate battery and flash first (low battery → code 14, insufficient flash → code 8).
  • Verify afterward — always confirm the new build with get_version.

Topic Mapping​

MessageDirectionTopicNote
RequestApp → Reader<tenantId>/MGMT/clients/cmnd/<serial>On the management endpoint.
ResponseReader → App<tenantId>/MGMT/clients/resp/<serial>The code 1 accept, and the code 4 lock returned to other commands during the install.
EventReader → App<tenantId>/MGMT/clients/event/<serial>The FIRMWARE_UPDATE notifications (start, progress, outcome). See alerts.

Topics follow the pattern <tenantId>/<topic>/<serial> — the tenant id assigned to you at provisioning (a slot you fill in, never a literal), the base topic you configure on the endpoint, and the reader's serial number, which the reader appends itself in its model-prefixed form (for example RFD40-212735201D0053). App → Reader means your app publishes and the reader subscribes; Reader → App means the reader publishes and your app subscribes.

See Topics and Channels.

Key Fields​

  • OSUpdateDetails.url (request) — the firmware .DAT download URL (a bad/unreachable URL → code 9).
  • OSUpdateDetails.authenticationType (request) — NONE or CERTIFICATE; for CERTIFICATE, supply caCertificateFile (installed reference) or caCertificateFileContent (inline PEM).
  • OSUpdateDetails.verificationType (request) — TLS verification of the download server; we recommend setting it for HTTPS downloads (see Request Semantics).
  • response.code (response) — 1 means accepted, not updated; confirm with get_version. See Response Codes.

Request Payload​

The envelope is command, requestId, and the OSUpdateDetails payload (url + authenticationType). Pick an update source below; the Example and Schema update together. Use the Schema tab for the full field reference and the Example tab for the concrete payload.

Request payload
Example: authenticationType NONE · HTTP
{
"command": "set_os",
"requestId": "abc123",
"OSUpdateDetails": {
"url": "http://192.168.29.39:8000/Build-3.10.27/Firmware/PAAFKS00-012-R00E0.DAT",
"authenticationType": "NONE",
"verificationType": "VERIFY_NONE"
}
}

Request Semantics​

Modes. authenticationType selects how the reader authenticates to the firmware source: NONE for an unauthenticated download (an open or internal server), or CERTIFICATE to authenticate the HTTPS download against a CA — supplied either as a pre-installed reference (caCertificateFile, installed via install_certificate) or inline (caCertificateFileContent), and paired with a verificationType for TLS server verification. Both CERTIFICATE forms are in the Request Payload selector: Pre-installed CA certificate HTTPS and Inline CA certificate HTTPS.

  • Authentication & transport security. For authenticationType: CERTIFICATE, supply the CA either as an installed reference (caCertificateFile, pre-installed via install_certificate) or inline (caCertificateFileContent); if caCertificateFileContent is empty, the reader uses an already installed filestore certificate. authenticationType: NONE means no authentication is needed to access the URL. verificationType sets how far the reader validates the download server's TLS certificate: VERIFY_HOST_AND_PEER checks the certificate chain and that the certificate matches the host; VERIFY_NONE does no verification and must not be used in production.
  • OSUpdateDetails is required. The contract requires it, so a request without it is invalid rather than merely unable to update. A bad or unreachable url returns code 9.

Response Payload​

The immediate reply is an accept — command, requestId, apiVersion, and response with code 1. It confirms receipt, not a completed update.

Response payload
Example: accept
{
"command": "set_os",
"requestId": "abc1234",
"apiVersion": "V1.1",
"response": {
"code": 1,
"description": "Command payload is accepted"
}
}

Lifecycle​

set_os is an accept-then-verify cycle, not a single request/response.

Acknowledgment​

The device replies response.code 1 ("Command payload is accepted") on the response topic. This confirms receipt only, and is identical for success and failure.

Progress​

A FIRMWARE_UPDATE notification appears on the event topic as the download/flash begins. The block below is the example the schema declares for it, not an observed one:

Event — FIRMWARE_UPDATE start (schema example)
{
"type": "NOTIFICATION",
"timestamp": "2026-06-18T11:30:50.387Z",
"state": "ONESHOT",
"id": "FIRMWARE_UPDATE",
"priority": "HIGH",
"alertDetails": {
"fwUpdateStatus": {
"updateStatus": "Starting FW update"
}
}
}

Notifications continue on the same topic while the install runs. Each of the five recorded during one update observed on an RFD40 carried alertDetails.fwUpdateStatus.overallProgress — percent of the update completed — beside a human-readable updateStatus. overallProgress is optional and the start example above carries none, so treat its absence as unknown rather than as zero. Three of those five notifications are shown here and under Confirmation below, across the 15.265 s in which overallProgress ran from 87% to 100%; the earlier part of that update was not recorded.

Event — FIRMWARE_UPDATE in progress, 87% (live)
{
"type": "NOTIFICATION",
"timestamp": "2026-07-07T11:49:50.628Z",
"state": "ONESHOT",
"id": "FIRMWARE_UPDATE",
"priority": "HIGH",
"alertDetails": {
"fwUpdateStatus": {
"updateStatus": "In progress",
"overallProgress": 87
}
}
}

The identical In progress value arrived twice — at 87%, then at 95% 10.014 s later — so only the first is shown. The notification after it carried a status string naming a component file inside the firmware package:

Event — FIRMWARE_UPDATE in progress, 98% (live)
{
"type": "NOTIFICATION",
"timestamp": "2026-07-07T11:50:04.424Z",
"state": "ONESHOT",
"id": "FIRMWARE_UPDATE",
"priority": "HIGH",
"alertDetails": {
"fwUpdateStatus": {
"updateStatus": "Updating file PAAFKS00-013-R04L0.DAL",
"overallProgress": 98
}
}
}
  • updateStatus is display text, not a machine token. It carries whatever the firmware shows an operator, file names included, and four of the five recorded notifications carried a value that is not one of the five fwUpdateStatus.updateStatus declares — only the terminal FW update success matched. The declared lowercase values updating, skipping and failed appeared in none of them: the reader sent sentences such as Updating file … and Skipping file … instead, so the obvious test updateStatus === 'updating' matches nothing this reader sends. Render the string as opaque text and drive logic from overallProgress and from the terminal literal under Confirmation below. The schema remains normative; this is an observed device deviation, cataloged in Known device deviations from the schema.
  • A status string can arrive incomplete. The notification at 99% reported Skipping file …, naming a path inside the firmware package, and the value ended mid-name, its final character recorded as an encoding-error artifact rather than as text. Whether the reader truncated the string or the recording chain corrupted it cannot be told from a single sample, and no maximum length can be inferred from one either: do not assume a length limit, and do not substring-match. overallProgress in that same notification was a well-formed 99, which is the practical case for branching on the number rather than on the text.
  • The stream's timing is not a contract. Across the recorded window the gap between notifications ran from 10.014 s down to 0.284 s while overallProgress advanced by 8, then 3, then 1, then 1 percentage points — neither a fixed interval nor a fixed increment, so do not derive a silence timeout from it. Emission is gated by eventConfiguration.firmwareUpdate in config_events and can be switched off altogether. The stream therefore gives you no deadline, so bound the wait on your own clock: set a wall-clock budget for the whole update from your own fleet's measurements, and on expiry poll get_version until the reader answers. Treat silence as unknown, never as failure.

Session Impact​

While the update runs, the reader rejects every other command on the response topic with code 4 ("Firmware update in progress"), echoing the blocked command's name:

Response — another command blocked during the update (code 4)
{
"command": "get_endpoint_config",
"requestId": "abc123",
"apiVersion": "V1.1",
"response": {
"code": 4,
"description": "Firmware update in progress"
}
}

The reader reflashes and reboots, and is briefly off the network.

Confirmation​

The reader also reports the outcome on the event topic. The terminal notification carries updateStatus: FW update success with overallProgress: 100:

Event — FIRMWARE_UPDATE success, 100% (live)
{
"type": "NOTIFICATION",
"timestamp": "2026-07-07T11:50:05.893Z",
"state": "ONESHOT",
"id": "FIRMWARE_UPDATE",
"priority": "HIGH",
"alertDetails": {
"fwUpdateStatus": {
"updateStatus": "FW update success",
"overallProgress": 100
}
}
}

That literal is the one updateStatus value worth an equality test — it is a declared enum member and the observed notification matched it exactly — with overallProgress: 100 as corroboration rather than as the test, because a single observation cannot establish that 100 always means success. Every notification recorded, the terminal one included, carried state: ONESHOT, so the terminal signal sits in fwUpdateStatus and not in the envelope: do not wait for a CLEAR.

Gate on the read-back, not on the notification. Run get_version and compare readerVersion.firmwareVersion against the target build (see the version note below). The notification tells you when to stop waiting; the read-back tells you whether the firmware changed. A failed update leaves the prior version in place; terminal failure is reported as fwUpdateStatus.updateStatus: failed in the FIRMWARE_UPDATE event (which carries no response code), not on the set_os accept. failed is the value the schema declares for that case; no failure arose in the traffic behind the examples above, and because two of the three bare-stem values the schema declares arrive as sentences on this firmware, a real failure may well arrive as a sentence too — so detect failure by the read-back, not by matching failed.

Response Codes​

set_os returns response.code. The codes applicable to this command are below; for the full table see the Response Codes and Correlation and Request/Response Pairing.

CodeDescription
1Command payload is accepted (IOT_STATUS_CMD_PAYLOAD_ACCEPTED) — the accept code for this command. The update proceeds asynchronously; verify with get_version.
0Success — terminal success; in practice set_os acks with 1, and completion is confirmed via the get_version read-back.
4Firmware update in progress (IOT_STATUS_FW_UPDATE_IN_PROGRESS) — returned to other commands while an update is running (the device-wide lock). Pause orchestration until the device returns.
8Insufficient flash size (IOT_ERROR_LOW_FLASH_SIZE) — not enough free flash to download the firmware. Free space or use a smaller package.
9File not found (IOT_STATUS_FILE_NOT_FOUND) — the firmware url is wrong or unreachable.
13Firmware update Failed (IOT_ERROR_FW_UPDATE_FAIL) — the defined failure code. In practice set_os acks only with 1; a failed update is reported as fwUpdateStatus.updateStatus: failed in the FIRMWARE_UPDATE event (which carries no response code), not on the set_os response — but do not equality-test that literal; see Confirmation.
14Battery is low, Cannot update firmware (IOT_ERROR_FW_UPDATE_FAIL_LOW_BATTERY) — charge or connect external power, then retry.
101Default error — generic catch-all.

Gate success strictly on the get_version read-back, not on the set_os response code — the reply code 1 acknowledges only that the payload was accepted, and is returned for both success and failure.

Examples​

Publish set_os; the device acks with the code 1 envelope under Response Payload, then runs the Lifecycle. The CERTIFICATE variants are in the Request Payload selector; the FIRMWARE_UPDATE notifications — start, in-flight progress, and terminal success — and the code 4 lock are shown under the Lifecycle above.

Notes and Caveats​

Reflashes and reboots the reader — plan for unreachability

The device goes dark and locks out other commands. While the update runs, the reader rejects every other command with code 4 ("Firmware update in progress") and is then unreachable across the reboot. Do not retry-storm — pause orchestration until the device re-attaches. The reply code 1 is returned for both success and failure, and there is no terminal result on the response topic: confirm the outcome with get_version and compare the firmware version.

  • Preconditions. Ensure the device is ready before issuing: low battery → code 14; insufficient flash → code 8.
  • Confirm by full version string. readerVersion.firmwareVersion is a free-form string. Compare the whole value against the target build — the build-variant prefix can change (e.g. PAAFKS00-013-R02 → SAAFKS00-013-R02), so match the full string rather than a substring. See get_version.

Sequence a firmware rollout as: pre-check with get_status (power/battery) → set_os → wait out the reboot (expect code 4 on other commands) → confirm with get_version. For the concept-level view, see Device Lifecycle.

Related: 📕 Device Lifecycle · 📕 get_version · 📕 install_certificate · 📕 reboot · 📕 Response Codes

Was this helpful?