Files
nrf5340_audio/tools/buildprog/buildprog.py

419 lines
13 KiB
Python

#
# Copyright (c) 2018 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
"""
Script to build and program the nRF5340 Audio project to multiple devices
"""
import argparse
import sys
import shutil
import os
import json
import subprocess
import re
import getpass
from pathlib import Path
from colorama import Fore, Style
from prettytable import PrettyTable
from nrf5340_audio_dk_devices import (
BuildType,
Channel,
DeviceConf,
BuildConf,
AudioDevice,
SelectFlags,
Core,
Transport,
)
from program import program_threads_run
BUILDPROG_FOLDER = Path(__file__).resolve().parent
NRF5340_AUDIO_FOLDER = (BUILDPROG_FOLDER / "../..").resolve()
NRF_FOLDER = (BUILDPROG_FOLDER / "../../../..").resolve()
if os.getenv("AUDIO_KIT_SERIAL_NUMBERS_JSON") is None:
AUDIO_KIT_SERIAL_NUMBERS_JSON = BUILDPROG_FOLDER / "nrf5340_audio_dk_devices.json"
else:
AUDIO_KIT_SERIAL_NUMBERS_JSON = Path(
os.getenv("AUDIO_KIT_SERIAL_NUMBERS_JSON"))
TARGET_BOARD_NRF5340_AUDIO_DK_APP_NAME = "nrf5340_audio_dk/nrf5340/cpuapp"
TARGET_AUDIO_FOLDER = NRF5340_AUDIO_FOLDER
TARGET_AUDIO_BUILD_FOLDER = TARGET_AUDIO_FOLDER / "tools/build"
UNICAST_SERVER_OVERLAY = NRF5340_AUDIO_FOLDER / "unicast_server/overlay-unicast_server.conf"
UNICAST_CLIENT_OVERLAY = NRF5340_AUDIO_FOLDER / "unicast_client/overlay-unicast_client.conf"
BROADCAST_SINK_OVERLAY = NRF5340_AUDIO_FOLDER / "broadcast_sink/overlay-broadcast_sink.conf"
BROADCAST_SOURCE_OVERLAY = NRF5340_AUDIO_FOLDER / "broadcast_source/overlay-broadcast_source.conf"
TARGET_RELEASE_FOLDER = "build_release"
TARGET_DEBUG_FOLDER = "build_debug"
MAX_USER_NAME_LEN = 248 - len('\0')
def __print_add_color(status):
if status == SelectFlags.FAIL:
return Fore.RED + status.value + Style.RESET_ALL
elif status == SelectFlags.DONE:
return Fore.GREEN + status.value + Style.RESET_ALL
return status.value
def __print_dev_conf(device_list):
"""Print settings in a formatted manner"""
table = PrettyTable()
table.field_names = [
"snr",
"snr conn",
"device",
"only reboot",
"core app programmed",
"core net programmed",
]
for device in device_list:
row = []
row.append(device.nrf5340_audio_dk_snr)
color = Fore.GREEN if device.snr_connected else Fore.YELLOW
row.append(color + str(device.snr_connected) + Style.RESET_ALL)
row.append(device.nrf5340_audio_dk_dev.value)
row.append(__print_add_color(device.only_reboot))
row.append(__print_add_color(device.core_app_programmed))
row.append(__print_add_color(device.core_net_programmed))
table.add_row(row)
print(table)
def __build_cmd_get(core: Core, device: AudioDevice, build: BuildType,
pristine, options):
build_cmd = (f"west build {TARGET_AUDIO_FOLDER} "
f"-b {TARGET_BOARD_NRF5340_AUDIO_DK_APP_NAME} "
f"--sysbuild")
if core == Core.app:
build_cmd += " --domain nrf5340_audio"
elif core == Core.net:
build_cmd += " --domain ipc_radio"
else:
raise Exception("Invalid core!")
if build == BuildType.debug:
release_flag = ""
elif build == BuildType.release:
release_flag = " -DFILE_SUFFIX=release"
else:
raise Exception("Invalid build type!")
device_flag = ""
if options.nrf21540:
device_flag += " -Dnrf5340_audio_SHIELD=nrf21540ek"
device_flag += " -Dipc_radio_SHIELD=nrf21540ek"
if options.custom_bt_name is not None and options.user_bt_name:
raise Exception(
"User BT name option is invalid when custom BT name is set")
if options.custom_bt_name is not None:
custom_bt_name = "_".join(options.custom_bt_name)[
:MAX_USER_NAME_LEN].upper()
device_flag += " -DCONFIG_BT_DEVICE_NAME=\\\"" + custom_bt_name + "\\\""
if options.user_bt_name:
user_specific_bt_name = (
"AUDIO_DEV_" + getpass.getuser())[:MAX_USER_NAME_LEN].upper()
device_flag += " -DCONFIG_BT_DEVICE_NAME=\\\"" + user_specific_bt_name + "\\\""
if options.transport == Transport.broadcast.name:
if device == AudioDevice.headset:
overlay_flag = f" -DEXTRA_CONF_FILE={BROADCAST_SINK_OVERLAY}"
else:
overlay_flag = f" -DEXTRA_CONF_FILE={BROADCAST_SOURCE_OVERLAY}"
else:
if device == AudioDevice.headset:
overlay_flag = f" -DEXTRA_CONF_FILE={UNICAST_SERVER_OVERLAY}"
else:
overlay_flag = f" -DEXTRA_CONF_FILE={UNICAST_CLIENT_OVERLAY}"
if os.name == 'nt':
release_flag = release_flag.replace('\\', '/')
if pristine:
build_cmd += " --pristine"
dest_folder = TARGET_AUDIO_BUILD_FOLDER / options.transport / device / core / build
return build_cmd, dest_folder, device_flag, release_flag, overlay_flag
def __build_module(build_config, options):
build_cmd, dest_folder, device_flag, release_flag, overlay_flag = __build_cmd_get(
build_config.core,
build_config.device,
build_config.build,
build_config.pristine,
options,
)
west_str = f"{build_cmd} -d {dest_folder} "
if build_config.pristine and dest_folder.exists():
shutil.rmtree(dest_folder)
# Only add compiler flags if folder doesn't exist already
if not dest_folder.exists():
west_str = west_str + device_flag + release_flag + overlay_flag
print("Run: " + west_str)
ret_val = os.system(west_str)
if ret_val:
raise Exception("cmake error: " + str(ret_val))
def __find_snr():
"""Rebooting or programming requires connected programmer/debugger"""
# Use nrfjprog executable for WSL compatibility
stdout = subprocess.check_output(
"nrfjprog --ids", shell=True).decode("utf-8")
snrs = re.findall(r"([\d]+)", stdout)
if not snrs:
print("No programmer/debugger connected to PC")
return list(map(int, snrs))
def __populate_hex_paths(dev, options):
"""Poplulate hex paths where relevant"""
_, temp_dest_folder, _, _, _ = __build_cmd_get(Core.app, dev.nrf5340_audio_dk_dev, options.build, options.pristine, options)
dev.hex_path_app = temp_dest_folder / "nrf5340_audio/zephyr/zephyr.hex"
_, temp_dest_folder, _, _, _ = __build_cmd_get(Core.net, dev.nrf5340_audio_dk_dev, options.build, options.pristine, options)
dev.hex_path_net = temp_dest_folder / "ipc_radio/zephyr/zephyr.hex"
def __finish(device_list):
"""Finish script. Print report"""
print("build_prog.py finished. Report:")
__print_dev_conf(device_list)
exit(0)
def __main():
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=(
"This script builds and programs the nRF5340 "
"Audio project on Windows and Linux"
),
epilog=("If there exists an environmental variable called \"AUDIO_KIT_SERIAL_NUMBERS_JSON\""
"which contains the location of a json file,"
"the program will use this file as a substitute for nrf5340_audio_dk_devices.json"),
allow_abbrev=False
)
parser.add_argument(
"-r",
"--only_reboot",
default=False,
action="store_true",
help="Only reboot, no building or programming",
)
parser.add_argument(
"-p",
"--program",
default=False,
action="store_true",
help="Will program and reboot nRF5340 Audio DK",
)
parser.add_argument(
"-c",
"--core",
type=str,
choices=[i.name for i in Core],
help="Select which cores to include in build",
)
parser.add_argument(
"--pristine",
default=False,
action="store_true",
help="Will build cleanly"
)
parser.add_argument(
"-b",
"--build",
required="-p" in sys.argv or "--program" in sys.argv,
choices=[i.name for i in BuildType],
help="Select the build type",
)
parser.add_argument(
"-d",
"--device",
required=("-r" in sys.argv or "--only_reboot" in sys.argv)
or (
("-b" in sys.argv or "--build" in sys.argv)
and ("both" in sys.argv or "app" in sys.argv)
),
choices=[i.name for i in AudioDevice],
help=(
"nRF5340 Audio on the application core can be "
"built for either ordinary headset "
"(earbuds/headphone..) use or gateway (USB dongle)"
),
)
parser.add_argument(
"-s",
"--sequential",
action="store_true",
dest="sequential_prog",
default=False,
help="Run nrfjprog sequentially instead of in parallel",
)
parser.add_argument(
"-f",
"--recover_on_fail",
action="store_true",
dest="recover_on_fail",
default=False,
help="Recover device if programming fails",
)
parser.add_argument(
"--nrf21540",
action="store_true",
dest="nrf21540",
default=False,
help="Set when using nRF21540 for extra TX power",
)
parser.add_argument(
"-cn",
"--custom_bt_name",
nargs='*',
dest="custom_bt_name",
default=None,
help="Use custom Bluetooth device name",
)
parser.add_argument(
"-u",
"--user_bt_name",
action="store_true",
dest="user_bt_name",
default=False,
help="Set to generate a user specific Bluetooth device name.\
Note that this will put the computer user name on air in clear text",
)
parser.add_argument(
"-t",
"--transport",
required=True,
choices=[i.name for i in Transport],
default=Transport.unicast.name,
help="Select the transport type",
)
options = parser.parse_args(args=sys.argv[1:])
# Post processing for Enums
if options.core is None:
cores = []
elif options.core == "both":
cores = [Core.app, Core.net]
else:
cores = [Core[options.core]]
if options.device is None:
devices = []
elif options.device == "both":
devices = [AudioDevice.gateway, AudioDevice.headset]
else:
devices = [AudioDevice[options.device]]
options.build = BuildType[options.build] if options.build else None
options.only_reboot = SelectFlags.TBD if options.only_reboot else SelectFlags.NOT
boards_snr_connected = __find_snr()
if not boards_snr_connected:
print("No snrs connected")
# Update device list
# This JSON file should be altered by the developer.
# Then run git update-index --skip-worktree FILENAME to avoid changes
# being pushed
with AUDIO_KIT_SERIAL_NUMBERS_JSON.open() as f:
dev_arr = json.load(f)
device_list = [
DeviceConf(
nrf5340_audio_dk_snr=dev["nrf5340_audio_dk_snr"],
channel=Channel[dev["channel"]],
snr_connected=(dev["nrf5340_audio_dk_snr"]
in boards_snr_connected),
recover_on_fail=options.recover_on_fail,
nrf5340_audio_dk_dev=AudioDevice[dev["nrf5340_audio_dk_dev"]],
cores=cores,
devices=devices,
_only_reboot=options.only_reboot,
)
for dev in dev_arr
]
__print_dev_conf(device_list)
# Initialization step finsihed
# Reboot step start
if options.only_reboot == SelectFlags.TBD:
program_threads_run(device_list, sequential=options.sequential_prog)
__finish(device_list)
# Reboot step finished
# Build step start
if options.build is not None:
print("Invoking build step")
build_configs = []
if AudioDevice.headset in devices:
for c in cores:
build_configs.append(
BuildConf(
core=c,
device=AudioDevice.headset,
pristine=options.pristine,
build=options.build,
)
)
if AudioDevice.gateway in devices:
for c in cores:
build_configs.append(
BuildConf(
core=c,
device=AudioDevice.gateway,
pristine=options.pristine,
build=options.build,
)
)
for build_cfg in build_configs:
__build_module(build_cfg, options)
# Build step finished
# Program step start
if options.program:
for dev in device_list:
if dev.snr_connected:
__populate_hex_paths(dev, options)
program_threads_run(device_list, sequential=options.sequential_prog)
# Program step finished
__finish(device_list)
if __name__ == "__main__":
__main()