diff --git a/CODEOWNERS b/CODEOWNERS index e2d0cbdaa3f..e349ebeacf0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -129,8 +129,8 @@ build.json @home-assistant/supervisor /homeassistant/components/binary_sensor/ @home-assistant/core /tests/components/binary_sensor/ @home-assistant/core /homeassistant/components/bizkaibus/ @UgaitzEtxebarria -/homeassistant/components/blebox/ @bbx-a @bbx-jp -/tests/components/blebox/ @bbx-a @bbx-jp +/homeassistant/components/blebox/ @bbx-a @bbx-jp @riokuu +/tests/components/blebox/ @bbx-a @bbx-jp @riokuu /homeassistant/components/blink/ @fronzbot /tests/components/blink/ @fronzbot /homeassistant/components/blueprint/ @home-assistant/core diff --git a/homeassistant/components/blebox/__init__.py b/homeassistant/components/blebox/__init__.py index b6a0045940d..a8f93dd0122 100644 --- a/homeassistant/components/blebox/__init__.py +++ b/homeassistant/components/blebox/__init__.py @@ -1,8 +1,8 @@ """The BleBox devices integration.""" import logging +from blebox_uniapi.box import Box from blebox_uniapi.error import Error -from blebox_uniapi.products import Products from blebox_uniapi.session import ApiHost from homeassistant.config_entries import ConfigEntry @@ -30,7 +30,6 @@ PARALLEL_UPDATES = 0 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up BleBox devices from a config entry.""" - websession = async_get_clientsession(hass) host = entry.data[CONF_HOST] @@ -40,7 +39,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: api_host = ApiHost(host, port, timeout, websession, hass.loop) try: - product = await Products.async_from_host(api_host) + product = await Box.async_from_host(api_host) except Error as ex: _LOGGER.error("Identify failed at %s:%d (%s)", api_host.host, api_host.port, ex) raise ConfigEntryNotReady from ex @@ -50,7 +49,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: product = domain_entry.setdefault(PRODUCT, product) hass.config_entries.async_setup_platforms(entry, PLATFORMS) - return True @@ -71,8 +69,8 @@ def create_blebox_entities( """Create entities from a BleBox product's features.""" product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT] - entities = [] + if entity_type in product.features: for feature in product.features[entity_type]: entities.append(entity_klass(feature)) diff --git a/homeassistant/components/blebox/air_quality.py b/homeassistant/components/blebox/air_quality.py index 31efd797678..daadbc831b6 100644 --- a/homeassistant/components/blebox/air_quality.py +++ b/homeassistant/components/blebox/air_quality.py @@ -1,4 +1,6 @@ """BleBox air quality entity.""" +from datetime import timedelta + from homeassistant.components.air_quality import AirQualityEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -6,6 +8,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import BleBoxEntity, create_blebox_entities +SCAN_INTERVAL = timedelta(seconds=5) + async def async_setup_entry( hass: HomeAssistant, diff --git a/homeassistant/components/blebox/climate.py b/homeassistant/components/blebox/climate.py index 20a019ec0ec..e279991df20 100644 --- a/homeassistant/components/blebox/climate.py +++ b/homeassistant/components/blebox/climate.py @@ -1,4 +1,6 @@ """BleBox climate entity.""" +from datetime import timedelta + from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ClimateEntityFeature, @@ -12,6 +14,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import BleBoxEntity, create_blebox_entities +SCAN_INTERVAL = timedelta(seconds=5) + async def async_setup_entry( hass: HomeAssistant, diff --git a/homeassistant/components/blebox/config_flow.py b/homeassistant/components/blebox/config_flow.py index 17dffe154d1..5ae975f83d9 100644 --- a/homeassistant/components/blebox/config_flow.py +++ b/homeassistant/components/blebox/config_flow.py @@ -1,8 +1,8 @@ """Config flow for BleBox devices integration.""" import logging +from blebox_uniapi.box import Box from blebox_uniapi.error import Error, UnsupportedBoxVersion -from blebox_uniapi.products import Products from blebox_uniapi.session import ApiHost import voluptuous as vol @@ -65,7 +65,6 @@ class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, step, exception, schema, host, port, message_id, log_fn ): """Handle step exceptions.""" - log_fn("%s at %s:%d (%s)", LOG_MSG[message_id], host, port, exception) return self.async_show_form( @@ -101,9 +100,8 @@ class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): websession = async_get_clientsession(hass) api_host = ApiHost(*addr, DEFAULT_SETUP_TIMEOUT, websession, hass.loop, _LOGGER) - try: - product = await Products.async_from_host(api_host) + product = await Box.async_from_host(api_host) except UnsupportedBoxVersion as ex: return self.handle_step_exception( diff --git a/homeassistant/components/blebox/light.py b/homeassistant/components/blebox/light.py index a582fe133c1..32cc6360db1 100644 --- a/homeassistant/components/blebox/light.py +++ b/homeassistant/components/blebox/light.py @@ -1,23 +1,34 @@ """BleBox light entities implementation.""" +from __future__ import annotations + +from datetime import timedelta import logging from blebox_uniapi.error import BadOnValueError +import blebox_uniapi.light +from blebox_uniapi.light import BleboxColorMode from homeassistant.components.light import ( ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_EFFECT, + ATTR_RGB_COLOR, ATTR_RGBW_COLOR, + ATTR_RGBWW_COLOR, ColorMode, LightEntity, + LightEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.util.color import color_rgb_to_hex, rgb_hex_to_rgb_list from . import BleBoxEntity, create_blebox_entities _LOGGER = logging.getLogger(__name__) +SCAN_INTERVAL = timedelta(seconds=5) + async def async_setup_entry( hass: HomeAssistant, @@ -31,6 +42,17 @@ async def async_setup_entry( ) +COLOR_MODE_MAP = { + BleboxColorMode.RGBW: ColorMode.RGBW, + BleboxColorMode.RGB: ColorMode.RGB, + BleboxColorMode.MONO: ColorMode.BRIGHTNESS, + BleboxColorMode.RGBorW: ColorMode.RGBW, # white hex is prioritised over RGB channel + BleboxColorMode.CT: ColorMode.COLOR_TEMP, + BleboxColorMode.CTx2: ColorMode.COLOR_TEMP, # two instances + BleboxColorMode.RGBWW: ColorMode.RGBWW, +} + + class BleBoxLightEntity(BleBoxEntity, LightEntity): """Representation of BleBox lights.""" @@ -38,6 +60,7 @@ class BleBoxLightEntity(BleBoxEntity, LightEntity): """Initialize a BleBox light.""" super().__init__(feature) self._attr_supported_color_modes = {self.color_mode} + self._attr_supported_features = LightEntityFeature.EFFECT @property def is_on(self) -> bool: @@ -49,46 +72,105 @@ class BleBoxLightEntity(BleBoxEntity, LightEntity): """Return the name.""" return self._feature.brightness + @property + def color_temp(self): + """Return color temperature.""" + return self._feature.color_temp + @property def color_mode(self): - """Return the color mode.""" - if self._feature.supports_white and self._feature.supports_color: - return ColorMode.RGBW - if self._feature.supports_brightness: - return ColorMode.BRIGHTNESS - return ColorMode.ONOFF + """Return the color mode. + + Set values to _attr_ibutes if needed. + """ + color_mode_tmp = COLOR_MODE_MAP.get(self._feature.color_mode, ColorMode.ONOFF) + if color_mode_tmp == ColorMode.COLOR_TEMP: + self._attr_min_mireds = 1 + self._attr_max_mireds = 255 + + return color_mode_tmp + + @property + def effect_list(self) -> list[str] | None: + """Return the list of supported effects.""" + return self._feature.effect_list + + @property + def effect(self) -> str | None: + """Return the current effect.""" + return self._feature.effect + + @property + def rgb_color(self): + """Return value for rgb.""" + if (rgb_hex := self._feature.rgb_hex) is None: + return None + return tuple( + blebox_uniapi.light.Light.normalise_elements_of_rgb( + blebox_uniapi.light.Light.rgb_hex_to_rgb_list(rgb_hex)[0:3] + ) + ) @property def rgbw_color(self): """Return the hue and saturation.""" if (rgbw_hex := self._feature.rgbw_hex) is None: return None + return tuple(blebox_uniapi.light.Light.rgb_hex_to_rgb_list(rgbw_hex)[0:4]) - return tuple(rgb_hex_to_rgb_list(rgbw_hex)[0:4]) + @property + def rgbww_color(self): + """Return value for rgbww.""" + if (rgbww_hex := self._feature.rgbww_hex) is None: + return None + return tuple(blebox_uniapi.light.Light.rgb_hex_to_rgb_list(rgbww_hex)) async def async_turn_on(self, **kwargs): """Turn the light on.""" rgbw = kwargs.get(ATTR_RGBW_COLOR) brightness = kwargs.get(ATTR_BRIGHTNESS) - + effect = kwargs.get(ATTR_EFFECT) + color_temp = kwargs.get(ATTR_COLOR_TEMP) + rgbww = kwargs.get(ATTR_RGBWW_COLOR) feature = self._feature value = feature.sensible_on_value - - if brightness is not None: - value = feature.apply_brightness(value, brightness) + rgb = kwargs.get(ATTR_RGB_COLOR) if rgbw is not None: - value = feature.apply_white(value, rgbw[3]) - value = feature.apply_color(value, color_rgb_to_hex(*rgbw[0:3])) - - try: - await self._feature.async_on(value) - except BadOnValueError as ex: - _LOGGER.error( - "Turning on '%s' failed: Bad value %s (%s)", self.name, value, ex + value = list(rgbw) + if color_temp is not None: + value = feature.return_color_temp_with_brightness( + int(color_temp), self.brightness ) + if rgbww is not None: + value = list(rgbww) + + if rgb is not None: + if self.color_mode == ColorMode.RGB and brightness is None: + brightness = self.brightness + value = list(rgb) + + if brightness is not None: + if self.color_mode == ATTR_COLOR_TEMP: + value = feature.return_color_temp_with_brightness( + self.color_temp, brightness + ) + else: + value = feature.apply_brightness(value, brightness) + + if effect is not None: + effect_value = self.effect_list.index(effect) + await self._feature.async_api_command("effect", effect_value) + else: + try: + await self._feature.async_on(value) + except BadOnValueError as ex: + _LOGGER.error( + "Turning on '%s' failed: Bad value %s (%s)", self.name, value, ex + ) + async def async_turn_off(self, **kwargs): """Turn the light off.""" await self._feature.async_off() diff --git a/homeassistant/components/blebox/manifest.json b/homeassistant/components/blebox/manifest.json index d9c0481fff6..5c57d5f6b9f 100644 --- a/homeassistant/components/blebox/manifest.json +++ b/homeassistant/components/blebox/manifest.json @@ -3,8 +3,8 @@ "name": "BleBox devices", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/blebox", - "requirements": ["blebox_uniapi==1.3.3"], - "codeowners": ["@bbx-a", "@bbx-jp"], + "requirements": ["blebox_uniapi==2.0.0"], + "codeowners": ["@bbx-a", "@bbx-jp", "@riokuu"], "iot_class": "local_polling", "loggers": ["blebox_uniapi"] } diff --git a/homeassistant/components/blebox/switch.py b/homeassistant/components/blebox/switch.py index 9586b37558f..50eba1d2c4a 100644 --- a/homeassistant/components/blebox/switch.py +++ b/homeassistant/components/blebox/switch.py @@ -1,4 +1,6 @@ """BleBox switch implementation.""" +from datetime import timedelta + from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -7,6 +9,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import BleBoxEntity, create_blebox_entities from .const import BLEBOX_TO_HASS_DEVICE_CLASSES +SCAN_INTERVAL = timedelta(seconds=5) + async def async_setup_entry( hass: HomeAssistant, diff --git a/requirements_all.txt b/requirements_all.txt index 11ed4a2113a..58d8cfb604a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -399,7 +399,7 @@ bimmer_connected==0.9.6 bizkaibus==0.1.1 # homeassistant.components.blebox -blebox_uniapi==1.3.3 +blebox_uniapi==2.0.0 # homeassistant.components.blink blinkpy==0.19.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index dc7366f151b..af336175e95 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -311,7 +311,7 @@ bellows==0.31.0 bimmer_connected==0.9.6 # homeassistant.components.blebox -blebox_uniapi==1.3.3 +blebox_uniapi==2.0.0 # homeassistant.components.blink blinkpy==0.19.0 diff --git a/tests/components/blebox/conftest.py b/tests/components/blebox/conftest.py index a63a0090c3a..548c7a5dc38 100644 --- a/tests/components/blebox/conftest.py +++ b/tests/components/blebox/conftest.py @@ -16,12 +16,11 @@ from tests.components.light.conftest import mock_light_profiles # noqa: F401 def patch_product_identify(path=None, **kwargs): """Patch the blebox_uniapi Products class.""" - if path is None: - path = "homeassistant.components.blebox.Products" - patcher = patch(path, mock.DEFAULT, blebox_uniapi.products.Products, True, True) - products_class = patcher.start() - products_class.async_from_host = AsyncMock(**kwargs) - return products_class + patcher = patch.object( + blebox_uniapi.box.Box, "async_from_host", AsyncMock(**kwargs) + ) + patcher.start() + return blebox_uniapi.box.Box def setup_product_mock(category, feature_mocks, path=None): @@ -84,7 +83,6 @@ async def async_setup_entities(hass, config, entity_ids): config_entry.add_to_hass(hass) assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() - entity_registry = er.async_get(hass) return [entity_registry.async_get(entity_id) for entity_id in entity_ids] diff --git a/tests/components/blebox/test_config_flow.py b/tests/components/blebox/test_config_flow.py index 03f5d0b4f2a..3f40880abf7 100644 --- a/tests/components/blebox/test_config_flow.py +++ b/tests/components/blebox/test_config_flow.py @@ -77,8 +77,8 @@ async def test_flow_works(hass, valid_feature_mock, flow_feature_mock): @pytest.fixture(name="product_class_mock") def product_class_mock_fixture(): """Return a mocked feature.""" - path = "homeassistant.components.blebox.config_flow.Products" - patcher = patch(path, DEFAULT, blebox_uniapi.products.Products, True, True) + path = "homeassistant.components.blebox.config_flow.Box" + patcher = patch(path, DEFAULT, blebox_uniapi.box.Box, True, True) yield patcher diff --git a/tests/components/blebox/test_light.py b/tests/components/blebox/test_light.py index a546424e14b..4663c216136 100644 --- a/tests/components/blebox/test_light.py +++ b/tests/components/blebox/test_light.py @@ -39,6 +39,8 @@ def dimmer_fixture(): is_on=True, supports_color=False, supports_white=False, + color_mode=blebox_uniapi.light.BleboxColorMode.MONO, + effect_list=None, ) product = feature.product type(product).name = PropertyMock(return_value="My dimmer") @@ -210,6 +212,8 @@ def wlightboxs_fixture(): is_on=None, supports_color=False, supports_white=False, + color_mode=blebox_uniapi.light.BleboxColorMode.MONO, + effect_list=["NONE", "PL", "RELAX"], ) product = feature.product type(product).name = PropertyMock(return_value="My wLightBoxS") @@ -310,6 +314,9 @@ def wlightbox_fixture(): supports_white=True, white_value=None, rgbw_hex=None, + color_mode=blebox_uniapi.light.BleboxColorMode.RGBW, + effect="NONE", + effect_list=["NONE", "PL", "POLICE"], ) product = feature.product type(product).name = PropertyMock(return_value="My wLightBox") @@ -379,7 +386,7 @@ async def test_wlightbox_on_rgbw(wlightbox, hass, config): def turn_on(value): feature_mock.is_on = True - assert value == "c1d2f3c7" + assert value == [193, 210, 243, 199] feature_mock.white_value = 0xC7 # on feature_mock.rgbw_hex = "c1d2f3c7"