mirror of
https://github.com/esphome/esphome.git
synced 2025-03-14 06:38:17 +00:00
HassIO Beginnings
This commit is contained in:
parent
ebb6d0d464
commit
254e0a63c2
4
MANIFEST.in
Normal file
4
MANIFEST.in
Normal 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
|
@ -27,28 +27,27 @@ def get_base_path(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
|
||||
try:
|
||||
from serial.tools.list_ports import comports
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
from serial.tools.list_ports import comports
|
||||
result = []
|
||||
descs = []
|
||||
for port, desc, info in comports():
|
||||
if not port:
|
||||
continue
|
||||
if "VID:PID" in info:
|
||||
result.append(port)
|
||||
descs.append(desc)
|
||||
result.append((port, desc))
|
||||
return result
|
||||
|
||||
|
||||
def choose_serial_port(config):
|
||||
result = get_serial_ports()
|
||||
|
||||
if not result:
|
||||
return None
|
||||
return 'OTA'
|
||||
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" [{}] Over The Air".format(len(result)))
|
||||
print(u" [{}] Over The Air ({})".format(len(result), get_upload_host(config)))
|
||||
print()
|
||||
while True:
|
||||
opt = raw_input('(number): ')
|
||||
@ -63,8 +62,8 @@ def discover_serial_ports():
|
||||
except ValueError:
|
||||
print(color('red', u"Invalid option: '{}'".format(opt)))
|
||||
if opt == len(result):
|
||||
return None
|
||||
return result[opt]
|
||||
return 'OTA'
|
||||
return result[opt][0]
|
||||
|
||||
|
||||
def run_platformio(*cmd):
|
||||
@ -153,9 +152,19 @@ def compile_program(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_program(config, args, port):
|
||||
_LOGGER.info("Uploading binary...")
|
||||
if port is not None:
|
||||
if port != 'OTA':
|
||||
return run_platformio('platformio', 'run', '-d', get_base_path(config),
|
||||
'-t', 'upload', '--upload-port', port)
|
||||
|
||||
@ -163,12 +172,7 @@ def upload_program(config, args, port):
|
||||
_LOGGER.error("No serial port found and OTA not enabled. Can't upload!")
|
||||
return -1
|
||||
|
||||
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]
|
||||
host = get_upload_host(config)
|
||||
|
||||
from esphomeyaml.components import ota
|
||||
from esphomeyaml import espota
|
||||
@ -184,11 +188,12 @@ def upload_program(config, args, port):
|
||||
return espota.main(espota_args)
|
||||
|
||||
|
||||
def show_logs(config, args, port):
|
||||
if port is not None and port != 'OTA':
|
||||
def show_logs(config, args, port, escape=False):
|
||||
if port != 'OTA':
|
||||
run_miniterm(config, port)
|
||||
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):
|
||||
@ -221,11 +226,98 @@ def setup_log():
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
setup_log()
|
||||
def command_wizard(args):
|
||||
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.add_argument('configuration', help='Your YAML configuration file.')
|
||||
|
||||
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
||||
subparsers.required = True
|
||||
subparsers.add_parser('config', help='Validate the configuration and spit it out.')
|
||||
@ -246,6 +338,8 @@ def main():
|
||||
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"
|
||||
"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, '
|
||||
'upload it, and start MQTT logs.')
|
||||
@ -258,6 +352,8 @@ def main():
|
||||
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('--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_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from "
|
||||
"retain messages.")
|
||||
@ -270,12 +366,25 @@ def main():
|
||||
"you through setting up esphomeyaml.")
|
||||
|
||||
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.")
|
||||
|
||||
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 wizard.wizard(args.configuration)
|
||||
return parser.parse_args(argv[1:])
|
||||
|
||||
|
||||
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
|
||||
|
||||
@ -283,58 +392,23 @@ def main():
|
||||
if config is None:
|
||||
return 1
|
||||
|
||||
if args.command == 'config':
|
||||
print(yaml_util.dump(config))
|
||||
return 0
|
||||
elif args.command == 'compile':
|
||||
if args.command in POST_CONFIG_ACTIONS:
|
||||
try:
|
||||
exit_code = write_cpp(config)
|
||||
return POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except ESPHomeYAMLError as e:
|
||||
_LOGGER.error(e)
|
||||
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))
|
||||
return 1
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
return run_esphomeyaml(sys.argv)
|
||||
except ESPHomeYAMLError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
@ -98,8 +98,7 @@ def to_code(config):
|
||||
ir = get_variable(config.get(CONF_IR_TRANSMITTER_ID), IRTransmitterComponent)
|
||||
send_data = exp_send_data(config)
|
||||
rhs = App.register_component(ir.create_transmitter(config[CONF_NAME], send_data))
|
||||
switch_ = Pvariable(DataTransmitter, config[CONF_ID], rhs)
|
||||
switch.register_switch(switch_, config)
|
||||
switch.register_switch(rhs, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_IR_TRANSMITTER'
|
||||
|
@ -1,8 +1,8 @@
|
||||
"""Constants used by esphomeyaml."""
|
||||
|
||||
MAJOR_VERSION = 1
|
||||
MINOR_VERSION = 5
|
||||
PATCH_VERSION = '3'
|
||||
MINOR_VERSION = 6
|
||||
PATCH_VERSION = '0'
|
||||
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
||||
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
||||
|
||||
|
0
esphomeyaml/hassio/__init__.py
Normal file
0
esphomeyaml/hassio/__init__.py
Normal file
148
esphomeyaml/hassio/hassio.py
Normal file
148
esphomeyaml/hassio/hassio.py
Normal file
@ -0,0 +1,148 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
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
|
||||
from esphomeyaml.__main__ import get_serial_ports
|
||||
from esphomeyaml.helpers import quote
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CONFIG_DIR = ''
|
||||
|
||||
|
||||
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 __init__(self, application, request, **kwargs):
|
||||
super(EsphomeyamlLogsHandler, self).__init__(application, request, **kwargs)
|
||||
|
||||
def build_command(self, message):
|
||||
config_file = CONFIG_DIR + '/' + message
|
||||
return ["esphomeyaml", config_file, "logs", '--escape', '--serial-port', 'OTA']
|
||||
|
||||
|
||||
class EsphomeyamlRunHandler(EsphomeyamlCommandWebSocket):
|
||||
def __init__(self, application, request, **kwargs):
|
||||
super(EsphomeyamlRunHandler, self).__init__(application, request, **kwargs)
|
||||
|
||||
def build_command(self, message):
|
||||
js = json.loads(message)
|
||||
config_file = CONFIG_DIR + '/' + js['configuration']
|
||||
return ["esphomeyaml", config_file, "run", '--upload-port', js["port"], '--escape']
|
||||
|
||||
|
||||
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 MainRequestHandler(tornado.web.RequestHandler):
|
||||
def get(self):
|
||||
files = [f for f in os.listdir(CONFIG_DIR) if f.endswith('.yaml')]
|
||||
self.render("templates/index.html", files=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"/serial-ports", SerialPortRequestHandler),
|
||||
(r"/wizard.html", WizardRequestHandler),
|
||||
(r'/static/(.*)', tornado.web.StaticFileHandler, {'path': static_path}),
|
||||
])
|
||||
|
||||
|
||||
def start_web_server(args):
|
||||
global CONFIG_DIR
|
||||
CONFIG_DIR = args.configuration
|
||||
|
||||
_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...")
|
5
esphomeyaml/hassio/static/materialize-stepper.min.css
vendored
Executable file
5
esphomeyaml/hassio/static/materialize-stepper.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
5
esphomeyaml/hassio/static/materialize-stepper.min.js
vendored
Executable file
5
esphomeyaml/hassio/static/materialize-stepper.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
617
esphomeyaml/hassio/templates/index.html
Normal file
617
esphomeyaml/hassio/templates/index.html
Normal file
@ -0,0 +1,617 @@
|
||||
<!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;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
.log-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: calc(100% - 48px);
|
||||
}
|
||||
|
||||
.log {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
</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 in 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">{{ file }}</span>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<a href="#" class="action-upload" data-node="{{ file }}">Upload</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="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-run" class="modal modal-fixed-footer">
|
||||
<div class="modal-content">
|
||||
<h4>Compile And Upload</h4>
|
||||
<div class="upload-port row">
|
||||
<div class="input-field col s8">
|
||||
<select></select>
|
||||
</div>
|
||||
<div class="col s4">
|
||||
<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-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">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" id="step-1-continue">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).
|
||||
</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">hassio.local</code>. 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"><HASS_CONFIG_FOLDER>/esphomeyaml/<NAME_OF_NODE>.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.
|
||||
</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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
||||
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;
|
||||
|
||||
document.querySelectorAll(".action-show-logs").forEach((showLogs) => {
|
||||
console.log(showLogs);
|
||||
showLogs.addEventListener('click', (e) => {
|
||||
configuration = e.target.getAttribute('data-node');
|
||||
const elem = document.getElementById("modal-logs");
|
||||
const instance = M.Modal.getInstance(elem);
|
||||
const log = elem.querySelector(".log");
|
||||
log.innerHTML = "";
|
||||
|
||||
instance.open();
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
logSocket.addEventListener('open', (event) => {
|
||||
logSocket.send(configuration);
|
||||
});
|
||||
logSocket.addEventListener('close', () => {
|
||||
M.toast({html: 'Terminated process.'});
|
||||
});
|
||||
|
||||
instance.options.onCloseStart = () => {
|
||||
logSocket.close();
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const modalRunElem = document.getElementById("modal-run");
|
||||
const submitButton = modalRunElem.querySelector('.upload-port-submit');
|
||||
let startRun = undefined;
|
||||
const select = modalRunElem.querySelector('select');
|
||||
const uploadPort = modalRunElem.querySelector(".upload-port");
|
||||
|
||||
document.querySelectorAll(".action-upload").forEach((actionUpload) => {
|
||||
actionUpload.addEventListener('click', (e) => {
|
||||
configuration = e.target.getAttribute('data-node');
|
||||
|
||||
const modal_instance = M.Modal.getInstance(modalRunElem);
|
||||
if (M.FormSelect.getInstance(select) === undefined) {
|
||||
M.FormSelect.getInstance(select).destroy();
|
||||
}
|
||||
const log = modalRunElem.querySelector(".log");
|
||||
log.innerHTML = "";
|
||||
|
||||
if (uploadPort.classList.contains('hide')) {
|
||||
uploadPort.classList.remove('hide');
|
||||
}
|
||||
|
||||
modal_instance.open();
|
||||
|
||||
startRun = (port) => {
|
||||
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);
|
||||
}
|
||||
});
|
||||
logSocket.addEventListener('open', () => {
|
||||
const msg = JSON.stringify({configuration: configuration, port: port});
|
||||
logSocket.send(msg);
|
||||
});
|
||||
logSocket.addEventListener('close', () => {
|
||||
M.toast({html: 'Terminated process.'});
|
||||
});
|
||||
modal_instance.options.onCloseStart = () => {
|
||||
logSocket.close();
|
||||
};
|
||||
};
|
||||
|
||||
fetch('/serial-ports').then(res => res.json())
|
||||
.then(response => {
|
||||
if (response.length > 1) {
|
||||
select.innerHTML = "";
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const val = response[i];
|
||||
select.innerHTML += `<option value="${val.port}">${val.port} (${val.desc})</option>`;
|
||||
}
|
||||
M.FormSelect.init(select, {});
|
||||
} else {
|
||||
uploadPort.classList.add('hide');
|
||||
startRun("OTA");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
submitButton.addEventListener('click', () => {
|
||||
const inst = M.FormSelect.getInstance(select);
|
||||
console.log(inst.getSelectedValues());
|
||||
startRun(inst.getSelectedValues()[0]);
|
||||
inst.destroy();
|
||||
uploadPort.classList.add('hide');
|
||||
});
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
document.getElementById('#step-1-continue').addEventListener('click', () => {
|
||||
console.log("NEXT STEP");
|
||||
$('.stepper').nextStep();
|
||||
});
|
||||
$('#step-1-continue').on('click', () => {
|
||||
|
||||
});
|
||||
};
|
||||
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>
|
@ -39,7 +39,7 @@ def initialize(config, subscriptions, on_message, username, password, client_id)
|
||||
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:
|
||||
pass # already have topic
|
||||
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):
|
||||
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)
|
||||
|
||||
|
@ -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):
|
||||
def sleep(time):
|
||||
pass
|
||||
@ -272,14 +284,10 @@ def wizard(path):
|
||||
print("Press ENTER for no password")
|
||||
ota_password = raw_input(color('bold_white', '(password): '))
|
||||
|
||||
config = BASE_CONFIG.format(name=name, platform=platform, board=board,
|
||||
ssid=ssid, psk=psk, broker=broker,
|
||||
mqtt_username=mqtt_username, mqtt_password=mqtt_password)
|
||||
|
||||
if ota_password:
|
||||
config += "ota:\n password: '{}'\n".format(ota_password)
|
||||
else:
|
||||
config += "ota:\n"
|
||||
config = wizard_file(name=name, platform=platform, board=board,
|
||||
ssid=ssid, psk=psk, broker=broker,
|
||||
mqtt_username=mqtt_username, mqtt_password=mqtt_password,
|
||||
ota_password=ota_password)
|
||||
|
||||
with codecs.open(path, 'w') as f_handle:
|
||||
f_handle.write(config)
|
||||
|
Loading…
x
Reference in New Issue
Block a user