Merge branch 'feat/idf_monitor_for_linux' into 'master'

feature: idf.py flash/monitor for linux target

Closes IDF-2417

See merge request espressif/esp-idf!15264
This commit is contained in:
Roland Dobai 2021-10-18 12:39:14 +00:00
commit b544b521f4
9 changed files with 283 additions and 228 deletions

View File

@ -369,4 +369,5 @@ test_linux_example:
script: script:
- cd ${IDF_PATH}/examples/build_system/cmake/linux_host_app - cd ${IDF_PATH}/examples/build_system/cmake/linux_host_app
- idf.py build - idf.py build
- build/linux_host_app.elf - timeout 5 ./build/linux_host_app.elf >test.log || true
- grep "Restarting" test.log

View File

@ -8,13 +8,23 @@
*/ */
#include "stdio.h" #include "stdio.h"
#include <unistd.h>
void app_main() { void app_main() {
while(1) {
printf("Hello, Host!\n"); 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) int main(int argc, char **argv)
{ {
setbuf(stdout, NULL);
app_main(); app_main();
return 0; return 0;
} }

View File

@ -4019,11 +4019,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
@ -4032,8 +4030,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
@ -4043,7 +4039,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
@ -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/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

View File

@ -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 # SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
# # SPDX-License-Identifier: Apache-2.0
# 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.
# #
# 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 queue
import re import re
import shlex
import subprocess
import sys
import threading import threading
import time import time
from builtins import bytes, object from builtins import bytes
from typing import Any, List, Optional, Type, Union
try:
from typing import Any, List, Optional, Union # noqa
except ImportError:
pass
import queue
import shlex
import sys
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,76 +92,110 @@ 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) # 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.gdb_helper = GDBHelper(toolchain_prefix, websocket_client, self.elf_file, self.serial.port,
self.serial.baudrate) self.serial.baudrate)
self.logger = Logger(self.elf_file, self.console, timestamps, timestamp_format, b'', enable_address_decoding, else:
toolchain_prefix) socket_mode = False
self.coredump = CoreDump(decode_coredumps, self.event_queue, self.logger, websocket_client, self.elf_file) 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): def __enter__(self) -> None:
# type: () -> None """ Use 'with self' to temporarily disable monitoring behaviour """
self.event_queue.put((TAG_SERIAL_FLUSH, b''), False) self.serial_reader.stop()
self.console_reader.stop()
def main_loop(self): def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore
# type: () -> None 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.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._main_loop()
self.gdb_helper.gdb_exit = False except KeyboardInterrupt:
time.sleep(GDB_EXIT_TIMEOUT) 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:
pass
finally:
try: try:
# Continue the program after exit from the GDB self.console_reader.stop()
self.serial.write(codecs.encode(GDB_UART_CONTINUE_COMMAND)) self.serial_reader.stop()
self.serial_handler.start_cmd_sent = True self.logger.stop_logging()
except serial.SerialException: # Cancelling _invoke_processing_last_line_timer is not
pass # this shouldn't happen, but sometimes port has closed in serial thread # important here because receiving empty data doesn't matter.
except UnicodeEncodeError: self._invoke_processing_last_line_timer = None
pass # this can happen if a non-ascii character was passed, ignoring except Exception: # noqa
pass
normal_print('\n')
def serial_write(self, *args, **kwargs): # type: ignore
raise NotImplementedError
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: try:
item = self.cmd_queue.get_nowait() item = self.cmd_queue.get_nowait()
except queue.Empty: except queue.Empty:
try: try:
item = self.event_queue.get(timeout=EVENT_QUEUE_TIMEOUT) item = self.event_queue.get(timeout=EVENT_QUEUE_TIMEOUT)
except queue.Empty: except queue.Empty:
continue return
event_tag, data = item event_tag, data = item
if event_tag == TAG_CMD: if event_tag == TAG_CMD:
self.serial_handler.handle_commands(data, self.target, self.run_make, self.console_reader, self.serial_handler.handle_commands(data, self.target, self.run_make, self.console_reader,
self.serial_reader) self.serial_reader)
elif event_tag == TAG_KEY: elif event_tag == TAG_KEY:
try: self.serial_write(codecs.encode(data))
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: elif event_tag == TAG_SERIAL:
self.serial_handler.handle_serial_input(data, self.console_parser, self.coredump, self.serial_handler.handle_serial_input(data, self.console_parser, self.coredump,
self.gdb_helper, self._line_matcher, self.gdb_helper, self._line_matcher,
@ -200,55 +216,59 @@ class Monitor(object):
self.check_gdb_stub_and_run, finalize_line=True) self.check_gdb_stub_and_run, finalize_line=True)
else: else:
raise RuntimeError('Bad event data %r' % ((event_tag, data),)) raise RuntimeError('Bad event data %r' % ((event_tag, data),))
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
except SerialStopException:
normal_print('Stopping condition has been received\n')
except KeyboardInterrupt:
pass
finally:
try:
self.console_reader.stop()
self.serial_reader.stop()
self.logger.stop_logging()
# Cancelling _invoke_processing_last_line_timer is not
# important here because receiving empty data doesn't matter.
self._invoke_processing_last_line_timer = None
except Exception: # noqa
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 __exit__(self, *args, **kwargs): # type: ignore 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 def _main_loop(self) -> None:
with self: if self.gdb_helper.gdb_exit:
run_make(target, self.make, self.console, self.console_parser, self.event_queue, self.cmd_queue, self.gdb_helper.gdb_exit = False
self.logger) 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
ws = WebSocketClient(args.ws) if args.ws else None
try:
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 # Pass the actual used port to callee of idf_monitor (e.g. make) through `ESPPORT` environment
# variable # variable
# To make sure the key as well as the value are str type, by the requirements of subprocess # To make sure the key as well as the value are str type, by the requirements of subprocess
espport_val = str(args.port) espport_val = str(args.port)
os.environ.update({ESPPORT_ENVIRON: espport_val}) os.environ.update({ESPPORT_ENVIRON: espport_val})
ws = WebSocketClient(args.ws) if args.ws else None cls = SerialMonitor
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)
yellow_print('--- idf_monitor on {p.name} {p.baudrate} ---'.format(p=serial_instance)) 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('--- 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),

View File

@ -1,16 +1,5 @@
# Copyright 2015-2021 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
# # SPDX-License-Identifier: Apache-2.0
# 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.
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

View File

@ -1,23 +1,12 @@
# Copyright 2015-2021 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
# # SPDX-License-Identifier: Apache-2.0
# 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.
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 .coredump import CoreDump
from .exceptions import SerialStopException # noqa: F401 from .exceptions import SerialStopException
from .gdbhelper import GDBHelper # noqa: F401 from .gdbhelper import GDBHelper
from .line_matcher import LineMatcher # noqa: F401 from .line_matcher import LineMatcher
from .logger import Logger # noqa: F401 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,6 +86,7 @@ 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()
if gdb_helper:
self.check_panic_decode_trigger(line, 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')):
@ -125,6 +115,7 @@ 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.
if gdb_helper:
gdb_helper.gdb_buffer = self._last_line_part[-6:] 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
@ -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()
@ -180,8 +179,8 @@ class SerialHandler:
self.logger.toggle_logging() self.logger.toggle_logging()
elif cmd == CMD_TOGGLE_TIMESTAMPS: elif cmd == CMD_TOGGLE_TIMESTAMPS:
self.logger.toggle_timestamps() self.logger.toggle_timestamps()
self.logger.toggle_logging()
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

View File

@ -1,18 +1,8 @@
# Copyright 2015-2021 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
# # SPDX-License-Identifier: Apache-2.0
# 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.
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()

View File

@ -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,20 +87,17 @@ def action_extensions(base_actions, project_path):
""" """
Run idf_monitor.py to watch build output Run idf_monitor.py to watch build output
""" """
project_desc = _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)
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]
if project_desc['target'] != 'linux':
esp_port = args.port or _get_default_serial_port(args) esp_port = args.port or _get_default_serial_port(args)
monitor_args += ['-p', esp_port] monitor_args += ['-p', esp_port]
@ -96,6 +105,7 @@ def action_extensions(base_actions, project_path):
monitor_baud = os.getenv('IDF_MONITOR_BAUD') or os.getenv('MONITORBAUD') or project_desc['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)

View File

@ -1,16 +1,5 @@
# Copyright 2019 Espressif Systems (Shanghai) PTE LTD # SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD
# # SPDX-License-Identifier: Apache-2.0
# 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.
from __future__ import unicode_literals from __future__ import unicode_literals
@ -53,7 +42,7 @@ 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', monitor = idf_monitor.SerialMonitor(serial_instance, args.elf_file, args.print_filter, 'make',
toolchain_prefix=args.toolchain_prefix, eol='CR', toolchain_prefix=args.toolchain_prefix, eol='CR',
decode_panic=args.decode_panic, target=args.target) 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')