feat(merge_bin): add support for uf2 format

This commit is contained in:
Peter Dragun
2023-08-23 16:11:00 +02:00
committed by Radim Karniš
parent 4c75bb6e59
commit 3d899b2f47
17 changed files with 539 additions and 36 deletions

View File

@@ -65,6 +65,15 @@ version_check:
reports:
junit: test/report.xml
check_uf2_ids:
<<: *host_tests_template
allow_failure: true
variables:
COVERAGE_PROCESS_START: "${CI_PROJECT_DIR}/test/.covconf"
PYTEST_ADDOPTS: "-sv --junitxml=test/report.xml --color=yes"
script:
- coverage run -m pytest ${CI_PROJECT_DIR}/test/test_uf2_ids.py
host_tests:
<<: *host_tests_template
variables:

View File

@@ -236,8 +236,7 @@ This information corresponds to the headers described in :ref:`image-format`.
Merge Binaries for Flashing: merge_bin
--------------------------------------
The ``merge_bin`` command will merge multiple binary files (of any kind) into a single file that can be flashed to a device later. Any gaps between the input files are padded with 0xFF bytes (same as unwritten flash contents).
The ``merge_bin`` command will merge multiple binary files (of any kind) into a single file that can be flashed to a device later. Any gaps between the input files are padded based on selected output format.
For example:
@@ -247,16 +246,12 @@ For example:
Will create a file ``merged-flash.bin`` with the contents of the other 3 files. This file can be later be written to flash with ``esptool.py write_flash 0x0 merged-flash.bin``.
.. note:
Because gaps between the input files are padded with 0xFF bytes, when the merged binary is written then any flash sectors between the individual files will be erased. To avoid this, write the files individually.
**Options:**
**Common options:**
* The ``merge_bin`` command supports the same ``--flash_mode``, ``--flash_size`` and ``--flash_freq`` options as the ``write_flash`` command to override the bootloader flash header (see above for details).
These options are applied to the output file contents in the same way as when writing to flash. Make sure to pass the ``--chip`` parameter if using these options, as the supported values and the bootloader offset both depend on the chip.
* The ``--target-offset 0xNNN`` option will create a merged binary that should be flashed at the specified offset, instead of at offset 0x0.
* The ``--fill-flash-size SIZE`` option will pad the merged binary with 0xFF bytes to the full flash specified size, for example ``--fill-flash-size 4MB`` will create a 4MB binary file.
* The ``--format`` option will change the format of the output file. For more information about formats see formats description below.
* It is possible to append options from a text file with ``@filename``. As an example, this can be conveniently used with the ESP-IDF build system, which produces a ``flash_args`` file in the build directory of a project:
.. code:: sh
@@ -264,6 +259,41 @@ Will create a file ``merged-flash.bin`` with the contents of the other 3 files.
cd build # The build directory of an ESP-IDF project
esptool.py --chip {IDF_TARGET_NAME} merge_bin -o merged-flash.bin @flash_args
RAW Output Format
^^^^^^^^^^^^^^^^^
The output of the command will be in ``raw`` format and gaps between individual files will be filled with `0xFF` bytes (same as unwritten flash contents).
.. note::
Because gaps between the input files are padded with `0xFF` bytes, when the merged binary is written then any flash sectors between the individual files will be erased. To avoid this, write the files individually.
**RAW options:**
* The ``--fill-flash-size SIZE`` option will pad the merged binary with `0xFF` bytes to the full flash specified size, for example ``--fill-flash-size 4MB`` will create a 4MB binary file.
* The ``--target-offset 0xNNN`` option will create a merged binary that should be flashed at the specified offset, instead of at offset 0x0.
UF2 Output Format
^^^^^^^^^^^^^^^^^
This command will generate a UF2 (`USB Flashing Format <https://github.com/microsoft/uf2>`_) binary.
This UF2 file can be copied to a USB mass storage device exposed by another ESP running the `ESP USB Bridge <https://github.com/espressif/esp-usb-bridge>`_ project. The bridge MCU will use it to flash the target MCU. This is as simple copying (or "drag-and-dropping") the file to the exposed disk accessed by a file explorer in your machine.
Gaps between the files will be filled with `0x00` bytes.
**UF2 options:**
* The ``--chunk-size`` option will set what portion of 512 byte block will be used for data. Common value is 256 bytes. By default the largest possible value will be used.
* The ``--md5-disable`` option will disable MD5 checksums at the end of each block. This can be useful for integration with e.g. `tinyuf2 <https://github.com/adafruit/tinyuf2>`__.
.. code:: sh
esptool.py --chip {IDF_TARGET_NAME} merge_bin --format uf2 -o merged-flash.uf2 --flash_mode dio --flash_size 4MB 0x1000 bootloader.bin 0x8000 partition-table.bin 0x10000 app.bin
Advanced Commands
-----------------

View File

@@ -587,18 +587,36 @@ def main(argv=None, esp=None):
"--output", "-o", help="Output filename", type=str, required=True
)
parser_merge_bin.add_argument(
"--format", "-f", help="Format of the output file", choices="raw", default="raw"
) # for future expansion
"--format",
"-f",
help="Format of the output file",
choices=["raw", "uf2"],
default="raw",
)
uf2_group = parser_merge_bin.add_argument_group("UF2 format")
uf2_group.add_argument(
"--chunk-size",
help="Specify the used data part of the 512 byte UF2 block. "
"A common value is 256. By default the largest possible value will be used.",
default=None,
type=arg_auto_chunk_size,
)
uf2_group.add_argument(
"--md5-disable",
help="Disable MD5 checksum in UF2 output",
action="store_true",
)
add_spi_flash_subparsers(parser_merge_bin, allow_keep=True, auto_detect=False)
parser_merge_bin.add_argument(
raw_group = parser_merge_bin.add_argument_group("RAW format")
raw_group.add_argument(
"--target-offset",
"-t",
help="Target offset where the output file will be flashed",
type=arg_auto_int,
default=0,
)
parser_merge_bin.add_argument(
raw_group.add_argument(
"--fill-flash-size",
help="If set, the final binary file will be padded with FF "
"bytes up to this flash size.",
@@ -910,6 +928,13 @@ def arg_auto_size(x):
return x if x == "all" else arg_auto_int(x)
def arg_auto_chunk_size(string: str) -> int:
num = int(string, 0)
if num & 3 != 0:
raise argparse.ArgumentTypeError("Chunk size should be a 4-byte aligned number")
return num
def get_port_list():
if list_ports is None:
raise FatalError(

View File

@@ -25,6 +25,7 @@ from .loader import (
timeout_per_mb,
)
from .targets import CHIP_DEFS, CHIP_LIST, ROM_LIST
from .uf2_writer import UF2Writer
from .util import (
FatalError,
NotImplementedInROMError,
@@ -1278,9 +1279,9 @@ def merge_bin(args):
msg = (
"Please specify the chip argument"
if args.chip == "auto"
else "Invalid chip choice: '{}'".format(args.chip)
else f"Invalid chip choice: '{args.chip}'"
)
msg = msg + " (choose from {})".format(", ".join(CHIP_LIST))
msg = f"{msg} (choose from {', '.join(CHIP_LIST)})"
raise FatalError(msg)
# sort the files by offset.
@@ -1291,33 +1292,46 @@ def merge_bin(args):
first_addr = input_files[0][0]
if first_addr < args.target_offset:
raise FatalError(
"Output file target offset is 0x%x. Input file offset 0x%x is before this."
% (args.target_offset, first_addr)
f"Output file target offset is {args.target_offset:#x}. "
f"Input file offset {first_addr:#x} is before this."
)
if args.format != "raw":
raise FatalError(
"This version of esptool only supports the 'raw' output format"
)
with open(args.output, "wb") as of:
def pad_to(flash_offs):
# account for output file offset if there is any
of.write(b"\xFF" * (flash_offs - args.target_offset - of.tell()))
for addr, argfile in input_files:
pad_to(addr)
image = argfile.read()
image = _update_image_flash_params(chip_class, addr, args, image)
of.write(image)
if args.fill_flash_size:
pad_to(flash_size_bytes(args.fill_flash_size))
if args.format == "uf2":
with UF2Writer(
chip_class.UF2_FAMILY_ID,
args.output,
args.chunk_size,
md5_enabled=not args.md5_disable,
) as writer:
for addr, argfile in input_files:
print(f"Adding {argfile.name} at {addr:#x}")
image = argfile.read()
image = _update_image_flash_params(chip_class, addr, args, image)
writer.add_file(addr, image)
print(
"Wrote 0x%x bytes to file %s, ready to flash to offset 0x%x"
% (of.tell(), args.output, args.target_offset)
f"Wrote {os.path.getsize(args.output):#x} bytes to file {args.output}, "
f"ready to be flashed with any ESP USB Bridge"
)
elif args.format == "raw":
with open(args.output, "wb") as of:
def pad_to(flash_offs):
# account for output file offset if there is any
of.write(b"\xFF" * (flash_offs - args.target_offset - of.tell()))
for addr, argfile in input_files:
pad_to(addr)
image = argfile.read()
image = _update_image_flash_params(chip_class, addr, args, image)
of.write(image)
if args.fill_flash_size:
pad_to(flash_size_bytes(args.fill_flash_size))
print(
f"Wrote {of.tell():#x} bytes to file {args.output}, "
f"ready to flash to offset {args.target_offset:#x}"
)
def version(args):
from . import __version__

View File

@@ -105,6 +105,8 @@ class ESP32ROM(ESPLoader):
FLASH_ENCRYPTED_WRITE_ALIGN = 32
UF2_FAMILY_ID = 0x1C5F21B0
""" Try to read the BLOCK1 (encryption key) and check if it is valid """
def is_flash_encryption_key_valid(self):

View File

@@ -61,6 +61,8 @@ class ESP32C2ROM(ESP32C3ROM):
[0x4037C000, 0x403C0000, "IRAM"],
]
UF2_FAMILY_ID = 0x2B88D29C
def get_pkg_version(self):
num_word = 1
return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 22) & 0x07

View File

@@ -99,6 +99,8 @@ class ESP32C3ROM(ESP32ROM):
[0x600FE000, 0x60100000, "MEM_INTERNAL2"],
]
UF2_FAMILY_ID = 0xD42BA06C
def get_pkg_version(self):
num_word = 3
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x07

View File

@@ -101,6 +101,8 @@ class ESP32C6ROM(ESP32C3ROM):
[0x600FE000, 0x60100000, "MEM_INTERNAL2"],
]
UF2_FAMILY_ID = 0x540DDF62
def get_pkg_version(self):
num_word = 3
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x07

View File

@@ -29,6 +29,8 @@ class ESP32H2ROM(ESP32C6ROM):
"12m": 0x2,
}
UF2_FAMILY_ID = 0x332726F6
def get_pkg_version(self):
num_word = 4
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 0) & 0x07

View File

@@ -79,6 +79,8 @@ class ESP32P4ROM(ESP32ROM):
[0x600FE000, 0x60100000, "MEM_INTERNAL2"],
]
UF2_FAMILY_ID = 0x3D308E94
def get_pkg_version(self):
# ESP32P4 TODO
return 0

View File

@@ -101,6 +101,8 @@ class ESP32S2ROM(ESP32ROM):
[0x50000000, 0x50002000, "RTC_DATA"],
]
UF2_FAMILY_ID = 0xBFDD4EEE
def get_pkg_version(self):
num_word = 4
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 0) & 0x0F

View File

@@ -117,6 +117,8 @@ class ESP32S3ROM(ESP32ROM):
[0x50000000, 0x50002000, "RTC_DATA"],
]
UF2_FAMILY_ID = 0xC47E5767
def get_pkg_version(self):
num_word = 3
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x07

View File

@@ -60,6 +60,8 @@ class ESP8266ROM(ESPLoader):
[0x40201010, 0x402E1010, "IROM"],
]
UF2_FAMILY_ID = 0x7EAB61ED
def get_efuses(self):
# Return the 128 bits of ESP8266 efuse as a single Python integer
result = self.read_reg(0x3FF0005C) << 96

96
esptool/uf2_writer.py Normal file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: GPL-2.0-or-later
# Code was originally licensed under Apache 2.0 before the release of ESP-IDF v5.2
import hashlib
import os
import struct
from typing import List
from esptool.util import div_roundup
class UF2Writer(object):
# The UF2 format is described here: https://github.com/microsoft/uf2
UF2_BLOCK_SIZE = 512
# max value of CHUNK_SIZE reduced by optional parts. Currently, MD5_PART only.
UF2_DATA_SIZE = 476
UF2_MD5_PART_SIZE = 24
UF2_FIRST_MAGIC = 0x0A324655
UF2_SECOND_MAGIC = 0x9E5D5157
UF2_FINAL_MAGIC = 0x0AB16F30
UF2_FLAG_FAMILYID_PRESENT = 0x00002000
UF2_FLAG_MD5_PRESENT = 0x00004000
def __init__(
self,
chip_id: int,
output_file: os.PathLike,
chunk_size: int,
md5_enabled: bool = True,
) -> None:
if not md5_enabled:
self.UF2_MD5_PART_SIZE = 0
self.UF2_FLAG_MD5_PRESENT = 0x00000000
self.md5_enabled = md5_enabled
self.chip_id = chip_id
self.CHUNK_SIZE = (
self.UF2_DATA_SIZE - self.UF2_MD5_PART_SIZE
if chunk_size is None
else chunk_size
)
self.f = open(output_file, "wb")
def __enter__(self) -> "UF2Writer":
return self
def __exit__(self, exc_type: str, exc_val: int, exc_tb: List) -> None:
if self.f:
self.f.close()
@staticmethod
def _to_uint32(num: int) -> bytes:
return struct.pack("<I", num)
def _write_block(
self, addr: int, chunk: bytes, len_chunk: int, block_no: int, blocks: int
) -> None:
assert len_chunk > 0
assert len_chunk <= self.CHUNK_SIZE
assert block_no < blocks
block = struct.pack(
"<IIIIIIII",
self.UF2_FIRST_MAGIC,
self.UF2_SECOND_MAGIC,
self.UF2_FLAG_FAMILYID_PRESENT | self.UF2_FLAG_MD5_PRESENT,
addr,
len_chunk,
block_no,
blocks,
self.chip_id,
)
block += chunk
if self.md5_enabled:
md5_part = struct.pack("<II", addr, len_chunk)
md5_part += hashlib.md5(chunk).digest()
assert len(md5_part) == self.UF2_MD5_PART_SIZE
block += md5_part
block += b"\x00" * (self.UF2_DATA_SIZE - self.UF2_MD5_PART_SIZE - len_chunk)
block += self._to_uint32(self.UF2_FINAL_MAGIC)
assert len(block) == self.UF2_BLOCK_SIZE
self.f.write(block)
def add_file(self, addr: int, image: bytes) -> None:
blocks = div_roundup(len(image), self.CHUNK_SIZE)
chunks = [
image[i : i + self.CHUNK_SIZE]
for i in range(0, len(image), self.CHUNK_SIZE)
]
for i, chunk in enumerate(chunks):
len_chunk = len(chunk)
self._write_block(addr, chunk, len_chunk, i, blocks)
addr += len_chunk

View File

@@ -101,6 +101,7 @@ setup(
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
],
python_requires=">=3.7",
setup_requires=(["wheel"] if "bdist_wheel" in sys.argv else []),
@@ -115,6 +116,7 @@ setup(
"pre-commit",
"pytest",
"pytest-rerunfailures",
"requests",
],
"hsm": [
"python-pkcs11",

View File

@@ -1,9 +1,14 @@
import filecmp
import hashlib
import itertools
import os
import os.path
import random
import struct
import subprocess
import sys
import tempfile
from functools import partial
IMAGES_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "images")
@@ -13,6 +18,8 @@ import pytest
try:
from esptool.util import byte
from esptool.uf2_writer import UF2Writer
from esptool.targets import CHIP_DEFS
except ImportError:
need_to_install_package_err()
@@ -189,3 +196,239 @@ class TestMergeBin:
assert bootloader == merged[: len(bootloader)]
assert helloworld == merged[0xF000 : 0xF000 + len(helloworld)]
self.assertAllFF(merged[0xF000 + len(helloworld) :])
class UF2Block(object):
def __init__(self, bs):
self.length = len(bs)
# See https://github.com/microsoft/uf2 for the format
first_part = "<" + "I" * 8
# payload is between
last_part = "<I"
first_part_len = struct.calcsize(first_part)
last_part_len = struct.calcsize(last_part)
(
self.magicStart0,
self.magicStart1,
self.flags,
self.targetAddr,
self.payloadSize,
self.blockNo,
self.numBlocks,
self.familyID,
) = struct.unpack(first_part, bs[:first_part_len])
self.data = bs[first_part_len:-last_part_len]
(self.magicEnd,) = struct.unpack(last_part, bs[-last_part_len:])
def __len__(self):
return self.length
class UF2BlockReader(object):
def __init__(self, f_name):
self.f_name = f_name
def get(self):
with open(self.f_name, "rb") as f:
for chunk in iter(partial(f.read, UF2Writer.UF2_BLOCK_SIZE), b""):
yield UF2Block(chunk)
class BinaryWriter(object):
def __init__(self, f_name):
self.f_name = f_name
def append(self, data):
# File is reopened several times in order to make sure that won't left open
with open(self.f_name, "ab") as f:
f.write(data)
@pytest.mark.host_test
class TestUF2:
def generate_binary(self, size):
with tempfile.NamedTemporaryFile(delete=False) as f:
for _ in range(size):
f.write(struct.pack("B", random.randrange(0, 1 << 8)))
return f.name
@staticmethod
def generate_chipID():
chip, rom = random.choice(list(CHIP_DEFS.items()))
family_id = rom.UF2_FAMILY_ID
return chip, family_id
def generate_uf2(
self,
of_name,
chip_id,
iter_addr_offset_tuples,
chunk_size=None,
md5_enable=True,
):
com_args = [
sys.executable,
"-m",
"esptool",
"--chip",
chip_id,
"merge_bin",
"--format",
"uf2",
"-o",
of_name,
]
if not md5_enable:
com_args.append("--md5-disable")
com_args += [] if chunk_size is None else ["--chunk-size", str(chunk_size)]
file_args = list(
itertools.chain(*[(hex(addr), f) for addr, f in iter_addr_offset_tuples])
)
output = subprocess.check_output(com_args + file_args, stderr=subprocess.STDOUT)
output = output.decode("utf-8")
print(output)
assert "warning" not in output.lower(), "merge_bin should not output warnings"
exp_list = [f"Adding {f} at {hex(addr)}" for addr, f in iter_addr_offset_tuples]
exp_list += [
f"bytes to file {of_name}, ready to be flashed with any ESP USB Bridge"
]
for e in exp_list:
assert e in output
return of_name
def process_blocks(self, uf2block, expected_chip_id, md5_enable=True):
flags = UF2Writer.UF2_FLAG_FAMILYID_PRESENT
if md5_enable:
flags |= UF2Writer.UF2_FLAG_MD5_PRESENT
parsed_binaries = []
block_list = [] # collect block numbers here
total_blocks = set() # collect total block numbers here
for block in UF2BlockReader(uf2block).get():
if block.blockNo == 0:
# new file has been detected
base_addr = block.targetAddr
current_addr = base_addr
binary_writer = BinaryWriter(self.generate_binary(0))
assert len(block) == UF2Writer.UF2_BLOCK_SIZE
assert block.magicStart0 == UF2Writer.UF2_FIRST_MAGIC
assert block.magicStart1 == UF2Writer.UF2_SECOND_MAGIC
assert block.flags & flags == flags
assert len(block.data) == UF2Writer.UF2_DATA_SIZE
payload = block.data[: block.payloadSize]
if md5_enable:
md5_obj = hashlib.md5(payload)
md5_part = block.data[
block.payloadSize : block.payloadSize + UF2Writer.UF2_MD5_PART_SIZE
]
address, length = struct.unpack("<II", md5_part[: -md5_obj.digest_size])
md5sum = md5_part[-md5_obj.digest_size :]
assert address == block.targetAddr
assert length == block.payloadSize
assert md5sum == md5_obj.digest()
assert block.familyID == expected_chip_id
assert block.magicEnd == UF2Writer.UF2_FINAL_MAGIC
assert current_addr == block.targetAddr
binary_writer.append(payload)
block_list.append(block.blockNo)
total_blocks.add(block.numBlocks)
if block.blockNo == block.numBlocks - 1:
assert block_list == list(range(block.numBlocks))
# we have found all blocks and in the right order
assert total_blocks == {
block.numBlocks
} # numBlocks are the same in all the blocks
del block_list[:]
total_blocks.clear()
parsed_binaries += [(base_addr, binary_writer.f_name)]
current_addr += block.payloadSize
return parsed_binaries
def common(self, t, chunk_size=None, md5_enable=True):
of_name = self.generate_binary(0)
try:
chip_name, chip_id = self.generate_chipID()
self.generate_uf2(of_name, chip_name, t, chunk_size, md5_enable)
parsed_t = self.process_blocks(of_name, chip_id, md5_enable)
assert len(t) == len(parsed_t)
for (orig_addr, orig_fname), (addr, fname) in zip(t, parsed_t):
assert orig_addr == addr
assert filecmp.cmp(orig_fname, fname)
finally:
os.unlink(of_name)
for _, file_name in t:
os.unlink(file_name)
def test_simple(self):
self.common([(0, self.generate_binary(1))])
def test_more_files(self):
self.common(
[(0x100, self.generate_binary(1)), (0x1000, self.generate_binary(1))]
)
def test_larger_files(self):
self.common(
[(0x100, self.generate_binary(6)), (0x1000, self.generate_binary(8))]
)
def test_boundaries(self):
self.common(
[
(0x100, self.generate_binary(UF2Writer.UF2_DATA_SIZE)),
(0x2000, self.generate_binary(UF2Writer.UF2_DATA_SIZE + 1)),
(0x3000, self.generate_binary(UF2Writer.UF2_DATA_SIZE - 1)),
]
)
def test_files_with_more_blocks(self):
self.common(
[
(0x100, self.generate_binary(3 * UF2Writer.UF2_DATA_SIZE)),
(0x2000, self.generate_binary(2 * UF2Writer.UF2_DATA_SIZE + 1)),
(0x3000, self.generate_binary(2 * UF2Writer.UF2_DATA_SIZE - 1)),
]
)
def test_very_large_files(self):
self.common(
[
(0x100, self.generate_binary(20 * UF2Writer.UF2_DATA_SIZE + 5)),
(0x10000, self.generate_binary(50 * UF2Writer.UF2_DATA_SIZE + 100)),
(0x100000, self.generate_binary(100 * UF2Writer.UF2_DATA_SIZE)),
]
)
def test_chunk_size(self):
chunk_size = 256
self.common(
[
(0x1000, self.generate_binary(chunk_size)),
(0x2000, self.generate_binary(chunk_size + 1)),
(0x3000, self.generate_binary(chunk_size - 1)),
],
chunk_size,
)
def test_md5_disable(self):
self.common(
[(0x100, self.generate_binary(1)), (0x2000, self.generate_binary(1))],
md5_enable=False,
)

66
test/test_uf2_ids.py Normal file
View File

@@ -0,0 +1,66 @@
import json
from conftest import need_to_install_package_err
import pytest
import requests
try:
from esptool.targets import CHIP_DEFS
except ImportError:
need_to_install_package_err()
FAMILIES_URL = (
"https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2families.json"
)
@pytest.fixture(scope="class")
def uf2_json():
"""Download UF2 family IDs from Microsoft UF2 repo and filter out ESP chips"""
res = requests.get(FAMILIES_URL)
assert res.status_code == 200
uf2_families_json = json.loads(res.content)
# filter out just ESP chips
chips = [
chip
for chip in uf2_families_json
if chip["short_name"].upper().startswith("ESP")
]
return chips
def test_check_uf2family_ids(uf2_json):
"""Compare UF2 family IDs from Microsoft UF2 repo and with stored values"""
# check if all UF2 family ids match
for chip in uf2_json:
assert int(chip["id"], 0) == CHIP_DEFS[chip["short_name"].lower()].UF2_FAMILY_ID
def test_check_uf2(uf2_json):
"""Check if all non-beta chip definition has UF2 family id in esptool
and also in Miscrosoft repo
"""
# remove beta chip definitions
esptool_chips = set(
[chip.upper() for chip in CHIP_DEFS.keys() if "beta" not in chip]
)
microsoft_repo_chips = set([chip["short_name"] for chip in uf2_json])
diff = esptool_chips.symmetric_difference(microsoft_repo_chips)
if diff:
out = []
# there was a difference between the chip support
for chip in diff:
if chip in esptool_chips:
out.append(
f"Missing chip definition for '{chip}' in esptool "
"which was defined in Microsoft UF2 Github repo."
)
else:
out.append(
f"Please consider adding support for chip '{chip}' "
f"to the UF2 repository: {FAMILIES_URL}"
)
pytest.fail("\n".join(out))