mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 17:57:11 +00:00
Add and delete Home Connect devices on CONNECTED/PAIRED and DEPAIRED events (#136952)
* Add and delete devices on CONNECT/PAIRED and DEPAIRED events * Simplify device depairing * small fixes Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Add always the devices * kind of revert changes to simplify the entity fetch and removing on connected/paired and depaired * cache `ha_id` * Fix typo * Remove unnecessary device info at HomeConnectEntity * Move common code of each platform to `common.py` * Added docstring to clarify usage * Apply suggestions Co-authored-by: Martin Hjelmare <marhje52@gmail.com> --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
147b5f549f
commit
30314ca32b
@ -21,6 +21,7 @@ from homeassistant.helpers.issue_registry import (
|
||||
async_delete_issue,
|
||||
)
|
||||
|
||||
from .common import setup_home_connect_entry
|
||||
from .const import (
|
||||
BSH_DOOR_STATE_CLOSED,
|
||||
BSH_DOOR_STATE_LOCKED,
|
||||
@ -113,24 +114,33 @@ BINARY_SENSORS = (
|
||||
)
|
||||
|
||||
|
||||
def _get_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
appliance: HomeConnectApplianceData,
|
||||
) -> list[HomeConnectEntity]:
|
||||
"""Get a list of entities."""
|
||||
entities: list[HomeConnectEntity] = []
|
||||
entities.extend(
|
||||
HomeConnectBinarySensor(entry.runtime_data, appliance, description)
|
||||
for description in BINARY_SENSORS
|
||||
if description.key in appliance.status
|
||||
)
|
||||
if StatusKey.BSH_COMMON_DOOR_STATE in appliance.status:
|
||||
entities.append(HomeConnectDoorBinarySensor(entry.runtime_data, appliance))
|
||||
return entities
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: HomeConnectConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Home Connect binary sensor."""
|
||||
|
||||
entities: list[BinarySensorEntity] = []
|
||||
for appliance in entry.runtime_data.data.values():
|
||||
entities.extend(
|
||||
HomeConnectBinarySensor(entry.runtime_data, appliance, description)
|
||||
for description in BINARY_SENSORS
|
||||
if description.key in appliance.status
|
||||
)
|
||||
if StatusKey.BSH_COMMON_DOOR_STATE in appliance.status:
|
||||
entities.append(HomeConnectDoorBinarySensor(entry.runtime_data, appliance))
|
||||
|
||||
async_add_entities(entities)
|
||||
setup_home_connect_entry(
|
||||
entry,
|
||||
_get_entities_for_appliance,
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
|
||||
class HomeConnectBinarySensor(HomeConnectEntity, BinarySensorEntity):
|
||||
|
99
homeassistant/components/home_connect/common.py
Normal file
99
homeassistant/components/home_connect/common.py
Normal file
@ -0,0 +1,99 @@
|
||||
"""Common callbacks for all Home Connect platforms."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from functools import partial
|
||||
from typing import cast
|
||||
|
||||
from aiohomeconnect.model import EventKey
|
||||
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
|
||||
from .entity import HomeConnectEntity
|
||||
|
||||
|
||||
def _handle_paired_or_connected_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
known_entity_unique_ids: dict[str, str],
|
||||
get_entities_for_appliance: Callable[
|
||||
[HomeConnectConfigEntry, HomeConnectApplianceData], list[HomeConnectEntity]
|
||||
],
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Handle a new paired appliance or an appliance that has been connected.
|
||||
|
||||
This function is used to handle connected events also, because some appliances
|
||||
don't report any data while they are off because they disconnect themselves
|
||||
when they are turned off, so we need to check if the entities have been added
|
||||
already or it is the first time we see them when the appliance is connected.
|
||||
"""
|
||||
entities: list[HomeConnectEntity] = []
|
||||
for appliance in entry.runtime_data.data.values():
|
||||
entities_to_add = [
|
||||
entity
|
||||
for entity in get_entities_for_appliance(entry, appliance)
|
||||
if entity.unique_id not in known_entity_unique_ids
|
||||
]
|
||||
known_entity_unique_ids.update(
|
||||
{
|
||||
cast(str, entity.unique_id): appliance.info.ha_id
|
||||
for entity in entities_to_add
|
||||
}
|
||||
)
|
||||
entities.extend(entities_to_add)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
def _handle_depaired_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
known_entity_unique_ids: dict[str, str],
|
||||
) -> None:
|
||||
"""Handle a removed appliance."""
|
||||
for entity_unique_id, appliance_id in known_entity_unique_ids.copy().items():
|
||||
if appliance_id not in entry.runtime_data.data:
|
||||
known_entity_unique_ids.pop(entity_unique_id, None)
|
||||
|
||||
|
||||
def setup_home_connect_entry(
|
||||
entry: HomeConnectConfigEntry,
|
||||
get_entities_for_appliance: Callable[
|
||||
[HomeConnectConfigEntry, HomeConnectApplianceData], list[HomeConnectEntity]
|
||||
],
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the callbacks for paired and depaired appliances."""
|
||||
known_entity_unique_ids: dict[str, str] = {}
|
||||
|
||||
entities: list[HomeConnectEntity] = []
|
||||
for appliance in entry.runtime_data.data.values():
|
||||
entities_to_add = get_entities_for_appliance(entry, appliance)
|
||||
known_entity_unique_ids.update(
|
||||
{
|
||||
cast(str, entity.unique_id): appliance.info.ha_id
|
||||
for entity in entities_to_add
|
||||
}
|
||||
)
|
||||
entities.extend(entities_to_add)
|
||||
async_add_entities(entities)
|
||||
|
||||
entry.async_on_unload(
|
||||
entry.runtime_data.async_add_special_listener(
|
||||
partial(
|
||||
_handle_paired_or_connected_appliance,
|
||||
entry,
|
||||
known_entity_unique_ids,
|
||||
get_entities_for_appliance,
|
||||
async_add_entities,
|
||||
),
|
||||
(
|
||||
EventKey.BSH_COMMON_APPLIANCE_PAIRED,
|
||||
EventKey.BSH_COMMON_APPLIANCE_CONNECTED,
|
||||
),
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(
|
||||
entry.runtime_data.async_add_special_listener(
|
||||
partial(_handle_depaired_appliance, entry, known_entity_unique_ids),
|
||||
(EventKey.BSH_COMMON_APPLIANCE_DEPAIRED,),
|
||||
)
|
||||
)
|
@ -3,7 +3,7 @@
|
||||
import asyncio
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@ -30,6 +30,7 @@ from propcache.api import cached_property
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import APPLIANCES_WITH_PROGRAMS, DOMAIN
|
||||
@ -46,9 +47,9 @@ EVENT_STREAM_RECONNECT_DELAY = 30
|
||||
class HomeConnectApplianceData:
|
||||
"""Class to hold Home Connect appliance data."""
|
||||
|
||||
events: dict[EventKey, Event] = field(default_factory=dict)
|
||||
events: dict[EventKey, Event]
|
||||
info: HomeAppliance
|
||||
programs: list[EnumerateProgram] = field(default_factory=list)
|
||||
programs: list[EnumerateProgram]
|
||||
settings: dict[SettingKey, GetSetting]
|
||||
status: dict[StatusKey, Status]
|
||||
|
||||
@ -83,6 +84,10 @@ class HomeConnectCoordinator(
|
||||
name=config_entry.entry_id,
|
||||
)
|
||||
self.client = client
|
||||
self._special_listeners: dict[
|
||||
CALLBACK_TYPE, tuple[CALLBACK_TYPE, tuple[EventKey, ...]]
|
||||
] = {}
|
||||
self.device_registry = dr.async_get(self.hass)
|
||||
|
||||
@cached_property
|
||||
def context_listeners(self) -> dict[tuple[str, EventKey], list[CALLBACK_TYPE]]:
|
||||
@ -107,6 +112,28 @@ class HomeConnectCoordinator(
|
||||
|
||||
return remove_listener_and_invalidate_context_listeners
|
||||
|
||||
@callback
|
||||
def async_add_special_listener(
|
||||
self,
|
||||
update_callback: CALLBACK_TYPE,
|
||||
context: tuple[EventKey, ...],
|
||||
) -> Callable[[], None]:
|
||||
"""Listen for special data updates.
|
||||
|
||||
These listeners will not be called on refresh.
|
||||
"""
|
||||
|
||||
@callback
|
||||
def remove_listener() -> None:
|
||||
"""Remove update listener."""
|
||||
self._special_listeners.pop(remove_listener)
|
||||
if not self._special_listeners:
|
||||
self._unschedule_refresh()
|
||||
|
||||
self._special_listeners[remove_listener] = (update_callback, context)
|
||||
|
||||
return remove_listener
|
||||
|
||||
@callback
|
||||
def start_event_listener(self) -> None:
|
||||
"""Start event listener."""
|
||||
@ -161,18 +188,49 @@ class HomeConnectCoordinator(
|
||||
events[event.key] = event
|
||||
self._call_event_listener(event_message)
|
||||
|
||||
case EventType.CONNECTED:
|
||||
self.data[event_message_ha_id].info.connected = True
|
||||
self._call_all_event_listeners_for_appliance(
|
||||
case EventType.CONNECTED | EventType.PAIRED:
|
||||
appliance_info = await self.client.get_specific_appliance(
|
||||
event_message_ha_id
|
||||
)
|
||||
|
||||
appliance_data = await self._get_appliance_data(
|
||||
appliance_info, self.data.get(appliance_info.ha_id)
|
||||
)
|
||||
if event_message_ha_id in self.data:
|
||||
self.data[event_message_ha_id].update(appliance_data)
|
||||
else:
|
||||
self.data[event_message_ha_id] = appliance_data
|
||||
for listener, context in list(
|
||||
self._special_listeners.values()
|
||||
) + list(self._listeners.values()):
|
||||
assert isinstance(context, tuple)
|
||||
if (
|
||||
EventKey.BSH_COMMON_APPLIANCE_DEPAIRED
|
||||
not in context
|
||||
):
|
||||
listener()
|
||||
|
||||
case EventType.DISCONNECTED:
|
||||
self.data[event_message_ha_id].info.connected = False
|
||||
self._call_all_event_listeners_for_appliance(
|
||||
event_message_ha_id
|
||||
)
|
||||
|
||||
case EventType.DEPAIRED:
|
||||
device = self.device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, event_message_ha_id)}
|
||||
)
|
||||
if device:
|
||||
self.device_registry.async_update_device(
|
||||
device_id=device.id,
|
||||
remove_config_entry_id=self.config_entry.entry_id,
|
||||
)
|
||||
self.data.pop(event_message_ha_id, None)
|
||||
for listener, context in self._special_listeners.values():
|
||||
assert isinstance(context, tuple)
|
||||
if EventKey.BSH_COMMON_APPLIANCE_DEPAIRED in context:
|
||||
listener()
|
||||
|
||||
except (EventStreamInterruptedError, HomeConnectRequestError) as error:
|
||||
_LOGGER.debug(
|
||||
"Non-breaking error (%s) while listening for events,"
|
||||
@ -217,81 +275,101 @@ class HomeConnectCoordinator(
|
||||
translation_placeholders=get_dict_from_home_connect_error(error),
|
||||
) from error
|
||||
|
||||
appliances_data = self.data or {}
|
||||
for appliance in appliances.homeappliances:
|
||||
try:
|
||||
settings = {
|
||||
setting.key: setting
|
||||
for setting in (
|
||||
await self.client.get_settings(appliance.ha_id)
|
||||
).settings
|
||||
}
|
||||
except HomeConnectError as error:
|
||||
_LOGGER.debug(
|
||||
"Error fetching settings for %s: %s",
|
||||
appliance.ha_id,
|
||||
error
|
||||
if isinstance(error, HomeConnectApiError)
|
||||
else type(error).__name__,
|
||||
)
|
||||
settings = {}
|
||||
try:
|
||||
status = {
|
||||
status.key: status
|
||||
for status in (await self.client.get_status(appliance.ha_id)).status
|
||||
}
|
||||
except HomeConnectError as error:
|
||||
_LOGGER.debug(
|
||||
"Error fetching status for %s: %s",
|
||||
appliance.ha_id,
|
||||
error
|
||||
if isinstance(error, HomeConnectApiError)
|
||||
else type(error).__name__,
|
||||
)
|
||||
status = {}
|
||||
appliance_data = HomeConnectApplianceData(
|
||||
info=appliance, settings=settings, status=status
|
||||
return {
|
||||
appliance.ha_id: await self._get_appliance_data(
|
||||
appliance, self.data.get(appliance.ha_id) if self.data else None
|
||||
)
|
||||
if appliance.ha_id in appliances_data:
|
||||
appliances_data[appliance.ha_id].update(appliance_data)
|
||||
appliance_data = appliances_data[appliance.ha_id]
|
||||
else:
|
||||
appliances_data[appliance.ha_id] = appliance_data
|
||||
if appliance.type in APPLIANCES_WITH_PROGRAMS:
|
||||
try:
|
||||
all_programs = await self.client.get_all_programs(appliance.ha_id)
|
||||
except HomeConnectError as error:
|
||||
_LOGGER.debug(
|
||||
"Error fetching programs for %s: %s",
|
||||
appliance.ha_id,
|
||||
error
|
||||
if isinstance(error, HomeConnectApiError)
|
||||
else type(error).__name__,
|
||||
)
|
||||
else:
|
||||
appliance_data.programs.extend(all_programs.programs)
|
||||
for program, event_key in (
|
||||
(
|
||||
all_programs.active,
|
||||
EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
|
||||
),
|
||||
(
|
||||
all_programs.selected,
|
||||
EventKey.BSH_COMMON_ROOT_SELECTED_PROGRAM,
|
||||
),
|
||||
):
|
||||
if program and program.key:
|
||||
appliance_data.events.update(
|
||||
{
|
||||
event_key: Event(
|
||||
event_key,
|
||||
event_key.value,
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
program.key,
|
||||
)
|
||||
}
|
||||
)
|
||||
for appliance in appliances.homeappliances
|
||||
}
|
||||
|
||||
return appliances_data
|
||||
async def _get_appliance_data(
|
||||
self,
|
||||
appliance: HomeAppliance,
|
||||
appliance_data_to_update: HomeConnectApplianceData | None = None,
|
||||
) -> HomeConnectApplianceData:
|
||||
"""Get appliance data."""
|
||||
self.device_registry.async_get_or_create(
|
||||
config_entry_id=self.config_entry.entry_id,
|
||||
identifiers={(DOMAIN, appliance.ha_id)},
|
||||
manufacturer=appliance.brand,
|
||||
name=appliance.name,
|
||||
model=appliance.vib,
|
||||
)
|
||||
try:
|
||||
settings = {
|
||||
setting.key: setting
|
||||
for setting in (
|
||||
await self.client.get_settings(appliance.ha_id)
|
||||
).settings
|
||||
}
|
||||
except HomeConnectError as error:
|
||||
_LOGGER.debug(
|
||||
"Error fetching settings for %s: %s",
|
||||
appliance.ha_id,
|
||||
error
|
||||
if isinstance(error, HomeConnectApiError)
|
||||
else type(error).__name__,
|
||||
)
|
||||
settings = {}
|
||||
try:
|
||||
status = {
|
||||
status.key: status
|
||||
for status in (await self.client.get_status(appliance.ha_id)).status
|
||||
}
|
||||
except HomeConnectError as error:
|
||||
_LOGGER.debug(
|
||||
"Error fetching status for %s: %s",
|
||||
appliance.ha_id,
|
||||
error
|
||||
if isinstance(error, HomeConnectApiError)
|
||||
else type(error).__name__,
|
||||
)
|
||||
status = {}
|
||||
|
||||
programs = []
|
||||
events = {}
|
||||
if appliance.type in APPLIANCES_WITH_PROGRAMS:
|
||||
try:
|
||||
all_programs = await self.client.get_all_programs(appliance.ha_id)
|
||||
except HomeConnectError as error:
|
||||
_LOGGER.debug(
|
||||
"Error fetching programs for %s: %s",
|
||||
appliance.ha_id,
|
||||
error
|
||||
if isinstance(error, HomeConnectApiError)
|
||||
else type(error).__name__,
|
||||
)
|
||||
else:
|
||||
programs.extend(all_programs.programs)
|
||||
for program, event_key in (
|
||||
(
|
||||
all_programs.active,
|
||||
EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
|
||||
),
|
||||
(
|
||||
all_programs.selected,
|
||||
EventKey.BSH_COMMON_ROOT_SELECTED_PROGRAM,
|
||||
),
|
||||
):
|
||||
if program and program.key:
|
||||
events[event_key] = Event(
|
||||
event_key,
|
||||
event_key.value,
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
program.key,
|
||||
)
|
||||
|
||||
appliance_data = HomeConnectApplianceData(
|
||||
events=events,
|
||||
info=appliance,
|
||||
programs=programs,
|
||||
settings=settings,
|
||||
status=status,
|
||||
)
|
||||
if appliance_data_to_update:
|
||||
appliance_data_to_update.update(appliance_data)
|
||||
appliance_data = appliance_data_to_update
|
||||
|
||||
return appliance_data
|
||||
|
@ -35,9 +35,6 @@ class HomeConnectEntity(CoordinatorEntity[HomeConnectCoordinator]):
|
||||
self._attr_unique_id = f"{appliance.info.ha_id}-{desc.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, appliance.info.ha_id)},
|
||||
manufacturer=appliance.info.brand,
|
||||
model=appliance.info.vib,
|
||||
name=appliance.info.name,
|
||||
)
|
||||
self.update_native_value()
|
||||
|
||||
|
@ -20,6 +20,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import color as color_util
|
||||
|
||||
from .common import setup_home_connect_entry
|
||||
from .const import (
|
||||
BSH_AMBIENT_LIGHT_COLOR_CUSTOM_COLOR,
|
||||
DOMAIN,
|
||||
@ -78,20 +79,28 @@ LIGHTS: tuple[HomeConnectLightEntityDescription, ...] = (
|
||||
)
|
||||
|
||||
|
||||
def _get_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
appliance: HomeConnectApplianceData,
|
||||
) -> list[HomeConnectEntity]:
|
||||
"""Get a list of entities."""
|
||||
return [
|
||||
HomeConnectLight(entry.runtime_data, appliance, description)
|
||||
for description in LIGHTS
|
||||
if description.key in appliance.settings
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: HomeConnectConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Home Connect light."""
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
HomeConnectLight(entry.runtime_data, appliance, description)
|
||||
for description in LIGHTS
|
||||
for appliance in entry.runtime_data.data.values()
|
||||
if description.key in appliance.settings
|
||||
],
|
||||
setup_home_connect_entry(
|
||||
entry,
|
||||
_get_entities_for_appliance,
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
|
||||
|
@ -15,6 +15,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .common import setup_home_connect_entry
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
SVE_TRANSLATION_KEY_SET_SETTING,
|
||||
@ -22,7 +23,7 @@ from .const import (
|
||||
SVE_TRANSLATION_PLACEHOLDER_KEY,
|
||||
SVE_TRANSLATION_PLACEHOLDER_VALUE,
|
||||
)
|
||||
from .coordinator import HomeConnectConfigEntry
|
||||
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
|
||||
from .entity import HomeConnectEntity
|
||||
from .utils import get_dict_from_home_connect_error
|
||||
|
||||
@ -78,19 +79,28 @@ NUMBERS = (
|
||||
)
|
||||
|
||||
|
||||
def _get_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
appliance: HomeConnectApplianceData,
|
||||
) -> list[HomeConnectEntity]:
|
||||
"""Get a list of entities."""
|
||||
return [
|
||||
HomeConnectNumberEntity(entry.runtime_data, appliance, description)
|
||||
for description in NUMBERS
|
||||
if description.key in appliance.settings
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: HomeConnectConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Home Connect number."""
|
||||
async_add_entities(
|
||||
[
|
||||
HomeConnectNumberEntity(entry.runtime_data, appliance, description)
|
||||
for description in NUMBERS
|
||||
for appliance in entry.runtime_data.data.values()
|
||||
if description.key in appliance.settings
|
||||
],
|
||||
setup_home_connect_entry(
|
||||
entry,
|
||||
_get_entities_for_appliance,
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
|
||||
|
@ -14,6 +14,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .common import setup_home_connect_entry
|
||||
from .const import APPLIANCES_WITH_PROGRAMS, DOMAIN, SVE_TRANSLATION_PLACEHOLDER_PROGRAM
|
||||
from .coordinator import (
|
||||
HomeConnectApplianceData,
|
||||
@ -69,18 +70,31 @@ PROGRAM_SELECT_ENTITY_DESCRIPTIONS = (
|
||||
)
|
||||
|
||||
|
||||
def _get_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
appliance: HomeConnectApplianceData,
|
||||
) -> list[HomeConnectEntity]:
|
||||
"""Get a list of entities."""
|
||||
return (
|
||||
[
|
||||
HomeConnectProgramSelectEntity(entry.runtime_data, appliance, desc)
|
||||
for desc in PROGRAM_SELECT_ENTITY_DESCRIPTIONS
|
||||
]
|
||||
if appliance.info.type in APPLIANCES_WITH_PROGRAMS
|
||||
else []
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: HomeConnectConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Home Connect select entities."""
|
||||
|
||||
async_add_entities(
|
||||
HomeConnectProgramSelectEntity(entry.runtime_data, appliance, desc)
|
||||
for appliance in entry.runtime_data.data.values()
|
||||
for desc in PROGRAM_SELECT_ENTITY_DESCRIPTIONS
|
||||
if appliance.info.type in APPLIANCES_WITH_PROGRAMS
|
||||
setup_home_connect_entry(
|
||||
entry,
|
||||
_get_entities_for_appliance,
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
|
||||
|
@ -17,13 +17,14 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util, slugify
|
||||
|
||||
from .common import setup_home_connect_entry
|
||||
from .const import (
|
||||
APPLIANCES_WITH_PROGRAMS,
|
||||
BSH_OPERATION_STATE_FINISHED,
|
||||
BSH_OPERATION_STATE_PAUSE,
|
||||
BSH_OPERATION_STATE_RUN,
|
||||
)
|
||||
from .coordinator import HomeConnectConfigEntry
|
||||
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
|
||||
from .entity import HomeConnectEntity
|
||||
|
||||
EVENT_OPTIONS = ["confirmed", "off", "present"]
|
||||
@ -243,37 +244,42 @@ EVENT_SENSORS = (
|
||||
)
|
||||
|
||||
|
||||
def _get_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
appliance: HomeConnectApplianceData,
|
||||
) -> list[HomeConnectEntity]:
|
||||
"""Get a list of entities."""
|
||||
return [
|
||||
*[
|
||||
HomeConnectEventSensor(entry.runtime_data, appliance, description)
|
||||
for description in EVENT_SENSORS
|
||||
if description.appliance_types
|
||||
and appliance.info.type in description.appliance_types
|
||||
],
|
||||
*[
|
||||
HomeConnectProgramSensor(entry.runtime_data, appliance, desc)
|
||||
for desc in BSH_PROGRAM_SENSORS
|
||||
if desc.appliance_types and appliance.info.type in desc.appliance_types
|
||||
],
|
||||
*[
|
||||
HomeConnectSensor(entry.runtime_data, appliance, description)
|
||||
for description in SENSORS
|
||||
if description.key in appliance.status
|
||||
],
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: HomeConnectConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Home Connect sensor."""
|
||||
|
||||
entities: list[SensorEntity] = []
|
||||
for appliance in entry.runtime_data.data.values():
|
||||
entities.extend(
|
||||
HomeConnectEventSensor(
|
||||
entry.runtime_data,
|
||||
appliance,
|
||||
description,
|
||||
)
|
||||
for description in EVENT_SENSORS
|
||||
if description.appliance_types
|
||||
and appliance.info.type in description.appliance_types
|
||||
)
|
||||
entities.extend(
|
||||
HomeConnectProgramSensor(entry.runtime_data, appliance, desc)
|
||||
for desc in BSH_PROGRAM_SENSORS
|
||||
if desc.appliance_types and appliance.info.type in desc.appliance_types
|
||||
)
|
||||
entities.extend(
|
||||
HomeConnectSensor(entry.runtime_data, appliance, description)
|
||||
for description in SENSORS
|
||||
if description.key in appliance.status
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
setup_home_connect_entry(
|
||||
entry,
|
||||
_get_entities_for_appliance,
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
|
||||
class HomeConnectSensor(HomeConnectEntity, SensorEntity):
|
||||
|
@ -21,6 +21,7 @@ from homeassistant.helpers.issue_registry import (
|
||||
)
|
||||
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
|
||||
|
||||
from .common import setup_home_connect_entry
|
||||
from .const import (
|
||||
BSH_POWER_OFF,
|
||||
BSH_POWER_ON,
|
||||
@ -100,33 +101,43 @@ POWER_SWITCH_DESCRIPTION = SwitchEntityDescription(
|
||||
)
|
||||
|
||||
|
||||
def _get_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
appliance: HomeConnectApplianceData,
|
||||
) -> list[HomeConnectEntity]:
|
||||
"""Get a list of entities."""
|
||||
entities: list[HomeConnectEntity] = []
|
||||
entities.extend(
|
||||
HomeConnectProgramSwitch(entry.runtime_data, appliance, program)
|
||||
for program in appliance.programs
|
||||
if program.key != ProgramKey.UNKNOWN
|
||||
)
|
||||
if SettingKey.BSH_COMMON_POWER_STATE in appliance.settings:
|
||||
entities.append(
|
||||
HomeConnectPowerSwitch(
|
||||
entry.runtime_data, appliance, POWER_SWITCH_DESCRIPTION
|
||||
)
|
||||
)
|
||||
entities.extend(
|
||||
HomeConnectSwitch(entry.runtime_data, appliance, description)
|
||||
for description in SWITCHES
|
||||
if description.key in appliance.settings
|
||||
)
|
||||
|
||||
return entities
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: HomeConnectConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Home Connect switch."""
|
||||
|
||||
entities: list[SwitchEntity] = []
|
||||
for appliance in entry.runtime_data.data.values():
|
||||
entities.extend(
|
||||
HomeConnectProgramSwitch(entry.runtime_data, appliance, program)
|
||||
for program in appliance.programs
|
||||
if program.key != ProgramKey.UNKNOWN
|
||||
)
|
||||
if SettingKey.BSH_COMMON_POWER_STATE in appliance.settings:
|
||||
entities.append(
|
||||
HomeConnectPowerSwitch(
|
||||
entry.runtime_data, appliance, POWER_SWITCH_DESCRIPTION
|
||||
)
|
||||
)
|
||||
entities.extend(
|
||||
HomeConnectSwitch(entry.runtime_data, appliance, description)
|
||||
for description in SWITCHES
|
||||
if description.key in appliance.settings
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
setup_home_connect_entry(
|
||||
entry,
|
||||
_get_entities_for_appliance,
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
|
||||
class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
|
||||
|
@ -11,6 +11,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .common import setup_home_connect_entry
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
SVE_TRANSLATION_KEY_SET_SETTING,
|
||||
@ -18,7 +19,7 @@ from .const import (
|
||||
SVE_TRANSLATION_PLACEHOLDER_KEY,
|
||||
SVE_TRANSLATION_PLACEHOLDER_VALUE,
|
||||
)
|
||||
from .coordinator import HomeConnectConfigEntry
|
||||
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
|
||||
from .entity import HomeConnectEntity
|
||||
from .utils import get_dict_from_home_connect_error
|
||||
|
||||
@ -30,20 +31,28 @@ TIME_ENTITIES = (
|
||||
)
|
||||
|
||||
|
||||
def _get_entities_for_appliance(
|
||||
entry: HomeConnectConfigEntry,
|
||||
appliance: HomeConnectApplianceData,
|
||||
) -> list[HomeConnectEntity]:
|
||||
"""Get a list of entities."""
|
||||
return [
|
||||
HomeConnectTimeEntity(entry.runtime_data, appliance, description)
|
||||
for description in TIME_ENTITIES
|
||||
if description.key in appliance.settings
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: HomeConnectConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Home Connect switch."""
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
HomeConnectTimeEntity(entry.runtime_data, appliance, description)
|
||||
for description in TIME_ENTITIES
|
||||
for appliance in entry.runtime_data.data.values()
|
||||
if description.key in appliance.settings
|
||||
],
|
||||
setup_home_connect_entry(
|
||||
entry,
|
||||
_get_entities_for_appliance,
|
||||
async_add_entities,
|
||||
)
|
||||
|
||||
|
||||
|
@ -18,8 +18,11 @@ from aiohomeconnect.model import (
|
||||
EventKey,
|
||||
EventMessage,
|
||||
EventType,
|
||||
GetSetting,
|
||||
HomeAppliance,
|
||||
Option,
|
||||
Program,
|
||||
SettingKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectApiError, HomeConnectError
|
||||
from aiohomeconnect.model.program import EnumerateProgram
|
||||
@ -145,6 +148,14 @@ async def mock_integration_setup(
|
||||
return run
|
||||
|
||||
|
||||
def _get_specific_appliance_side_effect(ha_id: str) -> HomeAppliance:
|
||||
"""Get specific appliance side effect."""
|
||||
for appliance in copy.deepcopy(MOCK_APPLIANCES).homeappliances:
|
||||
if appliance.ha_id == ha_id:
|
||||
return appliance
|
||||
raise HomeConnectApiError("error.key", "error description")
|
||||
|
||||
|
||||
def _get_set_program_side_effect(
|
||||
event_queue: asyncio.Queue[list[EventMessage]], event_key: EventKey
|
||||
):
|
||||
@ -253,6 +264,24 @@ async def _get_settings_side_effect(ha_id: str) -> ArrayOfSettings:
|
||||
)
|
||||
|
||||
|
||||
async def _get_setting_side_effect(ha_id: str, setting_key: SettingKey):
|
||||
"""Get setting."""
|
||||
for appliance in MOCK_APPLIANCES.homeappliances:
|
||||
if appliance.ha_id == ha_id:
|
||||
settings = MOCK_SETTINGS.get(
|
||||
next(
|
||||
appliance
|
||||
for appliance in MOCK_APPLIANCES.homeappliances
|
||||
if appliance.ha_id == ha_id
|
||||
).type,
|
||||
{},
|
||||
).get("data", {"settings": []})
|
||||
for setting_dict in cast(list[dict], settings["settings"]):
|
||||
if setting_dict["key"] == setting_key:
|
||||
return GetSetting.from_dict(setting_dict)
|
||||
raise HomeConnectApiError("error.key", "error description")
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def mock_client(request: pytest.FixtureRequest) -> MagicMock:
|
||||
"""Fixture to mock Client from HomeConnect."""
|
||||
@ -274,7 +303,10 @@ def mock_client(request: pytest.FixtureRequest) -> MagicMock:
|
||||
for event in await event_queue.get():
|
||||
yield event
|
||||
|
||||
mock.get_home_appliances = AsyncMock(return_value=MOCK_APPLIANCES)
|
||||
mock.get_home_appliances = AsyncMock(return_value=copy.deepcopy(MOCK_APPLIANCES))
|
||||
mock.get_specific_appliance = AsyncMock(
|
||||
side_effect=_get_specific_appliance_side_effect
|
||||
)
|
||||
mock.stream_all_events = stream_all_events
|
||||
mock.start_program = AsyncMock(
|
||||
side_effect=_get_set_program_side_effect(
|
||||
@ -296,6 +328,7 @@ def mock_client(request: pytest.FixtureRequest) -> MagicMock:
|
||||
side_effect=_get_set_key_value_side_effect(event_queue, "setting_key"),
|
||||
)
|
||||
mock.get_settings = AsyncMock(side_effect=_get_settings_side_effect)
|
||||
mock.get_setting = AsyncMock(side_effect=_get_setting_side_effect)
|
||||
mock.get_status = AsyncMock(return_value=copy.deepcopy(MOCK_STATUS))
|
||||
mock.get_all_programs = AsyncMock(side_effect=_get_all_programs_side_effect)
|
||||
mock.put_command = AsyncMock()
|
||||
@ -323,7 +356,7 @@ def mock_client_with_exception(request: pytest.FixtureRequest) -> MagicMock:
|
||||
for event in await event_queue.get():
|
||||
yield event
|
||||
|
||||
mock.get_home_appliances = AsyncMock(return_value=MOCK_APPLIANCES)
|
||||
mock.get_home_appliances = AsyncMock(return_value=copy.deepcopy(MOCK_APPLIANCES))
|
||||
mock.stream_all_events = stream_all_events
|
||||
|
||||
mock.start_program = AsyncMock(side_effect=exception)
|
||||
|
@ -1,9 +1,10 @@
|
||||
"""Tests for home_connect binary_sensor entities."""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from aiohomeconnect.model import ArrayOfEvents, Event, EventKey, EventMessage, EventType
|
||||
from aiohomeconnect.model.error import HomeConnectApiError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import automation, script
|
||||
@ -26,6 +27,7 @@ from homeassistant.const import (
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
import homeassistant.helpers.issue_registry as ir
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
@ -50,6 +52,110 @@ async def test_binary_sensors(
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
|
||||
async def test_paired_depaired_devices_flow(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that removed devices are correctly removed from and added to hass on API events."""
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert entity_entries
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DEPAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert not device
|
||||
for entity_entry in entity_entries:
|
||||
assert not entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
# Now that all everything related to the device is removed, pair it again
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.PAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
for entity_entry in entity_entries:
|
||||
assert entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
|
||||
async def test_connected_devices(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that devices reconnected.
|
||||
|
||||
Specifically those devices whose settings, status, etc. could
|
||||
not be obtained while disconnected and once connected, the entities are added.
|
||||
"""
|
||||
get_status_original_mock = client.get_status
|
||||
|
||||
def get_status_side_effect(ha_id: str):
|
||||
if ha_id == appliance_ha_id:
|
||||
raise HomeConnectApiError(
|
||||
"SDK.Error.HomeAppliance.Connection.Initialization.Failed"
|
||||
)
|
||||
return get_status_original_mock.return_value
|
||||
|
||||
client.get_status = AsyncMock(side_effect=get_status_side_effect)
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
client.get_status = get_status_original_mock
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert len(new_entity_entries) > len(entity_entries)
|
||||
|
||||
|
||||
async def test_binary_sensors_entity_availabilty(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, call
|
||||
from unittest.mock import AsyncMock, MagicMock, call
|
||||
|
||||
from aiohomeconnect.model import (
|
||||
ArrayOfEvents,
|
||||
@ -14,11 +14,12 @@ from aiohomeconnect.model import (
|
||||
GetSetting,
|
||||
SettingKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
from aiohomeconnect.model.error import HomeConnectApiError, HomeConnectError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.home_connect.const import (
|
||||
BSH_AMBIENT_LIGHT_COLOR_CUSTOM_COLOR,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
@ -32,6 +33,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -56,6 +58,124 @@ async def test_light(
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["Hood"], indirect=True)
|
||||
async def test_paired_depaired_devices_flow(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that removed devices are correctly removed from and added to hass on API events."""
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert entity_entries
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DEPAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert not device
|
||||
for entity_entry in entity_entries:
|
||||
assert not entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
# Now that all everything related to the device is removed, pair it again
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.PAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
for entity_entry in entity_entries:
|
||||
assert entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["Hood"], indirect=True)
|
||||
async def test_connected_devices(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that devices reconnected.
|
||||
|
||||
Specifically those devices whose settings, status, etc. could
|
||||
not be obtained while disconnected and once connected, the entities are added.
|
||||
"""
|
||||
get_settings_original_mock = client.get_settings
|
||||
get_available_programs_mock = client.get_available_programs
|
||||
|
||||
async def get_settings_side_effect(ha_id: str):
|
||||
if ha_id == appliance_ha_id:
|
||||
raise HomeConnectApiError(
|
||||
"SDK.Error.HomeAppliance.Connection.Initialization.Failed"
|
||||
)
|
||||
return await get_settings_original_mock.side_effect(ha_id)
|
||||
|
||||
async def get_available_programs_side_effect(ha_id: str):
|
||||
if ha_id == appliance_ha_id:
|
||||
raise HomeConnectApiError(
|
||||
"SDK.Error.HomeAppliance.Connection.Initialization.Failed"
|
||||
)
|
||||
return await get_available_programs_mock.side_effect(ha_id)
|
||||
|
||||
client.get_settings = AsyncMock(side_effect=get_settings_side_effect)
|
||||
client.get_available_programs = AsyncMock(
|
||||
side_effect=get_available_programs_side_effect
|
||||
)
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
client.get_settings = get_settings_original_mock
|
||||
client.get_available_programs = get_available_programs_mock
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert len(new_entity_entries) > len(entity_entries)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["Hood"], indirect=True)
|
||||
async def test_light_availabilty(
|
||||
hass: HomeAssistant,
|
||||
|
@ -12,10 +12,11 @@ from aiohomeconnect.model import (
|
||||
GetSetting,
|
||||
SettingKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
from aiohomeconnect.model.error import HomeConnectApiError, HomeConnectError
|
||||
from aiohomeconnect.model.setting import SettingConstraints
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.home_connect.const import DOMAIN
|
||||
from homeassistant.components.number import (
|
||||
ATTR_VALUE as SERVICE_ATTR_VALUE,
|
||||
DEFAULT_MAX_VALUE,
|
||||
@ -27,6 +28,7 @@ from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -49,6 +51,112 @@ async def test_number(
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["FridgeFreezer"], indirect=True)
|
||||
async def test_paired_depaired_devices_flow(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that removed devices are correctly removed from and added to hass on API events."""
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert entity_entries
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DEPAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert not device
|
||||
for entity_entry in entity_entries:
|
||||
assert not entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
# Now that all everything related to the device is removed, pair it again
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.PAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
for entity_entry in entity_entries:
|
||||
assert entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["FridgeFreezer"], indirect=True)
|
||||
async def test_connected_devices(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that devices reconnected.
|
||||
|
||||
Specifically those devices whose settings, status, etc. could
|
||||
not be obtained while disconnected and once connected, the entities are added.
|
||||
"""
|
||||
get_settings_original_mock = client.get_settings
|
||||
|
||||
def get_settings_side_effect(ha_id: str):
|
||||
if ha_id == appliance_ha_id:
|
||||
raise HomeConnectApiError(
|
||||
"SDK.Error.HomeAppliance.Connection.Initialization.Failed"
|
||||
)
|
||||
return get_settings_original_mock.return_value
|
||||
|
||||
client.get_settings = AsyncMock(side_effect=get_settings_side_effect)
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
client.get_settings = get_settings_original_mock
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert len(new_entity_entries) > len(entity_entries)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["FridgeFreezer"], indirect=True)
|
||||
async def test_number_entity_availabilty(
|
||||
hass: HomeAssistant,
|
||||
|
@ -20,6 +20,7 @@ from aiohomeconnect.model.program import (
|
||||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.home_connect.const import DOMAIN
|
||||
from homeassistant.components.select import (
|
||||
ATTR_OPTION,
|
||||
ATTR_OPTIONS,
|
||||
@ -35,7 +36,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -58,6 +59,99 @@ async def test_select(
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
async def test_paired_depaired_devices_flow(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that removed devices are correctly removed from and added to hass on API events."""
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert entity_entries
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DEPAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert not device
|
||||
for entity_entry in entity_entries:
|
||||
assert not entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
# Now that all everything related to the device is removed, pair it again
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.PAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
for entity_entry in entity_entries:
|
||||
assert entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
|
||||
async def test_connected_devices(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that devices reconnected.
|
||||
|
||||
Specifically those devices whose settings, status, etc. could
|
||||
not be obtained while disconnected and once connected, the entities are added.
|
||||
"""
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert entity_entries
|
||||
|
||||
|
||||
async def test_select_entity_availabilty(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Tests for home_connect sensor entities."""
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from aiohomeconnect.model import (
|
||||
ArrayOfEvents,
|
||||
@ -12,6 +12,7 @@ from aiohomeconnect.model import (
|
||||
Status,
|
||||
StatusKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectApiError
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
@ -22,10 +23,12 @@ from homeassistant.components.home_connect.const import (
|
||||
BSH_EVENT_PRESENT_STATE_CONFIRMED,
|
||||
BSH_EVENT_PRESENT_STATE_OFF,
|
||||
BSH_EVENT_PRESENT_STATE_PRESENT,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -94,6 +97,110 @@ async def test_sensors(
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
|
||||
async def test_paired_depaired_devices_flow(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that removed devices are correctly removed from and added to hass on API events."""
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert entity_entries
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DEPAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert not device
|
||||
for entity_entry in entity_entries:
|
||||
assert not entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
# Now that all everything related to the device is removed, pair it again
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.PAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
for entity_entry in entity_entries:
|
||||
assert entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
|
||||
async def test_connected_devices(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that devices reconnected.
|
||||
|
||||
Specifically those devices whose settings, status, etc. could
|
||||
not be obtained while disconnected and once connected, the entities are added.
|
||||
"""
|
||||
get_status_original_mock = client.get_status
|
||||
|
||||
def get_status_side_effect(ha_id: str):
|
||||
if ha_id == appliance_ha_id:
|
||||
raise HomeConnectApiError(
|
||||
"SDK.Error.HomeAppliance.Connection.Initialization.Failed"
|
||||
)
|
||||
return get_status_original_mock.return_value
|
||||
|
||||
client.get_status = AsyncMock(side_effect=get_status_side_effect)
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
client.get_status = get_status_original_mock
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert len(new_entity_entries) > len(entity_entries)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", [TEST_HC_APP], indirect=True)
|
||||
async def test_sensor_entity_availabilty(
|
||||
hass: HomeAssistant,
|
||||
|
@ -13,7 +13,7 @@ from aiohomeconnect.model import (
|
||||
ProgramKey,
|
||||
SettingKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
from aiohomeconnect.model.error import HomeConnectApiError, HomeConnectError
|
||||
from aiohomeconnect.model.event import ArrayOfEvents, EventType
|
||||
from aiohomeconnect.model.program import ArrayOfPrograms, EnumerateProgram
|
||||
from aiohomeconnect.model.setting import SettingConstraints
|
||||
@ -41,7 +41,11 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.helpers import (
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@ -66,6 +70,122 @@ async def test_switches(
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
|
||||
async def test_paired_depaired_devices_flow(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that removed devices are correctly removed from and added to hass on API events."""
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert entity_entries
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DEPAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert not device
|
||||
for entity_entry in entity_entries:
|
||||
assert not entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
# Now that all everything related to the device is removed, pair it again
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.PAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
for entity_entry in entity_entries:
|
||||
assert entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
|
||||
async def test_connected_devices(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that devices reconnected.
|
||||
|
||||
Specifically those devices whose settings, status, etc. could
|
||||
not be obtained while disconnected and once connected, the entities are added.
|
||||
"""
|
||||
get_settings_original_mock = client.get_settings
|
||||
get_available_programs_mock = client.get_available_programs
|
||||
|
||||
async def get_settings_side_effect(ha_id: str):
|
||||
if ha_id == appliance_ha_id:
|
||||
raise HomeConnectApiError(
|
||||
"SDK.Error.HomeAppliance.Connection.Initialization.Failed"
|
||||
)
|
||||
return await get_settings_original_mock.side_effect(ha_id)
|
||||
|
||||
async def get_available_programs_side_effect(ha_id: str):
|
||||
if ha_id == appliance_ha_id:
|
||||
raise HomeConnectApiError(
|
||||
"SDK.Error.HomeAppliance.Connection.Initialization.Failed"
|
||||
)
|
||||
return await get_available_programs_mock.side_effect(ha_id)
|
||||
|
||||
client.get_settings = AsyncMock(side_effect=get_settings_side_effect)
|
||||
client.get_available_programs = AsyncMock(
|
||||
side_effect=get_available_programs_side_effect
|
||||
)
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
client.get_settings = get_settings_original_mock
|
||||
client.get_available_programs = get_available_programs_mock
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert len(new_entity_entries) > len(entity_entries)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["Dishwasher"], indirect=True)
|
||||
async def test_switch_entity_availabilty(
|
||||
hass: HomeAssistant,
|
||||
|
@ -2,18 +2,26 @@
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from datetime import time
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from aiohomeconnect.model import ArrayOfSettings, EventMessage, GetSetting, SettingKey
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
from aiohomeconnect.model.event import ArrayOfEvents, EventType
|
||||
from aiohomeconnect.model import (
|
||||
ArrayOfEvents,
|
||||
ArrayOfSettings,
|
||||
EventMessage,
|
||||
EventType,
|
||||
GetSetting,
|
||||
SettingKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectApiError, HomeConnectError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.home_connect.const import DOMAIN
|
||||
from homeassistant.components.time import DOMAIN as TIME_DOMAIN, SERVICE_SET_VALUE
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TIME, STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -36,6 +44,112 @@ async def test_time(
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["Oven"], indirect=True)
|
||||
async def test_paired_depaired_devices_flow(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that removed devices are correctly removed from and added to hass on API events."""
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert entity_entries
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DEPAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert not device
|
||||
for entity_entry in entity_entries:
|
||||
assert not entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
# Now that all everything related to the device is removed, pair it again
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.PAIRED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
for entity_entry in entity_entries:
|
||||
assert entity_registry.async_get(entity_entry.entity_id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["Oven"], indirect=True)
|
||||
async def test_connected_devices(
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that devices reconnected.
|
||||
|
||||
Specifically those devices whose settings, status, etc. could
|
||||
not be obtained while disconnected and once connected, the entities are added.
|
||||
"""
|
||||
get_settings_original_mock = client.get_settings
|
||||
|
||||
async def get_settings_side_effect(ha_id: str):
|
||||
if ha_id == appliance_ha_id:
|
||||
raise HomeConnectApiError(
|
||||
"SDK.Error.HomeAppliance.Connection.Initialization.Failed"
|
||||
)
|
||||
return await get_settings_original_mock.side_effect(ha_id)
|
||||
|
||||
client.get_settings = AsyncMock(side_effect=get_settings_side_effect)
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
client.get_settings = get_settings_original_mock
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
data=ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)})
|
||||
assert device
|
||||
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id)
|
||||
assert len(new_entity_entries) > len(entity_entries)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["Oven"], indirect=True)
|
||||
async def test_time_entity_availabilty(
|
||||
hass: HomeAssistant,
|
||||
|
Loading…
x
Reference in New Issue
Block a user