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
|
@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
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue