Merge pull request #21508 from home-assistant/rc

0.88.2
This commit is contained in:
Paulus Schoutsen 2019-02-27 16:19:26 -08:00 committed by GitHub
commit 63fda29eaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 131 additions and 17 deletions

View File

@ -151,6 +151,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device['name'] = '{} {}'.format(device['id'], ipaddr) device['name'] = '{} {}'.format(device['id'], ipaddr)
device[ATTR_MODE] = MODE_RGBW device[ATTR_MODE] = MODE_RGBW
device[CONF_PROTOCOL] = None device[CONF_PROTOCOL] = None
device[CONF_CUSTOM_EFFECT] = None
light = FluxLight(device) light = FluxLight(device)
lights.append(light) lights.append(light)

View File

@ -176,7 +176,7 @@ class PersonManager:
CONF_ID: uuid.uuid4().hex, CONF_ID: uuid.uuid4().hex,
CONF_NAME: name, CONF_NAME: name,
CONF_USER_ID: user_id, CONF_USER_ID: user_id,
CONF_DEVICE_TRACKERS: device_trackers, CONF_DEVICE_TRACKERS: device_trackers or [],
} }
self.storage_data[person[CONF_ID]] = person self.storage_data[person[CONF_ID]] = person
self._async_schedule_save() self._async_schedule_save()
@ -337,6 +337,12 @@ class Person(RestoreEntity):
if state: if state:
self._parse_source_state(state) self._parse_source_state(state)
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 @callback
def person_start_hass(now): def person_start_hass(now):
self.person_updated() self.person_updated()

View File

@ -31,6 +31,11 @@ def _platform_validator(config):
platform = importlib.import_module( platform = importlib.import_module(
'homeassistant.components.scene.{}'.format( 'homeassistant.components.scene.{}'.format(
config[CONF_PLATFORM])) config[CONF_PLATFORM]))
except ImportError:
try:
platform = importlib.import_module(
'homeassistant.components.{}.scene'.format(
config[CONF_PLATFORM]))
except ImportError: except ImportError:
raise vol.Invalid('Invalid platform specified') from None raise vol.Invalid('Invalid platform specified') from None

View File

@ -1,13 +1,14 @@
"""Support for climate devices through the SmartThings cloud API.""" """Support for climate devices through the SmartThings cloud API."""
import asyncio import asyncio
from typing import Optional, Sequence import logging
from typing import Iterable, Optional, Sequence
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
STATE_AUTO, STATE_COOL, STATE_ECO, STATE_HEAT, SUPPORT_FAN_MODE, STATE_AUTO, STATE_COOL, STATE_ECO, STATE_HEAT, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
ClimateDevice) ClimateDevice, DOMAIN as CLIMATE_DOMAIN)
from homeassistant.const import ( from homeassistant.const import (
ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT) ATTR_TEMPERATURE, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT)
@ -38,6 +39,8 @@ UNIT_MAP = {
'F': TEMP_FAHRENHEIT 'F': TEMP_FAHRENHEIT
} }
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform( async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None): 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] broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id]
async_add_entities( async_add_entities(
[SmartThingsThermostat(device) for device in broker.devices.values() [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]]: def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]:
@ -90,6 +93,8 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
"""Init the class.""" """Init the class."""
super().__init__(device) super().__init__(device)
self._supported_features = self._determine_features() self._supported_features = self._determine_features()
self._current_operation = None
self._operations = None
def _determine_features(self): def _determine_features(self):
from pysmartthings import Capability from pysmartthings import Capability
@ -127,6 +132,7 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
if operation_state: if operation_state:
mode = STATE_TO_MODE[operation_state] mode = STATE_TO_MODE[operation_state]
await self._device.set_thermostat_mode(mode, set_status=True) await self._device.set_thermostat_mode(mode, set_status=True)
await self.async_update()
# Heat/cool setpoint # Heat/cool setpoint
heating_setpoint = None heating_setpoint = None
@ -151,6 +157,33 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
# the entity state ahead of receiving the confirming push updates # the entity state ahead of receiving the confirming push updates
self.async_schedule_update_ha_state(True) 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 @property
def current_fan_mode(self): def current_fan_mode(self):
"""Return the fan setting.""" """Return the fan setting."""
@ -164,7 +197,7 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
@property @property
def current_operation(self): def current_operation(self):
"""Return current operation ie. heat, cool, idle.""" """Return current operation ie. heat, cool, idle."""
return MODE_TO_STATE[self._device.status.thermostat_mode] return self._current_operation
@property @property
def current_temperature(self): def current_temperature(self):
@ -187,8 +220,7 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice):
@property @property
def operation_list(self): def operation_list(self):
"""Return the list of available operation modes.""" """Return the list of available operation modes."""
return {MODE_TO_STATE[mode] for mode in return self._operations
self._device.status.supported_thermostat_modes}
@property @property
def supported_features(self): def supported_features(self):

View File

@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
REQUIREMENTS = ['PyXiaomiGateway==0.11.1'] REQUIREMENTS = ['PyXiaomiGateway==0.11.2']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -99,8 +99,10 @@ class XiaomiGatewayLight(XiaomiDevice, Light):
if self._write_to_hub(self._sid, **{self._data_key: rgbhex}): if self._write_to_hub(self._sid, **{self._data_key: rgbhex}):
self._state = True self._state = True
self.schedule_update_ha_state()
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn the light off.""" """Turn the light off."""
if self._write_to_hub(self._sid, **{self._data_key: 0}): if self._write_to_hub(self._sid, **{self._data_key: 0}):
self._state = False self._state = False
self.schedule_update_ha_state()

View File

@ -2,7 +2,7 @@
"""Constants used by Home Assistant components.""" """Constants used by Home Assistant components."""
MAJOR_VERSION = 0 MAJOR_VERSION = 0
MINOR_VERSION = 88 MINOR_VERSION = 88
PATCH_VERSION = '1' PATCH_VERSION = '2'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 5, 3) REQUIRED_PYTHON_VER = (3, 5, 3)

View File

@ -26,3 +26,7 @@ pycrypto==1000000000.0.0
# Contains code to modify Home Assistant to work around our rules # Contains code to modify Home Assistant to work around our rules
python-systemair-savecair==1000000000.0.0 python-systemair-savecair==1000000000.0.0
# Newer version causes pylint to take forever
# https://github.com/timothycrosley/isort/issues/848
isort==4.3.4

View File

@ -63,7 +63,7 @@ PySwitchbot==0.5
PyTransportNSW==0.1.1 PyTransportNSW==0.1.1
# homeassistant.components.xiaomi_aqara # homeassistant.components.xiaomi_aqara
PyXiaomiGateway==0.11.1 PyXiaomiGateway==0.11.2
# homeassistant.components.rpi_gpio # homeassistant.components.rpi_gpio
# RPi.GPIO==0.6.5 # RPi.GPIO==0.6.5

View File

@ -157,6 +157,10 @@ pycrypto==1000000000.0.0
# Contains code to modify Home Assistant to work around our rules # Contains code to modify Home Assistant to work around our rules
python-systemair-savecair==1000000000.0.0 python-systemair-savecair==1000000000.0.0
# Newer version causes pylint to take forever
# https://github.com/timothycrosley/isort/issues/848
isort==4.3.4
""" """

View File

@ -101,6 +101,7 @@ async def test_valid_invalid_user_ids(hass, hass_admin_user):
async def test_setup_tracker(hass, hass_admin_user): async def test_setup_tracker(hass, hass_admin_user):
"""Test set up person with one device tracker.""" """Test set up person with one device tracker."""
hass.state = CoreState.not_running
user_id = hass_admin_user.id user_id = hass_admin_user.id
config = {DOMAIN: { config = {DOMAIN: {
'id': '1234', 'name': 'tracked person', 'user_id': user_id, '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): async def test_setup_two_trackers(hass, hass_admin_user):
"""Test set up person with two device trackers.""" """Test set up person with two device trackers."""
hass.state = CoreState.not_running
user_id = hass_admin_user.id user_id = hass_admin_user.id
config = {DOMAIN: { config = {DOMAIN: {
'id': '1234', 'name': 'tracked person', 'user_id': user_id, '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): async def test_ignore_unavailable_states(hass, hass_admin_user):
"""Test set up person with two device trackers, one unavailable.""" """Test set up person with two device trackers, one unavailable."""
hass.state = CoreState.not_running
user_id = hass_admin_user.id user_id = hass_admin_user.id
config = {DOMAIN: { config = {DOMAIN: {
'id': '1234', 'name': 'tracked person', 'user_id': user_id, '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} ATTR_SOURCE: DEVICE_TRACKER, ATTR_USER_ID: user_id}
state = State('person.tracked_person', 'home', attrs) state = State('person.tracked_person', 'home', attrs)
mock_restore_cache(hass, (state, )) mock_restore_cache(hass, (state, ))
hass.state = CoreState.starting hass.state = CoreState.not_running
mock_component(hass, 'recorder') mock_component(hass, 'recorder')
config = {DOMAIN: { config = {DOMAIN: {
'id': '1234', 'name': 'tracked person', 'user_id': user_id, '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 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): async def test_load_person_storage(hass, hass_admin_user, storage_setup):
"""Test set up person from storage.""" """Test set up person from storage."""
state = hass.states.get('person.tracked_person') state = hass.states.get('person.tracked_person')

View File

@ -19,7 +19,7 @@ from homeassistant.components.climate import (
from homeassistant.components.smartthings import climate from homeassistant.components.smartthings import climate
from homeassistant.components.smartthings.const import DOMAIN from homeassistant.components.smartthings.const import DOMAIN
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE) ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, STATE_UNKNOWN)
from .conftest import setup_platform from .conftest import setup_platform
@ -95,6 +95,25 @@ def thermostat_fixture(device_factory):
return device 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(): async def test_async_setup_platform():
"""Test setup platform does nothing (it uses config entries).""" """Test setup platform does nothing (it uses config entries)."""
await climate.async_setup_platform(None, None, None) await climate.async_setup_platform(None, None, None)
@ -152,6 +171,29 @@ async def test_thermostat_entity_state(hass, thermostat):
assert state.attributes[ATTR_CURRENT_HUMIDITY] == 34 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): async def test_set_fan_mode(hass, thermostat):
"""Test the fan mode is set successfully.""" """Test the fan mode is set successfully."""
await setup_platform(hass, CLIMATE_DOMAIN, thermostat) await setup_platform(hass, CLIMATE_DOMAIN, thermostat)