console_parser.py 6.6 KB

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