From fbfa3d1a181648a70ec9677e183675e040846f29 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 18 Feb 2019 13:04:04 -0800 Subject: [PATCH 1/9] Make sure that device trackers is always a list during creation (#21193) --- homeassistant/components/person/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 6fb7d42e0ee..3e0b00f7442 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -176,7 +176,7 @@ class PersonManager: CONF_ID: uuid.uuid4().hex, CONF_NAME: name, CONF_USER_ID: user_id, - CONF_DEVICE_TRACKERS: device_trackers, + CONF_DEVICE_TRACKERS: device_trackers or [], } self.storage_data[person[CONF_ID]] = person self._async_schedule_save() From f6811a85b617a7ffd24f29efa20e2acf308e5c13 Mon Sep 17 00:00:00 2001 From: David Conley Date: Fri, 22 Feb 2019 18:13:40 -0500 Subject: [PATCH 2/9] Allow custom_effect to be absent from Flux configuration (#21317) * Allow custom_effect to be absent from Flux configuration * set custom effect to none during setup --- homeassistant/components/light/flux_led.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py index 5ecf3f55e10..bfbb98ad57e 100644 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -151,6 +151,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device['name'] = '{} {}'.format(device['id'], ipaddr) device[ATTR_MODE] = MODE_RGBW device[CONF_PROTOCOL] = None + device[CONF_CUSTOM_EFFECT] = None light = FluxLight(device) lights.append(light) From 0f4c2ccd1ee82c695626220517484a30f58d65e6 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 23 Feb 2019 22:38:21 +0100 Subject: [PATCH 3/9] Fix person update on create (#21355) * Update tests to set correct hass running state * Update person on adding person if hass is running * Test creating person when hass is running --- homeassistant/components/person/__init__.py | 14 ++++++++++---- tests/components/person/test_init.py | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 3e0b00f7442..f2bca91205c 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -337,12 +337,18 @@ class Person(RestoreEntity): if state: self._parse_source_state(state) - @callback - def person_start_hass(now): + if self.hass.is_running: + # Update person now if hass is already running. self.person_updated() + else: + # Wait for hass start to not have race between person + # and device trackers finishing setup. + @callback + def person_start_hass(now): + self.person_updated() - self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, person_start_hass) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, person_start_hass) @callback def person_updated(self): diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index f2d796fb204..6c8c6ebd0dd 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -101,6 +101,7 @@ async def test_valid_invalid_user_ids(hass, hass_admin_user): async def test_setup_tracker(hass, hass_admin_user): """Test set up person with one device tracker.""" + hass.state = CoreState.not_running user_id = hass_admin_user.id config = {DOMAIN: { 'id': '1234', 'name': 'tracked person', 'user_id': user_id, @@ -148,6 +149,7 @@ async def test_setup_tracker(hass, hass_admin_user): async def test_setup_two_trackers(hass, hass_admin_user): """Test set up person with two device trackers.""" + hass.state = CoreState.not_running user_id = hass_admin_user.id config = {DOMAIN: { 'id': '1234', 'name': 'tracked person', 'user_id': user_id, @@ -191,6 +193,7 @@ async def test_setup_two_trackers(hass, hass_admin_user): async def test_ignore_unavailable_states(hass, hass_admin_user): """Test set up person with two device trackers, one unavailable.""" + hass.state = CoreState.not_running user_id = hass_admin_user.id config = {DOMAIN: { 'id': '1234', 'name': 'tracked person', 'user_id': user_id, @@ -234,7 +237,7 @@ async def test_restore_home_state(hass, hass_admin_user): ATTR_SOURCE: DEVICE_TRACKER, ATTR_USER_ID: user_id} state = State('person.tracked_person', 'home', attrs) mock_restore_cache(hass, (state, )) - hass.state = CoreState.starting + hass.state = CoreState.not_running mock_component(hass, 'recorder') config = {DOMAIN: { 'id': '1234', 'name': 'tracked person', 'user_id': user_id, @@ -263,6 +266,21 @@ async def test_duplicate_ids(hass, hass_admin_user): assert hass.states.get('person.test_user_2') is None +async def test_create_person_during_run(hass): + """Test that person is updated if created while hass is running.""" + config = {DOMAIN: {}} + assert await async_setup_component(hass, DOMAIN, config) + hass.states.async_set(DEVICE_TRACKER, 'home') + await hass.async_block_till_done() + + await hass.components.person.async_create_person( + 'tracked person', device_trackers=[DEVICE_TRACKER]) + await hass.async_block_till_done() + + state = hass.states.get('person.tracked_person') + assert state.state == 'home' + + async def test_load_person_storage(hass, hass_admin_user, storage_setup): """Test set up person from storage.""" state = hass.states.get('person.tracked_person') From d1349d4919c3e088d3eb8281668f066385b102f9 Mon Sep 17 00:00:00 2001 From: yosilevy <37745463+yosilevy@users.noreply.github.com> Date: Sat, 23 Feb 2019 19:26:27 +0200 Subject: [PATCH 4/9] Scene validator fix (#21362) --- homeassistant/components/scene/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 802512dbf5d..8a7934bd694 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -32,7 +32,12 @@ def _platform_validator(config): 'homeassistant.components.scene.{}'.format( config[CONF_PLATFORM])) except ImportError: - raise vol.Invalid('Invalid platform specified') from None + try: + platform = importlib.import_module( + 'homeassistant.components.{}.scene'.format( + config[CONF_PLATFORM])) + except ImportError: + raise vol.Invalid('Invalid platform specified') from None if not hasattr(platform, 'PLATFORM_SCHEMA'): return config From 2c1032794592a632db4c13330cfd46cdc8674d8a Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Sun, 24 Feb 2019 10:45:11 -0600 Subject: [PATCH 5/9] Improve tolerance of SmartThings Climate platform (#21383) * Improve resilience of platform with buggy handlers * Eliminate possibility of None in operation list * Refactor variable name --- .../components/smartthings/climate.py | 42 ++++++++++++++--- tests/components/smartthings/test_climate.py | 45 ++++++++++++++++++- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index ab7334f1316..6c7cc5d4c74 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -1,6 +1,7 @@ """Support for climate devices through the SmartThings cloud API.""" import asyncio -from typing import Optional, Sequence +import logging +from typing import Iterable, Optional, Sequence from homeassistant.components.climate import ( ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -38,6 +39,8 @@ UNIT_MAP = { 'F': TEMP_FAHRENHEIT } +_LOGGER = logging.getLogger(__name__) + async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): @@ -50,7 +53,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] async_add_entities( [SmartThingsThermostat(device) for device in broker.devices.values() - if broker.any_assigned(device.device_id, 'climate')]) + if broker.any_assigned(device.device_id, CLIMATE_DOMAIN)], True) def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: @@ -90,6 +93,8 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice): """Init the class.""" super().__init__(device) self._supported_features = self._determine_features() + self._current_operation = None + self._operations = None def _determine_features(self): from pysmartthings import Capability @@ -127,6 +132,7 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice): if operation_state: mode = STATE_TO_MODE[operation_state] await self._device.set_thermostat_mode(mode, set_status=True) + await self.async_update() # Heat/cool setpoint heating_setpoint = None @@ -151,6 +157,33 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice): # the entity state ahead of receiving the confirming push updates self.async_schedule_update_ha_state(True) + async def async_update(self): + """Update the attributes of the climate device.""" + thermostat_mode = self._device.status.thermostat_mode + self._current_operation = MODE_TO_STATE.get(thermostat_mode) + if self._current_operation is None: + _LOGGER.debug('Device %s (%s) returned an invalid' + 'thermostat mode: %s', self._device.label, + self._device.device_id, thermostat_mode) + + supported_modes = self._device.status.supported_thermostat_modes + if isinstance(supported_modes, Iterable): + operations = set() + for mode in supported_modes: + state = MODE_TO_STATE.get(mode) + if state is not None: + operations.add(state) + else: + _LOGGER.debug('Device %s (%s) returned an invalid ' + 'supported thermostat mode: %s', + self._device.label, self._device.device_id, + mode) + self._operations = operations + else: + _LOGGER.debug('Device %s (%s) returned invalid supported ' + 'thermostat modes: %s', self._device.label, + self._device.device_id, supported_modes) + @property def current_fan_mode(self): """Return the fan setting.""" @@ -164,7 +197,7 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice): @property def current_operation(self): """Return current operation ie. heat, cool, idle.""" - return MODE_TO_STATE[self._device.status.thermostat_mode] + return self._current_operation @property def current_temperature(self): @@ -187,8 +220,7 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice): @property def operation_list(self): """Return the list of available operation modes.""" - return {MODE_TO_STATE[mode] for mode in - self._device.status.supported_thermostat_modes} + return self._operations @property def supported_features(self): diff --git a/tests/components/smartthings/test_climate.py b/tests/components/smartthings/test_climate.py index c5646fb400f..432c4d4b7cb 100644 --- a/tests/components/smartthings/test_climate.py +++ b/tests/components/smartthings/test_climate.py @@ -19,7 +19,8 @@ from homeassistant.components.climate import ( from homeassistant.components.smartthings import climate from homeassistant.components.smartthings.const import DOMAIN from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE) + ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, STATE_OFF, + STATE_UNKNOWN) from .conftest import setup_platform @@ -95,6 +96,25 @@ def thermostat_fixture(device_factory): return device +@pytest.fixture(name="buggy_thermostat") +def buggy_thermostat_fixture(device_factory): + """Fixture returns a buggy thermostat.""" + device = device_factory( + "Buggy Thermostat", + capabilities=[ + Capability.temperature_measurement, + Capability.thermostat_cooling_setpoint, + Capability.thermostat_heating_setpoint, + Capability.thermostat_mode], + status={ + Attribute.thermostat_mode: 'heating', + Attribute.cooling_setpoint: 74, + Attribute.heating_setpoint: 68} + ) + device.status.attributes[Attribute.temperature] = Status(70, 'F', None) + return device + + async def test_async_setup_platform(): """Test setup platform does nothing (it uses config entries).""" await climate.async_setup_platform(None, None, None) @@ -152,6 +172,29 @@ async def test_thermostat_entity_state(hass, thermostat): assert state.attributes[ATTR_CURRENT_HUMIDITY] == 34 +async def test_buggy_thermostat_entity_state(hass, buggy_thermostat): + """Tests the state attributes properly match the thermostat type.""" + await setup_platform(hass, CLIMATE_DOMAIN, buggy_thermostat) + state = hass.states.get('climate.buggy_thermostat') + assert state.state == STATE_UNKNOWN + assert state.attributes[ATTR_SUPPORTED_FEATURES] == \ + SUPPORT_OPERATION_MODE | SUPPORT_TARGET_TEMPERATURE_HIGH | \ + SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_TARGET_TEMPERATURE + assert ATTR_OPERATION_LIST not in state.attributes + assert state.attributes[ATTR_TEMPERATURE] is None + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.1 # celsius + + +async def test_buggy_thermostat_invalid_mode(hass, buggy_thermostat): + """Tests when an invalid operation mode is included.""" + buggy_thermostat.status.update_attribute_value( + Attribute.supported_thermostat_modes, + ['heat', 'emergency heat', 'other']) + await setup_platform(hass, CLIMATE_DOMAIN, buggy_thermostat) + state = hass.states.get('climate.buggy_thermostat') + assert state.attributes[ATTR_OPERATION_LIST] == {'heat'} + + async def test_set_fan_mode(hass, thermostat): """Test the fan mode is set successfully.""" await setup_platform(hass, CLIMATE_DOMAIN, thermostat) From 51eb6c7b4ec97e59e9dc9eea179b39a27b828554 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Tue, 26 Feb 2019 21:37:21 +0100 Subject: [PATCH 6/9] Bump PyXiaomiGateway version to 0.11.2 (#21453) --- homeassistant/components/xiaomi_aqara/__init__.py | 2 +- homeassistant/components/xiaomi_aqara/light.py | 2 ++ requirements_all.txt | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index ce943fb2c93..5e47adc47f9 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util.dt import utcnow -REQUIREMENTS = ['PyXiaomiGateway==0.11.1'] +REQUIREMENTS = ['PyXiaomiGateway==0.11.2'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/xiaomi_aqara/light.py b/homeassistant/components/xiaomi_aqara/light.py index 8ad0f2522d2..30433ccea3d 100644 --- a/homeassistant/components/xiaomi_aqara/light.py +++ b/homeassistant/components/xiaomi_aqara/light.py @@ -99,8 +99,10 @@ class XiaomiGatewayLight(XiaomiDevice, Light): if self._write_to_hub(self._sid, **{self._data_key: rgbhex}): self._state = True + self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the light off.""" if self._write_to_hub(self._sid, **{self._data_key: 0}): self._state = False + self.schedule_update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index ac85efe3be7..629dca5cccf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -63,7 +63,7 @@ PySwitchbot==0.5 PyTransportNSW==0.1.1 # homeassistant.components.xiaomi_aqara -PyXiaomiGateway==0.11.1 +PyXiaomiGateway==0.11.2 # homeassistant.components.rpi_gpio # RPi.GPIO==0.6.5 From 626f8a8b09666507f049e21d66e2f026d432f8ec Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 27 Feb 2019 15:25:21 -0800 Subject: [PATCH 7/9] Bumped version to 0.88.2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 28c76366312..e2329a2de26 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 88 -PATCH_VERSION = '1' +PATCH_VERSION = '2' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From 651b0d3506555f11e5d2d54694f17e828e6e1ff3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 27 Feb 2019 15:27:55 -0800 Subject: [PATCH 8/9] Fix cherry-pick fail ST --- homeassistant/components/smartthings/climate.py | 2 +- tests/components/smartthings/test_climate.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index 6c7cc5d4c74..f784ed101a7 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -8,7 +8,7 @@ from homeassistant.components.climate import ( STATE_AUTO, STATE_COOL, STATE_ECO, STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, - ClimateDevice) + ClimateDevice, DOMAIN as CLIMATE_DOMAIN) from homeassistant.const import ( ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT) diff --git a/tests/components/smartthings/test_climate.py b/tests/components/smartthings/test_climate.py index 432c4d4b7cb..306bcacdb18 100644 --- a/tests/components/smartthings/test_climate.py +++ b/tests/components/smartthings/test_climate.py @@ -19,8 +19,7 @@ from homeassistant.components.climate import ( from homeassistant.components.smartthings import climate from homeassistant.components.smartthings.const import DOMAIN from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, STATE_OFF, - STATE_UNKNOWN) + ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, STATE_UNKNOWN) from .conftest import setup_platform From 14f912500e6e1d1b065c29a7896eb0b4b4725461 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 26 Feb 2019 12:33:40 -0800 Subject: [PATCH 9/9] Pin isort (#21463) --- homeassistant/package_constraints.txt | 4 ++++ script/gen_requirements_all.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index d0a192a9dc7..838c3f31bc5 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -26,3 +26,7 @@ pycrypto==1000000000.0.0 # Contains code to modify Home Assistant to work around our rules python-systemair-savecair==1000000000.0.0 + +# Newer version causes pylint to take forever +# https://github.com/timothycrosley/isort/issues/848 +isort==4.3.4 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 47028ef3530..926aadfc3a5 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -157,6 +157,10 @@ pycrypto==1000000000.0.0 # Contains code to modify Home Assistant to work around our rules python-systemair-savecair==1000000000.0.0 + +# Newer version causes pylint to take forever +# https://github.com/timothycrosley/isort/issues/848 +isort==4.3.4 """