def includes(config): ret = [] for include in config.get(CONF_INCLUDES, []): path = CORE.relative_path(include) res = os.path.relpath(path, CORE.relative_build_path('src', 'main.cpp')) ret.append(u'#include "{}"'.format(res)) return ret
def setup_text_sensor(text_sensor_obj, mqtt_obj, config): sensor_var = Pvariable(config[CONF_ID], text_sensor_obj, has_side_effects=False) mqtt_var = Pvariable(config[CONF_MQTT_ID], mqtt_obj, has_side_effects=False) CORE.add_job(setup_text_sensor_core_, sensor_var, mqtt_var, config)
def setup_binary_sensor(binary_sensor_obj, mqtt_obj, config): binary_sensor_var = Pvariable(config[CONF_ID], binary_sensor_obj, has_side_effects=False) mqtt_var = Pvariable(config[CONF_MQTT_ID], mqtt_obj, has_side_effects=False) CORE.add_job(setup_binary_sensor_core_, binary_sensor_var, mqtt_var, config)
def variable( id, # type: ID rhs, # type: Expression type=None # type: MockObj ): # type: (...) -> MockObj rhs = safe_exp(rhs) obj = MockObj(id, u'.') id.type = type or id.type assignment = AssignmentExpression(id.type, '', id, rhs, obj) CORE.add(assignment) CORE.register_variable(id, obj) obj.requires.append(assignment) return obj
def write_cpp(config): _LOGGER.info("Generating C++ source...") CORE.add_job(core_config.to_code, config[CONF_ESPHOMEYAML], domain='esphomeyaml') for domain in PRE_INITIALIZE: if domain == CONF_ESPHOMEYAML or domain not in config: continue CORE.add_job(get_component(domain).to_code, config[domain], domain=domain) for domain, component, conf in iter_components(config): if domain in PRE_INITIALIZE or not hasattr(component, 'to_code'): continue CORE.add_job(component.to_code, conf, domain=domain) CORE.flush_tasks() add(RawStatement('')) add(RawStatement('')) all_code = [] for exp in CORE.expressions: if not config[CONF_ESPHOMEYAML][CONF_USE_CUSTOM_CODE]: if isinstance(exp, Expression) and not exp.required: continue all_code.append(text_type(statement(exp))) writer.write_platformio_project() code_s = indent('\n'.join(line.rstrip() for line in all_code)) writer.write_cpp(code_s) return 0
def update_esphomelib_repo(): if CONF_REPOSITORY not in CORE.esphomelib_version: return if CONF_BRANCH not in CORE.esphomelib_version: # Git commit hash or tag cannot be updated return esphomelib_path = CORE.relative_build_path('.piolibdeps', 'esphomelib') rc, _, _ = run_system_command('git', '-C', esphomelib_path, '--help') if rc != 0: # git not installed or repo not downloaded yet return rc, _, _ = run_system_command('git', '-C', esphomelib_path, 'diff-index', '--quiet', 'HEAD', '--') if rc != 0: # local changes, cannot update _LOGGER.warning("Local changes in esphomelib copy from git. Will not auto-update.") return _LOGGER.info("Updating esphomelib copy from git (%s)", esphomelib_path) rc, stdout, _ = run_system_command('git', '-c', 'color.ui=always', '-C', esphomelib_path, 'pull', '--stat') if rc != 0: _LOGGER.warning("Couldn't auto-update local git copy of esphomelib.") return if IS_PY3: stdout = stdout.decode('utf-8', 'backslashreplace') safe_print(stdout.strip())
def clean_build(): for directory in ('.piolibdeps', '.pioenvs'): dir_path = CORE.relative_build_path(directory) if not os.path.isdir(dir_path): continue _LOGGER.info("Deleting %s", dir_path) shutil.rmtree(dir_path)
def write_cpp(code_s): path = CORE.relative_build_path('src', 'main.cpp') if os.path.isfile(path): try: with codecs.open(path, 'r', encoding='utf-8') as f_handle: text = f_handle.read() except OSError: raise EsphomeyamlError(u"Could not read C++ file at {}".format(path)) prev_file = text 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: prev_file = None mkdir_p(os.path.dirname(path)) code_format = CPP_BASE_FORMAT include_s = get_include_text() full_file = code_format[0] + CPP_INCLUDE_BEGIN + u'\n' + include_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] if prev_file == full_file: return with codecs.open(path, 'w+', encoding='utf-8') as f_handle: f_handle.write(full_file)
def write_platformio_project(): mkdir_p(CORE.build_path) platformio_ini = CORE.relative_build_path('platformio.ini') content = get_ini_content() write_gitignore() write_platformio_ini(content, platformio_ini)
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() 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. esphomeyaml 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 process_lambda( value, # type: Lambda parameters, # type: List[Tuple[Expression, str]] capture='=', # type: str return_type=None # type: Optional[Expression] ): # type: (...) -> Generator[LambdaExpression] from esphomeyaml.components.globals import GlobalVariableComponent if value is None: yield return parts = value.parts[:] for i, id in enumerate(value.requires_ids): for full_id, var in CORE.get_variable_with_full_id(id): yield if full_id is not None and isinstance(full_id.type, MockObjClass) and \ full_id.type.inherits_from(GlobalVariableComponent): 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 generic_gpio_pin_expression_(conf, mock_obj, default_mode): if conf is None: return number = conf[CONF_NUMBER] inverted = conf.get(CONF_INVERTED) if CONF_PCF8574 in conf: from esphomeyaml.components import pcf8574 for hub in CORE.get_variable(conf[CONF_PCF8574]): yield None if default_mode == u'INPUT': mode = pcf8574.PCF8675_GPIO_MODES[conf.get(CONF_MODE, u'INPUT')] yield hub.make_input_pin(number, mode, inverted) return if default_mode == u'OUTPUT': yield hub.make_output_pin(number, inverted) return raise EsphomeyamlError(u"Unknown default mode {}".format(default_mode)) if len(conf) == 1: yield IntLiteral(number) return mode = RawExpression(conf.get(CONF_MODE, default_mode)) yield mock_obj(number, mode, inverted)
def validate_local_esphomelib_version(value): value = cv.directory(value) path = CORE.relative_path(value) library_json = os.path.join(path, 'library.json') if not os.path.exists(library_json): raise vol.Invalid(u"Could not find '{}' file. '{}' does not seem to point to an " u"esphomelib copy.".format(library_json, value)) return value
def write_platformio_project(): mkdir_p(CORE.build_path) platformio_ini = CORE.relative_build_path('platformio.ini') content = get_ini_content() if 'esp32_ble_beacon' in CORE.config or 'esp32_ble_tracker' in CORE.config: content += 'board_build.partitions = partitions.csv\n' 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") write_gitignore() write_platformio_ini(content, platformio_ini)
def Pvariable( id, # type: ID rhs, # type: Expression has_side_effects=True, # type: bool type=None # type: MockObj ): # type: (...) -> MockObj rhs = safe_exp(rhs) if not has_side_effects and hasattr(rhs, '_has_side_effects'): # pylint: disable=attribute-defined-outside-init, protected-access rhs._has_side_effects = False obj = MockObj(id, u'->', has_side_effects=has_side_effects) id.type = type or id.type assignment = AssignmentExpression(id.type, '*', id, rhs, obj) CORE.add(assignment) CORE.register_variable(id, obj) obj.requires.append(assignment) return obj
def directory(value): value = string(value) path = CORE.relative_path(value) if not os.path.exists(path): raise vol.Invalid( u"Could not find directory '{}'. Please make sure it exists.". format(path)) if not os.path.isdir(path): raise vol.Invalid(u"Path '{}' is not a directory.".format(path)) return value
def file_(value): value = string(value) path = CORE.relative_path(value) if not os.path.exists(path): raise vol.Invalid( u"Could not find file '{}'. Please make sure it exists.".format( path)) if not os.path.isfile(path): raise vol.Invalid(u"Path '{}' is not a file.".format(path)) return value
def symlink_esphomelib_version(esphomelib_version): lib_path = CORE.relative_build_path('lib') dst_path = CORE.relative_build_path('lib', 'esphomelib') if CORE.is_local_esphomelib_copy: src_path = CORE.relative_path(esphomelib_version[CONF_LOCAL]) do_write = True if os.path.islink(dst_path): old_path = os.path.join(os.readlink(dst_path), lib_path) if old_path != lib_path: os.unlink(dst_path) else: do_write = False if do_write: mkdir_p(lib_path) os.symlink(src_path, dst_path) else: # Remove symlink when changing back from local version if os.path.islink(dst_path): os.unlink(dst_path)
def get_ini_content(): lib_deps = gather_lib_deps() build_flags = gather_build_flags() if CORE.is_esp8266 and CORE.board in ESP8266_FLASH_SIZES: flash_size = ESP8266_FLASH_SIZES[CORE.board] ld_scripts = ESP8266_LD_SCRIPTS[flash_size] ld_script = None if CORE.arduino_version in ('[email protected]', '[email protected]', '[email protected]', '[email protected]'): ld_script = ld_scripts[0] elif CORE.arduino_version == ARDUINO_VERSION_ESP8266_DEV: ld_script = ld_scripts[1] if ld_script is not None: build_flags.append('-Wl,-T{}'.format(ld_script)) data = { 'platform': CORE.config[CONF_ESPHOMEYAML][CONF_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 'esp32_ble_beacon' in CORE.config or 'esp32_ble_tracker' in CORE.config: 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_ESPHOMEYAML]: flash_mode = CORE.config[CONF_ESPHOMEYAML][CONF_BOARD_FLASH_MODE] data['board_build.flash_mode'] = flash_mode data.update(CORE.config[CONF_ESPHOMEYAML].get(CONF_PLATFORMIO_OPTIONS, {})) content = u'[env:{}]\n'.format(CORE.name) content += format_ini(data) return content
def gather_lib_deps(): lib_deps = set() esphomelib_version = CORE.config[CONF_ESPHOMEYAML][CONF_ESPHOMELIB_VERSION] if CONF_REPOSITORY in esphomelib_version: repo = esphomelib_version[CONF_REPOSITORY] ref = next((esphomelib_version[x] for x in (CONF_COMMIT, CONF_BRANCH, CONF_TAG) if x in esphomelib_version), None) if CONF_TAG in esphomelib_version and repo == LIBRARY_URI_REPO: this_version = GITHUB_ARCHIVE_ZIP.format(ref) elif ref is not None: this_version = repo + '#' + ref lib_deps.add(this_version) elif CORE.is_local_esphomelib_copy: src_path = CORE.relative_path(esphomelib_version[CONF_LOCAL]) # Manually add lib_deps because platformio seems to ignore them inside libs/ library_json_path = os.path.join(src_path, 'library.json') with codecs.open(library_json_path, 'r', encoding='utf-8') as f_handle: library_json_text = f_handle.read() library_json = json.loads(library_json_text) for dep in library_json.get('dependencies', []): if 'version' in dep and VERSION_REGEX.match( dep['version']) is not None: lib_deps.add(dep['name'] + '@' + dep['version']) else: lib_deps.add(dep['version']) lib_deps.add('esphomelib') else: lib_deps.add(esphomelib_version) lib_deps |= get_build_flags('LIB_DEPS') lib_deps |= get_build_flags('lib_deps') if CORE.is_esp32: lib_deps |= { 'Preferences', # Preferences helper } # Manual fix for AsyncTCP if CORE.config[CONF_ESPHOMEYAML].get( CONF_ARDUINO_VERSION) == ARDUINO_VERSION_ESP32_DEV: lib_deps.add( 'https://github.com/me-no-dev/AsyncTCP.git#idf-update') lib_deps.discard('[email protected]') # avoid changing build flags order return list(sorted(x for x in lib_deps if x))
def preload_core_config(config): if CONF_ESPHOMEYAML not in config: raise EsphomeyamlError(u"No esphomeyaml section in config") core_conf = config[CONF_ESPHOMEYAML] if CONF_PLATFORM not in core_conf: raise EsphomeyamlError("esphomeyaml.platform not specified.") if CONF_BOARD not in core_conf: raise EsphomeyamlError("esphomeyaml.board not specified.") if CONF_NAME not in core_conf: raise EsphomeyamlError("esphomeyaml.name not specified.") try: CORE.esp_platform = validate_platform(core_conf[CONF_PLATFORM]) CORE.board = validate_board(core_conf[CONF_BOARD]) CORE.name = cv.valid_name(core_conf[CONF_NAME]) CORE.build_path = CORE.relative_path( cv.string(core_conf.get(CONF_BUILD_PATH, default_build_path()))) except vol.Invalid as e: raise EsphomeyamlError(text_type(e))
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.EsphomeyamlError( 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 raw_data = MockObj(config[CONF_RAW_DATA_ID]) add( RawExpression('static const uint8_t {}[{}] PROGMEM = {}'.format( raw_data, len(data), ArrayInitializer(*[HexInt(x) for x in data], multiline=False)))) glyphs = [] for glyph in config[CONF_GLYPHS]: glyphs.append(Glyph(glyph, raw_data, *glyph_args[glyph])) rhs = App.make_font(ArrayInitializer(*glyphs), ascent, ascent + descent) Pvariable(config[CONF_ID], rhs)
def to_code(config): from PIL import Image path = CORE.relative_path(config[CONF_FILE]) try: image = Image.open(path) except Exception as e: raise core.EsphomeyamlError(u"Could not load image file {}: {}".format( path, e)) if CONF_RESIZE in config: image.thumbnail(config[CONF_RESIZE]) image = image.convert('1', dither=Image.NONE) width, height = image.size if width > 500 or height > 500: _LOGGER.warning( "The image you requested is very big. Please consider using the resize " "parameter") width8 = ((width + 7) // 8) * 8 data = [0 for _ in range(height * width8 // 8)] for y in range(height): for x in range(width): if image.getpixel((x, y)): continue pos = x + y * width8 data[pos // 8] |= 0x80 >> (pos % 8) raw_data = MockObj(config[CONF_RAW_DATA_ID]) add( RawExpression('static const uint8_t {}[{}] PROGMEM = {}'.format( raw_data, len(data), ArrayInitializer(*[HexInt(x) for x in data], multiline=False)))) rhs = App.make_image(raw_data, width, height) Pvariable(config[CONF_ID], rhs)
def setup_time(time_var, config): CORE.add_job(setup_time_core_, time_var, config)
def storage_path(): # type: () -> str return CORE.relative_path('.esphomeyaml', '{}.json'.format(CORE.config_filename))
def setup_display(display_var, config): CORE.add_job(setup_display_core_, display_var, config)
def register_sensor(var, config): sensor_var = Pvariable(config[CONF_ID], var, has_side_effects=True) rhs = App.register_sensor(sensor_var) mqtt_var = Pvariable(config[CONF_MQTT_ID], rhs, has_side_effects=True) CORE.add_job(setup_sensor_core_, sensor_var, mqtt_var, config)
def setup_stepper(stepper_var, config): CORE.add_job(setup_stepper_core_, stepper_var, config)
def setup_light(light_obj, mqtt_obj, config): light_var = Pvariable(config[CONF_ID], light_obj, has_side_effects=False) mqtt_var = Pvariable(config[CONF_MQTT_ID], mqtt_obj, has_side_effects=False) CORE.add_job(setup_light_core_, light_var, mqtt_var, config)
def build_automation(trigger, arg_type, config): CORE.add_job(build_automation_, trigger, arg_type, config)