# # 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()