ESP32学习笔记十六之空中升级Over The Air Updates(OTA)
OTA流程概述
在实际产品开发过程中,远程更新能够有效应对软件开发中出现的问题,并更精准地契合用户需求。
基于OTA更新机制,设备能够根据正常固件运行期间接收的数据进行更新。具体而言,该功能支持通过无线通信协议如Wi-Fi和蓝牙实现数据同步。
ESP32 Flash空间分区配置
目前使用的ESP-WROOM-32集成具有4MB SPICFlash存储器容量。在编译 esp32 程序时,通过执行 make menuconfig 并导航至 PartitionTable 文件,则可选择以下三种分区方案:一是选用无OTA功能的工厂程序文件;二是选用支持双OTA功能的工厂程序文件;三是自行定制分区设置的用户自定义文件。

该处仅涉及对配置文件中宏的调整,并非实质性的功能修改。
其对应的Flash分区配置源码路径位于:\esp-idf\components\partition_table
这些CSV文件则主要用于对Flash分区进行相应的参数设置。
无OTA分区:包含单个应用的分隔文件(如partitions_singleapp.csv)及其核心dump(如partitions_singleapp_core dump.csv)。 双OTA分区:支持多端同步更新的区域划分(如partitions_two_ota.csv)及其核心dump(如partitions_two_ota_core dump.csv)。
需至少配置博客模块管理两个‘OTASlot’分区(分别为ota_0和ota_1)以及数据分区来完成设备的分区表设置。
OTA 功能模块会将新的应用程序固件映像存储到空闲的 OTA 应用程序位置. 完成验证后,会重新配置 OTA 数据分区,以便将其应用在下一次开机.
OTA数据分区
OTA数据分区(type data, subtype ota) 必须嵌入到所有相关项目的分区表中,并参考官方文档以确保配置正确
ESP32的OTA升级的三种方式:
在三种升级情况下,必须通过串行端口完成第一个固件上传。
OTA 过程不具备强制的安全特性,在发布过程中需要严格限制开发者仅从合法或可信赖的来源获取更新信息。当更新完成时,系统将重启并执行最新的代码块以替代旧版本内容。开发者必须确保在运行该模块的应用程序通过安全机制关闭并重新加载新版本以避免潜在风险。
- OTA升级方式一-HTTP Server(针对产品应用)
通过连接到HTTP服务器并发起GET请求来升级固件;系统每次读取1KB的固件数据后进行写入操作。
该存储区域包含四个功能分区用于升级管理:包括用于主程序更新的OTA数据区、FactoryApp分区以及两个辅助更新区OTA_0和OTA_1。其中FactoryApp分区预装了初始固件版本。
在执行首次 OTA 升级操作时,OTA Demo 将目标固件加载至 OTA_0 分区,并在完成烧录后立即重写 OTA data 分区的数据以实现系统稳定运行;随后设备将重新启动。
当系统重启时会采集并分析OTA data分区的数据以完成计算过程。随后将被决定仅加载OTA_0分区的固件以执行更新流程而跳过常规Factory App分区的固件。从而确保设备能够顺利升级至最新版本。
类似地,在某次升级操作完成时,如果ESP32已经在运行中执行OTA_0分区内的固件升级,则后续再进行升级操作时,OTA Demo会将目标固件写入OTA_1分区。当系统再次启动时,在OTA_1分区实现固件升级。依此类推,在整个升级过程中,目标固件会在OTA_0和OTA_1两个分区之间交替烧录,并不会对出厂设置的Factory App固件产生任何影响。


- 先搭建一个HTTP服务器
为了更好地将hello_world例子复制到~/esp中,在hello_worldl(build)目录下生成相应的文件夹结构,并将其与hello-world.bin一并放入该目录中。
cp -r $IDF_PATH/examples/get-started/hello_world .
cd hello_world/build/
mkdir get
cd get/
cp ~/esp/hello_world/build/hello-world.bin .
cd ..
python -m SimpleHTTPServer 8070
AI写代码
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "nvs.h"
#include "nvs_flash.h"
//配置信息
#define EXAMPLE_WIFI_SSID "xxxxxxxx"
#define EXAMPLE_WIFI_PASS "xxxxxxxx"
#define EXAMPLE_SERVER_IP "192.168.43.67"
#define EXAMPLE_SERVER_PORT "8070"
#define EXAMPLE_FILENAME "/hello-world.bin"
//数据包长度
#define BUFFSIZE 1024
#define TEXT_BUFFSIZE 1024
static const char *TAG = "ota";
//OTA数据
static char ota_write_data[BUFFSIZE + 1] = { 0 };
//接收数据
static char text[BUFFSIZE + 1] = { 0 };
//镜像大小
static int binary_file_length = 0;
//socket句柄
static int socket_id = -1;
//wifi连接ok事件
static EventGroupHandle_t wifi_event_group;
const int CONNECTED_BIT = BIT0;
/*wifi状态机事件*/
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch (event->event_id) {
case SYSTEM_EVENT_STA_START://开始连接
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_GOT_IP://连上
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);//启动http
break;
case SYSTEM_EVENT_STA_DISCONNECTED://断开
//重连并重启http
ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
break;
default:
break;
}
return ESP_OK;
}
/*wifi初始化*/
static void initialise_wifi(void)
{
tcpip_adapter_init();
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_WIFI_SSID,
.password = EXAMPLE_WIFI_PASS,
},
};
ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
ESP_ERROR_CHECK( esp_wifi_start() );
}
/*read buffer by byte still delim ,return read bytes counts*/
static int read_until(char *buffer, char delim, int len)
{
int i = 0;
while (buffer[i] != delim && i < len) {
++i;
}
return i + 1;
}
/* resolve a packet from http socket
* return true if packet including \r\n\r\n that means http packet header finished,start to receive packet body
* otherwise return false
* */
static bool read_past_http_header(char text[], int total_len, esp_ota_handle_t update_handle)
{
/* i means current position */
int i = 0, i_read_len = 0;
while (text[i] != 0 && i < total_len) {
i_read_len = read_until(&text[i], '\n', total_len);
// if we resolve \r\n line,we think packet header is finished
if (i_read_len == 2) {
int i_write_len = total_len - (i + 2);
memset(ota_write_data, 0, BUFFSIZE);
/*copy first http packet body to write buffer*/
memcpy(ota_write_data, &(text[i + 2]), i_write_len);
esp_err_t err = esp_ota_write( update_handle, (const void *)ota_write_data, i_write_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%x", err);
return false;
} else {
ESP_LOGI(TAG, "esp_ota_write header OK");
binary_file_length += i_write_len;
}
return true;
}
i += i_read_len;
}
return false;
}
static bool connect_to_http_server()
{
ESP_LOGI(TAG, "Server IP: %s Server Port:%s", EXAMPLE_SERVER_IP, EXAMPLE_SERVER_PORT);
int http_connect_flag = -1;
struct sockaddr_in sock_info;
//新建socket
socket_id = socket(AF_INET, SOCK_STREAM, 0);
if (socket_id == -1) {
ESP_LOGE(TAG, "Create socket failed!");
return false;
}
//设置连接参数
memset(&sock_info, 0, sizeof(struct sockaddr_in));
sock_info.sin_family = AF_INET;
sock_info.sin_addr.s_addr = inet_addr(EXAMPLE_SERVER_IP);
sock_info.sin_port = htons(atoi(EXAMPLE_SERVER_PORT));
//连http服务器
http_connect_flag = connect(socket_id, (struct sockaddr *)&sock_info, sizeof(sock_info));
if (http_connect_flag == -1) {
ESP_LOGE(TAG, "Connect to server failed! errno=%d", errno);
close(socket_id);
return false;
} else {
ESP_LOGI(TAG, "Connected to server");
return true;
}
return false;
}
//异常处理,连接http服务器失败等异常
static void __attribute__((noreturn)) task_fatal_error()
{
ESP_LOGE(TAG, "Exiting task due to fatal error...");
close(socket_id);
(void)vTaskDelete(NULL);
while (1) {
;
}
}
//OTA任务
static void ota_example_task(void *pvParameter)
{
esp_err_t err;
/* update handle : set by esp_ota_begin(), must be freed via esp_ota_end() */
esp_ota_handle_t update_handle = 0 ;
const esp_partition_t *update_partition = NULL;
ESP_LOGI(TAG, "Starting OTA example...");
//获取当前boot位置
const esp_partition_t *configured = esp_ota_get_boot_partition();
//获取当前系统执行的固件所在的Flash分区
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);
//等待wifi连上后进行OTA,项目中可以使升级命令进入OTA
xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
false, true, portMAX_DELAY);
ESP_LOGI(TAG, "Connect to Wifi ! Start to Connect to Server....");
//连http服务器
if (connect_to_http_server()) {
ESP_LOGI(TAG, "Connected to http server");
} else {
ESP_LOGE(TAG, "Connect to http server failed!");
task_fatal_error();
}
//组http包发送
const char *GET_FORMAT =
"GET %s HTTP/1.0\r\n"
"Host: %s:%s\r\n"
"User-Agent: esp-idf/1.0 esp32\r\n\r\n";
char *http_request = NULL;
int get_len = asprintf(&http_request, GET_FORMAT, EXAMPLE_FILENAME, EXAMPLE_SERVER_IP, EXAMPLE_SERVER_PORT);
if (get_len < 0) {
ESP_LOGE(TAG, "Failed to allocate memory for GET request buffer");
task_fatal_error();
}
int res = send(socket_id, http_request, get_len, 0);
free(http_request);
if (res < 0) {
ESP_LOGE(TAG, "Send GET request to server failed");
task_fatal_error();
} else {
ESP_LOGI(TAG, "Send GET request to server succeeded");
}
//获取当前系统下一个(紧邻当前使用的OTA_X分区)可用于烧录升级固件的Flash分区
update_partition = esp_ota_get_next_update_partition(NULL);
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x",
update_partition->subtype, update_partition->address);
assert(update_partition != NULL);
//OTA写开始
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);
task_fatal_error();
}
ESP_LOGI(TAG, "esp_ota_begin succeeded");
bool resp_body_start = false, flag = true;
//接收完成
while (flag) {
memset(text, 0, TEXT_BUFFSIZE);
memset(ota_write_data, 0, BUFFSIZE);
//接收http包
int buff_len = recv(socket_id, text, TEXT_BUFFSIZE, 0);
if (buff_len < 0) { //包异常
ESP_LOGE(TAG, "Error: receive data error! errno=%d", errno);
task_fatal_error();
} else if (buff_len > 0 && !resp_body_start) { //包头
memcpy(ota_write_data, text, buff_len);
resp_body_start = read_past_http_header(text, buff_len, update_handle);
} else if (buff_len > 0 && resp_body_start) { //数据段包
memcpy(ota_write_data, text, buff_len);
//写flash
err = esp_ota_write( update_handle, (const void *)ota_write_data, buff_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%x", err);
task_fatal_error();
}
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, "Connection closed, all packets received");
close(socket_id);
} else {//未知错误
ESP_LOGE(TAG, "Unexpected recv result");
}
}
ESP_LOGI(TAG, "Total Write binary data length : %d", binary_file_length);
//OTA写结束
if (esp_ota_end(update_handle) != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_end failed!");
task_fatal_error();
}
//升级完成更新OTA data区数据,重启时根据OTA data区数据到Flash分区加载执行目标(新)固件
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);
task_fatal_error();
}
ESP_LOGI(TAG, "Prepare to restart system!");
esp_restart();
return ;
}
void app_main()
{
// Initialize NVS.
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
// OTA app partition table has a smaller NVS partition size than the non-OTA
// partition table. This size mismatch may cause NVS initialization to fail.
// If this happens, we erase NVS partition and initialize NVS again.
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK( err );
//wifi初始化
initialise_wifi();
//开启http任务,内部等wifi连上后进行http交互
xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
}
AI写代码
- OTA升级方式二-Arduino IDE方案固件更新
在固件开发阶段中,在满足以下条件的情况下:采用按顺序加载更高效的方法以更新较少的模块;这些方法仅当所更新的模块与同一网络环境中的Arduino IDE兼容时才有效。
参考实例:
#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
const char* ssid = "..........";
const char* password = "..........";
void setup() {
Serial.begin(115200);
Serial.println("Booting");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
// Port defaults to 3232
// ArduinoOTA.setPort(3232);
// Hostname defaults to esp3232-[MAC]
// ArduinoOTA.setHostname("myesp32");
// No authentication by default
// ArduinoOTA.setPassword("admin");
// Password can be set with it's md5 value as well
// MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
// ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
ArduinoOTA
.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
})
.onEnd([]() {
Serial.println("\nEnd");
})
.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
})
.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
void loop() {
ArduinoOTA.handle();
}
AI写代码

Web Browser方案固件更新
该方案使用场景:
直接从 Arduino IDE 加载是不方便或不可能的
用户无法从外部更新服务器公开 OTA 的模块
在设置更新服务器不可行时,将部署后的更新提供给少量模块
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
const char* host = "esp32";
const char* ssid = "xxx";
const char* password = "xxxx";
WebServer server(80);
/* * Login page
*/
const char* loginIndex =
"<form name='loginForm'>"
"<table width='20%' bgcolor='A09F9F' align='center'>"
"<tr>"
"<td colspan=2>"
"<center><font size=4><b>ESP32 Login Page</b></font></center>"
"<br>"
"</td>"
"<br>"
"<br>"
"</tr>"
"<td>Username:</td>"
"<td><input type='text' size=25 name='userid'><br></td>"
"</tr>"
"<br>"
"<br>"
"<tr>"
"<td>Password:</td>"
"<td><input type='Password' size=25 name='pwd'><br></td>"
"<br>"
"<br>"
"</tr>"
"<tr>"
"<td><input type='submit' οnclick='check(this.form)' value='Login'></td>"
"</tr>"
"</table>"
"</form>"
"<script>"
"function check(form)"
"{"
"if(form.userid.value=='admin' && form.pwd.value=='admin')"
"{"
"window.open('/serverIndex')"
"}"
"else"
"{"
" alert('Error Password or Username')/*displays error message*/"
"}"
"}"
"</script>";
/* * Server Index Page
*/
const char* serverIndex =
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
"<input type='file' name='update'>"
"<input type='submit' value='Update'>"
"</form>"
"<div id='prg'>progress: 0%</div>"
"<script>"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
" $.ajax({"
"url: '/update',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('progress: ' + Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!')"
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>";
/* * setup function
*/
void setup(void) {
Serial.begin(115200);
// Connect to WiFi network
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
/*use mdns for host name resolution*/
if (!MDNS.begin(host)) { //http://esp32.local
Serial.println("Error setting up MDNS responder!");
while (1) {
delay(1000);
}
}
Serial.println("mDNS responder started");
/*return index page which is stored in serverIndex */
server.on("/", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", loginIndex);
});
server.on("/serverIndex", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", serverIndex);
});
/*handling uploading firmware file */
server.on("/update", HTTP_POST, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
ESP.restart();
}, []() {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
Serial.printf("Update: %s\n", upload.filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
/* flashing firmware to ESP*/
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) { //true to set the size to the current progress
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
} else {
Update.printError(Serial);
}
}
});
server.begin();
}
void loop(void) {
server.handleClient();
delay(1);
}
AI写代码
HTTP服务器实现更新
ESPhttpUpdate 类能够检测系统更新并从 HTTP Web 服务器获取相应的二进制文件包。该类支持通过网络或 Internet 上的所有 IP 地址及域名访问更新包,并主要应用于远程服务器的批量升级操作。
/**
AWS S3 OTA Update
Date: 14th June 2017
Author: Arvind Ravulavaru <https://github.com/arvindr21>
Purpose: Perform an OTA update from a bin located in Amazon S3 (HTTP Only)
Upload:
Step 1 : Download the sample bin file from the examples folder
Step 2 : Upload it to your Amazon S3 account, in a bucket of your choice
Step 3 : Once uploaded, inside S3, select the bin file >> More (button on top of the file list) >> Make Public
Step 4 : You S3 URL => http://bucket-name.s3.ap-south-1.amazonaws.com/sketch-name.ino.bin
Step 5 : Build the above URL and fire it either in your browser or curl it `curl -I -v http://bucket-name.ap-south-1.amazonaws.com/sketch-name.ino.bin` to validate the same
Step 6: Plug in your SSID, Password, S3 Host and Bin file below
Build & upload
Step 1 : Menu > Sketch > Export Compiled Library. The bin file will be saved in the sketch folder (Menu > Sketch > Show Sketch folder)
Step 2 : Upload bin to S3 and continue the above process
// Check the bottom of this sketch for sample serial monitor log, during and after successful OTA Update
*/
#include <WiFi.h>
#include <Update.h>
WiFiClient client;
// Variables to validate
// response from S3
int contentLength = 0;
bool isValidContentType = false;
// Your SSID and PSWD that the chip needs
// to connect to
const char* SSID = "YOUR-SSID";
const char* PSWD = "YOUR-SSID-PSWD";
// S3 Bucket Config
String host = "bucket-name.s3.ap-south-1.amazonaws.com"; // Host => bucket-name.s3.region.amazonaws.com
int port = 80; // Non https. For HTTPS 443. As of today, HTTPS doesn't work.
String bin = "/sketch-name.ino.bin"; // bin file name with a slash in front.
// Utility to extract header value from headers
String getHeaderValue(String header, String headerName) {
return header.substring(strlen(headerName.c_str()));
}
// OTA Logic
void execOTA() {
Serial.println("Connecting to: " + String(host));
// Connect to S3
if (client.connect(host.c_str(), port)) {
// Connection Succeed.
// Fecthing the bin
Serial.println("Fetching Bin: " + String(bin));
// Get the contents of the bin file
client.print(String("GET ") + bin + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Cache-Control: no-cache\r\n" +
"Connection: close\r\n\r\n");
// Check what is being sent
// Serial.print(String("GET ") + bin + " HTTP/1.1\r\n" +
// "Host: " + host + "\r\n" +
// "Cache-Control: no-cache\r\n" +
// "Connection: close\r\n\r\n");
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
Serial.println("Client Timeout !");
client.stop();
return;
}
}
// Once the response is available,
// check stuff
/*
Response Structure
HTTP/1.1 200 OK
x-amz-id-2: NVKxnU1aIQMmpGKhSwpCBh8y2JPbak18QLIfE+OiUDOos+7UftZKjtCFqrwsGOZRN5Zee0jpTd0=
x-amz-request-id: 2D56B47560B764EC
Date: Wed, 14 Jun 2017 03:33:59 GMT
Last-Modified: Fri, 02 Jun 2017 14:50:11 GMT
ETag: "d2afebbaaebc38cd669ce36727152af9"
Accept-Ranges: bytes
Content-Type: application/octet-stream
Content-Length: 357280
Server: AmazonS3
{{BIN FILE CONTENTS}}
*/
while (client.available()) {
// read line till /n
String line = client.readStringUntil('\n');
// remove space, to check if the line is end of headers
line.trim();
// if the the line is empty,
// this is end of headers
// break the while and feed the
// remaining `client` to the
// Update.writeStream();
if (!line.length()) {
//headers ended
break; // and get the OTA started
}
// Check if the HTTP Response is 200
// else break and Exit Update
if (line.startsWith("HTTP/1.1")) {
if (line.indexOf("200") < 0) {
Serial.println("Got a non 200 status code from server. Exiting OTA Update.");
break;
}
}
// extract headers here
// Start with content length
if (line.startsWith("Content-Length: ")) {
contentLength = atoi((getHeaderValue(line, "Content-Length: ")).c_str());
Serial.println("Got " + String(contentLength) + " bytes from server");
}
// Next, the content type
if (line.startsWith("Content-Type: ")) {
String contentType = getHeaderValue(line, "Content-Type: ");
Serial.println("Got " + contentType + " payload.");
if (contentType == "application/octet-stream") {
isValidContentType = true;
}
}
}
} else {
// Connect to S3 failed
// May be try?
// Probably a choppy network?
Serial.println("Connection to " + String(host) + " failed. Please check your setup");
// retry??
// execOTA();
}
// Check what is the contentLength and if content type is `application/octet-stream`
Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType));
// check contentLength and content type
if (contentLength && isValidContentType) {
// Check if there is enough to OTA Update
bool canBegin = Update.begin(contentLength);
// If yes, begin
if (canBegin) {
Serial.println("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!");
// No activity would appear on the Serial monitor
// So be patient. This may take 2 - 5mins to complete
size_t written = Update.writeStream(client);
if (written == contentLength) {
Serial.println("Written : " + String(written) + " successfully");
} else {
Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?" );
// retry??
// execOTA();
}
if (Update.end()) {
Serial.println("OTA done!");
if (Update.isFinished()) {
Serial.println("Update successfully completed. Rebooting.");
ESP.restart();
} else {
Serial.println("Update not finished? Something went wrong!");
}
} else {
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
}
} else {
// not enough space to begin OTA
// Understand the partitions and
// space availability
Serial.println("Not enough space to begin OTA");
client.flush();
}
} else {
Serial.println("There was no content in the response");
client.flush();
}
}
void setup() {
//Begin Serial
Serial.begin(115200);
delay(10);
Serial.println("Connecting to " + String(SSID));
// Connect to provided SSID and PSWD
WiFi.begin(SSID, PSWD);
// Wait for connection to establish
while (WiFi.status() != WL_CONNECTED) {
Serial.print("."); // Keep the serial monitor lit!
delay(500);
}
// Connection Succeed
Serial.println("");
Serial.println("Connected to " + String(SSID));
// Execute OTA Update
execOTA();
}
void loop() {
// chill
}
/* * Serial Monitor log for this sketch
* * If the OTA succeeded, it would load the preference sketch, with a small modification. i.e.
* Print `OTA Update succeeded!! This is an example sketch : Preferences > StartCounter`
* And then keeps on restarting every 10 seconds, updating the preferences
* *
rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0x00
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0008,len:8
load:0x3fff0010,len:160
load:0x40078000,len:10632
load:0x40080000,len:252
entry 0x40080034
Connecting to SSID
......
Connected to SSID
Connecting to: bucket-name.s3.ap-south-1.amazonaws.com
Fetching Bin: /StartCounter.ino.bin
Got application/octet-stream payload.
Got 357280 bytes from server
contentLength : 357280, isValidContentType : 1
Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!
Written : 357280 successfully
OTA done!
Update successfully completed. Rebooting.
ets Jun 8 2016 00:22:57
rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0x00
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0008,len:8
load:0x3fff0010,len:160
load:0x40078000,len:10632
load:0x40080000,len:252
entry 0x40080034
OTA Update succeeded!! This is an example sketch : Preferences > StartCounter
Current counter value: 1
Restarting in 10 seconds...
E (102534) wifi: esp_wifi_stop 802 wifi is not init
ets Jun 8 2016 00:22:57
rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0x00
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0008,len:8
load:0x3fff0010,len:160
load:0x40078000,len:10632
load:0x40080000,len:252
entry 0x40080034
OTA Update succeeded!! This is an example sketch : Preferences > StartCounter
Current counter value: 2
Restarting in 10 seconds...
....
* */
AI写代码
