mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
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:
parent
3e8bc98f23
commit
0d795aad16
@ -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
|
||||
|
@ -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",
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -14,6 +14,8 @@ class OAuth2FlowHandler(
|
||||
|
||||
DOMAIN = DOMAIN
|
||||
|
||||
MINOR_VERSION = 2
|
||||
|
||||
@property
|
||||
def logger(self) -> logging.Logger:
|
||||
"""Return logger."""
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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."""
|
||||
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user