From f4d393a59e58da63debcdd5885350225de09d985 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 3 Jun 2018 11:18:53 +0200 Subject: [PATCH] Fix dashboard upload port selection --- esphomeyaml/dashboard/dashboard.py | 22 +- esphomeyaml/dashboard/templates/index.html | 409 +++++++++++---------- esphomeyaml/helpers.py | 3 + 3 files changed, 235 insertions(+), 199 deletions(-) diff --git a/esphomeyaml/dashboard/dashboard.py b/esphomeyaml/dashboard/dashboard.py index e92323943d..92d8b765bb 100644 --- a/esphomeyaml/dashboard/dashboard.py +++ b/esphomeyaml/dashboard/dashboard.py @@ -102,11 +102,14 @@ class SerialPortRequestHandler(tornado.web.RequestHandler): data = [] for port, desc in ports: if port == '/dev/ttyAMA0': - # ignore RPi built-in serial port - continue + desc = 'UART pins on GPIO header' + split_desc = desc.split(' - ') + if len(split_desc) == 2 and split_desc[0] == split_desc[1]: + # Some serial ports repeat their values + desc = split_desc[0] data.append({'port': port, 'desc': desc}) - data.append({'port': 'OTA', 'desc': 'Over-The-Air Upload/Logs'}) - self.write(json.dumps(data)) + data.append({'port': 'OTA', 'desc': 'Over-The-Air'}) + self.write(json.dumps(sorted(data, reverse=True))) class WizardRequestHandler(tornado.web.RequestHandler): @@ -119,7 +122,7 @@ class WizardRequestHandler(tornado.web.RequestHandler): with codecs.open(destination, 'w') as f_handle: f_handle.write(config) - self.redirect('/') + self.redirect('/?begin=True') class DownloadBinaryRequestHandler(tornado.web.RequestHandler): @@ -143,14 +146,15 @@ class DownloadBinaryRequestHandler(tornado.web.RequestHandler): class MainRequestHandler(tornado.web.RequestHandler): def get(self): + begin = bool(self.get_argument('begin', False)) files = sorted([f for f in os.listdir(CONFIG_DIR) if f.endswith('.yaml') and not f.startswith('.')]) full_path_files = [os.path.join(CONFIG_DIR, f) for f in files] self.render("templates/index.html", files=files, full_path_files=full_path_files, - version=const.__version__) + version=const.__version__, begin=begin) -def make_app(): +def make_app(debug=False): static_path = os.path.join(os.path.dirname(__file__), 'static') return tornado.web.Application([ (r"/", MainRequestHandler), @@ -161,7 +165,7 @@ def make_app(): (r"/serial-ports", SerialPortRequestHandler), (r"/wizard.html", WizardRequestHandler), (r'/static/(.*)', tornado.web.StaticFileHandler, {'path': static_path}), - ], debug=False) + ], debug=debug) def start_web_server(args): @@ -177,7 +181,7 @@ def start_web_server(args): _LOGGER.info("Starting dashboard web server on port %s and configuration dir %s...", args.port, CONFIG_DIR) - app = make_app() + app = make_app(args.verbose) app.listen(args.port) try: tornado.ioloop.IOLoop.current().start() diff --git a/esphomeyaml/dashboard/templates/index.html b/esphomeyaml/dashboard/templates/index.html index 903b5cb0f4..4cbfc1f565 100644 --- a/esphomeyaml/dashboard/templates/index.html +++ b/esphomeyaml/dashboard/templates/index.html @@ -121,9 +121,9 @@ } .modal { - width: 90%; - max-height: 85%; - height: 80% !important; + width: 95%; + max-height: 90%; + height: 85% !important; } .page-footer { @@ -155,7 +155,9 @@ } .select-port-container { - margin-top: 19px; + margin-top: 8px; + margin-right: 24px; + width: 350px; } @@ -165,9 +167,23 @@ +
+
+
Select Upload Port
+

+ Here you can select where esphomeyaml will attempt to show logs and upload firmwares to. + By default, this is "OTA", or Over-The-Air. Note that you might have to restart the HassIO add-on + for new serial ports to be detected. +

+
+
+
@@ -201,64 +217,38 @@ @@ -303,7 +293,7 @@
- +
@@ -455,7 +445,7 @@ add -
+
Set up your first Node

@@ -505,19 +495,67 @@ }; let configuration = ""; - const ws_url = 'ws://' + window.location.hostname + ':' + window.location.port; + let wsProtocol = "ws:"; + if (window.location.protocol === "https:") { + wsProtocol = 'wss:'; + } + const wsUrl = wsProtocol + '//' + window.location.hostname + ':' + window.location.port; + + const portSelect = document.querySelector('.nav-wrapper select'); + let ports = []; + + const fetchSerialPorts = (begin=false) => { + fetch('/serial-ports').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; + } + + 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) + M.toast({html: "Discovered new serial port."}); + }); + }; + + const getUploadPort = () => { + const inst = M.FormSelect.getInstance(portSelect); + if (inst === undefined) { + return "OTA"; + } + + inst._setSelectedStates(); + return inst.getSelectedValues()[0]; + }; + setInterval(fetchSerialPorts, 2500); + fetchSerialPorts(true); const logsModalElem = document.getElementById("modal-logs"); - const logsPortSelect = logsModalElem.querySelector('select'); - const logsPortDiv = logsModalElem.querySelector(".upload-port"); - const logsPortSubmit = logsModalElem.querySelector('.upload-port-submit'); - let logsStart = undefined; - - logsPortSubmit.addEventListener('click', () => { - const inst = M.FormSelect.getInstance(logsPortSelect); - logsStart(inst.getSelectedValues()[0]); - inst.destroy(); - }); document.querySelectorAll(".action-show-logs").forEach((showLogs) => { showLogs.addEventListener('click', (e) => { @@ -525,145 +563,15 @@ const modalInstance = M.Modal.getInstance(logsModalElem); const log = logsModalElem.querySelector(".log"); log.innerHTML = ""; - - if (M.FormSelect.getInstance(logsPortSelect) !== undefined) { - M.FormSelect.getInstance(logsPortSelect).destroy(); - } + const stopLogsButton = logsModalElem.querySelector(".stop-logs"); + let stopped = false; + stopLogsButton.innerHTML = "Stop"; modalInstance.open(); - if (logsPortDiv.classList.contains('hide')) { - logsPortDiv.classList.remove('hide'); - } + const filenameField = logsModalElem.querySelector('.filename'); + filenameField.innerHTML = configuration; - logsStart = (port) => { - logsPortDiv.classList.add('hide'); - const logSocket = new WebSocket(ws_url + "/logs"); - logSocket.addEventListener('message', (event) => { - const data = JSON.parse(event.data); - if (data.event === "line") { - const msg = data.data; - log.innerHTML += colorReplace(msg); - } else if (data.event === "exit") { - if (data.code === 0) { - M.toast({html: "Program exited successfully!"}); - } else { - M.toast({html: `Program failed with code ${data.code}`}); - } - } - }); - logSocket.addEventListener('open', () => { - const msg = JSON.stringify({configuration: configuration, port: port}); - logSocket.send(msg); - }); - logSocket.addEventListener('close', () => { - M.toast({html: 'Terminated process.'}); - }); - modalInstance.options.onCloseStart = () => { - logSocket.close(); - }; - }; - - fetch('/serial-ports').then(res => res.json()) - .then(response => { - if (response.length > 1) { - logsPortSelect.innerHTML = ""; - for (let i = 0; i < response.length; i++) { - const val = response[i]; - logsPortSelect.innerHTML += ``; - } - M.FormSelect.init(logsPortSelect, {}); - } else { - logsStart("OTA"); - } - }); - }); - }); - - const uploadModalElem = document.getElementById("modal-upload"); - const uploadPortSelect = uploadModalElem.querySelector('select'); - const uploadPortDiv = uploadModalElem.querySelector(".upload-port"); - const uploadPortSubmit = uploadModalElem.querySelector('.upload-port-submit'); - let uploadStart = undefined; - - uploadPortSubmit.addEventListener('click', () => { - const inst = M.FormSelect.getInstance(uploadPortSelect); - uploadStart(inst.getSelectedValues()[0]); - inst.destroy(); - }); - - document.querySelectorAll(".action-upload").forEach((showLogs) => { - showLogs.addEventListener('click', (e) => { - configuration = e.target.getAttribute('data-node'); - const modalInstance = M.Modal.getInstance(uploadModalElem); - const log = uploadModalElem.querySelector(".log"); - log.innerHTML = ""; - - if (M.FormSelect.getInstance(uploadPortSelect) !== undefined) { - M.FormSelect.getInstance(uploadPortSelect).destroy(); - } - modalInstance.open(); - - if (uploadPortDiv.classList.contains('hide')) { - uploadPortDiv.classList.remove('hide'); - } - - uploadStart = (port) => { - uploadPortDiv.classList.add('hide'); - const logSocket = new WebSocket(ws_url + "/run"); - logSocket.addEventListener('message', (event) => { - const data = JSON.parse(event.data); - if (data.event === "line") { - const msg = data.data; - log.innerHTML += colorReplace(msg); - } else if (data.event === "exit") { - if (data.code === 0) { - M.toast({html: "Program exited successfully!"}); - } else { - M.toast({html: `Program failed with code ${data.code}`}); - } - } - }); - logSocket.addEventListener('open', () => { - const msg = JSON.stringify({configuration: configuration, port: port}); - logSocket.send(msg); - }); - logSocket.addEventListener('close', () => { - M.toast({html: 'Terminated process.'}); - }); - modalInstance.options.onCloseStart = () => { - logSocket.close(); - }; - }; - - fetch('/serial-ports').then(res => res.json()) - .then(response => { - if (response.length > 1) { - uploadPortSelect.innerHTML = ""; - for (let i = 0; i < response.length; i++) { - const val = response[i]; - uploadPortSelect.innerHTML += ``; - } - M.FormSelect.init(uploadPortSelect, {}); - } else { - uploadStart("OTA"); - } - }); - }); - }); - - const compileModalElem = document.getElementById("modal-compile"); - const downloadButton = compileModalElem.querySelector('.download-binary'); - - document.querySelectorAll(".action-compile").forEach((showLogs) => { - showLogs.addEventListener('click', (e) => { - configuration = e.target.getAttribute('data-node'); - const modalInstance = M.Modal.getInstance(compileModalElem); - const log = compileModalElem.querySelector(".log"); - log.innerHTML = ""; - downloadButton.classList.add('disabled'); - modalInstance.open(); - - const logSocket = new WebSocket(ws_url + "/compile"); + const logSocket = new WebSocket(wsUrl + "/logs"); logSocket.addEventListener('message', (event) => { const data = JSON.parse(event.data); if (data.event === "line") { @@ -671,19 +579,23 @@ log.innerHTML += colorReplace(msg); } else if (data.event === "exit") { if (data.code === 0) { - M.toast({html: "Program exited successfully!"}); - downloadButton.classList.remove('disabled'); + M.toast({html: "Program exited successfully."}); } else { M.toast({html: `Program failed with code ${data.code}`}); } + + stopLogsButton.innerHTML = "Close"; + stopped = true; } }); logSocket.addEventListener('open', () => { - const msg = JSON.stringify({configuration: configuration}); + const msg = JSON.stringify({configuration: configuration, port: getUploadPort()}); logSocket.send(msg); }); logSocket.addEventListener('close', () => { - M.toast({html: 'Terminated process.'}); + if (!stopped) { + M.toast({html: 'Terminated process.'}); + } }); modalInstance.options.onCloseStart = () => { logSocket.close(); @@ -691,6 +603,105 @@ }); }); + 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(); + + 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}`}); + } + + 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(); + }; + }); + }); + + 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'); + + modalInstance.open(); + + 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}`}); + } + + 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; @@ -722,7 +733,7 @@ {% if len(files) == 0 %} {% end %} +{% if begin %} + +{% end %} + \ No newline at end of file diff --git a/esphomeyaml/helpers.py b/esphomeyaml/helpers.py index 42dc2478ea..f6313cf097 100644 --- a/esphomeyaml/helpers.py +++ b/esphomeyaml/helpers.py @@ -450,10 +450,12 @@ def flush_tasks(): raise ESPHomeYAMLError("Circular dependency detected!") task, domain = _TASKS.popleft() + _LOGGER.debug("Executing task for domain=%s", domain) try: task.next() _TASKS.append((task, domain)) except StopIteration: + _LOGGER.debug(" -> %s finished", domain) pass @@ -461,6 +463,7 @@ def add(expression, require=True): if require and isinstance(expression, Expression): expression.require() _EXPRESSIONS.append(expression) + _LOGGER.debug("Adding: %s", statement(expression)) return expression