Merge branch 'feature/Add_OTA_Demo' into 'master'

esp32 examples: Add OTA Demo

this demo would show you an OTA workflow and how to make it run.

See merge request !324
This commit is contained in:
Wu Jian Gang 2017-01-06 16:15:14 +08:00
commit e387a16e51
14 changed files with 501 additions and 36 deletions

View file

@ -378,4 +378,4 @@ const esp_partition_t *esp_ota_get_boot_partition(void)
ESP_LOGE(TAG, "not found current bin");
return NULL;
}
}
}

View file

@ -477,3 +477,12 @@ is set then the component can instruct the linker to link other binaries instead
.. _esp-idf-template: https://github.com/espressif/esp-idf-template
.. _GNU Make Manual: https://www.gnu.org/software/make/manual/make.html
.. _[_f1]: Actually, some components in esp-idf are "pure configuration" components that don't have a component.mk file, only a Makefile.projbuild and/or Kconfig.projbuild file. However, these components are unusual and most components have a component.mk file.
Custom sdkconfig defaults
-------------------------
For example projects or other projects where you don't want to specify a full sdkconfig configuration, but you do want to override some key values from the esp-idf defaults, it is possible to create a file ``sdkconfig.defaults`` in the project directory. This file will be used when running ``make defconfig``, or creating a new config from scratch.
To override the name of this file, set the ``SDKCONFIG_DEFAULTS`` environment variable.

View file

@ -6,11 +6,3 @@
PROJECT_NAME := ble_adv
include $(IDF_PATH)/make/project.mk
# Copy some defaults into the sdkconfig by default
# so BT stack is enabled
sdkconfig: sdkconfig.defaults
$(Q) cp $< $@
menuconfig: sdkconfig
defconfig: sdkconfig

View file

@ -8,11 +8,3 @@ PROJECT_NAME := blufi_demo
COMPONENT_ADD_INCLUDEDIRS := components/include
include $(IDF_PATH)/make/project.mk
# Copy some defaults into the sdkconfig by default
# so BT stack is enabled
sdkconfig: sdkconfig.defaults
$(Q) cp $< $@
menuconfig: sdkconfig
defconfig: sdkconfig

View file

@ -8,11 +8,3 @@ PROJECT_NAME := gatt_server_demos
COMPONENT_ADD_INCLUDEDIRS := components/include
include $(IDF_PATH)/make/project.mk
# Copy some defaults into the sdkconfig by default
# so BT stack is enabled
sdkconfig: sdkconfig.defaults
$(Q) cp $< $@
menuconfig: sdkconfig
defconfig: sdkconfig

View file

@ -8,11 +8,3 @@ PROJECT_NAME := gatt_client_demo
COMPONENT_ADD_INCLUDEDIRS := components/include
include $(IDF_PATH)/make/project.mk
# Copy some defaults into the sdkconfig by default
# so BT stack is enabled
sdkconfig: sdkconfig.defaults
$(Q) cp $< $@
menuconfig: sdkconfig
defconfig: sdkconfig

9
examples/26_ota/Makefile Normal file
View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

101
examples/26_ota/README.md Normal file
View file

@ -0,0 +1,101 @@
# 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 ESP32 can upgrade itself by downloading a new app "image" binary file, and storing it in flash.
In this example, the ESP32 has 3 images in flash: factory, 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.
Flashing the example over serial with "make flash" updates the factory app image. On first boot, the bootloader loads this factory app image which then performs an OTA update (triggered in the example code). The update downloads a new image from an http server and saves it into the OTA_0 partition. At this point the example code updates the ota_data partition to indicate the new app partition, and resets. The bootloader reads ota_data, determines the new OTA image has been selected, and runs it.
# Worflow
The OTA_workflow.png diagram demonstrates the overall workflow:
![OTA Workflow diagram](OTA_workflow.png)
## Step 1: Connect to AP
Connect your host PC to the same AP that you will use for the ESP32.
## 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 `01_hello_world` example.
Open a new terminal to run the HTTP server, then run these commands to build the example and start the server:
```
cd $IDF_PATH/examples/01_hello_world
make
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`.
NB: You've probably noticed there is nothing special about the "hello world" example when used for OTA updates. This is because any .bin app file which is built by esp-idf can be used as an app image for OTA. The only difference is whether it is written to a factory partition or an OTA partition.
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 "hello world" example, you can leave the default filename as-is.
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
# Troubleshooting
* Check your PC can ping the ESP32 at its IP, and that the IP, AP and other configuration settings are correct in menuconfig.
* Check if any firewall software is preventing incoming connections on the PC.
* Check you can see the configured file (default hello-world.bin) if you browse 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.

View file

@ -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 "/hello-world.bin"
help
Filename of the app image file to download for
the OTA update.
endmenu

View file

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

318
examples/26_ota/main/ota.c Normal file
View file

@ -0,0 +1,318 @@
/* 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 <string.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 "esp_partition.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.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 1024
#define TEXT_BUFFSIZE 1024
static const char *TAG = "ota";
/*an ota data write buffer ready to write to the flash*/
char ota_write_data[BUFFSIZE + 1] = { 0 };
/*an packet receive buffer*/
char text[BUFFSIZE + 1] = { 0 };
/* an image total length*/
int binary_file_length = 0;
/*socket id*/
int socket_id = -1;
char http_request[64] = {0};
/* operate handle : uninitialized value is zero ,every ota begin would exponential growth*/
esp_ota_handle_t out_handle = 0;
esp_partition_t operate_partition;
/* 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*/
int read_until(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;
}
/* resolve a packet from http socket
* return true if packet including \r\n\r\n that means http packet header finished,start to receive packet body
* otherwise return false
* */
bool resolve_pkg(char text[], int total_len, esp_ota_handle_t out_handle)
{
/* i means current position */
int i = 0, i_read_len = 0;
while (text[i] != 0 && i < total_len) {
i_read_len = read_until(&text[i], '\n', total_len);
// if we resolve \r\n line,we think packet header is finished
if (i_read_len == 2) {
int i_write_len = total_len - (i + 2);
memset(ota_write_data, 0, BUFFSIZE);
/*copy first http packet body to write buffer*/
memcpy(ota_write_data, &(text[i + 2]), i_write_len);
/*check write packet header first byte:0xE9 second byte:0x09 */
if (ota_write_data[0] == 0xE9 && i_write_len >= 2 && ota_write_data[1] == 0x09) {
ESP_LOGI(TAG, "OTA Write Header format Check OK. first byte is %02x ,second byte is %02x", ota_write_data[0], ota_write_data[1]);
} else {
ESP_LOGE(TAG, "OTA Write Header format Check Failed! first byte is %02x ,second byte is %02x", ota_write_data[0], ota_write_data[1]);
return false;
}
esp_err_t err = esp_ota_write( out_handle, (const void *)ota_write_data, i_write_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error: esp_ota_write failed! err=%x", err);
return false;
} else {
ESP_LOGI(TAG, "esp_ota_write header OK");
binary_file_length += i_write_len;
}
return true;
}
i += i_read_len;
}
return false;
}
bool connect_to_http_server()
{
ESP_LOGI(TAG, "Server IP: %s Server Port:%s", EXAMPLE_SERVER_IP, EXAMPLE_SERVER_PORT);
sprintf(http_request, "GET %s HTTP/1.1\r\nHost: %s:%s \r\n\r\n", EXAMPLE_FILENAME, 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;
}
bool ota_init()
{
esp_err_t err;
const esp_partition_t *esp_current_partition = esp_ota_get_boot_partition();
if (esp_current_partition->type != ESP_PARTITION_TYPE_APP) {
ESP_LOGE(TAG, "Error esp_current_partition->type != ESP_PARTITION_TYPE_APP");
return false;
}
esp_partition_t find_partition;
memset(&operate_partition, 0, sizeof(esp_partition_t));
/*choose which OTA image should we write to*/
switch (esp_current_partition->subtype) {
case ESP_PARTITION_SUBTYPE_APP_FACTORY:
find_partition.subtype = ESP_PARTITION_SUBTYPE_APP_OTA_0;
break;
case ESP_PARTITION_SUBTYPE_APP_OTA_0:
find_partition.subtype = ESP_PARTITION_SUBTYPE_APP_OTA_1;
break;
case ESP_PARTITION_SUBTYPE_APP_OTA_1:
find_partition.subtype = ESP_PARTITION_SUBTYPE_APP_OTA_0;
break;
default:
break;
}
find_partition.type = ESP_PARTITION_TYPE_APP;
const esp_partition_t *partition = esp_partition_find_first(find_partition.type, find_partition.subtype, NULL);
assert(partition != NULL);
memset(&operate_partition, 0, sizeof(esp_partition_t));
err = esp_ota_begin( partition, OTA_SIZE_UNKNOWN, &out_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed err=0x%x!", err);
return false;
} else {
memcpy(&operate_partition, partition, sizeof(esp_partition_t));
ESP_LOGI(TAG, "esp_ota_begin init OK");
return true;
}
return false;
}
void __attribute__((noreturn)) task_fatal_error()
{
ESP_LOGE(TAG, "Exiting task due to fatal error...");
close(socket_id);
(void)vTaskDelete(NULL);
}
void main_task(void *pvParameter)
{
esp_err_t err;
ESP_LOGI(TAG, "Starting OTA example...");
/* 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();
}
int res = -1;
/*send GET request to http server*/
res = send(socket_id, http_request, strlen(http_request), 0);
if (res == -1) {
ESP_LOGE(TAG, "Send GET request to server failed");
task_fatal_error();
} else {
ESP_LOGI(TAG, "Send GET request to server succeeded");
}
if ( ota_init() ) {
ESP_LOGI(TAG, "OTA Init succeeded");
} else {
ESP_LOGE(TAG, "OTA Init failed");
task_fatal_error();
}
bool pkg_body_start = false, flag = true;
/*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 && !pkg_body_start) { /*deal with packet header*/
memcpy(ota_write_data, text, buff_len);
pkg_body_start = resolve_pkg(text, buff_len, out_handle);
} else if (buff_len > 0 && pkg_body_start) { /*deal with packet body*/
memcpy(ota_write_data, text, buff_len);
err = esp_ota_write( out_handle, (const void *)ota_write_data, buff_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error: esp_ota_write failed! err=%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");
}
}
ESP_LOGI(TAG, "Total Write binary data length : %d", binary_file_length);
if (esp_ota_end(out_handle) != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_end failed!");
task_fatal_error();
}
err = esp_ota_set_boot_partition(&operate_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()
{
nvs_flash_init();
initialise_wifi();
xTaskCreate(&main_task, "main_task", 8192, NULL, 5, NULL);
}

View file

@ -0,0 +1,4 @@
# Default sdkconfig parameters to use the OTA
# partition table layout, with a 4MB flash size
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_TWO_OTA=y

View file

@ -11,6 +11,10 @@ KCONFIG_TOOL_DIR=$(IDF_PATH)/tools/kconfig
# unless it's overriden (happens for bootloader)
SDKCONFIG ?= $(PROJECT_PATH)/sdkconfig
# SDKCONFIG_DEFAULTS is an optional file containing default
# overrides (usually used for esp-idf examples)
SDKCONFIG_DEFAULTS ?= $(PROJECT_PATH)/sdkconfig.defaults
# reset MAKEFLAGS as the menuconfig makefile uses implicit compile rules
$(KCONFIG_TOOL_DIR)/mconf $(KCONFIG_TOOL_DIR)/conf:
MAKEFLAGS=$(ORIGINAL_MAKEFLAGS) CC=$(HOSTCC) LD=$(HOSTLD) \
@ -21,21 +25,29 @@ KCONFIG_TOOL_ENV=KCONFIG_AUTOHEADER=$(abspath $(BUILD_DIR_BASE)/include/sdkconfi
COMPONENT_KCONFIGS="$(COMPONENT_KCONFIGS)" KCONFIG_CONFIG=$(SDKCONFIG) \
COMPONENT_KCONFIGS_PROJBUILD="$(COMPONENT_KCONFIGS_PROJBUILD)"
menuconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig
menuconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig | defconfig
$(summary) MENUCONFIG
$(KCONFIG_TOOL_ENV) $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig
ifeq ("$(wildcard $(SDKCONFIG))","")
ifeq ("$(call prereq_if_explicit,defconfig)","")
# if not configuration is present and defconfig is not a target, run makeconfig
$(SDKCONFIG): menuconfig
# if not configuration is present and defconfig is not a target, run defconfig then menuconfig
$(SDKCONFIG): defconfig menuconfig
else
# otherwise, just defconfig
$(SDKCONFIG): defconfig
endif
endif
$(wildcard $(PROJECT_PATH)/sdkconfig.defaults): | menuconfig defconfig
cp $< $@
# defconfig creates a default config, based on SDKCONFIG_DEFAULTS if present
defconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig $(BUILD_DIR_BASE)
$(summary) DEFCONFIG
ifneq ("$(wildcard $(SDKCONFIG_DEFAULTS))","")
cp $(SDKCONFIG_DEFAULTS) $(SDKCONFIG)
endif
mkdir -p $(BUILD_DIR_BASE)/include/config
$(KCONFIG_TOOL_ENV) $(KCONFIG_TOOL_DIR)/conf --olddefconfig $(IDF_PATH)/Kconfig