예제 #1
0
def read_config(command_line_substitutions):
    _LOGGER.info("Reading configuration %s...", CORE.config_path)
    try:
        res = load_config(command_line_substitutions)
    except EsphomeError as err:
        _LOGGER.error("Error while reading config: %s", err)
        return None
    if res.errors:
        if not CORE.verbose:
            res = strip_default_ids(res)

        safe_print(color(Fore.BOLD_RED, "Failed config"))
        safe_print("")
        for path, domain in res.output_paths:
            if not res.is_in_error_path(path):
                continue

            errstr = color(Fore.BOLD_RED, f"{domain}:")
            errline = line_info(res, path)
            if errline:
                errstr += " " + errline
            safe_print(errstr)
            safe_print(indent(dump_dict(res, path)[0]))
        return None
    return OrderedDict(res)
예제 #2
0
def choose_prompt(options):
    if not options:
        raise EsphomeError(
            "Found no valid options for upload/logging, please make sure relevant "
            "sections (ota, api, mqtt, ...) are in your configuration and/or the "
            "device is plugged in.")

    if len(options) == 1:
        return options[0][1]

    safe_print("Found multiple options, please choose one:")
    for i, (desc, _) in enumerate(options):
        safe_print(f"  [{i+1}] {desc}")

    while True:
        opt = input("(number): ")
        if opt in options:
            opt = options.index(opt)
            break
        try:
            opt = int(opt)
            if opt < 1 or opt > len(options):
                raise ValueError
            break
        except ValueError:
            safe_print(color(Fore.RED, f"Invalid option: '{opt}'"))
    return options[opt - 1][1]
예제 #3
0
def line_info(config, path, highlight=True):
    """Display line config source."""
    if not highlight:
        return None
    obj = config.get_deepest_document_range_for_path(path)
    if obj:
        mark = obj.start_mark
        source = "[source {}:{}]".format(mark.document, mark.line + 1)
        return color(Fore.CYAN, source)
    return "None"
예제 #4
0
 def on_log(msg):
     time_ = datetime.now().time().strftime("[%H:%M:%S]")
     text = msg.message
     if msg.send_failed:
         text = color(
             Fore.WHITE,
             "(Message skipped because it was too big to fit in "
             "TCP buffer - This is only cosmetic)",
         )
     safe_print(time_ + text)
예제 #5
0
def get_fingerprint(config):
    addr = str(config[CONF_MQTT][CONF_BROKER]), int(config[CONF_MQTT][CONF_PORT])
    _LOGGER.info("Getting fingerprint from %s:%s", addr[0], addr[1])
    try:
        cert_pem = ssl.get_server_certificate(addr)
    except OSError as err:
        _LOGGER.error("Unable to connect to server: %s", err)
        return 1
    cert_der = ssl.PEM_cert_to_DER_cert(cert_pem)

    sha1 = hashlib.sha1(cert_der).hexdigest()

    safe_print("SHA1 Fingerprint: " + color(Fore.CYAN, sha1))
    safe_print(
        "Copy the string above into mqtt.ssl_fingerprints section of {}"
        "".format(CORE.config_path)
    )
    return 0
예제 #6
0
def command_update_all(args):
    import click

    success = {}
    files = list_yaml_files(args.configuration[0])
    twidth = 60

    def print_bar(middle_text):
        middle_text = f" {middle_text} "
        width = len(click.unstyle(middle_text))
        half_line = "=" * ((twidth - width) // 2)
        click.echo(f"{half_line}{middle_text}{half_line}")

    for f in files:
        print("Updating {}".format(color(Fore.CYAN, f)))
        print("-" * twidth)
        print()
        rc = run_external_process("esphome", "--dashboard", "run", "--no-logs",
                                  "--device", "OTA", f)
        if rc == 0:
            print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
            success[f] = True
        else:
            print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f))
            success[f] = False

        print()
        print()
        print()

    print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY")))
    failed = 0
    for f in files:
        if success[f]:
            print("  - {}: {}".format(f, color(Fore.GREEN, "SUCCESS")))
        else:
            print("  - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED")))
            failed += 1
    return failed
예제 #7
0
def dump_dict(config, path, at_root=True):
    # type: (Config, ConfigPath, bool) -> Tuple[str, bool]
    conf = config.get_nested_item(path)
    ret = ""
    multiline = False

    if at_root:
        error = config.get_error_for_path(path)
        if error is not None:
            ret += ("\n" +
                    color(Fore.BOLD_RED, _format_vol_invalid(error, config)) +
                    "\n")

    if isinstance(conf, (list, tuple)):
        multiline = True
        if not conf:
            ret += "[]"
            multiline = False

        for i in range(len(conf)):
            path_ = path + [i]
            error = config.get_error_for_path(path_)
            if error is not None:
                ret += ("\n" + color(
                    Fore.BOLD_RED, _format_vol_invalid(error, config)) + "\n")

            sep = "- "
            if config.is_in_error_path(path_):
                sep = color(Fore.RED, sep)
            msg, _ = dump_dict(config, path_, at_root=False)
            msg = indent(msg)
            inf = line_info(config,
                            path_,
                            highlight=config.is_in_error_path(path_))
            if inf is not None:
                msg = inf + "\n" + msg
            elif msg:
                msg = msg[2:]
            ret += sep + msg + "\n"
    elif isinstance(conf, dict):
        multiline = True
        if not conf:
            ret += "{}"
            multiline = False

        for k in conf.keys():
            path_ = path + [k]
            error = config.get_error_for_path(path_)
            if error is not None:
                ret += ("\n" + color(
                    Fore.BOLD_RED, _format_vol_invalid(error, config)) + "\n")

            st = f"{k}: "
            if config.is_in_error_path(path_):
                st = color(Fore.RED, st)
            msg, m = dump_dict(config, path_, at_root=False)

            inf = line_info(config,
                            path_,
                            highlight=config.is_in_error_path(path_))
            if m:
                msg = "\n" + indent(msg)

            if inf is not None:
                if m:
                    msg = " " + inf + msg
                else:
                    msg = msg + " " + inf
            ret += st + msg + "\n"
    elif isinstance(conf, str):
        if is_secret(conf):
            conf = "!secret {}".format(is_secret(conf))
        if not conf:
            conf += "''"

        if len(conf) > 80:
            conf = "|-\n" + indent(conf)
        error = config.get_error_for_path(path)
        col = Fore.BOLD_RED if error else Fore.KEEP
        ret += color(col, str(conf))
    elif isinstance(conf, core.Lambda):
        if is_secret(conf):
            conf = "!secret {}".format(is_secret(conf))

        conf = "!lambda |-\n" + indent(str(conf.value))
        error = config.get_error_for_path(path)
        col = Fore.BOLD_RED if error else Fore.KEEP
        ret += color(col, conf)
    elif conf is None:
        pass
    else:
        error = config.get_error_for_path(path)
        col = Fore.BOLD_RED if error else Fore.KEEP
        ret += color(col, str(conf))
        multiline = "\n" in ret

    return ret, multiline
예제 #8
0
파일: wizard.py 프로젝트: ielbury/esphome
def wizard(path):
    if not path.endswith(".yaml") and not path.endswith(".yml"):
        safe_print(
            "Please make your configuration file {} have the extension .yaml or .yml"
            "".format(color(Fore.CYAN, path)))
        return 1
    if os.path.exists(path):
        safe_print(
            "Uh oh, it seems like {} already exists, please delete that file first "
            "or chose another configuration file.".format(
                color(Fore.CYAN, path)))
        return 2
    safe_print("Hi there!")
    sleep(1.5)
    safe_print("I'm the wizard of ESPHome :)")
    sleep(1.25)
    safe_print("And I'm here to help you get started with ESPHome.")
    sleep(2.0)
    safe_print(
        "In 4 steps I'm going to guide you through creating a basic "
        "configuration file for your custom ESP8266/ESP32 firmware. Yay!")
    sleep(3.0)
    safe_print()
    safe_print_step(1, CORE_BIG)
    safe_print("First up, please choose a " + color(Fore.GREEN, "name") +
               " for your node.")
    safe_print(
        "It should be a unique name that can be used to identify the device later."
    )
    sleep(1)
    safe_print(
        "For example, I like calling the node in my living room {}.".format(
            color(Fore.BOLD_WHITE, "livingroom")))
    safe_print()
    sleep(1)
    name = input(color(Fore.BOLD_WHITE, "(name): "))

    while True:
        try:
            name = cv.valid_name(name)
            break
        except vol.Invalid:
            safe_print(
                color(
                    Fore.RED,
                    f'Oh noes, "{name}" isn\'t a valid name. Names can only '
                    f"include numbers, lower-case letters and hyphens. ",
                ))
            name = strip_accents(name).lower().replace(" ", "-")
            name = strip_accents(name).lower().replace("_", "-")
            name = "".join(c for c in name if c in ALLOWED_NAME_CHARS)
            safe_print('Shall I use "{}" as the name instead?'.format(
                color(Fore.CYAN, name)))
            sleep(0.5)
            name = default_input("(name [{}]): ", name)

    safe_print('Great! Your node is now called "{}".'.format(
        color(Fore.CYAN, name)))
    sleep(1)
    safe_print_step(2, ESP_BIG)
    safe_print(
        "Now I'd like to know what microcontroller you're using so that I can compile "
        "firmwares for it.")
    safe_print("Are you using an " + color(Fore.GREEN, "ESP32") + " or " +
               color(Fore.GREEN, "ESP8266") +
               " platform? (Choose ESP8266 for Sonoff devices)")
    while True:
        sleep(0.5)
        safe_print()
        safe_print("Please enter either ESP32 or ESP8266.")
        platform = input(color(Fore.BOLD_WHITE, "(ESP32/ESP8266): "))
        try:
            platform = vol.All(vol.Upper, vol.Any("ESP32",
                                                  "ESP8266"))(platform)
            break
        except vol.Invalid:
            safe_print(
                "Unfortunately, I can't find an espressif microcontroller called "
                '"{}". Please try again.'.format(platform))
    safe_print("Thanks! You've chosen {} as your platform.".format(
        color(Fore.CYAN, platform)))
    safe_print()
    sleep(1)

    if platform == "ESP32":
        board_link = (
            "http://docs.platformio.org/en/latest/platforms/espressif32.html#boards"
        )
    else:
        board_link = (
            "http://docs.platformio.org/en/latest/platforms/espressif8266.html#boards"
        )

    safe_print("Next, I need to know what " + color(Fore.GREEN, "board") +
               " you're using.")
    sleep(0.5)
    safe_print("Please go to {} and choose a board.".format(
        color(Fore.GREEN, board_link)))
    if platform == "ESP32":
        safe_print("(Type " + color(Fore.GREEN, "esp01_1m") +
                   " for Sonoff devices)")
    safe_print()
    # Don't sleep because user needs to copy link
    if platform == "ESP32":
        safe_print('For example "{}".'.format(
            color(Fore.BOLD_WHITE, "nodemcu-32s")))
        boards = list(ESP32_BOARD_PINS.keys())
    else:
        safe_print('For example "{}".'.format(
            color(Fore.BOLD_WHITE, "nodemcuv2")))
        boards = list(ESP8266_BOARD_PINS.keys())
    safe_print("Options: {}".format(", ".join(sorted(boards))))

    while True:
        board = input(color(Fore.BOLD_WHITE, "(board): "))
        try:
            board = vol.All(vol.Lower, vol.Any(*boards))(board)
            break
        except vol.Invalid:
            safe_print(
                color(Fore.RED,
                      f'Sorry, I don\'t think the board "{board}" exists.'))
            safe_print()
            sleep(0.25)
            safe_print()

    safe_print("Way to go! You've chosen {} as your board.".format(
        color(Fore.CYAN, board)))
    safe_print()
    sleep(1)

    safe_print_step(3, WIFI_BIG)
    safe_print("In this step, I'm going to create the configuration for "
               "WiFi.")
    safe_print()
    sleep(1)
    safe_print("First, what's the " + color(Fore.GREEN, "SSID") +
               f" (the name) of the WiFi network {name} should connect to?")
    sleep(1.5)
    safe_print('For example "{}".'.format(
        color(Fore.BOLD_WHITE, "Abraham Linksys")))
    while True:
        ssid = input(color(Fore.BOLD_WHITE, "(ssid): "))
        try:
            ssid = cv.ssid(ssid)
            break
        except vol.Invalid:
            safe_print(
                color(
                    Fore.RED,
                    'Unfortunately, "{}" doesn\'t seem to be a valid SSID. '
                    "Please try again.".format(ssid),
                ))
            safe_print()
            sleep(1)

    safe_print('Thank you very much! You\'ve just chosen "{}" as your SSID.'
               "".format(color(Fore.CYAN, ssid)))
    safe_print()
    sleep(0.75)

    safe_print(
        "Now please state the " + color(Fore.GREEN, "password") +
        " of the WiFi network so that I can connect to it (Leave empty for no password)"
    )
    safe_print()
    safe_print('For example "{}"'.format(color(Fore.BOLD_WHITE, "PASSWORD42")))
    sleep(0.5)
    psk = input(color(Fore.BOLD_WHITE, "(PSK): "))
    safe_print(
        "Perfect! WiFi is now set up (you can create static IPs and so on later)."
    )
    sleep(1.5)

    safe_print_step(4, OTA_BIG)
    safe_print(
        "Almost there! ESPHome can automatically upload custom firmwares over WiFi "
        "(over the air) and integrates into Home Assistant with a native API.")
    safe_print(
        "This can be insecure if you do not trust the WiFi network. Do you want to set "
        "a " + color(Fore.GREEN, "password") + " for connecting to this ESP?")
    safe_print()
    sleep(0.25)
    safe_print("Press ENTER for no password")
    password = input(color(Fore.BOLD_WHITE, "(password): "))

    wizard_write(
        path=path,
        name=name,
        platform=platform,
        board=board,
        ssid=ssid,
        psk=psk,
        password=password,
    )

    safe_print()
    safe_print(
        color(Fore.CYAN, "DONE! I've now written a new configuration file to ")
        + color(Fore.BOLD_CYAN, path))
    safe_print()
    safe_print("Next steps:")
    safe_print(
        '  > Check your Home Assistant "integrations" screen. If all goes well, you '
        "should see your ESP being discovered automatically.")
    safe_print("  > Then follow the rest of the getting started guide:")
    safe_print(
        "  > https://esphome.io/guides/getting_started_command_line.html")
    return 0
예제 #9
0
파일: __main__.py 프로젝트: krahabb/esphome
def command_rename(args, config):
    for c in args.name:
        if c not in ALLOWED_NAME_CHARS:
            print(
                color(
                    Fore.BOLD_RED,
                    f"'{c}' is an invalid character for names. Valid characters are: "
                    f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)",
                ))
            return 1
    # Load existing yaml file
    with open(CORE.config_path, mode="r+", encoding="utf-8") as raw_file:
        raw_contents = raw_file.read()

    yaml = yaml_util.load_yaml(CORE.config_path)
    if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
        print(
            color(Fore.BOLD_RED,
                  "Complex YAML files cannot be automatically renamed."))
        return 1
    old_name = yaml[CONF_ESPHOME][CONF_NAME]
    match = re.match(r"^\$\{?([a-zA-Z0-9_]+)\}?$", old_name)
    if match is None:
        new_raw = re.sub(
            rf"name:\s+[\"']?{old_name}[\"']?",
            f'name: "{args.name}"',
            raw_contents,
        )
    else:
        old_name = yaml[CONF_SUBSTITUTIONS][match.group(1)]
        if (len(
                re.findall(
                    rf"^\s+{match.group(1)}:\s+[\"']?{old_name}[\"']?",
                    raw_contents,
                    flags=re.MULTILINE,
                )) > 1):
            print(
                color(Fore.BOLD_RED,
                      "Too many matches in YAML to safely rename"))
            return 1

        new_raw = re.sub(
            rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
            f'\\1: "{args.name}"',
            raw_contents,
            flags=re.MULTILINE,
        )

    new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
    print(
        f"Updating {color(Fore.CYAN, CORE.config_path)} to {color(Fore.CYAN, new_path)}"
    )
    print()

    with open(new_path, mode="w", encoding="utf-8") as new_file:
        new_file.write(new_raw)

    rc = run_external_process("esphome", "config", new_path)
    if rc != 0:
        print(color(Fore.BOLD_RED, "Rename failed. Reverting changes."))
        os.remove(new_path)
        return 1

    cli_args = [
        "run",
        new_path,
        "--no-logs",
        "--device",
        CORE.address,
    ]

    if args.dashboard:
        cli_args.insert(0, "--dashboard")

    try:
        rc = run_external_process("esphome", *cli_args)
    except KeyboardInterrupt:
        rc = 1
    if rc != 0:
        os.remove(new_path)
        return 1

    os.remove(CORE.config_path)

    print(color(Fore.BOLD_GREEN, "SUCCESS"))
    print()
    return 0