diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index e0b008cab5e..10e18216ea0 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -87,13 +87,21 @@ def setup(hass, config): lambda item: util.split_entity_id(item)[0]) for domain, ent_ids in by_domain: + # We want to block for all calls and only return when all calls + # have been processed. If a service does not exist it causes a 10 + # second delay while we're blocking waiting for a response. + # But services can be registered on other HA instances that are + # listening to the bus too. So as a in between solution, we'll + # block only if the service is defined in the current HA instance. + blocking = hass.services.has_service(domain, service.service) + # Create a new dict for this call data = dict(service.data) # ent_ids is a generator, convert it to a list. data[ATTR_ENTITY_ID] = list(ent_ids) - hass.services.call(domain, service.service, data, True) + hass.services.call(domain, service.service, data, blocking) hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service) hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service) diff --git a/homeassistant/components/alarm_control_panel/verisure.py b/homeassistant/components/alarm_control_panel/verisure.py index cc9f8dde69d..ea48209a0fd 100644 --- a/homeassistant/components/alarm_control_panel/verisure.py +++ b/homeassistant/components/alarm_control_panel/verisure.py @@ -67,7 +67,7 @@ class VerisureAlarm(alarm.AlarmControlPanel): self._state = STATE_ALARM_DISARMED elif verisure.ALARM_STATUS[self._id].status == 'armedhome': self._state = STATE_ALARM_ARMED_HOME - elif verisure.ALARM_STATUS[self._id].status == 'armedaway': + elif verisure.ALARM_STATUS[self._id].status == 'armed': self._state = STATE_ALARM_ARMED_AWAY elif verisure.ALARM_STATUS[self._id].status != 'pending': _LOGGER.error( diff --git a/homeassistant/components/alexa.py b/homeassistant/components/alexa.py index 0b06f3c9a79..66ac9de0b43 100644 --- a/homeassistant/components/alexa.py +++ b/homeassistant/components/alexa.py @@ -11,6 +11,7 @@ import logging from homeassistant.const import HTTP_OK, HTTP_UNPROCESSABLE_ENTITY from homeassistant.util import template +from homeassistant.helpers.service import call_from_config DOMAIN = 'alexa' DEPENDENCIES = ['http'] @@ -23,6 +24,7 @@ API_ENDPOINT = '/api/alexa' CONF_INTENTS = 'intents' CONF_CARD = 'card' CONF_SPEECH = 'speech' +CONF_ACTION = 'action' def setup(hass, config): @@ -80,6 +82,7 @@ def _handle_alexa(handler, path_match, data): speech = config.get(CONF_SPEECH) card = config.get(CONF_CARD) + action = config.get(CONF_ACTION) # pylint: disable=unsubscriptable-object if speech is not None: @@ -89,6 +92,9 @@ def _handle_alexa(handler, path_match, data): response.add_card(CardType[card['type']], card['title'], card['content']) + if action is not None: + call_from_config(handler.server.hass, action, True) + handler.write_json(response.as_dict()) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 23d83f554ca..9c464f6954e 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -9,9 +9,9 @@ https://home-assistant.io/components/automation/ import logging from homeassistant.bootstrap import prepare_setup_platform -from homeassistant.util import split_entity_id -from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM +from homeassistant.const import CONF_PLATFORM from homeassistant.components import logbook +from homeassistant.helpers.service import call_from_config DOMAIN = 'automation' @@ -19,8 +19,6 @@ DEPENDENCIES = ['group'] CONF_ALIAS = 'alias' CONF_SERVICE = 'service' -CONF_SERVICE_ENTITY_ID = 'entity_id' -CONF_SERVICE_DATA = 'data' CONF_CONDITION = 'condition' CONF_ACTION = 'action' @@ -96,22 +94,7 @@ def _get_action(hass, config, name): _LOGGER.info('Executing %s', name) logbook.log_entry(hass, name, 'has been triggered', DOMAIN) - domain, service = split_entity_id(config[CONF_SERVICE]) - service_data = config.get(CONF_SERVICE_DATA, {}) - - if not isinstance(service_data, dict): - _LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA) - service_data = {} - - if CONF_SERVICE_ENTITY_ID in config: - try: - service_data[ATTR_ENTITY_ID] = \ - config[CONF_SERVICE_ENTITY_ID].split(",") - except AttributeError: - service_data[ATTR_ENTITY_ID] = \ - config[CONF_SERVICE_ENTITY_ID] - - hass.services.call(domain, service, service_data) + call_from_config(hass, config) return action diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index e81952eb770..e1b0e1de306 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -71,7 +71,7 @@ def setup_scanner(hass, config, see): location = '' if data['event'] == 'enter': - if data['desc'] == 'home': + if data['desc'].lower() == 'home': location = STATE_HOME else: location = data['desc'] diff --git a/homeassistant/components/light/vera.py b/homeassistant/components/light/vera.py index bcf27eab5f7..42a5e7b7899 100644 --- a/homeassistant/components/light/vera.py +++ b/homeassistant/components/light/vera.py @@ -7,16 +7,15 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.vera/ """ import logging -import time from requests.exceptions import RequestException from homeassistant.components.switch.vera import VeraSwitch from homeassistant.components.light import ATTR_BRIGHTNESS -from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, STATE_ON -REQUIREMENTS = ['pyvera==0.2.2'] +REQUIREMENTS = ['pyvera==0.2.3'] _LOGGER = logging.getLogger(__name__) @@ -59,7 +58,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): lights = [] for device in devices: - extra_data = device_data.get(device.deviceId, {}) + extra_data = device_data.get(device.device_id, {}) exclude = extra_data.get('exclude', False) if exclude is not True: @@ -86,5 +85,5 @@ class VeraLight(VeraSwitch): else: self.vera_device.switch_on() - self.last_command_send = time.time() - self.is_on_status = True + self._state = STATE_ON + self.update_ha_state() diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 87117cfd367..c0717edc860 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -20,7 +20,7 @@ from homeassistant.components.media_player import ( SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO) -REQUIREMENTS = ['pychromecast==0.6.13'] +REQUIREMENTS = ['pychromecast==0.6.14'] CONF_IGNORE_CEC = 'ignore_cec' CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png' SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 6925f942be4..52dd399cedf 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -35,7 +35,7 @@ SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK def config_from_file(filename, config=None): - ''' Small configuration file management function''' + """ Small configuration file management function. """ if config: # We're writing configuration try: @@ -85,7 +85,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-branches def setup_plexserver(host, token, hass, add_devices_callback): - ''' Setup a plexserver based on host parameter''' + """ Setup a plexserver based on host parameter. """ import plexapi.server import plexapi.exceptions diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index b5ea258c5cc..86dce3d511b 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -149,9 +149,9 @@ class MQTT(object): } if client_id is None: - self._mqttc = mqtt.Client() + self._mqttc = mqtt.Client(protocol=mqtt.MQTTv311) else: - self._mqttc = mqtt.Client(client_id) + self._mqttc = mqtt.Client(client_id, protocol=mqtt.MQTTv311) self._mqttc.user_data_set(self.userdata) diff --git a/homeassistant/components/sensor/eliqonline.py b/homeassistant/components/sensor/eliqonline.py index 151b679b10e..4e6b8a5c342 100644 --- a/homeassistant/components/sensor/eliqonline.py +++ b/homeassistant/components/sensor/eliqonline.py @@ -70,5 +70,8 @@ class EliqSensor(Entity): def update(self): """ Gets the latest data. """ - response = self.api.get_data_now(channelid=self.channel_id) - self._state = int(response.power) + try: + response = self.api.get_data_now(channelid=self.channel_id) + self._state = int(response.power) + except TypeError: # raised by eliqonline library on any HTTP error + pass diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index 14e8d60c7c6..84784a19546 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -13,14 +13,14 @@ from homeassistant.util import Throttle from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['pyowm==2.2.1'] +REQUIREMENTS = ['pyowm==2.3.0'] _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { 'weather': ['Condition', ''], 'temperature': ['Temperature', ''], 'wind_speed': ['Wind speed', 'm/s'], 'humidity': ['Humidity', '%'], - 'pressure': ['Pressure', 'hPa'], + 'pressure': ['Pressure', 'mbar'], 'clouds': ['Cloud coverage', '%'], 'rain': ['Rain', 'mm'], 'snow': ['Snow', 'mm'] diff --git a/homeassistant/components/sensor/vera.py b/homeassistant/components/sensor/vera.py index c7b95ce5d53..b381974ab31 100644 --- a/homeassistant/components/sensor/vera.py +++ b/homeassistant/components/sensor/vera.py @@ -15,7 +15,7 @@ from homeassistant.const import ( ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME, TEMP_CELCIUS, TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_STOP) -REQUIREMENTS = ['pyvera==0.2.2'] +REQUIREMENTS = ['pyvera==0.2.3'] _LOGGER = logging.getLogger(__name__) @@ -56,7 +56,7 @@ def get_devices(hass, config): vera_sensors = [] for device in devices: - extra_data = device_data.get(device.deviceId, {}) + extra_data = device_data.get(device.device_id, {}) exclude = extra_data.get('exclude', False) if exclude is not True: @@ -85,18 +85,14 @@ class VeraSensor(Entity): self.current_value = '' self._temperature_units = None - self.controller.register(vera_device) - self.controller.on( - vera_device, self._update_callback) + self.controller.register(vera_device, self._update_callback) def _update_callback(self, _device): """ Called by the vera device callback to update state. """ - _LOGGER.info( - 'Subscription update for %s', self.name) self.update_ha_state(True) def __str__(self): - return "%s %s %s" % (self.name, self.vera_device.deviceId, self.state) + return "%s %s %s" % (self.name, self.vera_device.device_id, self.state) @property def state(self): @@ -119,18 +115,18 @@ class VeraSensor(Entity): attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%' if self.vera_device.is_armable: - armed = self.vera_device.refresh_value('Armed') + armed = self.vera_device.get_value('Armed') attr[ATTR_ARMED] = 'True' if armed == '1' else 'False' if self.vera_device.is_trippable: - last_tripped = self.vera_device.refresh_value('LastTrip') + last_tripped = self.vera_device.get_value('LastTrip') if last_tripped is not None: utc_time = dt_util.utc_from_timestamp(int(last_tripped)) attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str( utc_time) else: attr[ATTR_LAST_TRIP_TIME] = None - tripped = self.vera_device.refresh_value('Tripped') + tripped = self.vera_device.get_value('Tripped') attr[ATTR_TRIPPED] = 'True' if tripped == '1' else 'False' attr['Vera Device Id'] = self.vera_device.vera_device_id @@ -143,7 +139,6 @@ class VeraSensor(Entity): def update(self): if self.vera_device.category == "Temperature Sensor": - self.vera_device.refresh_value('CurrentTemperature') current_temp = self.vera_device.get_value('CurrentTemperature') vera_temp_units = self.vera_device.veraController.temperature_units @@ -161,10 +156,9 @@ class VeraSensor(Entity): self.current_value = current_temp elif self.vera_device.category == "Light Sensor": - self.vera_device.refresh_value('CurrentLevel') self.current_value = self.vera_device.get_value('CurrentLevel') elif self.vera_device.category == "Sensor": - tripped = self.vera_device.refresh_value('Tripped') + tripped = self.vera_device.get_value('Tripped') self.current_value = 'Tripped' if tripped == '1' else 'Not Tripped' else: self.current_value = 'Unknown' diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index e4003cf7e9c..24f565feb48 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -25,7 +25,7 @@ SENSOR_TYPES = { 'precipitation': ['Condition', 'mm'], 'temperature': ['Temperature', '°C'], 'windSpeed': ['Wind speed', 'm/s'], - 'pressure': ['Pressure', 'hPa'], + 'pressure': ['Pressure', 'mbar'], 'windDirection': ['Wind direction', '°'], 'humidity': ['Humidity', '%'], 'fog': ['Fog', '%'], diff --git a/homeassistant/components/switch/vera.py b/homeassistant/components/switch/vera.py index 91a83086570..4094fe61f4f 100644 --- a/homeassistant/components/switch/vera.py +++ b/homeassistant/components/switch/vera.py @@ -7,19 +7,21 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.vera/ """ import logging -import time from requests.exceptions import RequestException import homeassistant.util.dt as dt_util -from homeassistant.helpers.entity import ToggleEntity +from homeassistant.components.switch import SwitchDevice + from homeassistant.const import ( ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME, - EVENT_HOMEASSISTANT_STOP) + EVENT_HOMEASSISTANT_STOP, + STATE_ON, + STATE_OFF) -REQUIREMENTS = ['pyvera==0.2.2'] +REQUIREMENTS = ['pyvera==0.2.3'] _LOGGER = logging.getLogger(__name__) @@ -60,7 +62,7 @@ def get_devices(hass, config): vera_switches = [] for device in devices: - extra_data = device_data.get(device.deviceId, {}) + extra_data = device_data.get(device.device_id, {}) exclude = extra_data.get('exclude', False) if exclude is not True: @@ -75,7 +77,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(get_devices(hass, config)) -class VeraSwitch(ToggleEntity): +class VeraSwitch(SwitchDevice): """ Represents a Vera Switch. """ def __init__(self, vera_device, controller, extra_data=None): @@ -86,19 +88,17 @@ class VeraSwitch(ToggleEntity): self._name = self.extra_data.get('name') else: self._name = self.vera_device.name - self.is_on_status = False - # for debouncing status check after command is sent - self.last_command_send = 0 + self._state = STATE_OFF - self.controller.register(vera_device) - self.controller.on( - vera_device, self._update_callback) + self.controller.register(vera_device, self._update_callback) def _update_callback(self, _device): """ Called by the vera device callback to update state. """ - _LOGGER.info( - 'Subscription update for %s', self.name) - self.update_ha_state(True) + if self.vera_device.is_switched_on(): + self._state = STATE_ON + else: + self._state = STATE_OFF + self.update_ha_state() @property def name(self): @@ -113,18 +113,18 @@ class VeraSwitch(ToggleEntity): attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%' if self.vera_device.is_armable: - armed = self.vera_device.refresh_value('Armed') + armed = self.vera_device.get_value('Armed') attr[ATTR_ARMED] = 'True' if armed == '1' else 'False' if self.vera_device.is_trippable: - last_tripped = self.vera_device.refresh_value('LastTrip') + last_tripped = self.vera_device.get_value('LastTrip') if last_tripped is not None: utc_time = dt_util.utc_from_timestamp(int(last_tripped)) attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str( utc_time) else: attr[ATTR_LAST_TRIP_TIME] = None - tripped = self.vera_device.refresh_value('Tripped') + tripped = self.vera_device.get_value('Tripped') attr[ATTR_TRIPPED] = 'True' if tripped == '1' else 'False' attr['Vera Device Id'] = self.vera_device.vera_device_id @@ -132,14 +132,14 @@ class VeraSwitch(ToggleEntity): return attr def turn_on(self, **kwargs): - self.last_command_send = time.time() self.vera_device.switch_on() - self.is_on_status = True + self._state = STATE_ON + self.update_ha_state() def turn_off(self, **kwargs): - self.last_command_send = time.time() self.vera_device.switch_off() - self.is_on_status = False + self._state = STATE_OFF + self.update_ha_state() @property def should_poll(self): @@ -149,13 +149,4 @@ class VeraSwitch(ToggleEntity): @property def is_on(self): """ True if device is on. """ - return self.is_on_status - - def update(self): - # We need to debounce the status call after turning switch on or off - # because the vera has some lag in updating the device status - try: - if (self.last_command_send + 5) < time.time(): - self.is_on_status = self.vera_device.is_switched_on() - except RequestException: - _LOGGER.warning('Could not update status for %s', self.name) + return self._state == STATE_ON diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index ad21463ea17..ed56305542d 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -12,7 +12,7 @@ from homeassistant.components.switch import SwitchDevice from homeassistant.const import ( STATE_ON, STATE_OFF, STATE_STANDBY, EVENT_HOMEASSISTANT_STOP) -REQUIREMENTS = ['pywemo==0.3.7'] +REQUIREMENTS = ['pywemo==0.3.8'] _LOGGER = logging.getLogger(__name__) _WEMO_SUBSCRIPTION_REGISTRY = None @@ -69,15 +69,14 @@ class WemoSwitch(SwitchDevice): def _update_callback(self, _device, _params): """ Called by the wemo device callback to update state. """ _LOGGER.info( - 'Subscription update for %s, sevice=%s', - self.name, _device) + 'Subscription update for %s', + _device) self.update_ha_state(True) @property def should_poll(self): - """ No polling should be needed with subscriptions """ - # but leave in for initial version in case of issues. - return True + """ No polling needed with subscriptions """ + return False @property def unique_id(self): diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index 5a4d7c7ea99..164835f07c7 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -28,7 +28,7 @@ DISCOVER_SWITCHES = 'verisure.switches' DISCOVER_ALARMS = 'verisure.alarm_control_panel' DEPENDENCIES = ['alarm_control_panel'] -REQUIREMENTS = ['vsure==0.4.3'] +REQUIREMENTS = ['vsure==0.4.5'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index 021146d1c32..95dfe7dd65e 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -36,7 +36,7 @@ def extract_entity_ids(hass, service): service_ent_id = service.data[ATTR_ENTITY_ID] if isinstance(service_ent_id, str): - return group.expand_entity_ids(hass, [service_ent_id.lower()]) + return group.expand_entity_ids(hass, [service_ent_id]) return [ent_id for ent_id in group.expand_entity_ids(hass, service_ent_id)] diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py new file mode 100644 index 00000000000..15cfe9b97c6 --- /dev/null +++ b/homeassistant/helpers/service.py @@ -0,0 +1,43 @@ +"""Service calling related helpers.""" +import logging + +from homeassistant.util import split_entity_id +from homeassistant.const import ATTR_ENTITY_ID + +CONF_SERVICE = 'service' +CONF_SERVICE_ENTITY_ID = 'entity_id' +CONF_SERVICE_DATA = 'data' + +_LOGGER = logging.getLogger(__name__) + + +def call_from_config(hass, config, blocking=False): + """Call a service based on a config hash.""" + if not isinstance(config, dict) or CONF_SERVICE not in config: + _LOGGER.error('Missing key %s: %s', CONF_SERVICE, config) + return + + try: + domain, service = split_entity_id(config[CONF_SERVICE]) + except ValueError: + _LOGGER.error('Invalid service specified: %s', config[CONF_SERVICE]) + return + + service_data = config.get(CONF_SERVICE_DATA) + + if service_data is None: + service_data = {} + elif isinstance(service_data, dict): + service_data = dict(service_data) + else: + _LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA) + service_data = {} + + entity_id = config.get(CONF_SERVICE_ENTITY_ID) + if isinstance(entity_id, str): + service_data[ATTR_ENTITY_ID] = [ent.strip() for ent in + entity_id.split(",")] + elif entity_id is not None: + service_data[ATTR_ENTITY_ID] = entity_id + + hass.services.call(domain, service, service_data, blocking) diff --git a/requirements_all.txt b/requirements_all.txt index 5566e67fb9f..f320408f138 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -59,7 +59,7 @@ tellcore-py==1.1.2 # homeassistant.components.light.vera # homeassistant.components.sensor.vera # homeassistant.components.switch.vera -pyvera==0.2.2 +pyvera==0.2.3 # homeassistant.components.wink # homeassistant.components.light.wink @@ -69,7 +69,7 @@ pyvera==0.2.2 python-wink==0.3.1 # homeassistant.components.media_player.cast -pychromecast==0.6.13 +pychromecast==0.6.14 # homeassistant.components.media_player.kodi jsonrpc-requests==0.1 @@ -135,7 +135,7 @@ python-forecastio==1.3.3 https://github.com/theolind/pymysensors/archive/d4b809c2167650691058d1e29bfd2c4b1792b4b0.zip#pymysensors==0.3 # homeassistant.components.sensor.openweathermap -pyowm==2.2.1 +pyowm==2.3.0 # homeassistant.components.sensor.rpi_gpio # homeassistant.components.switch.rpi_gpio @@ -173,7 +173,7 @@ hikvision==0.4 orvibo==1.1.0 # homeassistant.components.switch.wemo -pywemo==0.3.7 +pywemo==0.3.8 # homeassistant.components.tellduslive tellive-py==0.5.2 @@ -191,7 +191,7 @@ python-nest==2.6.0 radiotherm==1.2 # homeassistant.components.verisure -vsure==0.4.3 +vsure==0.4.5 # homeassistant.components.zwave pydispatcher==2.0.5 diff --git a/tests/components/sensor/test_yr.py b/tests/components/sensor/test_yr.py index f58aefbce43..780176dd1b8 100644 --- a/tests/components/sensor/test_yr.py +++ b/tests/components/sensor/test_yr.py @@ -4,12 +4,14 @@ tests.components.sensor.test_yr Tests Yr sensor. """ +from datetime import datetime from unittest.mock import patch import pytest import homeassistant.core as ha import homeassistant.components.sensor as sensor +import homeassistant.util.dt as dt_util @pytest.mark.usefixtures('betamax_session') @@ -26,14 +28,18 @@ class TestSensorYr: self.hass.stop() def test_default_setup(self, betamax_session): + now = datetime(2016, 1, 5, 1, tzinfo=dt_util.UTC) + with patch('homeassistant.components.sensor.yr.requests.Session', return_value=betamax_session): - assert sensor.setup(self.hass, { - 'sensor': { - 'platform': 'yr', - 'elevation': 0, - } - }) + with patch('homeassistant.components.sensor.yr.dt_util.utcnow', + return_value=now): + assert sensor.setup(self.hass, { + 'sensor': { + 'platform': 'yr', + 'elevation': 0, + } + }) state = self.hass.states.get('sensor.yr_symbol') diff --git a/tests/components/test_alexa.py b/tests/components/test_alexa.py index 741cfff4bb8..42acf5b3f62 100644 --- a/tests/components/test_alexa.py +++ b/tests/components/test_alexa.py @@ -27,12 +27,13 @@ API_URL = "http://127.0.0.1:{}{}".format(SERVER_PORT, alexa.API_ENDPOINT) HA_HEADERS = {const.HTTP_HEADER_HA_AUTH: API_PASSWORD} hass = None +calls = [] @patch('homeassistant.components.http.util.get_local_ip', return_value='127.0.0.1') def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name - """ Initalizes a Home Assistant server. """ + """Initalize a Home Assistant server for testing this module.""" global hass hass = ha.HomeAssistant() @@ -42,6 +43,8 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name {http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD, http.CONF_SERVER_PORT: SERVER_PORT}}) + hass.services.register('test', 'alexa', lambda call: calls.append(call)) + bootstrap.setup_component(hass, alexa.DOMAIN, { 'alexa': { 'intents': { @@ -61,7 +64,20 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name 'GetZodiacHoroscopeIntent': { 'speech': { 'type': 'plaintext', - 'text': 'You told us your sign is {{ ZodiacSign }}.' + 'text': 'You told us your sign is {{ ZodiacSign }}.', + } + }, + 'CallServiceIntent': { + 'speech': { + 'type': 'plaintext', + 'text': 'Service called', + }, + 'action': { + 'service': 'test.alexa', + 'data': { + 'hello': 1 + }, + 'entity_id': 'switch.test', } } } @@ -231,6 +247,39 @@ class TestAlexa(unittest.TestCase): text = req.json().get('response', {}).get('outputSpeech', {}).get('text') self.assertEqual('You are both home, you silly', text) + def test_intent_request_calling_service(self): + data = { + 'version': '1.0', + 'session': { + 'new': False, + 'sessionId': 'amzn1.echo-api.session.0000000-0000-0000-0000-00000000000', + 'application': { + 'applicationId': 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe' + }, + 'attributes': {}, + 'user': { + 'userId': 'amzn1.account.AM3B00000000000000000000000' + } + }, + 'request': { + 'type': 'IntentRequest', + 'requestId': ' amzn1.echo-api.request.0000000-0000-0000-0000-00000000000', + 'timestamp': '2015-05-13T12:34:56Z', + 'intent': { + 'name': 'CallServiceIntent', + } + } + } + call_count = len(calls) + req = _req(data) + self.assertEqual(200, req.status_code) + self.assertEqual(call_count + 1, len(calls)) + call = calls[-1] + self.assertEqual('test', call.domain) + self.assertEqual('alexa', call.service) + self.assertEqual(['switch.test'], call.data.get('entity_id')) + self.assertEqual(1, call.data.get('hello')) + def test_session_ended_request(self): data = { 'version': '1.0', diff --git a/tests/components/test_init.py b/tests/components/test_init.py index 4ff334c1b1e..cb170a5c24b 100644 --- a/tests/components/test_init.py +++ b/tests/components/test_init.py @@ -6,20 +6,22 @@ Tests core compoments. """ # pylint: disable=protected-access,too-many-public-methods import unittest +from unittest.mock import patch import homeassistant.core as ha -import homeassistant.loader as loader from homeassistant.const import ( STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF) import homeassistant.components as comps +from tests.common import get_test_home_assistant + class TestComponentsCore(unittest.TestCase): """ Tests homeassistant.components module. """ def setUp(self): # pylint: disable=invalid-name """ Init needed objects. """ - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.assertTrue(comps.setup(self.hass, {})) self.hass.states.set('light.Bowl', STATE_ON) @@ -58,3 +60,24 @@ class TestComponentsCore(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(runs)) + + @patch('homeassistant.core.ServiceRegistry.call') + def test_turn_on_to_not_block_for_domains_without_service(self, mock_call): + self.hass.services.register('light', SERVICE_TURN_ON, lambda x: x) + + # We can't test if our service call results in services being called + # because by mocking out the call service method, we mock out all + # So we mimick how the service registry calls services + service_call = ha.ServiceCall('homeassistant', 'turn_on', { + 'entity_id': ['light.test', 'sensor.bla', 'light.bla'] + }) + self.hass.services._services['homeassistant']['turn_on'](service_call) + + self.assertEqual(2, mock_call.call_count) + self.assertEqual( + ('light', 'turn_on', {'entity_id': ['light.bla', 'light.test']}, + True), + mock_call.call_args_list[0][0]) + self.assertEqual( + ('sensor', 'turn_on', {'entity_id': ['sensor.bla']}, False), + mock_call.call_args_list[1][0]) diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py new file mode 100644 index 00000000000..aa2cab07d0d --- /dev/null +++ b/tests/helpers/test_service.py @@ -0,0 +1,68 @@ +""" +tests.helpers.test_service +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Test service helpers. +""" +import unittest +from unittest.mock import patch + +from homeassistant.const import SERVICE_TURN_ON +from homeassistant.helpers import service + +from tests.common import get_test_home_assistant, mock_service + + +class TestServiceHelpers(unittest.TestCase): + """ + Tests the Home Assistant service helpers. + """ + + def setUp(self): # pylint: disable=invalid-name + """ things to be run when tests are started. """ + self.hass = get_test_home_assistant() + self.calls = mock_service(self.hass, 'test_domain', 'test_service') + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_split_entity_string(self): + service.call_from_config(self.hass, { + 'service': 'test_domain.test_service', + 'entity_id': 'hello.world, sensor.beer' + }) + self.hass.pool.block_till_done() + self.assertEqual(['hello.world', 'sensor.beer'], + self.calls[-1].data.get('entity_id')) + + def test_not_mutate_input(self): + orig = { + 'service': 'test_domain.test_service', + 'entity_id': 'hello.world, sensor.beer', + 'data': { + 'hello': 1, + }, + } + service.call_from_config(self.hass, orig) + self.hass.pool.block_till_done() + self.assertEqual({ + 'service': 'test_domain.test_service', + 'entity_id': 'hello.world, sensor.beer', + 'data': { + 'hello': 1, + }, + }, orig) + + @patch('homeassistant.helpers.service._LOGGER.error') + def test_fail_silently_if_no_service(self, mock_log): + service.call_from_config(self.hass, None) + self.assertEqual(1, mock_log.call_count) + + service.call_from_config(self.hass, {}) + self.assertEqual(2, mock_log.call_count) + + service.call_from_config(self.hass, { + 'service': 'invalid' + }) + self.assertEqual(3, mock_log.call_count)