1# SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD 2# SPDX-License-Identifier: Apache-2.0 3 4import queue 5import textwrap 6from typing import Optional 7 8from serial.tools import miniterm 9 10from .constants import (CMD_APP_FLASH, CMD_ENTER_BOOT, CMD_MAKE, CMD_OUTPUT_TOGGLE, CMD_RESET, CMD_STOP, 11 CMD_TOGGLE_LOGGING, CMD_TOGGLE_TIMESTAMPS, CTRL_A, CTRL_C, CTRL_F, CTRL_H, CTRL_I, CTRL_L, CTRL_P, 12 CTRL_R, CTRL_RBRACKET, CTRL_T, CTRL_X, CTRL_Y, TAG_CMD, TAG_KEY, __version__) 13from .output_helpers import red_print 14 15key_description = miniterm.key_description 16 17 18def prompt_next_action(reason, console, console_parser, event_queue, cmd_queue): 19 # type: (str, miniterm.Console, ConsoleParser, queue.Queue, queue.Queue) -> None 20 console.setup() # set up console to trap input characters 21 try: 22 red_print('--- {}'.format(reason)) 23 red_print(console_parser.get_next_action_text()) 24 25 k = CTRL_T # ignore CTRL-T here, so people can muscle-memory Ctrl-T Ctrl-F, etc. 26 while k == CTRL_T: 27 k = console.getkey() 28 finally: 29 console.cleanup() 30 ret = console_parser.parse_next_action_key(k) 31 if ret is not None: 32 cmd = ret[1] 33 if cmd == CMD_STOP: 34 # the stop command should be handled last 35 event_queue.put(ret) 36 else: 37 cmd_queue.put(ret) 38 39 40class ConsoleParser(object): 41 42 def __init__(self, eol='CRLF'): # type: (str) -> None 43 self.translate_eol = { 44 'CRLF': lambda c: c.replace('\n', '\r\n'), 45 'CR': lambda c: c.replace('\n', '\r'), 46 'LF': lambda c: c.replace('\r', '\n'), 47 }[eol] 48 self.menu_key = CTRL_T 49 self.exit_key = CTRL_RBRACKET 50 self._pressed_menu_key = False 51 self.eol = {'CRLF': b'\r\n', 'CR': b'\r', 'LF': b'\n'}[eol] 52 53 def parse(self, key): # type: (str) -> Optional[tuple] 54 ret = None 55 if self._pressed_menu_key: 56 ret = self._handle_menu_key(key) 57 elif key == self.menu_key: 58 self._pressed_menu_key = True 59 elif key == self.exit_key or key == CTRL_C: 60 ret = (TAG_CMD, CMD_STOP) 61 else: 62 key = self.translate_eol(key) 63 ret = (TAG_KEY, key) 64 return ret 65 66 def _handle_menu_key(self, c): # type: (str) -> Optional[tuple] 67 ret = None 68 if c == self.exit_key or c == self.menu_key: # send verbatim 69 ret = (TAG_KEY, c) 70 elif c in [CTRL_H, 'h', 'H', '?']: 71 red_print(self.get_help_text()) 72 elif c == CTRL_R: # Reset device via RTS 73 ret = (TAG_CMD, CMD_RESET) 74 elif c == CTRL_F: # Recompile & upload 75 ret = (TAG_CMD, CMD_MAKE) 76 elif c in [CTRL_A, 'a', 'A']: # Recompile & upload app only 77 # "CTRL-A" cannot be captured with the default settings of the Windows command line, therefore, "A" can be used 78 # instead 79 ret = (TAG_CMD, CMD_APP_FLASH) 80 elif c == CTRL_Y: # Toggle output display 81 ret = (TAG_CMD, CMD_OUTPUT_TOGGLE) 82 elif c == CTRL_L: # Toggle saving output into file 83 ret = (TAG_CMD, CMD_TOGGLE_LOGGING) 84 elif c in [CTRL_I, 'i', 'I']: # Toggle printing timestamps 85 ret = (TAG_CMD, CMD_TOGGLE_TIMESTAMPS) 86 elif c == CTRL_P: 87 # to fast trigger pause without press menu key 88 ret = (TAG_CMD, CMD_ENTER_BOOT) 89 elif c in [CTRL_X, 'x', 'X']: # Exiting from within the menu 90 ret = (TAG_CMD, CMD_STOP) 91 else: 92 red_print('--- unknown menu character {} --'.format(key_description(c))) 93 94 self._pressed_menu_key = False 95 return ret 96 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 (or CTRL+C) 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 128 def get_next_action_text(self): # type: () -> str 129 text = """\ 130 --- Press {} to exit monitor. 131 --- Press {} to build & flash project. 132 --- Press {} to build & flash app. 133 --- Press any other key to resume monitor (resets target). 134 """.format(key_description(self.exit_key), 135 key_description(CTRL_F), 136 key_description(CTRL_A)) 137 return textwrap.dedent(text) 138 139 def parse_next_action_key(self, c): # type: (str) -> Optional[tuple] 140 ret = None 141 if c == self.exit_key: 142 ret = (TAG_CMD, CMD_STOP) 143 elif c == CTRL_F: # Recompile & upload 144 ret = (TAG_CMD, CMD_MAKE) 145 elif c in [CTRL_A, 'a', 'A']: # Recompile & upload app only 146 # "CTRL-A" cannot be captured with the default settings of the Windows command line, therefore, "A" can be used 147 # instead 148 ret = (TAG_CMD, CMD_APP_FLASH) 149 return ret 150