mirror of
https://github.com/espressif/ESP8266_RTOS_SDK.git
synced 2025-07-02 23:10:08 +08:00
feat(examples): spilt ota example into sub-examples to adapt to various scenarios
This commit is contained in:
@ -0,0 +1,153 @@
|
||||
|
||||
# Important
|
||||
|
||||
The example only services the ESP8285 or modules integrated the flash which size is `1MB`.
|
||||
|
||||
---
|
||||
|
||||
# Simple OTA Demo
|
||||
|
||||
This example demonstrates a working OTA (over the air) firmware update workflow.
|
||||
|
||||
This example is a *simplified demonstration*, for production firmware updates you should use a secure protocol such as HTTPS.
|
||||
|
||||
---
|
||||
|
||||
# Aim
|
||||
|
||||
An app running on ESP8266 can upgrade itself by downloading a new app "image" binary file, and storing it in flash.
|
||||
|
||||
In this example, the ESP8266 has 2 images in flash: OTA_0, OTA_1. Each of these is a self-contained partition. The number of OTA image partition is determined by the partition table layout.
|
||||
|
||||
Flash the example through serial port with command "make flash" to update the OTA_0 app image. In first boot, the bootloader loads this OTA_0 app image which then will execute an OTA update (triggered in the example code). The OTA update will download a new image from an http server and save it into the OTA_1 partition. After that, the example code will update the ota_data partition to indicate the new app partition, and then reboot, which leads to the second boot. During the second boot, the bootloader will read the ota_data, and select to run the new OTA image.
|
||||
|
||||
# Custom partition configuration
|
||||
|
||||
If customers want to use their own partition tables with specific partition location. Please see following steps:
|
||||
|
||||
## Step 1: Create partition file
|
||||
|
||||
Create a partition managment file with "cvs" formate, please refer to "doc/en/api-guides/partition-tables.rst"
|
||||
|
||||
## Step 2: Select custom partition mode
|
||||
|
||||
1. Select custom partition tables at "menuconfig":
|
||||
|
||||
```
|
||||
Partition Table --->
|
||||
Partition Table (XXXXXX) --->
|
||||
(X) Custom partition table CSV
|
||||
```
|
||||
|
||||
2. Configurate custom partition location at:
|
||||
|
||||
```
|
||||
(XXXXXX)Custom partition CSV file
|
||||
```
|
||||
|
||||
Note: System will add the absolute path of the project to the head of the "Custom partition CSV file" automatically when compling.
|
||||
|
||||
3. Configurate patition table location if necessary:
|
||||
|
||||
```
|
||||
(XXXXXX)Partition table offset address at flash
|
||||
```
|
||||
|
||||
**make ota flash** will only download the app1 at **OTA_0 partition offset**.
|
||||
|
||||
# Workflow
|
||||
|
||||
The OTA_workflow.png diagram demonstrates the overall workflow:
|
||||
|
||||

|
||||
|
||||
## Step 1: Connect to AP
|
||||
|
||||
Connect your host PC to the same AP that you will use for the ESP8266.
|
||||
|
||||
## Step 2: Build OTA Example
|
||||
|
||||
To make the OTA example easy to understand and use, here the example will show `self-update-self`.
|
||||
|
||||
Jump into the OTA example directory, and type `make menuconfig` to configure the OTA example. Under the "Example Configuration" submenu, fill in the following details:
|
||||
|
||||
* WiFi SSID & Password
|
||||
* IP address of your host PC as "HTTP Server"
|
||||
* HTTP Port number (if using the Python HTTP server above, the default is correct)
|
||||
|
||||
If serving the "OTA" example, you can leave the default filename as-is.
|
||||
|
||||
Configurate the flash size:
|
||||
|
||||
```
|
||||
Serial flasher config --->
|
||||
Flash size (2 MB) --->
|
||||
(X) 1 MB
|
||||
```
|
||||
|
||||
Save your changes, and type `make ota` to build the example to generate the real OTA binary firmware.
|
||||
|
||||
The name of final generated binary firmware is `ota.ota.bin` and the binary firmware locates at directory of `build`.
|
||||
|
||||
There are 4 firmwares which are generated by `make ota`, but they are not the same: "ota.app1.bin" not equal to "ota.app2.bin" not equal to "ota.ota.bin" not equal to "ota.bin". "ota.ota.bin" is equal to "ota.app1.bin" plus "ota.app2.bin".
|
||||
Only `ota.ota.bin` can be used to be updated by this example.
|
||||
|
||||
## Step 2: Run HTTP Server
|
||||
|
||||
Python has a built-in HTTP server that can be used for example purposes.
|
||||
|
||||
Open a new terminal to run the HTTP server, then run these commands to build the example and start the server, then build project:
|
||||
|
||||
Start http server at the directory of "build":
|
||||
|
||||
```
|
||||
cd build
|
||||
python -m SimpleHTTPServer 8070
|
||||
```
|
||||
|
||||
The "8070" is the http server's TCP port which is configurated at menu.
|
||||
|
||||
While the server is running, the contents of the build directory can be browsed at http://localhost:8070/
|
||||
|
||||
NB: On some systems, the command may be `python2 -m SimpleHTTPServer`.
|
||||
|
||||
If you have any firewall software running that will block incoming access to port 8070, configure it to allow access while running the example.
|
||||
|
||||
## Step 4: Flash OTA Example
|
||||
|
||||
When flashing, use the `erase_flash` target first to erase the entire flash (this deletes any leftover data in the ota_data partition). Then flash the factory image over serial:
|
||||
|
||||
```
|
||||
make erase_flash flash
|
||||
```
|
||||
|
||||
(The `make erase_flash flash` means "erase everything, then flash". `make flash` only erases the parts of flash which are being rewritten.)
|
||||
|
||||
## Step 5: Run the OTA Example
|
||||
|
||||
When the example starts up, it will print "ota: Starting OTA example..." then:
|
||||
|
||||
1. Connect to the AP with configured SSID and password.
|
||||
2. Connect to the HTTP server and download the new image.
|
||||
3. Write the image to flash, and configure the next boot from this image.
|
||||
4. Reboot
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
* Check whether your PC can ping the ESP8266 at its IP, and make sure that the IP, AP and other configuration settings are correct in menuconfig.
|
||||
* Check if there is any firewall software on the PC that prevents incoming connections.
|
||||
* Check whether you can see the configured file (default project_template.ota.bin) when browsing the file listing at http://127.0.0.1/
|
||||
* If you have another PC or a phone, try viewing the file listing from the separate host.
|
||||
|
||||
## Error "ota_begin error err=0x104"
|
||||
|
||||
If you see this error then check that the configured (and actual) flash size is large enough for the partitions in the partition table. The default "two OTA slots" partition table only works with 4MB flash size. To use OTA with smaller flash sizes, create a custom partition table CSV (look in components/partition_table) and configure it in menuconfig.
|
||||
|
||||
If changing partition layout, it is usually wise to run "make erase_flash" between steps.
|
||||
|
||||
## Production Implementation
|
||||
|
||||
If scaling this example for production use, please consider:
|
||||
|
||||
* Using an encrypted communications channel such as HTTPS.
|
||||
* Dealing with timeouts or WiFi disconnections while flashing.
|
@ -0,0 +1,40 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config WIFI_SSID
|
||||
string "WiFi SSID"
|
||||
default "myssid"
|
||||
help
|
||||
SSID (network name) for the example to connect to.
|
||||
|
||||
config WIFI_PASSWORD
|
||||
string "WiFi Password"
|
||||
default "myssid"
|
||||
help
|
||||
WiFi password (WPA or WPA2) for the example to use.
|
||||
|
||||
Can be left blank if the network has no security set.
|
||||
|
||||
config SERVER_IP
|
||||
string "HTTP Server IP"
|
||||
default "192.168.0.3"
|
||||
help
|
||||
HTTP Server IP to download the image file from.
|
||||
|
||||
See example README.md for details.
|
||||
|
||||
config SERVER_PORT
|
||||
string "HTTP Server Port"
|
||||
default "8070"
|
||||
help
|
||||
HTTP Server port to connect to.
|
||||
Should be chosen not to conflict with any other port used
|
||||
on the system.
|
||||
|
||||
config EXAMPLE_FILENAME
|
||||
string "HTTP GET Filename"
|
||||
default "/project_template.bin"
|
||||
help
|
||||
Filename of the app image file to download for
|
||||
the OTA update.
|
||||
|
||||
endmenu
|
@ -32,24 +32,6 @@
|
||||
#define BUFFSIZE 1500
|
||||
#define TEXT_BUFFSIZE 1024
|
||||
|
||||
#ifdef CONFIG_ESP8266_OTA_FROM_OLD
|
||||
/*
|
||||
* Users should add your real firmware information here.
|
||||
* And the real infoarmation will be generated by "script".
|
||||
*/
|
||||
#ifdef CONFIG_ESPTOOLPY_FLASHSIZE_1MB
|
||||
/*
|
||||
* The configuration is related to file "partitions_two_ota_v2tov3.1MB".
|
||||
*/
|
||||
#define OTA_EXAMPLE_APP_OFFSET 0x6000UL
|
||||
#else
|
||||
/*
|
||||
* The configuration is related to file "partitions_two_ota_v2tov3.2MB".
|
||||
*/
|
||||
#define OTA_EXAMPLE_APP_OFFSET 0xf000UL
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef enum esp_ota_firm_state {
|
||||
ESP_OTA_INIT = 0,
|
||||
ESP_OTA_PREPARE,
|
||||
@ -94,10 +76,6 @@ static EventGroupHandle_t wifi_event_group;
|
||||
to the AP with an IP? */
|
||||
const int CONNECTED_BIT = BIT0;
|
||||
|
||||
#ifdef CONFIG_ESP8266_OTA_FROM_OLD
|
||||
static const uint32_t s_ota_app_offset = OTA_EXAMPLE_APP_OFFSET;
|
||||
#endif
|
||||
|
||||
static esp_err_t event_handler(void *ctx, system_event_t *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
@ -207,18 +185,8 @@ bool _esp_ota_firm_parse_http(esp_ota_firm_t *ota_firm, const char *text, size_t
|
||||
memset(length_str, 0, sizeof(length_str));
|
||||
memcpy(length_str, ptr, ptr2 - ptr);
|
||||
ota_firm->content_len = atoi(length_str);
|
||||
#ifdef CONFIG_ESP8266_OTA_FROM_OLD
|
||||
ota_firm->ota_size = ota_firm->content_len - s_ota_app_offset;
|
||||
ota_firm->ota_offset = s_ota_app_offset;
|
||||
#else
|
||||
#if defined(CONFIG_ESPTOOLPY_FLASHSIZE_1MB) && !defined(CONFIG_ESP8266_BOOT_COPY_APP)
|
||||
ota_firm->ota_size = ota_firm->content_len / ota_firm->ota_num;
|
||||
ota_firm->ota_offset = ota_firm->ota_size * ota_firm->update_ota_num;
|
||||
#else
|
||||
ota_firm->ota_size = ota_firm->content_len;
|
||||
ota_firm->ota_offset = 0;
|
||||
#endif
|
||||
#endif
|
||||
ESP_LOGI(TAG, "parse Content-Length:%d, ota_size %d", ota_firm->content_len, ota_firm->ota_size);
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
#
|
||||
# Serial flasher config
|
||||
#
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_1MB=y
|
||||
|
||||
#
|
||||
# Partition Table
|
||||
#
|
||||
CONFIG_PARTITION_TABLE_TWO_OTA=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_two_ota.1MB.csv"
|
||||
|
||||
#
|
||||
# Example Configuration
|
||||
#
|
||||
CONFIG_EXAMPLE_FILENAME="ota.ota.bin"
|
@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(native_ota)
|
@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := ota
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
@ -0,0 +1,163 @@
|
||||
|
||||
# Important
|
||||
|
||||
The example only services the ESP8285 or modules integrated the flash which size is `1MB`.
|
||||
|
||||
---
|
||||
|
||||
# Simple OTA Demo
|
||||
|
||||
This example demonstrates a working OTA (over the air) firmware update workflow.
|
||||
|
||||
This example is a *simplified demonstration*, for production firmware updates you should use a secure protocol such as HTTPS.
|
||||
|
||||
---
|
||||
|
||||
# Aim
|
||||
|
||||
An app running on ESP8266 can upgrade itself by downloading a new app "image" binary file, and storing it in flash.
|
||||
|
||||
In this example, the ESP8266 has 2 images in flash: OTA_0, OTA_1. Each of these is a self-contained partition. The number of OTA image partition is determined by the partition table layout.
|
||||
|
||||
Flash the example through serial port with command "make flash" to update the OTA_0 app image. In first boot, the bootloader loads this OTA_0 app image which then will execute an OTA update (triggered in the example code). The OTA update will download a new image from an http server and save it into the OTA_1 partition. After that, the example code will update the ota_data partition to indicate the new app partition, and then reboot, which leads to the second boot. During the second boot, the bootloader will read the ota_data, and select to run the new OTA image.
|
||||
|
||||
# Custom partition configuration
|
||||
|
||||
If customers want to use their own partition tables with specific partition location. Please see following steps:
|
||||
|
||||
## Step 1: Create partition file
|
||||
|
||||
Create a partition managment file with "cvs" formate, please refer to "doc/en/api-guides/partition-tables.rst"
|
||||
|
||||
## Step 2: Select custom partition mode
|
||||
|
||||
1. Select custom partition tables at "menuconfig":
|
||||
|
||||
```
|
||||
Partition Table --->
|
||||
Partition Table (XXXXXX) --->
|
||||
(X) Custom partition table CSV
|
||||
```
|
||||
|
||||
2. Configurate custom partition location at:
|
||||
|
||||
```
|
||||
(XXXXXX)Custom partition CSV file
|
||||
```
|
||||
|
||||
Note: System will add the absolute path of the project to the head of the "Custom partition CSV file" automatically when compling.
|
||||
|
||||
3. Configurate patition table location if necessary:
|
||||
|
||||
```
|
||||
(XXXXXX)Partition table offset address at flash
|
||||
```
|
||||
|
||||
**make ota flash** will only download the app1 at **OTA_0 partition offset**.
|
||||
|
||||
# Workflow
|
||||
|
||||
The OTA_workflow.png diagram demonstrates the overall workflow:
|
||||
|
||||

|
||||
|
||||
## Step 1: Connect to AP
|
||||
|
||||
Connect your host PC to the same AP that you will use for the ESP8266.
|
||||
|
||||
## Step 2: Build OTA Example
|
||||
|
||||
To make the OTA example easy to understand and use, here the example will show `self-update-self`.
|
||||
|
||||
Jump into the OTA example directory, and type `make menuconfig` to configure the OTA example. Under the "Example Configuration" submenu, fill in the following details:
|
||||
|
||||
* WiFi SSID & Password
|
||||
* IP address of your host PC as "HTTP Server"
|
||||
* HTTP Port number (if using the Python HTTP server above, the default is correct)
|
||||
|
||||
If serving the "OTA" example, you can leave the default filename as-is.
|
||||
|
||||
Configurate the flash size:
|
||||
|
||||
```
|
||||
Serial flasher config --->
|
||||
Flash size (2 MB) --->
|
||||
(X) 1 MB
|
||||
```
|
||||
|
||||
Enable firmware copy function:
|
||||
|
||||
```
|
||||
Component config --->
|
||||
ESP8266-specific --->
|
||||
[*] Boot copy app
|
||||
```
|
||||
|
||||
In general speaking, the 2 firmware(ota_o and ota_1) locates at ESP8285 or ESP8266 + 1MB flash are not same. But with this funcion,
|
||||
only one firmware is needed.
|
||||
|
||||
Save your changes, and type `make ota` to build the example to generate the real OTA binary firmware.
|
||||
|
||||
The name of final generated binary firmware is `ota.ota.bin` and the binary firmware locates at directory of `build`.
|
||||
|
||||
There are 4 firmwares which are generated by `make ota`, and they are the same: "ota.app1.bin" = "ota.app2.bin" = "ota.ota.bin" = "ota.bin". Any of them can be used to be updated by this example also.
|
||||
|
||||
## Step 2: Run HTTP Server
|
||||
|
||||
Python has a built-in HTTP server that can be used for example purposes.
|
||||
|
||||
Open a new terminal to run the HTTP server, then run these commands to build the example and start the server, then build project:
|
||||
|
||||
Start http server at the directory of "build":
|
||||
|
||||
```
|
||||
cd build
|
||||
python -m SimpleHTTPServer 8070
|
||||
```
|
||||
|
||||
The "8070" is the http server's TCP port which is configurated at menu.
|
||||
|
||||
While the server is running, the contents of the build directory can be browsed at http://localhost:8070/
|
||||
|
||||
NB: On some systems, the command may be `python2 -m SimpleHTTPServer`.
|
||||
|
||||
If you have any firewall software running that will block incoming access to port 8070, configure it to allow access while running the example.
|
||||
|
||||
## Step 4: Flash OTA Example
|
||||
|
||||
When flashing, use the `erase_flash` target first to erase the entire flash (this deletes any leftover data in the ota_data partition). Then flash the factory image over serial:
|
||||
|
||||
```
|
||||
make erase_flash flash
|
||||
```
|
||||
|
||||
(The `make erase_flash flash` means "erase everything, then flash". `make flash` only erases the parts of flash which are being rewritten.)
|
||||
|
||||
## Step 5: Run the OTA Example
|
||||
|
||||
When the example starts up, it will print "ota: Starting OTA example..." then:
|
||||
|
||||
1. Connect to the AP with configured SSID and password.
|
||||
2. Connect to the HTTP server and download the new image.
|
||||
3. Write the image to flash, and configure the next boot from this image.
|
||||
4. Reboot
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
* Check whether your PC can ping the ESP8266 at its IP, and make sure that the IP, AP and other configuration settings are correct in menuconfig.
|
||||
* Check if there is any firewall software on the PC that prevents incoming connections.
|
||||
* Check whether you can see the configured file (default project_template.ota.bin) when browsing the file listing at http://127.0.0.1/
|
||||
* If you have another PC or a phone, try viewing the file listing from the separate host.
|
||||
|
||||
## Error "ota_begin error err=0x104"
|
||||
|
||||
If you see this error then check that the configured (and actual) flash size is large enough for the partitions in the partition table. The default "two OTA slots" partition table only works with 4MB flash size. To use OTA with smaller flash sizes, create a custom partition table CSV (look in components/partition_table) and configure it in menuconfig.
|
||||
|
||||
If changing partition layout, it is usually wise to run "make erase_flash" between steps.
|
||||
|
||||
## Production Implementation
|
||||
|
||||
If scaling this example for production use, please consider:
|
||||
|
||||
* Using an encrypted communications channel such as HTTPS.
|
||||
* Dealing with timeouts or WiFi disconnections while flashing.
|
@ -0,0 +1,4 @@
|
||||
set(COMPONENT_SRCS "ota_example_main.c")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
@ -0,0 +1,40 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config WIFI_SSID
|
||||
string "WiFi SSID"
|
||||
default "myssid"
|
||||
help
|
||||
SSID (network name) for the example to connect to.
|
||||
|
||||
config WIFI_PASSWORD
|
||||
string "WiFi Password"
|
||||
default "myssid"
|
||||
help
|
||||
WiFi password (WPA or WPA2) for the example to use.
|
||||
|
||||
Can be left blank if the network has no security set.
|
||||
|
||||
config SERVER_IP
|
||||
string "HTTP Server IP"
|
||||
default "192.168.0.3"
|
||||
help
|
||||
HTTP Server IP to download the image file from.
|
||||
|
||||
See example README.md for details.
|
||||
|
||||
config SERVER_PORT
|
||||
string "HTTP Server Port"
|
||||
default "8070"
|
||||
help
|
||||
HTTP Server port to connect to.
|
||||
Should be chosen not to conflict with any other port used
|
||||
on the system.
|
||||
|
||||
config EXAMPLE_FILENAME
|
||||
string "HTTP GET Filename"
|
||||
default "/project_template.bin"
|
||||
help
|
||||
Filename of the app image file to download for
|
||||
the OTA update.
|
||||
|
||||
endmenu
|
@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
@ -0,0 +1,457 @@
|
||||
/* OTA example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#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 CONFIG_WIFI_SSID
|
||||
#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
|
||||
#define EXAMPLE_SERVER_IP CONFIG_SERVER_IP
|
||||
#define EXAMPLE_SERVER_PORT CONFIG_SERVER_PORT
|
||||
#define EXAMPLE_FILENAME CONFIG_EXAMPLE_FILENAME
|
||||
#define BUFFSIZE 1500
|
||||
#define TEXT_BUFFSIZE 1024
|
||||
|
||||
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;
|
||||
|
||||
static const char *TAG = "ota";
|
||||
/*an ota data write buffer ready to write to the flash*/
|
||||
static char ota_write_data[BUFFSIZE + 1] = { 0 };
|
||||
/*an packet receive buffer*/
|
||||
static char text[BUFFSIZE + 1] = { 0 };
|
||||
/* an image total length*/
|
||||
static int binary_file_length = 0;
|
||||
/*socket id*/
|
||||
static int socket_id = -1;
|
||||
|
||||
/* FreeRTOS event group to signal when we are connected & ready to make a request */
|
||||
static EventGroupHandle_t wifi_event_group;
|
||||
|
||||
/* The event group allows multiple bits for each event,
|
||||
but we only care about one event - are we connected
|
||||
to the AP with an IP? */
|
||||
const int CONNECTED_BIT = BIT0;
|
||||
|
||||
static esp_err_t event_handler(void *ctx, system_event_t *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case SYSTEM_EVENT_STA_START:
|
||||
esp_wifi_connect();
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_GOT_IP:
|
||||
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_DISCONNECTED:
|
||||
/* This is a workaround as ESP32 WiFi libs don't currently
|
||||
auto-reassociate. */
|
||||
esp_wifi_connect();
|
||||
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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(const char *buffer, char delim, int len)
|
||||
{
|
||||
// /*TODO: delim check,buffer check,further: do an buffer length limited*/
|
||||
int i = 0;
|
||||
while (buffer[i] != delim && i < len) {
|
||||
++i;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
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_id = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (socket_id == -1) {
|
||||
ESP_LOGE(TAG, "Create socket failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// set connect info
|
||||
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));
|
||||
|
||||
// connect to http server
|
||||
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;
|
||||
}
|
||||
|
||||
static void __attribute__((noreturn)) task_fatal_error()
|
||||
{
|
||||
ESP_LOGE(TAG, "Exiting task due to fatal error...");
|
||||
close(socket_id);
|
||||
(void)vTaskDelete(NULL);
|
||||
|
||||
while (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");
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
// 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");
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
*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 (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);
|
||||
|
||||
}
|
||||
|
||||
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... @ %p flash %s", ota_example_task, CONFIG_ESPTOOLPY_FLASHSIZE);
|
||||
|
||||
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);
|
||||
|
||||
/* Wait for the callback to set the CONNECTED_BIT in the
|
||||
event group.
|
||||
*/
|
||||
xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
|
||||
false, true, portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Connect to Wifi ! Start to Connect to Server....");
|
||||
|
||||
/*connect to http server*/
|
||||
if (connect_to_http_server()) {
|
||||
ESP_LOGI(TAG, "Connected to http server");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Connect to http server failed!");
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
/*send GET request to http server*/
|
||||
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");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 flag = true;
|
||||
esp_ota_firm_t ota_firm;
|
||||
|
||||
esp_ota_firm_init(&ota_firm, update_partition);
|
||||
|
||||
/*deal with all receive packet*/
|
||||
while (flag) {
|
||||
memset(text, 0, TEXT_BUFFSIZE);
|
||||
memset(ota_write_data, 0, BUFFSIZE);
|
||||
int buff_len = recv(socket_id, text, TEXT_BUFFSIZE, 0);
|
||||
if (buff_len < 0) { /*receive error*/
|
||||
ESP_LOGE(TAG, "Error: receive data error! errno=%d", errno);
|
||||
task_fatal_error();
|
||||
} else if (buff_len > 0) { /*deal with response body*/
|
||||
esp_ota_firm_parse_msg(&ota_firm, text, buff_len);
|
||||
|
||||
if (!esp_ota_firm_can_write(&ota_firm))
|
||||
continue;
|
||||
|
||||
memcpy(ota_write_data, 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 *)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) { /*packet over*/
|
||||
flag = false;
|
||||
ESP_LOGI(TAG, "Connection closed, all packets received");
|
||||
close(socket_id);
|
||||
} 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!");
|
||||
task_fatal_error();
|
||||
}
|
||||
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 );
|
||||
|
||||
initialise_wifi();
|
||||
xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
#
|
||||
# Serial flasher config
|
||||
#
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_1MB=y
|
||||
|
||||
#
|
||||
# Partition Table
|
||||
#
|
||||
CONFIG_PARTITION_TABLE_TWO_OTA=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_two_ota.1MB.csv"
|
||||
|
||||
#
|
||||
# ESP8266-specific
|
||||
#
|
||||
CONFIG_ESP8266_BOOT_COPY_APP=y
|
||||
|
||||
#
|
||||
# Example Configuration
|
||||
#
|
||||
CONFIG_EXAMPLE_FILENAME="ota.ota.bin"
|
@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(native_ota)
|
@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := ota
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
@ -0,0 +1,169 @@
|
||||
|
||||
# Important
|
||||
|
||||
The example only services the ESP8285 or modules integrated the flash which size is `1MB`.
|
||||
|
||||
---
|
||||
|
||||
# Simple OTA Demo
|
||||
|
||||
This example demonstrates a working OTA (over the air) firmware update workflow.
|
||||
|
||||
This example is a *simplified demonstration*, for production firmware updates you should use a secure protocol such as HTTPS.
|
||||
|
||||
---
|
||||
|
||||
# Aim
|
||||
|
||||
An app running on ESP8266 can upgrade itself by downloading a new app "image" binary file, and storing it in flash.
|
||||
|
||||
In this example, the ESP8266 has 2 images in flash: OTA_0, OTA_1. Each of these is a self-contained partition. The number of OTA image partition is determined by the partition table layout.
|
||||
|
||||
Flash the example through serial port with command "make flash" to update the OTA_0 app image. In first boot, the bootloader loads this OTA_0 app image which then will execute an OTA update (triggered in the example code). The OTA update will download a new image from an http server and save it into the OTA_1 partition. After that, the example code will update the ota_data partition to indicate the new app partition, and then reboot, which leads to the second boot. During the second boot, the bootloader will read the ota_data, and select to run the new OTA image.
|
||||
|
||||
# Custom partition configuration
|
||||
|
||||
If customers want to use their own partition tables with specific partition location. Please see following steps:
|
||||
|
||||
## Step 1: Create partition file
|
||||
|
||||
Create a partition managment file with "cvs" formate, please refer to "doc/en/api-guides/partition-tables.rst"
|
||||
|
||||
## Step 2: Select custom partition mode
|
||||
|
||||
1. Select custom partition tables at "menuconfig":
|
||||
|
||||
```
|
||||
Partition Table --->
|
||||
Partition Table (XXXXXX) --->
|
||||
(X) Custom partition table CSV
|
||||
```
|
||||
|
||||
2. Configurate custom partition location at:
|
||||
|
||||
```
|
||||
(XXXXXX)Custom partition CSV file
|
||||
```
|
||||
|
||||
Note: System will add the absolute path of the project to the head of the "Custom partition CSV file" automatically when compling.
|
||||
|
||||
3. Configurate patition table location if necessary:
|
||||
|
||||
```
|
||||
(XXXXXX)Partition table offset address at flash
|
||||
```
|
||||
|
||||
**make ota flash** will only download the app1 at **OTA_0 partition offset**.
|
||||
|
||||
# Workflow
|
||||
|
||||
The OTA_workflow.png diagram demonstrates the overall workflow:
|
||||
|
||||

|
||||
|
||||
## Step 1: Connect to AP
|
||||
|
||||
Connect your host PC to the same AP that you will use for the ESP8266.
|
||||
|
||||
## Step 2: Build OTA Example
|
||||
|
||||
To make the OTA example easy to understand and use, here the example will show `self-update-self`.
|
||||
|
||||
Jump into the OTA example directory, and type `make menuconfig` to configure the OTA example. Under the "Example Configuration" submenu, fill in the following details:
|
||||
|
||||
* WiFi SSID & Password
|
||||
* IP address of your host PC as "HTTP Server"
|
||||
* HTTP Port number (if using the Python HTTP server above, the default is correct)
|
||||
|
||||
If serving the "OTA" example, you can leave the default filename as-is.
|
||||
|
||||
Enable the firmware compatibility upgrade function:
|
||||
|
||||
```
|
||||
Component config --->
|
||||
ESP8266-specific --->
|
||||
[*] ESP8266 update from old SDK by OTA
|
||||
```
|
||||
|
||||
If you old SDK storing RF parameters is customized and want the new firmware to use it, please enable RF parameters reuse:
|
||||
|
||||
```
|
||||
Component config --->
|
||||
ESP8266-specific --->
|
||||
[*] Load old RF Parameters
|
||||
```
|
||||
|
||||
Configurate the flash size:
|
||||
|
||||
```
|
||||
Serial flasher config --->
|
||||
Flash size (2 MB) --->
|
||||
(X) 1 MB
|
||||
```
|
||||
|
||||
Save your changes, and type `make ota` to build the example to generate the real OTA binary firmware.
|
||||
|
||||
The name of final generated binary firmware is `ota.v2_to_v3.ota.bin` and the binary firmware locates at directory of `build`.
|
||||
And only this one can be used to be updated by this example.
|
||||
|
||||
You can read the document `docs/en/api-guilds/fota-from-old-new.rst` to know more about the compatibility upgrade function.
|
||||
|
||||
## Step 2: Run HTTP Server
|
||||
|
||||
Python has a built-in HTTP server that can be used for example purposes.
|
||||
|
||||
Open a new terminal to run the HTTP server, then run these commands to build the example and start the server, then build project:
|
||||
|
||||
Start http server at the directory of `build`:
|
||||
|
||||
```
|
||||
cd build
|
||||
python -m SimpleHTTPServer 8070
|
||||
```
|
||||
|
||||
The `8070` is the http server's TCP port which is configurated at menu.
|
||||
|
||||
While the server is running, the contents of the build directory can be browsed at http://localhost:8070/
|
||||
|
||||
NB: On some systems, the command may be `python2 -m SimpleHTTPServer`.
|
||||
|
||||
If you have any firewall software running that will block incoming access to port 8070, configure it to allow access while running the example.
|
||||
|
||||
## Step 4: Flash OTA Example
|
||||
|
||||
When flashing, use the `erase_flash` target first to erase the entire flash (this deletes any leftover data in the ota_data partition). Then flash the factory image over serial:
|
||||
|
||||
```
|
||||
make erase_flash flash
|
||||
```
|
||||
|
||||
(The `make erase_flash flash` means "erase everything, then flash". `make flash` only erases the parts of flash which are being rewritten.)
|
||||
|
||||
## Step 5: Run the OTA Example
|
||||
|
||||
When the example starts up, it will print "ota: Starting OTA example..." then:
|
||||
|
||||
1. Connect to the AP with configured SSID and password.
|
||||
2. Connect to the HTTP server and download the new image.
|
||||
3. Write the image to flash, and configure the next boot from this image.
|
||||
4. Reboot
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
* Check whether your PC can ping the ESP8266 at its IP, and make sure that the IP, AP and other configuration settings are correct in menuconfig.
|
||||
* Check if there is any firewall software on the PC that prevents incoming connections.
|
||||
* Check whether you can see the configured file (default project_template.ota.bin) when browsing the file listing at http://127.0.0.1/
|
||||
* If you have another PC or a phone, try viewing the file listing from the separate host.
|
||||
|
||||
## Error "ota_begin error err=0x104"
|
||||
|
||||
If you see this error then check that the configured (and actual) flash size is large enough for the partitions in the partition table. The default "two OTA slots" partition table only works with 4MB flash size. To use OTA with smaller flash sizes, create a custom partition table CSV (look in components/partition_table) and configure it in menuconfig.
|
||||
|
||||
If changing partition layout, it is usually wise to run "make erase_flash" between steps.
|
||||
|
||||
## Production Implementation
|
||||
|
||||
If scaling this example for production use, please consider:
|
||||
|
||||
* Using an encrypted communications channel such as HTTPS.
|
||||
* Dealing with timeouts or WiFi disconnections while flashing.
|
@ -0,0 +1,4 @@
|
||||
set(COMPONENT_SRCS "ota_example_main.c")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
@ -0,0 +1,466 @@
|
||||
/* OTA example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#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 CONFIG_WIFI_SSID
|
||||
#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
|
||||
#define EXAMPLE_SERVER_IP CONFIG_SERVER_IP
|
||||
#define EXAMPLE_SERVER_PORT CONFIG_SERVER_PORT
|
||||
#define EXAMPLE_FILENAME CONFIG_EXAMPLE_FILENAME
|
||||
#define BUFFSIZE 1500
|
||||
#define TEXT_BUFFSIZE 1024
|
||||
|
||||
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;
|
||||
|
||||
static const char *TAG = "ota";
|
||||
/*an ota data write buffer ready to write to the flash*/
|
||||
static char ota_write_data[BUFFSIZE + 1] = { 0 };
|
||||
/*an packet receive buffer*/
|
||||
static char text[BUFFSIZE + 1] = { 0 };
|
||||
/* an image total length*/
|
||||
static int binary_file_length = 0;
|
||||
/*socket id*/
|
||||
static int socket_id = -1;
|
||||
|
||||
/* FreeRTOS event group to signal when we are connected & ready to make a request */
|
||||
static EventGroupHandle_t wifi_event_group;
|
||||
|
||||
/* The event group allows multiple bits for each event,
|
||||
but we only care about one event - are we connected
|
||||
to the AP with an IP? */
|
||||
const int CONNECTED_BIT = BIT0;
|
||||
|
||||
static esp_err_t event_handler(void *ctx, system_event_t *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case SYSTEM_EVENT_STA_START:
|
||||
esp_wifi_connect();
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_GOT_IP:
|
||||
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_DISCONNECTED:
|
||||
/* This is a workaround as ESP32 WiFi libs don't currently
|
||||
auto-reassociate. */
|
||||
esp_wifi_connect();
|
||||
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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(const char *buffer, char delim, int len)
|
||||
{
|
||||
// /*TODO: delim check,buffer check,further: do an buffer length limited*/
|
||||
int i = 0;
|
||||
while (buffer[i] != delim && i < len) {
|
||||
++i;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
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_id = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (socket_id == -1) {
|
||||
ESP_LOGE(TAG, "Create socket failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// set connect info
|
||||
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));
|
||||
|
||||
// connect to http server
|
||||
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;
|
||||
}
|
||||
|
||||
static void __attribute__((noreturn)) task_fatal_error()
|
||||
{
|
||||
ESP_LOGE(TAG, "Exiting task due to fatal error...");
|
||||
close(socket_id);
|
||||
(void)vTaskDelete(NULL);
|
||||
|
||||
while (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];
|
||||
const esp_partition_t *ota0_partition;
|
||||
size_t skip_size;
|
||||
|
||||
ota0_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL);
|
||||
if (!ota0_partition) {
|
||||
ESP_LOGE(TAG, "Try to find OTA0 partition failed");
|
||||
return false;
|
||||
}
|
||||
skip_size = ota0_partition->address - 0x1000;
|
||||
|
||||
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 - skip_size;
|
||||
ota_firm->ota_offset = skip_size;
|
||||
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");
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
// 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");
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
*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 (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);
|
||||
|
||||
}
|
||||
|
||||
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... @ %p flash %s", ota_example_task, CONFIG_ESPTOOLPY_FLASHSIZE);
|
||||
|
||||
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);
|
||||
|
||||
/* Wait for the callback to set the CONNECTED_BIT in the
|
||||
event group.
|
||||
*/
|
||||
xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
|
||||
false, true, portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Connect to Wifi ! Start to Connect to Server....");
|
||||
|
||||
/*connect to http server*/
|
||||
if (connect_to_http_server()) {
|
||||
ESP_LOGI(TAG, "Connected to http server");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Connect to http server failed!");
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
/*send GET request to http server*/
|
||||
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");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 flag = true;
|
||||
esp_ota_firm_t ota_firm;
|
||||
|
||||
esp_ota_firm_init(&ota_firm, update_partition);
|
||||
|
||||
/*deal with all receive packet*/
|
||||
while (flag) {
|
||||
memset(text, 0, TEXT_BUFFSIZE);
|
||||
memset(ota_write_data, 0, BUFFSIZE);
|
||||
int buff_len = recv(socket_id, text, TEXT_BUFFSIZE, 0);
|
||||
if (buff_len < 0) { /*receive error*/
|
||||
ESP_LOGE(TAG, "Error: receive data error! errno=%d", errno);
|
||||
task_fatal_error();
|
||||
} else if (buff_len > 0) { /*deal with response body*/
|
||||
esp_ota_firm_parse_msg(&ota_firm, text, buff_len);
|
||||
|
||||
if (!esp_ota_firm_can_write(&ota_firm))
|
||||
continue;
|
||||
|
||||
memcpy(ota_write_data, 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 *)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) { /*packet over*/
|
||||
flag = false;
|
||||
ESP_LOGI(TAG, "Connection closed, all packets received");
|
||||
close(socket_id);
|
||||
} 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!");
|
||||
task_fatal_error();
|
||||
}
|
||||
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 );
|
||||
|
||||
initialise_wifi();
|
||||
xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
#
|
||||
# Serial flasher config
|
||||
#
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_1MB=y
|
||||
|
||||
#
|
||||
# Partition Table
|
||||
#
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_two_ota_v2tov3.1MB.csv"
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x5000
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_two_ota_v2tov3.1MB.csv"
|
||||
|
||||
#
|
||||
# Example Configuration
|
||||
#
|
||||
CONFIG_EXAMPLE_FILENAME="ota.v2_to_v3.ota.bin"
|
||||
|
||||
#
|
||||
# ESP8266-specific
|
||||
#
|
||||
CONFIG_ESP8266_OTA_FROM_OLD=y
|
@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(native_ota)
|
@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := ota
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
@ -0,0 +1,144 @@
|
||||
|
||||
# Important
|
||||
|
||||
The example only services the modules integrated the flash which size is `2MB` or `larger`.
|
||||
|
||||
---
|
||||
|
||||
# Simple OTA Demo
|
||||
|
||||
This example demonstrates a working OTA (over the air) firmware update workflow.
|
||||
|
||||
This example is a *simplified demonstration*, for production firmware updates you should use a secure protocol such as HTTPS.
|
||||
|
||||
---
|
||||
|
||||
# Aim
|
||||
|
||||
An app running on ESP8266 can upgrade itself by downloading a new app "image" binary file, and storing it in flash.
|
||||
|
||||
In this example, the ESP8266 has 2 images in flash: OTA_0, OTA_1. Each of these is a self-contained partition. The number of OTA image partition is determined by the partition table layout.
|
||||
|
||||
Flash the example through serial port with command "make flash" to update the OTA_0 app image. In first boot, the bootloader loads this OTA_0 app image which then will execute an OTA update (triggered in the example code). The OTA update will download a new image from an http server and save it into the OTA_1 partition. After that, the example code will update the ota_data partition to indicate the new app partition, and then reboot, which leads to the second boot. During the second boot, the bootloader will read the ota_data, and select to run the new OTA image.
|
||||
|
||||
# Custom partition configuration
|
||||
|
||||
If customers want to use their own partition tables with specific partition location. Please see following steps:
|
||||
|
||||
## Step 1: Create partition file
|
||||
|
||||
Create a partition managment file with "cvs" formate, please refer to "doc/en/api-guides/partition-tables.rst"
|
||||
|
||||
## Step 2: Select custom partition mode
|
||||
|
||||
1. Select custom partition tables at "menuconfig":
|
||||
|
||||
```
|
||||
Partition Table --->
|
||||
Partition Table (XXXXXX) --->
|
||||
(X) Custom partition table CSV
|
||||
```
|
||||
|
||||
2. Configurate custom partition location at:
|
||||
|
||||
```
|
||||
(XXXXXX)Custom partition CSV file
|
||||
```
|
||||
|
||||
Note: System will add the absolute path of the project to the head of the "Custom partition CSV file" automatically when compling.
|
||||
|
||||
3. Configurate patition table location if necessary:
|
||||
|
||||
```
|
||||
(XXXXXX)Partition table offset address at flash
|
||||
```
|
||||
|
||||
**make ota flash** will only download the app1 at **OTA_0 partition offset**.
|
||||
|
||||
# Workflow
|
||||
|
||||
The OTA_workflow.png diagram demonstrates the overall workflow:
|
||||
|
||||

|
||||
|
||||
## Step 1: Connect to AP
|
||||
|
||||
Connect your host PC to the same AP that you will use for the ESP8266.
|
||||
|
||||
## Step 2: Build OTA Example
|
||||
|
||||
To make the OTA example easy to understand and use, here the example will show `self-update-self`.
|
||||
|
||||
Jump into the OTA example directory, and type `make menuconfig` to configure the OTA example. Under the "Example Configuration" submenu, fill in the following details:
|
||||
|
||||
* WiFi SSID & Password
|
||||
* IP address of your host PC as "HTTP Server"
|
||||
* HTTP Port number (if using the Python HTTP server above, the default is correct)
|
||||
|
||||
If serving the "OTA" example, you can leave the default filename as-is.
|
||||
|
||||
Save your changes, and type `make ota` to build the example to generate the real OTA binary firmware.
|
||||
|
||||
The name of final generated binary firmware is `ota.ota.bin` and the binary firmware locates at directory of `build`.
|
||||
|
||||
There are 4 firmwares which are generated by `make ota`, and they are the same: "ota.app1.bin" = "ota.app2.bin" = "ota.ota.bin" = "ota.bin". Any of them can be used to be updated by this example also.
|
||||
|
||||
## Step 2: Run HTTP Server
|
||||
|
||||
Python has a built-in HTTP server that can be used for example purposes.
|
||||
|
||||
Open a new terminal to run the HTTP server, then run these commands to build the example and start the server, then build project:
|
||||
|
||||
Start http server at the directory of "build":
|
||||
|
||||
```
|
||||
cd build
|
||||
python -m SimpleHTTPServer 8070
|
||||
```
|
||||
|
||||
The "8070" is the http server's TCP port which is configurated at menu.
|
||||
|
||||
While the server is running, the contents of the build directory can be browsed at http://localhost:8070/
|
||||
|
||||
NB: On some systems, the command may be `python2 -m SimpleHTTPServer`.
|
||||
|
||||
If you have any firewall software running that will block incoming access to port 8070, configure it to allow access while running the example.
|
||||
|
||||
## Step 4: Flash OTA Example
|
||||
|
||||
When flashing, use the `erase_flash` target first to erase the entire flash (this deletes any leftover data in the ota_data partition). Then flash the factory image over serial:
|
||||
|
||||
```
|
||||
make erase_flash flash
|
||||
```
|
||||
|
||||
(The `make erase_flash flash` means "erase everything, then flash". `make flash` only erases the parts of flash which are being rewritten.)
|
||||
|
||||
## Step 5: Run the OTA Example
|
||||
|
||||
When the example starts up, it will print "ota: Starting OTA example..." then:
|
||||
|
||||
1. Connect to the AP with configured SSID and password.
|
||||
2. Connect to the HTTP server and download the new image.
|
||||
3. Write the image to flash, and configure the next boot from this image.
|
||||
4. Reboot
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
* Check whether your PC can ping the ESP8266 at its IP, and make sure that the IP, AP and other configuration settings are correct in menuconfig.
|
||||
* Check if there is any firewall software on the PC that prevents incoming connections.
|
||||
* Check whether you can see the configured file (default project_template.ota.bin) when browsing the file listing at http://127.0.0.1/
|
||||
* If you have another PC or a phone, try viewing the file listing from the separate host.
|
||||
|
||||
## Error "ota_begin error err=0x104"
|
||||
|
||||
If you see this error then check that the configured (and actual) flash size is large enough for the partitions in the partition table. The default "two OTA slots" partition table only works with 4MB flash size. To use OTA with smaller flash sizes, create a custom partition table CSV (look in components/partition_table) and configure it in menuconfig.
|
||||
|
||||
If changing partition layout, it is usually wise to run "make erase_flash" between steps.
|
||||
|
||||
## Production Implementation
|
||||
|
||||
If scaling this example for production use, please consider:
|
||||
|
||||
* Using an encrypted communications channel such as HTTPS.
|
||||
* Dealing with timeouts or WiFi disconnections while flashing.
|
@ -0,0 +1,4 @@
|
||||
set(COMPONENT_SRCS "ota_example_main.c")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
@ -0,0 +1,40 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config WIFI_SSID
|
||||
string "WiFi SSID"
|
||||
default "myssid"
|
||||
help
|
||||
SSID (network name) for the example to connect to.
|
||||
|
||||
config WIFI_PASSWORD
|
||||
string "WiFi Password"
|
||||
default "myssid"
|
||||
help
|
||||
WiFi password (WPA or WPA2) for the example to use.
|
||||
|
||||
Can be left blank if the network has no security set.
|
||||
|
||||
config SERVER_IP
|
||||
string "HTTP Server IP"
|
||||
default "192.168.0.3"
|
||||
help
|
||||
HTTP Server IP to download the image file from.
|
||||
|
||||
See example README.md for details.
|
||||
|
||||
config SERVER_PORT
|
||||
string "HTTP Server Port"
|
||||
default "8070"
|
||||
help
|
||||
HTTP Server port to connect to.
|
||||
Should be chosen not to conflict with any other port used
|
||||
on the system.
|
||||
|
||||
config EXAMPLE_FILENAME
|
||||
string "HTTP GET Filename"
|
||||
default "/project_template.bin"
|
||||
help
|
||||
Filename of the app image file to download for
|
||||
the OTA update.
|
||||
|
||||
endmenu
|
@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
@ -0,0 +1,457 @@
|
||||
/* OTA example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#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 CONFIG_WIFI_SSID
|
||||
#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
|
||||
#define EXAMPLE_SERVER_IP CONFIG_SERVER_IP
|
||||
#define EXAMPLE_SERVER_PORT CONFIG_SERVER_PORT
|
||||
#define EXAMPLE_FILENAME CONFIG_EXAMPLE_FILENAME
|
||||
#define BUFFSIZE 1500
|
||||
#define TEXT_BUFFSIZE 1024
|
||||
|
||||
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;
|
||||
|
||||
static const char *TAG = "ota";
|
||||
/*an ota data write buffer ready to write to the flash*/
|
||||
static char ota_write_data[BUFFSIZE + 1] = { 0 };
|
||||
/*an packet receive buffer*/
|
||||
static char text[BUFFSIZE + 1] = { 0 };
|
||||
/* an image total length*/
|
||||
static int binary_file_length = 0;
|
||||
/*socket id*/
|
||||
static int socket_id = -1;
|
||||
|
||||
/* FreeRTOS event group to signal when we are connected & ready to make a request */
|
||||
static EventGroupHandle_t wifi_event_group;
|
||||
|
||||
/* The event group allows multiple bits for each event,
|
||||
but we only care about one event - are we connected
|
||||
to the AP with an IP? */
|
||||
const int CONNECTED_BIT = BIT0;
|
||||
|
||||
static esp_err_t event_handler(void *ctx, system_event_t *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case SYSTEM_EVENT_STA_START:
|
||||
esp_wifi_connect();
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_GOT_IP:
|
||||
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_DISCONNECTED:
|
||||
/* This is a workaround as ESP32 WiFi libs don't currently
|
||||
auto-reassociate. */
|
||||
esp_wifi_connect();
|
||||
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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(const char *buffer, char delim, int len)
|
||||
{
|
||||
// /*TODO: delim check,buffer check,further: do an buffer length limited*/
|
||||
int i = 0;
|
||||
while (buffer[i] != delim && i < len) {
|
||||
++i;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
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_id = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (socket_id == -1) {
|
||||
ESP_LOGE(TAG, "Create socket failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// set connect info
|
||||
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));
|
||||
|
||||
// connect to http server
|
||||
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;
|
||||
}
|
||||
|
||||
static void __attribute__((noreturn)) task_fatal_error()
|
||||
{
|
||||
ESP_LOGE(TAG, "Exiting task due to fatal error...");
|
||||
close(socket_id);
|
||||
(void)vTaskDelete(NULL);
|
||||
|
||||
while (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");
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
// 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");
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
*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 (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);
|
||||
|
||||
}
|
||||
|
||||
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... @ %p flash %s", ota_example_task, CONFIG_ESPTOOLPY_FLASHSIZE);
|
||||
|
||||
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);
|
||||
|
||||
/* Wait for the callback to set the CONNECTED_BIT in the
|
||||
event group.
|
||||
*/
|
||||
xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
|
||||
false, true, portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Connect to Wifi ! Start to Connect to Server....");
|
||||
|
||||
/*connect to http server*/
|
||||
if (connect_to_http_server()) {
|
||||
ESP_LOGI(TAG, "Connected to http server");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Connect to http server failed!");
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
/*send GET request to http server*/
|
||||
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");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 flag = true;
|
||||
esp_ota_firm_t ota_firm;
|
||||
|
||||
esp_ota_firm_init(&ota_firm, update_partition);
|
||||
|
||||
/*deal with all receive packet*/
|
||||
while (flag) {
|
||||
memset(text, 0, TEXT_BUFFSIZE);
|
||||
memset(ota_write_data, 0, BUFFSIZE);
|
||||
int buff_len = recv(socket_id, text, TEXT_BUFFSIZE, 0);
|
||||
if (buff_len < 0) { /*receive error*/
|
||||
ESP_LOGE(TAG, "Error: receive data error! errno=%d", errno);
|
||||
task_fatal_error();
|
||||
} else if (buff_len > 0) { /*deal with response body*/
|
||||
esp_ota_firm_parse_msg(&ota_firm, text, buff_len);
|
||||
|
||||
if (!esp_ota_firm_can_write(&ota_firm))
|
||||
continue;
|
||||
|
||||
memcpy(ota_write_data, 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 *)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) { /*packet over*/
|
||||
flag = false;
|
||||
ESP_LOGI(TAG, "Connection closed, all packets received");
|
||||
close(socket_id);
|
||||
} 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!");
|
||||
task_fatal_error();
|
||||
}
|
||||
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 );
|
||||
|
||||
initialise_wifi();
|
||||
xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
#
|
||||
# Serial flasher config
|
||||
#
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
|
||||
|
||||
#
|
||||
# Partition Table
|
||||
#
|
||||
CONFIG_PARTITION_TABLE_TWO_OTA=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_two_ota.csv"
|
||||
|
||||
#
|
||||
# Example Configuration
|
||||
#
|
||||
CONFIG_EXAMPLE_FILENAME="ota.ota.bin"
|
@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(native_ota)
|
@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := ota
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
@ -0,0 +1,161 @@
|
||||
|
||||
# Important
|
||||
|
||||
The example only services the modules integrated the flash which size is `2MB` or `larger`.
|
||||
|
||||
---
|
||||
|
||||
# Simple OTA Demo
|
||||
|
||||
This example demonstrates a working OTA (over the air) firmware update workflow.
|
||||
|
||||
This example is a *simplified demonstration*, for production firmware updates you should use a secure protocol such as HTTPS.
|
||||
|
||||
---
|
||||
|
||||
# Aim
|
||||
|
||||
An app running on ESP8266 can upgrade itself by downloading a new app "image" binary file, and storing it in flash.
|
||||
|
||||
In this example, the ESP8266 has 2 images in flash: OTA_0, OTA_1. Each of these is a self-contained partition. The number of OTA image partition is determined by the partition table layout.
|
||||
|
||||
Flash the example through serial port with command "make flash" to update the OTA_0 app image. In first boot, the bootloader loads this OTA_0 app image which then will execute an OTA update (triggered in the example code). The OTA update will download a new image from an http server and save it into the OTA_1 partition. After that, the example code will update the ota_data partition to indicate the new app partition, and then reboot, which leads to the second boot. During the second boot, the bootloader will read the ota_data, and select to run the new OTA image.
|
||||
|
||||
# Custom partition configuration
|
||||
|
||||
If customers want to use their own partition tables with specific partition location. Please see following steps:
|
||||
|
||||
## Step 1: Create partition file
|
||||
|
||||
Create a partition managment file with "cvs" formate, please refer to "doc/en/api-guides/partition-tables.rst"
|
||||
|
||||
## Step 2: Select custom partition mode
|
||||
|
||||
1. Select custom partition tables at "menuconfig":
|
||||
|
||||
```
|
||||
Partition Table --->
|
||||
Partition Table (XXXXXX) --->
|
||||
(X) Custom partition table CSV
|
||||
```
|
||||
|
||||
2. Configurate custom partition location at:
|
||||
|
||||
```
|
||||
(XXXXXX)Custom partition CSV file
|
||||
```
|
||||
|
||||
Note: System will add the absolute path of the project to the head of the "Custom partition CSV file" automatically when compling.
|
||||
|
||||
3. Configurate patition table location if necessary:
|
||||
|
||||
```
|
||||
(XXXXXX)Partition table offset address at flash
|
||||
```
|
||||
|
||||
**make ota flash** will only download the app1 at **OTA_0 partition offset**.
|
||||
|
||||
# Workflow
|
||||
|
||||
The OTA_workflow.png diagram demonstrates the overall workflow:
|
||||
|
||||

|
||||
|
||||
## Step 1: Connect to AP
|
||||
|
||||
Connect your host PC to the same AP that you will use for the ESP8266.
|
||||
|
||||
## Step 2: Build OTA Example
|
||||
|
||||
To make the OTA example easy to understand and use, here the example will show `self-update-self`.
|
||||
|
||||
Jump into the OTA example directory, and type `make menuconfig` to configure the OTA example. Under the "Example Configuration" submenu, fill in the following details:
|
||||
|
||||
* WiFi SSID & Password
|
||||
* IP address of your host PC as "HTTP Server"
|
||||
* HTTP Port number (if using the Python HTTP server above, the default is correct)
|
||||
|
||||
If serving the "OTA" example, you can leave the default filename as-is.
|
||||
|
||||
Enable the firmware compatibility upgrade function:
|
||||
|
||||
```
|
||||
Component config --->
|
||||
ESP8266-specific --->
|
||||
[*] ESP8266 update from old SDK by OTA
|
||||
```
|
||||
|
||||
If you old SDK storing RF parameters is customized and want the new firmware to use it, please enable RF parameters reuse:
|
||||
|
||||
```
|
||||
Component config --->
|
||||
ESP8266-specific --->
|
||||
[*] Load old RF Parameters
|
||||
```
|
||||
|
||||
Save your changes, and type `make ota` to build the example to generate the real OTA binary firmware.
|
||||
|
||||
The name of final generated binary firmware is `ota.v2_to_v3.ota.bin` and the binary firmware locates at directory of `build`.
|
||||
And only this one can be used to be updated by this example.
|
||||
|
||||
You can read the document `docs/en/api-guilds/fota-from-old-new.rst` to know more about the compatibility upgrade function.
|
||||
|
||||
## Step 2: Run HTTP Server
|
||||
|
||||
Python has a built-in HTTP server that can be used for example purposes.
|
||||
|
||||
Open a new terminal to run the HTTP server, then run these commands to build the example and start the server, then build project:
|
||||
|
||||
Start http server at the directory of `build`:
|
||||
|
||||
```
|
||||
cd build
|
||||
python -m SimpleHTTPServer 8070
|
||||
```
|
||||
|
||||
The `8070` is the http server's TCP port which is configurated at menu.
|
||||
|
||||
While the server is running, the contents of the build directory can be browsed at http://localhost:8070/
|
||||
|
||||
NB: On some systems, the command may be `python2 -m SimpleHTTPServer`.
|
||||
|
||||
If you have any firewall software running that will block incoming access to port 8070, configure it to allow access while running the example.
|
||||
|
||||
## Step 4: Flash OTA Example
|
||||
|
||||
When flashing, use the `erase_flash` target first to erase the entire flash (this deletes any leftover data in the ota_data partition). Then flash the factory image over serial:
|
||||
|
||||
```
|
||||
make erase_flash flash
|
||||
```
|
||||
|
||||
(The `make erase_flash flash` means "erase everything, then flash". `make flash` only erases the parts of flash which are being rewritten.)
|
||||
|
||||
## Step 5: Run the OTA Example
|
||||
|
||||
When the example starts up, it will print "ota: Starting OTA example..." then:
|
||||
|
||||
1. Connect to the AP with configured SSID and password.
|
||||
2. Connect to the HTTP server and download the new image.
|
||||
3. Write the image to flash, and configure the next boot from this image.
|
||||
4. Reboot
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
* Check whether your PC can ping the ESP8266 at its IP, and make sure that the IP, AP and other configuration settings are correct in menuconfig.
|
||||
* Check if there is any firewall software on the PC that prevents incoming connections.
|
||||
* Check whether you can see the configured file (default project_template.ota.bin) when browsing the file listing at http://127.0.0.1/
|
||||
* If you have another PC or a phone, try viewing the file listing from the separate host.
|
||||
|
||||
## Error "ota_begin error err=0x104"
|
||||
|
||||
If you see this error then check that the configured (and actual) flash size is large enough for the partitions in the partition table. The default "two OTA slots" partition table only works with 4MB flash size. To use OTA with smaller flash sizes, create a custom partition table CSV (look in components/partition_table) and configure it in menuconfig.
|
||||
|
||||
If changing partition layout, it is usually wise to run "make erase_flash" between steps.
|
||||
|
||||
## Production Implementation
|
||||
|
||||
If scaling this example for production use, please consider:
|
||||
|
||||
* Using an encrypted communications channel such as HTTPS.
|
||||
* Dealing with timeouts or WiFi disconnections while flashing.
|
@ -0,0 +1,4 @@
|
||||
set(COMPONENT_SRCS "ota_example_main.c")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
@ -0,0 +1,40 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config WIFI_SSID
|
||||
string "WiFi SSID"
|
||||
default "myssid"
|
||||
help
|
||||
SSID (network name) for the example to connect to.
|
||||
|
||||
config WIFI_PASSWORD
|
||||
string "WiFi Password"
|
||||
default "myssid"
|
||||
help
|
||||
WiFi password (WPA or WPA2) for the example to use.
|
||||
|
||||
Can be left blank if the network has no security set.
|
||||
|
||||
config SERVER_IP
|
||||
string "HTTP Server IP"
|
||||
default "192.168.0.3"
|
||||
help
|
||||
HTTP Server IP to download the image file from.
|
||||
|
||||
See example README.md for details.
|
||||
|
||||
config SERVER_PORT
|
||||
string "HTTP Server Port"
|
||||
default "8070"
|
||||
help
|
||||
HTTP Server port to connect to.
|
||||
Should be chosen not to conflict with any other port used
|
||||
on the system.
|
||||
|
||||
config EXAMPLE_FILENAME
|
||||
string "HTTP GET Filename"
|
||||
default "/project_template.ota.bin"
|
||||
help
|
||||
Filename of the app image file to download for
|
||||
the OTA update.
|
||||
|
||||
endmenu
|
@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
@ -0,0 +1,466 @@
|
||||
/* OTA example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#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 CONFIG_WIFI_SSID
|
||||
#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
|
||||
#define EXAMPLE_SERVER_IP CONFIG_SERVER_IP
|
||||
#define EXAMPLE_SERVER_PORT CONFIG_SERVER_PORT
|
||||
#define EXAMPLE_FILENAME CONFIG_EXAMPLE_FILENAME
|
||||
#define BUFFSIZE 1500
|
||||
#define TEXT_BUFFSIZE 1024
|
||||
|
||||
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;
|
||||
|
||||
static const char *TAG = "ota";
|
||||
/*an ota data write buffer ready to write to the flash*/
|
||||
static char ota_write_data[BUFFSIZE + 1] = { 0 };
|
||||
/*an packet receive buffer*/
|
||||
static char text[BUFFSIZE + 1] = { 0 };
|
||||
/* an image total length*/
|
||||
static int binary_file_length = 0;
|
||||
/*socket id*/
|
||||
static int socket_id = -1;
|
||||
|
||||
/* FreeRTOS event group to signal when we are connected & ready to make a request */
|
||||
static EventGroupHandle_t wifi_event_group;
|
||||
|
||||
/* The event group allows multiple bits for each event,
|
||||
but we only care about one event - are we connected
|
||||
to the AP with an IP? */
|
||||
const int CONNECTED_BIT = BIT0;
|
||||
|
||||
static esp_err_t event_handler(void *ctx, system_event_t *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case SYSTEM_EVENT_STA_START:
|
||||
esp_wifi_connect();
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_GOT_IP:
|
||||
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_DISCONNECTED:
|
||||
/* This is a workaround as ESP32 WiFi libs don't currently
|
||||
auto-reassociate. */
|
||||
esp_wifi_connect();
|
||||
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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(const char *buffer, char delim, int len)
|
||||
{
|
||||
// /*TODO: delim check,buffer check,further: do an buffer length limited*/
|
||||
int i = 0;
|
||||
while (buffer[i] != delim && i < len) {
|
||||
++i;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
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_id = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (socket_id == -1) {
|
||||
ESP_LOGE(TAG, "Create socket failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// set connect info
|
||||
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));
|
||||
|
||||
// connect to http server
|
||||
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;
|
||||
}
|
||||
|
||||
static void __attribute__((noreturn)) task_fatal_error()
|
||||
{
|
||||
ESP_LOGE(TAG, "Exiting task due to fatal error...");
|
||||
close(socket_id);
|
||||
(void)vTaskDelete(NULL);
|
||||
|
||||
while (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];
|
||||
const esp_partition_t *ota0_partition;
|
||||
size_t skip_size;
|
||||
|
||||
ota0_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL);
|
||||
if (!ota0_partition) {
|
||||
ESP_LOGE(TAG, "Try to find OTA0 partition failed");
|
||||
return false;
|
||||
}
|
||||
skip_size = ota0_partition->address - 0x1000;
|
||||
|
||||
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 - skip_size;
|
||||
ota_firm->ota_offset = skip_size;
|
||||
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");
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
// 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");
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
*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 (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);
|
||||
|
||||
}
|
||||
|
||||
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... @ %p flash %s", ota_example_task, CONFIG_ESPTOOLPY_FLASHSIZE);
|
||||
|
||||
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);
|
||||
|
||||
/* Wait for the callback to set the CONNECTED_BIT in the
|
||||
event group.
|
||||
*/
|
||||
xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
|
||||
false, true, portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Connect to Wifi ! Start to Connect to Server....");
|
||||
|
||||
/*connect to http server*/
|
||||
if (connect_to_http_server()) {
|
||||
ESP_LOGI(TAG, "Connected to http server");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Connect to http server failed!");
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
/*send GET request to http server*/
|
||||
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");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 flag = true;
|
||||
esp_ota_firm_t ota_firm;
|
||||
|
||||
esp_ota_firm_init(&ota_firm, update_partition);
|
||||
|
||||
/*deal with all receive packet*/
|
||||
while (flag) {
|
||||
memset(text, 0, TEXT_BUFFSIZE);
|
||||
memset(ota_write_data, 0, BUFFSIZE);
|
||||
int buff_len = recv(socket_id, text, TEXT_BUFFSIZE, 0);
|
||||
if (buff_len < 0) { /*receive error*/
|
||||
ESP_LOGE(TAG, "Error: receive data error! errno=%d", errno);
|
||||
task_fatal_error();
|
||||
} else if (buff_len > 0) { /*deal with response body*/
|
||||
esp_ota_firm_parse_msg(&ota_firm, text, buff_len);
|
||||
|
||||
if (!esp_ota_firm_can_write(&ota_firm))
|
||||
continue;
|
||||
|
||||
memcpy(ota_write_data, 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 *)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) { /*packet over*/
|
||||
flag = false;
|
||||
ESP_LOGI(TAG, "Connection closed, all packets received");
|
||||
close(socket_id);
|
||||
} 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!");
|
||||
task_fatal_error();
|
||||
}
|
||||
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 );
|
||||
|
||||
initialise_wifi();
|
||||
xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
|
||||
}
|
@ -3,8 +3,9 @@
|
||||
# Bootloader is at 0x1000 - 0x8000, total 28KB
|
||||
# Partition table is at 0x8000 - 0x9000, total 4KB
|
||||
# Reserve 4 sectors at the end flash address for V2 updating to V3 and system can read V2 parameters.
|
||||
# phy_init data must be put the address before ota_0
|
||||
nvs, data, nvs, 0x9000, 0x4000
|
||||
otadata, data, ota, 0xd000, 0x2000
|
||||
otadata, data, ota, 0xD000, 0x2000
|
||||
phy_init, data, phy, 0xf000, 0x1000
|
||||
ota_0, 0, ota_0, 0x10000, 0xEC000
|
||||
ota_1, 0, ota_1, 0x110000,0xEC000
|
|
@ -0,0 +1,22 @@
|
||||
#
|
||||
# Serial flasher config
|
||||
#
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
|
||||
|
||||
#
|
||||
# Partition Table
|
||||
#
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_two_ota_v2tov3.2MB.csv"
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x8000
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_two_ota_v2tov3.2MB.csv"
|
||||
|
||||
#
|
||||
# Example Configuration
|
||||
#
|
||||
CONFIG_EXAMPLE_FILENAME="ota.v2_to_v3.ota.bin"
|
||||
|
||||
#
|
||||
# ESP8266-specific
|
||||
#
|
||||
CONFIG_ESP8266_OTA_FROM_OLD=y
|
@ -1,181 +1,35 @@
|
||||
# Native OTA
|
||||
|
||||
# Important
|
||||
We split native OTA example into several sub-examples to let custemors to choose which application scenario is they really want.
|
||||
|
||||
If your development board is based on **ESP8285** or **ESP8266 + 1MB flash**, you should read this document carefully, especially the Chapter **"Principle"**.
|
||||
|
||||
---
|
||||
|
||||
# Simple OTA Demo
|
||||
|
||||
This example demonstrates a working OTA (over the air) firmware update workflow.
|
||||
|
||||
This example is a *simplified demonstration*, for production firmware updates you should use a secure protocol such as HTTPS.
|
||||
|
||||
---
|
||||
|
||||
# Aim
|
||||
|
||||
An app running on ESP8266 can upgrade itself by downloading a new app "image" binary file, and storing it in flash.
|
||||
|
||||
In this example, the ESP8266 has 2 images in flash: OTA_0, OTA_1. Each of these is a self-contained partition. The number of OTA image partition is determined by the partition table layout.
|
||||
|
||||
Flash the example through serial port with command "make flash" to update the OTA_0 app image. In first boot, the bootloader loads this OTA_0 app image which then will execute an OTA update (triggered in the example code). The OTA update will download a new image from an http server and save it into the OTA_1 partition. After that, the example code will update the ota_data partition to indicate the new app partition, and then reboot, which leads to the second boot. During the second boot, the bootloader will read the ota_data, and select to run the new OTA image.
|
||||
|
||||
# Custom partition configuration
|
||||
|
||||
If customers want to use their own partition tables with specific partition location. Please see following steps:
|
||||
|
||||
## Step 1: Create partition file
|
||||
|
||||
Create a partition managment file with "cvs" formate, please refer to "doc/en/api-guides/partition-tables.rst"
|
||||
|
||||
## Step 2: Select custom partition mode
|
||||
|
||||
1. Select custom partition tables at "menuconfig":
|
||||
The projects' directory structure is following:
|
||||
|
||||
```
|
||||
Partition Table --->
|
||||
Partition Table (XXXXXX) --->
|
||||
(X) Custom partition table CSV
|
||||
1MB_flash ---> # Only for ESP8266 + 1MB flash or ESP8285
|
||||
|
||||
new_to_new # current firmware is based on new SDK and it does not update from old SDK's firmware
|
||||
(no old, no copy) # new firmware is based on new SDK
|
||||
# it can use copy mode, but here not use
|
||||
|
||||
|
||||
new_to_new # current firmware is based on new SDK and it does not update from old SDK's firmware
|
||||
(no old, copy) # new firmware is based on new SDK
|
||||
# it uses copy mode
|
||||
# copy mode: copy firmware from OTA1 partition to OTA0 partition and run OTA0's app, so only 1 firmware is needed
|
||||
|
||||
|
||||
new_to_new(old) # current firmware is based on new SDK and it updates from old SDK's firmware
|
||||
# new firmware is based on new SDK
|
||||
# it must use copy mode
|
||||
|
||||
|
||||
2+MB_flash ---> # Only for ESP8266 + 2MB(or larger size) flash
|
||||
|
||||
new_to_new(no old) # current firmware is based on new SDK and it does not update from old SDK's firmware
|
||||
# new firmware is based on new SDK
|
||||
|
||||
|
||||
new_to_new(old) # current firmware is based on new SDK and it updates from old SDK's firmware
|
||||
# new firmware is based on new SDK
|
||||
```
|
||||
|
||||
2. Configurate custom partition location at:
|
||||
|
||||
```
|
||||
(XXXXXX)Custom partition CSV file
|
||||
```
|
||||
|
||||
Note: System will add the absolute path of the project to the head of the "Custom partition CSV file" automatically when compling.
|
||||
|
||||
3. Configurate patition table location if necessary:
|
||||
|
||||
```
|
||||
(XXXXXX)Partition table offset address at flash
|
||||
```
|
||||
|
||||
**make ota flash** will only download the app1 at **APP1 partition offset**.
|
||||
|
||||
# Workflow
|
||||
|
||||
The OTA_workflow.png diagram demonstrates the overall workflow:
|
||||
|
||||

|
||||
|
||||
## Step 1: Connect to AP
|
||||
|
||||
Connect your host PC to the same AP that you will use for the ESP8266.
|
||||
|
||||
## Step 2: Run HTTP Server
|
||||
|
||||
Python has a built-in HTTP server that can be used for example purposes.
|
||||
|
||||
For our upgrade example OTA file, we're going to use the `get-started/project_template` example.
|
||||
|
||||
Open a new terminal to run the HTTP server, then run these commands to build the example and start the server, if your board's flash size is "1 MB", you should firstly configure flash size to be "1 MB"(default is "2 MB") at "menuconfig" and then build project:
|
||||
|
||||
Configure 1MB flash if it is needed:
|
||||
|
||||
```
|
||||
Serial flasher config --->
|
||||
Flash size (2 MB) --->
|
||||
(X) 1 MB
|
||||
```
|
||||
|
||||
Build project:
|
||||
|
||||
```
|
||||
cd $IDF_PATH/examples/get-started/project_template
|
||||
make ota
|
||||
```
|
||||
|
||||
Start http server at the directory of "build":
|
||||
|
||||
```
|
||||
cd build
|
||||
python -m SimpleHTTPServer 8070
|
||||
```
|
||||
|
||||
While the server is running, the contents of the build directory can be browsed at http://localhost:8070/
|
||||
|
||||
NB: On some systems, the command may be `python2 -m SimpleHTTPServer`.
|
||||
|
||||
If you have any firewall software running that will block incoming access to port 8070, configure it to allow access while running the example.
|
||||
|
||||
## Step 3: Build OTA Example
|
||||
|
||||
Change back to the OTA example directory, and type `make menuconfig` to configure the OTA example. Under the "Example Configuration" submenu, fill in the following details:
|
||||
|
||||
* WiFi SSID & Password
|
||||
* IP address of your host PC as "HTTP Server"
|
||||
* HTTP Port number (if using the Python HTTP server above, the default is correct)
|
||||
|
||||
If serving the "project_template" example, you can leave the default filename as-is.
|
||||
|
||||
Configure 1MB flash if need:
|
||||
|
||||
```
|
||||
Serial flasher config --->
|
||||
Flash size (2 MB) --->
|
||||
(X) 1 MB
|
||||
```
|
||||
|
||||
Configurate the application partition table information and it must be the same as the OTA example's information, you can refer to the **Custom partition configuration**.
|
||||
|
||||
Save your changes, and type `make` to build the example.
|
||||
|
||||
## Step 4: Flash OTA Example
|
||||
|
||||
When flashing, use the `erase_flash` target first to erase the entire flash (this deletes any leftover data in the ota_data partition). Then flash the factory image over serial:
|
||||
|
||||
```
|
||||
make erase_flash flash
|
||||
```
|
||||
|
||||
(The `make erase_flash flash` means "erase everything, then flash". `make flash` only erases the parts of flash which are being rewritten.)
|
||||
|
||||
## Step 5: Run the OTA Example
|
||||
|
||||
When the example starts up, it will print "ota: Starting OTA example..." then:
|
||||
|
||||
1. Connect to the AP with configured SSID and password.
|
||||
2. Connect to the HTTP server and download the new image.
|
||||
3. Write the image to flash, and configure the next boot from this image.
|
||||
4. Reboot
|
||||
|
||||
# Principle
|
||||
|
||||
Command "make ota" will generate 3 binaries: "xxx(project name).app1.bin", "xxx(project name).app2.bin" and "xxx(project name).ota.bin". You should only upload the "xxx(project name).ota.bin" to your OTA server and let the app download it as the example.
|
||||
|
||||
"xxx.app1.bin" is for downloading to OTA_0 partition, and "xxx.app2.bin" is for downloading to OTA_1 partition. If your board's flash size is larger than "1 MB" or you select "Copy OTA" function, then "xxx.app1.bin" = "xxx.app2.bin" = "xxx.ota.bin". Otherwise If your board's flash size is "1 MB" and you don't select "Copy OTA" function, "xxx.app1.bin" != "xxx.app2.bin" != "xxx.ota.bin", "xxx.ota.bin" = "xxx.app1.bin" + "xxx.app2.bin". So the flash size configuration is very important. Otherwise if and at the last The example will select the binary it needs and download it into flash.
|
||||
|
||||
Based on the above theory, we can see that for ESP8266 + 2MB flash(or larger size), app1 and app2 are the same, you can download it directly without any distinction. But for ESP8285 (ESP8266 + 1MB flash), the ota0 (app1) and ota1 (app2) are different, you need to distinguish which one should be downloaded, and to what location, during FOTA. Now, the way in the example code is to synthesize app1 and app2 into an "xxxx (project name).ota.bin". And only write the target app (app1 or app2) into the flash, according to the location of download, when FOTA; the other part will be discarded.
|
||||
|
||||
On the other hand, if you want to use ESP8285(ESP8266 + 1MB flash) and don't want to upload 2 binaries for OTA, you can enable the "Copy OTA" function in menuconfig.
|
||||
|
||||
```
|
||||
Component config --->
|
||||
ESP8266-specific --->
|
||||
[*] (**Expected**)Boot copy app
|
||||
```
|
||||
|
||||
After enabling "Copy OTA" mode, the system will always download the app bin into ota_1 partition and then re-boot. After reboot, the bootloader will unpack the app bin and copy it to the ota_0 partition, then run the application in ota_0 partition.
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
* Check whether your PC can ping the ESP8266 at its IP, and make sure that the IP, AP and other configuration settings are correct in menuconfig.
|
||||
* Check if there is any firewall software on the PC that prevents incoming connections.
|
||||
* Check whether you can see the configured file (default project_template.ota.bin) when browsing the file listing at http://127.0.0.1/
|
||||
* If you have another PC or a phone, try viewing the file listing from the separate host.
|
||||
|
||||
## Error "ota_begin error err=0x104"
|
||||
|
||||
If you see this error then check that the configured (and actual) flash size is large enough for the partitions in the partition table. The default "two OTA slots" partition table only works with 4MB flash size. To use OTA with smaller flash sizes, create a custom partition table CSV (look in components/partition_table) and configure it in menuconfig.
|
||||
|
||||
If changing partition layout, it is usually wise to run "make erase_flash" between steps.
|
||||
|
||||
## Production Implementation
|
||||
|
||||
If scaling this example for production use, please consider:
|
||||
|
||||
* Using an encrypted communications channel such as HTTPS.
|
||||
* Dealing with timeouts or WiFi disconnections while flashing.
|
||||
|
@ -1,9 +0,0 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
|
||||
# Bootloader is at 0x0000 - 0x4000, total 16KB
|
||||
# Partition table is at 0x4000 - 0x5000, total 4KB
|
||||
phy_init, data, phy, 0x5000, 0x1000
|
||||
ota_0, 0, ota_0, 0x6000, 0x7A000
|
||||
nvs, data, nvs, 0x80000, 0x4000
|
||||
otadata, data, ota, 0x84000, 0x2000
|
||||
ota_1, 0, ota_1, 0x86000, 0x7A000
|
|
@ -1,8 +0,0 @@
|
||||
# Default sdkconfig parameters to use the ESP8266 OTA
|
||||
CONFIG_LWIP_SOCKET_MULTITHREAD=
|
||||
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_1MB=y
|
||||
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_two_ota.1MB.mini.csv"
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x4000
|
@ -57,11 +57,14 @@ class proc_addr_file(argparse.Action):
|
||||
address = 0x1000
|
||||
except ValueError:
|
||||
raise argparse.ArgumentError(self, 'Address "%s" must be a number' % values[i])
|
||||
try:
|
||||
argfile = open(values[i + 1], 'rb')
|
||||
except IOError as e:
|
||||
raise argparse.ArgumentError(self, e)
|
||||
pairs.append((address, argfile))
|
||||
|
||||
# ota initial data is not need
|
||||
if 'ota_data_initial.bin' not in values[i + 1]:
|
||||
try:
|
||||
argfile = open(values[i + 1], 'rb')
|
||||
except IOError as e:
|
||||
raise argparse.ArgumentError(self, e)
|
||||
pairs.append((address, argfile))
|
||||
|
||||
end = 0
|
||||
pairs = sorted(pairs)
|
||||
@ -124,8 +127,6 @@ def pack3(args):
|
||||
except IOError as e:
|
||||
raise e
|
||||
|
||||
print('\r\n\033[1;31;40mOTA example should use following macro:\r\n\r\n #define OTA_EXAMPLE_APP_OFFSET 0x%x\r\n\033[0m'%(app_offset))
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='pack_fw v%s - ESP8266 ROM Bootloader Utility' % __version__, prog='pack_fw')
|
||||
|
||||
|
Reference in New Issue
Block a user