| # Copyright 2023 The Pigweed 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 | |
| # | |
| # https://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. | |
| """A handy wrapper that simplifies flashing a device.""" | |
| import argparse | |
| import logging | |
| import os | |
| from pathlib import Path | |
| import subprocess | |
| import sys | |
| _LOG = logging.getLogger('flash_device') | |
| def _log_subprocess_output(level, output) -> None: | |
| """Logs subprocess output line-by-line.""" | |
| lines = output.decode('utf-8', errors='replace').splitlines() | |
| for line in lines: | |
| _LOG.log(level, line) | |
| def _get_environment_variable(variable_name: str) -> str: | |
| val = os.environ.get(variable_name) | |
| if not val: | |
| raise ValueError( | |
| f'The environment variable `{variable_name}` isn\'t ' | |
| 'set, please activate the Pigweed environment first by sourcing' | |
| 'bootstrap.sh:\n' | |
| ' $ . bootstrap.sh' | |
| ) | |
| return val | |
| def flash_stm32_discovery(firmware_image: Path) -> bool: | |
| """Flashes a firmware image to a STM32F429I-DISC1 board.""" | |
| # TODO: This should use a real, shared flashing script. | |
| cipd_install_dir = _get_environment_variable('PW_PIGWEED_CIPD_INSTALL_DIR') | |
| pigweed_root = _get_environment_variable('PW_ROOT') | |
| cmd = ( | |
| 'openocd', | |
| '-s', | |
| f'{cipd_install_dir}/share/openocd/scripts', | |
| '-f', | |
| f'{pigweed_root}/targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg', # pylint: disable=line-too-long | |
| '-c', | |
| f'program {firmware_image} reset exit', | |
| ) | |
| process = subprocess.run( | |
| cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT | |
| ) | |
| if process.returncode: | |
| _log_subprocess_output(logging.ERROR, process.stdout) | |
| raise ValueError('Failed to flash target device') | |
| return process.returncode == 0 | |
| _SUPPORTED_DEVICES = { | |
| 'STM32-Discovery': flash_stm32_discovery, | |
| } | |
| def _parse_args() -> argparse.Namespace: | |
| """Parses commandline arguments.""" | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument( | |
| 'firmware_image', | |
| type=Path, | |
| help='Path to the firmware image to flash to an attached device.', | |
| ) | |
| parser.add_argument( | |
| '--device', | |
| '-d', | |
| dest='dev_name', | |
| type=str, | |
| required=True, | |
| choices=_SUPPORTED_DEVICES.keys(), | |
| help='Device type, selects the flashing command to use', | |
| ) | |
| return parser.parse_args() | |
| def flash_device(dev_name: str, firmware_image: Path) -> bool: | |
| """Flashes the specified firmware image to a device.""" | |
| if dev_name not in _SUPPORTED_DEVICES: | |
| raise ValueError( | |
| f'Unknown device `{dev_name}`, ' | |
| f'please choose one of {_SUPPORTED_DEVICES.keys()}' | |
| ) | |
| _LOG.info('Flashing %s via the %s tool...', firmware_image, dev_name) | |
| if _SUPPORTED_DEVICES[dev_name](firmware_image): | |
| _LOG.info('Done!') | |
| return True | |
| return False | |
| def main() -> int: | |
| """Parses commandline arguments and attempts to flash a device.""" | |
| return 0 if flash_device(**vars(_parse_args())) else 1 | |
| if __name__ == '__main__': | |
| sys.exit(main()) |