Fix backwards compatiblity with fan update to new model (#45951)
* Fix backwards compatiblity with fans update to new model There were some non-speeds and devices that report a none speed. These problems were discovered when updating zha tasmota and vesync to the new model in #45407 * Update coverage * fix check
This commit is contained in:
parent
369616a6c3
commit
a74ae3585a
3 changed files with 114 additions and 9 deletions
|
@ -15,6 +15,8 @@ from homeassistant.components.fan import (
|
|||
|
||||
PRESET_MODE_AUTO = "auto"
|
||||
PRESET_MODE_SMART = "smart"
|
||||
PRESET_MODE_SLEEP = "sleep"
|
||||
PRESET_MODE_ON = "on"
|
||||
|
||||
FULL_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION
|
||||
LIMITED_SUPPORT = SUPPORT_SET_SPEED
|
||||
|
@ -38,6 +40,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
SPEED_HIGH,
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_SMART,
|
||||
PRESET_MODE_SLEEP,
|
||||
PRESET_MODE_ON,
|
||||
],
|
||||
),
|
||||
DemoFan(
|
||||
|
@ -54,7 +58,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
"fan3",
|
||||
"Percentage Full Fan",
|
||||
FULL_SUPPORT,
|
||||
[PRESET_MODE_AUTO, PRESET_MODE_SMART],
|
||||
[
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_SMART,
|
||||
PRESET_MODE_SLEEP,
|
||||
PRESET_MODE_ON,
|
||||
],
|
||||
None,
|
||||
),
|
||||
DemoPercentageFan(
|
||||
|
@ -62,7 +71,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
"fan4",
|
||||
"Percentage Limited Fan",
|
||||
LIMITED_SUPPORT,
|
||||
[PRESET_MODE_AUTO, PRESET_MODE_SMART],
|
||||
[
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_SMART,
|
||||
PRESET_MODE_SLEEP,
|
||||
PRESET_MODE_ON,
|
||||
],
|
||||
None,
|
||||
),
|
||||
AsyncDemoPercentageFan(
|
||||
|
@ -70,7 +84,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
"fan5",
|
||||
"Preset Only Limited Fan",
|
||||
SUPPORT_PRESET_MODE,
|
||||
[PRESET_MODE_AUTO, PRESET_MODE_SMART],
|
||||
[
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_SMART,
|
||||
PRESET_MODE_SLEEP,
|
||||
PRESET_MODE_ON,
|
||||
],
|
||||
[],
|
||||
),
|
||||
]
|
||||
|
@ -99,7 +118,7 @@ class BaseDemoFan(FanEntity):
|
|||
self._unique_id = unique_id
|
||||
self._supported_features = supported_features
|
||||
self._speed = SPEED_OFF
|
||||
self._percentage = 0
|
||||
self._percentage = None
|
||||
self._speed_list = speed_list
|
||||
self._preset_modes = preset_modes
|
||||
self._preset_mode = None
|
||||
|
|
|
@ -64,18 +64,22 @@ ATTR_PRESET_MODES = "preset_modes"
|
|||
# into core integrations at some point so we are temporarily
|
||||
# accommodating them in the transition to percentages.
|
||||
_NOT_SPEED_OFF = "off"
|
||||
_NOT_SPEED_ON = "on"
|
||||
_NOT_SPEED_AUTO = "auto"
|
||||
_NOT_SPEED_SMART = "smart"
|
||||
_NOT_SPEED_INTERVAL = "interval"
|
||||
_NOT_SPEED_IDLE = "idle"
|
||||
_NOT_SPEED_FAVORITE = "favorite"
|
||||
_NOT_SPEED_SLEEP = "sleep"
|
||||
|
||||
_NOT_SPEEDS_FILTER = {
|
||||
_NOT_SPEED_OFF,
|
||||
_NOT_SPEED_ON,
|
||||
_NOT_SPEED_AUTO,
|
||||
_NOT_SPEED_SMART,
|
||||
_NOT_SPEED_INTERVAL,
|
||||
_NOT_SPEED_IDLE,
|
||||
_NOT_SPEED_SLEEP,
|
||||
_NOT_SPEED_FAVORITE,
|
||||
}
|
||||
|
||||
|
@ -83,6 +87,8 @@ _FAN_NATIVE = "_fan_native"
|
|||
|
||||
OFF_SPEED_VALUES = [SPEED_OFF, None]
|
||||
|
||||
LEGACY_SPEED_LIST = [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||
|
||||
|
||||
class NoValidSpeedsError(ValueError):
|
||||
"""Exception class when there are no valid speeds."""
|
||||
|
@ -386,7 +392,10 @@ class FanEntity(ToggleEntity):
|
|||
if preset_mode:
|
||||
return preset_mode
|
||||
if self._implemented_percentage:
|
||||
return self.percentage_to_speed(self.percentage)
|
||||
percentage = self.percentage
|
||||
if percentage is None:
|
||||
return None
|
||||
return self.percentage_to_speed(percentage)
|
||||
return None
|
||||
|
||||
@property
|
||||
|
@ -404,7 +413,7 @@ class FanEntity(ToggleEntity):
|
|||
"""Get the list of available speeds."""
|
||||
speeds = []
|
||||
if self._implemented_percentage:
|
||||
speeds += [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||
speeds += [SPEED_OFF, *LEGACY_SPEED_LIST]
|
||||
if self._implemented_preset_mode:
|
||||
speeds += self.preset_modes
|
||||
return speeds
|
||||
|
@ -434,6 +443,17 @@ class FanEntity(ToggleEntity):
|
|||
|
||||
return attrs
|
||||
|
||||
@property
|
||||
def _speed_list_without_preset_modes(self) -> list:
|
||||
"""Return the speed list without preset modes.
|
||||
|
||||
This property provides forward and backwards
|
||||
compatibility for conversion to percentage speeds.
|
||||
"""
|
||||
if not self._implemented_speed:
|
||||
return LEGACY_SPEED_LIST
|
||||
return speed_list_without_preset_modes(self.speed_list)
|
||||
|
||||
def speed_to_percentage(self, speed: str) -> int:
|
||||
"""
|
||||
Map a speed to a percentage.
|
||||
|
@ -453,7 +473,7 @@ class FanEntity(ToggleEntity):
|
|||
if speed in OFF_SPEED_VALUES:
|
||||
return 0
|
||||
|
||||
speed_list = speed_list_without_preset_modes(self.speed_list)
|
||||
speed_list = self._speed_list_without_preset_modes
|
||||
|
||||
if speed_list and speed not in speed_list:
|
||||
raise NotValidSpeedError(f"The speed {speed} is not a valid speed.")
|
||||
|
@ -487,7 +507,7 @@ class FanEntity(ToggleEntity):
|
|||
if percentage == 0:
|
||||
return SPEED_OFF
|
||||
|
||||
speed_list = speed_list_without_preset_modes(self.speed_list)
|
||||
speed_list = self._speed_list_without_preset_modes
|
||||
|
||||
try:
|
||||
return percentage_to_ordered_list_item(speed_list, percentage)
|
||||
|
|
|
@ -2,7 +2,12 @@
|
|||
import pytest
|
||||
|
||||
from homeassistant.components import fan
|
||||
from homeassistant.components.demo.fan import PRESET_MODE_AUTO, PRESET_MODE_SMART
|
||||
from homeassistant.components.demo.fan import (
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_ON,
|
||||
PRESET_MODE_SLEEP,
|
||||
PRESET_MODE_SMART,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ENTITY_MATCH_ALL,
|
||||
|
@ -60,6 +65,28 @@ async def test_turn_on_with_speed_and_percentage(hass, fan_entity_id):
|
|||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 100
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_SPEED: fan.SPEED_MEDIUM},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_MEDIUM
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 66
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_SPEED: fan.SPEED_LOW},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_LOW
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 33
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
|
@ -71,6 +98,39 @@ async def test_turn_on_with_speed_and_percentage(hass, fan_entity_id):
|
|||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_HIGH
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 100
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE: 66},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_MEDIUM
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 66
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE: 33},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_LOW
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 33
|
||||
|
||||
await hass.services.async_call(
|
||||
fan.DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: fan_entity_id, fan.ATTR_PERCENTAGE: 0},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(fan_entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes[fan.ATTR_SPEED] == fan.SPEED_OFF
|
||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fan_entity_id", FANS_WITH_PRESET_MODE_ONLY)
|
||||
async def test_turn_on_with_preset_mode_only(hass, fan_entity_id):
|
||||
|
@ -89,6 +149,8 @@ async def test_turn_on_with_preset_mode_only(hass, fan_entity_id):
|
|||
assert state.attributes[fan.ATTR_PRESET_MODES] == [
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_SMART,
|
||||
PRESET_MODE_SLEEP,
|
||||
PRESET_MODE_ON,
|
||||
]
|
||||
|
||||
await hass.services.async_call(
|
||||
|
@ -145,10 +207,14 @@ async def test_turn_on_with_preset_mode_and_speed(hass, fan_entity_id):
|
|||
fan.SPEED_HIGH,
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_SMART,
|
||||
PRESET_MODE_SLEEP,
|
||||
PRESET_MODE_ON,
|
||||
]
|
||||
assert state.attributes[fan.ATTR_PRESET_MODES] == [
|
||||
PRESET_MODE_AUTO,
|
||||
PRESET_MODE_SMART,
|
||||
PRESET_MODE_SLEEP,
|
||||
PRESET_MODE_ON,
|
||||
]
|
||||
|
||||
await hass.services.async_call(
|
||||
|
|
Loading…
Add table
Reference in a new issue