From 4d04402ad4473a95b300b9c9503e0daabb261ae1 Mon Sep 17 00:00:00 2001 From: Robert Contreras Date: Tue, 17 Sep 2024 06:56:20 -0700 Subject: [PATCH] Add Home Connect light entity for cooling appliances (#126090) * Add Home Connect light entities for fridge * Update homeassistant/components/home_connect/light.py --------- Co-authored-by: Joost Lekkerkerker --- .../components/home_connect/const.py | 9 ++ .../components/home_connect/light.py | 96 +++++++++++++++++-- .../home_connect/fixtures/settings.json | 16 ++++ tests/components/home_connect/test_light.py | 26 ++++- 4 files changed, 135 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/home_connect/const.py b/homeassistant/components/home_connect/const.py index 68bad33ec50..f86b43511ec 100644 --- a/homeassistant/components/home_connect/const.py +++ b/homeassistant/components/home_connect/const.py @@ -32,6 +32,15 @@ COFFEE_EVENT_BEAN_CONTAINER_EMPTY = ( COFFEE_EVENT_WATER_TANK_EMPTY = "ConsumerProducts.CoffeeMaker.Event.WaterTankEmpty" COFFEE_EVENT_DRIP_TRAY_FULL = "ConsumerProducts.CoffeeMaker.Event.DripTrayFull" +REFRIGERATION_INTERNAL_LIGHT_POWER = "Refrigeration.Common.Setting.Light.Internal.Power" +REFRIGERATION_INTERNAL_LIGHT_BRIGHTNESS = ( + "Refrigeration.Common.Setting.Light.Internal.Brightness" +) +REFRIGERATION_EXTERNAL_LIGHT_POWER = "Refrigeration.Common.Setting.Light.External.Power" +REFRIGERATION_EXTERNAL_LIGHT_BRIGHTNESS = ( + "Refrigeration.Common.Setting.Light.External.Brightness" +) + REFRIGERATION_SUPERMODEFREEZER = "Refrigeration.FridgeFreezer.Setting.SuperModeFreezer" REFRIGERATION_SUPERMODEREFRIGERATOR = ( "Refrigeration.FridgeFreezer.Setting.SuperModeRefrigerator" diff --git a/homeassistant/components/home_connect/light.py b/homeassistant/components/home_connect/light.py index 3b062fac66c..a1556d5caab 100644 --- a/homeassistant/components/home_connect/light.py +++ b/homeassistant/components/home_connect/light.py @@ -1,5 +1,6 @@ """Provides a light for Home Connect.""" +from dataclasses import dataclass import logging from math import ceil from typing import Any @@ -11,13 +12,15 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, ColorMode, LightEntity, + LightEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_ENTITIES +from homeassistant.const import CONF_DEVICE, CONF_ENTITIES from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback import homeassistant.util.color as color_util +from .api import HomeConnectDevice from .const import ( ATTR_VALUE, BSH_AMBIENT_LIGHT_BRIGHTNESS, @@ -28,12 +31,38 @@ from .const import ( COOKING_LIGHTING, COOKING_LIGHTING_BRIGHTNESS, DOMAIN, + REFRIGERATION_EXTERNAL_LIGHT_BRIGHTNESS, + REFRIGERATION_EXTERNAL_LIGHT_POWER, + REFRIGERATION_INTERNAL_LIGHT_BRIGHTNESS, + REFRIGERATION_INTERNAL_LIGHT_POWER, ) from .entity import HomeConnectEntity _LOGGER = logging.getLogger(__name__) +@dataclass(frozen=True, kw_only=True) +class HomeConnectLightEntityDescription(LightEntityDescription): + """Light entity description.""" + + on_key: str + brightness_key: str | None + + +LIGHTS: tuple[HomeConnectLightEntityDescription, ...] = ( + HomeConnectLightEntityDescription( + key="Internal Light", + on_key=REFRIGERATION_INTERNAL_LIGHT_POWER, + brightness_key=REFRIGERATION_INTERNAL_LIGHT_BRIGHTNESS, + ), + HomeConnectLightEntityDescription( + key="External Light", + on_key=REFRIGERATION_EXTERNAL_LIGHT_POWER, + brightness_key=REFRIGERATION_EXTERNAL_LIGHT_BRIGHTNESS, + ), +) + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -48,7 +77,18 @@ async def async_setup_entry( for device_dict in hc_api.devices: entity_dicts = device_dict.get(CONF_ENTITIES, {}).get("light", []) entity_list = [HomeConnectLight(**d) for d in entity_dicts] - entities += entity_list + device: HomeConnectDevice = device_dict[CONF_DEVICE] + # Auto-discover entities + entities.extend( + HomeConnectCoolingLight( + device=device, + ambient=False, + entity_description=description, + ) + for description in LIGHTS + if description.on_key in device.appliance.status + ) + entities.extend(entity_list) return entities async_add_entities(await hass.async_add_executor_job(get_entities), True) @@ -57,10 +97,14 @@ async def async_setup_entry( class HomeConnectLight(HomeConnectEntity, LightEntity): """Light for Home Connect.""" - def __init__(self, device, desc, ambient): + def __init__(self, device, desc, ambient) -> None: """Initialize the entity.""" super().__init__(device, desc) self._ambient = ambient + self._percentage_scale = (10, 100) + self._brightness_key: str | None + self._custom_color_key: str | None + self._color_key: str | None if ambient: self._brightness_key = BSH_AMBIENT_LIGHT_BRIGHTNESS self._key = BSH_AMBIENT_LIGHT_ENABLED @@ -97,10 +141,15 @@ class HomeConnectLight(HomeConnectEntity, LightEntity): except HomeConnectError as err: _LOGGER.error("Error while trying selecting customcolor: %s", err) if self._attr_brightness is not None: - brightness = 10 + ceil(self._attr_brightness / 255 * 90) + brightness_arg = self._attr_brightness if ATTR_BRIGHTNESS in kwargs: - brightness = 10 + ceil(kwargs[ATTR_BRIGHTNESS] / 255 * 90) + brightness_arg = kwargs[ATTR_BRIGHTNESS] + brightness = ceil( + color_util.brightness_to_value( + self._percentage_scale, brightness_arg + ) + ) hs_color = kwargs.get(ATTR_HS_COLOR, self._attr_hs_color) if hs_color is not None: @@ -120,8 +169,16 @@ class HomeConnectLight(HomeConnectEntity, LightEntity): ) elif ATTR_BRIGHTNESS in kwargs: - _LOGGER.debug("Changing brightness for: %s", self.name) - brightness = 10 + ceil(kwargs[ATTR_BRIGHTNESS] / 255 * 90) + _LOGGER.debug( + "Changing brightness for: %s, to: %s", + self.name, + kwargs[ATTR_BRIGHTNESS], + ) + brightness = ceil( + color_util.brightness_to_value( + self._percentage_scale, kwargs[ATTR_BRIGHTNESS] + ) + ) try: await self.hass.async_add_executor_job( self.device.appliance.set_setting, self._brightness_key, brightness @@ -172,7 +229,9 @@ class HomeConnectLight(HomeConnectEntity, LightEntity): rgb = color_util.rgb_hex_to_rgb_list(colorvalue) hsv = color_util.color_RGB_to_hsv(rgb[0], rgb[1], rgb[2]) self._attr_hs_color = (hsv[0], hsv[1]) - self._attr_brightness = ceil((hsv[2] - 10) * 255 / 90) + self._attr_brightness = color_util.value_to_brightness( + self._percentage_scale, hsv[2] + ) _LOGGER.debug("Updated, new brightness: %s", self._attr_brightness) else: @@ -180,7 +239,24 @@ class HomeConnectLight(HomeConnectEntity, LightEntity): if brightness is None: self._attr_brightness = None else: - self._attr_brightness = ceil( - (brightness.get(ATTR_VALUE) - 10) * 255 / 90 + self._attr_brightness = color_util.value_to_brightness( + self._percentage_scale, brightness[ATTR_VALUE] ) _LOGGER.debug("Updated, new brightness: %s", self._attr_brightness) + + +class HomeConnectCoolingLight(HomeConnectLight): + """Light entity for Cooling Appliances.""" + + def __init__( + self, + device: HomeConnectDevice, + ambient: bool, + entity_description: HomeConnectLightEntityDescription, + ) -> None: + """Initialize Cooling Light Entity.""" + super().__init__(device, entity_description.key, ambient) + self.entity_description = entity_description + self._key = entity_description.on_key + self._brightness_key = entity_description.brightness_key + self._percentage_scale = (1, 100) diff --git a/tests/components/home_connect/fixtures/settings.json b/tests/components/home_connect/fixtures/settings.json index 29d431419c6..1b9bec57276 100644 --- a/tests/components/home_connect/fixtures/settings.json +++ b/tests/components/home_connect/fixtures/settings.json @@ -138,6 +138,22 @@ "constraints": { "access": "readWrite" } + }, + { + "key": "Refrigeration.Common.Setting.Light.External.Power", + "value": true, + "type": "Boolean" + }, + { + "key": "Refrigeration.Common.Setting.Light.External.Brightness", + "value": 70, + "unit": "%", + "type": "Double", + "constraints": { + "min": 0, + "max": 100, + "access": "readWrite" + } } ] } diff --git a/tests/components/home_connect/test_light.py b/tests/components/home_connect/test_light.py index f37eb71b8aa..7d375ce0b62 100644 --- a/tests/components/home_connect/test_light.py +++ b/tests/components/home_connect/test_light.py @@ -3,7 +3,7 @@ from collections.abc import Awaitable, Callable, Generator from unittest.mock import MagicMock, Mock -from homeconnect.api import HomeConnectError +from homeconnect.api import HomeConnectAppliance, HomeConnectError import pytest from homeassistant.components.home_connect.const import ( @@ -12,6 +12,8 @@ from homeassistant.components.home_connect.const import ( BSH_AMBIENT_LIGHT_ENABLED, COOKING_LIGHTING, COOKING_LIGHTING_BRIGHTNESS, + REFRIGERATION_EXTERNAL_LIGHT_BRIGHTNESS, + REFRIGERATION_EXTERNAL_LIGHT_POWER, ) from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.config_entries import ConfigEntryState @@ -148,6 +150,19 @@ async def test_light( STATE_ON, "Hood", ), + ( + "light.fridgefreezer_external_light", + { + REFRIGERATION_EXTERNAL_LIGHT_POWER: { + "value": True, + }, + REFRIGERATION_EXTERNAL_LIGHT_BRIGHTNESS: {"value": 75}, + }, + SERVICE_TURN_ON, + {}, + STATE_ON, + "FridgeFreezer", + ), ], indirect=["appliance"], ) @@ -166,7 +181,14 @@ async def test_light_functionality( get_appliances: MagicMock, ) -> None: """Test light functionality.""" - appliance.status.update(SETTINGS_STATUS) + appliance.status.update( + HomeConnectAppliance.json2dict( + load_json_object_fixture("home_connect/settings.json") + .get(appliance.name) + .get("data") + .get("settings") + ) + ) get_appliances.return_value = [appliance] assert config_entry.state == ConfigEntryState.NOT_LOADED