diff --git a/esphomeyaml/__main__.py b/esphomeyaml/__main__.py index 1afc30dfc5..360d596a71 100644 --- a/esphomeyaml/__main__.py +++ b/esphomeyaml/__main__.py @@ -63,7 +63,7 @@ def choose_serial_port(config): return result[opt][0] -def run_miniterm(config, port, escape=False): +def run_miniterm(config, port): import serial if CONF_LOGGER not in config: _LOGGER.info("Logger is not enabled. Not starting UART logs.") @@ -84,7 +84,7 @@ def run_miniterm(config, port, escape=False): line = raw.replace('\r', '').replace('\n', '') time = datetime.now().time().strftime('[%H:%M:%S]') message = time + line - if escape: + if core.FROM_DASHBOARD: message = message.replace('\033', '\\033') safe_print(message) @@ -152,7 +152,7 @@ def upload_program(config, args, port): # if upload is to a serial port use platformio, otherwise assume ota serial_port = port.startswith('/') or port.startswith('COM') if port != 'OTA' and serial_port: - if CORE.is_esp8266 and args.use_esptoolpy: + if CORE.is_esp8266 and args.dashboard: return upload_using_esptool(config, port) return platformio_api.run_upload(config, args.verbose, port) @@ -187,13 +187,12 @@ def upload_program(config, args, port): CORE.firmware_bin) -def show_logs(config, args, port, escape=False): +def show_logs(config, args, port): serial_port = port.startswith('/') or port.startswith('COM') if port != 'OTA' and serial_port: - run_miniterm(config, port, escape=escape) + run_miniterm(config, port) return 0 - return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id, - escape=escape) + return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id) def clean_mqtt(config, args): @@ -282,7 +281,7 @@ def command_upload(args, config): def command_logs(args, config): port = args.serial_port or choose_serial_port(config) - return show_logs(config, args, port, escape=args.escape) + return show_logs(config, args, port) def command_run(args, config): @@ -300,7 +299,7 @@ def command_run(args, config): _LOGGER.info(u"Successfully uploaded program.") if args.no_logs: return 0 - return show_logs(config, args, port, escape=args.escape) + return show_logs(config, args, port) def command_clean_mqtt(args, config): @@ -381,21 +380,24 @@ def parse_args(argv): subparsers = parser.add_subparsers(help='Commands', dest='command') subparsers.required = True - subparsers.add_parser('config', help='Validate the configuration and spit it out.') + config = subparsers.add_parser('config', help='Validate the configuration and spit it out.') + config.add_argument('--dashboard', help="Internal flag used by the dashboard", + action='store_true') parser_compile = subparsers.add_parser('compile', help='Read the configuration and compile a program.') parser_compile.add_argument('--only-generate', help="Only generate source code, do not compile.", action='store_true') + parser_compile.add_argument('--dashboard', help="Internal flag used by the dashboard", + action='store_true') parser_upload = subparsers.add_parser('upload', help='Validate the configuration ' 'and upload the latest binary.') parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. " "For example /dev/cu.SLAB_USBtoUART.") parser_upload.add_argument('--host-port', help="Specify the host port.", type=int) - parser_upload.add_argument('--use-esptoolpy', - help="Use esptool.py for the uploading (only for ESP8266)", + parser_upload.add_argument('--dashboard', help="Internal flag used by the dashboard", action='store_true') parser_logs = subparsers.add_parser('logs', help='Validate the configuration ' @@ -406,7 +408,7 @@ def parse_args(argv): 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 running in dashboard", + parser_logs.add_argument('--dashboard', help="Internal flag used by the dashboard", action='store_true') parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, ' @@ -420,10 +422,7 @@ def parse_args(argv): 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 running in dashboard", - action='store_true') - parser_run.add_argument('--use-esptoolpy', - help="Use esptool.py for the uploading (only for ESP8266)", + parser_run.add_argument('--dashboard', help="Internal flag used by the dashboard", action='store_true') parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from " @@ -432,6 +431,8 @@ def parse_args(argv): parser_clean.add_argument('--username', help='Manually set the username.') parser_clean.add_argument('--password', help='Manually set the password.') parser_clean.add_argument('--client-id', help='Manually set the client id.') + parser_clean.add_argument('--dashboard', help="Internal flag used by the dashboard", + action='store_true') subparsers.add_parser('wizard', help="A helpful setup wizard that will guide " "you through setting up esphomeyaml.") @@ -440,7 +441,9 @@ def parse_args(argv): subparsers.add_parser('version', help="Print the esphomeyaml version and exit.") - subparsers.add_parser('clean', help="Delete all temporary build files.") + clean = subparsers.add_parser('clean', help="Delete all temporary build files.") + clean.add_argument('--dashboard', help="Internal flag used by the dashboard", + action='store_true') dashboard = subparsers.add_parser('dashboard', help="Create a simple web server for a dashboard.") @@ -455,14 +458,20 @@ def parse_args(argv): "add-on.", action="store_true") - subparsers.add_parser('hass-config', help="Dump the configuration entries that should be added" - "to Home Assistant when not using MQTT discovery.") + hass_config = subparsers.add_parser('hass-config', + help="Dump the configuration entries that should be added " + "to Home Assistant when not using MQTT discovery.") + hass_config.add_argument('--dashboard', help="Internal flag used by the dashboard", + action='store_true') return parser.parse_args(argv[1:]) def run_esphomeyaml(argv): args = parse_args(argv) + if hasattr(args, 'dashboard'): + core.FROM_DASHBOARD = args.dashboard + setup_log(args.verbose) if args.command in PRE_CONFIG_ACTIONS: try: diff --git a/esphomeyaml/components/wifi.py b/esphomeyaml/components/wifi.py index 34947f71c6..eda4fc0f3a 100644 --- a/esphomeyaml/components/wifi.py +++ b/esphomeyaml/components/wifi.py @@ -59,7 +59,7 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend({ def validate(config): if CONF_PASSWORD in config and CONF_SSID not in config: raise vol.Invalid("Cannot have WiFi password without SSID!") - if CONF_SSID not in config and CONF_AP not in config: + if (CONF_SSID not in config) and (CONF_AP not in config): raise vol.Invalid("Please specify at least an SSID or an Access Point " "to create.") return config diff --git a/esphomeyaml/config.py b/esphomeyaml/config.py index 9c47603c52..33bd1d9f43 100644 --- a/esphomeyaml/config.py +++ b/esphomeyaml/config.py @@ -2,10 +2,10 @@ from __future__ import print_function from collections import OrderedDict import importlib +import json import logging import voluptuous as vol -from voluptuous.humanize import humanize_error from esphomeyaml import core, core_config, yaml_util from esphomeyaml.const import CONF_ESPHOMEYAML, CONF_PLATFORM, CONF_WIFI, ESP_PLATFORMS @@ -268,22 +268,53 @@ def validate_config(config): REQUIRED = ['esphomeyaml', 'wifi'] -def _format_config_error(ex, domain, config): - message = u"Invalid config for [{}]: ".format(domain) +def _nested_getitem(data, path): + for item_index in path: + try: + data = data[item_index] + except (KeyError, IndexError, TypeError): + return None + return data + + +def _format_path(path): + return u'->'.join(unicode(m) for m in path) + + +def humanize_error(config, validation_error): + offending_item_summary = _nested_getitem(config, validation_error.path) + if isinstance(offending_item_summary, dict): + offending_item_summary = json.dumps(offending_item_summary) + return u'{}. Got {}'.format(validation_error, offending_item_summary) + + +def _format_config_error(ex, domain, config, recursion=False): + message = u"" if recursion else u"Invalid config for [{}]: ".format(domain) + if isinstance(ex, vol.MultipleInvalid): + return color('red', message + u'\n'.join(sorted( + _format_config_error(sub_error, domain, config, recursion=True) + for sub_error in ex.errors + ))) + if u'extra keys not allowed' in ex.error_message: - message += u'[{}] is an invalid option for [{}]. Check: {}->{}.' \ - .format(ex.path[-1], domain, domain, - u'->'.join(str(m) for m in ex.path)) + message += u'[{}] is an invalid option for [{}].' \ + .format(ex.path[-1], domain) + elif u'required key not provided' in ex.error_message: + message += u"'{}' is a required option for [{}]." \ + u"".format(ex.path[-1], domain) else: message += u'{}.'.format(humanize_error(config, ex)) + message += u' Check {}->{}.'.format(domain, _format_path(ex.path)) + message = color('red', message) + if isinstance(config, list): return message domain_config = config.get(domain, config) - message += u" (See {}, line {}). ".format( + message += color('cyan', u" (See {}, line {}). ".format( getattr(domain_config, '__config_file__', '?'), - getattr(domain_config, '__line__', '?')) + getattr(domain_config, '__line__', '?'))) return message @@ -316,7 +347,7 @@ def line_info(obj, **kwargs): return '?' -def dump_dict(layer, indent_count=3, listi=False, **kwargs): +def dump_dict(layer, indent_count=0, listi=False, **kwargs): def sort_dict_key(val): """Return the dict key for sorting.""" key = str.lower(val[0]) @@ -358,9 +389,7 @@ def read_config(): if excepts: safe_print(color('bold_white', u"Failed config")) for domain, config in excepts.iteritems(): - safe_print(u' {} {}'.format(color('bold_red', domain + u':'), - color('red', '', reset='red'))) - dump_dict(config, reset='red') - safe_print(color('reset')) + safe_print(color('bold_red', domain + u':')) + dump_dict(config) return None return OrderedDict(res) diff --git a/esphomeyaml/core.py b/esphomeyaml/core.py index fb431a1511..655539de15 100644 --- a/esphomeyaml/core.py +++ b/esphomeyaml/core.py @@ -398,3 +398,4 @@ CORE = EsphomeyamlCore() ConfigType = Dict[str, Any] CoreType = EsphomeyamlCore +FROM_DASHBOARD = False diff --git a/esphomeyaml/dashboard/dashboard.py b/esphomeyaml/dashboard/dashboard.py index a95aabd81e..213d203217 100644 --- a/esphomeyaml/dashboard/dashboard.py +++ b/esphomeyaml/dashboard/dashboard.py @@ -105,7 +105,7 @@ 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'] + return ["esphomeyaml", config_file, "logs", '--serial-port', js["port"], '--dashboard'] class EsphomeyamlRunHandler(EsphomeyamlCommandWebSocket): @@ -113,42 +113,42 @@ class EsphomeyamlRunHandler(EsphomeyamlCommandWebSocket): 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'] + '--dashboard'] 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"] + return ["esphomeyaml", config_file, "compile", '--dashboard'] class EsphomeyamlValidateHandler(EsphomeyamlCommandWebSocket): def build_command(self, message): js = json.loads(message) config_file = os.path.join(CONFIG_DIR, js['configuration']) - return ["esphomeyaml", config_file, "config"] + return ["esphomeyaml", config_file, "config", '--dashboard'] class EsphomeyamlCleanMqttHandler(EsphomeyamlCommandWebSocket): def build_command(self, message): js = json.loads(message) config_file = os.path.join(CONFIG_DIR, js['configuration']) - return ["esphomeyaml", config_file, "clean-mqtt"] + return ["esphomeyaml", config_file, "clean-mqtt", '--dashboard'] class EsphomeyamlCleanHandler(EsphomeyamlCommandWebSocket): def build_command(self, message): js = json.loads(message) config_file = os.path.join(CONFIG_DIR, js['configuration']) - return ["esphomeyaml", config_file, "clean"] + return ["esphomeyaml", config_file, "clean", '--dashboard'] class EsphomeyamlHassConfigHandler(EsphomeyamlCommandWebSocket): def build_command(self, message): js = json.loads(message) config_file = os.path.join(CONFIG_DIR, js['configuration']) - return ["esphomeyaml", config_file, "hass-config"] + return ["esphomeyaml", config_file, "hass-config", '--dashboard'] class SerialPortRequestHandler(BaseHandler): diff --git a/esphomeyaml/dashboard/static/esphomeyaml.js b/esphomeyaml/dashboard/static/esphomeyaml.js index 1daf5120d0..5a53bdef94 100644 --- a/esphomeyaml/dashboard/static/esphomeyaml.js +++ b/esphomeyaml/dashboard/static/esphomeyaml.js @@ -5,19 +5,19 @@ document.addEventListener('DOMContentLoaded', () => { const colorReplace = (input) => { input = input.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); input = input.replace(/\\033\[(?:0;)?31m/g, ''); -input = input.replace(/\\033\[(?:1;)?31m/g, ''); +input = input.replace(/\\033\[(?:0?1;)?31m/g, ''); input = input.replace(/\\033\[(?:0;)?32m/g, ''); -input = input.replace(/\\033\[(?:1;)?32m/g, ''); +input = input.replace(/\\033\[(?:0?1;)?32m/g, ''); input = input.replace(/\\033\[(?:0;)?33m/g, ''); -input = input.replace(/\\033\[(?:1;)?33m/g, ''); +input = input.replace(/\\033\[(?:0?1;)?33m/g, ''); input = input.replace(/\\033\[(?:0;)?35m/g, ''); -input = input.replace(/\\033\[(?:1;)?35m/g, ''); +input = input.replace(/\\033\[(?:0?1;)?35m/g, ''); input = input.replace(/\\033\[(?:0;)?36m/g, ''); -input = input.replace(/\\033\[(?:1;)?36m/g, ''); +input = input.replace(/\\033\[(?:0?1;)?36m/g, ''); input = input.replace(/\\033\[(?:0;)?37m/g, ''); -input = input.replace(/\\033\[(?:1;)?37m/g, ''); +input = input.replace(/\\033\[(?:0?1;)?37m/g, ''); input = input.replace(/\\033\[(?:0;)?38m/g, ''); -input = input.replace(/\\033\[(?:1;)?38m/g, ''); +input = input.replace(/\\033\[(?:0?1;)?38m/g, ''); input = input.replace(/\\033\[0m/g, ''); return input; @@ -26,50 +26,50 @@ return input; let configuration = ""; let wsProtocol = "ws:"; if (window.location.protocol === "https:") { -wsProtocol = 'wss:'; + wsProtocol = 'wss:'; } const wsUrl = wsProtocol + '//' + window.location.hostname + ':' + window.location.port; let isFetchingPing = false; const fetchPing = () => { -if (isFetchingPing) - return; -isFetchingPing = true; + if (isFetchingPing) + return; + isFetchingPing = true; -fetch('/ping', {credentials: "same-origin"}).then(res => res.json()) - .then(response => { - for (let filename in response) { - let node = document.querySelector(`.status-indicator[data-node="${filename}"]`); - if (node === null) - continue; + fetch('/ping', {credentials: "same-origin"}).then(res => res.json()) + .then(response => { + for (let filename in response) { + let node = document.querySelector(`.status-indicator[data-node="${filename}"]`); + if (node === null) + continue; - let status = response[filename]; - let klass; - if (status === null) { - klass = 'unknown'; - } else if (status === true) { - klass = 'online'; - node.setAttribute('data-last-connected', Date.now().toString()); - } else if (node.hasAttribute('data-last-connected')) { - const attr = parseInt(node.getAttribute('data-last-connected')); - if (Date.now() - attr <= 5000) { - klass = 'not-responding'; + let status = response[filename]; + let klass; + if (status === null) { + klass = 'unknown'; + } else if (status === true) { + klass = 'online'; + node.setAttribute('data-last-connected', Date.now().toString()); + } else if (node.hasAttribute('data-last-connected')) { + const attr = parseInt(node.getAttribute('data-last-connected')); + if (Date.now() - attr <= 5000) { + klass = 'not-responding'; + } else { + klass = 'offline'; + } } else { klass = 'offline'; } - } else { - klass = 'offline'; + + if (node.classList.contains(klass)) + continue; + + node.classList.remove('unknown', 'online', 'offline', 'not-responding'); + node.classList.add(klass); } - if (node.classList.contains(klass)) - continue; - - node.classList.remove('unknown', 'online', 'offline', 'not-responding'); - node.classList.add(klass); - } - - isFetchingPing = false; - }); + isFetchingPing = false; + }); }; setInterval(fetchPing, 2000); fetchPing(); @@ -78,53 +78,53 @@ const portSelect = document.querySelector('.nav-wrapper select'); let ports = []; const fetchSerialPorts = (begin=false) => { -fetch('/serial-ports', {credentials: "same-origin"}).then(res => res.json()) - .then(response => { - if (ports.length === response.length) { - let allEqual = true; + fetch('/serial-ports', {credentials: "same-origin"}).then(res => res.json()) + .then(response => { + if (ports.length === response.length) { + let allEqual = true; + for (let i = 0; i < response.length; i++) { + if (ports[i].port !== response[i].port) { + allEqual = false; + break; + } + } + if (allEqual) + return; + } + const hasNewPort = response.length >= ports.length; + + ports = response; + + const inst = M.FormSelect.getInstance(portSelect); + if (inst !== undefined) { + inst.destroy(); + } + + portSelect.innerHTML = ""; + const prevSelected = getUploadPort(); for (let i = 0; i < response.length; i++) { - if (ports[i].port !== response[i].port) { - allEqual = false; - break; + const val = response[i]; + if (val.port === prevSelected) { + portSelect.innerHTML += ``; + } else { + portSelect.innerHTML += ``; } } - if (allEqual) - return; - } - const hasNewPort = response.length >= ports.length; - ports = response; - - const inst = M.FormSelect.getInstance(portSelect); - if (inst !== undefined) { - inst.destroy(); - } - - portSelect.innerHTML = ""; - const prevSelected = getUploadPort(); - for (let i = 0; i < response.length; i++) { - const val = response[i]; - if (val.port === prevSelected) { - portSelect.innerHTML += ``; - } else { - portSelect.innerHTML += ``; - } - } - - M.FormSelect.init(portSelect, {}); - if (!begin && hasNewPort) - M.toast({html: "Discovered new serial port."}); - }); + M.FormSelect.init(portSelect, {}); + if (!begin && hasNewPort) + M.toast({html: "Discovered new serial port."}); + }); }; const getUploadPort = () => { -const inst = M.FormSelect.getInstance(portSelect); -if (inst === undefined) { - return "OTA"; -} + const inst = M.FormSelect.getInstance(portSelect); + if (inst === undefined) { + return "OTA"; + } -inst._setSelectedStates(); -return inst.getSelectedValues()[0]; + inst._setSelectedStates(); + return inst.getSelectedValues()[0]; }; setInterval(fetchSerialPorts, 5000); fetchSerialPorts(true); @@ -132,347 +132,348 @@ fetchSerialPorts(true); const logsModalElem = document.getElementById("modal-logs"); 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 = ""; - const stopLogsButton = logsModalElem.querySelector(".stop-logs"); - let stopped = false; - stopLogsButton.innerHTML = "Stop"; - modalInstance.open(); + showLogs.addEventListener('click', (e) => { + configuration = e.target.getAttribute('data-node'); + const modalInstance = M.Modal.getInstance(logsModalElem); + const log = logsModalElem.querySelector(".log"); + log.innerHTML = ""; + const stopLogsButton = logsModalElem.querySelector(".stop-logs"); + let stopped = false; + stopLogsButton.innerHTML = "Stop"; + modalInstance.open(); - const filenameField = logsModalElem.querySelector('.filename'); - filenameField.innerHTML = configuration; + const filenameField = logsModalElem.querySelector('.filename'); + filenameField.innerHTML = configuration; - const logSocket = new WebSocket(wsUrl + "/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") { - if (data.code === 0) { - M.toast({html: "Program exited successfully."}); - } else { - M.toast({html: `Program failed with code ${data.code}`}); + const logSocket = new WebSocket(wsUrl + "/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") { + if (data.code === 0) { + M.toast({html: "Program exited successfully."}); + } else { + M.toast({html: `Program failed with code ${data.code}`}); + } + + stopLogsButton.innerHTML = "Close"; + stopped = true; } - - stopLogsButton.innerHTML = "Close"; - stopped = true; - } + }); + logSocket.addEventListener('open', () => { + const msg = JSON.stringify({configuration: configuration, port: getUploadPort()}); + logSocket.send(msg); + }); + logSocket.addEventListener('close', () => { + if (!stopped) { + M.toast({html: 'Terminated process.'}); + } + }); + modalInstance.options.onCloseStart = () => { + logSocket.close(); + }; }); - logSocket.addEventListener('open', () => { - const msg = JSON.stringify({configuration: configuration, port: getUploadPort()}); - logSocket.send(msg); - }); - logSocket.addEventListener('close', () => { - if (!stopped) { - M.toast({html: 'Terminated process.'}); - } - }); - modalInstance.options.onCloseStart = () => { - logSocket.close(); - }; -}); }); const uploadModalElem = document.getElementById("modal-upload"); document.querySelectorAll(".action-upload").forEach((upload) => { -upload.addEventListener('click', (e) => { - configuration = e.target.getAttribute('data-node'); - const modalInstance = M.Modal.getInstance(uploadModalElem); - const log = uploadModalElem.querySelector(".log"); - log.innerHTML = ""; - const stopLogsButton = uploadModalElem.querySelector(".stop-logs"); - let stopped = false; - stopLogsButton.innerHTML = "Stop"; - modalInstance.open(); + upload.addEventListener('click', (e) => { + configuration = e.target.getAttribute('data-node'); + const modalInstance = M.Modal.getInstance(uploadModalElem); + const log = uploadModalElem.querySelector(".log"); + log.innerHTML = ""; + const stopLogsButton = uploadModalElem.querySelector(".stop-logs"); + let stopped = false; + stopLogsButton.innerHTML = "Stop"; + modalInstance.open(); - const filenameField = uploadModalElem.querySelector('.filename'); - filenameField.innerHTML = configuration; + const filenameField = uploadModalElem.querySelector('.filename'); + filenameField.innerHTML = configuration; - const logSocket = new WebSocket(wsUrl + "/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") { - if (data.code === 0) { - M.toast({html: "Program exited successfully."}); - } else { - M.toast({html: `Program failed with code ${data.code}`}); + const logSocket = new WebSocket(wsUrl + "/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") { + if (data.code === 0) { + M.toast({html: "Program exited successfully."}); + } else { + M.toast({html: `Program failed with code ${data.code}`}); + } + + stopLogsButton.innerHTML = "Close"; + stopped = true; } - - stopLogsButton.innerHTML = "Close"; - stopped = true; - } + }); + logSocket.addEventListener('open', () => { + const msg = JSON.stringify({configuration: configuration, port: getUploadPort()}); + logSocket.send(msg); + }); + logSocket.addEventListener('close', () => { + if (!stopped) { + M.toast({html: 'Terminated process.'}); + } + }); + modalInstance.options.onCloseStart = () => { + logSocket.close(); + }; }); - logSocket.addEventListener('open', () => { - const msg = JSON.stringify({configuration: configuration, port: getUploadPort()}); - logSocket.send(msg); - }); - logSocket.addEventListener('close', () => { - if (!stopped) { - M.toast({html: 'Terminated process.'}); - } - }); - modalInstance.options.onCloseStart = () => { - logSocket.close(); - }; -}); }); const validateModalElem = document.getElementById("modal-validate"); document.querySelectorAll(".action-validate").forEach((upload) => { -upload.addEventListener('click', (e) => { - configuration = e.target.getAttribute('data-node'); - const modalInstance = M.Modal.getInstance(validateModalElem); - const log = validateModalElem.querySelector(".log"); - log.innerHTML = ""; - const stopLogsButton = validateModalElem.querySelector(".stop-logs"); - let stopped = false; - stopLogsButton.innerHTML = "Stop"; - modalInstance.open(); + upload.addEventListener('click', (e) => { + configuration = e.target.getAttribute('data-node'); + const modalInstance = M.Modal.getInstance(validateModalElem); + const log = validateModalElem.querySelector(".log"); + log.innerHTML = ""; + const stopLogsButton = validateModalElem.querySelector(".stop-logs"); + let stopped = false; + stopLogsButton.innerHTML = "Stop"; + modalInstance.open(); - const filenameField = validateModalElem.querySelector('.filename'); - filenameField.innerHTML = configuration; + const filenameField = validateModalElem.querySelector('.filename'); + filenameField.innerHTML = configuration; - const logSocket = new WebSocket(wsUrl + "/validate"); - 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") { - if (data.code === 0) { - M.toast({ - html: `${configuration} is valid 👍`, - displayLength: 5000, - }); - } else { - M.toast({ - html: `${configuration} is invalid 😕`, - displayLength: 5000, - }); + const logSocket = new WebSocket(wsUrl + "/validate"); + 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") { + if (data.code === 0) { + M.toast({ + html: `${configuration} is valid 👍`, + displayLength: 5000, + }); + } else { + M.toast({ + html: `${configuration} is invalid 😕`, + displayLength: 5000, + }); + } + + stopLogsButton.innerHTML = "Close"; + stopped = true; } - - stopLogsButton.innerHTML = "Close"; - stopped = true; - } + }); + logSocket.addEventListener('open', () => { + const msg = JSON.stringify({configuration: configuration}); + logSocket.send(msg); + }); + logSocket.addEventListener('close', () => { + if (!stopped) { + M.toast({html: 'Terminated process.'}); + } + }); + modalInstance.options.onCloseStart = () => { + logSocket.close(); + }; }); - logSocket.addEventListener('open', () => { - const msg = JSON.stringify({configuration: configuration}); - logSocket.send(msg); - }); - logSocket.addEventListener('close', () => { - if (!stopped) { - M.toast({html: 'Terminated process.'}); - } - }); - modalInstance.options.onCloseStart = () => { - logSocket.close(); - }; -}); }); const compileModalElem = document.getElementById("modal-compile"); const downloadButton = compileModalElem.querySelector('.download-binary'); document.querySelectorAll(".action-compile").forEach((upload) => { -upload.addEventListener('click', (e) => { - configuration = e.target.getAttribute('data-node'); - const modalInstance = M.Modal.getInstance(compileModalElem); - const log = compileModalElem.querySelector(".log"); - log.innerHTML = ""; - const stopLogsButton = compileModalElem.querySelector(".stop-logs"); - let stopped = false; - stopLogsButton.innerHTML = "Stop"; - downloadButton.classList.add('disabled'); + upload.addEventListener('click', (e) => { + configuration = e.target.getAttribute('data-node'); + const modalInstance = M.Modal.getInstance(compileModalElem); + const log = compileModalElem.querySelector(".log"); + log.innerHTML = ""; + const stopLogsButton = compileModalElem.querySelector(".stop-logs"); + let stopped = false; + stopLogsButton.innerHTML = "Stop"; + downloadButton.classList.add('disabled'); - modalInstance.open(); + modalInstance.open(); - const filenameField = compileModalElem.querySelector('.filename'); - filenameField.innerHTML = configuration; + const filenameField = compileModalElem.querySelector('.filename'); + filenameField.innerHTML = configuration; - const logSocket = new WebSocket(wsUrl + "/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") { - if (data.code === 0) { - M.toast({html: "Program exited successfully."}); - downloadButton.classList.remove('disabled'); - } else { - M.toast({html: `Program failed with code ${data.code}`}); + const logSocket = new WebSocket(wsUrl + "/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") { + if (data.code === 0) { + M.toast({html: "Program exited successfully."}); + downloadButton.classList.remove('disabled'); + } else { + M.toast({html: `Program failed with code ${data.code}`}); + } + + stopLogsButton.innerHTML = "Close"; + stopped = true; } + }); + logSocket.addEventListener('open', () => { + const msg = JSON.stringify({configuration: configuration}); + logSocket.send(msg); + }); + logSocket.addEventListener('close', () => { + if (!stopped) { + M.toast({html: 'Terminated process.'}); + } + }); + modalInstance.options.onCloseStart = () => { + logSocket.close(); + }; + }); +}); - stopLogsButton.innerHTML = "Close"; - stopped = true; - } - }); - logSocket.addEventListener('open', () => { - const msg = JSON.stringify({configuration: configuration}); - logSocket.send(msg); - }); - logSocket.addEventListener('close', () => { - if (!stopped) { - 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 link = document.createElement("a"); + link.download = name; + link.href = '/download.bin?configuration=' + encodeURIComponent(configuration); + link.click(); }); const cleanMqttModalElem = document.getElementById("modal-clean-mqtt"); document.querySelectorAll(".action-clean-mqtt").forEach((btn) => { -btn.addEventListener('click', (e) => { - configuration = e.target.getAttribute('data-node'); - const modalInstance = M.Modal.getInstance(cleanMqttModalElem); - const log = cleanMqttModalElem.querySelector(".log"); - log.innerHTML = ""; - const stopLogsButton = cleanMqttModalElem.querySelector(".stop-logs"); - let stopped = false; - stopLogsButton.innerHTML = "Stop"; - modalInstance.open(); + btn.addEventListener('click', (e) => { + configuration = e.target.getAttribute('data-node'); + const modalInstance = M.Modal.getInstance(cleanMqttModalElem); + const log = cleanMqttModalElem.querySelector(".log"); + log.innerHTML = ""; + const stopLogsButton = cleanMqttModalElem.querySelector(".stop-logs"); + let stopped = false; + stopLogsButton.innerHTML = "Stop"; + modalInstance.open(); - const filenameField = cleanMqttModalElem.querySelector('.filename'); - filenameField.innerHTML = configuration; + const filenameField = cleanMqttModalElem.querySelector('.filename'); + filenameField.innerHTML = configuration; - const logSocket = new WebSocket(wsUrl + "/clean-mqtt"); - 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") { - stopLogsButton.innerHTML = "Close"; - stopped = true; - } + const logSocket = new WebSocket(wsUrl + "/clean-mqtt"); + 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") { + stopLogsButton.innerHTML = "Close"; + stopped = true; + } + }); + logSocket.addEventListener('open', () => { + const msg = JSON.stringify({configuration: configuration}); + logSocket.send(msg); + }); + logSocket.addEventListener('close', () => { + if (!stopped) { + M.toast({html: 'Terminated process.'}); + } + }); + modalInstance.options.onCloseStart = () => { + logSocket.close(); + }; }); - logSocket.addEventListener('open', () => { - const msg = JSON.stringify({configuration: configuration}); - logSocket.send(msg); - }); - logSocket.addEventListener('close', () => { - if (!stopped) { - M.toast({html: 'Terminated process.'}); - } - }); - modalInstance.options.onCloseStart = () => { - logSocket.close(); - }; -}); }); const cleanModalElem = document.getElementById("modal-clean"); document.querySelectorAll(".action-clean").forEach((btn) => { -btn.addEventListener('click', (e) => { - configuration = e.target.getAttribute('data-node'); - const modalInstance = M.Modal.getInstance(cleanModalElem); - const log = cleanModalElem.querySelector(".log"); - log.innerHTML = ""; - const stopLogsButton = cleanModalElem.querySelector(".stop-logs"); - let stopped = false; - stopLogsButton.innerHTML = "Stop"; - modalInstance.open(); + btn.addEventListener('click', (e) => { + configuration = e.target.getAttribute('data-node'); + const modalInstance = M.Modal.getInstance(cleanModalElem); + const log = cleanModalElem.querySelector(".log"); + log.innerHTML = ""; + const stopLogsButton = cleanModalElem.querySelector(".stop-logs"); + let stopped = false; + stopLogsButton.innerHTML = "Stop"; + modalInstance.open(); - const filenameField = cleanModalElem.querySelector('.filename'); - filenameField.innerHTML = configuration; + const filenameField = cleanModalElem.querySelector('.filename'); + filenameField.innerHTML = configuration; - const logSocket = new WebSocket(wsUrl + "/clean"); - 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") { - if (data.code === 0) { - M.toast({html: "Program exited successfully."}); - downloadButton.classList.remove('disabled'); - } else { - M.toast({html: `Program failed with code ${data.code}`}); + const logSocket = new WebSocket(wsUrl + "/clean"); + 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") { + if (data.code === 0) { + M.toast({html: "Program exited successfully."}); + downloadButton.classList.remove('disabled'); + } else { + M.toast({html: `Program failed with code ${data.code}`}); + } + stopLogsButton.innerHTML = "Close"; + stopped = true; } - stopLogsButton.innerHTML = "Close"; - stopped = true; - } + }); + logSocket.addEventListener('open', () => { + const msg = JSON.stringify({configuration: configuration}); + logSocket.send(msg); + }); + logSocket.addEventListener('close', () => { + if (!stopped) { + M.toast({html: 'Terminated process.'}); + } + }); + modalInstance.options.onCloseStart = () => { + logSocket.close(); + }; }); - logSocket.addEventListener('open', () => { - const msg = JSON.stringify({configuration: configuration}); - logSocket.send(msg); - }); - logSocket.addEventListener('close', () => { - if (!stopped) { - M.toast({html: 'Terminated process.'}); - } - }); - modalInstance.options.onCloseStart = () => { - logSocket.close(); - }; -}); }); const hassConfigModalElem = document.getElementById("modal-hass-config"); document.querySelectorAll(".action-hass-config").forEach((btn) => { -btn.addEventListener('click', (e) => { - configuration = e.target.getAttribute('data-node'); - const modalInstance = M.Modal.getInstance(hassConfigModalElem); - const log = hassConfigModalElem.querySelector(".log"); - log.innerHTML = ""; - const stopLogsButton = hassConfigModalElem.querySelector(".stop-logs"); - let stopped = false; - stopLogsButton.innerHTML = "Stop"; - modalInstance.open(); + btn.addEventListener('click', (e) => { + configuration = e.target.getAttribute('data-node'); + const modalInstance = M.Modal.getInstance(hassConfigModalElem); + const log = hassConfigModalElem.querySelector(".log"); + log.innerHTML = ""; + const stopLogsButton = hassConfigModalElem.querySelector(".stop-logs"); + let stopped = false; + stopLogsButton.innerHTML = "Stop"; + modalInstance.open(); - const filenameField = hassConfigModalElem.querySelector('.filename'); - filenameField.innerHTML = configuration; + const filenameField = hassConfigModalElem.querySelector('.filename'); + filenameField.innerHTML = configuration; - const logSocket = new WebSocket(wsUrl + "/hass-config"); - 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") { - if (data.code === 0) { - M.toast({html: "Program exited successfully."}); - downloadButton.classList.remove('disabled'); - } else { - M.toast({html: `Program failed with code ${data.code}`}); + const logSocket = new WebSocket(wsUrl + "/hass-config"); + 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") { + if (data.code === 0) { + M.toast({html: "Program exited successfully."}); + downloadButton.classList.remove('disabled'); + } else { + M.toast({html: `Program failed with code ${data.code}`}); + } + stopLogsButton.innerHTML = "Close"; + stopped = true; } - stopLogsButton.innerHTML = "Close"; - stopped = true; - } + }); + logSocket.addEventListener('open', () => { + const msg = JSON.stringify({configuration: configuration}); + logSocket.send(msg); + }); + logSocket.addEventListener('close', () => { + if (!stopped) { + M.toast({html: 'Terminated process.'}); + } + }); + modalInstance.options.onCloseStart = () => { + logSocket.close(); + }; }); - logSocket.addEventListener('open', () => { - const msg = JSON.stringify({configuration: configuration}); - logSocket.send(msg); - }); - logSocket.addEventListener('close', () => { - if (!stopped) { - M.toast({html: 'Terminated process.'}); - } - }); - modalInstance.options.onCloseStart = () => { - logSocket.close(); - }; -}); }); const editModalElem = document.getElementById("modal-editor"); @@ -486,58 +487,59 @@ editor.session.setOption('tabSize', 2); const saveButton = editModalElem.querySelector(".save-button"); const saveEditor = () => { -fetch(`/edit?configuration=${configuration}`, { - credentials: "same-origin", - method: "POST", - body: editor.getValue() - }).then(res => res.text()).then(() => { - M.toast({ - html: `Saved ${configuration}` + fetch(`/edit?configuration=${configuration}`, { + credentials: "same-origin", + method: "POST", + body: editor.getValue() + }).then(res => res.text()).then(() => { + M.toast({ + html: `Saved ${configuration}` + }); }); - }); }; editor.commands.addCommand({ -name: 'saveCommand', -bindKey: {win: 'Ctrl-S', mac: 'Command-S'}, -exec: saveEditor, -readOnly: false + name: 'saveCommand', + bindKey: {win: 'Ctrl-S', mac: 'Command-S'}, + exec: saveEditor, + readOnly: false }); saveButton.addEventListener('click', saveEditor); document.querySelectorAll(".action-edit").forEach((btn) => { -btn.addEventListener('click', (e) => { - configuration = e.target.getAttribute('data-node'); - const modalInstance = M.Modal.getInstance(editModalElem); - const filenameField = editModalElem.querySelector('.filename'); - filenameField.innerHTML = configuration; + btn.addEventListener('click', (e) => { + configuration = e.target.getAttribute('data-node'); + const modalInstance = M.Modal.getInstance(editModalElem); + const filenameField = editModalElem.querySelector('.filename'); + filenameField.innerHTML = configuration; - fetch(`/edit?configuration=${configuration}`, {credentials: "same-origin"}) - .then(res => res.text()).then(response => { - editor.setValue(response, -1); + fetch(`/edit?configuration=${configuration}`, {credentials: "same-origin"}) + .then(res => res.text()).then(response => { + editor.setValue(response, -1); + }); + + modalInstance.open(); }); - - modalInstance.open(); -}); }); const modalSetupElem = document.getElementById("modal-wizard"); const setupWizardStart = document.getElementById('setup-wizard-start'); const startWizard = () => { -const modalInstance = M.Modal.getInstance(modalSetupElem); -modalInstance.open(); + const modalInstance = M.Modal.getInstance(modalSetupElem); + modalInstance.open(); -modalInstance.options.onCloseStart = () => { + modalInstance.options.onCloseStart = () => { + }; + + $('.stepper').activateStepper({ + linearStepsNavigation: false, + autoFocusInput: true, + autoFormCreation: true, + showFeedbackLoader: true, + parallel: false + }); }; -$('.stepper').activateStepper({ - linearStepsNavigation: false, - autoFocusInput: true, - autoFormCreation: true, - showFeedbackLoader: true, - parallel: false -}); -}; setupWizardStart.addEventListener('click', startWizard); \ No newline at end of file diff --git a/esphomeyaml/helpers.py b/esphomeyaml/helpers.py index 623902775e..704b22188a 100644 --- a/esphomeyaml/helpers.py +++ b/esphomeyaml/helpers.py @@ -49,12 +49,18 @@ def cpp_string_escape(string, encoding='utf-8'): return '"' + result + '"' -def color(the_color, message='', reset=None): - """Color helper.""" +def color(the_color, message=''): + from esphomeyaml import core from colorlog.escape_codes import escape_codes, parse_colors + if not message: - return parse_colors(the_color) - return parse_colors(the_color) + message + escape_codes[reset or 'reset'] + res = parse_colors(the_color) + else: + res = parse_colors(the_color) + message + escape_codes['reset'] + + if core.FROM_DASHBOARD: + res = res.replace('\033', '\\033') + return res def run_system_command(*args): diff --git a/esphomeyaml/mqtt.py b/esphomeyaml/mqtt.py index d4bff66bbe..5d67ab65e0 100644 --- a/esphomeyaml/mqtt.py +++ b/esphomeyaml/mqtt.py @@ -9,6 +9,7 @@ import sys import paho.mqtt.client as mqtt +from esphomeyaml import core from esphomeyaml.const import CONF_BROKER, CONF_DISCOVERY_PREFIX, CONF_ESPHOMEYAML, \ CONF_LOG_TOPIC, CONF_MQTT, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_SSL_FINGERPRINTS, \ CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_USERNAME @@ -54,7 +55,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, escape=False): +def show_logs(config, topic=None, username=None, password=None, client_id=None): if topic is not None: pass # already have topic elif CONF_MQTT in config: @@ -73,7 +74,7 @@ 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]') message = time + msg.payload - if escape: + if core.FROM_DASHBOARD: message = message.replace('\033', '\\033') safe_print(message)