serial_ext.py 11 KB

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