最近在使用ESP8266自己DIY智能家居应用,作为一个智能小家电,在线更新固件是必不可少的,本文介绍一下本人的OTA方案,供大家参考。
一、方案选择
参考ESP8266_RTOS_SDK中给出的示例,就可以轻松搭建出本地服务器以供OTA升级,但是这个方案有一个局限性,就是ESP8266只能在内网更新,一旦离开将无法使用。所以上云使用是必须的,这样可以使用手机微信小程序远程控制,也可以搭配小米音箱或者HomeKit的生态组建智能交互环境。上云的话,乐鑫官方有提供ESPRESSIF Cloud服务,具体可以参考https://iot.espressif.cn,我这里本着折腾的原则٩(ˊᗜˋ*)و,选择用自己的腾讯云搭建一个OTA服务器,下面是我的折腾历程,帮大家排一下雷。。。
二、OTA服务搭建
由于我的博客是使用hexo提供Web服务的,所以想要直接下载文件很简单,在hexo网站项目的根目录下的source文件夹中创建download文件夹,你还可以在download下创建其他子文件夹,用于分类管理,然后将bin档上传到这个文件夹中就可以了。这样,你的下载url就是https://域名/download/子文件夹/bin档文件名。
然后是HTTPS证书的认证问题,这里需要注意的是国内的服务器申请域名是需要备案的,由于我的域名是在腾讯云购买的,所以相应的DNS解析也是由腾讯云提供,HTTPS证书也是由腾讯云提供。在腾讯云后台找到相应的地方,下载申请好的HTTPS证书,下载下来是个zip包,解压后发现腾讯云已经提供好各种Web服务对应的证书文件了,这里我们需要的是Apache文件夹中的1_root_bundle.crt文件,注意不是所有crt证书都可以转换为ESP8266可用的pem证书的,因为有些crt证书是已经组合过的证书了。linux环境下在命令行中输入如下指令将crt转换为pem:
openssl x509 -in 1_root_bundle.crt -out ca_cert.pem
在ESP8266项目根目录下创建server_certs文件夹,将生成的ca_cert.pem文件复制到这个文件夹中,至此,OTA服务的搭建就已经完成了。
三、固件编写
首先是menuconfig的配置,参考SDK中相关示例的README,需要修改Partition Table项,将Single factory app, no OTA修改为Factory app, two OTA definitions或者Custom partition table CSV,我这里直接修改为Factory app, two OTA definitions,因为本身代码不是特别复杂,所以直接用官方设定就可以了。如果要自定义分区表,请参考SDK中./docs/en/api-guides/partition-tables.rst的说明创建。
其次是修改component.mk文件,将HTTPS证书路径添加到编译过程中,在component.mk文件中添加下面这条语句:
COMPONENT_EMBED_TXTFILES := ${PROJECT_PATH}/server_certs/ca_cert.pem
然后是代码编写,参考SDK中给出的示例,只需要100行不到的代码,就可以实现HTTPS的OTA升级了,下面贴一下我基于官方SDK少量修改后的代码,给大家参考一下。官方SDK都是自解释命名方式,所以我只做少量注释。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "protocol_examples_common.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "mqtt_tcp.h"
static const char *TAG = "OTA";
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");
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
switch (evt->event_id)
{
case HTTP_EVENT_ERROR:
ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
break;
case HTTP_EVENT_ON_DATA:
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
break;
}
return ESP_OK;
}
void ota_task(void *pvParameter)
{
ESP_LOGI(TAG, "ota_task start\n");
esp_http_client_config_t config = {
.url = "https://godenx.club/download/update.bin", //这里我直接修改为我自己的bin文件下载链接
.cert_pem = (char *)server_cert_pem_start,
.event_handler = _http_event_handler,
.timeout_ms = 20000, //修改这里是因为网络不太稳定,延长超时时间提高OTA升级成功率
};
esp_err_t ret = esp_https_ota(&config);
if (ret == ESP_OK)
{
mqtt_return_msg(success); //我的项目是使用mqtt通讯的,所以这里有一个mqtt的通讯函数,用于汇报OTA升级成功
ESP_LOGE(TAG, "Firmware Upgrades Success!");
esp_restart();
}
else
{
mqtt_return_msg(fail); //通过mqtt汇报OTA升级失败
ESP_LOGE(TAG, "Firmware Upgrades Failed!");
}
vTaskDelete(NULL);
}
void ota_start()
{
xTaskCreate(&ota_task, "ota_task", 8192, NULL, 5, NULL);
}
这段示例代码已经实现了一个简单的OTA升级了,只需要适当修改部分地方,就可以直接拿来使用。需要OTA升级时,直接调用ota_start()
就可以了。当你的程序全部写好后,使用make ota
指令来生成OTA的bin档,bin档名称是以你的项目名开头,ota.bin结尾的文件,可以修改bin档名称,只需要对应修改下载链接就可以了,然后将bin档上传到服务器。可以给更新固件加入一些显示版本号的函数,用于区分是否升级成功。
四、DEBUG调试
我的项目使用的时mqtt通讯,所以我通过mqtt发送升级命令后,就可以在串口看到程序运行ota_task了,当时经常会遇到如下的问题,导致OTA升级失败。
E (531279) esp-tls: read error :-76
E (531279) TRANS_SSL: esp_tls_conn_read error, errno=No more processes
经过一番搜索,发现问题原因就是网络质量差,需要增大超时时间,具体方法就是在HTTP客户端配置中添加timeout_ms这个配置,经过一番测试,20000这个数值可以保证在我这边的网络下每次升级成功。
esp_http_client_config_t config = {
.timeout_ms = 20000,
};
Enjoy it!