diff --git a/homeassistant/components/fritzbox/light.py b/homeassistant/components/fritzbox/light.py index 0edcb8ee260..e01fb76ec11 100644 --- a/homeassistant/components/fritzbox/light.py +++ b/homeassistant/components/fritzbox/light.py @@ -3,6 +3,8 @@ from __future__ import annotations from typing import Any +from requests.exceptions import HTTPError + from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, @@ -21,6 +23,7 @@ from .const import ( COLOR_TEMP_MODE, CONF_COORDINATOR, DOMAIN as FRITZBOX_DOMAIN, + LOGGER, ) from .coordinator import FritzboxDataUpdateCoordinator @@ -135,16 +138,33 @@ class FritzboxLight(FritzBoxEntity, LightEntity): level = kwargs[ATTR_BRIGHTNESS] await self.hass.async_add_executor_job(self.device.set_level, level) if kwargs.get(ATTR_HS_COLOR) is not None: - hass_hue = int(kwargs[ATTR_HS_COLOR][0]) - hass_saturation = round(kwargs[ATTR_HS_COLOR][1] * 255.0 / 100.0) - # find supported hs values closest to what user selected - hue = min(self._supported_hs.keys(), key=lambda x: abs(x - hass_hue)) - saturation = min( - self._supported_hs[hue], key=lambda x: abs(x - hass_saturation) - ) - await self.hass.async_add_executor_job( - self.device.set_color, (hue, saturation) - ) + # Try setunmappedcolor first. This allows free color selection, + # but we don't know if its supported by all devices. + try: + # HA gives 0..360 for hue, fritz light only supports 0..359 + unmapped_hue = int(kwargs[ATTR_HS_COLOR][0] % 360) + unmapped_saturation = round(kwargs[ATTR_HS_COLOR][1] * 255.0 / 100.0) + await self.hass.async_add_executor_job( + self.device.set_unmapped_color, (unmapped_hue, unmapped_saturation) + ) + # This will raise 400 BAD REQUEST if the setunmappedcolor is not available + except HTTPError as err: + if err.response.status_code != 400: + raise + LOGGER.debug( + "fritzbox does not support method 'setunmappedcolor', fallback to 'setcolor'" + ) + # find supported hs values closest to what user selected + hue = min( + self._supported_hs.keys(), key=lambda x: abs(x - unmapped_hue) + ) + saturation = min( + self._supported_hs[hue], + key=lambda x: abs(x - unmapped_saturation), + ) + await self.hass.async_add_executor_job( + self.device.set_color, (hue, saturation) + ) if kwargs.get(ATTR_COLOR_TEMP) is not None: kelvin = color.color_temperature_kelvin_to_mired(kwargs[ATTR_COLOR_TEMP]) diff --git a/tests/components/fritzbox/test_light.py b/tests/components/fritzbox/test_light.py index b63d19da7e0..0759beb6849 100644 --- a/tests/components/fritzbox/test_light.py +++ b/tests/components/fritzbox/test_light.py @@ -113,7 +113,34 @@ async def test_turn_on_color(hass: HomeAssistant, fritz: Mock): assert await setup_config_entry( hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz ) + assert await hass.services.async_call( + DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_BRIGHTNESS: 100, ATTR_HS_COLOR: (100, 70)}, + True, + ) + assert device.set_state_on.call_count == 1 + assert device.set_level.call_count == 1 + assert device.set_unmapped_color.call_count == 1 + +async def test_turn_on_color_unsupported_api_method(hass: HomeAssistant, fritz: Mock): + """Test turn device on in mapped color mode if unmapped is not supported.""" + device = FritzDeviceLightMock() + device.get_color_temps.return_value = [2700, 6500] + device.get_colors.return_value = { + "Red": [("100", "70", "10"), ("100", "50", "10"), ("100", "30", "10")] + } + mockresponse = Mock() + mockresponse.status_code = 400 + + error = HTTPError("Bad Request") + error.response = mockresponse + device.set_unmapped_color.side_effect = error + + assert await setup_config_entry( + hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz + ) assert await hass.services.async_call( DOMAIN, SERVICE_TURN_ON, @@ -135,7 +162,6 @@ async def test_turn_off(hass: HomeAssistant, fritz: Mock): assert await setup_config_entry( hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz ) - assert await hass.services.async_call( DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_ID}, True )