mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
Support variables, icon, and picture for all compatible template platforms (#145893)
* Fix template entity variables in blueprints * add picture and icon tests * add variable test for all platforms * apply comments * Update all test names
This commit is contained in:
@ -41,13 +41,12 @@ from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.script import Script
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import CONF_OBJECT_ID, CONF_PICTURE, DOMAIN
|
||||
from .const import CONF_OBJECT_ID, DOMAIN
|
||||
from .entity import AbstractTemplateEntity
|
||||
from .template_entity import (
|
||||
LEGACY_FIELDS as TEMPLATE_ENTITY_LEGACY_FIELDS,
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
|
||||
TEMPLATE_ENTITY_ICON_SCHEMA,
|
||||
TemplateEntity,
|
||||
make_template_entity_common_modern_schema,
|
||||
rewrite_common_legacy_to_modern_conf,
|
||||
)
|
||||
|
||||
@ -105,15 +104,10 @@ ALARM_CONTROL_PANEL_SCHEMA = vol.All(
|
||||
CONF_CODE_FORMAT, default=TemplateCodeFormat.number.name
|
||||
): cv.enum(TemplateCodeFormat),
|
||||
vol.Optional(CONF_DISARM_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_NAME): cv.template,
|
||||
vol.Optional(CONF_PICTURE): cv.template,
|
||||
vol.Optional(CONF_STATE): cv.template,
|
||||
vol.Optional(CONF_TRIGGER_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
}
|
||||
)
|
||||
.extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA.schema)
|
||||
.extend(TEMPLATE_ENTITY_ICON_SCHEMA.schema),
|
||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||
)
|
||||
|
||||
|
||||
@ -419,9 +413,7 @@ class AlarmControlPanelTemplate(TemplateEntity, AbstractTemplateAlarmControlPane
|
||||
unique_id: str | None,
|
||||
) -> None:
|
||||
"""Initialize the panel."""
|
||||
TemplateEntity.__init__(
|
||||
self, hass, config=config, fallback_name=None, unique_id=unique_id
|
||||
)
|
||||
TemplateEntity.__init__(self, hass, config=config, unique_id=unique_id)
|
||||
AbstractTemplateAlarmControlPanel.__init__(self, config)
|
||||
if (object_id := config.get(CONF_OBJECT_ID)) is not None:
|
||||
self.entity_id = async_generate_entity_id(
|
||||
|
@ -26,29 +26,19 @@ from homeassistant.helpers.entity_platform import (
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import CONF_PRESS, DOMAIN
|
||||
from .template_entity import (
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
|
||||
TEMPLATE_ENTITY_ICON_SCHEMA,
|
||||
TemplateEntity,
|
||||
)
|
||||
from .template_entity import TemplateEntity, make_template_entity_common_modern_schema
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = "Template Button"
|
||||
DEFAULT_OPTIMISTIC = False
|
||||
|
||||
BUTTON_SCHEMA = (
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.template,
|
||||
vol.Required(CONF_PRESS): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
}
|
||||
)
|
||||
.extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA.schema)
|
||||
.extend(TEMPLATE_ENTITY_ICON_SCHEMA.schema)
|
||||
)
|
||||
BUTTON_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PRESS): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
}
|
||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||
|
||||
CONFIG_BUTTON_SCHEMA = vol.Schema(
|
||||
{
|
||||
|
@ -37,14 +37,13 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import TriggerUpdateCoordinator
|
||||
from .const import CONF_OBJECT_ID, CONF_PICTURE, DOMAIN
|
||||
from .const import CONF_OBJECT_ID, DOMAIN
|
||||
from .entity import AbstractTemplateEntity
|
||||
from .template_entity import (
|
||||
LEGACY_FIELDS as TEMPLATE_ENTITY_LEGACY_FIELDS,
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
|
||||
TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY,
|
||||
TEMPLATE_ENTITY_ICON_SCHEMA,
|
||||
TemplateEntity,
|
||||
make_template_entity_common_modern_schema,
|
||||
rewrite_common_legacy_to_modern_conf,
|
||||
)
|
||||
from .trigger_entity import TriggerEntity
|
||||
@ -100,21 +99,16 @@ COVER_SCHEMA = vol.All(
|
||||
vol.Inclusive(CLOSE_ACTION, CONF_OPEN_AND_CLOSE): cv.SCRIPT_SCHEMA,
|
||||
vol.Inclusive(OPEN_ACTION, CONF_OPEN_AND_CLOSE): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.template,
|
||||
vol.Optional(CONF_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(CONF_PICTURE): cv.template,
|
||||
vol.Optional(CONF_POSITION): cv.template,
|
||||
vol.Optional(CONF_STATE): cv.template,
|
||||
vol.Optional(CONF_TILT_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(CONF_TILT): cv.template,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Optional(POSITION_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(STOP_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(TILT_ACTION): cv.SCRIPT_SCHEMA,
|
||||
}
|
||||
)
|
||||
.extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA.schema)
|
||||
.extend(TEMPLATE_ENTITY_ICON_SCHEMA.schema),
|
||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema),
|
||||
cv.has_at_least_one_key(OPEN_ACTION, POSITION_ACTION),
|
||||
)
|
||||
|
||||
@ -463,9 +457,7 @@ class CoverTemplate(TemplateEntity, AbstractTemplateCover):
|
||||
unique_id,
|
||||
) -> None:
|
||||
"""Initialize the Template cover."""
|
||||
TemplateEntity.__init__(
|
||||
self, hass, config=config, fallback_name=None, unique_id=unique_id
|
||||
)
|
||||
TemplateEntity.__init__(self, hass, config=config, unique_id=unique_id)
|
||||
AbstractTemplateCover.__init__(self, config)
|
||||
if (object_id := config.get(CONF_OBJECT_ID)) is not None:
|
||||
self.entity_id = async_generate_entity_id(
|
||||
|
@ -37,14 +37,13 @@ from homeassistant.helpers.entity import async_generate_entity_id
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import CONF_OBJECT_ID, CONF_PICTURE, DOMAIN
|
||||
from .const import CONF_OBJECT_ID, DOMAIN
|
||||
from .entity import AbstractTemplateEntity
|
||||
from .template_entity import (
|
||||
LEGACY_FIELDS as TEMPLATE_ENTITY_LEGACY_FIELDS,
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY,
|
||||
TEMPLATE_ENTITY_ICON_SCHEMA,
|
||||
TemplateEntity,
|
||||
make_template_entity_common_modern_schema,
|
||||
rewrite_common_legacy_to_modern_conf,
|
||||
)
|
||||
|
||||
@ -85,12 +84,10 @@ FAN_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_DIRECTION): cv.template,
|
||||
vol.Optional(CONF_NAME): cv.template,
|
||||
vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_OSCILLATING): cv.template,
|
||||
vol.Optional(CONF_PERCENTAGE): cv.template,
|
||||
vol.Optional(CONF_PICTURE): cv.template,
|
||||
vol.Optional(CONF_PRESET_MODE): cv.template,
|
||||
vol.Optional(CONF_PRESET_MODES): cv.ensure_list,
|
||||
vol.Optional(CONF_SET_DIRECTION_ACTION): cv.SCRIPT_SCHEMA,
|
||||
@ -99,11 +96,8 @@ FAN_SCHEMA = vol.All(
|
||||
vol.Optional(CONF_SET_PRESET_MODE_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_SPEED_COUNT): vol.Coerce(int),
|
||||
vol.Optional(CONF_STATE): cv.template,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
}
|
||||
)
|
||||
.extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA.schema)
|
||||
.extend(TEMPLATE_ENTITY_ICON_SCHEMA.schema),
|
||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||
)
|
||||
|
||||
LEGACY_FAN_SCHEMA = vol.All(
|
||||
@ -488,9 +482,7 @@ class TemplateFan(TemplateEntity, AbstractTemplateFan):
|
||||
unique_id,
|
||||
) -> None:
|
||||
"""Initialize the fan."""
|
||||
TemplateEntity.__init__(
|
||||
self, hass, config=config, fallback_name=None, unique_id=unique_id
|
||||
)
|
||||
TemplateEntity.__init__(self, hass, config=config, unique_id=unique_id)
|
||||
AbstractTemplateFan.__init__(self, config)
|
||||
if (object_id := config.get(CONF_OBJECT_ID)) is not None:
|
||||
self.entity_id = async_generate_entity_id(
|
||||
|
@ -29,7 +29,10 @@ from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import TriggerUpdateCoordinator
|
||||
from .const import CONF_PICTURE
|
||||
from .template_entity import TemplateEntity, make_template_entity_common_schema
|
||||
from .template_entity import (
|
||||
TemplateEntity,
|
||||
make_template_entity_common_modern_attributes_schema,
|
||||
)
|
||||
from .trigger_entity import TriggerEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -43,7 +46,7 @@ IMAGE_SCHEMA = vol.Schema(
|
||||
vol.Required(CONF_URL): cv.template,
|
||||
vol.Optional(CONF_VERIFY_SSL, default=True): bool,
|
||||
}
|
||||
).extend(make_template_entity_common_schema(DEFAULT_NAME).schema)
|
||||
).extend(make_template_entity_common_modern_attributes_schema(DEFAULT_NAME).schema)
|
||||
|
||||
|
||||
IMAGE_CONFIG_SCHEMA = vol.Schema(
|
||||
|
@ -49,14 +49,13 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import color as color_util
|
||||
|
||||
from . import TriggerUpdateCoordinator
|
||||
from .const import CONF_OBJECT_ID, CONF_PICTURE, DOMAIN
|
||||
from .const import CONF_OBJECT_ID, DOMAIN
|
||||
from .entity import AbstractTemplateEntity
|
||||
from .template_entity import (
|
||||
LEGACY_FIELDS as TEMPLATE_ENTITY_LEGACY_FIELDS,
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
|
||||
TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY,
|
||||
TEMPLATE_ENTITY_ICON_SCHEMA,
|
||||
TemplateEntity,
|
||||
make_template_entity_common_modern_schema,
|
||||
rewrite_common_legacy_to_modern_conf,
|
||||
)
|
||||
from .trigger_entity import TriggerEntity
|
||||
@ -124,38 +123,31 @@ LEGACY_FIELDS = TEMPLATE_ENTITY_LEGACY_FIELDS | {
|
||||
|
||||
DEFAULT_NAME = "Template Light"
|
||||
|
||||
LIGHT_SCHEMA = (
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Inclusive(CONF_EFFECT_ACTION, "effect"): cv.SCRIPT_SCHEMA,
|
||||
vol.Inclusive(CONF_EFFECT_LIST, "effect"): cv.template,
|
||||
vol.Inclusive(CONF_EFFECT, "effect"): cv.template,
|
||||
vol.Optional(CONF_HS_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_HS): cv.template,
|
||||
vol.Optional(CONF_LEVEL_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_LEVEL): cv.template,
|
||||
vol.Optional(CONF_MAX_MIREDS): cv.template,
|
||||
vol.Optional(CONF_MIN_MIREDS): cv.template,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.template,
|
||||
vol.Optional(CONF_PICTURE): cv.template,
|
||||
vol.Optional(CONF_RGB_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_RGB): cv.template,
|
||||
vol.Optional(CONF_RGBW_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_RGBW): cv.template,
|
||||
vol.Optional(CONF_RGBWW_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_RGBWW): cv.template,
|
||||
vol.Optional(CONF_STATE): cv.template,
|
||||
vol.Optional(CONF_SUPPORTS_TRANSITION): cv.template,
|
||||
vol.Optional(CONF_TEMPERATURE_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_TEMPERATURE): cv.template,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
|
||||
}
|
||||
)
|
||||
.extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA.schema)
|
||||
.extend(TEMPLATE_ENTITY_ICON_SCHEMA.schema)
|
||||
)
|
||||
LIGHT_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Inclusive(CONF_EFFECT_ACTION, "effect"): cv.SCRIPT_SCHEMA,
|
||||
vol.Inclusive(CONF_EFFECT_LIST, "effect"): cv.template,
|
||||
vol.Inclusive(CONF_EFFECT, "effect"): cv.template,
|
||||
vol.Optional(CONF_HS_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_HS): cv.template,
|
||||
vol.Optional(CONF_LEVEL_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_LEVEL): cv.template,
|
||||
vol.Optional(CONF_MAX_MIREDS): cv.template,
|
||||
vol.Optional(CONF_MIN_MIREDS): cv.template,
|
||||
vol.Optional(CONF_RGB_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_RGB): cv.template,
|
||||
vol.Optional(CONF_RGBW_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_RGBW): cv.template,
|
||||
vol.Optional(CONF_RGBWW_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_RGBWW): cv.template,
|
||||
vol.Optional(CONF_STATE): cv.template,
|
||||
vol.Optional(CONF_SUPPORTS_TRANSITION): cv.template,
|
||||
vol.Optional(CONF_TEMPERATURE_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_TEMPERATURE): cv.template,
|
||||
vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
|
||||
}
|
||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||
|
||||
LEGACY_LIGHT_SCHEMA = vol.All(
|
||||
cv.deprecated(CONF_ENTITY_ID),
|
||||
@ -955,9 +947,7 @@ class LightTemplate(TemplateEntity, AbstractTemplateLight):
|
||||
unique_id: str | None,
|
||||
) -> None:
|
||||
"""Initialize the light."""
|
||||
TemplateEntity.__init__(
|
||||
self, hass, config=config, fallback_name=None, unique_id=unique_id
|
||||
)
|
||||
TemplateEntity.__init__(self, hass, config=config, unique_id=unique_id)
|
||||
AbstractTemplateLight.__init__(self, config)
|
||||
if (object_id := config.get(CONF_OBJECT_ID)) is not None:
|
||||
self.entity_id = async_generate_entity_id(
|
||||
|
@ -31,10 +31,9 @@ from .const import CONF_PICTURE, DOMAIN
|
||||
from .entity import AbstractTemplateEntity
|
||||
from .template_entity import (
|
||||
LEGACY_FIELDS as TEMPLATE_ENTITY_LEGACY_FIELDS,
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY,
|
||||
TEMPLATE_ENTITY_ICON_SCHEMA,
|
||||
TemplateEntity,
|
||||
make_template_entity_common_modern_schema,
|
||||
rewrite_common_legacy_to_modern_conf,
|
||||
)
|
||||
|
||||
@ -57,17 +56,13 @@ LOCK_SCHEMA = vol.All(
|
||||
{
|
||||
vol.Optional(CONF_CODE_FORMAT): cv.template,
|
||||
vol.Required(CONF_LOCK): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_NAME): cv.template,
|
||||
vol.Optional(CONF_OPEN): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(CONF_PICTURE): cv.template,
|
||||
vol.Required(CONF_STATE): cv.template,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Required(CONF_UNLOCK): cv.SCRIPT_SCHEMA,
|
||||
}
|
||||
)
|
||||
.extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA.schema)
|
||||
.extend(TEMPLATE_ENTITY_ICON_SCHEMA.schema),
|
||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||
)
|
||||
|
||||
|
||||
@ -313,9 +308,7 @@ class TemplateLock(TemplateEntity, AbstractTemplateLock):
|
||||
unique_id: str | None,
|
||||
) -> None:
|
||||
"""Initialize the lock."""
|
||||
TemplateEntity.__init__(
|
||||
self, hass, config=config, fallback_name=DEFAULT_NAME, unique_id=unique_id
|
||||
)
|
||||
TemplateEntity.__init__(self, hass, config=config, unique_id=unique_id)
|
||||
AbstractTemplateLock.__init__(self, config)
|
||||
name = self._attr_name
|
||||
if TYPE_CHECKING:
|
||||
|
@ -35,11 +35,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import TriggerUpdateCoordinator
|
||||
from .const import CONF_MAX, CONF_MIN, CONF_STEP, DOMAIN
|
||||
from .template_entity import (
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
|
||||
TEMPLATE_ENTITY_ICON_SCHEMA,
|
||||
TemplateEntity,
|
||||
)
|
||||
from .template_entity import TemplateEntity, make_template_entity_common_modern_schema
|
||||
from .trigger_entity import TriggerEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -49,23 +45,17 @@ CONF_SET_VALUE = "set_value"
|
||||
DEFAULT_NAME = "Template Number"
|
||||
DEFAULT_OPTIMISTIC = False
|
||||
|
||||
NUMBER_SCHEMA = (
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.template,
|
||||
vol.Required(CONF_STATE): cv.template,
|
||||
vol.Required(CONF_SET_VALUE): cv.SCRIPT_SCHEMA,
|
||||
vol.Required(CONF_STEP): cv.template,
|
||||
vol.Optional(CONF_MIN, default=DEFAULT_MIN_VALUE): cv.template,
|
||||
vol.Optional(CONF_MAX, default=DEFAULT_MAX_VALUE): cv.template,
|
||||
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
||||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
}
|
||||
)
|
||||
.extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA.schema)
|
||||
.extend(TEMPLATE_ENTITY_ICON_SCHEMA.schema)
|
||||
)
|
||||
NUMBER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_STATE): cv.template,
|
||||
vol.Required(CONF_SET_VALUE): cv.SCRIPT_SCHEMA,
|
||||
vol.Required(CONF_STEP): cv.template,
|
||||
vol.Optional(CONF_MIN, default=DEFAULT_MIN_VALUE): cv.template,
|
||||
vol.Optional(CONF_MAX, default=DEFAULT_MAX_VALUE): cv.template,
|
||||
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
||||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
||||
}
|
||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||
NUMBER_CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_NAME): cv.template,
|
||||
|
@ -32,11 +32,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import TriggerUpdateCoordinator
|
||||
from .const import DOMAIN
|
||||
from .template_entity import (
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
|
||||
TEMPLATE_ENTITY_ICON_SCHEMA,
|
||||
TemplateEntity,
|
||||
)
|
||||
from .template_entity import TemplateEntity, make_template_entity_common_modern_schema
|
||||
from .trigger_entity import TriggerEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -47,20 +43,14 @@ CONF_SELECT_OPTION = "select_option"
|
||||
DEFAULT_NAME = "Template Select"
|
||||
DEFAULT_OPTIMISTIC = False
|
||||
|
||||
SELECT_SCHEMA = (
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.template,
|
||||
vol.Required(CONF_STATE): cv.template,
|
||||
vol.Required(CONF_SELECT_OPTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Required(ATTR_OPTIONS): cv.template,
|
||||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
}
|
||||
)
|
||||
.extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA.schema)
|
||||
.extend(TEMPLATE_ENTITY_ICON_SCHEMA.schema)
|
||||
)
|
||||
SELECT_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_STATE): cv.template,
|
||||
vol.Required(CONF_SELECT_OPTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Required(ATTR_OPTIONS): cv.template,
|
||||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
||||
}
|
||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||
|
||||
|
||||
SELECT_CONFIG_SCHEMA = vol.Schema(
|
||||
|
@ -40,13 +40,12 @@ from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import TriggerUpdateCoordinator
|
||||
from .const import CONF_OBJECT_ID, CONF_PICTURE, CONF_TURN_OFF, CONF_TURN_ON, DOMAIN
|
||||
from .const import CONF_OBJECT_ID, CONF_TURN_OFF, CONF_TURN_ON, DOMAIN
|
||||
from .template_entity import (
|
||||
LEGACY_FIELDS as TEMPLATE_ENTITY_LEGACY_FIELDS,
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
|
||||
TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY,
|
||||
TEMPLATE_ENTITY_ICON_SCHEMA,
|
||||
TemplateEntity,
|
||||
make_template_entity_common_modern_schema,
|
||||
rewrite_common_legacy_to_modern_conf,
|
||||
)
|
||||
from .trigger_entity import TriggerEntity
|
||||
@ -60,20 +59,13 @@ LEGACY_FIELDS = TEMPLATE_ENTITY_LEGACY_FIELDS | {
|
||||
DEFAULT_NAME = "Template Switch"
|
||||
|
||||
|
||||
SWITCH_SCHEMA = (
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.template,
|
||||
vol.Optional(CONF_STATE): cv.template,
|
||||
vol.Required(CONF_TURN_ON): cv.SCRIPT_SCHEMA,
|
||||
vol.Required(CONF_TURN_OFF): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Optional(CONF_PICTURE): cv.template,
|
||||
}
|
||||
)
|
||||
.extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA.schema)
|
||||
.extend(TEMPLATE_ENTITY_ICON_SCHEMA.schema)
|
||||
)
|
||||
SWITCH_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_STATE): cv.template,
|
||||
vol.Required(CONF_TURN_ON): cv.SCRIPT_SCHEMA,
|
||||
vol.Required(CONF_TURN_OFF): cv.SCRIPT_SCHEMA,
|
||||
}
|
||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||
|
||||
LEGACY_SWITCH_SCHEMA = vol.All(
|
||||
cv.deprecated(ATTR_ENTITY_ID),
|
||||
@ -228,7 +220,7 @@ class SwitchTemplate(TemplateEntity, SwitchEntity, RestoreEntity):
|
||||
unique_id: str | None,
|
||||
) -> None:
|
||||
"""Initialize the Template switch."""
|
||||
super().__init__(hass, config=config, fallback_name=None, unique_id=unique_id)
|
||||
super().__init__(hass, config=config, unique_id=unique_id)
|
||||
if (object_id := config.get(CONF_OBJECT_ID)) is not None:
|
||||
self.entity_id = async_generate_entity_id(
|
||||
ENTITY_ID_FORMAT, object_id, hass=hass
|
||||
|
@ -94,16 +94,24 @@ TEMPLATE_ENTITY_COMMON_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def make_template_entity_common_schema(default_name: str) -> vol.Schema:
|
||||
def make_template_entity_common_modern_schema(
|
||||
default_name: str,
|
||||
) -> vol.Schema:
|
||||
"""Return a schema with default name."""
|
||||
return (
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_AVAILABILITY): cv.template,
|
||||
}
|
||||
)
|
||||
.extend(make_template_entity_base_schema(default_name).schema)
|
||||
.extend(TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA.schema)
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_AVAILABILITY): cv.template,
|
||||
vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
|
||||
}
|
||||
).extend(make_template_entity_base_schema(default_name).schema)
|
||||
|
||||
|
||||
def make_template_entity_common_modern_attributes_schema(
|
||||
default_name: str,
|
||||
) -> vol.Schema:
|
||||
"""Return a schema with default name."""
|
||||
return make_template_entity_common_modern_schema(default_name).extend(
|
||||
TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA.schema
|
||||
)
|
||||
|
||||
|
||||
|
@ -38,16 +38,14 @@ from homeassistant.helpers.entity import async_generate_entity_id
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import CONF_OBJECT_ID, CONF_PICTURE, DOMAIN
|
||||
from .const import CONF_OBJECT_ID, DOMAIN
|
||||
from .entity import AbstractTemplateEntity
|
||||
from .template_entity import (
|
||||
LEGACY_FIELDS as TEMPLATE_ENTITY_LEGACY_FIELDS,
|
||||
TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA,
|
||||
TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA_LEGACY,
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY,
|
||||
TEMPLATE_ENTITY_ICON_SCHEMA,
|
||||
TemplateEntity,
|
||||
make_template_entity_common_modern_attributes_schema,
|
||||
rewrite_common_legacy_to_modern_conf,
|
||||
)
|
||||
|
||||
@ -60,6 +58,8 @@ CONF_FAN_SPEED_LIST = "fan_speeds"
|
||||
CONF_FAN_SPEED = "fan_speed"
|
||||
CONF_FAN_SPEED_TEMPLATE = "fan_speed_template"
|
||||
|
||||
DEFAULT_NAME = "Template Vacuum"
|
||||
|
||||
ENTITY_ID_FORMAT = VACUUM_DOMAIN + ".{}"
|
||||
_VALID_STATES = [
|
||||
VacuumActivity.CLEANING,
|
||||
@ -80,13 +80,9 @@ VACUUM_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_BATTERY_LEVEL): cv.template,
|
||||
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(CONF_FAN_SPEED_LIST, default=[]): cv.ensure_list,
|
||||
vol.Optional(CONF_FAN_SPEED): cv.template,
|
||||
vol.Optional(CONF_NAME): cv.template,
|
||||
vol.Optional(CONF_PICTURE): cv.template,
|
||||
vol.Optional(CONF_STATE): cv.template,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Optional(SERVICE_CLEAN_SPOT): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(SERVICE_LOCATE): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(SERVICE_PAUSE): cv.SCRIPT_SCHEMA,
|
||||
@ -95,10 +91,7 @@ VACUUM_SCHEMA = vol.All(
|
||||
vol.Required(SERVICE_START): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(SERVICE_STOP): cv.SCRIPT_SCHEMA,
|
||||
}
|
||||
)
|
||||
.extend(TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA.schema)
|
||||
.extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA.schema)
|
||||
.extend(TEMPLATE_ENTITY_ICON_SCHEMA.schema),
|
||||
).extend(make_template_entity_common_modern_attributes_schema(DEFAULT_NAME).schema)
|
||||
)
|
||||
|
||||
LEGACY_VACUUM_SCHEMA = vol.All(
|
||||
@ -353,9 +346,7 @@ class TemplateVacuum(TemplateEntity, AbstractTemplateVacuum):
|
||||
unique_id,
|
||||
) -> None:
|
||||
"""Initialize the vacuum."""
|
||||
TemplateEntity.__init__(
|
||||
self, hass, config=config, fallback_name=None, unique_id=unique_id
|
||||
)
|
||||
TemplateEntity.__init__(self, hass, config=config, unique_id=unique_id)
|
||||
AbstractTemplateVacuum.__init__(self, config)
|
||||
if (object_id := config.get(CONF_OBJECT_ID)) is not None:
|
||||
self.entity_id = async_generate_entity_id(
|
||||
|
@ -32,7 +32,6 @@ from homeassistant.components.weather import (
|
||||
WeatherEntityFeature,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_NAME,
|
||||
CONF_TEMPERATURE_UNIT,
|
||||
CONF_UNIQUE_ID,
|
||||
STATE_UNAVAILABLE,
|
||||
@ -53,7 +52,11 @@ from homeassistant.util.unit_conversion import (
|
||||
)
|
||||
|
||||
from .coordinator import TriggerUpdateCoordinator
|
||||
from .template_entity import TemplateEntity, rewrite_common_legacy_to_modern_conf
|
||||
from .template_entity import (
|
||||
TemplateEntity,
|
||||
make_template_entity_common_modern_schema,
|
||||
rewrite_common_legacy_to_modern_conf,
|
||||
)
|
||||
from .trigger_entity import TriggerEntity
|
||||
|
||||
CHECK_FORECAST_KEYS = (
|
||||
@ -104,33 +107,33 @@ CONF_CLOUD_COVERAGE_TEMPLATE = "cloud_coverage_template"
|
||||
CONF_DEW_POINT_TEMPLATE = "dew_point_template"
|
||||
CONF_APPARENT_TEMPERATURE_TEMPLATE = "apparent_temperature_template"
|
||||
|
||||
DEFAULT_NAME = "Template Weather"
|
||||
|
||||
WEATHER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_NAME): cv.template,
|
||||
vol.Required(CONF_CONDITION_TEMPLATE): cv.template,
|
||||
vol.Required(CONF_TEMPERATURE_TEMPLATE): cv.template,
|
||||
vol.Required(CONF_HUMIDITY_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_APPARENT_TEMPERATURE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_ATTRIBUTION_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_PRESSURE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_WIND_SPEED_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_WIND_BEARING_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_OZONE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_VISIBILITY_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_CLOUD_COVERAGE_TEMPLATE): cv.template,
|
||||
vol.Required(CONF_CONDITION_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_DEW_POINT_TEMPLATE): cv.template,
|
||||
vol.Required(CONF_HUMIDITY_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_FORECAST_DAILY_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_FORECAST_HOURLY_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_FORECAST_TWICE_DAILY_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(TemperatureConverter.VALID_UNITS),
|
||||
vol.Optional(CONF_PRESSURE_UNIT): vol.In(PressureConverter.VALID_UNITS),
|
||||
vol.Optional(CONF_WIND_SPEED_UNIT): vol.In(SpeedConverter.VALID_UNITS),
|
||||
vol.Optional(CONF_VISIBILITY_UNIT): vol.In(DistanceConverter.VALID_UNITS),
|
||||
vol.Optional(CONF_OZONE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_PRECIPITATION_UNIT): vol.In(DistanceConverter.VALID_UNITS),
|
||||
vol.Optional(CONF_PRESSURE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_PRESSURE_UNIT): vol.In(PressureConverter.VALID_UNITS),
|
||||
vol.Required(CONF_TEMPERATURE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(TemperatureConverter.VALID_UNITS),
|
||||
vol.Optional(CONF_VISIBILITY_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_VISIBILITY_UNIT): vol.In(DistanceConverter.VALID_UNITS),
|
||||
vol.Optional(CONF_WIND_BEARING_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_WIND_GUST_SPEED_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_CLOUD_COVERAGE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_DEW_POINT_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_APPARENT_TEMPERATURE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_WIND_SPEED_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_WIND_SPEED_UNIT): vol.In(SpeedConverter.VALID_UNITS),
|
||||
}
|
||||
)
|
||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||
|
||||
PLATFORM_SCHEMA = WEATHER_PLATFORM_SCHEMA.extend(WEATHER_SCHEMA.schema)
|
||||
|
||||
|
@ -16,6 +16,22 @@ from homeassistant.components.blueprint import (
|
||||
DomainBlueprints,
|
||||
)
|
||||
from homeassistant.components.template import DOMAIN, SERVICE_RELOAD
|
||||
from homeassistant.components.template.config import (
|
||||
DOMAIN_ALARM_CONTROL_PANEL,
|
||||
DOMAIN_BINARY_SENSOR,
|
||||
DOMAIN_COVER,
|
||||
DOMAIN_FAN,
|
||||
DOMAIN_IMAGE,
|
||||
DOMAIN_LIGHT,
|
||||
DOMAIN_LOCK,
|
||||
DOMAIN_NUMBER,
|
||||
DOMAIN_SELECT,
|
||||
DOMAIN_SENSOR,
|
||||
DOMAIN_SWITCH,
|
||||
DOMAIN_VACUUM,
|
||||
DOMAIN_WEATHER,
|
||||
)
|
||||
from homeassistant.const import STATE_ON
|
||||
from homeassistant.core import Context, HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.setup import async_setup_component
|
||||
@ -459,3 +475,51 @@ async def test_no_blueprint(hass: HomeAssistant) -> None:
|
||||
template.helpers.blueprint_in_template(hass, "binary_sensor.test_entity")
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("domain", "set_state", "expected"),
|
||||
[
|
||||
(DOMAIN_ALARM_CONTROL_PANEL, STATE_ON, "armed_home"),
|
||||
(DOMAIN_BINARY_SENSOR, STATE_ON, STATE_ON),
|
||||
(DOMAIN_COVER, STATE_ON, "open"),
|
||||
(DOMAIN_FAN, STATE_ON, STATE_ON),
|
||||
(DOMAIN_IMAGE, "test.jpg", "2025-06-13T00:00:00+00:00"),
|
||||
(DOMAIN_LIGHT, STATE_ON, STATE_ON),
|
||||
(DOMAIN_LOCK, STATE_ON, "locked"),
|
||||
(DOMAIN_NUMBER, "1", "1.0"),
|
||||
(DOMAIN_SELECT, "option1", "option1"),
|
||||
(DOMAIN_SENSOR, "foo", "foo"),
|
||||
(DOMAIN_SWITCH, STATE_ON, STATE_ON),
|
||||
(DOMAIN_VACUUM, "cleaning", "cleaning"),
|
||||
(DOMAIN_WEATHER, "sunny", "sunny"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.freeze_time("2025-06-13 00:00:00+00:00")
|
||||
async def test_variables_for_entity(
|
||||
hass: HomeAssistant, domain: str, set_state: str, expected: str
|
||||
) -> None:
|
||||
"""Test regular template entities via blueprint with variables defined."""
|
||||
hass.states.async_set("sensor.test_state", set_state)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"template",
|
||||
{
|
||||
"template": [
|
||||
{
|
||||
"use_blueprint": {
|
||||
"path": f"test_{domain}_with_variables.yaml",
|
||||
"input": {"sensor": "sensor.test_state"},
|
||||
},
|
||||
"name": "Test",
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(f"{domain}.test")
|
||||
assert state is not None
|
||||
assert state.state == expected
|
||||
|
@ -11,7 +11,10 @@ from homeassistant import setup
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
|
||||
from homeassistant.components.template import DOMAIN
|
||||
from homeassistant.components.template.button import DEFAULT_NAME
|
||||
from homeassistant.components.template.const import CONF_PICTURE
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_PICTURE,
|
||||
ATTR_ICON,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_FRIENDLY_NAME,
|
||||
@ -247,6 +250,49 @@ async def test_name_template(hass: HomeAssistant) -> None:
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("field", "attribute", "test_template", "expected"),
|
||||
[
|
||||
(CONF_ICON, ATTR_ICON, "mdi:test{{ 1 + 1 }}", "mdi:test2"),
|
||||
(CONF_PICTURE, ATTR_ENTITY_PICTURE, "test{{ 1 + 1 }}.jpg", "test2.jpg"),
|
||||
],
|
||||
)
|
||||
async def test_templated_optional_config(
|
||||
hass: HomeAssistant,
|
||||
field: str,
|
||||
attribute: str,
|
||||
test_template: str,
|
||||
expected: str,
|
||||
) -> None:
|
||||
"""Test optional config templates."""
|
||||
with assert_setup_component(1, "template"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"template",
|
||||
{
|
||||
"template": {
|
||||
"button": {
|
||||
"press": {"service": "script.press"},
|
||||
field: test_template,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
_verify(
|
||||
hass,
|
||||
STATE_UNKNOWN,
|
||||
{
|
||||
attribute: expected,
|
||||
},
|
||||
"button.template_button",
|
||||
)
|
||||
|
||||
|
||||
async def test_unique_id(hass: HomeAssistant) -> None:
|
||||
"""Test: unique id is ok."""
|
||||
with assert_setup_component(1, "template"):
|
||||
|
@ -21,10 +21,13 @@ from homeassistant.components.number import (
|
||||
SERVICE_SET_VALUE as NUMBER_SERVICE_SET_VALUE,
|
||||
)
|
||||
from homeassistant.components.template import DOMAIN
|
||||
from homeassistant.components.template.const import CONF_PICTURE
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_ENTITY_PICTURE,
|
||||
ATTR_ICON,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_ICON,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
@ -58,6 +61,20 @@ _VALUE_INPUT_NUMBER_CONFIG = {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_STATE_ENTITY_ID = "number.test_state"
|
||||
|
||||
TEST_STATE_TRIGGER = {
|
||||
"trigger": {
|
||||
"trigger": "state",
|
||||
"entity_id": [TEST_STATE_ENTITY_ID],
|
||||
},
|
||||
"variables": {"triggering_entity": "{{ trigger.entity_id }}"},
|
||||
"action": [
|
||||
{"event": "action_event", "event_data": {"what": "{{ triggering_entity }}"}}
|
||||
],
|
||||
}
|
||||
TEST_REQUIRED = {"state": "0", "step": "1", "set_value": []}
|
||||
|
||||
|
||||
async def async_setup_modern_format(
|
||||
hass: HomeAssistant, count: int, number_config: dict[str, Any]
|
||||
@ -77,6 +94,24 @@ async def async_setup_modern_format(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def async_setup_trigger_format(
|
||||
hass: HomeAssistant, count: int, number_config: dict[str, Any]
|
||||
) -> None:
|
||||
"""Do setup of number integration via trigger format."""
|
||||
config = {"template": {**TEST_STATE_TRIGGER, "number": number_config}}
|
||||
|
||||
with assert_setup_component(count, template.DOMAIN):
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
template.DOMAIN,
|
||||
config,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def setup_number(
|
||||
hass: HomeAssistant,
|
||||
@ -89,6 +124,10 @@ async def setup_number(
|
||||
await async_setup_modern_format(
|
||||
hass, count, {"name": _TEST_OBJECT_ID, **number_config}
|
||||
)
|
||||
if style == ConfigurationStyle.TRIGGER:
|
||||
await async_setup_trigger_format(
|
||||
hass, count, {"name": _TEST_OBJECT_ID, **number_config}
|
||||
)
|
||||
|
||||
|
||||
async def test_setup_config_entry(
|
||||
@ -446,119 +485,49 @@ def _verify(
|
||||
assert attributes.get(CONF_UNIT_OF_MEASUREMENT) == expected_unit_of_measurement
|
||||
|
||||
|
||||
async def test_icon_template(hass: HomeAssistant) -> None:
|
||||
"""Test template numbers with icon templates."""
|
||||
with assert_setup_component(1, "input_number"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"input_number",
|
||||
{"input_number": _VALUE_INPUT_NUMBER_CONFIG},
|
||||
)
|
||||
|
||||
with assert_setup_component(1, "template"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"template",
|
||||
@pytest.mark.parametrize("count", [1])
|
||||
@pytest.mark.parametrize(
|
||||
("style", "initial_expected_state"),
|
||||
[(ConfigurationStyle.MODERN, ""), (ConfigurationStyle.TRIGGER, None)],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("number_config", "attribute", "expected"),
|
||||
[
|
||||
(
|
||||
{
|
||||
"template": {
|
||||
"unique_id": "b",
|
||||
"number": {
|
||||
"state": f"{{{{ states('{_VALUE_INPUT_NUMBER}') }}}}",
|
||||
"step": 1,
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"set_value": {
|
||||
"service": "input_number.set_value",
|
||||
"data_template": {
|
||||
"entity_id": _VALUE_INPUT_NUMBER,
|
||||
"value": "{{ value }}",
|
||||
},
|
||||
},
|
||||
"icon": "{% if ((states.input_number.value.state or 0) | int) > 50 %}mdi:greater{% else %}mdi:less{% endif %}",
|
||||
},
|
||||
}
|
||||
CONF_ICON: "{% if states.number.test_state.state == '1' %}mdi:check{% endif %}",
|
||||
**TEST_REQUIRED,
|
||||
},
|
||||
)
|
||||
|
||||
hass.states.async_set(_VALUE_INPUT_NUMBER, 49)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(_TEST_NUMBER)
|
||||
assert float(state.state) == 49
|
||||
assert state.attributes[ATTR_ICON] == "mdi:less"
|
||||
|
||||
await hass.services.async_call(
|
||||
INPUT_NUMBER_DOMAIN,
|
||||
INPUT_NUMBER_SERVICE_SET_VALUE,
|
||||
{CONF_ENTITY_ID: _VALUE_INPUT_NUMBER, INPUT_NUMBER_ATTR_VALUE: 51},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(_TEST_NUMBER)
|
||||
assert float(state.state) == 51
|
||||
assert state.attributes[ATTR_ICON] == "mdi:greater"
|
||||
|
||||
|
||||
async def test_icon_template_with_trigger(hass: HomeAssistant) -> None:
|
||||
"""Test template numbers with icon templates."""
|
||||
with assert_setup_component(1, "input_number"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"input_number",
|
||||
{"input_number": _VALUE_INPUT_NUMBER_CONFIG},
|
||||
)
|
||||
|
||||
with assert_setup_component(1, "template"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"template",
|
||||
ATTR_ICON,
|
||||
"mdi:check",
|
||||
),
|
||||
(
|
||||
{
|
||||
"template": {
|
||||
"trigger": {"platform": "state", "entity_id": _VALUE_INPUT_NUMBER},
|
||||
"unique_id": "b",
|
||||
"number": {
|
||||
"state": "{{ trigger.to_state.state }}",
|
||||
"step": 1,
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"set_value": {
|
||||
"service": "input_number.set_value",
|
||||
"data_template": {
|
||||
"entity_id": _VALUE_INPUT_NUMBER,
|
||||
"value": "{{ value }}",
|
||||
},
|
||||
},
|
||||
"icon": "{% if ((trigger.to_state.state or 0) | int) > 50 %}mdi:greater{% else %}mdi:less{% endif %}",
|
||||
},
|
||||
}
|
||||
CONF_PICTURE: "{% if states.number.test_state.state == '1' %}check.jpg{% endif %}",
|
||||
**TEST_REQUIRED,
|
||||
},
|
||||
)
|
||||
ATTR_ENTITY_PICTURE,
|
||||
"check.jpg",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("setup_number")
|
||||
async def test_templated_optional_config(
|
||||
hass: HomeAssistant,
|
||||
attribute: str,
|
||||
expected: str,
|
||||
initial_expected_state: str | None,
|
||||
) -> None:
|
||||
"""Test optional config templates."""
|
||||
state = hass.states.get(_TEST_NUMBER)
|
||||
assert state.attributes.get(attribute) == initial_expected_state
|
||||
|
||||
hass.states.async_set(_VALUE_INPUT_NUMBER, 49)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
state = hass.states.async_set(TEST_STATE_ENTITY_ID, "1")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(_TEST_NUMBER)
|
||||
assert float(state.state) == 49
|
||||
assert state.attributes[ATTR_ICON] == "mdi:less"
|
||||
|
||||
await hass.services.async_call(
|
||||
INPUT_NUMBER_DOMAIN,
|
||||
INPUT_NUMBER_SERVICE_SET_VALUE,
|
||||
{CONF_ENTITY_ID: _VALUE_INPUT_NUMBER, INPUT_NUMBER_ATTR_VALUE: 51},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(_TEST_NUMBER)
|
||||
assert float(state.state) == 51
|
||||
assert state.attributes[ATTR_ICON] == "mdi:greater"
|
||||
assert state.attributes[attribute] == expected
|
||||
|
||||
|
||||
async def test_device_id(
|
||||
|
@ -21,7 +21,15 @@ from homeassistant.components.select import (
|
||||
SERVICE_SELECT_OPTION as SELECT_SERVICE_SELECT_OPTION,
|
||||
)
|
||||
from homeassistant.components.template import DOMAIN
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_ICON, CONF_ENTITY_ID, STATE_UNKNOWN
|
||||
from homeassistant.components.template.const import CONF_PICTURE
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_ENTITY_PICTURE,
|
||||
ATTR_ICON,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_ICON,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import Context, HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.setup import async_setup_component
|
||||
@ -34,6 +42,24 @@ _TEST_OBJECT_ID = "template_select"
|
||||
_TEST_SELECT = f"select.{_TEST_OBJECT_ID}"
|
||||
# Represent for select's current_option
|
||||
_OPTION_INPUT_SELECT = "input_select.option"
|
||||
TEST_STATE_ENTITY_ID = "select.test_state"
|
||||
|
||||
TEST_STATE_TRIGGER = {
|
||||
"trigger": {
|
||||
"trigger": "state",
|
||||
"entity_id": [_OPTION_INPUT_SELECT, TEST_STATE_ENTITY_ID],
|
||||
},
|
||||
"variables": {"triggering_entity": "{{ trigger.entity_id }}"},
|
||||
"action": [
|
||||
{"event": "action_event", "event_data": {"what": "{{ triggering_entity }}"}}
|
||||
],
|
||||
}
|
||||
|
||||
TEST_OPTIONS = {
|
||||
"state": "test",
|
||||
"options": "{{ ['test', 'yes', 'no'] }}",
|
||||
"select_option": [],
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_modern_format(
|
||||
@ -54,6 +80,24 @@ async def async_setup_modern_format(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def async_setup_trigger_format(
|
||||
hass: HomeAssistant, count: int, select_config: dict[str, Any]
|
||||
) -> None:
|
||||
"""Do setup of select integration via trigger format."""
|
||||
config = {"template": {**TEST_STATE_TRIGGER, "select": select_config}}
|
||||
|
||||
with assert_setup_component(count, template.DOMAIN):
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
template.DOMAIN,
|
||||
config,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def setup_select(
|
||||
hass: HomeAssistant,
|
||||
@ -66,6 +110,10 @@ async def setup_select(
|
||||
await async_setup_modern_format(
|
||||
hass, count, {"name": _TEST_OBJECT_ID, **select_config}
|
||||
)
|
||||
if style == ConfigurationStyle.TRIGGER:
|
||||
await async_setup_trigger_format(
|
||||
hass, count, {"name": _TEST_OBJECT_ID, **select_config}
|
||||
)
|
||||
|
||||
|
||||
async def test_setup_config_entry(
|
||||
@ -395,138 +443,49 @@ def _verify(
|
||||
assert attributes.get(SELECT_ATTR_OPTIONS) == expected_options
|
||||
|
||||
|
||||
async def test_template_icon_with_entities(hass: HomeAssistant) -> None:
|
||||
"""Test templates with values from other entities."""
|
||||
with assert_setup_component(1, "input_select"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"input_select",
|
||||
@pytest.mark.parametrize("count", [1])
|
||||
@pytest.mark.parametrize(
|
||||
("style", "initial_expected_state"),
|
||||
[(ConfigurationStyle.MODERN, ""), (ConfigurationStyle.TRIGGER, None)],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("select_config", "attribute", "expected"),
|
||||
[
|
||||
(
|
||||
{
|
||||
"input_select": {
|
||||
"option": {
|
||||
"options": ["a", "b"],
|
||||
"initial": "a",
|
||||
"name": "Option",
|
||||
},
|
||||
}
|
||||
**TEST_OPTIONS,
|
||||
CONF_ICON: "{% if states.select.test_state.state == 'yes' %}mdi:check{% endif %}",
|
||||
},
|
||||
)
|
||||
|
||||
with assert_setup_component(1, "template"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"template",
|
||||
ATTR_ICON,
|
||||
"mdi:check",
|
||||
),
|
||||
(
|
||||
{
|
||||
"template": {
|
||||
"unique_id": "b",
|
||||
"select": {
|
||||
"state": f"{{{{ states('{_OPTION_INPUT_SELECT}') }}}}",
|
||||
"options": f"{{{{ state_attr('{_OPTION_INPUT_SELECT}', '{INPUT_SELECT_ATTR_OPTIONS}') }}}}",
|
||||
"select_option": {
|
||||
"service": "input_select.select_option",
|
||||
"data": {
|
||||
"entity_id": _OPTION_INPUT_SELECT,
|
||||
"option": "{{ option }}",
|
||||
},
|
||||
},
|
||||
"optimistic": True,
|
||||
"unique_id": "a",
|
||||
"icon": f"{{% if (states('{_OPTION_INPUT_SELECT}') == 'a') %}}mdi:greater{{% else %}}mdi:less{{% endif %}}",
|
||||
},
|
||||
}
|
||||
**TEST_OPTIONS,
|
||||
CONF_PICTURE: "{% if states.select.test_state.state == 'yes' %}check.jpg{% endif %}",
|
||||
},
|
||||
)
|
||||
ATTR_ENTITY_PICTURE,
|
||||
"check.jpg",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("setup_select")
|
||||
async def test_templated_optional_config(
|
||||
hass: HomeAssistant,
|
||||
attribute: str,
|
||||
expected: str,
|
||||
initial_expected_state: str | None,
|
||||
) -> None:
|
||||
"""Test optional config templates."""
|
||||
state = hass.states.get(_TEST_SELECT)
|
||||
assert state.attributes.get(attribute) == initial_expected_state
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
state = hass.states.async_set(TEST_STATE_ENTITY_ID, "yes")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(_TEST_SELECT)
|
||||
assert state.state == "a"
|
||||
assert state.attributes[ATTR_ICON] == "mdi:greater"
|
||||
|
||||
await hass.services.async_call(
|
||||
INPUT_SELECT_DOMAIN,
|
||||
INPUT_SELECT_SERVICE_SELECT_OPTION,
|
||||
{CONF_ENTITY_ID: _OPTION_INPUT_SELECT, INPUT_SELECT_ATTR_OPTION: "b"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(_TEST_SELECT)
|
||||
assert state.state == "b"
|
||||
assert state.attributes[ATTR_ICON] == "mdi:less"
|
||||
|
||||
|
||||
async def test_template_icon_with_trigger(hass: HomeAssistant) -> None:
|
||||
"""Test trigger based template select."""
|
||||
with assert_setup_component(1, "input_select"):
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"input_select",
|
||||
{
|
||||
"input_select": {
|
||||
"option": {
|
||||
"options": ["a", "b"],
|
||||
"initial": "a",
|
||||
"name": "Option",
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
"template",
|
||||
{
|
||||
"template": {
|
||||
"trigger": {"platform": "state", "entity_id": _OPTION_INPUT_SELECT},
|
||||
"select": {
|
||||
"unique_id": "b",
|
||||
"state": "{{ trigger.to_state.state }}",
|
||||
"options": f"{{{{ state_attr('{_OPTION_INPUT_SELECT}', '{INPUT_SELECT_ATTR_OPTIONS}') }}}}",
|
||||
"select_option": {
|
||||
"service": "input_select.select_option",
|
||||
"data": {
|
||||
"entity_id": _OPTION_INPUT_SELECT,
|
||||
"option": "{{ option }}",
|
||||
},
|
||||
},
|
||||
"optimistic": True,
|
||||
"icon": "{% if (trigger.to_state.state or '') == 'a' %}mdi:greater{% else %}mdi:less{% endif %}",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.services.async_call(
|
||||
INPUT_SELECT_DOMAIN,
|
||||
INPUT_SELECT_SERVICE_SELECT_OPTION,
|
||||
{CONF_ENTITY_ID: _OPTION_INPUT_SELECT, INPUT_SELECT_ATTR_OPTION: "b"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(_TEST_SELECT)
|
||||
assert state is not None
|
||||
assert state.state == "b"
|
||||
assert state.attributes[ATTR_ICON] == "mdi:less"
|
||||
|
||||
await hass.services.async_call(
|
||||
INPUT_SELECT_DOMAIN,
|
||||
INPUT_SELECT_SERVICE_SELECT_OPTION,
|
||||
{CONF_ENTITY_ID: _OPTION_INPUT_SELECT, INPUT_SELECT_ATTR_OPTION: "a"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(_TEST_SELECT)
|
||||
assert state.state == "a"
|
||||
assert state.attributes[ATTR_ICON] == "mdi:greater"
|
||||
assert state.attributes[attribute] == expected
|
||||
|
||||
|
||||
async def test_device_id(
|
||||
|
@ -5,6 +5,8 @@ from typing import Any
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components import template
|
||||
from homeassistant.components.template.const import CONF_PICTURE
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_WEATHER_APPARENT_TEMPERATURE,
|
||||
ATTR_WEATHER_CLOUD_COVERAGE,
|
||||
@ -21,12 +23,21 @@ from homeassistant.components.weather import (
|
||||
SERVICE_GET_FORECASTS,
|
||||
Forecast,
|
||||
)
|
||||
from homeassistant.const import ATTR_ATTRIBUTION, STATE_UNAVAILABLE, STATE_UNKNOWN
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
ATTR_ENTITY_PICTURE,
|
||||
ATTR_ICON,
|
||||
CONF_ICON,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import Context, HomeAssistant, State
|
||||
from homeassistant.helpers.restore_state import STORAGE_KEY as RESTORE_STATE_KEY
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .conftest import ConfigurationStyle
|
||||
|
||||
from tests.common import (
|
||||
assert_setup_component,
|
||||
async_mock_restore_state_shutdown_restart,
|
||||
@ -35,6 +46,80 @@ from tests.common import (
|
||||
|
||||
ATTR_FORECAST = "forecast"
|
||||
|
||||
TEST_OBJECT_ID = "template_weather"
|
||||
TEST_WEATHER = f"weather.{TEST_OBJECT_ID}"
|
||||
TEST_STATE_ENTITY_ID = "weather.test_state"
|
||||
|
||||
TEST_STATE_TRIGGER = {
|
||||
"trigger": {
|
||||
"trigger": "state",
|
||||
"entity_id": [TEST_STATE_ENTITY_ID],
|
||||
},
|
||||
"variables": {"triggering_entity": "{{ trigger.entity_id }}"},
|
||||
"action": [
|
||||
{"event": "action_event", "event_data": {"what": "{{ triggering_entity }}"}}
|
||||
],
|
||||
}
|
||||
TEST_REQUIRED = {
|
||||
"condition_template": "cloudy",
|
||||
"temperature_template": "{{ 20 }}",
|
||||
"humidity_template": "{{ 25 }}",
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_modern_format(
|
||||
hass: HomeAssistant, count: int, weather_config: dict[str, Any]
|
||||
) -> None:
|
||||
"""Do setup of weather integration via new format."""
|
||||
config = {"template": {"weather": weather_config}}
|
||||
|
||||
with assert_setup_component(count, template.DOMAIN):
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
template.DOMAIN,
|
||||
config,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def async_setup_trigger_format(
|
||||
hass: HomeAssistant, count: int, weather_config: dict[str, Any]
|
||||
) -> None:
|
||||
"""Do setup of weather integration via trigger format."""
|
||||
config = {"template": {**TEST_STATE_TRIGGER, "weather": weather_config}}
|
||||
|
||||
with assert_setup_component(count, template.DOMAIN):
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
template.DOMAIN,
|
||||
config,
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def setup_weather(
|
||||
hass: HomeAssistant,
|
||||
count: int,
|
||||
style: ConfigurationStyle,
|
||||
weather_config: dict[str, Any],
|
||||
) -> None:
|
||||
"""Do setup of weather integration."""
|
||||
if style == ConfigurationStyle.MODERN:
|
||||
await async_setup_modern_format(
|
||||
hass, count, {"name": TEST_OBJECT_ID, **weather_config}
|
||||
)
|
||||
if style == ConfigurationStyle.TRIGGER:
|
||||
await async_setup_trigger_format(
|
||||
hass, count, {"name": TEST_OBJECT_ID, **weather_config}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)])
|
||||
@pytest.mark.parametrize(
|
||||
@ -990,3 +1075,48 @@ async def test_new_style_template_state_text(hass: HomeAssistant) -> None:
|
||||
assert state is not None
|
||||
assert state.state == "sunny"
|
||||
assert state.attributes.get(v_attr) == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("count", [1])
|
||||
@pytest.mark.parametrize(
|
||||
("style", "initial_expected_state"),
|
||||
[(ConfigurationStyle.MODERN, ""), (ConfigurationStyle.TRIGGER, None)],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("weather_config", "attribute", "expected"),
|
||||
[
|
||||
(
|
||||
{
|
||||
CONF_ICON: "{% if states.weather.test_state.state == 'sunny' %}mdi:check{% endif %}",
|
||||
**TEST_REQUIRED,
|
||||
},
|
||||
ATTR_ICON,
|
||||
"mdi:check",
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_PICTURE: "{% if states.weather.test_state.state == 'sunny' %}check.jpg{% endif %}",
|
||||
**TEST_REQUIRED,
|
||||
},
|
||||
ATTR_ENTITY_PICTURE,
|
||||
"check.jpg",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("setup_weather")
|
||||
async def test_templated_optional_config(
|
||||
hass: HomeAssistant,
|
||||
attribute: str,
|
||||
expected: str,
|
||||
initial_expected_state: str | None,
|
||||
) -> None:
|
||||
"""Test optional config templates."""
|
||||
state = hass.states.get(TEST_WEATHER)
|
||||
assert state.attributes.get(attribute) == initial_expected_state
|
||||
|
||||
state = hass.states.async_set(TEST_STATE_ENTITY_ID, "sunny")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(TEST_WEATHER)
|
||||
|
||||
assert state.attributes[attribute] == expected
|
||||
|
@ -0,0 +1,16 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
alarm_control_panel:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
state: "{{ 'armed_home' if is_state(sensor,'on') else 'disarmed' }}"
|
@ -0,0 +1,16 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
binary_sensor:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
state: "{{ is_state(sensor, 'on') }}"
|
@ -0,0 +1,18 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
cover:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
state: "{{ is_state(sensor,'on') }}"
|
||||
open_cover: []
|
||||
close_cover: []
|
@ -0,0 +1,18 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
fan:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
state: "{{ is_state(sensor,'on') }}"
|
||||
turn_on: []
|
||||
turn_off: []
|
@ -0,0 +1,16 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
image:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
url: "{{ states(sensor) }}"
|
@ -0,0 +1,18 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
light:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
state: "{{ is_state(sensor,'on') }}"
|
||||
turn_on: []
|
||||
turn_off: []
|
@ -0,0 +1,18 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
lock:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
state: "{{ is_state(sensor,'on') }}"
|
||||
lock: []
|
||||
unlock: []
|
@ -0,0 +1,18 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
number:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
state: "{{ states(sensor) }}"
|
||||
set_value: []
|
||||
step: 1
|
@ -0,0 +1,18 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
select:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
state: "{{ states(sensor) }}"
|
||||
options: "{{ ['option1', 'option2'] }}"
|
||||
select_option: []
|
@ -0,0 +1,16 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
sensor:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
state: "{{ states(sensor) }}"
|
@ -0,0 +1,18 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
switch:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
state: "{{ is_state(sensor,'on') }}"
|
||||
turn_on: []
|
||||
turn_off: []
|
@ -0,0 +1,17 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
vacuum:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
state: "{{ states(sensor) }}"
|
||||
start: []
|
@ -0,0 +1,18 @@
|
||||
blueprint:
|
||||
name: Test With Variables
|
||||
description: Creates a test with variables
|
||||
domain: template
|
||||
input:
|
||||
sensor:
|
||||
name: Sensor Entity
|
||||
description: The sensor entity
|
||||
selector:
|
||||
entity:
|
||||
domain: sensor
|
||||
variables:
|
||||
sensor: !input sensor
|
||||
weather:
|
||||
availability: "{{ sensor | has_value }}"
|
||||
condition_template: "{{ states(sensor) }}"
|
||||
temperature_template: "{{ 20 }}"
|
||||
humidity_template: "{{ 25 }}"
|
Reference in New Issue
Block a user