Fetch current active and selected programs at Home Connect (#136948)

* Fetch current active and selected programs

* Intialize HomeConnectEntity first at SelectProgramEntity

* Use the right exception

* Use active/selected program from `get_all_programs`

This will allow us to reduce the number of requests that we need to perform to get all the data ready (only one requests vs. three requests)

* Remove no longer required mocks

* Fix
pull/136549/head^2
J. Diego Rodríguez Royo 2025-02-02 00:12:26 +01:00 committed by GitHub
parent bf6f790d09
commit 147b5f549f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 56 additions and 12 deletions

View File

@ -257,14 +257,9 @@ class HomeConnectCoordinator(
appliance_data = appliances_data[appliance.ha_id] appliance_data = appliances_data[appliance.ha_id]
else: else:
appliances_data[appliance.ha_id] = appliance_data appliances_data[appliance.ha_id] = appliance_data
if ( if appliance.type in APPLIANCES_WITH_PROGRAMS:
appliance.type in APPLIANCES_WITH_PROGRAMS
and not appliance_data.programs
):
try: try:
appliance_data.programs.extend( all_programs = await self.client.get_all_programs(appliance.ha_id)
(await self.client.get_all_programs(appliance.ha_id)).programs
)
except HomeConnectError as error: except HomeConnectError as error:
_LOGGER.debug( _LOGGER.debug(
"Error fetching programs for %s: %s", "Error fetching programs for %s: %s",
@ -273,4 +268,30 @@ class HomeConnectCoordinator(
if isinstance(error, HomeConnectApiError) if isinstance(error, HomeConnectApiError)
else type(error).__name__, 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,
)
}
)
return appliances_data return appliances_data

View File

@ -110,7 +110,6 @@ class HomeConnectProgramSelectEntity(HomeConnectEntity, SelectEntity):
or program.constraints.execution in desc.allowed_executions or program.constraints.execution in desc.allowed_executions
) )
] ]
self._attr_current_option = None
def update_native_value(self) -> None: def update_native_value(self) -> None:
"""Set the program value.""" """Set the program value."""

View File

@ -192,6 +192,7 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
desc = " ".join( desc = " ".join(
["Program", program.key.split(".")[-3], program.key.split(".")[-1]] ["Program", program.key.split(".")[-3], program.key.split(".")[-1]]
) )
self.program = program
super().__init__( super().__init__(
coordinator, coordinator,
appliance, appliance,
@ -200,7 +201,6 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
self._attr_name = f"{appliance.info.name} {desc}" self._attr_name = f"{appliance.info.name} {desc}"
self._attr_unique_id = f"{appliance.info.ha_id}-{desc}" self._attr_unique_id = f"{appliance.info.ha_id}-{desc}"
self._attr_has_entity_name = False self._attr_has_entity_name = False
self.program = program
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Call when entity is added to hass.""" """Call when entity is added to hass."""

View File

@ -19,8 +19,10 @@ from aiohomeconnect.model import (
EventMessage, EventMessage,
EventType, EventType,
Option, Option,
Program,
) )
from aiohomeconnect.model.error import HomeConnectApiError, HomeConnectError from aiohomeconnect.model.error import HomeConnectApiError, HomeConnectError
from aiohomeconnect.model.program import EnumerateProgram
import pytest import pytest
from homeassistant.components.application_credentials import ( from homeassistant.components.application_credentials import (
@ -227,7 +229,14 @@ async def _get_all_programs_side_effect(ha_id: str) -> ArrayOfPrograms:
if appliance_type not in MOCK_PROGRAMS: if appliance_type not in MOCK_PROGRAMS:
raise HomeConnectApiError("error.key", "error description") raise HomeConnectApiError("error.key", "error description")
return ArrayOfPrograms.from_dict(MOCK_PROGRAMS[appliance_type]["data"]) return ArrayOfPrograms(
[
EnumerateProgram.from_dict(program)
for program in MOCK_PROGRAMS[appliance_type]["data"]["programs"]
],
Program.from_dict(MOCK_PROGRAMS[appliance_type]["data"]["programs"][0]),
Program.from_dict(MOCK_PROGRAMS[appliance_type]["data"]["programs"][0]),
)
async def _get_settings_side_effect(ha_id: str) -> ArrayOfSettings: async def _get_settings_side_effect(ha_id: str) -> ArrayOfSettings:

View File

@ -174,6 +174,7 @@ async def test_filter_programs(
( (
"appliance_ha_id", "appliance_ha_id",
"entity_id", "entity_id",
"expected_initial_state",
"mock_method", "mock_method",
"program_key", "program_key",
"program_to_set", "program_to_set",
@ -183,6 +184,7 @@ async def test_filter_programs(
( (
"Dishwasher", "Dishwasher",
"select.dishwasher_selected_program", "select.dishwasher_selected_program",
"dishcare_dishwasher_program_auto_1",
"set_selected_program", "set_selected_program",
ProgramKey.DISHCARE_DISHWASHER_ECO_50, ProgramKey.DISHCARE_DISHWASHER_ECO_50,
"dishcare_dishwasher_program_eco_50", "dishcare_dishwasher_program_eco_50",
@ -191,6 +193,7 @@ async def test_filter_programs(
( (
"Dishwasher", "Dishwasher",
"select.dishwasher_active_program", "select.dishwasher_active_program",
"dishcare_dishwasher_program_auto_1",
"start_program", "start_program",
ProgramKey.DISHCARE_DISHWASHER_ECO_50, ProgramKey.DISHCARE_DISHWASHER_ECO_50,
"dishcare_dishwasher_program_eco_50", "dishcare_dishwasher_program_eco_50",
@ -202,6 +205,7 @@ async def test_filter_programs(
async def test_select_program_functionality( async def test_select_program_functionality(
appliance_ha_id: str, appliance_ha_id: str,
entity_id: str, entity_id: str,
expected_initial_state: str,
mock_method: str, mock_method: str,
program_key: ProgramKey, program_key: ProgramKey,
program_to_set: str, program_to_set: str,
@ -217,7 +221,7 @@ async def test_select_program_functionality(
assert await integration_setup(client) assert await integration_setup(client)
assert config_entry.state is ConfigEntryState.LOADED assert config_entry.state is ConfigEntryState.LOADED
assert hass.states.is_state(entity_id, "unknown") assert hass.states.is_state(entity_id, expected_initial_state)
await hass.services.async_call( await hass.services.async_call(
SELECT_DOMAIN, SELECT_DOMAIN,
SERVICE_SELECT_OPTION, SERVICE_SELECT_OPTION,
@ -301,6 +305,8 @@ async def test_select_exception_handling(
assert await integration_setup(client_with_exception) assert await integration_setup(client_with_exception)
assert config_entry.state is ConfigEntryState.LOADED assert config_entry.state is ConfigEntryState.LOADED
assert hass.states.is_state(entity_id, STATE_UNKNOWN)
# Assert that an exception is called. # Assert that an exception is called.
with pytest.raises(HomeConnectError): with pytest.raises(HomeConnectError):
await getattr(client_with_exception, mock_attr)() await getattr(client_with_exception, mock_attr)()

View File

@ -178,11 +178,18 @@ async def test_switch_functionality(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("entity_id", "program_key", "appliance_ha_id"), ("entity_id", "program_key", "initial_state", "appliance_ha_id"),
[ [
( (
"switch.dryer_program_mix", "switch.dryer_program_mix",
ProgramKey.LAUNDRY_CARE_DRYER_MIX, ProgramKey.LAUNDRY_CARE_DRYER_MIX,
STATE_OFF,
"Dryer",
),
(
"switch.dryer_program_cotton",
ProgramKey.LAUNDRY_CARE_DRYER_COTTON,
STATE_ON,
"Dryer", "Dryer",
), ),
], ],
@ -191,6 +198,7 @@ async def test_switch_functionality(
async def test_program_switch_functionality( async def test_program_switch_functionality(
entity_id: str, entity_id: str,
program_key: ProgramKey, program_key: ProgramKey,
initial_state: str,
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
integration_setup: Callable[[MagicMock], Awaitable[bool]], integration_setup: Callable[[MagicMock], Awaitable[bool]],
@ -227,6 +235,7 @@ async def test_program_switch_functionality(
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
assert hass.states.is_state(entity_id, initial_state)
await hass.services.async_call( await hass.services.async_call(
SWITCH_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id} SWITCH_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}