Skip to content

Commit e9676d9

Browse files
committed
Merge branch 'feature/add_ota_resumption' into 'master'
ota: Add resumption ota feature See merge request app-frameworks/esp-rainmaker!577
2 parents ebe3742 + e0bc3f5 commit e9676d9

File tree

8 files changed

+165
-4
lines changed

8 files changed

+165
-4
lines changed

components/esp_rainmaker/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## 1.7.2
4+
5+
### New Feature
6+
7+
- Add RainMaker OTA resumption feature. The OTA process will resume downloading from the last position instead of restarting the download.
8+
9+
OTA will check whether the MD5 values of the two consecutive files are the same. If the same, the OTA will continue; if they are different, the OTA will restart.
10+
Therefore, cloud support for delivering the file MD5 value is required (supported in backend version 3.0.0 and above).
11+
In addition, this feature requires IDF version 5.5 and above.
12+
313
## 1.7.1
414

515
### New Feature

components/esp_rainmaker/Kconfig.projbuild

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,13 @@ menu "ESP RainMaker Config"
393393
help
394394
Delay (in minutes) before re-fetching OTA details after all retry attempts fail (for OTA using topics).
395395

396+
config ESP_RMAKER_HTTP_OTA_RESUMPTION
397+
bool "Enable HTTP OTA resumption"
398+
default y
399+
depends on ESP_RMAKER_OTA_USE_HTTPS
400+
help
401+
If you enable this, the OTA process will resume downloading from the last position instead of restarting the download.
402+
396403
choice ESP_RMAKER_OTA_TYPE
397404
prompt "OTA Update Protocol Type"
398405
default ESP_RMAKER_OTA_USE_HTTPS

components/esp_rainmaker/idf_component.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
## IDF Component Manager Manifest File
2-
version: "1.7.1"
2+
version: "1.7.2"
33
description: ESP RainMaker firmware agent
44
url: https://github.com/espressif/esp-rainmaker/tree/master/components/esp_rainmaker
55
repository: https://github.com/espressif/esp-rainmaker.git

components/esp_rainmaker/include/esp_rmaker_ota.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ typedef struct {
7777
int filesize;
7878
/** The firmware version of the OTA image **/
7979
char *fw_version;
80+
/** The MD5 of the OTA File. Can be NULL if the MD5 isn't received from
81+
* the ESP RainMaker Cloud */
82+
char *file_md5;
8083
/** The OTA Job ID received from cloud **/
8184
char *ota_job_id;
8285
/** The server certificate passed in esp_rmaker_enable_ota() */

components/esp_rainmaker/src/ota/esp_rmaker_https_ota.c

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <esp_wifi_types.h>
1111
#include <esp_wifi.h>
1212
#include <errno.h>
13+
#include <nvs.h>
1314
#include <esp_https_ota.h>
1415

1516
#if CONFIG_BT_ENABLED
@@ -51,6 +52,83 @@ const char *ESP_RMAKER_OTA_DEFAULT_SERVER_CERT = esp_rmaker_ota_def_cert;
5152

5253
#define ESP_RMAKER_HTTPS_OTA_TIMEOUT_MS 5000
5354

55+
#ifdef CONFIG_ESP_RMAKER_HTTP_OTA_RESUMPTION
56+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
57+
#define RMAKER_OTA_HTTP_OTA_RESUMPTION
58+
#else
59+
#warning "HTTP OTA resumption, needs IDF version >= 5.5.0"
60+
#endif
61+
#endif
62+
63+
#ifdef RMAKER_OTA_HTTP_OTA_RESUMPTION
64+
#define RMAKER_OTA_WRITTEN_LENGTH_NVS_NAME "ota_writen"
65+
#define RMAKER_OTA_FILE_MD5_NVS_NAME "ota_file_md5"
66+
static esp_err_t esp_rmaker_https_ota_get_len_and_md5_from_nvs(uint32_t *written_len, char **file_md5)
67+
{
68+
*written_len = 0;
69+
*file_md5 = NULL;
70+
nvs_handle handle;
71+
esp_err_t err = nvs_open_from_partition(ESP_RMAKER_NVS_PART_NAME, RMAKER_OTA_NVS_NAMESPACE, NVS_READWRITE, &handle);
72+
if (err == ESP_OK) {
73+
uint32_t len = 0;
74+
err = nvs_get_u32(handle, RMAKER_OTA_WRITTEN_LENGTH_NVS_NAME, &len);
75+
if (err == ESP_OK) {
76+
*written_len = len;
77+
size_t file_md5_len = 0;
78+
err = nvs_get_str(handle, RMAKER_OTA_FILE_MD5_NVS_NAME, NULL, &file_md5_len);
79+
if (err == ESP_OK) {
80+
*file_md5 = MEM_CALLOC_EXTRAM(1, file_md5_len + 1);
81+
if (!*file_md5) {
82+
err = ESP_ERR_NO_MEM;
83+
} else {
84+
err = nvs_get_str(handle, RMAKER_OTA_FILE_MD5_NVS_NAME, *file_md5, &file_md5_len);
85+
if (err == ESP_OK) {
86+
(*file_md5)[file_md5_len] = '\0';
87+
} else {
88+
free(*file_md5);
89+
*file_md5 = NULL;
90+
}
91+
}
92+
}
93+
}
94+
nvs_close(handle);
95+
}
96+
return err;
97+
}
98+
99+
static esp_err_t esp_rmaker_https_ota_set_len_and_md5_to_nvs(uint32_t written_len, char *file_md5)
100+
{
101+
nvs_handle handle;
102+
esp_err_t err = nvs_open_from_partition(ESP_RMAKER_NVS_PART_NAME, RMAKER_OTA_NVS_NAMESPACE, NVS_READWRITE, &handle);
103+
if (err == ESP_OK) {
104+
err = nvs_set_u32(handle, RMAKER_OTA_WRITTEN_LENGTH_NVS_NAME, written_len);
105+
if (err == ESP_OK) {
106+
if (file_md5) {
107+
err = nvs_set_str(handle, RMAKER_OTA_FILE_MD5_NVS_NAME, file_md5);
108+
}
109+
if (err == ESP_OK) {
110+
nvs_commit(handle);
111+
}
112+
}
113+
nvs_close(handle);
114+
}
115+
return err;
116+
}
117+
118+
static esp_err_t esp_rmaker_https_ota_cleanup_ota_cfg_from_nvs(void)
119+
{
120+
nvs_handle handle;
121+
esp_err_t err = nvs_open_from_partition(ESP_RMAKER_NVS_PART_NAME, RMAKER_OTA_NVS_NAMESPACE, NVS_READWRITE, &handle);
122+
if (err == ESP_OK) {
123+
nvs_erase_key(handle, RMAKER_OTA_WRITTEN_LENGTH_NVS_NAME);
124+
nvs_erase_key(handle, RMAKER_OTA_FILE_MD5_NVS_NAME);
125+
nvs_commit(handle);
126+
nvs_close(handle);
127+
}
128+
return err;
129+
}
130+
#endif
131+
54132
static esp_err_t esp_rmaker_ota_use_https(esp_rmaker_ota_handle_t ota_handle, esp_rmaker_ota_data_t *ota_data, char *err_desc, size_t err_desc_size)
55133
{
56134
int buffer_size_tx = DEF_HTTP_TX_BUFFER_SIZE;
@@ -85,6 +163,33 @@ static esp_err_t esp_rmaker_ota_use_https(esp_rmaker_ota_handle_t ota_handle, es
85163
esp_https_ota_config_t ota_config = {
86164
.http_config = &config,
87165
};
166+
#ifdef RMAKER_OTA_HTTP_OTA_RESUMPTION
167+
/* Check if file md5 is present and match with the one in the ota_data, if yes, resume the OTA;
168+
otherwise, start from the beginning and set the written length 0 and file md5 to NVS */
169+
if (ota_data->file_md5) {
170+
char *file_md5 = NULL;
171+
uint32_t written_len = 0;
172+
bool resume_ota = false;
173+
if (esp_rmaker_https_ota_get_len_and_md5_from_nvs(&written_len, &file_md5) == ESP_OK) {
174+
if (strncmp(file_md5, ota_data->file_md5, strlen(ota_data->file_md5)) != 0) {
175+
ESP_LOGW(TAG, "File MD5 mismatch, seems a new firmware, not resuming OTA");
176+
} else {
177+
resume_ota = true;
178+
ota_config.ota_resumption = true;
179+
ota_config.ota_image_bytes_written = written_len;
180+
}
181+
free(file_md5);
182+
}
183+
if (!resume_ota) {
184+
/* Start from the beginning and set the written length 0 and file md5 to NVS */
185+
if (esp_rmaker_https_ota_set_len_and_md5_to_nvs(0, ota_data->file_md5) != ESP_OK) {
186+
ESP_LOGE(TAG, "Failed to set written length 0 and file MD5 to NVS");
187+
}
188+
}
189+
} else {
190+
ESP_LOGW(TAG, "File MD5 not present, not resuming OTA because can not verify the already downloaded firmware is the same as the new firmware, please upgrade the backend to support it");
191+
}
192+
#endif
88193
/* Using a warning just to highlight the message */
89194
ESP_LOGW(TAG, "Starting OTA. This may take time.");
90195
esp_https_ota_handle_t https_ota_handle = NULL;
@@ -158,6 +263,15 @@ static esp_err_t esp_rmaker_ota_use_https(esp_rmaker_ota_handle_t ota_handle, es
158263
ESP_LOGI(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle));
159264
count = 0;
160265
}
266+
#ifdef RMAKER_OTA_HTTP_OTA_RESUMPTION
267+
/* if file md5 is present, save the written length to NVS */
268+
if (ota_data->file_md5) {
269+
/* file md5 is present, only set the written length to NVS */
270+
if (esp_rmaker_https_ota_set_len_and_md5_to_nvs(esp_https_ota_get_image_len_read(https_ota_handle), NULL) != ESP_OK) {
271+
ESP_LOGE(TAG, "Failed to save OTA written length to NVS");
272+
}
273+
}
274+
#endif
161275
#ifdef CONFIG_ESP_RMAKER_OTA_PROGRESS_SUPPORT
162276
int image_size = esp_https_ota_get_image_size(https_ota_handle);
163277
int read_size = esp_https_ota_get_image_len_read(https_ota_handle);
@@ -186,7 +300,11 @@ static esp_err_t esp_rmaker_ota_use_https(esp_rmaker_ota_handle_t ota_handle, es
186300
err = ESP_FAIL;
187301
goto ota_end;
188302
}
189-
303+
#ifdef RMAKER_OTA_HTTP_OTA_RESUMPTION
304+
if (esp_rmaker_https_ota_cleanup_ota_cfg_from_nvs() != ESP_OK) {
305+
ESP_LOGE(TAG, "Failed to cleanup OTA config from NVS");
306+
}
307+
#endif
190308
/* Report completion before finishing */
191309
esp_rmaker_ota_report_status(ota_handle, OTA_STATUS_IN_PROGRESS, "Firmware Image download complete");
192310

components/esp_rainmaker/src/ota/esp_rmaker_ota.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ void esp_rmaker_ota_common_cb(void *priv)
148148
#endif
149149
.filesize = ota->filesize,
150150
.fw_version = ota->fw_version,
151+
.file_md5 = ota->file_md5,
151152
.ota_job_id = (char *)ota->transient_priv,
152153
.server_cert = ota->server_cert,
153154
.priv = ota->priv,

components/esp_rainmaker/src/ota/esp_rmaker_ota_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ typedef struct {
3333
const char *server_cert;
3434
char *url;
3535
char *fw_version;
36+
char *file_md5;
3637
#ifdef CONFIG_ESP_RMAKER_OTA_USE_MQTT
3738
char *stream_id;
3839
#endif

components/esp_rainmaker/src/ota/esp_rmaker_ota_using_topics.c

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ void esp_rmaker_ota_finish_using_topics(esp_rmaker_ota_t *ota)
195195
free(ota->fw_version);
196196
ota->fw_version = NULL;
197197
}
198+
if (ota->file_md5) {
199+
free(ota->file_md5);
200+
ota->file_md5 = NULL;
201+
}
198202
ota->ota_in_progress = false;
199203
}
200204
static void ota_url_handler(const char *topic, void *payload, size_t payload_len, void *priv_data)
@@ -215,12 +219,13 @@ static void ota_url_handler(const char *topic, void *payload, size_t payload_len
215219
{
216220
"ota_job_id": "<ota_job_id>",
217221
"url": "<fw_url>",
222+
"file_md5": "<file_md5>",
218223
"fw_version": "<fw_version>",
219224
"filesize": <size_in_bytes>
220225
}
221226
*/
222227
jparse_ctx_t jctx;
223-
char *url = NULL, *ota_job_id = NULL, *fw_version = NULL;
228+
char *url = NULL, *ota_job_id = NULL, *fw_version = NULL, *file_md5 = NULL;
224229
#ifdef CONFIG_ESP_RMAKER_OTA_USE_MQTT
225230
char *stream_id = NULL;
226231
#endif
@@ -270,7 +275,19 @@ static void ota_url_handler(const char *topic, void *payload, size_t payload_len
270275
}
271276
json_obj_get_string(&jctx, "url", url, len);
272277
ESP_LOGI(TAG, "URL: %s", url);
273-
278+
len = 0;
279+
ret = json_obj_get_strlen(&jctx, "file_md5", &len);
280+
if (ret == ESP_OK) {
281+
len++; /* Increment for NULL character */
282+
file_md5 = MEM_CALLOC_EXTRAM(1, len);
283+
if (!file_md5) {
284+
ESP_LOGE(TAG, "Aborted. File MD5 memory allocation failed");
285+
esp_rmaker_ota_report_status(ota_handle, OTA_STATUS_FAILED, "Aborted. File MD5 memory allocation failed");
286+
goto end;
287+
}
288+
json_obj_get_string(&jctx, "file_md5", file_md5, len);
289+
ESP_LOGI(TAG, "File MD5: %s", file_md5);
290+
}
274291
#ifdef CONFIG_ESP_RMAKER_OTA_USE_MQTT
275292
len = 0;
276293
ret = json_obj_get_strlen(&jctx, "stream_id", &len);
@@ -330,6 +347,7 @@ static void ota_url_handler(const char *topic, void *payload, size_t payload_len
330347
ota->stream_id = stream_id;
331348
#endif
332349
ota->fw_version = fw_version;
350+
ota->file_md5 = file_md5;
333351
ota->filesize = filesize;
334352
ota->ota_in_progress = true;
335353
if (esp_rmaker_work_queue_add_task(esp_rmaker_ota_common_cb, ota) != ESP_OK) {
@@ -348,6 +366,9 @@ static void ota_url_handler(const char *topic, void *payload, size_t payload_len
348366
if (fw_version) {
349367
free(fw_version);
350368
}
369+
if (file_md5) {
370+
free(file_md5);
371+
}
351372
esp_rmaker_ota_finish_using_topics(ota);
352373
json_parse_end(&jctx);
353374
return;

0 commit comments

Comments
 (0)