diff --git a/esphomeyaml/__main__.py b/esphomeyaml/__main__.py index 185d930b45..1afc30dfc5 100644 --- a/esphomeyaml/__main__.py +++ b/esphomeyaml/__main__.py @@ -444,12 +444,16 @@ def parse_args(argv): dashboard = subparsers.add_parser('dashboard', help="Create a simple web server for a dashboard.") - dashboard.add_argument("--port", help="The HTTP port to open connections on.", type=int, - default=6052) + dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.", + type=int, default=6052) dashboard.add_argument("--password", help="The optional password to require for all requests.", type=str, default='') dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.", action='store_true') + dashboard.add_argument("--hassio", + help="Internal flag used to tell esphomeyaml is started as a HassIO " + "add-on.", + action="store_true") subparsers.add_parser('hass-config', help="Dump the configuration entries that should be added" "to Home Assistant when not using MQTT discovery.") diff --git a/esphomeyaml/components/binary_sensor/custom.py b/esphomeyaml/components/binary_sensor/custom.py index e56592d40a..8fc3aa13c1 100644 --- a/esphomeyaml/components/binary_sensor/custom.py +++ b/esphomeyaml/components/binary_sensor/custom.py @@ -3,7 +3,7 @@ import voluptuous as vol from esphomeyaml.components import binary_sensor import esphomeyaml.config_validation as cv from esphomeyaml.const import CONF_BINARY_SENSORS, CONF_ID, CONF_LAMBDA -from esphomeyaml.cpp_generator import process_lambda +from esphomeyaml.cpp_generator import process_lambda, variable from esphomeyaml.cpp_types import std_vector CustomBinarySensorConstructor = binary_sensor.binary_sensor_ns.class_( diff --git a/esphomeyaml/components/display/__init__.py b/esphomeyaml/components/display/__init__.py index c05946f55c..db232551a4 100644 --- a/esphomeyaml/components/display/__init__.py +++ b/esphomeyaml/components/display/__init__.py @@ -1,9 +1,9 @@ # coding=utf-8 import voluptuous as vol -from esphomeyaml.components.text_sensor import add_job import esphomeyaml.config_validation as cv from esphomeyaml.const import CONF_LAMBDA, CONF_ROTATION, CONF_UPDATE_INTERVAL +from esphomeyaml.core import CORE from esphomeyaml.cpp_generator import add from esphomeyaml.cpp_types import esphomelib_ns @@ -52,7 +52,7 @@ def setup_display_core_(display_var, config): def setup_display(display_var, config): - add_job(setup_display_core_, display_var, config) + CORE.add_job(setup_display_core_, display_var, config) BUILD_FLAGS = '-DUSE_DISPLAY' diff --git a/esphomeyaml/components/output/esp8266_pwm.py b/esphomeyaml/components/output/esp8266_pwm.py index 009d9487f2..a42ff604be 100644 --- a/esphomeyaml/components/output/esp8266_pwm.py +++ b/esphomeyaml/components/output/esp8266_pwm.py @@ -6,7 +6,7 @@ import esphomeyaml.config_validation as cv from esphomeyaml.const import CONF_FREQUENCY, CONF_ID, CONF_NUMBER, CONF_PIN, ESP_PLATFORM_ESP8266 from esphomeyaml.cpp_generator import Pvariable, add from esphomeyaml.cpp_helpers import gpio_output_pin_expression, setup_component -from esphomeyaml.cpp_types import App +from esphomeyaml.cpp_types import App, Component ESP_PLATFORMS = [ESP_PLATFORM_ESP8266] diff --git a/esphomeyaml/core.py b/esphomeyaml/core.py index ec9f9f9e42..fb431a1511 100644 --- a/esphomeyaml/core.py +++ b/esphomeyaml/core.py @@ -6,13 +6,14 @@ import math import os import re -from typing import Any, Dict, List - from esphomeyaml.const import CONF_ARDUINO_VERSION, CONF_DOMAIN, CONF_ESPHOMELIB_VERSION, \ CONF_ESPHOMEYAML, CONF_HOSTNAME, CONF_LOCAL, CONF_MANUAL_IP, CONF_STATIC_IP, CONF_WIFI, \ ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266 from esphomeyaml.helpers import ensure_unique_string +# pylint: disable=unused-import, wrong-import-order +from typing import Any, Dict, List # noqa + _LOGGER = logging.getLogger(__name__) @@ -253,6 +254,7 @@ class ID(object): return hash(self.id) +# pylint: disable=too-many-instance-attributes class EsphomeyamlCore(object): def __init__(self): # The name of the node @@ -266,9 +268,9 @@ class EsphomeyamlCore(object): # The board that's used (for example nodemcuv2) self.board = None # type: str # The full raw configuration - self.raw_config = None # type: ConfigType + self.raw_config = {} # type: ConfigType # The validated configuration, this is None until the config has been validated - self.config = None # type: ConfigType + self.config = {} # type: ConfigType # The pending tasks in the task queue (mostly for C++ generation) self.pending_tasks = collections.deque() # The variable cache, for each ID this holds a MockObj of the variable obj diff --git a/esphomeyaml/cpp_generator.py b/esphomeyaml/cpp_generator.py index 6ae4a52d67..816f3ae3b8 100644 --- a/esphomeyaml/cpp_generator.py +++ b/esphomeyaml/cpp_generator.py @@ -1,11 +1,13 @@ from collections import OrderedDict -from typing import Any, Generator, List, Optional, Tuple, Union - -from esphomeyaml.core import CORE, HexInt, ID, Lambda, TimePeriod, TimePeriodMicroseconds, \ +from esphomeyaml.core import CORE, HexInt, Lambda, TimePeriod, TimePeriodMicroseconds, \ TimePeriodMilliseconds, TimePeriodSeconds from esphomeyaml.helpers import cpp_string_escape, indent_all_but_first_and_last +# pylint: disable=unused-import, wrong-import-order +from typing import Any, Generator, List, Optional, Tuple, Union # noqa +from esphomeyaml.core import ID # noqa + class Expression(object): def __init__(self): @@ -260,6 +262,7 @@ class FloatLiteral(Literal): return u"{:f}f".format(self.float_) +# pylint: disable=bad-continuation def safe_exp(obj # type: Union[Expression, bool, str, unicode, int, long, float, TimePeriod] ): # type: (...) -> Expression diff --git a/esphomeyaml/dashboard/dashboard.py b/esphomeyaml/dashboard/dashboard.py index 0c13918b79..eaec614eb2 100644 --- a/esphomeyaml/dashboard/dashboard.py +++ b/esphomeyaml/dashboard/dashboard.py @@ -10,28 +10,26 @@ import os import random import subprocess import threading +import urllib2 +import tornado +import tornado.concurrent +import tornado.gen +import tornado.ioloop +import tornado.iostream from tornado.log import access_log -from typing import Optional +import tornado.process +import tornado.web +import tornado.websocket from esphomeyaml import const from esphomeyaml.__main__ import get_serial_ports -from esphomeyaml.core import EsphomeyamlError from esphomeyaml.helpers import run_system_command from esphomeyaml.storage_json import StorageJSON, ext_storage_path from esphomeyaml.util import shlex_quote -try: - import tornado - import tornado.gen - import tornado.ioloop - import tornado.iostream - import tornado.process - import tornado.web - import tornado.websocket - import tornado.concurrent -except ImportError as err: - tornado = None +# pylint: disable=unused-import, wrong-import-order +from typing import Optional # noqa _LOGGER = logging.getLogger(__name__) CONFIG_DIR = '' @@ -274,9 +272,10 @@ class MainRequestHandler(BaseHandler): version = const.__version__ docs_link = 'https://beta.esphomelib.com/esphomeyaml/' if 'b' in version else \ 'https://esphomelib.com/esphomeyaml/' + mqtt_config = get_mqtt_config_lazy() self.render("templates/index.html", entries=entries, - version=version, begin=begin, docs_link=docs_link) + version=version, begin=begin, docs_link=docs_link, mqtt_config=mqtt_config) def _ping_func(filename, address): @@ -372,6 +371,7 @@ def make_app(debug=False): log_method = access_log.error request_time = 1000.0 * handler.request.request_time() + # pylint: disable=protected-access log_method("%d %s %.2fms", handler.get_status(), handler._request_summary(), request_time) @@ -395,13 +395,46 @@ def make_app(debug=False): return app +HASSIO_MQTT_CONFIG = None + + +def _get_mqtt_config_impl(): + token = os.getenv('HASSIO_TOKEN') + if token is None: + raise ValueError + + req = urllib2.Request('http://hassio/services/mqtt') + req.add_header('X-HASSIO-KEY', token) + resp = urllib2.urlopen(req) + content = resp.read() + mqtt_config = json.loads(content) + return { + 'addon': mqtt_config['addon'], + 'host': mqtt_config['host'], + 'username': mqtt_config.get('username', ''), + 'password': mqtt_config.get('password', '') + } + + +def get_mqtt_config_lazy(): + global HASSIO_MQTT_CONFIG + + if HASSIO_MQTT_CONFIG is None: + return None + + if not HASSIO_MQTT_CONFIG: + try: + HASSIO_MQTT_CONFIG = _get_mqtt_config_impl() + except Exception: # pylint: disable=broad-except + HASSIO_MQTT_CONFIG = None + + return HASSIO_MQTT_CONFIG + + def start_web_server(args): global CONFIG_DIR global PASSWORD - - if tornado is None: - raise EsphomeyamlError("Attempted to load dashboard, but tornado is not installed! " - "Please run \"pip2 install tornado esptool\" in your terminal.") + global HASSIO_MQTT_CONFIG CONFIG_DIR = args.configuration if not os.path.exists(CONFIG_DIR): @@ -409,10 +442,9 @@ def start_web_server(args): # HassIO options storage PASSWORD = args.password - if os.path.isfile('/data/options.json'): - with open('/data/options.json') as f: - js = json.load(f) - PASSWORD = js.get('password') or PASSWORD + + if args.hassio: + HASSIO_MQTT_CONFIG = False if PASSWORD: PASSWORD = hmac.new(str(PASSWORD)).digest() diff --git a/esphomeyaml/dashboard/templates/index.html b/esphomeyaml/dashboard/templates/index.html index 4fba0fcdb8..e0c61e1a92 100644 --- a/esphomeyaml/dashboard/templates/index.html +++ b/esphomeyaml/dashboard/templates/index.html @@ -446,29 +446,47 @@
MQTT
-

- esphomelib connects to your Home Assistant instance via - MQTT. If you haven't already, please set up - MQTT on your Home Assistant server, for example with the awesome - Mosquitto Hass.io Add-on. -

-

- When you're done with that, please enter your MQTT broker here. For example - 192.168.1.100 (Note - hassio.local doesn't always work, please use a static IP). - Please also specify the MQTT username and password you wish esphomelib to use - (leave them empty if you're not using any authentication). -

+ {% if mqtt_config is None %} +

+ esphomelib connects to your Home Assistant instance via + MQTT. + If you haven't already, please set up + MQTT on your Home Assistant server, for example with the + Mosquitto Hass.io Add-on. +

+

+ When you're done with that, please enter your MQTT broker here. For example + 192.168.1.100. + Please also specify the MQTT username and password you wish esphomelib to use + (leave them empty if you're not using any authentication). +

+ {% else %} +

+ It looks like you've already set up MQTT, the values below are taken from your "{{ escape(mqtt_config['addon']) }}" add-on. +

+ {% end %}
- + {% if mqtt_config is None %} + + {% else %} + + {% end %}
- + {% if mqtt_config is None %} + + {% else %} + + {% end%}
- + {% if mqtt_config is None %} + + {% else %} + + {% end %}
diff --git a/esphomeyaml/espota2.py b/esphomeyaml/espota2.py index b0244dd920..5f36381db1 100755 --- a/esphomeyaml/espota2.py +++ b/esphomeyaml/espota2.py @@ -31,7 +31,7 @@ MAGIC_BYTES = [0x6C, 0x26, 0xF7, 0x5C, 0x45] _LOGGER = logging.getLogger(__name__) -class ProgressBar: +class ProgressBar(object): def __init__(self): self.last_progress = None @@ -51,6 +51,7 @@ class ProgressBar: sys.stderr.write(text) sys.stderr.flush() + # pylint: disable=no-self-use def done(self): sys.stderr.write('\n') sys.stderr.flush() @@ -193,7 +194,7 @@ def perform_ota(sock, password, file_handle, filename): sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 8192) offset = 0 - bar = ProgressBar() + progress = ProgressBar() while True: chunk = file_handle.read(1024) if not chunk: @@ -206,8 +207,8 @@ def perform_ota(sock, password, file_handle, filename): sys.stderr.write('\n') raise OTAError("Error sending data: {}".format(err)) - bar.update(offset / float(file_size)) - bar.done() + progress.update(offset / float(file_size)) + progress.done() # Enable nodelay for last checks sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) diff --git a/esphomeyaml/mqtt.py b/esphomeyaml/mqtt.py index 34515b827b..d4bff66bbe 100644 --- a/esphomeyaml/mqtt.py +++ b/esphomeyaml/mqtt.py @@ -3,16 +3,16 @@ from __future__ import print_function from datetime import datetime import hashlib import logging +import socket import ssl import sys import paho.mqtt.client as mqtt -from esphomeyaml import core from esphomeyaml.const import CONF_BROKER, CONF_DISCOVERY_PREFIX, CONF_ESPHOMEYAML, \ CONF_LOG_TOPIC, CONF_MQTT, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_SSL_FINGERPRINTS, \ CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_USERNAME -from esphomeyaml.core import CORE +from esphomeyaml.core import CORE, EsphomeyamlError from esphomeyaml.helpers import color from esphomeyaml.util import safe_print @@ -41,7 +41,11 @@ def initialize(config, subscriptions, on_message, username, password, client_id) tls_version = ssl.PROTOCOL_SSLv23 client.tls_set(ca_certs=None, certfile=None, keyfile=None, cert_reqs=ssl.CERT_REQUIRED, tls_version=tls_version, ciphers=None) - client.connect(config[CONF_MQTT][CONF_BROKER], config[CONF_MQTT][CONF_PORT]) + + try: + client.connect(config[CONF_MQTT][CONF_BROKER], config[CONF_MQTT][CONF_PORT]) + except socket.error as err: + raise EsphomeyamlError("Cannot connect to MQTT broker: {}".format(err)) try: client.loop_forever() diff --git a/esphomeyaml/storage_json.py b/esphomeyaml/storage_json.py index 2a865b1c74..630163768f 100644 --- a/esphomeyaml/storage_json.py +++ b/esphomeyaml/storage_json.py @@ -2,11 +2,13 @@ import codecs import json import os -from typing import Any, Dict, Optional - -from esphomeyaml.core import CORE, CoreType +from esphomeyaml.core import CORE from esphomeyaml.helpers import mkdir_p +# pylint: disable=unused-import, wrong-import-order +from esphomeyaml.core import CoreType # noqa +from typing import Any, Dict, Optional # noqa + def storage_path(): # type: () -> str return CORE.relative_path('.esphomeyaml', '{}.json'.format(CORE.config_filename)) @@ -16,6 +18,7 @@ def ext_storage_path(base_path, config_filename): # type: (str, str) -> str return os.path.join(base_path, '.esphomeyaml', '{}.json'.format(config_filename)) +# pylint: disable=too-many-instance-attributes class StorageJSON(object): def __init__(self, storage_version, name, esphomelib_version, src_version, arduino_version, address, esp_platform, board, build_path, diff --git a/esphomeyaml/writer.py b/esphomeyaml/writer.py index 169a15f894..17f7a89df2 100644 --- a/esphomeyaml/writer.py +++ b/esphomeyaml/writer.py @@ -325,8 +325,8 @@ def write_platformio_ini(content, path): else: prev_file = None content_format = INI_BASE_FORMAT - full_file = content_format[0] + INI_AUTO_GENERATE_BEGIN + '\n' + \ - content + INI_AUTO_GENERATE_END + content_format[1] + full_file = content_format[0] + INI_AUTO_GENERATE_BEGIN + '\n' + content + full_file += INI_AUTO_GENERATE_END + content_format[1] if prev_file == full_file: return with codecs.open(path, mode='w+', encoding='utf-8') as f_handle: @@ -371,9 +371,9 @@ def write_cpp(code_s): include_s = get_include_text() - full_file = code_format[0] + CPP_INCLUDE_BEGIN + u'\n' + include_s + CPP_INCLUDE_END + \ - code_format[1] + CPP_AUTO_GENERATE_BEGIN + u'\n' + code_s + \ - CPP_AUTO_GENERATE_END + code_format[2] + full_file = code_format[0] + CPP_INCLUDE_BEGIN + u'\n' + include_s + CPP_INCLUDE_END + full_file += code_format[1] + CPP_AUTO_GENERATE_BEGIN + u'\n' + code_s + CPP_AUTO_GENERATE_END + full_file += code_format[2] if prev_file == full_file: return with codecs.open(path, 'w+', encoding='utf-8') as f_handle: