avatar

松果工作室

欢迎光临

  • 首页
  • freeRTOS
  • ESP
  • 开发手册
  • 快速笔记
  • 个人收藏
  • 工具
Home ESP32(五) ESP32 OTA
文章

ESP32(五) ESP32 OTA

Posted 2025-01-14 Updated 2025-03- 18
By YCP
62~79 min read

注意事项

  1. 修改于native_ota_example,并删除HTTPS证书的验证
  2. 固件上传于 gitee, 不会跑路白嫖的服务器
  3. 使用双分区, 默认例子使用3分区,太浪费,其实双分区也可以
  4. 程序验证固件版本,升级的固件版本与运行的版本相同,不予以升级
  5. 自诊断以判断IO为依据
  6. 固件初始化结束,按下按键即可开始升级

分区

# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
# app offset must 64k 0x10000
nvs      ,data ,nvs     ,0x9000        ,0x4000,
otadata  ,data ,ota     ,0xd000        ,0x2000,
phy_init ,data ,phy     ,0xf000        ,0x1000,
ota_0    ,app  ,ota_0   ,0x10000       ,0xf0000,
ota_1    ,app  ,ota_1   ,0x100000      ,0xf0000,

CFG

# FLASH SIZE
CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
CONFIG_PARTITION_TABLE_TWO_OTA=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"

# SPEED
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y

CONFIG_EXAMPLE_WIFI_SSID="ChinaNet-2xueyQ"
CONFIG_EXAMPLE_WIFI_PASSWORD="88888888"

CONFIG_ESP_TLS_INSECURE=y
CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y

代码

#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_app_format.h"
#include "esp_http_client.h"
#include "esp_flash_partitions.h"
#include "esp_partition.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include "protocol_examples_common.h"
#include "errno.h"
#include "esp_wifi.h"

#define BUFFSIZE 1024
#define HASH_LEN 32
#define OTA_URL_SIZE 256

#define CONFIG_EXAMPLE_OTA_RECV_TIMEOUT                 5000
#define CONFIG_EXAMPLE_FIRMWARE_UPG_URL                 "https://gitee.com/ycp666/upload/raw/master/native_ota.bin"
#define CONFIG_EXAMPLE_GPIO_DIAGNOSTIC                  0   // 自诊断
#define CONFIG_EXAMPLE_SKIP_VERSION_CHECK               1   // 版本检查
#define CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN  0   // 网址劫持判断
#define CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK           0   // 是否检测证书
#define CONFIG_EXAMPLE_KEY_PIN                          9   // 按下按键开始更新

static const char *TAG = "native_ota_example";
static char ota_write_data[BUFFSIZE + 1] = {0};
#if CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
#endif
static void http_cleanup(esp_http_client_handle_t client) {
    esp_http_client_close(client);
    esp_http_client_cleanup(client);
}

static void __attribute__((noreturn)) task_fatal_error(void) {
    ESP_LOGE(TAG, "Exiting task due to fatal error...");
    (void) vTaskDelete(NULL);
    while (1) { ;
    }
}

static void print_sha256(const uint8_t *image_hash, const char *label) {
    char hash_print[HASH_LEN * 2 + 1];
    hash_print[HASH_LEN * 2] = 0;
    for (int i = 0; i < HASH_LEN; ++i) {
        sprintf(&hash_print[i * 2], "%02x", image_hash[i]);
    }
    ESP_LOGI(TAG, "%s: %s", label, hash_print);
}

static void infinite_loop(void) {
    int i = 0;
    ESP_LOGI(TAG, "When a new firmware is available on the server, press the reset button to download it");
    while (1) {
        ESP_LOGI(TAG, "Waiting for a new firmware ... %d", ++i);
        vTaskDelay(2000 / portTICK_PERIOD_MS);
    }
}

static void ota_example_task(void *pvParameter) {
    /*
     * [ycp]: 不明白有什么用,检测启动分区和当前分区是否相同。可能害怕执行了两次这个任务
     */
    esp_err_t err;
    esp_ota_handle_t update_handle = 0;
    const esp_partition_t *update_partition = NULL;
    const esp_partition_t *configured = esp_ota_get_boot_partition();
    const esp_partition_t *running = esp_ota_get_running_partition();
    if (configured != running) {
        ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08"PRIx32", but running from offset 0x%08"PRIx32,
                 configured->address, running->address);
        ESP_LOGW(TAG,
                 "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)");
    }
    ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08"PRIx32")",
             running->type, running->subtype, running->address);

    /*
     * [ycp]: 远程更新网址相关配置
     */
    esp_http_client_config_t config = {
            .url = CONFIG_EXAMPLE_FIRMWARE_UPG_URL,
#if CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
            .cert_pem = (char *) server_cert_pem_start,
#endif
            .timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT,
            .keep_alive_enable = true,
    };

#if CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
    char url_buf[OTA_URL_SIZE];
    if (strcmp(config.url, "FROM_STDIN") == 0) {
        example_configure_stdin_stdout();
        fgets(url_buf, OTA_URL_SIZE, stdin);
        int len = strlen(url_buf);
        url_buf[len - 1] = '\0';
        config.url = url_buf;
    } else {
        ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url");
        abort();
    }
#endif

#if CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
    config.skip_cert_common_name_check = true;
#endif

    /*
     * [ycp]: 初始化 HTTP 客户端
     */
    esp_http_client_handle_t client = esp_http_client_init(&config);
    if (client == NULL) {
        ESP_LOGE(TAG, "Failed to initialise HTTP connection");
        task_fatal_error();
    }
    err = esp_http_client_open(client, 0);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
        esp_http_client_cleanup(client);
        task_fatal_error();
    }
    esp_http_client_fetch_headers(client);
    update_partition = esp_ota_get_next_update_partition(NULL);
    assert(update_partition != NULL);
    ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%"PRIx32,
             update_partition->subtype, update_partition->address);

    int binary_file_length = 0;
    /*deal with all receive packet*/
    bool image_header_was_checked = false;
    while (1) {
        /*
         * [ycp]: HTTP_GET 1024 个字节
         */
        int data_read = esp_http_client_read(client, ota_write_data, BUFFSIZE);
        if (data_read < 0) {
            ESP_LOGE(TAG, "Error: SSL data read error");
            http_cleanup(client);
            task_fatal_error();
        } else if (data_read > 0) {
            /*
             * [ycp]: 获取 1000字节成功并且是第一次升级,如果初始的 1000字节能包含 img 头就能继续执行
             */
            if (image_header_was_checked == false) {
                esp_app_desc_t new_app_info;
                if (data_read >
                    sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
                    // check current version with downloading
                    memcpy(&new_app_info,
                           &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)],
                           sizeof(esp_app_desc_t));
                    ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version);

                    /*
                     * [ycp]: 获取当前分区信息
                     */
                    esp_app_desc_t running_app_info;
                    if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
                        ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
                    }

                    /*
                     * [ycp]: 获取上个无效的 app 的信息
                     */
                    const esp_partition_t *last_invalid_app = esp_ota_get_last_invalid_partition();
                    esp_app_desc_t invalid_app_info;
                    if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) {
                        ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version);
                    }

                    /*
                     * [ycp]: 判断当前下载的是否与上一个升级失败的 app 是否一样,如果一样就别干了
                     */
                    if (last_invalid_app != NULL) {
                        if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) {
                            ESP_LOGW(TAG, "New version is the same as invalid version.");
                            ESP_LOGW(TAG,
                                     "Previously, there was an attempt to launch the firmware with %s version, but it failed.",
                                     invalid_app_info.version);
                            ESP_LOGW(TAG, "The firmware has been rolled back to the previous version.");
                            http_cleanup(client);
                            infinite_loop();
                        }
                    }
#if CONFIG_EXAMPLE_SKIP_VERSION_CHECK
                    /*
                     * [ycp]: 如果要升级的版本和当前运行的版本一样也别干了
                     */
                    if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) {
                        ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
                        http_cleanup(client);
                        infinite_loop();
                    }
#endif

                    /*
                     * [ycp]: 检查完头部开始正式更新
                     */
                    image_header_was_checked = true;
                    err = esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle);
                    if (err != ESP_OK) {
                        ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
                        http_cleanup(client);
                        esp_ota_abort(update_handle);
                        task_fatal_error();
                    }
                    ESP_LOGI(TAG, "esp_ota_begin succeeded");
                } else {
                    ESP_LOGE(TAG, "received package is not fit len");
                    http_cleanup(client);
                    esp_ota_abort(update_handle);
                    task_fatal_error();
                }
            }
            /*
             * [ycp]: 继续写入
             */
            err = esp_ota_write(update_handle, (const void *) ota_write_data, data_read);
            if (err != ESP_OK) {
                http_cleanup(client);
                esp_ota_abort(update_handle);
                task_fatal_error();
            }
            binary_file_length += data_read;
            ESP_LOGD(TAG, "Written image length %d", binary_file_length);
        } else if (data_read == 0) {
            /*
             * [ycp]: 由于 esp_http_client_read 从不返回负的错误码,所以我们依靠 errno 来检查底层传输连接是否关闭(如果有
             */
            if (errno == ECONNRESET || errno == ENOTCONN) {
                ESP_LOGE(TAG, "Connection closed, errno = %d", errno);
                break;
            }
            /*
             * [ycp]: 传输完成 , 结束更新
             */
            if (esp_http_client_is_complete_data_received(client) == true) {
                ESP_LOGI(TAG, "Connection closed");
                break;
            }
        }
    }
    /*
     * [ycp]: 再次确认传输完成
     */
    ESP_LOGI(TAG, "Total Write binary data length: %d", binary_file_length);
    if (esp_http_client_is_complete_data_received(client) != true) {
        ESP_LOGE(TAG, "Error in receiving complete file");
        http_cleanup(client);
        esp_ota_abort(update_handle);
        task_fatal_error();
    }

    /*
     * [ycp]: 记得 esp_ota_end
     */
    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");
        } else {
            ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
        }
        http_cleanup(client);
        task_fatal_error();
    }

    /*
     * [ycp]: 设置启动分区
     */
    err = esp_ota_set_boot_partition(update_partition);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err));
        http_cleanup(client);
        task_fatal_error();
    }
    ESP_LOGI(TAG, "Prepare to restart system!");
    esp_restart();
}

static bool diagnostic(void) {
    gpio_config_t io_conf;
    io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.pin_bit_mask = (1ULL << CONFIG_EXAMPLE_GPIO_DIAGNOSTIC);
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
    gpio_config(&io_conf);

    ESP_LOGI(TAG, "Diagnostics (5 sec)...");
    vTaskDelay(5000 / portTICK_PERIOD_MS);

    bool diagnostic_is_ok = gpio_get_level(CONFIG_EXAMPLE_GPIO_DIAGNOSTIC);

    gpio_reset_pin(CONFIG_EXAMPLE_GPIO_DIAGNOSTIC);
    return diagnostic_is_ok;
}

void app_main(void) {
  #if 1 //或许能删了
    uint8_t sha_256[HASH_LEN] = {0};
    esp_partition_t partition;

    /*
     * [ycp]:
     * default csv addr 0x8000
     * default csc len  0xC00
     * get sha256 digest for the partition table
     */
    partition.address = ESP_PARTITION_TABLE_OFFSET;
    partition.size = ESP_PARTITION_TABLE_MAX_LEN;
    partition.type = ESP_PARTITION_TYPE_DATA;
    esp_partition_get_sha256(&partition, sha_256);
    print_sha256(sha_256, "SHA-256 for the partition table: ");

    /*
     * [ycp]:
     * default boot addr 0x1000
     * default csv len  0x8000
     * get sha256 digest for bootloader
     */
    partition.address = ESP_BOOTLOADER_OFFSET;
    partition.size = ESP_PARTITION_TABLE_OFFSET;
    partition.type = ESP_PARTITION_TYPE_APP;
    esp_partition_get_sha256(&partition, sha_256);
    print_sha256(sha_256, "SHA-256 for bootloader: ");

    /*
     * [ycp]:
     * get sha256 digest for ota_0 running
     */
    esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256);
    print_sha256(sha_256, "SHA-256 for current firmware: ");
#endif
    /*
     * [ycp]:
     * diagnostic
     */
    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) {
        if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
            bool diagnostic_is_ok = diagnostic();
            if (diagnostic_is_ok) {
                ESP_LOGI(TAG, "Diagnostics completed successfully! Continuing execution ...");
                esp_ota_mark_app_valid_cancel_rollback();
            } else {
                ESP_LOGE(TAG, "Diagnostics failed! Start rollback to the previous version ...");
                esp_ota_mark_app_invalid_rollback_and_reboot();
            }
        }
    }

    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    ESP_ERROR_CHECK(example_connect());
    esp_wifi_set_ps(WIFI_PS_NONE);

    gpio_config_t btn_config = {
            .pin_bit_mask = (1ULL << CONFIG_EXAMPLE_KEY_PIN),
            .mode = GPIO_MODE_INPUT,
            .pull_up_en = GPIO_PULLUP_ENABLE,
            .pull_down_en = GPIO_PULLDOWN_DISABLE,
            .intr_type = GPIO_INTR_DISABLE,
    };
    gpio_config(&btn_config);
    while (1) {
        if (gpio_get_level(CONFIG_EXAMPLE_KEY_PIN) == 0) {
            break;
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
}

ESP
License:  CC BY 4.0
Share

Further Reading

OLDER

其他笔记

NEWER

多级菜单

Recently Updated

  • ESP32(八) 简单的webserver
  • ESP32(七) NVS
  • ESP32(四) STA & AP
  • 多级菜单
  • ESP32(五) ESP32 OTA

Trending Tags

WCH Linux Elec freeRTOS STM ESP Flutter Others SwiftUI

Contents

©2025 松果工作室. Some rights reserved.

Using the Halo theme Chirpy