Add support to climate devices in Google Assistant Fan Trait (#38337)

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
pull/38447/head
Marcio Granzotto Rodrigues 2020-07-31 23:05:00 -03:00 committed by GitHub
parent 2e340d2c2f
commit 416ee7f143
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 137 additions and 34 deletions

View File

@ -1153,55 +1153,89 @@ class FanSpeedTrait(_Trait):
@staticmethod @staticmethod
def supported(domain, features, device_class): def supported(domain, features, device_class):
"""Test if state is supported.""" """Test if state is supported."""
if domain != fan.DOMAIN: if domain == fan.DOMAIN:
return False return features & fan.SUPPORT_SET_SPEED
if domain == climate.DOMAIN:
return features & fan.SUPPORT_SET_SPEED return features & climate.SUPPORT_FAN_MODE
return False
def sync_attributes(self): def sync_attributes(self):
"""Return speed point and modes attributes for a sync request.""" """Return speed point and modes attributes for a sync request."""
modes = self.state.attributes.get(fan.ATTR_SPEED_LIST, []) domain = self.state.domain
speeds = [] speeds = []
for mode in modes: reversible = False
if mode not in self.speed_synonyms:
continue if domain == fan.DOMAIN:
speed = { modes = self.state.attributes.get(fan.ATTR_SPEED_LIST, [])
"speed_name": mode, for mode in modes:
"speed_values": [ if mode not in self.speed_synonyms:
{"speed_synonym": self.speed_synonyms.get(mode), "lang": "en"} continue
], speed = {
} "speed_name": mode,
speeds.append(speed) "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 { return {
"availableFanSpeeds": {"speeds": speeds, "ordered": True}, "availableFanSpeeds": {"speeds": speeds, "ordered": True},
"reversible": bool( "reversible": reversible,
self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
& fan.SUPPORT_DIRECTION
),
} }
def query_attributes(self): def query_attributes(self):
"""Return speed point and modes query attributes.""" """Return speed point and modes query attributes."""
attrs = self.state.attributes attrs = self.state.attributes
domain = self.state.domain
response = {} response = {}
if domain == climate.DOMAIN:
speed = attrs.get(fan.ATTR_SPEED) speed = attrs.get(climate.ATTR_FAN_MODE)
if speed is not None: if speed is not None:
response["on"] = speed != fan.SPEED_OFF response["currentFanSpeedSetting"] = speed
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 return response
async def execute(self, command, data, params, challenge): async def execute(self, command, data, params, challenge):
"""Execute an SetFanSpeed command.""" """Execute an SetFanSpeed command."""
await self.hass.services.async_call( domain = self.state.domain
fan.DOMAIN, if domain == climate.DOMAIN:
fan.SERVICE_SET_SPEED, await self.hass.services.async_call(
{ATTR_ENTITY_ID: self.state.entity_id, fan.ATTR_SPEED: params["fanSpeed"]}, climate.DOMAIN,
blocking=True, climate.SERVICE_SET_FAN_MODE,
context=data.context, {
) 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 @register_trait

View File

@ -229,7 +229,10 @@ DEMO_DEVICES = [
{ {
"id": "climate.hvac", "id": "climate.hvac",
"name": {"name": "Hvac"}, "name": {"name": "Hvac"},
"traits": ["action.devices.traits.TemperatureSetting"], "traits": [
"action.devices.traits.TemperatureSetting",
"action.devices.traits.FanSpeed",
],
"type": "action.devices.types.THERMOSTAT", "type": "action.devices.types.THERMOSTAT",
"willReportState": False, "willReportState": False,
"attributes": { "attributes": {
@ -247,7 +250,10 @@ DEMO_DEVICES = [
{ {
"id": "climate.ecobee", "id": "climate.ecobee",
"name": {"name": "Ecobee"}, "name": {"name": "Ecobee"},
"traits": ["action.devices.traits.TemperatureSetting"], "traits": [
"action.devices.traits.TemperatureSetting",
"action.devices.traits.FanSpeed",
],
"type": "action.devices.types.THERMOSTAT", "type": "action.devices.types.THERMOSTAT",
"willReportState": False, "willReportState": False,
}, },

View File

@ -231,6 +231,7 @@ async def test_query_climate_request(hass_fixture, assistant_client, auth_header
"thermostatTemperatureAmbient": 23, "thermostatTemperatureAmbient": 23,
"thermostatMode": "heatcool", "thermostatMode": "heatcool",
"thermostatTemperatureSetpointLow": 21, "thermostatTemperatureSetpointLow": 21,
"currentFanSpeedSetting": "Auto Low",
} }
assert devices["climate.hvac"] == { assert devices["climate.hvac"] == {
"online": True, "online": True,
@ -238,6 +239,7 @@ async def test_query_climate_request(hass_fixture, assistant_client, auth_header
"thermostatTemperatureAmbient": 22, "thermostatTemperatureAmbient": 22,
"thermostatMode": "cool", "thermostatMode": "cool",
"thermostatHumidityAmbient": 54, "thermostatHumidityAmbient": 54,
"currentFanSpeedSetting": "On High",
} }
@ -288,6 +290,7 @@ async def test_query_climate_request_f(hass_fixture, assistant_client, auth_head
"thermostatTemperatureAmbient": -5, "thermostatTemperatureAmbient": -5,
"thermostatMode": "heatcool", "thermostatMode": "heatcool",
"thermostatTemperatureSetpointLow": -6.1, "thermostatTemperatureSetpointLow": -6.1,
"currentFanSpeedSetting": "Auto Low",
} }
assert devices["climate.hvac"] == { assert devices["climate.hvac"] == {
"online": True, "online": True,
@ -295,6 +298,7 @@ async def test_query_climate_request_f(hass_fixture, assistant_client, auth_head
"thermostatTemperatureAmbient": -5.6, "thermostatTemperatureAmbient": -5.6,
"thermostatMode": "cool", "thermostatMode": "cool",
"thermostatHumidityAmbient": 54, "thermostatHumidityAmbient": 54,
"currentFanSpeedSetting": "On High",
} }
hass_fixture.config.units.temperature_unit = const.TEMP_CELSIUS hass_fixture.config.units.temperature_unit = const.TEMP_CELSIUS

View File

@ -1313,6 +1313,65 @@ async def test_fan_speed(hass):
assert calls[0].data == {"entity_id": "fan.living_room_fan", "speed": "medium"} 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): async def test_inputselector(hass):
"""Test input selector trait.""" """Test input selector trait."""
assert helpers.get_google_type(media_player.DOMAIN, None) is not None assert helpers.get_google_type(media_player.DOMAIN, None) is not None