2018-04-07 01:23:03 +02:00
|
|
|
from __future__ import print_function
|
|
|
|
|
|
|
|
import codecs
|
|
|
|
import os
|
|
|
|
import unicodedata
|
|
|
|
|
|
|
|
import voluptuous as vol
|
|
|
|
|
2018-10-20 12:41:00 +02:00
|
|
|
import esphomeyaml.config_validation as cv
|
2018-09-23 18:58:41 +02:00
|
|
|
from esphomeyaml.const import ESP_PLATFORMS, ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266
|
2018-04-07 01:23:03 +02:00
|
|
|
from esphomeyaml.helpers import color
|
2018-04-10 17:17:46 +02:00
|
|
|
# pylint: disable=anomalous-backslash-in-string
|
2018-09-23 18:58:41 +02:00
|
|
|
from esphomeyaml.pins import ESP32_BOARD_PINS, ESP8266_BOARD_PINS
|
2018-11-19 22:12:24 +01:00
|
|
|
from esphomeyaml.storage_json import StorageJSON, ext_storage_path
|
2018-10-20 12:41:00 +02:00
|
|
|
from esphomeyaml.util import safe_print
|
2018-09-23 18:58:41 +02:00
|
|
|
|
2018-04-10 17:17:46 +02:00
|
|
|
CORE_BIG = """ _____ ____ _____ ______
|
2018-04-07 01:23:03 +02:00
|
|
|
/ ____/ __ \| __ \| ____|
|
2018-04-10 17:17:46 +02:00
|
|
|
| | | | | | |__) | |__
|
|
|
|
| | | | | | _ /| __|
|
|
|
|
| |___| |__| | | \ \| |____
|
2018-04-07 01:23:03 +02:00
|
|
|
\_____\____/|_| \_\______|
|
|
|
|
"""
|
2018-04-10 17:17:46 +02:00
|
|
|
ESP_BIG = """ ______ _____ _____
|
|
|
|
| ____|/ ____| __ \\
|
2018-04-07 01:23:03 +02:00
|
|
|
| |__ | (___ | |__) |
|
2018-04-10 17:17:46 +02:00
|
|
|
| __| \___ \| ___/
|
|
|
|
| |____ ____) | |
|
|
|
|
|______|_____/|_|
|
2018-04-07 01:23:03 +02:00
|
|
|
"""
|
2018-04-10 17:17:46 +02:00
|
|
|
WIFI_BIG = """ __ ___ ______ _
|
2018-04-07 01:23:03 +02:00
|
|
|
\ \ / (_) ____(_)
|
2018-04-10 17:17:46 +02:00
|
|
|
\ \ /\ / / _| |__ _
|
2018-04-07 01:23:03 +02:00
|
|
|
\ \/ \/ / | | __| | |
|
|
|
|
\ /\ / | | | | |
|
|
|
|
\/ \/ |_|_| |_|
|
|
|
|
"""
|
2018-04-10 17:17:46 +02:00
|
|
|
MQTT_BIG = """ __ __ ____ _______ _______
|
2018-04-07 01:23:03 +02:00
|
|
|
| \/ |/ __ \__ __|__ __|
|
2018-04-10 17:17:46 +02:00
|
|
|
| \ / | | | | | | | |
|
|
|
|
| |\/| | | | | | | | |
|
|
|
|
| | | | |__| | | | | |
|
|
|
|
|_| |_|\___\_\ |_| |_|
|
2018-04-07 01:23:03 +02:00
|
|
|
"""
|
2018-04-10 17:17:46 +02:00
|
|
|
OTA_BIG = """ ____ _______
|
|
|
|
/ __ \__ __|/\\
|
|
|
|
| | | | | | / \\
|
|
|
|
| | | | | | / /\ \\
|
|
|
|
| |__| | | |/ ____ \\
|
2018-04-07 01:23:03 +02:00
|
|
|
\____/ |_/_/ \_\\
|
|
|
|
"""
|
|
|
|
|
|
|
|
# TODO handle escaping
|
2018-10-20 12:41:00 +02:00
|
|
|
BASE_CONFIG = u"""esphomeyaml:
|
2018-04-07 01:23:03 +02:00
|
|
|
name: {name}
|
|
|
|
platform: {platform}
|
|
|
|
board: {board}
|
|
|
|
|
|
|
|
wifi:
|
|
|
|
ssid: '{ssid}'
|
|
|
|
password: '{psk}'
|
|
|
|
|
|
|
|
mqtt:
|
|
|
|
broker: '{broker}'
|
|
|
|
username: '{mqtt_username}'
|
|
|
|
password: '{mqtt_password}'
|
|
|
|
|
|
|
|
# Enable logging
|
|
|
|
logger:
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
|
|
|
|
def wizard_file(**kwargs):
|
|
|
|
config = BASE_CONFIG.format(**kwargs)
|
|
|
|
|
|
|
|
if kwargs['ota_password']:
|
2018-10-20 12:41:00 +02:00
|
|
|
config += u"ota:\n password: '{}'\n".format(kwargs['ota_password'])
|
2018-05-21 16:40:22 +02:00
|
|
|
else:
|
2018-10-20 12:41:00 +02:00
|
|
|
config += u"ota:\n"
|
2018-05-21 16:40:22 +02:00
|
|
|
|
|
|
|
return config
|
|
|
|
|
|
|
|
|
2018-11-19 22:12:24 +01:00
|
|
|
def wizard_write(path, **kwargs):
|
|
|
|
name = kwargs['name']
|
|
|
|
board = kwargs['board']
|
2018-11-24 14:05:06 +01:00
|
|
|
if 'platform' not in kwargs:
|
2018-11-25 20:46:41 +01:00
|
|
|
kwargs['platform'] = 'ESP8266' if board in ESP8266_BOARD_PINS else 'ESP32'
|
2018-11-24 14:05:06 +01:00
|
|
|
platform = kwargs['platform']
|
|
|
|
|
|
|
|
with codecs.open(path, 'w') as f_handle:
|
|
|
|
f_handle.write(wizard_file(**kwargs))
|
2018-11-19 22:12:24 +01:00
|
|
|
storage = StorageJSON.from_wizard(name, name + '.local', platform, board)
|
|
|
|
storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path))
|
|
|
|
storage.save(storage_path)
|
|
|
|
|
|
|
|
|
2018-04-11 22:51:56 +02:00
|
|
|
if os.getenv('ESPHOMEYAML_QUICKWIZARD', False):
|
|
|
|
def sleep(time):
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
from time import sleep
|
|
|
|
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2018-10-20 12:41:00 +02:00
|
|
|
def safe_print_step(step, big):
|
|
|
|
safe_print()
|
|
|
|
safe_print()
|
|
|
|
safe_print("============= STEP {} =============".format(step))
|
|
|
|
safe_print(big)
|
|
|
|
safe_print("===================================")
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(0.25)
|
|
|
|
|
|
|
|
|
|
|
|
def default_input(text, default):
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print()
|
|
|
|
safe_print(u"Press ENTER for default ({})".format(default))
|
2018-04-07 01:23:03 +02:00
|
|
|
return raw_input(text.format(default)) or default
|
|
|
|
|
|
|
|
|
|
|
|
# From https://stackoverflow.com/a/518232/8924614
|
2018-04-10 17:17:46 +02:00
|
|
|
def strip_accents(string):
|
|
|
|
return u''.join(c for c in unicodedata.normalize('NFD', unicode(string))
|
2018-04-07 01:23:03 +02:00
|
|
|
if unicodedata.category(c) != 'Mn')
|
|
|
|
|
|
|
|
|
|
|
|
def wizard(path):
|
|
|
|
if not path.endswith('.yaml') and not path.endswith('.yml'):
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print(u"Please make your configuration file {} have the extension .yaml or .yml"
|
|
|
|
u"".format(color('cyan', path)))
|
2018-04-07 01:23:03 +02:00
|
|
|
return 1
|
|
|
|
if os.path.exists(path):
|
2018-10-20 12:41:00 +02:00
|
|
|
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)))
|
2018-04-07 01:23:03 +02:00
|
|
|
return 1
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("Hi there!")
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(1.5)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("I'm the wizard of esphomeyaml :)")
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(1.25)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("And I'm here to help you get started with esphomeyaml.")
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(2.0)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("In 5 steps I'm going to guide you through creating a basic "
|
|
|
|
"configuration file for your custom ESP8266/ESP32 firmware. Yay!")
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(3.0)
|
2018-10-20 12:41:00 +02:00
|
|
|
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.")
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(1)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("For example, I like calling the node in my living room {}.".format(
|
2018-04-07 01:23:03 +02:00
|
|
|
color('bold_white', "livingroom")))
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print()
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(1)
|
|
|
|
name = raw_input(color("bold_white", "(name): "))
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
name = cv.valid_name(name)
|
|
|
|
break
|
|
|
|
except vol.Invalid:
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print(color("red", u"Oh noes, \"{}\" isn't a valid name. Names can only include "
|
|
|
|
u"numbers, letters and underscores.".format(name)))
|
2018-04-07 01:23:03 +02:00
|
|
|
name = strip_accents(name).replace(' ', '_')
|
|
|
|
name = u''.join(c for c in name if c in cv.ALLOWED_NAME_CHARS)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print(u"Shall I use \"{}\" as the name instead?".format(color('cyan', name)))
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(0.5)
|
2018-10-20 12:41:00 +02:00
|
|
|
name = default_input(u"(name [{}]): ", name)
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print(u"Great! Your node is now called \"{}\".".format(color('cyan', name)))
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(1)
|
2018-10-20 12:41:00 +02:00
|
|
|
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)")
|
2018-04-07 01:23:03 +02:00
|
|
|
while True:
|
|
|
|
sleep(0.5)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print()
|
|
|
|
safe_print("Please enter either ESP32 or ESP8266.")
|
2018-04-07 01:23:03 +02:00
|
|
|
platform = raw_input(color("bold_white", "(ESP32/ESP8266): "))
|
|
|
|
try:
|
|
|
|
platform = vol.All(vol.Upper, vol.Any(*ESP_PLATFORMS))(platform)
|
|
|
|
break
|
|
|
|
except vol.Invalid:
|
2018-10-20 12:41:00 +02:00
|
|
|
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()
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(1)
|
|
|
|
|
|
|
|
if platform == ESP_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'
|
|
|
|
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("Next, I need to know what " + color('green', 'board') + " you're using.")
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(0.5)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("Please go to {} and choose a board.".format(color('green', board_link)))
|
2018-05-16 19:45:33 +02:00
|
|
|
if platform == ESP_PLATFORM_ESP8266:
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("(Type " + color('green', 'esp01_1m') + " for Sonoff devices)")
|
|
|
|
safe_print()
|
2018-04-07 01:23:03 +02:00
|
|
|
# Don't sleep because user needs to copy link
|
|
|
|
if platform == ESP_PLATFORM_ESP32:
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("For example \"{}\".".format(color("bold_white", 'nodemcu-32s')))
|
2018-09-23 18:58:41 +02:00
|
|
|
boards = list(ESP32_BOARD_PINS.keys())
|
2018-04-07 01:23:03 +02:00
|
|
|
else:
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("For example \"{}\".".format(color("bold_white", 'nodemcuv2')))
|
2018-09-23 18:58:41 +02:00
|
|
|
boards = list(ESP8266_BOARD_PINS.keys())
|
2018-04-07 01:23:03 +02:00
|
|
|
while True:
|
|
|
|
board = raw_input(color("bold_white", "(board): "))
|
|
|
|
try:
|
|
|
|
board = vol.All(vol.Lower, vol.Any(*boards))(board)
|
|
|
|
break
|
|
|
|
except vol.Invalid:
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print(color('red', "Sorry, I don't think the board \"{}\" exists."))
|
|
|
|
safe_print()
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(0.25)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("Possible options are {}".format(', '.join(boards)))
|
|
|
|
safe_print()
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print(u"Way to go! You've chosen {} as your board.".format(color('cyan', board)))
|
|
|
|
safe_print()
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(1)
|
|
|
|
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print_step(3, WIFI_BIG)
|
|
|
|
safe_print("In this step, I'm going to create the configuration for "
|
|
|
|
"WiFi.")
|
|
|
|
safe_print()
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(1)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("First, what's the " + color('green', 'SSID') +
|
|
|
|
u" (the name) of the WiFi network {} I should connect to?".format(name))
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(1.5)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("For example \"{}\".".format(color('bold_white', "Abraham Linksys")))
|
2018-04-07 01:23:03 +02:00
|
|
|
while True:
|
|
|
|
ssid = raw_input(color('bold_white', "(ssid): "))
|
|
|
|
try:
|
|
|
|
ssid = cv.ssid(ssid)
|
|
|
|
break
|
|
|
|
except vol.Invalid:
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print(color('red', u"Unfortunately, \"{}\" doesn't seem to be a valid SSID. "
|
|
|
|
u"Please try again.".format(ssid)))
|
|
|
|
safe_print()
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(1)
|
|
|
|
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print(u"Thank you very much! You've just chosen \"{}\" as your SSID."
|
|
|
|
u"".format(color('cyan', ssid)))
|
|
|
|
safe_print()
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(0.75)
|
|
|
|
|
2018-10-20 12:41:00 +02:00
|
|
|
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')))
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(0.5)
|
|
|
|
psk = raw_input(color('bold_white', '(PSK): '))
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("Perfect! WiFi is now set up (you can create static IPs and so on later).")
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(1.5)
|
|
|
|
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print_step(4, MQTT_BIG)
|
|
|
|
safe_print("Almost there! Now let's setup MQTT so that your node can connect to the "
|
|
|
|
"outside world.")
|
|
|
|
safe_print()
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(1)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("Please enter the " + color('green', 'address') + " of your MQTT broker.")
|
|
|
|
safe_print()
|
|
|
|
safe_print("For example \"{}\".".format(color('bold_white', '192.168.178.84')))
|
2018-11-25 20:21:51 +01:00
|
|
|
broker = raw_input(color('bold_white', "(broker): "))
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("Thanks! Now enter the " + color('green', 'username') + " and " +
|
|
|
|
color('green', 'password') +
|
|
|
|
" for the MQTT broker. Leave empty for no authentication.")
|
2018-04-07 01:23:03 +02:00
|
|
|
mqtt_username = raw_input(color('bold_white', '(username): '))
|
|
|
|
mqtt_password = ''
|
|
|
|
if mqtt_username:
|
|
|
|
mqtt_password = raw_input(color('bold_white', '(password): '))
|
|
|
|
|
|
|
|
show = '*' * len(mqtt_password)
|
|
|
|
if len(mqtt_password) >= 2:
|
|
|
|
show = mqtt_password[:2] + '*' * len(mqtt_password)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print(u"MQTT Username: \"{}\"; Password: \"{}\""
|
|
|
|
u"".format(color('cyan', mqtt_username), color('cyan', show)))
|
2018-04-07 01:23:03 +02:00
|
|
|
else:
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("No authentication for MQTT")
|
|
|
|
|
|
|
|
safe_print_step(5, OTA_BIG)
|
|
|
|
safe_print("Last step! esphomeyaml can automatically upload custom firmwares over WiFi "
|
|
|
|
"(over the air).")
|
|
|
|
safe_print("This can be insecure if you do not trust the WiFi network. Do you want to set "
|
|
|
|
"an " + color('green', 'OTA password') + " for remote updates?")
|
|
|
|
safe_print()
|
2018-04-07 01:23:03 +02:00
|
|
|
sleep(0.25)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print("Press ENTER for no password")
|
2018-04-07 01:23:03 +02:00
|
|
|
ota_password = raw_input(color('bold_white', '(password): '))
|
|
|
|
|
2018-11-19 22:12:24 +01:00
|
|
|
wizard_write(path=path, name=name, platform=platform, board=board,
|
|
|
|
ssid=ssid, psk=psk, broker=broker,
|
|
|
|
mqtt_username=mqtt_username, mqtt_password=mqtt_password,
|
|
|
|
ota_password=ota_password)
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2018-10-20 12:41:00 +02:00
|
|
|
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(" > If you haven't already, enable MQTT discovery in Home Assistant:")
|
|
|
|
safe_print()
|
|
|
|
safe_print(color('bold_white', "# In your configuration.yaml"))
|
|
|
|
safe_print(color('bold_white', "mqtt:"))
|
|
|
|
safe_print(color('bold_white', u" broker: {}".format(broker)))
|
|
|
|
safe_print(color('bold_white', " # ..."))
|
|
|
|
safe_print(color('bold_white', " discovery: True"))
|
|
|
|
safe_print()
|
|
|
|
safe_print(" > Then follow the rest of the getting started guide:")
|
|
|
|
safe_print(" > https://esphomelib.com/esphomeyaml/guides/getting_started_command_line.html")
|
2018-04-07 01:23:03 +02:00
|
|
|
return 0
|