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;ifield_data[uf].len; //获取字符长度
int off = puri->field_data[uf].off; //获取字符偏移
if(len && (puri->field_set&(0x01<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
#include
#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+以上,未发现异常,中途未出现过失败、中断。