日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ESP32 HttpServer模式下 本地OTA 例程(基于ESP-IDF类似Arduino下OTAWebUpdater例程)

發布時間:2023/12/3 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ESP32 HttpServer模式下 本地OTA 例程(基于ESP-IDF类似Arduino下OTAWebUpdater例程) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

由于項目需要ESP32連接app進行OTA,為了支持AP模式下與STA模式下的本地局域網OTA功能(不需要OTA服務器)。
咨詢樂鑫技術支持,ESP-IDF下沒有該模式的官方例程。網上也一直沒有找到相關例程,翻出來手冊看了看倒也不難。基于esp-idf\examples\system\ota\native_ota_example與esp-idf\examples\http_server\file_serving兩個例程,整理出來了這個demo分享并記錄一下。

demo包含:

  • wifi連接初始化(包括AP模式和STA模式)
  • OTA服務器(端口89):包含固件上傳頁面URI、POST文件接收URI、當前固件信息查詢URI
  • 固件上傳html:為原生js實現,post文件上傳,上傳進度及速度顯示,錯誤顯示
  • 固件診斷程序:通過將GPIO2拉高判斷固件是否運行成功,若失敗則回滾固件
  • BuildVer.sh:編譯并根據編譯時間生成版本號文件小工具
  • 工程下載

    ESP32 HttpServer模式下 本地OTA 例程(基于ESP-IDF類似Arduino下OTAWebUpdater例程)
    https://download.csdn.net/download/l851285812/18808145

    分區表

    分區表相關配置自行度娘,為節省flash空間該demo未使用factory工廠分區,使用sta_0分區作為默認分區

    NameTypeSubTypeOffsetSizeFlags
    nvsdatanvs0x900016k
    otadatadataota0xd0008k
    ota_0appota_01000k
    ota_1appota_11000k

    部分代碼如下

    1. OTA服務器初始化

    首先初始化三個URI并加載到89端口上啟動服務器,另以防意外將最大連接客戶端數量設置為1(config.max_open_sockets = 1)

    void HttpOTA_server_init() {httpd_config_t config = HTTPD_DEFAULT_CONFIG();config.max_open_sockets = 1;config.stack_size = 8192;config.server_port = 89;config.ctrl_port = 32779;ESP_LOGI(TAG, "Starting OTA server on port: '%d'", config.server_port);if (httpd_start(&HttpOTA_httpd, &config) == ESP_OK){httpd_uri_t HttpOTA_uri = { //OTA頁面.uri = "/HttpOTAWeb",.method = HTTP_GET,.handler = HttpOTA_handler,.user_ctx = NULL};httpd_register_uri_handler(HttpOTA_httpd, &HttpOTA_uri);httpd_uri_t Now_uri = { //當前固件信息.uri = "/Now",.method = HTTP_GET,.handler = Now_handler,.user_ctx = NULL};httpd_register_uri_handler(HttpOTA_httpd, &Now_uri);/* URI處理程序,用于將文件上傳到服務器*/httpd_uri_t file_upload = {.uri = "/upload",.method = HTTP_POST,.handler = upload_post_handler,.user_ctx = NULL };httpd_register_uri_handler(HttpOTA_httpd, &file_upload);} }
    2. 當前固件信息(以json字符串發送)
    //當前固件信息 static esp_err_t Now_handler(httpd_req_t *req) {httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); //跨域傳輸協議static char json_response[1024];esp_app_desc_t running_app_info;const esp_partition_t *running = esp_ota_get_running_partition();esp_ota_get_partition_description(running, &running_app_info);char * p = json_response;*p++ = '{';p+=sprintf(p, "\"OTAsubtype\":%d,", running->subtype - ESP_PARTITION_SUBTYPE_APP_OTA_MIN); //OTA分區p+=sprintf(p, "\"address\":%d,", running->address); //地址p+=sprintf(p, "\"version\":\"%s\",", running_app_info.version); //版本號p+=sprintf(p, "\"date\":\"%s\",", running_app_info.date); //日期p+=sprintf(p, "\"time\":\"%s\"", running_app_info.time); //時間*p++ = '}';*p++ = 0;httpd_resp_set_type(req, "application/json"); // 設置http響應類型return httpd_resp_send(req, json_response, strlen(json_response)); //發送一個完整的HTTP響應。內容在json_response中 }
    3. 固件上傳頁面

    使用Esp32HttpWeb_OTA\main\html\www\compress_pages.sh工具壓縮為.gz網頁燒寫。
    注意: html中的中文會被壓縮成亂碼。
    注意: 需要在CMakeLfists.txt編譯配置文件下注明該網頁:

    #嵌入二進制文件,該文件不會被格式化為c源文件,當前為壓縮網頁文件 set(COMPONENT_EMBED_FILES"html/www/HttpOTA.html.gz")

    固件上傳頁面URI:

    //OTA 頁面 static esp_err_t HttpOTA_handler(httpd_req_t *req) {httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); //跨域傳輸協議extern const unsigned char HttpOTA_html_gz_start[] asm("_binary_HttpOTA_html_gz_start");extern const unsigned char HttpOTA_html_gz_end[] asm("_binary_HttpOTA_html_gz_end");size_t HttpOTA_html_gz_len = HttpOTA_html_gz_end - HttpOTA_html_gz_start;httpd_resp_set_type(req, "text/html");httpd_resp_set_hdr(req, "Content-Encoding", "gzip");return httpd_resp_send(req, (const char *)HttpOTA_html_gz_start, HttpOTA_html_gz_len); }
    4. 固件接收URI

    循環每次接收1k數據到緩沖區。當接收到完整固件頭部后( 接收包大于 sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t) )
    則判斷固件是否合法(該固件僅判斷了版本號是否為包含"ESP_"),可自行添加判斷是否與正在運行固件版本號相同等,esp-idf\examples\system\ota\native_ota_example例程中包含相關代碼。

    /* 單個文件的最大大小*/ #define MAX_FILE_SIZE (1000*1024) // 1000 KB #define MAX_FILE_SIZE_STR "1000KB" /* 暫存緩沖區大小*/ #define SCRATCH_BUFSIZE 1024 /*將文件上傳到服務器的處理程序*/ uint8_t Upload_Timeout_num; static esp_err_t upload_post_handler(httpd_req_t *req) {httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); //跨域傳輸協議esp_err_t err;esp_ota_handle_t update_handle = 0 ;const esp_partition_t *update_partition = NULL;char SendStr[100];Upload_Timeout_num = 0;/* 文件不能大于限制*/if (req->content_len > MAX_FILE_SIZE) {ESP_LOGE(TAG, "文件過大 : %d bytes", req->content_len);/* 回應400錯誤請求 */httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,"File size must be less than"MAX_FILE_SIZE_STR "!");/* 返回失敗以關閉基礎連接,否則傳入的文件內容將使套接字繁忙 */return ESP_FAIL;}/*請求的內容長度給出了要上傳的文件的大小*/int remaining = req->content_len;int received,L_remaining = remaining;bool image_header_was_checked = false; //固件頭檢查標識char *OTA_buf = malloc(sizeof(char) * SCRATCH_BUFSIZE);while (remaining > 0) {// char str[6];// sprintf(str,"%.2f",(float)(L_remaining-remaining)/L_remaining*100);// ESP_LOGI(TAG, "剩余尺寸 : %d---%s%%", remaining,str);/* 將文件部分接收到緩沖區中 */if ((received = httpd_req_recv(req, OTA_buf, MIN(remaining, SCRATCH_BUFSIZE))) <= 0) {if (received == HTTPD_SOCK_ERR_TIMEOUT) {Upload_Timeout_num++;ESP_LOGE(TAG, "接收文件超時 %d", Upload_Timeout_num);/* 如果發生超時,請重試 */if (Upload_Timeout_num >= 3){Upload_Timeout_num = 0;ESP_LOGE(TAG, "超時過多!");httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "File receiving timeout!");return ESP_FAIL;}continue;}/* 如果出現無法恢復的錯誤,請關閉并刪除未完成的文件*/free(OTA_buf);ESP_LOGE(TAG, "文件接收失敗!");/* 響應500內部服務器錯誤 */httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to receive file!");if(update_handle) esp_ota_end(update_handle); //若已begin OTA則停止OTAreturn ESP_FAIL;}/*固件頭校驗*///接收到固件頭if (image_header_was_checked == false) {esp_app_desc_t new_app_info; //存儲新固件頭if (received > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {esp_app_desc_t running_app_info;const esp_partition_t *running = esp_ota_get_running_partition();if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK){ESP_LOGI(TAG, "當前運行固件版本: %s", running_app_info.version);ESP_LOGI(TAG, "當前運行固件編譯時間: %s,%s", running_app_info.date, running_app_info.time);}// 通過下載檢查新固件版本memcpy(&new_app_info, &OTA_buf[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));if (strstr(new_app_info.version, "ESP_") == NULL) //版本錯誤{ESP_LOGE(TAG, "新固件頭錯誤");httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Firmware header error!");return ESP_FAIL;}ESP_LOGI(TAG, "新固件版本: %s", new_app_info.version);ESP_LOGI(TAG, "新固件編譯時間: %s, %s", new_app_info.date, new_app_info.time);

    固件頭校驗完成后,配置新固件燒寫目標分區并調用esp_ota_begin( )開始OTA,注意該步驟將擦除目標OTA分區。
    注意: ESP32 OTA必須由esp_ota_begin( )開始并由esp_ota_end( )結束。

    //返回下一個應使用新固件寫入的OTA應用程序分區 #if 1//esp_ota_get_next_update_partition 自動選擇下一個可用ota分區update_partition = esp_ota_get_next_update_partition(NULL);ESP_LOGI(TAG, "寫入分區子類型 %#X 偏移 %#x", update_partition->subtype, update_partition->address);if (update_partition == NULL){httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "OTA Partition error!");return ESP_FAIL;} #else//手動選擇ota分區if (running->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_0){update_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL);}else{update_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL);}ESP_LOGI(TAG, "寫入分區子類型 %#X 偏移 %#x", update_partition->subtype, update_partition->address);if (update_partition == NULL){httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "OTA Partition error!");return ESP_FAIL;} #endifsprintf(SendStr, "To: OTA%d Ver: %s Time: %s, %s", update_partition->subtype - ESP_PARTITION_SUBTYPE_APP_OTA_MIN, new_app_info.version, new_app_info.date, new_app_info.time);//開始OTA OTA_SIZE_UNKNOWN將擦除整個分區err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);if (err != ESP_OK) {char str[25];sprintf(str, "esp_ota_begin failed (%s)", esp_err_to_name(err));ESP_LOGE(TAG, "%s", str);httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, str);return ESP_FAIL;}ESP_LOGI(TAG, "esp_ota_begin succeeded");image_header_was_checked = true; //固件頭驗證完成 可自行添加版本比對}else{httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "received package is not fit len!");return ESP_FAIL;}}

    循環將每次接收到的數據寫入目標OTA分區 esp_ota_write( );

    /*將固件分塊寫入OTA分區*/err = esp_ota_write(update_handle, (const void *)OTA_buf, received);if (err != ESP_OK) {char str[25];sprintf(str, "esp_ota_write failed (%s)", esp_err_to_name(err));ESP_LOGE(TAG, "%s", str);httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, str);return ESP_FAIL;}/*跟蹤剩余要上傳的文件的剩余大小*/remaining -= received;}free(OTA_buf);ESP_LOGI(TAG, "文件接收完成: %dByte",L_remaining);

    整個固件接收完成后調用esp_ota_end( )結束OTA并自動校驗固件完整性。

    err = esp_ota_end(update_handle);if (err != ESP_OK) {if (err == ESP_ERR_OTA_VALIDATE_FAILED) {ESP_LOGE(TAG, "Image validation failed, image is corrupted");}char str[25];sprintf(str, "esp_ota_end failed (%s)", esp_err_to_name(err));ESP_LOGE(TAG, "%s", str);httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, str);return ESP_FAIL;}ESP_LOGI(TAG, "固件校驗成功!");

    最后調用esp_ota_set_boot_partition( )將otadata配置為新OTA分區(下次上電啟動分區)
    并軟件復位 esp_restart();
    注意: httpd_resp_sendstr后若無延時,客戶端網頁將接受不到成功回復。

    err = esp_ota_set_boot_partition(update_partition);if (err != ESP_OK) {char str[50];sprintf(str, "esp_ota_set_boot_partition failed (%s)", esp_err_to_name(err));ESP_LOGE(TAG, "%s", str);httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, str);return ESP_FAIL;}// httpd_resp_sendstr(req, "OTA successfully");httpd_resp_sendstr(req,SendStr);vTaskDelay(500 / portTICK_PERIOD_MS); //延時等待消息發送ESP_LOGI(TAG, "準備重啟系統!");esp_restart();return ESP_OK; }

    固件上傳網頁

    總結

    以上是生活随笔為你收集整理的ESP32 HttpServer模式下 本地OTA 例程(基于ESP-IDF类似Arduino下OTAWebUpdater例程)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。