mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +00:00 
			
		
		
		
	Fix dashboard upload port selection
This commit is contained in:
		| @@ -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 | ||||
|  | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user