This commit is contained in:
Franck Nijhof 2025-04-25 09:47:05 +02:00 committed by GitHub
commit 360bffa3a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 315 additions and 96 deletions

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/chacon_dio", "documentation": "https://www.home-assistant.io/integrations/chacon_dio",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["dio_chacon_api"], "loggers": ["dio_chacon_api"],
"requirements": ["dio-chacon-wifi-api==1.2.1"] "requirements": ["dio-chacon-wifi-api==1.2.2"]
} }

View File

@ -241,9 +241,7 @@ class HomeConnectCoordinator(
appliance_data = await self._get_appliance_data( appliance_data = await self._get_appliance_data(
appliance_info, self.data.get(appliance_info.ha_id) appliance_info, self.data.get(appliance_info.ha_id)
) )
if event_message_ha_id in self.data: if event_message_ha_id not in self.data:
self.data[event_message_ha_id].update(appliance_data)
else:
self.data[event_message_ha_id] = appliance_data self.data[event_message_ha_id] = appliance_data
for listener, context in self._special_listeners.values(): for listener, context in self._special_listeners.values():
if ( if (

View File

@ -17,7 +17,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .common import setup_home_connect_entry from .common import setup_home_connect_entry
from .const import ( from .const import (
APPLIANCES_WITH_PROGRAMS,
AVAILABLE_MAPS_ENUM, AVAILABLE_MAPS_ENUM,
BEAN_AMOUNT_OPTIONS, BEAN_AMOUNT_OPTIONS,
BEAN_CONTAINER_OPTIONS, BEAN_CONTAINER_OPTIONS,
@ -313,7 +312,7 @@ def _get_entities_for_appliance(
HomeConnectProgramSelectEntity(entry.runtime_data, appliance, desc) HomeConnectProgramSelectEntity(entry.runtime_data, appliance, desc)
for desc in PROGRAM_SELECT_ENTITY_DESCRIPTIONS for desc in PROGRAM_SELECT_ENTITY_DESCRIPTIONS
] ]
if appliance.info.type in APPLIANCES_WITH_PROGRAMS if appliance.programs
else [] else []
), ),
*[ *[

View File

@ -14,6 +14,6 @@
"documentation": "https://www.home-assistant.io/integrations/homekit_controller", "documentation": "https://www.home-assistant.io/integrations/homekit_controller",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["aiohomekit", "commentjson"], "loggers": ["aiohomekit", "commentjson"],
"requirements": ["aiohomekit==3.2.13"], "requirements": ["aiohomekit==3.2.14"],
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."] "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
} }

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from datetime import timedelta
import logging import logging
from typing import Any from typing import Any
@ -120,6 +121,8 @@ SERVICE_PLAY_PRESET_SCHEMA = cv.make_entity_service_schema(
) )
RETRY_POLL_MAXIMUM = 3 RETRY_POLL_MAXIMUM = 3
SCAN_INTERVAL = timedelta(seconds=5)
PARALLEL_UPDATES = 1
async def async_setup_entry( async def async_setup_entry(

View File

@ -40,7 +40,7 @@ ATTR_NEXT_RAIN_DT_REF = "forecast_time_ref"
CONDITION_CLASSES: dict[str, list[str]] = { CONDITION_CLASSES: dict[str, list[str]] = {
ATTR_CONDITION_CLEAR_NIGHT: ["Nuit Claire", "Nuit claire"], ATTR_CONDITION_CLEAR_NIGHT: ["Nuit Claire", "Nuit claire", "Ciel clair"],
ATTR_CONDITION_CLOUDY: ["Très nuageux", "Couvert"], ATTR_CONDITION_CLOUDY: ["Très nuageux", "Couvert"],
ATTR_CONDITION_FOG: [ ATTR_CONDITION_FOG: [
"Brume ou bancs de brouillard", "Brume ou bancs de brouillard",
@ -48,9 +48,10 @@ CONDITION_CLASSES: dict[str, list[str]] = {
"Brouillard", "Brouillard",
"Brouillard givrant", "Brouillard givrant",
"Bancs de Brouillard", "Bancs de Brouillard",
"Brouillard dense",
], ],
ATTR_CONDITION_HAIL: ["Risque de grêle", "Risque de grèle"], ATTR_CONDITION_HAIL: ["Risque de grêle", "Risque de grèle"],
ATTR_CONDITION_LIGHTNING: ["Risque d'orages", "Orages"], ATTR_CONDITION_LIGHTNING: ["Risque d'orages", "Orages", "Orage avec grêle"],
ATTR_CONDITION_LIGHTNING_RAINY: [ ATTR_CONDITION_LIGHTNING_RAINY: [
"Pluie orageuses", "Pluie orageuses",
"Pluies orageuses", "Pluies orageuses",
@ -62,6 +63,7 @@ CONDITION_CLASSES: dict[str, list[str]] = {
"Éclaircies", "Éclaircies",
"Eclaircies", "Eclaircies",
"Peu nuageux", "Peu nuageux",
"Variable",
], ],
ATTR_CONDITION_POURING: ["Pluie forte"], ATTR_CONDITION_POURING: ["Pluie forte"],
ATTR_CONDITION_RAINY: [ ATTR_CONDITION_RAINY: [
@ -74,6 +76,7 @@ CONDITION_CLASSES: dict[str, list[str]] = {
"Pluie modérée", "Pluie modérée",
"Pluie / Averses", "Pluie / Averses",
"Averses", "Averses",
"Averses faibles",
"Pluie", "Pluie",
], ],
ATTR_CONDITION_SNOWY: [ ATTR_CONDITION_SNOWY: [
@ -81,6 +84,8 @@ CONDITION_CLASSES: dict[str, list[str]] = {
"Neige", "Neige",
"Averses de neige", "Averses de neige",
"Neige forte", "Neige forte",
"Neige faible",
"Averses de neige faible",
"Quelques flocons", "Quelques flocons",
], ],
ATTR_CONDITION_SNOWY_RAINY: ["Pluie et neige", "Pluie verglaçante"], ATTR_CONDITION_SNOWY_RAINY: ["Pluie et neige", "Pluie verglaçante"],

View File

@ -84,8 +84,10 @@
"options": { "options": {
"apparent_power": "[%key:component::sensor::entity_component::apparent_power::name%]", "apparent_power": "[%key:component::sensor::entity_component::apparent_power::name%]",
"aqi": "[%key:component::sensor::entity_component::aqi::name%]", "aqi": "[%key:component::sensor::entity_component::aqi::name%]",
"area": "[%key:component::sensor::entity_component::area::name%]",
"atmospheric_pressure": "[%key:component::sensor::entity_component::atmospheric_pressure::name%]", "atmospheric_pressure": "[%key:component::sensor::entity_component::atmospheric_pressure::name%]",
"battery": "[%key:component::sensor::entity_component::battery::name%]", "battery": "[%key:component::sensor::entity_component::battery::name%]",
"blood_glucose_concentration": "[%key:component::sensor::entity_component::blood_glucose_concentration::name%]",
"carbon_dioxide": "[%key:component::sensor::entity_component::carbon_dioxide::name%]", "carbon_dioxide": "[%key:component::sensor::entity_component::carbon_dioxide::name%]",
"carbon_monoxide": "[%key:component::sensor::entity_component::carbon_monoxide::name%]", "carbon_monoxide": "[%key:component::sensor::entity_component::carbon_monoxide::name%]",
"conductivity": "[%key:component::sensor::entity_component::conductivity::name%]", "conductivity": "[%key:component::sensor::entity_component::conductivity::name%]",

View File

@ -30,5 +30,5 @@
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["pysmartthings"], "loggers": ["pysmartthings"],
"quality_scale": "bronze", "quality_scale": "bronze",
"requirements": ["pysmartthings==3.0.4"] "requirements": ["pysmartthings==3.0.5"]
} }

View File

@ -3,7 +3,6 @@
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import VodafoneConfigEntry, VodafoneStationRouter from .coordinator import VodafoneConfigEntry, VodafoneStationRouter
PLATFORMS = [Platform.BUTTON, Platform.DEVICE_TRACKER, Platform.SENSOR] PLATFORMS = [Platform.BUTTON, Platform.DEVICE_TRACKER, Platform.SENSOR]
@ -36,7 +35,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: VodafoneConfigEntry) ->
coordinator = entry.runtime_data coordinator = entry.runtime_data
await coordinator.api.logout() await coordinator.api.logout()
await coordinator.api.close() await coordinator.api.close()
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok return unload_ok

View File

@ -25,7 +25,7 @@ if TYPE_CHECKING:
APPLICATION_NAME: Final = "HomeAssistant" APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2025 MAJOR_VERSION: Final = 2025
MINOR_VERSION: Final = 4 MINOR_VERSION: Final = 4
PATCH_VERSION: Final = "3" PATCH_VERSION: Final = "4"
__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, 13, 0) REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 0)

View File

@ -1,10 +1,10 @@
[build-system] [build-system]
requires = ["setuptools==77.0.3"] requires = ["setuptools==78.1.1"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project] [project]
name = "homeassistant" name = "homeassistant"
version = "2025.4.3" version = "2025.4.4"
license = "Apache-2.0" license = "Apache-2.0"
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"] license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
description = "Open-source home automation platform running on Python 3." description = "Open-source home automation platform running on Python 3."

6
requirements_all.txt generated
View File

@ -267,7 +267,7 @@ aiohasupervisor==0.3.0
aiohomeconnect==0.16.3 aiohomeconnect==0.16.3
# homeassistant.components.homekit_controller # homeassistant.components.homekit_controller
aiohomekit==3.2.13 aiohomekit==3.2.14
# homeassistant.components.mcp_server # homeassistant.components.mcp_server
aiohttp_sse==2.2.0 aiohttp_sse==2.2.0
@ -784,7 +784,7 @@ devolo-home-control-api==0.18.3
devolo-plc-api==1.5.1 devolo-plc-api==1.5.1
# homeassistant.components.chacon_dio # homeassistant.components.chacon_dio
dio-chacon-wifi-api==1.2.1 dio-chacon-wifi-api==1.2.2
# homeassistant.components.directv # homeassistant.components.directv
directv==0.4.0 directv==0.4.0
@ -2319,7 +2319,7 @@ pysma==0.7.5
pysmappee==0.2.29 pysmappee==0.2.29
# homeassistant.components.smartthings # homeassistant.components.smartthings
pysmartthings==3.0.4 pysmartthings==3.0.5
# homeassistant.components.smarty # homeassistant.components.smarty
pysmarty2==0.10.2 pysmarty2==0.10.2

View File

@ -252,7 +252,7 @@ aiohasupervisor==0.3.0
aiohomeconnect==0.16.3 aiohomeconnect==0.16.3
# homeassistant.components.homekit_controller # homeassistant.components.homekit_controller
aiohomekit==3.2.13 aiohomekit==3.2.14
# homeassistant.components.mcp_server # homeassistant.components.mcp_server
aiohttp_sse==2.2.0 aiohttp_sse==2.2.0
@ -675,7 +675,7 @@ devolo-home-control-api==0.18.3
devolo-plc-api==1.5.1 devolo-plc-api==1.5.1
# homeassistant.components.chacon_dio # homeassistant.components.chacon_dio
dio-chacon-wifi-api==1.2.1 dio-chacon-wifi-api==1.2.2
# homeassistant.components.directv # homeassistant.components.directv
directv==0.4.0 directv==0.4.0
@ -1889,7 +1889,7 @@ pysma==0.7.5
pysmappee==0.2.29 pysmappee==0.2.29
# homeassistant.components.smartthings # homeassistant.components.smartthings
pysmartthings==3.0.4 pysmartthings==3.0.5
# homeassistant.components.smarty # homeassistant.components.smarty
pysmarty2==0.10.2 pysmarty2==0.10.2

View File

@ -191,7 +191,6 @@ EXCEPTIONS = {
"enocean", # https://github.com/kipe/enocean/pull/142 "enocean", # https://github.com/kipe/enocean/pull/142
"imutils", # https://github.com/PyImageSearch/imutils/pull/292 "imutils", # https://github.com/PyImageSearch/imutils/pull/292
"iso4217", # Public domain "iso4217", # Public domain
"jaraco.itertools", # MIT - https://github.com/jaraco/jaraco.itertools/issues/21
"kiwiki_client", # https://github.com/c7h/kiwiki_client/pull/6 "kiwiki_client", # https://github.com/c7h/kiwiki_client/pull/6
"ld2410-ble", # https://github.com/930913/ld2410-ble/pull/7 "ld2410-ble", # https://github.com/930913/ld2410-ble/pull/7
"maxcube-api", # https://github.com/uebelack/python-maxcube-api/pull/48 "maxcube-api", # https://github.com/uebelack/python-maxcube-api/pull/48
@ -205,6 +204,11 @@ EXCEPTIONS = {
"repoze.lru", "repoze.lru",
"sharp_aquos_rc", # https://github.com/jmoore987/sharp_aquos_rc/pull/14 "sharp_aquos_rc", # https://github.com/jmoore987/sharp_aquos_rc/pull/14
"tapsaff", # https://github.com/bazwilliams/python-taps-aff/pull/5 "tapsaff", # https://github.com/bazwilliams/python-taps-aff/pull/5
# ---
# https://github.com/jaraco/skeleton/pull/170
# https://github.com/jaraco/skeleton/pull/171
"jaraco.itertools", # MIT - https://github.com/jaraco/jaraco.itertools/issues/21
"setuptools", # MIT
} }
TODO = { TODO = {

View File

@ -181,5 +181,29 @@
} }
] ]
} }
},
"Hood": {
"data": {
"programs": [
{
"key": "Cooking.Common.Program.Hood.Automatic",
"constraints": {
"execution": "selectandstart"
}
},
{
"key": "Cooking.Common.Program.Hood.Venting",
"constraints": {
"execution": "selectandstart"
}
},
{
"key": "Cooking.Common.Program.Hood.DelayedShutOff",
"constraints": {
"execution": "selectandstart"
}
}
]
}
} }
} }

View File

@ -90,6 +90,9 @@
'ha_id': 'BOSCH-HCS000000-D00000000004', 'ha_id': 'BOSCH-HCS000000-D00000000004',
'name': 'Hood', 'name': 'Hood',
'programs': list([ 'programs': list([
'Cooking.Common.Program.Hood.Automatic',
'Cooking.Common.Program.Hood.Venting',
'Cooking.Common.Program.Hood.DelayedShutOff',
]), ]),
'settings': dict({ 'settings': dict({
'BSH.Common.Setting.AmbientLightBrightness': 70, 'BSH.Common.Setting.AmbientLightBrightness': 70,

View File

@ -11,6 +11,7 @@ from aiohomeconnect.model import (
EventMessage, EventMessage,
EventType, EventType,
HomeAppliance, HomeAppliance,
StatusKey,
) )
from aiohomeconnect.model.error import HomeConnectApiError from aiohomeconnect.model.error import HomeConnectApiError
import pytest import pytest
@ -115,9 +116,19 @@ async def test_paired_depaired_devices_flow(
assert entity_registry.async_get(entity_entry.entity_id) assert entity_registry.async_get(entity_entry.entity_id)
@pytest.mark.parametrize("appliance", ["Washer"], indirect=True) @pytest.mark.parametrize(
("appliance", "keys_to_check"),
[
(
"Washer",
(StatusKey.BSH_COMMON_REMOTE_CONTROL_ACTIVE,),
)
],
indirect=["appliance"],
)
async def test_connected_devices( async def test_connected_devices(
appliance: HomeAppliance, appliance: HomeAppliance,
keys_to_check: tuple,
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
integration_setup: Callable[[MagicMock], Awaitable[bool]], integration_setup: Callable[[MagicMock], Awaitable[bool]],
@ -148,7 +159,17 @@ async def test_connected_devices(
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)})
assert device assert device
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) assert entity_registry.async_get_entity_id(
Platform.BINARY_SENSOR,
DOMAIN,
f"{appliance.ha_id}-{EventKey.BSH_COMMON_APPLIANCE_CONNECTED}",
)
for key in keys_to_check:
assert not entity_registry.async_get_entity_id(
Platform.BINARY_SENSOR,
DOMAIN,
f"{appliance.ha_id}-{key}",
)
await client.add_events( await client.add_events(
[ [
@ -161,10 +182,12 @@ async def test_connected_devices(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) for key in (*keys_to_check, EventKey.BSH_COMMON_APPLIANCE_CONNECTED):
assert device assert entity_registry.async_get_entity_id(
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) Platform.BINARY_SENSOR,
assert len(new_entity_entries) > len(entity_entries) DOMAIN,
f"{appliance.ha_id}-{key}",
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")

View File

@ -99,9 +99,19 @@ async def test_paired_depaired_devices_flow(
assert entity_registry.async_get(entity_entry.entity_id) assert entity_registry.async_get(entity_entry.entity_id)
@pytest.mark.parametrize("appliance", ["Washer"], indirect=True) @pytest.mark.parametrize(
("appliance", "keys_to_check"),
[
(
"Washer",
(CommandKey.BSH_COMMON_PAUSE_PROGRAM,),
)
],
indirect=["appliance"],
)
async def test_connected_devices( async def test_connected_devices(
appliance: HomeAppliance, appliance: HomeAppliance,
keys_to_check: tuple,
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
integration_setup: Callable[[MagicMock], Awaitable[bool]], integration_setup: Callable[[MagicMock], Awaitable[bool]],
@ -116,7 +126,7 @@ async def test_connected_devices(
not be obtained while disconnected and once connected, the entities are added. not be obtained while disconnected and once connected, the entities are added.
""" """
get_available_commands_original_mock = client.get_available_commands get_available_commands_original_mock = client.get_available_commands
get_available_programs_mock = client.get_available_programs get_all_programs_mock = client.get_all_programs
async def get_available_commands_side_effect(ha_id: str): async def get_available_commands_side_effect(ha_id: str):
if ha_id == appliance.ha_id: if ha_id == appliance.ha_id:
@ -125,28 +135,36 @@ async def test_connected_devices(
) )
return await get_available_commands_original_mock.side_effect(ha_id) return await get_available_commands_original_mock.side_effect(ha_id)
async def get_available_programs_side_effect(ha_id: str): async def get_all_programs_side_effect(ha_id: str):
if ha_id == appliance.ha_id: if ha_id == appliance.ha_id:
raise HomeConnectApiError( raise HomeConnectApiError(
"SDK.Error.HomeAppliance.Connection.Initialization.Failed" "SDK.Error.HomeAppliance.Connection.Initialization.Failed"
) )
return await get_available_programs_mock.side_effect(ha_id) return await get_all_programs_mock.side_effect(ha_id)
client.get_available_commands = AsyncMock( client.get_available_commands = AsyncMock(
side_effect=get_available_commands_side_effect side_effect=get_available_commands_side_effect
) )
client.get_available_programs = AsyncMock( client.get_all_programs = AsyncMock(side_effect=get_all_programs_side_effect)
side_effect=get_available_programs_side_effect
)
assert config_entry.state == ConfigEntryState.NOT_LOADED assert config_entry.state == ConfigEntryState.NOT_LOADED
assert await integration_setup(client) assert await integration_setup(client)
assert config_entry.state == ConfigEntryState.LOADED assert config_entry.state == ConfigEntryState.LOADED
client.get_available_commands = get_available_commands_original_mock client.get_available_commands = get_available_commands_original_mock
client.get_available_programs = get_available_programs_mock client.get_all_programs = get_all_programs_mock
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)})
assert device assert device
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) assert entity_registry.async_get_entity_id(
Platform.BUTTON,
DOMAIN,
f"{appliance.ha_id}-StopProgram",
)
for key in keys_to_check:
assert not entity_registry.async_get_entity_id(
Platform.BUTTON,
DOMAIN,
f"{appliance.ha_id}-{key}",
)
await client.add_events( await client.add_events(
[ [
@ -159,10 +177,12 @@ async def test_connected_devices(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) for key in (*keys_to_check, "StopProgram"):
assert device assert entity_registry.async_get_entity_id(
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) Platform.BUTTON,
assert len(new_entity_entries) > len(entity_entries) DOMAIN,
f"{appliance.ha_id}-{key}",
)
@pytest.mark.parametrize("appliance", ["Washer"], indirect=True) @pytest.mark.parametrize("appliance", ["Washer"], indirect=True)

View File

@ -119,9 +119,19 @@ async def test_paired_depaired_devices_flow(
assert entity_registry.async_get(entity_entry.entity_id) assert entity_registry.async_get(entity_entry.entity_id)
@pytest.mark.parametrize("appliance", ["Hood"], indirect=True) @pytest.mark.parametrize(
("appliance", "keys_to_check"),
[
(
"Hood",
(SettingKey.COOKING_COMMON_LIGHTING,),
)
],
indirect=["appliance"],
)
async def test_connected_devices( async def test_connected_devices(
appliance: HomeAppliance, appliance: HomeAppliance,
keys_to_check: tuple,
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
integration_setup: Callable[[MagicMock], Awaitable[bool]], integration_setup: Callable[[MagicMock], Awaitable[bool]],
@ -136,7 +146,6 @@ async def test_connected_devices(
not be obtained while disconnected and once connected, the entities are added. not be obtained while disconnected and once connected, the entities are added.
""" """
get_settings_original_mock = client.get_settings get_settings_original_mock = client.get_settings
get_available_programs_mock = client.get_available_programs
async def get_settings_side_effect(ha_id: str): async def get_settings_side_effect(ha_id: str):
if ha_id == appliance.ha_id: if ha_id == appliance.ha_id:
@ -145,26 +154,20 @@ async def test_connected_devices(
) )
return await get_settings_original_mock.side_effect(ha_id) 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_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 config_entry.state == ConfigEntryState.NOT_LOADED
assert await integration_setup(client) assert await integration_setup(client)
assert config_entry.state == ConfigEntryState.LOADED assert config_entry.state == ConfigEntryState.LOADED
client.get_settings = get_settings_original_mock 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)}) device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)})
assert device assert device
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) for key in keys_to_check:
assert not entity_registry.async_get_entity_id(
Platform.LIGHT,
DOMAIN,
f"{appliance.ha_id}-{key}",
)
await client.add_events( await client.add_events(
[ [
@ -177,10 +180,12 @@ async def test_connected_devices(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) for key in keys_to_check:
assert device assert entity_registry.async_get_entity_id(
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) Platform.LIGHT,
assert len(new_entity_entries) > len(entity_entries) DOMAIN,
f"{appliance.ha_id}-{key}",
)
@pytest.mark.parametrize("appliance", ["Hood"], indirect=True) @pytest.mark.parametrize("appliance", ["Hood"], indirect=True)

View File

@ -135,9 +135,21 @@ async def test_paired_depaired_devices_flow(
assert entity_registry.async_get(entity_entry.entity_id) assert entity_registry.async_get(entity_entry.entity_id)
@pytest.mark.parametrize("appliance", ["FridgeFreezer"], indirect=True) @pytest.mark.parametrize(
("appliance", "keys_to_check"),
[
(
"FridgeFreezer",
(
SettingKey.REFRIGERATION_FRIDGE_FREEZER_SETPOINT_TEMPERATURE_REFRIGERATOR,
),
)
],
indirect=["appliance"],
)
async def test_connected_devices( async def test_connected_devices(
appliance: HomeAppliance, appliance: HomeAppliance,
keys_to_check: tuple,
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
integration_setup: Callable[[MagicMock], Awaitable[bool]], integration_setup: Callable[[MagicMock], Awaitable[bool]],
@ -168,7 +180,12 @@ async def test_connected_devices(
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)})
assert device assert device
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) for key in keys_to_check:
assert not entity_registry.async_get_entity_id(
Platform.NUMBER,
DOMAIN,
f"{appliance.ha_id}-{key}",
)
await client.add_events( await client.add_events(
[ [
@ -181,10 +198,12 @@ async def test_connected_devices(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) for key in keys_to_check:
assert device assert entity_registry.async_get_entity_id(
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) Platform.NUMBER,
assert len(new_entity_entries) > len(entity_entries) DOMAIN,
f"{appliance.ha_id}-{key}",
)
@pytest.mark.parametrize("appliance", ["FridgeFreezer"], indirect=True) @pytest.mark.parametrize("appliance", ["FridgeFreezer"], indirect=True)

View File

@ -20,6 +20,7 @@ from aiohomeconnect.model import (
) )
from aiohomeconnect.model.error import ( from aiohomeconnect.model.error import (
ActiveProgramNotSetError, ActiveProgramNotSetError,
HomeConnectApiError,
HomeConnectError, HomeConnectError,
SelectedProgramNotSetError, SelectedProgramNotSetError,
TooManyRequestsError, TooManyRequestsError,
@ -138,9 +139,23 @@ async def test_paired_depaired_devices_flow(
assert entity_registry.async_get(entity_entry.entity_id) assert entity_registry.async_get(entity_entry.entity_id)
@pytest.mark.parametrize("appliance", ["Washer"], indirect=True) @pytest.mark.parametrize(
("appliance", "keys_to_check"),
[
(
"Hood",
(
EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
EventKey.BSH_COMMON_ROOT_SELECTED_PROGRAM,
SettingKey.COOKING_HOOD_COLOR_TEMPERATURE,
),
)
],
indirect=["appliance"],
)
async def test_connected_devices( async def test_connected_devices(
appliance: HomeAppliance, appliance: HomeAppliance,
keys_to_check: tuple,
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
integration_setup: Callable[[MagicMock], Awaitable[bool]], integration_setup: Callable[[MagicMock], Awaitable[bool]],
@ -154,13 +169,39 @@ async def test_connected_devices(
Specifically those devices whose settings, status, etc. could Specifically those devices whose settings, status, etc. could
not be obtained while disconnected and once connected, the entities are added. not be obtained while disconnected and once connected, the entities are added.
""" """
get_settings_original_mock = client.get_settings
get_all_programs_mock = client.get_all_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_all_programs_side_effect(ha_id: str):
if ha_id == appliance.ha_id:
raise HomeConnectApiError(
"SDK.Error.HomeAppliance.Connection.Initialization.Failed"
)
return await get_all_programs_mock.side_effect(ha_id)
client.get_settings = AsyncMock(side_effect=get_settings_side_effect)
client.get_all_programs = AsyncMock(side_effect=get_all_programs_side_effect)
assert config_entry.state == ConfigEntryState.NOT_LOADED assert config_entry.state == ConfigEntryState.NOT_LOADED
assert await integration_setup(client) assert await integration_setup(client)
assert config_entry.state == ConfigEntryState.LOADED assert config_entry.state == ConfigEntryState.LOADED
client.get_settings = get_settings_original_mock
client.get_all_programs = get_all_programs_mock
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)})
assert device assert device
for key in keys_to_check:
assert not entity_registry.async_get_entity_id(
Platform.SELECT,
DOMAIN,
f"{appliance.ha_id}-{key}",
)
await client.add_events( await client.add_events(
[ [
@ -173,10 +214,12 @@ async def test_connected_devices(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) for key in keys_to_check:
assert device assert entity_registry.async_get_entity_id(
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) Platform.SELECT,
assert entity_entries DOMAIN,
f"{appliance.ha_id}-{key}",
)
@pytest.mark.parametrize("appliance", ["Washer"], indirect=True) @pytest.mark.parametrize("appliance", ["Washer"], indirect=True)

View File

@ -154,9 +154,19 @@ async def test_paired_depaired_devices_flow(
assert entity_registry.async_get(entity_entry.entity_id) assert entity_registry.async_get(entity_entry.entity_id)
@pytest.mark.parametrize("appliance", ["Washer"], indirect=True) @pytest.mark.parametrize(
("appliance", "keys_to_check"),
[
(
"Washer",
(StatusKey.BSH_COMMON_OPERATION_STATE,),
)
],
indirect=["appliance"],
)
async def test_connected_devices( async def test_connected_devices(
appliance: HomeAppliance, appliance: HomeAppliance,
keys_to_check: tuple,
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
integration_setup: Callable[[MagicMock], Awaitable[bool]], integration_setup: Callable[[MagicMock], Awaitable[bool]],
@ -187,7 +197,12 @@ async def test_connected_devices(
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)})
assert device assert device
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) for key in keys_to_check:
assert not entity_registry.async_get_entity_id(
Platform.SENSOR,
DOMAIN,
f"{appliance.ha_id}-{key}",
)
await client.add_events( await client.add_events(
[ [
@ -200,10 +215,12 @@ async def test_connected_devices(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) for key in keys_to_check:
assert device assert entity_registry.async_get_entity_id(
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) Platform.SENSOR,
assert len(new_entity_entries) > len(entity_entries) DOMAIN,
f"{appliance.ha_id}-{key}",
)
@pytest.mark.parametrize("appliance", [TEST_HC_APP], indirect=True) @pytest.mark.parametrize("appliance", [TEST_HC_APP], indirect=True)

View File

@ -147,9 +147,23 @@ async def test_paired_depaired_devices_flow(
assert entity_registry.async_get(entity_entry.entity_id) assert entity_registry.async_get(entity_entry.entity_id)
@pytest.mark.parametrize("appliance", ["Washer"], indirect=True) @pytest.mark.parametrize(
("appliance", "keys_to_check"),
[
(
"Washer",
(
SettingKey.BSH_COMMON_POWER_STATE,
SettingKey.BSH_COMMON_CHILD_LOCK,
"Program Cotton",
),
)
],
indirect=["appliance"],
)
async def test_connected_devices( async def test_connected_devices(
appliance: HomeAppliance, appliance: HomeAppliance,
keys_to_check: tuple,
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
integration_setup: Callable[[MagicMock], Awaitable[bool]], integration_setup: Callable[[MagicMock], Awaitable[bool]],
@ -164,7 +178,7 @@ async def test_connected_devices(
not be obtained while disconnected and once connected, the entities are added. not be obtained while disconnected and once connected, the entities are added.
""" """
get_settings_original_mock = client.get_settings get_settings_original_mock = client.get_settings
get_available_programs_mock = client.get_available_programs get_all_programs_mock = client.get_all_programs
async def get_settings_side_effect(ha_id: str): async def get_settings_side_effect(ha_id: str):
if ha_id == appliance.ha_id: if ha_id == appliance.ha_id:
@ -173,26 +187,29 @@ async def test_connected_devices(
) )
return await get_settings_original_mock.side_effect(ha_id) return await get_settings_original_mock.side_effect(ha_id)
async def get_available_programs_side_effect(ha_id: str): async def get_all_programs_side_effect(ha_id: str):
if ha_id == appliance.ha_id: if ha_id == appliance.ha_id:
raise HomeConnectApiError( raise HomeConnectApiError(
"SDK.Error.HomeAppliance.Connection.Initialization.Failed" "SDK.Error.HomeAppliance.Connection.Initialization.Failed"
) )
return await get_available_programs_mock.side_effect(ha_id) return await get_all_programs_mock.side_effect(ha_id)
client.get_settings = AsyncMock(side_effect=get_settings_side_effect) client.get_settings = AsyncMock(side_effect=get_settings_side_effect)
client.get_available_programs = AsyncMock( client.get_all_programs = AsyncMock(side_effect=get_all_programs_side_effect)
side_effect=get_available_programs_side_effect
)
assert config_entry.state == ConfigEntryState.NOT_LOADED assert config_entry.state == ConfigEntryState.NOT_LOADED
assert await integration_setup(client) assert await integration_setup(client)
assert config_entry.state == ConfigEntryState.LOADED assert config_entry.state == ConfigEntryState.LOADED
client.get_settings = get_settings_original_mock client.get_settings = get_settings_original_mock
client.get_available_programs = get_available_programs_mock client.get_all_programs = get_all_programs_mock
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)})
assert device assert device
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) for key in keys_to_check:
assert not entity_registry.async_get_entity_id(
Platform.SWITCH,
DOMAIN,
f"{appliance.ha_id}-{key}",
)
await client.add_events( await client.add_events(
[ [
@ -205,10 +222,12 @@ async def test_connected_devices(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) for key in keys_to_check:
assert device assert entity_registry.async_get_entity_id(
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) Platform.SWITCH,
assert len(new_entity_entries) > len(entity_entries) DOMAIN,
f"{appliance.ha_id}-{key}",
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")

View File

@ -113,9 +113,19 @@ async def test_paired_depaired_devices_flow(
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
@pytest.mark.parametrize("appliance", ["Oven"], indirect=True) @pytest.mark.parametrize(
("appliance", "keys_to_check"),
[
(
"Oven",
(SettingKey.BSH_COMMON_ALARM_CLOCK,),
)
],
indirect=["appliance"],
)
async def test_connected_devices( async def test_connected_devices(
appliance: HomeAppliance, appliance: HomeAppliance,
keys_to_check: tuple,
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
integration_setup: Callable[[MagicMock], Awaitable[bool]], integration_setup: Callable[[MagicMock], Awaitable[bool]],
@ -146,7 +156,12 @@ async def test_connected_devices(
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)})
assert device assert device
entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) for key in keys_to_check:
assert not entity_registry.async_get_entity_id(
Platform.TIME,
DOMAIN,
f"{appliance.ha_id}-{key}",
)
await client.add_events( await client.add_events(
[ [
@ -159,10 +174,12 @@ async def test_connected_devices(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
device = device_registry.async_get_device(identifiers={(DOMAIN, appliance.ha_id)}) for key in keys_to_check:
assert device assert entity_registry.async_get_entity_id(
new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) Platform.TIME,
assert len(new_entity_entries) > len(entity_entries) DOMAIN,
f"{appliance.ha_id}-{key}",
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")

View File

@ -5,7 +5,7 @@ from datetime import UTC, datetime
from aiovodafone import VodafoneStationDevice from aiovodafone import VodafoneStationDevice
import pytest import pytest
from homeassistant.components.vodafone_station import DOMAIN from homeassistant.components.vodafone_station.const import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from .const import DEVICE_1_HOST, DEVICE_1_MAC, DEVICE_2_MAC from .const import DEVICE_1_HOST, DEVICE_1_MAC, DEVICE_2_MAC

View File

@ -3,6 +3,8 @@
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
from homeassistant.components.device_tracker import CONF_CONSIDER_HOME from homeassistant.components.device_tracker import CONF_CONSIDER_HOME
from homeassistant.components.vodafone_station.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
@ -31,3 +33,21 @@ async def test_reload_config_entry_with_options(
assert result["data"] == { assert result["data"] == {
CONF_CONSIDER_HOME: 37, CONF_CONSIDER_HOME: 37,
} }
async def test_unload_entry(
hass: HomeAssistant,
mock_vodafone_station_router: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test unloading the config entry."""
await setup_integration(hass, mock_config_entry)
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
assert mock_config_entry.state is ConfigEntryState.LOADED
assert await hass.config_entries.async_unload(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
assert not hass.data.get(DOMAIN)