Bump blebox_uniapi to 2.0.0 and adapt integration (#73834)

This commit is contained in:
Michał Huryn 2022-06-29 11:57:55 +02:00 committed by GitHub
parent 078c5cea86
commit b5af96e4bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 140 additions and 45 deletions

View File

@ -129,8 +129,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/binary_sensor/ @home-assistant/core /homeassistant/components/binary_sensor/ @home-assistant/core
/tests/components/binary_sensor/ @home-assistant/core /tests/components/binary_sensor/ @home-assistant/core
/homeassistant/components/bizkaibus/ @UgaitzEtxebarria /homeassistant/components/bizkaibus/ @UgaitzEtxebarria
/homeassistant/components/blebox/ @bbx-a @bbx-jp /homeassistant/components/blebox/ @bbx-a @bbx-jp @riokuu
/tests/components/blebox/ @bbx-a @bbx-jp /tests/components/blebox/ @bbx-a @bbx-jp @riokuu
/homeassistant/components/blink/ @fronzbot /homeassistant/components/blink/ @fronzbot
/tests/components/blink/ @fronzbot /tests/components/blink/ @fronzbot
/homeassistant/components/blueprint/ @home-assistant/core /homeassistant/components/blueprint/ @home-assistant/core

View File

@ -1,8 +1,8 @@
"""The BleBox devices integration.""" """The BleBox devices integration."""
import logging import logging
from blebox_uniapi.box import Box
from blebox_uniapi.error import Error from blebox_uniapi.error import Error
from blebox_uniapi.products import Products
from blebox_uniapi.session import ApiHost from blebox_uniapi.session import ApiHost
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -30,7 +30,6 @@ PARALLEL_UPDATES = 0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up BleBox devices from a config entry.""" """Set up BleBox devices from a config entry."""
websession = async_get_clientsession(hass) websession = async_get_clientsession(hass)
host = entry.data[CONF_HOST] 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) api_host = ApiHost(host, port, timeout, websession, hass.loop)
try: try:
product = await Products.async_from_host(api_host) product = await Box.async_from_host(api_host)
except Error as ex: except Error as ex:
_LOGGER.error("Identify failed at %s:%d (%s)", api_host.host, api_host.port, ex) _LOGGER.error("Identify failed at %s:%d (%s)", api_host.host, api_host.port, ex)
raise ConfigEntryNotReady from 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) product = domain_entry.setdefault(PRODUCT, product)
hass.config_entries.async_setup_platforms(entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
return True return True
@ -71,8 +69,8 @@ def create_blebox_entities(
"""Create entities from a BleBox product's features.""" """Create entities from a BleBox product's features."""
product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT] product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT]
entities = [] entities = []
if entity_type in product.features: if entity_type in product.features:
for feature in product.features[entity_type]: for feature in product.features[entity_type]:
entities.append(entity_klass(feature)) entities.append(entity_klass(feature))

View File

@ -1,4 +1,6 @@
"""BleBox air quality entity.""" """BleBox air quality entity."""
from datetime import timedelta
from homeassistant.components.air_quality import AirQualityEntity from homeassistant.components.air_quality import AirQualityEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -6,6 +8,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import BleBoxEntity, create_blebox_entities from . import BleBoxEntity, create_blebox_entities
SCAN_INTERVAL = timedelta(seconds=5)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,

View File

@ -1,4 +1,6 @@
"""BleBox climate entity.""" """BleBox climate entity."""
from datetime import timedelta
from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import ( from homeassistant.components.climate.const import (
ClimateEntityFeature, ClimateEntityFeature,
@ -12,6 +14,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import BleBoxEntity, create_blebox_entities from . import BleBoxEntity, create_blebox_entities
SCAN_INTERVAL = timedelta(seconds=5)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,

View File

@ -1,8 +1,8 @@
"""Config flow for BleBox devices integration.""" """Config flow for BleBox devices integration."""
import logging import logging
from blebox_uniapi.box import Box
from blebox_uniapi.error import Error, UnsupportedBoxVersion from blebox_uniapi.error import Error, UnsupportedBoxVersion
from blebox_uniapi.products import Products
from blebox_uniapi.session import ApiHost from blebox_uniapi.session import ApiHost
import voluptuous as vol 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 self, step, exception, schema, host, port, message_id, log_fn
): ):
"""Handle step exceptions.""" """Handle step exceptions."""
log_fn("%s at %s:%d (%s)", LOG_MSG[message_id], host, port, exception) log_fn("%s at %s:%d (%s)", LOG_MSG[message_id], host, port, exception)
return self.async_show_form( return self.async_show_form(
@ -101,9 +100,8 @@ class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
websession = async_get_clientsession(hass) websession = async_get_clientsession(hass)
api_host = ApiHost(*addr, DEFAULT_SETUP_TIMEOUT, websession, hass.loop, _LOGGER) api_host = ApiHost(*addr, DEFAULT_SETUP_TIMEOUT, websession, hass.loop, _LOGGER)
try: try:
product = await Products.async_from_host(api_host) product = await Box.async_from_host(api_host)
except UnsupportedBoxVersion as ex: except UnsupportedBoxVersion as ex:
return self.handle_step_exception( return self.handle_step_exception(

View File

@ -1,23 +1,34 @@
"""BleBox light entities implementation.""" """BleBox light entities implementation."""
from __future__ import annotations
from datetime import timedelta
import logging import logging
from blebox_uniapi.error import BadOnValueError from blebox_uniapi.error import BadOnValueError
import blebox_uniapi.light
from blebox_uniapi.light import BleboxColorMode
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_EFFECT,
ATTR_RGB_COLOR,
ATTR_RGBW_COLOR, ATTR_RGBW_COLOR,
ATTR_RGBWW_COLOR,
ColorMode, ColorMode,
LightEntity, LightEntity,
LightEntityFeature,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback 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 from . import BleBoxEntity, create_blebox_entities
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=5)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, 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): class BleBoxLightEntity(BleBoxEntity, LightEntity):
"""Representation of BleBox lights.""" """Representation of BleBox lights."""
@ -38,6 +60,7 @@ class BleBoxLightEntity(BleBoxEntity, LightEntity):
"""Initialize a BleBox light.""" """Initialize a BleBox light."""
super().__init__(feature) super().__init__(feature)
self._attr_supported_color_modes = {self.color_mode} self._attr_supported_color_modes = {self.color_mode}
self._attr_supported_features = LightEntityFeature.EFFECT
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
@ -49,46 +72,105 @@ class BleBoxLightEntity(BleBoxEntity, LightEntity):
"""Return the name.""" """Return the name."""
return self._feature.brightness return self._feature.brightness
@property
def color_temp(self):
"""Return color temperature."""
return self._feature.color_temp
@property @property
def color_mode(self): def color_mode(self):
"""Return the color mode.""" """Return the color mode.
if self._feature.supports_white and self._feature.supports_color:
return ColorMode.RGBW Set values to _attr_ibutes if needed.
if self._feature.supports_brightness: """
return ColorMode.BRIGHTNESS color_mode_tmp = COLOR_MODE_MAP.get(self._feature.color_mode, ColorMode.ONOFF)
return 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 @property
def rgbw_color(self): def rgbw_color(self):
"""Return the hue and saturation.""" """Return the hue and saturation."""
if (rgbw_hex := self._feature.rgbw_hex) is None: if (rgbw_hex := self._feature.rgbw_hex) is None:
return 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): async def async_turn_on(self, **kwargs):
"""Turn the light on.""" """Turn the light on."""
rgbw = kwargs.get(ATTR_RGBW_COLOR) rgbw = kwargs.get(ATTR_RGBW_COLOR)
brightness = kwargs.get(ATTR_BRIGHTNESS) 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 feature = self._feature
value = feature.sensible_on_value value = feature.sensible_on_value
rgb = kwargs.get(ATTR_RGB_COLOR)
if brightness is not None:
value = feature.apply_brightness(value, brightness)
if rgbw is not None: if rgbw is not None:
value = feature.apply_white(value, rgbw[3]) value = list(rgbw)
value = feature.apply_color(value, color_rgb_to_hex(*rgbw[0:3])) if color_temp is not None:
value = feature.return_color_temp_with_brightness(
try: int(color_temp), self.brightness
await self._feature.async_on(value)
except BadOnValueError as ex:
_LOGGER.error(
"Turning on '%s' failed: Bad value %s (%s)", self.name, value, ex
) )
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): async def async_turn_off(self, **kwargs):
"""Turn the light off.""" """Turn the light off."""
await self._feature.async_off() await self._feature.async_off()

View File

@ -3,8 +3,8 @@
"name": "BleBox devices", "name": "BleBox devices",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/blebox", "documentation": "https://www.home-assistant.io/integrations/blebox",
"requirements": ["blebox_uniapi==1.3.3"], "requirements": ["blebox_uniapi==2.0.0"],
"codeowners": ["@bbx-a", "@bbx-jp"], "codeowners": ["@bbx-a", "@bbx-jp", "@riokuu"],
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["blebox_uniapi"] "loggers": ["blebox_uniapi"]
} }

View File

@ -1,4 +1,6 @@
"""BleBox switch implementation.""" """BleBox switch implementation."""
from datetime import timedelta
from homeassistant.components.switch import SwitchEntity from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -7,6 +9,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import BleBoxEntity, create_blebox_entities from . import BleBoxEntity, create_blebox_entities
from .const import BLEBOX_TO_HASS_DEVICE_CLASSES from .const import BLEBOX_TO_HASS_DEVICE_CLASSES
SCAN_INTERVAL = timedelta(seconds=5)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,

View File

@ -399,7 +399,7 @@ bimmer_connected==0.9.6
bizkaibus==0.1.1 bizkaibus==0.1.1
# homeassistant.components.blebox # homeassistant.components.blebox
blebox_uniapi==1.3.3 blebox_uniapi==2.0.0
# homeassistant.components.blink # homeassistant.components.blink
blinkpy==0.19.0 blinkpy==0.19.0

View File

@ -311,7 +311,7 @@ bellows==0.31.0
bimmer_connected==0.9.6 bimmer_connected==0.9.6
# homeassistant.components.blebox # homeassistant.components.blebox
blebox_uniapi==1.3.3 blebox_uniapi==2.0.0
# homeassistant.components.blink # homeassistant.components.blink
blinkpy==0.19.0 blinkpy==0.19.0

View File

@ -16,12 +16,11 @@ from tests.components.light.conftest import mock_light_profiles # noqa: F401
def patch_product_identify(path=None, **kwargs): def patch_product_identify(path=None, **kwargs):
"""Patch the blebox_uniapi Products class.""" """Patch the blebox_uniapi Products class."""
if path is None: patcher = patch.object(
path = "homeassistant.components.blebox.Products" blebox_uniapi.box.Box, "async_from_host", AsyncMock(**kwargs)
patcher = patch(path, mock.DEFAULT, blebox_uniapi.products.Products, True, True) )
products_class = patcher.start() patcher.start()
products_class.async_from_host = AsyncMock(**kwargs) return blebox_uniapi.box.Box
return products_class
def setup_product_mock(category, feature_mocks, path=None): 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) config_entry.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, config) assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done() await hass.async_block_till_done()
entity_registry = er.async_get(hass) entity_registry = er.async_get(hass)
return [entity_registry.async_get(entity_id) for entity_id in entity_ids] return [entity_registry.async_get(entity_id) for entity_id in entity_ids]

View File

@ -77,8 +77,8 @@ async def test_flow_works(hass, valid_feature_mock, flow_feature_mock):
@pytest.fixture(name="product_class_mock") @pytest.fixture(name="product_class_mock")
def product_class_mock_fixture(): def product_class_mock_fixture():
"""Return a mocked feature.""" """Return a mocked feature."""
path = "homeassistant.components.blebox.config_flow.Products" path = "homeassistant.components.blebox.config_flow.Box"
patcher = patch(path, DEFAULT, blebox_uniapi.products.Products, True, True) patcher = patch(path, DEFAULT, blebox_uniapi.box.Box, True, True)
yield patcher yield patcher

View File

@ -39,6 +39,8 @@ def dimmer_fixture():
is_on=True, is_on=True,
supports_color=False, supports_color=False,
supports_white=False, supports_white=False,
color_mode=blebox_uniapi.light.BleboxColorMode.MONO,
effect_list=None,
) )
product = feature.product product = feature.product
type(product).name = PropertyMock(return_value="My dimmer") type(product).name = PropertyMock(return_value="My dimmer")
@ -210,6 +212,8 @@ def wlightboxs_fixture():
is_on=None, is_on=None,
supports_color=False, supports_color=False,
supports_white=False, supports_white=False,
color_mode=blebox_uniapi.light.BleboxColorMode.MONO,
effect_list=["NONE", "PL", "RELAX"],
) )
product = feature.product product = feature.product
type(product).name = PropertyMock(return_value="My wLightBoxS") type(product).name = PropertyMock(return_value="My wLightBoxS")
@ -310,6 +314,9 @@ def wlightbox_fixture():
supports_white=True, supports_white=True,
white_value=None, white_value=None,
rgbw_hex=None, rgbw_hex=None,
color_mode=blebox_uniapi.light.BleboxColorMode.RGBW,
effect="NONE",
effect_list=["NONE", "PL", "POLICE"],
) )
product = feature.product product = feature.product
type(product).name = PropertyMock(return_value="My wLightBox") 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): def turn_on(value):
feature_mock.is_on = True feature_mock.is_on = True
assert value == "c1d2f3c7" assert value == [193, 210, 243, 199]
feature_mock.white_value = 0xC7 # on feature_mock.white_value = 0xC7 # on
feature_mock.rgbw_hex = "c1d2f3c7" feature_mock.rgbw_hex = "c1d2f3c7"