squeezelite-ota.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. /* OTA example
  2. This example code is in the Public Domain (or CC0 licensed, at your option.)
  3. Unless required by applicable law or agreed to in writing, this
  4. software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  5. CONDITIONS OF ANY KIND, either express or implied.
  6. */
  7. #include "freertos/FreeRTOS.h"
  8. #include "freertos/task.h"
  9. #include "esp_system.h"
  10. #include "esp_event.h"
  11. #include "esp_log.h"
  12. #include "esp_ota_ops.h"
  13. #include "esp_https_ota.h"
  14. #include "string.h"
  15. #include <stdbool.h>
  16. #include "nvs.h"
  17. #include "nvs_flash.h"
  18. #include "cmd_system.h"
  19. #include "esp_err.h"
  20. #include "tcpip_adapter.h"
  21. #include "squeezelite-ota.h"
  22. static const char *TAG = "squeezelite-ota";
  23. extern const uint8_t server_cert_pem_start[] asm("_binary_github_pem_start");
  24. extern const uint8_t server_cert_pem_end[] asm("_binary_github_pem_end");
  25. extern bool wait_for_wifi();
  26. extern char current_namespace[];
  27. static struct {
  28. char status_text[31];
  29. uint32_t ota_actual_len;
  30. uint32_t ota_total_len;
  31. char * redirected_url;
  32. char * current_url;
  33. bool bRedirectFound;
  34. bool bOTAStarted;
  35. bool bInitialized;
  36. } ota_status;
  37. uint8_t lastpct=0;
  38. uint8_t newpct=0;
  39. static esp_http_client_config_t config;
  40. static esp_http_client_config_t ota_config;
  41. static esp_http_client_handle_t client;
  42. const char * ota_get_status(){
  43. if(!ota_status.bInitialized)
  44. {
  45. memset(ota_status.status_text, 0x00,sizeof(ota_status.status_text));
  46. ota_status.bInitialized = true;
  47. }
  48. return ota_status.status_text;
  49. }
  50. uint8_t ota_get_pct_complete(){
  51. return ota_status.ota_total_len==0?0:
  52. (uint8_t)((float)ota_status.ota_actual_len/(float)ota_status.ota_total_len*100.0f);
  53. }
  54. static void __attribute__((noreturn)) task_fatal_error(void)
  55. {
  56. ESP_LOGE(TAG, "Exiting task due to fatal error...");
  57. (void)vTaskDelete(NULL);
  58. while (1) {
  59. ;
  60. }
  61. }
  62. #define FREE_RESET(p) if(p!=NULL) { free(p); p=NULL; }
  63. esp_err_t _http_event_handler(esp_http_client_event_t *evt)
  64. {
  65. // --------------
  66. // Received parameters
  67. //
  68. // esp_http_client_event_id_tevent_id event_id, to know the cause of the event
  69. // esp_http_client_handle_t client
  70. // esp_http_client_handle_t context
  71. // void *data data of the event
  72. // int data_len - data length of data
  73. // void *user_data -- user_data context, from esp_http_client_config_t user_data
  74. // char *header_key For HTTP_EVENT_ON_HEADER event_id, it’s store current http header key
  75. // char *header_value For HTTP_EVENT_ON_HEADER event_id, it’s store current http header value
  76. // --------------
  77. switch (evt->event_id) {
  78. case HTTP_EVENT_ERROR:
  79. ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
  80. //strncpy(ota_status,"HTTP_EVENT_ERROR",sizeof(ota_status)-1);
  81. break;
  82. case HTTP_EVENT_ON_CONNECTED:
  83. ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
  84. if(ota_status.bOTAStarted) snprintf(ota_status.status_text,sizeof(ota_status.status_text)-1,"Installing...");
  85. ota_status.ota_total_len=0;
  86. ota_status.ota_actual_len=0;
  87. lastpct=0;
  88. newpct=0;
  89. ESP_LOGD(TAG,"Heap internal:%zu (min:%zu) external:%zu (min:%zu)\n",
  90. heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
  91. heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
  92. heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
  93. heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM));
  94. break;
  95. case HTTP_EVENT_HEADER_SENT:
  96. ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
  97. break;
  98. case HTTP_EVENT_ON_HEADER:
  99. ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, status_code=%d, key=%s, value=%s",esp_http_client_get_status_code(evt->client),evt->header_key, evt->header_value);
  100. if (strcasecmp(evt->header_key, "location") == 0) {
  101. FREE_RESET(ota_status.redirected_url);
  102. ota_status.redirected_url=strdup(evt->header_value);
  103. ESP_LOGW(TAG,"OTA will redirect to url: %s",ota_status.redirected_url);
  104. ota_status.bRedirectFound= true;
  105. }
  106. if (strcasecmp(evt->header_key, "content-length") == 0) {
  107. ota_status.ota_total_len = atol(evt->header_value);
  108. ESP_LOGW(TAG, "Content length found: %s, parsed to %d", evt->header_value, ota_status.ota_total_len);
  109. }
  110. break;
  111. case HTTP_EVENT_ON_DATA:
  112. if(!ota_status.bOTAStarted) {
  113. ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, status_code=%d, len=%d",esp_http_client_get_status_code(evt->client), evt->data_len);
  114. }
  115. else if(ota_status.bOTAStarted && esp_http_client_get_status_code(evt->client) == 200 ){
  116. ota_status.ota_actual_len+=evt->data_len;
  117. if(ota_get_pct_complete()%5 == 0) newpct = ota_get_pct_complete();
  118. if(lastpct!=newpct )
  119. {
  120. lastpct=newpct;
  121. ESP_LOGD(TAG,"Receiving OTA data chunk len: %d, %d of %d (%d pct)", evt->data_len, ota_status.ota_actual_len, ota_status.ota_total_len, newpct);
  122. ESP_LOGD(TAG,"Heap internal:%zu (min:%zu) external:%zu (min:%zu)\n",
  123. heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
  124. heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
  125. heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
  126. heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM));
  127. }
  128. }
  129. break;
  130. case HTTP_EVENT_ON_FINISH:
  131. ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
  132. ESP_LOGD(TAG,"Heap internal:%zu (min:%zu) external:%zu (min:%zu)\n",
  133. heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
  134. heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
  135. heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
  136. heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM));
  137. break;
  138. case HTTP_EVENT_DISCONNECTED:
  139. ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
  140. break;
  141. }
  142. return ESP_OK;
  143. }
  144. static void check_http_redirect(void)
  145. {
  146. esp_err_t err=ESP_OK;
  147. ESP_LOGD(TAG, "Checking for http redirects. initializing http client");
  148. client = esp_http_client_init(&config);
  149. if (client == NULL) {
  150. ESP_LOGE(TAG, "Failed to initialise HTTP connection");
  151. task_fatal_error();
  152. }
  153. ESP_LOGD(TAG, "opening http connection");
  154. err = esp_http_client_open(client, 0);
  155. if (err != ESP_OK) {
  156. ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
  157. esp_http_client_cleanup(client);
  158. task_fatal_error();
  159. }
  160. ESP_LOGD(TAG, "fetching headers");
  161. esp_http_client_fetch_headers(client);
  162. if (err == ESP_OK) {
  163. ESP_LOGI(TAG, "redirect check retrieved headers");
  164. } else {
  165. ESP_LOGE(TAG, "redirect check returned %s", esp_err_to_name(err));
  166. }
  167. }
  168. esp_err_t init_config(esp_http_client_config_t * conf, const char * url){
  169. memset(conf, 0x00, sizeof(esp_http_client_config_t));
  170. conf->cert_pem = (char *)server_cert_pem_start;
  171. conf->event_handler = _http_event_handler;
  172. conf->buffer_size = 1024*2;
  173. conf->disable_auto_redirect=true;
  174. conf->skip_cert_common_name_check = false;
  175. conf->url = strdup(url);
  176. conf->max_redirection_count = 0;
  177. return ESP_OK;
  178. }
  179. void ota_task(void *pvParameter)
  180. {
  181. char * passedURL=(char *)pvParameter;
  182. memset(&ota_status, 0x00, sizeof(ota_status));
  183. ota_status.bInitialized = true;
  184. ESP_LOGD(TAG, "HTTP ota Thread started");
  185. snprintf(ota_status.status_text,sizeof(ota_status.status_text)-1,"Initializing...");
  186. ota_status.bRedirectFound=false;
  187. if(passedURL==NULL || strlen(passedURL)==0){
  188. ESP_LOGE(TAG,"HTTP OTA called without a url");
  189. vTaskDelete(NULL);
  190. return ;
  191. }
  192. ota_status.current_url= strdup(passedURL);
  193. init_config(&config,ota_status.current_url);
  194. FREE_RESET(pvParameter);
  195. snprintf(ota_status.status_text,sizeof(ota_status.status_text)-1,"Checking for redirect...");
  196. check_http_redirect();
  197. if(ota_status.bRedirectFound && ota_status.redirected_url== NULL){
  198. // OTA Failed miserably. Errors would have been logged somewhere
  199. ESP_LOGE(TAG,"Redirect check failed to identify target URL. Bailing out");
  200. vTaskDelete(NULL);
  201. }
  202. ESP_LOGD(TAG,"Calling esp_https_ota");
  203. init_config(&ota_config,ota_status.bRedirectFound?ota_status.redirected_url:ota_status.current_url);
  204. ota_status.bOTAStarted = true;
  205. snprintf(ota_status.status_text,sizeof(ota_status.status_text)-1,"Starting OTA...");
  206. // pause to let the system catch up
  207. vTaskDelay(1500/ portTICK_RATE_MS);
  208. esp_err_t err = esp_https_ota(&config);
  209. if (err == ESP_OK) {
  210. snprintf(ota_status.status_text,sizeof(ota_status.status_text)-1,"Success!");
  211. esp_restart();
  212. } else {
  213. snprintf(ota_status.status_text,sizeof(ota_status.status_text)-1,"Error: %s",esp_err_to_name(err));
  214. ESP_LOGE(TAG, "Firmware upgrade failed with error : %s", esp_err_to_name(err));
  215. }
  216. FREE_RESET(ota_status.current_url);
  217. FREE_RESET(ota_status.redirected_url);
  218. vTaskDelete(NULL);
  219. }
  220. void start_ota(const char * bin_url)
  221. {
  222. ESP_LOGW(TAG, "Called to update the firmware from url: %s",bin_url);
  223. #if RECOVERY_APPLICATION
  224. // Initialize NVS.
  225. int ret=0;
  226. esp_err_t err = nvs_flash_init();
  227. if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
  228. // todo: If we ever change the size of the nvs partition, we need to figure out a mechanism to enlarge the nvs.
  229. // 1.OTA app partition table has a smaller NVS partition size than the non-OTA
  230. // partition table. This size mismatch may cause NVS initialization to fail.
  231. // 2.NVS partition contains data in new format and cannot be recognized by this version of code.
  232. // If this happens, we erase NVS partition and initialize NVS again.
  233. ESP_ERROR_CHECK(nvs_flash_erase());
  234. err = nvs_flash_init();
  235. }
  236. ESP_ERROR_CHECK(err);
  237. char * urlPtr=malloc((strlen(bin_url)+1)*sizeof(char));
  238. strcpy(urlPtr,bin_url);
  239. // the first thing we need to do here is to erase the firmware url
  240. // to avoid a boot loop
  241. nvs_handle nvs;
  242. err = nvs_open(current_namespace, NVS_READWRITE, &nvs);
  243. if (err == ESP_OK) {
  244. err = nvs_erase_key(nvs, "fwurl");
  245. if (err == ESP_OK) {
  246. err = nvs_commit(nvs);
  247. if (err == ESP_OK) {
  248. ESP_LOGI(TAG, "Value with key '%s' erased", "fwurl");
  249. }
  250. }
  251. nvs_close(nvs);
  252. }
  253. ESP_LOGI(TAG, "Waiting for other processes to start");
  254. vTaskDelay(2500/ portTICK_RATE_MS);
  255. ESP_LOGI(TAG, "Starting ota: %s", urlPtr);
  256. ret=xTaskCreate(&ota_task, "ota_task", 1024*10,(void *) urlPtr, 4, NULL);
  257. if (ret != pdPASS) {
  258. ESP_LOGI(TAG, "create thread %s failed", "ota_task");
  259. }
  260. #else
  261. ESP_LOGW(TAG, "Rebooting to recovery to complete the installation");
  262. guided_factory();
  263. #endif
  264. }