ESP8266/ESP32 SDK3 OTA升级

1、本示例适合于ESP8266和ESP32的OTA升级,使用官方的RTOS SDK3的框架编程,用户只要给出URL,代码自动解析出域名、IP、端口、文件路径等信息,然后通过HTTP请求下载固件。

2、本人测试固件放到阿里云对象存储OSS中,可以参考以下链接说明。或者自己搭建局域网服务器。

阿里云对象存储上传文件_dear_Wally的博客-CSDN博客

3、user_fota.c


#include "user_fota.h"

//===================================================================
//							变量定义
//===================================================================
static const char *TAG = "user_fota";
static bool ota_fail_flag;
static bool ota_runing_flag = false;



/********************************************************************
 *@brief	    复制src到新内存
 *@param[in]    src  
 *@return       返回字符串指针,指针不为NULL时,用完之后需要释放内存
 *******************************************************************/
char *user_copy_new_memory(const char *src)
{
    char *dec = NULL;

    int len = strlen(src);
    dec = (char *)malloc(len+1); 

    if(dec == NULL)
    {
        ESP_LOGE(TAG, "user_copy_new_memory malloc fail");
        return NULL;  
    }

    int i;
    for(i=0;i<len;i++)
    {
        dec[i] = src[i];
    }

    dec[i] = '\0';  

    return dec;
}


/********************************************************************
 *@brief	    获取url的字符串
 *@param[in]    url  完整的url
 *@param[in]    puri 调用http_parser_parse_url解析之后的值
 *@param[in]    uf   需要获取url字符串的类型
 *@return       返回字符串指针,指针不为NULL时,用完之后需要释放内存
 *******************************************************************/
char *user_url_parser_get_str(const char *url,const struct http_parser_url *puri,uint8_t uf)
{
    char *data = NULL;
    int len = puri->field_data[uf].len;         //获取字符长度
    int off = puri->field_data[uf].off;         //获取字符偏移

    if(len && (puri->field_set&(0x01<<uf)))     //字符有效并且长度不为0
    {
        data = (char *)malloc(len+1);           //申请内存,最后一个字节需要填入'\0',所有加1

        if(data != NULL)
        {
            int i;
            for(i=0;i<len;i++)
            {
                data[i] = url[off+i];           //复制字符串到返回内存空间
            }
            data[i] = '\0';                     //最后一个字节需要填入'\0'
        }
    }
    return data;
}


/********************************************************************
 *@brief	    获取OTA是否正在运行
 *@param[in]     
 *@return       true:正在OTA
 *******************************************************************/
bool fota_is_runing()
{
    return ota_runing_flag;
}

/*read buffer by byte still delim ,return read bytes counts*/
static int read_until(const char *buffer, char delim, int len)
{
    int i = 0;
    while (buffer[i] != delim && i < len) {
        ++i;
    }
    return i + 1;
}

bool _esp_ota_firm_parse_http(esp_ota_firm_t *ota_firm, const char *text, size_t total_len, size_t *parse_len)
{
    /* i means current position */
    int i = 0, i_read_len = 0;
    char *ptr = NULL, *ptr2 = NULL;
    char length_str[32];

    while (text[i] != 0 && i < total_len) {
        if (ota_firm->content_len == 0 && (ptr = (char *)strstr(text, "Content-Length")) != NULL) {
            ptr += 16;
            ptr2 = (char *)strstr(ptr, "\r\n");
            memset(length_str, 0, sizeof(length_str));
            memcpy(length_str, ptr, ptr2 - ptr);
            ota_firm->content_len = atoi(length_str);
            ota_firm->ota_size = ota_firm->content_len;
            ota_firm->ota_offset = 0;
            ESP_LOGI(TAG, "parse Content-Length:%d, ota_size %d", ota_firm->content_len, ota_firm->ota_size);
        }

        i_read_len = read_until(&text[i], '\n', total_len - i);

        if (i_read_len > total_len - i) {
            ESP_LOGE(TAG, "recv malformed http header");
            ota_fail_flag = true;
            return false;
        }

        // if resolve \r\n line, http header is finished
        if (i_read_len == 2) {
            if (ota_firm->content_len == 0) {
                ESP_LOGE(TAG, "did not parse Content-Length item");
                ota_fail_flag = true;
                return false;
            }

            *parse_len = i + 2;

            return true;
        }

        i += i_read_len;
    }

    return false;
}

static size_t esp_ota_firm_do_parse_msg(esp_ota_firm_t *ota_firm, const char *in_buf, size_t in_len)
{
    size_t tmp;
    size_t parsed_bytes = in_len; 

    switch (ota_firm->state) {
        case ESP_OTA_INIT:
            if (_esp_ota_firm_parse_http(ota_firm, in_buf, in_len, &tmp)) {
                ota_firm->state = ESP_OTA_PREPARE;
                ESP_LOGD(TAG, "Http parse %d bytes", tmp);
                parsed_bytes = tmp;
            }
            break;
        case ESP_OTA_PREPARE:
            ota_firm->read_bytes += in_len;

            if (ota_firm->read_bytes >= ota_firm->ota_offset) {
                ota_firm->buf = &in_buf[in_len - (ota_firm->read_bytes - ota_firm->ota_offset)];
                ota_firm->bytes = ota_firm->read_bytes - ota_firm->ota_offset;
                ota_firm->write_bytes += ota_firm->read_bytes - ota_firm->ota_offset;
                ota_firm->state = ESP_OTA_START;
                ESP_LOGD(TAG, "Receive %d bytes and start to update", ota_firm->read_bytes);
                ESP_LOGD(TAG, "Write %d total %d", ota_firm->bytes, ota_firm->write_bytes);
            }

            break;
        case ESP_OTA_START:
            if (ota_firm->write_bytes + in_len > ota_firm->ota_size) {
                ota_firm->bytes = ota_firm->ota_size - ota_firm->write_bytes;
                ota_firm->state = ESP_OTA_RECVED;
            } else
                ota_firm->bytes = in_len;

            ota_firm->buf = in_buf;

            ota_firm->write_bytes += ota_firm->bytes;

            ESP_LOGD(TAG, "Write %d total %d", ota_firm->bytes, ota_firm->write_bytes);

            break;
        case ESP_OTA_RECVED:
            parsed_bytes = 0;
            ota_firm->state = ESP_OTA_FINISH;
            break;
        default:
            parsed_bytes = 0;
            ESP_LOGD(TAG, "State is %d", ota_firm->state);
            break;
    }

    return parsed_bytes;
}

static void esp_ota_firm_parse_msg(esp_ota_firm_t *ota_firm, const char *in_buf, size_t in_len)
{
    size_t parse_bytes = 0;

    ESP_LOGD(TAG, "Input %d bytes", in_len);

    do {
        size_t bytes = esp_ota_firm_do_parse_msg(ota_firm, in_buf + parse_bytes, in_len - parse_bytes);
        ESP_LOGD(TAG, "Parse %d bytes", bytes);
        
        if(ota_fail_flag)
        {
            return;
        }
        
        if (bytes)
            parse_bytes += bytes;
    } while (parse_bytes != in_len);
}

static inline int esp_ota_firm_is_finished(esp_ota_firm_t *ota_firm)
{
    return (ota_firm->state == ESP_OTA_FINISH || ota_firm->state == ESP_OTA_RECVED);
}

static inline int esp_ota_firm_can_write(esp_ota_firm_t *ota_firm)
{
    return (ota_firm->state == ESP_OTA_START || ota_firm->state == ESP_OTA_RECVED);
}

static inline const char* esp_ota_firm_get_write_buf(esp_ota_firm_t *ota_firm)
{
    return ota_firm->buf;
}

static inline size_t esp_ota_firm_get_write_bytes(esp_ota_firm_t *ota_firm)
{
    return ota_firm->bytes;
}

static void esp_ota_firm_init(esp_ota_firm_t *ota_firm, const esp_partition_t *update_partition)
{
    memset(ota_firm, 0, sizeof(esp_ota_firm_t));
    ota_firm->state = ESP_OTA_INIT;
    ota_firm->ota_num = get_ota_partition_count();
    ota_firm->update_ota_num = update_partition->subtype - ESP_PARTITION_SUBTYPE_APP_OTA_0;

    ESP_LOGI(TAG, "Totoal OTA number %d update to %d part", ota_firm->ota_num, ota_firm->update_ota_num);

}



/********************************************************************
 *@brief	释放HTTP信息内存
 *@param[in]
 *@return   <0失败 
 *******************************************************************/
void user_free_http_info(user_http_info_t *user_http_info)
{
    if(user_http_info->url_buff != NULL)
    {
        free(user_http_info->url_buff);
        user_http_info->url_buff = NULL;
    }

    if(user_http_info->url_type_buff != NULL)
    {
        free(user_http_info->url_type_buff);
        user_http_info->url_type_buff = NULL;
    }

    if(user_http_info->url_host_buff != NULL)
    {
        free(user_http_info->url_host_buff);
        user_http_info->url_host_buff = NULL;
    }

    if(user_http_info->url_port_buff != NULL)
    {
        free(user_http_info->url_port_buff);
        user_http_info->url_port_buff = NULL;
    }

    if(user_http_info->url_path_buff != NULL)
    {
        free(user_http_info->url_path_buff);
        user_http_info->url_path_buff = NULL;
    }
}



/********************************************************************
 *@brief	连接到HTTP服务器
 *@param[in]
 *@return   <0失败 
 *******************************************************************/
int user_connect_http_server(const char *url,user_http_info_t *user_http_info)
{
    user_http_info->url_buff      = NULL;
    user_http_info->url_type_buff = NULL;
    user_http_info->url_host_buff = NULL;
    user_http_info->url_port_buff = NULL;
    user_http_info->url_path_buff = NULL;
    user_http_info->http_socket   = -1;

    //复制URL到新内存空间
    user_http_info->url_buff = user_copy_new_memory(url); 
    if(user_http_info->url_buff == NULL)
    {
        ESP_LOGE(TAG, "http url malloc fail");
        return -1;  
    }

    ESP_LOGD(TAG, "http url parser = %s", user_http_info->url_buff);

    struct http_parser_url puri;
    http_parser_url_init(&puri);

    int ret = http_parser_parse_url(user_http_info->url_buff, strlen(user_http_info->url_buff), 0, &puri);
    if (ret != 0) {
        ESP_LOGE(TAG, "http url parser fail: %d",ret);
        return -1;  
    }

    //获取协议类型
    user_http_info->url_type_buff = user_url_parser_get_str(user_http_info->url_buff,&puri,UF_SCHEMA);

    //获取主机域名
    user_http_info->url_host_buff = user_url_parser_get_str(user_http_info->url_buff,&puri,UF_HOST);
    if(user_http_info->url_host_buff == NULL)
    {
        ESP_LOGE(TAG, "http url host parser fail");
        return -1;  
    }       

    //获取主机端口
    user_http_info->url_port_buff = user_url_parser_get_str(user_http_info->url_buff,&puri,UF_PORT);
    if(user_http_info->url_port_buff == NULL)
    {
        //端口没有给出时默认为80
        user_http_info->url_port_buff = user_copy_new_memory("80");
        if(user_http_info->url_port_buff == NULL)
        {
            ESP_LOGE(TAG, "http port_buff user_copy_new_memory fail");
            return -1;  
        }
    } 

    //获取文件路径
    user_http_info->url_path_buff = user_url_parser_get_str(user_http_info->url_buff,&puri,UF_PATH);
    if(user_http_info->url_path_buff == NULL)
    {
        ESP_LOGE(TAG, "http url path parser fail");
        return -1;  
    } 

    const struct addrinfo hints = {
        .ai_family   = AF_INET,
        .ai_socktype = SOCK_STREAM,
    };

    struct  addrinfo *http_addrinfo = NULL;
    struct  in_addr  *ip_addr;

    //域名转换为IP
    ret = getaddrinfo(user_http_info->url_host_buff, user_http_info->url_port_buff, &hints, &http_addrinfo);
    if(ret != 0 || http_addrinfo == NULL) {
        ESP_LOGE(TAG, "http DNS failed ret=%d http_addrinfo=%p", ret, http_addrinfo);
        return -1;  
    }

    //获取IP地址
    ip_addr = &((struct sockaddr_in *)http_addrinfo->ai_addr)->sin_addr;
    ESP_LOGI(TAG, "http DNS succeeded. IP=%s", inet_ntoa(*ip_addr));

    //创建TCP socket
    user_http_info->http_socket = socket(http_addrinfo->ai_family, http_addrinfo->ai_socktype, 0);
    if(user_http_info->http_socket < 0) {
        ESP_LOGE(TAG, "http allocate socket failed.");
        return -1;  
    }
    ESP_LOGI(TAG, "http socket success");

    //通过TCP连接到服务器
    ret = connect(user_http_info->http_socket, http_addrinfo->ai_addr, http_addrinfo->ai_addrlen);

    freeaddrinfo(http_addrinfo);

    if(ret != 0)
    {
        close(user_http_info->http_socket);
        return -1;  
    }

    ESP_LOGI(TAG, "http connect success");

    return 0;
}


/********************************************************************
 *@brief	FOTA任务函数
 *@param[in]
 *@return
 *******************************************************************/
static void user_fota_task(void *pvParameters)
{

    ESP_LOGD(TAG, "starting fota. flash: %s", CONFIG_ESPTOOLPY_FLASHSIZE);
    ota_runing_flag = true;
    ota_fail_flag   = false;

    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%08x, but running from offset 0x%08x",
                 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%08x)",
             running->type, running->subtype, running->address);


    user_http_info_t user_http_info;
    int ret;
    
    //OTA0固件地址为0x10000,OTA1固件地址为0x110000
    if(running->address == 0x10000)
    {
        //升级版本为1.0.1的固件
        ret = user_connect_http_server(USER_FOTA_URL1,&user_http_info);
    }
    else
    {
        //升级版本为1.0.0的固件
        ret = user_connect_http_server(USER_FOTA_URL0,&user_http_info);
    }

    if(ret < 0)
    {
        goto fail1;
    }

    //GET请求
    const char *GET_FORMAT =
            "GET %s HTTP/1.0\r\n"
            "Host: %s:%s\r\n"
            "Accept: application/octet-stream\r\n"
            "Accept-Encoding: identity\r\n"
            "User-Agent: esp8266\r\n\r\n";

    char *http_request = NULL;
    int request_len = asprintf(&http_request, GET_FORMAT, user_http_info.url_path_buff, user_http_info.url_host_buff , user_http_info.url_port_buff);
    //是否分配内存失败
    if (request_len < 0) 
    {
        ESP_LOGE(TAG, "http request asprintf failed");
        goto fail2;
    }

    //打印请求内容
    printf("http_request:\r\n%s",http_request);


    char *http_recv_buff = NULL;
    char *http_text_buff = NULL;
    if((http_recv_buff = (char *)malloc(USER_FOTA_RECV_BUFFSIZE+1)) == NULL)
    {
        ESP_LOGE(TAG, "http http_recv_buff malloc fail");
        goto fail3; 
    }
    if((http_text_buff = (char *)malloc(USER_FOTA_TEXT_BUFFSIZE+1)) == NULL)
    {
        ESP_LOGE(TAG, "http http_text_buff malloc fail");
        goto fail3; 
    }


    //发送请求
    if(send(user_http_info.http_socket, http_request, request_len, 0) < 0)
    {
         ESP_LOGE(TAG, "http send failed"); 
         goto fail3;
    }

    ESP_LOGI(TAG, "http send success");


    //可以释放内存,后面没有使用该变量
    free(http_request);
    http_request = NULL;
    user_free_http_info(&user_http_info);


    //获取下一个更新分区
    update_partition = esp_ota_get_next_update_partition(NULL);
    ESP_LOGD(TAG, "writing to partition subtype %d at offset 0x%x",
             update_partition->subtype, update_partition->address);
    assert(update_partition != NULL);


    ESP_LOGD(TAG, "esp_ota_begin ........");

    //擦除OTA区域FLASH,这里等待时间较长
    err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "esp_ota_begin failed, error=%d", err);
        goto fail3;
    }
    ESP_LOGD(TAG, "esp_ota_begin succeeded");

    int binary_file_length = 0;
    bool flag = true;
    esp_ota_firm_t ota_firm;

    esp_ota_firm_init(&ota_firm, update_partition);


    while (flag) {
        memset(http_recv_buff, 0, USER_FOTA_RECV_BUFFSIZE);
        memset(http_text_buff, 0, USER_FOTA_TEXT_BUFFSIZE);

        int buff_len = recv(user_http_info.http_socket, http_recv_buff, USER_FOTA_RECV_BUFFSIZE, 0);

        //接收错误
        if (buff_len < 0) 
        {
            ESP_LOGE(TAG, "Error: receive data error! errno=%d", errno);
            goto fail3;
        } 
        //接收数据
        else if (buff_len > 0) 
        { 
            esp_ota_firm_parse_msg(&ota_firm, http_recv_buff, buff_len);
            if(ota_fail_flag)
            {
                goto fail3;
            }

            if (!esp_ota_firm_can_write(&ota_firm))
                continue;

            memcpy(http_text_buff, esp_ota_firm_get_write_buf(&ota_firm), esp_ota_firm_get_write_bytes(&ota_firm));
            buff_len = esp_ota_firm_get_write_bytes(&ota_firm);

            err = esp_ota_write( update_handle, (const void *)http_text_buff, buff_len);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%x", err);
                goto fail3;
            }
            binary_file_length += buff_len;
            ESP_LOGI(TAG, "Have written image length %d", binary_file_length);
        } 
        //接收完成
        else if (buff_len == 0) 
        {  
            flag = false;
            ESP_LOGI(TAG, "ota all packets received");
        } else {
            ESP_LOGE(TAG, "Unexpected recv result");
        }

        if (esp_ota_firm_is_finished(&ota_firm))
            break;
    }

    ESP_LOGI(TAG, "Total Write binary data length : %d", binary_file_length);

    //校验固件
    if (esp_ota_end(update_handle) != ESP_OK) {
        ESP_LOGE(TAG, "esp_ota_end failed!");
        goto fail3;
    }

    //设在启动分区
    err = esp_ota_set_boot_partition(update_partition);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "esp_ota_set_boot_partition failed! err=0x%x", err);
        goto fail3;
    }

    ESP_LOGI(TAG, "ota success restart system!");

    //关闭TCP连接
    close(user_http_info.http_socket);

    vTaskDelay(100/portTICK_RATE_MS);

    //重启设备
    esp_restart();


fail3:
    if(http_recv_buff != NULL)
    {
        free(http_recv_buff);
    }
    if(http_text_buff != NULL)
    {
        free(http_text_buff);
    }

fail2:
    if(http_request != NULL)
    {
        free(http_request);
    }

    close(user_http_info.http_socket);

fail1:
    user_free_http_info(&user_http_info);      


    ESP_LOGE(TAG, "ota failed!");

    ota_runing_flag = false;

    vTaskDelete(NULL);
}



/********************************************************************
 *@brief	FOTA初始化函数
 *@param[in]
 *@return
 *******************************************************************/
void user_fota_init()
{
    xTaskCreate(user_fota_task, "user_fota_task", 8192, NULL, 5, NULL);
}

4、user_fota.h

#ifndef _USER_FOTA_H_
#define _USER_FOTA_H_

#include <stdio.h> 
#include <string.h>  
#include "sdkconfig.h" 
#include "freertos/FreeRTOS.h" 
#include "freertos/task.h" 
#include "freertos/event_groups.h" 
#include "esp_system.h" 
#include "esp_spi_flash.h" 
#include "nvs_flash.h" 
#include "esp_log.h"  
#include "esp_netif.h" 
#include "esp_event.h" 
#include "esp_wifi.h"  
#include "lwip/sockets.h" 
#include "lwip/dns.h" 
#include "lwip/netdb.h"  
#include "esp_log.h"  

#include "nvs.h" 
#include "nvs_flash.h" 
#include "esp_ota_ops.h" 
#include "esp_http_client.h" 
#include "esp_https_ota.h" 
#include "esp_ota_ops.h" 
#include "esp_netif.h"




//===================================================================
//							常量定义
//===================================================================
//固件版本为1.0.0的URL
#define USER_FOTA_URL0              "http://xxxx.oss-cn-shenzhen.aliyuncs.com/esp8266/fota_v1.0.0.bin"

//固件版本为1.0.1的URL
#define USER_FOTA_URL1              "http://xxxx.oss-cn-shenzhen.aliyuncs.com/esp8266/fota_v1.0.1.bin"


#define USER_FOTA_RECV_BUFFSIZE     1024
#define USER_FOTA_TEXT_BUFFSIZE     1500





//===================================================================
//							定义结构体
//===================================================================
typedef enum esp_ota_firm_state {
    ESP_OTA_INIT = 0,
    ESP_OTA_PREPARE,
    ESP_OTA_START,
    ESP_OTA_RECVED,
    ESP_OTA_FINISH,
} esp_ota_firm_state_t;

typedef struct esp_ota_firm {
    uint8_t ota_num;
    uint8_t update_ota_num;

    esp_ota_firm_state_t state;

    size_t content_len;

    size_t read_bytes;
    size_t write_bytes;

    size_t ota_size;
    size_t ota_offset;

    const char *buf;
    size_t bytes;
} esp_ota_firm_t;


typedef struct {
    char *url_buff;            
    char *url_type_buff;        //解析后的url协议类型字符串缓存
    char *url_host_buff;        //解析后的url主机域名字符串缓存
    char *url_port_buff;        //解析后的url主机端口字符串缓存
    char *url_path_buff;        //解析后的url文件路径字符串缓存

    int  http_socket;           //当前连接http的套接字 
}user_http_info_t;



//===================================================================
//							函数声明
//===================================================================
void user_fota_init();
bool fota_is_runing();


#endif

5、使用说明

  更改头文件的URL为你服务器固件的URL,这里我们放两个固件fota_v1.0.0.bin和fota_v1.0.1.bin,执行OTA时会循环升级这两个固件

//固件版本为1.0.0的URL
#define USER_FOTA_URL0              "http://xxxx.oss-cn-shenzhen.aliyuncs.com/esp8266/fota_v1.0.0.bin"

//固件版本为1.0.1的URL
#define USER_FOTA_URL1              "http://xxxx.oss-cn-shenzhen.aliyuncs.com/esp8266/fota_v1.0.1.bin"

WIFI连接成功之后,执行以下代码开始OTA

//当前没有运行OTA,才能进行OTA
if(!fota_is_runing())
{
    user_fota_init();
}

6、本人写了个自动OTA demo,每次上电连接WIFI之后进行OTA,目前升级1000+以上,未发现异常,中途未出现过失败、中断。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dear_Wally

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值