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 34build_elf_path = None 35baud_rate = None 36 37def cmd_check(cmd, cwd=None, stderr=subprocess.STDOUT): 38 return subprocess.check_output(cmd, cwd=cwd, stderr=stderr) 39 40 41def cmd_exec(cmd, cwd=None, shell=False): 42 return subprocess.check_call(cmd, cwd=cwd, shell=shell) 43 44 45def get_esp_serial_port(module_path): 46 try: 47 import serial.tools.list_ports 48 esptool_path = os.path.join(module_path, 'tools', 'esptool_py') 49 sys.path.insert(0, esptool_path) 50 import esptool 51 ports = list(sorted(p.device for p in serial.tools.list_ports.comports())) 52 # high baud rate could cause the failure of creation of the connection 53 esp = esptool.get_default_connected_device(serial_list=ports, port=None, connect_attempts=4, 54 initial_baud=115200) 55 if esp is None: 56 log.die("No serial ports found. Connect a device, or use '-p PORT' " 57 "option to set a specific port.") 58 59 serial_port = esp.serial_port 60 esp._port.close() 61 62 return serial_port 63 except Exception as e: 64 log.die("Error detecting ESP serial port: {}".format(e)) 65 return None 66 67 68def parse_runners_yaml(): 69 def runners_yaml_path(build_dir): 70 ret = Path(build_dir) / 'zephyr' / 'runners.yaml' 71 if not ret.is_file(): 72 log.die("could not find build configuration") 73 return ret 74 75 def load_runners_yaml(path): 76 # Load runners.yaml and convert to Python object. 77 78 try: 79 with open(path, 'r') as f: 80 content = yaml.safe_load(f.read()) 81 except Exception as e: 82 log.die("runners.yaml file open error: {}".format(e)) 83 84 if not content.get('runners'): 85 log.wrn("no pre-configured runners in {}; " 86 "this probably won't work".format(path)) 87 88 return content 89 90 global build_elf_path 91 global baud_rate 92 93 build_dir = find_build_dir(None, True) 94 domain = load_domains(build_dir).get_default_domain() 95 96 # build dir differs when sysbuild is used 97 if domain.build_dir: 98 build_dir = domain.build_dir 99 100 yaml_path = runners_yaml_path(build_dir) 101 runners_yaml = load_runners_yaml(yaml_path) 102 103 # get build elf file path 104 build_elf_path = Path(build_dir) / 'zephyr' / runners_yaml['config']['elf_file'] 105 106 # parse specific arguments for the tool 107 yaml_args = runners_yaml['args']['esp32'] 108 109 # Iterate through the list to find default baud rate 110 for item in yaml_args: 111 if item.startswith('--esp-monitor-baud='): 112 baud_rate = item.split('=')[1] 113 break 114 115 # if specific default baud rate is not defined in runners.yaml, default to 115200 116 if not baud_rate: 117 baud_rate = "115200" 118 119class Tools(WestCommand): 120 121 def __init__(self): 122 super().__init__( 123 'espressif', 124 # Keep this in sync with the string in west-commands.yml. 125 'Espressif tools for west framework.', 126 dedent(''' 127 This interface allows having esp-idf monitor support.'''), 128 accepts_unknown_args=False) 129 130 def do_add_parser(self, parser_adder): 131 132 parse_runners_yaml() 133 134 parser = parser_adder.add_parser(self.name, 135 help=self.help, 136 description=self.description) 137 138 parser.add_argument('command', choices=['monitor'], 139 help='open serial port based on esp-idf monitor') 140 141 # monitor arguments 142 group = parser.add_argument_group('monitor optional arguments') 143 group.add_argument('-b', '--baud', default=baud_rate, help='Serial port baud rate') 144 group.add_argument('-p', '--port', help='Serial port address') 145 group.add_argument('-e', '--elf', help='ELF file') 146 group.add_argument('-n', '--eol', default='CRLF', help='EOL to use') 147 group.add_argument('-d', '--enable-address-decoding', action='store_true', 148 help='Enable address decoding in the monitor') 149 150 return parser 151 152 def do_run(self, args, unknown_args): 153 154 module_path = ( 155 Path(os.getenv("ZEPHYR_BASE")).absolute() 156 / r".." 157 / "modules" 158 / "hal" 159 / "espressif" 160 ) 161 162 if not module_path.exists(): 163 log.die('cannot find espressif hal in $ZEPHYR_BASE/../modules/hal/ path') 164 165 if args.command == "monitor": 166 self.monitor(module_path, args) 167 168 def monitor(self, module_path, args): 169 170 elf_path = args.elf 171 if elf_path: 172 elf_path = os.path.abspath(elf_path) 173 else: 174 elf_path = 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/idf_monitor.py") 182 cmd_path = Path(os.getcwd()) 183 184 cmd = [sys.executable, str(monitor_path), "-p", esp_port, "-b", args.baud, str(elf_path), "--eol", args.eol] 185 186 # Adding "-d" argument for idf_monitor.py disables the address decoding 187 if not args.enable_address_decoding: 188 cmd.append("-d") 189 190 cmd_exec(cmd, cwd=cmd_path) 191