Related
- OTA updates — firmware rollouts delivered over persistent RPC.
- Sending telemetry — the device→server direction.
- Security & compliance — scopes, the audit log, and params-hash redaction.
Telemetry flows device→server. RPC flows the other way: the platform sends a command to a device — set a fan speed, read back a config, reboot, start a firmware update. This page covers the three command types, the status lifecycle every command moves through, how responses are correlated, what happens when the device is offline, and the authorization and audit rules around commanding.
| Type | Waits for a result? | Survives an offline device? |
|---|---|---|
| One-way | No — fire and forget | No (delivered only if online) |
| Two-way | Yes — returns the device’s response | No (times out if offline) |
| Persistent | Optionally | Yes — stored and delivered on reconnect, retried |
setFan, setLed.timeout_ms and return the device’s response
inline. Use them to read state back: getConfig, getStatus.Every command is an RPC record that moves through a defined set of statuses:
stateDiagram-v2 [*] --> queued : RPC created queued --> sent : adapter publishes to device sent --> delivered : device ACKs receipt delivered --> successful : device returns result sent --> successful : one-way / fast result queued --> timeout : non-persistent, device offline sent --> timeout : no response before timeout_ms delivered --> failed : device reports an error queued --> expired : persistent, expiration_time passed sent --> queued : persistent retry (no ack) queued --> cancelled : operator cancels sent --> cancelled : operator cancels successful --> [*] timeout --> [*] failed --> [*] expired --> [*] cancelled --> [*]
The full status set: queued, sent, delivered, successful, timeout,
expired, failed, cancelled, deleted.
response_json.timeout_ms (or a non-persistent command for
an offline device).error_code is set).expiration_time passed before delivery.Every command carries a correlation_id. The protocol adapter stamps it on the
outbound message; the device echoes it on its response; the adapter matches the
response back to the originating RPC record by that id. This is what lets a
device — or a whole fleet over a shared MQTT response topic — reply asynchronously
and have each answer routed to the right command.
A response whose correlation_id matches no active RPC is ignored (or stored as
an orphan diagnostic, per adapter config), and
corem_rpc_orphan_responses_total{protocol="…"} increments.
What happens to a command for an offline device depends entirely on whether it is persistent.
No adapter can deliver the command. After timeout_ms elapses the request returns
DEADLINE_EXCEEDED / HTTP 504 and the RPC record becomes timeout.
sequenceDiagram participant Op as Operator participant Core as CORE-M participant Dev as Device (offline) Op->>Core: SendDeviceRpc (non-persistent) Core-->>Core: status=queued, device offline Note over Core: timeout_ms elapses Core->>Op: DEADLINE_EXCEEDED / 504 (status=timeout) Dev--xCore: never reached
The command stays queued and waits. When the device reconnects before
expiration_time, the adapter delivers it. If no ack arrives, it returns to
queued and retries_remaining decrements, retried until success, cancel,
expiry, or max retries. If expiration_time passes first, it becomes
expired and is never delivered.
sequenceDiagram participant Op as Operator participant Core as CORE-M participant Dev as Device Op->>Core: SendDeviceRpc (persistent, expires +1h) Core-->>Core: status=queued (device offline) Note over Dev: device reconnects Dev->>Core: connect Core->>Dev: deliver command (status=sent) Dev->>Core: ACK + result (status=successful)
A persistent command’s expiration_time is a hard deadline. Once it passes, the
RPC scheduler marks the record expired, no command is delivered even if the
device later reconnects, and event rpc.expired.{tenant}.{rpc_id} is published.
Set expiration_time to the longest you’d still want the action to happen — a
reboot you queued days ago is probably no longer wanted.
rpc permission. A member without it is
rejected with PERMISSION_DENIED and no record is created.rpc:execute. A token scoped to,
say, telemetry:read calling RPC is rejected with PERMISSION_DENIED and
rpc.denied records reason="missing_scope".rpc.created with the actor, the
device, the method, the persistent flag, and a hash of the params. If the
method is marked sensitive, raw params are redacted — only the hash is kept.Read a device’s config back with a two-way RPC. You send the command and get a
correlation_id; you then fetch the matched response.
Send the command. Call the device RPC endpoint with the method, JSON
params, and a timeout. You need the rpc:execute scope (token) or rpc
permission (user).
curl -X POST https://api.kronoxdata.com/api/v1/devices/dev_8f3a/rpc \ -H "Authorization: Bearer <token-with-rpc:execute>" \ -H "Content-Type: application/json" \ -d '{ "method": "getConfig", "params_json": "{}", "timeout_ms": 10000 }'Receive the correlation id. The call returns immediately with the id you use to retrieve the result.
{ "correlation_id": "c-7f3a91" }The device replies. The adapter delivers getConfig to the device, the
device responds {"report_interval":30} stamped with c-7f3a91, and the
adapter matches it to the RPC record. The record transitions
queued → sent → delivered → successful and the response lands in
response_json.
Fetch the result. Poll by correlation id.
curl https://api.kronoxdata.com/api/v1/devices/rpc/c-7f3a91 \ -H "Authorization: Bearer <token-with-rpc:execute>"{ "found": true, "payload_json": "{\"report_interval\":30}"}The RPC record is transport-agnostic; the protocol adapter handles the actual delivery and response correlation.
| Protocol | Outbound command | Response correlation |
|---|---|---|
| MQTT | Published to the device’s configured command topic | Device publishes to a response topic; adapter matches by correlation_id |
| HTTP | Device fetches/long-polls; or device submits an uplink of kind="rpc_response" | Uplink carries the correlation_id |
| CoAP / LwM2M | Delivered over the device’s CoAP channel | Response matched by correlation_id / protocol request id |
| Gateway | Relayed to a child device via its gateway | Gateway relays the child’s response with correlation_id |
Related