mirror of
https://github.com/home-assistant/core.git
synced 2025-08-05 05:35:11 +02:00
Merge pull request #67838 from home-assistant/rc
This commit is contained in:
@@ -1,13 +1,13 @@
|
|||||||
"""Support for Elgato Lights."""
|
"""Support for Elgato Lights."""
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
|
|
||||||
from elgato import Elgato, Info, State
|
from elgato import Elgato, ElgatoConnectionError, Info, State
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_PORT, Platform
|
from homeassistant.const import CONF_HOST, CONF_PORT, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import DOMAIN, LOGGER, SCAN_INTERVAL
|
from .const import DOMAIN, LOGGER, SCAN_INTERVAL
|
||||||
|
|
||||||
@@ -31,12 +31,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
session=session,
|
session=session,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _async_update_data() -> State:
|
||||||
|
"""Fetch Elgato data."""
|
||||||
|
try:
|
||||||
|
return await elgato.state()
|
||||||
|
except ElgatoConnectionError as err:
|
||||||
|
raise UpdateFailed(err) from err
|
||||||
|
|
||||||
coordinator: DataUpdateCoordinator[State] = DataUpdateCoordinator(
|
coordinator: DataUpdateCoordinator[State] = DataUpdateCoordinator(
|
||||||
hass,
|
hass,
|
||||||
LOGGER,
|
LOGGER,
|
||||||
name=f"{DOMAIN}_{entry.data[CONF_HOST]}",
|
name=f"{DOMAIN}_{entry.data[CONF_HOST]}",
|
||||||
update_interval=SCAN_INTERVAL,
|
update_interval=SCAN_INTERVAL,
|
||||||
update_method=elgato.state,
|
update_method=_async_update_data,
|
||||||
)
|
)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
@@ -392,6 +392,8 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator):
|
|||||||
)
|
)
|
||||||
self.mesh_role = MeshRoles.NONE
|
self.mesh_role = MeshRoles.NONE
|
||||||
for mac, info in hosts.items():
|
for mac, info in hosts.items():
|
||||||
|
if info.ip_address:
|
||||||
|
info.wan_access = self._get_wan_access(info.ip_address)
|
||||||
if self.manage_device_info(info, mac, consider_home):
|
if self.manage_device_info(info, mac, consider_home):
|
||||||
new_device = True
|
new_device = True
|
||||||
self.send_signal_device_update(new_device)
|
self.send_signal_device_update(new_device)
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"name": "Home Assistant Frontend",
|
"name": "Home Assistant Frontend",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"home-assistant-frontend==20220301.0"
|
"home-assistant-frontend==20220301.1"
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"api",
|
"api",
|
||||||
@@ -13,7 +13,8 @@
|
|||||||
"diagnostics",
|
"diagnostics",
|
||||||
"http",
|
"http",
|
||||||
"lovelace",
|
"lovelace",
|
||||||
"onboarding", "search",
|
"onboarding",
|
||||||
|
"search",
|
||||||
"system_log",
|
"system_log",
|
||||||
"websocket_api"
|
"websocket_api"
|
||||||
],
|
],
|
||||||
|
@@ -222,7 +222,7 @@ class GrowattData:
|
|||||||
date_now = dt.now().date()
|
date_now = dt.now().date()
|
||||||
last_updated_time = dt.parse_time(str(sorted_keys[-1]))
|
last_updated_time = dt.parse_time(str(sorted_keys[-1]))
|
||||||
mix_detail["lastdataupdate"] = datetime.datetime.combine(
|
mix_detail["lastdataupdate"] = datetime.datetime.combine(
|
||||||
date_now, last_updated_time
|
date_now, last_updated_time, dt.DEFAULT_TIME_ZONE
|
||||||
)
|
)
|
||||||
|
|
||||||
# Dashboard data is largely inaccurate for mix system but it is the only call with the ability to return the combined
|
# Dashboard data is largely inaccurate for mix system but it is the only call with the ability to return the combined
|
||||||
|
@@ -285,16 +285,15 @@ class Thermostat(HomeAccessory):
|
|||||||
CHAR_CURRENT_HUMIDITY, value=50
|
CHAR_CURRENT_HUMIDITY, value=50
|
||||||
)
|
)
|
||||||
|
|
||||||
fan_modes = self.fan_modes = {
|
fan_modes = {}
|
||||||
fan_mode.lower(): fan_mode
|
|
||||||
for fan_mode in attributes.get(ATTR_FAN_MODES, [])
|
|
||||||
}
|
|
||||||
self.ordered_fan_speeds = []
|
self.ordered_fan_speeds = []
|
||||||
if (
|
|
||||||
features & SUPPORT_FAN_MODE
|
if features & SUPPORT_FAN_MODE:
|
||||||
and fan_modes
|
fan_modes = {
|
||||||
and PRE_DEFINED_FAN_MODES.intersection(fan_modes)
|
fan_mode.lower(): fan_mode
|
||||||
):
|
for fan_mode in attributes.get(ATTR_FAN_MODES) or []
|
||||||
|
}
|
||||||
|
if fan_modes and PRE_DEFINED_FAN_MODES.intersection(fan_modes):
|
||||||
self.ordered_fan_speeds = [
|
self.ordered_fan_speeds = [
|
||||||
speed for speed in ORDERED_FAN_SPEEDS if speed in fan_modes
|
speed for speed in ORDERED_FAN_SPEEDS if speed in fan_modes
|
||||||
]
|
]
|
||||||
|
@@ -271,7 +271,7 @@ _PLATFORM_SCHEMA_BASE = SCHEMA_BASE.extend(
|
|||||||
vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||||
vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template,
|
vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
||||||
vol.Optional(CONF_HOLD_LIST, default=list): cv.ensure_list,
|
vol.Optional(CONF_HOLD_LIST): cv.ensure_list,
|
||||||
vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template,
|
vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
@@ -298,7 +298,7 @@ _PLATFORM_SCHEMA_BASE = SCHEMA_BASE.extend(
|
|||||||
),
|
),
|
||||||
vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
|
vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
|
||||||
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
||||||
vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
|
vol.Optional(CONF_SEND_IF_OFF): cv.boolean,
|
||||||
vol.Optional(CONF_ACTION_TEMPLATE): cv.template,
|
vol.Optional(CONF_ACTION_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_ACTION_TOPIC): mqtt.valid_subscribe_topic,
|
vol.Optional(CONF_ACTION_TOPIC): mqtt.valid_subscribe_topic,
|
||||||
# CONF_PRESET_MODE_COMMAND_TOPIC and CONF_PRESET_MODES_LIST must be used together
|
# CONF_PRESET_MODE_COMMAND_TOPIC and CONF_PRESET_MODES_LIST must be used together
|
||||||
@@ -431,6 +431,12 @@ class MqttClimate(MqttEntity, ClimateEntity):
|
|||||||
self._feature_preset_mode = False
|
self._feature_preset_mode = False
|
||||||
self._optimistic_preset_mode = None
|
self._optimistic_preset_mode = None
|
||||||
|
|
||||||
|
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
||||||
|
self._send_if_off = True
|
||||||
|
# AWAY and HOLD mode topics and templates are deprecated,
|
||||||
|
# support will be removed with release 2022.9
|
||||||
|
self._hold_list = []
|
||||||
|
|
||||||
MqttEntity.__init__(self, hass, config, config_entry, discovery_data)
|
MqttEntity.__init__(self, hass, config, config_entry, discovery_data)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -499,6 +505,15 @@ class MqttClimate(MqttEntity, ClimateEntity):
|
|||||||
|
|
||||||
self._command_templates = command_templates
|
self._command_templates = command_templates
|
||||||
|
|
||||||
|
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
||||||
|
if CONF_SEND_IF_OFF in config:
|
||||||
|
self._send_if_off = config[CONF_SEND_IF_OFF]
|
||||||
|
|
||||||
|
# AWAY and HOLD mode topics and templates are deprecated,
|
||||||
|
# support will be removed with release 2022.9
|
||||||
|
if CONF_HOLD_LIST in config:
|
||||||
|
self._hold_list = config[CONF_HOLD_LIST]
|
||||||
|
|
||||||
def _prepare_subscribe_topics(self): # noqa: C901
|
def _prepare_subscribe_topics(self): # noqa: C901
|
||||||
"""(Re)Subscribe to topics."""
|
"""(Re)Subscribe to topics."""
|
||||||
topics = {}
|
topics = {}
|
||||||
@@ -806,7 +821,9 @@ class MqttClimate(MqttEntity, ClimateEntity):
|
|||||||
):
|
):
|
||||||
presets.append(PRESET_AWAY)
|
presets.append(PRESET_AWAY)
|
||||||
|
|
||||||
presets.extend(self._config[CONF_HOLD_LIST])
|
# AWAY and HOLD mode topics and templates are deprecated,
|
||||||
|
# support will be removed with release 2022.9
|
||||||
|
presets.extend(self._hold_list)
|
||||||
|
|
||||||
if presets:
|
if presets:
|
||||||
presets.insert(0, PRESET_NONE)
|
presets.insert(0, PRESET_NONE)
|
||||||
@@ -847,10 +864,7 @@ class MqttClimate(MqttEntity, ClimateEntity):
|
|||||||
setattr(self, attr, temp)
|
setattr(self, attr, temp)
|
||||||
|
|
||||||
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
||||||
if (
|
if self._send_if_off or self._current_operation != HVAC_MODE_OFF:
|
||||||
self._config[CONF_SEND_IF_OFF]
|
|
||||||
or self._current_operation != HVAC_MODE_OFF
|
|
||||||
):
|
|
||||||
payload = self._command_templates[cmnd_template](temp)
|
payload = self._command_templates[cmnd_template](temp)
|
||||||
await self._publish(cmnd_topic, payload)
|
await self._publish(cmnd_topic, payload)
|
||||||
|
|
||||||
@@ -890,7 +904,7 @@ class MqttClimate(MqttEntity, ClimateEntity):
|
|||||||
async def async_set_swing_mode(self, swing_mode):
|
async def async_set_swing_mode(self, swing_mode):
|
||||||
"""Set new swing mode."""
|
"""Set new swing mode."""
|
||||||
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
||||||
if self._config[CONF_SEND_IF_OFF] or self._current_operation != HVAC_MODE_OFF:
|
if self._send_if_off or self._current_operation != HVAC_MODE_OFF:
|
||||||
payload = self._command_templates[CONF_SWING_MODE_COMMAND_TEMPLATE](
|
payload = self._command_templates[CONF_SWING_MODE_COMMAND_TEMPLATE](
|
||||||
swing_mode
|
swing_mode
|
||||||
)
|
)
|
||||||
@@ -903,7 +917,7 @@ class MqttClimate(MqttEntity, ClimateEntity):
|
|||||||
async def async_set_fan_mode(self, fan_mode):
|
async def async_set_fan_mode(self, fan_mode):
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
||||||
if self._config[CONF_SEND_IF_OFF] or self._current_operation != HVAC_MODE_OFF:
|
if self._send_if_off or self._current_operation != HVAC_MODE_OFF:
|
||||||
payload = self._command_templates[CONF_FAN_MODE_COMMAND_TEMPLATE](fan_mode)
|
payload = self._command_templates[CONF_FAN_MODE_COMMAND_TEMPLATE](fan_mode)
|
||||||
await self._publish(CONF_FAN_MODE_COMMAND_TOPIC, payload)
|
await self._publish(CONF_FAN_MODE_COMMAND_TOPIC, payload)
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.components.light import ATTR_TRANSITION
|
from homeassistant.components.light import ATTR_TRANSITION
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_PLATFORM, SERVICE_TURN_ON
|
from homeassistant.const import CONF_PLATFORM, SERVICE_TURN_ON, STATE_UNAVAILABLE
|
||||||
from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant
|
from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
@@ -117,7 +117,11 @@ class Scene(RestoreEntity):
|
|||||||
"""Call when the scene is added to hass."""
|
"""Call when the scene is added to hass."""
|
||||||
await super().async_internal_added_to_hass()
|
await super().async_internal_added_to_hass()
|
||||||
state = await self.async_get_last_state()
|
state = await self.async_get_last_state()
|
||||||
if state is not None and state.state is not None:
|
if (
|
||||||
|
state is not None
|
||||||
|
and state.state is not None
|
||||||
|
and state.state != STATE_UNAVAILABLE
|
||||||
|
):
|
||||||
self.__last_activated = state.state
|
self.__last_activated = state.state
|
||||||
|
|
||||||
def activate(self, **kwargs: Any) -> None:
|
def activate(self, **kwargs: Any) -> None:
|
||||||
|
@@ -15,6 +15,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
|||||||
|
|
||||||
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER, TIMEOUT
|
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER, TIMEOUT
|
||||||
|
|
||||||
|
MAX_POSSIBLE_STEP = 1000
|
||||||
|
|
||||||
|
|
||||||
class SensiboDataUpdateCoordinator(DataUpdateCoordinator):
|
class SensiboDataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
"""A Sensibo Data Update Coordinator."""
|
"""A Sensibo Data Update Coordinator."""
|
||||||
@@ -74,7 +76,11 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
.get("values", [0, 1])
|
.get("values", [0, 1])
|
||||||
)
|
)
|
||||||
if temperatures_list:
|
if temperatures_list:
|
||||||
temperature_step = temperatures_list[1] - temperatures_list[0]
|
diff = MAX_POSSIBLE_STEP
|
||||||
|
for i in range(len(temperatures_list) - 1):
|
||||||
|
if temperatures_list[i + 1] - temperatures_list[i] < diff:
|
||||||
|
diff = temperatures_list[i + 1] - temperatures_list[i]
|
||||||
|
temperature_step = diff
|
||||||
|
|
||||||
active_features = list(ac_states)
|
active_features = list(ac_states)
|
||||||
full_features = set()
|
full_features = set()
|
||||||
|
@@ -317,4 +317,14 @@ class BlockSleepingClimate(
|
|||||||
|
|
||||||
if self.device_block and self.block:
|
if self.device_block and self.block:
|
||||||
_LOGGER.debug("Entity %s attached to blocks", self.name)
|
_LOGGER.debug("Entity %s attached to blocks", self.name)
|
||||||
|
|
||||||
|
assert self.block.channel
|
||||||
|
|
||||||
|
self._preset_modes = [
|
||||||
|
PRESET_NONE,
|
||||||
|
*self.wrapper.device.settings["thermostats"][int(self.block.channel)][
|
||||||
|
"schedule_profile_names"
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"name": "Xiaomi Miio",
|
"name": "Xiaomi Miio",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/xiaomi_miio",
|
"documentation": "https://www.home-assistant.io/integrations/xiaomi_miio",
|
||||||
"requirements": ["construct==2.10.56", "micloud==0.5", "python-miio==0.5.10"],
|
"requirements": ["construct==2.10.56", "micloud==0.5", "python-miio==0.5.11"],
|
||||||
"codeowners": ["@rytilahti", "@syssi", "@starkillerOG", "@bieniu"],
|
"codeowners": ["@rytilahti", "@syssi", "@starkillerOG", "@bieniu"],
|
||||||
"zeroconf": ["_miio._udp.local."],
|
"zeroconf": ["_miio._udp.local."],
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
|
@@ -7,7 +7,7 @@ from .backports.enum import StrEnum
|
|||||||
|
|
||||||
MAJOR_VERSION: Final = 2022
|
MAJOR_VERSION: Final = 2022
|
||||||
MINOR_VERSION: Final = 3
|
MINOR_VERSION: Final = 3
|
||||||
PATCH_VERSION: Final = "2"
|
PATCH_VERSION: Final = "3"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
||||||
|
@@ -6,6 +6,7 @@ import asyncio
|
|||||||
from collections.abc import Awaitable, Iterable, Mapping, MutableMapping
|
from collections.abc import Awaitable, Iterable, Mapping, MutableMapping
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from enum import Enum, auto
|
||||||
import functools as ft
|
import functools as ft
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
@@ -207,6 +208,19 @@ class EntityCategory(StrEnum):
|
|||||||
SYSTEM = "system"
|
SYSTEM = "system"
|
||||||
|
|
||||||
|
|
||||||
|
class EntityPlatformState(Enum):
|
||||||
|
"""The platform state of an entity."""
|
||||||
|
|
||||||
|
# Not Added: Not yet added to a platform, polling updates are written to the state machine
|
||||||
|
NOT_ADDED = auto()
|
||||||
|
|
||||||
|
# Added: Added to a platform, polling updates are written to the state machine
|
||||||
|
ADDED = auto()
|
||||||
|
|
||||||
|
# Removed: Removed from a platform, polling updates are not written to the state machine
|
||||||
|
REMOVED = auto()
|
||||||
|
|
||||||
|
|
||||||
def convert_to_entity_category(
|
def convert_to_entity_category(
|
||||||
value: EntityCategory | str | None, raise_report: bool = True
|
value: EntityCategory | str | None, raise_report: bool = True
|
||||||
) -> EntityCategory | None:
|
) -> EntityCategory | None:
|
||||||
@@ -294,7 +308,7 @@ class Entity(ABC):
|
|||||||
_context_set: datetime | None = None
|
_context_set: datetime | None = None
|
||||||
|
|
||||||
# If entity is added to an entity platform
|
# If entity is added to an entity platform
|
||||||
_added = False
|
_platform_state = EntityPlatformState.NOT_ADDED
|
||||||
|
|
||||||
# Entity Properties
|
# Entity Properties
|
||||||
_attr_assumed_state: bool = False
|
_attr_assumed_state: bool = False
|
||||||
@@ -553,6 +567,10 @@ class Entity(ABC):
|
|||||||
@callback
|
@callback
|
||||||
def _async_write_ha_state(self) -> None:
|
def _async_write_ha_state(self) -> None:
|
||||||
"""Write the state to the state machine."""
|
"""Write the state to the state machine."""
|
||||||
|
if self._platform_state == EntityPlatformState.REMOVED:
|
||||||
|
# Polling returned after the entity has already been removed
|
||||||
|
return
|
||||||
|
|
||||||
if self.registry_entry and self.registry_entry.disabled_by:
|
if self.registry_entry and self.registry_entry.disabled_by:
|
||||||
if not self._disabled_reported:
|
if not self._disabled_reported:
|
||||||
self._disabled_reported = True
|
self._disabled_reported = True
|
||||||
@@ -758,7 +776,7 @@ class Entity(ABC):
|
|||||||
parallel_updates: asyncio.Semaphore | None,
|
parallel_updates: asyncio.Semaphore | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Start adding an entity to a platform."""
|
"""Start adding an entity to a platform."""
|
||||||
if self._added:
|
if self._platform_state == EntityPlatformState.ADDED:
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
f"Entity {self.entity_id} cannot be added a second time to an entity platform"
|
f"Entity {self.entity_id} cannot be added a second time to an entity platform"
|
||||||
)
|
)
|
||||||
@@ -766,7 +784,7 @@ class Entity(ABC):
|
|||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.platform = platform
|
self.platform = platform
|
||||||
self.parallel_updates = parallel_updates
|
self.parallel_updates = parallel_updates
|
||||||
self._added = True
|
self._platform_state = EntityPlatformState.ADDED
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def add_to_platform_abort(self) -> None:
|
def add_to_platform_abort(self) -> None:
|
||||||
@@ -774,7 +792,7 @@ class Entity(ABC):
|
|||||||
self.hass = None # type: ignore[assignment]
|
self.hass = None # type: ignore[assignment]
|
||||||
self.platform = None
|
self.platform = None
|
||||||
self.parallel_updates = None
|
self.parallel_updates = None
|
||||||
self._added = False
|
self._platform_state = EntityPlatformState.NOT_ADDED
|
||||||
|
|
||||||
async def add_to_platform_finish(self) -> None:
|
async def add_to_platform_finish(self) -> None:
|
||||||
"""Finish adding an entity to a platform."""
|
"""Finish adding an entity to a platform."""
|
||||||
@@ -792,12 +810,12 @@ class Entity(ABC):
|
|||||||
If the entity doesn't have a non disabled entry in the entity registry,
|
If the entity doesn't have a non disabled entry in the entity registry,
|
||||||
or if force_remove=True, its state will be removed.
|
or if force_remove=True, its state will be removed.
|
||||||
"""
|
"""
|
||||||
if self.platform and not self._added:
|
if self.platform and self._platform_state != EntityPlatformState.ADDED:
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
f"Entity {self.entity_id} async_remove called twice"
|
f"Entity {self.entity_id} async_remove called twice"
|
||||||
)
|
)
|
||||||
|
|
||||||
self._added = False
|
self._platform_state = EntityPlatformState.REMOVED
|
||||||
|
|
||||||
if self._on_remove is not None:
|
if self._on_remove is not None:
|
||||||
while self._on_remove:
|
while self._on_remove:
|
||||||
|
@@ -14,7 +14,7 @@ certifi>=2021.5.30
|
|||||||
ciso8601==2.2.0
|
ciso8601==2.2.0
|
||||||
cryptography==35.0.0
|
cryptography==35.0.0
|
||||||
hass-nabucasa==0.54.0
|
hass-nabucasa==0.54.0
|
||||||
home-assistant-frontend==20220301.0
|
home-assistant-frontend==20220301.1
|
||||||
httpx==0.21.3
|
httpx==0.21.3
|
||||||
ifaddr==0.1.7
|
ifaddr==0.1.7
|
||||||
jinja2==3.0.3
|
jinja2==3.0.3
|
||||||
|
@@ -843,7 +843,7 @@ hole==0.7.0
|
|||||||
holidays==0.13
|
holidays==0.13
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20220301.0
|
home-assistant-frontend==20220301.1
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
# homeassistant-pyozw==0.1.10
|
# homeassistant-pyozw==0.1.10
|
||||||
@@ -1952,7 +1952,7 @@ python-kasa==0.4.1
|
|||||||
# python-lirc==1.2.3
|
# python-lirc==1.2.3
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_miio
|
# homeassistant.components.xiaomi_miio
|
||||||
python-miio==0.5.10
|
python-miio==0.5.11
|
||||||
|
|
||||||
# homeassistant.components.mpd
|
# homeassistant.components.mpd
|
||||||
python-mpd2==3.0.4
|
python-mpd2==3.0.4
|
||||||
|
@@ -553,7 +553,7 @@ hole==0.7.0
|
|||||||
holidays==0.13
|
holidays==0.13
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20220301.0
|
home-assistant-frontend==20220301.1
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
# homeassistant-pyozw==0.1.10
|
# homeassistant-pyozw==0.1.10
|
||||||
@@ -1219,7 +1219,7 @@ python-juicenet==1.0.2
|
|||||||
python-kasa==0.4.1
|
python-kasa==0.4.1
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_miio
|
# homeassistant.components.xiaomi_miio
|
||||||
python-miio==0.5.10
|
python-miio==0.5.11
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
python-nest==4.2.0
|
python-nest==4.2.0
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = homeassistant
|
name = homeassistant
|
||||||
version = 2022.3.2
|
version = 2022.3.3
|
||||||
author = The Home Assistant Authors
|
author = The Home Assistant Authors
|
||||||
author_email = hello@home-assistant.io
|
author_email = hello@home-assistant.io
|
||||||
license = Apache-2.0
|
license = Apache-2.0
|
||||||
|
@@ -15,6 +15,8 @@ from homeassistant.components.climate.const import (
|
|||||||
ATTR_HVAC_MODES,
|
ATTR_HVAC_MODES,
|
||||||
ATTR_MAX_TEMP,
|
ATTR_MAX_TEMP,
|
||||||
ATTR_MIN_TEMP,
|
ATTR_MIN_TEMP,
|
||||||
|
ATTR_PRESET_MODE,
|
||||||
|
ATTR_PRESET_MODES,
|
||||||
ATTR_SWING_MODE,
|
ATTR_SWING_MODE,
|
||||||
ATTR_SWING_MODES,
|
ATTR_SWING_MODES,
|
||||||
ATTR_TARGET_TEMP_HIGH,
|
ATTR_TARGET_TEMP_HIGH,
|
||||||
@@ -74,6 +76,7 @@ from homeassistant.components.homekit.type_thermostats import (
|
|||||||
from homeassistant.components.water_heater import DOMAIN as DOMAIN_WATER_HEATER
|
from homeassistant.components.water_heater import DOMAIN as DOMAIN_WATER_HEATER
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
|
ATTR_FRIENDLY_NAME,
|
||||||
ATTR_SUPPORTED_FEATURES,
|
ATTR_SUPPORTED_FEATURES,
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
CONF_TEMPERATURE_UNIT,
|
CONF_TEMPERATURE_UNIT,
|
||||||
@@ -2349,3 +2352,127 @@ async def test_thermostat_with_fan_modes_with_off(hass, hk_driver, events):
|
|||||||
assert len(call_set_fan_mode) == 2
|
assert len(call_set_fan_mode) == 2
|
||||||
assert call_set_fan_mode[-1].data[ATTR_ENTITY_ID] == entity_id
|
assert call_set_fan_mode[-1].data[ATTR_ENTITY_ID] == entity_id
|
||||||
assert call_set_fan_mode[-1].data[ATTR_FAN_MODE] == FAN_OFF
|
assert call_set_fan_mode[-1].data[ATTR_FAN_MODE] == FAN_OFF
|
||||||
|
|
||||||
|
|
||||||
|
async def test_thermostat_with_fan_modes_set_to_none(hass, hk_driver, events):
|
||||||
|
"""Test a thermostate with fan modes set to None."""
|
||||||
|
entity_id = "climate.test"
|
||||||
|
hass.states.async_set(
|
||||||
|
entity_id,
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
{
|
||||||
|
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE
|
||||||
|
| SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||||
|
| SUPPORT_FAN_MODE
|
||||||
|
| SUPPORT_SWING_MODE,
|
||||||
|
ATTR_FAN_MODES: None,
|
||||||
|
ATTR_SWING_MODES: [SWING_BOTH, SWING_OFF, SWING_HORIZONTAL],
|
||||||
|
ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE,
|
||||||
|
ATTR_FAN_MODE: FAN_AUTO,
|
||||||
|
ATTR_SWING_MODE: SWING_BOTH,
|
||||||
|
ATTR_HVAC_MODES: [
|
||||||
|
HVAC_MODE_HEAT,
|
||||||
|
HVAC_MODE_HEAT_COOL,
|
||||||
|
HVAC_MODE_FAN_ONLY,
|
||||||
|
HVAC_MODE_COOL,
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
HVAC_MODE_AUTO,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||||
|
hk_driver.add_accessory(acc)
|
||||||
|
|
||||||
|
await acc.run()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert acc.char_cooling_thresh_temp.value == 23.0
|
||||||
|
assert acc.char_heating_thresh_temp.value == 19.0
|
||||||
|
assert acc.ordered_fan_speeds == []
|
||||||
|
assert CHAR_ROTATION_SPEED not in acc.fan_chars
|
||||||
|
assert CHAR_TARGET_FAN_STATE not in acc.fan_chars
|
||||||
|
assert CHAR_SWING_MODE in acc.fan_chars
|
||||||
|
assert CHAR_CURRENT_FAN_STATE in acc.fan_chars
|
||||||
|
|
||||||
|
|
||||||
|
async def test_thermostat_with_fan_modes_set_to_none_not_supported(
|
||||||
|
hass, hk_driver, events
|
||||||
|
):
|
||||||
|
"""Test a thermostate with fan modes set to None and supported feature missing."""
|
||||||
|
entity_id = "climate.test"
|
||||||
|
hass.states.async_set(
|
||||||
|
entity_id,
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
{
|
||||||
|
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE
|
||||||
|
| SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||||
|
| SUPPORT_SWING_MODE,
|
||||||
|
ATTR_FAN_MODES: None,
|
||||||
|
ATTR_SWING_MODES: [SWING_BOTH, SWING_OFF, SWING_HORIZONTAL],
|
||||||
|
ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE,
|
||||||
|
ATTR_FAN_MODE: FAN_AUTO,
|
||||||
|
ATTR_SWING_MODE: SWING_BOTH,
|
||||||
|
ATTR_HVAC_MODES: [
|
||||||
|
HVAC_MODE_HEAT,
|
||||||
|
HVAC_MODE_HEAT_COOL,
|
||||||
|
HVAC_MODE_FAN_ONLY,
|
||||||
|
HVAC_MODE_COOL,
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
HVAC_MODE_AUTO,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||||
|
hk_driver.add_accessory(acc)
|
||||||
|
|
||||||
|
await acc.run()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert acc.char_cooling_thresh_temp.value == 23.0
|
||||||
|
assert acc.char_heating_thresh_temp.value == 19.0
|
||||||
|
assert acc.ordered_fan_speeds == []
|
||||||
|
assert CHAR_ROTATION_SPEED not in acc.fan_chars
|
||||||
|
assert CHAR_TARGET_FAN_STATE not in acc.fan_chars
|
||||||
|
assert CHAR_SWING_MODE in acc.fan_chars
|
||||||
|
assert CHAR_CURRENT_FAN_STATE in acc.fan_chars
|
||||||
|
|
||||||
|
|
||||||
|
async def test_thermostat_with_supported_features_target_temp_but_fan_mode_set(
|
||||||
|
hass, hk_driver, events
|
||||||
|
):
|
||||||
|
"""Test a thermostate with fan mode and supported feature missing."""
|
||||||
|
entity_id = "climate.test"
|
||||||
|
hass.states.async_set(
|
||||||
|
entity_id,
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
{
|
||||||
|
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE,
|
||||||
|
ATTR_MIN_TEMP: 44.6,
|
||||||
|
ATTR_MAX_TEMP: 95,
|
||||||
|
ATTR_PRESET_MODES: ["home", "away"],
|
||||||
|
ATTR_TEMPERATURE: 67,
|
||||||
|
ATTR_TARGET_TEMP_HIGH: None,
|
||||||
|
ATTR_TARGET_TEMP_LOW: None,
|
||||||
|
ATTR_FAN_MODE: FAN_AUTO,
|
||||||
|
ATTR_FAN_MODES: None,
|
||||||
|
ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE,
|
||||||
|
ATTR_FAN_MODE: FAN_AUTO,
|
||||||
|
ATTR_PRESET_MODE: "home",
|
||||||
|
ATTR_FRIENDLY_NAME: "Rec Room",
|
||||||
|
ATTR_HVAC_MODES: [
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
HVAC_MODE_HEAT,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||||
|
hk_driver.add_accessory(acc)
|
||||||
|
|
||||||
|
await acc.run()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert acc.ordered_fan_speeds == []
|
||||||
|
assert not acc.fan_chars
|
||||||
|
@@ -333,6 +333,43 @@ async def test_set_fan_mode(hass, mqtt_mock):
|
|||||||
assert state.attributes.get("fan_mode") == "high"
|
assert state.attributes.get("fan_mode") == "high"
|
||||||
|
|
||||||
|
|
||||||
|
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"send_if_off,assert_async_publish",
|
||||||
|
[
|
||||||
|
({}, [call("fan-mode-topic", "low", 0, False)]),
|
||||||
|
({"send_if_off": True}, [call("fan-mode-topic", "low", 0, False)]),
|
||||||
|
({"send_if_off": False}, []),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_set_fan_mode_send_if_off(
|
||||||
|
hass, mqtt_mock, send_if_off, assert_async_publish
|
||||||
|
):
|
||||||
|
"""Test setting of fan mode if the hvac is off."""
|
||||||
|
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||||
|
config[CLIMATE_DOMAIN].update(send_if_off)
|
||||||
|
assert await async_setup_component(hass, CLIMATE_DOMAIN, config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get(ENTITY_CLIMATE) is not None
|
||||||
|
|
||||||
|
# Turn on HVAC
|
||||||
|
await common.async_set_hvac_mode(hass, "cool", ENTITY_CLIMATE)
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
# Updates for fan_mode should be sent when the device is turned on
|
||||||
|
await common.async_set_fan_mode(hass, "high", ENTITY_CLIMATE)
|
||||||
|
mqtt_mock.async_publish.assert_called_once_with("fan-mode-topic", "high", 0, False)
|
||||||
|
|
||||||
|
# Turn off HVAC
|
||||||
|
await common.async_set_hvac_mode(hass, "off", ENTITY_CLIMATE)
|
||||||
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
# Updates for fan_mode should be sent if SEND_IF_OFF is not set or is True
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
await common.async_set_fan_mode(hass, "low", ENTITY_CLIMATE)
|
||||||
|
mqtt_mock.async_publish.assert_has_calls(assert_async_publish)
|
||||||
|
|
||||||
|
|
||||||
async def test_set_swing_mode_bad_attr(hass, mqtt_mock, caplog):
|
async def test_set_swing_mode_bad_attr(hass, mqtt_mock, caplog):
|
||||||
"""Test setting swing mode without required attribute."""
|
"""Test setting swing mode without required attribute."""
|
||||||
assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG)
|
assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG)
|
||||||
@@ -385,6 +422,43 @@ async def test_set_swing(hass, mqtt_mock):
|
|||||||
assert state.attributes.get("swing_mode") == "on"
|
assert state.attributes.get("swing_mode") == "on"
|
||||||
|
|
||||||
|
|
||||||
|
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"send_if_off,assert_async_publish",
|
||||||
|
[
|
||||||
|
({}, [call("swing-mode-topic", "on", 0, False)]),
|
||||||
|
({"send_if_off": True}, [call("swing-mode-topic", "on", 0, False)]),
|
||||||
|
({"send_if_off": False}, []),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_set_swing_mode_send_if_off(
|
||||||
|
hass, mqtt_mock, send_if_off, assert_async_publish
|
||||||
|
):
|
||||||
|
"""Test setting of swing mode if the hvac is off."""
|
||||||
|
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||||
|
config[CLIMATE_DOMAIN].update(send_if_off)
|
||||||
|
assert await async_setup_component(hass, CLIMATE_DOMAIN, config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get(ENTITY_CLIMATE) is not None
|
||||||
|
|
||||||
|
# Turn on HVAC
|
||||||
|
await common.async_set_hvac_mode(hass, "cool", ENTITY_CLIMATE)
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
# Updates for swing_mode should be sent when the device is turned on
|
||||||
|
await common.async_set_swing_mode(hass, "off", ENTITY_CLIMATE)
|
||||||
|
mqtt_mock.async_publish.assert_called_once_with("swing-mode-topic", "off", 0, False)
|
||||||
|
|
||||||
|
# Turn off HVAC
|
||||||
|
await common.async_set_hvac_mode(hass, "off", ENTITY_CLIMATE)
|
||||||
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
# Updates for swing_mode should be sent if SEND_IF_OFF is not set or is True
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
await common.async_set_swing_mode(hass, "on", ENTITY_CLIMATE)
|
||||||
|
mqtt_mock.async_publish.assert_has_calls(assert_async_publish)
|
||||||
|
|
||||||
|
|
||||||
async def test_set_target_temperature(hass, mqtt_mock):
|
async def test_set_target_temperature(hass, mqtt_mock):
|
||||||
"""Test setting the target temperature."""
|
"""Test setting the target temperature."""
|
||||||
assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG)
|
assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG)
|
||||||
@@ -421,6 +495,45 @@ async def test_set_target_temperature(hass, mqtt_mock):
|
|||||||
mqtt_mock.async_publish.reset_mock()
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
|
# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"send_if_off,assert_async_publish",
|
||||||
|
[
|
||||||
|
({}, [call("temperature-topic", "21.0", 0, False)]),
|
||||||
|
({"send_if_off": True}, [call("temperature-topic", "21.0", 0, False)]),
|
||||||
|
({"send_if_off": False}, []),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_set_target_temperature_send_if_off(
|
||||||
|
hass, mqtt_mock, send_if_off, assert_async_publish
|
||||||
|
):
|
||||||
|
"""Test setting of target temperature if the hvac is off."""
|
||||||
|
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||||
|
config[CLIMATE_DOMAIN].update(send_if_off)
|
||||||
|
assert await async_setup_component(hass, CLIMATE_DOMAIN, config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get(ENTITY_CLIMATE) is not None
|
||||||
|
|
||||||
|
# Turn on HVAC
|
||||||
|
await common.async_set_hvac_mode(hass, "cool", ENTITY_CLIMATE)
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
# Updates for target temperature should be sent when the device is turned on
|
||||||
|
await common.async_set_temperature(hass, 16.0, ENTITY_CLIMATE)
|
||||||
|
mqtt_mock.async_publish.assert_called_once_with(
|
||||||
|
"temperature-topic", "16.0", 0, False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Turn off HVAC
|
||||||
|
await common.async_set_hvac_mode(hass, "off", ENTITY_CLIMATE)
|
||||||
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
# Updates for target temperature sent should be if SEND_IF_OFF is not set or is True
|
||||||
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
await common.async_set_temperature(hass, 21.0, ENTITY_CLIMATE)
|
||||||
|
mqtt_mock.async_publish.assert_has_calls(assert_async_publish)
|
||||||
|
|
||||||
|
|
||||||
async def test_set_target_temperature_pessimistic(hass, mqtt_mock):
|
async def test_set_target_temperature_pessimistic(hass, mqtt_mock):
|
||||||
"""Test setting the target temperature."""
|
"""Test setting the target temperature."""
|
||||||
config = copy.deepcopy(DEFAULT_CONFIG)
|
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||||
|
@@ -9,6 +9,7 @@ from homeassistant.const import (
|
|||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
ENTITY_MATCH_ALL,
|
ENTITY_MATCH_ALL,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
from homeassistant.core import State
|
from homeassistant.core import State
|
||||||
@@ -177,6 +178,34 @@ async def test_restore_state(hass, entities, enable_custom_integrations):
|
|||||||
assert hass.states.get("scene.test").state == "2021-01-01T23:59:59+00:00"
|
assert hass.states.get("scene.test").state == "2021-01-01T23:59:59+00:00"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_restore_state_does_not_restore_unavailable(
|
||||||
|
hass, entities, enable_custom_integrations
|
||||||
|
):
|
||||||
|
"""Test we restore state integration but ignore unavailable."""
|
||||||
|
mock_restore_cache(hass, (State("scene.test", STATE_UNAVAILABLE),))
|
||||||
|
|
||||||
|
light_1, light_2 = await setup_lights(hass, entities)
|
||||||
|
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
scene.DOMAIN,
|
||||||
|
{
|
||||||
|
"scene": [
|
||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"entities": {
|
||||||
|
light_1.entity_id: "on",
|
||||||
|
light_2.entity_id: "on",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hass.states.get("scene.test").state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
async def activate(hass, entity_id=ENTITY_MATCH_ALL):
|
async def activate(hass, entity_id=ENTITY_MATCH_ALL):
|
||||||
"""Activate a scene."""
|
"""Activate a scene."""
|
||||||
data = {}
|
data = {}
|
||||||
|
@@ -545,6 +545,22 @@ async def test_async_remove_runs_callbacks(hass):
|
|||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_remove_ignores_in_flight_polling(hass):
|
||||||
|
"""Test in flight polling is ignored after removing."""
|
||||||
|
result = []
|
||||||
|
|
||||||
|
ent = entity.Entity()
|
||||||
|
ent.hass = hass
|
||||||
|
ent.entity_id = "test.test"
|
||||||
|
ent.async_on_remove(lambda: result.append(1))
|
||||||
|
ent.async_write_ha_state()
|
||||||
|
assert hass.states.get("test.test").state == STATE_UNKNOWN
|
||||||
|
await ent.async_remove()
|
||||||
|
assert len(result) == 1
|
||||||
|
assert hass.states.get("test.test") is None
|
||||||
|
ent.async_write_ha_state()
|
||||||
|
|
||||||
|
|
||||||
async def test_set_context(hass):
|
async def test_set_context(hass):
|
||||||
"""Test setting context."""
|
"""Test setting context."""
|
||||||
context = Context()
|
context = Context()
|
||||||
|
@@ -390,6 +390,30 @@ async def test_async_remove_with_platform(hass):
|
|||||||
assert len(hass.states.async_entity_ids()) == 0
|
assert len(hass.states.async_entity_ids()) == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_remove_with_platform_update_finishes(hass):
|
||||||
|
"""Remove an entity when an update finishes after its been removed."""
|
||||||
|
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
||||||
|
entity1 = MockEntity(name="test_1")
|
||||||
|
|
||||||
|
async def _delayed_update(*args, **kwargs):
|
||||||
|
await asyncio.sleep(0.01)
|
||||||
|
|
||||||
|
entity1.async_update = _delayed_update
|
||||||
|
|
||||||
|
# Add, remove, add, remove and make sure no updates
|
||||||
|
# cause the entity to reappear after removal
|
||||||
|
for i in range(2):
|
||||||
|
await component.async_add_entities([entity1])
|
||||||
|
assert len(hass.states.async_entity_ids()) == 1
|
||||||
|
entity1.async_write_ha_state()
|
||||||
|
assert hass.states.get(entity1.entity_id) is not None
|
||||||
|
task = asyncio.create_task(entity1.async_update_ha_state(True))
|
||||||
|
await entity1.async_remove()
|
||||||
|
assert len(hass.states.async_entity_ids()) == 0
|
||||||
|
await task
|
||||||
|
assert len(hass.states.async_entity_ids()) == 0
|
||||||
|
|
||||||
|
|
||||||
async def test_not_adding_duplicate_entities_with_unique_id(hass, caplog):
|
async def test_not_adding_duplicate_entities_with_unique_id(hass, caplog):
|
||||||
"""Test for not adding duplicate entities."""
|
"""Test for not adding duplicate entities."""
|
||||||
caplog.set_level(logging.ERROR)
|
caplog.set_level(logging.ERROR)
|
||||||
|
Reference in New Issue
Block a user