Use BSH keys as unique ID's suffix at Home Connect (#126143)

* Use BSH keys as as unique id suffix instead of the simple description

* Update tests/components/home_connect/test_init.py

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
J. Diego Rodríguez Royo 2024-10-06 12:40:13 +02:00 committed by GitHub
parent 3e8bc98f23
commit 0d795aad16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 283 additions and 92 deletions

View File

@ -4,18 +4,20 @@ from __future__ import annotations
from datetime import timedelta
import logging
from typing import Any
from requests import HTTPError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_DEVICE_ID, CONF_DEVICE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import (
config_entry_oauth2_flow,
config_validation as cv,
device_registry as dr,
)
from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import Throttle
@ -28,6 +30,7 @@ from .const import (
BSH_PAUSE,
BSH_RESUME,
DOMAIN,
OLD_NEW_UNIQUE_ID_SUFFIX_MAP,
SERVICE_OPTION_ACTIVE,
SERVICE_OPTION_SELECTED,
SERVICE_PAUSE_PROGRAM,
@ -268,3 +271,31 @@ async def update_all_devices(hass: HomeAssistant, entry: ConfigEntry) -> None:
await hass.async_add_executor_job(device.initialize)
except HTTPError as err:
_LOGGER.warning("Cannot update devices: %s", err.response.status_code)
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version)
if config_entry.version == 1 and config_entry.minor_version == 1:
@callback
def update_unique_id(
entity_entry: RegistryEntry,
) -> dict[str, Any] | None:
"""Update unique ID of entity entry."""
for old_id_suffix, new_id_suffix in OLD_NEW_UNIQUE_ID_SUFFIX_MAP.items():
if entity_entry.unique_id.endswith(f"-{old_id_suffix}"):
return {
"new_unique_id": entity_entry.unique_id.replace(
old_id_suffix, new_id_suffix
)
}
return None
await async_migrate_entries(hass, config_entry.entry_id, update_unique_id)
hass.config_entries.async_update_entry(config_entry, minor_version=2)
_LOGGER.debug("Migration to version %s successful", config_entry.version)
return True

View File

@ -24,6 +24,7 @@ from homeassistant.helpers.dispatcher import dispatcher_send
from .const import (
ATTR_AMBIENT,
ATTR_BSH_KEY,
ATTR_DESC,
ATTR_DEVICE,
ATTR_KEY,
@ -32,9 +33,16 @@ from .const import (
ATTR_UNIT,
ATTR_VALUE,
BSH_ACTIVE_PROGRAM,
BSH_AMBIENT_LIGHT_ENABLED,
BSH_COMMON_OPTION_DURATION,
BSH_COMMON_OPTION_PROGRAM_PROGRESS,
BSH_OPERATION_STATE,
BSH_POWER_OFF,
BSH_POWER_STANDBY,
BSH_REMAINING_PROGRAM_TIME,
BSH_REMOTE_CONTROL_ACTIVATION_STATE,
BSH_REMOTE_START_ALLOWANCE_STATE,
COOKING_LIGHTING,
SIGNAL_UPDATE_ENTITIES,
)
@ -181,21 +189,39 @@ class DeviceWithPrograms(HomeConnectDevice):
device.
"""
sensors = {
"Remaining Program Time": (None, None, SensorDeviceClass.TIMESTAMP, 1),
"Duration": (UnitOfTime.SECONDS, "mdi:update", None, 1),
"Program Progress": (PERCENTAGE, "mdi:progress-clock", None, 1),
BSH_REMAINING_PROGRAM_TIME: (
"Remaining Program Time",
None,
None,
SensorDeviceClass.TIMESTAMP,
1,
),
BSH_COMMON_OPTION_DURATION: (
"Duration",
UnitOfTime.SECONDS,
"mdi:update",
None,
1,
),
BSH_COMMON_OPTION_PROGRAM_PROGRESS: (
"Program Progress",
PERCENTAGE,
"mdi:progress-clock",
None,
1,
),
}
return [
{
ATTR_DEVICE: self,
ATTR_DESC: k,
ATTR_BSH_KEY: k,
ATTR_DESC: desc,
ATTR_UNIT: unit,
ATTR_KEY: f"BSH.Common.Option.{k.replace(' ', '')}",
ATTR_ICON: icon,
ATTR_DEVICE_CLASS: device_class,
ATTR_SIGN: sign,
}
for k, (unit, icon, device_class, sign) in sensors.items()
for k, (desc, unit, icon, device_class, sign) in sensors.items()
]
@ -208,9 +234,9 @@ class DeviceWithOpState(HomeConnectDevice):
return [
{
ATTR_DEVICE: self,
ATTR_BSH_KEY: BSH_OPERATION_STATE,
ATTR_DESC: "Operation State",
ATTR_UNIT: None,
ATTR_KEY: BSH_OPERATION_STATE,
ATTR_ICON: "mdi:state-machine",
ATTR_DEVICE_CLASS: None,
ATTR_SIGN: 1,
@ -225,6 +251,7 @@ class DeviceWithDoor(HomeConnectDevice):
"""Get a dictionary with info about the door binary sensor."""
return {
ATTR_DEVICE: self,
ATTR_BSH_KEY: "Door",
ATTR_DESC: "Door",
ATTR_SENSOR_TYPE: "door",
ATTR_DEVICE_CLASS: "door",
@ -236,7 +263,12 @@ class DeviceWithLight(HomeConnectDevice):
def get_light_entity(self) -> dict[str, Any]:
"""Get a dictionary with info about the lighting."""
return {ATTR_DEVICE: self, ATTR_DESC: "Light", ATTR_AMBIENT: None}
return {
ATTR_DEVICE: self,
ATTR_BSH_KEY: COOKING_LIGHTING,
ATTR_DESC: "Light",
ATTR_AMBIENT: None,
}
class DeviceWithAmbientLight(HomeConnectDevice):
@ -244,7 +276,12 @@ class DeviceWithAmbientLight(HomeConnectDevice):
def get_ambientlight_entity(self) -> dict[str, Any]:
"""Get a dictionary with info about the ambient lighting."""
return {ATTR_DEVICE: self, ATTR_DESC: "AmbientLight", ATTR_AMBIENT: True}
return {
ATTR_DEVICE: self,
ATTR_BSH_KEY: BSH_AMBIENT_LIGHT_ENABLED,
ATTR_DESC: "AmbientLight",
ATTR_AMBIENT: True,
}
class DeviceWithRemoteControl(HomeConnectDevice):
@ -254,6 +291,7 @@ class DeviceWithRemoteControl(HomeConnectDevice):
"""Get a dictionary with info about the remote control sensor."""
return {
ATTR_DEVICE: self,
ATTR_BSH_KEY: BSH_REMOTE_CONTROL_ACTIVATION_STATE,
ATTR_DESC: "Remote Control",
ATTR_SENSOR_TYPE: "remote_control",
}
@ -266,6 +304,7 @@ class DeviceWithRemoteStart(HomeConnectDevice):
"""Get a dictionary with info about the remote start sensor."""
return {
ATTR_DEVICE: self,
ATTR_BSH_KEY: BSH_REMOTE_START_ALLOWANCE_STATE,
ATTR_DESC: "Remote Start",
ATTR_SENSOR_TYPE: "remote_start",
}

View File

@ -39,7 +39,7 @@ _LOGGER = logging.getLogger(__name__)
class HomeConnectBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Entity Description class for binary sensors."""
state_key: str | None
desc: str
device_class: BinarySensorDeviceClass | None = BinarySensorDeviceClass.DOOR
boolean_map: dict[str, bool] = field(
default_factory=lambda: {
@ -51,16 +51,16 @@ class HomeConnectBinarySensorEntityDescription(BinarySensorEntityDescription):
BINARY_SENSORS: tuple[HomeConnectBinarySensorEntityDescription, ...] = (
HomeConnectBinarySensorEntityDescription(
key="Chiller Door",
state_key=REFRIGERATION_STATUS_DOOR_CHILLER,
key=REFRIGERATION_STATUS_DOOR_CHILLER,
desc="Chiller Door",
),
HomeConnectBinarySensorEntityDescription(
key="Freezer Door",
state_key=REFRIGERATION_STATUS_DOOR_FREEZER,
key=REFRIGERATION_STATUS_DOOR_FREEZER,
desc="Freezer Door",
),
HomeConnectBinarySensorEntityDescription(
key="Refrigerator Door",
state_key=REFRIGERATION_STATUS_DOOR_REFRIGERATOR,
key=REFRIGERATION_STATUS_DOOR_REFRIGERATOR,
desc="Refrigerator Door",
),
)
@ -85,7 +85,7 @@ async def async_setup_entry(
device=device, entity_description=description
)
for description in BINARY_SENSORS
if description.state_key in device.appliance.status
if description.key in device.appliance.status
)
return entities
@ -98,12 +98,13 @@ class HomeConnectBinarySensor(HomeConnectEntity, BinarySensorEntity):
def __init__(
self,
device: HomeConnectDevice,
bsh_key: str,
desc: str,
sensor_type: str,
device_class: BinarySensorDeviceClass | None = None,
) -> None:
"""Initialize the entity."""
super().__init__(device, desc)
super().__init__(device, bsh_key, desc)
self._attr_device_class = device_class
self._type = sensor_type
self._false_value_list = None
@ -162,7 +163,7 @@ class HomeConnectFridgeDoorBinarySensor(HomeConnectEntity, BinarySensorEntity):
) -> None:
"""Initialize the entity."""
self.entity_description = entity_description
super().__init__(device, entity_description.key)
super().__init__(device, entity_description.key, entity_description.desc)
async def async_update(self) -> None:
"""Update the binary sensor's status."""
@ -172,9 +173,7 @@ class HomeConnectFridgeDoorBinarySensor(HomeConnectEntity, BinarySensorEntity):
self.state,
)
self._attr_is_on = self.entity_description.boolean_map.get(
self.device.appliance.status.get(self.entity_description.state_key, {}).get(
ATTR_VALUE
)
self.device.appliance.status.get(self.bsh_key, {}).get(ATTR_VALUE)
)
self._attr_available = self._attr_is_on is not None
_LOGGER.debug(

View File

@ -14,6 +14,8 @@ class OAuth2FlowHandler(
DOMAIN = DOMAIN
MINOR_VERSION = 2
@property
def logger(self) -> logging.Logger:
"""Return logger."""

View File

@ -14,6 +14,10 @@ BSH_REMOTE_CONTROL_ACTIVATION_STATE = "BSH.Common.Status.RemoteControlActive"
BSH_REMOTE_START_ALLOWANCE_STATE = "BSH.Common.Status.RemoteControlStartAllowed"
BSH_CHILD_LOCK_STATE = "BSH.Common.Setting.ChildLock"
BSH_REMAINING_PROGRAM_TIME = "BSH.Common.Option.RemainingProgramTime"
BSH_COMMON_OPTION_DURATION = "BSH.Common.Option.Duration"
BSH_COMMON_OPTION_PROGRAM_PROGRESS = "BSH.Common.Option.ProgramProgress"
BSH_EVENT_PRESENT_STATE_PRESENT = "BSH.Common.EnumType.EventPresentState.Present"
BSH_EVENT_PRESENT_STATE_CONFIRMED = "BSH.Common.EnumType.EventPresentState.Confirmed"
BSH_EVENT_PRESENT_STATE_OFF = "BSH.Common.EnumType.EventPresentState.Off"
@ -92,6 +96,7 @@ SERVICE_SETTING = "change_setting"
SERVICE_START_PROGRAM = "start_program"
ATTR_AMBIENT = "ambient"
ATTR_BSH_KEY = "bsh_key"
ATTR_DESC = "desc"
ATTR_DEVICE = "device"
ATTR_KEY = "key"
@ -100,3 +105,30 @@ ATTR_SENSOR_TYPE = "sensor_type"
ATTR_SIGN = "sign"
ATTR_UNIT = "unit"
ATTR_VALUE = "value"
OLD_NEW_UNIQUE_ID_SUFFIX_MAP = {
"ChildLock": BSH_CHILD_LOCK_STATE,
"Operation State": BSH_OPERATION_STATE,
"Light": COOKING_LIGHTING,
"AmbientLight": BSH_AMBIENT_LIGHT_ENABLED,
"Power": BSH_POWER_STATE,
"Remaining Program Time": BSH_REMAINING_PROGRAM_TIME,
"Duration": BSH_COMMON_OPTION_DURATION,
"Program Progress": BSH_COMMON_OPTION_PROGRAM_PROGRESS,
"Remote Control": BSH_REMOTE_CONTROL_ACTIVATION_STATE,
"Remote Start": BSH_REMOTE_START_ALLOWANCE_STATE,
"Supermode Freezer": REFRIGERATION_SUPERMODEFREEZER,
"Supermode Refrigerator": REFRIGERATION_SUPERMODEREFRIGERATOR,
"Dispenser Enabled": REFRIGERATION_DISPENSER,
"Internal Light": REFRIGERATION_INTERNAL_LIGHT_POWER,
"External Light": REFRIGERATION_EXTERNAL_LIGHT_POWER,
"Chiller Door": REFRIGERATION_STATUS_DOOR_CHILLER,
"Freezer Door": REFRIGERATION_STATUS_DOOR_FREEZER,
"Refrigerator Door": REFRIGERATION_STATUS_DOOR_REFRIGERATOR,
"Door Alarm Freezer": REFRIGERATION_EVENT_DOOR_ALARM_FREEZER,
"Door Alarm Refrigerator": REFRIGERATION_EVENT_DOOR_ALARM_REFRIGERATOR,
"Temperature Alarm Freezer": REFRIGERATION_EVENT_TEMP_ALARM_FREEZER,
"Bean Container Empty": COFFEE_EVENT_BEAN_CONTAINER_EMPTY,
"Water Tank Empty": COFFEE_EVENT_WATER_TANK_EMPTY,
"Drip Tray Full": COFFEE_EVENT_DRIP_TRAY_FULL,
}

View File

@ -18,11 +18,12 @@ class HomeConnectEntity(Entity):
_attr_should_poll = False
def __init__(self, device: HomeConnectDevice, desc: str) -> None:
def __init__(self, device: HomeConnectDevice, bsh_key: str, desc: str) -> None:
"""Initialize the entity."""
self.device = device
self.bsh_key = bsh_key
self._attr_name = f"{device.appliance.name} {desc}"
self._attr_unique_id = f"{device.appliance.haId}-{desc}"
self._attr_unique_id = f"{device.appliance.haId}-{bsh_key}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device.appliance.haId)},
manufacturer=device.appliance.brand,

View File

@ -27,8 +27,6 @@ from .const import (
BSH_AMBIENT_LIGHT_COLOR,
BSH_AMBIENT_LIGHT_COLOR_CUSTOM_COLOR,
BSH_AMBIENT_LIGHT_CUSTOM_COLOR,
BSH_AMBIENT_LIGHT_ENABLED,
COOKING_LIGHTING,
COOKING_LIGHTING_BRIGHTNESS,
DOMAIN,
REFRIGERATION_EXTERNAL_LIGHT_BRIGHTNESS,
@ -45,19 +43,19 @@ _LOGGER = logging.getLogger(__name__)
class HomeConnectLightEntityDescription(LightEntityDescription):
"""Light entity description."""
on_key: str
desc: str
brightness_key: str | None
LIGHTS: tuple[HomeConnectLightEntityDescription, ...] = (
HomeConnectLightEntityDescription(
key="Internal Light",
on_key=REFRIGERATION_INTERNAL_LIGHT_POWER,
key=REFRIGERATION_INTERNAL_LIGHT_POWER,
desc="Internal Light",
brightness_key=REFRIGERATION_INTERNAL_LIGHT_BRIGHTNESS,
),
HomeConnectLightEntityDescription(
key="External Light",
on_key=REFRIGERATION_EXTERNAL_LIGHT_POWER,
key=REFRIGERATION_EXTERNAL_LIGHT_POWER,
desc="External Light",
brightness_key=REFRIGERATION_EXTERNAL_LIGHT_BRIGHTNESS,
),
)
@ -86,7 +84,7 @@ async def async_setup_entry(
entity_description=description,
)
for description in LIGHTS
if description.on_key in device.appliance.status
if description.key in device.appliance.status
)
entities.extend(entity_list)
return entities
@ -97,9 +95,11 @@ async def async_setup_entry(
class HomeConnectLight(HomeConnectEntity, LightEntity):
"""Light for Home Connect."""
def __init__(self, device, desc, ambient) -> None:
def __init__(
self, device: HomeConnectDevice, bsh_key: str, desc: str, ambient: bool
) -> None:
"""Initialize the entity."""
super().__init__(device, desc)
super().__init__(device, bsh_key, desc)
self._ambient = ambient
self._percentage_scale = (10, 100)
self._brightness_key: str | None
@ -107,14 +107,12 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
self._color_key: str | None
if ambient:
self._brightness_key = BSH_AMBIENT_LIGHT_BRIGHTNESS
self._key = BSH_AMBIENT_LIGHT_ENABLED
self._custom_color_key = BSH_AMBIENT_LIGHT_CUSTOM_COLOR
self._color_key = BSH_AMBIENT_LIGHT_COLOR
self._attr_color_mode = ColorMode.HS
self._attr_supported_color_modes = {ColorMode.HS}
else:
self._brightness_key = COOKING_LIGHTING_BRIGHTNESS
self._key = COOKING_LIGHTING
self._custom_color_key = None
self._color_key = None
self._attr_color_mode = ColorMode.BRIGHTNESS
@ -126,7 +124,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
_LOGGER.debug("Switching ambient light on for: %s", self.name)
try:
await self.hass.async_add_executor_job(
self.device.appliance.set_setting, self._key, True
self.device.appliance.set_setting, self.bsh_key, True
)
except HomeConnectError as err:
_LOGGER.error("Error while trying to turn on ambient light: %s", err)
@ -189,7 +187,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
_LOGGER.debug("Switching light on for: %s", self.name)
try:
await self.hass.async_add_executor_job(
self.device.appliance.set_setting, self._key, True
self.device.appliance.set_setting, self.bsh_key, True
)
except HomeConnectError as err:
_LOGGER.error("Error while trying to turn on light: %s", err)
@ -201,7 +199,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
_LOGGER.debug("Switching light off for: %s", self.name)
try:
await self.hass.async_add_executor_job(
self.device.appliance.set_setting, self._key, False
self.device.appliance.set_setting, self.bsh_key, False
)
except HomeConnectError as err:
_LOGGER.error("Error while trying to turn off light: %s", err)
@ -209,9 +207,11 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
async def async_update(self) -> None:
"""Update the light's status."""
if self.device.appliance.status.get(self._key, {}).get(ATTR_VALUE) is True:
if self.device.appliance.status.get(self.bsh_key, {}).get(ATTR_VALUE) is True:
self._attr_is_on = True
elif self.device.appliance.status.get(self._key, {}).get(ATTR_VALUE) is False:
elif (
self.device.appliance.status.get(self.bsh_key, {}).get(ATTR_VALUE) is False
):
self._attr_is_on = False
else:
self._attr_is_on = None
@ -255,8 +255,9 @@ class HomeConnectCoolingLight(HomeConnectLight):
entity_description: HomeConnectLightEntityDescription,
) -> None:
"""Initialize Cooling Light Entity."""
super().__init__(device, entity_description.key, ambient)
super().__init__(
device, entity_description.key, entity_description.desc, ambient
)
self.entity_description = entity_description
self._key = entity_description.on_key
self._brightness_key = entity_description.brightness_key
self._percentage_scale = (1, 100)

View File

@ -46,45 +46,39 @@ class HomeConnectSensorEntityDescription(SensorEntityDescription):
options: list[str] | None = field(
default_factory=lambda: ["confirmed", "off", "present"]
)
state_key: str
desc: str
appliance_types: tuple[str, ...]
SENSORS: tuple[HomeConnectSensorEntityDescription, ...] = (
HomeConnectSensorEntityDescription(
key="Door Alarm Freezer",
translation_key="alarm_sensor_freezer",
state_key=REFRIGERATION_EVENT_DOOR_ALARM_FREEZER,
key=REFRIGERATION_EVENT_DOOR_ALARM_FREEZER,
desc="Door Alarm Freezer",
appliance_types=("FridgeFreezer", "Freezer"),
),
HomeConnectSensorEntityDescription(
key="Door Alarm Refrigerator",
translation_key="alarm_sensor_fridge",
state_key=REFRIGERATION_EVENT_DOOR_ALARM_REFRIGERATOR,
key=REFRIGERATION_EVENT_DOOR_ALARM_REFRIGERATOR,
desc="Door Alarm Refrigerator",
appliance_types=("FridgeFreezer", "Refrigerator"),
),
HomeConnectSensorEntityDescription(
key="Temperature Alarm Freezer",
translation_key="alarm_sensor_temp",
state_key=REFRIGERATION_EVENT_TEMP_ALARM_FREEZER,
key=REFRIGERATION_EVENT_TEMP_ALARM_FREEZER,
desc="Temperature Alarm Freezer",
appliance_types=("FridgeFreezer", "Freezer"),
),
HomeConnectSensorEntityDescription(
key="Bean Container Empty",
translation_key="alarm_sensor_coffee_bean_container",
state_key=COFFEE_EVENT_BEAN_CONTAINER_EMPTY,
key=COFFEE_EVENT_BEAN_CONTAINER_EMPTY,
desc="Bean Container Empty",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key="Water Tank Empty",
translation_key="alarm_sensor_coffee_water_tank",
state_key=COFFEE_EVENT_WATER_TANK_EMPTY,
key=COFFEE_EVENT_WATER_TANK_EMPTY,
desc="Water Tank Empty",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key="Drip Tray Full",
translation_key="alarm_sensor_coffee_drip_tray",
state_key=COFFEE_EVENT_DRIP_TRAY_FULL,
key=COFFEE_EVENT_DRIP_TRAY_FULL,
desc="Drip Tray Full",
appliance_types=("CoffeeMaker",),
),
)
@ -128,16 +122,15 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity):
def __init__(
self,
device: HomeConnectDevice,
bsh_key: str,
desc: str,
key: str,
unit: str,
icon: str,
device_class: SensorDeviceClass,
sign: int = 1,
) -> None:
"""Initialize the entity."""
super().__init__(device, desc)
self._key = key
super().__init__(device, bsh_key, desc)
self._sign = sign
self._attr_native_unit_of_measurement = unit
self._attr_icon = icon
@ -151,10 +144,10 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity):
async def async_update(self) -> None:
"""Update the sensor's status."""
status = self.device.appliance.status
if self._key not in status:
if self.bsh_key not in status:
self._attr_native_value = None
elif self.device_class == SensorDeviceClass.TIMESTAMP:
if ATTR_VALUE not in status[self._key]:
if ATTR_VALUE not in status[self.bsh_key]:
self._attr_native_value = None
elif (
self._attr_native_value is not None
@ -175,13 +168,13 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity):
BSH_OPERATION_STATE_FINISHED,
]
):
seconds = self._sign * float(status[self._key][ATTR_VALUE])
seconds = self._sign * float(status[self.bsh_key][ATTR_VALUE])
self._attr_native_value = dt_util.utcnow() + timedelta(seconds=seconds)
else:
self._attr_native_value = None
else:
self._attr_native_value = status[self._key].get(ATTR_VALUE)
if self._key == BSH_OPERATION_STATE:
self._attr_native_value = status[self.bsh_key].get(ATTR_VALUE)
if self.bsh_key == BSH_OPERATION_STATE:
# Value comes back as an enum, we only really care about the
# last part, so split it off
# https://developer.home-connect.com/docs/status/operation_state
@ -203,7 +196,9 @@ class HomeConnectAlarmSensor(HomeConnectEntity, SensorEntity):
) -> None:
"""Initialize the entity."""
self.entity_description = entity_description
super().__init__(device, self.entity_description.key)
super().__init__(
device, self.entity_description.key, self.entity_description.desc
)
@property
def available(self) -> bool:
@ -213,7 +208,7 @@ class HomeConnectAlarmSensor(HomeConnectEntity, SensorEntity):
async def async_update(self) -> None:
"""Update the sensor's status."""
self._attr_native_value = (
self.device.appliance.status.get(self.entity_description.state_key, {})
self.device.appliance.status.get(self.bsh_key, {})
.get(ATTR_VALUE, BSH_EVENT_PRESENT_STATE_OFF)
.rsplit(".", maxsplit=1)[-1]
.lower()

View File

@ -34,22 +34,21 @@ _LOGGER = logging.getLogger(__name__)
class HomeConnectSwitchEntityDescription(SwitchEntityDescription):
"""Switch entity description."""
on_key: str
desc: str
SWITCHES: tuple[HomeConnectSwitchEntityDescription, ...] = (
HomeConnectSwitchEntityDescription(
key="Supermode Freezer",
on_key=REFRIGERATION_SUPERMODEFREEZER,
key=REFRIGERATION_SUPERMODEFREEZER,
desc="Supermode Freezer",
),
HomeConnectSwitchEntityDescription(
key="Supermode Refrigerator",
on_key=REFRIGERATION_SUPERMODEREFRIGERATOR,
key=REFRIGERATION_SUPERMODEREFRIGERATOR,
desc="Supermode Refrigerator",
),
HomeConnectSwitchEntityDescription(
key="Dispenser Enabled",
on_key=REFRIGERATION_DISPENSER,
translation_key="refrigeration_dispenser",
key=REFRIGERATION_DISPENSER,
desc="Dispenser Enabled",
),
)
@ -75,7 +74,7 @@ async def async_setup_entry(
entities.extend(
HomeConnectSwitch(device=hc_device, entity_description=description)
for description in SWITCHES
if description.on_key in hc_device.appliance.status
if description.key in hc_device.appliance.status
)
return entities
@ -96,7 +95,7 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
"""Initialize the entity."""
self.entity_description = entity_description
self._attr_available = False
super().__init__(device=device, desc=entity_description.key)
super().__init__(device, entity_description.key, entity_description.desc)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on setting."""
@ -104,7 +103,7 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
_LOGGER.debug("Turning on %s", self.entity_description.key)
try:
await self.hass.async_add_executor_job(
self.device.appliance.set_setting, self.entity_description.on_key, True
self.device.appliance.set_setting, self.entity_description.key, True
)
except HomeConnectError as err:
_LOGGER.error("Error while trying to turn on: %s", err)
@ -120,7 +119,7 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
_LOGGER.debug("Turning off %s", self.entity_description.key)
try:
await self.hass.async_add_executor_job(
self.device.appliance.set_setting, self.entity_description.on_key, False
self.device.appliance.set_setting, self.entity_description.key, False
)
except HomeConnectError as err:
_LOGGER.error("Error while trying to turn off: %s", err)
@ -134,7 +133,7 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
"""Update the switch's status."""
self._attr_is_on = self.device.appliance.status.get(
self.entity_description.on_key, {}
self.entity_description.key, {}
).get(ATTR_VALUE)
self._attr_available = True
_LOGGER.debug(
@ -154,7 +153,7 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
desc = " ".join(
["Program", program_name.split(".")[-3], program_name.split(".")[-1]]
)
super().__init__(device, desc)
super().__init__(device, desc, desc)
self.program_name = program_name
async def async_turn_on(self, **kwargs: Any) -> None:
@ -192,7 +191,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
def __init__(self, device: HomeConnectDevice) -> None:
"""Initialize the entity."""
super().__init__(device, "Power")
super().__init__(device, BSH_POWER_STATE, "Power")
async def async_turn_on(self, **kwargs: Any) -> None:
"""Switch the device on."""
@ -259,7 +258,7 @@ class HomeConnectChildLockSwitch(HomeConnectEntity, SwitchEntity):
def __init__(self, device: HomeConnectDevice) -> None:
"""Initialize the entity."""
super().__init__(device, "ChildLock")
super().__init__(device, BSH_CHILD_LOCK_STATE, "ChildLock")
async def async_turn_on(self, **kwargs: Any) -> None:
"""Switch child lock on."""

View File

@ -67,6 +67,20 @@ def mock_config_entry(token_entry: dict[str, Any]) -> MockConfigEntry:
"auth_implementation": FAKE_AUTH_IMPL,
"token": token_entry,
},
minor_version=2,
)
@pytest.fixture(name="config_entry_v1_1")
def mock_config_entry_v1_1(token_entry: dict[str, Any]) -> MockConfigEntry:
"""Fixture for a config entry."""
return MockConfigEntry(
domain=DOMAIN,
data={
"auth_implementation": FAKE_AUTH_IMPL,
"token": token_entry,
},
minor_version=1,
)

View File

@ -2,18 +2,31 @@
from collections.abc import Awaitable, Callable
from typing import Any
from unittest.mock import MagicMock, Mock
from unittest.mock import MagicMock, Mock, patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from requests import HTTPError
import requests_mock
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.home_connect import SCAN_INTERVAL
from homeassistant.components.home_connect.const import DOMAIN, OAUTH2_TOKEN
from homeassistant.components.home_connect.const import (
BSH_CHILD_LOCK_STATE,
BSH_OPERATION_STATE,
BSH_POWER_STATE,
BSH_REMOTE_START_ALLOWANCE_STATE,
COOKING_LIGHTING,
DOMAIN,
OAUTH2_TOKEN,
)
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import device_registry as dr, entity_registry as er
from .conftest import (
CLIENT_ID,
@ -294,3 +307,68 @@ async def test_services_exception(
with pytest.raises(ValueError):
await hass.services.async_call(**service_call)
async def test_entity_migration(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
config_entry_v1_1: MockConfigEntry,
appliance: Mock,
platforms: list[Platform],
) -> None:
"""Test entity migration."""
config_entry_v1_1.add_to_hass(hass)
device_entry = device_registry.async_get_or_create(
config_entry_id=config_entry_v1_1.entry_id,
identifiers={(DOMAIN, appliance.haId)},
)
test_entities = [
(
SENSOR_DOMAIN,
"Operation State",
BSH_OPERATION_STATE,
),
(
SWITCH_DOMAIN,
"ChildLock",
BSH_CHILD_LOCK_STATE,
),
(
SWITCH_DOMAIN,
"Power",
BSH_POWER_STATE,
),
(
BINARY_SENSOR_DOMAIN,
"Remote Start",
BSH_REMOTE_START_ALLOWANCE_STATE,
),
(
LIGHT_DOMAIN,
"Light",
COOKING_LIGHTING,
),
]
for domain, old_unique_id_suffix, _ in test_entities:
entity_registry.async_get_or_create(
domain,
DOMAIN,
f"{appliance.haId}-{old_unique_id_suffix}",
device_id=device_entry.id,
config_entry=config_entry_v1_1,
)
with patch("homeassistant.components.home_connect.PLATFORMS", platforms):
await hass.config_entries.async_setup(config_entry_v1_1.entry_id)
await hass.async_block_till_done()
for domain, _, expected_unique_id_suffix in test_entities:
assert entity_registry.async_get_entity_id(
domain, DOMAIN, f"{appliance.haId}-{expected_unique_id_suffix}"
)
assert config_entry_v1_1.minor_version == 2