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