跳到主要内容

How to implement a feature that allows both the device and the mobile app to proactively check for OTA updates?

This describes the workflow and implementation for User Approval OTA in ESP RainMaker. Unlike the "User Approval" option in the dashboard, this approach gives nodes the capability to decide whether to apply an update or wait for explicit user approval, providing better control and security over the update process.

Components

Node Side

  • OTA Service (esp.service.ota) with two parameters:
    • Operation (esp.param.ota_operation): For receiving commands
    • Info (esp.param.ota_info): For sharing update information

Client Application

  • Monitors the Info parameter for available updates
  • Provides user interface for update approval
  • Sends commands through the Operation parameter

Workflow Steps

  1. Update Check Initiation — Can be triggered automatically (via CONFIG_ESP_RMAKER_OTA_AUTOFETCH) or manually through the check operation command.
  2. Update Available — Node updates the Info parameter with update details and waits for user approval. Status reported as delayed with description Waiting for Approval.
  3. Update Approval — User reviews update information in app. App sends start command upon user approval and caches the job ID for further querying via the nodes/ota_status REST API.
  4. Update Process — Node downloads firmware, validates it, applies the update, and reboots if successful.
  5. Update Monitoring — Node reports the update status as part of the regular OTA process. User can monitor via the nodes/ota_status REST API using the node ID and job ID.

JSON Structures

Node Configuration JSON

{
"node_id": "<node-id>",
"config_version": "2020-03-20",
"devices": [],
"services": [
{
"name": "OTA",
"type": "esp.service.ota",
"params": [
{
"name": "Operation",
"type": "esp.param.ota_operation",
"data_type": "string",
"properties": ["write"],
"valid_strs": ["check", "start"]
},
{
"name": "Info",
"type": "esp.param.ota_info",
"data_type": "object",
"properties": ["read"]
}
]
}
]
}

Node Parameters JSON

Initial State

{
"OTA": {
"Operation": "",
"Info": "{}"
}
}

When Update Available

{
"OTA": {
"Operation": "",
"Info": {
"job_id": "job_12345",
"fw_version": "2.0.0"
}
}
}

Parameter Update Messages

From App to Node (Operation)

{ "OTA": { "Operation": "check" } }

or

{ "OTA": { "Operation": "start" } }

From Node to App (Info)

{
"OTA": {
"Info": {
"job_id": "job_12345",
"fw_version": "2.0.0"
}
}
}

Implementation Example

Node Side Implementation

/* Create the OTA service */
esp_rmaker_device_t *ota_service = esp_rmaker_service_create("OTA", "esp.service.ota", NULL);

/* Add "Operation" string parameter with write property and valid values */
esp_rmaker_param_t *operation_param = esp_rmaker_param_create("Operation",
"esp.param.ota_operation",
esp_rmaker_str(""),
PROP_FLAG_WRITE);
const char *valid_strs[] = {"check", "start"};
esp_rmaker_param_add_valid_str_list(operation_param, valid_strs, 2);
esp_rmaker_device_add_param(ota_service, operation_param);

/* Add "Info" object parameter with read-only property */
esp_rmaker_param_t *info_param = esp_rmaker_param_create("Info",
"esp.param.ota_info",
esp_rmaker_obj("{}"),
PROP_FLAG_READ);
esp_rmaker_device_add_param(ota_service, info_param);

/* Register the write callback for the OTA service */
esp_rmaker_device_add_cb(ota_service, ota_service_write_cb, NULL);

/* Add the OTA service to the node */
esp_rmaker_node_add_device(node, ota_service);

/* Register custom OTA callback */
esp_rmaker_ota_config_t ota_config = {
.ota_cb = custom_ota_callback,
};
esp_rmaker_ota_enable(&ota_config, OTA_USING_TOPICS);

Write Callback Implementation

static esp_err_t ota_service_write_cb(const esp_rmaker_device_t *device,
const esp_rmaker_param_t *param,
const esp_rmaker_param_val_t val,
void *priv_data,
esp_rmaker_write_ctx_t *ctx)
{
if (strcmp(esp_rmaker_param_get_name(param), "Operation") == 0) {
if (strcmp(val.val.s, "start") == 0) {
ota_approved = true;
ESP_LOGI(TAG, "OTA approved.");
esp_rmaker_ota_fetch_with_delay(1);
} else if (strcmp(val.val.s, "check") == 0) {
ESP_LOGI(TAG, "Checking for OTA updates.");
esp_rmaker_ota_fetch_with_delay(1);
}
}
return ESP_OK;
}

Custom OTA Callback

static esp_err_t custom_ota_callback(esp_rmaker_ota_handle_t handle,
esp_rmaker_ota_data_t *ota_data)
{
esp_rmaker_param_t *info_param = esp_rmaker_device_get_param_by_name(ota_service, "Info");

/* If OTA is already approved, proceed with the update */
if (ota_approved) {
ota_approved = false; /* Approval valid only for one fetch */
esp_err_t err = esp_rmaker_ota_default_cb(handle, ota_data);
/* Clear the Info parameter after successful OTA update */
esp_rmaker_param_update_and_report(info_param, esp_rmaker_obj("{}"));
return err;
}

/* Update the Info parameter with job ID and firmware version */
if (info_param && (ota_data->ota_job_id || ota_data->fw_version)) {
json_gen_str_t jstr;
char json_str[128];
json_gen_str_start(&jstr, json_str, sizeof(json_str), NULL, NULL);
json_gen_start_object(&jstr);
if (ota_data->ota_job_id) {
json_gen_obj_set_string(&jstr, "job_id", ota_data->ota_job_id);
}
if (ota_data->fw_version) {
json_gen_obj_set_string(&jstr, "fw_version", ota_data->fw_version);
}
json_gen_end_object(&jstr);
json_gen_str_end(&jstr);
esp_rmaker_param_update_and_report(info_param, esp_rmaker_obj(json_str));
}

esp_rmaker_ota_report_status(handle, OTA_STATUS_DELAYED, "Waiting for Approval");
return ESP_OK;
}

Configuration Options

The following options are available under ESP RainMaker Config → ESP RainMaker OTA Config in menuconfig:

ConfigDefaultDescription
CONFIG_ESP_RMAKER_OTA_AUTOFETCHyAutomatically fetch OTA updates on boot
CONFIG_ESP_RMAKER_OTA_AUTOFETCH_PERIOD0 (once on boot)Periodic check interval in hours (0–168)

Console Commands (for testing)

After initialising the console with esp_rmaker_console_init(), you can register an ota command:

static int ota_cmd_handler(int argc, char **argv)
{
if (argc < 2) {
ESP_LOGE(TAG, "Invalid arguments. Use 'ota start' or 'ota check'.");
return ESP_ERR_INVALID_ARG;
}

if (strcmp(argv[1], "start") == 0) {
ota_approved = true;
ESP_LOGI(TAG, "OTA approved via console command.");
esp_rmaker_ota_fetch_with_delay(1);
} else if (strcmp(argv[1], "check") == 0) {
ESP_LOGI(TAG, "Checking for OTA updates via console command.");
esp_rmaker_ota_fetch_with_delay(1);
} else {
ESP_LOGE(TAG, "Unknown argument: %s. Use 'start' or 'check'.", argv[1]);
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}

void register_ota_command()
{
const esp_console_cmd_t ota_cmd = {
.command = "ota",
.help = "Manage OTA updates. Usage: ota <start|check>",
.hint = NULL,
.func = &ota_cmd_handler,
};
ESP_ERROR_CHECK(esp_console_cmd_register(&ota_cmd));
}

Usage:

ota check    # Check for updates
ota start # Start approved update

On this page