serial_ext.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. import json
  4. import os
  5. import sys
  6. from typing import Any, Dict, List
  7. import click
  8. from idf_monitor_base.output_helpers import yellow_print
  9. from idf_py_actions.errors import FatalError, NoSerialPortFoundError
  10. from idf_py_actions.global_options import global_options
  11. from idf_py_actions.tools import PropertyDict, RunTool, ensure_build_directory, get_sdkconfig_value, run_target
  12. PYTHON = sys.executable
  13. def action_extensions(base_actions: Dict, project_path: str) -> Dict:
  14. def _get_project_desc(ctx: click.core.Context, args: PropertyDict) -> Any:
  15. desc_path = os.path.join(args.build_dir, 'project_description.json')
  16. if not os.path.exists(desc_path):
  17. ensure_build_directory(args, ctx.info_name)
  18. with open(desc_path, 'r') as f:
  19. project_desc = json.load(f)
  20. return project_desc
  21. def _get_default_serial_port(args: PropertyDict) -> Any:
  22. # Import is done here in order to move it after the check_environment() ensured that pyserial has been installed
  23. try:
  24. import esptool
  25. import serial.tools.list_ports
  26. ports = list(sorted(p.device for p in serial.tools.list_ports.comports()))
  27. # high baud rate could cause the failure of creation of the connection
  28. esp = esptool.get_default_connected_device(serial_list=ports, port=None, connect_attempts=4,
  29. initial_baud=115200)
  30. if esp is None:
  31. raise NoSerialPortFoundError(
  32. "No serial ports found. Connect a device, or use '-p PORT' option to set a specific port.")
  33. serial_port = esp.serial_port
  34. esp._port.close()
  35. return serial_port
  36. except NoSerialPortFoundError:
  37. raise
  38. except Exception as e:
  39. raise FatalError('An exception occurred during detection of the serial port: {}'.format(e))
  40. def _get_esptool_args(args: PropertyDict) -> List:
  41. esptool_path = os.path.join(os.environ['IDF_PATH'], 'components/esptool_py/esptool/esptool.py')
  42. esptool_wrapper_path = os.environ.get('ESPTOOL_WRAPPER', '')
  43. if args.port is None:
  44. args.port = _get_default_serial_port(args)
  45. result = [PYTHON]
  46. if os.path.exists(esptool_wrapper_path):
  47. result += [esptool_wrapper_path]
  48. result += [esptool_path]
  49. result += ['-p', args.port]
  50. result += ['-b', str(args.baud)]
  51. with open(os.path.join(args.build_dir, 'flasher_args.json')) as f:
  52. flasher_args = json.load(f)
  53. extra_esptool_args = flasher_args['extra_esptool_args']
  54. result += ['--before', extra_esptool_args['before']]
  55. result += ['--after', extra_esptool_args['after']]
  56. result += ['--chip', extra_esptool_args['chip']]
  57. if not extra_esptool_args['stub']:
  58. result += ['--no-stub']
  59. return result
  60. def _get_commandline_options(ctx: click.core.Context) -> List:
  61. """ Return all the command line options up to first action """
  62. # This approach ignores argument parsing done Click
  63. result = []
  64. for arg in sys.argv:
  65. if arg in ctx.command.commands_with_aliases:
  66. break
  67. result.append(arg)
  68. return result
  69. def monitor(action: str, ctx: click.core.Context, args: PropertyDict, print_filter: str, monitor_baud: str, encrypted: bool,
  70. no_reset: bool, timestamps: bool, timestamp_format: str, force_color: bool) -> None:
  71. """
  72. Run idf_monitor.py to watch build output
  73. """
  74. project_desc = _get_project_desc(ctx, args)
  75. elf_file = os.path.join(args.build_dir, project_desc['app_elf'])
  76. idf_monitor = os.path.join(os.environ['IDF_PATH'], 'tools/idf_monitor.py')
  77. monitor_args = [PYTHON, idf_monitor]
  78. if project_desc['target'] != 'linux':
  79. if no_reset and args.port is None:
  80. msg = ('WARNING: --no-reset is ignored. '
  81. 'Please specify the port with the --port argument in order to use this option.')
  82. yellow_print(msg)
  83. no_reset = False
  84. esp_port = args.port or _get_default_serial_port(args)
  85. monitor_args += ['-p', esp_port]
  86. baud = monitor_baud or os.getenv('IDF_MONITOR_BAUD') or os.getenv('MONITORBAUD')
  87. if baud is None:
  88. # Baud hasn't been changed locally (by local baud argument nor by environment variables)
  89. #
  90. # Use the global baud rate if it has been changed by the command line.
  91. # Use project_desc['monitor_baud'] as the last option.
  92. global_baud_defined = ctx._parameter_source['baud'] == click.core.ParameterSource.COMMANDLINE
  93. baud = args.baud if global_baud_defined else project_desc['monitor_baud']
  94. monitor_args += ['-b', baud]
  95. monitor_args += ['--toolchain-prefix', project_desc['monitor_toolprefix']]
  96. coredump_decode = get_sdkconfig_value(project_desc['config_file'], 'CONFIG_ESP_COREDUMP_DECODE')
  97. if coredump_decode is not None:
  98. monitor_args += ['--decode-coredumps', coredump_decode]
  99. target_arch_riscv = get_sdkconfig_value(project_desc['config_file'], 'CONFIG_IDF_TARGET_ARCH_RISCV')
  100. monitor_args += ['--target', project_desc['target']]
  101. revision = project_desc.get('rev')
  102. if revision:
  103. monitor_args += ['--revision', revision]
  104. if target_arch_riscv:
  105. monitor_args += ['--decode-panic', 'backtrace']
  106. if print_filter is not None:
  107. monitor_args += ['--print_filter', print_filter]
  108. if elf_file:
  109. monitor_args += [elf_file]
  110. if encrypted:
  111. monitor_args += ['--encrypted']
  112. if no_reset:
  113. monitor_args += ['--no-reset']
  114. if timestamps:
  115. monitor_args += ['--timestamps']
  116. if timestamp_format:
  117. monitor_args += ['--timestamp-format', timestamp_format]
  118. if force_color or os.name == 'nt':
  119. monitor_args += ['--force-color']
  120. idf_py = [PYTHON] + _get_commandline_options(ctx) # commands to re-run idf.py
  121. monitor_args += ['-m', ' '.join("'%s'" % a for a in idf_py)]
  122. hints = not args.no_hints
  123. RunTool('idf_monitor', monitor_args, args.project_dir, build_dir=args.build_dir, hints=hints, interactive=True)()
  124. def flash(action: str, ctx: click.core.Context, args: PropertyDict) -> None:
  125. """
  126. Run esptool to flash the entire project, from an argfile generated by the build system
  127. """
  128. ensure_build_directory(args, ctx.info_name)
  129. project_desc = _get_project_desc(ctx, args)
  130. if project_desc['target'] == 'linux':
  131. yellow_print('skipping flash since running on linux...')
  132. return
  133. esp_port = args.port or _get_default_serial_port(args)
  134. run_target(action, args, {'ESPBAUD': str(args.baud), 'ESPPORT': esp_port})
  135. def erase_flash(action: str, ctx: click.core.Context, args: PropertyDict) -> None:
  136. ensure_build_directory(args, ctx.info_name)
  137. esptool_args = _get_esptool_args(args)
  138. esptool_args += ['erase_flash']
  139. RunTool('esptool.py', esptool_args, args.build_dir, hints=not args.no_hints)()
  140. def global_callback(ctx: click.core.Context, global_args: Dict, tasks: PropertyDict) -> None:
  141. encryption = any([task.name in ('encrypted-flash', 'encrypted-app-flash') for task in tasks])
  142. if encryption:
  143. for task in tasks:
  144. if task.name == 'monitor':
  145. task.action_args['encrypted'] = True
  146. break
  147. def ota_targets(target_name: str, ctx: click.core.Context, args: PropertyDict) -> None:
  148. """
  149. Execute the target build system to build target 'target_name'.
  150. Additionally set global variables for baud and port.
  151. Calls ensure_build_directory() which will run cmake to generate a build
  152. directory (with the specified generator) as needed.
  153. """
  154. args.port = args.port or _get_default_serial_port(args)
  155. ensure_build_directory(args, ctx.info_name)
  156. run_target(target_name, args, {'ESPBAUD': str(args.baud), 'ESPPORT': args.port})
  157. baud_rate = {
  158. 'names': ['-b', '--baud'],
  159. 'help': 'Baud rate for flashing. It can imply monitor baud rate as well if it hasn\'t been defined locally.',
  160. 'scope': 'global',
  161. 'envvar': 'ESPBAUD',
  162. 'default': 460800,
  163. }
  164. port = {
  165. 'names': ['-p', '--port'],
  166. 'help': 'Serial port.',
  167. 'scope': 'global',
  168. 'envvar': 'ESPPORT',
  169. 'default': None,
  170. }
  171. BAUD_AND_PORT = [baud_rate, port]
  172. serial_actions = {
  173. 'global_action_callbacks': [global_callback],
  174. 'actions': {
  175. 'flash': {
  176. 'callback': flash,
  177. 'help': 'Flash the project.',
  178. 'options': global_options + BAUD_AND_PORT,
  179. 'order_dependencies': ['all', 'erase-flash'],
  180. },
  181. 'erase-flash': {
  182. 'callback': erase_flash,
  183. 'help': 'Erase entire flash chip.',
  184. 'options': BAUD_AND_PORT,
  185. },
  186. 'erase_flash': {
  187. 'callback': erase_flash,
  188. 'deprecated': {
  189. 'since': 'v4.4',
  190. 'removed': 'next major release',
  191. 'message': 'Have you wanted to run "erase-flash" instead?',
  192. },
  193. 'hidden': True,
  194. 'help': 'Erase entire flash chip.',
  195. 'options': BAUD_AND_PORT,
  196. },
  197. 'monitor': {
  198. 'callback':
  199. monitor,
  200. 'help':
  201. 'Display serial output.',
  202. 'options': [
  203. port, {
  204. 'names': ['--print-filter', '--print_filter'],
  205. 'help':
  206. ('Filter monitor output. '
  207. 'Restrictions on what to print can be specified as a series of <tag>:<log_level> items '
  208. 'where <tag> is the tag string and <log_level> is a character from the set '
  209. '{N, E, W, I, D, V, *} referring to a level. '
  210. 'For example, "tag1:W" matches and prints only the outputs written with '
  211. 'ESP_LOGW("tag1", ...) or at lower verbosity level, i.e. ESP_LOGE("tag1", ...). '
  212. 'Not specifying a <log_level> or using "*" defaults to Verbose level. '
  213. 'Please see the IDF Monitor section of the ESP-IDF documentation '
  214. 'for a more detailed description and further examples.'),
  215. 'default':
  216. None,
  217. }, {
  218. 'names': ['--monitor-baud', '-b'],
  219. 'type':
  220. click.INT,
  221. 'help': ('Baud rate for monitor. '
  222. 'If this option is not provided IDF_MONITOR_BAUD and MONITORBAUD '
  223. 'environment variables, global baud rate and project_description.json in build directory '
  224. "(generated by CMake from project's sdkconfig) "
  225. 'will be checked for default value.'),
  226. }, {
  227. 'names': ['--encrypted', '-E'],
  228. 'is_flag': True,
  229. 'help': ('Enable encrypted flash targets. '
  230. 'IDF Monitor will invoke encrypted-flash and encrypted-app-flash targets '
  231. 'if this option is set. This option is set by default if IDF Monitor was invoked '
  232. 'together with encrypted-flash or encrypted-app-flash target.'),
  233. }, {
  234. 'names': ['--no-reset'],
  235. 'is_flag': True,
  236. 'help': ('Disable reset on monitor startup. '
  237. 'IDF Monitor will not reset the MCU target by toggling DTR/RTS lines on startup '
  238. 'if this option is set.'),
  239. }, {
  240. 'names': ['--timestamps'],
  241. 'is_flag': True,
  242. 'help': 'Print a time stamp in the beginning of each line.',
  243. }, {
  244. 'names': ['--timestamp-format'],
  245. 'help': ('Set the formatting of timestamps compatible with strftime(). '
  246. 'For example, "%Y-%m-%d %H:%M:%S".'),
  247. 'default': None
  248. }, {
  249. 'names': ['--force-color'],
  250. 'is_flag': True,
  251. 'help': 'Always print ANSI for colors',
  252. }
  253. ],
  254. 'order_dependencies': [
  255. 'flash',
  256. 'encrypted-flash',
  257. 'partition-table-flash',
  258. 'bootloader-flash',
  259. 'app-flash',
  260. 'encrypted-app-flash',
  261. ],
  262. },
  263. 'partition-table-flash': {
  264. 'callback': flash,
  265. 'help': 'Flash partition table only.',
  266. 'options': BAUD_AND_PORT,
  267. 'order_dependencies': ['partition-table', 'erase-flash'],
  268. },
  269. 'bootloader-flash': {
  270. 'callback': flash,
  271. 'help': 'Flash bootloader only.',
  272. 'options': BAUD_AND_PORT,
  273. 'order_dependencies': ['bootloader', 'erase-flash'],
  274. },
  275. 'app-flash': {
  276. 'callback': flash,
  277. 'help': 'Flash the app only.',
  278. 'options': BAUD_AND_PORT,
  279. 'order_dependencies': ['app', 'erase-flash'],
  280. },
  281. 'encrypted-app-flash': {
  282. 'callback': flash,
  283. 'help': 'Flash the encrypted app only.',
  284. 'order_dependencies': ['app', 'erase-flash'],
  285. },
  286. 'encrypted-flash': {
  287. 'callback': flash,
  288. 'help': 'Flash the encrypted project.',
  289. 'order_dependencies': ['all', 'erase-flash'],
  290. },
  291. 'erase-otadata': {
  292. 'callback': ota_targets,
  293. 'help': 'Erase otadata partition.',
  294. 'options': global_options + BAUD_AND_PORT,
  295. },
  296. 'read-otadata': {
  297. 'callback': ota_targets,
  298. 'help': 'Read otadata partition.',
  299. 'options': global_options + BAUD_AND_PORT,
  300. },
  301. },
  302. }
  303. return serial_actions