mirror of
https://github.com/esphome/esphome.git
synced 2024-12-23 16:47:57 +01:00
parent
4fe0c95ccb
commit
7a895adec9
@ -15,7 +15,7 @@ from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
|
|||||||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
||||||
from esphome.helpers import color, indent
|
from esphome.helpers import color, indent
|
||||||
from esphome.py_compat import IS_PY2, safe_input
|
from esphome.py_compat import IS_PY2, safe_input
|
||||||
from esphome.util import run_external_command, run_external_process, safe_print
|
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -187,8 +187,7 @@ def upload_program(config, args, host):
|
|||||||
ota_conf = config[CONF_OTA]
|
ota_conf = config[CONF_OTA]
|
||||||
remote_port = ota_conf[CONF_PORT]
|
remote_port = ota_conf[CONF_PORT]
|
||||||
password = ota_conf[CONF_PASSWORD]
|
password = ota_conf[CONF_PASSWORD]
|
||||||
res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def show_logs(config, args, port):
|
def show_logs(config, args, port):
|
||||||
@ -350,11 +349,52 @@ def command_dashboard(args):
|
|||||||
return dashboard.start_web_server(args)
|
return dashboard.start_web_server(args)
|
||||||
|
|
||||||
|
|
||||||
|
def command_update_all(args):
|
||||||
|
import click
|
||||||
|
|
||||||
|
success = {}
|
||||||
|
files = list_yaml_files(args.configuration)
|
||||||
|
twidth = 60
|
||||||
|
|
||||||
|
def print_bar(middle_text):
|
||||||
|
middle_text = " {} ".format(middle_text)
|
||||||
|
width = len(click.unstyle(middle_text))
|
||||||
|
half_line = "=" * ((twidth - width) / 2)
|
||||||
|
click.echo("%s%s%s" % (half_line, middle_text, half_line))
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
print("Updating {}".format(color('cyan', f)))
|
||||||
|
print('-' * twidth)
|
||||||
|
print()
|
||||||
|
rc = run_external_process('esphome', f, 'run', '--no-logs')
|
||||||
|
if rc == 0:
|
||||||
|
print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f))
|
||||||
|
success[f] = True
|
||||||
|
else:
|
||||||
|
print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f))
|
||||||
|
success[f] = False
|
||||||
|
|
||||||
|
print()
|
||||||
|
print()
|
||||||
|
print()
|
||||||
|
|
||||||
|
print_bar('[{}]'.format(color('bold_white', 'SUMMARY')))
|
||||||
|
failed = 0
|
||||||
|
for f in files:
|
||||||
|
if success[f]:
|
||||||
|
print(" - {}: {}".format(f, color('green', 'SUCCESS')))
|
||||||
|
else:
|
||||||
|
print(" - {}: {}".format(f, color('bold_red', 'FAILED')))
|
||||||
|
failed += 1
|
||||||
|
return failed
|
||||||
|
|
||||||
|
|
||||||
PRE_CONFIG_ACTIONS = {
|
PRE_CONFIG_ACTIONS = {
|
||||||
'wizard': command_wizard,
|
'wizard': command_wizard,
|
||||||
'version': command_version,
|
'version': command_version,
|
||||||
'dashboard': command_dashboard,
|
'dashboard': command_dashboard,
|
||||||
'vscode': command_vscode,
|
'vscode': command_vscode,
|
||||||
|
'update-all': command_update_all,
|
||||||
}
|
}
|
||||||
|
|
||||||
POST_CONFIG_ACTIONS = {
|
POST_CONFIG_ACTIONS = {
|
||||||
@ -446,6 +486,8 @@ def parse_args(argv):
|
|||||||
vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
|
vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
|
||||||
vscode.add_argument('--ace', action='store_true')
|
vscode.add_argument('--ace', action='store_true')
|
||||||
|
|
||||||
|
subparsers.add_parser('update-all', help=argparse.SUPPRESS)
|
||||||
|
|
||||||
return parser.parse_args(argv[1:])
|
return parser.parse_args(argv[1:])
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import tornado.process
|
|||||||
import tornado.web
|
import tornado.web
|
||||||
import tornado.websocket
|
import tornado.websocket
|
||||||
|
|
||||||
from esphome import const
|
from esphome import const, util
|
||||||
from esphome.__main__ import get_serial_ports
|
from esphome.__main__ import get_serial_ports
|
||||||
from esphome.helpers import mkdir_p, get_bool_env, run_system_command
|
from esphome.helpers import mkdir_p, get_bool_env, run_system_command
|
||||||
from esphome.py_compat import IS_PY2, decode_text
|
from esphome.py_compat import IS_PY2, decode_text
|
||||||
@ -93,17 +93,7 @@ class DashboardSettings(object):
|
|||||||
return os.path.join(self.config_dir, *args)
|
return os.path.join(self.config_dir, *args)
|
||||||
|
|
||||||
def list_yaml_files(self):
|
def list_yaml_files(self):
|
||||||
files = []
|
return util.list_yaml_files(self.config_dir)
|
||||||
for file in os.listdir(self.config_dir):
|
|
||||||
if not file.endswith('.yaml'):
|
|
||||||
continue
|
|
||||||
if file.startswith('.'):
|
|
||||||
continue
|
|
||||||
if file == 'secrets.yaml':
|
|
||||||
continue
|
|
||||||
files.append(file)
|
|
||||||
files.sort()
|
|
||||||
return files
|
|
||||||
|
|
||||||
|
|
||||||
settings = DashboardSettings()
|
settings = DashboardSettings()
|
||||||
@ -122,6 +112,7 @@ def template_args():
|
|||||||
'get_static_file_url': get_static_file_url,
|
'get_static_file_url': get_static_file_url,
|
||||||
'relative_url': settings.relative_url,
|
'relative_url': settings.relative_url,
|
||||||
'streamer_mode': get_bool_env('ESPHOME_STREAMER_MODE'),
|
'streamer_mode': get_bool_env('ESPHOME_STREAMER_MODE'),
|
||||||
|
'config_dir': settings.config_dir,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -315,6 +306,11 @@ class EsphomeAceEditorHandler(EsphomeCommandWebSocket):
|
|||||||
return ["esphome", "--dashboard", "-q", settings.config_dir, "vscode", "--ace"]
|
return ["esphome", "--dashboard", "-q", settings.config_dir, "vscode", "--ace"]
|
||||||
|
|
||||||
|
|
||||||
|
class EsphomeUpdateAllHandler(EsphomeCommandWebSocket):
|
||||||
|
def build_command(self, json_message):
|
||||||
|
return ["esphome", "--dashboard", settings.config_dir, "update-all"]
|
||||||
|
|
||||||
|
|
||||||
class SerialPortRequestHandler(BaseHandler):
|
class SerialPortRequestHandler(BaseHandler):
|
||||||
@authenticated
|
@authenticated
|
||||||
def get(self):
|
def get(self):
|
||||||
@ -690,6 +686,7 @@ def make_app(debug=False):
|
|||||||
(rel + "clean", EsphomeCleanHandler),
|
(rel + "clean", EsphomeCleanHandler),
|
||||||
(rel + "vscode", EsphomeVscodeHandler),
|
(rel + "vscode", EsphomeVscodeHandler),
|
||||||
(rel + "ace", EsphomeAceEditorHandler),
|
(rel + "ace", EsphomeAceEditorHandler),
|
||||||
|
(rel + "update-all", EsphomeUpdateAllHandler),
|
||||||
(rel + "edit", EditRequestHandler),
|
(rel + "edit", EditRequestHandler),
|
||||||
(rel + "download.bin", DownloadBinaryRequestHandler),
|
(rel + "download.bin", DownloadBinaryRequestHandler),
|
||||||
(rel + "serial-ports", SerialPortRequestHandler),
|
(rel + "serial-ports", SerialPortRequestHandler),
|
||||||
|
@ -131,10 +131,14 @@ ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .s
|
|||||||
|
|
||||||
.select-port-container {
|
.select-port-container {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
margin-right: 24px;
|
margin-right: 10px;
|
||||||
width: 350px;
|
width: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#dropdown-nav-trigger {
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.select-port-container .select-dropdown {
|
.select-port-container .select-dropdown {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
@ -333,6 +333,10 @@ class LogModalElem {
|
|||||||
this.activeSocket.close();
|
this.activeSocket.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open(event) {
|
||||||
|
this._onPress(event);
|
||||||
|
}
|
||||||
|
|
||||||
_onPress(event) {
|
_onPress(event) {
|
||||||
this.activeConfig = event.target.getAttribute('data-node');
|
this.activeConfig = event.target.getAttribute('data-node');
|
||||||
this._setupModalInstance();
|
this._setupModalInstance();
|
||||||
@ -745,3 +749,32 @@ jQuery.validator.addMethod("nospaces", (value, element) => {
|
|||||||
jQuery.validator.addMethod("lowercase", (value, element) => {
|
jQuery.validator.addMethod("lowercase", (value, element) => {
|
||||||
return value === value.toLowerCase();
|
return value === value.toLowerCase();
|
||||||
}, "Name must be lowercase.");
|
}, "Name must be lowercase.");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const updateAllModal = new LogModalElem({
|
||||||
|
name: 'update-all',
|
||||||
|
onPrepare: (modalElem, config) => {
|
||||||
|
modalElem.querySelector('.stop-logs').innerHTML = "Stop";
|
||||||
|
downloadButton.classList.add('disabled');
|
||||||
|
},
|
||||||
|
onProcessExit: (modalElem, code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
M.toast({html: "Program exited successfully."});
|
||||||
|
downloadButton.classList.remove('disabled');
|
||||||
|
} else {
|
||||||
|
M.toast({html: `Program failed with code ${data.code}`});
|
||||||
|
}
|
||||||
|
modalElem.querySelector(".stop-logs").innerHTML = "Close";
|
||||||
|
},
|
||||||
|
onSocketClose: (modalElem) => {
|
||||||
|
M.toast({html: 'Terminated process.'});
|
||||||
|
},
|
||||||
|
dismissible: false,
|
||||||
|
});
|
||||||
|
updateAllModal.setup();
|
||||||
|
|
||||||
|
const updateAllButton = document.getElementById('update-all-button');
|
||||||
|
updateAllButton.addEventListener('click', (e) => {
|
||||||
|
updateAllModal.open(e);
|
||||||
|
});
|
||||||
|
@ -31,10 +31,16 @@
|
|||||||
<nav>
|
<nav>
|
||||||
<div class="nav-wrapper indigo">
|
<div class="nav-wrapper indigo">
|
||||||
<a href="#" class="brand-logo left">ESPHome Dashboard</a>
|
<a href="#" class="brand-logo left">ESPHome Dashboard</a>
|
||||||
|
<i class="material-icons dropdown-trigger right" id="dropdown-nav-trigger" data-target="dropdown-nav-actions">more_vert</i>
|
||||||
<div class="select-port-container right" id="select-port-target">
|
<div class="select-port-container right" id="select-port-target">
|
||||||
<select></select>
|
<select></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ul id="dropdown-nav-actions" class="select-action dropdown-content card-dropdown-action">
|
||||||
|
<li><a id="update-all-button" class="modal-close waves-effect waves-green btn-flat"
|
||||||
|
data-node="{{ escape(config_dir) }}">Update All</a></li>
|
||||||
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{% if begin %}
|
{% if begin %}
|
||||||
@ -445,6 +451,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="modal-update-all" class="modal modal-fixed-footer">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Update All</h4>
|
||||||
|
<pre class="log"></pre>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a class="btn-floating btn-large ribbon-fab waves-effect waves-light pink accent-2" id="setup-wizard-start">
|
<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>
|
<i class="material-icons">add</i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -299,3 +299,4 @@ def run_ota(remote_host, remote_port, password, filename):
|
|||||||
return run_ota_impl_(remote_host, remote_port, password, filename)
|
return run_ota_impl_(remote_host, remote_port, password, filename)
|
||||||
except OTAError as err:
|
except OTAError as err:
|
||||||
_LOGGER.error(err)
|
_LOGGER.error(err)
|
||||||
|
return 1
|
||||||
|
@ -3,6 +3,7 @@ from __future__ import print_function
|
|||||||
import collections
|
import collections
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@ -207,3 +208,16 @@ class OrderedDict(collections.OrderedDict):
|
|||||||
root[1] = first[0] = link
|
root[1] = first[0] = link
|
||||||
else:
|
else:
|
||||||
super(OrderedDict, self).move_to_end(key, last=last) # pylint: disable=no-member
|
super(OrderedDict, self).move_to_end(key, last=last) # pylint: disable=no-member
|
||||||
|
|
||||||
|
|
||||||
|
def list_yaml_files(folder):
|
||||||
|
files = filter_yaml_files([os.path.join(folder, p) for p in os.listdir(folder)])
|
||||||
|
files.sort()
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
def filter_yaml_files(files):
|
||||||
|
files = [f for f in files if os.path.splitext(f)[1] == '.yaml']
|
||||||
|
files = [f for f in files if os.path.basename(f) != 'secrets.yaml']
|
||||||
|
files = [f for f in files if not os.path.basename(f).startswith('.')]
|
||||||
|
return files
|
||||||
|
@ -14,7 +14,7 @@ from esphome import core
|
|||||||
from esphome.config_helpers import read_config_file
|
from esphome.config_helpers import read_config_file
|
||||||
from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, DocumentRange
|
from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, DocumentRange
|
||||||
from esphome.py_compat import text_type, IS_PY2
|
from esphome.py_compat import text_type, IS_PY2
|
||||||
from esphome.util import OrderedDict
|
from esphome.util import OrderedDict, filter_yaml_files
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -260,12 +260,12 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
|
|||||||
|
|
||||||
@_add_data_ref
|
@_add_data_ref
|
||||||
def construct_include_dir_list(self, node):
|
def construct_include_dir_list(self, node):
|
||||||
files = _filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
files = filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
||||||
return [_load_yaml_internal(f) for f in files]
|
return [_load_yaml_internal(f) for f in files]
|
||||||
|
|
||||||
@_add_data_ref
|
@_add_data_ref
|
||||||
def construct_include_dir_merge_list(self, node):
|
def construct_include_dir_merge_list(self, node):
|
||||||
files = _filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
files = filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
||||||
merged_list = []
|
merged_list = []
|
||||||
for fname in files:
|
for fname in files:
|
||||||
loaded_yaml = _load_yaml_internal(fname)
|
loaded_yaml = _load_yaml_internal(fname)
|
||||||
@ -275,7 +275,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
|
|||||||
|
|
||||||
@_add_data_ref
|
@_add_data_ref
|
||||||
def construct_include_dir_named(self, node):
|
def construct_include_dir_named(self, node):
|
||||||
files = _filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
files = filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
||||||
mapping = OrderedDict()
|
mapping = OrderedDict()
|
||||||
for fname in files:
|
for fname in files:
|
||||||
filename = os.path.splitext(os.path.basename(fname))[0]
|
filename = os.path.splitext(os.path.basename(fname))[0]
|
||||||
@ -284,7 +284,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
|
|||||||
|
|
||||||
@_add_data_ref
|
@_add_data_ref
|
||||||
def construct_include_dir_merge_named(self, node):
|
def construct_include_dir_merge_named(self, node):
|
||||||
files = _filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
files = filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
||||||
mapping = OrderedDict()
|
mapping = OrderedDict()
|
||||||
for fname in files:
|
for fname in files:
|
||||||
loaded_yaml = _load_yaml_internal(fname)
|
loaded_yaml = _load_yaml_internal(fname)
|
||||||
@ -297,12 +297,6 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
|
|||||||
return Lambda(text_type(node.value))
|
return Lambda(text_type(node.value))
|
||||||
|
|
||||||
|
|
||||||
def _filter_yaml_files(files):
|
|
||||||
files = [f for f in files if os.path.basename(f) != SECRET_YAML]
|
|
||||||
files = [f for f in files if not os.path.basename(f).startswith('.')]
|
|
||||||
return files
|
|
||||||
|
|
||||||
|
|
||||||
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:int', ESPHomeLoader.construct_yaml_int)
|
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:int', ESPHomeLoader.construct_yaml_int)
|
||||||
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:float', ESPHomeLoader.construct_yaml_float)
|
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:float', ESPHomeLoader.construct_yaml_float)
|
||||||
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:binary', ESPHomeLoader.construct_yaml_binary)
|
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:binary', ESPHomeLoader.construct_yaml_binary)
|
||||||
|
Loading…
Reference in New Issue
Block a user