diff --git a/homeassistant/components/hue/v2/group.py b/homeassistant/components/hue/v2/group.py index 7d63df131d8..8ce6d287551 100644 --- a/homeassistant/components/hue/v2/group.py +++ b/homeassistant/components/hue/v2/group.py @@ -96,6 +96,8 @@ class GroupedHueLight(HueBaseEntity, LightEntity): self.api: HueBridgeV2 = bridge.api self._attr_supported_features |= LightEntityFeature.FLASH self._attr_supported_features |= LightEntityFeature.TRANSITION + self._restore_brightness: float | None = None + self._brightness_pct: float = 0 # we create a virtual service/device for Hue zones/rooms # so we have a parent for grouped lights and scenes self._attr_device_info = DeviceInfo( @@ -153,6 +155,18 @@ class GroupedHueLight(HueBaseEntity, LightEntity): brightness = normalize_hue_brightness(kwargs.get(ATTR_BRIGHTNESS)) flash = kwargs.get(ATTR_FLASH) + if self._restore_brightness and brightness is None: + # The Hue bridge sets the brightness to 1% when turning on a bulb + # when a transition was used to turn off the bulb. + # This issue has been reported on the Hue forum several times: + # https://developers.meethue.com/forum/t/brightness-turns-down-to-1-automatically-shortly-after-sending-off-signal-hue-bug/5692 + # https://developers.meethue.com/forum/t/lights-turn-on-with-lowest-brightness-via-siri-if-turned-off-via-api/6700 + # https://developers.meethue.com/forum/t/using-transitiontime-with-on-false-resets-bri-to-1/4585 + # https://developers.meethue.com/forum/t/bri-value-changing-in-switching-lights-on-off/6323 + # https://developers.meethue.com/forum/t/fade-in-fade-out/6673 + brightness = self._restore_brightness + self._restore_brightness = None + if flash is not None: await self.async_set_flash(flash) return @@ -170,6 +184,8 @@ class GroupedHueLight(HueBaseEntity, LightEntity): async def async_turn_off(self, **kwargs: Any) -> None: """Turn the light off.""" transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION)) + if transition is not None: + self._restore_brightness = self._brightness_pct flash = kwargs.get(ATTR_FLASH) if flash is not None: @@ -244,6 +260,7 @@ class GroupedHueLight(HueBaseEntity, LightEntity): if len(supported_color_modes) == 0: # only add color mode brightness if no color variants supported_color_modes.add(ColorMode.BRIGHTNESS) + self._brightness_pct = total_brightness / lights_with_dimming_support self._attr_brightness = round( ((total_brightness / lights_with_dimming_support) / 100) * 255 ) diff --git a/homeassistant/components/hue/v2/light.py b/homeassistant/components/hue/v2/light.py index ed5d0151b03..348d60d8de2 100644 --- a/homeassistant/components/hue/v2/light.py +++ b/homeassistant/components/hue/v2/light.py @@ -94,6 +94,7 @@ class HueLight(HueBaseEntity, LightEntity): self._supported_color_modes.add(ColorMode.BRIGHTNESS) # support transition if brightness control self._attr_supported_features |= LightEntityFeature.TRANSITION + self._last_brightness: float | None = None self._color_temp_active: bool = False # get list of supported effects (combine effects and timed_effects) self._attr_effect_list = [] @@ -209,6 +210,17 @@ class HueLight(HueBaseEntity, LightEntity): xy_color = kwargs.get(ATTR_XY_COLOR) color_temp = normalize_hue_colortemp(kwargs.get(ATTR_COLOR_TEMP)) brightness = normalize_hue_brightness(kwargs.get(ATTR_BRIGHTNESS)) + if self._last_brightness and brightness is None: + # The Hue bridge sets the brightness to 1% when turning on a bulb + # when a transition was used to turn off the bulb. + # This issue has been reported on the Hue forum several times: + # https://developers.meethue.com/forum/t/brightness-turns-down-to-1-automatically-shortly-after-sending-off-signal-hue-bug/5692 + # https://developers.meethue.com/forum/t/lights-turn-on-with-lowest-brightness-via-siri-if-turned-off-via-api/6700 + # https://developers.meethue.com/forum/t/using-transitiontime-with-on-false-resets-bri-to-1/4585 + # https://developers.meethue.com/forum/t/bri-value-changing-in-switching-lights-on-off/6323 + # https://developers.meethue.com/forum/t/fade-in-fade-out/6673 + brightness = self._last_brightness + self._last_brightness = None self._color_temp_active = color_temp is not None flash = kwargs.get(ATTR_FLASH) effect = effect_str = kwargs.get(ATTR_EFFECT) @@ -245,6 +257,8 @@ class HueLight(HueBaseEntity, LightEntity): async def async_turn_off(self, **kwargs: Any) -> None: """Turn the light off.""" transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION)) + if transition is not None and self.resource.dimming: + self._last_brightness = self.resource.dimming.brightness flash = kwargs.get(ATTR_FLASH) if flash is not None: diff --git a/tests/components/hue/test_light_v2.py b/tests/components/hue/test_light_v2.py index 1dd20bc1350..c32abecbd0b 100644 --- a/tests/components/hue/test_light_v2.py +++ b/tests/components/hue/test_light_v2.py @@ -217,6 +217,7 @@ async def test_light_turn_off_service( # verify the light is on before we start assert hass.states.get(test_light_id).state == "on" + brightness_pct = hass.states.get(test_light_id).attributes["brightness"] / 255 * 100 # now call the HA turn_off service await hass.services.async_call( @@ -256,6 +257,23 @@ async def test_light_turn_off_service( assert mock_bridge_v2.mock_requests[1]["json"]["on"]["on"] is False assert mock_bridge_v2.mock_requests[1]["json"]["dynamics"]["duration"] == 200 + # test turn_on resets brightness + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": test_light_id}, + blocking=True, + ) + assert len(mock_bridge_v2.mock_requests) == 3 + assert mock_bridge_v2.mock_requests[2]["json"]["on"]["on"] is True + assert ( + round( + mock_bridge_v2.mock_requests[2]["json"]["dimming"]["brightness"] + - brightness_pct + ) + == 0 + ) + # test again with sending long flash await hass.services.async_call( "light", @@ -263,8 +281,8 @@ async def test_light_turn_off_service( {"entity_id": test_light_id, "flash": "long"}, blocking=True, ) - assert len(mock_bridge_v2.mock_requests) == 3 - assert mock_bridge_v2.mock_requests[2]["json"]["alert"]["action"] == "breathe" + assert len(mock_bridge_v2.mock_requests) == 4 + assert mock_bridge_v2.mock_requests[3]["json"]["alert"]["action"] == "breathe" # test again with sending short flash await hass.services.async_call( @@ -273,8 +291,8 @@ async def test_light_turn_off_service( {"entity_id": test_light_id, "flash": "short"}, blocking=True, ) - assert len(mock_bridge_v2.mock_requests) == 4 - assert mock_bridge_v2.mock_requests[3]["json"]["identify"]["action"] == "identify" + assert len(mock_bridge_v2.mock_requests) == 5 + assert mock_bridge_v2.mock_requests[4]["json"]["identify"]["action"] == "identify" async def test_light_added(hass: HomeAssistant, mock_bridge_v2) -> None: @@ -481,6 +499,17 @@ async def test_grouped_lights( assert mock_bridge_v2.mock_requests[0]["json"]["on"]["on"] is False assert mock_bridge_v2.mock_requests[0]["json"]["dynamics"]["duration"] == 200 + # Test turn_on resets brightness + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": test_light_id}, + blocking=True, + ) + assert len(mock_bridge_v2.mock_requests) == 2 + assert mock_bridge_v2.mock_requests[1]["json"]["on"]["on"] is True + assert mock_bridge_v2.mock_requests[1]["json"]["dimming"]["brightness"] == 100 + # Test sending short flash effect to a grouped light mock_bridge_v2.mock_requests.clear() test_light_id = "light.test_zone"