From 0f49a9cb7b1ea81328a6bec068ee1bd73b5e49d0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 28 Apr 2019 12:09:20 -0700 Subject: [PATCH] Return state when changing optimistic covers (#23498) --- .../components/google_assistant/trait.py | 29 +++-- .../components/google_assistant/test_trait.py | 115 +++++++++++++----- 2 files changed, 105 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index ac2f65af058..11bb4f28aeb 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -1027,6 +1027,8 @@ class OpenCloseTrait(_Trait): COMMAND_OPENCLOSE ] + override_position = None + @staticmethod def supported(domain, features, device_class): """Test if state is supported.""" @@ -1043,20 +1045,22 @@ class OpenCloseTrait(_Trait): def sync_attributes(self): """Return opening direction.""" - attrs = {} + response = {} if self.state.domain == binary_sensor.DOMAIN: - attrs['queryOnlyOpenClose'] = True - return attrs + response['queryOnlyOpenClose'] = True + return response def query_attributes(self): """Return state query attributes.""" domain = self.state.domain response = {} - if domain == cover.DOMAIN: - # When it's an assumed state, we will always report it as 50% - # Google will not issue an open command if the assumed state is - # open, even if that is currently incorrect. + if self.override_position is not None: + response['openPercent'] = self.override_position + + elif domain == cover.DOMAIN: + # When it's an assumed state, we will return that querying state + # is not supported. if self.state.attributes.get(ATTR_ASSUMED_STATE): raise SmartHomeError( ERR_NOT_SUPPORTED, @@ -1067,7 +1071,7 @@ class OpenCloseTrait(_Trait): ERR_NOT_SUPPORTED, 'Querying state is not supported') - position = self.state.attributes.get( + position = self.override_position or self.state.attributes.get( cover.ATTR_CURRENT_POSITION ) @@ -1096,7 +1100,6 @@ class OpenCloseTrait(_Trait): ): _verify_pin_challenge(data, challenge) - position = self.state.attributes.get(cover.ATTR_CURRENT_POSITION) if params['openPercent'] == 0: await self.hass.services.async_call( cover.DOMAIN, cover.SERVICE_CLOSE_COVER, { @@ -1107,7 +1110,8 @@ class OpenCloseTrait(_Trait): cover.DOMAIN, cover.SERVICE_OPEN_COVER, { ATTR_ENTITY_ID: self.state.entity_id }, blocking=True, context=data.context) - elif position is not None: + elif (self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) & + cover.SUPPORT_SET_POSITION): await self.hass.services.async_call( cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION, { ATTR_ENTITY_ID: self.state.entity_id, @@ -1118,6 +1122,11 @@ class OpenCloseTrait(_Trait): ERR_FUNCTION_NOT_SUPPORTED, 'Setting a position is not supported') + if (self.state.attributes.get(ATTR_ASSUMED_STATE) or + self.state.state == STATE_UNKNOWN): + print("YOO") + self.override_position = params['openPercent'] + @register_trait class VolumeTrait(_Trait): diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 96ca8d82f5e..3be25b498b5 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1109,36 +1109,9 @@ async def test_openclose_cover(hass): assert trait.OpenCloseTrait.supported(cover.DOMAIN, cover.SUPPORT_SET_POSITION, None) - # No position trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, { - }), BASIC_CONFIG) - - assert trt.sync_attributes() == {} - assert trt.query_attributes() == { - 'openPercent': 100 - } - - # No state - trt = trait.OpenCloseTrait(hass, State('cover.bla', STATE_UNKNOWN, { - }), BASIC_CONFIG) - - assert trt.sync_attributes() == {} - - with pytest.raises(helpers.SmartHomeError): - trt.query_attributes() - - # Assumed state - trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, { - ATTR_ASSUMED_STATE: True, - }), BASIC_CONFIG) - - assert trt.sync_attributes() == {} - - with pytest.raises(helpers.SmartHomeError): - trt.query_attributes() - - trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, { - cover.ATTR_CURRENT_POSITION: 75 + cover.ATTR_CURRENT_POSITION: 75, + ATTR_SUPPORTED_FEATURES: cover.SUPPORT_SET_POSITION, }), BASIC_CONFIG) assert trt.sync_attributes() == {} @@ -1158,6 +1131,89 @@ async def test_openclose_cover(hass): } +async def test_openclose_cover_unknown_state(hass): + """Test OpenClose trait support for cover domain with unknown state.""" + assert helpers.get_google_type(cover.DOMAIN, None) is not None + assert trait.OpenCloseTrait.supported(cover.DOMAIN, + cover.SUPPORT_SET_POSITION, None) + + # No state + trt = trait.OpenCloseTrait(hass, State('cover.bla', STATE_UNKNOWN, { + }), BASIC_CONFIG) + + assert trt.sync_attributes() == {} + + with pytest.raises(helpers.SmartHomeError): + trt.query_attributes() + + calls = async_mock_service( + hass, cover.DOMAIN, cover.SERVICE_OPEN_COVER) + await trt.execute( + trait.COMMAND_OPENCLOSE, BASIC_DATA, + {'openPercent': 100}, {}) + assert len(calls) == 1 + assert calls[0].data == { + ATTR_ENTITY_ID: 'cover.bla', + } + + assert trt.query_attributes() == {'openPercent': 100} + + +async def test_openclose_cover_assumed_state(hass): + """Test OpenClose trait support for cover domain.""" + assert helpers.get_google_type(cover.DOMAIN, None) is not None + assert trait.OpenCloseTrait.supported(cover.DOMAIN, + cover.SUPPORT_SET_POSITION, None) + + trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, { + ATTR_ASSUMED_STATE: True, + ATTR_SUPPORTED_FEATURES: cover.SUPPORT_SET_POSITION, + }), BASIC_CONFIG) + + assert trt.sync_attributes() == {} + + with pytest.raises(helpers.SmartHomeError): + trt.query_attributes() + + calls = async_mock_service( + hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION) + await trt.execute( + trait.COMMAND_OPENCLOSE, BASIC_DATA, + {'openPercent': 40}, {}) + assert len(calls) == 1 + assert calls[0].data == { + ATTR_ENTITY_ID: 'cover.bla', + cover.ATTR_POSITION: 40 + } + + assert trt.query_attributes() == {'openPercent': 40} + + +async def test_openclose_cover_no_position(hass): + """Test OpenClose trait support for cover domain.""" + assert helpers.get_google_type(cover.DOMAIN, None) is not None + assert trait.OpenCloseTrait.supported(cover.DOMAIN, + cover.SUPPORT_SET_POSITION, None) + + trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, { + }), BASIC_CONFIG) + + assert trt.sync_attributes() == {} + assert trt.query_attributes() == { + 'openPercent': 100 + } + + calls = async_mock_service( + hass, cover.DOMAIN, cover.SERVICE_CLOSE_COVER) + await trt.execute( + trait.COMMAND_OPENCLOSE, BASIC_DATA, + {'openPercent': 0}, {}) + assert len(calls) == 1 + assert calls[0].data == { + ATTR_ENTITY_ID: 'cover.bla', + } + + @pytest.mark.parametrize('device_class', ( cover.DEVICE_CLASS_DOOR, cover.DEVICE_CLASS_GARAGE, @@ -1170,6 +1226,7 @@ async def test_openclose_cover_secure(hass, device_class): trt = trait.OpenCloseTrait(hass, State('cover.bla', cover.STATE_OPEN, { ATTR_DEVICE_CLASS: device_class, + ATTR_SUPPORTED_FEATURES: cover.SUPPORT_SET_POSITION, cover.ATTR_CURRENT_POSITION: 75 }), PIN_CONFIG)