コード例 #1
0
def run_esphome(argv):
    args = parse_args(argv)
    CORE.dashboard = args.dashboard

    setup_log(args.verbose, args.quiet)
    if args.deprecated_argv_suggestion is not None and args.command != "vscode":
        _LOGGER.warning(
            "Calling ESPHome with the configuration before the command is deprecated "
            "and will be removed in the future. ")
        _LOGGER.warning("Please instead use:")
        _LOGGER.warning("   esphome %s",
                        " ".join(args.deprecated_argv_suggestion[1:]))

    if sys.version_info < (3, 7, 0):
        _LOGGER.error(
            "You're running ESPHome with Python <3.7. ESPHome is no longer compatible "
            "with this Python version. Please reinstall ESPHome with Python 3.7+"
        )
        return 1

    if args.command in PRE_CONFIG_ACTIONS:
        try:
            return PRE_CONFIG_ACTIONS[args.command](args)
        except EsphomeError as e:
            _LOGGER.error(e, exc_info=args.verbose)
            return 1

    for conf_path in args.configuration:
        CORE.config_path = conf_path
        CORE.dashboard = args.dashboard

        config = read_config(
            dict(args.substitution) if args.substitution else {})
        if config is None:
            return 1
        CORE.config = config

        if args.command not in POST_CONFIG_ACTIONS:
            safe_print(f"Unknown command {args.command}")

        try:
            rc = POST_CONFIG_ACTIONS[args.command](args, config)
        except EsphomeError as e:
            _LOGGER.error(e, exc_info=args.verbose)
            return 1
        if rc != 0:
            return rc

        CORE.reset()
    return 0
コード例 #2
0
def run_esphome(argv):
    args = parse_args(argv)
    CORE.dashboard = args.dashboard

    setup_log(args.verbose, args.quiet)
    if args.command != "version" and not args.configuration:
        _LOGGER.error("Missing configuration parameter, see esphome --help.")
        return 1

    if sys.version_info < (3, 7, 0):
        _LOGGER.error(
            "You're running ESPHome with Python <3.7. ESPHome is no longer compatible "
            "with this Python version. Please reinstall ESPHome with Python 3.7+"
        )
        return 1

    if args.command in PRE_CONFIG_ACTIONS:
        try:
            return PRE_CONFIG_ACTIONS[args.command](args)
        except EsphomeError as e:
            _LOGGER.error(e)
            return 1

    for conf_path in args.configuration:
        CORE.config_path = conf_path
        CORE.dashboard = args.dashboard

        # Added so that the creds generator for TecnoCore knows if it should sanitize passwords or not.
        CORE.command = args.command

        config = read_config(
            dict(args.substitution) if args.substitution else {})

        if config is None:
            return 1
        CORE.config = config

        if args.command not in POST_CONFIG_ACTIONS:
            safe_print(f"Unknown command {args.command}")

        try:
            rc = POST_CONFIG_ACTIONS[args.command](args, config)
        except EsphomeError as e:
            _LOGGER.error(e)
            return 1
        if rc != 0:
            return rc

        CORE.reset()
    return 0
コード例 #3
0
ファイル: config.py プロジェクト: bleeisme/esphome
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("bold_red", "Failed config"))
        safe_print("")
        for path, domain in res.output_paths:
            if not res.is_in_error_path(path):
                continue

            errstr = color("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)
コード例 #4
0
ファイル: __main__.py プロジェクト: zinefer/esphome
def run_esphome(argv):
    args = parse_args(argv)
    CORE.dashboard = args.dashboard

    setup_log(args.verbose, args.quiet)
    if args.command != 'version' and not args.configuration:
        _LOGGER.error("Missing configuration parameter, see esphome --help.")
        return 1

    if IS_PY2:
        _LOGGER.warning(
            "You're using ESPHome with python 2. Support for python 2 is deprecated "
            "and will be removed in 1.15.0. Please reinstall ESPHome with python 3.6 "
            "or higher.")
    elif IS_PY3 and sys.version_info < (3, 6, 0):
        _LOGGER.warning(
            "You're using ESPHome with python 3.5. Support for python 3.5 is "
            "deprecated and will be removed in 1.15.0. Please reinstall ESPHome with "
            "python 3.6 or higher.")

    if args.command in PRE_CONFIG_ACTIONS:
        try:
            return PRE_CONFIG_ACTIONS[args.command](args)
        except EsphomeError as e:
            _LOGGER.error(e)
            return 1

    for conf_path in args.configuration:
        CORE.config_path = conf_path
        CORE.dashboard = args.dashboard

        config = read_config()
        if config is None:
            return 1
        CORE.config = config

        if args.command not in POST_CONFIG_ACTIONS:
            safe_print(u"Unknown command {}".format(args.command))

        try:
            rc = POST_CONFIG_ACTIONS[args.command](args, config)
        except EsphomeError as e:
            _LOGGER.error(e)
            return 1
        if rc != 0:
            return rc

        CORE.reset()
    return 0
コード例 #5
0
def get_fingerprint(config):
    addr = config[CONF_MQTT][CONF_BROKER], 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 IOError 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(u"SHA1 Fingerprint: " + color('cyan', sha1))
    safe_print(u"Copy the string above into mqtt.ssl_fingerprints section of {}"
               u"".format(CORE.config_path))
    return 0
コード例 #6
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(f"SHA1 Fingerprint: {color(Fore.CYAN, sha1)}")
    safe_print(
        f"Copy the string above into mqtt.ssl_fingerprints section of {CORE.config_path}"
    )
    return 0
コード例 #7
0
def run_esphome(argv):
    args = parse_args(argv)
    CORE.dashboard = args.dashboard

    setup_log(args.verbose, args.quiet)
    if args.command != 'version' and not args.configuration:
        _LOGGER.error("Missing configuration parameter, see esphome --help.")
        return 1

    if sys.version_info < (3, 6, 0):
        _LOGGER.error(
            "You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
            "with this Python version. Please reinstall ESPHome with Python 3.6+"
        )
        return 1

    if args.command in PRE_CONFIG_ACTIONS:
        try:
            return PRE_CONFIG_ACTIONS[args.command](args)
        except EsphomeError as e:
            _LOGGER.error(e)
            return 1

    for conf_path in args.configuration:
        CORE.config_path = conf_path
        CORE.dashboard = args.dashboard

        config = read_config()
        if config is None:
            return 1
        CORE.config = config

        if args.command not in POST_CONFIG_ACTIONS:
            safe_print(f"Unknown command {args.command}")

        try:
            rc = POST_CONFIG_ACTIONS[args.command](args, config)
        except EsphomeError as e:
            _LOGGER.error(e)
            return 1
        if rc != 0:
            return rc

        CORE.reset()
    return 0
コード例 #8
0
def run_miniterm(config, port):
    import serial
    from esphome import platformio_api

    if CONF_LOGGER not in config:
        _LOGGER.info("Logger is not enabled. Not starting UART logs.")
        return
    baud_rate = config["logger"][CONF_BAUD_RATE]
    if baud_rate == 0:
        _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
        return
    _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)

    backtrace_state = False
    ser = serial.Serial()
    ser.baudrate = baud_rate
    ser.port = port

    # We can't set to False by default since it leads to toggling and hence
    # ESP32 resets on some platforms.
    if config["logger"][CONF_DEASSERT_RTS_DTR]:
        ser.dtr = False
        ser.rts = False

    with ser:
        while True:
            try:
                raw = ser.readline()
            except serial.SerialException:
                _LOGGER.error("Serial port closed!")
                return
            line = (
                raw.replace(b"\r", b"")
                .replace(b"\n", b"")
                .decode("utf8", "backslashreplace")
            )
            time = datetime.now().time().strftime("[%H:%M:%S]")
            message = time + line
            safe_print(message)

            backtrace_state = platformio_api.process_stacktrace(
                config, line, backtrace_state=backtrace_state
            )
コード例 #9
0
ファイル: __main__.py プロジェクト: zinefer/esphome
def choose_prompt(options):
    if not options:
        raise EsphomeError(
            "Found no valid options for upload/logging, please make sure relevant "
            "sections (ota, mqtt, ...) are in your configuration and/or the device "
            "is plugged in.")

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

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

    while True:
        opt = safe_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('red', u"Invalid option: '{}'".format(opt)))
    return options[opt - 1][1]
コード例 #10
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]
コード例 #11
0
def run_esphome(argv):
    args = parse_args(argv)
    CORE.dashboard = args.dashboard

    setup_log(args.verbose, args.quiet)
    if args.command != 'version' and not args.configuration:
        _LOGGER.error("Missing configuration parameter, see esphome --help.")
        return 1

    if args.command in PRE_CONFIG_ACTIONS:
        try:
            return PRE_CONFIG_ACTIONS[args.command](args)
        except EsphomeError as e:
            _LOGGER.error(e)
            return 1

    for conf_path in args.configuration:
        CORE.config_path = conf_path
        CORE.dashboard = args.dashboard

        config = read_config(args.verbose)
        if config is None:
            return 1
        CORE.config = config

        if args.command not in POST_CONFIG_ACTIONS:
            safe_print(u"Unknown command {}".format(args.command))

        try:
            rc = POST_CONFIG_ACTIONS[args.command](args, config)
        except EsphomeError as e:
            _LOGGER.error(e)
            return 1
        if rc != 0:
            return rc

        CORE.reset()
    return 0
コード例 #12
0
ファイル: __main__.py プロジェクト: lanbing8023/esphome-tools
def command_hass_config(args, config):
    from esphome.components import mqtt as mqtt_component

    _LOGGER.info(
        "This is what you should put in your Home Assistant YAML configuration."
    )
    _LOGGER.info(
        "Please note this is only necessary if you're not using MQTT discovery."
    )
    data = mqtt_component.GenerateHassConfigData(config)
    hass_config = OrderedDict()
    for domain, component, conf in iter_components(config):
        if not hasattr(component, 'to_hass_config'):
            continue
        func = getattr(component, 'to_hass_config')
        ret = func(data, conf)
        if not isinstance(ret, (list, tuple)):
            ret = [ret]
        ret = [x for x in ret if x is not None]
        domain_conf = hass_config.setdefault(domain.split('.')[0], [])
        domain_conf += ret

    safe_print(yaml_util.dump(hass_config))
    return 0
コード例 #13
0
def read_config():
    _LOGGER.info("Reading configuration %s...", CORE.config_path)
    try:
        res = load_config()
    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('bold_red', "Failed config"))
        safe_print('')
        for path, domain in res.output_paths:
            if not res.is_in_error_path(path):
                continue

            safe_print(color('bold_red', f'{domain}:') + ' ' +
                       (line_info(res.get_nested_item(path)) or ''))
            safe_print(indent(dump_dict(res, path)[0]))
        return None
    return OrderedDict(res)
コード例 #14
0
ファイル: config.py プロジェクト: lanbing8023/esphome-tools
def read_config(verbose):
    _LOGGER.info("Reading configuration...")
    try:
        res = load_config()
    except EsphomeError as err:
        _LOGGER.error(u"Error while reading config: %s", err)
        return None
    if res.errors:
        if not verbose:
            res = strip_default_ids(res)

        safe_print(color('bold_red', u"Failed config"))
        safe_print('')
        for path, domain in res.domains:
            if not res.is_in_error_path(path):
                continue

            safe_print(color('bold_red', u'{}:'.format(domain)) + u' ' +
                       (line_info(res.nested_item(path)) or u''))
            safe_print(indent(dump_dict(res, path)[0]))
        return None
    return OrderedDict(res)
コード例 #15
0
def choose_prompt(options):
    if not options:
        raise ValueError

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

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

    while True:
        opt = safe_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('red', u"Invalid option: '{}'".format(opt)))
    return options[opt - 1][1]
コード例 #16
0
 def on_message(client, userdata, msg):
     time_ = datetime.now().time().strftime("[%H:%M:%S]")
     payload = msg.payload.decode(errors="backslashreplace")
     message = time_ + payload
     safe_print(message)
コード例 #17
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
コード例 #18
0
ファイル: wizard.py プロジェクト: ielbury/esphome
def default_input(text, default):
    safe_print()
    safe_print(f"Press ENTER for default ({default})")
    return input(text.format(default)) or default
コード例 #19
0
 def on_message(client, userdata, msg):
     time_ = datetime.now().time().strftime(u'[%H:%M:%S]')
     payload = decode_text(msg.payload)
     message = time_ + payload
     safe_print(message)
コード例 #20
0
 def on_log(msg):
     time_ = datetime.now().time().strftime("[%H:%M:%S]")
     text = msg.message.decode("utf8", "backslashreplace")
     safe_print(time_ + text)
コード例 #21
0
ファイル: __main__.py プロジェクト: zinefer/esphome
def command_version(args):
    safe_print(u"Version: {}".format(const.__version__))
    return 0
コード例 #22
0
def command_config(args, config):
    _LOGGER.info("Configuration is valid!")
    if not CORE.verbose:
        config = strip_default_ids(config)
    safe_print(yaml_util.dump(config))
    return 0
コード例 #23
0
def default_input(text, default):
    safe_print()
    safe_print(u"Press ENTER for default ({})".format(default))
    return safe_input(text.format(default)) or default
コード例 #24
0
def command_version(args):
    safe_print(f"Version: {const.__version__}")
    return 0
コード例 #25
0
def wizard(path):
    if not path.endswith('.yaml') and not path.endswith('.yml'):
        safe_print(
            u"Please make your configuration file {} have the extension .yaml or .yml"
            u"".format(color('cyan', path)))
        return 1
    if os.path.exists(path):
        safe_print(
            u"Uh oh, it seems like {} already exists, please delete that file first "
            u"or chose another configuration file.".format(color('cyan',
                                                                 path)))
        return 1
    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('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('bold_white', "livingroom")))
    safe_print()
    sleep(1)
    name = safe_input(color("bold_white", "(name): "))
    while True:
        try:
            name = cv.valid_name(name)
            break
        except vol.Invalid:
            safe_print(
                color(
                    "red",
                    u"Oh noes, \"{}\" isn't a valid name. Names can only include "
                    u"numbers, letters and underscores.".format(name)))
            name = strip_accents(name).replace(' ', '_')
            name = u''.join(c for c in name if c in cv.ALLOWED_NAME_CHARS)
            safe_print(u"Shall I use \"{}\" as the name instead?".format(
                color('cyan', name)))
            sleep(0.5)
            name = default_input(u"(name [{}]): ", name)

    safe_print(u"Great! Your node is now called \"{}\".".format(
        color('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('green', 'ESP32') + " or " +
               color('green', 'ESP8266') +
               " platform? (Choose ESP8266 for Sonoff devices)")
    while True:
        sleep(0.5)
        safe_print()
        safe_print("Please enter either ESP32 or ESP8266.")
        platform = safe_input(color("bold_white", "(ESP32/ESP8266): "))
        try:
            platform = vol.All(vol.Upper, vol.Any('ESP32',
                                                  'ESP8266'))(platform)
            break
        except vol.Invalid:
            safe_print(
                u"Unfortunately, I can't find an espressif microcontroller called "
                u"\"{}\". Please try again.".format(platform))
    safe_print(u"Thanks! You've chosen {} as your platform.".format(
        color('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('green', 'board') +
               " you're using.")
    sleep(0.5)
    safe_print("Please go to {} and choose a board.".format(
        color('green', board_link)))
    if platform == 'ESP32':
        safe_print("(Type " + color('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("bold_white", 'nodemcu-32s')))
        boards = list(ESP32_BOARD_PINS.keys())
    else:
        safe_print("For example \"{}\".".format(
            color("bold_white", 'nodemcuv2')))
        boards = list(ESP8266_BOARD_PINS.keys())
    safe_print("Options: {}".format(', '.join(boards)))

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

    safe_print(u"Way to go! You've chosen {} as your board.".format(
        color('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('green', 'SSID') +
               u" (the name) of the WiFi network {} I should connect to?".
               format(name))
    sleep(1.5)
    safe_print("For example \"{}\".".format(
        color('bold_white', "Abraham Linksys")))
    while True:
        ssid = safe_input(color('bold_white', "(ssid): "))
        try:
            ssid = cv.ssid(ssid)
            break
        except vol.Invalid:
            safe_print(
                color(
                    'red',
                    u"Unfortunately, \"{}\" doesn't seem to be a valid SSID. "
                    u"Please try again.".format(ssid)))
            safe_print()
            sleep(1)

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

    safe_print(
        "Now please state the " + color('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('bold_white', 'PASSWORD42')))
    sleep(0.5)
    psk = safe_input(color('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('green', 'password') + " for connecting to this ESP?")
    safe_print()
    sleep(0.25)
    safe_print("Press ENTER for no password")
    password = safe_input(color('bold_white', '(password): '))

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

    safe_print()
    safe_print(
        color('cyan', "DONE! I've now written a new configuration file to ") +
        color('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