mirror of
https://gitee.com/beecue/fastbee.git
synced 2025-12-20 01:45:55 +08:00
添加智能灯固件代码
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
# OTA Example
|
||||
|
||||
**Notes:** *This guide is common for all ota examples*
|
||||
|
||||
## Overview
|
||||
|
||||
ESP32 application can do upgrading at runtime by downloading new image from specific server through Wi-Fi or Ethernet and then flash it into some partitions. There’re two ways in ESP-IDF to perform Over The Air (OTA) upgrading:
|
||||
|
||||
- Using the native APIs provided by `app_update` component.
|
||||
- Using simplified APIs provided by `esp_https_ota` component, which adds an abstraction layer over the native OTA APIs in order to upgrading with HTTPS protocol.
|
||||
|
||||
Both methods are demonstrated in OTA Demos under `native_ota_example` and `simple_ota_example` respectively.
|
||||
|
||||
For simplicity, the OTA examples choose the pre-defined partition table by enabling `CONFIG_PARTITION_TABLE_TWO_OTA` option in menuconfig, which supports three app partitions: factory, OTA_0 and OTA_1. For more information about partition table, please refer to [Partition Tables](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/partition-tables.html).
|
||||
|
||||
On first boot, the bootloader will load the factory app image (i.e. the example image) and then triggers an OTA upgrading. It will download a new image from HTTPS server and save it into the OTA_0 partition. It will update the ota_data partition automatically as well to indicate which app should boot from in the next reset. The bootloader will read the content in ota_data partition and run the selected application.
|
||||
|
||||
The OTA workflow can be demonstrated as in the following diagram:
|
||||
|
||||

|
||||
|
||||
## How to use the examples
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To run the OTA examples, you need an ESP32 dev board (e.g. ESP32-WROVER Kit) or ESP32 core board (e.g. ESP32-DevKitC). If you want to test the OTA with Ethernet, make sure your board has set up the Ethernet correctly. For extra information about setting up Ethernet, please refer to Ethernet examples.
|
||||
|
||||
### Configure the project
|
||||
|
||||
Open the project configuration menu (`idf.py menuconfig`).
|
||||
|
||||
In the `Example Connection Configuration` menu:
|
||||
|
||||
* Choose the network interface in `Connect using` option based on your board. Currently we support both Wi-Fi and Ethernet.
|
||||
* If you have selected the Wi-Fi interface, you also have to set:
|
||||
* Wi-Fi SSID and Wi-Fi password that your ESP32 will connect to
|
||||
* If you have selected the Ethernet interface, you also have to:
|
||||
* Set PHY model under `Ethernet PHY Device` option, e.g. IP101.
|
||||
|
||||
In the `Example Configuration` menu:
|
||||
|
||||
* Set the URL of the new firmware that you will download from in the `Firmware Upgrade URL` option, whose format should be `https://<host-ip-address>:<host-port>/<firmware-image-filename>`, e.g. `https://192.168.2.106:8070/hello-world.bin`
|
||||
* **Notes:** The server part of this URL (e.g. `192.168.2.106`) must match the **CN** field used when [generating the certificate and key](#run-https-server).
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build and flash the project.. This command will find if partition table has ota_data partition (as in our case) then ota_data will erase to initial. It allows to run the newly loaded app from a factory partition.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
### Run HTTPS Server
|
||||
|
||||
After a successful build, we need to create a self-signed certificate and run a simple HTTPS server as follows:
|
||||
|
||||

|
||||
|
||||
* Enter a directory where holds the root of the HTTPS server, e.g. `cd build`.
|
||||
* To create a new self-signed certificate and key, you can simply run command `openssl req -x509 -newkey rsa:2048 -keyout ca_key.pem -out ca_cert.pem -days 365 -nodes`.
|
||||
* When prompted for the `Common Name (CN)`, enter the name of the server that the ESP32 will connect to. Regarding this example, it is probably the IP address. The HTTPS client will make sure that the `CN` matches the address given in the HTTPS URL.
|
||||
* To start the HTTPS server, you can simply run command `openssl s_server -WWW -key ca_key.pem -cert ca_cert.pem -port 8070`.
|
||||
* In the same directory, there should be the firmware (e.g. hello-world.bin) that ESP32 will download later. It can be any other ESP-IDF application as well, as long as you also update the `Firmware Upgrade URL` in the menuconfig. The only difference is that when flashed via serial the binary is flashed to the "factory" app partition, and an OTA update flashes to an OTA app partition.
|
||||
* **Notes:** If you have any firewall software running that will block incoming access to port *8070*, configure it to allow access while running the example.
|
||||
* **Notes:** For Windows users, you should add `winpty` before `openssl` command:
|
||||
* `winpty openssl req -x509 -newkey rsa:2048 -keyout ca_key.pem -out ca_cert.pem -days 365 -nodes`
|
||||
* `winpty openssl s_server -WWW -key ca_key.pem -cert ca_cert.pem -port 8070`
|
||||
|
||||
### Flash Certificate to ESP32
|
||||
|
||||
Before you flash the example, make sure to copy the generated certificate to `server_certs` directory inside OTA example directory so that it can be flashed into ESP32 together with the firmware, e.g. `cp ca_cert.pem ../server_certs/`.
|
||||
|
||||
```
|
||||
cp ca_cert.pem /path/to/ota/example/server_certs/
|
||||
```
|
||||
|
||||
### Internal workflow of the OTA Example
|
||||
|
||||
When the example starts up, it will print "Starting OTA example" to the console and then:
|
||||
|
||||
1. Connect to the AP with configured SSID and Password (Wi-Fi case) or just by Ethernet.
|
||||
2. Connect to the HTTPS server and download the new image.
|
||||
3. Write the image to flash, and configure the next boot from this image.
|
||||
4. Reboot
|
||||
|
||||
If you want to rollback to factory app (or the first OTA partition when the factory partition do not exist) after the upgrade, then run the command `idf.py erase_otadata`. It can erase the ota_data partition to initial state.
|
||||
|
||||
**Notes:** This assumes that the partition table of this project is the one that is on the device.
|
||||
|
||||
### Output from HTTPS server
|
||||
|
||||
```bash
|
||||
FILE:hello-world.bin
|
||||
ACCEPT
|
||||
```
|
||||
|
||||
|
||||
## Support rollback
|
||||
|
||||
This feature allows you to roll back to the previous firmware if the app is not operable. Option `CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE` allows you to track the first boot of the application (see the ``Over The Air Updates (OTA)`` article).
|
||||
For ``native_ota_example``, added a bit of code to demonstrate how a rollback works. To use it, you need enable the `CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE` option in menuconfig and under the "Example Configuration" submenu to set "Number of the GPIO input for diagnostic" to manage the rollback process.
|
||||
|
||||
To trigger a rollback, this GPIO must be pulled low while the message `Diagnostics (5 sec)...` which will be on first boot.
|
||||
If GPIO is not pulled low then the operable of the app will be confirmed.
|
||||
|
||||
## Support the version of application
|
||||
|
||||
For ``native_ota_example``, code has been added to demonstrate how to check the version of the application and prevent infinite firmware updates. Only the application with the new version can be downloaded. Version checking is performed after the very first firmware image package has been received, which contains data about the firmware version. The application version can be taken from three places:
|
||||
|
||||
1. If `CONFIG_APP_PROJECT_VER_FROM_CONFIG` option is set, the value of `CONFIG_APP_PROJECT_VER` will be used.
|
||||
2. Else, if ``PROJECT_VER`` variable set in project Cmake/Makefile file, its value will be used.
|
||||
3. Else, if the ``$PROJECT_PATH/version.txt`` exists, its contents will be used as ``PROJECT_VER``.
|
||||
4. Else, if the project is located inside a Git repository, the output of ``git describe`` will be used.
|
||||
5. Otherwise, ``PROJECT_VER`` will be "1".
|
||||
|
||||
In ``native_ota_example``, ``$PROJECT_PATH/version.txt`` is used to define the version of app. Change the version in the file to compile the new firmware.
|
||||
|
||||
## 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 whether you can see the configured file (default hello-world.bin), by checking the output of the command `curl -v https://<host-ip-address>:<host-port>/<firmware-image-filename>`
|
||||
* 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 "idf.py erase_flash" between steps.
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(advanced_https_ota)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := advanced_https_ota
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,469 @@
|
||||
import re
|
||||
import os
|
||||
import socket
|
||||
from threading import Thread
|
||||
import ssl
|
||||
|
||||
from tiny_test_fw import DUT
|
||||
import ttfw_idf
|
||||
import random
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
import BaseHTTPServer
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
except ImportError:
|
||||
import http.server as BaseHTTPServer
|
||||
from http.server import SimpleHTTPRequestHandler
|
||||
|
||||
server_cert = "-----BEGIN CERTIFICATE-----\n" \
|
||||
"MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"\
|
||||
"BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n"\
|
||||
"aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjA3MDk1OTE2WhcNMjAwNjA2MDk1OTE2WjBF\n"\
|
||||
"MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n"\
|
||||
"ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"\
|
||||
"CgKCAQEAlzfCyv3mIv7TlLkObxunKfCdrJ/zgdANrsx0RBtpEPhV560hWJ0fEin0\n"\
|
||||
"nIOMpJSiF9E6QsPdr6Q+eogH4XnOMU9JE+iG743N1dPfGEzJvRlyct/Ck8SswKPC\n"\
|
||||
"9+VXsnOdZmUw9y/xtANbURA/TspvPzz3Avv382ffffrJGh7ooOmaZSCZFlSYHLZA\n"\
|
||||
"w/XlRr0sSRbLpFGY0gXjaAV8iHHiPDYLy4kZOepjV9U51xi+IGsL4w75zuMgsHyF\n"\
|
||||
"3nJeGYHgtGVBrkL0ZKG5udY0wcBjysjubDJC4iSlNiq2HD3fhs7j6CZddV2v845M\n"\
|
||||
"lVKNxP0kO4Uj4D8r+5USWC8JKfAwxQIDAQABo1AwTjAdBgNVHQ4EFgQU6OE7ssfY\n"\
|
||||
"IIPTDThiUoofUpsD5NwwHwYDVR0jBBgwFoAU6OE7ssfYIIPTDThiUoofUpsD5Nww\n"\
|
||||
"DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAXIlHS/FJWfmcinUAxyBd\n"\
|
||||
"/xd5Lu8ykeru6oaUCci+Vk9lyoMMES7lQ+b/00d5x7AcTawkTil9EWpBTPTOTraA\n"\
|
||||
"lzJMQhNKmSLk0iIoTtAJtSZgUSpIIozqK6lenxQQDsHbXKU6h+u9H6KZE8YcjsFl\n"\
|
||||
"6vL7sw9BVotw/VxfgjQ5OSGLgoLrdVT0z5C2qOuwOgz1c7jNiJhtMdwN+cOtnJp2\n"\
|
||||
"fuBgEYyE3eeuWogvkWoDcIA8r17Ixzkpq2oJsdvZcHZPIZShPKW2SHUsl98KDemu\n"\
|
||||
"y0pQyExmQUbwKE4vbFb9XuWCcL9XaOHQytyszt2DeD67AipvoBwVU7/LBOvqnsmy\n"\
|
||||
"hA==\n"\
|
||||
"-----END CERTIFICATE-----\n"
|
||||
|
||||
server_key = "-----BEGIN PRIVATE KEY-----\n"\
|
||||
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXN8LK/eYi/tOU\n"\
|
||||
"uQ5vG6cp8J2sn/OB0A2uzHREG2kQ+FXnrSFYnR8SKfScg4yklKIX0TpCw92vpD56\n"\
|
||||
"iAfhec4xT0kT6Ibvjc3V098YTMm9GXJy38KTxKzAo8L35Veyc51mZTD3L/G0A1tR\n"\
|
||||
"ED9Oym8/PPcC+/fzZ999+skaHuig6ZplIJkWVJgctkDD9eVGvSxJFsukUZjSBeNo\n"\
|
||||
"BXyIceI8NgvLiRk56mNX1TnXGL4gawvjDvnO4yCwfIXecl4ZgeC0ZUGuQvRkobm5\n"\
|
||||
"1jTBwGPKyO5sMkLiJKU2KrYcPd+GzuPoJl11Xa/zjkyVUo3E/SQ7hSPgPyv7lRJY\n"\
|
||||
"Lwkp8DDFAgMBAAECggEAfBhAfQE7mUByNbxgAgI5fot9eaqR1Nf+QpJ6X2H3KPwC\n"\
|
||||
"02sa0HOwieFwYfj6tB1doBoNq7i89mTc+QUlIn4pHgIowHO0OGawomeKz5BEhjCZ\n"\
|
||||
"4XeLYGSoODary2+kNkf2xY8JTfFEcyvGBpJEwc4S2VyYgRRx+IgnumTSH+N5mIKZ\n"\
|
||||
"SXWNdZIuHEmkwod+rPRXs6/r+PH0eVW6WfpINEbr4zVAGXJx2zXQwd2cuV1GTJWh\n"\
|
||||
"cPVOXLu+XJ9im9B370cYN6GqUnR3fui13urYbnWnEf3syvoH/zuZkyrVChauoFf8\n"\
|
||||
"8EGb74/HhXK7Q2s8NRakx2c7OxQifCbcy03liUMmyQKBgQDFAob5B/66N4Q2cq/N\n"\
|
||||
"MWPf98kYBYoLaeEOhEJhLQlKk0pIFCTmtpmUbpoEes2kCUbH7RwczpYko8tlKyoB\n"\
|
||||
"6Fn6RY4zQQ64KZJI6kQVsjkYpcP/ihnOY6rbds+3yyv+4uPX7Eh9sYZwZMggE19M\n"\
|
||||
"CkFHkwAjiwqhiiSlUxe20sWmowKBgQDEfx4lxuFzA1PBPeZKGVBTxYPQf+DSLCre\n"\
|
||||
"ZFg3ZmrxbCjRq1O7Lra4FXWD3dmRq7NDk79JofoW50yD8wD7I0B7opdDfXD2idO8\n"\
|
||||
"0dBnWUKDr2CAXyoLEINce9kJPbx4kFBQRN9PiGF7VkDQxeQ3kfS8CvcErpTKCOdy\n"\
|
||||
"5wOwBTwJdwKBgDiTFTeGeDv5nVoVbS67tDao7XKchJvqd9q3WGiXikeELJyuTDqE\n"\
|
||||
"zW22pTwMF+m3UEAxcxVCrhMvhkUzNAkANHaOatuFHzj7lyqhO5QPbh4J3FMR0X9X\n"\
|
||||
"V8VWRSg+jA/SECP9koOl6zlzd5Tee0tW1pA7QpryXscs6IEhb3ns5R2JAoGAIkzO\n"\
|
||||
"RmnhEOKTzDex611f2D+yMsMfy5BKK2f4vjLymBH5TiBKDXKqEpgsW0huoi8Gq9Uu\n"\
|
||||
"nvvXXAgkIyRYF36f0vUe0nkjLuYAQAWgC2pZYgNLJR13iVbol0xHJoXQUHtgiaJ8\n"\
|
||||
"GLYFzjHQPqFMpSalQe3oELko39uOC1CoJCHFySECgYBeycUnRBikCO2n8DNhY4Eg\n"\
|
||||
"9Y3oxcssRt6ea5BZwgW2eAYi7/XqKkmxoSoOykUt3MJx9+EkkrL17bxFSpkj1tvL\n"\
|
||||
"qvxn7egtsKjjgGNAxwXC4MwCvhveyUQQxtQb8AqGrGqo4jEEN0L15cnP38i2x1Uo\n"\
|
||||
"muhfskWf4MABV0yTUaKcGg==\n"\
|
||||
"-----END PRIVATE KEY-----\n"
|
||||
|
||||
|
||||
def get_my_ip():
|
||||
s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s1.connect(("8.8.8.8", 80))
|
||||
my_ip = s1.getsockname()[0]
|
||||
s1.close()
|
||||
return my_ip
|
||||
|
||||
|
||||
def get_server_status(host_ip, port):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_status = sock.connect_ex((host_ip, port))
|
||||
sock.close()
|
||||
if server_status == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def create_file(server_file, file_data):
|
||||
with open(server_file, "w+") as file:
|
||||
file.write(file_data)
|
||||
|
||||
|
||||
def get_ca_cert(ota_image_dir):
|
||||
os.chdir(ota_image_dir)
|
||||
server_file = os.path.join(ota_image_dir, "server_cert.pem")
|
||||
create_file(server_file, server_cert)
|
||||
|
||||
key_file = os.path.join(ota_image_dir, "server_key.pem")
|
||||
create_file(key_file, server_key)
|
||||
return server_file, key_file
|
||||
|
||||
|
||||
def https_request_handler():
|
||||
"""
|
||||
Returns a request handler class that handles broken pipe exception
|
||||
"""
|
||||
class RequestHandler(SimpleHTTPRequestHandler):
|
||||
def finish(self):
|
||||
try:
|
||||
if not self.wfile.closed:
|
||||
self.wfile.flush()
|
||||
self.wfile.close()
|
||||
except socket.error:
|
||||
pass
|
||||
self.rfile.close()
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.handle(self)
|
||||
except socket.error:
|
||||
pass
|
||||
|
||||
return RequestHandler
|
||||
|
||||
|
||||
def start_https_server(ota_image_dir, server_ip, server_port):
|
||||
server_file, key_file = get_ca_cert(ota_image_dir)
|
||||
requestHandler = https_request_handler()
|
||||
httpd = BaseHTTPServer.HTTPServer((server_ip, server_port), requestHandler)
|
||||
|
||||
httpd.socket = ssl.wrap_socket(httpd.socket,
|
||||
keyfile=key_file,
|
||||
certfile=server_file, server_side=True)
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
def start_chunked_server(ota_image_dir, server_port):
|
||||
server_file, key_file = get_ca_cert(ota_image_dir)
|
||||
chunked_server = subprocess.Popen(["openssl", "s_server", "-WWW", "-key", key_file, "-cert", server_file, "-port", str(server_port)])
|
||||
return chunked_server
|
||||
|
||||
|
||||
def redirect_handler_factory(url):
|
||||
"""
|
||||
Returns a request handler class that redirects to supplied `url`
|
||||
"""
|
||||
class RedirectHandler(SimpleHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
print("Sending resp, URL: " + url)
|
||||
self.send_response(301)
|
||||
self.send_header('Location', url)
|
||||
self.end_headers()
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.handle(self)
|
||||
except socket.error:
|
||||
pass
|
||||
|
||||
return RedirectHandler
|
||||
|
||||
|
||||
def start_redirect_server(ota_image_dir, server_ip, server_port, redirection_port):
|
||||
os.chdir(ota_image_dir)
|
||||
server_file, key_file = get_ca_cert(ota_image_dir)
|
||||
redirectHandler = redirect_handler_factory("https://" + server_ip + ":" + str(redirection_port) + "/advanced_https_ota.bin")
|
||||
|
||||
httpd = BaseHTTPServer.HTTPServer((server_ip, server_port),
|
||||
redirectHandler)
|
||||
|
||||
httpd.socket = ssl.wrap_socket(httpd.socket,
|
||||
keyfile=key_file,
|
||||
certfile=server_file, server_side=True)
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_advanced_https_ota_example(env, extra_data):
|
||||
"""
|
||||
This is a positive test case, which downloads complete binary file multiple number of times.
|
||||
Number of iterations can be specified in variable iterations.
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Fetch OTA image over HTTPS
|
||||
3. Reboot with the new OTA image
|
||||
"""
|
||||
dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT)
|
||||
# Number of iterations to validate OTA
|
||||
iterations = 3
|
||||
server_port = 8001
|
||||
# File to be downloaded. This file is generated after compilation
|
||||
bin_name = "advanced_https_ota.bin"
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, bin_name)
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
host_ip = get_my_ip()
|
||||
if (get_server_status(host_ip, server_port) is False):
|
||||
thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
|
||||
thread1.daemon = True
|
||||
thread1.start()
|
||||
dut1.start_app()
|
||||
for i in range(iterations):
|
||||
dut1.expect("Loaded app from partition at offset", timeout=30)
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
|
||||
print("Connected to AP with IP: {}".format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
thread1.close()
|
||||
dut1.expect("Starting Advanced OTA example", timeout=30)
|
||||
|
||||
print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + bin_name))
|
||||
dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + bin_name)
|
||||
dut1.expect("Loaded app from partition at offset", timeout=60)
|
||||
dut1.expect("Starting Advanced OTA example", timeout=30)
|
||||
dut1.reset()
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_advanced_https_ota_example_truncated_bin(env, extra_data):
|
||||
"""
|
||||
Working of OTA if binary file is truncated is validated in this test case.
|
||||
Application should return with error message in this case.
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Generate truncated binary file
|
||||
3. Fetch OTA image over HTTPS
|
||||
4. Check working of code if bin is truncated
|
||||
"""
|
||||
dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT)
|
||||
server_port = 8001
|
||||
# Original binary file generated after compilation
|
||||
bin_name = "advanced_https_ota.bin"
|
||||
# Truncated binary file to be generated from original binary file
|
||||
truncated_bin_name = "truncated.bin"
|
||||
# Size of truncated file to be grnerated. This value can range from 288 bytes (Image header size) to size of original binary file
|
||||
# truncated_bin_size is set to 64000 to reduce consumed by the test case
|
||||
truncated_bin_size = 64000
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, bin_name)
|
||||
f = open(binary_file, "r+")
|
||||
fo = open(os.path.join(dut1.app.binary_path, truncated_bin_name), "w+")
|
||||
fo.write(f.read(truncated_bin_size))
|
||||
fo.close()
|
||||
f.close()
|
||||
binary_file = os.path.join(dut1.app.binary_path, truncated_bin_name)
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
host_ip = get_my_ip()
|
||||
if (get_server_status(host_ip, server_port) is False):
|
||||
thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
|
||||
thread1.daemon = True
|
||||
thread1.start()
|
||||
dut1.start_app()
|
||||
dut1.expect("Loaded app from partition at offset", timeout=30)
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
|
||||
print("Connected to AP with IP: {}".format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
dut1.expect("Starting Advanced OTA example", timeout=30)
|
||||
|
||||
print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name))
|
||||
dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name)
|
||||
dut1.expect("Image validation failed, image is corrupted", timeout=30)
|
||||
os.remove(binary_file)
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_advanced_https_ota_example_truncated_header(env, extra_data):
|
||||
"""
|
||||
Working of OTA if headers of binary file are truncated is vaildated in this test case.
|
||||
Application should return with error message in this case.
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Generate binary file with truncated headers
|
||||
3. Fetch OTA image over HTTPS
|
||||
4. Check working of code if headers are not sent completely
|
||||
"""
|
||||
dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT)
|
||||
server_port = 8001
|
||||
# Original binary file generated after compilation
|
||||
bin_name = "advanced_https_ota.bin"
|
||||
# Truncated binary file to be generated from original binary file
|
||||
truncated_bin_name = "truncated_header.bin"
|
||||
# Size of truncated file to be grnerated. This value should be less than 288 bytes (Image header size)
|
||||
truncated_bin_size = 180
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, bin_name)
|
||||
f = open(binary_file, "r+")
|
||||
fo = open(os.path.join(dut1.app.binary_path, truncated_bin_name), "w+")
|
||||
fo.write(f.read(truncated_bin_size))
|
||||
fo.close()
|
||||
f.close()
|
||||
binary_file = os.path.join(dut1.app.binary_path, truncated_bin_name)
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
host_ip = get_my_ip()
|
||||
if (get_server_status(host_ip, server_port) is False):
|
||||
thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
|
||||
thread1.daemon = True
|
||||
thread1.start()
|
||||
dut1.start_app()
|
||||
dut1.expect("Loaded app from partition at offset", timeout=30)
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
|
||||
print("Connected to AP with IP: {}".format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
dut1.expect("Starting Advanced OTA example", timeout=30)
|
||||
|
||||
print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name))
|
||||
dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name)
|
||||
dut1.expect("advanced_https_ota_example: esp_https_ota_read_img_desc failed", timeout=30)
|
||||
os.remove(binary_file)
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_advanced_https_ota_example_random(env, extra_data):
|
||||
"""
|
||||
Working of OTA if random data is added in binary file are validated in this test case.
|
||||
Magic byte verification should fail in this case.
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Generate random binary image
|
||||
3. Fetch OTA image over HTTPS
|
||||
4. Check working of code for random binary file
|
||||
"""
|
||||
dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT)
|
||||
server_port = 8001
|
||||
# Random binary file to be generated
|
||||
random_bin_name = "random.bin"
|
||||
# Size of random binary file. 32000 is choosen, to reduce the time required to run the test-case
|
||||
random_bin_size = 32000
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, random_bin_name)
|
||||
fo = open(binary_file, "w+")
|
||||
# First byte of binary file is always set to zero. If first byte is generated randomly,
|
||||
# in some cases it may generate 0xE9 which will result in failure of testcase.
|
||||
fo.write(str(0))
|
||||
for i in range(random_bin_size - 1):
|
||||
fo.write(str(random.randrange(0,255,1)))
|
||||
fo.close()
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
host_ip = get_my_ip()
|
||||
if (get_server_status(host_ip, server_port) is False):
|
||||
thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
|
||||
thread1.daemon = True
|
||||
thread1.start()
|
||||
dut1.start_app()
|
||||
dut1.expect("Loaded app from partition at offset", timeout=30)
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
|
||||
print("Connected to AP with IP: {}".format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
dut1.expect("Starting Advanced OTA example", timeout=30)
|
||||
|
||||
print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + random_bin_name))
|
||||
dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + random_bin_name)
|
||||
dut1.expect("esp_ota_ops: OTA image has invalid magic byte", timeout=10)
|
||||
os.remove(binary_file)
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_advanced_https_ota_example_chunked(env, extra_data):
|
||||
"""
|
||||
This is a positive test case, which downloads complete binary file multiple number of times.
|
||||
Number of iterations can be specified in variable iterations.
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Fetch OTA image over HTTPS
|
||||
3. Reboot with the new OTA image
|
||||
"""
|
||||
dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT)
|
||||
# File to be downloaded. This file is generated after compilation
|
||||
bin_name = "advanced_https_ota.bin"
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, bin_name)
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
host_ip = get_my_ip()
|
||||
chunked_server = start_chunked_server(dut1.app.binary_path, 8070)
|
||||
dut1.start_app()
|
||||
dut1.expect("Loaded app from partition at offset", timeout=30)
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
|
||||
print("Connected to AP with IP: {}".format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
dut1.expect("Starting Advanced OTA example", timeout=30)
|
||||
|
||||
print("writing to device: {}".format("https://" + host_ip + ":8070/" + bin_name))
|
||||
dut1.write("https://" + host_ip + ":8070/" + bin_name)
|
||||
dut1.expect("Loaded app from partition at offset", timeout=60)
|
||||
dut1.expect("Starting Advanced OTA example", timeout=30)
|
||||
chunked_server.kill()
|
||||
os.remove(os.path.join(dut1.app.binary_path, "server_cert.pem"))
|
||||
os.remove(os.path.join(dut1.app.binary_path, "server_key.pem"))
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_advanced_https_ota_example_redirect_url(env, extra_data):
|
||||
"""
|
||||
This is a positive test case, which starts a server and a redirection server.
|
||||
Redirection server redirects http_request to different port
|
||||
Number of iterations can be specified in variable iterations.
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Fetch OTA image over HTTPS
|
||||
3. Reboot with the new OTA image
|
||||
"""
|
||||
dut1 = env.get_dut("advanced_https_ota_example", "examples/system/ota/advanced_https_ota", dut_class=ttfw_idf.ESP32DUT)
|
||||
server_port = 8001
|
||||
# Port to which the request should be redirecetd
|
||||
redirection_server_port = 8081
|
||||
# File to be downloaded. This file is generated after compilation
|
||||
bin_name = "advanced_https_ota.bin"
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, bin_name)
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("advanced_https_ota_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("advanced_https_ota_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
host_ip = get_my_ip()
|
||||
if (get_server_status(host_ip, server_port) is False):
|
||||
thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
|
||||
thread1.daemon = True
|
||||
thread1.start()
|
||||
thread2 = Thread(target=start_redirect_server, args=(dut1.app.binary_path, host_ip, redirection_server_port, server_port))
|
||||
thread2.daemon = True
|
||||
thread2.start()
|
||||
dut1.start_app()
|
||||
dut1.expect("Loaded app from partition at offset", timeout=30)
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
|
||||
print("Connected to AP with IP: {}".format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
thread1.close()
|
||||
thread2.close()
|
||||
dut1.expect("Starting Advanced OTA example", timeout=30)
|
||||
|
||||
print("writing to device: {}".format("https://" + host_ip + ":" + str(redirection_server_port) + "/" + bin_name))
|
||||
dut1.write("https://" + host_ip + ":" + str(redirection_server_port) + "/" + bin_name)
|
||||
dut1.expect("Loaded app from partition at offset", timeout=60)
|
||||
dut1.expect("Starting Advanced OTA example", timeout=30)
|
||||
dut1.reset()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_advanced_https_ota_example()
|
||||
test_examples_protocol_advanced_https_ota_example_chunked()
|
||||
test_examples_protocol_advanced_https_ota_example_redirect_url()
|
||||
test_examples_protocol_advanced_https_ota_example_truncated_bin()
|
||||
test_examples_protocol_advanced_https_ota_example_truncated_header()
|
||||
test_examples_protocol_advanced_https_ota_example_random()
|
||||
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "advanced_https_ota_example.c"
|
||||
INCLUDE_DIRS "."
|
||||
# Embed the server root certificate into the final binary
|
||||
EMBED_TXTFILES ${project_dir}/server_certs/ca_cert.pem)
|
||||
@@ -0,0 +1,31 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_FIRMWARE_UPGRADE_URL
|
||||
string "Firmware Upgrade URL"
|
||||
default "https://192.168.2.106:8070/hello-world.bin"
|
||||
help
|
||||
URL of server which hosts the firmware image.
|
||||
|
||||
config EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
|
||||
bool
|
||||
default y if EXAMPLE_FIRMWARE_UPGRADE_URL = "FROM_STDIN"
|
||||
|
||||
config EXAMPLE_SKIP_COMMON_NAME_CHECK
|
||||
bool "Skip server certificate CN fieldcheck"
|
||||
default n
|
||||
help
|
||||
This allows you to skip the validation of OTA server certificate CN field.
|
||||
|
||||
config EXAMPLE_SKIP_VERSION_CHECK
|
||||
bool "Skip firmware version check"
|
||||
default n
|
||||
help
|
||||
This allows you to skip the firmware version check.
|
||||
|
||||
config EXAMPLE_OTA_RECV_TIMEOUT
|
||||
int "OTA Receive Timeout"
|
||||
default 5000
|
||||
help
|
||||
Maximum time for reception
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,170 @@
|
||||
/* Advanced HTTPS 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_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_https_ota.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#include "esp_wifi.h"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "advanced_https_ota_example";
|
||||
extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
|
||||
extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
|
||||
|
||||
#define OTA_URL_SIZE 256
|
||||
|
||||
static esp_err_t validate_image_header(esp_app_desc_t *new_app_info)
|
||||
{
|
||||
if (new_app_info == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||
esp_app_desc_t running_app_info;
|
||||
if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
|
||||
}
|
||||
|
||||
#ifndef CONFIG_EXAMPLE_SKIP_VERSION_CHECK
|
||||
if (memcmp(new_app_info->version, running_app_info.version, sizeof(new_app_info->version)) == 0) {
|
||||
ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void advanced_ota_example_task(void *pvParameter)
|
||||
{
|
||||
ESP_LOGI(TAG, "Starting Advanced OTA example");
|
||||
|
||||
esp_err_t ota_finish_err = ESP_OK;
|
||||
esp_http_client_config_t config = {
|
||||
.url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL,
|
||||
.cert_pem = (char *)server_cert_pem_start,
|
||||
.timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
|
||||
char url_buf[OTA_URL_SIZE];
|
||||
if (strcmp(config.url, "FROM_STDIN") == 0) {
|
||||
example_configure_stdin_stdout();
|
||||
fgets(url_buf, OTA_URL_SIZE, stdin);
|
||||
int len = strlen(url_buf);
|
||||
url_buf[len - 1] = '\0';
|
||||
config.url = url_buf;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url");
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
|
||||
config.skip_cert_common_name_check = true;
|
||||
#endif
|
||||
|
||||
esp_https_ota_config_t ota_config = {
|
||||
.http_config = &config,
|
||||
};
|
||||
|
||||
esp_https_ota_handle_t https_ota_handle = NULL;
|
||||
esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "ESP HTTPS OTA Begin failed");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
esp_app_desc_t app_desc;
|
||||
err = esp_https_ota_get_img_desc(https_ota_handle, &app_desc);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_https_ota_read_img_desc failed");
|
||||
goto ota_end;
|
||||
}
|
||||
err = validate_image_header(&app_desc);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "image header verification failed");
|
||||
goto ota_end;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
err = esp_https_ota_perform(https_ota_handle);
|
||||
if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
|
||||
break;
|
||||
}
|
||||
// esp_https_ota_perform returns after every read operation which gives user the ability to
|
||||
// monitor the status of OTA upgrade by calling esp_https_ota_get_image_len_read, which gives length of image
|
||||
// data read so far.
|
||||
ESP_LOGD(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle));
|
||||
}
|
||||
|
||||
if (esp_https_ota_is_complete_data_received(https_ota_handle) != true) {
|
||||
// the OTA image was not completely received and user can customise the response to this situation.
|
||||
ESP_LOGE(TAG, "Complete data was not received.");
|
||||
}
|
||||
|
||||
ota_end:
|
||||
ota_finish_err = esp_https_ota_finish(https_ota_handle);
|
||||
if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) {
|
||||
ESP_LOGI(TAG, "ESP_HTTPS_OTA upgrade successful. Rebooting ...");
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
esp_restart();
|
||||
} else {
|
||||
if (ota_finish_err == ESP_ERR_OTA_VALIDATE_FAILED) {
|
||||
ESP_LOGE(TAG, "Image validation failed, image is corrupted");
|
||||
}
|
||||
ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed %d", ota_finish_err);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialize NVS.
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
// 1.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.
|
||||
// 2.NVS partition contains data in new format and cannot be recognized by this version of code.
|
||||
// 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 );
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
/* Ensure to disable any WiFi power save mode, this allows best throughput
|
||||
* and hence timings for overall OTA operation.
|
||||
*/
|
||||
esp_wifi_set_ps(WIFI_PS_NONE);
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
|
||||
xTaskCreate(&advanced_ota_example_task, "advanced_ota_example_task", 1024 * 8, NULL, 5, NULL);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
COMPONENT_EMBED_TXTFILES := ${PROJECT_PATH}/server_certs/ca_cert.pem
|
||||
@@ -0,0 +1,4 @@
|
||||
CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN"
|
||||
CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y
|
||||
CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y
|
||||
CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=3000
|
||||
@@ -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
|
||||
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjA3MDk1OTE2WhcNMjAwNjA2MDk1OTE2WjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEAlzfCyv3mIv7TlLkObxunKfCdrJ/zgdANrsx0RBtpEPhV560hWJ0fEin0
|
||||
nIOMpJSiF9E6QsPdr6Q+eogH4XnOMU9JE+iG743N1dPfGEzJvRlyct/Ck8SswKPC
|
||||
9+VXsnOdZmUw9y/xtANbURA/TspvPzz3Avv382ffffrJGh7ooOmaZSCZFlSYHLZA
|
||||
w/XlRr0sSRbLpFGY0gXjaAV8iHHiPDYLy4kZOepjV9U51xi+IGsL4w75zuMgsHyF
|
||||
3nJeGYHgtGVBrkL0ZKG5udY0wcBjysjubDJC4iSlNiq2HD3fhs7j6CZddV2v845M
|
||||
lVKNxP0kO4Uj4D8r+5USWC8JKfAwxQIDAQABo1AwTjAdBgNVHQ4EFgQU6OE7ssfY
|
||||
IIPTDThiUoofUpsD5NwwHwYDVR0jBBgwFoAU6OE7ssfYIIPTDThiUoofUpsD5Nww
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAXIlHS/FJWfmcinUAxyBd
|
||||
/xd5Lu8ykeru6oaUCci+Vk9lyoMMES7lQ+b/00d5x7AcTawkTil9EWpBTPTOTraA
|
||||
lzJMQhNKmSLk0iIoTtAJtSZgUSpIIozqK6lenxQQDsHbXKU6h+u9H6KZE8YcjsFl
|
||||
6vL7sw9BVotw/VxfgjQ5OSGLgoLrdVT0z5C2qOuwOgz1c7jNiJhtMdwN+cOtnJp2
|
||||
fuBgEYyE3eeuWogvkWoDcIA8r17Ixzkpq2oJsdvZcHZPIZShPKW2SHUsl98KDemu
|
||||
y0pQyExmQUbwKE4vbFb9XuWCcL9XaOHQytyszt2DeD67AipvoBwVU7/LBOvqnsmy
|
||||
hA==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(native_ota)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := native_ota
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Native OTA example
|
||||
|
||||
This example is based on `app_update` component's APIs.
|
||||
|
||||
## Configuration
|
||||
|
||||
Refer the README.md in the parent directory for the setup details.
|
||||
@@ -0,0 +1,385 @@
|
||||
import re
|
||||
import os
|
||||
import socket
|
||||
from threading import Thread
|
||||
import ssl
|
||||
|
||||
from tiny_test_fw import DUT
|
||||
import ttfw_idf
|
||||
import random
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
import BaseHTTPServer
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
except ImportError:
|
||||
import http.server as BaseHTTPServer
|
||||
from http.server import SimpleHTTPRequestHandler
|
||||
|
||||
server_cert = "-----BEGIN CERTIFICATE-----\n" \
|
||||
"MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"\
|
||||
"BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n"\
|
||||
"aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjA3MDk1OTE2WhcNMjAwNjA2MDk1OTE2WjBF\n"\
|
||||
"MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n"\
|
||||
"ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"\
|
||||
"CgKCAQEAlzfCyv3mIv7TlLkObxunKfCdrJ/zgdANrsx0RBtpEPhV560hWJ0fEin0\n"\
|
||||
"nIOMpJSiF9E6QsPdr6Q+eogH4XnOMU9JE+iG743N1dPfGEzJvRlyct/Ck8SswKPC\n"\
|
||||
"9+VXsnOdZmUw9y/xtANbURA/TspvPzz3Avv382ffffrJGh7ooOmaZSCZFlSYHLZA\n"\
|
||||
"w/XlRr0sSRbLpFGY0gXjaAV8iHHiPDYLy4kZOepjV9U51xi+IGsL4w75zuMgsHyF\n"\
|
||||
"3nJeGYHgtGVBrkL0ZKG5udY0wcBjysjubDJC4iSlNiq2HD3fhs7j6CZddV2v845M\n"\
|
||||
"lVKNxP0kO4Uj4D8r+5USWC8JKfAwxQIDAQABo1AwTjAdBgNVHQ4EFgQU6OE7ssfY\n"\
|
||||
"IIPTDThiUoofUpsD5NwwHwYDVR0jBBgwFoAU6OE7ssfYIIPTDThiUoofUpsD5Nww\n"\
|
||||
"DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAXIlHS/FJWfmcinUAxyBd\n"\
|
||||
"/xd5Lu8ykeru6oaUCci+Vk9lyoMMES7lQ+b/00d5x7AcTawkTil9EWpBTPTOTraA\n"\
|
||||
"lzJMQhNKmSLk0iIoTtAJtSZgUSpIIozqK6lenxQQDsHbXKU6h+u9H6KZE8YcjsFl\n"\
|
||||
"6vL7sw9BVotw/VxfgjQ5OSGLgoLrdVT0z5C2qOuwOgz1c7jNiJhtMdwN+cOtnJp2\n"\
|
||||
"fuBgEYyE3eeuWogvkWoDcIA8r17Ixzkpq2oJsdvZcHZPIZShPKW2SHUsl98KDemu\n"\
|
||||
"y0pQyExmQUbwKE4vbFb9XuWCcL9XaOHQytyszt2DeD67AipvoBwVU7/LBOvqnsmy\n"\
|
||||
"hA==\n"\
|
||||
"-----END CERTIFICATE-----\n"
|
||||
|
||||
server_key = "-----BEGIN PRIVATE KEY-----\n"\
|
||||
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXN8LK/eYi/tOU\n"\
|
||||
"uQ5vG6cp8J2sn/OB0A2uzHREG2kQ+FXnrSFYnR8SKfScg4yklKIX0TpCw92vpD56\n"\
|
||||
"iAfhec4xT0kT6Ibvjc3V098YTMm9GXJy38KTxKzAo8L35Veyc51mZTD3L/G0A1tR\n"\
|
||||
"ED9Oym8/PPcC+/fzZ999+skaHuig6ZplIJkWVJgctkDD9eVGvSxJFsukUZjSBeNo\n"\
|
||||
"BXyIceI8NgvLiRk56mNX1TnXGL4gawvjDvnO4yCwfIXecl4ZgeC0ZUGuQvRkobm5\n"\
|
||||
"1jTBwGPKyO5sMkLiJKU2KrYcPd+GzuPoJl11Xa/zjkyVUo3E/SQ7hSPgPyv7lRJY\n"\
|
||||
"Lwkp8DDFAgMBAAECggEAfBhAfQE7mUByNbxgAgI5fot9eaqR1Nf+QpJ6X2H3KPwC\n"\
|
||||
"02sa0HOwieFwYfj6tB1doBoNq7i89mTc+QUlIn4pHgIowHO0OGawomeKz5BEhjCZ\n"\
|
||||
"4XeLYGSoODary2+kNkf2xY8JTfFEcyvGBpJEwc4S2VyYgRRx+IgnumTSH+N5mIKZ\n"\
|
||||
"SXWNdZIuHEmkwod+rPRXs6/r+PH0eVW6WfpINEbr4zVAGXJx2zXQwd2cuV1GTJWh\n"\
|
||||
"cPVOXLu+XJ9im9B370cYN6GqUnR3fui13urYbnWnEf3syvoH/zuZkyrVChauoFf8\n"\
|
||||
"8EGb74/HhXK7Q2s8NRakx2c7OxQifCbcy03liUMmyQKBgQDFAob5B/66N4Q2cq/N\n"\
|
||||
"MWPf98kYBYoLaeEOhEJhLQlKk0pIFCTmtpmUbpoEes2kCUbH7RwczpYko8tlKyoB\n"\
|
||||
"6Fn6RY4zQQ64KZJI6kQVsjkYpcP/ihnOY6rbds+3yyv+4uPX7Eh9sYZwZMggE19M\n"\
|
||||
"CkFHkwAjiwqhiiSlUxe20sWmowKBgQDEfx4lxuFzA1PBPeZKGVBTxYPQf+DSLCre\n"\
|
||||
"ZFg3ZmrxbCjRq1O7Lra4FXWD3dmRq7NDk79JofoW50yD8wD7I0B7opdDfXD2idO8\n"\
|
||||
"0dBnWUKDr2CAXyoLEINce9kJPbx4kFBQRN9PiGF7VkDQxeQ3kfS8CvcErpTKCOdy\n"\
|
||||
"5wOwBTwJdwKBgDiTFTeGeDv5nVoVbS67tDao7XKchJvqd9q3WGiXikeELJyuTDqE\n"\
|
||||
"zW22pTwMF+m3UEAxcxVCrhMvhkUzNAkANHaOatuFHzj7lyqhO5QPbh4J3FMR0X9X\n"\
|
||||
"V8VWRSg+jA/SECP9koOl6zlzd5Tee0tW1pA7QpryXscs6IEhb3ns5R2JAoGAIkzO\n"\
|
||||
"RmnhEOKTzDex611f2D+yMsMfy5BKK2f4vjLymBH5TiBKDXKqEpgsW0huoi8Gq9Uu\n"\
|
||||
"nvvXXAgkIyRYF36f0vUe0nkjLuYAQAWgC2pZYgNLJR13iVbol0xHJoXQUHtgiaJ8\n"\
|
||||
"GLYFzjHQPqFMpSalQe3oELko39uOC1CoJCHFySECgYBeycUnRBikCO2n8DNhY4Eg\n"\
|
||||
"9Y3oxcssRt6ea5BZwgW2eAYi7/XqKkmxoSoOykUt3MJx9+EkkrL17bxFSpkj1tvL\n"\
|
||||
"qvxn7egtsKjjgGNAxwXC4MwCvhveyUQQxtQb8AqGrGqo4jEEN0L15cnP38i2x1Uo\n"\
|
||||
"muhfskWf4MABV0yTUaKcGg==\n"\
|
||||
"-----END PRIVATE KEY-----\n"
|
||||
|
||||
|
||||
def get_my_ip():
|
||||
s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s1.connect(("8.8.8.8", 80))
|
||||
my_ip = s1.getsockname()[0]
|
||||
s1.close()
|
||||
return my_ip
|
||||
|
||||
|
||||
def get_server_status(host_ip, port):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_status = sock.connect_ex((host_ip, port))
|
||||
sock.close()
|
||||
if server_status == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def create_file(server_file, file_data):
|
||||
with open(server_file, "w+") as file:
|
||||
file.write(file_data)
|
||||
|
||||
|
||||
def get_ca_cert(ota_image_dir):
|
||||
os.chdir(ota_image_dir)
|
||||
server_file = os.path.join(ota_image_dir, "server_cert.pem")
|
||||
create_file(server_file, server_cert)
|
||||
|
||||
key_file = os.path.join(ota_image_dir, "server_key.pem")
|
||||
create_file(key_file, server_key)
|
||||
return server_file, key_file
|
||||
|
||||
|
||||
def https_request_handler():
|
||||
"""
|
||||
Returns a request handler class that handles broken pipe exception
|
||||
"""
|
||||
class RequestHandler(SimpleHTTPRequestHandler):
|
||||
def finish(self):
|
||||
try:
|
||||
if not self.wfile.closed:
|
||||
self.wfile.flush()
|
||||
self.wfile.close()
|
||||
except socket.error:
|
||||
pass
|
||||
self.rfile.close()
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
BaseHTTPServer.BaseHTTPRequestHandler.handle(self)
|
||||
except socket.error:
|
||||
pass
|
||||
|
||||
return RequestHandler
|
||||
|
||||
|
||||
def start_https_server(ota_image_dir, server_ip, server_port):
|
||||
server_file, key_file = get_ca_cert(ota_image_dir)
|
||||
requestHandler = https_request_handler()
|
||||
httpd = BaseHTTPServer.HTTPServer((server_ip, server_port), requestHandler)
|
||||
|
||||
httpd.socket = ssl.wrap_socket(httpd.socket,
|
||||
keyfile=key_file,
|
||||
certfile=server_file, server_side=True)
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
def start_chunked_server(ota_image_dir, server_port):
|
||||
server_file, key_file = get_ca_cert(ota_image_dir)
|
||||
chunked_server = subprocess.Popen(["openssl", "s_server", "-WWW", "-key", key_file, "-cert", server_file, "-port", str(server_port)])
|
||||
return chunked_server
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_native_ota_example(env, extra_data):
|
||||
"""
|
||||
This is a positive test case, which downloads complete binary file multiple number of times.
|
||||
Number of iterations can be specified in variable iterations.
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Fetch OTA image over HTTPS
|
||||
3. Reboot with the new OTA image
|
||||
"""
|
||||
dut1 = env.get_dut("native_ota_example", "examples/system/ota/native_ota_example", dut_class=ttfw_idf.ESP32DUT)
|
||||
server_port = 8002
|
||||
# No. of times working of application to be validated
|
||||
iterations = 3
|
||||
# File to be downloaded. This file is generated after compilation
|
||||
bin_name = "native_ota.bin"
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, bin_name)
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("native_ota_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("native_ota_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
host_ip = get_my_ip()
|
||||
if (get_server_status(host_ip, server_port) is False):
|
||||
thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
|
||||
thread1.daemon = True
|
||||
thread1.start()
|
||||
dut1.start_app()
|
||||
for i in range(iterations):
|
||||
dut1.expect("Loaded app from partition at offset", timeout=30)
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
|
||||
print("Connected to AP with IP: {}".format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
thread1.close()
|
||||
dut1.expect("Starting OTA example", timeout=30)
|
||||
|
||||
print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + bin_name))
|
||||
dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + bin_name)
|
||||
dut1.expect("Loaded app from partition at offset", timeout=60)
|
||||
dut1.expect("Starting OTA example", timeout=30)
|
||||
dut1.reset()
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_native_ota_example_truncated_bin(env, extra_data):
|
||||
"""
|
||||
Working of OTA if binary file is truncated is validated in this test case.
|
||||
Application should return with error message in this case.
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Generate truncated binary file
|
||||
3. Fetch OTA image over HTTPS
|
||||
4. Check working of code if bin is truncated
|
||||
"""
|
||||
dut1 = env.get_dut("native_ota_example", "examples/system/ota/native_ota_example", dut_class=ttfw_idf.ESP32DUT)
|
||||
server_port = 8002
|
||||
# Original binary file generated after compilation
|
||||
bin_name = "native_ota.bin"
|
||||
# Truncated binary file to be generated from original binary file
|
||||
truncated_bin_name = "truncated.bin"
|
||||
# Size of truncated file to be grnerated. This value can range from 288 bytes (Image header size) to size of original binary file
|
||||
# truncated_bin_size is set to 64000 to reduce consumed by the test case
|
||||
truncated_bin_size = 64000
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, bin_name)
|
||||
f = open(binary_file, "r+")
|
||||
fo = open(os.path.join(dut1.app.binary_path, truncated_bin_name), "w+")
|
||||
fo.write(f.read(truncated_bin_size))
|
||||
fo.close()
|
||||
f.close()
|
||||
binary_file = os.path.join(dut1.app.binary_path, truncated_bin_name)
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("native_ota_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("native_ota_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
host_ip = get_my_ip()
|
||||
if (get_server_status(host_ip, server_port) is False):
|
||||
thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
|
||||
thread1.daemon = True
|
||||
thread1.start()
|
||||
dut1.start_app()
|
||||
dut1.expect("Loaded app from partition at offset", timeout=30)
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=60)
|
||||
print("Connected to AP with IP: {}".format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
dut1.expect("Starting OTA example", timeout=30)
|
||||
|
||||
print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name))
|
||||
dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name)
|
||||
dut1.expect("native_ota_example: Image validation failed, image is corrupted", timeout=20)
|
||||
os.remove(binary_file)
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_native_ota_example_truncated_header(env, extra_data):
|
||||
"""
|
||||
Working of OTA if headers of binary file are truncated is vaildated in this test case.
|
||||
Application should return with error message in this case.
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Generate binary file with truncated headers
|
||||
3. Fetch OTA image over HTTPS
|
||||
4. Check working of code if headers are not sent completely
|
||||
"""
|
||||
dut1 = env.get_dut("native_ota_example", "examples/system/ota/native_ota_example", dut_class=ttfw_idf.ESP32DUT)
|
||||
server_port = 8002
|
||||
# Original binary file generated after compilation
|
||||
bin_name = "native_ota.bin"
|
||||
# Truncated binary file to be generated from original binary file
|
||||
truncated_bin_name = "truncated_header.bin"
|
||||
# Size of truncated file to be grnerated. This value should be less than 288 bytes (Image header size)
|
||||
truncated_bin_size = 180
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, bin_name)
|
||||
f = open(binary_file, "r+")
|
||||
fo = open(os.path.join(dut1.app.binary_path, truncated_bin_name), "w+")
|
||||
fo.write(f.read(truncated_bin_size))
|
||||
fo.close()
|
||||
f.close()
|
||||
binary_file = os.path.join(dut1.app.binary_path, truncated_bin_name)
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("native_ota_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("native_ota_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
host_ip = get_my_ip()
|
||||
if (get_server_status(host_ip, server_port) is False):
|
||||
thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
|
||||
thread1.daemon = True
|
||||
thread1.start()
|
||||
dut1.start_app()
|
||||
dut1.expect("Loaded app from partition at offset", timeout=30)
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=60)
|
||||
print("Connected to AP with IP: {}".format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
dut1.expect("Starting OTA example", timeout=30)
|
||||
|
||||
print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name))
|
||||
dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + truncated_bin_name)
|
||||
dut1.expect("native_ota_example: received package is not fit len", timeout=20)
|
||||
os.remove(binary_file)
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_native_ota_example_random(env, extra_data):
|
||||
"""
|
||||
Working of OTA if random data is added in binary file are validated in this test case.
|
||||
Magic byte verification should fail in this case.
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Generate random binary image
|
||||
3. Fetch OTA image over HTTPS
|
||||
4. Check working of code for random binary file
|
||||
"""
|
||||
dut1 = env.get_dut("native_ota_example", "examples/system/ota/native_ota_example", dut_class=ttfw_idf.ESP32DUT)
|
||||
server_port = 8002
|
||||
# Random binary file to be generated
|
||||
random_bin_name = "random.bin"
|
||||
# Size of random binary file. 32000 is choosen, to reduce the time required to run the test-case
|
||||
random_bin_size = 32000
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, random_bin_name)
|
||||
fo = open(binary_file, "w+")
|
||||
# First byte of binary file is always set to zero. If first byte is generated randomly,
|
||||
# in some cases it may generate 0xE9 which will result in failure of testcase.
|
||||
fo.write(str(0))
|
||||
for i in range(random_bin_size - 1):
|
||||
fo.write(str(random.randrange(0,255,1)))
|
||||
fo.close()
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("native_ota_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("native_ota_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
host_ip = get_my_ip()
|
||||
if (get_server_status(host_ip, server_port) is False):
|
||||
thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, server_port))
|
||||
thread1.daemon = True
|
||||
thread1.start()
|
||||
dut1.start_app()
|
||||
dut1.expect("Loaded app from partition at offset", timeout=30)
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=60)
|
||||
print("Connected to AP with IP: {}".format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
dut1.expect("Starting OTA example", timeout=30)
|
||||
|
||||
print("writing to device: {}".format("https://" + host_ip + ":" + str(server_port) + "/" + random_bin_name))
|
||||
dut1.write("https://" + host_ip + ":" + str(server_port) + "/" + random_bin_name)
|
||||
dut1.expect("esp_ota_ops: OTA image has invalid magic byte", timeout=20)
|
||||
os.remove(binary_file)
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_native_ota_example_chunked(env, extra_data):
|
||||
"""
|
||||
This is a positive test case, which downloads complete binary file multiple number of times.
|
||||
Number of iterations can be specified in variable iterations.
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Fetch OTA image over HTTPS
|
||||
3. Reboot with the new OTA image
|
||||
"""
|
||||
dut1 = env.get_dut("native_ota_example", "examples/system/ota/native_ota_example", dut_class=ttfw_idf.ESP32DUT)
|
||||
# File to be downloaded. This file is generated after compilation
|
||||
bin_name = "native_ota.bin"
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, bin_name)
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("native_ota_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("native_ota_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
host_ip = get_my_ip()
|
||||
chunked_server = start_chunked_server(dut1.app.binary_path, 8070)
|
||||
dut1.start_app()
|
||||
dut1.expect("Loaded app from partition at offset", timeout=30)
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
|
||||
print("Connected to AP with IP: {}".format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
|
||||
dut1.expect("Starting OTA example", timeout=30)
|
||||
print("writing to device: {}".format("https://" + host_ip + ":8070/" + bin_name))
|
||||
dut1.write("https://" + host_ip + ":8070/" + bin_name)
|
||||
dut1.expect("Loaded app from partition at offset", timeout=60)
|
||||
dut1.expect("Starting OTA example", timeout=30)
|
||||
chunked_server.kill()
|
||||
os.remove(os.path.join(dut1.app.binary_path, "server_cert.pem"))
|
||||
os.remove(os.path.join(dut1.app.binary_path, "server_key.pem"))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_native_ota_example()
|
||||
test_examples_protocol_native_ota_example_chunked()
|
||||
test_examples_protocol_native_ota_example_truncated_bin()
|
||||
test_examples_protocol_native_ota_example_truncated_header()
|
||||
test_examples_protocol_native_ota_example_random()
|
||||
@@ -0,0 +1,5 @@
|
||||
# Embed the server root certificate into the final binary
|
||||
idf_build_get_property(project_dir PROJECT_DIR)
|
||||
idf_component_register(SRCS "native_ota_example.c"
|
||||
INCLUDE_DIRS "."
|
||||
EMBED_TXTFILES ${project_dir}/server_certs/ca_cert.pem)
|
||||
@@ -0,0 +1,42 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_FIRMWARE_UPG_URL
|
||||
string "Firmware Upgrade URL"
|
||||
default "https://192.168.2.106:8070/hello-world.bin"
|
||||
help
|
||||
URL of server which hosts the firmware image.
|
||||
|
||||
config EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
|
||||
bool
|
||||
default y if EXAMPLE_FIRMWARE_UPG_URL = "FROM_STDIN"
|
||||
|
||||
config EXAMPLE_SKIP_COMMON_NAME_CHECK
|
||||
bool "Skip server certificate CN fieldcheck"
|
||||
default n
|
||||
help
|
||||
This allows you to skip the validation of OTA server certificate CN field.
|
||||
|
||||
config EXAMPLE_SKIP_VERSION_CHECK
|
||||
bool "Skip firmware version check"
|
||||
default n
|
||||
help
|
||||
This allows you to skip the firmware version check.
|
||||
|
||||
config EXAMPLE_GPIO_DIAGNOSTIC
|
||||
int "Number of the GPIO input for diagnostic"
|
||||
range 0 39
|
||||
default 4
|
||||
help
|
||||
Used to demonstrate how a rollback works.
|
||||
The selected GPIO will be configured as an input with internal pull-up enabled.
|
||||
To trigger a rollback, this GPIO must be pulled low while the message
|
||||
`Diagnostics (5 sec)...` which will be on first boot.
|
||||
If GPIO is not pulled low then the operable of the app will be confirmed.
|
||||
|
||||
config EXAMPLE_OTA_RECV_TIMEOUT
|
||||
int "OTA Receive Timeout"
|
||||
default 5000
|
||||
help
|
||||
Maximum time for reception
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,6 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
COMPONENT_EMBED_TXTFILES := ${PROJECT_PATH}/server_certs/ca_cert.pem
|
||||
@@ -0,0 +1,335 @@
|
||||
/* 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 "esp_system.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_flash_partitions.h"
|
||||
#include "esp_partition.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "protocol_examples_common.h"
|
||||
#include "errno.h"
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#include "esp_wifi.h"
|
||||
#endif
|
||||
|
||||
#define BUFFSIZE 1024
|
||||
#define HASH_LEN 32 /* SHA-256 digest length */
|
||||
|
||||
static const char *TAG = "native_ota_example";
|
||||
/*an ota data write buffer ready to write to the flash*/
|
||||
static char ota_write_data[BUFFSIZE + 1] = { 0 };
|
||||
extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
|
||||
extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
|
||||
|
||||
#define OTA_URL_SIZE 256
|
||||
|
||||
static void http_cleanup(esp_http_client_handle_t client)
|
||||
{
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
static void __attribute__((noreturn)) task_fatal_error(void)
|
||||
{
|
||||
ESP_LOGE(TAG, "Exiting task due to fatal error...");
|
||||
(void)vTaskDelete(NULL);
|
||||
|
||||
while (1) {
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
static void print_sha256 (const uint8_t *image_hash, const char *label)
|
||||
{
|
||||
char hash_print[HASH_LEN * 2 + 1];
|
||||
hash_print[HASH_LEN * 2] = 0;
|
||||
for (int i = 0; i < HASH_LEN; ++i) {
|
||||
sprintf(&hash_print[i * 2], "%02x", image_hash[i]);
|
||||
}
|
||||
ESP_LOGI(TAG, "%s: %s", label, hash_print);
|
||||
}
|
||||
|
||||
static void infinite_loop(void)
|
||||
{
|
||||
int i = 0;
|
||||
ESP_LOGI(TAG, "When a new firmware is available on the server, press the reset button to download it");
|
||||
while(1) {
|
||||
ESP_LOGI(TAG, "Waiting for a new firmware ... %d", ++i);
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
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);
|
||||
|
||||
esp_http_client_config_t config = {
|
||||
.url = CONFIG_EXAMPLE_FIRMWARE_UPG_URL,
|
||||
.cert_pem = (char *)server_cert_pem_start,
|
||||
.timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
|
||||
char url_buf[OTA_URL_SIZE];
|
||||
if (strcmp(config.url, "FROM_STDIN") == 0) {
|
||||
example_configure_stdin_stdout();
|
||||
fgets(url_buf, OTA_URL_SIZE, stdin);
|
||||
int len = strlen(url_buf);
|
||||
url_buf[len - 1] = '\0';
|
||||
config.url = url_buf;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url");
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
|
||||
config.skip_cert_common_name_check = true;
|
||||
#endif
|
||||
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
if (client == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to initialise HTTP connection");
|
||||
task_fatal_error();
|
||||
}
|
||||
err = esp_http_client_open(client, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
|
||||
esp_http_client_cleanup(client);
|
||||
task_fatal_error();
|
||||
}
|
||||
esp_http_client_fetch_headers(client);
|
||||
|
||||
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);
|
||||
|
||||
int binary_file_length = 0;
|
||||
/*deal with all receive packet*/
|
||||
bool image_header_was_checked = false;
|
||||
while (1) {
|
||||
int data_read = esp_http_client_read(client, ota_write_data, BUFFSIZE);
|
||||
if (data_read < 0) {
|
||||
ESP_LOGE(TAG, "Error: SSL data read error");
|
||||
http_cleanup(client);
|
||||
task_fatal_error();
|
||||
} else if (data_read > 0) {
|
||||
if (image_header_was_checked == false) {
|
||||
esp_app_desc_t new_app_info;
|
||||
if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
|
||||
// check current version with downloading
|
||||
memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
|
||||
ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version);
|
||||
|
||||
esp_app_desc_t running_app_info;
|
||||
if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
|
||||
}
|
||||
|
||||
const esp_partition_t* last_invalid_app = esp_ota_get_last_invalid_partition();
|
||||
esp_app_desc_t invalid_app_info;
|
||||
if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version);
|
||||
}
|
||||
|
||||
// check current version with last invalid partition
|
||||
if (last_invalid_app != NULL) {
|
||||
if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) {
|
||||
ESP_LOGW(TAG, "New version is the same as invalid version.");
|
||||
ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version);
|
||||
ESP_LOGW(TAG, "The firmware has been rolled back to the previous version.");
|
||||
http_cleanup(client);
|
||||
infinite_loop();
|
||||
}
|
||||
}
|
||||
#ifndef CONFIG_EXAMPLE_SKIP_VERSION_CHECK
|
||||
if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) {
|
||||
ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
|
||||
http_cleanup(client);
|
||||
infinite_loop();
|
||||
}
|
||||
#endif
|
||||
|
||||
image_header_was_checked = true;
|
||||
|
||||
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
|
||||
http_cleanup(client);
|
||||
task_fatal_error();
|
||||
}
|
||||
ESP_LOGI(TAG, "esp_ota_begin succeeded");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "received package is not fit len");
|
||||
http_cleanup(client);
|
||||
task_fatal_error();
|
||||
}
|
||||
}
|
||||
err = esp_ota_write( update_handle, (const void *)ota_write_data, data_read);
|
||||
if (err != ESP_OK) {
|
||||
http_cleanup(client);
|
||||
task_fatal_error();
|
||||
}
|
||||
binary_file_length += data_read;
|
||||
ESP_LOGD(TAG, "Written image length %d", binary_file_length);
|
||||
} else if (data_read == 0) {
|
||||
/*
|
||||
* As esp_http_client_read never returns negative error code, we rely on
|
||||
* `errno` to check for underlying transport connectivity closure if any
|
||||
*/
|
||||
if (errno == ECONNRESET || errno == ENOTCONN) {
|
||||
ESP_LOGE(TAG, "Connection closed, errno = %d", errno);
|
||||
break;
|
||||
}
|
||||
if (esp_http_client_is_complete_data_received(client) == true) {
|
||||
ESP_LOGI(TAG, "Connection closed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "Total Write binary data length: %d", binary_file_length);
|
||||
if (esp_http_client_is_complete_data_received(client) != true) {
|
||||
ESP_LOGE(TAG, "Error in receiving complete file");
|
||||
http_cleanup(client);
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
err = esp_ota_end(update_handle);
|
||||
if (err != ESP_OK) {
|
||||
if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
|
||||
ESP_LOGE(TAG, "Image validation failed, image is corrupted");
|
||||
}
|
||||
ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
|
||||
http_cleanup(client);
|
||||
task_fatal_error();
|
||||
}
|
||||
|
||||
err = esp_ota_set_boot_partition(update_partition);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err));
|
||||
http_cleanup(client);
|
||||
task_fatal_error();
|
||||
}
|
||||
ESP_LOGI(TAG, "Prepare to restart system!");
|
||||
esp_restart();
|
||||
return ;
|
||||
}
|
||||
|
||||
static bool diagnostic(void)
|
||||
{
|
||||
gpio_config_t io_conf;
|
||||
io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << CONFIG_EXAMPLE_GPIO_DIAGNOSTIC);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
ESP_LOGI(TAG, "Diagnostics (5 sec)...");
|
||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||
|
||||
bool diagnostic_is_ok = gpio_get_level(CONFIG_EXAMPLE_GPIO_DIAGNOSTIC);
|
||||
|
||||
gpio_reset_pin(CONFIG_EXAMPLE_GPIO_DIAGNOSTIC);
|
||||
return diagnostic_is_ok;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
uint8_t sha_256[HASH_LEN] = { 0 };
|
||||
esp_partition_t partition;
|
||||
|
||||
// get sha256 digest for the partition table
|
||||
partition.address = ESP_PARTITION_TABLE_OFFSET;
|
||||
partition.size = ESP_PARTITION_TABLE_MAX_LEN;
|
||||
partition.type = ESP_PARTITION_TYPE_DATA;
|
||||
esp_partition_get_sha256(&partition, sha_256);
|
||||
print_sha256(sha_256, "SHA-256 for the partition table: ");
|
||||
|
||||
// get sha256 digest for bootloader
|
||||
partition.address = ESP_BOOTLOADER_OFFSET;
|
||||
partition.size = ESP_PARTITION_TABLE_OFFSET;
|
||||
partition.type = ESP_PARTITION_TYPE_APP;
|
||||
esp_partition_get_sha256(&partition, sha_256);
|
||||
print_sha256(sha_256, "SHA-256 for bootloader: ");
|
||||
|
||||
// get sha256 digest for running partition
|
||||
esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256);
|
||||
print_sha256(sha_256, "SHA-256 for current firmware: ");
|
||||
|
||||
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||
esp_ota_img_states_t ota_state;
|
||||
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
|
||||
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
|
||||
// run diagnostic function ...
|
||||
bool diagnostic_is_ok = diagnostic();
|
||||
if (diagnostic_is_ok) {
|
||||
ESP_LOGI(TAG, "Diagnostics completed successfully! Continuing execution ...");
|
||||
esp_ota_mark_app_valid_cancel_rollback();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Diagnostics failed! Start rollback to the previous version ...");
|
||||
esp_ota_mark_app_invalid_rollback_and_reboot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize NVS.
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
// 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 );
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
/* Ensure to disable any WiFi power save mode, this allows best throughput
|
||||
* and hence timings for overall OTA operation.
|
||||
*/
|
||||
esp_wifi_set_ps(WIFI_PS_NONE);
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
|
||||
xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
CONFIG_EXAMPLE_FIRMWARE_UPG_URL="FROM_STDIN"
|
||||
CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y
|
||||
CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y
|
||||
CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=3000
|
||||
@@ -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
|
||||
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjA3MDk1OTE2WhcNMjAwNjA2MDk1OTE2WjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEAlzfCyv3mIv7TlLkObxunKfCdrJ/zgdANrsx0RBtpEPhV560hWJ0fEin0
|
||||
nIOMpJSiF9E6QsPdr6Q+eogH4XnOMU9JE+iG743N1dPfGEzJvRlyct/Ck8SswKPC
|
||||
9+VXsnOdZmUw9y/xtANbURA/TspvPzz3Avv382ffffrJGh7ooOmaZSCZFlSYHLZA
|
||||
w/XlRr0sSRbLpFGY0gXjaAV8iHHiPDYLy4kZOepjV9U51xi+IGsL4w75zuMgsHyF
|
||||
3nJeGYHgtGVBrkL0ZKG5udY0wcBjysjubDJC4iSlNiq2HD3fhs7j6CZddV2v845M
|
||||
lVKNxP0kO4Uj4D8r+5USWC8JKfAwxQIDAQABo1AwTjAdBgNVHQ4EFgQU6OE7ssfY
|
||||
IIPTDThiUoofUpsD5NwwHwYDVR0jBBgwFoAU6OE7ssfYIIPTDThiUoofUpsD5Nww
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAXIlHS/FJWfmcinUAxyBd
|
||||
/xd5Lu8ykeru6oaUCci+Vk9lyoMMES7lQ+b/00d5x7AcTawkTil9EWpBTPTOTraA
|
||||
lzJMQhNKmSLk0iIoTtAJtSZgUSpIIozqK6lenxQQDsHbXKU6h+u9H6KZE8YcjsFl
|
||||
6vL7sw9BVotw/VxfgjQ5OSGLgoLrdVT0z5C2qOuwOgz1c7jNiJhtMdwN+cOtnJp2
|
||||
fuBgEYyE3eeuWogvkWoDcIA8r17Ixzkpq2oJsdvZcHZPIZShPKW2SHUsl98KDemu
|
||||
y0pQyExmQUbwKE4vbFb9XuWCcL9XaOHQytyszt2DeD67AipvoBwVU7/LBOvqnsmy
|
||||
hA==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
@@ -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(otatool)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := otatool
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# OTA Tool Example
|
||||
|
||||
This example demonstrates common operations the OTA tool [otatool.py](../../../../components/app_update/otatool.py) allows the user to perform:
|
||||
|
||||
- reading, writing and erasing OTA partitions,
|
||||
- switching boot partitions, and
|
||||
- switching to factory partition.
|
||||
|
||||
Users taking a look at this example should focus on the contents of the Python script [otatool_example.py](otatool_example.py) or shell script [otatool_example.sh](otatool_example.sh). The scripts contain
|
||||
programmatic invocation of the tool's functions via the Python API and command-line interface, respectively. Note
|
||||
that on Windows, the shell script example requires a POSIX-compatible environment via MSYS2/Git Bash/WSL etc.
|
||||
|
||||
The built application in this example outputs the currently running partition, whose output is used to verify if the tool switched OTA
|
||||
partitions succesfully. The built application binary is written to all OTA partitions at the start of the example to be able to determine the running
|
||||
partition for all switches performed.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Before running either of the example scripts, it is necessary to build and flash the firmware using the usual means:
|
||||
|
||||
Make:
|
||||
```bash
|
||||
make build flash
|
||||
```
|
||||
|
||||
CMake:
|
||||
```bash
|
||||
idf.py build flash
|
||||
```
|
||||
|
||||
### Running [otatool_example.py](otatool_example.py)
|
||||
|
||||
The example can be executed by running the script [otatool_example.py](otatool_example.py) or [otatool_example.sh](otatool_example.sh).
|
||||
|
||||
Python script:
|
||||
```bash
|
||||
python otatool_example.py
|
||||
```
|
||||
|
||||
Shell script:
|
||||
```
|
||||
./otatool_example.sh
|
||||
```
|
||||
|
||||
The script searches for valid target devices connected to the host and performs the operations on the first one it finds. This could present problems if there
|
||||
are multiple viable target devices attached to the host. To perform the operations on a specific device, specify the port it is attached to during script invocation ("/dev/ttyUSB2" for example):
|
||||
|
||||
Python script:
|
||||
```bash
|
||||
python otatool_example.py --port /dev/ttyUSB2
|
||||
```
|
||||
|
||||
|
||||
Shell script:
|
||||
```
|
||||
./otatool_example.sh /dev/ttyUSB2
|
||||
```
|
||||
|
||||
## Example output
|
||||
|
||||
Running the script produces the following output:
|
||||
|
||||
```
|
||||
Writing factory firmware to ota_0
|
||||
Writing factory firmware to ota_1
|
||||
Switching to factory app
|
||||
Switching to OTA slot 0
|
||||
Switching to OTA slot 1 (twice in a row)
|
||||
Switching to OTA slot 0 (twice in a row)
|
||||
Switching to factory app
|
||||
Switching to OTA slot 1
|
||||
|
||||
Partition tool operations performed successfully
|
||||
|
||||
```
|
||||
@@ -0,0 +1,34 @@
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_WIFI')
|
||||
def test_otatool_example(env, extra_data):
|
||||
dut = env.get_dut('otatool', 'examples/system/ota/otatool', dut_class=ttfw_idf.ESP32DUT)
|
||||
|
||||
# Verify factory firmware
|
||||
dut.start_app()
|
||||
dut.expect("OTA Tool Example")
|
||||
dut.expect("Example end")
|
||||
|
||||
# Close connection to DUT
|
||||
dut.receive_thread.exit()
|
||||
dut.port_inst.close()
|
||||
|
||||
script_path = os.path.join(os.getenv("IDF_PATH"), "examples", "system", "ota", "otatool", "otatool_example.py")
|
||||
binary_path = ""
|
||||
|
||||
for flash_file in dut.app.flash_files:
|
||||
if "otatool.bin" in flash_file[1]:
|
||||
binary_path = flash_file[1]
|
||||
break
|
||||
|
||||
subprocess.check_call([sys.executable, script_path, "--binary", binary_path])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_otatool_example()
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Demonstrates the use of otatool.py, a tool for performing ota partition level
|
||||
# operations.
|
||||
#
|
||||
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import os
|
||||
import sys
|
||||
import serial
|
||||
import subprocess
|
||||
import re
|
||||
import argparse
|
||||
|
||||
from subprocess import CalledProcessError
|
||||
|
||||
|
||||
def get_running_partition(port=None):
|
||||
# Monitor the serial output of target device. The firmware outputs the currently
|
||||
# running partition
|
||||
|
||||
IDF_PATH = os.path.expandvars("$IDF_PATH")
|
||||
sys.path.append(os.path.join(IDF_PATH, 'components', 'esptool_py', 'esptool'))
|
||||
import esptool
|
||||
|
||||
ESPTOOL_PY = os.path.join(IDF_PATH, "components", "esptool_py", "esptool", "esptool.py")
|
||||
|
||||
baud = os.environ.get("ESPTOOL_BAUD", esptool.ESPLoader.ESP_ROM_BAUD)
|
||||
|
||||
if not port:
|
||||
error_message = "Unable to obtain default target device port.\nSerial log:\n\n"
|
||||
try:
|
||||
# Check what esptool.py finds on what port the device is connected to
|
||||
output = subprocess.check_output([sys.executable, ESPTOOL_PY, "chip_id"]) # may raise CalledProcessError
|
||||
pattern = r"Serial port ([\S]+)"
|
||||
pattern = re.compile(pattern.encode())
|
||||
|
||||
port = re.search(pattern, output).group(1) # may raise AttributeError
|
||||
except CalledProcessError as e:
|
||||
raise Exception(error_message + e.output)
|
||||
except AttributeError:
|
||||
raise Exception(error_message + output)
|
||||
|
||||
serial_instance = serial.serial_for_url(port.decode("utf-8"), baud, do_not_open=True)
|
||||
|
||||
serial_instance.dtr = False
|
||||
serial_instance.rts = False
|
||||
|
||||
serial_instance.rts = True
|
||||
serial_instance.open()
|
||||
serial_instance.rts = False
|
||||
|
||||
# Read until example end and find the currently running partition string
|
||||
content = serial_instance.read_until(b"Example end")
|
||||
pattern = re.compile(b"Running partition: ([a-z0-9_]+)")
|
||||
running = re.search(pattern, content).group(1)
|
||||
|
||||
return running.decode("utf-8")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--port", default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
res = get_running_partition(args.port)
|
||||
except Exception as e:
|
||||
print(e.message)
|
||||
sys.exit(1)
|
||||
|
||||
print(res)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "otatool_main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -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,26 @@
|
||||
/* OTA Tool 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 "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_partition.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "OTA Tool Example");
|
||||
|
||||
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||
|
||||
// Display the running partition
|
||||
ESP_LOGI(TAG, "Running partition: %s", running->label);
|
||||
|
||||
ESP_LOGI(TAG, "Example end");
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Demonstrates the use of otatool.py, a tool for performing ota partition level
|
||||
# operations.
|
||||
#
|
||||
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
from get_running_partition import get_running_partition
|
||||
|
||||
|
||||
def assert_file_same(file1, file2, err):
|
||||
with open(file1, "rb") as f1:
|
||||
with open(file2, "rb") as f2:
|
||||
f1 = f1.read()
|
||||
f2 = f2.read()
|
||||
|
||||
if len(f1) < len(f2):
|
||||
f2 = f2[:len(f1)]
|
||||
else:
|
||||
f1 = f1[:len(f2)]
|
||||
|
||||
if not f1 == f2:
|
||||
raise Exception(err)
|
||||
|
||||
|
||||
def assert_running_partition(expected, port=None):
|
||||
running = get_running_partition(port)
|
||||
if running != expected:
|
||||
raise Exception("Running partition %s does not match expected %s" % (running, expected))
|
||||
|
||||
|
||||
def main():
|
||||
COMPONENTS_PATH = os.path.expandvars(os.path.join("$IDF_PATH", "components"))
|
||||
OTATOOL_DIR = os.path.join(COMPONENTS_PATH, "app_update")
|
||||
|
||||
sys.path.append(OTATOOL_DIR)
|
||||
from otatool import OtatoolTarget
|
||||
|
||||
parser = argparse.ArgumentParser("ESP-IDF OTA Tool Example")
|
||||
|
||||
parser.add_argument("--port", "-p", help="port where the device to perform operations on is connected")
|
||||
parser.add_argument("--binary", "-b", help="path to built example binary", default=os.path.join("build", "otatool.bin"))
|
||||
args = parser.parse_args()
|
||||
|
||||
target = OtatoolTarget(args.port)
|
||||
|
||||
print("Writing factory firmware to ota_0")
|
||||
target.write_ota_partition(0, args.binary)
|
||||
|
||||
print("Writing factory firmware to ota_1")
|
||||
target.write_ota_partition("ota_1", args.binary)
|
||||
|
||||
# Verify that the contents of the two ota slots are the same as that of the factory partition
|
||||
print("Checking written firmware to ota_0 and ota_1 match factory firmware")
|
||||
target.read_ota_partition("ota_0", "app0.bin")
|
||||
target.read_ota_partition(1, "app1.bin")
|
||||
|
||||
assert_file_same("app0.bin", args.binary, "Slot 0 app does not match factory app")
|
||||
assert_file_same("app1.bin", args.binary, "Slot 1 app does not match factory app")
|
||||
|
||||
# Switch to factory app
|
||||
print("Switching to factory app")
|
||||
target.erase_otadata()
|
||||
assert_running_partition("factory")
|
||||
|
||||
# Switch to slot 0
|
||||
print("Switching to OTA slot 0")
|
||||
target.switch_ota_partition(0)
|
||||
assert_running_partition("ota_0")
|
||||
|
||||
# Switch to slot 1 twice in a row
|
||||
print("Switching to OTA slot 1 (twice in a row)")
|
||||
target.switch_ota_partition(1)
|
||||
assert_running_partition("ota_1")
|
||||
target.switch_ota_partition("ota_1")
|
||||
assert_running_partition("ota_1")
|
||||
|
||||
# Switch to slot 0 twice in a row
|
||||
print("Switching to OTA slot 0 (twice in a row)")
|
||||
target.switch_ota_partition(0)
|
||||
assert_running_partition("ota_0")
|
||||
target.switch_ota_partition("ota_0")
|
||||
assert_running_partition("ota_0")
|
||||
|
||||
# Switch to factory app
|
||||
print("Switching to factory app")
|
||||
target.erase_otadata()
|
||||
assert_running_partition("factory")
|
||||
|
||||
# Switch to slot 1
|
||||
print("Switching to OTA slot 1")
|
||||
target.switch_ota_partition(1)
|
||||
assert_running_partition("ota_1")
|
||||
|
||||
# Example end and cleanup
|
||||
print("\nOTA tool operations executed successfully!")
|
||||
clean_files = ["app0.bin", "app1.bin"]
|
||||
for clean_file in clean_files:
|
||||
os.unlink(clean_file)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Demonstrates command-line interface of OTA Partitions Tool, otatool.py
|
||||
#
|
||||
#
|
||||
# $1 - serial port where target device to operate on is connnected to, by default the first found valid serial port
|
||||
# $2 - path to this example's built binary file (parttool.bin), by default $PWD/build/otatool.bin
|
||||
|
||||
PORT=$1
|
||||
OTATOOL_PY="python $IDF_PATH/components/app_update/otatool.py -q"
|
||||
|
||||
if [[ "$PORT" != "" ]]; then
|
||||
OTATOOL_PY="$OTATOOL_PY --port $PORT"
|
||||
fi
|
||||
|
||||
BINARY=$2
|
||||
|
||||
if [[ "$BINARY" == "" ]]; then
|
||||
BINARY=build/otatool.bin
|
||||
fi
|
||||
|
||||
function assert_file_same()
|
||||
{
|
||||
sz_a=$(stat -c %s $1)
|
||||
sz_b=$(stat -c %s $2)
|
||||
sz=$((sz_a < sz_b ? sz_a : sz_b))
|
||||
res=$(cmp -s -n $sz $1 $2) ||
|
||||
(echo "!!!!!!!!!!!!!!!!!!!"
|
||||
echo "FAILURE: $3"
|
||||
echo "!!!!!!!!!!!!!!!!!!!")
|
||||
}
|
||||
|
||||
function assert_running_partition()
|
||||
{
|
||||
running=$(python get_running_partition.py)
|
||||
if [[ "$running" != "$1" ]]; then
|
||||
echo "!!!!!!!!!!!!!!!!!!!"
|
||||
echo "FAILURE: Running partition '$running' does not match expected '$1'"
|
||||
echo "!!!!!!!!!!!!!!!!!!!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Flash the example firmware to OTA partitions. The first write uses slot number to identify OTA
|
||||
# partition, the second one uses the name.
|
||||
echo "Writing factory firmware to ota_0"
|
||||
$OTATOOL_PY write_ota_partition --slot 0 --input $BINARY
|
||||
|
||||
echo "Writing factory firmware to ota_1"
|
||||
$OTATOOL_PY write_ota_partition --name ota_1 --input $BINARY
|
||||
|
||||
# Read back the written firmware
|
||||
$OTATOOL_PY read_ota_partition --name ota_0 --output app0.bin
|
||||
$OTATOOL_PY read_ota_partition --slot 1 --output app1.bin
|
||||
|
||||
assert_file_same $BINARY app0.bin "Slot 0 app does not match factory app"
|
||||
assert_file_same $BINARY app1.bin "Slot 1 app does not match factory app"
|
||||
|
||||
# Switch to factory app
|
||||
echo "Switching to factory app"
|
||||
$OTATOOL_PY erase_otadata
|
||||
assert_running_partition factory
|
||||
|
||||
# Switch to slot 0
|
||||
echo "Switching to OTA slot 0"
|
||||
$OTATOOL_PY switch_ota_partition --slot 0
|
||||
assert_running_partition ota_0
|
||||
|
||||
# Switch to slot 1 twice in a row
|
||||
echo "Switching to OTA slot 1 (twice in a row)"
|
||||
$OTATOOL_PY switch_ota_partition --slot 1
|
||||
assert_running_partition ota_1
|
||||
$OTATOOL_PY switch_ota_partition --name ota_1
|
||||
assert_running_partition ota_1
|
||||
|
||||
# Switch to slot 0 twice in a row
|
||||
echo "Switching to OTA slot 0 (twice in a row)"
|
||||
$OTATOOL_PY switch_ota_partition --slot 0
|
||||
assert_running_partition ota_0
|
||||
$OTATOOL_PY switch_ota_partition --name ota_0
|
||||
assert_running_partition ota_0
|
||||
|
||||
# Switch to factory app
|
||||
echo "Switching to factory app"
|
||||
$OTATOOL_PY erase_otadata
|
||||
assert_running_partition factory
|
||||
|
||||
# Switch to slot 1
|
||||
echo "Switching to OTA slot 1"
|
||||
$OTATOOL_PY switch_ota_partition --slot 1
|
||||
assert_running_partition ota_1
|
||||
|
||||
# Example end and cleanup
|
||||
printf "\nPartition tool operations performed successfully\n"
|
||||
rm -rf app0.bin app1.bin
|
||||
@@ -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
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(simple_ota)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := simple_ota
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Simple OTA example
|
||||
|
||||
This example is based on `esp_https_ota` component's APIs.
|
||||
|
||||
## Configuration
|
||||
|
||||
Refer README.md in the parent directory for setup details
|
||||
@@ -0,0 +1,139 @@
|
||||
import re
|
||||
import os
|
||||
import socket
|
||||
from threading import Thread
|
||||
import ssl
|
||||
|
||||
from tiny_test_fw import DUT
|
||||
import ttfw_idf
|
||||
|
||||
try:
|
||||
import BaseHTTPServer
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
except ImportError:
|
||||
import http.server as BaseHTTPServer
|
||||
from http.server import SimpleHTTPRequestHandler
|
||||
|
||||
server_cert = "-----BEGIN CERTIFICATE-----\n" \
|
||||
"MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"\
|
||||
"BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n"\
|
||||
"aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjA3MDk1OTE2WhcNMjAwNjA2MDk1OTE2WjBF\n"\
|
||||
"MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n"\
|
||||
"ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"\
|
||||
"CgKCAQEAlzfCyv3mIv7TlLkObxunKfCdrJ/zgdANrsx0RBtpEPhV560hWJ0fEin0\n"\
|
||||
"nIOMpJSiF9E6QsPdr6Q+eogH4XnOMU9JE+iG743N1dPfGEzJvRlyct/Ck8SswKPC\n"\
|
||||
"9+VXsnOdZmUw9y/xtANbURA/TspvPzz3Avv382ffffrJGh7ooOmaZSCZFlSYHLZA\n"\
|
||||
"w/XlRr0sSRbLpFGY0gXjaAV8iHHiPDYLy4kZOepjV9U51xi+IGsL4w75zuMgsHyF\n"\
|
||||
"3nJeGYHgtGVBrkL0ZKG5udY0wcBjysjubDJC4iSlNiq2HD3fhs7j6CZddV2v845M\n"\
|
||||
"lVKNxP0kO4Uj4D8r+5USWC8JKfAwxQIDAQABo1AwTjAdBgNVHQ4EFgQU6OE7ssfY\n"\
|
||||
"IIPTDThiUoofUpsD5NwwHwYDVR0jBBgwFoAU6OE7ssfYIIPTDThiUoofUpsD5Nww\n"\
|
||||
"DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAXIlHS/FJWfmcinUAxyBd\n"\
|
||||
"/xd5Lu8ykeru6oaUCci+Vk9lyoMMES7lQ+b/00d5x7AcTawkTil9EWpBTPTOTraA\n"\
|
||||
"lzJMQhNKmSLk0iIoTtAJtSZgUSpIIozqK6lenxQQDsHbXKU6h+u9H6KZE8YcjsFl\n"\
|
||||
"6vL7sw9BVotw/VxfgjQ5OSGLgoLrdVT0z5C2qOuwOgz1c7jNiJhtMdwN+cOtnJp2\n"\
|
||||
"fuBgEYyE3eeuWogvkWoDcIA8r17Ixzkpq2oJsdvZcHZPIZShPKW2SHUsl98KDemu\n"\
|
||||
"y0pQyExmQUbwKE4vbFb9XuWCcL9XaOHQytyszt2DeD67AipvoBwVU7/LBOvqnsmy\n"\
|
||||
"hA==\n"\
|
||||
"-----END CERTIFICATE-----\n"
|
||||
|
||||
server_key = "-----BEGIN PRIVATE KEY-----\n"\
|
||||
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXN8LK/eYi/tOU\n"\
|
||||
"uQ5vG6cp8J2sn/OB0A2uzHREG2kQ+FXnrSFYnR8SKfScg4yklKIX0TpCw92vpD56\n"\
|
||||
"iAfhec4xT0kT6Ibvjc3V098YTMm9GXJy38KTxKzAo8L35Veyc51mZTD3L/G0A1tR\n"\
|
||||
"ED9Oym8/PPcC+/fzZ999+skaHuig6ZplIJkWVJgctkDD9eVGvSxJFsukUZjSBeNo\n"\
|
||||
"BXyIceI8NgvLiRk56mNX1TnXGL4gawvjDvnO4yCwfIXecl4ZgeC0ZUGuQvRkobm5\n"\
|
||||
"1jTBwGPKyO5sMkLiJKU2KrYcPd+GzuPoJl11Xa/zjkyVUo3E/SQ7hSPgPyv7lRJY\n"\
|
||||
"Lwkp8DDFAgMBAAECggEAfBhAfQE7mUByNbxgAgI5fot9eaqR1Nf+QpJ6X2H3KPwC\n"\
|
||||
"02sa0HOwieFwYfj6tB1doBoNq7i89mTc+QUlIn4pHgIowHO0OGawomeKz5BEhjCZ\n"\
|
||||
"4XeLYGSoODary2+kNkf2xY8JTfFEcyvGBpJEwc4S2VyYgRRx+IgnumTSH+N5mIKZ\n"\
|
||||
"SXWNdZIuHEmkwod+rPRXs6/r+PH0eVW6WfpINEbr4zVAGXJx2zXQwd2cuV1GTJWh\n"\
|
||||
"cPVOXLu+XJ9im9B370cYN6GqUnR3fui13urYbnWnEf3syvoH/zuZkyrVChauoFf8\n"\
|
||||
"8EGb74/HhXK7Q2s8NRakx2c7OxQifCbcy03liUMmyQKBgQDFAob5B/66N4Q2cq/N\n"\
|
||||
"MWPf98kYBYoLaeEOhEJhLQlKk0pIFCTmtpmUbpoEes2kCUbH7RwczpYko8tlKyoB\n"\
|
||||
"6Fn6RY4zQQ64KZJI6kQVsjkYpcP/ihnOY6rbds+3yyv+4uPX7Eh9sYZwZMggE19M\n"\
|
||||
"CkFHkwAjiwqhiiSlUxe20sWmowKBgQDEfx4lxuFzA1PBPeZKGVBTxYPQf+DSLCre\n"\
|
||||
"ZFg3ZmrxbCjRq1O7Lra4FXWD3dmRq7NDk79JofoW50yD8wD7I0B7opdDfXD2idO8\n"\
|
||||
"0dBnWUKDr2CAXyoLEINce9kJPbx4kFBQRN9PiGF7VkDQxeQ3kfS8CvcErpTKCOdy\n"\
|
||||
"5wOwBTwJdwKBgDiTFTeGeDv5nVoVbS67tDao7XKchJvqd9q3WGiXikeELJyuTDqE\n"\
|
||||
"zW22pTwMF+m3UEAxcxVCrhMvhkUzNAkANHaOatuFHzj7lyqhO5QPbh4J3FMR0X9X\n"\
|
||||
"V8VWRSg+jA/SECP9koOl6zlzd5Tee0tW1pA7QpryXscs6IEhb3ns5R2JAoGAIkzO\n"\
|
||||
"RmnhEOKTzDex611f2D+yMsMfy5BKK2f4vjLymBH5TiBKDXKqEpgsW0huoi8Gq9Uu\n"\
|
||||
"nvvXXAgkIyRYF36f0vUe0nkjLuYAQAWgC2pZYgNLJR13iVbol0xHJoXQUHtgiaJ8\n"\
|
||||
"GLYFzjHQPqFMpSalQe3oELko39uOC1CoJCHFySECgYBeycUnRBikCO2n8DNhY4Eg\n"\
|
||||
"9Y3oxcssRt6ea5BZwgW2eAYi7/XqKkmxoSoOykUt3MJx9+EkkrL17bxFSpkj1tvL\n"\
|
||||
"qvxn7egtsKjjgGNAxwXC4MwCvhveyUQQxtQb8AqGrGqo4jEEN0L15cnP38i2x1Uo\n"\
|
||||
"muhfskWf4MABV0yTUaKcGg==\n"\
|
||||
"-----END PRIVATE KEY-----\n"
|
||||
|
||||
|
||||
def get_my_ip():
|
||||
s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s1.connect(("8.8.8.8", 80))
|
||||
my_ip = s1.getsockname()[0]
|
||||
s1.close()
|
||||
return my_ip
|
||||
|
||||
|
||||
def start_https_server(ota_image_dir, server_ip, server_port):
|
||||
# parser = argparse.ArgumentParser()
|
||||
# parser.add_argument('-p', '--port', dest='port', type= int,
|
||||
# help= "Server Port", default= 8000)
|
||||
# args = parser.parse_args()
|
||||
os.chdir(ota_image_dir)
|
||||
|
||||
server_file = os.path.join(ota_image_dir, "server_cert.pem")
|
||||
cert_file_handle = open(server_file, "w+")
|
||||
cert_file_handle.write(server_cert)
|
||||
cert_file_handle.close()
|
||||
|
||||
key_file = os.path.join(ota_image_dir, "server_key.pem")
|
||||
key_file_handle = open("server_key.pem", "w+")
|
||||
key_file_handle.write(server_key)
|
||||
key_file_handle.close()
|
||||
|
||||
httpd = BaseHTTPServer.HTTPServer((server_ip, server_port),
|
||||
SimpleHTTPRequestHandler)
|
||||
|
||||
httpd.socket = ssl.wrap_socket(httpd.socket,
|
||||
keyfile=key_file,
|
||||
certfile=server_file, server_side=True)
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_simple_ota_example(env, extra_data):
|
||||
"""
|
||||
steps: |
|
||||
1. join AP
|
||||
2. Fetch OTA image over HTTPS
|
||||
3. Reboot with the new OTA image
|
||||
"""
|
||||
dut1 = env.get_dut("simple_ota_example", "examples/system/ota/simple_ota_example", dut_class=ttfw_idf.ESP32DUT)
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, "simple_ota.bin")
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("simple_ota_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("simple_ota_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
# start test
|
||||
host_ip = get_my_ip()
|
||||
thread1 = Thread(target=start_https_server, args=(dut1.app.binary_path, host_ip, 8000))
|
||||
thread1.daemon = True
|
||||
thread1.start()
|
||||
dut1.start_app()
|
||||
dut1.expect("Loaded app from partition at offset 0x10000", timeout=30)
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30)
|
||||
print("Connected to AP with IP: {}".format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
thread1.close()
|
||||
dut1.expect("Starting OTA example", timeout=30)
|
||||
|
||||
print("writing to device: {}".format("https://" + host_ip + ":8000/simple_ota.bin"))
|
||||
dut1.write("https://" + host_ip + ":8000/simple_ota.bin")
|
||||
dut1.expect("Loaded app from partition at offset 0x110000", timeout=60)
|
||||
dut1.expect("Starting OTA example", timeout=30)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_simple_ota_example()
|
||||
@@ -0,0 +1,5 @@
|
||||
# Embed the server root certificate into the final binary
|
||||
idf_build_get_property(project_dir PROJECT_DIR)
|
||||
idf_component_register(SRCS "simple_ota_example.c"
|
||||
INCLUDE_DIRS "."
|
||||
EMBED_TXTFILES ${project_dir}/server_certs/ca_cert.pem)
|
||||
@@ -0,0 +1,20 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_FIRMWARE_UPGRADE_URL
|
||||
string "firmware upgrade url endpoint"
|
||||
default "https://192.168.0.3:8070/hello-world.bin"
|
||||
help
|
||||
URL of server which hosts the firmware
|
||||
image.
|
||||
|
||||
config EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
|
||||
bool
|
||||
default y if EXAMPLE_FIRMWARE_UPGRADE_URL = "FROM_STDIN"
|
||||
|
||||
config EXAMPLE_SKIP_COMMON_NAME_CHECK
|
||||
bool "Skip server certificate CN fieldcheck"
|
||||
default n
|
||||
help
|
||||
This allows you to skip the validation of OTA server certificate CN field.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,6 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
COMPONENT_EMBED_TXTFILES := ${PROJECT_PATH}/server_certs/ca_cert.pem
|
||||
@@ -0,0 +1,132 @@
|
||||
/* 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 "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_https_ota.h"
|
||||
#include "protocol_examples_common.h"
|
||||
#include "string.h"
|
||||
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#include "esp_wifi.h"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "simple_ota_example";
|
||||
extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
|
||||
extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
|
||||
|
||||
#define OTA_URL_SIZE 256
|
||||
|
||||
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
|
||||
{
|
||||
switch (evt->event_id) {
|
||||
case HTTP_EVENT_ERROR:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
|
||||
break;
|
||||
case HTTP_EVENT_ON_CONNECTED:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
|
||||
break;
|
||||
case HTTP_EVENT_HEADER_SENT:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
|
||||
break;
|
||||
case HTTP_EVENT_ON_HEADER:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
|
||||
break;
|
||||
case HTTP_EVENT_ON_DATA:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
|
||||
break;
|
||||
case HTTP_EVENT_ON_FINISH:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
|
||||
break;
|
||||
case HTTP_EVENT_DISCONNECTED:
|
||||
ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void simple_ota_example_task(void *pvParameter)
|
||||
{
|
||||
ESP_LOGI(TAG, "Starting OTA example");
|
||||
|
||||
esp_http_client_config_t config = {
|
||||
.url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL,
|
||||
.cert_pem = (char *)server_cert_pem_start,
|
||||
.event_handler = _http_event_handler,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
|
||||
char url_buf[OTA_URL_SIZE];
|
||||
if (strcmp(config.url, "FROM_STDIN") == 0) {
|
||||
example_configure_stdin_stdout();
|
||||
fgets(url_buf, OTA_URL_SIZE, stdin);
|
||||
int len = strlen(url_buf);
|
||||
url_buf[len - 1] = '\0';
|
||||
config.url = url_buf;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url");
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
|
||||
config.skip_cert_common_name_check = true;
|
||||
#endif
|
||||
|
||||
esp_err_t ret = esp_https_ota(&config);
|
||||
if (ret == ESP_OK) {
|
||||
esp_restart();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Firmware upgrade failed");
|
||||
}
|
||||
while (1) {
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialize NVS.
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
// 1.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.
|
||||
// 2.NVS partition contains data in new format and cannot be recognized by this version of code.
|
||||
// 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);
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
/* Ensure to disable any WiFi power save mode, this allows best throughput
|
||||
* and hence timings for overall OTA operation.
|
||||
*/
|
||||
esp_wifi_set_ps(WIFI_PS_NONE);
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
|
||||
xTaskCreate(&simple_ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN"
|
||||
CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y
|
||||
@@ -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
|
||||
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAP4LF7E72HakMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjA3MDk1OTE2WhcNMjAwNjA2MDk1OTE2WjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEAlzfCyv3mIv7TlLkObxunKfCdrJ/zgdANrsx0RBtpEPhV560hWJ0fEin0
|
||||
nIOMpJSiF9E6QsPdr6Q+eogH4XnOMU9JE+iG743N1dPfGEzJvRlyct/Ck8SswKPC
|
||||
9+VXsnOdZmUw9y/xtANbURA/TspvPzz3Avv382ffffrJGh7ooOmaZSCZFlSYHLZA
|
||||
w/XlRr0sSRbLpFGY0gXjaAV8iHHiPDYLy4kZOepjV9U51xi+IGsL4w75zuMgsHyF
|
||||
3nJeGYHgtGVBrkL0ZKG5udY0wcBjysjubDJC4iSlNiq2HD3fhs7j6CZddV2v845M
|
||||
lVKNxP0kO4Uj4D8r+5USWC8JKfAwxQIDAQABo1AwTjAdBgNVHQ4EFgQU6OE7ssfY
|
||||
IIPTDThiUoofUpsD5NwwHwYDVR0jBBgwFoAU6OE7ssfYIIPTDThiUoofUpsD5Nww
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAXIlHS/FJWfmcinUAxyBd
|
||||
/xd5Lu8ykeru6oaUCci+Vk9lyoMMES7lQ+b/00d5x7AcTawkTil9EWpBTPTOTraA
|
||||
lzJMQhNKmSLk0iIoTtAJtSZgUSpIIozqK6lenxQQDsHbXKU6h+u9H6KZE8YcjsFl
|
||||
6vL7sw9BVotw/VxfgjQ5OSGLgoLrdVT0z5C2qOuwOgz1c7jNiJhtMdwN+cOtnJp2
|
||||
fuBgEYyE3eeuWogvkWoDcIA8r17Ixzkpq2oJsdvZcHZPIZShPKW2SHUsl98KDemu
|
||||
y0pQyExmQUbwKE4vbFb9XuWCcL9XaOHQytyszt2DeD67AipvoBwVU7/LBOvqnsmy
|
||||
hA==
|
||||
-----END CERTIFICATE-----
|
||||
Reference in New Issue
Block a user