Ver código fonte

idf.py: add linux target support for `idf.py flash` and `idf.py monitor`

Fu Hanxi 4 anos atrás
pai
commit
bee690aa8d

+ 0 - 6
tools/ci/check_copyright_ignore.txt

@@ -4024,11 +4024,9 @@ tools/find_build_apps/common.py
 tools/find_build_apps/make.py
 tools/gdb_panic_server.py
 tools/gen_esp_err_to_name.py
-tools/idf_monitor.py
 tools/idf_monitor_base/ansi_color_converter.py
 tools/idf_monitor_base/argument_parser.py
 tools/idf_monitor_base/chip_specific_config.py
-tools/idf_monitor_base/console_parser.py
 tools/idf_monitor_base/console_reader.py
 tools/idf_monitor_base/constants.py
 tools/idf_monitor_base/coredump.py
@@ -4037,8 +4035,6 @@ tools/idf_monitor_base/gdbhelper.py
 tools/idf_monitor_base/line_matcher.py
 tools/idf_monitor_base/logger.py
 tools/idf_monitor_base/output_helpers.py
-tools/idf_monitor_base/serial_handler.py
-tools/idf_monitor_base/serial_reader.py
 tools/idf_monitor_base/stoppable_thread.py
 tools/idf_monitor_base/web_socket_client.py
 tools/idf_py_actions/constants.py
@@ -4048,7 +4044,6 @@ tools/idf_py_actions/debug_ext.py
 tools/idf_py_actions/dfu_ext.py
 tools/idf_py_actions/errors.py
 tools/idf_py_actions/global_options.py
-tools/idf_py_actions/serial_ext.py
 tools/idf_py_actions/tools.py
 tools/idf_py_actions/uf2_ext.py
 tools/kconfig/conf.c
@@ -4160,7 +4155,6 @@ tools/test_apps/system/startup/app_test.py
 tools/test_apps/system/startup/main/chip_info_patch.c
 tools/test_apps/system/startup/main/test_startup_main.c
 tools/test_idf_monitor/dummy.c
-tools/test_idf_monitor/idf_monitor_wrapper.py
 tools/test_idf_monitor/run_test_idf_monitor.py
 tools/test_idf_py/extra_path/some_ext.py
 tools/test_idf_py/idf_ext.py

+ 174 - 136
tools/idf_monitor.py

@@ -9,19 +9,8 @@
 # - If core dump output is detected, it is converted to a human-readable report
 #   by espcoredump.py.
 #
-# Copyright 2015-2021 Espressif Systems (Shanghai) CO LTD
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
+# SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Apache-2.0
 #
 # Contains elements taken from miniterm "Very simple serial terminal" which
 # is part of pySerial. https://github.com/pyserial/pyserial
@@ -30,23 +19,17 @@
 # Originally released under BSD-3-Clause license.
 #
 
-from __future__ import division, print_function, unicode_literals
-
 import codecs
 import os
-import re
-import threading
-import time
-from builtins import bytes, object
-
-try:
-    from typing import Any, List, Optional, Union  # noqa
-except ImportError:
-    pass
-
 import queue
+import re
 import shlex
+import subprocess
 import sys
+import threading
+import time
+from builtins import bytes
+from typing import Any, List, Optional, Type, Union
 
 import serial
 import serial.tools.list_ports
@@ -67,16 +50,16 @@ from idf_monitor_base.line_matcher import LineMatcher
 from idf_monitor_base.logger import Logger
 from idf_monitor_base.output_helpers import normal_print, yellow_print
 from idf_monitor_base.serial_handler import SerialHandler, run_make
-from idf_monitor_base.serial_reader import SerialReader
+from idf_monitor_base.serial_reader import LinuxReader, SerialReader
 from idf_monitor_base.web_socket_client import WebSocketClient
 from serial.tools import miniterm
 
 key_description = miniterm.key_description
 
 
-class Monitor(object):
+class Monitor:
     """
-    Monitor application main class.
+    Monitor application base class.
 
     This was originally derived from miniterm.Miniterm, but it turned out to be easier to write from scratch for this
     purpose.
@@ -96,12 +79,11 @@ class Monitor(object):
         decode_coredumps=COREDUMP_DECODE_INFO,  # type: str
         decode_panic=PANIC_DECODE_DISABLE,  # type: str
         target='esp32',  # type: str
-        websocket_client=None,  # type: WebSocketClient
+        websocket_client=None,  # type: Optional[WebSocketClient]
         enable_address_decoding=True,  # type: bool
         timestamps=False,  # type: bool
         timestamp_format=''  # type: str
     ):
-        super(Monitor, self).__init__()
         self.event_queue = queue.Queue()  # type: queue.Queue
         self.cmd_queue = queue.Queue()  # type: queue.Queue
         self.console = miniterm.Console()
@@ -110,104 +92,70 @@ class Monitor(object):
         self.console.output = get_converter(self.console.output)
         self.console.byte_output = get_converter(self.console.byte_output)
 
-        # testing hook - data from serial can make exit the monitor
-        socket_mode = serial_instance.port.startswith('socket://')
-        self.serial = serial_instance
-
-        self.console_parser = ConsoleParser(eol)
-        self.console_reader = ConsoleReader(self.console, self.event_queue, self.cmd_queue, self.console_parser,
-                                            socket_mode)
-        self.serial_reader = SerialReader(self.serial, self.event_queue)
         self.elf_file = elf_file
+        self.logger = Logger(self.elf_file, self.console, timestamps, timestamp_format, b'', enable_address_decoding,
+                             toolchain_prefix)
+        self.coredump = CoreDump(decode_coredumps, self.event_queue, self.logger, websocket_client, self.elf_file)
 
         # allow for possibility the "make" arg is a list of arguments (for idf.py)
         self.make = make if os.path.exists(make) else shlex.split(make)  # type: Any[Union[str, List[str]], str]
         self.target = target
 
-        self._line_matcher = LineMatcher(print_filter)
-        self.gdb_helper = GDBHelper(toolchain_prefix, websocket_client, self.elf_file, self.serial.port,
-                                    self.serial.baudrate)
-        self.logger = Logger(self.elf_file, self.console, timestamps, timestamp_format, b'', enable_address_decoding,
-                             toolchain_prefix)
-        self.coredump = CoreDump(decode_coredumps, self.event_queue, self.logger, websocket_client, self.elf_file)
+        # testing hook - data from serial can make exit the monitor
+        if isinstance(self, SerialMonitor):
+            socket_mode = serial_instance.port.startswith('socket://')
+            self.serial = serial_instance
+            self.serial_reader = SerialReader(self.serial, self.event_queue)
+
+            self.gdb_helper = GDBHelper(toolchain_prefix, websocket_client, self.elf_file, self.serial.port,
+                                        self.serial.baudrate)
+        else:
+            socket_mode = False
+            self.serial = subprocess.Popen([elf_file], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                                           stderr=subprocess.STDOUT)
+            self.serial_reader = LinuxReader(self.serial, self.event_queue)
+
+            self.gdb_helper = None
+
         self.serial_handler = SerialHandler(b'', socket_mode, self.logger, decode_panic, PANIC_IDLE, b'', target,
                                             False, False, self.serial, encrypted)
 
+        self.console_parser = ConsoleParser(eol)
+        self.console_reader = ConsoleReader(self.console, self.event_queue, self.cmd_queue, self.console_parser,
+                                            socket_mode)
+
+        self._line_matcher = LineMatcher(print_filter)
+
         # internal state
         self._invoke_processing_last_line_timer = None  # type: Optional[threading.Timer]
 
-    def invoke_processing_last_line(self):
-        # type: () -> None
-        self.event_queue.put((TAG_SERIAL_FLUSH, b''), False)
+    def __enter__(self) -> None:
+        """ Use 'with self' to temporarily disable monitoring behaviour """
+        self.serial_reader.stop()
+        self.console_reader.stop()
+
+    def __exit__(self, exc_type, exc_val, exc_tb) -> None:  # type: ignore
+        raise NotImplementedError
 
-    def main_loop(self):
-        # type: () -> None
+    def run_make(self, target: str) -> None:
+        with self:
+            run_make(target, self.make, self.console, self.console_parser, self.event_queue, self.cmd_queue,
+                     self.logger)
+
+    def _pre_start(self) -> None:
         self.console_reader.start()
         self.serial_reader.start()
-        self.gdb_helper.gdb_exit = False
-        self.serial_handler.start_cmd_sent = False
+
+    def main_loop(self) -> None:
+        self._pre_start()
+
         try:
             while self.console_reader.alive and self.serial_reader.alive:
                 try:
-                    if self.gdb_helper.gdb_exit:
-                        self.gdb_helper.gdb_exit = False
-                        time.sleep(GDB_EXIT_TIMEOUT)
-                        try:
-                            # Continue the program after exit from the GDB
-                            self.serial.write(codecs.encode(GDB_UART_CONTINUE_COMMAND))
-                            self.serial_handler.start_cmd_sent = True
-                        except serial.SerialException:
-                            pass  # this shouldn't happen, but sometimes port has closed in serial thread
-                        except UnicodeEncodeError:
-                            pass  # this can happen if a non-ascii character was passed, ignoring
-
-                    try:
-                        item = self.cmd_queue.get_nowait()
-                    except queue.Empty:
-                        try:
-                            item = self.event_queue.get(timeout=EVENT_QUEUE_TIMEOUT)
-                        except queue.Empty:
-                            continue
-
-                    event_tag, data = item
-                    if event_tag == TAG_CMD:
-                        self.serial_handler.handle_commands(data, self.target, self.run_make, self.console_reader,
-                                                            self.serial_reader)
-                    elif event_tag == TAG_KEY:
-                        try:
-                            self.serial.write(codecs.encode(data))
-                        except serial.SerialException:
-                            pass  # this shouldn't happen, but sometimes port has closed in serial thread
-                        except UnicodeEncodeError:
-                            pass  # this can happen if a non-ascii character was passed, ignoring
-                    elif event_tag == TAG_SERIAL:
-                        self.serial_handler.handle_serial_input(data, self.console_parser, self.coredump,
-                                                                self.gdb_helper, self._line_matcher,
-                                                                self.check_gdb_stub_and_run)
-                        if self._invoke_processing_last_line_timer is not None:
-                            self._invoke_processing_last_line_timer.cancel()
-                        self._invoke_processing_last_line_timer = threading.Timer(LAST_LINE_THREAD_INTERVAL,
-                                                                                  self.invoke_processing_last_line)
-                        self._invoke_processing_last_line_timer.start()
-                        # If no further data is received in the next short period
-                        # of time then the _invoke_processing_last_line_timer
-                        # generates an event which will result in the finishing of
-                        # the last line. This is fix for handling lines sent
-                        # without EOL.
-                    elif event_tag == TAG_SERIAL_FLUSH:
-                        self.serial_handler.handle_serial_input(data, self.console_parser, self.coredump,
-                                                                self.gdb_helper, self._line_matcher,
-                                                                self.check_gdb_stub_and_run, finalize_line=True)
-                    else:
-                        raise RuntimeError('Bad event data %r' % ((event_tag, data),))
+                    self._main_loop()
                 except KeyboardInterrupt:
-                    try:
-                        yellow_print('To exit from IDF monitor please use \"Ctrl+]\"')
-                        self.serial.write(codecs.encode(CTRL_C))
-                    except serial.SerialException:
-                        pass  # this shouldn't happen, but sometimes port has closed in serial thread
-                    except UnicodeEncodeError:
-                        pass  # this can happen if a non-ascii character was passed, ignoring
+                    yellow_print('To exit from IDF monitor please use \"Ctrl+]\"')
+                    self.serial_write(codecs.encode(CTRL_C))
         except SerialStopException:
             normal_print('Stopping condition has been received\n')
         except KeyboardInterrupt:
@@ -224,31 +172,103 @@ class Monitor(object):
                 pass
             normal_print('\n')
 
-    def __enter__(self):
-        # type: () -> None
-        """ Use 'with self' to temporarily disable monitoring behaviour """
-        self.serial_reader.stop()
-        self.console_reader.stop()
+    def serial_write(self, *args, **kwargs):  # type: ignore
+        raise NotImplementedError
 
-    def __exit__(self, *args, **kwargs):  # type: ignore
+    def check_gdb_stub_and_run(self, line: bytes) -> None:
+        raise NotImplementedError
+
+    def invoke_processing_last_line(self) -> None:
+        self.event_queue.put((TAG_SERIAL_FLUSH, b''), False)
+
+    def _main_loop(self) -> None:
+        try:
+            item = self.cmd_queue.get_nowait()
+        except queue.Empty:
+            try:
+                item = self.event_queue.get(timeout=EVENT_QUEUE_TIMEOUT)
+            except queue.Empty:
+                return
+
+        event_tag, data = item
+        if event_tag == TAG_CMD:
+            self.serial_handler.handle_commands(data, self.target, self.run_make, self.console_reader,
+                                                self.serial_reader)
+        elif event_tag == TAG_KEY:
+            self.serial_write(codecs.encode(data))
+        elif event_tag == TAG_SERIAL:
+            self.serial_handler.handle_serial_input(data, self.console_parser, self.coredump,
+                                                    self.gdb_helper, self._line_matcher,
+                                                    self.check_gdb_stub_and_run)
+            if self._invoke_processing_last_line_timer is not None:
+                self._invoke_processing_last_line_timer.cancel()
+            self._invoke_processing_last_line_timer = threading.Timer(LAST_LINE_THREAD_INTERVAL,
+                                                                      self.invoke_processing_last_line)
+            self._invoke_processing_last_line_timer.start()
+            # If no further data is received in the next short period
+            # of time then the _invoke_processing_last_line_timer
+            # generates an event which will result in the finishing of
+            # the last line. This is fix for handling lines sent
+            # without EOL.
+        elif event_tag == TAG_SERIAL_FLUSH:
+            self.serial_handler.handle_serial_input(data, self.console_parser, self.coredump,
+                                                    self.gdb_helper, self._line_matcher,
+                                                    self.check_gdb_stub_and_run, finalize_line=True)
+        else:
+            raise RuntimeError('Bad event data %r' % ((event_tag, data),))
+
+
+class SerialMonitor(Monitor):
+    def __exit__(self, exc_type, exc_val, exc_tb) -> None:  # type: ignore
         """ Use 'with self' to temporarily disable monitoring behaviour """
         self.console_reader.start()
         self.serial_reader.gdb_exit = self.gdb_helper.gdb_exit  # write gdb_exit flag
         self.serial_reader.start()
 
-    def check_gdb_stub_and_run(self, line):  # type: (bytes) -> None
+    def _pre_start(self) -> None:
+        super()._pre_start()
+        self.gdb_helper.gdb_exit = False
+        self.serial_handler.start_cmd_sent = False
+
+    def serial_write(self, *args, **kwargs):  # type: ignore
+        self.serial: serial.Serial
+        try:
+            self.serial.write(*args, **kwargs)
+        except serial.SerialException:
+            pass  # this shouldn't happen, but sometimes port has closed in serial thread
+        except UnicodeEncodeError:
+            pass  # this can happen if a non-ascii character was passed, ignoring
+
+    def check_gdb_stub_and_run(self, line: bytes) -> None:  # type: ignore # The base class one is a None value
         if self.gdb_helper.check_gdb_stub_trigger(line):
             with self:  # disable console control
                 self.gdb_helper.run_gdb()
 
-    def run_make(self, target):  # type: (str) -> None
-        with self:
-            run_make(target, self.make, self.console, self.console_parser, self.event_queue, self.cmd_queue,
-                     self.logger)
+    def _main_loop(self) -> None:
+        if self.gdb_helper.gdb_exit:
+            self.gdb_helper.gdb_exit = False
+            time.sleep(GDB_EXIT_TIMEOUT)
+            # Continue the program after exit from the GDB
+            self.serial_write(codecs.encode(GDB_UART_CONTINUE_COMMAND))
+            self.serial_handler.start_cmd_sent = True
 
+        super()._main_loop()
 
-def main():  # type: () -> None
 
+class LinuxMonitor(Monitor):
+    def __exit__(self, exc_type, exc_val, exc_tb) -> None:  # type: ignore
+        """ Use 'with self' to temporarily disable monitoring behaviour """
+        self.console_reader.start()
+        self.serial_reader.start()
+
+    def serial_write(self, *args, **kwargs):  # type: ignore
+        self.serial.stdin.write(*args, **kwargs)
+
+    def check_gdb_stub_and_run(self, line: bytes) -> None:
+        return  # fake function for linux target
+
+
+def main() -> None:
     parser = get_parser()
     args = parser.parse_args()
 
@@ -263,9 +283,6 @@ def main():  # type: () -> None
         yellow_print('--- WARNING: Serial ports accessed as /dev/tty.* will hang gdb if launched.')
         yellow_print('--- Using %s instead...' % args.port)
 
-    serial_instance = serial.serial_for_url(args.port, args.baud, do_not_open=True)
-    serial_instance.dtr = False
-    serial_instance.rts = False
     args.elf_file.close()  # don't need this as a file
 
     # remove the parallel jobserver arguments from MAKEFLAGS, as any
@@ -279,21 +296,42 @@ def main():  # type: () -> None
     except KeyError:
         pass  # not running a make jobserver
 
-    # Pass the actual used port to callee of idf_monitor (e.g. make) through `ESPPORT` environment
-    # variable
-    # To make sure the key as well as the value are str type, by the requirements of subprocess
-    espport_val = str(args.port)
-    os.environ.update({ESPPORT_ENVIRON: espport_val})
-
     ws = WebSocketClient(args.ws) if args.ws else None
     try:
-        monitor = Monitor(serial_instance, args.elf_file.name, args.print_filter, args.make, args.encrypted,
-                          args.toolchain_prefix, args.eol,
-                          args.decode_coredumps, args.decode_panic, args.target,
-                          ws, enable_address_decoding=not args.disable_address_decoding,
-                          timestamps=args.timestamps, timestamp_format=args.timestamp_format)
+        cls: Type[Monitor]
+        if args.target == 'linux':
+            serial_instance = None
+            cls = LinuxMonitor
+            yellow_print('--- idf_monitor on linux ---')
+        else:
+            serial_instance = serial.serial_for_url(args.port, args.baud, do_not_open=True)
+            serial_instance.dtr = False
+            serial_instance.rts = False
+
+            # Pass the actual used port to callee of idf_monitor (e.g. make) through `ESPPORT` environment
+            # variable
+            # To make sure the key as well as the value are str type, by the requirements of subprocess
+            espport_val = str(args.port)
+            os.environ.update({ESPPORT_ENVIRON: espport_val})
+
+            cls = SerialMonitor
+            yellow_print('--- idf_monitor on {p.name} {p.baudrate} ---'.format(p=serial_instance))
+
+        monitor = cls(serial_instance,
+                      args.elf_file.name,
+                      args.print_filter,
+                      args.make,
+                      args.encrypted,
+                      args.toolchain_prefix,
+                      args.eol,
+                      args.decode_coredumps,
+                      args.decode_panic,
+                      args.target,
+                      ws,
+                      not args.disable_address_decoding,
+                      args.timestamps,
+                      args.timestamp_format)
 
-        yellow_print('--- idf_monitor on {p.name} {p.baudrate} ---'.format(p=serial_instance))
         yellow_print('--- Quit: {} | Menu: {} | Help: {} followed by {} ---'.format(
             key_description(monitor.console_parser.exit_key),
             key_description(monitor.console_parser.menu_key),

+ 3 - 15
tools/idf_monitor_base/console_parser.py

@@ -1,16 +1,5 @@
-# Copyright 2015-2021 Espressif Systems (Shanghai) CO LTD
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
+# SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Apache-2.0
 
 import queue
 import textwrap
@@ -21,7 +10,7 @@ from serial.tools import miniterm
 from .constants import (CMD_APP_FLASH, CMD_ENTER_BOOT, CMD_MAKE, CMD_OUTPUT_TOGGLE, CMD_RESET, CMD_STOP,
                         CMD_TOGGLE_LOGGING, CMD_TOGGLE_TIMESTAMPS, CTRL_A, CTRL_F, CTRL_H, CTRL_I, CTRL_L, CTRL_P,
                         CTRL_R, CTRL_RBRACKET, CTRL_T, CTRL_X, CTRL_Y, TAG_CMD, TAG_KEY, __version__)
-from .output_helpers import red_print, yellow_print
+from .output_helpers import red_print
 
 key_description = miniterm.key_description
 
@@ -94,7 +83,6 @@ class ConsoleParser(object):
         elif c in [CTRL_I, 'i', 'I']:  # Toggle printing timestamps
             ret = (TAG_CMD, CMD_TOGGLE_TIMESTAMPS)
         elif c == CTRL_P:
-            yellow_print('Pause app (enter bootloader mode), press Ctrl-T Ctrl-R to restart')
             # to fast trigger pause without press menu key
             ret = (TAG_CMD, CMD_ENTER_BOOT)
         elif c in [CTRL_X, 'x', 'X']:  # Exiting from within the menu

+ 25 - 25
tools/idf_monitor_base/serial_handler.py

@@ -1,23 +1,12 @@
-# Copyright 2015-2021 Espressif Systems (Shanghai) CO LTD
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
+# SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Apache-2.0
 
 import os
 import queue  # noqa: F401
 import re
 import subprocess
 import time
-from typing import Callable
+from typing import Callable, Optional
 
 import serial  # noqa: F401
 from serial.tools import miniterm  # noqa: F401
@@ -28,13 +17,13 @@ from .console_reader import ConsoleReader  # noqa: F401
 from .constants import (CMD_APP_FLASH, CMD_ENTER_BOOT, CMD_MAKE, CMD_OUTPUT_TOGGLE, CMD_RESET, CMD_STOP,
                         CMD_TOGGLE_LOGGING, CMD_TOGGLE_TIMESTAMPS, PANIC_DECODE_DISABLE, PANIC_END, PANIC_IDLE,
                         PANIC_READING, PANIC_STACK_DUMP, PANIC_START)
-from .coredump import CoreDump  # noqa: F401
-from .exceptions import SerialStopException  # noqa: F401
-from .gdbhelper import GDBHelper  # noqa: F401
-from .line_matcher import LineMatcher  # noqa: F401
-from .logger import Logger  # noqa: F401
+from .coredump import CoreDump
+from .exceptions import SerialStopException
+from .gdbhelper import GDBHelper
+from .line_matcher import LineMatcher
+from .logger import Logger
 from .output_helpers import yellow_print
-from .serial_reader import SerialReader  # noqa: F401
+from .serial_reader import Reader
 
 
 def run_make(target, make, console, console_parser, event_queue, cmd_queue, logger):
@@ -60,7 +49,7 @@ class SerialHandler:
     The class is responsible for buffering serial input and performing corresponding commands.
     """
     def __init__(self, last_line_part, serial_check_exit, logger, decode_panic, reading_panic, panic_buffer, target,
-                 force_line_print, start_cmd_sent, serial_instance, encrypted, ):
+                 force_line_print, start_cmd_sent, serial_instance, encrypted):
         # type: (bytes, bool, Logger, str, int, bytes,str, bool, bool, serial.Serial, bool) -> None
         self._last_line_part = last_line_part
         self._serial_check_exit = serial_check_exit
@@ -76,7 +65,7 @@ class SerialHandler:
 
     def handle_serial_input(self, data, console_parser, coredump, gdb_helper, line_matcher,
                             check_gdb_stub_and_run, finalize_line=False):
-        #  type: (bytes, ConsoleParser, CoreDump, GDBHelper, LineMatcher, Callable, bool) -> None
+        #  type: (bytes, ConsoleParser, CoreDump, Optional[GDBHelper], LineMatcher, Callable, bool) -> None
         # Remove "+" after Continue command
         if self.start_cmd_sent:
             self.start_cmd_sent = False
@@ -97,7 +86,8 @@ class SerialHandler:
                 continue
             if self._serial_check_exit and line == console_parser.exit_key.encode('latin-1'):
                 raise SerialStopException()
-            self.check_panic_decode_trigger(line, gdb_helper)
+            if gdb_helper:
+                self.check_panic_decode_trigger(line, gdb_helper)
             with coredump.check(line):
                 if self._force_line_print or line_matcher.match(line.decode(errors='ignore')):
                     self.logger.print(line + b'\n')
@@ -125,7 +115,8 @@ class SerialHandler:
             self.logger.pc_address_buffer = self._last_line_part[-9:]
             # GDB sequence can be cut in half also. GDB sequence is 7
             # characters long, therefore, we save the last 6 characters.
-            gdb_helper.gdb_buffer = self._last_line_part[-6:]
+            if gdb_helper:
+                gdb_helper.gdb_buffer = self._last_line_part[-6:]
             self._last_line_part = b''
         # else: keeping _last_line_part and it will be processed the next time
         # handle_serial_input is invoked
@@ -151,7 +142,7 @@ class SerialHandler:
             self._panic_buffer = b''
 
     def handle_commands(self, cmd, chip, run_make_func, console_reader, serial_reader):
-        # type: (int, str, Callable, ConsoleReader, SerialReader) -> None
+        # type: (int, str, Callable, ConsoleReader, Reader) -> None
         config = get_chip_config(chip)
         reset_delay = config['reset']
         enter_boot_set = config['enter_boot_set']
@@ -160,6 +151,14 @@ class SerialHandler:
         high = False
         low = True
 
+        if chip == 'linux':
+            if cmd in [CMD_RESET,
+                       CMD_MAKE,
+                       CMD_APP_FLASH,
+                       CMD_ENTER_BOOT]:
+                yellow_print('linux target does not support this command')
+                return
+
         if cmd == CMD_STOP:
             console_reader.stop()
             serial_reader.stop()
@@ -181,6 +180,7 @@ class SerialHandler:
         elif cmd == CMD_TOGGLE_TIMESTAMPS:
             self.logger.toggle_timestamps()
         elif cmd == CMD_ENTER_BOOT:
+            yellow_print('Pause app (enter bootloader mode), press Ctrl-T Ctrl-R to restart')
             self.serial_instance.setDTR(high)  # IO0=HIGH
             self.serial_instance.setRTS(low)  # EN=LOW, chip in reset
             self.serial_instance.setDTR(self.serial_instance.dtr)  # usbser.sys workaround

+ 35 - 14
tools/idf_monitor_base/serial_reader.py

@@ -1,18 +1,8 @@
-# Copyright 2015-2021 Espressif Systems (Shanghai) CO LTD
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
+# SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Apache-2.0
 
 import queue
+import subprocess
 import sys
 import time
 
@@ -23,10 +13,15 @@ from .output_helpers import red_print, yellow_print
 from .stoppable_thread import StoppableThread
 
 
-class SerialReader(StoppableThread):
+class Reader(StoppableThread):
+    """ Output Reader base class """
+
+
+class SerialReader(Reader):
     """ Read serial data from the serial port and push to the
     event queue, until stopped.
     """
+
     def __init__(self, serial_instance, event_queue):
         #  type: (serial.Serial, queue.Queue) -> None
         super(SerialReader, self).__init__()
@@ -96,3 +91,29 @@ class SerialReader(StoppableThread):
                 self.serial.cancel_read()
             except Exception:  # noqa
                 pass
+
+
+class LinuxReader(Reader):
+    """ Read data from the subprocess that runs runnable and push to the
+    event queue, until stopped.
+    """
+
+    def __init__(self, process, event_queue):
+        #  type: (subprocess.Popen, queue.Queue) -> None
+        super().__init__()
+        self.proc = process
+        self.event_queue = event_queue
+
+        self._stdout = iter(self.proc.stdout.readline, b'')  # type: ignore
+
+    def run(self):  # type: () -> None
+        try:
+            while self.alive:
+                for line in self._stdout:
+                    if line:
+                        self.event_queue.put((TAG_SERIAL, line), False)
+        finally:
+            self.proc.terminate()
+
+    def _cancel(self):  # type: () -> None
+        self.proc.terminate()

+ 28 - 13
tools/idf_py_actions/serial_ext.py

@@ -1,8 +1,12 @@
+# SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Apache-2.0
+
 import json
 import os
 import sys
 
 import click
+from idf_monitor_base.output_helpers import yellow_print
 from idf_py_actions.errors import FatalError, NoSerialPortFoundError
 from idf_py_actions.global_options import global_options
 from idf_py_actions.tools import ensure_build_directory, get_sdkconfig_value, run_target, run_tool
@@ -11,6 +15,14 @@ PYTHON = sys.executable
 
 
 def action_extensions(base_actions, project_path):
+    def _get_project_desc(ctx, args):
+        desc_path = os.path.join(args.build_dir, 'project_description.json')
+        if not os.path.exists(desc_path):
+            ensure_build_directory(args, ctx.info_name)
+        with open(desc_path, 'r') as f:
+            project_desc = json.load(f)
+        return project_desc
+
     def _get_default_serial_port(args):
         # Import is done here in order to move it after the check_environment() ensured that pyserial has been installed
         try:
@@ -75,27 +87,25 @@ def action_extensions(base_actions, project_path):
         """
         Run idf_monitor.py to watch build output
         """
-
-        desc_path = os.path.join(args.build_dir, 'project_description.json')
-        if not os.path.exists(desc_path):
-            ensure_build_directory(args, ctx.info_name)
-        with open(desc_path, 'r') as f:
-            project_desc = json.load(f)
-
+        project_desc = _get_project_desc(ctx, args)
         elf_file = os.path.join(args.build_dir, project_desc['app_elf'])
         if not os.path.exists(elf_file):
             raise FatalError("ELF file '%s' not found. You need to build & flash the project before running 'monitor', "
                              'and the binary on the device must match the one in the build directory exactly. '
                              "Try '%s flash monitor'." % (elf_file, ctx.info_name), ctx)
+
         idf_monitor = os.path.join(os.environ['IDF_PATH'], 'tools/idf_monitor.py')
         monitor_args = [PYTHON, idf_monitor]
-        esp_port = args.port or _get_default_serial_port(args)
-        monitor_args += ['-p', esp_port]
 
-        if not monitor_baud:
-            monitor_baud = os.getenv('IDF_MONITOR_BAUD') or os.getenv('MONITORBAUD') or project_desc['monitor_baud']
+        if project_desc['target'] != 'linux':
+            esp_port = args.port or _get_default_serial_port(args)
+            monitor_args += ['-p', esp_port]
+
+            if not monitor_baud:
+                monitor_baud = os.getenv('IDF_MONITOR_BAUD') or os.getenv('MONITORBAUD') or project_desc['monitor_baud']
+
+            monitor_args += ['-b', monitor_baud]
 
-        monitor_args += ['-b', monitor_baud]
         monitor_args += ['--toolchain-prefix', project_desc['monitor_toolprefix']]
 
         coredump_decode = get_sdkconfig_value(project_desc['config_file'], 'CONFIG_ESP_COREDUMP_DECODE')
@@ -137,8 +147,13 @@ def action_extensions(base_actions, project_path):
         Run esptool to flash the entire project, from an argfile generated by the build system
         """
         ensure_build_directory(args, ctx.info_name)
+        project_desc = _get_project_desc(ctx, args)
+        if project_desc['target'] == 'linux':
+            yellow_print('skipping flash since running on linux...')
+            return
+
         esp_port = args.port or _get_default_serial_port(args)
-        run_target(action, args, {'ESPBAUD': str(args.baud),'ESPPORT': esp_port})
+        run_target(action, args, {'ESPBAUD': str(args.baud), 'ESPPORT': esp_port})
 
     def erase_flash(action, ctx, args):
         ensure_build_directory(args, ctx.info_name)

+ 5 - 16
tools/test_idf_monitor/idf_monitor_wrapper.py

@@ -1,16 +1,5 @@
-# Copyright 2019 Espressif Systems (Shanghai) PTE LTD
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
+# SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Apache-2.0
 
 from __future__ import unicode_literals
 
@@ -53,9 +42,9 @@ def main():
     args = parser.parse_args()
 
     serial_instance = serial.serial_for_url(args.port, 115200, do_not_open=True)
-    monitor = idf_monitor.Monitor(serial_instance, args.elf_file, args.print_filter, 'make',
-                                  toolchain_prefix=args.toolchain_prefix, eol='CR',
-                                  decode_panic=args.decode_panic, target=args.target)
+    monitor = idf_monitor.SerialMonitor(serial_instance, args.elf_file, args.print_filter, 'make',
+                                        toolchain_prefix=args.toolchain_prefix, eol='CR',
+                                        decode_panic=args.decode_panic, target=args.target)
     sys.stderr.write('Monitor instance has been created.\n')
     monitor_thread = threading.Thread(target=monitor_serial_reader_state,
                                       args=(monitor.serial_reader, args.serial_alive_file))