mirror of
https://github.com/esphome/esphome.git
synced 2024-11-22 11:47:30 +01:00
Fix dashboard upload port selection
This commit is contained in:
parent
967aa53bad
commit
f4d393a59e
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@ -165,9 +167,23 @@
|
||||
<nav>
|
||||
<div class="nav-wrapper indigo">
|
||||
<a href="#" class="brand-logo left">esphomeyaml Dashboard</a>
|
||||
<div class="select-port-container right" id="select-port-target">
|
||||
<select></select>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="tap-target pink lighten-1 select-port" data-target="select-port-target">
|
||||
<div class="tap-target-content">
|
||||
<h5>Select Upload Port</h5>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ribbon"></div>
|
||||
</header>
|
||||
|
||||
@ -201,64 +217,38 @@
|
||||
|
||||
<div id="modal-logs" class="modal modal-fixed-footer">
|
||||
<div class="modal-content">
|
||||
<h4>Show Logs</h4>
|
||||
<div class="upload-port row">
|
||||
<div class="col s12">
|
||||
<h5>Found multiple serial ports, please choose one:</h5>
|
||||
</div>
|
||||
<div class="input-field col s8">
|
||||
<select></select>
|
||||
</div>
|
||||
<div class="col s4 select-port-container">
|
||||
<button class="btn waves-effect waves-light upload-port-submit" type="submit" name="action">Select
|
||||
<i class="material-icons right">send</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<h4>Show Logs <code class="inlinecode filename"></code></h4>
|
||||
<div class="log-container">
|
||||
<pre class="log"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="modal-close waves-effect waves-green btn-flat">Close</a>
|
||||
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="modal-upload" class="modal modal-fixed-footer">
|
||||
<div class="modal-content">
|
||||
<h4>Compile And Upload</h4>
|
||||
<div class="upload-port row">
|
||||
<div class="col s12">
|
||||
<h5>Found multiple upload options, please choose one:</h5>
|
||||
</div>
|
||||
<div class="input-field col s8">
|
||||
<select></select>
|
||||
</div>
|
||||
<div class="col s4 select-port-container">
|
||||
<button class="btn waves-effect waves-light upload-port-submit" type="submit" name="action">Select
|
||||
<i class="material-icons right">send</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<h4>Compile And Upload <code class="inlinecode filename"></code></h4>
|
||||
<div class="log-container">
|
||||
<pre class="log"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="modal-close waves-effect waves-green btn-flat">Stop</a>
|
||||
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="modal-compile" class="modal modal-fixed-footer">
|
||||
<div class="modal-content">
|
||||
<h4>Compile</h4>
|
||||
<h4>Compile <code class="inlinecode filename"></code></h4>
|
||||
<div class="log-container">
|
||||
<pre class="log"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="modal-close waves-effect waves-green btn-flat disabled download-binary">Download Binary</a>
|
||||
<a class="modal-close waves-effect waves-green btn-flat">Stop</a>
|
||||
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -303,7 +293,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-actions">
|
||||
<button class="waves-effect waves-dark btn indigo next-step"">CONTINUE</button>
|
||||
<button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@ -455,7 +445,7 @@
|
||||
<i class="material-icons">add</i>
|
||||
</a>
|
||||
|
||||
<div class="tap-target pink lighten-1" data-target="setup-wizard-start">
|
||||
<div class="tap-target pink lighten-1 setup-wizard" data-target="setup-wizard-start">
|
||||
<div class="tap-target-content">
|
||||
<h5>Set up your first Node</h5>
|
||||
<p>
|
||||
@ -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 += `<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)
|
||||
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,19 +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");
|
||||
const logSocket = new WebSocket(wsUrl + "/logs");
|
||||
logSocket.addEventListener('message', (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.event === "line") {
|
||||
@ -545,71 +579,47 @@
|
||||
log.innerHTML += colorReplace(msg);
|
||||
} else if (data.event === "exit") {
|
||||
if (data.code === 0) {
|
||||
M.toast({html: "Program exited successfully!"});
|
||||
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: port});
|
||||
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();
|
||||
};
|
||||
};
|
||||
|
||||
fetch('/serial-ports').then(res => res.json())
|
||||
.then(response => {
|
||||
if (response.length > 1) {
|
||||
logsPortSelect.innerHTML = "";
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const val = response[i];
|
||||
logsPortSelect.innerHTML += `<option value="${val.port}">${val.port} (${val.desc})</option>`;
|
||||
}
|
||||
M.FormSelect.init(logsPortSelect, {});
|
||||
} else {
|
||||
logsStart("OTA");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const uploadModalElem = document.getElementById("modal-upload");
|
||||
const uploadPortSelect = uploadModalElem.querySelector('select');
|
||||
const uploadPortDiv = uploadModalElem.querySelector(".upload-port");
|
||||
const uploadPortSubmit = uploadModalElem.querySelector('.upload-port-submit');
|
||||
let uploadStart = undefined;
|
||||
|
||||
uploadPortSubmit.addEventListener('click', () => {
|
||||
const inst = M.FormSelect.getInstance(uploadPortSelect);
|
||||
uploadStart(inst.getSelectedValues()[0]);
|
||||
inst.destroy();
|
||||
});
|
||||
|
||||
document.querySelectorAll(".action-upload").forEach((showLogs) => {
|
||||
showLogs.addEventListener('click', (e) => {
|
||||
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 = "";
|
||||
|
||||
if (M.FormSelect.getInstance(uploadPortSelect) !== undefined) {
|
||||
M.FormSelect.getInstance(uploadPortSelect).destroy();
|
||||
}
|
||||
const stopLogsButton = uploadModalElem.querySelector(".stop-logs");
|
||||
let stopped = false;
|
||||
stopLogsButton.innerHTML = "Stop";
|
||||
modalInstance.open();
|
||||
|
||||
if (uploadPortDiv.classList.contains('hide')) {
|
||||
uploadPortDiv.classList.remove('hide');
|
||||
}
|
||||
const filenameField = uploadModalElem.querySelector('.filename');
|
||||
filenameField.innerHTML = configuration;
|
||||
|
||||
uploadStart = (port) => {
|
||||
uploadPortDiv.classList.add('hide');
|
||||
const logSocket = new WebSocket(ws_url + "/run");
|
||||
const logSocket = new WebSocket(wsUrl + "/run");
|
||||
logSocket.addEventListener('message', (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.event === "line") {
|
||||
@ -617,53 +627,50 @@
|
||||
log.innerHTML += colorReplace(msg);
|
||||
} else if (data.event === "exit") {
|
||||
if (data.code === 0) {
|
||||
M.toast({html: "Program exited successfully!"});
|
||||
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: port});
|
||||
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();
|
||||
};
|
||||
};
|
||||
|
||||
fetch('/serial-ports').then(res => res.json())
|
||||
.then(response => {
|
||||
if (response.length > 1) {
|
||||
uploadPortSelect.innerHTML = "";
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
const val = response[i];
|
||||
uploadPortSelect.innerHTML += `<option value="${val.port}">${val.port} (${val.desc})</option>`;
|
||||
}
|
||||
M.FormSelect.init(uploadPortSelect, {});
|
||||
} else {
|
||||
uploadStart("OTA");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const compileModalElem = document.getElementById("modal-compile");
|
||||
const downloadButton = compileModalElem.querySelector('.download-binary');
|
||||
|
||||
document.querySelectorAll(".action-compile").forEach((showLogs) => {
|
||||
showLogs.addEventListener('click', (e) => {
|
||||
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 logSocket = new WebSocket(ws_url + "/compile");
|
||||
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") {
|
||||
@ -671,11 +678,14 @@
|
||||
log.innerHTML += colorReplace(msg);
|
||||
} else if (data.event === "exit") {
|
||||
if (data.code === 0) {
|
||||
M.toast({html: "Program exited successfully!"});
|
||||
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', () => {
|
||||
@ -683,14 +693,15 @@
|
||||
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 %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tapTargetElem = document.querySelector('.tap-target');
|
||||
const tapTargetElem = document.querySelector('.tap-target.setup-wizard');
|
||||
const tapTargetInstance = M.TapTarget.getInstance(tapTargetElem);
|
||||
tapTargetInstance.options.onOpen = () => {
|
||||
$('.tap-target-origin').on('click', () => {
|
||||
@ -734,5 +745,23 @@
|
||||
</script>
|
||||
{% end %}
|
||||
|
||||
{% if begin %}
|
||||
<script>
|
||||
window.history.replaceState({}, document.title, "/");
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tapTargetElem = document.querySelector('.tap-target.select-port');
|
||||
const tapTargetInstance = M.TapTarget.getInstance(tapTargetElem);
|
||||
tapTargetInstance.open();
|
||||
|
||||
tapTargetInstance.contentEl.style["top"] = "300px";
|
||||
tapTargetInstance.contentEl.style["padding"] = "250px";
|
||||
tapTargetInstance.waveEl.style["top"] = "250px";
|
||||
tapTargetInstance.waveEl.style["left"] = "250px";
|
||||
tapTargetInstance.waveEl.style["width"] = "300px";
|
||||
tapTargetInstance.waveEl.style["height"] = "300px";
|
||||
});
|
||||
</script>
|
||||
{% end %}
|
||||
|
||||
</body>
|
||||
</html>
|
@ -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
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user