OVMS3-idf/tools/idf_py_actions/serial_ext.py
David Cermak 3b5c3d6521 idf.py: debug targets for easier execution of openocd, gdb, gdbui
Support for execution of asynchronous target, such as openocd, which
by default runs in the background, but if it's the only target idf.py
outputs the openocd in the console waiting for termination by user.

Supports also blocking commands gdb and gdbtui to start a debugging
session in an active console.

Supports running gdbgui running a UI debugging session in a browser
window, using the active console for other commands, such as openocd
or monitor.

Supports combining the debug targets in one action list, such as
idf.py openocd gdbgui monitor
2020-04-07 17:37:48 +02:00

249 lines
10 KiB
Python

import json
import os
import sys
import click
from idf_py_actions.errors import FatalError
from idf_py_actions.global_options import global_options
from idf_py_actions.tools import ensure_build_directory, run_tool, run_target, get_sdkconfig_value
PYTHON = sys.executable
def action_extensions(base_actions, project_path):
def _get_default_serial_port():
""" Return a default serial port. esptool can do this (smarter), but it can create
inconsistencies where esptool.py uses one port and idf_monitor uses another.
Same logic as esptool.py search order, reverse sort by name and choose the first port.
"""
# Import is done here in order to move it after the check_environment() ensured that pyserial has been installed
import serial.tools.list_ports
ports = list(reversed(sorted(p.device for p in serial.tools.list_ports.comports())))
try:
print("Choosing default port %s (use '-p PORT' option to set a specific serial port)" %
ports[0].encode("ascii", "ignore"))
return ports[0]
except IndexError:
raise RuntimeError(
"No serial ports found. Connect a device, or use '-p PORT' option to set a specific port.")
def _get_esptool_args(args):
esptool_path = os.path.join(os.environ["IDF_PATH"], "components/esptool_py/esptool/esptool.py")
if args.port is None:
args.port = _get_default_serial_port()
result = [PYTHON, esptool_path]
result += ["-p", args.port]
result += ["-b", str(args.baud)]
with open(os.path.join(args.build_dir, "flasher_args.json")) as f:
flasher_args = json.load(f)
extra_esptool_args = flasher_args["extra_esptool_args"]
result += ["--before", extra_esptool_args["before"]]
result += ["--after", extra_esptool_args["after"]]
result += ["--chip", extra_esptool_args["chip"]]
if not extra_esptool_args["stub"]:
result += ["--no-stub"]
return result
def _get_commandline_options(ctx):
""" Return all the command line options up to first action """
# This approach ignores argument parsing done Click
result = []
for arg in sys.argv:
if arg in ctx.command.commands_with_aliases:
break
result.append(arg)
return result
def monitor(action, ctx, args, print_filter, monitor_baud, encrypted):
"""
Run idf_monitor.py to watch build output
"""
if args.port is None:
args.port = _get_default_serial_port()
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"])
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]
if args.port is not None:
monitor_args += ["-p", args.port]
if not monitor_baud:
if os.getenv("IDF_MONITOR_BAUD"):
monitor_baud = os.getenv("IDF_MONITOR_BAUD", None)
elif os.getenv("MONITORBAUD"):
monitor_baud = os.getenv("MONITORBAUD", None)
else:
monitor_baud = project_desc["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_ESP32_CORE_DUMP_DECODE")
if coredump_decode is not None:
monitor_args += ["--decode-coredumps", coredump_decode]
if print_filter is not None:
monitor_args += ["--print_filter", print_filter]
monitor_args += [elf_file]
if encrypted:
monitor_args += ['--encrypted']
idf_py = [PYTHON] + _get_commandline_options(ctx) # commands to re-run idf.py
monitor_args += ["-m", " ".join("'%s'" % a for a in idf_py)]
if "MSYSTEM" in os.environ:
monitor_args = ["winpty"] + monitor_args
run_tool("idf_monitor", monitor_args, args.project_dir)
def flash(action, ctx, args):
ensure_build_directory(args, ctx.info_name)
"""
Run esptool to flash the entire project, from an argfile generated by the build system
"""
if args.port is None:
args.port = _get_default_serial_port()
run_target(action, args, {"ESPPORT": args.port,
"ESPBAUD": str(args.baud)})
def erase_flash(action, ctx, args):
esptool_args = _get_esptool_args(args)
esptool_args += ["erase_flash"]
run_tool("esptool.py", esptool_args, args.build_dir)
def global_callback(ctx, global_args, tasks):
encryption = any([task.name in ("encrypted-flash", "encrypted-app-flash") for task in tasks])
if encryption:
for task in tasks:
if task.name == "monitor":
task.action_args["encrypted"] = True
break
baud_rate = {
"names": ["-b", "--baud"],
"help": "Baud rate for flashing.",
"scope": "global",
"envvar": "ESPBAUD",
"default": 460800,
}
port = {
"names": ["-p", "--port"],
"help": "Serial port.",
"scope": "global",
"envvar": "ESPPORT",
"default": None,
}
serial_actions = {
"global_action_callbacks": [global_callback],
"actions": {
"flash": {
"callback": flash,
"help": "Flash the project.",
"options": global_options + [baud_rate, port],
"order_dependencies": ["all", "erase_flash"],
},
"erase_flash": {
"callback": erase_flash,
"help": "Erase entire flash chip.",
"options": [baud_rate, port],
},
"monitor": {
"callback":
monitor,
"help":
"Display serial output.",
"options": [
port, {
"names": ["--print-filter", "--print_filter"],
"help":
("Filter monitor output.\n"
"Restrictions on what to print can be specified as a series of <tag>:<log_level> items "
"where <tag> is the tag string and <log_level> is a character from the set "
"{N, E, W, I, D, V, *} referring to a level. "
'For example, "tag1:W" matches and prints only the outputs written with '
'ESP_LOGW("tag1", ...) or at lower verbosity level, i.e. ESP_LOGE("tag1", ...). '
'Not specifying a <log_level> or using "*" defaults to Verbose level.\n'
'Please see the IDF Monitor section of the ESP-IDF documentation '
'for a more detailed description and further examples.'),
"default":
None,
}, {
"names": ["--monitor-baud", "-B"],
"type":
click.INT,
"help": ("Baud rate for monitor.\n"
"If this option is not provided IDF_MONITOR_BAUD and MONITORBAUD "
"environment variables and project_description.json in build directory "
"(generated by CMake from project's sdkconfig) "
"will be checked for default value."),
}, {
"names": ["--encrypted", "-E"],
"is_flag": True,
"help": ("Enable encrypted flash targets.\n"
"IDF Monitor will invoke encrypted-flash and encrypted-app-flash targets "
"if this option is set. This option is set by default if IDF Monitor was invoked "
"together with encrypted-flash or encrypted-app-flash target."),
}
],
"order_dependencies": [
"flash",
"encrypted-flash",
"partition_table-flash",
"bootloader-flash",
"app-flash",
"encrypted-app-flash",
],
},
"partition_table-flash": {
"callback": flash,
"help": "Flash partition table only.",
"options": [baud_rate, port],
"order_dependencies": ["partition_table", "erase_flash"],
},
"bootloader-flash": {
"callback": flash,
"help": "Flash bootloader only.",
"options": [baud_rate, port],
"order_dependencies": ["bootloader", "erase_flash"],
},
"app-flash": {
"callback": flash,
"help": "Flash the app only.",
"options": [baud_rate, port],
"order_dependencies": ["app", "erase_flash"],
},
"encrypted-app-flash": {
"callback": flash,
"help": "Flash the encrypted app only.",
"order_dependencies": ["app", "erase_flash"],
},
"encrypted-flash": {
"callback": flash,
"help": "Flash the encrypted project.",
"order_dependencies": ["all", "erase_flash"],
},
},
}
return serial_actions