Add support to climate devices in Google Assistant Fan Trait (#38337)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>pull/38447/head
parent
2e340d2c2f
commit
416ee7f143
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue