mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 09:47:52 +00:00
Add a base class for template entities to inherit from (#139645)
* add-abstract-template-entity-base-class * review 1 changes
This commit is contained in:
parent
a778092941
commit
890d3f4af4
4
CODEOWNERS
generated
4
CODEOWNERS
generated
@ -1529,8 +1529,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/tedee/ @patrickhilker @zweckj
|
||||
/homeassistant/components/tellduslive/ @fredrike
|
||||
/tests/components/tellduslive/ @fredrike
|
||||
/homeassistant/components/template/ @PhracturedBlue @home-assistant/core
|
||||
/tests/components/template/ @PhracturedBlue @home-assistant/core
|
||||
/homeassistant/components/template/ @Petro31 @PhracturedBlue @home-assistant/core
|
||||
/tests/components/template/ @Petro31 @PhracturedBlue @home-assistant/core
|
||||
/homeassistant/components/tesla_fleet/ @Bre77
|
||||
/tests/components/tesla_fleet/ @Bre77
|
||||
/homeassistant/components/tesla_wall_connector/ @einarhauks
|
||||
|
@ -36,7 +36,6 @@ from homeassistant.helpers.entity_platform import (
|
||||
AddEntitiesCallback,
|
||||
)
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.script import Script
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import slugify
|
||||
|
||||
@ -199,70 +198,31 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity, Restore
|
||||
name = self._attr_name
|
||||
assert name is not None
|
||||
self._template = config.get(CONF_VALUE_TEMPLATE)
|
||||
self._disarm_script = None
|
||||
self._attr_code_arm_required: bool = config[CONF_CODE_ARM_REQUIRED]
|
||||
self._attr_code_format = config[CONF_CODE_FORMAT].value
|
||||
if (disarm_action := config.get(CONF_DISARM_ACTION)) is not None:
|
||||
self._disarm_script = Script(hass, disarm_action, name, DOMAIN)
|
||||
self._arm_away_script = None
|
||||
if (arm_away_action := config.get(CONF_ARM_AWAY_ACTION)) is not None:
|
||||
self._arm_away_script = Script(hass, arm_away_action, name, DOMAIN)
|
||||
self._arm_home_script = None
|
||||
if (arm_home_action := config.get(CONF_ARM_HOME_ACTION)) is not None:
|
||||
self._arm_home_script = Script(hass, arm_home_action, name, DOMAIN)
|
||||
self._arm_night_script = None
|
||||
if (arm_night_action := config.get(CONF_ARM_NIGHT_ACTION)) is not None:
|
||||
self._arm_night_script = Script(hass, arm_night_action, name, DOMAIN)
|
||||
self._arm_vacation_script = None
|
||||
if (arm_vacation_action := config.get(CONF_ARM_VACATION_ACTION)) is not None:
|
||||
self._arm_vacation_script = Script(hass, arm_vacation_action, name, DOMAIN)
|
||||
self._arm_custom_bypass_script = None
|
||||
if (
|
||||
arm_custom_bypass_action := config.get(CONF_ARM_CUSTOM_BYPASS_ACTION)
|
||||
) is not None:
|
||||
self._arm_custom_bypass_script = Script(
|
||||
hass, arm_custom_bypass_action, name, DOMAIN
|
||||
)
|
||||
self._trigger_script = None
|
||||
if (trigger_action := config.get(CONF_TRIGGER_ACTION)) is not None:
|
||||
self._trigger_script = Script(hass, trigger_action, name, DOMAIN)
|
||||
|
||||
self._attr_supported_features = AlarmControlPanelEntityFeature(0)
|
||||
for action_id, supported_feature in (
|
||||
(CONF_DISARM_ACTION, 0),
|
||||
(CONF_ARM_AWAY_ACTION, AlarmControlPanelEntityFeature.ARM_AWAY),
|
||||
(CONF_ARM_HOME_ACTION, AlarmControlPanelEntityFeature.ARM_HOME),
|
||||
(CONF_ARM_NIGHT_ACTION, AlarmControlPanelEntityFeature.ARM_NIGHT),
|
||||
(CONF_ARM_VACATION_ACTION, AlarmControlPanelEntityFeature.ARM_VACATION),
|
||||
(
|
||||
CONF_ARM_CUSTOM_BYPASS_ACTION,
|
||||
AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS,
|
||||
),
|
||||
(CONF_TRIGGER_ACTION, AlarmControlPanelEntityFeature.TRIGGER),
|
||||
):
|
||||
if action_config := config.get(action_id):
|
||||
self.add_script(action_id, action_config, name, DOMAIN)
|
||||
self._attr_supported_features |= supported_feature
|
||||
|
||||
self._state: AlarmControlPanelState | None = None
|
||||
self._attr_device_info = async_device_info_to_link_from_device_id(
|
||||
hass,
|
||||
config.get(CONF_DEVICE_ID),
|
||||
)
|
||||
supported_features = AlarmControlPanelEntityFeature(0)
|
||||
if self._arm_night_script is not None:
|
||||
supported_features = (
|
||||
supported_features | AlarmControlPanelEntityFeature.ARM_NIGHT
|
||||
)
|
||||
|
||||
if self._arm_home_script is not None:
|
||||
supported_features = (
|
||||
supported_features | AlarmControlPanelEntityFeature.ARM_HOME
|
||||
)
|
||||
|
||||
if self._arm_away_script is not None:
|
||||
supported_features = (
|
||||
supported_features | AlarmControlPanelEntityFeature.ARM_AWAY
|
||||
)
|
||||
|
||||
if self._arm_vacation_script is not None:
|
||||
supported_features = (
|
||||
supported_features | AlarmControlPanelEntityFeature.ARM_VACATION
|
||||
)
|
||||
|
||||
if self._arm_custom_bypass_script is not None:
|
||||
supported_features = (
|
||||
supported_features | AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
|
||||
)
|
||||
|
||||
if self._trigger_script is not None:
|
||||
supported_features = (
|
||||
supported_features | AlarmControlPanelEntityFeature.TRIGGER
|
||||
)
|
||||
self._attr_supported_features = supported_features
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore last state."""
|
||||
@ -330,7 +290,7 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity, Restore
|
||||
"""Arm the panel to Away."""
|
||||
await self._async_alarm_arm(
|
||||
AlarmControlPanelState.ARMED_AWAY,
|
||||
script=self._arm_away_script,
|
||||
script=self._action_scripts.get(CONF_ARM_AWAY_ACTION),
|
||||
code=code,
|
||||
)
|
||||
|
||||
@ -338,7 +298,7 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity, Restore
|
||||
"""Arm the panel to Home."""
|
||||
await self._async_alarm_arm(
|
||||
AlarmControlPanelState.ARMED_HOME,
|
||||
script=self._arm_home_script,
|
||||
script=self._action_scripts.get(CONF_ARM_HOME_ACTION),
|
||||
code=code,
|
||||
)
|
||||
|
||||
@ -346,7 +306,7 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity, Restore
|
||||
"""Arm the panel to Night."""
|
||||
await self._async_alarm_arm(
|
||||
AlarmControlPanelState.ARMED_NIGHT,
|
||||
script=self._arm_night_script,
|
||||
script=self._action_scripts.get(CONF_ARM_NIGHT_ACTION),
|
||||
code=code,
|
||||
)
|
||||
|
||||
@ -354,7 +314,7 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity, Restore
|
||||
"""Arm the panel to Vacation."""
|
||||
await self._async_alarm_arm(
|
||||
AlarmControlPanelState.ARMED_VACATION,
|
||||
script=self._arm_vacation_script,
|
||||
script=self._action_scripts.get(CONF_ARM_VACATION_ACTION),
|
||||
code=code,
|
||||
)
|
||||
|
||||
@ -362,20 +322,22 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity, Restore
|
||||
"""Arm the panel to Custom Bypass."""
|
||||
await self._async_alarm_arm(
|
||||
AlarmControlPanelState.ARMED_CUSTOM_BYPASS,
|
||||
script=self._arm_custom_bypass_script,
|
||||
script=self._action_scripts.get(CONF_ARM_CUSTOM_BYPASS_ACTION),
|
||||
code=code,
|
||||
)
|
||||
|
||||
async def async_alarm_disarm(self, code: str | None = None) -> None:
|
||||
"""Disarm the panel."""
|
||||
await self._async_alarm_arm(
|
||||
AlarmControlPanelState.DISARMED, script=self._disarm_script, code=code
|
||||
AlarmControlPanelState.DISARMED,
|
||||
script=self._action_scripts.get(CONF_DISARM_ACTION),
|
||||
code=code,
|
||||
)
|
||||
|
||||
async def async_alarm_trigger(self, code: str | None = None) -> None:
|
||||
"""Trigger the panel."""
|
||||
await self._async_alarm_arm(
|
||||
AlarmControlPanelState.TRIGGERED,
|
||||
script=self._trigger_script,
|
||||
script=self._action_scripts.get(CONF_TRIGGER_ACTION),
|
||||
code=code,
|
||||
)
|
||||
|
@ -23,7 +23,6 @@ from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
AddEntitiesCallback,
|
||||
)
|
||||
from homeassistant.helpers.script import Script
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import CONF_PRESS, DOMAIN
|
||||
@ -121,11 +120,8 @@ class TemplateButtonEntity(TemplateEntity, ButtonEntity):
|
||||
"""Initialize the button."""
|
||||
super().__init__(hass, config=config, unique_id=unique_id)
|
||||
assert self._attr_name is not None
|
||||
self._command_press = (
|
||||
Script(hass, config.get(CONF_PRESS), self._attr_name, DOMAIN)
|
||||
if config.get(CONF_PRESS, None) is not None
|
||||
else None
|
||||
)
|
||||
if action := config.get(CONF_PRESS):
|
||||
self.add_script(CONF_PRESS, action, self._attr_name, DOMAIN)
|
||||
self._attr_device_class = config.get(CONF_DEVICE_CLASS)
|
||||
self._attr_state = None
|
||||
self._attr_device_info = async_device_info_to_link_from_device_id(
|
||||
@ -135,5 +131,5 @@ class TemplateButtonEntity(TemplateEntity, ButtonEntity):
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Press the button."""
|
||||
if self._command_press:
|
||||
await self.async_run_script(self._command_press, context=self._context)
|
||||
if script := self._action_scripts.get(CONF_PRESS):
|
||||
await self.async_run_script(script, context=self._context)
|
||||
|
@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@ -30,7 +30,6 @@ from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.script import Script
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import DOMAIN
|
||||
@ -103,7 +102,7 @@ PLATFORM_SCHEMA = COVER_PLATFORM_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
async def _async_create_entities(hass, config):
|
||||
async def _async_create_entities(hass: HomeAssistant, config):
|
||||
"""Create the Template cover."""
|
||||
covers = []
|
||||
|
||||
@ -141,11 +140,11 @@ class CoverTemplate(TemplateEntity, CoverEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass,
|
||||
hass: HomeAssistant,
|
||||
object_id,
|
||||
config,
|
||||
config: dict[str, Any],
|
||||
unique_id,
|
||||
):
|
||||
) -> None:
|
||||
"""Initialize the Template cover."""
|
||||
super().__init__(
|
||||
hass, config=config, fallback_name=object_id, unique_id=unique_id
|
||||
@ -153,45 +152,40 @@ class CoverTemplate(TemplateEntity, CoverEntity):
|
||||
self.entity_id = async_generate_entity_id(
|
||||
ENTITY_ID_FORMAT, object_id, hass=hass
|
||||
)
|
||||
friendly_name = self._attr_name
|
||||
name = self._attr_name
|
||||
if TYPE_CHECKING:
|
||||
assert name is not None
|
||||
self._template = config.get(CONF_VALUE_TEMPLATE)
|
||||
self._position_template = config.get(CONF_POSITION_TEMPLATE)
|
||||
self._tilt_template = config.get(CONF_TILT_TEMPLATE)
|
||||
self._attr_device_class = config.get(CONF_DEVICE_CLASS)
|
||||
self._open_script = None
|
||||
if (open_action := config.get(OPEN_ACTION)) is not None:
|
||||
self._open_script = Script(hass, open_action, friendly_name, DOMAIN)
|
||||
self._close_script = None
|
||||
if (close_action := config.get(CLOSE_ACTION)) is not None:
|
||||
self._close_script = Script(hass, close_action, friendly_name, DOMAIN)
|
||||
self._stop_script = None
|
||||
if (stop_action := config.get(STOP_ACTION)) is not None:
|
||||
self._stop_script = Script(hass, stop_action, friendly_name, DOMAIN)
|
||||
self._position_script = None
|
||||
if (position_action := config.get(POSITION_ACTION)) is not None:
|
||||
self._position_script = Script(hass, position_action, friendly_name, DOMAIN)
|
||||
self._tilt_script = None
|
||||
if (tilt_action := config.get(TILT_ACTION)) is not None:
|
||||
self._tilt_script = Script(hass, tilt_action, friendly_name, DOMAIN)
|
||||
|
||||
# The config requires (open and close scripts) or a set position script,
|
||||
# therefore the base supported features will always include them.
|
||||
self._attr_supported_features = (
|
||||
CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
|
||||
)
|
||||
for action_id, supported_feature in (
|
||||
(OPEN_ACTION, 0),
|
||||
(CLOSE_ACTION, 0),
|
||||
(STOP_ACTION, CoverEntityFeature.STOP),
|
||||
(POSITION_ACTION, CoverEntityFeature.SET_POSITION),
|
||||
(TILT_ACTION, TILT_FEATURES),
|
||||
):
|
||||
if action_config := config.get(action_id):
|
||||
self.add_script(action_id, action_config, name, DOMAIN)
|
||||
self._attr_supported_features |= supported_feature
|
||||
|
||||
optimistic = config.get(CONF_OPTIMISTIC)
|
||||
self._optimistic = optimistic or (
|
||||
optimistic is None and not self._template and not self._position_template
|
||||
)
|
||||
tilt_optimistic = config.get(CONF_TILT_OPTIMISTIC)
|
||||
self._tilt_optimistic = tilt_optimistic or not self._tilt_template
|
||||
self._position = None
|
||||
self._position: int | None = None
|
||||
self._is_opening = False
|
||||
self._is_closing = False
|
||||
self._tilt_value = None
|
||||
|
||||
supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
|
||||
if self._stop_script is not None:
|
||||
supported_features |= CoverEntityFeature.STOP
|
||||
if self._position_script is not None:
|
||||
supported_features |= CoverEntityFeature.SET_POSITION
|
||||
if self._tilt_script is not None:
|
||||
supported_features |= TILT_FEATURES
|
||||
self._attr_supported_features = supported_features
|
||||
self._tilt_value: int | None = None
|
||||
|
||||
@callback
|
||||
def _async_setup_templates(self) -> None:
|
||||
@ -317,7 +311,7 @@ class CoverTemplate(TemplateEntity, CoverEntity):
|
||||
|
||||
None is unknown, 0 is closed, 100 is fully open.
|
||||
"""
|
||||
if self._position_template or self._position_script:
|
||||
if self._position_template or self._action_scripts.get(POSITION_ACTION):
|
||||
return self._position
|
||||
return None
|
||||
|
||||
@ -331,11 +325,11 @@ class CoverTemplate(TemplateEntity, CoverEntity):
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Move the cover up."""
|
||||
if self._open_script:
|
||||
await self.async_run_script(self._open_script, context=self._context)
|
||||
elif self._position_script:
|
||||
if (open_script := self._action_scripts.get(OPEN_ACTION)) is not None:
|
||||
await self.async_run_script(open_script, context=self._context)
|
||||
elif (position_script := self._action_scripts.get(POSITION_ACTION)) is not None:
|
||||
await self.async_run_script(
|
||||
self._position_script,
|
||||
position_script,
|
||||
run_variables={"position": 100},
|
||||
context=self._context,
|
||||
)
|
||||
@ -345,11 +339,11 @@ class CoverTemplate(TemplateEntity, CoverEntity):
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
"""Move the cover down."""
|
||||
if self._close_script:
|
||||
await self.async_run_script(self._close_script, context=self._context)
|
||||
elif self._position_script:
|
||||
if (close_script := self._action_scripts.get(CLOSE_ACTION)) is not None:
|
||||
await self.async_run_script(close_script, context=self._context)
|
||||
elif (position_script := self._action_scripts.get(POSITION_ACTION)) is not None:
|
||||
await self.async_run_script(
|
||||
self._position_script,
|
||||
position_script,
|
||||
run_variables={"position": 0},
|
||||
context=self._context,
|
||||
)
|
||||
@ -359,14 +353,14 @@ class CoverTemplate(TemplateEntity, CoverEntity):
|
||||
|
||||
async def async_stop_cover(self, **kwargs: Any) -> None:
|
||||
"""Fire the stop action."""
|
||||
if self._stop_script:
|
||||
await self.async_run_script(self._stop_script, context=self._context)
|
||||
if (stop_script := self._action_scripts.get(STOP_ACTION)) is not None:
|
||||
await self.async_run_script(stop_script, context=self._context)
|
||||
|
||||
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
||||
"""Set cover position."""
|
||||
self._position = kwargs[ATTR_POSITION]
|
||||
await self.async_run_script(
|
||||
self._position_script,
|
||||
self._action_scripts[POSITION_ACTION],
|
||||
run_variables={"position": self._position},
|
||||
context=self._context,
|
||||
)
|
||||
@ -377,7 +371,7 @@ class CoverTemplate(TemplateEntity, CoverEntity):
|
||||
"""Tilt the cover open."""
|
||||
self._tilt_value = 100
|
||||
await self.async_run_script(
|
||||
self._tilt_script,
|
||||
self._action_scripts[TILT_ACTION],
|
||||
run_variables={"tilt": self._tilt_value},
|
||||
context=self._context,
|
||||
)
|
||||
@ -388,7 +382,7 @@ class CoverTemplate(TemplateEntity, CoverEntity):
|
||||
"""Tilt the cover closed."""
|
||||
self._tilt_value = 0
|
||||
await self.async_run_script(
|
||||
self._tilt_script,
|
||||
self._action_scripts[TILT_ACTION],
|
||||
run_variables={"tilt": self._tilt_value},
|
||||
context=self._context,
|
||||
)
|
||||
@ -399,7 +393,7 @@ class CoverTemplate(TemplateEntity, CoverEntity):
|
||||
"""Move the cover tilt to a specific position."""
|
||||
self._tilt_value = kwargs[ATTR_TILT_POSITION]
|
||||
await self.async_run_script(
|
||||
self._tilt_script,
|
||||
self._action_scripts[TILT_ACTION],
|
||||
run_variables={"tilt": self._tilt_value},
|
||||
context=self._context,
|
||||
)
|
||||
|
66
homeassistant/components/template/entity.py
Normal file
66
homeassistant/components/template/entity.py
Normal file
@ -0,0 +1,66 @@
|
||||
"""Template entity base class."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.core import Context, HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.script import Script, _VarsType
|
||||
from homeassistant.helpers.template import TemplateStateFromEntityId
|
||||
|
||||
|
||||
class AbstractTemplateEntity(Entity):
|
||||
"""Actions linked to a template entity."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Initialize the entity."""
|
||||
|
||||
self.hass = hass
|
||||
self._action_scripts: dict[str, Script] = {}
|
||||
|
||||
@property
|
||||
def referenced_blueprint(self) -> str | None:
|
||||
"""Return referenced blueprint or None."""
|
||||
raise NotImplementedError
|
||||
|
||||
@callback
|
||||
def _render_script_variables(self) -> dict:
|
||||
"""Render configured variables."""
|
||||
raise NotImplementedError
|
||||
|
||||
def add_script(
|
||||
self,
|
||||
script_id: str,
|
||||
config: Sequence[dict[str, Any]],
|
||||
name: str,
|
||||
domain: str,
|
||||
):
|
||||
"""Add an action script."""
|
||||
|
||||
# Cannot use self.hass because it may be None in child class
|
||||
# at instantiation.
|
||||
self._action_scripts[script_id] = Script(
|
||||
self.hass,
|
||||
config,
|
||||
f"{name} {script_id}",
|
||||
domain,
|
||||
)
|
||||
|
||||
async def async_run_script(
|
||||
self,
|
||||
script: Script,
|
||||
*,
|
||||
run_variables: _VarsType | None = None,
|
||||
context: Context | None = None,
|
||||
) -> None:
|
||||
"""Run an action script."""
|
||||
if run_variables is None:
|
||||
run_variables = {}
|
||||
await script.async_run(
|
||||
run_variables={
|
||||
"this": TemplateStateFromEntityId(self.hass, self.entity_id),
|
||||
**self._render_script_variables(),
|
||||
**run_variables,
|
||||
},
|
||||
context=context,
|
||||
)
|
@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@ -32,7 +32,6 @@ from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.script import Script
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import DOMAIN
|
||||
@ -89,7 +88,7 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
async def _async_create_entities(hass, config):
|
||||
async def _async_create_entities(hass: HomeAssistant, config):
|
||||
"""Create the Template Fans."""
|
||||
fans = []
|
||||
|
||||
@ -127,11 +126,11 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass,
|
||||
hass: HomeAssistant,
|
||||
object_id,
|
||||
config,
|
||||
config: dict[str, Any],
|
||||
unique_id,
|
||||
):
|
||||
) -> None:
|
||||
"""Initialize the fan."""
|
||||
super().__init__(
|
||||
hass, config=config, fallback_name=object_id, unique_id=unique_id
|
||||
@ -140,7 +139,9 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||
self.entity_id = async_generate_entity_id(
|
||||
ENTITY_ID_FORMAT, object_id, hass=hass
|
||||
)
|
||||
friendly_name = self._attr_name
|
||||
name = self._attr_name
|
||||
if TYPE_CHECKING:
|
||||
assert name is not None
|
||||
|
||||
self._template = config.get(CONF_VALUE_TEMPLATE)
|
||||
self._percentage_template = config.get(CONF_PERCENTAGE_TEMPLATE)
|
||||
@ -148,44 +149,28 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||
self._oscillating_template = config.get(CONF_OSCILLATING_TEMPLATE)
|
||||
self._direction_template = config.get(CONF_DIRECTION_TEMPLATE)
|
||||
|
||||
self._on_script = Script(hass, config[CONF_ON_ACTION], friendly_name, DOMAIN)
|
||||
self._off_script = Script(hass, config[CONF_OFF_ACTION], friendly_name, DOMAIN)
|
||||
|
||||
self._set_percentage_script = None
|
||||
if set_percentage_action := config.get(CONF_SET_PERCENTAGE_ACTION):
|
||||
self._set_percentage_script = Script(
|
||||
hass, set_percentage_action, friendly_name, DOMAIN
|
||||
)
|
||||
|
||||
self._set_preset_mode_script = None
|
||||
if set_preset_mode_action := config.get(CONF_SET_PRESET_MODE_ACTION):
|
||||
self._set_preset_mode_script = Script(
|
||||
hass, set_preset_mode_action, friendly_name, DOMAIN
|
||||
)
|
||||
|
||||
self._set_oscillating_script = None
|
||||
if set_oscillating_action := config.get(CONF_SET_OSCILLATING_ACTION):
|
||||
self._set_oscillating_script = Script(
|
||||
hass, set_oscillating_action, friendly_name, DOMAIN
|
||||
)
|
||||
|
||||
self._set_direction_script = None
|
||||
if set_direction_action := config.get(CONF_SET_DIRECTION_ACTION):
|
||||
self._set_direction_script = Script(
|
||||
hass, set_direction_action, friendly_name, DOMAIN
|
||||
)
|
||||
for action_id in (
|
||||
CONF_ON_ACTION,
|
||||
CONF_OFF_ACTION,
|
||||
CONF_SET_PERCENTAGE_ACTION,
|
||||
CONF_SET_PRESET_MODE_ACTION,
|
||||
CONF_SET_OSCILLATING_ACTION,
|
||||
CONF_SET_DIRECTION_ACTION,
|
||||
):
|
||||
if action_config := config.get(action_id):
|
||||
self.add_script(action_id, action_config, name, DOMAIN)
|
||||
|
||||
self._state: bool | None = False
|
||||
self._percentage = None
|
||||
self._preset_mode = None
|
||||
self._oscillating = None
|
||||
self._direction = None
|
||||
self._percentage: int | None = None
|
||||
self._preset_mode: str | None = None
|
||||
self._oscillating: bool | None = None
|
||||
self._direction: str | None = None
|
||||
|
||||
# Number of valid speeds
|
||||
self._speed_count = config.get(CONF_SPEED_COUNT)
|
||||
|
||||
# List of valid preset modes
|
||||
self._preset_modes = config.get(CONF_PRESET_MODES)
|
||||
self._preset_modes: list[str] | None = config.get(CONF_PRESET_MODES)
|
||||
|
||||
if self._percentage_template:
|
||||
self._attr_supported_features |= FanEntityFeature.SET_SPEED
|
||||
@ -207,7 +192,7 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||
return self._speed_count or 100
|
||||
|
||||
@property
|
||||
def preset_modes(self) -> list[str]:
|
||||
def preset_modes(self) -> list[str] | None:
|
||||
"""Get the list of available preset modes."""
|
||||
return self._preset_modes
|
||||
|
||||
@ -244,7 +229,7 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||
) -> None:
|
||||
"""Turn on the fan."""
|
||||
await self.async_run_script(
|
||||
self._on_script,
|
||||
self._action_scripts[CONF_ON_ACTION],
|
||||
run_variables={
|
||||
ATTR_PERCENTAGE: percentage,
|
||||
ATTR_PRESET_MODE: preset_mode,
|
||||
@ -263,7 +248,9 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off the fan."""
|
||||
await self.async_run_script(self._off_script, context=self._context)
|
||||
await self.async_run_script(
|
||||
self._action_scripts[CONF_OFF_ACTION], context=self._context
|
||||
)
|
||||
|
||||
if self._template is None:
|
||||
self._state = False
|
||||
@ -273,9 +260,9 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||
"""Set the percentage speed of the fan."""
|
||||
self._percentage = percentage
|
||||
|
||||
if self._set_percentage_script:
|
||||
if (script := self._action_scripts.get(CONF_SET_PERCENTAGE_ACTION)) is not None:
|
||||
await self.async_run_script(
|
||||
self._set_percentage_script,
|
||||
script,
|
||||
run_variables={ATTR_PERCENTAGE: self._percentage},
|
||||
context=self._context,
|
||||
)
|
||||
@ -288,9 +275,11 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||
"""Set the preset_mode of the fan."""
|
||||
self._preset_mode = preset_mode
|
||||
|
||||
if self._set_preset_mode_script:
|
||||
if (
|
||||
script := self._action_scripts.get(CONF_SET_PRESET_MODE_ACTION)
|
||||
) is not None:
|
||||
await self.async_run_script(
|
||||
self._set_preset_mode_script,
|
||||
script,
|
||||
run_variables={ATTR_PRESET_MODE: self._preset_mode},
|
||||
context=self._context,
|
||||
)
|
||||
@ -301,25 +290,25 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||
|
||||
async def async_oscillate(self, oscillating: bool) -> None:
|
||||
"""Set oscillation of the fan."""
|
||||
if self._set_oscillating_script is None:
|
||||
if (script := self._action_scripts.get(CONF_SET_OSCILLATING_ACTION)) is None:
|
||||
return
|
||||
|
||||
self._oscillating = oscillating
|
||||
await self.async_run_script(
|
||||
self._set_oscillating_script,
|
||||
script,
|
||||
run_variables={ATTR_OSCILLATING: self.oscillating},
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
async def async_set_direction(self, direction: str) -> None:
|
||||
"""Set the direction of the fan."""
|
||||
if self._set_direction_script is None:
|
||||
if (script := self._action_scripts.get(CONF_SET_DIRECTION_ACTION)) is None:
|
||||
return
|
||||
|
||||
if direction in _VALID_DIRECTIONS:
|
||||
self._direction = direction
|
||||
await self.async_run_script(
|
||||
self._set_direction_script,
|
||||
script,
|
||||
run_variables={ATTR_DIRECTION: direction},
|
||||
context=self._context,
|
||||
)
|
||||
|
@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@ -39,7 +39,6 @@ from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.script import Script
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import color as color_util
|
||||
|
||||
@ -127,7 +126,7 @@ PLATFORM_SCHEMA = vol.All(
|
||||
)
|
||||
|
||||
|
||||
async def _async_create_entities(hass, config):
|
||||
async def _async_create_entities(hass: HomeAssistant, config):
|
||||
"""Create the Template Lights."""
|
||||
lights = []
|
||||
|
||||
@ -164,11 +163,11 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass,
|
||||
hass: HomeAssistant,
|
||||
object_id,
|
||||
config,
|
||||
config: dict[str, Any],
|
||||
unique_id,
|
||||
):
|
||||
) -> None:
|
||||
"""Initialize the light."""
|
||||
super().__init__(
|
||||
hass, config=config, fallback_name=object_id, unique_id=unique_id
|
||||
@ -176,52 +175,31 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
self.entity_id = async_generate_entity_id(
|
||||
ENTITY_ID_FORMAT, object_id, hass=hass
|
||||
)
|
||||
friendly_name = self._attr_name
|
||||
name = self._attr_name
|
||||
if TYPE_CHECKING:
|
||||
assert name is not None
|
||||
|
||||
self._template = config.get(CONF_VALUE_TEMPLATE)
|
||||
self._on_script = Script(hass, config[CONF_ON_ACTION], friendly_name, DOMAIN)
|
||||
self._off_script = Script(hass, config[CONF_OFF_ACTION], friendly_name, DOMAIN)
|
||||
self._level_script = None
|
||||
if (level_action := config.get(CONF_LEVEL_ACTION)) is not None:
|
||||
self._level_script = Script(hass, level_action, friendly_name, DOMAIN)
|
||||
self._level_template = config.get(CONF_LEVEL_TEMPLATE)
|
||||
self._temperature_script = None
|
||||
if (temperature_action := config.get(CONF_TEMPERATURE_ACTION)) is not None:
|
||||
self._temperature_script = Script(
|
||||
hass, temperature_action, friendly_name, DOMAIN
|
||||
)
|
||||
self._temperature_template = config.get(CONF_TEMPERATURE_TEMPLATE)
|
||||
self._color_script = None
|
||||
if (color_action := config.get(CONF_COLOR_ACTION)) is not None:
|
||||
self._color_script = Script(hass, color_action, friendly_name, DOMAIN)
|
||||
self._color_template = config.get(CONF_COLOR_TEMPLATE)
|
||||
self._hs_script = None
|
||||
if (hs_action := config.get(CONF_HS_ACTION)) is not None:
|
||||
self._hs_script = Script(hass, hs_action, friendly_name, DOMAIN)
|
||||
self._hs_template = config.get(CONF_HS_TEMPLATE)
|
||||
self._rgb_script = None
|
||||
if (rgb_action := config.get(CONF_RGB_ACTION)) is not None:
|
||||
self._rgb_script = Script(hass, rgb_action, friendly_name, DOMAIN)
|
||||
self._rgb_template = config.get(CONF_RGB_TEMPLATE)
|
||||
self._rgbw_script = None
|
||||
if (rgbw_action := config.get(CONF_RGBW_ACTION)) is not None:
|
||||
self._rgbw_script = Script(hass, rgbw_action, friendly_name, DOMAIN)
|
||||
self._rgbw_template = config.get(CONF_RGBW_TEMPLATE)
|
||||
self._rgbww_script = None
|
||||
if (rgbww_action := config.get(CONF_RGBWW_ACTION)) is not None:
|
||||
self._rgbww_script = Script(hass, rgbww_action, friendly_name, DOMAIN)
|
||||
self._rgbww_template = config.get(CONF_RGBWW_TEMPLATE)
|
||||
self._effect_script = None
|
||||
if (effect_action := config.get(CONF_EFFECT_ACTION)) is not None:
|
||||
self._effect_script = Script(hass, effect_action, friendly_name, DOMAIN)
|
||||
self._effect_list_template = config.get(CONF_EFFECT_LIST_TEMPLATE)
|
||||
self._effect_template = config.get(CONF_EFFECT_TEMPLATE)
|
||||
self._max_mireds_template = config.get(CONF_MAX_MIREDS_TEMPLATE)
|
||||
self._min_mireds_template = config.get(CONF_MIN_MIREDS_TEMPLATE)
|
||||
self._supports_transition_template = config.get(CONF_SUPPORTS_TRANSITION)
|
||||
|
||||
for action_id in (CONF_ON_ACTION, CONF_OFF_ACTION, CONF_EFFECT_ACTION):
|
||||
if action_config := config.get(action_id):
|
||||
self.add_script(action_id, action_config, name, DOMAIN)
|
||||
|
||||
self._state = False
|
||||
self._brightness = None
|
||||
self._temperature = None
|
||||
self._temperature: int | None = None
|
||||
self._hs_color = None
|
||||
self._rgb_color = None
|
||||
self._rgbw_color = None
|
||||
@ -235,21 +213,18 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
self._supported_color_modes = None
|
||||
|
||||
color_modes = {ColorMode.ONOFF}
|
||||
if self._level_script is not None:
|
||||
color_modes.add(ColorMode.BRIGHTNESS)
|
||||
if self._temperature_script is not None:
|
||||
color_modes.add(ColorMode.COLOR_TEMP)
|
||||
if self._hs_script is not None:
|
||||
color_modes.add(ColorMode.HS)
|
||||
if self._color_script is not None:
|
||||
color_modes.add(ColorMode.HS)
|
||||
if self._rgb_script is not None:
|
||||
color_modes.add(ColorMode.RGB)
|
||||
if self._rgbw_script is not None:
|
||||
color_modes.add(ColorMode.RGBW)
|
||||
if self._rgbww_script is not None:
|
||||
color_modes.add(ColorMode.RGBWW)
|
||||
|
||||
for action_id, color_mode in (
|
||||
(CONF_TEMPERATURE_ACTION, ColorMode.COLOR_TEMP),
|
||||
(CONF_LEVEL_ACTION, ColorMode.BRIGHTNESS),
|
||||
(CONF_COLOR_ACTION, ColorMode.HS),
|
||||
(CONF_HS_ACTION, ColorMode.HS),
|
||||
(CONF_RGB_ACTION, ColorMode.RGB),
|
||||
(CONF_RGBW_ACTION, ColorMode.RGBW),
|
||||
(CONF_RGBWW_ACTION, ColorMode.RGBWW),
|
||||
):
|
||||
if (action_config := config.get(action_id)) is not None:
|
||||
self.add_script(action_id, action_config, name, DOMAIN)
|
||||
color_modes.add(color_mode)
|
||||
self._supported_color_modes = filter_supported_color_modes(color_modes)
|
||||
if len(self._supported_color_modes) > 1:
|
||||
self._color_mode = ColorMode.UNKNOWN
|
||||
@ -257,7 +232,7 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
self._color_mode = next(iter(self._supported_color_modes))
|
||||
|
||||
self._attr_supported_features = LightEntityFeature(0)
|
||||
if self._effect_script is not None:
|
||||
if self._action_scripts.get(CONF_EFFECT_ACTION) is not None:
|
||||
self._attr_supported_features |= LightEntityFeature.EFFECT
|
||||
if self._supports_transition is True:
|
||||
self._attr_supported_features |= LightEntityFeature.TRANSITION
|
||||
@ -321,12 +296,12 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
return self._effect_list
|
||||
|
||||
@property
|
||||
def color_mode(self):
|
||||
def color_mode(self) -> ColorMode | None:
|
||||
"""Return current color mode."""
|
||||
return self._color_mode
|
||||
|
||||
@property
|
||||
def supported_color_modes(self):
|
||||
def supported_color_modes(self) -> set[ColorMode] | None:
|
||||
"""Flag supported color modes."""
|
||||
return self._supported_color_modes
|
||||
|
||||
@ -555,17 +530,28 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
if ATTR_TRANSITION in kwargs and self._supports_transition is True:
|
||||
common_params["transition"] = kwargs[ATTR_TRANSITION]
|
||||
|
||||
if ATTR_COLOR_TEMP_KELVIN in kwargs and self._temperature_script:
|
||||
if (
|
||||
ATTR_COLOR_TEMP_KELVIN in kwargs
|
||||
and (
|
||||
temperature_script := self._action_scripts.get(CONF_TEMPERATURE_ACTION)
|
||||
)
|
||||
is not None
|
||||
):
|
||||
common_params["color_temp"] = color_util.color_temperature_kelvin_to_mired(
|
||||
kwargs[ATTR_COLOR_TEMP_KELVIN]
|
||||
)
|
||||
|
||||
await self.async_run_script(
|
||||
self._temperature_script,
|
||||
temperature_script,
|
||||
run_variables=common_params,
|
||||
context=self._context,
|
||||
)
|
||||
elif ATTR_EFFECT in kwargs and self._effect_script:
|
||||
elif (
|
||||
ATTR_EFFECT in kwargs
|
||||
and (effect_script := self._action_scripts.get(CONF_EFFECT_ACTION))
|
||||
is not None
|
||||
):
|
||||
assert self._effect_list is not None
|
||||
effect = kwargs[ATTR_EFFECT]
|
||||
if effect not in self._effect_list:
|
||||
_LOGGER.error(
|
||||
@ -579,27 +565,38 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
common_params["effect"] = effect
|
||||
|
||||
await self.async_run_script(
|
||||
self._effect_script, run_variables=common_params, context=self._context
|
||||
effect_script, run_variables=common_params, context=self._context
|
||||
)
|
||||
elif ATTR_HS_COLOR in kwargs and self._color_script:
|
||||
elif (
|
||||
ATTR_HS_COLOR in kwargs
|
||||
and (color_script := self._action_scripts.get(CONF_COLOR_ACTION))
|
||||
is not None
|
||||
):
|
||||
hs_value = kwargs[ATTR_HS_COLOR]
|
||||
common_params["hs"] = hs_value
|
||||
common_params["h"] = int(hs_value[0])
|
||||
common_params["s"] = int(hs_value[1])
|
||||
|
||||
await self.async_run_script(
|
||||
self._color_script, run_variables=common_params, context=self._context
|
||||
color_script, run_variables=common_params, context=self._context
|
||||
)
|
||||
elif ATTR_HS_COLOR in kwargs and self._hs_script:
|
||||
elif (
|
||||
ATTR_HS_COLOR in kwargs
|
||||
and (hs_script := self._action_scripts.get(CONF_HS_ACTION)) is not None
|
||||
):
|
||||
hs_value = kwargs[ATTR_HS_COLOR]
|
||||
common_params["hs"] = hs_value
|
||||
common_params["h"] = int(hs_value[0])
|
||||
common_params["s"] = int(hs_value[1])
|
||||
|
||||
await self.async_run_script(
|
||||
self._hs_script, run_variables=common_params, context=self._context
|
||||
hs_script, run_variables=common_params, context=self._context
|
||||
)
|
||||
elif ATTR_RGBWW_COLOR in kwargs and self._rgbww_script:
|
||||
elif (
|
||||
ATTR_RGBWW_COLOR in kwargs
|
||||
and (rgbww_script := self._action_scripts.get(CONF_RGBWW_ACTION))
|
||||
is not None
|
||||
):
|
||||
rgbww_value = kwargs[ATTR_RGBWW_COLOR]
|
||||
common_params["rgbww"] = rgbww_value
|
||||
common_params["rgb"] = (
|
||||
@ -614,9 +611,12 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
common_params["ww"] = int(rgbww_value[4])
|
||||
|
||||
await self.async_run_script(
|
||||
self._rgbww_script, run_variables=common_params, context=self._context
|
||||
rgbww_script, run_variables=common_params, context=self._context
|
||||
)
|
||||
elif ATTR_RGBW_COLOR in kwargs and self._rgbw_script:
|
||||
elif (
|
||||
ATTR_RGBW_COLOR in kwargs
|
||||
and (rgbw_script := self._action_scripts.get(CONF_RGBW_ACTION)) is not None
|
||||
):
|
||||
rgbw_value = kwargs[ATTR_RGBW_COLOR]
|
||||
common_params["rgbw"] = rgbw_value
|
||||
common_params["rgb"] = (
|
||||
@ -630,9 +630,12 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
common_params["w"] = int(rgbw_value[3])
|
||||
|
||||
await self.async_run_script(
|
||||
self._rgbw_script, run_variables=common_params, context=self._context
|
||||
rgbw_script, run_variables=common_params, context=self._context
|
||||
)
|
||||
elif ATTR_RGB_COLOR in kwargs and self._rgb_script:
|
||||
elif (
|
||||
ATTR_RGB_COLOR in kwargs
|
||||
and (rgb_script := self._action_scripts.get(CONF_RGB_ACTION)) is not None
|
||||
):
|
||||
rgb_value = kwargs[ATTR_RGB_COLOR]
|
||||
common_params["rgb"] = rgb_value
|
||||
common_params["r"] = int(rgb_value[0])
|
||||
@ -640,15 +643,21 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
common_params["b"] = int(rgb_value[2])
|
||||
|
||||
await self.async_run_script(
|
||||
self._rgb_script, run_variables=common_params, context=self._context
|
||||
rgb_script, run_variables=common_params, context=self._context
|
||||
)
|
||||
elif ATTR_BRIGHTNESS in kwargs and self._level_script:
|
||||
elif (
|
||||
ATTR_BRIGHTNESS in kwargs
|
||||
and (level_script := self._action_scripts.get(CONF_LEVEL_ACTION))
|
||||
is not None
|
||||
):
|
||||
await self.async_run_script(
|
||||
self._level_script, run_variables=common_params, context=self._context
|
||||
level_script, run_variables=common_params, context=self._context
|
||||
)
|
||||
else:
|
||||
await self.async_run_script(
|
||||
self._on_script, run_variables=common_params, context=self._context
|
||||
self._action_scripts[CONF_ON_ACTION],
|
||||
run_variables=common_params,
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
if optimistic_set:
|
||||
@ -656,14 +665,15 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the light off."""
|
||||
off_script = self._action_scripts[CONF_OFF_ACTION]
|
||||
if ATTR_TRANSITION in kwargs and self._supports_transition is True:
|
||||
await self.async_run_script(
|
||||
self._off_script,
|
||||
off_script,
|
||||
run_variables={"transition": kwargs[ATTR_TRANSITION]},
|
||||
context=self._context,
|
||||
)
|
||||
else:
|
||||
await self.async_run_script(self._off_script, context=self._context)
|
||||
await self.async_run_script(off_script, context=self._context)
|
||||
if self._template is None:
|
||||
self._state = False
|
||||
self.async_write_ha_state()
|
||||
@ -1013,7 +1023,7 @@ class LightTemplate(TemplateEntity, LightEntity):
|
||||
if render in (None, "None", ""):
|
||||
self._supports_transition = False
|
||||
return
|
||||
self._attr_supported_features &= ~LightEntityFeature.TRANSITION
|
||||
self._attr_supported_features &= LightEntityFeature.EFFECT
|
||||
self._supports_transition = bool(render)
|
||||
if self._supports_transition:
|
||||
self._attr_supported_features |= LightEntityFeature.TRANSITION
|
||||
|
@ -23,7 +23,6 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ServiceValidationError, TemplateError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.script import Script
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import DOMAIN
|
||||
@ -90,13 +89,18 @@ class TemplateLock(TemplateEntity, LockEntity):
|
||||
)
|
||||
self._state: LockState | None = None
|
||||
name = self._attr_name
|
||||
assert name
|
||||
if TYPE_CHECKING:
|
||||
assert name is not None
|
||||
|
||||
self._state_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
self._command_lock = Script(hass, config[CONF_LOCK], name, DOMAIN)
|
||||
self._command_unlock = Script(hass, config[CONF_UNLOCK], name, DOMAIN)
|
||||
if CONF_OPEN in config:
|
||||
self._command_open = Script(hass, config[CONF_OPEN], name, DOMAIN)
|
||||
self._attr_supported_features |= LockEntityFeature.OPEN
|
||||
for action_id, supported_feature in (
|
||||
(CONF_LOCK, 0),
|
||||
(CONF_UNLOCK, 0),
|
||||
(CONF_OPEN, LockEntityFeature.OPEN),
|
||||
):
|
||||
if action_config := config.get(action_id):
|
||||
self.add_script(action_id, action_config, name, DOMAIN)
|
||||
self._attr_supported_features |= supported_feature
|
||||
self._code_format_template = config.get(CONF_CODE_FORMAT_TEMPLATE)
|
||||
self._code_format: str | None = None
|
||||
self._code_format_template_error: TemplateError | None = None
|
||||
@ -210,7 +214,9 @@ class TemplateLock(TemplateEntity, LockEntity):
|
||||
tpl_vars = {ATTR_CODE: kwargs.get(ATTR_CODE) if kwargs else None}
|
||||
|
||||
await self.async_run_script(
|
||||
self._command_lock, run_variables=tpl_vars, context=self._context
|
||||
self._action_scripts[CONF_LOCK],
|
||||
run_variables=tpl_vars,
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
async def async_unlock(self, **kwargs: Any) -> None:
|
||||
@ -226,7 +232,9 @@ class TemplateLock(TemplateEntity, LockEntity):
|
||||
tpl_vars = {ATTR_CODE: kwargs.get(ATTR_CODE) if kwargs else None}
|
||||
|
||||
await self.async_run_script(
|
||||
self._command_unlock, run_variables=tpl_vars, context=self._context
|
||||
self._action_scripts[CONF_UNLOCK],
|
||||
run_variables=tpl_vars,
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
async def async_open(self, **kwargs: Any) -> None:
|
||||
@ -242,7 +250,9 @@ class TemplateLock(TemplateEntity, LockEntity):
|
||||
tpl_vars = {ATTR_CODE: kwargs.get(ATTR_CODE) if kwargs else None}
|
||||
|
||||
await self.async_run_script(
|
||||
self._command_open, run_variables=tpl_vars, context=self._context
|
||||
self._action_scripts[CONF_OPEN],
|
||||
run_variables=tpl_vars,
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
def _raise_template_error_if_available(self):
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "template",
|
||||
"name": "Template",
|
||||
"after_dependencies": ["group"],
|
||||
"codeowners": ["@PhracturedBlue", "@home-assistant/core"],
|
||||
"codeowners": ["@Petro31", "@PhracturedBlue", "@home-assistant/core"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["blueprint"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/template",
|
||||
|
@ -157,9 +157,7 @@ class TemplateNumber(TemplateEntity, NumberEntity):
|
||||
super().__init__(hass, config=config, unique_id=unique_id)
|
||||
assert self._attr_name is not None
|
||||
self._value_template = config[CONF_STATE]
|
||||
self._command_set_value = Script(
|
||||
hass, config[CONF_SET_VALUE], self._attr_name, DOMAIN
|
||||
)
|
||||
self.add_script(CONF_SET_VALUE, config[CONF_SET_VALUE], self._attr_name, DOMAIN)
|
||||
|
||||
self._step_template = config[CONF_STEP]
|
||||
self._min_value_template = config[CONF_MIN]
|
||||
@ -210,9 +208,9 @@ class TemplateNumber(TemplateEntity, NumberEntity):
|
||||
if self._optimistic:
|
||||
self._attr_native_value = value
|
||||
self.async_write_ha_state()
|
||||
if self._command_set_value:
|
||||
if (set_value := self._action_scripts.get(CONF_SET_VALUE)) is not None:
|
||||
await self.async_run_script(
|
||||
self._command_set_value,
|
||||
set_value,
|
||||
run_variables={ATTR_VALUE: value},
|
||||
context=self._context,
|
||||
)
|
||||
|
@ -143,8 +143,8 @@ class TemplateSelect(TemplateEntity, SelectEntity):
|
||||
assert self._attr_name is not None
|
||||
self._value_template = config[CONF_STATE]
|
||||
if (selection_option := config.get(CONF_SELECT_OPTION)) is not None:
|
||||
self._command_select_option = Script(
|
||||
hass, selection_option, self._attr_name, DOMAIN
|
||||
self.add_script(
|
||||
CONF_SELECT_OPTION, selection_option, self._attr_name, DOMAIN
|
||||
)
|
||||
self._options_template = config[ATTR_OPTIONS]
|
||||
self._attr_assumed_state = self._optimistic = config.get(CONF_OPTIMISTIC, False)
|
||||
@ -177,9 +177,9 @@ class TemplateSelect(TemplateEntity, SelectEntity):
|
||||
if self._optimistic:
|
||||
self._attr_current_option = option
|
||||
self.async_write_ha_state()
|
||||
if self._command_select_option:
|
||||
if (select_option := self._action_scripts.get(CONF_SELECT_OPTION)) is not None:
|
||||
await self.async_run_script(
|
||||
self._command_select_option,
|
||||
select_option,
|
||||
run_variables={ATTR_OPTION: option},
|
||||
context=self._context,
|
||||
)
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@ -33,7 +33,6 @@ from homeassistant.helpers.entity_platform import (
|
||||
AddEntitiesCallback,
|
||||
)
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.script import Script
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import CONF_TURN_OFF, CONF_TURN_ON, DOMAIN
|
||||
@ -74,7 +73,7 @@ SWITCH_CONFIG_SCHEMA = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
async def _async_create_entities(hass, config):
|
||||
async def _async_create_entities(hass: HomeAssistant, config: ConfigType):
|
||||
"""Create the Template switches."""
|
||||
switches = []
|
||||
|
||||
@ -134,11 +133,11 @@ class SwitchTemplate(TemplateEntity, SwitchEntity, RestoreEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass,
|
||||
hass: HomeAssistant,
|
||||
object_id,
|
||||
config,
|
||||
config: ConfigType,
|
||||
unique_id,
|
||||
):
|
||||
) -> None:
|
||||
"""Initialize the Template switch."""
|
||||
super().__init__(
|
||||
hass, config=config, fallback_name=object_id, unique_id=unique_id
|
||||
@ -147,18 +146,16 @@ class SwitchTemplate(TemplateEntity, SwitchEntity, RestoreEntity):
|
||||
self.entity_id = async_generate_entity_id(
|
||||
ENTITY_ID_FORMAT, object_id, hass=hass
|
||||
)
|
||||
friendly_name = self._attr_name
|
||||
name = self._attr_name
|
||||
if TYPE_CHECKING:
|
||||
assert name is not None
|
||||
self._template = config.get(CONF_VALUE_TEMPLATE)
|
||||
self._on_script = (
|
||||
Script(hass, config.get(CONF_TURN_ON), friendly_name, DOMAIN)
|
||||
if config.get(CONF_TURN_ON) is not None
|
||||
else None
|
||||
)
|
||||
self._off_script = (
|
||||
Script(hass, config.get(CONF_TURN_OFF), friendly_name, DOMAIN)
|
||||
if config.get(CONF_TURN_OFF) is not None
|
||||
else None
|
||||
)
|
||||
|
||||
if on_action := config.get(CONF_TURN_ON):
|
||||
self.add_script(CONF_TURN_ON, on_action, name, DOMAIN)
|
||||
if off_action := config.get(CONF_TURN_OFF):
|
||||
self.add_script(CONF_TURN_OFF, off_action, name, DOMAIN)
|
||||
|
||||
self._state: bool | None = False
|
||||
self._attr_assumed_state = self._template is None
|
||||
self._attr_device_info = async_device_info_to_link_from_device_id(
|
||||
@ -209,16 +206,16 @@ class SwitchTemplate(TemplateEntity, SwitchEntity, RestoreEntity):
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Fire the on action."""
|
||||
if self._on_script:
|
||||
await self.async_run_script(self._on_script, context=self._context)
|
||||
if (on_script := self._action_scripts.get(CONF_TURN_ON)) is not None:
|
||||
await self.async_run_script(on_script, context=self._context)
|
||||
if self._template is None:
|
||||
self._state = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Fire the off action."""
|
||||
if self._off_script:
|
||||
await self.async_run_script(self._off_script, context=self._context)
|
||||
if (off_script := self._action_scripts.get(CONF_TURN_OFF)) is not None:
|
||||
await self.async_run_script(off_script, context=self._context)
|
||||
if self._template is None:
|
||||
self._state = False
|
||||
self.async_write_ha_state()
|
||||
|
@ -24,7 +24,6 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import (
|
||||
CALLBACK_TYPE,
|
||||
Context,
|
||||
Event,
|
||||
EventStateChangedData,
|
||||
HomeAssistant,
|
||||
@ -41,7 +40,7 @@ from homeassistant.helpers.event import (
|
||||
TrackTemplateResultInfo,
|
||||
async_track_template_result,
|
||||
)
|
||||
from homeassistant.helpers.script import Script, _VarsType
|
||||
from homeassistant.helpers.script_variables import ScriptVariables
|
||||
from homeassistant.helpers.start import async_at_start
|
||||
from homeassistant.helpers.template import (
|
||||
Template,
|
||||
@ -61,6 +60,7 @@ from .const import (
|
||||
CONF_AVAILABILITY_TEMPLATE,
|
||||
CONF_PICTURE,
|
||||
)
|
||||
from .entity import AbstractTemplateEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -248,7 +248,7 @@ class _TemplateAttribute:
|
||||
return
|
||||
|
||||
|
||||
class TemplateEntity(Entity): # pylint: disable=hass-enforce-class-module
|
||||
class TemplateEntity(AbstractTemplateEntity): # pylint: disable=hass-enforce-class-module
|
||||
"""Entity that uses templates to calculate attributes."""
|
||||
|
||||
_attr_available = True
|
||||
@ -268,6 +268,7 @@ class TemplateEntity(Entity): # pylint: disable=hass-enforce-class-module
|
||||
unique_id: str | None = None,
|
||||
) -> None:
|
||||
"""Template Entity."""
|
||||
super().__init__(hass)
|
||||
self._template_attrs: dict[Template, list[_TemplateAttribute]] = {}
|
||||
self._template_result_info: TrackTemplateResultInfo | None = None
|
||||
self._attr_extra_state_attributes = {}
|
||||
@ -285,6 +286,7 @@ class TemplateEntity(Entity): # pylint: disable=hass-enforce-class-module
|
||||
]
|
||||
| None
|
||||
) = None
|
||||
self._run_variables: ScriptVariables | dict
|
||||
if config is None:
|
||||
self._attribute_templates = attribute_templates
|
||||
self._availability_template = availability_template
|
||||
@ -339,18 +341,6 @@ class TemplateEntity(Entity): # pylint: disable=hass-enforce-class-module
|
||||
variables=variables, parse_result=False
|
||||
)
|
||||
|
||||
@callback
|
||||
def _render_variables(self) -> dict:
|
||||
if isinstance(self._run_variables, dict):
|
||||
return self._run_variables
|
||||
|
||||
return self._run_variables.async_render(
|
||||
self.hass,
|
||||
{
|
||||
"this": TemplateStateFromEntityId(self.hass, self.entity_id),
|
||||
},
|
||||
)
|
||||
|
||||
@callback
|
||||
def _update_available(self, result: str | TemplateError) -> None:
|
||||
if isinstance(result, TemplateError):
|
||||
@ -387,6 +377,18 @@ class TemplateEntity(Entity): # pylint: disable=hass-enforce-class-module
|
||||
return None
|
||||
return cast(str, self._blueprint_inputs[CONF_USE_BLUEPRINT][CONF_PATH])
|
||||
|
||||
def _render_script_variables(self) -> dict[str, Any]:
|
||||
"""Render configured variables."""
|
||||
if isinstance(self._run_variables, dict):
|
||||
return self._run_variables
|
||||
|
||||
return self._run_variables.async_render(
|
||||
self.hass,
|
||||
{
|
||||
"this": TemplateStateFromEntityId(self.hass, self.entity_id),
|
||||
},
|
||||
)
|
||||
|
||||
def add_template_attribute(
|
||||
self,
|
||||
attribute: str,
|
||||
@ -488,7 +490,7 @@ class TemplateEntity(Entity): # pylint: disable=hass-enforce-class-module
|
||||
|
||||
variables = {
|
||||
"this": TemplateStateFromEntityId(self.hass, self.entity_id),
|
||||
**self._render_variables(),
|
||||
**self._render_script_variables(),
|
||||
}
|
||||
|
||||
for template, attributes in self._template_attrs.items():
|
||||
@ -581,22 +583,3 @@ class TemplateEntity(Entity): # pylint: disable=hass-enforce-class-module
|
||||
"""Call for forced update."""
|
||||
assert self._template_result_info
|
||||
self._template_result_info.async_refresh()
|
||||
|
||||
async def async_run_script(
|
||||
self,
|
||||
script: Script,
|
||||
*,
|
||||
run_variables: _VarsType | None = None,
|
||||
context: Context | None = None,
|
||||
) -> None:
|
||||
"""Run an action script."""
|
||||
if run_variables is None:
|
||||
run_variables = {}
|
||||
await script.async_run(
|
||||
run_variables={
|
||||
"this": TemplateStateFromEntityId(self.hass, self.entity_id),
|
||||
**self._render_variables(),
|
||||
**run_variables,
|
||||
},
|
||||
context=context,
|
||||
)
|
||||
|
@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@ -33,7 +33,6 @@ from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.script import Script
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import DOMAIN
|
||||
@ -90,7 +89,7 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
async def _async_create_entities(hass, config):
|
||||
async def _async_create_entities(hass: HomeAssistant, config: ConfigType):
|
||||
"""Create the Template Vacuums."""
|
||||
vacuums = []
|
||||
|
||||
@ -127,11 +126,11 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass,
|
||||
hass: HomeAssistant,
|
||||
object_id,
|
||||
config,
|
||||
config: ConfigType,
|
||||
unique_id,
|
||||
):
|
||||
) -> None:
|
||||
"""Initialize the vacuum."""
|
||||
super().__init__(
|
||||
hass, config=config, fallback_name=object_id, unique_id=unique_id
|
||||
@ -139,7 +138,9 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity):
|
||||
self.entity_id = async_generate_entity_id(
|
||||
ENTITY_ID_FORMAT, object_id, hass=hass
|
||||
)
|
||||
friendly_name = self._attr_name
|
||||
name = self._attr_name
|
||||
if TYPE_CHECKING:
|
||||
assert name is not None
|
||||
|
||||
self._template = config.get(CONF_VALUE_TEMPLATE)
|
||||
self._battery_level_template = config.get(CONF_BATTERY_LEVEL_TEMPLATE)
|
||||
@ -148,43 +149,18 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity):
|
||||
VacuumEntityFeature.START | VacuumEntityFeature.STATE
|
||||
)
|
||||
|
||||
self._start_script = Script(hass, config[SERVICE_START], friendly_name, DOMAIN)
|
||||
|
||||
self._pause_script = None
|
||||
if pause_action := config.get(SERVICE_PAUSE):
|
||||
self._pause_script = Script(hass, pause_action, friendly_name, DOMAIN)
|
||||
self._attr_supported_features |= VacuumEntityFeature.PAUSE
|
||||
|
||||
self._stop_script = None
|
||||
if stop_action := config.get(SERVICE_STOP):
|
||||
self._stop_script = Script(hass, stop_action, friendly_name, DOMAIN)
|
||||
self._attr_supported_features |= VacuumEntityFeature.STOP
|
||||
|
||||
self._return_to_base_script = None
|
||||
if return_to_base_action := config.get(SERVICE_RETURN_TO_BASE):
|
||||
self._return_to_base_script = Script(
|
||||
hass, return_to_base_action, friendly_name, DOMAIN
|
||||
)
|
||||
self._attr_supported_features |= VacuumEntityFeature.RETURN_HOME
|
||||
|
||||
self._clean_spot_script = None
|
||||
if clean_spot_action := config.get(SERVICE_CLEAN_SPOT):
|
||||
self._clean_spot_script = Script(
|
||||
hass, clean_spot_action, friendly_name, DOMAIN
|
||||
)
|
||||
self._attr_supported_features |= VacuumEntityFeature.CLEAN_SPOT
|
||||
|
||||
self._locate_script = None
|
||||
if locate_action := config.get(SERVICE_LOCATE):
|
||||
self._locate_script = Script(hass, locate_action, friendly_name, DOMAIN)
|
||||
self._attr_supported_features |= VacuumEntityFeature.LOCATE
|
||||
|
||||
self._set_fan_speed_script = None
|
||||
if set_fan_speed_action := config.get(SERVICE_SET_FAN_SPEED):
|
||||
self._set_fan_speed_script = Script(
|
||||
hass, set_fan_speed_action, friendly_name, DOMAIN
|
||||
)
|
||||
self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED
|
||||
for action_id, supported_feature in (
|
||||
(SERVICE_START, 0),
|
||||
(SERVICE_PAUSE, VacuumEntityFeature.PAUSE),
|
||||
(SERVICE_STOP, VacuumEntityFeature.STOP),
|
||||
(SERVICE_RETURN_TO_BASE, VacuumEntityFeature.RETURN_HOME),
|
||||
(SERVICE_CLEAN_SPOT, VacuumEntityFeature.CLEAN_SPOT),
|
||||
(SERVICE_LOCATE, VacuumEntityFeature.LOCATE),
|
||||
(SERVICE_SET_FAN_SPEED, VacuumEntityFeature.FAN_SPEED),
|
||||
):
|
||||
if action_config := config.get(action_id):
|
||||
self.add_script(action_id, action_config, name, DOMAIN)
|
||||
self._attr_supported_features |= supported_feature
|
||||
|
||||
self._state = None
|
||||
self._battery_level = None
|
||||
@ -203,62 +179,50 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity):
|
||||
|
||||
async def async_start(self) -> None:
|
||||
"""Start or resume the cleaning task."""
|
||||
await self.async_run_script(self._start_script, context=self._context)
|
||||
await self.async_run_script(
|
||||
self._action_scripts[SERVICE_START], context=self._context
|
||||
)
|
||||
|
||||
async def async_pause(self) -> None:
|
||||
"""Pause the cleaning task."""
|
||||
if self._pause_script is None:
|
||||
return
|
||||
|
||||
await self.async_run_script(self._pause_script, context=self._context)
|
||||
if (script := self._action_scripts.get(SERVICE_PAUSE)) is not None:
|
||||
await self.async_run_script(script, context=self._context)
|
||||
|
||||
async def async_stop(self, **kwargs: Any) -> None:
|
||||
"""Stop the cleaning task."""
|
||||
if self._stop_script is None:
|
||||
return
|
||||
|
||||
await self.async_run_script(self._stop_script, context=self._context)
|
||||
if (script := self._action_scripts.get(SERVICE_STOP)) is not None:
|
||||
await self.async_run_script(script, context=self._context)
|
||||
|
||||
async def async_return_to_base(self, **kwargs: Any) -> None:
|
||||
"""Set the vacuum cleaner to return to the dock."""
|
||||
if self._return_to_base_script is None:
|
||||
return
|
||||
|
||||
await self.async_run_script(self._return_to_base_script, context=self._context)
|
||||
if (script := self._action_scripts.get(SERVICE_RETURN_TO_BASE)) is not None:
|
||||
await self.async_run_script(script, context=self._context)
|
||||
|
||||
async def async_clean_spot(self, **kwargs: Any) -> None:
|
||||
"""Perform a spot clean-up."""
|
||||
if self._clean_spot_script is None:
|
||||
return
|
||||
|
||||
await self.async_run_script(self._clean_spot_script, context=self._context)
|
||||
if (script := self._action_scripts.get(SERVICE_CLEAN_SPOT)) is not None:
|
||||
await self.async_run_script(script, context=self._context)
|
||||
|
||||
async def async_locate(self, **kwargs: Any) -> None:
|
||||
"""Locate the vacuum cleaner."""
|
||||
if self._locate_script is None:
|
||||
return
|
||||
|
||||
await self.async_run_script(self._locate_script, context=self._context)
|
||||
if (script := self._action_scripts.get(SERVICE_LOCATE)) is not None:
|
||||
await self.async_run_script(script, context=self._context)
|
||||
|
||||
async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None:
|
||||
"""Set fan speed."""
|
||||
if self._set_fan_speed_script is None:
|
||||
return
|
||||
|
||||
if fan_speed in self._attr_fan_speed_list:
|
||||
self._attr_fan_speed = fan_speed
|
||||
await self.async_run_script(
|
||||
self._set_fan_speed_script,
|
||||
run_variables={ATTR_FAN_SPEED: fan_speed},
|
||||
context=self._context,
|
||||
)
|
||||
else:
|
||||
if fan_speed not in self._attr_fan_speed_list:
|
||||
_LOGGER.error(
|
||||
"Received invalid fan speed: %s for entity %s. Expected: %s",
|
||||
fan_speed,
|
||||
self.entity_id,
|
||||
self._attr_fan_speed_list,
|
||||
)
|
||||
return
|
||||
|
||||
if (script := self._action_scripts.get(SERVICE_SET_FAN_SPEED)) is not None:
|
||||
await self.async_run_script(
|
||||
script, run_variables={ATTR_FAN_SPEED: fan_speed}, context=self._context
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_setup_templates(self) -> None:
|
||||
|
17
tests/components/template/test_entity.py
Normal file
17
tests/components/template/test_entity.py
Normal file
@ -0,0 +1,17 @@
|
||||
"""Test abstract template entity."""
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.template import entity as abstract_entity
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
async def test_template_entity_not_implemented(hass: HomeAssistant) -> None:
|
||||
"""Test abstract template entity raises not implemented error."""
|
||||
|
||||
entity = abstract_entity.AbstractTemplateEntity(None)
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = entity.referenced_blueprint
|
||||
|
||||
with pytest.raises(NotImplementedError):
|
||||
entity._render_script_variables()
|
@ -9,7 +9,7 @@ from homeassistant.helpers import template
|
||||
|
||||
async def test_template_entity_requires_hass_set(hass: HomeAssistant) -> None:
|
||||
"""Test template entity requires hass to be set before accepting templates."""
|
||||
entity = template_entity.TemplateEntity(hass)
|
||||
entity = template_entity.TemplateEntity(None)
|
||||
|
||||
with pytest.raises(ValueError, match="^hass cannot be None"):
|
||||
entity.add_template_attribute("_hello", template.Template("Hello"))
|
||||
|
Loading…
x
Reference in New Issue
Block a user