console_parser.py 6.7 KB

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