Make Google Assistant fan speed percent and step speeds mutually exclusive (#162770)

This commit is contained in:
Kyle Johnson
2026-02-23 16:26:09 -06:00
committed by GitHub
parent af9ea5ea7a
commit 3693bc5878
2 changed files with 30 additions and 28 deletions

View File

@@ -1752,15 +1752,15 @@ class FanSpeedTrait(_Trait):
"""Initialize a trait for a state."""
super().__init__(hass, state, config)
if state.domain == fan.DOMAIN:
speed_count = min(
FAN_SPEED_MAX_SPEED_COUNT,
round(
100 / (self.state.attributes.get(fan.ATTR_PERCENTAGE_STEP) or 1.0)
),
speed_count = round(
100 / (self.state.attributes.get(fan.ATTR_PERCENTAGE_STEP) or 1.0)
)
self._ordered_speed = [
f"{speed}/{speed_count}" for speed in range(1, speed_count + 1)
]
if speed_count <= FAN_SPEED_MAX_SPEED_COUNT:
self._ordered_speed = [
f"{speed}/{speed_count}" for speed in range(1, speed_count + 1)
]
else:
self._ordered_speed = []
@staticmethod
def supported(domain, features, device_class, _):
@@ -1786,7 +1786,11 @@ class FanSpeedTrait(_Trait):
result.update(
{
"reversible": reversible,
"supportsFanSpeedPercent": True,
# supportsFanSpeedPercent is mutually exclusive with
# availableFanSpeeds, where supportsFanSpeedPercent takes
# precedence. Report it only when step speeds are not
# supported so Google renders a percent slider (1-100%).
"supportsFanSpeedPercent": not self._ordered_speed,
}
)
@@ -1832,10 +1836,12 @@ class FanSpeedTrait(_Trait):
if domain == fan.DOMAIN:
percent = attrs.get(fan.ATTR_PERCENTAGE) or 0
response["currentFanSpeedPercent"] = percent
response["currentFanSpeedSetting"] = percentage_to_ordered_list_item(
self._ordered_speed, percent
)
if self._ordered_speed:
response["currentFanSpeedSetting"] = percentage_to_ordered_list_item(
self._ordered_speed, percent
)
else:
response["currentFanSpeedPercent"] = percent
return response
@@ -1855,7 +1861,7 @@ class FanSpeedTrait(_Trait):
)
if domain == fan.DOMAIN:
if fan_speed := params.get("fanSpeed"):
if self._ordered_speed and (fan_speed := params.get("fanSpeed")):
fan_speed_percent = ordered_list_item_to_percentage(
self._ordered_speed, fan_speed
)

View File

@@ -2,7 +2,7 @@
from datetime import datetime, timedelta
from typing import Any
from unittest.mock import ANY, patch
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
@@ -2291,12 +2291,10 @@ async def test_fan_speed(hass: HomeAssistant) -> None:
assert trt.sync_attributes() == {
"reversible": False,
"supportsFanSpeedPercent": True,
"availableFanSpeeds": ANY,
}
assert trt.query_attributes() == {
"currentFanSpeedPercent": 33,
"currentFanSpeedSetting": ANY,
}
assert trt.can_execute(trait.COMMAND_SET_FAN_SPEED, params={"fanSpeedPercent": 10})
@@ -2311,7 +2309,7 @@ async def test_fan_speed(hass: HomeAssistant) -> None:
async def test_fan_speed_without_percentage_step(hass: HomeAssistant) -> None:
"""Test FanSpeed trait speed control percentage step for fan domain."""
"""Test FanSpeed trait falls back to percent-only when percentage_step is missing."""
assert helpers.get_google_type(fan.DOMAIN, None) is not None
assert trait.FanSpeedTrait.supported(
fan.DOMAIN, FanEntityFeature.SET_SPEED, None, None
@@ -2322,6 +2320,9 @@ async def test_fan_speed_without_percentage_step(hass: HomeAssistant) -> None:
State(
"fan.living_room_fan",
STATE_ON,
attributes={
"percentage": 50,
},
),
BASIC_CONFIG,
)
@@ -2329,12 +2330,10 @@ async def test_fan_speed_without_percentage_step(hass: HomeAssistant) -> None:
assert trt.sync_attributes() == {
"reversible": False,
"supportsFanSpeedPercent": True,
"availableFanSpeeds": ANY,
}
# If a fan state has (temporary) no percentage_step attribute return 1 available
assert trt.query_attributes() == {
"currentFanSpeedPercent": 0,
"currentFanSpeedSetting": "1/5",
"currentFanSpeedPercent": 50,
}
@@ -2343,7 +2342,7 @@ async def test_fan_speed_without_percentage_step(hass: HomeAssistant) -> None:
[
(
33,
1.0,
20.0,
"2/5",
[
["Low", "Min", "Slow", "1"],
@@ -2356,7 +2355,7 @@ async def test_fan_speed_without_percentage_step(hass: HomeAssistant) -> None:
),
(
40,
1.0,
20.0,
"2/5",
[
["Low", "Min", "Slow", "1"],
@@ -2421,7 +2420,7 @@ async def test_fan_speed_ordered(
assert trt.sync_attributes() == {
"reversible": False,
"supportsFanSpeedPercent": True,
"supportsFanSpeedPercent": False,
"availableFanSpeeds": {
"ordered": True,
"speeds": [
@@ -2435,7 +2434,6 @@ async def test_fan_speed_ordered(
}
assert trt.query_attributes() == {
"currentFanSpeedPercent": percentage,
"currentFanSpeedSetting": speed,
}
@@ -2484,12 +2482,10 @@ async def test_fan_reverse(
assert trt.sync_attributes() == {
"reversible": True,
"supportsFanSpeedPercent": True,
"availableFanSpeeds": ANY,
}
assert trt.query_attributes() == {
"currentFanSpeedPercent": 33,
"currentFanSpeedSetting": ANY,
}
assert trt.can_execute(trait.COMMAND_REVERSE, params={})