用原生API app_update + bin加密的方式在Web中对ESP32进行OTA

前言

OTA 是 “Over-The-Air” 的缩写,指的是通过无线通信网络(如Wi-Fi、蜂窝网络等)对设备进行固件或软件的更新和升级。这种更新方式允许在设备部署后远程进行固件更新,而无需物理连接到计算机或其他设备上。OTA技术在物联网(IoT)领域中非常常见,因为它提供了一种方便、快捷且经济的方式来更新大量分布在不同地点的设备。
ESP-IDF 提供两种方法执行无线 (OTA) 升级:

使用组件提供的原生 API app_update;
使用组件提供的简化API esp_https_ota,提供作为客户端,通过HTTPS升级的功能。

官方例程native_ota_example和simple_ota_example分别演示了这两组API的使用。

  此博客不讲解OTA的原理,旨在使用原生API app_update + bin加密的方式在Web中对ESP32进行OTA演示,可应用在某些不能联外网的环境中,并可防止生产文件外泄。
  参考例程:native_ota_example、simple_ota_example、pre_encrypted_ota。

准备

  • Windows系统;
  • OpenSSL (百度网盘:https://pan.baidu.com/s/19m1WAQzWCcY4QCTlNZduWg?pwd=0q6f 提取码:0q6f);
  • 装了ESP-IDF的VsCode(ESP-IDF v5.2.1);
  • ESP32开发板(ESP32、ESP32-S2、ESP32-S3皆可);
  • 已经搭建好web server的工程(若不理解此工程,可访问此工程的教程)。

步骤

  • 打开工程进入menuconfig输入Partition Table,在Partition Table选择Factory app, two OTA definitions,此举目的在于选择内置的分区表,可以看到给app程序分配的都为1M,若是编译代码后生成的bin文件大于1M,或者flash size不满足如此分配,则需要自定义分区表;
    在这里插入图片描述
    在这里插入图片描述
  • (此步骤需要安装好OpenSSL)在工程的根目录下新建rsa_key文件夹,在电脑的开始菜单中输入并打开Win64 OpenSSL Command Prompt,进入到工程的根目录,并输入下面的命令,会自动生成一个RSA 私钥 private.pem,密钥的长度为 3072 位;
  • 注意:此私钥是唯一的,移植OTA功能时,请重新生成此私钥;
openssl genrsa -out rsa_key/private.pem 3072
  • 在工程的根目录下新建components文件夹,依照例程pre_encrypted_ota的README.md文件提示,将加密所需要的组件ESP Encrypted Image Abstraction Layer下载并解压到里面;
  • 在main目录下添加ota.c和ota.h文件,在CMakeLists.txt中添加ota.c为源代码文件,将private.pem添加为嵌入文本文件,并调用组件提供的函数create_esp_enc_img。如此编译时,组件会将外部提供的RSA私钥与自动生成的 AES-GCM 密钥和初始化向量(IV)结合使用,生成一个*_secure.bin的文件;
idf_build_get_property(project_dir PROJECT_DIR)
idf_component_register(SRCS "station_example_main.c" "web_server.c" "ota.c"
                    INCLUDE_DIRS "."
                    EMBED_FILES "web_client.html"
                    EMBED_TXTFILES ${
   project_dir}/rsa_key/private.pem
                    )

create_esp_enc_img(${
   CMAKE_BINARY_DIR}/${
   CMAKE_PROJECT_NAME}.bin
    ${
   project_dir}/rsa_key/private.pem ${
   CMAKE_BINARY_DIR}/${
   CMAKE_PROJECT_NAME}_secure.bin app)
  • 在ota.h文件添加内容:
#ifndef _OTA_H_
#define _OTA_H_
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "web_server.h"
#include "cJSON.h"

typedef enum
{
   
    TIMEOUT = -4,           //超时
    FORMAT_ERROR = -3,      //格式错误
    WRONG_VERSION = -2,     //错误的版本
    STATE_ERROR = -1,       //状态错误
    STOP = 0,               //停止
    READY=1,                //就绪
    UNDERWAY=2 ,            //进行中
    SUCCEED=3,              //成功  
}OTA_STATE_TYPE;

int ota_send_state(OTA_STATE_TYPE state);
void ota_version_init(void);
int ota_rece_data( DATA_PARCEL* buffer);
int ota_control(cJSON* json_msg , int socket);

#endif
  • 在ota.c添加内容:
#include "ota.h"
#include <string.h>
#include "esp_log.h"
#include "esp_encrypted_img.h"
#include "esp_app_format.h"
#include "esp_ota_ops.h"
#include "esp_wifi.h"
#include "driver/gpio.h"

typedef struct 
{
   
    char FileName[33];  //文件名字
    uint32_t FileSize;  //文件大小
    uint32_t need_rece_num ; //需要接收的次数
    uint32_t rece_num;  //已经接收的次数
    uint32_t rece_size; //已经接收大小
    float rece_progress ; //接收进度%
    OTA_STATE_TYPE state;
    int ws_client;//客户端
}OTA_FILE_TYPE;

extern const char rsa_private_pem_start[] asm("_binary_private_pem_start");
extern const char rsa_private_pem_end[]   asm("_binary_private_pem_end");

static QueueHandle_t  ota_rece_data_queue = NULL ;  //接收数据句柄
static TaskHandle_t ota_task_handle = NULL;         //ota任务句柄
static esp_ota_handle_t ota_handle = 0 ;            //ota句柄
static esp_decrypt_handle_t decrypt_handle= NULL ;  //解码句柄
static OTA_FILE_TYPE bin_msg;                       //bin文件信息

#define HASH_LEN 32 /* SHA256 摘要长度*/
static const char *TAG = "OTA_TASK";


/*诊断功能*/
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 << 4);
    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(4);

    gpio_reset_pin(4);
    return diagnostic_is_ok;
}

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);
}

/*删除ota任务*/
static void ota_delete_task(OTA_STATE_TYPE state)
{
   
    printf("ota_delete_task state:%d\r\n",state);
    esp_ota_abort(ota_handle);//结束OTA,释放句柄
    ota_handle = 0;
    ota_send_state(state);//将状态发送
    vQueueDelete(ota_rece_data_queue);//删除消息队列
    ota_rece_data_queue = NULL;
    esp_encrypted_img_decrypt_end(decrypt_handle);//Esp加密img解密结束
    decrypt_handle = NULL;
    vTaskDelete(ota_task_handle);//删除OTA任务
}

/*解密 根据pre_encrypted_ota例程的函数修改*/
static esp_err_t decrypt(DATA_PARCEL *args)
{
   
    esp_err_t err = ESP_FAIL;
    pre_enc_decrypt_arg_t pargs = {
   };
    pargs.data_in = (char *)args->data;//指向要解密的数据的指针
    pargs.data_in_len = args->len;//数据长度
    err = esp_encrypted_img_decrypt_data(decrypt_handle, &pargs);//开始解密,内部使用了malloc来存解析后的数据
    if (err != ESP_OK && err != ESP_ERR_NOT_FINISHED) {
   //操作尚未完全完成
        return err;
    
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值