examples: create example for both Python and CLI otatool interfaces

This commit is contained in:
Renz Christian Bagaporo 2019-05-27 11:10:00 +08:00
parent 5d41322412
commit 7f7a9272f0
5 changed files with 275 additions and 160 deletions

View File

@ -6,7 +6,9 @@ This example demonstrates common operations the OTA tool [otatool.py](../../../c
- 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). The script contains programmatic invocations of the tool [otatool.py](../../../components/app_update/otatool.py) in Python for the operations mentioned above; and can serve as a guide for users wanting to do the same in their applications.
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
@ -16,38 +18,46 @@ partition for all switches performed.
### Build and Flash
Before running the example script [otatool_example.py](otatool_example.py), it is necessary to build and flash the firmware using the usual means:
Before running either of the example scripts, it is necessary to build and flash the firmware using the usual means:
Make:
```bash
# If using Make
make build flash
```
# If using CMake
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). Either run it directly using
```bash
./otatool_example.py
```
or run it using
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
```
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:
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
# The target device is attached to /dev/ttyUSB2, for example
python otatool_example.py --port /dev/ttyUSB2
```
Shell script:
```
./otatool_example.sh /dev/ttyUSB2
```
## Example output
Running the script produces the following output:
@ -55,16 +65,13 @@ Running the script produces the following output:
```
Writing factory firmware to ota_0
Writing factory firmware to ota_1
Checking written firmware to ota_0 and ota_1 match factory firmware
Switching to ota partition name factory
Switching to ota partition name factory
Switching to ota partition slot 0
Switching to ota partition name ota_1
Switching to ota partition slot 1
Switching to ota partition name ota_0
Switching to ota partition slot 0
Switching to ota partition name factory
Switching to ota partition slot 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
OTA tool operations executed successfully!
```

View File

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

View File

@ -18,20 +18,12 @@
# limitations under the License.
import os
import sys
import subprocess
import argparse
import serial
import re
IDF_PATH = os.path.expandvars("$IDF_PATH")
OTATOOL_PY = os.path.join(IDF_PATH, "components", "app_update", "otatool.py")
ESPTOOL_PY = os.path.join(IDF_PATH, "components", "esptool_py", "esptool", "esptool.py")
INVOKE_ARGS = [sys.executable, OTATOOL_PY, "-q"]
from get_running_partition import get_running_partition
def sized_file_compare(file1, file2):
def assert_file_same(file1, file2, err):
with open(file1, "rb") as f1:
with open(file2, "rb") as f2:
f1 = f1.read()
@ -42,122 +34,22 @@ def sized_file_compare(file1, file2):
else:
f1 = f1[:len(f2)]
return f1 == f2
if not f1 == f2:
raise Exception(err)
def check(condition, message):
if not condition:
print("Error: " + message)
sys.exit(1)
def flash_example_firmware_to_ota_partitions(args):
# Invokes the command
#
# otatool.py -q write_ota_partition --slot <part_slot> or
# otatool.py -q write_ota_partition --name <part_name>
#
# to write the contents of a file to the specified ota partition (either using name or the slot number)
print("Writing factory firmware to ota_0")
invoke_args = INVOKE_ARGS + ["write_ota_partition", "--slot", "0", "--input", args.binary]
subprocess.check_call(invoke_args)
print("Writing factory firmware to ota_1")
invoke_args = INVOKE_ARGS + ["write_ota_partition", "--name", "ota_1", "--input", args.binary]
subprocess.check_call(invoke_args)
# 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")
# Invokes the command
#
# otatool.py -q read_ota_partition --slot <part_slot> or
# otatool.py -q read_ota_partition --name <part_name>
#
# to read the contents of a specified ota partition (either using name or the slot number) and write to a file
invoke_args = INVOKE_ARGS + ["read_ota_partition", "--slot", "0", "--output", "app_0.bin"]
subprocess.check_call(invoke_args)
invoke_args = INVOKE_ARGS + ["read_ota_partition", "--name", "ota_1", "--output", "app_1.bin"]
subprocess.check_call(invoke_args)
ota_same = sized_file_compare("app_0.bin", args.binary)
check(ota_same, "Slot 0 app does not match factory app")
ota_same = sized_file_compare("app_1.bin", args.binary)
check(ota_same, "Slot 1 app does not match factory app")
def check_running_ota_partition(expected, port=None):
# Monitor the serial output of target device. The firmware outputs the currently
# running partition. It should match the partition the otatool switched to.
if expected == 0 or expected == "ota_0":
expected = b"ota_0"
elif expected == 1 or expected == "ota_1":
expected = b"ota_1"
else:
expected = b"factory"
sys.path.append(os.path.join(IDF_PATH, 'components', 'esptool_py', 'esptool'))
import esptool
baud = os.environ.get("ESPTOOL_BAUD", esptool.ESPLoader.ESP_ROM_BAUD)
if not port:
# Check what esptool.py finds on what port the device is connected to
output = subprocess.check_output([sys.executable, ESPTOOL_PY, "chip_id"])
pattern = r"Serial port ([\S]+)"
pattern = re.compile(pattern.encode())
port = re.search(pattern, output).group(1)
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)
check(expected == running, "Running partition %s does not match expected %s" % (running, expected))
def switch_partition(part, port):
if isinstance(part, int):
spec = "slot"
else:
spec = "name"
print("Switching to ota partition %s %s" % (spec, str(part)))
if str(part) == "factory":
# Invokes the command
#
# otatool.py -q erase_otadata
#
# to erase the otadata partition, effectively setting boot firmware to
# factory
subprocess.check_call(INVOKE_ARGS + ["erase_otadata"])
else:
# Invokes the command
#
# otatool.py -q switch_otadata --slot <part_slot> or
# otatool.py -q switch_otadata --name <part_name>
#
# to switch to the indicated ota partition (either using name or the slot number)
subprocess.check_call(INVOKE_ARGS + ["switch_otadata", "--" + spec, str(part)])
check_running_ota_partition(part, port)
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():
global INVOKE_ARGS
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")
@ -165,29 +57,61 @@ def main():
parser.add_argument("--binary", "-b", help="path to built example binary", default=os.path.join("build", "otatool.bin"))
args = parser.parse_args()
if args.port:
INVOKE_ARGS += ["--port", args.port]
target = OtatoolTarget(args.port)
# Flash the factory firmware to all ota partitions
flash_example_firmware_to_ota_partitions(args)
print("Writing factory firmware to ota_0")
target.write_ota_partition(0, args.binary)
# Perform switching ota partitions
switch_partition("factory", args.port)
switch_partition("factory", args.port) # check switching to factory partition twice in a row
print("Writing factory firmware to ota_1")
target.write_ota_partition("ota_1", args.binary)
switch_partition(0, args.port)
# 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")
switch_partition("ota_1", args.port)
switch_partition(1, args.port) # check switching to ota_1 partition twice in a row
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_partition("ota_0", args.port)
switch_partition(0, args.port) # check switching to ota_0 partition twice in a row
# Switch to factory app
print("Switching to factory app")
target.erase_otadata()
assert_running_partition("factory")
switch_partition("factory", args.port)
# Switch to slot 0
print("Switching to OTA slot 0")
target.switch_ota_partition(0)
assert_running_partition("ota_0")
switch_partition(1, args.port) # check switching to ota_1 partition from factory
# 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__':

View File

@ -0,0 +1,95 @@
#!/bin/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

View File

@ -23,7 +23,9 @@ examples/build_system/cmake/idf_as_lib/run-esp32.sh
examples/build_system/cmake/idf_as_lib/run.sh
examples/storage/parttool/parttool_example.py
examples/storage/parttool/parttool_example.sh
examples/system/ota/otatool/get_running_partition.py
examples/system/ota/otatool/otatool_example.py
examples/system/ota/otatool/otatool_example.sh
tools/check_kconfigs.py
tools/check_python_dependencies.py
tools/ci/apply_bot_filter.py