mirror of
https://github.com/home-assistant/core.git
synced 2025-04-27 10:47:51 +00:00
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
@ -15,6 +15,8 @@ from homeassistant.components.fan import (
|
|||||||
|
|
||||||
PRESET_MODE_AUTO = "auto"
|
PRESET_MODE_AUTO = "auto"
|
||||||
PRESET_MODE_SMART = "smart"
|
PRESET_MODE_SMART = "smart"
|
||||||
|
PRESET_MODE_SLEEP = "sleep"
|
||||||
|
PRESET_MODE_ON = "on"
|
||||||
|
|
||||||
FULL_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION
|
FULL_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION
|
||||||
LIMITED_SUPPORT = SUPPORT_SET_SPEED
|
LIMITED_SUPPORT = SUPPORT_SET_SPEED
|
||||||
@ -38,6 +40,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
SPEED_HIGH,
|
SPEED_HIGH,
|
||||||
PRESET_MODE_AUTO,
|
PRESET_MODE_AUTO,
|
||||||
PRESET_MODE_SMART,
|
PRESET_MODE_SMART,
|
||||||
|
PRESET_MODE_SLEEP,
|
||||||
|
PRESET_MODE_ON,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
DemoFan(
|
DemoFan(
|
||||||
@ -54,7 +58,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
"fan3",
|
"fan3",
|
||||||
"Percentage Full Fan",
|
"Percentage Full Fan",
|
||||||
FULL_SUPPORT,
|
FULL_SUPPORT,
|
||||||
[PRESET_MODE_AUTO, PRESET_MODE_SMART],
|
[
|
||||||
|
PRESET_MODE_AUTO,
|
||||||
|
PRESET_MODE_SMART,
|
||||||
|
PRESET_MODE_SLEEP,
|
||||||
|
PRESET_MODE_ON,
|
||||||
|
],
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
DemoPercentageFan(
|
DemoPercentageFan(
|
||||||
@ -62,7 +71,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
"fan4",
|
"fan4",
|
||||||
"Percentage Limited Fan",
|
"Percentage Limited Fan",
|
||||||
LIMITED_SUPPORT,
|
LIMITED_SUPPORT,
|
||||||
[PRESET_MODE_AUTO, PRESET_MODE_SMART],
|
[
|
||||||
|
PRESET_MODE_AUTO,
|
||||||
|
PRESET_MODE_SMART,
|
||||||
|
PRESET_MODE_SLEEP,
|
||||||
|
PRESET_MODE_ON,
|
||||||
|
],
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
AsyncDemoPercentageFan(
|
AsyncDemoPercentageFan(
|
||||||
@ -70,7 +84,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
"fan5",
|
"fan5",
|
||||||
"Preset Only Limited Fan",
|
"Preset Only Limited Fan",
|
||||||
SUPPORT_PRESET_MODE,
|
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._unique_id = unique_id
|
||||||
self._supported_features = supported_features
|
self._supported_features = supported_features
|
||||||
self._speed = SPEED_OFF
|
self._speed = SPEED_OFF
|
||||||
self._percentage = 0
|
self._percentage = None
|
||||||
self._speed_list = speed_list
|
self._speed_list = speed_list
|
||||||
self._preset_modes = preset_modes
|
self._preset_modes = preset_modes
|
||||||
self._preset_mode = None
|
self._preset_mode = None
|
||||||
|
@ -64,18 +64,22 @@ ATTR_PRESET_MODES = "preset_modes"
|
|||||||
# into core integrations at some point so we are temporarily
|
# into core integrations at some point so we are temporarily
|
||||||
# accommodating them in the transition to percentages.
|
# accommodating them in the transition to percentages.
|
||||||
_NOT_SPEED_OFF = "off"
|
_NOT_SPEED_OFF = "off"
|
||||||
|
_NOT_SPEED_ON = "on"
|
||||||
_NOT_SPEED_AUTO = "auto"
|
_NOT_SPEED_AUTO = "auto"
|
||||||
_NOT_SPEED_SMART = "smart"
|
_NOT_SPEED_SMART = "smart"
|
||||||
_NOT_SPEED_INTERVAL = "interval"
|
_NOT_SPEED_INTERVAL = "interval"
|
||||||
_NOT_SPEED_IDLE = "idle"
|
_NOT_SPEED_IDLE = "idle"
|
||||||
_NOT_SPEED_FAVORITE = "favorite"
|
_NOT_SPEED_FAVORITE = "favorite"
|
||||||
|
_NOT_SPEED_SLEEP = "sleep"
|
||||||
|
|
||||||
_NOT_SPEEDS_FILTER = {
|
_NOT_SPEEDS_FILTER = {
|
||||||
_NOT_SPEED_OFF,
|
_NOT_SPEED_OFF,
|
||||||
|
_NOT_SPEED_ON,
|
||||||
_NOT_SPEED_AUTO,
|
_NOT_SPEED_AUTO,
|
||||||
_NOT_SPEED_SMART,
|
_NOT_SPEED_SMART,
|
||||||
_NOT_SPEED_INTERVAL,
|
_NOT_SPEED_INTERVAL,
|
||||||
_NOT_SPEED_IDLE,
|
_NOT_SPEED_IDLE,
|
||||||
|
_NOT_SPEED_SLEEP,
|
||||||
_NOT_SPEED_FAVORITE,
|
_NOT_SPEED_FAVORITE,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +87,8 @@ _FAN_NATIVE = "_fan_native"
|
|||||||
|
|
||||||
OFF_SPEED_VALUES = [SPEED_OFF, None]
|
OFF_SPEED_VALUES = [SPEED_OFF, None]
|
||||||
|
|
||||||
|
LEGACY_SPEED_LIST = [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||||
|
|
||||||
|
|
||||||
class NoValidSpeedsError(ValueError):
|
class NoValidSpeedsError(ValueError):
|
||||||
"""Exception class when there are no valid speeds."""
|
"""Exception class when there are no valid speeds."""
|
||||||
@ -386,7 +392,10 @@ class FanEntity(ToggleEntity):
|
|||||||
if preset_mode:
|
if preset_mode:
|
||||||
return preset_mode
|
return preset_mode
|
||||||
if self._implemented_percentage:
|
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
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -404,7 +413,7 @@ class FanEntity(ToggleEntity):
|
|||||||
"""Get the list of available speeds."""
|
"""Get the list of available speeds."""
|
||||||
speeds = []
|
speeds = []
|
||||||
if self._implemented_percentage:
|
if self._implemented_percentage:
|
||||||
speeds += [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
speeds += [SPEED_OFF, *LEGACY_SPEED_LIST]
|
||||||
if self._implemented_preset_mode:
|
if self._implemented_preset_mode:
|
||||||
speeds += self.preset_modes
|
speeds += self.preset_modes
|
||||||
return speeds
|
return speeds
|
||||||
@ -434,6 +443,17 @@ class FanEntity(ToggleEntity):
|
|||||||
|
|
||||||
return attrs
|
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:
|
def speed_to_percentage(self, speed: str) -> int:
|
||||||
"""
|
"""
|
||||||
Map a speed to a percentage.
|
Map a speed to a percentage.
|
||||||
@ -453,7 +473,7 @@ class FanEntity(ToggleEntity):
|
|||||||
if speed in OFF_SPEED_VALUES:
|
if speed in OFF_SPEED_VALUES:
|
||||||
return 0
|
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:
|
if speed_list and speed not in speed_list:
|
||||||
raise NotValidSpeedError(f"The speed {speed} is not a valid speed.")
|
raise NotValidSpeedError(f"The speed {speed} is not a valid speed.")
|
||||||
@ -487,7 +507,7 @@ class FanEntity(ToggleEntity):
|
|||||||
if percentage == 0:
|
if percentage == 0:
|
||||||
return SPEED_OFF
|
return SPEED_OFF
|
||||||
|
|
||||||
speed_list = speed_list_without_preset_modes(self.speed_list)
|
speed_list = self._speed_list_without_preset_modes
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return percentage_to_ordered_list_item(speed_list, percentage)
|
return percentage_to_ordered_list_item(speed_list, percentage)
|
||||||
|
@ -2,7 +2,12 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import fan
|
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 (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
ENTITY_MATCH_ALL,
|
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_SPEED] == fan.SPEED_HIGH
|
||||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 100
|
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(
|
await hass.services.async_call(
|
||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
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_SPEED] == fan.SPEED_HIGH
|
||||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 100
|
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)
|
@pytest.mark.parametrize("fan_entity_id", FANS_WITH_PRESET_MODE_ONLY)
|
||||||
async def test_turn_on_with_preset_mode_only(hass, fan_entity_id):
|
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] == [
|
assert state.attributes[fan.ATTR_PRESET_MODES] == [
|
||||||
PRESET_MODE_AUTO,
|
PRESET_MODE_AUTO,
|
||||||
PRESET_MODE_SMART,
|
PRESET_MODE_SMART,
|
||||||
|
PRESET_MODE_SLEEP,
|
||||||
|
PRESET_MODE_ON,
|
||||||
]
|
]
|
||||||
|
|
||||||
await hass.services.async_call(
|
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,
|
fan.SPEED_HIGH,
|
||||||
PRESET_MODE_AUTO,
|
PRESET_MODE_AUTO,
|
||||||
PRESET_MODE_SMART,
|
PRESET_MODE_SMART,
|
||||||
|
PRESET_MODE_SLEEP,
|
||||||
|
PRESET_MODE_ON,
|
||||||
]
|
]
|
||||||
assert state.attributes[fan.ATTR_PRESET_MODES] == [
|
assert state.attributes[fan.ATTR_PRESET_MODES] == [
|
||||||
PRESET_MODE_AUTO,
|
PRESET_MODE_AUTO,
|
||||||
PRESET_MODE_SMART,
|
PRESET_MODE_SMART,
|
||||||
|
PRESET_MODE_SLEEP,
|
||||||
|
PRESET_MODE_ON,
|
||||||
]
|
]
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user