def _load_idedata(config): platformio_ini = Path(CORE.relative_build_path("platformio.ini")) temp_idedata = Path( CORE.relative_internal_path("idedata", f"{CORE.name}.json")) changed = False if not platformio_ini.is_file() or not temp_idedata.is_file(): changed = True elif platformio_ini.stat().st_mtime >= temp_idedata.stat().st_mtime: changed = True if not changed: try: return json.loads(temp_idedata.read_text(encoding="utf-8")) except ValueError: pass temp_idedata.parent.mkdir(exist_ok=True, parents=True) data = _run_idedata(config) temp_idedata.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") return data
async def to_code(config): cg.add_global(cg.global_ns.namespace("esphome").using) cg.add( cg.App.pre_setup( config[CONF_NAME], cg.RawExpression('__DATE__ ", " __TIME__'), config[CONF_NAME_ADD_MAC_SUFFIX], )) CORE.add_job(_add_automations, config) # Set LWIP build constants for ESP8266 if CORE.is_esp8266: CORE.add_job(_esp8266_add_lwip_type) cg.add_build_flag("-fno-exceptions") # Libraries for lib in config[CONF_LIBRARIES]: if "@" in lib: name, vers = lib.split("@", 1) cg.add_library(name, vers) elif "://" in lib: # Repository... if "=" in lib: name, repo = lib.split("=", 1) cg.add_library(name, None, repo) else: cg.add_library(None, None, lib) else: cg.add_library(lib, None) if CORE.is_esp8266: # Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when # out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make # new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of # a NULL pointer (so the stacktrace makes more sense), and for consistency with Arduino 3, # which always aborts if exceptions are disabled. # For cases where nullptrs can be handled, use nothrow: `new (std::nothrow) T;` cg.add_build_flag("-DNEW_OOM_ABORT") cg.add_build_flag("-Wno-unused-variable") cg.add_build_flag("-Wno-unused-but-set-variable") cg.add_build_flag("-Wno-sign-compare") if config.get(CONF_ESP8266_RESTORE_FROM_FLASH, False): cg.add_define("USE_ESP8266_PREFERENCES_FLASH") if config[CONF_INCLUDES]: CORE.add_job(add_includes, config[CONF_INCLUDES]) cg.add_define("ESPHOME_BOARD", CORE.board) if CONF_PROJECT in config: cg.add_define("ESPHOME_PROJECT_NAME", config[CONF_PROJECT][CONF_NAME]) cg.add_define("ESPHOME_PROJECT_VERSION", config[CONF_PROJECT][CONF_VERSION])
def variable( id, # type: ID rhs, # type: SafeExpType type=None # type: MockObj ): # type: (...) -> MockObj """Declare a new variable (not pointer type) in the code generation. :param id: The ID used to declare the variable. :param rhs: The expression to place on the right hand side of the assignment. :param type: Manually define a type for the variable, only use this when it's not possible to do so during config validation phase (for example because of template arguments). :returns The new variable as a MockObj. """ assert isinstance(id, ID) rhs = safe_exp(rhs) obj = MockObj(id, u'.') if type is not None: id.type = type assignment = AssignmentExpression(id.type, '', id, rhs, obj) CORE.add(assignment) CORE.register_variable(id, obj) return obj
def copy_src_tree(): source_files = {} for _, component, _ in iter_components(CORE.config): source_files.update(component.source_files) # Convert to list and sort source_files_l = list(source_files.items()) source_files_l.sort() # Build #include list for esphome.h include_l = [] for target, path in source_files_l: if os.path.splitext(path)[1] in HEADER_FILE_EXTENSIONS: include_l.append(f'#include "{target}"') include_l.append('') include_s = '\n'.join(include_l) source_files_copy = source_files.copy() source_files_copy.pop(DEFINES_H_TARGET) for path in walk_files(CORE.relative_src_path('esphome')): if os.path.splitext(path)[1] not in SOURCE_FILE_EXTENSIONS: # Not a source file, ignore continue # Transform path to target path name target = os.path.relpath(path, CORE.relative_src_path()).replace(os.path.sep, '/') if target in (DEFINES_H_TARGET, VERSION_H_TARGET): # Ignore defines.h, will be dealt with later continue if target not in source_files_copy: # Source file removed, delete target os.remove(path) else: src_path = source_files_copy.pop(target) copy_file_if_changed(src_path, path) # Now copy new files for target, src_path in source_files_copy.items(): dst_path = CORE.relative_src_path(*target.split('/')) copy_file_if_changed(src_path, dst_path) # Finally copy defines write_file_if_changed(CORE.relative_src_path('esphome', 'core', 'defines.h'), generate_defines_h()) write_file_if_changed(CORE.relative_src_path('esphome', 'README.txt'), ESPHOME_README_TXT) write_file_if_changed(CORE.relative_src_path('esphome.h'), ESPHOME_H_FORMAT.format(include_s)) write_file_if_changed(CORE.relative_src_path('esphome', 'core', 'version.h'), VERSION_H_FORMAT.format(__version__))
def preload_core_config(config): core_key = 'esphome' if 'esphomeyaml' in config: _LOGGER.warning("The esphomeyaml section has been renamed to esphome in 1.11.0. " "Please replace 'esphomeyaml:' in your configuration with 'esphome:'.") config[CONF_ESPHOME] = config.pop('esphomeyaml') core_key = 'esphomeyaml' if CONF_ESPHOME not in config: raise cv.RequiredFieldInvalid("required key not provided", CONF_ESPHOME) with cv.prepend_path(core_key): out = PRELOAD_CONFIG_SCHEMA(config[CONF_ESPHOME]) CORE.name = out[CONF_NAME] CORE.esp_platform = out[CONF_PLATFORM] with cv.prepend_path(core_key): out2 = PRELOAD_CONFIG_SCHEMA2(config[CONF_ESPHOME]) CORE.board = out2[CONF_BOARD] CORE.build_path = CORE.relative_config_path(out2[CONF_BUILD_PATH])
def write_cpp(code_s): path = CORE.relative_src_path('main.cpp') if os.path.isfile(path): text = read_file(path) code_format = find_begin_end(text, CPP_AUTO_GENERATE_BEGIN, CPP_AUTO_GENERATE_END) code_format_ = find_begin_end(code_format[0], CPP_INCLUDE_BEGIN, CPP_INCLUDE_END) code_format = (code_format_[0], code_format_[1], code_format[1]) else: code_format = CPP_BASE_FORMAT copy_src_tree() global_s = u'#include "esphome.h"\n' global_s += CORE.cpp_global_section full_file = code_format[0] + CPP_INCLUDE_BEGIN + u'\n' + global_s + CPP_INCLUDE_END full_file += code_format[1] + CPP_AUTO_GENERATE_BEGIN + u'\n' + code_s + CPP_AUTO_GENERATE_END full_file += code_format[2] write_file_if_changed(full_file, path)
def migrate_src_version_0_to_1(): main_cpp = CORE.relative_build_path("src", "main.cpp") if not os.path.isfile(main_cpp): return content = read_file(main_cpp) if CPP_INCLUDE_BEGIN in content: return content, count = replace_file_content(content, r"\s*delay\((?:16|20)\);", "") if count != 0: _LOGGER.info("Migration: Removed %s occurrence of 'delay(16);' in %s", count, main_cpp) content, count = replace_file_content(content, r"using namespace esphomelib;", "") if count != 0: _LOGGER.info( "Migration: Removed %s occurrence of 'using namespace esphomelib;' " "in %s", count, main_cpp, ) if CPP_INCLUDE_BEGIN not in content: content, count = replace_file_content( content, r'#include "esphomelib/application.h"', CPP_INCLUDE_BEGIN + "\n" + CPP_INCLUDE_END, ) if count == 0: _LOGGER.error( "Migration failed. ESPHome 1.10.0 needs to have a new auto-generated " "include section in the %s file. Please remove %s and let it be " "auto-generated again.", main_cpp, main_cpp, ) _LOGGER.info("Migration: Added include section to %s", main_cpp) write_file_if_changed(main_cpp, content)
def process_lambda( value: Lambda, parameters: List[Tuple[SafeExpType, str]], capture: str = "=", return_type: SafeExpType = None, ) -> Generator[LambdaExpression, None, None]: """Process the given lambda value into a LambdaExpression. This is a coroutine because lambdas can depend on other IDs, you need to await it with 'yield'! :param value: The lambda to process. :param parameters: The parameters to pass to the Lambda, list of tuples :param capture: The capture expression for the lambda, usually ''. :param return_type: The return type of the lambda. :return: The generated lambda expression. """ from esphome.components.globals import GlobalsComponent if value is None: yield return parts = value.parts[:] for i, id in enumerate(value.requires_ids): full_id, var = yield CORE.get_variable_with_full_id(id) if (full_id is not None and isinstance(full_id.type, MockObjClass) and full_id.type.inherits_from(GlobalsComponent)): parts[i * 3 + 1] = var.value() continue if parts[i * 3 + 2] == ".": parts[i * 3 + 1] = var._ else: parts[i * 3 + 1] = var parts[i * 3 + 2] = "" if isinstance(value, ESPHomeDataBase) and value.esp_range is not None: location = value.esp_range.start_mark location.line += value.content_offset else: location = None yield LambdaExpression(parts, parameters, capture, return_type, location)
def migrate_src_version_0_to_1(): main_cpp = CORE.relative_build_path('src', 'main.cpp') if not os.path.isfile(main_cpp): return with codecs.open(main_cpp, 'r', encoding='utf-8') as f_handle: content = orig_content = f_handle.read() if CPP_INCLUDE_BEGIN in content: return content, count = replace_file_content(content, r'\s*delay\((?:16|20)\);', '') if count != 0: _LOGGER.info("Migration: Removed %s occurrence of 'delay(16);' in %s", count, main_cpp) content, count = replace_file_content(content, r'using namespace esphomelib;', '') if count != 0: _LOGGER.info( "Migration: Removed %s occurrence of 'using namespace esphomelib;' " "in %s", count, main_cpp) if CPP_INCLUDE_BEGIN not in content: content, count = replace_file_content( content, r'#include "esphomelib/application.h"', CPP_INCLUDE_BEGIN + u'\n' + CPP_INCLUDE_END) if count == 0: _LOGGER.error( "Migration failed. ESPHome 1.10.0 needs to have a new auto-generated " "include section in the %s file. Please remove %s and let it be " "auto-generated again.", main_cpp, main_cpp) _LOGGER.info("Migration: Added include section to %s", main_cpp) if orig_content == content: return with codecs.open(main_cpp, 'w', encoding='utf-8') as f_handle: f_handle.write(content)
def get_ini_content(): lib_deps = gather_lib_deps() build_flags = gather_build_flags() data = { 'platform': CORE.arduino_version, 'board': CORE.board, 'framework': 'arduino', 'lib_deps': lib_deps + ['${common.lib_deps}'], 'build_flags': build_flags + ['${common.build_flags}'], 'upload_speed': UPLOAD_SPEED_OVERRIDE.get(CORE.board, 115200), } if CORE.is_esp32: data['board_build.partitions'] = "partitions.csv" partitions_csv = CORE.relative_build_path('partitions.csv') if not os.path.isfile(partitions_csv): with open(partitions_csv, "w") as f: f.write("nvs, data, nvs, 0x009000, 0x005000,\n") f.write("otadata, data, ota, 0x00e000, 0x002000,\n") f.write("app0, app, ota_0, 0x010000, 0x190000,\n") f.write("app1, app, ota_1, 0x200000, 0x190000,\n") f.write("eeprom, data, 0x99, 0x390000, 0x001000,\n") f.write("spiffs, data, spiffs, 0x391000, 0x00F000\n") if CONF_BOARD_FLASH_MODE in CORE.config[CONF_ESPHOME]: flash_mode = CORE.config[CONF_ESPHOME][CONF_BOARD_FLASH_MODE] data['board_build.flash_mode'] = flash_mode # Ignore libraries that are not explicitly used, but may # be added by LDF # data['lib_ldf_mode'] = 'chain' data.update(CORE.config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS, {})) content = u'[env:{}]\n'.format(CORE.name) content += format_ini(data) return content
def to_code(config): from PIL import ImageFont path = CORE.relative_path(config[CONF_FILE]) try: font = ImageFont.truetype(path, config[CONF_SIZE]) except Exception as e: raise core.EsphomeError(u"Could not load truetype file {}: {}".format( path, e)) ascent, descent = font.getmetrics() glyph_args = {} data = [] for glyph in config[CONF_GLYPHS]: mask = font.getmask(glyph, mode='1') _, (offset_x, offset_y) = font.font.getsize(glyph) width, height = mask.size width8 = ((width + 7) // 8) * 8 glyph_data = [0 for _ in range(height * width8 // 8)] # noqa: F812 for y in range(height): for x in range(width): if not mask.getpixel((x, y)): continue pos = x + y * width8 glyph_data[pos // 8] |= 0x80 >> (pos % 8) glyph_args[glyph] = (len(data), offset_x, offset_y, width, height) data += glyph_data rhs = safe_exp([HexInt(x) for x in data]) prog_arr = progmem_array(config[CONF_RAW_DATA_ID], rhs) glyphs = [] for glyph in config[CONF_GLYPHS]: glyphs.append(Glyph(glyph, prog_arr, *glyph_args[glyph])) rhs = App.make_font(glyphs, ascent, ascent + descent) Pvariable(config[CONF_ID], rhs)
def process_lambda( value, # type: Lambda parameters, # type: List[Tuple[SafeExpType, str]] capture='=', # type: str return_type=None # type: Optional[SafeExpType] ): # type: (...) -> Generator[LambdaExpression] """Process the given lambda value into a LambdaExpression. This is a coroutine because lambdas can depend on other IDs, you need to await it with 'yield'! :param value: The lambda to process. :param parameters: The parameters to pass to the Lambda, list of tuples :param capture: The capture expression for the lambda, usually ''. :param return_type: The return type of the lambda. :return: The generated lambda expression. """ from esphome.components.globals import GlobalsComponent if value is None: yield return parts = value.parts[:] for i, id in enumerate(value.requires_ids): full_id, var = yield CORE.get_variable_with_full_id(id) if full_id is not None and isinstance(full_id.type, MockObjClass) and \ full_id.type.inherits_from(GlobalsComponent): parts[i * 3 + 1] = var.value() continue if parts[i * 3 + 2] == '.': parts[i * 3 + 1] = var._ else: parts[i * 3 + 1] = var parts[i * 3 + 2] = '' yield LambdaExpression(parts, parameters, capture, return_type)
def file_(value): import json value = string(value) path = CORE.relative_config_path(value) if CORE.vscode and ( not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path) ): print( json.dumps( { "type": "check_file_exists", "path": path, } ) ) data = json.loads(input()) assert data["type"] == "file_exists_response" if data["content"]: return value raise Invalid( "Could not find file '{}'. Please make sure it exists (full path: {})." "".format(path, os.path.abspath(path)) ) if not os.path.exists(path): raise Invalid( "Could not find file '{}'. Please make sure it exists (full path: {})." "".format(path, os.path.abspath(path)) ) if not os.path.isfile(path): raise Invalid( "Path '{}' is not a file (full path: {})." "".format(path, os.path.abspath(path)) ) return value
def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": """Declare a new pointer variable in the code generation. :param id_: The ID used to declare the variable. :param rhs: The expression to place on the right hand side of the assignment. :param type_: Manually define a type for the variable, only use this when it's not possible to do so during config validation phase (for example because of template arguments). :returns The new variable as a MockObj. """ rhs = safe_exp(rhs) obj = MockObj(id_, "->") if type_ is not None: id_.type = type_ decl = VariableDeclarationExpression(id_.type, "*", id_) CORE.add_global(decl) assignment = AssignmentExpression(None, None, id_, rhs, obj) CORE.add(assignment) CORE.register_variable(id_, obj) return obj
def to_code(config): cg.add_global(cg.global_ns.namespace("esphome").using) cg.add( cg.App.pre_setup( config[CONF_NAME], cg.RawExpression('__DATE__ ", " __TIME__'), config[CONF_NAME_ADD_MAC_SUFFIX], ) ) CORE.add_job(_add_automations, config) # Set LWIP build constants for ESP8266 if CORE.is_esp8266: CORE.add_job(_esp8266_add_lwip_type) cg.add_build_flag("-fno-exceptions") # Libraries if CORE.is_esp32: cg.add_library("ESPmDNS", None) elif CORE.is_esp8266: cg.add_library("ESP8266WiFi", None) cg.add_library("ESP8266mDNS", None) for lib in config[CONF_LIBRARIES]: if "@" in lib: name, vers = lib.split("@", 1) cg.add_library(name, vers) else: cg.add_library(lib, None) cg.add_build_flag("-Wno-unused-variable") cg.add_build_flag("-Wno-unused-but-set-variable") cg.add_build_flag("-Wno-sign-compare") if config.get(CONF_ESP8266_RESTORE_FROM_FLASH, False): cg.add_define("USE_ESP8266_PREFERENCES_FLASH") if config[CONF_INCLUDES]: CORE.add_job(add_includes, config[CONF_INCLUDES])
async def register_text_sensor(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) cg.add(cg.App.register_text_sensor(var)) await setup_text_sensor_core_(var, config)
def register_binary_sensor(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) cg.add(cg.App.register_binary_sensor(var)) yield setup_binary_sensor_core_(var, config)
def register_fan(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) cg.add(cg.App.register_fan(var)) yield cg.register_component(var, config) yield setup_fan_core_(var, config)
async def register_canbus(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.new_Pvariable(config[CONF_ID], var) await setup_canbus_core_(var, config)
def register_text_sensor(var, config): if not CORE.has_id(config[CONF_ID]): var = Pvariable(config[CONF_ID], var, has_side_effects=True) add(App.register_text_sensor(var)) CORE.add_job(setup_text_sensor_core_, var, config)
def setup_text_sensor(text_sensor_obj, config): if not CORE.has_id(config[CONF_ID]): text_sensor_obj = Pvariable(config[CONF_ID], text_sensor_obj, has_side_effects=True) CORE.add_job(setup_text_sensor_core_, text_sensor_obj, config)
def storage_path(): # type: () -> str return CORE.relative_path('.esphome', '{}.json'.format(CORE.config_filename))
async def register_stepper(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) await setup_stepper_core_(var, config)
def write_gitignore(): path = CORE.relative_config_path(".gitignore") if not os.path.isfile(path): with open(path, "w") as f: f.write(GITIGNORE_CONTENT)
async def register_select(var, config, *, options: List[str]): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) cg.add(cg.App.register_select(var)) await setup_select_core_(var, config, options=options)
def setup_component(obj, config): if CONF_SETUP_PRIORITY in config: CORE.add(obj.set_setup_priority(config[CONF_SETUP_PRIORITY]))
def register_output(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) yield setup_output_platform_(var, config)
def setup_time(time_var, config): CORE.add_job(setup_time_core_, time_var, config)
def read_relative_config_path(value): return Path(CORE.relative_config_path(value)).read_text()
async def register_button(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) cg.add(cg.App.register_button(var)) await setup_button_core_(var, config)