console_parser.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. # SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. import queue
  4. import textwrap
  5. from typing import Optional
  6. from serial.tools import miniterm
  7. from .constants import (CMD_APP_FLASH, CMD_ENTER_BOOT, CMD_MAKE, CMD_OUTPUT_TOGGLE, CMD_RESET, CMD_STOP,
  8. CMD_TOGGLE_LOGGING, CMD_TOGGLE_TIMESTAMPS, CTRL_A, CTRL_F, CTRL_H, CTRL_I, CTRL_L, CTRL_P,
  9. CTRL_R, CTRL_RBRACKET, CTRL_T, CTRL_X, CTRL_Y, TAG_CMD, TAG_KEY, __version__)
  10. from .output_helpers import red_print
  11. key_description = miniterm.key_description
  12. def prompt_next_action(reason, console, console_parser, event_queue, cmd_queue):
  13. # type: (str, miniterm.Console, ConsoleParser, queue.Queue, queue.Queue) -> None
  14. console.setup() # set up console to trap input characters
  15. try:
  16. red_print('--- {}'.format(reason))
  17. red_print(console_parser.get_next_action_text())
  18. k = CTRL_T # ignore CTRL-T here, so people can muscle-memory Ctrl-T Ctrl-F, etc.
  19. while k == CTRL_T:
  20. k = console.getkey()
  21. finally:
  22. console.cleanup()
  23. ret = console_parser.parse_next_action_key(k)
  24. if ret is not None:
  25. cmd = ret[1]
  26. if cmd == CMD_STOP:
  27. # the stop command should be handled last
  28. event_queue.put(ret)
  29. else:
  30. cmd_queue.put(ret)
  31. class ConsoleParser(object):
  32. def __init__(self, eol='CRLF'): # type: (str) -> None
  33. self.translate_eol = {
  34. 'CRLF': lambda c: c.replace('\n', '\r\n'),
  35. 'CR': lambda c: c.replace('\n', '\r'),
  36. 'LF': lambda c: c.replace('\r', '\n'),
  37. }[eol]
  38. self.menu_key = CTRL_T
  39. self.exit_key = CTRL_RBRACKET
  40. self._pressed_menu_key = False
  41. def parse(self, key): # type: (str) -> Optional[tuple]
  42. ret = None
  43. if self._pressed_menu_key:
  44. ret = self._handle_menu_key(key)
  45. elif key == self.menu_key:
  46. self._pressed_menu_key = True
  47. elif key == self.exit_key:
  48. ret = (TAG_CMD, CMD_STOP)
  49. else:
  50. key = self.translate_eol(key)
  51. ret = (TAG_KEY, key)
  52. return ret
  53. def _handle_menu_key(self, c): # type: (str) -> Optional[tuple]
  54. ret = None
  55. if c == self.exit_key or c == self.menu_key: # send verbatim
  56. ret = (TAG_KEY, c)
  57. elif c in [CTRL_H, 'h', 'H', '?']:
  58. red_print(self.get_help_text())
  59. elif c == CTRL_R: # Reset device via RTS
  60. ret = (TAG_CMD, CMD_RESET)
  61. elif c == CTRL_F: # Recompile & upload
  62. ret = (TAG_CMD, CMD_MAKE)
  63. elif c in [CTRL_A, 'a', 'A']: # Recompile & upload app only
  64. # "CTRL-A" cannot be captured with the default settings of the Windows command line, therefore, "A" can be used
  65. # instead
  66. ret = (TAG_CMD, CMD_APP_FLASH)
  67. elif c == CTRL_Y: # Toggle output display
  68. ret = (TAG_CMD, CMD_OUTPUT_TOGGLE)
  69. elif c == CTRL_L: # Toggle saving output into file
  70. ret = (TAG_CMD, CMD_TOGGLE_LOGGING)
  71. elif c in [CTRL_I, 'i', 'I']: # Toggle printing timestamps
  72. ret = (TAG_CMD, CMD_TOGGLE_TIMESTAMPS)
  73. elif c == CTRL_P:
  74. # to fast trigger pause without press menu key
  75. ret = (TAG_CMD, CMD_ENTER_BOOT)
  76. elif c in [CTRL_X, 'x', 'X']: # Exiting from within the menu
  77. ret = (TAG_CMD, CMD_STOP)
  78. else:
  79. red_print('--- unknown menu character {} --'.format(key_description(c)))
  80. self._pressed_menu_key = False
  81. return ret
  82. def get_help_text(self): # type: () -> str
  83. text = """\
  84. --- idf_monitor ({version}) - ESP-IDF monitor tool
  85. --- based on miniterm from pySerial
  86. ---
  87. --- {exit:8} Exit program
  88. --- {menu:8} Menu escape key, followed by:
  89. --- Menu keys:
  90. --- {menu:14} Send the menu character itself to remote
  91. --- {exit:14} Send the exit character itself to remote
  92. --- {reset:14} Reset target board via RTS line
  93. --- {makecmd:14} Build & flash project
  94. --- {appmake:14} Build & flash app only
  95. --- {output:14} Toggle output display
  96. --- {log:14} Toggle saving output into file
  97. --- {timestamps:14} Toggle printing timestamps
  98. --- {pause:14} Reset target into bootloader to pause app via RTS line
  99. --- {menuexit:14} Exit program
  100. """.format(version=__version__,
  101. exit=key_description(self.exit_key),
  102. menu=key_description(self.menu_key),
  103. reset=key_description(CTRL_R),
  104. makecmd=key_description(CTRL_F),
  105. appmake=key_description(CTRL_A) + ' (or A)',
  106. output=key_description(CTRL_Y),
  107. log=key_description(CTRL_L),
  108. timestamps=key_description(CTRL_I) + ' (or I)',
  109. pause=key_description(CTRL_P),
  110. menuexit=key_description(CTRL_X) + ' (or X)')
  111. return textwrap.dedent(text)
  112. def get_next_action_text(self): # type: () -> str
  113. text = """\
  114. --- Press {} to exit monitor.
  115. --- Press {} to build & flash project.
  116. --- Press {} to build & flash app.
  117. --- Press any other key to resume monitor (resets target).
  118. """.format(key_description(self.exit_key),
  119. key_description(CTRL_F),
  120. key_description(CTRL_A))
  121. return textwrap.dedent(text)
  122. def parse_next_action_key(self, c): # type: (str) -> Optional[tuple]
  123. ret = None
  124. if c == self.exit_key:
  125. ret = (TAG_CMD, CMD_STOP)
  126. elif c == CTRL_F: # Recompile & upload
  127. ret = (TAG_CMD, CMD_MAKE)
  128. elif c in [CTRL_A, 'a', 'A']: # Recompile & upload app only
  129. # "CTRL-A" cannot be captured with the default settings of the Windows command line, therefore, "A" can be used
  130. # instead
  131. ret = (TAG_CMD, CMD_APP_FLASH)
  132. return ret