mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 14:17:45 +00:00
Home Connect entities availability based on the connected state of the appliance (#136951)
* Base the entity availability on the connected state of the appliance * cache `ha_id` Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Inlcude coordinator `available` property at entity Co-authored-by: Martin Hjelmare <marhje52@gmail.com> --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
efcfd97d1b
commit
63ab13681a
@ -121,9 +121,10 @@ class HomeConnectCoordinator(
|
||||
while True:
|
||||
try:
|
||||
async for event_message in self.client.stream_all_events():
|
||||
event_message_ha_id = event_message.ha_id
|
||||
match event_message.type:
|
||||
case EventType.STATUS:
|
||||
statuses = self.data[event_message.ha_id].status
|
||||
statuses = self.data[event_message_ha_id].status
|
||||
for event in event_message.data.items:
|
||||
status_key = StatusKey(event.key)
|
||||
if status_key in statuses:
|
||||
@ -134,10 +135,11 @@ class HomeConnectCoordinator(
|
||||
raw_key=status_key.value,
|
||||
value=event.value,
|
||||
)
|
||||
self._call_event_listener(event_message)
|
||||
|
||||
case EventType.NOTIFY:
|
||||
settings = self.data[event_message.ha_id].settings
|
||||
events = self.data[event_message.ha_id].events
|
||||
settings = self.data[event_message_ha_id].settings
|
||||
events = self.data[event_message_ha_id].events
|
||||
for event in event_message.data.items:
|
||||
if event.key in SettingKey:
|
||||
setting_key = SettingKey(event.key)
|
||||
@ -151,13 +153,25 @@ class HomeConnectCoordinator(
|
||||
)
|
||||
else:
|
||||
events[event.key] = event
|
||||
self._call_event_listener(event_message)
|
||||
|
||||
case EventType.EVENT:
|
||||
events = self.data[event_message.ha_id].events
|
||||
events = self.data[event_message_ha_id].events
|
||||
for event in event_message.data.items:
|
||||
events[event.key] = event
|
||||
self._call_event_listener(event_message)
|
||||
|
||||
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(
|
||||
event_message_ha_id
|
||||
)
|
||||
|
||||
case EventType.DISCONNECTED:
|
||||
self.data[event_message_ha_id].info.connected = False
|
||||
self._call_all_event_listeners_for_appliance(
|
||||
event_message_ha_id
|
||||
)
|
||||
|
||||
except (EventStreamInterruptedError, HomeConnectRequestError) as error:
|
||||
_LOGGER.debug(
|
||||
@ -186,6 +200,12 @@ class HomeConnectCoordinator(
|
||||
):
|
||||
listener()
|
||||
|
||||
@callback
|
||||
def _call_all_event_listeners_for_appliance(self, ha_id: str):
|
||||
for listener, context in self._listeners.values():
|
||||
if isinstance(context, tuple) and context[0] == ha_id:
|
||||
listener()
|
||||
|
||||
async def _async_update_data(self) -> dict[str, HomeConnectApplianceData]:
|
||||
"""Fetch data from Home Connect."""
|
||||
try:
|
||||
|
@ -56,3 +56,10 @@ class HomeConnectEntity(CoordinatorEntity[HomeConnectCoordinator]):
|
||||
def bsh_key(self) -> str:
|
||||
"""Return the BSH key."""
|
||||
return self.entity_description.key
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return (
|
||||
self.appliance.info.connected and self._attr_available and super().available
|
||||
)
|
||||
|
@ -18,7 +18,13 @@ from homeassistant.components.home_connect.const import (
|
||||
)
|
||||
from homeassistant.components.script import scripts_with_entity
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN, Platform
|
||||
from homeassistant.const import (
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.issue_registry as ir
|
||||
from homeassistant.setup import async_setup_component
|
||||
@ -44,6 +50,59 @@ async def test_binary_sensors(
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
|
||||
async def test_binary_sensors_entity_availabilty(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
) -> None:
|
||||
"""Test if binary sensor entities availability are based on the appliance connection state."""
|
||||
entity_ids = [
|
||||
"binary_sensor.washer_door",
|
||||
"binary_sensor.washer_remote_control",
|
||||
]
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DISCONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
assert hass.states.is_state(entity_id, STATE_UNAVAILABLE)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("value", "expected"),
|
||||
[
|
||||
|
@ -27,6 +27,7 @@ from homeassistant.const import (
|
||||
SERVICE_TURN_ON,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
@ -55,6 +56,59 @@ async def test_light(
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["Hood"], indirect=True)
|
||||
async def test_light_availabilty(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
) -> None:
|
||||
"""Test if light entities availability are based on the appliance connection state."""
|
||||
entity_ids = [
|
||||
"light.hood_functional_light",
|
||||
]
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DISCONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
assert hass.states.is_state(entity_id, STATE_UNAVAILABLE)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"entity_id",
|
||||
|
@ -4,7 +4,14 @@ from collections.abc import Awaitable, Callable
|
||||
import random
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from aiohomeconnect.model import ArrayOfSettings, GetSetting, SettingKey
|
||||
from aiohomeconnect.model import (
|
||||
ArrayOfEvents,
|
||||
ArrayOfSettings,
|
||||
EventMessage,
|
||||
EventType,
|
||||
GetSetting,
|
||||
SettingKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
from aiohomeconnect.model.setting import SettingConstraints
|
||||
import pytest
|
||||
@ -17,7 +24,7 @@ from homeassistant.components.number import (
|
||||
SERVICE_SET_VALUE,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
@ -42,6 +49,64 @@ async def test_number(
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["FridgeFreezer"], indirect=True)
|
||||
async def test_number_entity_availabilty(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
) -> None:
|
||||
"""Test if number entities availability are based on the appliance connection state."""
|
||||
entity_ids = [
|
||||
f"{NUMBER_DOMAIN.lower()}.fridgefreezer_refrigerator_temperature",
|
||||
]
|
||||
|
||||
client.get_setting.side_effect = None
|
||||
# Setting constrains are not needed for this test
|
||||
# so we rise an error to easily test the availability
|
||||
client.get_setting = AsyncMock(side_effect=HomeConnectError())
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DISCONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
assert hass.states.is_state(entity_id, STATE_UNAVAILABLE)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["FridgeFreezer"], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
|
@ -29,6 +29,7 @@ from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_SELECT_OPTION,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
Platform,
|
||||
)
|
||||
@ -57,6 +58,58 @@ async def test_select(
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
async def test_select_entity_availabilty(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
) -> None:
|
||||
"""Test if select entities availability are based on the appliance connection state."""
|
||||
entity_ids = [
|
||||
"select.washer_active_program",
|
||||
]
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DISCONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
assert hass.states.is_state(entity_id, STATE_UNAVAILABLE)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_filter_programs(
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
|
@ -24,7 +24,7 @@ from homeassistant.components.home_connect.const import (
|
||||
BSH_EVENT_PRESENT_STATE_PRESENT,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@ -94,6 +94,60 @@ async def test_sensors(
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", [TEST_HC_APP], indirect=True)
|
||||
async def test_sensor_entity_availabilty(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
) -> None:
|
||||
"""Test if sensor entities availability are based on the appliance connection state."""
|
||||
entity_ids = [
|
||||
"sensor.dishwasher_operation_state",
|
||||
"sensor.dishwasher_salt_nearly_empty",
|
||||
]
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DISCONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
assert hass.states.is_state(entity_id, STATE_UNAVAILABLE)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
# Appliance_ha_id program sequence with a delayed start.
|
||||
PROGRAM_SEQUENCE_EVENTS = (
|
||||
EVENT_PROG_DELAYED_START,
|
||||
|
@ -36,6 +36,7 @@ from homeassistant.const import (
|
||||
SERVICE_TURN_ON,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
@ -65,6 +66,61 @@ async def test_switches(
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["Dishwasher"], indirect=True)
|
||||
async def test_switch_entity_availabilty(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
) -> None:
|
||||
"""Test if switch entities availability are based on the appliance connection state."""
|
||||
entity_ids = [
|
||||
"switch.dishwasher_power",
|
||||
"switch.dishwasher_child_lock",
|
||||
"switch.dishwasher_program_eco50",
|
||||
]
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DISCONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
assert hass.states.is_state(entity_id, STATE_UNAVAILABLE)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"entity_id",
|
||||
|
@ -4,13 +4,14 @@ from collections.abc import Awaitable, Callable
|
||||
from datetime import time
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from aiohomeconnect.model import ArrayOfSettings, GetSetting, SettingKey
|
||||
from aiohomeconnect.model import ArrayOfSettings, EventMessage, GetSetting, SettingKey
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
from aiohomeconnect.model.event import ArrayOfEvents, EventType
|
||||
import pytest
|
||||
|
||||
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, Platform
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TIME, STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
@ -35,6 +36,59 @@ async def test_time(
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["Oven"], indirect=True)
|
||||
async def test_time_entity_availabilty(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
) -> None:
|
||||
"""Test if time entities availability are based on the appliance connection state."""
|
||||
entity_ids = [
|
||||
"time.oven_alarm_clock",
|
||||
]
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.DISCONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
assert hass.states.is_state(entity_id, STATE_UNAVAILABLE)
|
||||
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
EventType.CONNECTED,
|
||||
ArrayOfEvents([]),
|
||||
)
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", ["Oven"], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "setting_key"),
|
||||
|
Loading…
x
Reference in New Issue
Block a user