diff --git a/examples/storage/parttool/README.md b/examples/storage/parttool/README.md index b84203e03f..8b888fcfdd 100644 --- a/examples/storage/parttool/README.md +++ b/examples/storage/parttool/README.md @@ -4,10 +4,11 @@ This example demonstrates common operations the partitions tool [parttool.py](.. - reading, writing and erasing partitions, - retrieving info on a certain partition, -- dumping the entire partition table, and -- generating a blank partition file. +- dumping the entire partition table -Users taking a look at this example should focus on the contents of the python script [parttool_example.py](parttool_example.py). The script contains programmatic invocations of [parttool.py](../../../components/partition_table/parttool.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 [parttool_example.py](parttool_example.py) or shell script [parttool_example.sh](parttool_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 example performs the operations mentioned above in a straightforward manner: it performs writes to partitions and then verifies correct content by reading it back. For partitions, contents are compared to the originally written file. For the partition table, contents are verified against the partition table CSV @@ -17,50 +18,54 @@ file. An erased partition's contents is compared to a generated blank file. ### Build and Flash -Before running the example script [parttool_example.py](parttool_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 [parttool_example.py](parttool_example.py) -The example can be executed by running the script [parttool_example.py](parttool_example.py). Either run it directly using - -```bash -./parttool_example.py -``` - -or run it using +The example can be executed by running the script [parttool_example.py](parttool_example.py) or [parttool_example.sh](parttool_example.sh). +Python script: ```bash python parttool_example.py ``` +Shell script: +``` +./parttool_example.sh +``` + The script searches for valid target devices connected to the host and performs the operations on the first one it finds. To perform the operations on a specific device, specify the port it is attached to during script invocation: +Python script: ```bash -# The target device is attached to /dev/ttyUSB2, for example python parttool_example.py --port /dev/ttyUSB2 ``` +Shell script: +``` +./parttool_example.sh /dev/ttyUSB2 +``` + ## Example output Running the script produces the following output: ``` Checking if device app binary matches built binary -Checking if device partition table matches partition table csv -Retrieving data partition offset and size Found data partition at offset 0x110000 with size 0x10000 Writing to data partition Reading data partition Erasing data partition -Generating blank data partition file Reading data partition Partition tool operations performed successfully! diff --git a/examples/storage/parttool/parttool_example.py b/examples/storage/parttool/parttool_example.py index 24b8fcd18f..bf2c6f7cdf 100755 --- a/examples/storage/parttool/parttool_example.py +++ b/examples/storage/parttool/parttool_example.py @@ -18,19 +18,12 @@ # limitations under the License. import os import sys -import subprocess import argparse -IDF_PATH = os.path.expandvars("$IDF_PATH") - -PARTTOOL_PY = os.path.join(IDF_PATH, "components", "partition_table", "parttool.py") - -PARTITION_TABLE_OFFSET = 0x8000 - -INVOKE_ARGS = [sys.executable, PARTTOOL_PY, "-q", "--partition-table-offset", str(PARTITION_TABLE_OFFSET)] +PARTITION_TABLE_DIR = os.path.join("components", "partition_table", "") -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() @@ -41,121 +34,17 @@ def sized_file_compare(file1, file2): else: f1 = f1[:len(f2)] - return f1 == f2 - - -def check(condition, message): - if not condition: - print("Error: " + message) - sys.exit(1) - - -def write_data_partition(size): - print("Writing to data partition") - with open("write.bin", "wb") as f: - # Create a file to write to the data partition with randomly generated content - f.write(os.urandom(int(size, 16))) - - # Invokes the command - # - # parttool.py --partition-table-offset 0x8000 -q --partition-name storage write_partition --input write.bin - # - # to write the contents of a file to a partition in the device. - invoke_args = INVOKE_ARGS + ["--partition-name", "storage", "write_partition", "--input", f.name] - subprocess.check_call(invoke_args) - return f.name - - -def read_data_partition(): - print("Reading data partition") - # Invokes the command - # - # parttool.py --partition-table-offset 0x8000 -q --partition-name storage read_partition --output read.bin - # - # to read the contents of a partition in the device, which is then written to a file. - f = "read.bin" - invoke_args = INVOKE_ARGS + ["--partition-name", "storage", "read_partition", "--output", f] - subprocess.check_call(invoke_args) - return f - - -def get_data_partition_info(): - print("Retrieving data partition offset and size") - # Invokes the command - # - # parttool.py --partition-table-offset 0x8000 -q --partition-name storage get_partition_info --info offset size - # - # to get the offset and size of a partition named 'storage'. - invoke_args = INVOKE_ARGS + ["--partition-name", "storage", "get_partition_info", "--info", "offset", "size"] - - (offset, size) = subprocess.check_output(invoke_args).strip().split(b" ") - return (offset, size) - - -def check_app(args): - print("Checking if device app binary matches built binary") - # Invokes the command - # - # parttool.py --partition-table-offset 0x8000 --partition-type app --partition-subtype factory read_partition --output app.bin" - # - # to read the app binary and write it to a file. The read app binary is compared to the built binary in the build folder. - invoke_args = INVOKE_ARGS + ["--partition-type", "app", "--partition-subtype", "factory", "read_partition", "--output", "app.bin"] - subprocess.check_call(invoke_args) - - app_same = sized_file_compare("app.bin", args.binary) - check(app_same, "Device app binary does not match built binary") - - -def check_partition_table(): - sys.path.append(os.path.join(IDF_PATH, "components", "partition_table")) - import gen_esp32part as gen - - print("Checking if device partition table matches partition table csv") - # Invokes the command - # - # parttool.py --partition-table-offset 0x8000 get_partition_info --table table.bin - # - # to read the device partition table and write it to a file. The read partition table is compared to - # the partition table csv. - invoke_args = INVOKE_ARGS + ["get_partition_info", "--table", "table.bin"] - subprocess.check_call(invoke_args) - - with open("table.bin", "rb") as read: - partition_table_csv = os.path.join(IDF_PATH, "examples", "storage", "parttool", "partitions_example.csv") - with open(partition_table_csv, "r") as csv: - read = gen.PartitionTable.from_binary(read.read()) - csv = gen.PartitionTable.from_csv(csv.read()) - check(read == csv, "Device partition table does not match csv partition table") - - -def erase_data_partition(): - print("Erasing data partition") - # Invokes the command - # - # parttool.py --partition-table-offset 0x8000 --partition-name storage erase_partition - # - # to erase the 'storage' partition. - invoke_args = INVOKE_ARGS + ["--partition-name", "storage", "erase_partition"] - subprocess.check_call(invoke_args) - - -def generate_blank_data_file(): - print("Generating blank data partition file") - - # Invokes the command - # - # parttool.py --partition-table-offset 0x8000 --partition-name storage generate_blank_partition_file --output blank.bin - # - # to generate a blank partition file and write it to a file. The blank partition file has the same size as the - # 'storage' partition. - f = "blank.bin" - invoke_args = INVOKE_ARGS + ["--partition-name", "storage", "generate_blank_partition_file", "--output", f] - subprocess.check_call(invoke_args) - return f + if not f1 == f2: + raise Exception(err) def main(): - global INVOKE_ARGS + COMPONENTS_PATH = os.path.expandvars(os.path.join("$IDF_PATH", "components")) + PARTTOOL_DIR = os.path.join(COMPONENTS_PATH, "partition_table") + + sys.path.append(PARTTOOL_DIR) + from parttool import PartitionName, PartitionType, ParttoolTarget + from gen_empty_partition import generate_blanked_file parser = argparse.ArgumentParser("ESP-IDF Partitions Tool Example") @@ -164,43 +53,53 @@ def main(): args = parser.parse_args() - if args.port: - INVOKE_ARGS += ["--port", args.port] + target = ParttoolTarget(args.port) - # Before proceeding, do checks to verify whether the app and partition table in the device matches the built binary and - # the generated partition table during build - check_app(args) - check_partition_table() + # Read app partition and save the contents to a file. The app partition is identified + # using type-subtype combination + print("Checking if device app binary matches built binary") + factory = PartitionType("app", "factory") + target.read_partition(factory, "app.bin") + assert_file_same(args.binary, "app.bin", "Device app binary does not match built binary") - # Get the offset and size of the data partition - (offset, size) = get_data_partition_info() + # Retrieve info on data storage partition, this time identifying it by name. + storage = PartitionName("storage") + storage_info = target.get_partition_info(storage) + print("Found data partition at offset 0x{:x} with size 0x{:x}".format(storage_info.offset, storage_info.size)) - print("Found data partition at offset %s with size %s" % (offset, size)) + # Create a file whose contents will be written to the storage partition + with open("write.bin", "wb") as f: + # Create a file to write to the data partition with randomly generated content + f.write(os.urandom(storage_info.size)) - # Write a generated file of random bytes to the found data partition - written = write_data_partition(size) + # Write the contents of the created file to storage partition + print("Writing to data partition") + target.write_partition(storage, "write.bin") - # Read back the contents of the data partition - read = read_data_partition() + # Read back the contents of the storage partition + print("Reading data partition") + target.read_partition(storage, "read.bin") - # Compare the written and read back data - data_same = sized_file_compare(read, written) - check(data_same, "Read contents of the data partition does not match written data") + assert_file_same("write.bin", "read.bin", "Read contents of storage partition does not match source file contents") - # Erase the data partition - erase_data_partition() + # Erase contents of the storage partition + print("Erasing data partition") + target.erase_partition(storage) - # Read back the erase data partition, which should be all 0xFF's after erasure - read = read_data_partition() + # Read back the erased data partition + print("Reading data partition") + target.read_partition(storage, "read.bin") - # Generate blank partition file (all 0xFF's) - blank = generate_blank_data_file() + # Generate a file of all 0xFF + generate_blanked_file(storage_info.size, "blank.bin") - # Verify that the partition has been erased by comparing the contents to the generated blank file - data_same = sized_file_compare(read, blank) - check(data_same, "Erased data partition contents does not match blank partition file") + assert_file_same("blank.bin", "read.bin", "Contents of storage partition not fully erased") + # Example end and cleanup print("\nPartition tool operations performed successfully!") + clean_files = ["app.bin", "read.bin", "blank.bin", "write.bin"] + for clean_file in clean_files: + os.unlink(clean_file) if __name__ == '__main__': diff --git a/examples/storage/parttool/parttool_example.sh b/examples/storage/parttool/parttool_example.sh new file mode 100644 index 0000000000..888c914686 --- /dev/null +++ b/examples/storage/parttool/parttool_example.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# +# Demonstrates command-line interface of Partition Tool, parttool.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/parttool.bin +PORT=$1 +PARTTOOL_PY="python $IDF_PATH/components/partition_table/parttool.py -q" + +if [[ "$PORT" != "" ]]; then + PARTTOOL_PY="$PARTTOOL_PY --port $PORT" +fi + +GEN_EMPTY_PARTITION_PY="python $IDF_PATH/components/partition_table/gen_empty_partition.py" + +BINARY=$2 + +if [[ "$BINARY" == "" ]]; then + BINARY=build/parttool.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 "!!!!!!!!!!!!!!!!!!!") +} + +# Read app partition and save the contents to a file. The app partition is identified +# using type-subtype combination +echo "Checking if device app binary matches built binary" +$PARTTOOL_PY read_partition --partition-type=app --partition-subtype=factory --output=app.bin +assert_file_same app.bin $BINARY "Device app binary does not match built binary" + +# Retrieve info on data storage partition, this time identifying it by name. +offset=$($PARTTOOL_PY get_partition_info --partition-name=storage --info offset) +size=$($PARTTOOL_PY get_partition_info --partition-name=storage --info size) +echo "Found data partition at offset $offset with size $size" + +# Create a file whose contents will be written to the storage partition +head -c $(($size)) /dev/urandom > write.bin + +# Write the contents of the created file to storage partition +echo "Writing to data partition" +$PARTTOOL_PY write_partition --partition-name=storage --input write.bin + +# Read back the contents of the storage partition +echo "Reading data partition" +$PARTTOOL_PY read_partition --partition-name=storage --output read.bin + +assert_file_same write.bin read.bin "Read contents of storage partition does not match source file contents" + +# Erase contents of the storage partition +echo "Erasing data partition" +$PARTTOOL_PY erase_partition --partition-name=storage + +# Read back the erased data partition +echo "Reading data partition" +$PARTTOOL_PY read_partition --partition-name=storage --output read.bin + +# Generate a file of all 0xFF +$GEN_EMPTY_PARTITION_PY $(($size)) blank.bin + +assert_file_same read.bin blank.bin "Contents of storage partition not fully erased" + +# Example end and cleanup +printf "\nPartition tool operations performed successfully\n" +rm -rf app.bin read.bin blank.bin write.bin \ No newline at end of file diff --git a/tools/ci/executable-list.txt b/tools/ci/executable-list.txt index 0dc1c7fd15..f6ac3d1e7a 100644 --- a/tools/ci/executable-list.txt +++ b/tools/ci/executable-list.txt @@ -22,6 +22,7 @@ examples/build_system/cmake/idf_as_lib/build.sh 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/otatool_example.py tools/check_kconfigs.py tools/check_python_dependencies.py