赞
踩
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+以上,未发现异常,中途未出现过失败、中断。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。