mirror of
https://github.com/home-assistant/core.git
synced 2026-03-17 16:32:04 +01:00
Compare commits
10 Commits
epenet/202
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a2fc97696 | ||
|
|
447d616097 | ||
|
|
d3102e718d | ||
|
|
69ee49735a | ||
|
|
35a99dd4a4 | ||
|
|
51c3397be8 | ||
|
|
57f0fd2ed2 | ||
|
|
fa7a216afe | ||
|
|
20f4426e1d | ||
|
|
ba30563772 |
@@ -89,18 +89,18 @@
|
||||
"step": {
|
||||
"advanced": {
|
||||
"data": {
|
||||
"api_key": "API Token",
|
||||
"api_key": "API token",
|
||||
"api_user": "User ID",
|
||||
"url": "[%key:common::config_flow::data::url%]",
|
||||
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
|
||||
},
|
||||
"data_description": {
|
||||
"api_key": "API Token of the Habitica account",
|
||||
"api_key": "API token of the Habitica account",
|
||||
"api_user": "User ID of your Habitica account",
|
||||
"url": "URL of the Habitica installation to connect to. Defaults to `{default_url}`",
|
||||
"verify_ssl": "Enable SSL certificate verification for secure connections. Disable only if connecting to a Habitica instance using a self-signed certificate"
|
||||
},
|
||||
"description": "You can retrieve your `User ID` and `API Token` from [**Settings -> Site Data**]({site_data}) on Habitica or the instance you want to connect to",
|
||||
"description": "You can retrieve your 'User ID' and 'API token' from [**Settings -> Site Data**]({site_data}) on Habitica or the instance you want to connect to",
|
||||
"title": "[%key:component::habitica::config::step::user::menu_options::advanced%]"
|
||||
},
|
||||
"login": {
|
||||
@@ -126,7 +126,7 @@
|
||||
"api_key": "[%key:component::habitica::config::step::advanced::data_description::api_key%]"
|
||||
},
|
||||
"description": "Enter your new API token below. You can find it in Habitica under 'Settings -> Site Data'",
|
||||
"name": "Re-authorize via API Token"
|
||||
"name": "Re-authorize via API token"
|
||||
},
|
||||
"reauth_login": {
|
||||
"data": {
|
||||
|
||||
@@ -203,105 +203,80 @@ class MoldIndicator(SensorEntity):
|
||||
def _async_setup_sensor(self) -> None:
|
||||
"""Set up the sensor and start tracking state changes."""
|
||||
|
||||
@callback
|
||||
def mold_indicator_sensors_state_listener(
|
||||
event: Event[EventStateChangedData],
|
||||
) -> None:
|
||||
"""Handle for state changes for dependent sensors."""
|
||||
new_state = event.data["new_state"]
|
||||
old_state = event.data["old_state"]
|
||||
entity = event.data["entity_id"]
|
||||
_LOGGER.debug(
|
||||
"Sensor state change for %s that had old state %s and new state %s",
|
||||
entity,
|
||||
old_state,
|
||||
new_state,
|
||||
)
|
||||
|
||||
if self._update_sensor(entity, old_state, new_state):
|
||||
if self._preview_callback:
|
||||
calculated_state = self._async_calculate_state()
|
||||
self._preview_callback(
|
||||
calculated_state.state, calculated_state.attributes
|
||||
)
|
||||
# only write state to the state machine if we are not in preview mode
|
||||
else:
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
@callback
|
||||
def mold_indicator_startup() -> None:
|
||||
"""Add listeners and get 1st state."""
|
||||
_LOGGER.debug("Startup for %s", self.entity_id)
|
||||
|
||||
self.async_on_remove(
|
||||
async_track_state_change_event(
|
||||
self.hass,
|
||||
list(self._entities.values()),
|
||||
mold_indicator_sensors_state_listener,
|
||||
self._entities.values(),
|
||||
self._async_mold_indicator_sensor_state_listener,
|
||||
)
|
||||
)
|
||||
|
||||
# Replay current state of source entities
|
||||
for entity_id in self._entities.values():
|
||||
state = self.hass.states.get(entity_id)
|
||||
state_event: Event[EventStateChangedData] = Event(
|
||||
"", {"entity_id": entity_id, "new_state": state, "old_state": None}
|
||||
)
|
||||
self._async_mold_indicator_sensor_state_listener(
|
||||
state_event, update_state=False
|
||||
)
|
||||
|
||||
# Read initial state
|
||||
indoor_temp = self.hass.states.get(self._entities[CONF_INDOOR_TEMP])
|
||||
outdoor_temp = self.hass.states.get(self._entities[CONF_OUTDOOR_TEMP])
|
||||
indoor_hum = self.hass.states.get(self._entities[CONF_INDOOR_HUMIDITY])
|
||||
self._recalculate()
|
||||
|
||||
schedule_update = self._update_sensor(
|
||||
self._entities[CONF_INDOOR_TEMP], None, indoor_temp
|
||||
)
|
||||
if self._preview_callback:
|
||||
calculated_state = self._async_calculate_state()
|
||||
self._preview_callback(calculated_state.state, calculated_state.attributes)
|
||||
|
||||
schedule_update = (
|
||||
False
|
||||
if not self._update_sensor(
|
||||
self._entities[CONF_OUTDOOR_TEMP], None, outdoor_temp
|
||||
)
|
||||
else schedule_update
|
||||
)
|
||||
@callback
|
||||
def _async_mold_indicator_sensor_state_listener(
|
||||
self, event: Event[EventStateChangedData], update_state: bool = True
|
||||
) -> None:
|
||||
"""Handle state changes for dependent sensors."""
|
||||
entity_id = event.data["entity_id"]
|
||||
new_state = event.data["new_state"]
|
||||
|
||||
schedule_update = (
|
||||
False
|
||||
if not self._update_sensor(
|
||||
self._entities[CONF_INDOOR_HUMIDITY], None, indoor_hum
|
||||
)
|
||||
else schedule_update
|
||||
)
|
||||
_LOGGER.debug(
|
||||
"Sensor state change for %s that had old state %s and new state %s",
|
||||
entity_id,
|
||||
event.data["old_state"],
|
||||
new_state,
|
||||
)
|
||||
|
||||
if schedule_update and not self._preview_callback:
|
||||
self.async_schedule_update_ha_state(True)
|
||||
if self._preview_callback:
|
||||
# re-calculate dewpoint and mold indicator
|
||||
self._calc_dewpoint()
|
||||
self._calc_moldindicator()
|
||||
if self._attr_native_value is None:
|
||||
self._attr_available = False
|
||||
else:
|
||||
self._attr_available = True
|
||||
calculated_state = self._async_calculate_state()
|
||||
self._preview_callback(
|
||||
calculated_state.state, calculated_state.attributes
|
||||
)
|
||||
|
||||
mold_indicator_startup()
|
||||
|
||||
def _update_sensor(
|
||||
self, entity: str, old_state: State | None, new_state: State | None
|
||||
) -> bool:
|
||||
"""Update information based on new sensor states."""
|
||||
_LOGGER.debug("Sensor update for %s", entity)
|
||||
if new_state is None:
|
||||
return False
|
||||
|
||||
# If old_state is not set and new state is unknown then it means
|
||||
# that the sensor just started up
|
||||
if old_state is None and new_state.state == STATE_UNKNOWN:
|
||||
return False
|
||||
|
||||
if entity == self._entities[CONF_INDOOR_TEMP]:
|
||||
# update state depending on which sensor changed
|
||||
if entity_id == self._entities[CONF_INDOOR_TEMP]:
|
||||
self._indoor_temp = self._get_temperature_from_state(new_state)
|
||||
elif entity == self._entities[CONF_OUTDOOR_TEMP]:
|
||||
elif entity_id == self._entities[CONF_OUTDOOR_TEMP]:
|
||||
self._outdoor_temp = self._get_temperature_from_state(new_state)
|
||||
elif entity == self._entities[CONF_INDOOR_HUMIDITY]:
|
||||
elif entity_id == self._entities[CONF_INDOOR_HUMIDITY]:
|
||||
self._indoor_hum = self._get_humidity_from_state(new_state)
|
||||
|
||||
return True
|
||||
if not update_state:
|
||||
return
|
||||
|
||||
self._recalculate()
|
||||
|
||||
if self._preview_callback:
|
||||
calculated_state = self._async_calculate_state()
|
||||
self._preview_callback(calculated_state.state, calculated_state.attributes)
|
||||
# only write state to the state machine if we are not in preview mode
|
||||
else:
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def _recalculate(self) -> None:
|
||||
"""Recalculate mold indicator from cached sensor values."""
|
||||
# Check if all sensors are available
|
||||
if None in (self._indoor_temp, self._indoor_hum, self._outdoor_temp):
|
||||
self._attr_available = False
|
||||
self._attr_native_value = None
|
||||
self._dewpoint = None
|
||||
self._crit_temp = None
|
||||
return
|
||||
|
||||
# Calculate dewpoint and mold indicator
|
||||
self._calc_dewpoint()
|
||||
self._calc_moldindicator()
|
||||
self._attr_available = self._attr_native_value is not None
|
||||
|
||||
def _get_value_from_state(
|
||||
self,
|
||||
@@ -376,26 +351,6 @@ class MoldIndicator(SensorEntity):
|
||||
|
||||
return self._get_value_from_state(state, validate_humidity)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Calculate latest state."""
|
||||
_LOGGER.debug("Update state for %s", self.entity_id)
|
||||
# check all sensors
|
||||
if None in (self._indoor_temp, self._indoor_hum, self._outdoor_temp):
|
||||
self._attr_available = False
|
||||
self._dewpoint = None
|
||||
self._crit_temp = None
|
||||
return
|
||||
|
||||
# re-calculate dewpoint and mold indicator
|
||||
self._calc_dewpoint()
|
||||
self._calc_moldindicator()
|
||||
if self._attr_native_value is None:
|
||||
self._attr_available = False
|
||||
self._dewpoint = None
|
||||
self._crit_temp = None
|
||||
else:
|
||||
self._attr_available = True
|
||||
|
||||
def _calc_dewpoint(self) -> None:
|
||||
"""Calculate the dewpoint for the indoor air."""
|
||||
# Use magnus approximation to calculate the dew point
|
||||
|
||||
@@ -21,8 +21,8 @@ from homeassistant.util.percentage import (
|
||||
)
|
||||
from homeassistant.util.scaling import int_states_in_range
|
||||
|
||||
from . import PranaConfigEntry
|
||||
from .entity import PranaBaseEntity, PranaCoordinator
|
||||
from .coordinator import PranaConfigEntry, PranaCoordinator
|
||||
from .entity import PranaBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import PranaConfigEntry
|
||||
from .entity import PranaBaseEntity, PranaCoordinator
|
||||
from .coordinator import PranaConfigEntry, PranaCoordinator
|
||||
from .entity import PranaBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from homeassistant.components.switch import SwitchEntity, SwitchEntityDescriptio
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import PranaConfigEntry, PranaCoordinator
|
||||
from .coordinator import PranaConfigEntry, PranaCoordinator
|
||||
from .entity import PranaBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@@ -105,6 +105,9 @@
|
||||
"robot_cleaner_driving_mode": {
|
||||
"default": "mdi:car-cog"
|
||||
},
|
||||
"robot_cleaner_sound_mode": {
|
||||
"default": "mdi:bell-cog"
|
||||
},
|
||||
"robot_cleaner_water_spray_level": {
|
||||
"default": "mdi:spray-bottle"
|
||||
},
|
||||
|
||||
@@ -26,6 +26,12 @@ LAMP_TO_HA = {
|
||||
"off": "off",
|
||||
}
|
||||
|
||||
SOUND_MODE_TO_HA = {
|
||||
"voice": "voice",
|
||||
"beep": "tone",
|
||||
"mute": "mute",
|
||||
}
|
||||
|
||||
DRIVING_MODE_TO_HA = {
|
||||
"areaThenWalls": "area_then_walls",
|
||||
"wallFirst": "walls_first",
|
||||
@@ -244,6 +250,16 @@ CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
value_is_integer=True,
|
||||
),
|
||||
Capability.SAMSUNG_CE_ROBOT_CLEANER_SYSTEM_SOUND_MODE: SmartThingsSelectDescription(
|
||||
key=Capability.SAMSUNG_CE_ROBOT_CLEANER_SYSTEM_SOUND_MODE,
|
||||
translation_key="robot_cleaner_sound_mode",
|
||||
options_attribute=Attribute.SUPPORTED_SOUND_MODES,
|
||||
status_attribute=Attribute.SOUND_MODE,
|
||||
command=Command.SET_SOUND_MODE,
|
||||
options_map=SOUND_MODE_TO_HA,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
Capability.SAMSUNG_CE_ROBOT_CLEANER_CLEANING_TYPE: SmartThingsSelectDescription(
|
||||
key=Capability.SAMSUNG_CE_ROBOT_CLEANER_CLEANING_TYPE,
|
||||
translation_key="robot_cleaner_cleaning_type",
|
||||
|
||||
@@ -254,6 +254,14 @@
|
||||
"walls_first": "Walls first"
|
||||
}
|
||||
},
|
||||
"robot_cleaner_sound_mode": {
|
||||
"name": "Sound mode",
|
||||
"state": {
|
||||
"mute": "Mute",
|
||||
"tone": "Tone",
|
||||
"voice": "Voice"
|
||||
}
|
||||
},
|
||||
"robot_cleaner_water_spray_level": {
|
||||
"name": "Water level",
|
||||
"state": {
|
||||
|
||||
@@ -195,9 +195,22 @@ class TeslaFleetEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]])
|
||||
except TeslaFleetError as e:
|
||||
raise UpdateFailed(e.message) from e
|
||||
|
||||
if not isinstance(data, dict):
|
||||
LOGGER.debug(
|
||||
"%s got unexpected live status response type: %s",
|
||||
self.name,
|
||||
type(data).__name__,
|
||||
)
|
||||
return self.data
|
||||
|
||||
# Convert Wall Connectors from array to dict
|
||||
wall_connectors = data.get("wall_connectors")
|
||||
if not isinstance(wall_connectors, list):
|
||||
wall_connectors = []
|
||||
data["wall_connectors"] = {
|
||||
wc["din"]: wc for wc in (data.get("wall_connectors") or [])
|
||||
wc["din"]: wc
|
||||
for wc in wall_connectors
|
||||
if isinstance(wc, dict) and "din" in wc
|
||||
}
|
||||
|
||||
self.updated_once = True
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, cast
|
||||
from typing import TYPE_CHECKING, TypedDict, cast
|
||||
|
||||
from aiohttp.client_exceptions import ClientError
|
||||
import tibber
|
||||
@@ -38,6 +39,58 @@ FIVE_YEARS = 5 * 365 * 24
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TibberHomeData(TypedDict):
|
||||
"""Data for a Tibber home used by the price sensor."""
|
||||
|
||||
currency: str
|
||||
price_unit: str
|
||||
current_price: float | None
|
||||
current_price_time: datetime | None
|
||||
intraday_price_ranking: float | None
|
||||
max_price: float
|
||||
avg_price: float
|
||||
min_price: float
|
||||
off_peak_1: float
|
||||
peak: float
|
||||
off_peak_2: float
|
||||
month_cost: float | None
|
||||
peak_hour: float | None
|
||||
peak_hour_time: datetime | None
|
||||
month_cons: float | None
|
||||
app_nickname: str | None
|
||||
grid_company: str | None
|
||||
estimated_annual_consumption: int | None
|
||||
|
||||
|
||||
def _build_home_data(home: tibber.TibberHome) -> TibberHomeData:
|
||||
"""Build TibberHomeData from a TibberHome for the price sensor."""
|
||||
current_price, last_updated, price_rank = home.current_price_data()
|
||||
attributes = home.current_attributes()
|
||||
result: TibberHomeData = {
|
||||
"currency": home.currency,
|
||||
"price_unit": home.price_unit,
|
||||
"current_price": current_price,
|
||||
"current_price_time": last_updated,
|
||||
"intraday_price_ranking": price_rank,
|
||||
"max_price": attributes["max_price"],
|
||||
"avg_price": attributes["avg_price"],
|
||||
"min_price": attributes["min_price"],
|
||||
"off_peak_1": attributes["off_peak_1"],
|
||||
"peak": attributes["peak"],
|
||||
"off_peak_2": attributes["off_peak_2"],
|
||||
"month_cost": home.month_cost,
|
||||
"peak_hour": home.peak_hour,
|
||||
"peak_hour_time": home.peak_hour_time,
|
||||
"month_cons": home.month_cons,
|
||||
"app_nickname": home.info["viewer"]["home"].get("appNickname"),
|
||||
"grid_company": home.info["viewer"]["home"]["meteringPointData"]["gridCompany"],
|
||||
"estimated_annual_consumption": home.info["viewer"]["home"][
|
||||
"meteringPointData"
|
||||
]["estimatedAnnualConsumption"],
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
class TibberDataCoordinator(DataUpdateCoordinator[None]):
|
||||
"""Handle Tibber data and insert statistics."""
|
||||
|
||||
@@ -57,13 +110,16 @@ class TibberDataCoordinator(DataUpdateCoordinator[None]):
|
||||
name=f"Tibber {tibber_connection.name}",
|
||||
update_interval=timedelta(minutes=20),
|
||||
)
|
||||
self._tibber_connection = tibber_connection
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Update data via API."""
|
||||
tibber_connection = await self.config_entry.runtime_data.async_get_client(
|
||||
self.hass
|
||||
)
|
||||
|
||||
try:
|
||||
await self._tibber_connection.fetch_consumption_data_active_homes()
|
||||
await self._tibber_connection.fetch_production_data_active_homes()
|
||||
await tibber_connection.fetch_consumption_data_active_homes()
|
||||
await tibber_connection.fetch_production_data_active_homes()
|
||||
await self._insert_statistics()
|
||||
except tibber.RetryableHttpExceptionError as err:
|
||||
raise UpdateFailed(f"Error communicating with API ({err.status})") from err
|
||||
@@ -75,7 +131,10 @@ class TibberDataCoordinator(DataUpdateCoordinator[None]):
|
||||
|
||||
async def _insert_statistics(self) -> None:
|
||||
"""Insert Tibber statistics."""
|
||||
for home in self._tibber_connection.get_homes():
|
||||
tibber_connection = await self.config_entry.runtime_data.async_get_client(
|
||||
self.hass
|
||||
)
|
||||
for home in tibber_connection.get_homes():
|
||||
sensors: list[tuple[str, bool, str | None, str]] = []
|
||||
if home.hourly_consumption_data:
|
||||
sensors.append(
|
||||
@@ -194,6 +253,76 @@ class TibberDataCoordinator(DataUpdateCoordinator[None]):
|
||||
async_add_external_statistics(self.hass, metadata, statistics)
|
||||
|
||||
|
||||
class TibberPriceCoordinator(DataUpdateCoordinator[dict[str, TibberHomeData]]):
|
||||
"""Handle Tibber price data and insert statistics."""
|
||||
|
||||
config_entry: TibberConfigEntry
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: TibberConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize the price coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=config_entry,
|
||||
name=f"{DOMAIN} price",
|
||||
update_interval=timedelta(minutes=1),
|
||||
)
|
||||
|
||||
def _seconds_until_next_15_minute(self) -> float:
|
||||
"""Return seconds until the next 15-minute boundary (0, 15, 30, 45) in UTC."""
|
||||
now = dt_util.utcnow()
|
||||
next_minute = ((now.minute // 15) + 1) * 15
|
||||
if next_minute >= 60:
|
||||
next_run = now.replace(minute=0, second=0, microsecond=0) + timedelta(
|
||||
hours=1
|
||||
)
|
||||
else:
|
||||
next_run = now.replace(
|
||||
minute=next_minute, second=0, microsecond=0, tzinfo=dt_util.UTC
|
||||
)
|
||||
return (next_run - now).total_seconds()
|
||||
|
||||
async def _async_update_data(self) -> dict[str, TibberHomeData]:
|
||||
"""Update data via API and return per-home data for sensors."""
|
||||
tibber_connection = await self.config_entry.runtime_data.async_get_client(
|
||||
self.hass
|
||||
)
|
||||
active_homes = tibber_connection.get_homes(only_active=True)
|
||||
try:
|
||||
await asyncio.gather(
|
||||
tibber_connection.fetch_consumption_data_active_homes(),
|
||||
tibber_connection.fetch_production_data_active_homes(),
|
||||
)
|
||||
|
||||
now = dt_util.now()
|
||||
homes_to_update = [
|
||||
home
|
||||
for home in active_homes
|
||||
if (
|
||||
(last_data_timestamp := home.last_data_timestamp) is None
|
||||
or (last_data_timestamp - now).total_seconds() < 11 * 3600
|
||||
)
|
||||
]
|
||||
|
||||
if homes_to_update:
|
||||
await asyncio.gather(
|
||||
*(home.update_info_and_price_info() for home in homes_to_update)
|
||||
)
|
||||
except tibber.RetryableHttpExceptionError as err:
|
||||
raise UpdateFailed(f"Error communicating with API ({err.status})") from err
|
||||
except tibber.FatalHttpExceptionError as err:
|
||||
raise UpdateFailed(f"Error communicating with API ({err.status})") from err
|
||||
|
||||
result = {home.home_id: _build_home_data(home) for home in active_homes}
|
||||
|
||||
self.update_interval = timedelta(seconds=self._seconds_until_next_15_minute())
|
||||
return result
|
||||
|
||||
|
||||
class TibberDataAPICoordinator(DataUpdateCoordinator[dict[str, TibberDevice]]):
|
||||
"""Fetch and cache Tibber Data API device capabilities."""
|
||||
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
import datetime
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from random import randrange
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
@@ -42,18 +40,20 @@ from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
)
|
||||
from homeassistant.util import Throttle, dt as dt_util
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER, TibberConfigEntry
|
||||
from .coordinator import TibberDataAPICoordinator, TibberDataCoordinator
|
||||
from .coordinator import (
|
||||
TibberDataAPICoordinator,
|
||||
TibberDataCoordinator,
|
||||
TibberPriceCoordinator,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ICON = "mdi:currency-usd"
|
||||
SCAN_INTERVAL = timedelta(minutes=1)
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
|
||||
PARALLEL_UPDATES = 0
|
||||
TWENTY_MINUTES = 20 * 60
|
||||
|
||||
RT_SENSORS_UNIQUE_ID_MIGRATION = {
|
||||
"accumulated_consumption_last_hour": "accumulated consumption current hour",
|
||||
@@ -610,6 +610,7 @@ async def _async_setup_graphql_sensors(
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
coordinator: TibberDataCoordinator | None = None
|
||||
price_coordinator: TibberPriceCoordinator | None = None
|
||||
entities: list[TibberSensor] = []
|
||||
for home in tibber_connection.get_homes(only_active=False):
|
||||
try:
|
||||
@@ -626,7 +627,9 @@ async def _async_setup_graphql_sensors(
|
||||
raise PlatformNotReady from err
|
||||
|
||||
if home.has_active_subscription:
|
||||
entities.append(TibberSensorElPrice(home))
|
||||
if price_coordinator is None:
|
||||
price_coordinator = TibberPriceCoordinator(hass, entry)
|
||||
entities.append(TibberSensorElPrice(price_coordinator, home))
|
||||
if coordinator is None:
|
||||
coordinator = TibberDataCoordinator(hass, entry, tibber_connection)
|
||||
entities.extend(
|
||||
@@ -737,19 +740,21 @@ class TibberSensor(SensorEntity):
|
||||
return device_info
|
||||
|
||||
|
||||
class TibberSensorElPrice(TibberSensor):
|
||||
class TibberSensorElPrice(TibberSensor, CoordinatorEntity[TibberPriceCoordinator]):
|
||||
"""Representation of a Tibber sensor for el price."""
|
||||
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_translation_key = "electricity_price"
|
||||
|
||||
def __init__(self, tibber_home: TibberHome) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: TibberPriceCoordinator,
|
||||
tibber_home: TibberHome,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(tibber_home=tibber_home)
|
||||
self._last_updated: datetime.datetime | None = None
|
||||
self._spread_load_constant = randrange(TWENTY_MINUTES)
|
||||
|
||||
super().__init__(coordinator=coordinator, tibber_home=tibber_home)
|
||||
self._attr_available = False
|
||||
self._attr_native_unit_of_measurement = tibber_home.price_unit
|
||||
self._attr_extra_state_attributes = {
|
||||
"app_nickname": None,
|
||||
"grid_company": None,
|
||||
@@ -768,51 +773,38 @@ class TibberSensorElPrice(TibberSensor):
|
||||
|
||||
self._device_name = self._home_name
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Get the latest data and updates the states."""
|
||||
now = dt_util.now()
|
||||
if (
|
||||
not self._tibber_home.last_data_timestamp
|
||||
or (self._tibber_home.last_data_timestamp - now).total_seconds()
|
||||
< 10 * 3600 - self._spread_load_constant
|
||||
or not self.available
|
||||
):
|
||||
_LOGGER.debug("Asking for new data")
|
||||
await self._fetch_data()
|
||||
|
||||
elif (
|
||||
self._tibber_home.price_total
|
||||
and self._last_updated
|
||||
and self._last_updated.hour == now.hour
|
||||
and now - self._last_updated < timedelta(minutes=15)
|
||||
and self._tibber_home.last_data_timestamp
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
data = self.coordinator.data
|
||||
if not data or (
|
||||
(home_data := data.get(self._tibber_home.home_id)) is None
|
||||
or (current_price := home_data.get("current_price")) is None
|
||||
):
|
||||
self._attr_available = False
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
res = self._tibber_home.current_price_data()
|
||||
self._attr_native_value, self._last_updated, price_rank = res
|
||||
self._attr_extra_state_attributes["intraday_price_ranking"] = price_rank
|
||||
|
||||
attrs = self._tibber_home.current_attributes()
|
||||
self._attr_extra_state_attributes.update(attrs)
|
||||
self._attr_available = self._attr_native_value is not None
|
||||
self._attr_native_unit_of_measurement = self._tibber_home.price_unit
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
async def _fetch_data(self) -> None:
|
||||
_LOGGER.debug("Fetching data")
|
||||
try:
|
||||
await self._tibber_home.update_info_and_price_info()
|
||||
except TimeoutError, aiohttp.ClientError:
|
||||
return
|
||||
data = self._tibber_home.info["viewer"]["home"]
|
||||
self._attr_extra_state_attributes["app_nickname"] = data["appNickname"]
|
||||
self._attr_extra_state_attributes["grid_company"] = data["meteringPointData"][
|
||||
"gridCompany"
|
||||
self._attr_native_unit_of_measurement = home_data.get(
|
||||
"price_unit", self._tibber_home.price_unit
|
||||
)
|
||||
self._attr_native_value = current_price
|
||||
self._attr_extra_state_attributes["intraday_price_ranking"] = home_data.get(
|
||||
"intraday_price_ranking"
|
||||
)
|
||||
self._attr_extra_state_attributes["max_price"] = home_data["max_price"]
|
||||
self._attr_extra_state_attributes["avg_price"] = home_data["avg_price"]
|
||||
self._attr_extra_state_attributes["min_price"] = home_data["min_price"]
|
||||
self._attr_extra_state_attributes["off_peak_1"] = home_data["off_peak_1"]
|
||||
self._attr_extra_state_attributes["peak"] = home_data["peak"]
|
||||
self._attr_extra_state_attributes["off_peak_2"] = home_data["off_peak_2"]
|
||||
self._attr_extra_state_attributes["app_nickname"] = home_data["app_nickname"]
|
||||
self._attr_extra_state_attributes["grid_company"] = home_data["grid_company"]
|
||||
self._attr_extra_state_attributes["estimated_annual_consumption"] = home_data[
|
||||
"estimated_annual_consumption"
|
||||
]
|
||||
self._attr_extra_state_attributes["estimated_annual_consumption"] = data[
|
||||
"meteringPointData"
|
||||
]["estimatedAnnualConsumption"]
|
||||
self._attr_available = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class TibberDataSensor(TibberSensor, CoordinatorEntity[TibberDataCoordinator]):
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
import contextlib
|
||||
from collections.abc import Callable
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pywemo.exceptions import ActionException
|
||||
|
||||
@@ -64,23 +64,20 @@ class WemoEntity(CoordinatorEntity[DeviceCoordinator]):
|
||||
"""Return the device info."""
|
||||
return self._device_info
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _wemo_call_wrapper(self, message: str) -> Generator[None]:
|
||||
"""Wrap calls to the device that change its state.
|
||||
async def _async_wemo_call(self, message: str, action: Callable[[], Any]) -> None:
|
||||
"""Run a WeMo device action in the executor and update listeners.
|
||||
|
||||
1. Takes care of making available=False when communications with the
|
||||
device fails.
|
||||
2. Ensures all entities sharing the same coordinator are aware of
|
||||
updates to the device state.
|
||||
Handles errors from the device and ensures all entities sharing the
|
||||
same coordinator are aware of updates to the device state.
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
await self.hass.async_add_executor_job(action)
|
||||
except ActionException as err:
|
||||
_LOGGER.warning("Could not %s for %s (%s)", message, self.name, err)
|
||||
self.coordinator.last_exception = err
|
||||
self.coordinator.last_update_success = False # Used for self.available.
|
||||
self.coordinator.last_update_success = False
|
||||
finally:
|
||||
self.hass.add_job(self.coordinator.async_update_listeners)
|
||||
self.coordinator.async_update_listeners()
|
||||
|
||||
|
||||
class WemoBinaryStateEntity(WemoEntity):
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import functools as ft
|
||||
import math
|
||||
from typing import Any
|
||||
|
||||
@@ -60,14 +61,16 @@ async def async_setup_entry(
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
|
||||
# This will call WemoHumidifier.set_humidity(target_humidity=VALUE)
|
||||
# This will call WemoHumidifier.async_set_humidity(target_humidity=VALUE)
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_HUMIDITY, SET_HUMIDITY_SCHEMA, WemoHumidifier.set_humidity.__name__
|
||||
SERVICE_SET_HUMIDITY,
|
||||
SET_HUMIDITY_SCHEMA,
|
||||
WemoHumidifier.async_set_humidity.__name__,
|
||||
)
|
||||
|
||||
# This will call WemoHumidifier.reset_filter_life()
|
||||
# This will call WemoHumidifier.async_reset_filter_life()
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_RESET_FILTER_LIFE, None, WemoHumidifier.reset_filter_life.__name__
|
||||
SERVICE_RESET_FILTER_LIFE, None, WemoHumidifier.async_reset_filter_life.__name__
|
||||
)
|
||||
|
||||
|
||||
@@ -124,25 +127,26 @@ class WemoHumidifier(WemoBinaryStateEntity, FanEntity):
|
||||
self._last_fan_on_mode = self.wemo.fan_mode
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
def turn_on(
|
||||
async def async_turn_on(
|
||||
self,
|
||||
percentage: int | None = None,
|
||||
preset_mode: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Turn the fan on."""
|
||||
self._set_percentage(percentage)
|
||||
await self._async_set_percentage(percentage)
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch off."""
|
||||
with self._wemo_call_wrapper("turn off"):
|
||||
self.wemo.set_state(FanMode.Off)
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the fan off."""
|
||||
await self._async_wemo_call(
|
||||
"turn off", ft.partial(self.wemo.set_state, FanMode.Off)
|
||||
)
|
||||
|
||||
def set_percentage(self, percentage: int) -> None:
|
||||
async def async_set_percentage(self, percentage: int) -> None:
|
||||
"""Set the fan_mode of the Humidifier."""
|
||||
self._set_percentage(percentage)
|
||||
await self._async_set_percentage(percentage)
|
||||
|
||||
def _set_percentage(self, percentage: int | None) -> None:
|
||||
async def _async_set_percentage(self, percentage: int | None) -> None:
|
||||
if percentage is None:
|
||||
named_speed = self._last_fan_on_mode
|
||||
elif percentage == 0:
|
||||
@@ -152,10 +156,11 @@ class WemoHumidifier(WemoBinaryStateEntity, FanEntity):
|
||||
math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage))
|
||||
)
|
||||
|
||||
with self._wemo_call_wrapper("set speed"):
|
||||
self.wemo.set_state(named_speed)
|
||||
await self._async_wemo_call(
|
||||
"set speed", ft.partial(self.wemo.set_state, named_speed)
|
||||
)
|
||||
|
||||
def set_humidity(self, target_humidity: float) -> None:
|
||||
async def async_set_humidity(self, target_humidity: float) -> None:
|
||||
"""Set the target humidity level for the Humidifier."""
|
||||
if target_humidity < 50:
|
||||
pywemo_humidity = DesiredHumidity.FortyFivePercent
|
||||
@@ -168,10 +173,10 @@ class WemoHumidifier(WemoBinaryStateEntity, FanEntity):
|
||||
elif target_humidity >= 100:
|
||||
pywemo_humidity = DesiredHumidity.OneHundredPercent
|
||||
|
||||
with self._wemo_call_wrapper("set humidity"):
|
||||
self.wemo.set_humidity(pywemo_humidity)
|
||||
await self._async_wemo_call(
|
||||
"set humidity", ft.partial(self.wemo.set_humidity, pywemo_humidity)
|
||||
)
|
||||
|
||||
def reset_filter_life(self) -> None:
|
||||
async def async_reset_filter_life(self) -> None:
|
||||
"""Reset the filter life to 100%."""
|
||||
with self._wemo_call_wrapper("reset filter life"):
|
||||
self.wemo.reset_filter_life()
|
||||
await self._async_wemo_call("reset filter life", self.wemo.reset_filter_life)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools as ft
|
||||
from typing import Any, cast
|
||||
|
||||
from pywemo import Bridge, BridgeLight, Dimmer
|
||||
@@ -166,7 +167,7 @@ class WemoLight(WemoEntity, LightEntity):
|
||||
"""Return true if device is on."""
|
||||
return self.light.state.get("onoff", WEMO_OFF) != WEMO_OFF
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the light on."""
|
||||
xy_color = None
|
||||
|
||||
@@ -184,7 +185,7 @@ class WemoLight(WemoEntity, LightEntity):
|
||||
"force_update": False,
|
||||
}
|
||||
|
||||
with self._wemo_call_wrapper("turn on"):
|
||||
def _turn_on() -> None:
|
||||
if xy_color is not None:
|
||||
self.light.set_color(xy_color, transition=transition_time)
|
||||
|
||||
@@ -195,12 +196,14 @@ class WemoLight(WemoEntity, LightEntity):
|
||||
|
||||
self.light.turn_on(**turn_on_kwargs)
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
await self._async_wemo_call("turn on", _turn_on)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the light off."""
|
||||
transition_time = int(kwargs.get(ATTR_TRANSITION, 0))
|
||||
|
||||
with self._wemo_call_wrapper("turn off"):
|
||||
self.light.turn_off(transition=transition_time)
|
||||
await self._async_wemo_call(
|
||||
"turn off", ft.partial(self.light.turn_off, transition=transition_time)
|
||||
)
|
||||
|
||||
|
||||
class WemoDimmer(WemoBinaryStateEntity, LightEntity):
|
||||
@@ -216,20 +219,19 @@ class WemoDimmer(WemoBinaryStateEntity, LightEntity):
|
||||
wemo_brightness: int = self.wemo.get_brightness()
|
||||
return int((wemo_brightness * 255) / 100)
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the dimmer on."""
|
||||
# Wemo dimmer switches use a range of [0, 100] to control
|
||||
# brightness. Level 255 might mean to set it to previous value
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = kwargs[ATTR_BRIGHTNESS]
|
||||
brightness = int((brightness / 255) * 100)
|
||||
with self._wemo_call_wrapper("set brightness"):
|
||||
self.wemo.set_brightness(brightness)
|
||||
await self._async_wemo_call(
|
||||
"set brightness", ft.partial(self.wemo.set_brightness, brightness)
|
||||
)
|
||||
else:
|
||||
with self._wemo_call_wrapper("turn on"):
|
||||
self.wemo.on()
|
||||
await self._async_wemo_call("turn on", self.wemo.on)
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the dimmer off."""
|
||||
with self._wemo_call_wrapper("turn off"):
|
||||
self.wemo.off()
|
||||
await self._async_wemo_call("turn off", self.wemo.off)
|
||||
|
||||
@@ -119,12 +119,10 @@ class WemoSwitch(WemoBinaryStateEntity, SwitchEntity):
|
||||
return "mdi:coffee"
|
||||
return None
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch on."""
|
||||
with self._wemo_call_wrapper("turn on"):
|
||||
self.wemo.on()
|
||||
await self._async_wemo_call("turn on", self.wemo.on)
|
||||
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch off."""
|
||||
with self._wemo_call_wrapper("turn off"):
|
||||
self.wemo.off()
|
||||
await self._async_wemo_call("turn off", self.wemo.off)
|
||||
|
||||
@@ -25,9 +25,9 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_alarm_control_panels(hass: HomeAssistant) -> list[str]:
|
||||
async def target_alarm_control_panels(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple alarm_control_panel entities associated with different targets."""
|
||||
return (await target_entities(hass, "alarm_control_panel"))["included"]
|
||||
return await target_entities(hass, "alarm_control_panel")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -120,7 +120,7 @@ async def test_alarm_control_panel_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_alarm_control_panel_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_alarm_control_panels: list[str],
|
||||
target_alarm_control_panels: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -129,10 +129,10 @@ async def test_alarm_control_panel_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the alarm_control_panel state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_alarm_control_panels) - {entity_id}
|
||||
other_entity_ids = set(target_alarm_control_panels["included"]) - {entity_id}
|
||||
|
||||
# Set all alarm_control_panels, including the tested alarm_control_panel, to the initial state
|
||||
for eid in target_alarm_control_panels:
|
||||
for eid in target_alarm_control_panels["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -227,7 +227,7 @@ async def test_alarm_control_panel_state_condition_behavior_any(
|
||||
)
|
||||
async def test_alarm_control_panel_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_alarm_control_panels: list[str],
|
||||
target_alarm_control_panels: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -236,10 +236,10 @@ async def test_alarm_control_panel_state_condition_behavior_all(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the alarm_control_panel state condition with the 'all' behavior."""
|
||||
other_entity_ids = set(target_alarm_control_panels) - {entity_id}
|
||||
other_entity_ids = set(target_alarm_control_panels["included"]) - {entity_id}
|
||||
|
||||
# Set all alarm_control_panels, including the tested alarm_control_panel, to the initial state
|
||||
for eid in target_alarm_control_panels:
|
||||
for eid in target_alarm_control_panels["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -8,19 +8,18 @@ from homeassistant.components.alarm_control_panel import (
|
||||
AlarmControlPanelEntityFeature,
|
||||
AlarmControlPanelState,
|
||||
)
|
||||
from homeassistant.const import ATTR_SUPPORTED_FEATURES, CONF_ENTITY_ID
|
||||
from homeassistant.const import ATTR_SUPPORTED_FEATURES
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
other_states,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -331,25 +330,14 @@ async def test_alarm_control_panel_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the alarm_control_panel state trigger fires when the last alarm_control_panel changes to a specific state."""
|
||||
other_entity_ids = set(target_alarm_control_panels["included"]) - {entity_id}
|
||||
|
||||
# Set all alarm control panels, including the tested one, to the initial state
|
||||
for eid in target_alarm_control_panels["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_alarm_control_panels,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -21,9 +21,9 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_assist_satellites(hass: HomeAssistant) -> list[str]:
|
||||
async def target_assist_satellites(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple assist satellite entities associated with different targets."""
|
||||
return (await target_entities(hass, "assist_satellite"))["included"]
|
||||
return await target_entities(hass, "assist_satellite")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -74,7 +74,7 @@ async def test_assist_satellite_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_assist_satellite_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_assist_satellites: list[str],
|
||||
target_assist_satellites: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -83,10 +83,10 @@ async def test_assist_satellite_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the assist satellite state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_assist_satellites) - {entity_id}
|
||||
other_entity_ids = set(target_assist_satellites["included"]) - {entity_id}
|
||||
|
||||
# Set all assist satellites, including the tested one, to the initial state
|
||||
for eid in target_assist_satellites:
|
||||
for eid in target_assist_satellites["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -142,7 +142,7 @@ async def test_assist_satellite_state_condition_behavior_any(
|
||||
)
|
||||
async def test_assist_satellite_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_assist_satellites: list[str],
|
||||
target_assist_satellites: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -151,10 +151,10 @@ async def test_assist_satellite_state_condition_behavior_all(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the assist satellite state condition with the 'all' behavior."""
|
||||
other_entity_ids = set(target_assist_satellites) - {entity_id}
|
||||
other_entity_ids = set(target_assist_satellites["included"]) - {entity_id}
|
||||
|
||||
# Set all assist satellites, including the tested one, to the initial state
|
||||
for eid in target_assist_satellites:
|
||||
for eid in target_assist_satellites["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -5,19 +5,17 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.assist_satellite.entity import AssistSatelliteState
|
||||
from homeassistant.const import CONF_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
other_states,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -196,25 +194,14 @@ async def test_assist_satellite_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the assist_satellite state trigger fires when the last assist_satellite changes to a specific state."""
|
||||
other_entity_ids = set(target_assist_satellites["included"]) - {entity_id}
|
||||
|
||||
# Set all assist satellites, including the tested one, to the initial state
|
||||
for eid in target_assist_satellites["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_assist_satellites,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -25,9 +25,9 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_climates(hass: HomeAssistant) -> list[str]:
|
||||
async def target_climates(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple climate entities associated with different targets."""
|
||||
return (await target_entities(hass, "climate"))["included"]
|
||||
return await target_entities(hass, "climate")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -76,7 +76,7 @@ async def test_climate_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_climate_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_climates: list[str],
|
||||
target_climates: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -85,10 +85,10 @@ async def test_climate_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the climate state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_climates) - {entity_id}
|
||||
other_entity_ids = set(target_climates["included"]) - {entity_id}
|
||||
|
||||
# Set all climates, including the tested climate, to the initial state
|
||||
for eid in target_climates:
|
||||
for eid in target_climates["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -141,7 +141,7 @@ async def test_climate_state_condition_behavior_any(
|
||||
)
|
||||
async def test_climate_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_climates: list[str],
|
||||
target_climates: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -150,10 +150,10 @@ async def test_climate_state_condition_behavior_all(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the climate state condition with the 'all' behavior."""
|
||||
other_entity_ids = set(target_climates) - {entity_id}
|
||||
other_entity_ids = set(target_climates["included"]) - {entity_id}
|
||||
|
||||
# Set all climates, including the tested climate, to the initial state
|
||||
for eid in target_climates:
|
||||
for eid in target_climates["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -205,7 +205,7 @@ async def test_climate_state_condition_behavior_all(
|
||||
)
|
||||
async def test_climate_attribute_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_climates: list[str],
|
||||
target_climates: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -214,10 +214,10 @@ async def test_climate_attribute_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the climate attribute condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_climates) - {entity_id}
|
||||
other_entity_ids = set(target_climates["included"]) - {entity_id}
|
||||
|
||||
# Set all climates, including the tested climate, to the initial state
|
||||
for eid in target_climates:
|
||||
for eid in target_climates["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -268,7 +268,7 @@ async def test_climate_attribute_condition_behavior_any(
|
||||
)
|
||||
async def test_climate_attribute_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_climates: list[str],
|
||||
target_climates: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -277,10 +277,10 @@ async def test_climate_attribute_condition_behavior_all(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the climate attribute condition with the 'all' behavior."""
|
||||
other_entity_ids = set(target_climates) - {entity_id}
|
||||
other_entity_ids = set(target_climates["included"]) - {entity_id}
|
||||
|
||||
# Set all climates, including the tested climate, to the initial state
|
||||
for eid in target_climates:
|
||||
for eid in target_climates["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -24,16 +24,15 @@ from homeassistant.helpers.trigger import async_validate_trigger_config
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
other_states,
|
||||
parametrize_numerical_attribute_changed_trigger_states,
|
||||
parametrize_numerical_attribute_crossed_threshold_trigger_states,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -409,31 +408,18 @@ async def test_climate_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the climate state trigger fires when the last climate changes to a specific state."""
|
||||
other_entity_ids = set(target_climates["included"]) - {entity_id}
|
||||
|
||||
# Set all climates, including the tested climate, to the initial state
|
||||
for eid in target_climates["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(
|
||||
hass, trigger, {"behavior": "last"} | trigger_options, trigger_target_config
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_climates,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
@pytest.mark.parametrize(
|
||||
@@ -480,27 +466,14 @@ async def test_climate_state_attribute_trigger_behavior_last(
|
||||
states: list[tuple[tuple[str, dict], int]],
|
||||
) -> None:
|
||||
"""Test that the climate state trigger fires when the last climate state changes to a specific state."""
|
||||
other_entity_ids = set(target_climates["included"]) - {entity_id}
|
||||
|
||||
# Set all climates, including the tested climate, to the initial state
|
||||
for eid in target_climates["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(
|
||||
hass, trigger, {"behavior": "last"} | trigger_options, trigger_target_config
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_climates,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
@@ -953,3 +953,51 @@ async def assert_trigger_behavior_first(
|
||||
set_or_remove_state(hass, excluded_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
|
||||
async def assert_trigger_behavior_last(
|
||||
hass: HomeAssistant,
|
||||
*,
|
||||
service_calls: list[ServiceCall],
|
||||
target_entities: dict[str, list[str]],
|
||||
trigger_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
trigger: str,
|
||||
trigger_options: dict[str, Any],
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test trigger fires in mode last."""
|
||||
other_entity_ids = set(target_entities["included"]) - {entity_id}
|
||||
excluded_entity_ids = set(target_entities["excluded"]) - {entity_id}
|
||||
|
||||
for eid in target_entities["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
for eid in excluded_entity_ids:
|
||||
set_or_remove_state(hass, eid, states[0]["excluded"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(
|
||||
hass, trigger, {"behavior": "last"} | trigger_options, trigger_target_config
|
||||
)
|
||||
|
||||
for state in states[1:]:
|
||||
excluded_state = state["excluded"]
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
for excluded_entity_id in excluded_entity_ids:
|
||||
set_or_remove_state(hass, excluded_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
@@ -13,10 +13,10 @@ from tests.components.common import (
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -259,37 +259,17 @@ async def test_cover_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test cover trigger fires when the last cover changes state."""
|
||||
other_entity_ids = set(target_covers["included"]) - {entity_id}
|
||||
excluded_entity_ids = set(target_covers["excluded"]) - {entity_id}
|
||||
|
||||
for eid in target_covers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
for eid in excluded_entity_ids:
|
||||
set_or_remove_state(hass, eid, states[0]["excluded"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
excluded_state = state["excluded"]
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
for excluded_entity_id in excluded_entity_ids:
|
||||
set_or_remove_state(hass, excluded_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_covers,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
|
||||
@@ -20,9 +20,9 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_device_trackers(hass: HomeAssistant) -> list[str]:
|
||||
async def target_device_trackers(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple device tracker entities associated with different targets."""
|
||||
return (await target_entities(hass, "device_tracker"))["included"]
|
||||
return await target_entities(hass, "device_tracker")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -61,7 +61,7 @@ async def test_device_tracker_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_device_tracker_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_device_trackers: list[str],
|
||||
target_device_trackers: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -70,10 +70,10 @@ async def test_device_tracker_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the device tracker state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_device_trackers) - {entity_id}
|
||||
other_entity_ids = set(target_device_trackers["included"]) - {entity_id}
|
||||
|
||||
# Set all device trackers, including the tested one, to the initial state
|
||||
for eid in target_device_trackers:
|
||||
for eid in target_device_trackers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -119,7 +119,7 @@ async def test_device_tracker_state_condition_behavior_any(
|
||||
)
|
||||
async def test_device_tracker_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_device_trackers: list[str],
|
||||
target_device_trackers: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -128,10 +128,10 @@ async def test_device_tracker_state_condition_behavior_all(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the device tracker state condition with the 'all' behavior."""
|
||||
other_entity_ids = set(target_device_trackers) - {entity_id}
|
||||
other_entity_ids = set(target_device_trackers["included"]) - {entity_id}
|
||||
|
||||
# Set all device trackers, including the tested one, to the initial state
|
||||
for eid in target_device_trackers:
|
||||
for eid in target_device_trackers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -4,18 +4,17 @@ from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.const import CONF_ENTITY_ID, STATE_HOME, STATE_NOT_HOME
|
||||
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -161,25 +160,14 @@ async def test_device_tracker_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the device_tracker home triggers when the last device_tracker changes to a specific state."""
|
||||
other_entity_ids = set(target_device_trackers["included"]) - {entity_id}
|
||||
|
||||
# Set all device_trackers, including the tested device_tracker, to the initial state
|
||||
for eid in target_device_trackers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_device_trackers,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -13,10 +13,10 @@ from tests.components.common import (
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -251,37 +251,17 @@ async def test_door_trigger_binary_sensor_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test door trigger fires when the last binary_sensor changes state."""
|
||||
other_entity_ids = set(target_binary_sensors["included"]) - {entity_id}
|
||||
excluded_entity_ids = set(target_binary_sensors["excluded"]) - {entity_id}
|
||||
|
||||
for eid in target_binary_sensors["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
for eid in excluded_entity_ids:
|
||||
set_or_remove_state(hass, eid, states[0]["excluded"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
excluded_state = state["excluded"]
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
for excluded_entity_id in excluded_entity_ids:
|
||||
set_or_remove_state(hass, excluded_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_binary_sensors,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
@@ -411,37 +391,17 @@ async def test_door_trigger_cover_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test door trigger fires when the last cover changes state."""
|
||||
other_entity_ids = set(target_covers["included"]) - {entity_id}
|
||||
excluded_entity_ids = set(target_covers["excluded"]) - {entity_id}
|
||||
|
||||
for eid in target_covers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
for eid in excluded_entity_ids:
|
||||
set_or_remove_state(hass, eid, states[0]["excluded"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
excluded_state = state["excluded"]
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
for excluded_entity_id in excluded_entity_ids:
|
||||
set_or_remove_state(hass, excluded_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_covers,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
|
||||
@@ -20,19 +20,19 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_fans(hass: HomeAssistant) -> list[str]:
|
||||
async def target_fans(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple fan entities associated with different targets."""
|
||||
return (await target_entities(hass, "fan"))["included"]
|
||||
return await target_entities(hass, "fan")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_switches(hass: HomeAssistant) -> list[str]:
|
||||
async def target_switches(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple switch entities associated with different targets.
|
||||
|
||||
Note: The switches are used to ensure that only fan entities are considered
|
||||
in the condition evaluation and not other toggle entities.
|
||||
"""
|
||||
return (await target_entities(hass, "switch"))["included"]
|
||||
return await target_entities(hass, "switch")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -71,8 +71,8 @@ async def test_fan_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_fan_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_fans: list[str],
|
||||
target_switches: list[str],
|
||||
target_fans: dict[str, list[str]],
|
||||
target_switches: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -81,10 +81,10 @@ async def test_fan_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the fan state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_fans) - {entity_id}
|
||||
other_entity_ids = set(target_fans["included"]) - {entity_id}
|
||||
|
||||
# Set all fans, including the tested fan, to the initial state
|
||||
for eid in target_fans:
|
||||
for eid in target_fans["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -97,7 +97,7 @@ async def test_fan_state_condition_behavior_any(
|
||||
|
||||
# Set state for switches to ensure that they don't impact the condition
|
||||
for state in states:
|
||||
for eid in target_switches:
|
||||
for eid in target_switches["included"]:
|
||||
set_or_remove_state(hass, eid, state["included"])
|
||||
await hass.async_block_till_done()
|
||||
assert condition(hass) is False
|
||||
@@ -137,7 +137,7 @@ async def test_fan_state_condition_behavior_any(
|
||||
)
|
||||
async def test_fan_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_fans: list[str],
|
||||
target_fans: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -150,10 +150,10 @@ async def test_fan_state_condition_behavior_all(
|
||||
hass.states.async_set("switch.label_switch_1", STATE_OFF)
|
||||
hass.states.async_set("switch.label_switch_2", STATE_ON)
|
||||
|
||||
other_entity_ids = set(target_fans) - {entity_id}
|
||||
other_entity_ids = set(target_fans["included"]) - {entity_id}
|
||||
|
||||
# Set all fans, including the tested fan, to the initial state
|
||||
for eid in target_fans:
|
||||
for eid in target_fans["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -4,18 +4,17 @@ from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.const import CONF_ENTITY_ID, STATE_OFF, STATE_ON
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -162,25 +161,14 @@ async def test_fan_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the fan state trigger fires when the last fan changes to a specific state."""
|
||||
other_entity_ids = set(target_fans["included"]) - {entity_id}
|
||||
|
||||
# Set all fans, including the tested fan, to the initial state
|
||||
for eid in target_fans["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_fans,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -13,10 +13,10 @@ from tests.components.common import (
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -251,37 +251,17 @@ async def test_garage_door_trigger_binary_sensor_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test garage door trigger fires when the last binary_sensor changes state."""
|
||||
other_entity_ids = set(target_binary_sensors["included"]) - {entity_id}
|
||||
excluded_entity_ids = set(target_binary_sensors["excluded"]) - {entity_id}
|
||||
|
||||
for eid in target_binary_sensors["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
for eid in excluded_entity_ids:
|
||||
set_or_remove_state(hass, eid, states[0]["excluded"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
excluded_state = state["excluded"]
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
for excluded_entity_id in excluded_entity_ids:
|
||||
set_or_remove_state(hass, excluded_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_binary_sensors,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
@@ -411,37 +391,17 @@ async def test_garage_door_trigger_cover_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test garage door trigger fires when the last cover changes state."""
|
||||
other_entity_ids = set(target_covers["included"]) - {entity_id}
|
||||
excluded_entity_ids = set(target_covers["excluded"]) - {entity_id}
|
||||
|
||||
for eid in target_covers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
for eid in excluded_entity_ids:
|
||||
set_or_remove_state(hass, eid, states[0]["excluded"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
excluded_state = state["excluded"]
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
for excluded_entity_id in excluded_entity_ids:
|
||||
set_or_remove_state(hass, excluded_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_covers,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
|
||||
@@ -13,10 +13,10 @@ from tests.components.common import (
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -238,37 +238,17 @@ async def test_gate_trigger_cover_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test gate trigger fires when the last cover changes state."""
|
||||
other_entity_ids = set(target_covers["included"]) - {entity_id}
|
||||
excluded_entity_ids = set(target_covers["excluded"]) - {entity_id}
|
||||
|
||||
for eid in target_covers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
for eid in excluded_entity_ids:
|
||||
set_or_remove_state(hass, eid, states[0]["excluded"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
excluded_state = state["excluded"]
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
for excluded_entity_id in excluded_entity_ids:
|
||||
set_or_remove_state(hass, excluded_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_covers,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
|
||||
@@ -21,9 +21,9 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_humidifiers(hass: HomeAssistant) -> list[str]:
|
||||
async def target_humidifiers(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple humidifier entities associated with different targets."""
|
||||
return (await target_entities(hass, "humidifier"))["included"]
|
||||
return await target_entities(hass, "humidifier")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -64,7 +64,7 @@ async def test_humidifier_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_humidifier_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_humidifiers: list[str],
|
||||
target_humidifiers: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -73,10 +73,10 @@ async def test_humidifier_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the humidifier state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_humidifiers) - {entity_id}
|
||||
other_entity_ids = set(target_humidifiers["included"]) - {entity_id}
|
||||
|
||||
# Set all humidifiers, including the tested humidifier, to the initial state
|
||||
for eid in target_humidifiers:
|
||||
for eid in target_humidifiers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -122,7 +122,7 @@ async def test_humidifier_state_condition_behavior_any(
|
||||
)
|
||||
async def test_humidifier_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_humidifiers: list[str],
|
||||
target_humidifiers: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -131,10 +131,10 @@ async def test_humidifier_state_condition_behavior_all(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the humidifier state condition with the 'all' behavior."""
|
||||
other_entity_ids = set(target_humidifiers) - {entity_id}
|
||||
other_entity_ids = set(target_humidifiers["included"]) - {entity_id}
|
||||
|
||||
# Set all humidifiers, including the tested humidifier, to the initial state
|
||||
for eid in target_humidifiers:
|
||||
for eid in target_humidifiers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -181,7 +181,7 @@ async def test_humidifier_state_condition_behavior_all(
|
||||
)
|
||||
async def test_humidifier_attribute_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_humidifiers: list[str],
|
||||
target_humidifiers: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -190,10 +190,10 @@ async def test_humidifier_attribute_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the humidifier attribute condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_humidifiers) - {entity_id}
|
||||
other_entity_ids = set(target_humidifiers["included"]) - {entity_id}
|
||||
|
||||
# Set all humidifiers, including the tested humidifier, to the initial state
|
||||
for eid in target_humidifiers:
|
||||
for eid in target_humidifiers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -239,7 +239,7 @@ async def test_humidifier_attribute_condition_behavior_any(
|
||||
)
|
||||
async def test_humidifier_attribute_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_humidifiers: list[str],
|
||||
target_humidifiers: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -248,10 +248,10 @@ async def test_humidifier_attribute_condition_behavior_all(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the humidifier attribute condition with the 'all' behavior."""
|
||||
other_entity_ids = set(target_humidifiers) - {entity_id}
|
||||
other_entity_ids = set(target_humidifiers["included"]) - {entity_id}
|
||||
|
||||
# Set all humidifiers, including the tested humidifier, to the initial state
|
||||
for eid in target_humidifiers:
|
||||
for eid in target_humidifiers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -5,18 +5,17 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.humidifier.const import ATTR_ACTION, HumidifierAction
|
||||
from homeassistant.const import CONF_ENTITY_ID, STATE_OFF, STATE_ON
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -255,28 +254,17 @@ async def test_humidifier_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the humidifier state trigger fires when the last humidifier changes to a specific state."""
|
||||
other_entity_ids = set(target_humidifiers["included"]) - {entity_id}
|
||||
|
||||
# Set all humidifiers, including the tested humidifier, to the initial state
|
||||
for eid in target_humidifiers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_humidifiers,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
@@ -311,27 +299,14 @@ async def test_humidifier_state_attribute_trigger_behavior_last(
|
||||
states: list[tuple[tuple[str, dict], int]],
|
||||
) -> None:
|
||||
"""Test that the humidifier state trigger fires when the last humidifier state changes to a specific state."""
|
||||
other_entity_ids = set(target_humidifiers["included"]) - {entity_id}
|
||||
|
||||
# Set all humidifiers, including the tested humidifier, to the initial state
|
||||
for eid in target_humidifiers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(
|
||||
hass, trigger, {"behavior": "last"} | trigger_options, trigger_target_config
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_humidifiers,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
@@ -20,13 +20,13 @@ from tests.components.common import (
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_numerical_attribute_changed_trigger_states,
|
||||
parametrize_numerical_attribute_crossed_threshold_trigger_states,
|
||||
parametrize_numerical_state_value_changed_trigger_states,
|
||||
parametrize_numerical_state_value_crossed_threshold_trigger_states,
|
||||
parametrize_target_entities,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -176,30 +176,18 @@ async def test_humidity_trigger_sensor_crossed_threshold_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test humidity crossed_threshold trigger fires when the last sensor changes state."""
|
||||
other_entity_ids = set(target_sensors["included"]) - {entity_id}
|
||||
|
||||
for eid in target_sensors["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(
|
||||
hass, trigger, {"behavior": "last"} | trigger_options, trigger_target_config
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_sensors,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
|
||||
# --- Climate domain tests (value in current_humidity attribute) ---
|
||||
|
||||
@@ -314,30 +302,18 @@ async def test_humidity_trigger_climate_crossed_threshold_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test humidity crossed_threshold trigger fires when the last climate changes state."""
|
||||
other_entity_ids = set(target_climates["included"]) - {entity_id}
|
||||
|
||||
for eid in target_climates["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(
|
||||
hass, trigger, {"behavior": "last"} | trigger_options, trigger_target_config
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_climates,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
|
||||
# --- Humidifier domain tests (value in current_humidity attribute) ---
|
||||
|
||||
@@ -452,30 +428,18 @@ async def test_humidity_trigger_humidifier_crossed_threshold_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test humidity crossed_threshold trigger fires when the last humidifier changes state."""
|
||||
other_entity_ids = set(target_humidifiers["included"]) - {entity_id}
|
||||
|
||||
for eid in target_humidifiers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(
|
||||
hass, trigger, {"behavior": "last"} | trigger_options, trigger_target_config
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_humidifiers,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
|
||||
# --- Weather domain tests (value in humidity attribute) ---
|
||||
|
||||
@@ -590,30 +554,18 @@ async def test_humidity_trigger_weather_crossed_threshold_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test humidity crossed_threshold trigger fires when the last weather changes state."""
|
||||
other_entity_ids = set(target_weathers["included"]) - {entity_id}
|
||||
|
||||
for eid in target_weathers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(
|
||||
hass, trigger, {"behavior": "last"} | trigger_options, trigger_target_config
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_weathers,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
|
||||
# --- Device class exclusion test ---
|
||||
|
||||
|
||||
@@ -5,18 +5,17 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.input_boolean import DOMAIN
|
||||
from homeassistant.const import CONF_ENTITY_ID, STATE_OFF, STATE_ON
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -163,25 +162,14 @@ async def test_input_boolean_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the input_boolean state trigger fires when the last input_boolean changes to a specific state."""
|
||||
other_entity_ids = set(target_input_booleans["included"]) - {entity_id}
|
||||
|
||||
# Set all input_booleans, including the tested one, to the initial state
|
||||
for eid in target_input_booleans["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_input_booleans,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -21,9 +21,9 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_lawn_mowers(hass: HomeAssistant) -> list[str]:
|
||||
async def target_lawn_mowers(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple lawn mower entities associated with different targets."""
|
||||
return (await target_entities(hass, "lawn_mower"))["included"]
|
||||
return await target_entities(hass, "lawn_mower")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -80,7 +80,7 @@ async def test_lawn_mower_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_lawn_mower_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_lawn_mowers: list[str],
|
||||
target_lawn_mowers: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -89,10 +89,10 @@ async def test_lawn_mower_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the lawn mower state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_lawn_mowers) - {entity_id}
|
||||
other_entity_ids = set(target_lawn_mowers["included"]) - {entity_id}
|
||||
|
||||
# Set all lawn mowers, including the tested lawn mower, to the initial state
|
||||
for eid in target_lawn_mowers:
|
||||
for eid in target_lawn_mowers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -153,7 +153,7 @@ async def test_lawn_mower_state_condition_behavior_any(
|
||||
)
|
||||
async def test_lawn_mower_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_lawn_mowers: list[str],
|
||||
target_lawn_mowers: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -162,10 +162,10 @@ async def test_lawn_mower_state_condition_behavior_all(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the lawn mower state condition with the 'all' behavior."""
|
||||
other_entity_ids = set(target_lawn_mowers) - {entity_id}
|
||||
other_entity_ids = set(target_lawn_mowers["included"]) - {entity_id}
|
||||
|
||||
# Set all lawn mowers, including the tested lawn mower, to the initial state
|
||||
for eid in target_lawn_mowers:
|
||||
for eid in target_lawn_mowers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -5,19 +5,17 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.lawn_mower import LawnMowerActivity
|
||||
from homeassistant.const import CONF_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
other_states,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -212,25 +210,14 @@ async def test_lawn_mower_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the lawn_mower state trigger fires when the last lawn_mower changes to a specific state."""
|
||||
other_entity_ids = set(target_lawn_mowers["included"]) - {entity_id}
|
||||
|
||||
# Set all lawn mowers, including the tested one, to the initial state
|
||||
for eid in target_lawn_mowers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_lawn_mowers,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -20,19 +20,19 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_lights(hass: HomeAssistant) -> list[str]:
|
||||
async def target_lights(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple light entities associated with different targets."""
|
||||
return (await target_entities(hass, "light"))["included"]
|
||||
return await target_entities(hass, "light")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_switches(hass: HomeAssistant) -> list[str]:
|
||||
async def target_switches(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple switch entities associated with different targets.
|
||||
|
||||
Note: The switches are used to ensure that only light entities are considered
|
||||
in the condition evaluation and not other toggle entities.
|
||||
"""
|
||||
return (await target_entities(hass, "switch"))["included"]
|
||||
return await target_entities(hass, "switch")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -71,8 +71,8 @@ async def test_light_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_light_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_lights: list[str],
|
||||
target_switches: list[str],
|
||||
target_lights: dict[str, list[str]],
|
||||
target_switches: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -81,10 +81,10 @@ async def test_light_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the light state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_lights) - {entity_id}
|
||||
other_entity_ids = set(target_lights["included"]) - {entity_id}
|
||||
|
||||
# Set all lights, including the tested light, to the initial state
|
||||
for eid in target_lights:
|
||||
for eid in target_lights["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -97,7 +97,7 @@ async def test_light_state_condition_behavior_any(
|
||||
|
||||
# Set state for switches to ensure that they don't impact the condition
|
||||
for state in states:
|
||||
for eid in target_switches:
|
||||
for eid in target_switches["included"]:
|
||||
set_or_remove_state(hass, eid, state["included"])
|
||||
await hass.async_block_till_done()
|
||||
assert condition(hass) is False
|
||||
@@ -137,7 +137,7 @@ async def test_light_state_condition_behavior_any(
|
||||
)
|
||||
async def test_light_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_lights: list[str],
|
||||
target_lights: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -150,10 +150,10 @@ async def test_light_state_condition_behavior_all(
|
||||
hass.states.async_set("switch.label_switch_1", STATE_OFF)
|
||||
hass.states.async_set("switch.label_switch_2", STATE_ON)
|
||||
|
||||
other_entity_ids = set(target_lights) - {entity_id}
|
||||
other_entity_ids = set(target_lights["included"]) - {entity_id}
|
||||
|
||||
# Set all lights, including the tested light, to the initial state
|
||||
for eid in target_lights:
|
||||
for eid in target_lights["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -5,13 +5,7 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS
|
||||
from homeassistant.const import (
|
||||
CONF_ABOVE,
|
||||
CONF_BELOW,
|
||||
CONF_ENTITY_ID,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.const import CONF_ABOVE, CONF_BELOW, STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers.trigger import (
|
||||
CONF_LOWER_LIMIT,
|
||||
@@ -22,13 +16,12 @@ from homeassistant.helpers.trigger import (
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -381,28 +374,17 @@ async def test_light_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the light state trigger fires when the last light changes to a specific state."""
|
||||
other_entity_ids = set(target_lights["included"]) - {entity_id}
|
||||
|
||||
# Set all lights, including the tested light, to the initial state
|
||||
for eid in target_lights["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_lights,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
@@ -430,27 +412,14 @@ async def test_light_state_attribute_trigger_behavior_last(
|
||||
states: list[tuple[tuple[str, dict], int]],
|
||||
) -> None:
|
||||
"""Test that the light state trigger fires when the last light state changes to a specific state."""
|
||||
other_entity_ids = set(target_lights["included"]) - {entity_id}
|
||||
|
||||
# Set all lights, including the tested light, to the initial state
|
||||
for eid in target_lights["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(
|
||||
hass, trigger, {"behavior": "last"} | trigger_options, trigger_target_config
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_lights,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
@@ -21,9 +21,9 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_locks(hass: HomeAssistant) -> list[str]:
|
||||
async def target_locks(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple lock entities associated with different targets."""
|
||||
return (await target_entities(hass, "lock"))["included"]
|
||||
return await target_entities(hass, "lock")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -74,7 +74,7 @@ async def test_lock_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_lock_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_locks: list[str],
|
||||
target_locks: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -83,10 +83,10 @@ async def test_lock_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the lock state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_locks) - {entity_id}
|
||||
other_entity_ids = set(target_locks["included"]) - {entity_id}
|
||||
|
||||
# Set all locks, including the tested lock, to the initial state
|
||||
for eid in target_locks:
|
||||
for eid in target_locks["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -142,7 +142,7 @@ async def test_lock_state_condition_behavior_any(
|
||||
)
|
||||
async def test_lock_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_locks: list[str],
|
||||
target_locks: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -151,10 +151,10 @@ async def test_lock_state_condition_behavior_all(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the lock state condition with the 'all' behavior."""
|
||||
other_entity_ids = set(target_locks) - {entity_id}
|
||||
other_entity_ids = set(target_locks["included"]) - {entity_id}
|
||||
|
||||
# Set all locks, including the tested lock, to the initial state
|
||||
for eid in target_locks:
|
||||
for eid in target_locks["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -5,19 +5,17 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.lock import DOMAIN, LockState
|
||||
from homeassistant.const import CONF_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
other_states,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -196,25 +194,14 @@ async def test_lock_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the lock state trigger fires when the last lock changes to a specific state."""
|
||||
other_entity_ids = set(target_locks["included"]) - {entity_id}
|
||||
|
||||
# Set all locks, including the tested one, to the initial state
|
||||
for eid in target_locks["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_locks,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -21,9 +21,9 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_media_players(hass: HomeAssistant) -> list[str]:
|
||||
async def target_media_players(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple media player entities associated with different targets."""
|
||||
return (await target_entities(hass, "media_player"))["included"]
|
||||
return await target_entities(hass, "media_player")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -92,7 +92,7 @@ async def test_media_player_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_media_player_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_media_players: list[str],
|
||||
target_media_players: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -101,10 +101,10 @@ async def test_media_player_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the media player state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_media_players) - {entity_id}
|
||||
other_entity_ids = set(target_media_players["included"]) - {entity_id}
|
||||
|
||||
# Set all media players, including the tested media player, to the initial state
|
||||
for eid in target_media_players:
|
||||
for eid in target_media_players["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -177,7 +177,7 @@ async def test_media_player_state_condition_behavior_any(
|
||||
)
|
||||
async def test_media_player_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_media_players: list[str],
|
||||
target_media_players: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -186,10 +186,10 @@ async def test_media_player_state_condition_behavior_all(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the media player state condition with the 'all' behavior."""
|
||||
other_entity_ids = set(target_media_players) - {entity_id}
|
||||
other_entity_ids = set(target_media_players["included"]) - {entity_id}
|
||||
|
||||
# Set all media players, including the tested media player, to the initial state
|
||||
for eid in target_media_players:
|
||||
for eid in target_media_players["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -5,18 +5,16 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.media_player import MediaPlayerState
|
||||
from homeassistant.const import CONF_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -171,25 +169,14 @@ async def test_media_player_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the media player state trigger fires when the last media player changes to a specific state."""
|
||||
other_entity_ids = set(target_media_players["included"]) - {entity_id}
|
||||
|
||||
# Set all media players, including the tested media player, to the initial state
|
||||
for eid in target_media_players["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_media_players,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -214,6 +214,7 @@ async def test_unknown_sensor(hass: HomeAssistant) -> None:
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.states.async_set(
|
||||
"test.indoortemp",
|
||||
@@ -292,6 +293,7 @@ async def test_sensor_changed(hass: HomeAssistant) -> None:
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.states.async_set(
|
||||
"test.indoortemp", "30", {ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.CELSIUS}
|
||||
@@ -310,3 +312,126 @@ async def test_sensor_changed(hass: HomeAssistant) -> None:
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("sensor.mold_indicator").state == "23"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("new_state", [STATE_UNAVAILABLE, STATE_UNKNOWN])
|
||||
async def test_unavailable_sensor_recovery(hass: HomeAssistant, new_state: str) -> None:
|
||||
"""Test recovery when sensor becomes unavailable/unknown and then available again."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
sensor.DOMAIN,
|
||||
{
|
||||
"sensor": {
|
||||
"platform": "mold_indicator",
|
||||
"indoor_temp_sensor": "test.indoortemp",
|
||||
"outdoor_temp_sensor": "test.outdoortemp",
|
||||
"indoor_humidity_sensor": "test.indoorhumidity",
|
||||
"calibration_factor": 2.0,
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Initial state should be valid
|
||||
moldind = hass.states.get("sensor.mold_indicator")
|
||||
assert moldind
|
||||
assert moldind.state == "68"
|
||||
|
||||
# Set indoor temp to unavailable
|
||||
hass.states.async_set(
|
||||
"test.indoortemp",
|
||||
new_state,
|
||||
{ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.CELSIUS},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
moldind = hass.states.get("sensor.mold_indicator")
|
||||
assert moldind
|
||||
assert moldind.state == STATE_UNAVAILABLE
|
||||
assert moldind.attributes.get(ATTR_DEWPOINT) is None
|
||||
assert moldind.attributes.get(ATTR_CRITICAL_TEMP) is None
|
||||
|
||||
# Recover by setting a valid value - should immediately work
|
||||
hass.states.async_set(
|
||||
"test.indoortemp", "20", {ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.CELSIUS}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
moldind = hass.states.get("sensor.mold_indicator")
|
||||
assert moldind
|
||||
assert moldind.state == "68"
|
||||
assert moldind.attributes.get(ATTR_DEWPOINT) is not None
|
||||
assert moldind.attributes.get(ATTR_CRITICAL_TEMP) is not None
|
||||
|
||||
|
||||
async def test_all_sensors_unavailable_recovery(hass: HomeAssistant) -> None:
|
||||
"""Test recovery when all sensors become unavailable and then available again."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
sensor.DOMAIN,
|
||||
{
|
||||
"sensor": {
|
||||
"platform": "mold_indicator",
|
||||
"indoor_temp_sensor": "test.indoortemp",
|
||||
"outdoor_temp_sensor": "test.outdoortemp",
|
||||
"indoor_humidity_sensor": "test.indoorhumidity",
|
||||
"calibration_factor": 2.0,
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Initial state should be valid
|
||||
moldind = hass.states.get("sensor.mold_indicator")
|
||||
assert moldind
|
||||
assert moldind.state == "68"
|
||||
|
||||
# Set all sensors to unavailable
|
||||
hass.states.async_set(
|
||||
"test.indoortemp",
|
||||
STATE_UNAVAILABLE,
|
||||
{ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.CELSIUS},
|
||||
)
|
||||
hass.states.async_set(
|
||||
"test.outdoortemp",
|
||||
STATE_UNAVAILABLE,
|
||||
{ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.CELSIUS},
|
||||
)
|
||||
hass.states.async_set(
|
||||
"test.indoorhumidity",
|
||||
STATE_UNAVAILABLE,
|
||||
{ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
moldind = hass.states.get("sensor.mold_indicator")
|
||||
assert moldind
|
||||
assert moldind.state == STATE_UNAVAILABLE
|
||||
|
||||
# Recover all sensors one by one
|
||||
hass.states.async_set(
|
||||
"test.indoortemp", "20", {ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.CELSIUS}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
moldind = hass.states.get("sensor.mold_indicator")
|
||||
assert moldind
|
||||
assert moldind.state == STATE_UNAVAILABLE # Still unavailable, needs all sensors
|
||||
|
||||
hass.states.async_set(
|
||||
"test.outdoortemp", "10", {ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.CELSIUS}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
moldind = hass.states.get("sensor.mold_indicator")
|
||||
assert moldind
|
||||
assert moldind.state == STATE_UNAVAILABLE # Still unavailable, needs humidity
|
||||
|
||||
hass.states.async_set(
|
||||
"test.indoorhumidity", "50", {ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
moldind = hass.states.get("sensor.mold_indicator")
|
||||
assert moldind
|
||||
assert moldind.state == "68" # Now should recover fully
|
||||
assert moldind.attributes.get(ATTR_DEWPOINT) is not None
|
||||
assert moldind.attributes.get(ATTR_CRITICAL_TEMP) is not None
|
||||
|
||||
@@ -12,10 +12,10 @@ from tests.components.common import (
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -174,37 +174,17 @@ async def test_motion_trigger_binary_sensor_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test motion trigger fires when the last binary_sensor changes state."""
|
||||
other_entity_ids = set(target_binary_sensors["included"]) - {entity_id}
|
||||
excluded_entity_ids = set(target_binary_sensors["excluded"]) - {entity_id}
|
||||
|
||||
for eid in target_binary_sensors["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
for eid in excluded_entity_ids:
|
||||
set_or_remove_state(hass, eid, states[0]["excluded"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
excluded_state = state["excluded"]
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
for excluded_entity_id in excluded_entity_ids:
|
||||
set_or_remove_state(hass, excluded_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_binary_sensors,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
# --- Device class exclusion tests ---
|
||||
|
||||
@@ -12,10 +12,10 @@ from tests.components.common import (
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -174,37 +174,17 @@ async def test_occupancy_trigger_binary_sensor_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test occupancy trigger fires when the last binary_sensor changes state."""
|
||||
other_entity_ids = set(target_binary_sensors["included"]) - {entity_id}
|
||||
excluded_entity_ids = set(target_binary_sensors["excluded"]) - {entity_id}
|
||||
|
||||
for eid in target_binary_sensors["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
for eid in excluded_entity_ids:
|
||||
set_or_remove_state(hass, eid, states[0]["excluded"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
excluded_state = state["excluded"]
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
for excluded_entity_id in excluded_entity_ids:
|
||||
set_or_remove_state(hass, excluded_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_binary_sensors,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
# --- Device class exclusion tests ---
|
||||
|
||||
@@ -20,9 +20,9 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_persons(hass: HomeAssistant) -> list[str]:
|
||||
async def target_persons(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple person entities associated with different targets."""
|
||||
return (await target_entities(hass, "person"))["included"]
|
||||
return await target_entities(hass, "person")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -61,7 +61,7 @@ async def test_person_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_person_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_persons: list[str],
|
||||
target_persons: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -70,10 +70,10 @@ async def test_person_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the person state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_persons) - {entity_id}
|
||||
other_entity_ids = set(target_persons["included"]) - {entity_id}
|
||||
|
||||
# Set all persons, including the tested person, to the initial state
|
||||
for eid in target_persons:
|
||||
for eid in target_persons["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -119,7 +119,7 @@ async def test_person_state_condition_behavior_any(
|
||||
)
|
||||
async def test_person_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_persons: list[str],
|
||||
target_persons: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -128,10 +128,10 @@ async def test_person_state_condition_behavior_all(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the person state condition with the 'all' behavior."""
|
||||
other_entity_ids = set(target_persons) - {entity_id}
|
||||
other_entity_ids = set(target_persons["included"]) - {entity_id}
|
||||
|
||||
# Set all persons, including the tested person, to the initial state
|
||||
for eid in target_persons:
|
||||
for eid in target_persons["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -5,18 +5,17 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.person.const import DOMAIN
|
||||
from homeassistant.const import CONF_ENTITY_ID, STATE_HOME, STATE_NOT_HOME
|
||||
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -162,25 +161,14 @@ async def test_person_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the person home triggers when the last person changes to a specific state."""
|
||||
other_entity_ids = set(target_persons["included"]) - {entity_id}
|
||||
|
||||
# Set all persons, including the tested person, to the initial state
|
||||
for eid in target_persons["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_persons,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -5,18 +5,17 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.remote import DOMAIN
|
||||
from homeassistant.const import CONF_ENTITY_ID, STATE_OFF, STATE_ON
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -160,25 +159,14 @@ async def test_remote_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the remote triggers when the last remote changes to a specific state."""
|
||||
other_entity_ids = set(target_remotes["included"]) - {entity_id}
|
||||
|
||||
# Set all remotes, including the tested remote, to the initial state
|
||||
for eid in target_remotes["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_remotes,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -28,10 +28,10 @@ from tests.components.common import (
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -178,28 +178,17 @@ async def test_schedule_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the schedule state trigger fires when the last schedule changes to a specific state."""
|
||||
other_entity_ids = set(target_schedules["included"]) - {entity_id}
|
||||
|
||||
# Set all schedules, including the tested one, to the initial state
|
||||
for eid in target_schedules["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_schedules,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
|
||||
@@ -20,19 +20,19 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_sirens(hass: HomeAssistant) -> list[str]:
|
||||
async def target_sirens(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple siren entities associated with different targets."""
|
||||
return (await target_entities(hass, "siren"))["included"]
|
||||
return await target_entities(hass, "siren")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_switches(hass: HomeAssistant) -> list[str]:
|
||||
async def target_switches(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple switch entities associated with different targets.
|
||||
|
||||
Note: The switches are used to ensure that only siren entities are considered
|
||||
in the condition evaluation and not other toggle entities.
|
||||
"""
|
||||
return (await target_entities(hass, "switch"))["included"]
|
||||
return await target_entities(hass, "switch")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -71,8 +71,8 @@ async def test_siren_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_siren_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_sirens: list[str],
|
||||
target_switches: list[str],
|
||||
target_sirens: dict[str, list[str]],
|
||||
target_switches: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -81,10 +81,10 @@ async def test_siren_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the siren state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_sirens) - {entity_id}
|
||||
other_entity_ids = set(target_sirens["included"]) - {entity_id}
|
||||
|
||||
# Set all sirens, including the tested siren, to the initial state
|
||||
for eid in target_sirens:
|
||||
for eid in target_sirens["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -97,7 +97,7 @@ async def test_siren_state_condition_behavior_any(
|
||||
|
||||
# Set state for switches to ensure that they don't impact the condition
|
||||
for state in states:
|
||||
for eid in target_switches:
|
||||
for eid in target_switches["included"]:
|
||||
set_or_remove_state(hass, eid, state["included"])
|
||||
await hass.async_block_till_done()
|
||||
assert condition(hass) is False
|
||||
@@ -137,7 +137,7 @@ async def test_siren_state_condition_behavior_any(
|
||||
)
|
||||
async def test_siren_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_sirens: list[str],
|
||||
target_sirens: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -149,10 +149,10 @@ async def test_siren_state_condition_behavior_all(
|
||||
# Set state for two switches to ensure that they don't impact the condition
|
||||
hass.states.async_set("switch.label_switch_1", STATE_OFF)
|
||||
hass.states.async_set("switch.label_switch_2", STATE_ON)
|
||||
other_entity_ids = set(target_sirens) - {entity_id}
|
||||
other_entity_ids = set(target_sirens["included"]) - {entity_id}
|
||||
|
||||
# Set all sirens, including the tested siren, to the initial state
|
||||
for eid in target_sirens:
|
||||
for eid in target_sirens["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -5,18 +5,17 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.siren import DOMAIN
|
||||
from homeassistant.const import CONF_ENTITY_ID, STATE_OFF, STATE_ON
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -163,25 +162,14 @@ async def test_siren_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the siren state trigger fires when the last siren changes to a specific state."""
|
||||
other_entity_ids = set(target_sirens["included"]) - {entity_id}
|
||||
|
||||
# Set all sirens, including the tested one, to the initial state
|
||||
for eid in target_sirens["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_sirens,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -725,6 +725,66 @@
|
||||
'state': 'medium',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_sound_mode-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'mute',
|
||||
'tone',
|
||||
'voice',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.robot_vacuum_sound_mode',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sound mode',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sound mode',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'robot_cleaner_sound_mode',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_samsungce.robotCleanerSystemSoundMode_soundMode_soundMode',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_sound_mode-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot Vacuum Sound mode',
|
||||
'options': list([
|
||||
'mute',
|
||||
'tone',
|
||||
'voice',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'select.robot_vacuum_sound_mode',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'tone',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_water_level-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
|
||||
@@ -20,19 +20,19 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_lights(hass: HomeAssistant) -> list[str]:
|
||||
async def target_lights(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple light entities associated with different targets.
|
||||
|
||||
Note: The lights are used to ensure that only switch entities are considered
|
||||
in the condition evaluation and not other toggle entities.
|
||||
"""
|
||||
return (await target_entities(hass, "light"))["included"]
|
||||
return await target_entities(hass, "light")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_switches(hass: HomeAssistant) -> list[str]:
|
||||
async def target_switches(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple switch entities associated with different targets."""
|
||||
return (await target_entities(hass, "switch"))["included"]
|
||||
return await target_entities(hass, "switch")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -71,8 +71,8 @@ async def test_switch_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_switch_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_lights: list[str],
|
||||
target_switches: list[str],
|
||||
target_lights: dict[str, list[str]],
|
||||
target_switches: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -81,10 +81,10 @@ async def test_switch_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the switch state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_switches) - {entity_id}
|
||||
other_entity_ids = set(target_switches["included"]) - {entity_id}
|
||||
|
||||
# Set all switches, including the tested switch, to the initial state
|
||||
for eid in target_switches:
|
||||
for eid in target_switches["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -97,7 +97,7 @@ async def test_switch_state_condition_behavior_any(
|
||||
|
||||
# Set state for lights to ensure that they don't impact the condition
|
||||
for state in states:
|
||||
for eid in target_lights:
|
||||
for eid in target_lights["included"]:
|
||||
set_or_remove_state(hass, eid, state["included"])
|
||||
await hass.async_block_till_done()
|
||||
assert condition(hass) is False
|
||||
@@ -137,7 +137,7 @@ async def test_switch_state_condition_behavior_any(
|
||||
)
|
||||
async def test_switch_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_switches: list[str],
|
||||
target_switches: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -150,10 +150,10 @@ async def test_switch_state_condition_behavior_all(
|
||||
hass.states.async_set("switch.label_switch_1", STATE_OFF)
|
||||
hass.states.async_set("switch.label_switch_2", STATE_ON)
|
||||
|
||||
other_entity_ids = set(target_switches) - {entity_id}
|
||||
other_entity_ids = set(target_switches["included"]) - {entity_id}
|
||||
|
||||
# Set all switches, including the tested switch, to the initial state
|
||||
for eid in target_switches:
|
||||
for eid in target_switches["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -5,18 +5,17 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.switch import DOMAIN
|
||||
from homeassistant.const import CONF_ENTITY_ID, STATE_OFF, STATE_ON
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -163,25 +162,14 @@ async def test_switch_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the switch state trigger fires when the last switch changes to a specific state."""
|
||||
other_entity_ids = set(target_switches["included"]) - {entity_id}
|
||||
|
||||
# Set all switches, including the tested one, to the initial state
|
||||
for eid in target_switches["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_switches,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
|
||||
from . import setup_platform
|
||||
from .conftest import create_config_entry
|
||||
from .const import VEHICLE_ASLEEP, VEHICLE_DATA_ALT
|
||||
from .const import LIVE_STATUS, VEHICLE_ASLEEP, VEHICLE_DATA_ALT
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
@@ -352,6 +352,42 @@ async def test_energy_live_refresh_error(
|
||||
assert normal_config_entry.state is state
|
||||
|
||||
|
||||
async def test_energy_live_refresh_bad_response(
|
||||
hass: HomeAssistant,
|
||||
normal_config_entry: MockConfigEntry,
|
||||
mock_live_status: AsyncMock,
|
||||
) -> None:
|
||||
"""Test coordinator refresh with malformed live status payload."""
|
||||
bad_live_status = deepcopy(LIVE_STATUS)
|
||||
bad_live_status["response"] = "site data is unavailable"
|
||||
mock_live_status.side_effect = None
|
||||
mock_live_status.return_value = bad_live_status
|
||||
|
||||
await setup_platform(hass, normal_config_entry)
|
||||
|
||||
assert normal_config_entry.state is ConfigEntryState.LOADED
|
||||
assert (state := hass.states.get("sensor.test_battery_level"))
|
||||
assert state.state != "unavailable"
|
||||
|
||||
|
||||
async def test_energy_live_refresh_bad_wall_connectors(
|
||||
hass: HomeAssistant,
|
||||
normal_config_entry: MockConfigEntry,
|
||||
mock_live_status: AsyncMock,
|
||||
) -> None:
|
||||
"""Test coordinator refresh with malformed wall connector payload."""
|
||||
bad_live_status = deepcopy(LIVE_STATUS)
|
||||
bad_live_status["response"]["wall_connectors"] = "site data is unavailable"
|
||||
mock_live_status.side_effect = None
|
||||
mock_live_status.return_value = bad_live_status
|
||||
|
||||
await setup_platform(hass, normal_config_entry)
|
||||
|
||||
assert normal_config_entry.state is ConfigEntryState.LOADED
|
||||
assert (state := hass.states.get("sensor.test_battery_level"))
|
||||
assert state.state != "unavailable"
|
||||
|
||||
|
||||
# Test Energy Site Coordinator
|
||||
@pytest.mark.parametrize(("side_effect", "state"), ERRORS)
|
||||
async def test_energy_site_refresh_error(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -10,12 +10,97 @@ from homeassistant.components.recorder import Recorder
|
||||
from homeassistant.components.tibber.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_component import async_update_entity
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .conftest import create_tibber_device
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
def _create_home(*, current_price: float | None = 1.25) -> MagicMock:
|
||||
"""Create a mocked Tibber home with an active subscription."""
|
||||
home = MagicMock()
|
||||
home.home_id = "home-id"
|
||||
home.name = "Home"
|
||||
home.currency = "NOK"
|
||||
home.price_unit = "NOK/kWh"
|
||||
home.has_active_subscription = True
|
||||
home.has_real_time_consumption = False
|
||||
home.last_data_timestamp = None
|
||||
home.update_info = AsyncMock(return_value=None)
|
||||
home.update_info_and_price_info = AsyncMock(return_value=None)
|
||||
home.current_price_data = MagicMock(
|
||||
return_value=(current_price, dt_util.utcnow(), 0.4)
|
||||
)
|
||||
home.current_attributes = MagicMock(
|
||||
return_value={
|
||||
"max_price": 1.8,
|
||||
"avg_price": 1.2,
|
||||
"min_price": 0.8,
|
||||
"off_peak_1": 0.9,
|
||||
"peak": 1.7,
|
||||
"off_peak_2": 1.0,
|
||||
}
|
||||
)
|
||||
home.month_cost = 111.1
|
||||
home.peak_hour = 2.5
|
||||
home.peak_hour_time = dt_util.utcnow()
|
||||
home.month_cons = 222.2
|
||||
home.hourly_consumption_data = []
|
||||
home.hourly_production_data = []
|
||||
home.info = {
|
||||
"viewer": {
|
||||
"home": {
|
||||
"appNickname": "Home",
|
||||
"address": {"address1": "Street 1"},
|
||||
"meteringPointData": {
|
||||
"gridCompany": "GridCo",
|
||||
"estimatedAnnualConsumption": 12000,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
return home
|
||||
|
||||
|
||||
async def test_price_sensor_state_unit_and_attributes(
|
||||
recorder_mock: Recorder,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
tibber_mock: MagicMock,
|
||||
setup_credentials: None,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test price sensor state and attributes."""
|
||||
home = _create_home(current_price=1.25)
|
||||
tibber_mock.get_homes.return_value = [home]
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = entity_registry.async_get_entity_id("sensor", DOMAIN, home.home_id)
|
||||
assert entity_id is not None
|
||||
|
||||
await async_update_entity(hass, entity_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert float(state.state) == 1.25
|
||||
assert state.attributes["unit_of_measurement"] == "NOK/kWh"
|
||||
assert state.attributes["app_nickname"] == "Home"
|
||||
assert state.attributes["grid_company"] == "GridCo"
|
||||
assert state.attributes["estimated_annual_consumption"] == 12000
|
||||
assert state.attributes["intraday_price_ranking"] == 0.4
|
||||
assert state.attributes["max_price"] == 1.8
|
||||
assert state.attributes["avg_price"] == 1.2
|
||||
assert state.attributes["min_price"] == 0.8
|
||||
assert state.attributes["off_peak_1"] == 0.9
|
||||
assert state.attributes["peak"] == 1.7
|
||||
assert state.attributes["off_peak_2"] == 1.0
|
||||
|
||||
|
||||
async def test_data_api_sensors_are_created(
|
||||
recorder_mock: Recorder,
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -24,6 +24,10 @@ async def test_async_setup_entry(
|
||||
tibber_connection.fetch_production_data_active_homes.return_value = None
|
||||
tibber_connection.get_homes = mock_get_homes
|
||||
|
||||
runtime_data = AsyncMock()
|
||||
runtime_data.async_get_client.return_value = tibber_connection
|
||||
config_entry.runtime_data = runtime_data
|
||||
|
||||
coordinator = TibberDataCoordinator(hass, config_entry, tibber_connection)
|
||||
await coordinator._async_update_data()
|
||||
await async_wait_recording_done(hass)
|
||||
|
||||
@@ -5,18 +5,17 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.update import DOMAIN
|
||||
from homeassistant.const import CONF_ENTITY_ID, STATE_OFF, STATE_ON
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -147,25 +146,14 @@ async def test_update_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the update state trigger fires when the last update changes to a specific state."""
|
||||
other_entity_ids = set(target_updates["included"]) - {entity_id}
|
||||
|
||||
# Set all updates, including the tested one, to the initial state
|
||||
for eid in target_updates["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_updates,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -21,9 +21,9 @@ from tests.components.common import (
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def target_vacuums(hass: HomeAssistant) -> list[str]:
|
||||
async def target_vacuums(hass: HomeAssistant) -> dict[str, list[str]]:
|
||||
"""Create multiple vacuum entities associated with different targets."""
|
||||
return (await target_entities(hass, "vacuum"))["included"]
|
||||
return await target_entities(hass, "vacuum")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -80,7 +80,7 @@ async def test_vacuum_conditions_gated_by_labs_flag(
|
||||
)
|
||||
async def test_vacuum_state_condition_behavior_any(
|
||||
hass: HomeAssistant,
|
||||
target_vacuums: list[str],
|
||||
target_vacuums: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -89,10 +89,10 @@ async def test_vacuum_state_condition_behavior_any(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the vacuum state condition with the 'any' behavior."""
|
||||
other_entity_ids = set(target_vacuums) - {entity_id}
|
||||
other_entity_ids = set(target_vacuums["included"]) - {entity_id}
|
||||
|
||||
# Set all vacuums, including the tested vacuum, to the initial state
|
||||
for eid in target_vacuums:
|
||||
for eid in target_vacuums["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -153,7 +153,7 @@ async def test_vacuum_state_condition_behavior_any(
|
||||
)
|
||||
async def test_vacuum_state_condition_behavior_all(
|
||||
hass: HomeAssistant,
|
||||
target_vacuums: list[str],
|
||||
target_vacuums: dict[str, list[str]],
|
||||
condition_target_config: dict,
|
||||
entity_id: str,
|
||||
entities_in_target: int,
|
||||
@@ -162,10 +162,10 @@ async def test_vacuum_state_condition_behavior_all(
|
||||
states: list[ConditionStateDescription],
|
||||
) -> None:
|
||||
"""Test the vacuum state condition with the 'all' behavior."""
|
||||
other_entity_ids = set(target_vacuums) - {entity_id}
|
||||
other_entity_ids = set(target_vacuums["included"]) - {entity_id}
|
||||
|
||||
# Set all vacuums, including the tested vacuum, to the initial state
|
||||
for eid in target_vacuums:
|
||||
for eid in target_vacuums["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -5,19 +5,17 @@ from typing import Any
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.vacuum import VacuumActivity
|
||||
from homeassistant.const import CONF_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from tests.components.common import (
|
||||
TriggerStateDescription,
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
other_states,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -212,25 +210,14 @@ async def test_vacuum_state_trigger_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test that the vacuum state trigger fires when the last vacuum changes to a specific state."""
|
||||
other_entity_ids = set(target_vacuums["included"]) - {entity_id}
|
||||
|
||||
# Set all vacuums, including the tested one, to the initial state
|
||||
for eid in target_vacuums["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_vacuums,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
@@ -146,11 +146,6 @@ async def test_avaliable_after_update(
|
||||
{ATTR_ENTITY_ID: [wemo_entity.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
# _wemo_call_wrapper schedules async_update_listeners via hass.add_job
|
||||
# from the executor thread, which goes through two levels of call_soon
|
||||
# before the entity state is written.
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get(wemo_entity.entity_id).state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@@ -73,11 +73,6 @@ async def test_turn_on_brightness(
|
||||
{ATTR_ENTITY_ID: [wemo_entity.entity_id], ATTR_BRIGHTNESS: 204},
|
||||
blocking=True,
|
||||
)
|
||||
# _wemo_call_wrapper schedules async_update_listeners via hass.add_job
|
||||
# from the executor thread, which goes through two levels of call_soon
|
||||
# before the entity state is written.
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
pywemo_device.set_brightness.assert_called_once_with(80)
|
||||
states = hass.states.get(wemo_entity.entity_id)
|
||||
|
||||
@@ -13,10 +13,10 @@ from tests.components.common import (
|
||||
arm_trigger,
|
||||
assert_trigger_behavior_any,
|
||||
assert_trigger_behavior_first,
|
||||
assert_trigger_behavior_last,
|
||||
assert_trigger_gated_by_labs_flag,
|
||||
parametrize_target_entities,
|
||||
parametrize_trigger_states,
|
||||
set_or_remove_state,
|
||||
target_entities,
|
||||
)
|
||||
|
||||
@@ -251,37 +251,17 @@ async def test_window_trigger_binary_sensor_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test window trigger fires when the last binary_sensor changes state."""
|
||||
other_entity_ids = set(target_binary_sensors["included"]) - {entity_id}
|
||||
excluded_entity_ids = set(target_binary_sensors["excluded"]) - {entity_id}
|
||||
|
||||
for eid in target_binary_sensors["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
for eid in excluded_entity_ids:
|
||||
set_or_remove_state(hass, eid, states[0]["excluded"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
excluded_state = state["excluded"]
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
for excluded_entity_id in excluded_entity_ids:
|
||||
set_or_remove_state(hass, excluded_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_binary_sensors,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
@@ -411,37 +391,17 @@ async def test_window_trigger_cover_behavior_last(
|
||||
states: list[TriggerStateDescription],
|
||||
) -> None:
|
||||
"""Test window trigger fires when the last cover changes state."""
|
||||
other_entity_ids = set(target_covers["included"]) - {entity_id}
|
||||
excluded_entity_ids = set(target_covers["excluded"]) - {entity_id}
|
||||
|
||||
for eid in target_covers["included"]:
|
||||
set_or_remove_state(hass, eid, states[0]["included"])
|
||||
await hass.async_block_till_done()
|
||||
for eid in excluded_entity_ids:
|
||||
set_or_remove_state(hass, eid, states[0]["excluded"])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||
|
||||
for state in states[1:]:
|
||||
excluded_state = state["excluded"]
|
||||
included_state = state["included"]
|
||||
for other_entity_id in other_entity_ids:
|
||||
set_or_remove_state(hass, other_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
set_or_remove_state(hass, entity_id, included_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == state["count"]
|
||||
for service_call in service_calls:
|
||||
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||
service_calls.clear()
|
||||
|
||||
for excluded_entity_id in excluded_entity_ids:
|
||||
set_or_remove_state(hass, excluded_entity_id, excluded_state)
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
await assert_trigger_behavior_last(
|
||||
hass,
|
||||
service_calls=service_calls,
|
||||
target_entities=target_covers,
|
||||
trigger_target_config=trigger_target_config,
|
||||
entity_id=entity_id,
|
||||
entities_in_target=entities_in_target,
|
||||
trigger=trigger,
|
||||
trigger_options=trigger_options,
|
||||
states=states,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_labs_preview_features")
|
||||
|
||||
Reference in New Issue
Block a user