serial_ext.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import json
  2. import os
  3. import sys
  4. import click
  5. from idf_py_actions.errors import FatalError, NoSerialPortFoundError
  6. from idf_py_actions.global_options import global_options
  7. from idf_py_actions.tools import ensure_build_directory, get_sdkconfig_value, run_target, run_tool
  8. PYTHON = sys.executable
  9. def action_extensions(base_actions, project_path):
  10. def _get_default_serial_port(args):
  11. # Import is done here in order to move it after the check_environment() ensured that pyserial has been installed
  12. try:
  13. import serial.tools.list_ports
  14. esptool_path = os.path.join(os.environ['IDF_PATH'], 'components/esptool_py/esptool/')
  15. sys.path.insert(0, esptool_path)
  16. import esptool
  17. ports = list(sorted(p.device for p in serial.tools.list_ports.comports()))
  18. # high baud rate could cause the failure of creation of the connection
  19. esp = esptool.get_default_connected_device(serial_list=ports, port=None, connect_attempts=4,
  20. initial_baud=115200)
  21. if esp is None:
  22. raise NoSerialPortFoundError(
  23. "No serial ports found. Connect a device, or use '-p PORT' option to set a specific port.")
  24. serial_port = esp.serial_port
  25. esp._port.close()
  26. return serial_port
  27. except NoSerialPortFoundError:
  28. raise
  29. except Exception as e:
  30. raise FatalError('An exception occurred during detection of the serial port: {}'.format(e))
  31. def _get_esptool_args(args):
  32. esptool_path = os.path.join(os.environ['IDF_PATH'], 'components/esptool_py/esptool/esptool.py')
  33. esptool_wrapper_path = os.environ.get('ESPTOOL_WRAPPER', '')
  34. if args.port is None:
  35. args.port = _get_default_serial_port(args)
  36. result = [PYTHON]
  37. if os.path.exists(esptool_wrapper_path):
  38. result += [esptool_wrapper_path]
  39. result += [esptool_path]
  40. result += ['-p', args.port]
  41. result += ['-b', str(args.baud)]
  42. with open(os.path.join(args.build_dir, 'flasher_args.json')) as f:
  43. flasher_args = json.load(f)
  44. extra_esptool_args = flasher_args['extra_esptool_args']
  45. result += ['--before', extra_esptool_args['before']]
  46. result += ['--after', extra_esptool_args['after']]
  47. result += ['--chip', extra_esptool_args['chip']]
  48. if not extra_esptool_args['stub']:
  49. result += ['--no-stub']
  50. return result
  51. def _get_commandline_options(ctx):
  52. """ Return all the command line options up to first action """
  53. # This approach ignores argument parsing done Click
  54. result = []
  55. for arg in sys.argv:
  56. if arg in ctx.command.commands_with_aliases:
  57. break
  58. result.append(arg)
  59. return result
  60. def monitor(action, ctx, args, print_filter, monitor_baud, encrypted, timestamps, timestamp_format):
  61. """
  62. Run idf_monitor.py to watch build output
  63. """
  64. desc_path = os.path.join(args.build_dir, 'project_description.json')
  65. if not os.path.exists(desc_path):
  66. ensure_build_directory(args, ctx.info_name)
  67. with open(desc_path, 'r') as f:
  68. project_desc = json.load(f)
  69. elf_file = os.path.join(args.build_dir, project_desc['app_elf'])
  70. if not os.path.exists(elf_file):
  71. raise FatalError("ELF file '%s' not found. You need to build & flash the project before running 'monitor', "
  72. 'and the binary on the device must match the one in the build directory exactly. '
  73. "Try '%s flash monitor'." % (elf_file, ctx.info_name), ctx)
  74. idf_monitor = os.path.join(os.environ['IDF_PATH'], 'tools/idf_monitor.py')
  75. monitor_args = [PYTHON, idf_monitor]
  76. esp_port = args.port or _get_default_serial_port(args)
  77. monitor_args += ['-p', esp_port]
  78. if not monitor_baud:
  79. monitor_baud = os.getenv('IDF_MONITOR_BAUD') or os.getenv('MONITORBAUD') or project_desc['monitor_baud']
  80. monitor_args += ['-b', monitor_baud]
  81. monitor_args += ['--toolchain-prefix', project_desc['monitor_toolprefix']]
  82. coredump_decode = get_sdkconfig_value(project_desc['config_file'], 'CONFIG_ESP_COREDUMP_DECODE')
  83. if coredump_decode is not None:
  84. monitor_args += ['--decode-coredumps', coredump_decode]
  85. target_arch_riscv = get_sdkconfig_value(project_desc['config_file'], 'CONFIG_IDF_TARGET_ARCH_RISCV')
  86. monitor_args += ['--target', project_desc['target']]
  87. revision = project_desc.get('rev')
  88. if revision:
  89. monitor_args += ['--revision', revision]
  90. if target_arch_riscv:
  91. monitor_args += ['--decode-panic', 'backtrace']
  92. if print_filter is not None:
  93. monitor_args += ['--print_filter', print_filter]
  94. monitor_args += [elf_file]
  95. if encrypted:
  96. monitor_args += ['--encrypted']
  97. if timestamps:
  98. monitor_args += ['--timestamps']
  99. if timestamp_format:
  100. monitor_args += ['--timestamp-format', timestamp_format]
  101. idf_py = [PYTHON] + _get_commandline_options(ctx) # commands to re-run idf.py
  102. monitor_args += ['-m', ' '.join("'%s'" % a for a in idf_py)]
  103. if 'MSYSTEM' in os.environ:
  104. monitor_args = ['winpty'] + monitor_args
  105. run_tool('idf_monitor', monitor_args, args.project_dir)
  106. def flash(action, ctx, args):
  107. """
  108. Run esptool to flash the entire project, from an argfile generated by the build system
  109. """
  110. ensure_build_directory(args, ctx.info_name)
  111. esp_port = args.port or _get_default_serial_port(args)
  112. run_target(action, args, {'ESPBAUD': str(args.baud),'ESPPORT': esp_port})
  113. def erase_flash(action, ctx, args):
  114. ensure_build_directory(args, ctx.info_name)
  115. esptool_args = _get_esptool_args(args)
  116. esptool_args += ['erase_flash']
  117. run_tool('esptool.py', esptool_args, args.build_dir)
  118. def global_callback(ctx, global_args, tasks):
  119. encryption = any([task.name in ('encrypted-flash', 'encrypted-app-flash') for task in tasks])
  120. if encryption:
  121. for task in tasks:
  122. if task.name == 'monitor':
  123. task.action_args['encrypted'] = True
  124. break
  125. baud_rate = {
  126. 'names': ['-b', '--baud'],
  127. 'help': 'Baud rate for flashing.',
  128. 'scope': 'global',
  129. 'envvar': 'ESPBAUD',
  130. 'default': 460800,
  131. }
  132. port = {
  133. 'names': ['-p', '--port'],
  134. 'help': 'Serial port.',
  135. 'scope': 'global',
  136. 'envvar': 'ESPPORT',
  137. 'default': None,
  138. }
  139. serial_actions = {
  140. 'global_action_callbacks': [global_callback],
  141. 'actions': {
  142. 'flash': {
  143. 'callback': flash,
  144. 'help': 'Flash the project.',
  145. 'options': global_options + [baud_rate, port],
  146. 'order_dependencies': ['all', 'erase_flash'],
  147. },
  148. 'erase_flash': {
  149. 'callback': erase_flash,
  150. 'help': 'Erase entire flash chip.',
  151. 'options': [baud_rate, port],
  152. },
  153. 'monitor': {
  154. 'callback':
  155. monitor,
  156. 'help':
  157. 'Display serial output.',
  158. 'options': [
  159. port, {
  160. 'names': ['--print-filter', '--print_filter'],
  161. 'help':
  162. ('Filter monitor output. '
  163. 'Restrictions on what to print can be specified as a series of <tag>:<log_level> items '
  164. 'where <tag> is the tag string and <log_level> is a character from the set '
  165. '{N, E, W, I, D, V, *} referring to a level. '
  166. 'For example, "tag1:W" matches and prints only the outputs written with '
  167. 'ESP_LOGW("tag1", ...) or at lower verbosity level, i.e. ESP_LOGE("tag1", ...). '
  168. 'Not specifying a <log_level> or using "*" defaults to Verbose level. '
  169. 'Please see the IDF Monitor section of the ESP-IDF documentation '
  170. 'for a more detailed description and further examples.'),
  171. 'default':
  172. None,
  173. }, {
  174. 'names': ['--monitor-baud', '-B'],
  175. 'type':
  176. click.INT,
  177. 'help': ('Baud rate for monitor. '
  178. 'If this option is not provided IDF_MONITOR_BAUD and MONITORBAUD '
  179. 'environment variables and project_description.json in build directory '
  180. "(generated by CMake from project's sdkconfig) "
  181. 'will be checked for default value.'),
  182. }, {
  183. 'names': ['--encrypted', '-E'],
  184. 'is_flag': True,
  185. 'help': ('Enable encrypted flash targets. '
  186. 'IDF Monitor will invoke encrypted-flash and encrypted-app-flash targets '
  187. 'if this option is set. This option is set by default if IDF Monitor was invoked '
  188. 'together with encrypted-flash or encrypted-app-flash target.'),
  189. }, {
  190. 'names': ['--timestamps'],
  191. 'is_flag': True,
  192. 'help': 'Print a time stamp in the beginning of each line.',
  193. }, {
  194. 'names': ['--timestamp-format'],
  195. 'help': ('Set the formatting of timestamps compatible with strftime(). '
  196. 'For example, "%Y-%m-%d %H:%M:%S".'),
  197. 'default': None
  198. }
  199. ],
  200. 'order_dependencies': [
  201. 'flash',
  202. 'encrypted-flash',
  203. 'partition_table-flash',
  204. 'bootloader-flash',
  205. 'app-flash',
  206. 'encrypted-app-flash',
  207. ],
  208. },
  209. 'partition_table-flash': {
  210. 'callback': flash,
  211. 'help': 'Flash partition table only.',
  212. 'options': [baud_rate, port],
  213. 'order_dependencies': ['partition_table', 'erase_flash'],
  214. },
  215. 'bootloader-flash': {
  216. 'callback': flash,
  217. 'help': 'Flash bootloader only.',
  218. 'options': [baud_rate, port],
  219. 'order_dependencies': ['bootloader', 'erase_flash'],
  220. },
  221. 'app-flash': {
  222. 'callback': flash,
  223. 'help': 'Flash the app only.',
  224. 'options': [baud_rate, port],
  225. 'order_dependencies': ['app', 'erase_flash'],
  226. },
  227. 'encrypted-app-flash': {
  228. 'callback': flash,
  229. 'help': 'Flash the encrypted app only.',
  230. 'order_dependencies': ['app', 'erase_flash'],
  231. },
  232. 'encrypted-flash': {
  233. 'callback': flash,
  234. 'help': 'Flash the encrypted project.',
  235. 'order_dependencies': ['all', 'erase_flash'],
  236. },
  237. },
  238. }
  239. return serial_actions