blob: d9cc17a219439530f250d3441706c41ab5cf61bf [file] [log] [blame] [edit]
#
# Copyright (c) Memfault, Inc.
# See License.txt for details
#
import os
import platform
import shutil
import sys
from glob import glob
from invoke import Collection, task
from pyftdi.usbtools import UsbTools
from .gdb import gdb_build_cmd
TASKS_DIR = os.path.dirname(__file__)
MEMFAULT_SDK_ROOT = os.path.join(TASKS_DIR, "..")
ESP32_PLATFORM_ROOT = os.path.join(MEMFAULT_SDK_ROOT, "examples", "esp32")
ESP32_IDF_ROOT = os.path.join(ESP32_PLATFORM_ROOT, "esp-idf")
ESP32_IDF_SCRIPT = os.path.join(ESP32_IDF_ROOT, "tools", "idf.py")
ESP32_COREDUMP_SCRIPT = os.path.join(ESP32_IDF_ROOT, "components", "espcoredump", "espcoredump.py")
ESP32_TEST_APP_ROOT = os.path.join(ESP32_PLATFORM_ROOT, "apps", "memfault_demo_app")
ESP32_TEST_APP_ELF = os.path.join(ESP32_TEST_APP_ROOT, "build", "memfault-esp32-demo-app.elf")
ESP32_FTDI_VID_PID = [(0x0403, 0x6010)]
OPENOCD_GDB_PORT_DEFAULT = 3333
def _run_idf_script(ctx, *args, **kwargs):
# allow selecting a specific python interpreter instead of the active one.
# this is necessary in CI, because the mbed build task modifies the python
# environment 😖, and idf.py runs a pkg_resources.require() which fails if
# the mbed task modified any deps in an incompatible way. it's better to
# keep the esp_idf environment isolated and just use it to run idf.py
python = os.getenv("ESP_IDF_PYTHON", sys.executable)
with ctx.cd(ESP32_TEST_APP_ROOT):
ctx.run(
"{python} {idf} {args}".format(
python=python, idf=ESP32_IDF_SCRIPT, args=" ".join(args)
),
env={"IDF_PATH": ESP32_IDF_ROOT},
**kwargs,
)
def _esp32_guess_console_port():
def _esp32_find_console_port():
host_os = platform.system()
if host_os == "Darwin":
usb_paths = glob("/dev/cu.usbserial-*1")
if usb_paths:
return usb_paths[0]
# Try pyftdi only on MacOS as it fails on Linux.
# Actually, Espressif's esptool.py seems not to
# like this at all.
print("Trying FTDI first")
devs = UsbTools.find_all(ESP32_FTDI_VID_PID)
if devs:
return "ftdi://ftdi:2232/2"
elif host_os == "Linux":
# Try ttyUSB1, no need to glob.
serial_device = "/dev/ttyUSB1"
print("Trying {serdev}".format(serdev=serial_device))
if os.path.exists(serial_device):
return serial_device
print(
"Cannot find ESP32 console /dev/... nor ftdi:// path, please specify it manually using"
" --port"
)
sys.exit(1)
port = _esp32_find_console_port()
print("No --port specified, using console port {port}".format(port=port))
return port
@task
def run_xtensa_toolchain_check(ctx):
if sys.version_info.major < 3:
# shutil which is only available for python3
return
xtensa_toolchain = shutil.which("xtensa-esp32-elf-gcc")
if xtensa_toolchain is None:
msg = (
"Couldn't find arm toolchain. Currently using the toolchain which can be"
" found here {}".format(
"https://docs.espressif.com/projects/esp-idf/en/v3.1/get-started/index.html#setup-toolchain"
)
)
raise FileNotFoundError(msg)
@task(pre=[run_xtensa_toolchain_check])
def esp32_app_build(ctx):
"""Build the ESP32 test app"""
_run_idf_script(ctx, "build")
@task
def esp32_app_clean(ctx):
"""Clean the ESP32 test app"""
_run_idf_script(ctx, "fullclean")
@task(pre=[run_xtensa_toolchain_check])
def esp32s2_app_build(ctx):
"""Build the ESP32-S2 test app"""
# !NOTE! 'set-target' was added in ESP-IDF v4.1. If you are using an older
# version of ESP-IDF, building for the ESP32-S2 + ESP32-S3 won't work.
_run_idf_script(ctx, "set-target esp32s2")
_run_idf_script(ctx, "build")
@task(pre=[run_xtensa_toolchain_check])
def esp32s3_app_build(ctx):
"""Build the ESP32-S3 test app"""
_run_idf_script(ctx, "set-target esp32s3")
_run_idf_script(ctx, "build")
@task
def esp32_app_flash(ctx, port=None):
"""Flash the ESP32 test app"""
if port is None:
port = _esp32_guess_console_port()
_run_idf_script(ctx, "-p {port}".format(port=port), "flash")
@task
def esp32_console(ctx, port=None):
"""Flash the ESP32 test app"""
if port is None:
port = _esp32_guess_console_port()
# For now, just use miniterm, idf_monitor.py doesn't play nice with pyftdi for some reason :(
# _run_idf_script(ctx, '-p {port}'.format(port=port), 'monitor', pty=True)
ctx.run("miniterm.py --eol CR --raw {port} 115200".format(port=port), pty=True)
@task
def esp32_app_menuconfig(ctx):
"""Run menuconfig for the ESP32 test app"""
_run_idf_script(ctx, "menuconfig", pty=True)
@task
def esp32_openocd(ctx):
"""Launch openocd"""
if "ESP32_OPENOCD" not in os.environ:
print("Set ESP32_OPENOCD environment variable to point to openocd-esp32 root directory!")
print(
"Download the openocd-esp32 binaries here:"
" https://github.com/espressif/openocd-esp32/releases"
)
sys.exit(-1)
with ctx.cd(os.environ["ESP32_OPENOCD"]):
ctx.run(
"bin/openocd -s share/openocd/scripts "
"-f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg",
pty=True,
)
@task
def esp32_app_gdb(ctx, gdb=None, reset=False):
"""Launches xtensa-gdb with app elf and connects to openocd gdb server"""
if gdb is None:
gdb = OPENOCD_GDB_PORT_DEFAULT
with ctx.cd(ESP32_TEST_APP_ROOT):
gdb_cmd = gdb_build_cmd(
"", ESP32_TEST_APP_ELF, gdb, gdb_prefix="xtensa-esp32-elf-", reset=reset
)
ctx.run(gdb_cmd, pty=True)
ns = Collection("esp32")
ns.add_task(esp32_console, name="console")
ns.add_task(esp32_openocd, name="gdbserver")
ns.add_task(esp32_app_build, name="build")
ns.add_task(esp32s2_app_build, name="build-s2")
ns.add_task(esp32s3_app_build, name="build-s3")
ns.add_task(esp32_app_clean, name="clean")
ns.add_task(esp32_app_flash, name="flash")
ns.add_task(esp32_app_gdb, name="app-gdb")
ns.add_task(esp32_app_menuconfig, name="app-menuconfig")