mirror of
https://github.com/home-assistant/core.git
synced 2025-07-30 16:57:19 +00:00
Add optimistic option to fan yaml (#149390)
This commit is contained in:
parent
d3f18c1678
commit
49bd15718c
@ -43,6 +43,7 @@ from .entity import AbstractTemplateEntity
|
|||||||
from .helpers import async_setup_template_platform
|
from .helpers import async_setup_template_platform
|
||||||
from .template_entity import (
|
from .template_entity import (
|
||||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY,
|
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY,
|
||||||
|
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
|
||||||
TemplateEntity,
|
TemplateEntity,
|
||||||
make_template_entity_common_modern_schema,
|
make_template_entity_common_modern_schema,
|
||||||
)
|
)
|
||||||
@ -81,8 +82,7 @@ LEGACY_FIELDS = {
|
|||||||
|
|
||||||
DEFAULT_NAME = "Template Fan"
|
DEFAULT_NAME = "Template Fan"
|
||||||
|
|
||||||
FAN_YAML_SCHEMA = vol.All(
|
FAN_COMMON_SCHEMA = vol.Schema(
|
||||||
vol.Schema(
|
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_DIRECTION): cv.template,
|
vol.Optional(CONF_DIRECTION): cv.template,
|
||||||
vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
@ -98,7 +98,10 @@ FAN_YAML_SCHEMA = vol.All(
|
|||||||
vol.Optional(CONF_SPEED_COUNT): vol.Coerce(int),
|
vol.Optional(CONF_SPEED_COUNT): vol.Coerce(int),
|
||||||
vol.Optional(CONF_STATE): cv.template,
|
vol.Optional(CONF_STATE): cv.template,
|
||||||
}
|
}
|
||||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
)
|
||||||
|
|
||||||
|
FAN_YAML_SCHEMA = FAN_COMMON_SCHEMA.extend(TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA).extend(
|
||||||
|
make_template_entity_common_modern_schema(DEFAULT_NAME).schema
|
||||||
)
|
)
|
||||||
|
|
||||||
FAN_LEGACY_YAML_SCHEMA = vol.All(
|
FAN_LEGACY_YAML_SCHEMA = vol.All(
|
||||||
@ -154,13 +157,12 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
|
|||||||
"""Representation of a template fan features."""
|
"""Representation of a template fan features."""
|
||||||
|
|
||||||
_entity_id_format = ENTITY_ID_FORMAT
|
_entity_id_format = ENTITY_ID_FORMAT
|
||||||
|
_optimistic_entity = True
|
||||||
|
|
||||||
# The super init is not called because TemplateEntity and TriggerEntity will call AbstractTemplateEntity.__init__.
|
# The super init is not called because TemplateEntity and TriggerEntity will call AbstractTemplateEntity.__init__.
|
||||||
# This ensures that the __init__ on AbstractTemplateEntity is not called twice.
|
# This ensures that the __init__ on AbstractTemplateEntity is not called twice.
|
||||||
def __init__(self, config: dict[str, Any]) -> None: # pylint: disable=super-init-not-called
|
def __init__(self, config: dict[str, Any]) -> None: # pylint: disable=super-init-not-called
|
||||||
"""Initialize the features."""
|
"""Initialize the features."""
|
||||||
|
|
||||||
self._template = config.get(CONF_STATE)
|
|
||||||
self._percentage_template = config.get(CONF_PERCENTAGE)
|
self._percentage_template = config.get(CONF_PERCENTAGE)
|
||||||
self._preset_mode_template = config.get(CONF_PRESET_MODE)
|
self._preset_mode_template = config.get(CONF_PRESET_MODE)
|
||||||
self._oscillating_template = config.get(CONF_OSCILLATING)
|
self._oscillating_template = config.get(CONF_OSCILLATING)
|
||||||
@ -177,7 +179,6 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
|
|||||||
|
|
||||||
# List of valid preset modes
|
# List of valid preset modes
|
||||||
self._preset_modes: list[str] | None = config.get(CONF_PRESET_MODES)
|
self._preset_modes: list[str] | None = config.get(CONF_PRESET_MODES)
|
||||||
self._attr_assumed_state = self._template is None
|
|
||||||
|
|
||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
|
FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_ON
|
||||||
@ -339,7 +340,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
|
|||||||
if percentage is not None:
|
if percentage is not None:
|
||||||
await self.async_set_percentage(percentage)
|
await self.async_set_percentage(percentage)
|
||||||
|
|
||||||
if self._template is None:
|
if self._attr_assumed_state:
|
||||||
self._state = True
|
self._state = True
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@ -349,7 +350,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
|
|||||||
self._action_scripts[CONF_OFF_ACTION], context=self._context
|
self._action_scripts[CONF_OFF_ACTION], context=self._context
|
||||||
)
|
)
|
||||||
|
|
||||||
if self._template is None:
|
if self._attr_assumed_state:
|
||||||
self._state = False
|
self._state = False
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@ -364,10 +365,10 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
|
|||||||
context=self._context,
|
context=self._context,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self._template is None:
|
if self._attr_assumed_state:
|
||||||
self._state = percentage != 0
|
self._state = percentage != 0
|
||||||
|
|
||||||
if self._template is None or self._percentage_template is None:
|
if self._attr_assumed_state or self._percentage_template is None:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
@ -381,10 +382,10 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
|
|||||||
context=self._context,
|
context=self._context,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self._template is None:
|
if self._attr_assumed_state:
|
||||||
self._state = True
|
self._state = True
|
||||||
|
|
||||||
if self._template is None or self._preset_mode_template is None:
|
if self._attr_assumed_state or self._preset_mode_template is None:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_oscillate(self, oscillating: bool) -> None:
|
async def async_oscillate(self, oscillating: bool) -> None:
|
||||||
|
@ -1833,3 +1833,51 @@ async def test_nested_unique_id(
|
|||||||
entry = entity_registry.async_get("fan.test_b")
|
entry = entity_registry.async_get("fan.test_b")
|
||||||
assert entry
|
assert entry
|
||||||
assert entry.unique_id == "x-b"
|
assert entry.unique_id == "x-b"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("count", "fan_config"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
"name": TEST_OBJECT_ID,
|
||||||
|
"state": "{{ is_state('sensor.test_sensor', 'on') }}",
|
||||||
|
"turn_on": [],
|
||||||
|
"turn_off": [],
|
||||||
|
"optimistic": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"style",
|
||||||
|
[ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER],
|
||||||
|
)
|
||||||
|
@pytest.mark.usefixtures("setup_fan")
|
||||||
|
async def test_optimistic_option(hass: HomeAssistant) -> None:
|
||||||
|
"""Test optimistic yaml option."""
|
||||||
|
hass.states.async_set(_STATE_TEST_SENSOR, STATE_OFF)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
fan.DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": TEST_ENTITY_ID},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
hass.states.async_set(_STATE_TEST_SENSOR, STATE_ON)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
hass.states.async_set(_STATE_TEST_SENSOR, STATE_OFF)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(TEST_ENTITY_ID)
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
Loading…
x
Reference in New Issue
Block a user