فهرست منبع

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

Fu Hanxi 4 سال پیش
والد
کامیت
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/find_build_apps/make.py
 tools/gdb_panic_server.py
 tools/gdb_panic_server.py
 tools/gen_esp_err_to_name.py
 tools/gen_esp_err_to_name.py
-tools/idf_monitor.py
 tools/idf_monitor_base/ansi_color_converter.py
 tools/idf_monitor_base/ansi_color_converter.py
 tools/idf_monitor_base/argument_parser.py
 tools/idf_monitor_base/argument_parser.py
 tools/idf_monitor_base/chip_specific_config.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/console_reader.py
 tools/idf_monitor_base/constants.py
 tools/idf_monitor_base/constants.py
 tools/idf_monitor_base/coredump.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/line_matcher.py
 tools/idf_monitor_base/logger.py
 tools/idf_monitor_base/logger.py
 tools/idf_monitor_base/output_helpers.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/stoppable_thread.py
 tools/idf_monitor_base/web_socket_client.py
 tools/idf_monitor_base/web_socket_client.py
 tools/idf_py_actions/constants.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/dfu_ext.py
 tools/idf_py_actions/errors.py
 tools/idf_py_actions/errors.py
 tools/idf_py_actions/global_options.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/tools.py
 tools/idf_py_actions/uf2_ext.py
 tools/idf_py_actions/uf2_ext.py
 tools/kconfig/conf.c
 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/chip_info_patch.c
 tools/test_apps/system/startup/main/test_startup_main.c
 tools/test_apps/system/startup/main/test_startup_main.c
 tools/test_idf_monitor/dummy.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_monitor/run_test_idf_monitor.py
 tools/test_idf_py/extra_path/some_ext.py
 tools/test_idf_py/extra_path/some_ext.py
 tools/test_idf_py/idf_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
 # - If core dump output is detected, it is converted to a human-readable report
 #   by espcoredump.py.
 #   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
 # Contains elements taken from miniterm "Very simple serial terminal" which
 # is part of pySerial. https://github.com/pyserial/pyserial
 # is part of pySerial. https://github.com/pyserial/pyserial
@@ -30,23 +19,17 @@
 # Originally released under BSD-3-Clause license.
 # Originally released under BSD-3-Clause license.
 #
 #
 
 
-from __future__ import division, print_function, unicode_literals
-
 import codecs
 import codecs
 import os
 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 queue
+import re
 import shlex
 import shlex
+import subprocess
 import sys
 import sys
+import threading
+import time
+from builtins import bytes
+from typing import Any, List, Optional, Type, Union
 
 
 import serial
 import serial
 import serial.tools.list_ports
 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.logger import Logger
 from idf_monitor_base.output_helpers import normal_print, yellow_print
 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_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 idf_monitor_base.web_socket_client import WebSocketClient
 from serial.tools import miniterm
 from serial.tools import miniterm
 
 
 key_description = miniterm.key_description
 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
     This was originally derived from miniterm.Miniterm, but it turned out to be easier to write from scratch for this
     purpose.
     purpose.
@@ -96,12 +79,11 @@ class Monitor(object):
         decode_coredumps=COREDUMP_DECODE_INFO,  # type: str
         decode_coredumps=COREDUMP_DECODE_INFO,  # type: str
         decode_panic=PANIC_DECODE_DISABLE,  # type: str
         decode_panic=PANIC_DECODE_DISABLE,  # type: str
         target='esp32',  # type: str
         target='esp32',  # type: str
-        websocket_client=None,  # type: WebSocketClient
+        websocket_client=None,  # type: Optional[WebSocketClient]
         enable_address_decoding=True,  # type: bool
         enable_address_decoding=True,  # type: bool
         timestamps=False,  # type: bool
         timestamps=False,  # type: bool
         timestamp_format=''  # type: str
         timestamp_format=''  # type: str
     ):
     ):
-        super(Monitor, self).__init__()
         self.event_queue = queue.Queue()  # type: queue.Queue
         self.event_queue = queue.Queue()  # type: queue.Queue
         self.cmd_queue = queue.Queue()  # type: queue.Queue
         self.cmd_queue = queue.Queue()  # type: queue.Queue
         self.console = miniterm.Console()
         self.console = miniterm.Console()
@@ -110,104 +92,70 @@ class Monitor(object):
         self.console.output = get_converter(self.console.output)
         self.console.output = get_converter(self.console.output)
         self.console.byte_output = get_converter(self.console.byte_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.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)
         # 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.make = make if os.path.exists(make) else shlex.split(make)  # type: Any[Union[str, List[str]], str]
         self.target = target
         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,
         self.serial_handler = SerialHandler(b'', socket_mode, self.logger, decode_panic, PANIC_IDLE, b'', target,
                                             False, False, self.serial, encrypted)
                                             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
         # internal state
         self._invoke_processing_last_line_timer = None  # type: Optional[threading.Timer]
         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.console_reader.start()
         self.serial_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:
         try:
             while self.console_reader.alive and self.serial_reader.alive:
             while self.console_reader.alive and self.serial_reader.alive:
                 try:
                 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:
                 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:
         except SerialStopException:
             normal_print('Stopping condition has been received\n')
             normal_print('Stopping condition has been received\n')
         except KeyboardInterrupt:
         except KeyboardInterrupt:
@@ -224,31 +172,103 @@ class Monitor(object):
                 pass
                 pass
             normal_print('\n')
             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 """
         """ Use 'with self' to temporarily disable monitoring behaviour """
         self.console_reader.start()
         self.console_reader.start()
         self.serial_reader.gdb_exit = self.gdb_helper.gdb_exit  # write gdb_exit flag
         self.serial_reader.gdb_exit = self.gdb_helper.gdb_exit  # write gdb_exit flag
         self.serial_reader.start()
         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):
         if self.gdb_helper.check_gdb_stub_trigger(line):
             with self:  # disable console control
             with self:  # disable console control
                 self.gdb_helper.run_gdb()
                 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()
     parser = get_parser()
     args = parser.parse_args()
     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('--- WARNING: Serial ports accessed as /dev/tty.* will hang gdb if launched.')
         yellow_print('--- Using %s instead...' % args.port)
         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
     args.elf_file.close()  # don't need this as a file
 
 
     # remove the parallel jobserver arguments from MAKEFLAGS, as any
     # remove the parallel jobserver arguments from MAKEFLAGS, as any
@@ -279,21 +296,42 @@ def main():  # type: () -> None
     except KeyError:
     except KeyError:
         pass  # not running a make jobserver
         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
     ws = WebSocketClient(args.ws) if args.ws else None
     try:
     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(
         yellow_print('--- Quit: {} | Menu: {} | Help: {} followed by {} ---'.format(
             key_description(monitor.console_parser.exit_key),
             key_description(monitor.console_parser.exit_key),
             key_description(monitor.console_parser.menu_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 queue
 import textwrap
 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,
 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,
                         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__)
                         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
 key_description = miniterm.key_description
 
 
@@ -94,7 +83,6 @@ class ConsoleParser(object):
         elif c in [CTRL_I, 'i', 'I']:  # Toggle printing timestamps
         elif c in [CTRL_I, 'i', 'I']:  # Toggle printing timestamps
             ret = (TAG_CMD, CMD_TOGGLE_TIMESTAMPS)
             ret = (TAG_CMD, CMD_TOGGLE_TIMESTAMPS)
         elif c == CTRL_P:
         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
             # to fast trigger pause without press menu key
             ret = (TAG_CMD, CMD_ENTER_BOOT)
             ret = (TAG_CMD, CMD_ENTER_BOOT)
         elif c in [CTRL_X, 'x', 'X']:  # Exiting from within the menu
         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 os
 import queue  # noqa: F401
 import queue  # noqa: F401
 import re
 import re
 import subprocess
 import subprocess
 import time
 import time
-from typing import Callable
+from typing import Callable, Optional
 
 
 import serial  # noqa: F401
 import serial  # noqa: F401
 from serial.tools import miniterm  # 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,
 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,
                         CMD_TOGGLE_LOGGING, CMD_TOGGLE_TIMESTAMPS, PANIC_DECODE_DISABLE, PANIC_END, PANIC_IDLE,
                         PANIC_READING, PANIC_STACK_DUMP, PANIC_START)
                         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 .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):
 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.
     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,
     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
         # type: (bytes, bool, Logger, str, int, bytes,str, bool, bool, serial.Serial, bool) -> None
         self._last_line_part = last_line_part
         self._last_line_part = last_line_part
         self._serial_check_exit = serial_check_exit
         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,
     def handle_serial_input(self, data, console_parser, coredump, gdb_helper, line_matcher,
                             check_gdb_stub_and_run, finalize_line=False):
                             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
         # Remove "+" after Continue command
         if self.start_cmd_sent:
         if self.start_cmd_sent:
             self.start_cmd_sent = False
             self.start_cmd_sent = False
@@ -97,7 +86,8 @@ class SerialHandler:
                 continue
                 continue
             if self._serial_check_exit and line == console_parser.exit_key.encode('latin-1'):
             if self._serial_check_exit and line == console_parser.exit_key.encode('latin-1'):
                 raise SerialStopException()
                 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):
             with coredump.check(line):
                 if self._force_line_print or line_matcher.match(line.decode(errors='ignore')):
                 if self._force_line_print or line_matcher.match(line.decode(errors='ignore')):
                     self.logger.print(line + b'\n')
                     self.logger.print(line + b'\n')
@@ -125,7 +115,8 @@ class SerialHandler:
             self.logger.pc_address_buffer = self._last_line_part[-9:]
             self.logger.pc_address_buffer = self._last_line_part[-9:]
             # GDB sequence can be cut in half also. GDB sequence is 7
             # GDB sequence can be cut in half also. GDB sequence is 7
             # characters long, therefore, we save the last 6 characters.
             # 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''
             self._last_line_part = b''
         # else: keeping _last_line_part and it will be processed the next time
         # else: keeping _last_line_part and it will be processed the next time
         # handle_serial_input is invoked
         # handle_serial_input is invoked
@@ -151,7 +142,7 @@ class SerialHandler:
             self._panic_buffer = b''
             self._panic_buffer = b''
 
 
     def handle_commands(self, cmd, chip, run_make_func, console_reader, serial_reader):
     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)
         config = get_chip_config(chip)
         reset_delay = config['reset']
         reset_delay = config['reset']
         enter_boot_set = config['enter_boot_set']
         enter_boot_set = config['enter_boot_set']
@@ -160,6 +151,14 @@ class SerialHandler:
         high = False
         high = False
         low = True
         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:
         if cmd == CMD_STOP:
             console_reader.stop()
             console_reader.stop()
             serial_reader.stop()
             serial_reader.stop()
@@ -181,6 +180,7 @@ class SerialHandler:
         elif cmd == CMD_TOGGLE_TIMESTAMPS:
         elif cmd == CMD_TOGGLE_TIMESTAMPS:
             self.logger.toggle_timestamps()
             self.logger.toggle_timestamps()
         elif cmd == CMD_ENTER_BOOT:
         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.setDTR(high)  # IO0=HIGH
             self.serial_instance.setRTS(low)  # EN=LOW, chip in reset
             self.serial_instance.setRTS(low)  # EN=LOW, chip in reset
             self.serial_instance.setDTR(self.serial_instance.dtr)  # usbser.sys workaround
             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 queue
+import subprocess
 import sys
 import sys
 import time
 import time
 
 
@@ -23,10 +13,15 @@ from .output_helpers import red_print, yellow_print
 from .stoppable_thread import StoppableThread
 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
     """ Read serial data from the serial port and push to the
     event queue, until stopped.
     event queue, until stopped.
     """
     """
+
     def __init__(self, serial_instance, event_queue):
     def __init__(self, serial_instance, event_queue):
         #  type: (serial.Serial, queue.Queue) -> None
         #  type: (serial.Serial, queue.Queue) -> None
         super(SerialReader, self).__init__()
         super(SerialReader, self).__init__()
@@ -96,3 +91,29 @@ class SerialReader(StoppableThread):
                 self.serial.cancel_read()
                 self.serial.cancel_read()
             except Exception:  # noqa
             except Exception:  # noqa
                 pass
                 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 json
 import os
 import os
 import sys
 import sys
 
 
 import click
 import click
+from idf_monitor_base.output_helpers import yellow_print
 from idf_py_actions.errors import FatalError, NoSerialPortFoundError
 from idf_py_actions.errors import FatalError, NoSerialPortFoundError
 from idf_py_actions.global_options import global_options
 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
 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 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):
     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
         # Import is done here in order to move it after the check_environment() ensured that pyserial has been installed
         try:
         try:
@@ -75,27 +87,25 @@ def action_extensions(base_actions, project_path):
         """
         """
         Run idf_monitor.py to watch build output
         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'])
         elf_file = os.path.join(args.build_dir, project_desc['app_elf'])
         if not os.path.exists(elf_file):
         if not os.path.exists(elf_file):
             raise FatalError("ELF file '%s' not found. You need to build & flash the project before running 'monitor', "
             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. '
                              '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)
                              "Try '%s flash monitor'." % (elf_file, ctx.info_name), ctx)
+
         idf_monitor = os.path.join(os.environ['IDF_PATH'], 'tools/idf_monitor.py')
         idf_monitor = os.path.join(os.environ['IDF_PATH'], 'tools/idf_monitor.py')
         monitor_args = [PYTHON, idf_monitor]
         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']]
         monitor_args += ['--toolchain-prefix', project_desc['monitor_toolprefix']]
 
 
         coredump_decode = get_sdkconfig_value(project_desc['config_file'], 'CONFIG_ESP_COREDUMP_DECODE')
         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
         Run esptool to flash the entire project, from an argfile generated by the build system
         """
         """
         ensure_build_directory(args, ctx.info_name)
         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)
         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):
     def erase_flash(action, ctx, args):
         ensure_build_directory(args, ctx.info_name)
         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
 from __future__ import unicode_literals
 
 
@@ -53,9 +42,9 @@ def main():
     args = parser.parse_args()
     args = parser.parse_args()
 
 
     serial_instance = serial.serial_for_url(args.port, 115200, do_not_open=True)
     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')
     sys.stderr.write('Monitor instance has been created.\n')
     monitor_thread = threading.Thread(target=monitor_serial_reader_state,
     monitor_thread = threading.Thread(target=monitor_serial_reader_state,
                                       args=(monitor.serial_reader, args.serial_alive_file))
                                       args=(monitor.serial_reader, args.serial_alive_file))