diff --git a/.gitlab/ci/host-test.yml b/.gitlab/ci/host-test.yml index 9f31b9909a..3ed2cac064 100644 --- a/.gitlab/ci/host-test.yml +++ b/.gitlab/ci/host-test.yml @@ -369,4 +369,5 @@ test_linux_example: script: - cd ${IDF_PATH}/examples/build_system/cmake/linux_host_app - idf.py build - - build/linux_host_app.elf + - timeout 5 ./build/linux_host_app.elf >test.log || true + - grep "Restarting" test.log diff --git a/examples/build_system/cmake/linux_host_app/main/linux_host_app.cpp b/examples/build_system/cmake/linux_host_app/main/linux_host_app.cpp index c51a9fb764..7c646d1743 100644 --- a/examples/build_system/cmake/linux_host_app/main/linux_host_app.cpp +++ b/examples/build_system/cmake/linux_host_app/main/linux_host_app.cpp @@ -8,13 +8,23 @@ */ #include "stdio.h" +#include void app_main() { - printf("Hello, Host!\n"); + while(1) { + printf("Hello, Host!\n"); + + for (int i = 10; i >= 0; i--) { + printf("Restarting in %d seconds...\n", i); + sleep(1); + } + } } int main(int argc, char **argv) { + setbuf(stdout, NULL); app_main(); + return 0; } diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index d072cc010a..3c5029668e 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -4019,11 +4019,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 @@ -4032,8 +4030,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 @@ -4043,7 +4039,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 @@ -4155,7 +4150,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 diff --git a/tools/idf_monitor.py b/tools/idf_monitor.py index 32f0525071..93c672e44e 100755 --- a/tools/idf_monitor.py +++ b/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 queue import re +import shlex +import subprocess +import sys import threading import time -from builtins import bytes, object - -try: - from typing import Any, List, Optional, Union # noqa -except ImportError: - pass - -import queue -import shlex -import sys +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 main_loop(self): - # type: () -> None + def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore + raise NotImplementedError + + 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), diff --git a/tools/idf_monitor_base/console_parser.py b/tools/idf_monitor_base/console_parser.py index 50071cbba5..5075c9bbec 100644 --- a/tools/idf_monitor_base/console_parser.py +++ b/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 diff --git a/tools/idf_monitor_base/serial_handler.py b/tools/idf_monitor_base/serial_handler.py index 9281fd40c9..af267088f2 100644 --- a/tools/idf_monitor_base/serial_handler.py +++ b/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() @@ -180,8 +179,8 @@ class SerialHandler: self.logger.toggle_logging() elif cmd == CMD_TOGGLE_TIMESTAMPS: self.logger.toggle_timestamps() - self.logger.toggle_logging() 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 diff --git a/tools/idf_monitor_base/serial_reader.py b/tools/idf_monitor_base/serial_reader.py index f78a575fa6..e2a70841f9 100644 --- a/tools/idf_monitor_base/serial_reader.py +++ b/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() diff --git a/tools/idf_py_actions/serial_ext.py b/tools/idf_py_actions/serial_ext.py index 4d63e8df2e..585fa4fac5 100644 --- a/tools/idf_py_actions/serial_ext.py +++ b/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) diff --git a/tools/test_idf_monitor/idf_monitor_wrapper.py b/tools/test_idf_monitor/idf_monitor_wrapper.py index 3381bb98a0..74cc7950a1 100644 --- a/tools/test_idf_monitor/idf_monitor_wrapper.py +++ b/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))