Skip to content

Commit b637129

Browse files
Migrate from homeconnect dependency to aiohomeconnect (#136116)
* Migrate from homeconnect dependency to aiohomeconnect * Reload the integration if there is an API error on event stream * fix typos at coordinator tests * Setup config entry at coordinator tests * fix ruff * Bump aiohomeconnect to version 0.11.4 * Fix set program options * Use context based updates at coordinator * Improved how `context_callbacks` cache is invalidated * fix * fixes and improvements at coordinator Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Remove stale Entity inheritance * Small improvement for light subscriptions * Remove non-needed function It had its purpose before some refactoring before the firs commit, no is no needed as is only used at HomeConnectEntity constructor * Static methods and variables at conftest * Refresh the data after an event stream interruption * Cleaned debug logs * Fetch programs at coordinator * Improvements Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Simplify obtaining power settings from coordinator data Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Remove unnecessary statement * use `is UNDEFINED` instead of `isinstance` * Request power setting only when it is strictly necessary * Bump aiohomeconnect to 0.12.1 * use raw keys for diagnostics * Use keyword arguments where needed * Remove unnecessary statements Co-authored-by: Martin Hjelmare <marhje52@gmail.com> --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
1 parent 4e3e1e9 commit b637129

33 files changed

+3050
-2574
lines changed

homeassistant/components/home_connect/__init__.py

Lines changed: 147 additions & 186 deletions
Large diffs are not rendered by default.
Lines changed: 11 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,28 @@
11
"""API for Home Connect bound to HASS OAuth."""
22

3-
from asyncio import run_coroutine_threadsafe
4-
import logging
3+
from aiohomeconnect.client import AbstractAuth
4+
from aiohomeconnect.const import API_ENDPOINT
55

6-
import homeconnect
7-
from homeconnect.api import HomeConnectAppliance, HomeConnectError
8-
9-
from homeassistant.config_entries import ConfigEntry
106
from homeassistant.core import HomeAssistant
117
from homeassistant.helpers import config_entry_oauth2_flow
12-
from homeassistant.helpers.dispatcher import dispatcher_send
13-
14-
from .const import ATTR_KEY, ATTR_VALUE, BSH_ACTIVE_PROGRAM, SIGNAL_UPDATE_ENTITIES
15-
16-
_LOGGER = logging.getLogger(__name__)
8+
from homeassistant.helpers.httpx_client import get_async_client
179

1810

19-
class ConfigEntryAuth(homeconnect.HomeConnectAPI):
11+
class AsyncConfigEntryAuth(AbstractAuth):
2012
"""Provide Home Connect authentication tied to an OAuth2 based config entry."""
2113

2214
def __init__(
2315
self,
2416
hass: HomeAssistant,
25-
config_entry: ConfigEntry,
26-
implementation: config_entry_oauth2_flow.AbstractOAuth2Implementation,
17+
oauth_session: config_entry_oauth2_flow.OAuth2Session,
2718
) -> None:
2819
"""Initialize Home Connect Auth."""
2920
self.hass = hass
30-
self.config_entry = config_entry
31-
self.session = config_entry_oauth2_flow.OAuth2Session(
32-
hass, config_entry, implementation
33-
)
34-
super().__init__(self.session.token)
35-
self.devices: list[HomeConnectDevice] = []
36-
37-
def refresh_tokens(self) -> dict:
38-
"""Refresh and return new Home Connect tokens using Home Assistant OAuth2 session."""
39-
run_coroutine_threadsafe(
40-
self.session.async_ensure_token_valid(), self.hass.loop
41-
).result()
42-
43-
return self.session.token
44-
45-
def get_devices(self) -> list[HomeConnectAppliance]:
46-
"""Get a dictionary of devices."""
47-
appl: list[HomeConnectAppliance] = self.get_appliances()
48-
self.devices = [HomeConnectDevice(self.hass, app) for app in appl]
49-
return self.devices
50-
51-
52-
class HomeConnectDevice:
53-
"""Generic Home Connect device."""
54-
55-
def __init__(self, hass: HomeAssistant, appliance: HomeConnectAppliance) -> None:
56-
"""Initialize the device class."""
57-
self.hass = hass
58-
self.appliance = appliance
21+
super().__init__(get_async_client(hass), host=API_ENDPOINT)
22+
self.session = oauth_session
5923

60-
def initialize(self) -> None:
61-
"""Fetch the info needed to initialize the device."""
62-
try:
63-
self.appliance.get_status()
64-
except (HomeConnectError, ValueError):
65-
_LOGGER.debug("Unable to fetch appliance status. Probably offline")
66-
try:
67-
self.appliance.get_settings()
68-
except (HomeConnectError, ValueError):
69-
_LOGGER.debug("Unable to fetch settings. Probably offline")
70-
try:
71-
program_active = self.appliance.get_programs_active()
72-
except (HomeConnectError, ValueError):
73-
_LOGGER.debug("Unable to fetch active programs. Probably offline")
74-
program_active = None
75-
if program_active and ATTR_KEY in program_active:
76-
self.appliance.status[BSH_ACTIVE_PROGRAM] = {
77-
ATTR_VALUE: program_active[ATTR_KEY]
78-
}
79-
self.appliance.listen_events(callback=self.event_callback)
24+
async def async_get_access_token(self) -> str:
25+
"""Return a valid access token."""
26+
await self.session.async_ensure_token_valid()
8027

81-
def event_callback(self, appliance: HomeConnectAppliance) -> None:
82-
"""Handle event."""
83-
_LOGGER.debug("Update triggered on %s", appliance.name)
84-
_LOGGER.debug(self.appliance.status)
85-
dispatcher_send(self.hass, SIGNAL_UPDATE_ENTITIES, appliance.haId)
28+
return self.session.token["access_token"]

homeassistant/components/home_connect/application_credentials.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""Application credentials platform for Home Connect."""
22

3+
from aiohomeconnect.const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN
4+
35
from homeassistant.components.application_credentials import AuthorizationServer
46
from homeassistant.core import HomeAssistant
57

6-
from .const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN
7-
88

99
async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer:
1010
"""Return authorization server."""

homeassistant/components/home_connect/binary_sensor.py

Lines changed: 44 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""Provides a binary sensor for Home Connect."""
22

33
from dataclasses import dataclass
4-
import logging
4+
from typing import cast
5+
6+
from aiohomeconnect.model import StatusKey
57

68
from homeassistant.components.automation import automations_with_entity
79
from homeassistant.components.binary_sensor import (
@@ -19,26 +21,21 @@
1921
async_delete_issue,
2022
)
2123

22-
from . import HomeConnectConfigEntry
23-
from .api import HomeConnectDevice
2424
from .const import (
25-
ATTR_VALUE,
26-
BSH_DOOR_STATE,
2725
BSH_DOOR_STATE_CLOSED,
2826
BSH_DOOR_STATE_LOCKED,
2927
BSH_DOOR_STATE_OPEN,
30-
BSH_REMOTE_CONTROL_ACTIVATION_STATE,
31-
BSH_REMOTE_START_ALLOWANCE_STATE,
3228
DOMAIN,
33-
REFRIGERATION_STATUS_DOOR_CHILLER,
3429
REFRIGERATION_STATUS_DOOR_CLOSED,
35-
REFRIGERATION_STATUS_DOOR_FREEZER,
3630
REFRIGERATION_STATUS_DOOR_OPEN,
37-
REFRIGERATION_STATUS_DOOR_REFRIGERATOR,
31+
)
32+
from .coordinator import (
33+
HomeConnectApplianceData,
34+
HomeConnectConfigEntry,
35+
HomeConnectCoordinator,
3836
)
3937
from .entity import HomeConnectEntity
4038

41-
_LOGGER = logging.getLogger(__name__)
4239
REFRIGERATION_DOOR_BOOLEAN_MAP = {
4340
REFRIGERATION_STATUS_DOOR_CLOSED: False,
4441
REFRIGERATION_STATUS_DOOR_OPEN: True,
@@ -54,19 +51,19 @@ class HomeConnectBinarySensorEntityDescription(BinarySensorEntityDescription):
5451

5552
BINARY_SENSORS = (
5653
HomeConnectBinarySensorEntityDescription(
57-
key=BSH_REMOTE_CONTROL_ACTIVATION_STATE,
54+
key=StatusKey.BSH_COMMON_REMOTE_CONTROL_ACTIVE,
5855
translation_key="remote_control",
5956
),
6057
HomeConnectBinarySensorEntityDescription(
61-
key=BSH_REMOTE_START_ALLOWANCE_STATE,
58+
key=StatusKey.BSH_COMMON_REMOTE_CONTROL_START_ALLOWED,
6259
translation_key="remote_start",
6360
),
6461
HomeConnectBinarySensorEntityDescription(
65-
key="BSH.Common.Status.LocalControlActive",
62+
key=StatusKey.BSH_COMMON_LOCAL_CONTROL_ACTIVE,
6663
translation_key="local_control",
6764
),
6865
HomeConnectBinarySensorEntityDescription(
69-
key="BSH.Common.Status.BatteryChargingState",
66+
key=StatusKey.BSH_COMMON_BATTERY_CHARGING_STATE,
7067
device_class=BinarySensorDeviceClass.BATTERY_CHARGING,
7168
boolean_map={
7269
"BSH.Common.EnumType.BatteryChargingState.Charging": True,
@@ -75,7 +72,7 @@ class HomeConnectBinarySensorEntityDescription(BinarySensorEntityDescription):
7572
translation_key="battery_charging_state",
7673
),
7774
HomeConnectBinarySensorEntityDescription(
78-
key="BSH.Common.Status.ChargingConnection",
75+
key=StatusKey.BSH_COMMON_CHARGING_CONNECTION,
7976
device_class=BinarySensorDeviceClass.PLUG,
8077
boolean_map={
8178
"BSH.Common.EnumType.ChargingConnection.Connected": True,
@@ -84,31 +81,31 @@ class HomeConnectBinarySensorEntityDescription(BinarySensorEntityDescription):
8481
translation_key="charging_connection",
8582
),
8683
HomeConnectBinarySensorEntityDescription(
87-
key="ConsumerProducts.CleaningRobot.Status.DustBoxInserted",
84+
key=StatusKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_DUST_BOX_INSERTED,
8885
translation_key="dust_box_inserted",
8986
),
9087
HomeConnectBinarySensorEntityDescription(
91-
key="ConsumerProducts.CleaningRobot.Status.Lifted",
88+
key=StatusKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_LIFTED,
9289
translation_key="lifted",
9390
),
9491
HomeConnectBinarySensorEntityDescription(
95-
key="ConsumerProducts.CleaningRobot.Status.Lost",
92+
key=StatusKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_LOST,
9693
translation_key="lost",
9794
),
9895
HomeConnectBinarySensorEntityDescription(
99-
key=REFRIGERATION_STATUS_DOOR_CHILLER,
96+
key=StatusKey.REFRIGERATION_COMMON_DOOR_CHILLER_COMMON,
10097
boolean_map=REFRIGERATION_DOOR_BOOLEAN_MAP,
10198
device_class=BinarySensorDeviceClass.DOOR,
10299
translation_key="chiller_door",
103100
),
104101
HomeConnectBinarySensorEntityDescription(
105-
key=REFRIGERATION_STATUS_DOOR_FREEZER,
102+
key=StatusKey.REFRIGERATION_COMMON_DOOR_FREEZER,
106103
boolean_map=REFRIGERATION_DOOR_BOOLEAN_MAP,
107104
device_class=BinarySensorDeviceClass.DOOR,
108105
translation_key="freezer_door",
109106
),
110107
HomeConnectBinarySensorEntityDescription(
111-
key=REFRIGERATION_STATUS_DOOR_REFRIGERATOR,
108+
key=StatusKey.REFRIGERATION_COMMON_DOOR_REFRIGERATOR,
112109
boolean_map=REFRIGERATION_DOOR_BOOLEAN_MAP,
113110
device_class=BinarySensorDeviceClass.DOOR,
114111
translation_key="refrigerator_door",
@@ -123,45 +120,33 @@ async def async_setup_entry(
123120
) -> None:
124121
"""Set up the Home Connect binary sensor."""
125122

126-
def get_entities() -> list[BinarySensorEntity]:
127-
entities: list[BinarySensorEntity] = []
128-
for device in entry.runtime_data.devices:
129-
entities.extend(
130-
HomeConnectBinarySensor(device, description)
131-
for description in BINARY_SENSORS
132-
if description.key in device.appliance.status
133-
)
134-
if BSH_DOOR_STATE in device.appliance.status:
135-
entities.append(HomeConnectDoorBinarySensor(device))
136-
return entities
123+
entities: list[BinarySensorEntity] = []
124+
for appliance in entry.runtime_data.data.values():
125+
entities.extend(
126+
HomeConnectBinarySensor(entry.runtime_data, appliance, description)
127+
for description in BINARY_SENSORS
128+
if description.key in appliance.status
129+
)
130+
if StatusKey.BSH_COMMON_DOOR_STATE in appliance.status:
131+
entities.append(HomeConnectDoorBinarySensor(entry.runtime_data, appliance))
137132

138-
async_add_entities(await hass.async_add_executor_job(get_entities), True)
133+
async_add_entities(entities)
139134

140135

141136
class HomeConnectBinarySensor(HomeConnectEntity, BinarySensorEntity):
142137
"""Binary sensor for Home Connect."""
143138

144139
entity_description: HomeConnectBinarySensorEntityDescription
145140

146-
@property
147-
def available(self) -> bool:
148-
"""Return true if the binary sensor is available."""
149-
return self._attr_is_on is not None
150-
151-
async def async_update(self) -> None:
152-
"""Update the binary sensor's status."""
153-
if not self.device.appliance.status or not (
154-
status := self.device.appliance.status.get(self.bsh_key, {}).get(ATTR_VALUE)
155-
):
156-
self._attr_is_on = None
157-
return
158-
if self.entity_description.boolean_map:
141+
def update_native_value(self) -> None:
142+
"""Set the native value of the binary sensor."""
143+
status = self.appliance.status[cast(StatusKey, self.bsh_key)].value
144+
if isinstance(status, bool):
145+
self._attr_is_on = status
146+
elif self.entity_description.boolean_map:
159147
self._attr_is_on = self.entity_description.boolean_map.get(status)
160-
elif status not in [True, False]:
161-
self._attr_is_on = None
162148
else:
163-
self._attr_is_on = status
164-
_LOGGER.debug("Updated, new state: %s", self._attr_is_on)
149+
self._attr_is_on = None
165150

166151

167152
class HomeConnectDoorBinarySensor(HomeConnectBinarySensor):
@@ -171,13 +156,15 @@ class HomeConnectDoorBinarySensor(HomeConnectBinarySensor):
171156

172157
def __init__(
173158
self,
174-
device: HomeConnectDevice,
159+
coordinator: HomeConnectCoordinator,
160+
appliance: HomeConnectApplianceData,
175161
) -> None:
176162
"""Initialize the entity."""
177163
super().__init__(
178-
device,
164+
coordinator,
165+
appliance,
179166
HomeConnectBinarySensorEntityDescription(
180-
key=BSH_DOOR_STATE,
167+
key=StatusKey.BSH_COMMON_DOOR_STATE,
181168
device_class=BinarySensorDeviceClass.DOOR,
182169
boolean_map={
183170
BSH_DOOR_STATE_CLOSED: False,
@@ -186,8 +173,8 @@ def __init__(
186173
},
187174
),
188175
)
189-
self._attr_unique_id = f"{device.appliance.haId}-Door"
190-
self._attr_name = f"{device.appliance.name} Door"
176+
self._attr_unique_id = f"{appliance.info.ha_id}-Door"
177+
self._attr_name = f"{appliance.info.name} Door"
191178

192179
async def async_added_to_hass(self) -> None:
193180
"""Call when entity is added to hass."""
@@ -234,6 +221,7 @@ async def async_added_to_hass(self) -> None:
234221

235222
async def async_will_remove_from_hass(self) -> None:
236223
"""Call when entity will be removed from hass."""
224+
await super().async_will_remove_from_hass()
237225
async_delete_issue(
238226
self.hass, DOMAIN, f"deprecated_binary_common_door_sensor_{self.entity_id}"
239227
)

0 commit comments

Comments
 (0)