1
0
mirror of https://github.com/esphome/esphome.git synced 2025-02-13 00:18:21 +00:00

Better ANSI color escaping

This commit is contained in:
Otto Winter 2018-11-24 18:32:18 +01:00
parent 8587e7ad74
commit 41d5dcded1
No known key found for this signature in database
GPG Key ID: DB66C0BE6013F97E
8 changed files with 488 additions and 440 deletions

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -398,3 +398,4 @@ CORE = EsphomeyamlCore()
ConfigType = Dict[str, Any]
CoreType = EsphomeyamlCore
FROM_DASHBOARD = False

View File

@ -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):

View File

@ -5,19 +5,19 @@ document.addEventListener('DOMContentLoaded', () => {
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?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?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?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?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?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?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\[(?:0?1;)?38m/g, '<span class="vv bold">');
input = input.replace(/\\033\[0m/g, '</span>');
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 += `<option value="${val.port}" selected>${val.port} (${val.desc})</option>`;
} else {
portSelect.innerHTML += `<option value="${val.port}">${val.port} (${val.desc})</option>`;
}
}
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 += `<option value="${val.port}" selected>${val.port} (${val.desc})</option>`;
} else {
portSelect.innerHTML += `<option value="${val.port}">${val.port} (${val.desc})</option>`;
}
}
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: `<code class="inlinecode">${configuration}</code> is valid 👍`,
displayLength: 5000,
});
} else {
M.toast({
html: `<code class="inlinecode">${configuration}</code> 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: `<code class="inlinecode">${configuration}</code> is valid 👍`,
displayLength: 5000,
});
} else {
M.toast({
html: `<code class="inlinecode">${configuration}</code> 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 <code class="inlinecode">${configuration}</code>`
fetch(`/edit?configuration=${configuration}`, {
credentials: "same-origin",
method: "POST",
body: editor.getValue()
}).then(res => res.text()).then(() => {
M.toast({
html: `Saved <code class="inlinecode">${configuration}</code>`
});
});
});
};
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);

View File

@ -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):

View File

@ -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)