Avoid increasing yeelight rate limit when the state is already set (#54410)

This commit is contained in:
J. Nick Koston 2021-08-10 17:17:49 -05:00 committed by GitHub
parent 4da451fcf7
commit 4ae6435a64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 144 additions and 3 deletions

View File

@ -2,6 +2,7 @@
from __future__ import annotations
import logging
import math
import voluptuous as vol
import yeelight
@ -576,6 +577,13 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
async def async_set_brightness(self, brightness, duration) -> None:
"""Set bulb brightness."""
if brightness:
if math.floor(self.brightness) == math.floor(brightness):
_LOGGER.debug("brightness already set to: %s", brightness)
# Already set, and since we get pushed updates
# we avoid setting it again to ensure we do not
# hit the rate limit
return
_LOGGER.debug("Setting brightness: %s", brightness)
await self._bulb.async_set_brightness(
brightness / 255 * 100, duration=duration, light_type=self.light_type
@ -585,6 +593,13 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
async def async_set_hs(self, hs_color, duration) -> None:
"""Set bulb's color."""
if hs_color and COLOR_MODE_HS in self.supported_color_modes:
if self.color_mode == COLOR_MODE_HS and self.hs_color == hs_color:
_LOGGER.debug("HS already set to: %s", hs_color)
# Already set, and since we get pushed updates
# we avoid setting it again to ensure we do not
# hit the rate limit
return
_LOGGER.debug("Setting HS: %s", hs_color)
await self._bulb.async_set_hsv(
hs_color[0], hs_color[1], duration=duration, light_type=self.light_type
@ -594,9 +609,16 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
async def async_set_rgb(self, rgb, duration) -> None:
"""Set bulb's color."""
if rgb and COLOR_MODE_RGB in self.supported_color_modes:
if self.color_mode == COLOR_MODE_RGB and self.rgb_color == rgb:
_LOGGER.debug("RGB already set to: %s", rgb)
# Already set, and since we get pushed updates
# we avoid setting it again to ensure we do not
# hit the rate limit
return
_LOGGER.debug("Setting RGB: %s", rgb)
await self._bulb.async_set_rgb(
rgb[0], rgb[1], rgb[2], duration=duration, light_type=self.light_type
*rgb, duration=duration, light_type=self.light_type
)
@_async_cmd
@ -604,7 +626,16 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
"""Set bulb's color temperature."""
if colortemp and COLOR_MODE_COLOR_TEMP in self.supported_color_modes:
temp_in_k = mired_to_kelvin(colortemp)
_LOGGER.debug("Setting color temp: %s K", temp_in_k)
if (
self.color_mode == COLOR_MODE_COLOR_TEMP
and self.color_temp == colortemp
):
_LOGGER.debug("Color temp already set to: %s", temp_in_k)
# Already set, and since we get pushed updates
# we avoid setting it again to ensure we do not
# hit the rate limit
return
await self._bulb.async_set_color_temp(
temp_in_k, duration=duration, light_type=self.light_type

View File

@ -1,6 +1,6 @@
"""Test the Yeelight light."""
import logging
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import ANY, AsyncMock, MagicMock, call, patch
from yeelight import (
BulbException,
@ -19,6 +19,7 @@ from yeelight.main import _MODEL_SPECS
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_BRIGHTNESS_PCT,
ATTR_COLOR_TEMP,
ATTR_EFFECT,
ATTR_FLASH,
@ -428,6 +429,115 @@ async def test_services(hass: HomeAssistant, caplog):
)
async def test_state_already_set_avoid_ratelimit(hass: HomeAssistant):
"""Ensure we suppress state changes that will increase the rate limit when there is no change."""
mocked_bulb = _mocked_bulb()
properties = {**PROPERTIES}
properties.pop("active_mode")
properties["color_mode"] = "3" # HSV
mocked_bulb.last_properties = properties
mocked_bulb.bulb_type = BulbType.Color
config_entry = MockConfigEntry(
domain=DOMAIN, data={**CONFIG_ENTRY_DATA, CONF_NIGHTLIGHT_SWITCH: False}
)
config_entry.add_to_hass(hass)
with patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# We use asyncio.create_task now to avoid
# blocking starting so we need to block again
await hass.async_block_till_done()
await hass.services.async_call(
"light",
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ENTITY_LIGHT,
ATTR_HS_COLOR: (PROPERTIES["hue"], PROPERTIES["sat"]),
},
blocking=True,
)
assert mocked_bulb.async_set_hsv.mock_calls == []
assert mocked_bulb.async_set_rgb.mock_calls == []
assert mocked_bulb.async_set_color_temp.mock_calls == []
assert mocked_bulb.async_set_brightness.mock_calls == []
mocked_bulb.last_properties["color_mode"] = 1
rgb = int(PROPERTIES["rgb"])
blue = rgb & 0xFF
green = (rgb >> 8) & 0xFF
red = (rgb >> 16) & 0xFF
await hass.services.async_call(
"light",
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_RGB_COLOR: (red, green, blue)},
blocking=True,
)
assert mocked_bulb.async_set_hsv.mock_calls == []
assert mocked_bulb.async_set_rgb.mock_calls == []
assert mocked_bulb.async_set_color_temp.mock_calls == []
assert mocked_bulb.async_set_brightness.mock_calls == []
mocked_bulb.async_set_rgb.reset_mock()
await hass.services.async_call(
"light",
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ENTITY_LIGHT,
ATTR_BRIGHTNESS_PCT: PROPERTIES["current_brightness"],
},
blocking=True,
)
assert mocked_bulb.async_set_hsv.mock_calls == []
assert mocked_bulb.async_set_rgb.mock_calls == []
assert mocked_bulb.async_set_color_temp.mock_calls == []
assert mocked_bulb.async_set_brightness.mock_calls == []
await hass.services.async_call(
"light",
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_COLOR_TEMP: 250},
blocking=True,
)
assert mocked_bulb.async_set_hsv.mock_calls == []
assert mocked_bulb.async_set_rgb.mock_calls == []
# Should call for the color mode change
assert mocked_bulb.async_set_color_temp.mock_calls == [
call(4000, duration=350, light_type=ANY)
]
assert mocked_bulb.async_set_brightness.mock_calls == []
mocked_bulb.async_set_color_temp.reset_mock()
mocked_bulb.last_properties["color_mode"] = 2
await hass.services.async_call(
"light",
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_COLOR_TEMP: 250},
blocking=True,
)
assert mocked_bulb.async_set_hsv.mock_calls == []
assert mocked_bulb.async_set_rgb.mock_calls == []
assert mocked_bulb.async_set_color_temp.mock_calls == []
assert mocked_bulb.async_set_brightness.mock_calls == []
mocked_bulb.last_properties["color_mode"] = 3
# This last change should generate a call even though
# the color mode is the same since the HSV has changed
await hass.services.async_call(
"light",
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_LIGHT, ATTR_HS_COLOR: (5, 5)},
blocking=True,
)
assert mocked_bulb.async_set_hsv.mock_calls == [
call(5.0, 5.0, duration=350, light_type=ANY)
]
assert mocked_bulb.async_set_rgb.mock_calls == []
assert mocked_bulb.async_set_color_temp.mock_calls == []
assert mocked_bulb.async_set_brightness.mock_calls == []
async def test_device_types(hass: HomeAssistant, caplog):
"""Test different device types."""
mocked_bulb = _mocked_bulb()