HassIO add-on (#18)

* HassIO Beginnings

* Updates

* Fix pylint errors

* Fix pylint error
This commit is contained in:
Otto Winter 2018-05-21 16:40:22 +02:00 committed by GitHub
parent ebb6d0d464
commit 94d7ac4ef0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1140 additions and 106 deletions

4
MANIFEST.in Normal file
View File

@ -0,0 +1,4 @@
include README.md
include esphomeyaml/hassio/templates/index.html
include esphomeyaml/hassio/static/materialize-stepper.min.css
include esphomeyaml/hassio/static/materialize-stepper.min.js

View File

@ -1,18 +1,19 @@
from __future__ import print_function from __future__ import print_function
import argparse import argparse
from datetime import datetime
import logging import logging
import os import os
import random import random
import sys import sys
from esphomeyaml import core, mqtt, wizard, writer, yaml_util, const from esphomeyaml import const, core, mqtt, wizard, writer, yaml_util
from esphomeyaml.config import core_to_code, get_component, iter_components, read_config from esphomeyaml.config import core_to_code, get_component, iter_components, read_config
from esphomeyaml.const import CONF_BAUD_RATE, CONF_DOMAIN, CONF_ESPHOMEYAML, CONF_HOSTNAME, \ from esphomeyaml.const import CONF_BAUD_RATE, CONF_DOMAIN, CONF_ESPHOMEYAML, CONF_HOSTNAME, \
CONF_LOGGER, CONF_MANUAL_IP, CONF_NAME, CONF_STATIC_IP, CONF_WIFI CONF_LOGGER, CONF_MANUAL_IP, CONF_NAME, CONF_STATIC_IP, CONF_WIFI, ESP_PLATFORM_ESP8266
from esphomeyaml.core import ESPHomeYAMLError from esphomeyaml.core import ESPHomeYAMLError
from esphomeyaml.helpers import AssignmentExpression, RawStatement, _EXPRESSIONS, add, add_task, \ from esphomeyaml.helpers import AssignmentExpression, Expression, RawStatement, _EXPRESSIONS, add, \
color, get_variable, indent, quote, statement, Expression add_task, color, get_variable, indent, quote, statement
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -27,28 +28,27 @@ def get_base_path(config):
return os.path.join(os.path.dirname(core.CONFIG_PATH), get_name(config)) return os.path.join(os.path.dirname(core.CONFIG_PATH), get_name(config))
def discover_serial_ports(): def get_serial_ports():
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py # from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
try:
from serial.tools.list_ports import comports from serial.tools.list_ports import comports
except ImportError:
return None
result = [] result = []
descs = []
for port, desc, info in comports(): for port, desc, info in comports():
if not port: if not port:
continue continue
if "VID:PID" in info: if "VID:PID" in info:
result.append(port) result.append((port, desc))
descs.append(desc) return result
def choose_serial_port(config):
result = get_serial_ports()
if not result: if not result:
return None return 'OTA'
print(u"Found multiple serial port options, please choose one:") print(u"Found multiple serial port options, please choose one:")
for i, (res, desc) in enumerate(zip(result, descs)): for i, (res, desc) in enumerate(result):
print(u" [{}] {} ({})".format(i, res, desc)) print(u" [{}] {} ({})".format(i, res, desc))
print(u" [{}] Over The Air".format(len(result))) print(u" [{}] Over The Air ({})".format(len(result), get_upload_host(config)))
print() print()
while True: while True:
opt = raw_input('(number): ') opt = raw_input('(number): ')
@ -63,11 +63,11 @@ def discover_serial_ports():
except ValueError: except ValueError:
print(color('red', u"Invalid option: '{}'".format(opt))) print(color('red', u"Invalid option: '{}'".format(opt)))
if opt == len(result): if opt == len(result):
return None return 'OTA'
return result[opt] return result[opt][0]
def run_platformio(*cmd): def run_platformio(*cmd, **kwargs):
def mock_exit(return_code): def mock_exit(return_code):
raise SystemExit(return_code) raise SystemExit(return_code)
@ -76,10 +76,13 @@ def run_platformio(*cmd):
full_cmd = u' '.join(quote(x) for x in cmd) full_cmd = u' '.join(quote(x) for x in cmd)
_LOGGER.info(u"Running: %s", full_cmd) _LOGGER.info(u"Running: %s", full_cmd)
try: try:
func = kwargs.get('main')
if func is None:
import platformio.__main__ import platformio.__main__
func = platformio.__main__.main
sys.argv = list(cmd) sys.argv = list(cmd)
sys.exit = mock_exit sys.exit = mock_exit
return platformio.__main__.main() return func() or 0
except KeyboardInterrupt: except KeyboardInterrupt:
return 1 return 1
except SystemExit as err: except SystemExit as err:
@ -92,13 +95,19 @@ def run_platformio(*cmd):
sys.exit = orig_exit sys.exit = orig_exit
def run_miniterm(config, port): def run_miniterm(config, port, escape=False):
from serial.tools import miniterm import serial
baud_rate = config.get(CONF_LOGGER, {}).get(CONF_BAUD_RATE, 115200) baud_rate = config.get(CONF_LOGGER, {}).get(CONF_BAUD_RATE, 115200)
sys.argv = ['miniterm', '--raw', '--exit-char', '3'] _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
miniterm.main(
default_port=port, with serial.Serial(port, baudrate=baud_rate) as ser:
default_baudrate=baud_rate) while True:
line = ser.readline()
time = datetime.now().time().strftime('[%H:%M:%S]')
message = time + line.decode('unicode-escape').replace('\r', '').replace('\n', '')
if escape:
message = message.replace('\033', '\\033').encode('ascii', 'replace')
print(message)
def write_cpp(config): def write_cpp(config):
@ -153,9 +162,32 @@ def compile_program(config):
return run_platformio('platformio', 'run', '-d', get_base_path(config)) return run_platformio('platformio', 'run', '-d', get_base_path(config))
def get_upload_host(config):
if CONF_MANUAL_IP in config[CONF_WIFI]:
host = str(config[CONF_WIFI][CONF_MANUAL_IP][CONF_STATIC_IP])
elif CONF_HOSTNAME in config[CONF_WIFI]:
host = config[CONF_WIFI][CONF_HOSTNAME] + config[CONF_WIFI][CONF_DOMAIN]
else:
host = config[CONF_ESPHOMEYAML][CONF_NAME] + config[CONF_WIFI][CONF_DOMAIN]
return host
def upload_using_esptool(config, port):
import esptool
name = get_name(config)
path = os.path.join(get_base_path(config), '.pioenvs', 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)
def upload_program(config, args, port): def upload_program(config, args, port):
_LOGGER.info("Uploading binary...") _LOGGER.info("Uploading binary...")
if port is not None: if port != 'OTA':
if core.ESP_PLATFORM == ESP_PLATFORM_ESP8266 and args.use_esptoolpy:
return upload_using_esptool(config, port)
return run_platformio('platformio', 'run', '-d', get_base_path(config), return run_platformio('platformio', 'run', '-d', get_base_path(config),
'-t', 'upload', '--upload-port', port) '-t', 'upload', '--upload-port', port)
@ -163,12 +195,7 @@ def upload_program(config, args, port):
_LOGGER.error("No serial port found and OTA not enabled. Can't upload!") _LOGGER.error("No serial port found and OTA not enabled. Can't upload!")
return -1 return -1
if CONF_MANUAL_IP in config[CONF_WIFI]: host = get_upload_host(config)
host = str(config[CONF_WIFI][CONF_MANUAL_IP][CONF_STATIC_IP])
elif CONF_HOSTNAME in config[CONF_WIFI]:
host = config[CONF_WIFI][CONF_HOSTNAME] + config[CONF_WIFI][CONF_DOMAIN]
else:
host = config[CONF_ESPHOMEYAML][CONF_NAME] + config[CONF_WIFI][CONF_DOMAIN]
from esphomeyaml.components import ota from esphomeyaml.components import ota
from esphomeyaml import espota from esphomeyaml import espota
@ -184,11 +211,12 @@ def upload_program(config, args, port):
return espota.main(espota_args) return espota.main(espota_args)
def show_logs(config, args, port): def show_logs(config, args, port, escape=False):
if port is not None and port != 'OTA': if port != 'OTA':
run_miniterm(config, port) run_miniterm(config, port, escape=escape)
return 0 return 0
return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id) return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id,
escape=escape)
def clean_mqtt(config, args): def clean_mqtt(config, args):
@ -221,11 +249,98 @@ def setup_log():
pass pass
def main(): def command_wizard(args):
setup_log() return wizard.wizard(args.configuration)
def command_config(args, config):
print(yaml_util.dump(config))
return 0
def command_compile(args, config):
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
exit_code = compile_program(config)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully compiled program.")
return 0
def command_upload(args, config):
port = args.upload_port or choose_serial_port(config)
exit_code = upload_program(config, args, port)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully uploaded program.")
return 0
def command_logs(args, config):
port = args.serial_port or choose_serial_port(config)
return show_logs(config, args, port, escape=args.escape)
def command_run(args, config):
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
exit_code = compile_program(config)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully compiled program.")
port = args.upload_port or choose_serial_port(config)
exit_code = upload_program(config, args, port)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully uploaded program.")
if args.no_logs:
return 0
return show_logs(config, args, port, escape=args.escape)
def command_clean_mqtt(args, config):
return clean_mqtt(config, args)
def command_mqtt_fingerprint(args, config):
return mqtt.get_fingerprint(config)
def command_version(args):
print(u"Version: {}".format(const.__version__))
return 0
def command_hassio(args):
from esphomeyaml.hassio import hassio
return hassio.start_web_server(args)
PRE_CONFIG_ACTIONS = {
'wizard': command_wizard,
'version': command_version,
'hassio': command_hassio
}
POST_CONFIG_ACTIONS = {
'config': command_config,
'compile': command_compile,
'upload': command_upload,
'logs': command_logs,
'run': command_run,
'clean-mqtt': command_clean_mqtt,
'mqtt-fingerprint': command_mqtt_fingerprint,
}
def parse_args(argv):
parser = argparse.ArgumentParser(prog='esphomeyaml') parser = argparse.ArgumentParser(prog='esphomeyaml')
parser.add_argument('configuration', help='Your YAML configuration file.') parser.add_argument('configuration', help='Your YAML configuration file.')
subparsers = parser.add_subparsers(help='Commands', dest='command') subparsers = parser.add_subparsers(help='Commands', dest='command')
subparsers.required = True subparsers.required = True
subparsers.add_parser('config', help='Validate the configuration and spit it out.') subparsers.add_parser('config', help='Validate the configuration and spit it out.')
@ -237,6 +352,9 @@ def main():
parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. " parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. "
"For example /dev/cu.SLAB_USBtoUART.") "For example /dev/cu.SLAB_USBtoUART.")
parser_upload.add_argument('--host-port', help="Specify the host port.", type=int) parser_upload.add_argument('--host-port', help="Specify the host port.", type=int)
parser_upload.add_argument('--use-esptoolpy',
help="Use esptool.py for HassIO (only for ESP8266)",
action='store_true')
parser_logs = subparsers.add_parser('logs', help='Validate the configuration ' parser_logs = subparsers.add_parser('logs', help='Validate the configuration '
'and show all MQTT logs.') 'and show all MQTT logs.')
@ -246,6 +364,8 @@ def main():
parser_logs.add_argument('--client-id', help='Manually set the client id.') parser_logs.add_argument('--client-id', help='Manually set the client id.')
parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use" parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use"
"For example /dev/cu.SLAB_USBtoUART.") "For example /dev/cu.SLAB_USBtoUART.")
parser_logs.add_argument('--escape', help="Escape ANSI color codes for HassIO",
action='store_true')
parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, ' parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, '
'upload it, and start MQTT logs.') 'upload it, and start MQTT logs.')
@ -258,6 +378,10 @@ def main():
parser_run.add_argument('--username', help='Manually set the MQTT username for logs.') parser_run.add_argument('--username', help='Manually set the MQTT username for logs.')
parser_run.add_argument('--password', help='Manually set the MQTT password for logs.') parser_run.add_argument('--password', help='Manually set the MQTT password for logs.')
parser_run.add_argument('--client-id', help='Manually set the client id for logs.') parser_run.add_argument('--client-id', help='Manually set the client id for logs.')
parser_run.add_argument('--escape', help="Escape ANSI color codes for HassIO",
action='store_true')
parser_run.add_argument('--use-esptoolpy', help="Use esptool.py for HassIO (only for ESP8266)",
action='store_true')
parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from " parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from "
"retain messages.") "retain messages.")
@ -270,12 +394,25 @@ def main():
"you through setting up esphomeyaml.") "you through setting up esphomeyaml.")
subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.") subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.")
subparsers.add_parser('version', help="Print the esphomeyaml version and exit.") subparsers.add_parser('version', help="Print the esphomeyaml version and exit.")
args = parser.parse_args() hassio = subparsers.add_parser('hassio', help="Create a simple webserver for a HassIO add-on.")
hassio.add_argument("--port", help="The HTTP port to open connections on.", type=int,
default=6052)
if args.command == 'wizard': return parser.parse_args(argv[1:])
return wizard.wizard(args.configuration)
def run_esphomeyaml(argv):
setup_log()
args = parse_args(argv)
if args.command in PRE_CONFIG_ACTIONS:
try:
return PRE_CONFIG_ACTIONS[args.command](args)
except ESPHomeYAMLError as e:
_LOGGER.error(e)
return 1
core.CONFIG_PATH = args.configuration core.CONFIG_PATH = args.configuration
@ -283,58 +420,25 @@ def main():
if config is None: if config is None:
return 1 return 1
if args.command == 'config': if args.command in POST_CONFIG_ACTIONS:
print(yaml_util.dump(config))
return 0
elif args.command == 'compile':
try: try:
exit_code = write_cpp(config) return POST_CONFIG_ACTIONS[args.command](args, config)
except ESPHomeYAMLError as e: except ESPHomeYAMLError as e:
_LOGGER.error(e) _LOGGER.error(e)
return 1 return 1
if exit_code != 0:
return exit_code
exit_code = compile_program(config)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully compiled program.")
return 0
elif args.command == 'upload':
port = args.upload_port or discover_serial_ports()
exit_code = upload_program(config, args, port)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully uploaded program.")
return 0
elif args.command == 'logs':
port = args.serial_port or discover_serial_ports()
return show_logs(config, args, port)
elif args.command == 'clean-mqtt':
return clean_mqtt(config, args)
elif args.command == 'mqtt-fingerprint':
return mqtt.get_fingerprint(config)
elif args.command == 'run':
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
exit_code = compile_program(config)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully compiled program.")
port = args.upload_port or discover_serial_ports()
exit_code = upload_program(config, args, port)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully uploaded program.")
if args.no_logs:
return 0
return show_logs(config, args, port)
elif args.command == 'version':
print(u"Version: {}".format(const.__version__))
return 0
print(u"Unknown command {}".format(args.command)) print(u"Unknown command {}".format(args.command))
return 1 return 1
def main():
try:
return run_esphomeyaml(sys.argv)
except ESPHomeYAMLError as e:
_LOGGER.error(e)
return 1
except KeyboardInterrupt:
return 1
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())

View File

@ -4,11 +4,10 @@ import esphomeyaml.config_validation as cv
from esphomeyaml.components import switch from esphomeyaml.components import switch
from esphomeyaml.components.ir_transmitter import IRTransmitterComponent from esphomeyaml.components.ir_transmitter import IRTransmitterComponent
from esphomeyaml.const import CONF_ADDRESS, CONF_CARRIER_FREQUENCY, CONF_COMMAND, CONF_DATA, \ from esphomeyaml.const import CONF_ADDRESS, CONF_CARRIER_FREQUENCY, CONF_COMMAND, CONF_DATA, \
CONF_ID, CONF_INVERTED, CONF_IR_TRANSMITTER_ID, CONF_LG, CONF_NAME, CONF_NBITS, CONF_NEC, \ CONF_INVERTED, CONF_IR_TRANSMITTER_ID, CONF_LG, CONF_NAME, CONF_NBITS, CONF_NEC, \
CONF_PANASONIC, CONF_RAW, CONF_REPEAT, CONF_SONY, CONF_TIMES, CONF_WAIT_TIME CONF_PANASONIC, CONF_RAW, CONF_REPEAT, CONF_SONY, CONF_TIMES, CONF_WAIT_TIME
from esphomeyaml.core import ESPHomeYAMLError from esphomeyaml.core import ESPHomeYAMLError
from esphomeyaml.helpers import App, ArrayInitializer, HexIntLiteral, Pvariable, \ from esphomeyaml.helpers import App, ArrayInitializer, HexIntLiteral, get_variable
get_variable
DEPENDENCIES = ['ir_transmitter'] DEPENDENCIES = ['ir_transmitter']
@ -98,8 +97,7 @@ def to_code(config):
ir = get_variable(config.get(CONF_IR_TRANSMITTER_ID), IRTransmitterComponent) ir = get_variable(config.get(CONF_IR_TRANSMITTER_ID), IRTransmitterComponent)
send_data = exp_send_data(config) send_data = exp_send_data(config)
rhs = App.register_component(ir.create_transmitter(config[CONF_NAME], send_data)) rhs = App.register_component(ir.create_transmitter(config[CONF_NAME], send_data))
switch_ = Pvariable(DataTransmitter, config[CONF_ID], rhs) switch.register_switch(rhs, config)
switch.register_switch(switch_, config)
BUILD_FLAGS = '-DUSE_IR_TRANSMITTER' BUILD_FLAGS = '-DUSE_IR_TRANSMITTER'

View File

@ -26,7 +26,7 @@ zero_to_one_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1))
positive_int = vol.All(vol.Coerce(int), vol.Range(min=0)) positive_int = vol.All(vol.Coerce(int), vol.Range(min=0))
positive_not_null_int = vol.All(vol.Coerce(int), vol.Range(min=0, min_included=False)) positive_not_null_int = vol.All(vol.Coerce(int), vol.Range(min=0, min_included=False))
ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_' ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_'
RESERVED_IDS = [ RESERVED_IDS = [
# C++ keywords http://en.cppreference.com/w/cpp/keyword # C++ keywords http://en.cppreference.com/w/cpp/keyword

View File

@ -1,8 +1,8 @@
"""Constants used by esphomeyaml.""" """Constants used by esphomeyaml."""
MAJOR_VERSION = 1 MAJOR_VERSION = 1
MINOR_VERSION = 5 MINOR_VERSION = 6
PATCH_VERSION = '3' PATCH_VERSION = '0'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)

View File

View File

@ -0,0 +1,176 @@
from __future__ import print_function
import codecs
import json
import logging
import os
import random
import subprocess
try:
import tornado
import tornado.gen
import tornado.ioloop
import tornado.iostream
import tornado.process
import tornado.web
import tornado.websocket
import tornado.concurrent
except ImportError as err:
pass
from esphomeyaml import const, core, __main__
from esphomeyaml.__main__ import get_serial_ports, get_base_path, get_name
from esphomeyaml.helpers import quote
_LOGGER = logging.getLogger(__name__)
CONFIG_DIR = ''
# pylint: disable=abstract-method, arguments-differ
class EsphomeyamlCommandWebSocket(tornado.websocket.WebSocketHandler):
def __init__(self, application, request, **kwargs):
super(EsphomeyamlCommandWebSocket, self).__init__(application, request, **kwargs)
self.proc = None
self.closed = False
def on_message(self, message):
if self.proc is not None:
return
command = self.build_command(message)
_LOGGER.debug(u"WebSocket opened for command %s", [quote(x) for x in command])
self.proc = tornado.process.Subprocess(command,
stdout=tornado.process.Subprocess.STREAM,
stderr=subprocess.STDOUT)
self.proc.set_exit_callback(self.proc_on_exit)
tornado.ioloop.IOLoop.current().spawn_callback(self.redirect_stream)
@tornado.gen.coroutine
def redirect_stream(self):
while True:
try:
data = yield self.proc.stdout.read_until_regex('[\n\r]')
except tornado.iostream.StreamClosedError:
break
if data.endswith('\r') and random.randrange(100) < 90:
continue
data = data.replace('\033', '\\033')
self.write_message({'event': 'line', 'data': data})
def proc_on_exit(self, returncode):
if not self.closed:
_LOGGER.debug("Process exited with return code %s", returncode)
self.write_message({'event': 'exit', 'code': returncode})
def on_close(self):
self.closed = True
if self.proc is not None and self.proc.returncode is None:
_LOGGER.debug("Terminating process")
self.proc.proc.terminate()
def build_command(self, message):
raise NotImplementedError
class EsphomeyamlLogsHandler(EsphomeyamlCommandWebSocket):
def build_command(self, message):
js = json.loads(message)
config_file = CONFIG_DIR + '/' + js['configuration']
return ["esphomeyaml", config_file, "logs", '--serial-port', js["port"], '--escape']
class EsphomeyamlRunHandler(EsphomeyamlCommandWebSocket):
def build_command(self, message):
js = json.loads(message)
config_file = os.path.join(CONFIG_DIR, js['configuration'])
return ["esphomeyaml", config_file, "run", '--upload-port', js["port"],
'--escape', '--use-esptoolpy']
class EsphomeyamlCompileHandler(EsphomeyamlCommandWebSocket):
def build_command(self, message):
js = json.loads(message)
config_file = os.path.join(CONFIG_DIR, js['configuration'])
return ["esphomeyaml", config_file, "compile"]
class SerialPortRequestHandler(tornado.web.RequestHandler):
def get(self):
ports = get_serial_ports()
data = []
for port, desc in ports:
if port == '/dev/ttyAMA0':
# ignore RPi built-in serial port
continue
data.append({'port': port, 'desc': desc})
data.append({'port': 'OTA', 'desc': 'Over-The-Air Upload/Logs'})
self.write(json.dumps(data))
class WizardRequestHandler(tornado.web.RequestHandler):
def post(self):
from esphomeyaml import wizard
kwargs = {k: ''.join(v) for k, v in self.request.arguments.iteritems()}
config = wizard.wizard_file(**kwargs)
destination = os.path.join(CONFIG_DIR, kwargs['name'] + '.yaml')
with codecs.open(destination, 'w') as f_handle:
f_handle.write(config)
self.redirect('/')
class DownloadBinaryRequestHandler(tornado.web.RequestHandler):
def get(self):
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)
name = get_name(config)
path = os.path.join(get_base_path(config), '.pioenvs', name, 'firmware.bin')
self.set_header('Content-Type', 'application/octet-stream')
self.set_header("Content-Disposition", 'attachment; filename="{}.bin"'.format(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()
class MainRequestHandler(tornado.web.RequestHandler):
def get(self):
files = [f for f in os.listdir(CONFIG_DIR) if f.endswith('.yaml')]
full_path_files = [os.path.join(CONFIG_DIR, f) for f in files]
self.render("templates/index.html", files=files, full_path_files=full_path_files,
version=const.__version__)
def make_app():
static_path = os.path.join(os.path.dirname(__file__), 'static')
return tornado.web.Application([
(r"/", MainRequestHandler),
(r"/logs", EsphomeyamlLogsHandler),
(r"/run", EsphomeyamlRunHandler),
(r"/compile", EsphomeyamlCompileHandler),
(r"/download.bin", DownloadBinaryRequestHandler),
(r"/serial-ports", SerialPortRequestHandler),
(r"/wizard.html", WizardRequestHandler),
(r'/static/(.*)', tornado.web.StaticFileHandler, {'path': static_path}),
], debug=True)
def start_web_server(args):
global CONFIG_DIR
CONFIG_DIR = args.configuration
if not os.path.exists(CONFIG_DIR):
os.makedirs(CONFIG_DIR)
_LOGGER.info("Starting HassIO add-on web server on port %s and configuration dir %s...",
args.port, CONFIG_DIR)
app = make_app()
app.listen(args.port)
try:
tornado.ioloop.IOLoop.current().start()
except KeyboardInterrupt:
_LOGGER.info("Shutting down...")

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,731 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>esphomeyaml Dashboard</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css">
<link rel="stylesheet" href="/static/materialize-stepper.min.css">
<!-- jQuery :( -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.8.5/jquery-ui.min.js" integrity="sha256-fOse6WapxTrUSJOJICXXYwHRJOPa6C1OUQXi7C9Ddy8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.15.0/jquery.validate.min.js"></script>
<script src="/static/materialize-stepper.min.js"></script>
<style>
nav .brand-logo {
margin-left: 48px;
font-size: 20px;
}
main .container {
margin-top: -12vh;
flex-shrink: 0;
}
.ribbon {
width: 100%;
height: 17vh;
background-color: #3F51B5;
flex-shrink: 0;
}
.ribbon-fab:not(.tap-target-origin) {
position: absolute;
right: 24px;
top: calc(17vh + 34px);
}
i.very-large {
font-size: 8rem;
padding-top: 2px;
color: #424242;
}
.card .card-content {
padding-left: 18px;
padding-bottom: 10px;
}
.chip {
height: 26px;
font-size: 12px;
line-height: 26px;
}
.log {
background-color: #1c1c1c;
margin-top: 0;
margin-bottom: 0;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px;
padding: 16px;
overflow: auto;
line-height: 1.45;
border-radius: 3px;
word-wrap: normal;
color: #DDD;
}
.inlinecode {
box-sizing: border-box;
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
background-color: rgba(27,31,35,0.05);
border-radius: 3px;
font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
}
.log.bold {
font-weight: bold;
}
.log .v {
color: #888888;
}
.log .d {
color: #00DDDD;
}
.log .c {
color: magenta;
}
.log .i {
color: limegreen;
}
.log .w {
color: yellow;
}
.log .e {
color: red;
font-weight: bold;
}
.log .e {
color: red;
}
.log .ww {
color: white;
}
.modal {
width: 90%;
max-height: 85%;
height: 80% !important;
}
.log {
}
.page-footer {
padding-top: 0;
}
body {
display: flex;
min-height: 100vh;
flex-direction: column;
}
main {
flex: 1 0 auto;
}
ul.browser-default {
padding-left: 30px;
margin-top: 10px;
margin-bottom: 15px;
}
ul.browser-default li {
list-style-type: initial;
}
ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .step.done::before, ul.stepper.horizontal .step.active .step-title::before, ul.stepper.horizontal .step.done .step-title::before {
background-color: #3f51b5 !important;
}
.select-port-container {
margin-top: 19px;
}
</style>
</head>
<body>
<header>
<nav>
<div class="nav-wrapper indigo">
<a href="#" class="brand-logo left">esphomeyaml Dashboard</a>
</div>
</nav>
<div class="ribbon"></div>
</header>
<main>
<div class="container">
{% for file, full_path in zip(files, full_path_files) %}
<div class="row">
<div class="col s8 offset-s2 m10 offset-m1 l12">
<div class="card horizontal">
<div class="card-image center-align">
<i class="material-icons very-large icon-grey">memory</i>
</div>
<div class="card-stacked">
<div class="card-content">
<span class="card-title">{{ escape(file) }}</span>
<p>
Full path: <code class="inlinecode">{{ escape(full_path) }}</code>
</p>
</div>
<div class="card-action">
<a href="#" class="action-upload" data-node="{{ file }}">Upload</a>
<a href="#" class="action-compile" data-node="{{ file }}">Compile</a>
<a href="#" class="action-show-logs" data-node="{{ file }}">Show Logs</a>
</div>
</div>
</div>
</div>
</div>
{% end %}
</div>
<div id="modal-logs" class="modal modal-fixed-footer">
<div class="modal-content">
<h4>Show Logs</h4>
<div class="upload-port row">
<div class="col s12">
<h5>Found multiple serial ports, please choose one:</h5>
</div>
<div class="input-field col s8">
<select></select>
</div>
<div class="col s4 select-port-container">
<button class="btn waves-effect waves-light upload-port-submit" type="submit" name="action">Select
<i class="material-icons right">send</i>
</button>
</div>
</div>
<div class="log-container">
<pre class="log"></pre>
</div>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat">Close</a>
</div>
</div>
<div id="modal-upload" class="modal modal-fixed-footer">
<div class="modal-content">
<h4>Compile And Upload</h4>
<div class="upload-port row">
<div class="col s12">
<h5>Found multiple upload options, please choose one:</h5>
</div>
<div class="input-field col s8">
<select></select>
</div>
<div class="col s4 select-port-container">
<button class="btn waves-effect waves-light upload-port-submit" type="submit" name="action">Select
<i class="material-icons right">send</i>
</button>
</div>
</div>
<div class="log-container">
<pre class="log"></pre>
</div>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat">Stop</a>
</div>
</div>
<div id="modal-compile" class="modal modal-fixed-footer">
<div class="modal-content">
<h4>Compile</h4>
<div class="log-container">
<pre class="log"></pre>
</div>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat disabled download-binary">Download Binary</a>
<a class="modal-close waves-effect waves-green btn-flat">Stop</a>
</div>
</div>
<div id="modal-wizard" class="modal">
<div class="modal-content">
<form action="/wizard.html" method="POST">
<ul class="stepper linear">
<li class="step active">
<div class="step-title waves-effect">Introduction And Name</div>
<div class="step-content">
<div class="row">
<p>
Hi there! I'm the esphomeyaml setup wizard and will guide you through setting up
your first ESP8266 or ESP32-powered device using esphomeyaml.
</p>
<a href="https://www.espressif.com/en/products/hardware/esp8266ex/overview" target="_blank">ESP8266s</a> and
their successors (the <a href="https://www.espressif.com/en/products/hardware/esp32/overview" target="_blank">ESP32s</a>)
are great low-cost microcontrollers that can communicate with the outside world using WiFi.
They're found in many devices such as the popular Sonoff/iTead, but also exist as development boards
such as the <a href="http://nodemcu.com/index_en.html" target="_blank">NodeMCU</a>.
<p>
</p>
<a href="https://esphomelib.com/esphomeyaml/index.html" target="_blank">esphomeyaml</a>,
the tool you're using here, creates custom firmwares for these devices using YAML configuration
files (similar to the ones you might be used to with Home Assistant).
<p>
</p>
This wizard will create a basic YAML configuration file for your "node" (the microcontroller).
Later, you will be able to customize this file and add some of
<a href="https://github.com/OttoWinter/esphomelib" target="_blank">esphomelib's</a>
many integrations.
<p>
<p>
First, I need to know what this node should be called. Choose this name wisely, changing this
later makes Over-The-Air Update attempts difficult.
It may only contain the characters <code class="inlinecode">a-z</code>,
<code class="inlinecode">0-9</code> and <code class="inlinecode">_</code>
</p>
<div class="input-field col s12">
<input id="node_name" class="validate" type="text" name="name" required>
<label for="node_name">Name of node</label>
</div>
</div>
<div class="step-actions">
<button class="waves-effect waves-dark btn indigo next-step"">CONTINUE</button>
</div>
</div>
</li>
<li class="step">
<div class="step-title waves-effect">Device Type</div>
<div class="step-content">
<div class="row">
<p>
Great! Now I need to know what type of microcontroller you're using so that I can compile firmware for them.
Please choose either ESP32 or ESP8266 (use ESP8266 for Sonoff devices). Note that the ESP32 is currently
unsupported if HassIO is running on a Raspberry Pi.
</p>
<div class="input-field col s12">
<select id="esp_type" name="platform" required>
<option value="ESP8266">ESP8266</option>
<option value="ESP32">ESP32</option>
</select>
<label>Microcontroller Type</label>
</div>
<p>
I'm also going to need to know which type of board you're using. Please go to
<a href="http://docs.platformio.org/en/latest/platforms/espressif32.html#boards" target="_blank">ESP32 boards</a> or
<a href="http://docs.platformio.org/en/latest/platforms/espressif8266.html#boards" target="_blank">ESP8266 boards</a>,
find your board and enter it here. For example, enter <code class="inlinecode">nodemcuv2</code>
for ESP8266 NodeMCU boards. Note: Use <code class="inlinecode">esp01_1m</code> for Sonoff devices.
</p>
<div class="input-field col s12">
<input id="board_type" class="validate" type="text" name="board" required>
<label for="board_type">Board Type</label>
</div>
</div>
<div class="step-actions">
<button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
</div>
</div>
</li>
<li class="step">
<div class="step-title waves-effect">WiFi And Over-The-Air Updates</div>
<div class="step-content">
<div class="row">
<p>
Thanks! Now I need to know what WiFi Access Point I should instruct the node to connect to.
Please enter an SSID (name of the WiFi network) and password (leave empty for no password).
</p>
<div class="input-field col s12">
<input id="wifi_ssid" class="validate" type="text" name="ssid" required>
<label for="wifi_ssid">WiFi SSID</label>
</div>
<div class="input-field col s12">
<input id="wifi_password" name="psk" type="password">
<label for="wifi_password">WiFi Password</label>
</div>
<p>
Esphomelib automatically sets up an Over-The-Air update server on the node
so that you only need to flash a firmware once. Optionally, you can set a password for this
upload process here.
</p>
<div class="input-field col s12">
<input id="ota_password" class="validate" name="ota_password" type="password">
<label for="ota_password">OTA Password</label>
</div>
</div>
<div class="step-actions">
<button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
</div>
</div>
</li>
<li class="step">
<div class="step-title waves-effect">MQTT</div>
<div class="step-content">
<div class="row">
<p>
esphomelib connects to your Home Assistant instance via
<a href="https://www.home-assistant.io/docs/mqtt/">MQTT</a>. If you haven't already, please set up
MQTT on your Home Assistant server, for example with the awesome
<a href="https://www.home-assistant.io/addons/mosquitto/">Mosquitto Hass.io Add-on</a>.
</p>
<p>
When you're done with that, please enter your MQTT broker here. For example
<code class="inlinecode">192.168.1.100</code> (Note
<code class="inlinecode">hassio.local</code> often doesn't work, please use a static IP).
Please also specify the MQTT username and password you wish esphomelib to use
(leave them empty if you're not using any authentication).
</p>
<div class="input-field col s12">
<input id="mqtt_broker" class="validate" type="text" name="broker" required>
<label for="mqtt_broker">MQTT Broker</label>
</div>
<div class="input-field col s6">
<input id="mqtt_username" class="validate" type="text" name="mqtt_username">
<label for="mqtt_username">MQTT Username</label>
</div>
<div class="input-field col s6">
<input id="mqtt_password" class="validate" name="mqtt_password" type="password">
<label for="mqtt_password">MQTT Password</label>
</div>
</div>
<div class="step-actions">
<button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
</div>
</div>
</li>
<li class="step">
<div class="step-title waves-effect">Done!</div>
<div class="step-content">
<p>
Hooray! 🎉🎉🎉 You've successfully created your first esphomeyaml configuration file.
When you click Submit, I will save this configuration file under
<code class="inlinecode">&lt;HASS_CONFIG_FOLDER&gt;/esphomeyaml/&lt;NAME_OF_NODE&gt;.yaml</code> and
you will be able to edit this file with the
<a href="https://www.home-assistant.io/addons/configurator/" target="_blank">HASS Configuratior add-on</a>.
</p>
<h5>Next steps</h5>
<ul class="browser-default">
<li>
Flash the firmware. This can be done using the “UPLOAD” option in the dashboard. See
<a href="https://esphomelib.com/esphomeyaml/index.html#using-with" target="_blank">this</a>
for guides on how to flash different types of devices. Note that you need to restart this add-on
for newly plugged in serial devices to be detected.
</li>
<li>
See the <a href="https://esphomelib.com/esphomeyaml/index.html" target="_blank">esphomeyaml index</a>
for a list of supported sensors/devices.
</li>
<li>
Join the <a href="https://discord.gg/KhAMKrd" target="_blank">Discord server</a> and say hi. When I
have time, I would be happy to help with issues and discuss new features.
</li>
<li>
Star <a href="https://github.com/OttoWinter/esphomelib" target="_blank">esphomelib</a> and
<a href="https://github.com/OttoWinter/esphomeyaml" target="_blank">esphomeyaml</a> on GitHub and
report issues using the bug trackers there.
</li>
</ul>
<div class="step-actions">
<button class="waves-effect waves-dark btn indigo" type="submit">SUBMIT</button>
</div>
</div>
</li>
</ul>
</form>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-green btn-flat">Abort</a>
</div>
</div>
<a class="btn-floating btn-large ribbon-fab waves-effect waves-light pink accent-2" id="setup-wizard-start">
<i class="material-icons">add</i>
</a>
<div class="tap-target pink lighten-1" data-target="setup-wizard-start">
<div class="tap-target-content">
<h5>Set up your first Node</h5>
<p>
Huh... It seems like you you don't have any esphomeyaml configuration files yet...
Fortunately, there's a setup wizard that will step you through setting up your first node 🎉
</p>
</div>
</div>
</main>
<footer class="page-footer indigo darken-1">
<div class="container">
</div>
<div class="footer-copyright">
<div class="container">
© 2018 Copyright Otto Winter, Made with <a class="grey-text text-lighten-4" href="https://materializecss.com/" target="_blank">Materialize</a>
<a class="grey-text text-lighten-4 right" href="https://esphomelib.com/esphomeyaml/index.html" target="_blank">esphomeyaml {{ version }} Documentation</a>
</div>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', () => {
M.AutoInit(document.body);
});
const colorReplace = (input) => {
input = input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
input = input.replace(/\\033\[(?:0;)?31m/g, '<span class="e">');
input = input.replace(/\\033\[(?:1;)?31m/g, '<span class="e bold">');
input = input.replace(/\\033\[(?:0;)?32m/g, '<span class="i">');
input = input.replace(/\\033\[(?:1;)?32m/g, '<span class="i bold">');
input = input.replace(/\\033\[(?:0;)?33m/g, '<span class="w">');
input = input.replace(/\\033\[(?:1;)?33m/g, '<span class="w bold">');
input = input.replace(/\\033\[(?:0;)?35m/g, '<span class="c">');
input = input.replace(/\\033\[(?:1;)?35m/g, '<span class="c bold">');
input = input.replace(/\\033\[(?:0;)?36m/g, '<span class="d">');
input = input.replace(/\\033\[(?:1;)?36m/g, '<span class="d bold">');
input = input.replace(/\\033\[(?:0;)?37m/g, '<span class="v">');
input = input.replace(/\\033\[(?:1;)?37m/g, '<span class="v bold">');
input = input.replace(/\\033\[(?:0;)?38m/g, '<span class="vv">');
input = input.replace(/\\033\[(?:1;)?38m/g, '<span class="vv bold">');
input = input.replace(/\\033\[0m/g, '</span>');
return input;
};
let configuration = "";
const ws_url = 'ws://' + window.location.hostname + ':' + window.location.port;
const logsModalElem = document.getElementById("modal-logs");
const logsPortSelect = logsModalElem.querySelector('select');
const logsPortDiv = logsModalElem.querySelector(".upload-port");
const logsPortSubmit = logsModalElem.querySelector('.upload-port-submit');
let logsStart = undefined;
logsPortSubmit.addEventListener('click', () => {
const inst = M.FormSelect.getInstance(logsPortSelect);
logsStart(inst.getSelectedValues()[0]);
inst.destroy();
});
document.querySelectorAll(".action-show-logs").forEach((showLogs) => {
showLogs.addEventListener('click', (e) => {
configuration = e.target.getAttribute('data-node');
const modalInstance = M.Modal.getInstance(logsModalElem);
const log = logsModalElem.querySelector(".log");
log.innerHTML = "";
if (M.FormSelect.getInstance(logsPortSelect) !== undefined) {
M.FormSelect.getInstance(logsPortSelect).destroy();
}
modalInstance.open();
if (logsPortDiv.classList.contains('hide')) {
logsPortDiv.classList.remove('hide');
}
logsStart = (port) => {
logsPortDiv.classList.add('hide');
const logSocket = new WebSocket(ws_url + "/logs");
logSocket.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.event === "line") {
const msg = data.data;
log.innerHTML += colorReplace(msg);
} else if (data.event === "exit") {
M.toast({html: `Program exited with code ${data.code}`});
}
});
logSocket.addEventListener('open', () => {
const msg = JSON.stringify({configuration: configuration, port: port});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
M.toast({html: 'Terminated process.'});
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
};
fetch('/serial-ports').then(res => res.json())
.then(response => {
if (response.length > 1) {
logsPortSelect.innerHTML = "";
for (let i = 0; i < response.length; i++) {
const val = response[i];
logsPortSelect.innerHTML += `<option value="${val.port}">${val.port} (${val.desc})</option>`;
}
M.FormSelect.init(logsPortSelect, {});
} else {
logsStart("OTA");
}
});
});
});
const uploadModalElem = document.getElementById("modal-upload");
const uploadPortSelect = uploadModalElem.querySelector('select');
const uploadPortDiv = uploadModalElem.querySelector(".upload-port");
const uploadPortSubmit = uploadModalElem.querySelector('.upload-port-submit');
let uploadStart = undefined;
uploadPortSubmit.addEventListener('click', () => {
const inst = M.FormSelect.getInstance(uploadPortSelect);
uploadStart(inst.getSelectedValues()[0]);
inst.destroy();
});
document.querySelectorAll(".action-upload").forEach((showLogs) => {
showLogs.addEventListener('click', (e) => {
configuration = e.target.getAttribute('data-node');
const modalInstance = M.Modal.getInstance(uploadModalElem);
const log = uploadModalElem.querySelector(".log");
log.innerHTML = "";
if (M.FormSelect.getInstance(uploadPortSelect) !== undefined) {
M.FormSelect.getInstance(uploadPortSelect).destroy();
}
modalInstance.open();
if (uploadPortDiv.classList.contains('hide')) {
uploadPortDiv.classList.remove('hide');
}
uploadStart = (port) => {
uploadPortDiv.classList.add('hide');
const logSocket = new WebSocket(ws_url + "/run");
logSocket.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.event === "line") {
const msg = data.data;
log.innerHTML += colorReplace(msg);
} else if (data.event === "exit") {
M.toast({html: `Program exited with code ${data.code}`});
}
});
logSocket.addEventListener('open', () => {
const msg = JSON.stringify({configuration: configuration, port: port});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
M.toast({html: 'Terminated process.'});
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
};
fetch('/serial-ports').then(res => res.json())
.then(response => {
if (response.length > 1) {
uploadPortSelect.innerHTML = "";
for (let i = 0; i < response.length; i++) {
const val = response[i];
uploadPortSelect.innerHTML += `<option value="${val.port}">${val.port} (${val.desc})</option>`;
}
M.FormSelect.init(uploadPortSelect, {});
} else {
uploadStart("OTA");
}
});
});
});
const compileModalElem = document.getElementById("modal-compile");
const downloadButton = compileModalElem.querySelector('.download-binary');
document.querySelectorAll(".action-compile").forEach((showLogs) => {
showLogs.addEventListener('click', (e) => {
configuration = e.target.getAttribute('data-node');
const modalInstance = M.Modal.getInstance(compileModalElem);
const log = compileModalElem.querySelector(".log");
log.innerHTML = "";
downloadButton.classList.add('disabled');
modalInstance.open();
const logSocket = new WebSocket(ws_url + "/compile");
logSocket.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.event === "line") {
const msg = data.data;
log.innerHTML += colorReplace(msg);
} else if (data.event === "exit") {
M.toast({html: `Program exited with code ${data.code}`});
if (data.code === 0) {
downloadButton.classList.remove('disabled');
}
}
});
logSocket.addEventListener('open', () => {
const msg = JSON.stringify({configuration: configuration});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
M.toast({html: 'Terminated process.'});
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
});
});
downloadButton.addEventListener('click', () => {
const link = document.createElement("a");
link.download = name;
link.href = '/download.bin?configuration=' + encodeURIComponent(configuration);
link.click();
});
const modalSetupElem = document.getElementById("modal-wizard");
const setupWizardStart = document.getElementById('setup-wizard-start');
const startWizard = () => {
const modalInstance = M.Modal.getInstance(modalSetupElem);
modalInstance.open();
modalInstance.options.onCloseStart = () => {
};
$('.stepper').activateStepper({
linearStepsNavigation: false,
autoFocusInput: true,
autoFormCreation: true,
showFeedbackLoader: true,
parallel: false
});
};
setupWizardStart.addEventListener('click', startWizard);
</script>
{% if len(files) == 0 %}
<script>
document.addEventListener('DOMContentLoaded', () => {
const tapTargetElem = document.querySelector('.tap-target');
const tapTargetInstance = M.TapTarget.getInstance(tapTargetElem);
tapTargetInstance.options.onOpen = () => {
$('.tap-target-origin').on('click', () => {
startWizard();
});
};
tapTargetInstance.open();
});
</script>
{% end %}
</body>
</html>

View File

@ -39,7 +39,7 @@ def initialize(config, subscriptions, on_message, username, password, client_id)
return 0 return 0
def show_logs(config, topic=None, username=None, password=None, client_id=None): def show_logs(config, topic=None, username=None, password=None, client_id=None, escape=False):
if topic is not None: if topic is not None:
pass # already have topic pass # already have topic
elif CONF_MQTT in config: elif CONF_MQTT in config:
@ -57,7 +57,10 @@ def show_logs(config, topic=None, username=None, password=None, client_id=None):
def on_message(client, userdata, msg): def on_message(client, userdata, msg):
time = datetime.now().time().strftime(u'[%H:%M:%S]') time = datetime.now().time().strftime(u'[%H:%M:%S]')
print(time + msg.payload) message = msg.payload.decode('utf-8')
if escape:
message = message.replace('\033', '\\033')
print(time + message)
return initialize(config, [topic], on_message, username, password, client_id) return initialize(config, [topic], on_message, username, password, client_id)

View File

@ -70,6 +70,18 @@ logger:
""" """
def wizard_file(**kwargs):
config = BASE_CONFIG.format(**kwargs)
if kwargs['ota_password']:
config += "ota:\n password: '{}'\n".format(kwargs['ota_password'])
else:
config += "ota:\n"
return config
if os.getenv('ESPHOMEYAML_QUICKWIZARD', False): if os.getenv('ESPHOMEYAML_QUICKWIZARD', False):
def sleep(time): def sleep(time):
pass pass
@ -272,14 +284,10 @@ def wizard(path):
print("Press ENTER for no password") print("Press ENTER for no password")
ota_password = raw_input(color('bold_white', '(password): ')) ota_password = raw_input(color('bold_white', '(password): '))
config = BASE_CONFIG.format(name=name, platform=platform, board=board, config = wizard_file(name=name, platform=platform, board=board,
ssid=ssid, psk=psk, broker=broker, ssid=ssid, psk=psk, broker=broker,
mqtt_username=mqtt_username, mqtt_password=mqtt_password) mqtt_username=mqtt_username, mqtt_password=mqtt_password,
ota_password=ota_password)
if ota_password:
config += "ota:\n password: '{}'\n".format(ota_password)
else:
config += "ota:\n"
with codecs.open(path, 'w') as f_handle: with codecs.open(path, 'w') as f_handle:
f_handle.write(config) f_handle.write(config)