From 646ed5de528a2227ac1239426305f1a3abb0d3b9 Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Thu, 15 Mar 2018 12:31:31 +0100 Subject: [PATCH] Added cover.group platform (replaces #12303) (#12692) * Added cover.group platform * Added async/await, smaller changes * Made (async_update) methods regular methods * Small improvements * Changed classname * Changes based on feedback * Service calls * update_supported_features is now a callback method * combined all 'update_attr_*' methods in 'async_update' * Small changes * Fixes * is_closed * current_position * current_tilt_position * Updated tests * Small changes 2 --- CODEOWNERS | 1 + homeassistant/components/cover/group.py | 271 ++++++++++++++++++ tests/components/cover/test_group.py | 350 ++++++++++++++++++++++++ 3 files changed, 622 insertions(+) create mode 100755 homeassistant/components/cover/group.py create mode 100644 tests/components/cover/test_group.py diff --git a/CODEOWNERS b/CODEOWNERS index fedab8f6ae4..d8ebc3cff56 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -49,6 +49,7 @@ homeassistant/components/camera/yi.py @bachya homeassistant/components/climate/ephember.py @ttroy50 homeassistant/components/climate/eq3btsmart.py @rytilahti homeassistant/components/climate/sensibo.py @andrey-git +homeassistant/components/cover/group.py @cdce8p homeassistant/components/cover/template.py @PhracturedBlue homeassistant/components/device_tracker/automatic.py @armills homeassistant/components/device_tracker/tile.py @bachya diff --git a/homeassistant/components/cover/group.py b/homeassistant/components/cover/group.py new file mode 100755 index 00000000000..c1ea33a9cc7 --- /dev/null +++ b/homeassistant/components/cover/group.py @@ -0,0 +1,271 @@ +""" +This platform allows several cover to be grouped into one cover. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/cover.group/ +""" +import logging + +import voluptuous as vol + +from homeassistant.core import callback +from homeassistant.components.cover import ( + DOMAIN, PLATFORM_SCHEMA, CoverDevice, ATTR_POSITION, + ATTR_CURRENT_POSITION, ATTR_TILT_POSITION, ATTR_CURRENT_TILT_POSITION, + SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP, SUPPORT_SET_POSITION, + SUPPORT_OPEN_TILT, SUPPORT_CLOSE_TILT, + SUPPORT_STOP_TILT, SUPPORT_SET_TILT_POSITION, + SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION, + SERVICE_STOP_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_CLOSE_COVER_TILT, + SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION) +from homeassistant.const import ( + ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, + CONF_ENTITIES, CONF_NAME, STATE_CLOSED) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_state_change + +_LOGGER = logging.getLogger(__name__) + +KEY_OPEN_CLOSE = 'open_close' +KEY_STOP = 'stop' +KEY_POSITION = 'position' + +DEFAULT_NAME = 'Cover Group' + + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_ENTITIES): cv.entities_domain(DOMAIN), +}) + + +async def async_setup_platform(hass, config, async_add_devices, + discovery_info=None): + """Set up the Group Cover platform.""" + async_add_devices( + [CoverGroup(config[CONF_NAME], config[CONF_ENTITIES])]) + + +class CoverGroup(CoverDevice): + """Representation of a CoverGroup.""" + + def __init__(self, name, entities): + """Initialize a CoverGroup entity.""" + self._name = name + self._is_closed = False + self._cover_position = 100 + self._tilt_position = None + self._supported_features = 0 + self._assumed_state = True + + self._entities = entities + self._covers = {KEY_OPEN_CLOSE: set(), KEY_STOP: set(), + KEY_POSITION: set()} + self._tilts = {KEY_OPEN_CLOSE: set(), KEY_STOP: set(), + KEY_POSITION: set()} + + @callback + def update_supported_features(self, entity_id, old_state, new_state, + update_state=True): + """Update dictionaries with supported features.""" + if not new_state: + for values in self._covers.values(): + values.discard(entity_id) + for values in self._tilts.values(): + values.discard(entity_id) + if update_state: + self.async_schedule_update_ha_state(True) + return + + features = new_state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + + if features & (SUPPORT_OPEN | SUPPORT_CLOSE): + self._covers[KEY_OPEN_CLOSE].add(entity_id) + else: + self._covers[KEY_OPEN_CLOSE].discard(entity_id) + if features & (SUPPORT_STOP): + self._covers[KEY_STOP].add(entity_id) + else: + self._covers[KEY_STOP].discard(entity_id) + if features & (SUPPORT_SET_POSITION): + self._covers[KEY_POSITION].add(entity_id) + else: + self._covers[KEY_POSITION].discard(entity_id) + + if features & (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT): + self._tilts[KEY_OPEN_CLOSE].add(entity_id) + else: + self._tilts[KEY_OPEN_CLOSE].discard(entity_id) + if features & (SUPPORT_STOP_TILT): + self._tilts[KEY_STOP].add(entity_id) + else: + self._tilts[KEY_STOP].discard(entity_id) + if features & (SUPPORT_SET_TILT_POSITION): + self._tilts[KEY_POSITION].add(entity_id) + else: + self._tilts[KEY_POSITION].discard(entity_id) + + if update_state: + self.async_schedule_update_ha_state(True) + + async def async_added_to_hass(self): + """Register listeners.""" + for entity_id in self._entities: + new_state = self.hass.states.get(entity_id) + self.update_supported_features(entity_id, None, new_state, + update_state=False) + async_track_state_change(self.hass, self._entities, + self.update_supported_features) + await self.async_update() + + @property + def name(self): + """Return the name of the cover.""" + return self._name + + @property + def assumed_state(self): + """Enable buttons even if at end position.""" + return self._assumed_state + + @property + def should_poll(self): + """Disable polling for cover group.""" + return False + + @property + def supported_features(self): + """Flag supported features for the cover.""" + return self._supported_features + + @property + def is_closed(self): + """Return if all covers in group are closed.""" + return self._is_closed + + @property + def current_cover_position(self): + """Return current position for all covers.""" + return self._cover_position + + @property + def current_cover_tilt_position(self): + """Return current tilt position for all covers.""" + return self._tilt_position + + async def async_open_cover(self, **kwargs): + """Move the covers up.""" + data = {ATTR_ENTITY_ID: self._covers[KEY_OPEN_CLOSE]} + await self.hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, data, blocking=True) + + async def async_close_cover(self, **kwargs): + """Move the covers down.""" + data = {ATTR_ENTITY_ID: self._covers[KEY_OPEN_CLOSE]} + await self.hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, data, blocking=True) + + async def async_stop_cover(self, **kwargs): + """Fire the stop action.""" + data = {ATTR_ENTITY_ID: self._covers[KEY_STOP]} + await self.hass.services.async_call( + DOMAIN, SERVICE_STOP_COVER, data, blocking=True) + + async def async_set_cover_position(self, **kwargs): + """Set covers position.""" + data = {ATTR_ENTITY_ID: self._covers[KEY_POSITION], + ATTR_POSITION: kwargs[ATTR_POSITION]} + await self.hass.services.async_call( + DOMAIN, SERVICE_SET_COVER_POSITION, data, blocking=True) + + async def async_open_cover_tilt(self, **kwargs): + """Tilt covers open.""" + data = {ATTR_ENTITY_ID: self._tilts[KEY_OPEN_CLOSE]} + await self.hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER_TILT, data, blocking=True) + + async def async_close_cover_tilt(self, **kwargs): + """Tilt covers closed.""" + data = {ATTR_ENTITY_ID: self._tilts[KEY_OPEN_CLOSE]} + await self.hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER_TILT, data, blocking=True) + + async def async_stop_cover_tilt(self, **kwargs): + """Stop cover tilt.""" + data = {ATTR_ENTITY_ID: self._tilts[KEY_STOP]} + await self.hass.services.async_call( + DOMAIN, SERVICE_STOP_COVER_TILT, data, blocking=True) + + async def async_set_cover_tilt_position(self, **kwargs): + """Set tilt position.""" + data = {ATTR_ENTITY_ID: self._tilts[KEY_POSITION], + ATTR_TILT_POSITION: kwargs[ATTR_TILT_POSITION]} + await self.hass.services.async_call( + DOMAIN, SERVICE_SET_COVER_TILT_POSITION, data, blocking=True) + + async def async_update(self): + """Update state and attributes.""" + self._assumed_state = False + + self._is_closed = True + for entity_id in self._entities: + state = self.hass.states.get(entity_id) + if not state: + continue + if state.state != STATE_CLOSED: + self._is_closed = False + break + + self._cover_position = None + if self._covers[KEY_POSITION]: + position = -1 + self._cover_position = 0 if self.is_closed else 100 + for entity_id in self._covers[KEY_POSITION]: + state = self.hass.states.get(entity_id) + pos = state.attributes.get(ATTR_CURRENT_POSITION) + if position == -1: + position = pos + elif position != pos: + self._assumed_state = True + break + else: + if position != -1: + self._cover_position = position + + self._tilt_position = None + if self._tilts[KEY_POSITION]: + position = -1 + self._tilt_position = 100 + for entity_id in self._tilts[KEY_POSITION]: + state = self.hass.states.get(entity_id) + pos = state.attributes.get(ATTR_CURRENT_TILT_POSITION) + if position == -1: + position = pos + elif position != pos: + self._assumed_state = True + break + else: + if position != -1: + self._tilt_position = position + + supported_features = 0 + supported_features |= SUPPORT_OPEN | SUPPORT_CLOSE \ + if self._covers[KEY_OPEN_CLOSE] else 0 + supported_features |= SUPPORT_STOP \ + if self._covers[KEY_STOP] else 0 + supported_features |= SUPPORT_SET_POSITION \ + if self._covers[KEY_POSITION] else 0 + supported_features |= SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT \ + if self._tilts[KEY_OPEN_CLOSE] else 0 + supported_features |= SUPPORT_STOP_TILT \ + if self._tilts[KEY_STOP] else 0 + supported_features |= SUPPORT_SET_TILT_POSITION \ + if self._tilts[KEY_POSITION] else 0 + self._supported_features = supported_features + + if not self._assumed_state: + for entity_id in self._entities: + state = self.hass.states.get(entity_id) + if state and state.attributes.get(ATTR_ASSUMED_STATE): + self._assumed_state = True + break diff --git a/tests/components/cover/test_group.py b/tests/components/cover/test_group.py new file mode 100644 index 00000000000..288e1c5e047 --- /dev/null +++ b/tests/components/cover/test_group.py @@ -0,0 +1,350 @@ +"""The tests for the group cover platform.""" + +import unittest +from datetime import timedelta +import homeassistant.util.dt as dt_util + +from homeassistant import setup +from homeassistant.components import cover +from homeassistant.components.cover import ( + ATTR_CURRENT_POSITION, ATTR_CURRENT_TILT_POSITION, DOMAIN) +from homeassistant.components.cover.group import DEFAULT_NAME +from homeassistant.const import ( + ATTR_ASSUMED_STATE, ATTR_FRIENDLY_NAME, ATTR_SUPPORTED_FEATURES, + CONF_ENTITIES, STATE_OPEN, STATE_CLOSED) +from tests.common import ( + assert_setup_component, get_test_home_assistant, fire_time_changed) + +COVER_GROUP = 'cover.cover_group' +DEMO_COVER = 'cover.kitchen_window' +DEMO_COVER_POS = 'cover.hall_window' +DEMO_COVER_TILT = 'cover.living_room_window' +DEMO_TILT = 'cover.tilt_demo' + +CONFIG = { + DOMAIN: [ + {'platform': 'demo'}, + {'platform': 'group', + CONF_ENTITIES: [ + DEMO_COVER, DEMO_COVER_POS, DEMO_COVER_TILT, DEMO_TILT]} + ] +} + + +class TestMultiCover(unittest.TestCase): + """Test the group cover platform.""" + + def setUp(self): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + def tearDown(self): + """Stop down everything that was started.""" + self.hass.stop() + + def test_attributes(self): + """Test handling of state attributes.""" + config = {DOMAIN: {'platform': 'group', CONF_ENTITIES: [ + DEMO_COVER, DEMO_COVER_POS, DEMO_COVER_TILT, DEMO_TILT]}} + + with assert_setup_component(1, DOMAIN): + assert setup.setup_component(self.hass, DOMAIN, config) + + state = self.hass.states.get(COVER_GROUP) + attr = state.attributes + self.assertEqual(state.state, STATE_CLOSED) + self.assertEqual(attr.get(ATTR_FRIENDLY_NAME), DEFAULT_NAME) + self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None) + self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 0) + self.assertEqual(attr.get(ATTR_CURRENT_POSITION), None) + self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), None) + + # Add Entity that supports open / close / stop + self.hass.states.set( + DEMO_COVER, STATE_OPEN, {ATTR_SUPPORTED_FEATURES: 11}) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + attr = state.attributes + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None) + self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 11) + self.assertEqual(attr.get(ATTR_CURRENT_POSITION), None) + self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), None) + + # Add Entity that supports set_cover_position + self.hass.states.set( + DEMO_COVER_POS, STATE_OPEN, + {ATTR_SUPPORTED_FEATURES: 4, ATTR_CURRENT_POSITION: 70}) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + attr = state.attributes + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None) + self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 15) + self.assertEqual(attr.get(ATTR_CURRENT_POSITION), 70) + self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), None) + + # Add Entity that supports open tilt / close tilt / stop tilt + self.hass.states.set( + DEMO_TILT, STATE_OPEN, {ATTR_SUPPORTED_FEATURES: 112}) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + attr = state.attributes + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None) + self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 127) + self.assertEqual(attr.get(ATTR_CURRENT_POSITION), 70) + self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), None) + + # Add Entity that supports set_tilt_position + self.hass.states.set( + DEMO_COVER_TILT, STATE_OPEN, + {ATTR_SUPPORTED_FEATURES: 128, ATTR_CURRENT_TILT_POSITION: 60}) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + attr = state.attributes + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None) + self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 255) + self.assertEqual(attr.get(ATTR_CURRENT_POSITION), 70) + self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), 60) + + # ### Test assumed state ### + # ########################## + + # For covers + self.hass.states.set( + DEMO_COVER, STATE_OPEN, + {ATTR_SUPPORTED_FEATURES: 4, ATTR_CURRENT_POSITION: 100}) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + attr = state.attributes + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(attr.get(ATTR_ASSUMED_STATE), True) + self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 244) + self.assertEqual(attr.get(ATTR_CURRENT_POSITION), 100) + self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), 60) + + self.hass.states.remove(DEMO_COVER) + self.hass.block_till_done() + self.hass.states.remove(DEMO_COVER_POS) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + attr = state.attributes + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None) + self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 240) + self.assertEqual(attr.get(ATTR_CURRENT_POSITION), None) + self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), 60) + + # For tilts + self.hass.states.set( + DEMO_TILT, STATE_OPEN, + {ATTR_SUPPORTED_FEATURES: 128, ATTR_CURRENT_TILT_POSITION: 100}) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + attr = state.attributes + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(attr.get(ATTR_ASSUMED_STATE), True) + self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 128) + self.assertEqual(attr.get(ATTR_CURRENT_POSITION), None) + self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), 100) + + self.hass.states.remove(DEMO_COVER_TILT) + self.hass.states.set(DEMO_TILT, STATE_CLOSED) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + attr = state.attributes + self.assertEqual(state.state, STATE_CLOSED) + self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None) + self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 0) + self.assertEqual(attr.get(ATTR_CURRENT_POSITION), None) + self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), None) + + self.hass.states.set( + DEMO_TILT, STATE_CLOSED, {ATTR_ASSUMED_STATE: True}) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + attr = state.attributes + self.assertEqual(attr.get(ATTR_ASSUMED_STATE), True) + + def test_open_covers(self): + """Test open cover function.""" + with assert_setup_component(2, DOMAIN): + assert setup.setup_component(self.hass, DOMAIN, CONFIG) + + cover.open_cover(self.hass, COVER_GROUP) + self.hass.block_till_done() + for _ in range(10): + future = dt_util.utcnow() + timedelta(seconds=1) + fire_time_changed(self.hass, future) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(state.attributes.get(ATTR_CURRENT_POSITION), 100) + + self.assertEqual(self.hass.states.get(DEMO_COVER).state, STATE_OPEN) + self.assertEqual(self.hass.states.get(DEMO_COVER_POS) + .attributes.get(ATTR_CURRENT_POSITION), 100) + self.assertEqual(self.hass.states.get(DEMO_COVER_TILT) + .attributes.get(ATTR_CURRENT_POSITION), 100) + + def test_close_covers(self): + """Test close cover function.""" + with assert_setup_component(2, DOMAIN): + assert setup.setup_component(self.hass, DOMAIN, CONFIG) + + cover.close_cover(self.hass, COVER_GROUP) + self.hass.block_till_done() + for _ in range(10): + future = dt_util.utcnow() + timedelta(seconds=1) + fire_time_changed(self.hass, future) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + self.assertEqual(state.state, STATE_CLOSED) + self.assertEqual(state.attributes.get(ATTR_CURRENT_POSITION), 0) + + self.assertEqual(self.hass.states.get(DEMO_COVER).state, STATE_CLOSED) + self.assertEqual(self.hass.states.get(DEMO_COVER_POS) + .attributes.get(ATTR_CURRENT_POSITION), 0) + self.assertEqual(self.hass.states.get(DEMO_COVER_TILT) + .attributes.get(ATTR_CURRENT_POSITION), 0) + + def test_stop_covers(self): + """Test stop cover function.""" + with assert_setup_component(2, DOMAIN): + assert setup.setup_component(self.hass, DOMAIN, CONFIG) + + cover.open_cover(self.hass, COVER_GROUP) + self.hass.block_till_done() + future = dt_util.utcnow() + timedelta(seconds=1) + fire_time_changed(self.hass, future) + self.hass.block_till_done() + cover.stop_cover(self.hass, COVER_GROUP) + self.hass.block_till_done() + future = dt_util.utcnow() + timedelta(seconds=1) + fire_time_changed(self.hass, future) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(state.attributes.get(ATTR_CURRENT_POSITION), 100) + + self.assertEqual(self.hass.states.get(DEMO_COVER).state, STATE_OPEN) + self.assertEqual(self.hass.states.get(DEMO_COVER_POS) + .attributes.get(ATTR_CURRENT_POSITION), 20) + self.assertEqual(self.hass.states.get(DEMO_COVER_TILT) + .attributes.get(ATTR_CURRENT_POSITION), 80) + + def test_set_cover_position(self): + """Test set cover position function.""" + with assert_setup_component(2, DOMAIN): + assert setup.setup_component(self.hass, DOMAIN, CONFIG) + + cover.set_cover_position(self.hass, 50, COVER_GROUP) + self.hass.block_till_done() + for _ in range(4): + future = dt_util.utcnow() + timedelta(seconds=1) + fire_time_changed(self.hass, future) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(state.attributes.get(ATTR_CURRENT_POSITION), 50) + + self.assertEqual(self.hass.states.get(DEMO_COVER).state, STATE_CLOSED) + self.assertEqual(self.hass.states.get(DEMO_COVER_POS) + .attributes.get(ATTR_CURRENT_POSITION), 50) + self.assertEqual(self.hass.states.get(DEMO_COVER_TILT) + .attributes.get(ATTR_CURRENT_POSITION), 50) + + def test_open_tilts(self): + """Test open tilt function.""" + with assert_setup_component(2, DOMAIN): + assert setup.setup_component(self.hass, DOMAIN, CONFIG) + + cover.open_cover_tilt(self.hass, COVER_GROUP) + self.hass.block_till_done() + for _ in range(5): + future = dt_util.utcnow() + timedelta(seconds=1) + fire_time_changed(self.hass, future) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(state.attributes.get(ATTR_CURRENT_TILT_POSITION), 100) + + self.assertEqual(self.hass.states.get(DEMO_COVER_TILT) + .attributes.get(ATTR_CURRENT_TILT_POSITION), 100) + + def test_close_tilts(self): + """Test close tilt function.""" + with assert_setup_component(2, DOMAIN): + assert setup.setup_component(self.hass, DOMAIN, CONFIG) + + cover.close_cover_tilt(self.hass, COVER_GROUP) + self.hass.block_till_done() + for _ in range(5): + future = dt_util.utcnow() + timedelta(seconds=1) + fire_time_changed(self.hass, future) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(state.attributes.get(ATTR_CURRENT_TILT_POSITION), 0) + + self.assertEqual(self.hass.states.get(DEMO_COVER_TILT) + .attributes.get(ATTR_CURRENT_TILT_POSITION), 0) + + def test_stop_tilts(self): + """Test stop tilts function.""" + with assert_setup_component(2, DOMAIN): + assert setup.setup_component(self.hass, DOMAIN, CONFIG) + + cover.open_cover_tilt(self.hass, COVER_GROUP) + self.hass.block_till_done() + future = dt_util.utcnow() + timedelta(seconds=1) + fire_time_changed(self.hass, future) + self.hass.block_till_done() + cover.stop_cover_tilt(self.hass, COVER_GROUP) + self.hass.block_till_done() + future = dt_util.utcnow() + timedelta(seconds=1) + fire_time_changed(self.hass, future) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(state.attributes.get(ATTR_CURRENT_TILT_POSITION), 60) + + self.assertEqual(self.hass.states.get(DEMO_COVER_TILT) + .attributes.get(ATTR_CURRENT_TILT_POSITION), 60) + + def test_set_tilt_positions(self): + """Test set tilt position function.""" + with assert_setup_component(2, DOMAIN): + assert setup.setup_component(self.hass, DOMAIN, CONFIG) + + cover.set_cover_tilt_position(self.hass, 80, COVER_GROUP) + self.hass.block_till_done() + for _ in range(3): + future = dt_util.utcnow() + timedelta(seconds=1) + fire_time_changed(self.hass, future) + self.hass.block_till_done() + + state = self.hass.states.get(COVER_GROUP) + self.assertEqual(state.state, STATE_OPEN) + self.assertEqual(state.attributes.get(ATTR_CURRENT_TILT_POSITION), 80) + + self.assertEqual(self.hass.states.get(DEMO_COVER_TILT) + .attributes.get(ATTR_CURRENT_TILT_POSITION), 80)