Remote Procedure Call (RPC)
Receive RPC calls from the App SDK and return a response. Your app sends a request, your device processes it and replies.
Overview
RPC uses a request/reply pattern — your app sends a request, your device processes it and sends a response. Unlike commands, the caller waits for a response.
API
listen()
Register a handler for incoming RPC calls matching a given name.
- JavaScript
- Python
- C++
await device.rpc.listen(name, callback)| Parameter | Type | Description |
|---|---|---|
name | string | The RPC name to listen for |
callback | function | Called when an RPC request arrives. Receives an RpcRequest object |
Throws DuplicateListenerError if a listener with the same name is already registered
Throws ValidationError if name contains invalid characters
await device.rpc.listen(name, callback)| Parameter | Type | Description |
|---|---|---|
name | string | The RPC name to listen for |
callback | function | Called when an RPC request arrives. Receives an RpcRequest object |
Throws DuplicateListenerError if a listener with the same name is already registered
Throws ValidationError if name contains invalid characters
device->rpc.listen(name, callback) → relay_err_t| Parameter | Type | Description |
|---|---|---|
name | const char* | The RPC name to listen for |
callback | relay_rpc_cb_t | void (*)(relay_rpc_request_t* request) — called when an RPC request arrives |
Returns RELAY_ERR_DUPLICATE_LISTENER if a listener with the same name is already registered, RELAY_ERR_VALIDATION if name contains invalid characters
Names can only contain letters, numbers, hyphens, and underscores (A-Z, a-z, 0-9, -, _). This applies to RPC names, command names, and event names.
off()
Unregister a previously registered RPC listener.
- JavaScript
- Python
- C++
await device.rpc.off(name)Returns true if the listener was removed, false if no listener existed for that name
await device.rpc.off(name)Returns true if the listener was removed, false if no listener existed for that name
device->rpc.off(name) → relay_err_tReturns RELAY_OK on success
The RpcRequest Object
When an RPC call arrives, your callback receives a request object.
- JavaScript
- Python
- C++
| Property/Method | Description |
|---|---|
request.payload | The data sent by the caller |
request.respond(data) | Send a success response back |
request.error(data) | Send an error response back |
| Property/Method | Description |
|---|---|
request.payload | The data sent by the caller |
request.respond(data) | Send a success response back |
request.error(data) | Send an error response back |
| Field/Method | Type | Description |
|---|---|---|
request->payload | const char* | Raw JSON string from the caller |
request->payload_len | size_t | Length of the payload |
request->respond(data) | relay_err_t | Send a success response (const cJSON*) |
request->error(data) | relay_err_t | Send an error response (const cJSON*) |
The caller owns the cJSON* objects passed to respond() / error() and must free them after the call.
Every RPC handler should call either respond() or error() — the caller is waiting for a reply.
Listen for RPC Calls
- JavaScript
- Python
- C++
await device.rpc.listen("get_status", (request) => {
const status = {
uptime: process.uptime(),
temperature: readSensor(),
firmware: "1.2.0",
};
request.respond(status);
});
async def handle_get_status(request):
status = {
"uptime": get_uptime(),
"temperature": read_sensor(),
"firmware": "1.2.0",
}
request.respond(status)
await device.rpc.listen("get_status", handle_get_status)
device->rpc.listen("get_status", [](relay_rpc_request_t* request) {
cJSON* status = cJSON_CreateObject();
cJSON_AddNumberToObject(status, "uptime", esp_timer_get_time() / 1000000);
cJSON_AddNumberToObject(status, "temperature", read_sensor());
cJSON_AddStringToObject(status, "firmware", "1.2.0");
request->respond(status);
cJSON_Delete(status);
});
Responding with an Error
If the request can't be fulfilled, use request.error() instead of request.respond().
- JavaScript
- Python
- C++
await device.rpc.listen("restart_service", (request) => {
const { service } = request.payload;
if (!isValidService(service)) {
request.error({ message: `Unknown service: ${service}` });
return;
}
restartService(service);
request.respond({ restarted: service });
});
async def handle_restart(request):
service = request.payload.get("service")
if not is_valid_service(service):
request.error({"message": f"Unknown service: {service}"})
return
restart_service(service)
request.respond({"restarted": service})
await device.rpc.listen("restart_service", handle_restart)
device->rpc.listen("restart_service", [](relay_rpc_request_t* request) {
cJSON* body = cJSON_Parse(request->payload);
const char* service = cJSON_GetObjectItem(body, "service")->valuestring;
if (!is_valid_service(service)) {
cJSON* err = cJSON_CreateObject();
cJSON_AddStringToObject(err, "message", "Unknown service");
request->error(err);
cJSON_Delete(err);
} else {
restart_service(service);
cJSON* resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "restarted", service);
request->respond(resp);
cJSON_Delete(resp);
}
cJSON_Delete(body);
});
Remove a Listener
- JavaScript
- Python
- C++
await device.rpc.off("get_status");
await device.rpc.off("get_status")
device->rpc.off("get_status");
Safe to call even if no listener exists for that name.
Error Handling
- JavaScript
- Python
- C++
// registering the same name twice throws DuplicateListenerError
await device.rpc.listen("get_status", handler);
try {
await device.rpc.listen("get_status", anotherHandler);
} catch (err) {
// DuplicateListenerError: "get_status" is already registered
console.error(err.message);
}
await device.rpc.listen("get_status", handler)
try:
await device.rpc.listen("get_status", another_handler)
except DuplicateListenerError as err:
# "get_status" is already registered
print(err)
device->rpc.listen("get_status", handler);
relay_err_t err = device->rpc.listen("get_status", another_handler);
if (err == RELAY_ERR_DUPLICATE_LISTENER) {
ESP_LOGE("app", "get_status is already registered");
}
To replace a listener, call off() first, then listen() again with the new handler.