2018-04-07 01:23:03 +02:00
|
|
|
import argparse
|
2019-05-12 23:04:36 +02:00
|
|
|
import functools
|
2018-04-07 01:23:03 +02:00
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import sys
|
2019-04-17 12:06:00 +02:00
|
|
|
from datetime import datetime
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2019-04-17 12:06:00 +02:00
|
|
|
from esphome import const, writer, yaml_util
|
2019-05-12 23:04:36 +02:00
|
|
|
import esphome.codegen as cg
|
2019-04-17 12:06:00 +02:00
|
|
|
from esphome.config import iter_components, read_config, strip_default_ids
|
|
|
|
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
|
2019-06-18 19:31:22 +02:00
|
|
|
CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
|
2019-05-12 23:04:36 +02:00
|
|
|
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
2019-02-13 16:54:02 +01:00
|
|
|
from esphome.helpers import color, indent
|
2020-07-24 10:09:43 +02:00
|
|
|
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files, \
|
|
|
|
get_serial_ports
|
2018-04-07 01:23:03 +02:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2018-12-24 14:15:24 +01:00
|
|
|
def choose_prompt(options):
|
|
|
|
if not options:
|
2019-08-27 22:00:34 +02:00
|
|
|
raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
|
2020-06-15 03:41:02 +02:00
|
|
|
"sections (ota, api, mqtt, ...) are in your configuration and/or the "
|
|
|
|
"device is plugged in.")
|
2018-12-24 14:15:24 +01:00
|
|
|
|
|
|
|
if len(options) == 1:
|
|
|
|
return options[0][1]
|
|
|
|
|
2019-12-07 18:28:55 +01:00
|
|
|
safe_print("Found multiple options, please choose one:")
|
2018-12-24 14:15:24 +01:00
|
|
|
for i, (desc, _) in enumerate(options):
|
2019-12-07 18:28:55 +01:00
|
|
|
safe_print(f" [{i+1}] {desc}")
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2018-04-10 20:17:20 +02:00
|
|
|
while True:
|
2019-12-07 18:28:55 +01:00
|
|
|
opt = input('(number): ')
|
2018-12-24 14:15:24 +01:00
|
|
|
if opt in options:
|
|
|
|
opt = options.index(opt)
|
2018-04-10 20:17:20 +02:00
|
|
|
break
|
|
|
|
try:
|
|
|
|
opt = int(opt)
|
2018-12-24 14:15:24 +01:00
|
|
|
if opt < 1 or opt > len(options):
|
2018-04-10 20:17:20 +02:00
|
|
|
raise ValueError
|
|
|
|
break
|
|
|
|
except ValueError:
|
2019-12-07 18:28:55 +01:00
|
|
|
safe_print(color('red', f"Invalid option: '{opt}'"))
|
2018-12-24 14:15:24 +01:00
|
|
|
return options[opt - 1][1]
|
|
|
|
|
|
|
|
|
|
|
|
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
|
|
|
|
options = []
|
2020-07-24 10:09:43 +02:00
|
|
|
for port in get_serial_ports():
|
|
|
|
options.append((f"{port.path} ({port.description})", port.path))
|
2018-12-24 14:15:24 +01:00
|
|
|
if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
|
2019-12-07 18:28:55 +01:00
|
|
|
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
2018-12-24 14:15:24 +01:00
|
|
|
if default == 'OTA':
|
|
|
|
return CORE.address
|
|
|
|
if show_mqtt and 'mqtt' in CORE.config:
|
2019-12-07 18:28:55 +01:00
|
|
|
options.append(("MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
|
2018-12-24 14:15:24 +01:00
|
|
|
if default == 'OTA':
|
|
|
|
return 'MQTT'
|
|
|
|
if default is not None:
|
|
|
|
return default
|
|
|
|
if check_default is not None and check_default in [opt[1] for opt in options]:
|
|
|
|
return check_default
|
|
|
|
return choose_prompt(options)
|
|
|
|
|
|
|
|
|
|
|
|
def get_port_type(port):
|
|
|
|
if port.startswith('/') or port.startswith('COM'):
|
|
|
|
return 'SERIAL'
|
|
|
|
if port == 'MQTT':
|
|
|
|
return 'MQTT'
|
|
|
|
return 'NETWORK'
|
2018-04-07 01:23:03 +02:00
|
|
|
|
|
|
|
|
2018-12-05 21:22:06 +01:00
|
|
|
def run_miniterm(config, port):
|
2018-05-21 16:40:22 +02:00
|
|
|
import serial
|
2019-04-08 22:19:21 +02:00
|
|
|
from esphome import platformio_api
|
|
|
|
|
2018-10-04 19:00:51 +02:00
|
|
|
if CONF_LOGGER not in config:
|
|
|
|
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
|
|
|
return
|
|
|
|
baud_rate = config['logger'][CONF_BAUD_RATE]
|
|
|
|
if baud_rate == 0:
|
|
|
|
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
|
2018-05-21 16:40:22 +02:00
|
|
|
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
|
|
|
|
2018-11-03 14:06:14 +01:00
|
|
|
backtrace_state = False
|
2018-05-21 16:40:22 +02:00
|
|
|
with serial.Serial(port, baudrate=baud_rate) as ser:
|
|
|
|
while True:
|
2018-08-13 19:11:33 +02:00
|
|
|
try:
|
|
|
|
raw = ser.readline()
|
|
|
|
except serial.SerialException:
|
|
|
|
_LOGGER.error("Serial port closed!")
|
|
|
|
return
|
2019-12-07 18:28:55 +01:00
|
|
|
line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace')
|
2018-05-21 16:40:22 +02:00
|
|
|
time = datetime.now().time().strftime('[%H:%M:%S]')
|
2018-06-05 23:19:46 +02:00
|
|
|
message = time + line
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print(message)
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2018-11-03 14:06:14 +01:00
|
|
|
backtrace_state = platformio_api.process_stacktrace(
|
|
|
|
config, line, backtrace_state=backtrace_state)
|
|
|
|
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2019-05-12 23:04:36 +02:00
|
|
|
def wrap_to_code(name, comp):
|
|
|
|
coro = coroutine(comp.to_code)
|
|
|
|
|
|
|
|
@functools.wraps(comp.to_code)
|
|
|
|
@coroutine_with_priority(coro.priority)
|
|
|
|
def wrapped(conf):
|
2019-12-07 18:28:55 +01:00
|
|
|
cg.add(cg.LineComment(f"{name}:"))
|
2019-05-12 23:04:36 +02:00
|
|
|
if comp.config_schema is not None:
|
2019-05-28 16:00:00 +02:00
|
|
|
conf_str = yaml_util.dump(conf)
|
2019-08-27 21:51:59 +02:00
|
|
|
conf_str = conf_str.replace('//', '')
|
2019-05-28 16:00:00 +02:00
|
|
|
cg.add(cg.LineComment(indent(conf_str)))
|
2019-05-12 23:04:36 +02:00
|
|
|
yield coro(conf)
|
|
|
|
|
|
|
|
return wrapped
|
|
|
|
|
|
|
|
|
2018-04-07 01:23:03 +02:00
|
|
|
def write_cpp(config):
|
2020-07-14 17:59:03 +02:00
|
|
|
generate_cpp_contents(config)
|
|
|
|
return write_cpp_file()
|
|
|
|
|
|
|
|
|
|
|
|
def generate_cpp_contents(config):
|
2018-04-07 01:23:03 +02:00
|
|
|
_LOGGER.info("Generating C++ source...")
|
2018-04-18 18:43:13 +02:00
|
|
|
|
2019-05-12 23:04:36 +02:00
|
|
|
for name, component, conf in iter_components(CORE.config):
|
2019-04-17 12:06:00 +02:00
|
|
|
if component.to_code is not None:
|
2019-05-12 23:04:36 +02:00
|
|
|
coro = wrap_to_code(name, component)
|
|
|
|
CORE.add_job(coro, conf)
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2018-12-05 21:22:06 +01:00
|
|
|
CORE.flush_tasks()
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2020-07-14 17:59:03 +02:00
|
|
|
|
|
|
|
def write_cpp_file():
|
2018-12-05 21:22:06 +01:00
|
|
|
writer.write_platformio_project()
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2019-04-17 12:06:00 +02:00
|
|
|
code_s = indent(CORE.cpp_main_section)
|
2018-12-05 21:22:06 +01:00
|
|
|
writer.write_cpp(code_s)
|
2018-04-07 01:23:03 +02:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2018-06-02 22:22:20 +02:00
|
|
|
def compile_program(args, config):
|
2019-04-08 22:19:21 +02:00
|
|
|
from esphome import platformio_api
|
|
|
|
|
2018-04-07 01:23:03 +02:00
|
|
|
_LOGGER.info("Compiling app...")
|
2019-10-19 14:04:14 +02:00
|
|
|
return platformio_api.run_compile(config, CORE.verbose)
|
2018-04-07 01:23:03 +02:00
|
|
|
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
def upload_using_esptool(config, port):
|
2019-03-03 16:50:06 +01:00
|
|
|
path = CORE.firmware_bin
|
2019-11-14 12:42:50 +01:00
|
|
|
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get('upload_speed', 460800)
|
|
|
|
|
|
|
|
def run_esptool(baud_rate):
|
|
|
|
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
|
|
|
|
'--baud', str(baud_rate),
|
|
|
|
'--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
|
|
|
|
|
|
|
|
if os.environ.get('ESPHOME_USE_SUBPROCESS') is None:
|
|
|
|
import esptool
|
|
|
|
# pylint: disable=protected-access
|
|
|
|
return run_external_command(esptool._main, *cmd)
|
|
|
|
|
|
|
|
return run_external_process(*cmd)
|
|
|
|
|
|
|
|
rc = run_esptool(first_baudrate)
|
|
|
|
if rc == 0 or first_baudrate == 115200:
|
|
|
|
return rc
|
|
|
|
# Try with 115200 baud rate, with some serial chips the faster baud rates do not work well
|
|
|
|
_LOGGER.info("Upload with baud rate %s failed. Trying again with baud rate 115200.",
|
|
|
|
first_baudrate)
|
|
|
|
return run_esptool(115200)
|
2018-05-21 16:40:22 +02:00
|
|
|
|
|
|
|
|
2018-12-24 14:15:24 +01:00
|
|
|
def upload_program(config, args, host):
|
2018-06-07 20:36:00 +02:00
|
|
|
# if upload is to a serial port use platformio, otherwise assume ota
|
2018-12-24 14:15:24 +01:00
|
|
|
if get_port_type(host) == 'SERIAL':
|
2019-04-08 22:19:21 +02:00
|
|
|
from esphome import platformio_api
|
|
|
|
|
2018-12-05 21:22:06 +01:00
|
|
|
if CORE.is_esp8266:
|
2018-12-24 14:15:24 +01:00
|
|
|
return upload_using_esptool(config, host)
|
2019-10-19 14:04:14 +02:00
|
|
|
return platformio_api.run_upload(config, CORE.verbose, host)
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2019-02-13 16:54:02 +01:00
|
|
|
from esphome import espota2
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2019-11-12 15:28:23 +01:00
|
|
|
if CONF_OTA not in config:
|
|
|
|
raise EsphomeError("Cannot upload Over the Air as the config does not include the ota: "
|
|
|
|
"component")
|
|
|
|
|
2019-04-17 12:06:00 +02:00
|
|
|
ota_conf = config[CONF_OTA]
|
|
|
|
remote_port = ota_conf[CONF_PORT]
|
|
|
|
password = ota_conf[CONF_PASSWORD]
|
2019-06-07 14:26:28 +02:00
|
|
|
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
2018-04-07 01:23:03 +02:00
|
|
|
|
|
|
|
|
2018-12-05 21:22:06 +01:00
|
|
|
def show_logs(config, args, port):
|
2019-01-13 16:21:10 +01:00
|
|
|
if 'logger' not in config:
|
2019-02-13 16:54:02 +01:00
|
|
|
raise EsphomeError("Logger is not configured!")
|
2018-12-24 14:15:24 +01:00
|
|
|
if get_port_type(port) == 'SERIAL':
|
2018-12-05 21:22:06 +01:00
|
|
|
run_miniterm(config, port)
|
2018-04-07 01:23:03 +02:00
|
|
|
return 0
|
2019-01-13 16:21:10 +01:00
|
|
|
if get_port_type(port) == 'NETWORK' and 'api' in config:
|
2019-04-08 21:57:25 +02:00
|
|
|
from esphome.api.client import run_logs
|
|
|
|
|
2018-12-24 14:15:24 +01:00
|
|
|
return run_logs(config, port)
|
2019-01-13 16:21:10 +01:00
|
|
|
if get_port_type(port) == 'MQTT' and 'mqtt' in config:
|
2019-04-08 21:57:25 +02:00
|
|
|
from esphome import mqtt
|
|
|
|
|
2018-12-24 14:15:24 +01:00
|
|
|
return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id)
|
|
|
|
|
2019-04-17 12:06:00 +02:00
|
|
|
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
2018-04-07 01:23:03 +02:00
|
|
|
|
|
|
|
|
|
|
|
def clean_mqtt(config, args):
|
2019-04-08 21:57:25 +02:00
|
|
|
from esphome import mqtt
|
|
|
|
|
2018-04-07 01:23:03 +02:00
|
|
|
return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id)
|
|
|
|
|
|
|
|
|
2019-04-22 21:56:30 +02:00
|
|
|
def setup_log(debug=False, quiet=False):
|
|
|
|
if debug:
|
|
|
|
log_level = logging.DEBUG
|
2019-10-19 14:04:14 +02:00
|
|
|
CORE.verbose = True
|
2019-04-22 21:56:30 +02:00
|
|
|
elif quiet:
|
|
|
|
log_level = logging.CRITICAL
|
|
|
|
else:
|
|
|
|
log_level = logging.INFO
|
2018-06-02 22:22:20 +02:00
|
|
|
logging.basicConfig(level=log_level)
|
2018-11-03 14:06:14 +01:00
|
|
|
fmt = "%(levelname)s %(message)s"
|
2019-12-07 18:28:55 +01:00
|
|
|
colorfmt = f"%(log_color)s{fmt}%(reset)s"
|
2018-04-07 01:23:03 +02:00
|
|
|
datefmt = '%H:%M:%S'
|
|
|
|
|
|
|
|
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
|
|
|
|
|
|
|
try:
|
2020-11-19 23:39:16 +01:00
|
|
|
import colorama
|
|
|
|
colorama.init(strip=True)
|
|
|
|
|
2018-04-07 01:23:03 +02:00
|
|
|
from colorlog import ColoredFormatter
|
|
|
|
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
|
|
|
|
colorfmt,
|
|
|
|
datefmt=datefmt,
|
|
|
|
reset=True,
|
|
|
|
log_colors={
|
|
|
|
'DEBUG': 'cyan',
|
|
|
|
'INFO': 'green',
|
|
|
|
'WARNING': 'yellow',
|
|
|
|
'ERROR': 'red',
|
|
|
|
'CRITICAL': 'red',
|
|
|
|
}
|
|
|
|
))
|
|
|
|
except ImportError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
def command_wizard(args):
|
2019-04-08 22:19:21 +02:00
|
|
|
from esphome import wizard
|
|
|
|
|
2019-07-01 11:09:06 +02:00
|
|
|
return wizard.wizard(args.configuration[0])
|
2018-05-21 16:40:22 +02:00
|
|
|
|
|
|
|
|
|
|
|
def command_config(args, config):
|
2018-12-05 21:22:06 +01:00
|
|
|
_LOGGER.info("Configuration is valid!")
|
2019-10-19 14:04:14 +02:00
|
|
|
if not CORE.verbose:
|
2018-06-02 22:22:20 +02:00
|
|
|
config = strip_default_ids(config)
|
2018-10-20 12:41:00 +02:00
|
|
|
safe_print(yaml_util.dump(config))
|
2018-05-21 16:40:22 +02:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2019-04-22 21:56:30 +02:00
|
|
|
def command_vscode(args):
|
|
|
|
from esphome import vscode
|
|
|
|
|
2019-07-01 11:09:06 +02:00
|
|
|
CORE.config_path = args.configuration[0]
|
2019-05-11 11:41:09 +02:00
|
|
|
vscode.read_config(args)
|
2019-04-22 21:56:30 +02:00
|
|
|
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
def command_compile(args, config):
|
|
|
|
exit_code = write_cpp(config)
|
|
|
|
if exit_code != 0:
|
|
|
|
return exit_code
|
2018-09-19 14:51:39 +02:00
|
|
|
if args.only_generate:
|
2019-12-07 18:28:55 +01:00
|
|
|
_LOGGER.info("Successfully generated source code.")
|
2018-09-23 18:58:41 +02:00
|
|
|
return 0
|
2018-06-02 22:22:20 +02:00
|
|
|
exit_code = compile_program(args, config)
|
2018-05-21 16:40:22 +02:00
|
|
|
if exit_code != 0:
|
|
|
|
return exit_code
|
2019-12-07 18:28:55 +01:00
|
|
|
_LOGGER.info("Successfully compiled program.")
|
2018-05-21 16:40:22 +02:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
def command_upload(args, config):
|
2018-12-24 14:15:24 +01:00
|
|
|
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
|
|
|
show_ota=True, show_mqtt=False, show_api=False)
|
2018-05-21 16:40:22 +02:00
|
|
|
exit_code = upload_program(config, args, port)
|
|
|
|
if exit_code != 0:
|
|
|
|
return exit_code
|
2019-12-07 18:28:55 +01:00
|
|
|
_LOGGER.info("Successfully uploaded program.")
|
2018-05-21 16:40:22 +02:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
def command_logs(args, config):
|
2018-12-24 14:15:24 +01:00
|
|
|
port = choose_upload_log_host(default=args.serial_port, check_default=None,
|
|
|
|
show_ota=False, show_mqtt=True, show_api=True)
|
2018-12-05 21:22:06 +01:00
|
|
|
return show_logs(config, args, port)
|
2018-05-21 16:40:22 +02:00
|
|
|
|
|
|
|
|
|
|
|
def command_run(args, config):
|
|
|
|
exit_code = write_cpp(config)
|
|
|
|
if exit_code != 0:
|
|
|
|
return exit_code
|
2018-06-02 22:22:20 +02:00
|
|
|
exit_code = compile_program(args, config)
|
2018-05-21 16:40:22 +02:00
|
|
|
if exit_code != 0:
|
|
|
|
return exit_code
|
2019-12-07 18:28:55 +01:00
|
|
|
_LOGGER.info("Successfully compiled program.")
|
2018-12-24 14:15:24 +01:00
|
|
|
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
|
|
|
show_ota=True, show_mqtt=False, show_api=True)
|
2018-05-21 16:40:22 +02:00
|
|
|
exit_code = upload_program(config, args, port)
|
|
|
|
if exit_code != 0:
|
|
|
|
return exit_code
|
2019-12-07 18:28:55 +01:00
|
|
|
_LOGGER.info("Successfully uploaded program.")
|
2018-05-21 16:40:22 +02:00
|
|
|
if args.no_logs:
|
|
|
|
return 0
|
2018-12-24 14:15:24 +01:00
|
|
|
port = choose_upload_log_host(default=args.upload_port, check_default=port,
|
|
|
|
show_ota=False, show_mqtt=True, show_api=True)
|
2018-12-05 21:22:06 +01:00
|
|
|
return show_logs(config, args, port)
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
|
|
|
|
def command_clean_mqtt(args, config):
|
|
|
|
return clean_mqtt(config, args)
|
|
|
|
|
|
|
|
|
|
|
|
def command_mqtt_fingerprint(args, config):
|
2019-04-08 21:57:25 +02:00
|
|
|
from esphome import mqtt
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
return mqtt.get_fingerprint(config)
|
|
|
|
|
|
|
|
|
|
|
|
def command_version(args):
|
2019-12-07 18:28:55 +01:00
|
|
|
safe_print(f"Version: {const.__version__}")
|
2018-05-21 16:40:22 +02:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2018-10-14 18:52:21 +02:00
|
|
|
def command_clean(args, config):
|
|
|
|
try:
|
2018-12-05 21:22:06 +01:00
|
|
|
writer.clean_build()
|
2018-10-14 18:52:21 +02:00
|
|
|
except OSError as err:
|
|
|
|
_LOGGER.error("Error deleting build files: %s", err)
|
|
|
|
return 1
|
|
|
|
_LOGGER.info("Done!")
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2018-05-27 14:15:24 +02:00
|
|
|
def command_dashboard(args):
|
2019-02-13 16:54:02 +01:00
|
|
|
from esphome.dashboard import dashboard
|
2018-05-21 16:40:22 +02:00
|
|
|
|
2018-05-27 14:15:24 +02:00
|
|
|
return dashboard.start_web_server(args)
|
2018-05-21 16:40:22 +02:00
|
|
|
|
|
|
|
|
2019-06-07 14:26:28 +02:00
|
|
|
def command_update_all(args):
|
|
|
|
import click
|
|
|
|
|
|
|
|
success = {}
|
2019-07-01 11:09:06 +02:00
|
|
|
files = list_yaml_files(args.configuration[0])
|
2019-06-07 14:26:28 +02:00
|
|
|
twidth = 60
|
|
|
|
|
|
|
|
def print_bar(middle_text):
|
2019-12-07 18:28:55 +01:00
|
|
|
middle_text = f" {middle_text} "
|
2019-06-07 14:26:28 +02:00
|
|
|
width = len(click.unstyle(middle_text))
|
2019-10-30 16:16:14 +01:00
|
|
|
half_line = "=" * ((twidth - width) // 2)
|
2019-12-07 18:28:55 +01:00
|
|
|
click.echo(f"{half_line}{middle_text}{half_line}")
|
2019-06-07 14:26:28 +02:00
|
|
|
|
|
|
|
for f in files:
|
|
|
|
print("Updating {}".format(color('cyan', f)))
|
|
|
|
print('-' * twidth)
|
|
|
|
print()
|
2019-11-02 19:35:55 +01:00
|
|
|
rc = run_external_process('esphome', '--dashboard', f, 'run', '--no-logs', '--upload-port',
|
|
|
|
'OTA')
|
2019-06-07 14:26:28 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
PRE_CONFIG_ACTIONS = {
|
|
|
|
'wizard': command_wizard,
|
|
|
|
'version': command_version,
|
2019-04-22 21:56:30 +02:00
|
|
|
'dashboard': command_dashboard,
|
|
|
|
'vscode': command_vscode,
|
2019-06-07 14:26:28 +02:00
|
|
|
'update-all': command_update_all,
|
2018-05-21 16:40:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
POST_CONFIG_ACTIONS = {
|
|
|
|
'config': command_config,
|
|
|
|
'compile': command_compile,
|
|
|
|
'upload': command_upload,
|
|
|
|
'logs': command_logs,
|
|
|
|
'run': command_run,
|
|
|
|
'clean-mqtt': command_clean_mqtt,
|
|
|
|
'mqtt-fingerprint': command_mqtt_fingerprint,
|
2018-10-14 18:52:21 +02:00
|
|
|
'clean': command_clean,
|
2018-05-21 16:40:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def parse_args(argv):
|
2019-12-07 18:28:55 +01:00
|
|
|
parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}')
|
2019-02-13 16:54:02 +01:00
|
|
|
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
2018-06-02 22:22:20 +02:00
|
|
|
action='store_true')
|
2019-04-22 21:56:30 +02:00
|
|
|
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
|
|
|
|
action='store_true')
|
|
|
|
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
|
2020-06-21 20:33:01 +02:00
|
|
|
parser.add_argument('-s', '--substitution', nargs=2, action='append',
|
|
|
|
help='Add a substitution', metavar=('key', 'value'))
|
2019-07-01 11:09:06 +02:00
|
|
|
parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*')
|
2018-05-21 16:40:22 +02:00
|
|
|
|
2018-04-07 01:23:03 +02:00
|
|
|
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
|
|
|
subparsers.required = True
|
2018-04-10 16:21:32 +02:00
|
|
|
subparsers.add_parser('config', help='Validate the configuration and spit it out.')
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2018-09-23 18:58:41 +02:00
|
|
|
parser_compile = subparsers.add_parser('compile',
|
|
|
|
help='Read the configuration and compile a program.')
|
2018-09-19 14:51:39 +02:00
|
|
|
parser_compile.add_argument('--only-generate',
|
2018-09-23 18:58:41 +02:00
|
|
|
help="Only generate source code, do not compile.",
|
|
|
|
action='store_true')
|
2018-04-07 01:23:03 +02:00
|
|
|
|
|
|
|
parser_upload = subparsers.add_parser('upload', help='Validate the configuration '
|
|
|
|
'and upload the latest binary.')
|
|
|
|
parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. "
|
2018-04-10 20:17:20 +02:00
|
|
|
"For example /dev/cu.SLAB_USBtoUART.")
|
2018-04-07 01:23:03 +02:00
|
|
|
|
|
|
|
parser_logs = subparsers.add_parser('logs', help='Validate the configuration '
|
|
|
|
'and show all MQTT logs.')
|
|
|
|
parser_logs.add_argument('--topic', help='Manually set the topic to subscribe to.')
|
|
|
|
parser_logs.add_argument('--username', help='Manually set the username.')
|
|
|
|
parser_logs.add_argument('--password', help='Manually set the password.')
|
|
|
|
parser_logs.add_argument('--client-id', help='Manually set the client id.')
|
2018-04-10 20:17:20 +02:00
|
|
|
parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use"
|
|
|
|
"For example /dev/cu.SLAB_USBtoUART.")
|
2018-04-07 01:23:03 +02:00
|
|
|
|
|
|
|
parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, '
|
|
|
|
'upload it, and start MQTT logs.')
|
2018-06-07 20:36:00 +02:00
|
|
|
parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. "
|
2018-04-10 20:17:20 +02:00
|
|
|
"For example /dev/cu.SLAB_USBtoUART.")
|
2018-04-07 01:23:03 +02:00
|
|
|
parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.',
|
|
|
|
action='store_true')
|
|
|
|
parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.')
|
|
|
|
parser_run.add_argument('--username', help='Manually set the MQTT username for logs.')
|
|
|
|
parser_run.add_argument('--password', help='Manually set the MQTT password for logs.')
|
|
|
|
parser_run.add_argument('--client-id', help='Manually set the client id for logs.')
|
|
|
|
|
|
|
|
parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from "
|
|
|
|
"retain messages.")
|
|
|
|
parser_clean.add_argument('--topic', help='Manually set the topic to subscribe to.')
|
|
|
|
parser_clean.add_argument('--username', help='Manually set the username.')
|
|
|
|
parser_clean.add_argument('--password', help='Manually set the password.')
|
|
|
|
parser_clean.add_argument('--client-id', help='Manually set the client id.')
|
|
|
|
|
2018-04-10 16:21:32 +02:00
|
|
|
subparsers.add_parser('wizard', help="A helpful setup wizard that will guide "
|
2019-02-13 16:54:02 +01:00
|
|
|
"you through setting up esphome.")
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2018-04-18 18:43:13 +02:00
|
|
|
subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.")
|
2018-05-21 16:40:22 +02:00
|
|
|
|
2019-02-13 16:54:02 +01:00
|
|
|
subparsers.add_parser('version', help="Print the esphome version and exit.")
|
2018-04-18 18:43:13 +02:00
|
|
|
|
2018-10-14 18:52:21 +02:00
|
|
|
subparsers.add_parser('clean', help="Delete all temporary build files.")
|
|
|
|
|
2018-05-27 14:15:24 +02:00
|
|
|
dashboard = subparsers.add_parser('dashboard',
|
2018-10-20 15:18:12 +02:00
|
|
|
help="Create a simple web server for a dashboard.")
|
2018-12-05 21:22:06 +01:00
|
|
|
dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.",
|
|
|
|
type=int, default=6052)
|
2019-10-13 13:52:02 +02:00
|
|
|
dashboard.add_argument("--username", help="The optional username to require "
|
|
|
|
"for authentication.",
|
|
|
|
type=str, default='')
|
|
|
|
dashboard.add_argument("--password", help="The optional password to require "
|
|
|
|
"for authentication.",
|
2018-06-07 20:47:06 +02:00
|
|
|
type=str, default='')
|
2018-10-04 19:01:02 +02:00
|
|
|
dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.",
|
|
|
|
action='store_true')
|
2018-12-05 21:22:06 +01:00
|
|
|
dashboard.add_argument("--hassio",
|
2019-04-22 21:56:30 +02:00
|
|
|
help=argparse.SUPPRESS,
|
2018-12-05 21:22:06 +01:00
|
|
|
action="store_true")
|
2019-01-02 12:21:26 +01:00
|
|
|
dashboard.add_argument("--socket",
|
|
|
|
help="Make the dashboard serve under a unix socket", type=str)
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2019-05-11 11:41:09 +02:00
|
|
|
vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
|
|
|
|
vscode.add_argument('--ace', action='store_true')
|
2019-04-22 21:56:30 +02:00
|
|
|
|
2019-06-07 14:26:28 +02:00
|
|
|
subparsers.add_parser('update-all', help=argparse.SUPPRESS)
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
return parser.parse_args(argv[1:])
|
|
|
|
|
|
|
|
|
2019-02-13 16:54:02 +01:00
|
|
|
def run_esphome(argv):
|
2018-05-21 16:40:22 +02:00
|
|
|
args = parse_args(argv)
|
2018-12-18 19:31:43 +01:00
|
|
|
CORE.dashboard = args.dashboard
|
2018-12-05 21:22:06 +01:00
|
|
|
|
2019-04-22 21:56:30 +02:00
|
|
|
setup_log(args.verbose, args.quiet)
|
2019-07-01 11:09:06 +02:00
|
|
|
if args.command != 'version' and not args.configuration:
|
2019-05-31 10:43:11 +02:00
|
|
|
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
|
|
|
return 1
|
|
|
|
|
2019-12-07 18:28:55 +01:00
|
|
|
if sys.version_info < (3, 6, 0):
|
|
|
|
_LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
|
|
|
|
"with this Python version. Please reinstall ESPHome with Python 3.6+")
|
|
|
|
return 1
|
2019-10-21 23:32:12 +02:00
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
if args.command in PRE_CONFIG_ACTIONS:
|
|
|
|
try:
|
|
|
|
return PRE_CONFIG_ACTIONS[args.command](args)
|
2019-02-13 16:54:02 +01:00
|
|
|
except EsphomeError as e:
|
2018-05-21 16:40:22 +02:00
|
|
|
_LOGGER.error(e)
|
|
|
|
return 1
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2019-07-01 11:09:06 +02:00
|
|
|
for conf_path in args.configuration:
|
|
|
|
CORE.config_path = conf_path
|
|
|
|
CORE.dashboard = args.dashboard
|
2018-04-18 18:43:13 +02:00
|
|
|
|
2020-06-21 20:33:01 +02:00
|
|
|
config = read_config(dict(args.substitution) if args.substitution else {})
|
2019-07-01 11:09:06 +02:00
|
|
|
if config is None:
|
|
|
|
return 1
|
|
|
|
CORE.config = config
|
|
|
|
|
|
|
|
if args.command not in POST_CONFIG_ACTIONS:
|
2019-12-07 18:28:55 +01:00
|
|
|
safe_print(f"Unknown command {args.command}")
|
2018-04-07 01:23:03 +02:00
|
|
|
|
2018-05-20 12:41:52 +02:00
|
|
|
try:
|
2019-07-01 11:09:06 +02:00
|
|
|
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
2019-02-13 16:54:02 +01:00
|
|
|
except EsphomeError as e:
|
2018-05-20 12:41:52 +02:00
|
|
|
_LOGGER.error(e)
|
|
|
|
return 1
|
2019-07-01 11:09:06 +02:00
|
|
|
if rc != 0:
|
|
|
|
return rc
|
|
|
|
|
|
|
|
CORE.reset()
|
|
|
|
return 0
|
2018-04-07 01:23:03 +02:00
|
|
|
|
|
|
|
|
2018-05-21 16:40:22 +02:00
|
|
|
def main():
|
|
|
|
try:
|
2019-02-13 16:54:02 +01:00
|
|
|
return run_esphome(sys.argv)
|
|
|
|
except EsphomeError as e:
|
2018-05-21 16:40:22 +02:00
|
|
|
_LOGGER.error(e)
|
|
|
|
return 1
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
2018-04-07 01:23:03 +02:00
|
|
|
if __name__ == "__main__":
|
|
|
|
sys.exit(main())
|