blob: 05cf43fedf08a393e188575fea7c032421e078cb [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2020 Project CHIP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import optparse
import os
import subprocess
from pathlib import Path
from sys import platform
import yaml
global commandQueue
commandQueue = ""
class TermColors:
STRBOLD = '\033[1m'
STRRESET = '\033[0m'
STRCYAN = '\033[1;36m'
STRYELLOW = '\033[1;33m'
def splash():
splashText = TermColors.STRBOLD + TermColors.STRYELLOW + '''\
______ __ __ _______ _______
/ || | | | | ____|| ____|
| ,----'| |__| | | |__ | |__
| | | __ | | __| | __|
| `----.| | | | | |____ | |
\\______||__| |__| |_______||__|\
''' + TermColors.STRRESET
return splash
def printc(strInput):
color = TermColors.STRCYAN
print(color + strInput + TermColors.STRRESET)
def loadConfig(paths):
config = dict()
config["nrfconnect"] = dict()
config["esp32"] = dict()
configFile = paths["scriptFolder"] + "/config.yaml"
if (os.path.exists(configFile)):
configStream = open(configFile, 'r')
config = yaml.load(configStream, Loader=yaml.SafeLoader)
configStream.close()
else:
print("Running for the first time and configuring config.yaml. " +
"Change this configuration file to include correct configuration " +
"for the vendor's SDK")
configStream = open(configFile, 'w')
config["nrfconnect"]["ZEPHYR_BASE"] = os.environ.get('ZEPHYR_BASE')
config["nrfconnect"]["TTY"] = None
config["esp32"]["IDF_PATH"] = os.environ.get('IDF_PATH')
config["esp32"]["TTY"] = None
print(yaml.dump(config))
yaml.dump(config, configStream)
configStream.close()
return config
def definePaths():
paths = dict()
paths["scriptFolder"] = os.path.abspath(os.path.dirname(__file__))
paths["matterFolder"] = paths["scriptFolder"] + "/../../"
paths["rootSampleFolder"] = paths["scriptFolder"]
paths["devices"] = []
for filepath in Path(f"{paths['rootSampleFolder']}/devices").rglob('*.zap'):
paths["devices"].append(
str(os.path.splitext(os.path.basename(filepath))[0]))
return paths
def checkPythonVersion():
if sys.version_info[0] < 3:
print('Must use Python 3. Current version is ' +
str(sys.version_info[0]))
exit(1)
def queuePrint(str):
global commandQueue
if (len(commandQueue) > 0):
commandQueue = commandQueue + "; "
commandQueue = commandQueue + "echo -e " + str
def queueCommand(command):
global commandQueue
if (len(commandQueue) > 0):
commandQueue = commandQueue + "; "
commandQueue = commandQueue + command
def execQueue():
global commandQueue
subprocess.run(commandQueue, shell=True, executable=shellApp)
commandQueue = ""
def hexInputToInt(valIn):
'''Parses inputs as hexadecimal numbers, takes into account optional 0x
prefix
'''
if (type(valIn) is str):
if (valIn[0:2].lower() == "0x"):
valOut = valIn[2:]
else:
valOut = valIn
valOut = int(valOut, 16)
else:
valOut = valIn
return valOut
def main(argv):
checkPythonVersion()
paths = definePaths()
config = loadConfig(paths)
global myEnv
myEnv = os.environ.copy()
#
# Build environment switches
#
global shellApp
if platform == "linux" or platform == "linux2":
shellApp = '/bin/bash'
elif platform == "darwin":
shellApp = '/bin/zsh'
elif platform == "win32":
print('Windows is currently not supported. Use Linux or MacOS platforms')
exit(1)
#
# Arguments parser
#
deviceTypes = "\n ".join(paths["devices"])
usage = f'''usage: chef.py [options]
Platforms:
nrfconnect
esp32
linux
Device Types:
{deviceTypes}
Notes:
- Whenever you change a device type, make sure to also use options -zbe
- Be careful if you have more than one device connected. The script assumes you have only one device connected and might flash the wrong one\
'''
parser = optparse.OptionParser(usage=usage)
parser.add_option("-u", "--update_toolchain", help="updates toolchain & installs zap",
action="store_true", dest="doUpdateToolchain")
parser.add_option("-b", "--build", help="builds",
action="store_true", dest="doBuild")
parser.add_option("-c", "--clean", help="clean build. Only valid if also building",
action="store_true", dest="doClean")
parser.add_option("-f", "--flash", help="flashes device",
action="store_true", dest="doFlash")
parser.add_option("-e", "--erase", help="erases flash before flashing. Only valid if also flashing",
action="store_true", dest="doErase")
parser.add_option("-i", "--terminal", help="opens terminal to interact with with device",
action="store_true", dest="doInteract")
parser.add_option("-m", "--menuconfig", help="runs menuconfig on platforms that support it",
action="store_true", dest="doMenuconfig")
parser.add_option("", "--bootstrap_zap", help="installs zap dependencies",
action="store_true", dest="doBootstrapZap")
parser.add_option("-z", "--zap", help="runs zap to generate data model & interaction model artifacts",
action="store_true", dest="doRunZap")
parser.add_option("-g", "--zapgui", help="runs zap GUI display to allow editing of data model",
action="store_true", dest="doRunGui")
parser.add_option("-d", "--device", dest="sampleDeviceTypeName",
help="specifies device type. Default is lighting. See info above for supported device types", metavar="TARGET", default="lighting")
parser.add_option("-t", "--target", type='choice',
action='store',
dest="buildTarget",
help="specifies target platform. Default is esp32. See info below for currently supported target platforms",
choices=['nrfconnect', 'esp32', 'linux', ],
metavar="TARGET",
default="esp32")
parser.add_option("-r", "--rpc", help="enables Pigweed RPC interface. Enabling RPC disables the shell interface. Your sdkconfig configurations will be reverted to default. Default is PW RPC off. When enabling or disabling this flag, on the first build force a clean build with -c", action="store_true", dest="doRPC")
parser.add_option("-v", "--vid", dest="vid",
help="specifies the Vendor ID. Default is 0xFFF1", metavar="VID", default=0xFFF1)
parser.add_option("-p", "--pid", dest="pid",
help="specifies the Product ID. Default is 0x8000", metavar="PID", default=0x8000)
parser.add_option("", "--rpc_console", help="Opens PW RPC Console",
action="store_true", dest="doRPC_CONSOLE")
parser.add_option("-y", "--tty", help="Enumerated USB tty/serial interface enumerated for your physical device. E.g.: /dev/ACM0",
dest="tty", metavar="TTY", default=None)
options, _ = parser.parse_args(argv)
splash()
#
# Platform Folder
#
queuePrint(f"Target is set to {options.sampleDeviceTypeName}")
paths["genFolder"] = paths["rootSampleFolder"] + f"/out/{options.sampleDeviceTypeName}/zap-generated/"
queuePrint("Setting up environment...")
if options.buildTarget == "esp32":
if config['esp32']['IDF_PATH'] is None:
print('Path for esp32 SDK was not found. Make sure esp32.IDF_PATH is set on your config.yaml file')
exit(1)
paths["platFolder"] = os.path.normpath(
paths["rootSampleFolder"] + "/esp32")
queueCommand(f'source {config["esp32"]["IDF_PATH"]}/export.sh')
elif options.buildTarget == "nrfconnect":
if config['nrfconnect']['ZEPHYR_BASE'] is None:
print('Path for nrfconnect SDK was not found. Make sure nrfconnect.ZEPHYR_BASE is set on your config.yaml file')
exit(1)
paths["platFolder"] = os.path.normpath(
paths["rootSampleFolder"] + "/nrfconnect")
queueCommand(f'source {config["nrfconnect"]["ZEPHYR_BASE"]}/zephyr-env.sh')
queueCommand("export ZEPHYR_TOOLCHAIN_VARIANT=gnuarmemb")
elif options.buildTarget == "linux":
pass
else:
print(f"Target {options.buildTarget} not supported")
queueCommand(f"source {paths['matterFolder']}/scripts/activate.sh")
#
# Toolchain update
#
if options.doUpdateToolchain:
if options.buildTarget == "esp32":
queuePrint("ESP32 toolchain update not supported. Skipping")
elif options.buildTarget == "nrfconnect":
queuePrint("Updating toolchain")
queueCommand(
f"cd {paths['matterFolder']} && python3 scripts/setup/nrfconnect/update_ncs.py --update")
elif options.buildTarget == "linux":
queuePrint("Linux toolchain update not supported. Skipping")
#
# ZAP bootstrapping
#
if options.doBootstrapZap:
if platform == "linux" or platform == "linux2":
queuePrint("Installing ZAP OS package dependencies")
queueCommand(f"sudo apt-get install sudo apt-get install node node-yargs npm libpixman-1-dev libcairo2-dev libpango1.0-dev node-pre-gyp libjpeg9-dev libgif-dev node-typescript")
if platform == "darwin":
queuePrint("Installation of ZAP OS packages not supported on MacOS")
if platform == "win32":
queuePrint(
"Installation of ZAP OS packages not supported on Windows")
queuePrint("Running NPM to install ZAP Node.JS dependencies")
queueCommand(
f"cd {paths['matterFolder']}/third_party/zap/repo/ && npm install")
#
# Cluster customization
#
if options.doRunGui:
queuePrint("Starting ZAP GUI editor")
queueCommand(f"cd {paths['rootSampleFolder']}/devices")
queueCommand(
f"{paths['matterFolder']}/scripts/tools/zap/run_zaptool.sh {options.sampleDeviceTypeName}.zap")
if options.doRunZap:
queuePrint("Running ZAP script to generate artifacts")
queueCommand(f"mkdir -p {paths['genFolder']}/")
queueCommand(f"rm {paths['genFolder']}/*")
queueCommand(
f"{paths['matterFolder']}/scripts/tools/zap/generate.py {paths['rootSampleFolder']}/devices/{options.sampleDeviceTypeName}.zap -o {paths['genFolder']}")
# af-gen-event.h is not generated
queueCommand(f"touch {paths['genFolder']}/af-gen-event.h")
#
# Menuconfig
#
if options.doMenuconfig:
if options.buildTarget == "esp32":
queueCommand(f"cd {paths['rootSampleFolder']}/esp32")
queueCommand("idf.py menuconfig")
elif options.buildTarget == "nrfconnect":
queueCommand(f"cd {paths['rootSampleFolder']}/nrfconnect")
queueCommand("west build -t menuconfig")
elif options.buildTarget == "linux":
queuePrint("Menuconfig not available on Linux target. Skipping")
#
# Build
#
if options.doBuild:
queuePrint("Building...")
if options.doRPC:
queuePrint("RPC PW enabled")
queueCommand(
f"export SDKCONFIG_DEFAULTS={paths['rootSampleFolder']}/esp32/sdkconfig_rpc.defaults")
else:
queuePrint("RPC PW disabled")
queueCommand(
f"export SDKCONFIG_DEFAULTS={paths['rootSampleFolder']}/esp32/sdkconfig.defaults")
options.vid = hexInputToInt(options.vid)
options.pid = hexInputToInt(options.pid)
queuePrint(
f"Product ID 0x{options.pid:02X} / Vendor ID 0x{options.vid:02X}")
queueCommand(f"cd {paths['rootSampleFolder']}")
if (options.buildTarget == "esp32") or (options.buildTarget == "nrfconnect"):
queueCommand(f'''
cat > project_include.cmake <<EOF
set(CONFIG_DEVICE_VENDOR_ID {options.vid})
set(CONFIG_DEVICE_PRODUCT_ID {options.pid})
set(CONFIG_ENABLE_PW_RPC {"1" if options.doRPC else "0"})
set(SAMPLE_NAME {options.sampleDeviceTypeName})
EOF
true''')
if options.buildTarget == "esp32":
queueCommand(f"cd {paths['rootSampleFolder']}/esp32")
if options.doClean:
queueCommand(f"rm {paths['rootSampleFolder']}/esp32/sdkconfig")
queueCommand(f"cd {paths['rootSampleFolder']}/esp32")
queueCommand(f"rm -rf {paths['rootSampleFolder']}/esp32/build")
queueCommand("idf.py fullclean")
queueCommand("idf.py build")
elif options.buildTarget == "nrfconnect":
queueCommand(f"cd {paths['rootSampleFolder']}/nrfconnect")
if options.doClean:
# queueCommand(f"rm -rf {paths['rootSampleFolder']}/nrfconnect/build")
queueCommand(f"west build -b nrf52840dk_nrf52840")
else:
queueCommand(f"west build -b nrf52840dk_nrf52840")
elif options.buildTarget == "linux":
queueCommand(f"cd {paths['rootSampleFolder']}/linux")
queueCommand(f'''
cat > args.gni <<EOF
import("//build_overrides/chip.gni")
import("\\${{chip_root}}/config/standalone/args.gni")
chip_shell_cmd_server = false
target_defines = ["CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID={options.vid}", "CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID={options.pid}", "CONFIG_ENABLE_PW_RPC={'1' if options.doRPC else '0'}"]
EOF
cat > sample.gni <<EOF
sample_zap_file = "{options.sampleDeviceTypeName}.zap"
sample_name = "{options.sampleDeviceTypeName}"
EOF
true''')
if options.doClean:
queueCommand(f"rm -rf out")
queueCommand("gn gen out")
queueCommand("ninja -C out")
#
# Compilation DB TODO
#
#
# Flash
#
if options.doFlash:
queuePrint("Flashing target")
if options.buildTarget == "esp32":
if config['esp32']['TTY'] is None:
print('The path for the serial enumeration for esp32 is not set. Make sure esp32.TTY is set on your config.yaml file')
exit(1)
queueCommand(f"cd {paths['rootSampleFolder']}/esp32")
if options.doErase:
queueCommand(
f"idf.py -p {config['esp32']['TTY']} erase-flash")
queueCommand(f"idf.py -p {config['esp32']['TTY']} flash")
elif options.buildTarget == "nrfconnect":
queueCommand(f"cd {paths['rootSampleFolder']}/nrfconnect")
if options.doErase:
queueCommand("rm -rf build")
else:
queueCommand("west flash")
#
# Terminal interaction
#
if options.doInteract:
queuePrint("Starting terminal...")
if options.buildTarget == "esp32":
if config['esp32']['TTY'] is None:
print('The path for the serial enumeration for esp32 is not set. Make sure esp32.TTY is set on your config.yaml file')
exit(1)
queueCommand(f"cd {paths['rootSampleFolder']}/esp32")
queueCommand(f"idf.py -p {config['esp32']['TTY']} monitor")
elif options.buildTarget == "nrfconnect":
if config['nrfconnect']['TTY'] is None:
print('The path for the serial enumeration for nordic is not set. Make sure nrfconnect.TTY is set on your config.yaml file')
exit(1)
queueCommand("killall screen")
queueCommand(f"screen {config['nrfconnect']['TTY']} 115200")
elif options.buildTarget == "linux":
queuePrint(
f"{paths['rootSampleFolder']}/linux/out/{options.sampleDeviceTypeName}")
queueCommand(
f"{paths['rootSampleFolder']}/linux/out/{options.sampleDeviceTypeName}")
#
# RPC Console
#
if options.doRPC_CONSOLE:
queueCommand(
f"python3 -m chip_rpc.console --device {config['esp32']['TTY']}")
queuePrint("Done")
execQueue()
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))