blob: 6d3fc6d0ced0bf1a7e3e5f1e30cdec0231247685 [file] [log] [blame] [edit]
# 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())