Command
Receive commands from the App SDK asynchronously. Unlike RPC, commands are fire-and-forget — your device does not send a response back to the caller.
Overview
Commands use durable delivery — if your device is offline when a command is sent, it will be delivered when the device reconnects. If your handler throws an error, the command is automatically retried after a 5-second delay.
RPC vs Command
| RPC | Command | |
|---|---|---|
| Direction | App SDK → Device → App SDK | App SDK → Device |
| Response | Device sends a response | No response |
| Delivery | Requires active connection | Durable — delivered even if device is offline |
| Retry | No retry | Auto-retry on handler failure (5s delay) |
API
listen()
Register a handler for incoming commands matching a given name.
- JavaScript
- Python
- C++
await device.command.listen(name, callback)| Parameter | Type | Description |
|---|---|---|
name | string | The command name to listen for |
callback | function | Called when a command arrives. Receives the command payload |
Throws DuplicateListenerError if a listener with the same name is already registered
Throws ValidationError if name contains invalid characters
await device.command.listen(name, callback)| Parameter | Type | Description |
|---|---|---|
name | string | The command name to listen for |
callback | function | Called when a command arrives. Receives the command payload |
Throws DuplicateListenerError if a listener with the same name is already registered
Throws ValidationError if name contains invalid characters
device->command.listen(name, callback) → relay_err_t| Parameter | Type | Description |
|---|---|---|
name | const char* | The command name to listen for |
callback | relay_data_cb_t | void (*)(const cJSON* data) — called when a command arrives |
Returns RELAY_ERR_DUPLICATE_LISTENER if a listener with the same name is already registered, RELAY_ERR_VALIDATION if name contains invalid characters
The incoming msgpack payload is automatically decoded to a cJSON* object. The SDK owns this object — do not call cJSON_Delete on it.
Names can only contain letters, numbers, hyphens, and underscores (A-Z, a-z, 0-9, -, _).
off()
Unregister a previously registered command listener.
- JavaScript
- Python
- C++
await device.command.off(name)Returns true if the listener was removed, false if no listener existed for that name
await device.command.off(name)Returns true if the listener was removed, false if no listener existed for that name
device->command.off(name) → relay_err_tReturns RELAY_OK on success
Listen for Commands
- JavaScript
- Python
- C++
await device.command.listen("reboot", (payload) => {
console.log("Reboot requested:", payload);
initiateReboot();
});
await device.command.listen("update_firmware", (payload) => {
const { version, url } = payload;
console.log(`Updating to ${version} from ${url}`);
downloadAndInstall(url);
});
async def handle_reboot(payload):
print("Reboot requested:", payload)
initiate_reboot()
async def handle_update(payload):
version = payload["version"]
url = payload["url"]
print(f"Updating to {version} from {url}")
download_and_install(url)
await device.command.listen("reboot", handle_reboot)
await device.command.listen("update_firmware", handle_update)
device->command.listen("reboot", [](const cJSON* data) {
ESP_LOGI("app", "Reboot requested");
esp_restart();
});
device->command.listen("update_firmware", [](const cJSON* data) {
const char* version = cJSON_GetObjectItem(data, "version")->valuestring;
const char* url = cJSON_GetObjectItem(data, "url")->valuestring;
ESP_LOGI("app", "Updating to %s from %s", version, url);
download_and_install(url);
});
Guaranteed Delivery
Commands are durably stored and delivered even if your device is offline when the command is sent. Once your device reconnects, any pending commands are delivered in order.
- JavaScript
- Python
- C++
If your handler throws an error, the command is automatically retried after a 5-second delay, up to a maximum of 5 attempts. If the handler succeeds (no error thrown), the command is acknowledged and won't be delivered again.
await device.command.listen("deploy", (payload) => {
const success = deployConfig(payload);
if (!success) {
// throwing an error triggers a retry in 5 seconds
throw new Error("Deploy failed — will retry");
}
});
If your handler raises an error, the command is automatically retried after a 5-second delay, up to a maximum of 5 attempts. If the handler succeeds (no error raised), the command is acknowledged and won't be delivered again.
async def handle_deploy(payload):
success = deploy_config(payload)
if not success:
# raising an error triggers a retry in 5 seconds
raise Exception("Deploy failed — will retry")
await device.command.listen("deploy", handle_deploy)
The command is acknowledged once your callback returns. If your handler needs to signal failure, manage retry logic in your application code.
device->command.listen("deploy", [](const cJSON* data) {
bool success = deploy_config(data);
if (!success) {
ESP_LOGW("app", "Deploy failed");
// handle retry in your application logic
}
});
Remove a Listener
- JavaScript
- Python
- C++
await device.command.off("reboot");
await device.command.off("reboot")
device->command.off("reboot");
Safe to call even if no listener exists for that name.