# encoding: utf-8 """ Waf tool for ESP32 build """ from waflib import Build, ConfigSet, Configure, Context, Task, Utils from waflib import Errors, Logs from waflib.TaskGen import before, after_method, before_method, feature from waflib.Configure import conf from collections import OrderedDict import os import shutil import sys import re import pickle import subprocess def configure(cfg): mcu_esp32s3 = True if (cfg.variant[0:7] == "esp32s3") else False target = "esp32s3" if mcu_esp32s3 else "esp32" bldnode = cfg.bldnode.make_node(cfg.variant) def srcpath(path): return cfg.srcnode.make_node(path).abspath() def bldpath(path): return bldnode.make_node(path).abspath() #Load cmake builder and make cfg.load('cmake') #define env and location for the cmake esp32 file env = cfg.env env.AP_HAL_ESP32 = srcpath('libraries/AP_HAL_ESP32/targets/'+target+'/esp-idf') env.AP_PROGRAM_FEATURES += ['esp32_ap_program'] env.ESP_IDF_PREFIX_REL = 'esp-idf' prefix_node = bldnode.make_node(env.ESP_IDF_PREFIX_REL) env.ESP32_TARGET = target env.BUILDROOT = bldpath('') env.SRCROOT = srcpath('') env.APJ_TOOL = srcpath('Tools/scripts/apj_tool.py') #Check if esp-idf env are loaded, or load it try: env.IDF = os.environ['IDF_PATH'] except: env.IDF = cfg.srcnode.abspath()+"/modules/esp_idf" print("USING EXPRESSIF IDF:"+str(env.IDF)) try: env.DEFAULT_PARAMETERS = os.environ['DEFAULT_PARAMETERS'] except: env.DEFAULT_PARAMETERS = cfg.srcnode.abspath()+"/libraries/AP_HAL_ESP32/boards/defaults.parm" print("USING DEFAULT_PARAMETERS:"+str(env.DEFAULT_PARAMETERS)) #env.append_value('GIT_SUBMODULES', 'esp_idf') # delete the output sdkconfig file when the input defaults changes. we take the # stamp as the output so we can compute the path to the sdkconfig, yet it # doesn't have to exist when we're done. class clean_sdkconfig(Task.Task): def keyword(self): return "delete sdkconfig generated from" def run(self): prefix = ".clean-stamp-" for out in self.outputs: if not out.name.startswith(prefix): raise ValueError("not a stamp file: "+out) dest = out.parent.abspath()+"/"+out.name[len(prefix):] if os.path.exists(dest): os.unlink(dest) # waf needs the output to exist after the task, so touch it open(out.abspath(), "w").close() def pre_build(self): """Configure esp-idf as lib target""" lib_vars = OrderedDict() lib_vars['ARDUPILOT_CMD'] = self.cmd lib_vars['WAF_BUILD_TARGET'] = self.targets lib_vars['ARDUPILOT_LIB'] = self.bldnode.find_or_declare('lib/').abspath() lib_vars['ARDUPILOT_BIN'] = self.bldnode.find_or_declare('lib/bin').abspath() target = self.env.ESP32_TARGET esp_idf = self.cmake( name='esp-idf', cmake_vars=lib_vars, cmake_src='libraries/AP_HAL_ESP32/targets/'+target+'/esp-idf', cmake_bld='esp-idf_build', ) esp_idf_showinc = esp_idf.build('showinc', target='esp-idf_build/includes.list') # task to delete the sdkconfig (thereby causing it to be regenerated) when # the .defaults changes. it uses a stamp to find the sdkconfig. changing # the sdkconfig WILL NOT cause it to be deleted as it's not an input. this # is by design so the user can tweak it for testing purposes. clean_sdkconfig_task = esp_idf_showinc.create_task("clean_sdkconfig", src=self.srcnode.find_or_declare(self.env.AP_HAL_ESP32+"/sdkconfig.defaults"), tgt=self.bldnode.find_or_declare("esp-idf_build/.clean-stamp-sdkconfig")) esp_idf_showinc.post() # ensure the sdkconfig will be deleted before the cmake configure occurs # that regenerates it esp_idf_showinc.cmake_config_task.set_run_after(clean_sdkconfig_task) from waflib import Task class load_generated_includes(Task.Task): """After includes.list generated include it in env""" always_run = True def run(tsk): bld = tsk.generator.bld includes = bld.bldnode.find_or_declare('esp-idf_build/includes.list').read().split() #print(includes) bld.env.prepend_value('INCLUDES', includes) tsk = load_generated_includes(env=self.env) tsk.set_inputs(self.path.find_resource('esp-idf_build/includes.list')) self.add_to_group(tsk) @feature('esp32_ap_program') @after_method('process_source') def esp32_firmware(self): self.link_task.always_run = True esp_idf = self.bld.cmake('esp-idf') build = esp_idf.build('all', target='esp-idf_build/ardupilot.bin') build.post() build.cmake_build_task.set_run_after(self.link_task) # tool that can update the default params in a .bin or .apj #self.default_params_task = self.create_task('set_default_parameters', # src='esp-idf_build/ardupilot.bin') #self.default_params_task.set_run_after(self.generate_bin_task) # optional upload is last if self.bld.options.upload: flasher = esp_idf.build('flash') flasher.post() class set_default_parameters(Task.Task): color='CYAN' always_run = True def keyword(self): return "setting default params" def run(self): # TODO: disabled this task outright as apjtool appears to destroy checksums and/or the esp32 partition table # TIP: if u do try this, afterwards, be sure to 'rm -rf build/esp32buzz/idf-plane/*.bin' and re-run waf return # (752) esp_image: Checksum failed. Calculated 0xd3 read 0xa3 # (752) boot: OTA app partition slot 0 is not bootable # (753) esp_image: image at 0x200000 has invalid magic byte # (759) boot_comm: mismatch chip ID, expected 0, found 65535 # (766) boot_comm: can't run on lower chip revision, expected 1, found 255 # (773) esp_image: image at 0x200000 has invalid SPI mode 255 # (779) esp_image: image at 0x200000 has invalid SPI size 15 # (786) boot: OTA app partition slot 1 is not bootable # (792) boot: No bootable app partitions in the partition table # skip task if nothing to do. if not self.env.DEFAULT_PARAMETERS: return default_parameters = self.env.get_flat('DEFAULT_PARAMETERS').replace("'", "") #print("apj defaults file:"+str(default_parameters)) _bin = str(self.inputs[0]) # paranoia check before and after apj_tool to see if file hash has changed... cmd = "shasum -b {0}".format( _bin ) result = subprocess.check_output(cmd, shell=True) prehash = str(result).split(' ')[0][2:] cmd = "{1} {2} --set-file {3}".format(self.env.SRCROOT, self.env.APJ_TOOL, _bin, default_parameters ) print(cmd) result = subprocess.check_output(cmd, shell=True) if not isinstance(result, str): result = result.decode() for i in str(result).split('\n'): print("\t"+i) # paranoia check before and after apj_tool to see if file hash has changed... cmd = "shasum -b {0}".format( _bin ) result = subprocess.check_output(cmd, shell=True) posthash = str(result).split(' ')[0][2:] # display --show output, helpful. cmd = "{1} {2} --show ".format(self.env.SRCROOT, self.env.APJ_TOOL, _bin ) print(cmd) result = subprocess.check_output(cmd, shell=True) if not isinstance(result, str): result = result.decode() for i in str(result).split('\n'): print("\t"+i) # were embedded params updated in .bin? if prehash == posthash: print("Embedded params in .bin unchanged (probably already up-to-date)") else: print("Embedded params in .bin UPDATED")