ESP32(五) ESP32 OTA
注意事项
- 修改于
native_ota_example
,并删除HTTPS证书的验证 - 固件上传于 gitee, 不会跑路白嫖的服务器
- 使用双分区, 默认例子使用3分区,太浪费,其实双分区也可以
- 程序验证固件版本,升级的固件版本与运行的版本相同,不予以升级
- 自诊断以判断IO为依据
- 固件初始化结束,按下按键即可开始升级
分区
# 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);
}
License:
CC BY 4.0