2018-05-21 16:40:22 +02:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<title>esphomeyaml Dashboard</title>
|
|
|
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css">
|
|
|
|
|
|
|
|
<link rel="stylesheet" href="/static/materialize-stepper.min.css">
|
|
|
|
|
|
|
|
<!-- jQuery :( -->
|
|
|
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
|
|
|
|
<script src="https://code.jquery.com/ui/1.8.5/jquery-ui.min.js" integrity="sha256-fOse6WapxTrUSJOJICXXYwHRJOPa6C1OUQXi7C9Ddy8=" crossorigin="anonymous"></script>
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
|
|
|
|
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.15.0/jquery.validate.min.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
<script src="/static/materialize-stepper.min.js"></script>
|
|
|
|
|
2018-11-23 21:49:23 +01:00
|
|
|
<link rel="shortcut icon" href="/static/favicon.ico">
|
2018-11-23 21:41:21 +01:00
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
<style>
|
|
|
|
nav .brand-logo {
|
|
|
|
margin-left: 48px;
|
|
|
|
font-size: 20px;
|
|
|
|
}
|
|
|
|
|
|
|
|
main .container {
|
|
|
|
margin-top: -12vh;
|
|
|
|
flex-shrink: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.ribbon {
|
|
|
|
width: 100%;
|
|
|
|
height: 17vh;
|
|
|
|
background-color: #3F51B5;
|
|
|
|
flex-shrink: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.ribbon-fab:not(.tap-target-origin) {
|
|
|
|
position: absolute;
|
|
|
|
right: 24px;
|
|
|
|
top: calc(17vh + 34px);
|
|
|
|
}
|
|
|
|
|
|
|
|
i.very-large {
|
|
|
|
font-size: 8rem;
|
|
|
|
padding-top: 2px;
|
|
|
|
color: #424242;
|
|
|
|
}
|
|
|
|
|
|
|
|
.card .card-content {
|
|
|
|
padding-left: 18px;
|
|
|
|
padding-bottom: 10px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.log {
|
|
|
|
background-color: #1c1c1c;
|
|
|
|
margin-top: 0;
|
|
|
|
margin-bottom: 0;
|
|
|
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
|
|
font-size: 12px;
|
|
|
|
padding: 16px;
|
|
|
|
overflow: auto;
|
|
|
|
line-height: 1.45;
|
|
|
|
border-radius: 3px;
|
2018-06-01 18:06:18 +02:00
|
|
|
white-space: pre-wrap;
|
|
|
|
overflow-wrap: break-word;
|
2018-05-21 16:40:22 +02:00
|
|
|
color: #DDD;
|
|
|
|
}
|
|
|
|
|
|
|
|
.inlinecode {
|
|
|
|
box-sizing: border-box;
|
|
|
|
padding: 0.2em 0.4em;
|
|
|
|
margin: 0;
|
|
|
|
font-size: 85%;
|
|
|
|
background-color: rgba(27,31,35,0.05);
|
|
|
|
border-radius: 3px;
|
|
|
|
font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
|
|
|
|
}
|
|
|
|
|
|
|
|
.log.bold {
|
|
|
|
font-weight: bold;
|
|
|
|
}
|
|
|
|
|
|
|
|
.log .v {
|
|
|
|
color: #888888;
|
|
|
|
}
|
|
|
|
|
|
|
|
.log .d {
|
|
|
|
color: #00DDDD;
|
|
|
|
}
|
|
|
|
|
|
|
|
.log .c {
|
|
|
|
color: magenta;
|
|
|
|
}
|
|
|
|
|
|
|
|
.log .i {
|
|
|
|
color: limegreen;
|
|
|
|
}
|
|
|
|
|
|
|
|
.log .w {
|
|
|
|
color: yellow;
|
|
|
|
}
|
|
|
|
|
|
|
|
.log .e {
|
|
|
|
color: red;
|
|
|
|
font-weight: bold;
|
|
|
|
}
|
|
|
|
|
|
|
|
.log .e {
|
|
|
|
color: red;
|
|
|
|
}
|
|
|
|
|
|
|
|
.log .ww {
|
|
|
|
color: white;
|
|
|
|
}
|
|
|
|
|
|
|
|
.modal {
|
2018-06-03 11:18:53 +02:00
|
|
|
width: 95%;
|
|
|
|
max-height: 90%;
|
|
|
|
height: 85% !important;
|
2018-05-21 16:40:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
.page-footer {
|
|
|
|
padding-top: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
body {
|
|
|
|
display: flex;
|
|
|
|
min-height: 100vh;
|
|
|
|
flex-direction: column;
|
|
|
|
}
|
|
|
|
|
|
|
|
main {
|
|
|
|
flex: 1 0 auto;
|
|
|
|
}
|
|
|
|
|
|
|
|
ul.browser-default {
|
|
|
|
padding-left: 30px;
|
|
|
|
margin-top: 10px;
|
|
|
|
margin-bottom: 15px;
|
|
|
|
}
|
|
|
|
|
|
|
|
ul.browser-default li {
|
|
|
|
list-style-type: initial;
|
|
|
|
}
|
|
|
|
|
|
|
|
ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .step.done::before, ul.stepper.horizontal .step.active .step-title::before, ul.stepper.horizontal .step.done .step-title::before {
|
|
|
|
background-color: #3f51b5 !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
.select-port-container {
|
2018-06-03 11:18:53 +02:00
|
|
|
margin-top: 8px;
|
|
|
|
margin-right: 24px;
|
|
|
|
width: 350px;
|
2018-05-21 16:40:22 +02:00
|
|
|
}
|
2018-10-04 19:01:02 +02:00
|
|
|
|
|
|
|
.dropdown-trigger {
|
|
|
|
cursor: pointer;
|
|
|
|
}
|
2018-11-19 22:12:24 +01:00
|
|
|
|
|
|
|
/* https://github.com/tnhu/status-indicator/blob/master/styles.css */
|
|
|
|
.status-indicator .status-indicator-icon {
|
|
|
|
display: inline-block;
|
|
|
|
border-radius: 50%;
|
|
|
|
width: 10px;
|
|
|
|
height: 10px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.status-indicator.unknown .status-indicator-icon {
|
|
|
|
background-color: rgb(216, 226, 233);
|
|
|
|
}
|
|
|
|
|
|
|
|
.status-indicator.unknown .status-indicator-text::after {
|
|
|
|
content: "Unknown status";
|
|
|
|
}
|
|
|
|
|
|
|
|
.status-indicator.offline .status-indicator-icon {
|
|
|
|
background-color: rgb(255, 77, 77);
|
|
|
|
}
|
|
|
|
|
|
|
|
.status-indicator.offline .status-indicator-text::after {
|
|
|
|
content: "Offline";
|
|
|
|
}
|
|
|
|
|
|
|
|
.status-indicator.not-responding .status-indicator-icon {
|
|
|
|
background-color: rgb(255, 170, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
.status-indicator.not-responding .status-indicator-text::after {
|
|
|
|
content: "Not Responding";
|
|
|
|
}
|
|
|
|
|
|
|
|
@keyframes status-indicator-pulse-online {
|
|
|
|
0% {
|
|
|
|
box-shadow: 0 0 0 0 rgba(75, 210, 143, .5);
|
|
|
|
}
|
|
|
|
25% {
|
|
|
|
box-shadow: 0 0 0 10px rgba(75, 210, 143, 0);
|
|
|
|
}
|
|
|
|
30% {
|
|
|
|
box-shadow: 0 0 0 0 rgba(75, 210, 143, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.status-indicator.online .status-indicator-icon {
|
|
|
|
background-color: rgb(75, 210, 143);
|
|
|
|
animation-duration: 5s;
|
|
|
|
animation-timing-function: ease-in-out;
|
|
|
|
animation-iteration-count: infinite;
|
|
|
|
animation-direction: normal;
|
|
|
|
animation-delay: 0s;
|
|
|
|
animation-fill-mode: none;
|
|
|
|
animation-name: status-indicator-pulse-online;
|
|
|
|
}
|
|
|
|
|
|
|
|
.status-indicator.online .status-indicator-text::after {
|
|
|
|
content: "Online";
|
|
|
|
}
|
2018-05-21 16:40:22 +02:00
|
|
|
</style>
|
2018-08-25 22:18:22 +02:00
|
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
2018-05-21 16:40:22 +02:00
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
|
|
|
|
<header>
|
|
|
|
<nav>
|
|
|
|
<div class="nav-wrapper indigo">
|
|
|
|
<a href="#" class="brand-logo left">esphomeyaml Dashboard</a>
|
2018-06-03 11:18:53 +02:00
|
|
|
<div class="select-port-container right" id="select-port-target">
|
|
|
|
<select></select>
|
|
|
|
</div>
|
2018-05-21 16:40:22 +02:00
|
|
|
</div>
|
|
|
|
</nav>
|
|
|
|
|
2018-06-03 11:18:53 +02:00
|
|
|
<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>
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
<div class="ribbon"></div>
|
|
|
|
</header>
|
|
|
|
|
|
|
|
<main>
|
|
|
|
<div class="container">
|
2018-11-19 22:12:24 +01:00
|
|
|
{% for i, entry in enumerate(entries) %}
|
2018-05-21 16:40:22 +02:00
|
|
|
<div class="row">
|
|
|
|
<div class="col s8 offset-s2 m10 offset-m1 l12">
|
|
|
|
<div class="card horizontal">
|
|
|
|
<div class="card-image center-align">
|
|
|
|
<i class="material-icons very-large icon-grey">memory</i>
|
|
|
|
</div>
|
|
|
|
<div class="card-stacked">
|
|
|
|
<div class="card-content">
|
2018-10-04 19:01:02 +02:00
|
|
|
<span class="card-title">
|
2018-11-19 22:12:24 +01:00
|
|
|
{{ escape(entry.name) }}
|
2018-10-04 19:01:02 +02:00
|
|
|
<i class="material-icons right dropdown-trigger" data-target="dropdown-{{ i }}">more_vert</i>
|
|
|
|
</span>
|
2018-05-21 16:40:22 +02:00
|
|
|
<p>
|
2018-11-19 22:12:24 +01:00
|
|
|
<span class="status-indicator unknown" data-node="{{ entry.filename }}">
|
|
|
|
<span class="status-indicator-icon"></span>
|
|
|
|
<span class="status-indicator-text"></span></span>. Full path: <code class="inlinecode">{{ escape(entry.full_path) }}</code>
|
2018-05-21 16:40:22 +02:00
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<div class="card-action">
|
2018-11-19 22:12:24 +01:00
|
|
|
<a href="#" class="action-upload" data-node="{{ entry.filename }}">Upload</a>
|
|
|
|
<a href="#" class="action-compile" data-node="{{ entry.filename }}">Compile</a>
|
|
|
|
<a href="#" class="action-show-logs" data-node="{{ entry.filename }}">Show Logs</a>
|
|
|
|
<a href="#" class="action-validate" data-node="{{ entry.filename }}">Validate</a>
|
2018-05-21 16:40:22 +02:00
|
|
|
</div>
|
2018-10-04 19:01:02 +02:00
|
|
|
<ul id="dropdown-{{ i }}" class="dropdown-content">
|
2018-11-19 22:12:24 +01:00
|
|
|
<li><a href="#" class="action-clean-mqtt" data-node="{{ entry.filename }}">Clean MQTT</a></li>
|
|
|
|
<li><a href="#" class="action-clean" data-node="{{ entry.filename }}">Clean Build</a></li>
|
|
|
|
<li><a href="#" class="action-hass-config" data-node="{{ entry.filename }}">Home Assistant Configuration</a></li>
|
2018-10-04 19:01:02 +02:00
|
|
|
</ul>
|
2018-05-21 16:40:22 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{% end %}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div id="modal-logs" class="modal modal-fixed-footer">
|
|
|
|
<div class="modal-content">
|
2018-06-03 11:18:53 +02:00
|
|
|
<h4>Show Logs <code class="inlinecode filename"></code></h4>
|
2018-05-21 16:40:22 +02:00
|
|
|
<div class="log-container">
|
|
|
|
<pre class="log"></pre>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
2018-06-03 11:18:53 +02:00
|
|
|
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Close</a>
|
2018-05-21 16:40:22 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div id="modal-upload" class="modal modal-fixed-footer">
|
|
|
|
<div class="modal-content">
|
2018-06-03 11:18:53 +02:00
|
|
|
<h4>Compile And Upload <code class="inlinecode filename"></code></h4>
|
2018-05-21 16:40:22 +02:00
|
|
|
<div class="log-container">
|
|
|
|
<pre class="log"></pre>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
2018-06-03 11:18:53 +02:00
|
|
|
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
|
2018-05-21 16:40:22 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div id="modal-compile" class="modal modal-fixed-footer">
|
|
|
|
<div class="modal-content">
|
2018-06-03 11:18:53 +02:00
|
|
|
<h4>Compile <code class="inlinecode filename"></code></h4>
|
2018-05-21 16:40:22 +02:00
|
|
|
<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>
|
2018-06-03 11:18:53 +02:00
|
|
|
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
|
2018-05-21 16:40:22 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2018-06-03 12:16:43 +02:00
|
|
|
<div id="modal-validate" class="modal modal-fixed-footer">
|
|
|
|
<div class="modal-content">
|
|
|
|
<h4>Validate <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-logs">Stop</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
<div id="modal-wizard" class="modal">
|
|
|
|
<div class="modal-content">
|
|
|
|
<form action="/wizard.html" method="POST">
|
|
|
|
<ul class="stepper linear">
|
|
|
|
<li class="step active">
|
|
|
|
<div class="step-title waves-effect">Introduction And Name</div>
|
|
|
|
<div class="step-content">
|
|
|
|
<div class="row">
|
|
|
|
<p>
|
|
|
|
Hi there! I'm the esphomeyaml setup wizard and will guide you through setting up
|
|
|
|
your first ESP8266 or ESP32-powered device using esphomeyaml.
|
|
|
|
</p>
|
|
|
|
<a href="https://www.espressif.com/en/products/hardware/esp8266ex/overview" target="_blank">ESP8266s</a> and
|
|
|
|
their successors (the <a href="https://www.espressif.com/en/products/hardware/esp32/overview" target="_blank">ESP32s</a>)
|
|
|
|
are great low-cost microcontrollers that can communicate with the outside world using WiFi.
|
|
|
|
They're found in many devices such as the popular Sonoff/iTead, but also exist as development boards
|
2018-06-03 12:16:43 +02:00
|
|
|
such as the <a href="https://esphomelib.com/esphomeyaml/devices/nodemcu_esp8266.html" target="_blank">NodeMCU</a>.
|
2018-05-21 16:40:22 +02:00
|
|
|
<p>
|
|
|
|
</p>
|
|
|
|
<a href="https://esphomelib.com/esphomeyaml/index.html" target="_blank">esphomeyaml</a>,
|
|
|
|
the tool you're using here, creates custom firmwares for these devices using YAML configuration
|
|
|
|
files (similar to the ones you might be used to with Home Assistant).
|
|
|
|
<p>
|
|
|
|
</p>
|
|
|
|
This wizard will create a basic YAML configuration file for your "node" (the microcontroller).
|
|
|
|
Later, you will be able to customize this file and add some of
|
|
|
|
<a href="https://github.com/OttoWinter/esphomelib" target="_blank">esphomelib's</a>
|
|
|
|
many integrations.
|
|
|
|
<p>
|
|
|
|
<p>
|
|
|
|
First, I need to know what this node should be called. Choose this name wisely, changing this
|
|
|
|
later makes Over-The-Air Update attempts difficult.
|
2018-08-13 19:11:33 +02:00
|
|
|
Names must be <strong>lowercase</strong> and <strong>must not contain spaces</strong> (allowed characters: <code class="inlinecode">a-z</code>,
|
|
|
|
<code class="inlinecode">0-9</code> and <code class="inlinecode">_</code>)
|
2018-05-21 16:40:22 +02:00
|
|
|
</p>
|
|
|
|
<div class="input-field col s12">
|
|
|
|
<input id="node_name" class="validate" type="text" name="name" required>
|
|
|
|
<label for="node_name">Name of node</label>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="step-actions">
|
2018-06-03 11:18:53 +02:00
|
|
|
<button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
|
2018-05-21 16:40:22 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
<li class="step">
|
|
|
|
<div class="step-title waves-effect">Device Type</div>
|
|
|
|
<div class="step-content">
|
|
|
|
<div class="row">
|
|
|
|
<p>
|
|
|
|
Great! Now I need to know what type of microcontroller you're using so that I can compile firmware for them.
|
2018-11-24 14:05:06 +01:00
|
|
|
Please choose the board you're using below. If you're not sure you can also use similar ones
|
|
|
|
or even the "Generic" option. In most cases that will work too.
|
2018-05-21 16:40:22 +02:00
|
|
|
</p>
|
|
|
|
<div class="input-field col s12">
|
2018-11-24 14:05:06 +01:00
|
|
|
<select id="board" name="board" required>
|
|
|
|
<optgroup label="ESP8266">
|
|
|
|
<option value="esp01_1m">Generic ESP8266 (for example Sonoff)</option>
|
|
|
|
<option value="nodemcuv2">NodeMCU</option>
|
|
|
|
<option value="d1_mini">Wemos D1 and Wemos D1 mini</option>
|
|
|
|
<option value="d1_mini_lite">Wemos D1 mini Lite</option>
|
|
|
|
<option value="d1_mini_pro">Wemos D1 mini Pro</option>
|
|
|
|
<option value="huzzah">Adafruit HUZZAH ESP8266</option>
|
|
|
|
<option value="oak">DigiStump Oak</option>
|
|
|
|
<option value="thing">Sparkfun ESP8266 Thing</option>
|
|
|
|
<option value="thingdev">Sparkfun ESP8266 Thing - Dev Board</option>
|
|
|
|
</optgroup>
|
|
|
|
<optgroup label="ESP32">
|
|
|
|
<option value="esp-wrover-kit">Generic ESP32 (WROVER Module)</option>
|
|
|
|
<option value="nodemcu-32s">NodeMCU-32S</option>
|
|
|
|
<option value="lolin_d32">Wemos Lolin D32</option>
|
|
|
|
<option value="lolin_d32_pro">Wemos Lolin D32 Pro</option>
|
|
|
|
<option value="featheresp32">Adafruit ESP32 Feather</option>
|
|
|
|
<option value="m5stack-core-esp32">M5Stack Core ESP32</option>
|
|
|
|
</optgroup>
|
|
|
|
<optgroup label="Other ESP8266s">
|
|
|
|
<option value="gen4iod">4D Systems gen4 IoD Range</option>
|
|
|
|
<option value="wifi_slot">Amperka WiFi Slot</option>
|
|
|
|
<option value="espduino">Doit ESPDuino</option>
|
|
|
|
<option value="espectro">DycodeX ESPectro Core</option>
|
|
|
|
<option value="espino">ESPino</option>
|
|
|
|
<option value="esp_wroom_02">Espressif ESP-WROOM-02 module</option>
|
|
|
|
<option value="esp12e">Espressif ESP-12E module</option>
|
|
|
|
<option value="esp01">Espressif ESP-01 512k module</option>
|
|
|
|
<option value="esp07">Espressif ESP-07 module</option>
|
|
|
|
<option value="esp8285">Generic ESP8285 module</option>
|
|
|
|
<option value="espresso_lite_v1">ESPert ESPresso Lite 1.0</option>
|
|
|
|
<option value="espresso_lite_v2">ESPert ESPresso Lite 2.0</option>
|
|
|
|
<option value="phoenix_v1">ESPert Phoenix 1.0</option>
|
|
|
|
<option value="wifinfo">WiFInfo</option>
|
|
|
|
<option value="heltec_wifi_kit_8">Heltec WiFi kit 8</option>
|
|
|
|
<option value="nodemcu">NodeMCU 0.9</option>
|
|
|
|
<option value="modwifi">Olimex MOD-WIFI</option>
|
|
|
|
<option value="wio_link">SeedStudio Wio Link</option>
|
|
|
|
<option value="wio_node">SeedStudio Wio Node</option>
|
|
|
|
<option value="sparkfunBlynk">Sparkfun Blynk Board</option>
|
|
|
|
<option value="esp210">SweetPea ESP-210</option>
|
|
|
|
<option value="espinotee">ThaiEasyElec ESPino</option>
|
|
|
|
<option value="d1">Wemos D1 Revision 1</option>
|
|
|
|
<option value="wifiduino">WiFiDuino</option>
|
|
|
|
<option value="xinabox_cw01">XinaBox CW01</option>
|
|
|
|
</optgroup>
|
|
|
|
<optgroup label="Other ESP32s">
|
|
|
|
<option value="lolin32">Wemos Lolin 32</option>
|
|
|
|
<option value="esp32dev">Espressif ESP32 Dev Module</option>
|
|
|
|
<option value="m5stack-fire">M5Stack FIRE</option>
|
|
|
|
<option value="wemosbat">Wemos WiFi & Bluetooth Battery</option>
|
|
|
|
<option value="node32s">Aiyarafun Node32s</option>
|
|
|
|
<option value="espea32">April Brother ESPea32</option>
|
|
|
|
<option value="firebeetle32">DFRobot FireBeetle-ESP32</option>
|
|
|
|
<option value="esp32doit-devkit-v1">Doit ESP32 Devkit v1</option>
|
|
|
|
<option value="pocket_32">Dongsen Tech Pocket 32</option>
|
|
|
|
<option value="espectro32">DycodeX ESPectro32</option>
|
|
|
|
<option value="esp32vn-iot-uno">ESP32vn IoT Uno</option>
|
|
|
|
<option value="esp320">Electronic SweetPeas ESP320</option>
|
|
|
|
<option value="pico32">Espressif ESP32 Pico Kit</option>
|
|
|
|
<option value="odroid_esp32">Hardkernel Odroid GO</option>
|
|
|
|
<option value="heltec_wifi_kit_32">Heltec WIFI Kit 32</option>
|
|
|
|
<option value="heltec_wifi_lora_32">Heltec WIFI LoRa 32</option>
|
|
|
|
<option value="hornbill32dev">Hornbill ESP32 Dev</option>
|
|
|
|
<option value="hornbill32minima">Hornbill ESP32 Minima</option>
|
|
|
|
<option value="intorobot">IntoRobot Fig</option>
|
|
|
|
<option value="mhetesp32devkit">MH-ET Live ESP32 Devkit</option>
|
|
|
|
<option value="mhetesp32minikit">MH-ET Live ESP32 Minikit</option>
|
|
|
|
<option value="nano32">MakerAsia Nano32</option>
|
|
|
|
<option value="microduino-core-esp32">Microduino Core ESP32</option>
|
|
|
|
<option value="quantum">Noduino Quantum</option>
|
|
|
|
<option value="esp32-evb">Olimex ESP32-EVB</option>
|
|
|
|
<option value="esp32-gateway">Olimex ESP32-GATEWAY</option>
|
|
|
|
<option value="esp32-pro">Olimex ESP32-PRO</option>
|
|
|
|
<option value="onehorse32dev">Onehorse ESP32 Dev Module</option>
|
|
|
|
<option value="alksesp32">RoboticsBrno ALKS ESP32</option>
|
|
|
|
<option value="esp32thing">Sparkfun ESP32 Thing</option>
|
|
|
|
<option value="ttgo-lora32-v1">TTGO LoRa32-OLED v1</option>
|
|
|
|
<option value="espino32">ThaiEasyElec ESPino32</option>
|
|
|
|
<option value="widora-air">Widora AIR</option>
|
|
|
|
<option value="xinabox_cw02">XinaBox CW02</option>
|
|
|
|
<option value="iotbusio">oddWires IoT-Bus Io</option>
|
|
|
|
<option value="iotbusproteus">oddWires Proteus IoT-Bus</option>
|
|
|
|
<option value="nina_w10">u-blox NINA-W10 series</option>
|
|
|
|
</optgroup>
|
2018-05-21 16:40:22 +02:00
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="step-actions">
|
|
|
|
<button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
<li class="step">
|
|
|
|
<div class="step-title waves-effect">WiFi And Over-The-Air Updates</div>
|
|
|
|
<div class="step-content">
|
|
|
|
<div class="row">
|
|
|
|
<p>
|
|
|
|
Thanks! Now I need to know what WiFi Access Point I should instruct the node to connect to.
|
|
|
|
Please enter an SSID (name of the WiFi network) and password (leave empty for no password).
|
|
|
|
</p>
|
|
|
|
<div class="input-field col s12">
|
|
|
|
<input id="wifi_ssid" class="validate" type="text" name="ssid" required>
|
|
|
|
<label for="wifi_ssid">WiFi SSID</label>
|
|
|
|
</div>
|
|
|
|
<div class="input-field col s12">
|
|
|
|
<input id="wifi_password" name="psk" type="password">
|
|
|
|
<label for="wifi_password">WiFi Password</label>
|
|
|
|
</div>
|
|
|
|
<p>
|
|
|
|
Esphomelib automatically sets up an Over-The-Air update server on the node
|
2018-06-03 12:16:43 +02:00
|
|
|
so that you only need to flash a firmware via USB once.
|
|
|
|
Optionally, you can set a password for this upload process here:
|
2018-05-21 16:40:22 +02:00
|
|
|
</p>
|
|
|
|
<div class="input-field col s12">
|
|
|
|
<input id="ota_password" class="validate" name="ota_password" type="password">
|
|
|
|
<label for="ota_password">OTA Password</label>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="step-actions">
|
|
|
|
<button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
<li class="step">
|
|
|
|
<div class="step-title waves-effect">MQTT</div>
|
|
|
|
<div class="step-content">
|
|
|
|
<div class="row">
|
2018-11-23 18:57:13 +01:00
|
|
|
{% if mqtt_config is None %}
|
|
|
|
<p>
|
|
|
|
esphomelib connects to your Home Assistant instance via
|
|
|
|
<a href="https://www.home-assistant.io/docs/mqtt/">MQTT</a>.
|
|
|
|
If you haven't already, please set up
|
|
|
|
MQTT on your Home Assistant server, for example with the
|
|
|
|
<a href="https://www.home-assistant.io/addons/mosquitto/">Mosquitto Hass.io Add-on</a>.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
When you're done with that, please enter your MQTT broker here. For example
|
|
|
|
<code class="inlinecode">192.168.1.100</code>.
|
|
|
|
Please also specify the MQTT username and password you wish esphomelib to use
|
|
|
|
(leave them empty if you're not using any authentication).
|
|
|
|
</p>
|
|
|
|
{% else %}
|
2018-11-24 09:02:37 +01:00
|
|
|
<p>
|
|
|
|
esphomelib connects to your Home Assistant instance via
|
|
|
|
<a href="https://www.home-assistant.io/docs/mqtt/">MQTT</a>. In this section you will have
|
|
|
|
to tell esphomelib which MQTT "broker" to use.
|
|
|
|
|
|
|
|
</p>
|
2018-11-23 18:57:13 +01:00
|
|
|
<p>
|
2018-11-23 23:19:54 +01:00
|
|
|
It looks like you've already set up MQTT, the values below are taken from your HassIO MQTT add-on.
|
2018-11-24 09:02:37 +01:00
|
|
|
Please confirm they are correct and press CONTINUE.
|
2018-11-23 18:57:13 +01:00
|
|
|
</p>
|
|
|
|
{% end %}
|
2018-05-21 16:40:22 +02:00
|
|
|
<div class="input-field col s12">
|
2018-11-23 18:57:13 +01:00
|
|
|
{% if mqtt_config is None %}
|
|
|
|
<input id="mqtt_broker" class="validate" type="text" name="broker" required>
|
|
|
|
{% else %}
|
|
|
|
<input id="mqtt_broker" class="validate" type="text" name="broker" value="{{ mqtt_config['host'] }}" required>
|
|
|
|
{% end %}
|
2018-05-21 16:40:22 +02:00
|
|
|
<label for="mqtt_broker">MQTT Broker</label>
|
|
|
|
</div>
|
|
|
|
<div class="input-field col s6">
|
2018-11-23 18:57:13 +01:00
|
|
|
{% if mqtt_config is None %}
|
|
|
|
<input id="mqtt_username" class="validate" type="text" name="mqtt_username">
|
|
|
|
{% else %}
|
|
|
|
<input id="mqtt_username" class="validate" type="text" name="mqtt_username" value="{{ mqtt_config['username'] }}">
|
|
|
|
{% end%}
|
2018-05-21 16:40:22 +02:00
|
|
|
<label for="mqtt_username">MQTT Username</label>
|
|
|
|
</div>
|
|
|
|
<div class="input-field col s6">
|
2018-11-23 18:57:13 +01:00
|
|
|
{% if mqtt_config is None %}
|
|
|
|
<input id="mqtt_password" class="validate" name="mqtt_password" type="password">
|
|
|
|
{% else %}
|
|
|
|
<input id="mqtt_password" class="validate" name="mqtt_password" type="password" value="{{ mqtt_config['password'] }}">
|
|
|
|
{% end %}
|
2018-05-21 16:40:22 +02:00
|
|
|
<label for="mqtt_password">MQTT Password</label>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="step-actions">
|
|
|
|
<button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
<li class="step">
|
|
|
|
<div class="step-title waves-effect">Done!</div>
|
|
|
|
<div class="step-content">
|
|
|
|
<p>
|
|
|
|
Hooray! 🎉🎉🎉 You've successfully created your first esphomeyaml configuration file.
|
|
|
|
When you click Submit, I will save this configuration file under
|
|
|
|
<code class="inlinecode"><HASS_CONFIG_FOLDER>/esphomeyaml/<NAME_OF_NODE>.yaml</code> and
|
|
|
|
you will be able to edit this file with the
|
|
|
|
<a href="https://www.home-assistant.io/addons/configurator/" target="_blank">HASS Configuratior add-on</a>.
|
|
|
|
</p>
|
|
|
|
<h5>Next steps</h5>
|
|
|
|
<ul class="browser-default">
|
|
|
|
<li>
|
|
|
|
Flash the firmware. This can be done using the “UPLOAD” option in the dashboard. See
|
2018-06-03 12:16:43 +02:00
|
|
|
<a href="https://esphomelib.com/esphomeyaml/index.html#devices" target="_blank">this</a>
|
2018-05-21 16:40:22 +02:00
|
|
|
for guides on how to flash different types of devices. Note that you need to restart this add-on
|
|
|
|
for newly plugged in serial devices to be detected.
|
|
|
|
</li>
|
2018-06-03 12:16:43 +02:00
|
|
|
<li>
|
|
|
|
With the current configuration, your node will only connect to WiFi and MQTT. To make it actually <i>do</i>
|
|
|
|
stuff, follow
|
|
|
|
<a href="https://esphomelib.com/esphomeyaml/guides/getting_started_hassio.html#adding-some-basic-features">
|
|
|
|
the rest of the getting started guide
|
|
|
|
</a>.
|
|
|
|
</li>
|
2018-05-21 16:40:22 +02:00
|
|
|
<li>
|
|
|
|
See the <a href="https://esphomelib.com/esphomeyaml/index.html" target="_blank">esphomeyaml index</a>
|
|
|
|
for a list of supported sensors/devices.
|
|
|
|
</li>
|
|
|
|
<li>
|
2018-06-03 12:16:43 +02:00
|
|
|
Join the <a href="https://discord.gg/KhAMKrd" target="_blank">Discord server</a> and say hi! When I
|
2018-05-21 16:40:22 +02:00
|
|
|
have time, I would be happy to help with issues and discuss new features.
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
Star <a href="https://github.com/OttoWinter/esphomelib" target="_blank">esphomelib</a> and
|
2018-06-03 12:16:43 +02:00
|
|
|
<a href="https://github.com/OttoWinter/esphomeyaml" target="_blank">esphomeyaml</a> on GitHub
|
|
|
|
if you find this software awesome and report issues using the bug trackers there.
|
2018-05-21 16:40:22 +02:00
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
<div class="step-actions">
|
|
|
|
<button class="waves-effect waves-dark btn indigo" type="submit">SUBMIT</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
|
|
<a href="#!" class="modal-close waves-effect waves-green btn-flat">Abort</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2018-10-04 19:01:02 +02:00
|
|
|
<div id="modal-clean-mqtt" class="modal modal-fixed-footer">
|
|
|
|
<div class="modal-content">
|
|
|
|
<h4>Clean MQTT discovery <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-logs">Stop</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2018-10-14 18:52:21 +02:00
|
|
|
<div id="modal-clean" class="modal modal-fixed-footer">
|
|
|
|
<div class="modal-content">
|
|
|
|
<h4>Clean Build Files <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-logs">Stop</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2018-11-03 14:08:31 +01:00
|
|
|
<div id="modal-hass-config" class="modal modal-fixed-footer">
|
|
|
|
<div class="modal-content">
|
|
|
|
<h4>Generate Home Assistant Configuration <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-logs">Stop</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
<a class="btn-floating btn-large ribbon-fab waves-effect waves-light pink accent-2" id="setup-wizard-start">
|
|
|
|
<i class="material-icons">add</i>
|
|
|
|
</a>
|
|
|
|
|
2018-06-03 11:18:53 +02:00
|
|
|
<div class="tap-target pink lighten-1 setup-wizard" data-target="setup-wizard-start">
|
2018-05-21 16:40:22 +02:00
|
|
|
<div class="tap-target-content">
|
|
|
|
<h5>Set up your first Node</h5>
|
|
|
|
<p>
|
|
|
|
Huh... It seems like you you don't have any esphomeyaml configuration files yet...
|
|
|
|
Fortunately, there's a setup wizard that will step you through setting up your first node 🎉
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</main>
|
|
|
|
|
|
|
|
<footer class="page-footer indigo darken-1">
|
|
|
|
<div class="container">
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<div class="footer-copyright">
|
|
|
|
<div class="container">
|
|
|
|
© 2018 Copyright Otto Winter, Made with <a class="grey-text text-lighten-4" href="https://materializecss.com/" target="_blank">Materialize</a>
|
2018-11-19 22:12:24 +01:00
|
|
|
<a class="grey-text text-lighten-4 right" href="{{ docs_link }}" target="_blank">esphomeyaml {{ version }} Documentation</a>
|
2018-05-21 16:40:22 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</footer>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
M.AutoInit(document.body);
|
|
|
|
});
|
|
|
|
|
|
|
|
const colorReplace = (input) => {
|
|
|
|
input = input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
|
|
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;)?32m/g, '<span class="i">');
|
|
|
|
input = input.replace(/\\033\[(?: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;)?35m/g, '<span class="c">');
|
|
|
|
input = input.replace(/\\033\[(?: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;)?37m/g, '<span class="v">');
|
|
|
|
input = input.replace(/\\033\[(?: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\[0m/g, '</span>');
|
|
|
|
|
|
|
|
return input;
|
|
|
|
};
|
|
|
|
|
|
|
|
let configuration = "";
|
2018-06-03 11:18:53 +02:00
|
|
|
let wsProtocol = "ws:";
|
|
|
|
if (window.location.protocol === "https:") {
|
|
|
|
wsProtocol = 'wss:';
|
|
|
|
}
|
|
|
|
const wsUrl = wsProtocol + '//' + window.location.hostname + ':' + window.location.port;
|
|
|
|
|
2018-11-19 22:12:24 +01:00
|
|
|
let isFetchingPing = false;
|
|
|
|
const fetchPing = () => {
|
|
|
|
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;
|
|
|
|
|
|
|
|
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';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.classList.contains(klass))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
node.classList.remove('unknown', 'online', 'offline', 'not-responding');
|
|
|
|
node.classList.add(klass);
|
|
|
|
}
|
|
|
|
|
|
|
|
isFetchingPing = false;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
setInterval(fetchPing, 2000);
|
|
|
|
fetchPing();
|
|
|
|
|
2018-06-03 11:18:53 +02:00
|
|
|
const portSelect = document.querySelector('.nav-wrapper select');
|
|
|
|
let ports = [];
|
|
|
|
|
|
|
|
const fetchSerialPorts = (begin=false) => {
|
2018-06-07 20:47:06 +02:00
|
|
|
fetch('/serial-ports', {credentials: "same-origin"}).then(res => res.json())
|
2018-06-03 11:18:53 +02:00
|
|
|
.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;
|
|
|
|
}
|
2018-10-04 19:01:02 +02:00
|
|
|
const hasNewPort = response.length >= ports.length;
|
2018-06-03 11:18:53 +02:00
|
|
|
|
|
|
|
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, {});
|
2018-10-04 19:01:02 +02:00
|
|
|
if (!begin && hasNewPort)
|
2018-06-03 11:18:53 +02:00
|
|
|
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];
|
|
|
|
};
|
2018-06-05 23:19:28 +02:00
|
|
|
setInterval(fetchSerialPorts, 5000);
|
2018-06-03 11:18:53 +02:00
|
|
|
fetchSerialPorts(true);
|
2018-05-21 16:40:22 +02:00
|
|
|
|
|
|
|
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 = "";
|
2018-06-03 11:18:53 +02:00
|
|
|
const stopLogsButton = logsModalElem.querySelector(".stop-logs");
|
|
|
|
let stopped = false;
|
|
|
|
stopLogsButton.innerHTML = "Stop";
|
2018-05-21 16:40:22 +02:00
|
|
|
modalInstance.open();
|
|
|
|
|
2018-06-03 11:18:53 +02:00
|
|
|
const filenameField = logsModalElem.querySelector('.filename');
|
|
|
|
filenameField.innerHTML = configuration;
|
2018-05-21 16:40:22 +02:00
|
|
|
|
2018-06-03 11:18:53 +02:00
|
|
|
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."});
|
2018-05-21 16:40:22 +02:00
|
|
|
} else {
|
2018-06-03 11:18:53 +02:00
|
|
|
M.toast({html: `Program failed with code ${data.code}`});
|
2018-05-21 16:40:22 +02:00
|
|
|
}
|
2018-06-03 11:18:53 +02:00
|
|
|
|
|
|
|
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();
|
|
|
|
};
|
2018-05-21 16:40:22 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
const uploadModalElem = document.getElementById("modal-upload");
|
|
|
|
|
2018-06-03 11:18:53 +02:00
|
|
|
document.querySelectorAll(".action-upload").forEach((upload) => {
|
|
|
|
upload.addEventListener('click', (e) => {
|
2018-05-21 16:40:22 +02:00
|
|
|
configuration = e.target.getAttribute('data-node');
|
|
|
|
const modalInstance = M.Modal.getInstance(uploadModalElem);
|
|
|
|
const log = uploadModalElem.querySelector(".log");
|
|
|
|
log.innerHTML = "";
|
2018-06-03 11:18:53 +02:00
|
|
|
const stopLogsButton = uploadModalElem.querySelector(".stop-logs");
|
|
|
|
let stopped = false;
|
|
|
|
stopLogsButton.innerHTML = "Stop";
|
2018-05-21 16:40:22 +02:00
|
|
|
modalInstance.open();
|
|
|
|
|
2018-06-03 11:18:53 +02:00
|
|
|
const filenameField = uploadModalElem.querySelector('.filename');
|
|
|
|
filenameField.innerHTML = configuration;
|
2018-05-21 16:40:22 +02:00
|
|
|
|
2018-06-03 11:18:53 +02:00
|
|
|
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."});
|
2018-05-21 16:40:22 +02:00
|
|
|
} else {
|
2018-06-03 11:18:53 +02:00
|
|
|
M.toast({html: `Program failed with code ${data.code}`});
|
2018-05-21 16:40:22 +02:00
|
|
|
}
|
2018-06-03 11:18:53 +02:00
|
|
|
|
|
|
|
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();
|
|
|
|
};
|
2018-05-21 16:40:22 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-06-03 12:16:43 +02:00
|
|
|
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();
|
|
|
|
|
|
|
|
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,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
};
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
const compileModalElem = document.getElementById("modal-compile");
|
|
|
|
const downloadButton = compileModalElem.querySelector('.download-binary');
|
|
|
|
|
2018-06-03 11:18:53 +02:00
|
|
|
document.querySelectorAll(".action-compile").forEach((upload) => {
|
|
|
|
upload.addEventListener('click', (e) => {
|
2018-05-21 16:40:22 +02:00
|
|
|
configuration = e.target.getAttribute('data-node');
|
|
|
|
const modalInstance = M.Modal.getInstance(compileModalElem);
|
|
|
|
const log = compileModalElem.querySelector(".log");
|
|
|
|
log.innerHTML = "";
|
2018-06-03 11:18:53 +02:00
|
|
|
const stopLogsButton = compileModalElem.querySelector(".stop-logs");
|
|
|
|
let stopped = false;
|
|
|
|
stopLogsButton.innerHTML = "Stop";
|
2018-05-21 16:40:22 +02:00
|
|
|
downloadButton.classList.add('disabled');
|
2018-06-03 11:18:53 +02:00
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
modalInstance.open();
|
|
|
|
|
2018-06-03 11:18:53 +02:00
|
|
|
const filenameField = compileModalElem.querySelector('.filename');
|
|
|
|
filenameField.innerHTML = configuration;
|
|
|
|
|
|
|
|
const logSocket = new WebSocket(wsUrl + "/compile");
|
2018-05-21 16:40:22 +02:00
|
|
|
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) {
|
2018-06-03 11:18:53 +02:00
|
|
|
M.toast({html: "Program exited successfully."});
|
2018-05-21 16:40:22 +02:00
|
|
|
downloadButton.classList.remove('disabled');
|
2018-05-27 14:15:24 +02:00
|
|
|
} else {
|
|
|
|
M.toast({html: `Program failed with code ${data.code}`});
|
2018-05-21 16:40:22 +02:00
|
|
|
}
|
2018-06-03 11:18:53 +02:00
|
|
|
|
|
|
|
stopLogsButton.innerHTML = "Close";
|
|
|
|
stopped = true;
|
2018-05-21 16:40:22 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
logSocket.addEventListener('open', () => {
|
|
|
|
const msg = JSON.stringify({configuration: configuration});
|
|
|
|
logSocket.send(msg);
|
|
|
|
});
|
|
|
|
logSocket.addEventListener('close', () => {
|
2018-06-03 11:18:53 +02:00
|
|
|
if (!stopped) {
|
|
|
|
M.toast({html: 'Terminated process.'});
|
|
|
|
}
|
2018-05-21 16:40:22 +02:00
|
|
|
});
|
|
|
|
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();
|
|
|
|
});
|
|
|
|
|
2018-10-04 19:01:02 +02:00
|
|
|
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();
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
logSocket.addEventListener('open', () => {
|
|
|
|
const msg = JSON.stringify({configuration: configuration});
|
2018-10-14 18:52:21 +02:00
|
|
|
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();
|
|
|
|
|
|
|
|
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}`});
|
|
|
|
}
|
|
|
|
stopLogsButton.innerHTML = "Close";
|
|
|
|
stopped = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
logSocket.addEventListener('open', () => {
|
|
|
|
const msg = JSON.stringify({configuration: configuration});
|
2018-11-03 14:08:31 +01:00
|
|
|
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();
|
|
|
|
|
|
|
|
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}`});
|
|
|
|
}
|
|
|
|
stopLogsButton.innerHTML = "Close";
|
|
|
|
stopped = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
logSocket.addEventListener('open', () => {
|
|
|
|
const msg = JSON.stringify({configuration: configuration});
|
2018-10-04 19:01:02 +02:00
|
|
|
logSocket.send(msg);
|
|
|
|
});
|
|
|
|
logSocket.addEventListener('close', () => {
|
|
|
|
if (!stopped) {
|
|
|
|
M.toast({html: 'Terminated process.'});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
modalInstance.options.onCloseStart = () => {
|
|
|
|
logSocket.close();
|
|
|
|
};
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
const modalSetupElem = document.getElementById("modal-wizard");
|
|
|
|
const setupWizardStart = document.getElementById('setup-wizard-start');
|
|
|
|
const startWizard = () => {
|
|
|
|
const modalInstance = M.Modal.getInstance(modalSetupElem);
|
|
|
|
modalInstance.open();
|
|
|
|
|
|
|
|
modalInstance.options.onCloseStart = () => {
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
$('.stepper').activateStepper({
|
|
|
|
linearStepsNavigation: false,
|
|
|
|
autoFocusInput: true,
|
|
|
|
autoFormCreation: true,
|
|
|
|
showFeedbackLoader: true,
|
|
|
|
parallel: false
|
|
|
|
});
|
|
|
|
};
|
|
|
|
setupWizardStart.addEventListener('click', startWizard);
|
|
|
|
</script>
|
|
|
|
|
2018-11-19 22:12:24 +01:00
|
|
|
{% if len(entries) == 0 %}
|
2018-05-21 16:40:22 +02:00
|
|
|
<script>
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
2018-06-03 11:18:53 +02:00
|
|
|
const tapTargetElem = document.querySelector('.tap-target.setup-wizard');
|
2018-05-21 16:40:22 +02:00
|
|
|
const tapTargetInstance = M.TapTarget.getInstance(tapTargetElem);
|
|
|
|
tapTargetInstance.options.onOpen = () => {
|
|
|
|
$('.tap-target-origin').on('click', () => {
|
|
|
|
startWizard();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
tapTargetInstance.open();
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
{% end %}
|
|
|
|
|
2018-06-03 11:18:53 +02:00
|
|
|
{% 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 %}
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
</body>
|
2018-06-03 12:16:43 +02:00
|
|
|
</html>
|