Example #1
0
def to_code(config):
    from PIL import Image

    for conf in config:
        path = relative_path(conf[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 conf:
            image.thumbnail(conf[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(conf[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(conf[CONF_ID], rhs)
Example #2
0
def run_platformio_cli_run(config, verbose, *args, **kwargs):
    build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH])
    command = ['run', '-d', build_path]
    if verbose:
        command += ['-v']
    command += list(args)
    return run_platformio_cli(*command, **kwargs)
Example #3
0
def compile_program(args, config):
    _LOGGER.info("Compiling app...")
    build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH])
    command = ['platformio', 'run', '-d', build_path]
    if args.verbose:
        command.append('-v')
    return run_platformio(*command)
Example #4
0
def update_esphomelib_repo(config):
    esphomelib_version = config[CONF_ESPHOMELIB_VERSION]
    if CONF_REPOSITORY not in esphomelib_version:
        return

    build_path = relative_path(config[CONF_BUILD_PATH])
    esphomelib_path = os.path.join(build_path, '.piolibdeps', 'esphomelib')
    is_default_branch = all(x not in esphomelib_version
                            for x in (CONF_BRANCH, CONF_TAG, CONF_COMMIT))
    if not (CONF_BRANCH in esphomelib_version or is_default_branch):
        # Git commit hash or tag cannot be updated
        return

    rc, _, _ = run_command('git', '-C', esphomelib_path, '--help')
    if rc != 0:
        # git not installed or repo not downloaded yet
        return
    rc, _, _ = run_command('git', '-C', esphomelib_path, 'diff-index',
                           '--quiet', 'HEAD', '--')
    if rc != 0:
        # local changes, cannot update
        _LOGGER.warn(
            "Local changes in esphomelib copy from git. Will not auto-update.")
        return
    rc, _, _ = run_command('git', '-C', esphomelib_path, 'pull')
    if rc != 0:
        _LOGGER.warn("Couldn't auto-update local git copy of esphomelib.")
        return
Example #5
0
def write_cpp(config):
    _LOGGER.info("Generating C++ source...")

    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
        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
        add_job(component.to_code, conf, domain=domain)

    flush_tasks()
    add(RawStatement(''))
    add(RawStatement(''))
    all_code = []
    for exp in _EXPRESSIONS:
        if not config[CONF_ESPHOMEYAML][CONF_USE_CUSTOM_CODE]:
            if isinstance(exp, Expression) and not exp.required:
                continue
            if isinstance(exp, AssignmentExpression) and not exp.obj.required:
                if not exp.has_side_effects():
                    continue
                exp = exp.rhs
        all_code.append(unicode(statement(exp)))

    build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH])
    writer.write_platformio_project(config, build_path)

    code_s = indent('\n'.join(line.rstrip() for line in all_code))
    cpp_path = os.path.join(build_path, 'src', 'main.cpp')
    writer.write_cpp(code_s, cpp_path)
    return 0
Example #6
0
def validate_local_esphomelib_version(value):
    value = cv.directory(value)
    path = 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
Example #7
0
def command_clean(args, config):
    build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH])
    try:
        writer.clean_build(build_path)
    except OSError as err:
        _LOGGER.error("Error deleting build files: %s", err)
        return 1
    _LOGGER.info("Done!")
    return 0
Example #8
0
def upload_using_esptool(config, port):
    import esptool

    build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH])
    path = os.path.join(build_path, '.pioenvs', core.NAME, 'firmware.bin')
    # pylint: disable=protected-access
    return run_platformio('esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
                          '--chip', 'esp8266', '--port', port, 'write_flash', '0x0',
                          path, main=esptool._main)
Example #9
0
def file_(value):
    value = string(value)
    path = helpers.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
Example #10
0
def directory(value):
    value = string(value)
    path = helpers.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
Example #11
0
def upload_program(config, args, port):
    build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH])

    # if upload is to a serial port use platformio, otherwise assume ota
    serial_port = port.startswith('/') or port.startswith('COM')
    if port != 'OTA' and serial_port:
        if core.ESP_PLATFORM == ESP_PLATFORM_ESP8266 and args.use_esptoolpy:
            return upload_using_esptool(config, port)
        command = [
            'platformio', 'run', '-d', build_path, '-t', 'upload',
            '--upload-port', port
        ]
        if args.verbose:
            command.append('-v')
        return run_platformio(*command)

    if 'ota' not in config:
        _LOGGER.error(
            "No serial port found and OTA not enabled. Can't upload!")
        return -1

    # If hostname/ip is explicitly provided as upload-port argument, use this instead of zeroconf
    # hostname. This is to support use cases where zeroconf (hostname.local) does not work.
    if port != 'OTA':
        host = port
    else:
        host = get_upload_host(config)

    from esphomeyaml.components import ota
    from esphomeyaml import espota2

    bin_file = os.path.join(build_path, '.pioenvs', core.NAME, 'firmware.bin')
    if args.host_port is not None:
        host_port = args.host_port
    else:
        host_port = int(
            os.getenv('ESPHOMEYAML_OTA_HOST_PORT',
                      random.randint(10000, 60000)))

    verbose = args.verbose
    remote_port = ota.get_port(config)
    password = ota.get_auth(config)

    res = espota2.run_ota(host, remote_port, password, bin_file)
    if res == 0:
        return res
    _LOGGER.warn("OTA v2 method failed. Trying with legacy OTA...")
    return espota2.run_legacy_ota(verbose, host_port, host, remote_port,
                                  password, bin_file)
Example #12
0
def upload_program(config, args, port):
    _LOGGER.info("Uploading binary...")
    build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH])

    # if upload is to a serial port use platformio, otherwise assume ota
    serial_port = port.startswith('/') or port.startswith('COM')
    if port != 'OTA' and serial_port:
        if core.ESP_PLATFORM == ESP_PLATFORM_ESP8266 and args.use_esptoolpy:
            return upload_using_esptool(config, port)
        command = [
            'platformio', 'run', '-d', build_path, '-t', 'upload',
            '--upload-port', port
        ]
        if args.verbose:
            command.append('-v')
        return run_platformio(*command)

    if 'ota' not in config:
        _LOGGER.error(
            "No serial port found and OTA not enabled. Can't upload!")
        return -1

    # If hostname/ip is explicitly provided as upload-port argument, use this instead of zeroconf
    # hostname. This is to support use cases where zeroconf (hostname.local) does not work.
    if port != 'OTA':
        host = port
    else:
        host = get_upload_host(config)

    from esphomeyaml.components import ota
    from esphomeyaml import espota

    bin_file = os.path.join(build_path, '.pioenvs', core.NAME, 'firmware.bin')
    if args.host_port is not None:
        host_port = args.host_port
    else:
        host_port = int(
            os.getenv('ESPHOMEYAML_OTA_HOST_PORT',
                      random.randint(10000, 60000)))
    espota_args = [
        'espota.py', '--debug', '--progress', '-i', host, '-p',
        str(ota.get_port(config)), '-f', bin_file, '-a',
        ota.get_auth(config), '-P',
        str(host_port)
    ]
    if args.verbose:
        espota_args.append('-d')
    return espota.main(espota_args)
Example #13
0
def to_code(config):
    from PIL import ImageFont

    for conf in config:
        path = relative_path(conf[CONF_FILE])
        try:
            font = ImageFont.truetype(path, conf[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 conf[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(conf[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 conf[CONF_GLYPHS]:
            glyphs.append(Glyph(glyph, raw_data, *glyph_args[glyph]))

        rhs = App.make_font(ArrayInitializer(*glyphs), ascent,
                            ascent + descent)
        Pvariable(conf[CONF_ID], rhs)
Example #14
0
    def get(self):
        if not self.is_authenticated():
            self.redirect('/login')
            return

        configuration = self.get_argument('configuration')
        config_file = os.path.join(CONFIG_DIR, configuration)
        core.CONFIG_PATH = config_file
        config = __main__.read_config(core.CONFIG_PATH)
        build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH])
        path = os.path.join(build_path, '.pioenvs', core.NAME, 'firmware.bin')
        self.set_header('Content-Type', 'application/octet-stream')
        self.set_header("Content-Disposition",
                        'attachment; filename="{}.bin"'.format(core.NAME))
        with open(path, 'rb') as f:
            while 1:
                data = f.read(16384)  # or some other nice-sized chunk
                if not data:
                    break
                self.write(data)
        self.finish()
Example #15
0
def get_ini_content(config, path):
    version_specific_settings = determine_platformio_version_settings()
    options = {
        u'env': config[CONF_ESPHOMEYAML][CONF_NAME],
        u'platform': config[CONF_ESPHOMEYAML][CONF_ARDUINO_VERSION],
        u'board': config[CONF_ESPHOMEYAML][CONF_BOARD],
        u'build_flags': u'',
        u'upload_speed': UPLOAD_SPEED_OVERRIDE.get(core.BOARD, 115200),
    }
    build_flags = set()
    if not config[CONF_ESPHOMEYAML][CONF_USE_CUSTOM_CODE]:
        build_flags |= get_build_flags(config, 'build_flags')
        build_flags |= get_build_flags(config, 'BUILD_FLAGS')
        build_flags.add(u"-DESPHOMEYAML_USE")
    build_flags |= get_build_flags(config, 'required_build_flags')
    build_flags |= get_build_flags(config, 'REQUIRED_BUILD_FLAGS')

    # avoid changing build flags order
    build_flags = sorted(list(build_flags))
    if build_flags:
        options[u'build_flags'] = u'\n    '.join(build_flags)

    lib_deps = set()

    lib_version = config[CONF_ESPHOMEYAML][CONF_ESPHOMELIB_VERSION]
    lib_path = os.path.join(path, 'lib')
    dst_path = os.path.join(lib_path, 'esphomelib')
    this_version = None
    if CONF_REPOSITORY in lib_version:
        tag = next((lib_version[x] for x in (CONF_COMMIT, CONF_BRANCH, CONF_TAG)
                    if x in lib_version), None)
        this_version = lib_version[CONF_REPOSITORY]
        if tag is not None:
            this_version += '#' + tag
        lib_deps.add(this_version)
        if os.path.islink(dst_path):
            os.unlink(dst_path)
    elif CONF_LOCAL in lib_version:
        this_version = lib_version[CONF_LOCAL]
        src_path = relative_path(this_version)
        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)

        # 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'])
    else:
        this_version = lib_version
        lib_deps.add(lib_version)

    version_file = os.path.join(path, '.esphomelib_version')
    version = None
    if os.path.isfile(version_file):
        with open(version_file, 'r') as ver_f:
            version = ver_f.read()

    if version != this_version:
        _LOGGER.info("Esphomelib version change detected. Cleaning build files...")
        try:
            clean_build(path)
        except OSError as err:
            _LOGGER.warn("Error deleting build files (%s)! Ignoring...", err)

        with open(version_file, 'w') as ver_f:
            ver_f.write(this_version)

    lib_deps |= get_build_flags(config, 'LIB_DEPS')
    lib_deps |= get_build_flags(config, 'lib_deps')
    if core.ESP_PLATFORM == ESP_PLATFORM_ESP32:
        lib_deps |= {
            'Preferences',  # Preferences helper
        }
        # Manual fix for AsyncTCP
        if config[CONF_ESPHOMEYAML].get(CONF_ARDUINO_VERSION) == ARDUINO_VERSION_ESP32_DEV:
            lib_deps.add('https://github.com/me-no-dev/AsyncTCP.git#idf-update')
    # avoid changing build flags order
    lib_deps = sorted(x for x in lib_deps if x)
    if lib_deps:
        options[u'lib_deps'] = u'\n    '.join(lib_deps)

    content = INI_CONTENT_FORMAT.format(**options)
    if CONF_BOARD_FLASH_MODE in config[CONF_ESPHOMEYAML]:
        flash_mode_key = version_specific_settings['flash_mode_key']
        flash_mode = config[CONF_ESPHOMEYAML][CONF_BOARD_FLASH_MODE]
        content += "{} = {}\n".format(flash_mode_key, flash_mode)
    return content