Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OTA over HTTPS (MEGH-6086) #339

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions components/esp_rainmaker/include/esp_rmaker_ota.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ typedef enum {
OTA_STATUS_REJECTED,
} ota_status_t;

/** Returns string representation of ota_status_t for reporting
*
* @param status ota_status_t variant
* @return string representation for provided status, "invalid" if not valid status
*/
char *esp_rmaker_ota_status_to_string(ota_status_t status);

/** OTA Workflow type */
typedef enum {
/** OTA will be performed using services and parameters. */
Expand All @@ -65,6 +72,19 @@ typedef enum {

/** The OTA Handle to be used by the OTA callback */
typedef void *esp_rmaker_ota_handle_t;
/** Function Prototype for Reporting Intermediate OTA States
*
* This function is called to notify RainMaker dashbord of OTA Progress
*
* @param ota_job_id Job Id to report.
* @param status OTA status to report.
* @param additional_info Descriptionn to report.
*
* @return ESP_OK on success
* @return error on faliure
*/
typedef esp_err_t (*esp_rmaker_ota_report_fn_t)(char *ota_job_id, ota_status_t status, char* additional_info);


/** OTA Data */
typedef struct {
Expand All @@ -83,6 +103,8 @@ typedef struct {
char *priv;
/** OTA Metadata. Applicable only for OTA using Topics. Will be received (if applicable) from the backend, along with the OTA URL */
char *metadata;
/** The Function to be called for reporting OTA status. This can be used if needed to override transport of OTA*/
esp_rmaker_ota_report_fn_t report_fn;
} esp_rmaker_ota_data_t;

/** Function prototype for OTA Callback
Expand Down Expand Up @@ -223,6 +245,21 @@ esp_err_t esp_rmaker_ota_report_status(esp_rmaker_ota_handle_t ota_handle, ota_s
* */
esp_err_t esp_rmaker_ota_default_cb(esp_rmaker_ota_handle_t handle, esp_rmaker_ota_data_t *ota_data);

/** Clear Rollback flag
* The default OTA callback stores a value in NVS as a flag to denote if an OTA was recently installed.
* This flag is then read to decide if firmware has been rolled back.
* This function can be called to erase that flag.
*/
esp_err_t esp_rmaker_ota_erase_rollback_flag(void);

/** Returns whether OTA validation is pending.
* Returns true if firmware validation is pending after an OTA.
* This can be reset using esp_rmaker_ota_erase_rollback_flag()
*
* @return true if validation is pending, false otherwises
*/
bool esp_rmaker_ota_is_ota_validation_pending(void);

/** Fetch OTA Info
*
* For OTA using Topics, this API can be used to explicitly ask the backend if an OTA is available.
Expand Down
65 changes: 51 additions & 14 deletions components/esp_rainmaker/src/ota/esp_rmaker_ota.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,16 @@ esp_err_t esp_rmaker_ota_report_status(esp_rmaker_ota_handle_t ota_handle, ota_s
}
esp_rmaker_ota_t *ota = (esp_rmaker_ota_t *)ota_handle;
esp_err_t err = ESP_FAIL;
if (ota->type == OTA_USING_PARAMS) {
err = esp_rmaker_ota_report_status_using_params(ota_handle, status, additional_info);
} else if (ota->type == OTA_USING_TOPICS) {
err = esp_rmaker_ota_report_status_using_topics(ota_handle, status, additional_info);

if (ota->report_fn) {
char *job_id = NULL;
if (ota->transient_priv) {
job_id = ota->transient_priv;
}
err = ota->report_fn(job_id, status, additional_info);

} else {
ESP_LOGE(TAG, "Report fn not found");
}
if (err == ESP_OK) {
esp_rmaker_ota_t *ota = (esp_rmaker_ota_t *)ota_handle;
Expand Down Expand Up @@ -337,6 +343,24 @@ esp_err_t esp_rmaker_ota_default_cb(esp_rmaker_ota_handle_t ota_handle, esp_rmak
if (!ota_data->url) {
return ESP_FAIL;
}

esp_rmaker_ota_handle_t handle_internal = NULL;

if (!ota_handle) {
/* Since ota_handle is not present, this default callback might be being called directly externally
* We'll set the bare minimum fields required by the esp_rmaker_report_status.
*/
handle_internal = MEM_CALLOC_EXTRAM(1, sizeof(esp_rmaker_ota_t));
if(handle_internal == NULL){
ESP_LOGE(TAG, "Failed to allocate memory for OTA handle.");
return ESP_ERR_NO_MEM;
}
esp_rmaker_ota_t *ota = (esp_rmaker_ota_t *) handle_internal;
ota->report_fn = ota_data->report_fn;
ota->transient_priv = (void *) ota_data->ota_job_id;
ota_handle = handle_internal;
}

/* Handle OTA metadata, if any */
if (ota_data->metadata) {
if (esp_rmaker_ota_handle_metadata(ota_handle, ota_data) != OTA_OK) {
Expand Down Expand Up @@ -386,7 +410,9 @@ esp_err_t esp_rmaker_ota_default_cb(esp_rmaker_ota_handle_t ota_handle, esp_rmak
if (err != ESP_OK) {
ESP_LOGE(TAG, "ESP HTTPS OTA Begin failed");
esp_rmaker_ota_report_status(ota_handle, OTA_STATUS_FAILED, "ESP HTTPS OTA Begin failed");
return ESP_FAIL;

err = ESP_FAIL;
goto end;
}

#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
Expand Down Expand Up @@ -488,7 +514,7 @@ esp_err_t esp_rmaker_ota_default_cb(esp_rmaker_ota_handle_t ota_handle, esp_rmak
ESP_LOGI(TAG, "OTA upgrade successful. Auto reboot is disabled. Requesting a Reboot via Event handler.");
esp_rmaker_ota_post_event(RMAKER_OTA_EVENT_REQ_FOR_REBOOT, NULL, 0);
#endif
return ESP_OK;
err = ESP_OK;
} else {
if (ota_finish_err == ESP_ERR_OTA_VALIDATE_FAILED) {
ESP_LOGE(TAG, "Image validation failed, image is corrupted");
Expand All @@ -500,7 +526,12 @@ esp_err_t esp_rmaker_ota_default_cb(esp_rmaker_ota_handle_t ota_handle, esp_rmak
ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed %d", ota_finish_err);
}
}
return ESP_FAIL;

end:
if (handle_internal) {
free(handle_internal);
}
return err;
}

static void event_handler(void* arg, esp_event_base_t event_base,
Expand Down Expand Up @@ -592,7 +623,7 @@ static esp_err_t esp_ota_check_for_mqtt(esp_rmaker_ota_t *ota)
return esp_event_handler_register(RMAKER_COMMON_EVENT, RMAKER_MQTT_EVENT_CONNECTED, &event_handler, ota);
}

static esp_err_t esp_rmaker_erase_rollback_flag(void)
esp_err_t esp_rmaker_ota_erase_rollback_flag(void)
{
nvs_handle handle;
esp_err_t err = nvs_open_from_partition(ESP_RMAKER_NVS_PART_NAME, RMAKER_OTA_NVS_NAMESPACE, NVS_READWRITE, &handle);
Expand All @@ -604,20 +635,26 @@ static esp_err_t esp_rmaker_erase_rollback_flag(void)
return ESP_OK;
}

static void esp_rmaker_ota_manage_rollback(esp_rmaker_ota_t *ota)
{
/* If rollback is enabled, and the ota update flag is found, it means that the OTA validation is pending
*/
bool esp_rmaker_ota_is_ota_validation_pending(void) {
nvs_handle handle;
esp_err_t err = nvs_open_from_partition(ESP_RMAKER_NVS_PART_NAME, RMAKER_OTA_NVS_NAMESPACE, NVS_READWRITE, &handle);
if (err == ESP_OK) {
uint8_t ota_update = 0;
size_t len = sizeof(ota_update);

/* If rollback is enabled, and the ota update flag is found, it means that the OTA validation is pending */
if ((err = nvs_get_blob(handle, RMAKER_OTA_UPDATE_FLAG_NVS_NAME, &ota_update, &len)) == ESP_OK) {
ota->validation_in_progress = true;
return true;
}
nvs_close(handle);
}

return false;
}

static void esp_rmaker_ota_manage_rollback(esp_rmaker_ota_t *ota)
{
ota->validation_in_progress = esp_rmaker_ota_is_ota_validation_pending();
const esp_partition_t *running = esp_ota_get_running_partition();
esp_ota_img_states_t ota_state;
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
Expand Down Expand Up @@ -654,7 +691,7 @@ static void esp_rmaker_ota_manage_rollback(esp_rmaker_ota_t *ota)
*/
if (ota->validation_in_progress) {
ota->rolled_back = true;
esp_rmaker_erase_rollback_flag();
esp_rmaker_ota_erase_rollback_flag();
if (ota->type == OTA_USING_PARAMS) {
/* Calling this only for OTA_USING_PARAMS, because for OTA_USING_TOPICS,
* the work queue function will manage the status reporting later.
Expand Down
6 changes: 3 additions & 3 deletions components/esp_rainmaker/src/ota/esp_rmaker_ota_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ typedef struct {
ota_status_t last_reported_status;
void *transient_priv;
char *metadata;
esp_rmaker_ota_report_fn_t report_fn;
} esp_rmaker_ota_t;

char *esp_rmaker_ota_status_to_string(ota_status_t status);
void esp_rmaker_ota_common_cb(void *priv);
void esp_rmaker_ota_finish_using_params(esp_rmaker_ota_t *ota);
void esp_rmaker_ota_finish_using_topics(esp_rmaker_ota_t *ota);
esp_err_t esp_rmaker_ota_enable_using_params(esp_rmaker_ota_t *ota);
esp_err_t esp_rmaker_ota_report_status_using_params(esp_rmaker_ota_handle_t ota_handle,
esp_err_t esp_rmaker_ota_report_status_using_params(char *ota_job_id,
ota_status_t status, char *additional_info);
esp_err_t esp_rmaker_ota_enable_using_topics(esp_rmaker_ota_t *ota);
esp_err_t esp_rmaker_ota_report_status_using_topics(esp_rmaker_ota_handle_t ota_handle,
esp_err_t esp_rmaker_ota_report_status_using_topics(char *ota_job_id,
ota_status_t status, char *additional_info);
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ static esp_err_t esp_rmaker_ota_service_cb(const esp_rmaker_device_t *device, co
return ESP_FAIL;
}

esp_err_t esp_rmaker_ota_report_status_using_params(esp_rmaker_ota_handle_t ota_handle, ota_status_t status, char *additional_info)
esp_err_t esp_rmaker_ota_report_status_using_params(char *ota_job_id, ota_status_t status, char *additional_info)
{
const esp_rmaker_device_t *device = esp_rmaker_node_get_device_by_name(esp_rmaker_get_node(), ESP_RMAKER_OTA_SERV_NAME);
if (!device) {
Expand All @@ -100,6 +100,7 @@ esp_err_t esp_rmaker_ota_report_status_using_params(esp_rmaker_ota_handle_t ota_
/* Enable the ESP RainMaker specific OTA */
esp_err_t esp_rmaker_ota_enable_using_params(esp_rmaker_ota_t *ota)
{
ota->report_fn = esp_rmaker_ota_report_status_using_params;
esp_rmaker_device_t *service = esp_rmaker_ota_service_create(ESP_RMAKER_OTA_SERV_NAME, ota);
if (!service) {
ESP_LOGE(TAG, "Failed to create OTA Service");
Expand Down
13 changes: 7 additions & 6 deletions components/esp_rainmaker/src/ota/esp_rmaker_ota_using_topics.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,19 @@ static uint64_t ota_autofetch_period = (OTA_AUTOFETCH_PERIOD * 60 * 60 * 1000000

static const char *TAG = "esp_rmaker_ota_using_topics";

esp_err_t esp_rmaker_ota_report_status_using_topics(esp_rmaker_ota_handle_t ota_handle, ota_status_t status, char *additional_info)
esp_err_t esp_rmaker_ota_report_status_using_topics(char *ota_job_id, ota_status_t status, char *additional_info)
{
if (!ota_handle) {
return ESP_FAIL;
if (!ota_job_id) {
ESP_LOGE(TAG, "Reporting failed. OTA Job ID not found");
return ESP_ERR_INVALID_ARG;
}
esp_rmaker_ota_t *ota = (esp_rmaker_ota_t *)ota_handle;

char publish_payload[200];
json_gen_str_t jstr;
json_gen_str_start(&jstr, publish_payload, sizeof(publish_payload), NULL, NULL);
json_gen_start_object(&jstr);
if (ota->transient_priv) {
json_gen_obj_set_string(&jstr, "ota_job_id", (char *)ota->transient_priv);
if (ota_job_id) {
json_gen_obj_set_string(&jstr, "ota_job_id", ota_job_id);
} else {
/* This will get executed only when the OTA status is being reported after a reboot, either to
* indicate successful verification of new firmware, or to indicate that firmware was rolled back
Expand Down Expand Up @@ -321,6 +321,7 @@ static void esp_rmaker_ota_work_fn(void *priv_data)
/* Enable the ESP RainMaker specific OTA */
esp_err_t esp_rmaker_ota_enable_using_topics(esp_rmaker_ota_t *ota)
{
ota->report_fn = esp_rmaker_ota_report_status_using_topics;
esp_err_t err = esp_rmaker_work_queue_add_task(esp_rmaker_ota_work_fn, ota);
if (err == ESP_OK) {
ESP_LOGI(TAG, "OTA enabled with Topics");
Expand Down
7 changes: 7 additions & 0 deletions components/rmaker_ota_https/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
set(priv_req rmaker_common esp_rainmaker esp_timer esp_netif esp_http_client esp-tls app_update mbedtls esp_schedule json_parser json_generator)

idf_component_register(SRC_DIRS "src"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src"
PRIV_REQUIRES ${priv_req}
)
26 changes: 26 additions & 0 deletions components/rmaker_ota_https/Kconfig.projbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
menu "ESP RainMaker HTTPS OTA Config"
config OTA_HTTPS_AUTOFETCH_ENABLED
bool "Enable Autofetch"
default y
help
Enable periodic checking of OTA.

config OTA_HTTPS_AUTOFETCH_PERIOD
int "Autofetch Period"
depends on OTA_HTTPS_AUTOFETCH_ENABLED
default 2
help
The time period(in hours) for checking OTA.
If disabled, OTA is only checked once, when HTTPS OTA is enabled.

config OTA_HTTPS_ROLLBACK_PERIOD
int "Rollback Period"
default 90
help
The time period(in seconds) for checking if OTA is valid.
If OTA is not marked valid in this timeframe, firmware is rolled back.

config OTA_HTTPS_APPLY_STACK_SIZE
int "Stack size(in kilobytes) of the task reponsible for calling the OTA callback"
default 3072
endmenu
Loading