idf_monitor.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. #!/usr/bin/env python
  2. #
  3. # esp-idf serial output monitor tool. Does some helpful things:
  4. # - Looks up hex addresses in ELF file with addr2line
  5. # - Reset ESP32 via serial RTS line (Ctrl-T Ctrl-R)
  6. # - Run flash build target to rebuild and flash entire project (Ctrl-T Ctrl-F)
  7. # - Run app-flash build target to rebuild and flash app only (Ctrl-T Ctrl-A)
  8. # - If gdbstub output is detected, gdb is automatically loaded
  9. # - If core dump output is detected, it is converted to a human-readable report
  10. # by espcoredump.py.
  11. #
  12. # Copyright 2015-2021 Espressif Systems (Shanghai) CO LTD
  13. #
  14. # Licensed under the Apache License, Version 2.0 (the "License");
  15. # you may not use this file except in compliance with the License.
  16. # You may obtain a copy of the License at
  17. #
  18. # http://www.apache.org/licenses/LICENSE-2.0
  19. #
  20. # Unless required by applicable law or agreed to in writing, software
  21. # distributed under the License is distributed on an "AS IS" BASIS,
  22. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23. # See the License for the specific language governing permissions and
  24. # limitations under the License.
  25. #
  26. # Contains elements taken from miniterm "Very simple serial terminal" which
  27. # is part of pySerial. https://github.com/pyserial/pyserial
  28. # (C)2002-2015 Chris Liechti <cliechti@gmx.net>
  29. #
  30. # Originally released under BSD-3-Clause license.
  31. #
  32. from __future__ import division, print_function, unicode_literals
  33. import argparse
  34. import codecs
  35. import os
  36. import re
  37. import subprocess
  38. import threading
  39. import time
  40. from builtins import bytes, object
  41. try:
  42. from typing import List, Optional, Union # noqa
  43. except ImportError:
  44. pass
  45. from idf_monitor_base.chip_specific_config import get_chip_config
  46. from idf_monitor_base.console_parser import ConsoleParser, prompt_next_action
  47. from idf_monitor_base.console_reader import ConsoleReader
  48. from idf_monitor_base.constants import (CMD_APP_FLASH, CMD_ENTER_BOOT, CMD_MAKE, CMD_OUTPUT_TOGGLE, CMD_RESET,
  49. CMD_STOP, CMD_TOGGLE_LOGGING, CMD_TOGGLE_TIMESTAMPS, CTRL_H,
  50. DEFAULT_PRINT_FILTER, DEFAULT_TOOLCHAIN_PREFIX, MATCH_PCADDR,
  51. PANIC_DECODE_BACKTRACE, PANIC_DECODE_DISABLE, PANIC_END, PANIC_IDLE,
  52. PANIC_READING, PANIC_STACK_DUMP, PANIC_START, TAG_CMD, TAG_KEY, TAG_SERIAL,
  53. TAG_SERIAL_FLUSH)
  54. from idf_monitor_base.coredump import COREDUMP_DECODE_DISABLE, COREDUMP_DECODE_INFO, CoreDump
  55. from idf_monitor_base.exceptions import SerialStopException
  56. from idf_monitor_base.gdbhelper import GDBHelper
  57. from idf_monitor_base.line_matcher import LineMatcher
  58. from idf_monitor_base.output_helpers import Logger, lookup_pc_address, normal_print, yellow_print
  59. from idf_monitor_base.serial_reader import SerialReader
  60. from idf_monitor_base.web_socket_client import WebSocketClient
  61. from serial.tools import miniterm
  62. try:
  63. import queue # noqa
  64. except ImportError:
  65. import Queue as queue # type: ignore # noqa
  66. import shlex
  67. import sys
  68. import serial
  69. import serial.tools.list_ports
  70. # Windows console stuff
  71. from idf_monitor_base.ansi_color_converter import get_converter
  72. key_description = miniterm.key_description
  73. class Monitor(object):
  74. """
  75. Monitor application main class.
  76. This was originally derived from miniterm.Miniterm, but it turned out to be easier to write from scratch for this
  77. purpose.
  78. Main difference is that all event processing happens in the main thread, not the worker threads.
  79. """
  80. def __init__(
  81. self,
  82. serial_instance, # type: serial.Serial
  83. elf_file, # type: str
  84. print_filter, # type: str
  85. make='make', # type: str
  86. encrypted=False, # type: bool
  87. toolchain_prefix=DEFAULT_TOOLCHAIN_PREFIX, # type: str
  88. eol='CRLF', # type: str
  89. decode_coredumps=COREDUMP_DECODE_INFO, # type: str
  90. decode_panic=PANIC_DECODE_DISABLE, # type: str
  91. target='esp32', # type: str
  92. websocket_client=None, # type: WebSocketClient
  93. enable_address_decoding=True, # type: bool
  94. timestamps=False, # type: bool
  95. timestamp_format='' # type: str
  96. ):
  97. super(Monitor, self).__init__()
  98. self.event_queue = queue.Queue() # type: queue.Queue
  99. self.cmd_queue = queue.Queue() # type: queue.Queue
  100. self.console = miniterm.Console()
  101. self.enable_address_decoding = enable_address_decoding
  102. sys.stderr = get_converter(sys.stderr, decode_output=True)
  103. self.console.output = get_converter(self.console.output)
  104. self.console.byte_output = get_converter(self.console.byte_output)
  105. socket_mode = serial_instance.port.startswith(
  106. 'socket://') # testing hook - data from serial can make exit the monitor
  107. self.serial = serial_instance
  108. self.console_parser = ConsoleParser(eol)
  109. self.console_reader = ConsoleReader(self.console, self.event_queue, self.cmd_queue, self.console_parser,
  110. socket_mode)
  111. self.serial_reader = SerialReader(self.serial, self.event_queue)
  112. self.elf_file = elf_file
  113. if not os.path.exists(make):
  114. # allow for possibility the "make" arg is a list of arguments (for idf.py)
  115. self.make = shlex.split(make) # type: Union[str, List[str]]
  116. else:
  117. self.make = make
  118. self.encrypted = encrypted
  119. self.toolchain_prefix = toolchain_prefix
  120. self.websocket_client = websocket_client
  121. self.target = target
  122. # internal state
  123. self._last_line_part = b''
  124. self._pc_address_buffer = b''
  125. self._line_matcher = LineMatcher(print_filter)
  126. self._invoke_processing_last_line_timer = None # type: Optional[threading.Timer]
  127. self._force_line_print = False
  128. self._serial_check_exit = socket_mode
  129. self._decode_panic = decode_panic
  130. self._reading_panic = PANIC_IDLE
  131. self._panic_buffer = b''
  132. self.start_cmd_sent = False
  133. self.gdb_helper = GDBHelper(self.toolchain_prefix, self.websocket_client, self.elf_file, self.serial.port,
  134. self.serial.baudrate)
  135. self.logger = Logger(self.elf_file, self.console, timestamps, timestamp_format)
  136. self.coredump = CoreDump(decode_coredumps, self.event_queue, self.logger, self.websocket_client, self.elf_file)
  137. def invoke_processing_last_line(self):
  138. # type: () -> None
  139. self.event_queue.put((TAG_SERIAL_FLUSH, b''), False)
  140. def main_loop(self):
  141. # type: () -> None
  142. self.console_reader.start()
  143. self.serial_reader.start()
  144. self.gdb_helper.gdb_exit = False
  145. self.start_cmd_sent = False
  146. try:
  147. while self.console_reader.alive and self.serial_reader.alive:
  148. try:
  149. if self.gdb_helper.gdb_exit:
  150. self.gdb_helper.gdb_exit = False
  151. time.sleep(0.3)
  152. try:
  153. # Continue the program after exit from the GDB
  154. self.serial.write(codecs.encode('+$c#63'))
  155. self.start_cmd_sent = True
  156. except serial.SerialException:
  157. pass # this shouldn't happen, but sometimes port has closed in serial thread
  158. except UnicodeEncodeError:
  159. pass # this can happen if a non-ascii character was passed, ignoring
  160. try:
  161. item = self.cmd_queue.get_nowait()
  162. except queue.Empty:
  163. try:
  164. item = self.event_queue.get(True, 0.03)
  165. except queue.Empty:
  166. continue
  167. (event_tag, data) = item
  168. if event_tag == TAG_CMD:
  169. self.handle_commands(data, self.target)
  170. elif event_tag == TAG_KEY:
  171. try:
  172. self.serial.write(codecs.encode(data))
  173. except serial.SerialException:
  174. pass # this shouldn't happen, but sometimes port has closed in serial thread
  175. except UnicodeEncodeError:
  176. pass # this can happen if a non-ascii character was passed, ignoring
  177. elif event_tag == TAG_SERIAL:
  178. self.handle_serial_input(data)
  179. if self._invoke_processing_last_line_timer is not None:
  180. self._invoke_processing_last_line_timer.cancel()
  181. self._invoke_processing_last_line_timer = threading.Timer(0.1,
  182. self.invoke_processing_last_line)
  183. self._invoke_processing_last_line_timer.start()
  184. # If no further data is received in the next short period
  185. # of time then the _invoke_processing_last_line_timer
  186. # generates an event which will result in the finishing of
  187. # the last line. This is fix for handling lines sent
  188. # without EOL.
  189. elif event_tag == TAG_SERIAL_FLUSH:
  190. self.handle_serial_input(data, finalize_line=True)
  191. else:
  192. raise RuntimeError('Bad event data %r' % ((event_tag, data),))
  193. except KeyboardInterrupt:
  194. try:
  195. yellow_print('To exit from IDF monitor please use \"Ctrl+]\"')
  196. self.serial.write(codecs.encode('\x03'))
  197. except serial.SerialException:
  198. pass # this shouldn't happen, but sometimes port has closed in serial thread
  199. except UnicodeEncodeError:
  200. pass # this can happen if a non-ascii character was passed, ignoring
  201. except SerialStopException:
  202. normal_print('Stopping condition has been received\n')
  203. except KeyboardInterrupt:
  204. pass
  205. finally:
  206. try:
  207. self.console_reader.stop()
  208. self.serial_reader.stop()
  209. self.logger.stop_logging()
  210. # Cancelling _invoke_processing_last_line_timer is not
  211. # important here because receiving empty data doesn't matter.
  212. self._invoke_processing_last_line_timer = None
  213. except Exception:
  214. pass
  215. normal_print('\n')
  216. def check_gdb_stub_and_run(self, line): # type: (bytes) -> None
  217. if self.gdb_helper.check_gdb_stub_trigger(line):
  218. with self: # disable console control
  219. self.gdb_helper.run_gdb()
  220. def handle_serial_input(self, data, finalize_line=False):
  221. # type: (bytes, bool) -> None
  222. # Remove "+" after Continue command
  223. if self.start_cmd_sent is True:
  224. self.start_cmd_sent = False
  225. pos = data.find(b'+')
  226. if pos != -1:
  227. data = data[(pos + 1):]
  228. sp = data.split(b'\n')
  229. if self._last_line_part != b'':
  230. # add unprocessed part from previous "data" to the first line
  231. sp[0] = self._last_line_part + sp[0]
  232. self._last_line_part = b''
  233. if sp[-1] != b'':
  234. # last part is not a full line
  235. self._last_line_part = sp.pop()
  236. for line in sp:
  237. if line == b'':
  238. continue
  239. if self._serial_check_exit and line == self.console_parser.exit_key.encode('latin-1'):
  240. raise SerialStopException()
  241. self.check_panic_decode_trigger(line)
  242. with self.coredump.check(line):
  243. if self._force_line_print or self._line_matcher.match(line.decode(errors='ignore')):
  244. self.logger.print(line + b'\n')
  245. self.handle_possible_pc_address_in_line(line)
  246. self.check_gdb_stub_and_run(line)
  247. self._force_line_print = False
  248. # Now we have the last part (incomplete line) in _last_line_part. By
  249. # default we don't touch it and just wait for the arrival of the rest
  250. # of the line. But after some time when we didn't received it we need
  251. # to make a decision.
  252. force_print_or_matched = any((
  253. self._force_line_print,
  254. (finalize_line and self._line_matcher.match(self._last_line_part.decode(errors='ignore')))
  255. ))
  256. if self._last_line_part != b'' and force_print_or_matched:
  257. self._force_line_print = True
  258. self.logger.print(self._last_line_part)
  259. self.handle_possible_pc_address_in_line(self._last_line_part)
  260. self.check_gdb_stub_and_run(self._last_line_part)
  261. # It is possible that the incomplete line cuts in half the PC
  262. # address. A small buffer is kept and will be used the next time
  263. # handle_possible_pc_address_in_line is invoked to avoid this problem.
  264. # MATCH_PCADDR matches 10 character long addresses. Therefore, we
  265. # keep the last 9 characters.
  266. self._pc_address_buffer = self._last_line_part[-9:]
  267. # GDB sequence can be cut in half also. GDB sequence is 7
  268. # characters long, therefore, we save the last 6 characters.
  269. self.gdb_helper.gdb_buffer = self._last_line_part[-6:]
  270. self._last_line_part = b''
  271. # else: keeping _last_line_part and it will be processed the next time
  272. # handle_serial_input is invoked
  273. def handle_possible_pc_address_in_line(self, line): # type: (bytes) -> None
  274. line = self._pc_address_buffer + line
  275. self._pc_address_buffer = b''
  276. if not self.enable_address_decoding:
  277. return
  278. for m in re.finditer(MATCH_PCADDR, line.decode(errors='ignore')):
  279. translation = lookup_pc_address(m.group(), self.toolchain_prefix, self.elf_file)
  280. if translation:
  281. self.logger.print(translation, console_printer=yellow_print)
  282. def __enter__(self):
  283. # type: () -> None
  284. """ Use 'with self' to temporarily disable monitoring behaviour """
  285. self.serial_reader.stop()
  286. self.console_reader.stop()
  287. def __exit__(self, *args, **kwargs): # type: ignore
  288. """ Use 'with self' to temporarily disable monitoring behaviour """
  289. self.console_reader.start()
  290. self.serial_reader.gdb_exit = self.gdb_helper.gdb_exit # write gdb_exit flag
  291. self.serial_reader.start()
  292. def run_make(self, target): # type: (str) -> None
  293. with self:
  294. if isinstance(self.make, list):
  295. popen_args = self.make + [target]
  296. else:
  297. popen_args = [self.make, target]
  298. yellow_print('Running %s...' % ' '.join(popen_args))
  299. p = subprocess.Popen(popen_args, env=os.environ)
  300. try:
  301. p.wait()
  302. except KeyboardInterrupt:
  303. p.wait()
  304. if p.returncode != 0:
  305. prompt_next_action('Build failed', self.console, self.console_parser, self.event_queue,
  306. self.cmd_queue)
  307. else:
  308. self.logger.output_enabled = True
  309. def check_panic_decode_trigger(self, line): # type: (bytes) -> None
  310. if self._decode_panic == PANIC_DECODE_DISABLE:
  311. return
  312. if self._reading_panic == PANIC_IDLE and re.search(PANIC_START, line.decode('ascii', errors='ignore')):
  313. self._reading_panic = PANIC_READING
  314. yellow_print('Stack dump detected')
  315. if self._reading_panic == PANIC_READING and PANIC_STACK_DUMP in line:
  316. self.logger.output_enabled = False
  317. if self._reading_panic == PANIC_READING:
  318. self._panic_buffer += line.replace(b'\r', b'') + b'\n'
  319. if self._reading_panic == PANIC_READING and PANIC_END in line:
  320. self._reading_panic = PANIC_IDLE
  321. self.logger.output_enabled = True
  322. self.gdb_helper.process_panic_output(self._panic_buffer, self.logger, self.target)
  323. self._panic_buffer = b''
  324. def handle_commands(self, cmd, chip): # type: (int, str) -> None
  325. config = get_chip_config(chip)
  326. reset_delay = config['reset']
  327. enter_boot_set = config['enter_boot_set']
  328. enter_boot_unset = config['enter_boot_unset']
  329. high = False
  330. low = True
  331. if cmd == CMD_STOP:
  332. self.console_reader.stop()
  333. self.serial_reader.stop()
  334. elif cmd == CMD_RESET:
  335. self.serial.setRTS(low)
  336. self.serial.setDTR(self.serial.dtr) # usbser.sys workaround
  337. time.sleep(reset_delay)
  338. self.serial.setRTS(high)
  339. self.serial.setDTR(self.serial.dtr) # usbser.sys workaround
  340. self.logger.output_enabled = True
  341. elif cmd == CMD_MAKE:
  342. self.run_make('encrypted-flash' if self.encrypted else 'flash')
  343. elif cmd == CMD_APP_FLASH:
  344. self.run_make('encrypted-app-flash' if self.encrypted else 'app-flash')
  345. elif cmd == CMD_OUTPUT_TOGGLE:
  346. self.logger.output_toggle()
  347. elif cmd == CMD_TOGGLE_LOGGING:
  348. self.logger.toggle_logging()
  349. elif cmd == CMD_TOGGLE_TIMESTAMPS:
  350. self.logger.toggle_timestamps()
  351. self.logger.toggle_logging()
  352. elif cmd == CMD_ENTER_BOOT:
  353. self.serial.setDTR(high) # IO0=HIGH
  354. self.serial.setRTS(low) # EN=LOW, chip in reset
  355. self.serial.setDTR(self.serial.dtr) # usbser.sys workaround
  356. time.sleep(enter_boot_set) # timeouts taken from esptool.py, includes esp32r0 workaround. defaults: 0.1
  357. self.serial.setDTR(low) # IO0=LOW
  358. self.serial.setRTS(high) # EN=HIGH, chip out of reset
  359. self.serial.setDTR(self.serial.dtr) # usbser.sys workaround
  360. time.sleep(enter_boot_unset) # timeouts taken from esptool.py, includes esp32r0 workaround. defaults: 0.05
  361. self.serial.setDTR(high) # IO0=HIGH, done
  362. else:
  363. raise RuntimeError('Bad command data %d' % cmd) # type: ignore
  364. def main(): # type: () -> None
  365. parser = argparse.ArgumentParser('idf_monitor - a serial output monitor for esp-idf')
  366. parser.add_argument(
  367. '--port', '-p',
  368. help='Serial port device',
  369. default=os.environ.get('ESPTOOL_PORT', '/dev/ttyUSB0')
  370. )
  371. parser.add_argument(
  372. '--disable-address-decoding', '-d',
  373. help="Don't print lines about decoded addresses from the application ELF file",
  374. action='store_true',
  375. default=True if os.environ.get('ESP_MONITOR_DECODE') == 0 else False
  376. )
  377. parser.add_argument(
  378. '--baud', '-b',
  379. help='Serial port baud rate',
  380. type=int,
  381. default=os.getenv('IDF_MONITOR_BAUD', os.getenv('MONITORBAUD', 115200)))
  382. parser.add_argument(
  383. '--make', '-m',
  384. help='Command to run make',
  385. type=str, default='make')
  386. parser.add_argument(
  387. '--encrypted',
  388. help='Use encrypted targets while running make',
  389. action='store_true')
  390. parser.add_argument(
  391. '--toolchain-prefix',
  392. help='Triplet prefix to add before cross-toolchain names',
  393. default=DEFAULT_TOOLCHAIN_PREFIX)
  394. parser.add_argument(
  395. '--eol',
  396. choices=['CR', 'LF', 'CRLF'],
  397. type=lambda c: c.upper(),
  398. help='End of line to use when sending to the serial port',
  399. default='CR')
  400. parser.add_argument(
  401. 'elf_file', help='ELF file of application',
  402. type=argparse.FileType('rb'))
  403. parser.add_argument(
  404. '--print_filter',
  405. help='Filtering string',
  406. default=DEFAULT_PRINT_FILTER)
  407. parser.add_argument(
  408. '--decode-coredumps',
  409. choices=[COREDUMP_DECODE_INFO, COREDUMP_DECODE_DISABLE],
  410. default=COREDUMP_DECODE_INFO,
  411. help='Handling of core dumps found in serial output'
  412. )
  413. parser.add_argument(
  414. '--decode-panic',
  415. choices=[PANIC_DECODE_BACKTRACE, PANIC_DECODE_DISABLE],
  416. default=PANIC_DECODE_DISABLE,
  417. help='Handling of panic handler info found in serial output'
  418. )
  419. parser.add_argument(
  420. '--target',
  421. help='Target name (used when stack dump decoding is enabled)',
  422. default=os.environ.get('IDF_TARGET', 'esp32')
  423. )
  424. parser.add_argument(
  425. '--revision',
  426. help='Revision of the target',
  427. type=int,
  428. default=0
  429. )
  430. parser.add_argument(
  431. '--ws',
  432. default=os.environ.get('ESP_IDF_MONITOR_WS', None),
  433. help='WebSocket URL for communicating with IDE tools for debugging purposes'
  434. )
  435. parser.add_argument(
  436. '--timestamps',
  437. help='Add timestamp for each line',
  438. default=False,
  439. action='store_true')
  440. parser.add_argument(
  441. '--timestamp-format',
  442. default=os.environ.get('ESP_IDF_MONITOR_TIMESTAMP_FORMAT', '%Y-%m-%d %H:%M:%S'),
  443. help='Set a strftime()-compatible timestamp format'
  444. )
  445. args = parser.parse_args()
  446. # GDB uses CreateFile to open COM port, which requires the COM name to be r'\\.\COMx' if the COM
  447. # number is larger than 10
  448. if os.name == 'nt' and args.port.startswith('COM'):
  449. args.port = args.port.replace('COM', r'\\.\COM')
  450. yellow_print('--- WARNING: GDB cannot open serial ports accessed as COMx')
  451. yellow_print('--- Using %s instead...' % args.port)
  452. elif args.port.startswith('/dev/tty.') and sys.platform == 'darwin':
  453. args.port = args.port.replace('/dev/tty.', '/dev/cu.')
  454. yellow_print('--- WARNING: Serial ports accessed as /dev/tty.* will hang gdb if launched.')
  455. yellow_print('--- Using %s instead...' % args.port)
  456. serial_instance = serial.serial_for_url(args.port, args.baud,
  457. do_not_open=True)
  458. serial_instance.dtr = False
  459. serial_instance.rts = False
  460. args.elf_file.close() # don't need this as a file
  461. # remove the parallel jobserver arguments from MAKEFLAGS, as any
  462. # parent make is only running 1 job (monitor), so we can re-spawn
  463. # all of the child makes we need (the -j argument remains part of
  464. # MAKEFLAGS)
  465. try:
  466. makeflags = os.environ['MAKEFLAGS']
  467. makeflags = re.sub(r'--jobserver[^ =]*=[0-9,]+ ?', '', makeflags)
  468. os.environ['MAKEFLAGS'] = makeflags
  469. except KeyError:
  470. pass # not running a make jobserver
  471. # Pass the actual used port to callee of idf_monitor (e.g. make) through `ESPPORT` environment
  472. # variable
  473. # To make sure the key as well as the value are str type, by the requirements of subprocess
  474. espport_key = str('ESPPORT')
  475. espport_val = str(args.port)
  476. os.environ.update({espport_key: espport_val})
  477. ws = WebSocketClient(args.ws) if args.ws else None
  478. try:
  479. monitor = Monitor(serial_instance, args.elf_file.name, args.print_filter, args.make, args.encrypted,
  480. args.toolchain_prefix, args.eol,
  481. args.decode_coredumps, args.decode_panic, args.target,
  482. ws, enable_address_decoding=not args.disable_address_decoding,
  483. timestamps=args.timestamps, timestamp_format=args.timestamp_format)
  484. yellow_print('--- idf_monitor on {p.name} {p.baudrate} ---'.format(p=serial_instance))
  485. yellow_print('--- Quit: {} | Menu: {} | Help: {} followed by {} ---'.format(
  486. key_description(monitor.console_parser.exit_key),
  487. key_description(monitor.console_parser.menu_key),
  488. key_description(monitor.console_parser.menu_key),
  489. key_description(CTRL_H)))
  490. if args.print_filter != DEFAULT_PRINT_FILTER:
  491. yellow_print('--- Print filter: {} ---'.format(args.print_filter))
  492. monitor.main_loop()
  493. except KeyboardInterrupt:
  494. pass
  495. finally:
  496. if ws:
  497. ws.close()
  498. if __name__ == '__main__':
  499. main()