From 416ee7f143c65e398b58160a5a5963c8a239ba81 Mon Sep 17 00:00:00 2001 From: Marcio Granzotto Rodrigues Date: Fri, 31 Jul 2020 23:05:00 -0300 Subject: [PATCH] Add support to climate devices in Google Assistant Fan Trait (#38337) Co-authored-by: Paulus Schoutsen --- .../components/google_assistant/trait.py | 98 +++++++++++++------ tests/components/google_assistant/__init__.py | 10 +- .../google_assistant/test_google_assistant.py | 4 + .../components/google_assistant/test_trait.py | 59 +++++++++++ 4 files changed, 137 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index da3363fb4d9..90b5016260d 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -1153,55 +1153,89 @@ class FanSpeedTrait(_Trait): @staticmethod def supported(domain, features, device_class): """Test if state is supported.""" - if domain != fan.DOMAIN: - return False - - return features & fan.SUPPORT_SET_SPEED + if domain == fan.DOMAIN: + return features & fan.SUPPORT_SET_SPEED + if domain == climate.DOMAIN: + return features & climate.SUPPORT_FAN_MODE + return False def sync_attributes(self): """Return speed point and modes attributes for a sync request.""" - modes = self.state.attributes.get(fan.ATTR_SPEED_LIST, []) + domain = self.state.domain speeds = [] - for mode in modes: - if mode not in self.speed_synonyms: - continue - speed = { - "speed_name": mode, - "speed_values": [ - {"speed_synonym": self.speed_synonyms.get(mode), "lang": "en"} - ], - } - speeds.append(speed) + reversible = False + + if domain == fan.DOMAIN: + modes = self.state.attributes.get(fan.ATTR_SPEED_LIST, []) + for mode in modes: + if mode not in self.speed_synonyms: + continue + speed = { + "speed_name": mode, + "speed_values": [ + {"speed_synonym": self.speed_synonyms.get(mode), "lang": "en"} + ], + } + speeds.append(speed) + reversible = bool( + self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + & fan.SUPPORT_DIRECTION + ) + elif domain == climate.DOMAIN: + modes = self.state.attributes.get(climate.ATTR_FAN_MODES, []) + for mode in modes: + speed = { + "speed_name": mode, + "speed_values": [{"speed_synonym": [mode], "lang": "en"}], + } + speeds.append(speed) return { "availableFanSpeeds": {"speeds": speeds, "ordered": True}, - "reversible": bool( - self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - & fan.SUPPORT_DIRECTION - ), + "reversible": reversible, } def query_attributes(self): """Return speed point and modes query attributes.""" attrs = self.state.attributes + domain = self.state.domain response = {} - - speed = attrs.get(fan.ATTR_SPEED) - if speed is not None: - response["on"] = speed != fan.SPEED_OFF - response["currentFanSpeedSetting"] = speed - + if domain == climate.DOMAIN: + speed = attrs.get(climate.ATTR_FAN_MODE) + if speed is not None: + response["currentFanSpeedSetting"] = speed + if domain == fan.DOMAIN: + speed = attrs.get(fan.ATTR_SPEED) + if speed is not None: + response["on"] = speed != fan.SPEED_OFF + response["currentFanSpeedSetting"] = speed return response async def execute(self, command, data, params, challenge): """Execute an SetFanSpeed command.""" - await self.hass.services.async_call( - fan.DOMAIN, - fan.SERVICE_SET_SPEED, - {ATTR_ENTITY_ID: self.state.entity_id, fan.ATTR_SPEED: params["fanSpeed"]}, - blocking=True, - context=data.context, - ) + domain = self.state.domain + if domain == climate.DOMAIN: + await self.hass.services.async_call( + climate.DOMAIN, + climate.SERVICE_SET_FAN_MODE, + { + ATTR_ENTITY_ID: self.state.entity_id, + climate.ATTR_FAN_MODE: params["fanSpeed"], + }, + blocking=True, + context=data.context, + ) + if domain == fan.DOMAIN: + await self.hass.services.async_call( + fan.DOMAIN, + fan.SERVICE_SET_SPEED, + { + ATTR_ENTITY_ID: self.state.entity_id, + fan.ATTR_SPEED: params["fanSpeed"], + }, + blocking=True, + context=data.context, + ) @register_trait diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index a801a6c960f..f665fa53ed2 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -229,7 +229,10 @@ DEMO_DEVICES = [ { "id": "climate.hvac", "name": {"name": "Hvac"}, - "traits": ["action.devices.traits.TemperatureSetting"], + "traits": [ + "action.devices.traits.TemperatureSetting", + "action.devices.traits.FanSpeed", + ], "type": "action.devices.types.THERMOSTAT", "willReportState": False, "attributes": { @@ -247,7 +250,10 @@ DEMO_DEVICES = [ { "id": "climate.ecobee", "name": {"name": "Ecobee"}, - "traits": ["action.devices.traits.TemperatureSetting"], + "traits": [ + "action.devices.traits.TemperatureSetting", + "action.devices.traits.FanSpeed", + ], "type": "action.devices.types.THERMOSTAT", "willReportState": False, }, diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py index e4beaa14bba..cd268a5c2d9 100644 --- a/tests/components/google_assistant/test_google_assistant.py +++ b/tests/components/google_assistant/test_google_assistant.py @@ -231,6 +231,7 @@ async def test_query_climate_request(hass_fixture, assistant_client, auth_header "thermostatTemperatureAmbient": 23, "thermostatMode": "heatcool", "thermostatTemperatureSetpointLow": 21, + "currentFanSpeedSetting": "Auto Low", } assert devices["climate.hvac"] == { "online": True, @@ -238,6 +239,7 @@ async def test_query_climate_request(hass_fixture, assistant_client, auth_header "thermostatTemperatureAmbient": 22, "thermostatMode": "cool", "thermostatHumidityAmbient": 54, + "currentFanSpeedSetting": "On High", } @@ -288,6 +290,7 @@ async def test_query_climate_request_f(hass_fixture, assistant_client, auth_head "thermostatTemperatureAmbient": -5, "thermostatMode": "heatcool", "thermostatTemperatureSetpointLow": -6.1, + "currentFanSpeedSetting": "Auto Low", } assert devices["climate.hvac"] == { "online": True, @@ -295,6 +298,7 @@ async def test_query_climate_request_f(hass_fixture, assistant_client, auth_head "thermostatTemperatureAmbient": -5.6, "thermostatMode": "cool", "thermostatHumidityAmbient": 54, + "currentFanSpeedSetting": "On High", } hass_fixture.config.units.temperature_unit = const.TEMP_CELSIUS diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 9d99736c87f..faad53fbc66 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1313,6 +1313,65 @@ async def test_fan_speed(hass): assert calls[0].data == {"entity_id": "fan.living_room_fan", "speed": "medium"} +async def test_climate_fan_speed(hass): + """Test FanSpeed trait speed control support for climate domain.""" + assert helpers.get_google_type(climate.DOMAIN, None) is not None + assert trait.FanSpeedTrait.supported(climate.DOMAIN, climate.SUPPORT_FAN_MODE, None) + + trt = trait.FanSpeedTrait( + hass, + State( + "climate.living_room_ac", + "on", + attributes={ + "fan_modes": ["auto", "low", "medium", "high"], + "fan_mode": "low", + }, + ), + BASIC_CONFIG, + ) + + assert trt.sync_attributes() == { + "availableFanSpeeds": { + "ordered": True, + "speeds": [ + { + "speed_name": "auto", + "speed_values": [{"speed_synonym": ["auto"], "lang": "en"}], + }, + { + "speed_name": "low", + "speed_values": [{"speed_synonym": ["low"], "lang": "en"}], + }, + { + "speed_name": "medium", + "speed_values": [{"speed_synonym": ["medium"], "lang": "en"}], + }, + { + "speed_name": "high", + "speed_values": [{"speed_synonym": ["high"], "lang": "en"}], + }, + ], + }, + "reversible": False, + } + + assert trt.query_attributes() == { + "currentFanSpeedSetting": "low", + } + + assert trt.can_execute(trait.COMMAND_FANSPEED, params={"fanSpeed": "medium"}) + + calls = async_mock_service(hass, climate.DOMAIN, climate.SERVICE_SET_FAN_MODE) + await trt.execute(trait.COMMAND_FANSPEED, BASIC_DATA, {"fanSpeed": "medium"}, {}) + + assert len(calls) == 1 + assert calls[0].data == { + "entity_id": "climate.living_room_ac", + "fan_mode": "medium", + } + + async def test_inputselector(hass): """Test input selector trait.""" assert helpers.get_google_type(media_player.DOMAIN, None) is not None