1# Copyright (c) 2020 Espressif Systems (Shanghai) Co., Ltd. 2# 3# SPDX-License-Identifier: Apache-2.0 4 5'''tools.py 6 7Espressif west extension for serial port monitor logging.''' 8 9import os 10import platform 11import subprocess 12import sys 13import yaml 14from pathlib import Path 15 16from textwrap import dedent 17from west.commands import WestCommand 18from west.configuration import config 19from west import log 20 21# This relies on this file being in hal_espressif/west/tools.py 22# If you move this file, you'll break it, so be careful. 23THIS_ZEPHYR = Path(__file__).parents[4] / 'zephyr' 24ZEPHYR_BASE = Path(os.environ.get('ZEPHYR_BASE', THIS_ZEPHYR)) 25 26sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts", "west_commands")) 27 28from build_helpers import load_domains 29from build_helpers import is_zephyr_build, find_build_dir # noqa: E402 30from runners.core import BuildConfiguration # noqa: E402 31 32ESP_IDF_REMOTE = "https://github.com/zephyrproject-rtos/hal_espressif" 33 34 35def cmd_check(cmd, cwd=None, stderr=subprocess.STDOUT): 36 return subprocess.check_output(cmd, cwd=cwd, stderr=stderr) 37 38 39def cmd_exec(cmd, cwd=None, shell=False): 40 return subprocess.check_call(cmd, cwd=cwd, shell=shell) 41 42 43def get_esp_serial_port(module_path): 44 try: 45 import serial.tools.list_ports 46 esptool_path = os.path.join(module_path, 'components', 'esptool_py', 'esptool') 47 sys.path.insert(0, esptool_path) 48 import esptool 49 ports = list(sorted(p.device for p in serial.tools.list_ports.comports())) 50 # high baud rate could cause the failure of creation of the connection 51 esp = esptool.get_default_connected_device(serial_list=ports, port=None, connect_attempts=4, 52 initial_baud=115200) 53 if esp is None: 54 log.die("No serial ports found. Connect a device, or use '-p PORT' " 55 "option to set a specific port.") 56 57 serial_port = esp.serial_port 58 esp._port.close() 59 60 return serial_port 61 except Exception as e: 62 log.die("Error detecting ESP serial port: {}".format(e)) 63 return None 64 65 66def get_build_dir(path, die_if_none=True): 67 # Get the build directory for the given argument list and environment. 68 69 guess = config.get('build', 'guess-dir', fallback='never') 70 guess = guess == 'runners' 71 dir = find_build_dir(path, guess) 72 73 if dir and is_zephyr_build(dir): 74 return dir 75 elif die_if_none: 76 msg = 'could not find build directory and ' 77 if dir: 78 msg = msg + 'neither {} nor {} are zephyr build directories.' 79 else: 80 msg = msg + ('{} is not a build directory and the default build ' 81 'directory cannot be determined. ') 82 log.die(msg.format(os.getcwd(), dir)) 83 else: 84 return None 85 86 87def get_build_elf_path(): 88 def runners_yaml_path(build_dir): 89 ret = Path(build_dir) / 'zephyr' / 'runners.yaml' 90 if not ret.is_file(): 91 log.die("could not find build configuration") 92 return ret 93 94 def load_runners_yaml(path): 95 # Load runners.yaml and convert to Python object. 96 97 try: 98 with open(path, 'r') as f: 99 content = yaml.safe_load(f.read()) 100 except Exception as e: 101 log.die("runners.yaml file open error: {}".format(e)) 102 103 if not content.get('runners'): 104 log.wrn("no pre-configured runners in {}; " 105 "this probably won't work".format(path)) 106 107 return content 108 109 build_dir = get_build_dir(None) 110 domain = load_domains(build_dir).get_default_domain() 111 112 # build dir differs when sysbuild is used 113 if domain.name != 'app': 114 build_dir = Path(build_dir) / domain.name 115 build_dir = get_build_dir(build_dir) 116 117 yaml_path = runners_yaml_path(build_dir) 118 runners_yaml = load_runners_yaml(yaml_path) 119 120 return Path(build_dir) / 'zephyr' / runners_yaml['config']['elf_file'] 121 122 123class Tools(WestCommand): 124 125 def __init__(self): 126 super().__init__( 127 'espressif', 128 # Keep this in sync with the string in west-commands.yml. 129 'Espressif tools for west framework.', 130 dedent(''' 131 This interface allows having esp-idf monitor support.'''), 132 accepts_unknown_args=False) 133 134 def do_add_parser(self, parser_adder): 135 parser = parser_adder.add_parser(self.name, 136 help=self.help, 137 description=self.description) 138 139 parser.add_argument('command', choices=['monitor'], 140 help='open serial port based on esp-idf monitor') 141 142 # monitor arguments 143 group = parser.add_argument_group('monitor optional arguments') 144 group.add_argument('-b', '--baud', default="115200", help='Serial port baud rate') 145 group.add_argument('-p', '--port', help='Serial port address') 146 group.add_argument('-e', '--elf', help='ELF file') 147 group.add_argument('-n', '--eol', default='CRLF', help='EOL to use') 148 149 return parser 150 151 def do_run(self, args, unknown_args): 152 153 module_path = ( 154 Path(os.getenv("ZEPHYR_BASE")).absolute() 155 / r".." 156 / "modules" 157 / "hal" 158 / "espressif" 159 ) 160 161 if not module_path.exists(): 162 log.die('cannot find espressif hal in $ZEPHYR_BASE/../modules/hal/ path') 163 164 if args.command == "monitor": 165 self.monitor(module_path, args) 166 167 def monitor(self, module_path, args): 168 169 elf_path = args.elf 170 if elf_path: 171 elf_path = os.path.abspath(elf_path) 172 else: 173 # get build elf file path 174 elf_path = get_build_elf_path() 175 176 esp_port = args.port 177 if not esp_port: 178 # detect usb port using esptool 179 esp_port = get_esp_serial_port(module_path) 180 181 monitor_path = Path(module_path, "tools/idf_monitor.py") 182 cmd_path = Path(os.getcwd()) 183 if platform.system() == 'Windows': 184 cmd_exec(("python.exe", monitor_path, "-p", esp_port, 185 "-b", args.baud, elf_path, "--eol", args.eol), cwd=cmd_path) 186 else: 187 cmd_exec((sys.executable, monitor_path, "-p", esp_port, "-b", args.baud, 188 elf_path, "--eol", args.eol), cwd=cmd_path) 189