| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- #!/usr/bin/env python3
- # Copyright (c) 2020 Project CHIP Authors
- #
- # 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.
- """Flash an ESP32 device.
- This is layered so that a caller can perform individual operations
- through an `Flasher` instance, or operations according to a command line.
- For `Flasher`, see the class documentation. For the parse_command()
- interface or standalone execution:
- usage: esp32_firmware_utils.py [-h] [--verbose] [--erase] [--application FILE]
- [--verify_application] [--reset] [--skip_reset]
- [--esptool FILE] [--parttool FILE]
- [--sdkconfig FILE] [--chip CHIP] [--port PORT]
- [--baud BAUD] [--before ACTION]
- [--after ACTION] [--flash_mode MODE]
- [--flash_freq FREQ] [--flash_size SIZE]
- [--compress] [--bootloader FILE]
- [--bootloader_offset OFFSET] [--partition FILE]
- [--partition_offset OFFSET]
- [--application_offset OFFSET]
- Flash ESP32 device
- optional arguments:
- -h, --help show this help message and exit
- configuration:
- --verbose, -v Report more verbosely
- --esptool FILE File name of the esptool executable
- --parttool FILE File name of the parttool executable
- --sdkconfig FILE File containing option defaults
- --chip CHIP Target chip type
- --port PORT Serial port device
- --baud BAUD Serial port baud rate
- --before ACTION What to do before connecting
- --after ACTION What to do when finished
- --flash_mode MODE, --flash-mode MODE
- Flash mode
- --flash_freq FREQ, --flash-freq FREQ
- Flash frequency
- --flash_size SIZE, --flash-size SIZE
- Flash size
- --compress, -z Compress data in transfer
- --bootloader FILE Bootloader image
- --bootloader_offset OFFSET, --bootloader-offset OFFSET
- Bootloader offset
- --partition FILE Partition table image
- --partition_offset OFFSET, --partition-offset OFFSET
- Partition table offset
- --application_offset OFFSET, --application-offset OFFSET
- Application offset
- operations:
- --erase Erase device
- --application FILE Flash an image
- --verify_application, --verify-application
- Verify the image after flashing
- --reset Reset device after flashing
- --skip_reset, --skip-reset
- Do not reset device after flashing
- """
- import os
- import pathlib
- import sys
- import firmware_utils
- # Additional options that can be use to configure an `Flasher`
- # object (as dictionary keys) and/or passed as command line options.
- ESP32_OPTIONS = {
- # Configuration options define properties used in flashing operations.
- 'configuration': {
- # Tool configuration options.
- 'esptool': {
- 'help': 'File name of the esptool executable',
- 'default': None,
- 'argparse': {
- 'metavar': 'FILE',
- },
- 'command': [
- {'option': 'esptool'},
- {'optional': 'port'},
- {'optional': 'baud'},
- {'optional': 'before'},
- {'optional': 'after'},
- ()
- ],
- 'verify': ['{esptool}', 'version'],
- 'error':
- """\
- Unable to execute {esptool}.
- Please ensure that this tool is installed and
- that $IDF_PATH is set. See the ESP32 example
- README for installation instructions.
- """,
- },
- 'parttool': {
- 'help': 'File name of the parttool executable',
- 'default': None,
- 'argparse': {
- 'metavar': 'FILE'
- },
- 'command': [
- {'option': 'parttool'},
- {'optional': 'port'},
- {'optional': 'baud'},
- {
- 'option': 'partition',
- 'result': ['--partition-table-file', '{partition}'],
- 'expand': True
- },
- ()
- ],
- 'verify': ['{parttool}', '--quiet'],
- 'error':
- """\
- Unable to execute {parttool}.
- Please ensure that this tool is installed and
- that $IDF_PATH is set. See the ESP32 example
- README for installation instructions.
- """,
- },
- 'sdkconfig': {
- 'help': 'File containing option defaults',
- 'default': None,
- 'argparse': {
- 'metavar': 'FILE'
- },
- },
- # Device configuration options.
- 'chip': {
- 'help': 'Target chip type',
- 'default': 'esp32',
- 'argparse': {
- 'metavar': 'CHIP'
- },
- 'sdkconfig': 'CONFIG_IDF_TARGET',
- },
- 'port': {
- 'help': 'Serial port device',
- 'default': None,
- 'argparse': {
- 'metavar': 'PORT',
- },
- 'sdkconfig': 'CONFIG_ESPTOOLPY_PORT',
- },
- 'baud': {
- 'help': 'Serial port baud rate',
- 'default': None,
- 'argparse': {
- 'metavar': 'BAUD',
- },
- 'sdkconfig': 'CONFIG_ESPTOOLPY_BAUD',
- },
- 'before': {
- 'help': 'What to do before connecting',
- 'default': None,
- 'argparse': {
- 'metavar': 'ACTION',
- },
- 'sdkconfig': 'CONFIG_ESPTOOLPY_BEFORE',
- },
- 'after': {
- 'help': 'What to do when finished',
- 'default': None,
- 'argparse': {
- 'metavar': 'ACTION',
- },
- 'sdkconfig': 'CONFIG_ESPTOOLPY_AFTER',
- },
- 'flash_mode': {
- 'help': 'Flash mode',
- 'default': None,
- 'argparse': {
- 'metavar': 'MODE',
- },
- 'sdkconfig': 'CONFIG_ESPTOOLPY_FLASHMODE',
- },
- 'flash_freq': {
- 'help': 'Flash frequency',
- 'default': None,
- 'argparse': {
- 'metavar': 'FREQ',
- },
- 'sdkconfig': 'CONFIG_ESPTOOLPY_FLASHFREQ',
- },
- 'flash_size': {
- 'help': 'Flash size',
- 'default': None,
- 'argparse': {
- 'metavar': 'SIZE',
- },
- 'sdkconfig': 'CONFIG_ESPTOOLPY_FLASHSIZE',
- },
- 'compress': {
- 'help': 'Compress data in transfer',
- 'default': None,
- 'alias': ['-z'],
- 'argparse': {
- 'action': 'store_true'
- },
- 'sdkconfig': 'CONFIG_ESPTOOLPY_COMPRESSED',
- },
- # Flashing things.
- 'bootloader': {
- 'help': 'Bootloader image',
- 'default': None,
- 'argparse': {
- 'metavar': 'FILE',
- 'type': pathlib.Path,
- },
- },
- 'bootloader_offset': {
- 'help': 'Bootloader offset',
- 'default': '0x1000',
- 'argparse': {
- 'metavar': 'OFFSET'
- },
- 'sdkconfig': 'CONFIG_BOOTLOADER_OFFSET_IN_FLASH',
- },
- 'partition': {
- 'help': 'Partition table image',
- 'default': None,
- 'argparse': {
- 'metavar': 'FILE',
- 'type': pathlib.Path,
- },
- },
- 'partition_offset': {
- 'help': 'Partition table offset',
- 'default': None,
- 'argparse': {
- 'metavar': 'OFFSET'
- },
- 'sdkconfig': 'CONFIG_PARTITION_TABLE_OFFSET',
- },
- 'application_offset': {
- 'help': 'Application offset',
- 'default': None,
- 'argparse': {
- 'metavar': 'OFFSET'
- },
- },
- },
- }
- def namespace_defaults(dst, src):
- for key, value in src.items():
- if key not in dst or getattr(dst, key) is None:
- setattr(dst, key, value)
- class Flasher(firmware_utils.Flasher):
- """Manage esp32 flashing."""
- def __init__(self, **options):
- super().__init__(platform='ESP32', module=__name__, **options)
- self.define_options(ESP32_OPTIONS)
- def _postprocess_argv(self):
- if self.option.sdkconfig:
- namespace_defaults(self.option,
- self.read_sdkconfig(self.option.sdkconfig))
- # idf = os.environ.get('IDF_PATH')
- # if idf:
- # if self.option.esptool is None:
- # self.option.esptool = os.path.join(
- # idf,
- # 'components/esptool_py/esptool/esptool.py')
- # if self.option.parttool is None:
- # self.option.parttool = os.path.join(
- # idf,
- # 'components/partition_table/parttool.py')
- def read_sdkconfig(self, filename):
- """Given an ESP32 sdkconfig file, read it for values of options
- not otherwise set.
- """
- config_map = {}
- for key, info in vars(self.info).items():
- config_key = info.get('sdkconfig')
- if config_key:
- config_map[config_key] = key
- result = {}
- with open(filename) as f:
- for line in f:
- k, eq, v = line.strip().partition('=')
- if eq == '=' and k in config_map:
- result[config_map[k]] = v.strip('"')
- return result
- IDF_PATH_TOOLS = {
- 'esptool': 'components/esptool_py/esptool/esptool.py',
- 'parttool': 'components/partition_table/parttool.py',
- }
- def locate_tool(self, tool):
- if tool in self.IDF_PATH_TOOLS:
- idf_path = os.environ.get('IDF_PATH')
- if idf_path:
- return os.path.join(idf_path, self.IDF_PATH_TOOLS[tool])
- return super().locate_tool(tool)
- # Common command line arguments for esptool flashing subcommands.
- FLASH_ARGUMENTS = [
- {'optional': 'flash_mode'},
- {'optional': 'flash_freq'},
- {'optional': 'flash_size'},
- {
- 'match': '{compress}',
- 'test': [(True, '-z'), ('y', '-z')],
- 'default': '-u'
- },
- ]
- def erase(self):
- """Perform `commander device masserase`."""
- return self.run_tool('esptool', ['erase_flash'], {}, 'Erase device')
- def verify(self, image, address=0):
- """Verify image(s)."""
- if not isinstance(image, list):
- image = [address, image]
- return self.run_tool(
- 'esptool',
- ['verify_flash', self.FLASH_ARGUMENTS, image],
- name='Verify',
- pass_message='Verified',
- fail_message='Not verified',
- fail_level=2)
- def flash(self, image, address=0):
- """Flash image(s)."""
- if not isinstance(image, list):
- image = [address, image]
- return self.run_tool(
- 'esptool',
- ['write_flash', self.FLASH_ARGUMENTS, image],
- name='Flash')
- PARTITION_INFO = {
- 'phy': ['--partition-type', 'data', '--partition-subtype', 'phy'],
- 'application': ['--partition-boot-default'],
- 'ota': ['--partition-type', 'data', '--partition-subtype', 'ota'],
- 'factory': [
- '--partition-type', 'app', '--partition-subtype', 'factory'
- ],
- }
- def get_partition_info(self, item, info, options=None):
- """Run parttool to get partition information."""
- return self.run_tool(
- 'parttool',
- ['get_partition_info', self.PARTITION_INFO[item], '--info', info],
- options or {},
- capture_output=True).stdout.strip()
- def make_wrapper(self, argv):
- self.parser.add_argument(
- '--use-parttool',
- metavar='FILENAME',
- help='partition tool to configure flashing script')
- self.parser.add_argument(
- '--use-partition-file',
- metavar='FILENAME',
- help='partition file to configure flashing script')
- self.parser.add_argument(
- '--use-sdkconfig',
- metavar='FILENAME',
- help='sdkconfig to configure flashing script')
- super().make_wrapper(argv)
- def _platform_wrapper_args(self, args):
- if args.use_sdkconfig:
- # Include values from sdkconfig so that it isn't needed at
- # flashing time.
- namespace_defaults(args, self.read_sdkconfig(args.use_sdkconfig))
- parttool = args.use_parttool or args.parttool
- partfile = args.use_partition_file or args.partition
- if parttool and partfile:
- # Get unspecified offsets from the partition file now,
- # so that parttool isn't needed at flashing time.
- if args.application and args.application_offset is None:
- args.application_offset = self.get_partition_info(
- 'application', 'offset',
- {'parttool': parttool, 'partition': partfile})
- def actions(self):
- """Perform actions on the device according to self.option."""
- self.log(3, 'Options:', self.option)
- if self.option.erase:
- if self.erase().err:
- return self
- if self.option.application:
- application = self.option.application
- bootloader = self.option.bootloader
- partition = self.option.partition
- # Collect the flashable items.
- flash = []
- if bootloader:
- flash += [self.option.bootloader_offset, bootloader]
- if application:
- offset = self.option.application_offset
- if offset is None:
- offset = self.get_partition_info('application', 'offset')
- flash += [offset, application]
- if partition:
- flash += [self.option.partition_offset, partition]
- # esptool.py doesn't have an independent reset command, so we add
- # an `--after` option to the final operation, which may be either
- # flash or verify.
- if self.option.after is None:
- if self.option.reset or self.option.reset is None:
- self.option.after = 'hard_reset'
- else:
- self.option.after = 'no_reset'
- if self.option.verify_application:
- verify_after = self.option.after
- self.option.after = 'no_reset'
- if self.flash(flash).err:
- return self
- if self.option.verify_application:
- self.option.after = verify_after
- if self.verify(flash).err:
- return self
- return self
- # Mobly integration
- class ESP32Platform:
- def __init__(self, flasher_args):
- self.flasher = Flasher(**flasher_args)
- def flash(self):
- self.flasher.flash_command([os.getcwd()])
- def verify_platform_args(platform_args):
- required_args = [
- 'application',
- 'parttool',
- 'port',
- 'baud',
- 'before',
- 'after',
- 'flash_mode',
- 'flash_freq',
- 'flash_size',
- 'compress',
- 'bootloader',
- 'partition',
- 'partition_offset',
- 'application_offset',
- ]
- difference = set(required_args) - set(platform_args)
- if difference:
- raise ValueError("Required arguments missing: %s" % difference)
- def create_platform(platform_args):
- verify_platform_args(platform_args[0])
- return ESP32Platform(platform_args[0])
- # End of Mobly integration
- if __name__ == '__main__':
- sys.exit(Flasher().flash_command(sys.argv))
|