From 5a7b66e207c0622b53c309e13cd38ffdf88cbc67 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 24 Dec 2018 14:15:24 +0100 Subject: [PATCH] Fix some stuff --- Dockerfile | 6 +- esphomeyaml/__main__.py | 112 +++++++++++--------- esphomeyaml/api/api.proto | 1 + esphomeyaml/api/api_pb2.py | 119 +++++++++++++-------- esphomeyaml/api/client.py | 120 ++++++++++++---------- esphomeyaml/components/logger.py | 12 ++- esphomeyaml/components/sensor/__init__.py | 8 +- esphomeyaml/components/wifi.py | 22 ++-- esphomeyaml/config.py | 5 +- esphomeyaml/core.py | 10 +- esphomeyaml/espota2.py | 2 +- esphomeyaml/writer.py | 2 +- 12 files changed, 242 insertions(+), 177 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2c3d4da606..48342c0124 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,11 +18,7 @@ VOLUME /config WORKDIR /usr/src/app COPY docker/platformio.ini /pio/platformio.ini -ARG ESPHOMELIB_VERSION="" -RUN platformio run -d /pio; rm -rf /pio && \ - /bin/bash -c "if [ ! -z '$ESPHOMELIB_VERSION']; then \ - platformio lib -g install '${ESPHOMELIB_VERSION}'; \ - fi" +RUN platformio run -d /pio; rm -rf /pio COPY . . RUN pip install --no-cache-dir --no-binary :all: -e . && \ diff --git a/esphomeyaml/__main__.py b/esphomeyaml/__main__.py index 8b04e1df95..f0987a0913 100644 --- a/esphomeyaml/__main__.py +++ b/esphomeyaml/__main__.py @@ -10,10 +10,11 @@ import sys from esphomeyaml import const, core_config, mqtt, platformio_api, wizard, writer, yaml_util from esphomeyaml.api.client import run_logs +from esphomeyaml.components import wifi from esphomeyaml.config import get_component, iter_components, read_config, strip_default_ids from esphomeyaml.const import CONF_BAUD_RATE, CONF_DOMAIN, CONF_ESPHOMEYAML, \ CONF_HOSTNAME, CONF_LOGGER, CONF_MANUAL_IP, CONF_NAME, CONF_STATIC_IP, CONF_USE_CUSTOM_CODE, \ - CONF_WIFI + CONF_WIFI, CONF_BROKER from esphomeyaml.core import CORE, EsphomeyamlError from esphomeyaml.cpp_generator import Expression, RawStatement, add, statement from esphomeyaml.helpers import color, indent @@ -39,31 +40,57 @@ def get_serial_ports(): return result -def choose_serial_port(config): - result = get_serial_ports() +def choose_prompt(options): + if not options: + raise ValueError + + if len(options) == 1: + return options[0][1] + + safe_print(u"Found multiple options, please choose one:") + for i, (desc, _) in enumerate(options): + safe_print(u" [{}] {}".format(i + 1, desc)) - if not result: - return 'OTA' - safe_print(u"Found multiple serial port options, please choose one:") - for i, (res, desc) in enumerate(result): - safe_print(u" [{}] {} ({})".format(i, res, desc)) - safe_print(u" [{}] Over The Air ({})".format(len(result), get_upload_host(config))) - safe_print() while True: opt = raw_input('(number): ') - if opt in result: - opt = result.index(opt) + if opt in options: + opt = options.index(opt) break try: opt = int(opt) - if opt < 0 or opt > len(result): + if opt < 1 or opt > len(options): raise ValueError break except ValueError: safe_print(color('red', u"Invalid option: '{}'".format(opt))) - if opt == len(result): - return 'OTA' - return result[opt][0] + return options[opt - 1][1] + + +def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api): + options = [] + for res, desc in get_serial_ports(): + options.append((u"{} ({})".format(res, desc), res)) + if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config): + options.append((u"Over The Air ({})".format(CORE.address), CORE.address)) + if default == 'OTA': + return CORE.address + if show_mqtt and 'mqtt' in CORE.config: + options.append((u"MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT')) + 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' def run_miniterm(config, port): @@ -132,16 +159,6 @@ def compile_program(args, config): return rc -def get_upload_host(config): - if CONF_MANUAL_IP in config[CONF_WIFI]: - host = str(config[CONF_WIFI][CONF_MANUAL_IP][CONF_STATIC_IP]) - elif CONF_HOSTNAME in config[CONF_WIFI]: - host = config[CONF_WIFI][CONF_HOSTNAME] + config[CONF_WIFI][CONF_DOMAIN] - else: - host = config[CONF_ESPHOMEYAML][CONF_NAME] + config[CONF_WIFI][CONF_DOMAIN] - return host - - def upload_using_esptool(config, port): import esptool @@ -152,24 +169,12 @@ def upload_using_esptool(config, port): return run_external_command(esptool._main, *cmd) -def upload_program(config, args, port): +def upload_program(config, args, host): # if upload is to a serial port use platformio, otherwise assume ota - serial_port = port.startswith('/') or port.startswith('COM') - if port != 'OTA' and serial_port: + if get_port_type(host) == 'SERIAL': if CORE.is_esp8266: - return upload_using_esptool(config, port) - return platformio_api.run_upload(config, args.verbose, port) - - if 'ota' not in config: - _LOGGER.error("No serial port found and OTA not enabled. Can't upload!") - return -1 - - # If hostname/ip is explicitly provided as upload-port argument, use this instead of zeroconf - # hostname. This is to support use cases where zeroconf (hostname.local) does not work. - if port != 'OTA': - host = port - else: - host = get_upload_host(config) + return upload_using_esptool(config, host) + return platformio_api.run_upload(config, args.verbose, host) from esphomeyaml.components import ota from esphomeyaml import espota2 @@ -199,13 +204,15 @@ def upload_program(config, args, port): def show_logs(config, args, port): - serial_port = port.startswith('/') or port.startswith('COM') - if port != 'OTA' and serial_port: + if get_port_type(port) == 'SERIAL': run_miniterm(config, port) return 0 - if 'api' in config: - return run_logs(config, get_upload_host(config)) - return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id) + elif get_port_type(port) == 'NETWORK': + return run_logs(config, port) + elif get_port_type(port) == 'MQTT': + return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id) + + raise ValueError def clean_mqtt(config, args): @@ -266,7 +273,8 @@ def command_compile(args, config): def command_upload(args, config): - port = args.upload_port or choose_serial_port(config) + port = choose_upload_log_host(default=args.upload_port, check_default=None, + show_ota=True, show_mqtt=False, show_api=False) exit_code = upload_program(config, args, port) if exit_code != 0: return exit_code @@ -275,7 +283,8 @@ def command_upload(args, config): def command_logs(args, config): - port = args.serial_port or choose_serial_port(config) + port = choose_upload_log_host(default=args.serial_port, check_default=None, + show_ota=False, show_mqtt=True, show_api=True) return show_logs(config, args, port) @@ -287,13 +296,16 @@ def command_run(args, config): if exit_code != 0: return exit_code _LOGGER.info(u"Successfully compiled program.") - port = args.upload_port or choose_serial_port(config) + port = choose_upload_log_host(default=args.upload_port, check_default=None, + show_ota=True, show_mqtt=False, show_api=True) exit_code = upload_program(config, args, port) if exit_code != 0: return exit_code _LOGGER.info(u"Successfully uploaded program.") if args.no_logs: return 0 + port = choose_upload_log_host(default=args.upload_port, check_default=port, + show_ota=False, show_mqtt=True, show_api=True) return show_logs(config, args, port) diff --git a/esphomeyaml/api/api.proto b/esphomeyaml/api/api.proto index 33d087099a..bde079be16 100644 --- a/esphomeyaml/api/api.proto +++ b/esphomeyaml/api/api.proto @@ -290,6 +290,7 @@ message SubscribeLogsResponse { LogLevel level = 1; string tag = 2; string message = 3; + bool send_failed = 4; } message SubscribeServiceCallsRequest { diff --git a/esphomeyaml/api/api_pb2.py b/esphomeyaml/api/api_pb2.py index 3c6131c287..2212db8339 100644 --- a/esphomeyaml/api/api_pb2.py +++ b/esphomeyaml/api/api_pb2.py @@ -20,7 +20,7 @@ DESCRIPTOR = _descriptor.FileDescriptor( package='', syntax='proto3', serialized_options=None, - serialized_pb=_b('\n\tapi.proto\"#\n\x0cHelloRequest\x12\x13\n\x0b\x63lient_info\x18\x01 \x01(\t\"Z\n\rHelloResponse\x12\x19\n\x11\x61pi_version_major\x18\x01 \x01(\r\x12\x19\n\x11\x61pi_version_minor\x18\x02 \x01(\r\x12\x13\n\x0bserver_info\x18\x03 \x01(\t\"\"\n\x0e\x43onnectRequest\x12\x10\n\x08password\x18\x01 \x01(\t\"+\n\x0f\x43onnectResponse\x12\x18\n\x10invalid_password\x18\x01 \x01(\x08\"\x13\n\x11\x44isconnectRequest\"\x14\n\x12\x44isconnectResponse\"\r\n\x0bPingRequest\"\x0e\n\x0cPingResponse\"\x13\n\x11\x44\x65viceInfoRequest\"\xad\x01\n\x12\x44\x65viceInfoResponse\x12\x15\n\ruses_password\x18\x01 \x01(\x08\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0bmac_address\x18\x03 \x01(\t\x12\x1c\n\x14\x65sphome_core_version\x18\x04 \x01(\t\x12\x18\n\x10\x63ompilation_time\x18\x05 \x01(\t\x12\r\n\x05model\x18\x06 \x01(\t\x12\x16\n\x0ehas_deep_sleep\x18\x07 \x01(\x08\"\x15\n\x13ListEntitiesRequest\"\x9a\x01\n ListEntitiesBinarySensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x14\n\x0c\x64\x65vice_class\x18\x05 \x01(\t\x12\x1f\n\x17is_status_binary_sensor\x18\x06 \x01(\x08\"s\n\x19ListEntitiesCoverResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x15\n\ris_optimistic\x18\x05 \x01(\x08\"\x90\x01\n\x17ListEntitiesFanResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1c\n\x14supports_oscillation\x18\x05 \x01(\x08\x12\x16\n\x0esupports_speed\x18\x06 \x01(\x08\"\x8a\x02\n\x19ListEntitiesLightResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1b\n\x13supports_brightness\x18\x05 \x01(\x08\x12\x14\n\x0csupports_rgb\x18\x06 \x01(\x08\x12\x1c\n\x14supports_white_value\x18\x07 \x01(\x08\x12\"\n\x1asupports_color_temperature\x18\x08 \x01(\x08\x12\x12\n\nmin_mireds\x18\t \x01(\x02\x12\x12\n\nmax_mireds\x18\n \x01(\x02\x12\x0f\n\x07\x65\x66\x66\x65\x63ts\x18\x0b \x03(\t\"\xa3\x01\n\x1aListEntitiesSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x1b\n\x13unit_of_measurement\x18\x06 \x01(\t\x12\x19\n\x11\x61\x63\x63uracy_decimals\x18\x07 \x01(\x05\"\x7f\n\x1aListEntitiesSwitchResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x12\n\noptimistic\x18\x06 \x01(\x08\"o\n\x1eListEntitiesTextSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\"\x1a\n\x18ListEntitiesDoneResponse\"\x18\n\x16SubscribeStatesRequest\"7\n\x19\x42inarySensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\"t\n\x12\x43overStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12-\n\x05state\x18\x02 \x01(\x0e\x32\x1e.CoverStateResponse.CoverState\"\"\n\nCoverState\x12\x08\n\x04OPEN\x10\x00\x12\n\n\x06\x43LOSED\x10\x01\"]\n\x10\x46\x61nStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x13\n\x0boscillating\x18\x03 \x01(\x08\x12\x18\n\x05speed\x18\x04 \x01(\x0e\x32\t.FanSpeed\"\xa8\x01\n\x12LightStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x12\n\nbrightness\x18\x03 \x01(\x02\x12\x0b\n\x03red\x18\x04 \x01(\x02\x12\r\n\x05green\x18\x05 \x01(\x02\x12\x0c\n\x04\x62lue\x18\x06 \x01(\x02\x12\r\n\x05white\x18\x07 \x01(\x02\x12\x19\n\x11\x63olor_temperature\x18\x08 \x01(\x02\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\t \x01(\t\"1\n\x13SensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x02\"1\n\x13SwitchStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\"5\n\x17TextSensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\t\"\x98\x01\n\x13\x43overCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\x32\n\x07\x63ommand\x18\x03 \x01(\x0e\x32!.CoverCommandRequest.CoverCommand\"-\n\x0c\x43overCommand\x12\x08\n\x04OPEN\x10\x00\x12\t\n\x05\x43LOSE\x10\x01\x12\x08\n\x04STOP\x10\x02\"\x9d\x01\n\x11\x46\x61nCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x11\n\thas_speed\x18\x04 \x01(\x08\x12\x18\n\x05speed\x18\x05 \x01(\x0e\x32\t.FanSpeed\x12\x17\n\x0fhas_oscillating\x18\x06 \x01(\x08\x12\x13\n\x0boscillating\x18\x07 \x01(\x08\"\x95\x03\n\x13LightCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x16\n\x0ehas_brightness\x18\x04 \x01(\x08\x12\x12\n\nbrightness\x18\x05 \x01(\x02\x12\x0f\n\x07has_rgb\x18\x06 \x01(\x08\x12\x0b\n\x03red\x18\x07 \x01(\x02\x12\r\n\x05green\x18\x08 \x01(\x02\x12\x0c\n\x04\x62lue\x18\t \x01(\x02\x12\x11\n\thas_white\x18\n \x01(\x08\x12\r\n\x05white\x18\x0b \x01(\x02\x12\x1d\n\x15has_color_temperature\x18\x0c \x01(\x08\x12\x19\n\x11\x63olor_temperature\x18\r \x01(\x02\x12\x1d\n\x15has_transition_length\x18\x0e \x01(\x08\x12\x19\n\x11transition_length\x18\x0f \x01(\r\x12\x18\n\x10has_flash_length\x18\x10 \x01(\x08\x12\x14\n\x0c\x66lash_length\x18\x11 \x01(\r\x12\x12\n\nhas_effect\x18\x12 \x01(\x08\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\x13 \x01(\t\"2\n\x14SwitchCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\"E\n\x14SubscribeLogsRequest\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x13\n\x0b\x64ump_config\x18\x02 \x01(\x08\"O\n\x15SubscribeLogsResponse\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x0b\n\x03tag\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\"\x1e\n\x1cSubscribeServiceCallsRequest\"\xdf\x02\n\x13ServiceCallResponse\x12\x0f\n\x07service\x18\x01 \x01(\t\x12,\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x1e.ServiceCallResponse.DataEntry\x12=\n\rdata_template\x18\x03 \x03(\x0b\x32&.ServiceCallResponse.DataTemplateEntry\x12\x36\n\tvariables\x18\x04 \x03(\x0b\x32#.ServiceCallResponse.VariablesEntry\x1a+\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11\x44\x61taTemplateEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x30\n\x0eVariablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"7\n\"SubscribeHomeAssistantStateRequest\x12\x11\n\tentity_id\x18\x01 \x01(\t\"G\n#SubscribeHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t\"\x10\n\x0eGetTimeRequest\"(\n\x0fGetTimeResponse\x12\x15\n\repoch_seconds\x18\x01 \x01(\x07*)\n\x08\x46\x61nSpeed\x12\x07\n\x03LOW\x10\x00\x12\n\n\x06MEDIUM\x10\x01\x12\x08\n\x04HIGH\x10\x02*]\n\x08LogLevel\x12\x08\n\x04NONE\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x08\n\x04WARN\x10\x02\x12\x08\n\x04INFO\x10\x03\x12\t\n\x05\x44\x45\x42UG\x10\x04\x12\x0b\n\x07VERBOSE\x10\x05\x12\x10\n\x0cVERY_VERBOSE\x10\x06\x62\x06proto3') + serialized_pb=_b('\n\tapi.proto\"#\n\x0cHelloRequest\x12\x13\n\x0b\x63lient_info\x18\x01 \x01(\t\"Z\n\rHelloResponse\x12\x19\n\x11\x61pi_version_major\x18\x01 \x01(\r\x12\x19\n\x11\x61pi_version_minor\x18\x02 \x01(\r\x12\x13\n\x0bserver_info\x18\x03 \x01(\t\"\"\n\x0e\x43onnectRequest\x12\x10\n\x08password\x18\x01 \x01(\t\"+\n\x0f\x43onnectResponse\x12\x18\n\x10invalid_password\x18\x01 \x01(\x08\"\x13\n\x11\x44isconnectRequest\"\x14\n\x12\x44isconnectResponse\"\r\n\x0bPingRequest\"\x0e\n\x0cPingResponse\"\x13\n\x11\x44\x65viceInfoRequest\"\xad\x01\n\x12\x44\x65viceInfoResponse\x12\x15\n\ruses_password\x18\x01 \x01(\x08\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0bmac_address\x18\x03 \x01(\t\x12\x1c\n\x14\x65sphome_core_version\x18\x04 \x01(\t\x12\x18\n\x10\x63ompilation_time\x18\x05 \x01(\t\x12\r\n\x05model\x18\x06 \x01(\t\x12\x16\n\x0ehas_deep_sleep\x18\x07 \x01(\x08\"\x15\n\x13ListEntitiesRequest\"\x9a\x01\n ListEntitiesBinarySensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x14\n\x0c\x64\x65vice_class\x18\x05 \x01(\t\x12\x1f\n\x17is_status_binary_sensor\x18\x06 \x01(\x08\"s\n\x19ListEntitiesCoverResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x15\n\ris_optimistic\x18\x05 \x01(\x08\"\x90\x01\n\x17ListEntitiesFanResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1c\n\x14supports_oscillation\x18\x05 \x01(\x08\x12\x16\n\x0esupports_speed\x18\x06 \x01(\x08\"\x8a\x02\n\x19ListEntitiesLightResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1b\n\x13supports_brightness\x18\x05 \x01(\x08\x12\x14\n\x0csupports_rgb\x18\x06 \x01(\x08\x12\x1c\n\x14supports_white_value\x18\x07 \x01(\x08\x12\"\n\x1asupports_color_temperature\x18\x08 \x01(\x08\x12\x12\n\nmin_mireds\x18\t \x01(\x02\x12\x12\n\nmax_mireds\x18\n \x01(\x02\x12\x0f\n\x07\x65\x66\x66\x65\x63ts\x18\x0b \x03(\t\"\xa3\x01\n\x1aListEntitiesSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x1b\n\x13unit_of_measurement\x18\x06 \x01(\t\x12\x19\n\x11\x61\x63\x63uracy_decimals\x18\x07 \x01(\x05\"\x7f\n\x1aListEntitiesSwitchResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x12\n\noptimistic\x18\x06 \x01(\x08\"o\n\x1eListEntitiesTextSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\"\x1a\n\x18ListEntitiesDoneResponse\"\x18\n\x16SubscribeStatesRequest\"7\n\x19\x42inarySensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\"t\n\x12\x43overStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12-\n\x05state\x18\x02 \x01(\x0e\x32\x1e.CoverStateResponse.CoverState\"\"\n\nCoverState\x12\x08\n\x04OPEN\x10\x00\x12\n\n\x06\x43LOSED\x10\x01\"]\n\x10\x46\x61nStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x13\n\x0boscillating\x18\x03 \x01(\x08\x12\x18\n\x05speed\x18\x04 \x01(\x0e\x32\t.FanSpeed\"\xa8\x01\n\x12LightStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x12\n\nbrightness\x18\x03 \x01(\x02\x12\x0b\n\x03red\x18\x04 \x01(\x02\x12\r\n\x05green\x18\x05 \x01(\x02\x12\x0c\n\x04\x62lue\x18\x06 \x01(\x02\x12\r\n\x05white\x18\x07 \x01(\x02\x12\x19\n\x11\x63olor_temperature\x18\x08 \x01(\x02\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\t \x01(\t\"1\n\x13SensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x02\"1\n\x13SwitchStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\"5\n\x17TextSensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\t\"\x98\x01\n\x13\x43overCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\x32\n\x07\x63ommand\x18\x03 \x01(\x0e\x32!.CoverCommandRequest.CoverCommand\"-\n\x0c\x43overCommand\x12\x08\n\x04OPEN\x10\x00\x12\t\n\x05\x43LOSE\x10\x01\x12\x08\n\x04STOP\x10\x02\"\x9d\x01\n\x11\x46\x61nCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x11\n\thas_speed\x18\x04 \x01(\x08\x12\x18\n\x05speed\x18\x05 \x01(\x0e\x32\t.FanSpeed\x12\x17\n\x0fhas_oscillating\x18\x06 \x01(\x08\x12\x13\n\x0boscillating\x18\x07 \x01(\x08\"\x95\x03\n\x13LightCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x16\n\x0ehas_brightness\x18\x04 \x01(\x08\x12\x12\n\nbrightness\x18\x05 \x01(\x02\x12\x0f\n\x07has_rgb\x18\x06 \x01(\x08\x12\x0b\n\x03red\x18\x07 \x01(\x02\x12\r\n\x05green\x18\x08 \x01(\x02\x12\x0c\n\x04\x62lue\x18\t \x01(\x02\x12\x11\n\thas_white\x18\n \x01(\x08\x12\r\n\x05white\x18\x0b \x01(\x02\x12\x1d\n\x15has_color_temperature\x18\x0c \x01(\x08\x12\x19\n\x11\x63olor_temperature\x18\r \x01(\x02\x12\x1d\n\x15has_transition_length\x18\x0e \x01(\x08\x12\x19\n\x11transition_length\x18\x0f \x01(\r\x12\x18\n\x10has_flash_length\x18\x10 \x01(\x08\x12\x14\n\x0c\x66lash_length\x18\x11 \x01(\r\x12\x12\n\nhas_effect\x18\x12 \x01(\x08\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\x13 \x01(\t\"2\n\x14SwitchCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\"E\n\x14SubscribeLogsRequest\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x13\n\x0b\x64ump_config\x18\x02 \x01(\x08\"d\n\x15SubscribeLogsResponse\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x0b\n\x03tag\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x13\n\x0bsend_failed\x18\x04 \x01(\x08\"\x1e\n\x1cSubscribeServiceCallsRequest\"\xdf\x02\n\x13ServiceCallResponse\x12\x0f\n\x07service\x18\x01 \x01(\t\x12,\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x1e.ServiceCallResponse.DataEntry\x12=\n\rdata_template\x18\x03 \x03(\x0b\x32&.ServiceCallResponse.DataTemplateEntry\x12\x36\n\tvariables\x18\x04 \x03(\x0b\x32#.ServiceCallResponse.VariablesEntry\x1a+\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11\x44\x61taTemplateEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x30\n\x0eVariablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"%\n#SubscribeHomeAssistantStatesRequest\"8\n#SubscribeHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t\">\n\x1aHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t\"\x10\n\x0eGetTimeRequest\"(\n\x0fGetTimeResponse\x12\x15\n\repoch_seconds\x18\x01 \x01(\x07*)\n\x08\x46\x61nSpeed\x12\x07\n\x03LOW\x10\x00\x12\n\n\x06MEDIUM\x10\x01\x12\x08\n\x04HIGH\x10\x02*]\n\x08LogLevel\x12\x08\n\x04NONE\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x08\n\x04WARN\x10\x02\x12\x08\n\x04INFO\x10\x03\x12\t\n\x05\x44\x45\x42UG\x10\x04\x12\x0b\n\x07VERBOSE\x10\x05\x12\x10\n\x0cVERY_VERBOSE\x10\x06\x62\x06proto3') ) _FANSPEED = _descriptor.EnumDescriptor( @@ -44,8 +44,8 @@ _FANSPEED = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=3770, - serialized_end=3811, + serialized_start=3822, + serialized_end=3863, ) _sym_db.RegisterEnumDescriptor(_FANSPEED) @@ -87,8 +87,8 @@ _LOGLEVEL = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=3813, - serialized_end=3906, + serialized_start=3865, + serialized_end=3958, ) _sym_db.RegisterEnumDescriptor(_LOGLEVEL) @@ -1757,6 +1757,13 @@ _SUBSCRIBELOGSRESPONSE = _descriptor.Descriptor( message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='send_failed', full_name='SubscribeLogsResponse.send_failed', index=3, + number=4, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -1770,7 +1777,7 @@ _SUBSCRIBELOGSRESPONSE = _descriptor.Descriptor( oneofs=[ ], serialized_start=3113, - serialized_end=3192, + serialized_end=3213, ) @@ -1793,8 +1800,8 @@ _SUBSCRIBESERVICECALLSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3194, - serialized_end=3224, + serialized_start=3215, + serialized_end=3245, ) @@ -1831,8 +1838,8 @@ _SERVICECALLRESPONSE_DATAENTRY = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3432, - serialized_end=3475, + serialized_start=3453, + serialized_end=3496, ) _SERVICECALLRESPONSE_DATATEMPLATEENTRY = _descriptor.Descriptor( @@ -1868,8 +1875,8 @@ _SERVICECALLRESPONSE_DATATEMPLATEENTRY = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3477, - serialized_end=3528, + serialized_start=3498, + serialized_end=3549, ) _SERVICECALLRESPONSE_VARIABLESENTRY = _descriptor.Descriptor( @@ -1905,8 +1912,8 @@ _SERVICECALLRESPONSE_VARIABLESENTRY = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3530, - serialized_end=3578, + serialized_start=3551, + serialized_end=3599, ) _SERVICECALLRESPONSE = _descriptor.Descriptor( @@ -1956,25 +1963,18 @@ _SERVICECALLRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3227, - serialized_end=3578, + serialized_start=3248, + serialized_end=3599, ) -_SUBSCRIBEHOMEASSISTANTSTATEREQUEST = _descriptor.Descriptor( - name='SubscribeHomeAssistantStateRequest', - full_name='SubscribeHomeAssistantStateRequest', +_SUBSCRIBEHOMEASSISTANTSTATESREQUEST = _descriptor.Descriptor( + name='SubscribeHomeAssistantStatesRequest', + full_name='SubscribeHomeAssistantStatesRequest', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - _descriptor.FieldDescriptor( - name='entity_id', full_name='SubscribeHomeAssistantStateRequest.entity_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -1987,8 +1987,8 @@ _SUBSCRIBEHOMEASSISTANTSTATEREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3580, - serialized_end=3635, + serialized_start=3601, + serialized_end=3638, ) @@ -2006,8 +2006,39 @@ _SUBSCRIBEHOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor( message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3640, + serialized_end=3696, +) + + +_HOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor( + name='HomeAssistantStateResponse', + full_name='HomeAssistantStateResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ _descriptor.FieldDescriptor( - name='state', full_name='SubscribeHomeAssistantStateResponse.state', index=1, + name='entity_id', full_name='HomeAssistantStateResponse.entity_id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='state', full_name='HomeAssistantStateResponse.state', index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, @@ -2025,8 +2056,8 @@ _SUBSCRIBEHOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3637, - serialized_end=3708, + serialized_start=3698, + serialized_end=3760, ) @@ -2049,8 +2080,8 @@ _GETTIMEREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3710, - serialized_end=3726, + serialized_start=3762, + serialized_end=3778, ) @@ -2080,8 +2111,8 @@ _GETTIMERESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3728, - serialized_end=3768, + serialized_start=3780, + serialized_end=3820, ) _COVERSTATERESPONSE.fields_by_name['state'].enum_type = _COVERSTATERESPONSE_COVERSTATE @@ -2133,8 +2164,9 @@ DESCRIPTOR.message_types_by_name['SubscribeLogsRequest'] = _SUBSCRIBELOGSREQUEST DESCRIPTOR.message_types_by_name['SubscribeLogsResponse'] = _SUBSCRIBELOGSRESPONSE DESCRIPTOR.message_types_by_name['SubscribeServiceCallsRequest'] = _SUBSCRIBESERVICECALLSREQUEST DESCRIPTOR.message_types_by_name['ServiceCallResponse'] = _SERVICECALLRESPONSE -DESCRIPTOR.message_types_by_name['SubscribeHomeAssistantStateRequest'] = _SUBSCRIBEHOMEASSISTANTSTATEREQUEST +DESCRIPTOR.message_types_by_name['SubscribeHomeAssistantStatesRequest'] = _SUBSCRIBEHOMEASSISTANTSTATESREQUEST DESCRIPTOR.message_types_by_name['SubscribeHomeAssistantStateResponse'] = _SUBSCRIBEHOMEASSISTANTSTATERESPONSE +DESCRIPTOR.message_types_by_name['HomeAssistantStateResponse'] = _HOMEASSISTANTSTATERESPONSE DESCRIPTOR.message_types_by_name['GetTimeRequest'] = _GETTIMEREQUEST DESCRIPTOR.message_types_by_name['GetTimeResponse'] = _GETTIMERESPONSE DESCRIPTOR.enum_types_by_name['FanSpeed'] = _FANSPEED @@ -2410,12 +2442,12 @@ _sym_db.RegisterMessage(ServiceCallResponse.DataEntry) _sym_db.RegisterMessage(ServiceCallResponse.DataTemplateEntry) _sym_db.RegisterMessage(ServiceCallResponse.VariablesEntry) -SubscribeHomeAssistantStateRequest = _reflection.GeneratedProtocolMessageType('SubscribeHomeAssistantStateRequest', (_message.Message,), dict( - DESCRIPTOR = _SUBSCRIBEHOMEASSISTANTSTATEREQUEST, +SubscribeHomeAssistantStatesRequest = _reflection.GeneratedProtocolMessageType('SubscribeHomeAssistantStatesRequest', (_message.Message,), dict( + DESCRIPTOR = _SUBSCRIBEHOMEASSISTANTSTATESREQUEST, __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStateRequest) + # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStatesRequest) )) -_sym_db.RegisterMessage(SubscribeHomeAssistantStateRequest) +_sym_db.RegisterMessage(SubscribeHomeAssistantStatesRequest) SubscribeHomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType('SubscribeHomeAssistantStateResponse', (_message.Message,), dict( DESCRIPTOR = _SUBSCRIBEHOMEASSISTANTSTATERESPONSE, @@ -2424,6 +2456,13 @@ SubscribeHomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType(' )) _sym_db.RegisterMessage(SubscribeHomeAssistantStateResponse) +HomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType('HomeAssistantStateResponse', (_message.Message,), dict( + DESCRIPTOR = _HOMEASSISTANTSTATERESPONSE, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:HomeAssistantStateResponse) + )) +_sym_db.RegisterMessage(HomeAssistantStateResponse) + GetTimeRequest = _reflection.GeneratedProtocolMessageType('GetTimeRequest', (_message.Message,), dict( DESCRIPTOR = _GETTIMEREQUEST, __module__ = 'api_pb2' diff --git a/esphomeyaml/api/client.py b/esphomeyaml/api/client.py index a3fe23e7ee..58516a48bf 100644 --- a/esphomeyaml/api/client.py +++ b/esphomeyaml/api/client.py @@ -13,7 +13,7 @@ from esphomeyaml import const import esphomeyaml.api.api_pb2 as pb from esphomeyaml.const import CONF_PASSWORD, CONF_PORT from esphomeyaml.core import EsphomeyamlError -from esphomeyaml.helpers import resolve_ip_address +from esphomeyaml.helpers import resolve_ip_address, indent, color from esphomeyaml.util import safe_print _LOGGER = logging.getLogger(__name__) @@ -100,6 +100,8 @@ class APIClient(threading.Thread): self._port = port # type: int self._password = password # type: Optional[str] self._socket = None # type: Optional[socket.socket] + self._socket_open_event = threading.Event() + self._socket_write_lock = threading.Lock() self._connected = False self._authenticated = False self._message_handlers = [] @@ -111,9 +113,8 @@ class APIClient(threading.Thread): self.on_connect = None self.on_login = None self.auto_reconnect = False - self._running = False + self._running_event = threading.Event() self._stop_event = threading.Event() - self._socket_open = False @property def stopped(self): @@ -131,12 +132,28 @@ class APIClient(threading.Thread): try: self.ping() except APIConnectionError: - self._on_error() - self._refresh_ping() + self._fatal_error() + else: + self._refresh_ping() self._ping_timer = threading.Timer(self._keepalive, func) self._ping_timer.start() + def _cancel_ping(self): + if self._ping_timer is not None: + self._ping_timer.cancel() + self._ping_timer = None + + def _close_socket(self): + self._cancel_ping() + if self._socket is not None: + self._socket.close() + self._socket = None + self._socket_open_event.clear() + self._connected = False + self._authenticated = False + self._message_handlers = [] + def stop(self, force=False): if self.stopped: raise ValueError @@ -146,29 +163,19 @@ class APIClient(threading.Thread): self.disconnect() except APIConnectionError: pass - if self._socket is not None: - self._socket.close() - self._socket = None + self._close_socket() self._stop_event.set() - if self._ping_timer is not None: - self._ping_timer.cancel() - self._ping_timer = None if not force: self.join() def connect(self): - if not self._running: + if not self._running_event.wait(0.1): raise APIConnectionError("You need to call start() first!") if self._connected: raise APIConnectionError("Already connected!") - self._message_handlers = [] - self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._socket.settimeout(10.0) - self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - try: ip = resolve_ip_address(self._address) except EsphomeyamlError as err: @@ -179,21 +186,24 @@ class APIClient(threading.Thread): raise APIConnectionError(err) _LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip) + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.settimeout(10.0) + self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) try: self._socket.connect((ip, self._port)) except socket.error as err: - self._on_error() + self._fatal_error() raise APIConnectionError("Error connecting to {}: {}".format(ip, err)) - self._socket_open = True - self._socket.settimeout(0.1) + self._socket_open_event.set() + hello = pb.HelloRequest() hello.client_info = 'esphomeyaml v{}'.format(const.__version__) try: resp = self._send_message_await_response(hello, pb.HelloResponse) except APIConnectionError as err: - self._on_error() + self._fatal_error() raise err _LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address, resp.server_info, resp.api_version_major, resp.api_version_minor) @@ -203,7 +213,7 @@ class APIClient(threading.Thread): def _check_connected(self): if not self._connected: - self._on_error() + self._fatal_error() raise APIConnectionError("Must be connected!") def login(self): @@ -222,25 +232,25 @@ class APIClient(threading.Thread): if self.on_login is not None: self.on_login() - def _on_error(self): - if self._connected and self.on_disconnect is not None: + def _fatal_error(self): + was_connected = self._connected + + self._close_socket() + + if was_connected and self.on_disconnect is not None: self.on_disconnect() - if self._socket is not None: - self._socket.close() - self._socket = None - self._socket_open = False - - self._connected = False - self._authenticated = False - def _write(self, data): # type: (bytes) -> None + if self._socket is None: + raise APIConnectionError("Socket closed") + _LOGGER.debug("Write: %s", ' '.join('{:02X}'.format(ord(x)) for x in data)) - try: - self._socket.sendall(data) - except socket.error as err: - self._on_error() - raise APIConnectionError("Error while writing data: {}".format(err)) + with self._socket_write_lock: + try: + self._socket.sendall(data) + except socket.error as err: + self._fatal_error() + raise APIConnectionError("Error while writing data: {}".format(err)) def _send_message(self, msg): # type: (message.Message) -> None @@ -251,7 +261,7 @@ class APIClient(threading.Thread): raise ValueError encoded = msg.SerializeToString() - _LOGGER.debug("Sending %s: %s", type(message), unicode(message)) + _LOGGER.debug("Sending %s:\n%s", type(msg), indent(unicode(msg))) req = chr(0x00) req += _varuint_to_bytes(len(encoded)) req += _varuint_to_bytes(message_type) @@ -302,11 +312,8 @@ class APIClient(threading.Thread): self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse) except APIConnectionError: pass - if self._socket is not None: - self._socket.close() - self._socket = None - self._socket_open = False - self._connected = False + self._close_socket() + if self.on_disconnect is not None: self.on_disconnect() @@ -335,7 +342,7 @@ class APIClient(threading.Thread): while len(ret) < amount: if self.stopped: raise APIConnectionError("Stopped!") - if self._socket is None or not self._socket_open: + if not self._socket_open_event.is_set(): raise APIConnectionError("No socket!") try: val = self._socket.recv(amount - len(ret)) @@ -353,8 +360,7 @@ class APIClient(threading.Thread): return _bytes_to_varuint(raw) def _run_once(self): - if self._socket is None or not self._socket_open: - time.sleep(0.1) + if not self._socket_open_event.wait(0.1): return # Preamble @@ -371,14 +377,14 @@ class APIClient(threading.Thread): msg = MESSAGE_TYPE_TO_PROTO[msg_type]() msg.ParseFromString(raw_msg) - _LOGGER.debug("Got message of type %s: %s", type(msg), msg) + _LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg))) for msg_handler in self._message_handlers[:]: msg_handler(msg) self._handle_internal_messages(msg) self._refresh_ping() def run(self): - self._running = True + self._running_event.set() while not self.stopped: try: self._run_once() @@ -387,8 +393,8 @@ class APIClient(threading.Thread): break if self._connected: _LOGGER.error("Error while reading incoming messages: %s", err) - self._on_error() - self._running = False + self._fatal_error() + self._running_event.clear() def _handle_internal_messages(self, msg): if isinstance(msg, pb.DisconnectRequest): @@ -397,7 +403,6 @@ class APIClient(threading.Thread): self._socket.close() self._socket = None self._connected = False - self._socket_open = False if self.on_disconnect is not None: self.on_disconnect() elif isinstance(msg, pb.PingRequest): @@ -428,26 +433,29 @@ def run_logs(config, address): while retry_timer: retry_timer.pop(0).cancel() - error = None try: cli.connect() cli.login() except APIConnectionError as error: pass - - if error is None: + else: _LOGGER.info("Successfully connected to %s", address) return wait_time = min(2**tries, 300) - _LOGGER.warning(u"Couldn't connect to API. Trying to reconnect in %s seconds", wait_time) + _LOGGER.warning(u"Couldn't connect to API (%s). Trying to reconnect in %s seconds", + error, wait_time) timer = threading.Timer(wait_time, functools.partial(try_connect, tries + 1, is_disconnect)) timer.start() retry_timer.append(timer) def on_log(msg): time_ = datetime.now().time().strftime(u'[%H:%M:%S]') - safe_print(time_ + msg.message) + text = msg.message + if msg.send_failed: + text = color('white', '(Message not received because it was too big to fit in ' + 'TCP buffer)') + safe_print(time_ + text) has_connects = [] diff --git a/esphomeyaml/components/logger.py b/esphomeyaml/components/logger.py index 33fc6e270a..d86d3db64f 100644 --- a/esphomeyaml/components/logger.py +++ b/esphomeyaml/components/logger.py @@ -6,7 +6,7 @@ from esphomeyaml.automation import ACTION_REGISTRY, LambdaAction import esphomeyaml.config_validation as cv from esphomeyaml.const import CONF_ARGS, CONF_BAUD_RATE, CONF_FORMAT, CONF_ID, CONF_LEVEL, \ CONF_LOGS, CONF_TAG, CONF_TX_BUFFER_SIZE -from esphomeyaml.core import EsphomeyamlError, Lambda +from esphomeyaml.core import EsphomeyamlError, Lambda, CORE from esphomeyaml.cpp_generator import Pvariable, RawExpression, add, process_lambda, statement from esphomeyaml.cpp_types import App, Component, esphomelib_ns, global_ns @@ -69,9 +69,15 @@ def to_code(config): def required_build_flags(config): + flags = [] if CONF_LEVEL in config: - return u'-DESPHOMELIB_LOG_LEVEL={}'.format(str(LOG_LEVELS[config[CONF_LEVEL]])) - return None + flags.append(u'-DESPHOMELIB_LOG_LEVEL={}'.format(str(LOG_LEVELS[config[CONF_LEVEL]]))) + this_severity = LOG_LEVEL_SEVERITY.index(config[CONF_LEVEL]) + verbose_severity = LOG_LEVEL_SEVERITY.index('VERBOSE') + if CORE.is_esp8266 and config.get(CONF_BAUD_RATE) != 0 and \ + this_severity >= verbose_severity: + flags.append(u"-DDEBUG_ESP_PORT=Serial") + return flags def maybe_simple_message(schema): diff --git a/esphomeyaml/components/sensor/__init__.py b/esphomeyaml/components/sensor/__init__.py index 10d1fa4ba1..4e72835c96 100644 --- a/esphomeyaml/components/sensor/__init__.py +++ b/esphomeyaml/components/sensor/__init__.py @@ -46,13 +46,13 @@ FILTERS_SCHEMA = cv.ensure_list({ vol.Optional(CONF_FILTER_OUT): cv.float_, vol.Optional(CONF_FILTER_NAN): None, vol.Optional(CONF_SLIDING_WINDOW_MOVING_AVERAGE): vol.All(vol.Schema({ - vol.Required(CONF_WINDOW_SIZE): cv.positive_not_null_int, - vol.Required(CONF_SEND_EVERY): cv.positive_not_null_int, + vol.Optional(CONF_WINDOW_SIZE, default=15): cv.positive_not_null_int, + vol.Optional(CONF_SEND_EVERY, default=15): cv.positive_not_null_int, vol.Optional(CONF_SEND_FIRST_AT): cv.positive_not_null_int, }), validate_send_first_at), vol.Optional(CONF_EXPONENTIAL_MOVING_AVERAGE): vol.Schema({ - vol.Required(CONF_ALPHA): cv.positive_float, - vol.Required(CONF_SEND_EVERY): cv.positive_not_null_int, + vol.Optional(CONF_ALPHA, default=0.1): cv.positive_float, + vol.Optional(CONF_SEND_EVERY, default=15): cv.positive_not_null_int, }), vol.Optional(CONF_LAMBDA): cv.lambda_, vol.Optional(CONF_THROTTLE): cv.positive_time_period_milliseconds, diff --git a/esphomeyaml/components/wifi.py b/esphomeyaml/components/wifi.py index 80e592ddcf..f67565059f 100644 --- a/esphomeyaml/components/wifi.py +++ b/esphomeyaml/components/wifi.py @@ -62,11 +62,10 @@ WIFI_NETWORK_BASE = vol.Schema({ }) WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend({ - vol.Optional(CONF_MANUAL_IP): AP_MANUAL_IP_SCHEMA, + }) WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend({ - vol.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA, vol.Optional(CONF_BSSID): cv.mac_address, }) @@ -79,8 +78,6 @@ def validate(config): network = {CONF_SSID: config.pop(CONF_SSID)} if CONF_PASSWORD in config: network[CONF_PASSWORD] = config.pop(CONF_PASSWORD) - if CONF_MANUAL_IP in config: - network[CONF_MANUAL_IP] = config.pop(CONF_MANUAL_IP) if CONF_NETWORKS in config: raise vol.Invalid("You cannot use the 'ssid:' option together with 'networks:'. Please " "copy your network into the 'networks:' key") @@ -127,7 +124,7 @@ def manual_ip(config): ) -def wifi_network(config): +def wifi_network(config, static_ip): ap = variable(config[CONF_ID], WiFiAP()) if CONF_SSID in config: add(ap.set_ssid(config[CONF_SSID])) @@ -138,21 +135,28 @@ def wifi_network(config): add(ap.set_bssid(ArrayInitializer(*bssid, multiline=False))) if CONF_CHANNEL in config: add(ap.set_channel(config[CONF_CHANNEL])) - if CONF_MANUAL_IP in config: - add(ap.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) + if static_ip is not None: + add(ap.set_manual_ip(manual_ip(static_ip))) return ap +def get_upload_host(config): + if CONF_MANUAL_IP in config: + return str(config[CONF_MANUAL_IP][CONF_STATIC_IP]) + hostname = config.get(CONF_HOSTNAME) or CORE.name + return hostname + config[CONF_DOMAIN] + + def to_code(config): rhs = App.init_wifi() wifi = Pvariable(config[CONF_ID], rhs) for network in config.get(CONF_NETWORKS, []): - add(wifi.add_sta(wifi_network(network))) + add(wifi.add_sta(wifi_network(network, config.get(CONF_MANUAL_IP)))) if CONF_AP in config: - add(wifi.set_ap(wifi_network(config[CONF_AP]))) + add(wifi.set_ap(wifi_network(config[CONF_AP], config.get(CONF_MANUAL_IP)))) if CONF_HOSTNAME in config: add(wifi.set_hostname(config[CONF_HOSTNAME])) diff --git a/esphomeyaml/config.py b/esphomeyaml/config.py index 7eb5740208..29653efa96 100644 --- a/esphomeyaml/config.py +++ b/esphomeyaml/config.py @@ -378,7 +378,10 @@ def validate_config(config): continue result[domain][i] = p_validated - do_id_pass(result) + if not result.errors: + # Only parse IDs if no validation error. Otherwise + # user gets confusing messages + do_id_pass(result) return result diff --git a/esphomeyaml/core.py b/esphomeyaml/core.py index eed2d3c188..1ab3d6fa02 100644 --- a/esphomeyaml/core.py +++ b/esphomeyaml/core.py @@ -307,13 +307,9 @@ class EsphomeyamlCore(object): @property def address(self): # type: () -> str - if CONF_MANUAL_IP in self.config[CONF_WIFI]: - return str(self.config[CONF_WIFI][CONF_MANUAL_IP][CONF_STATIC_IP]) - elif CONF_HOSTNAME in self.config[CONF_WIFI]: - hostname = self.config[CONF_WIFI][CONF_HOSTNAME] - else: - hostname = self.name - return hostname + self.config[CONF_WIFI][CONF_DOMAIN] + from esphomeyaml.components import wifi + + return wifi.get_upload_host(self.config[CONF_WIFI]) @property def esphomelib_version(self): # type: () -> Dict[str, str] diff --git a/esphomeyaml/espota2.py b/esphomeyaml/espota2.py index 6f1ba5240e..cda59d5b63 100755 --- a/esphomeyaml/espota2.py +++ b/esphomeyaml/espota2.py @@ -224,7 +224,7 @@ def perform_ota(sock, password, file_handle, filename): _LOGGER.info("OTA successful") # Do not connect logs until it is fully on - time.sleep(2) + time.sleep(1) def run_ota_impl_(remote_host, remote_port, password, filename): diff --git a/esphomeyaml/writer.py b/esphomeyaml/writer.py index 6314bd4bd9..e7b101b33a 100644 --- a/esphomeyaml/writer.py +++ b/esphomeyaml/writer.py @@ -279,7 +279,7 @@ def gather_lib_deps(): # Manual fix for AsyncTCP if CORE.config[CONF_ESPHOMEYAML].get(CONF_ARDUINO_VERSION) == ARDUINO_VERSION_ESP32_DEV: lib_deps.add('https://github.com/me-no-dev/AsyncTCP.git#idf-update') - lib_deps.remove('AsyncTCP@1.0.1') + lib_deps.discard('AsyncTCP@1.0.1') # avoid changing build flags order return sorted(x for x in lib_deps if x)