Do not return cached values for entity states in emulated_hue (#87642)

* Do not return cached values for state and brightness

* Move building the uncached state dict behind a lru_cache (_build_entity_state_dict)

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Thomas Hollstegge 2023-02-07 19:39:08 +01:00 committed by Paulus Schoutsen
parent 2e541e7ef6
commit 354d77d26b
2 changed files with 92 additions and 67 deletions

View File

@ -634,17 +634,38 @@ def get_entity_state_dict(config: Config, entity: State) -> dict[str, Any]:
# Remove the now stale cached entry. # Remove the now stale cached entry.
config.cached_states.pop(entity.entity_id) config.cached_states.pop(entity.entity_id)
if cached_state is None:
return _build_entity_state_dict(entity)
data: dict[str, Any] = cached_state
# Make sure brightness is valid
if data[STATE_BRIGHTNESS] is None:
data[STATE_BRIGHTNESS] = HUE_API_STATE_BRI_MAX if data[STATE_ON] else 0
# Make sure hue/saturation are valid
if (data[STATE_HUE] is None) or (data[STATE_SATURATION] is None):
data[STATE_HUE] = 0
data[STATE_SATURATION] = 0
# If the light is off, set the color to off
if data[STATE_BRIGHTNESS] == 0:
data[STATE_HUE] = 0
data[STATE_SATURATION] = 0
_clamp_values(data)
return data
@lru_cache(maxsize=512)
def _build_entity_state_dict(entity: State) -> dict[str, Any]:
"""Build a state dict for an entity."""
data: dict[str, Any] = { data: dict[str, Any] = {
STATE_ON: False, STATE_ON: entity.state != STATE_OFF,
STATE_BRIGHTNESS: None, STATE_BRIGHTNESS: None,
STATE_HUE: None, STATE_HUE: None,
STATE_SATURATION: None, STATE_SATURATION: None,
STATE_COLOR_TEMP: None, STATE_COLOR_TEMP: None,
} }
if cached_state is None:
data[STATE_ON] = entity.state != STATE_OFF
if data[STATE_ON]: if data[STATE_ON]:
data[STATE_BRIGHTNESS] = hass_to_hue_brightness( data[STATE_BRIGHTNESS] = hass_to_hue_brightness(
entity.attributes.get(ATTR_BRIGHTNESS, 0) entity.attributes.get(ATTR_BRIGHTNESS, 0)
@ -688,23 +709,12 @@ def get_entity_state_dict(config: Config, entity: State) -> dict[str, Any]:
elif entity.domain == cover.DOMAIN: elif entity.domain == cover.DOMAIN:
level = entity.attributes.get(ATTR_CURRENT_POSITION, 0) level = entity.attributes.get(ATTR_CURRENT_POSITION, 0)
data[STATE_BRIGHTNESS] = round(level / 100 * HUE_API_STATE_BRI_MAX) data[STATE_BRIGHTNESS] = round(level / 100 * HUE_API_STATE_BRI_MAX)
else: _clamp_values(data)
data = cached_state return data
# Make sure brightness is valid
if data[STATE_BRIGHTNESS] is None:
data[STATE_BRIGHTNESS] = HUE_API_STATE_BRI_MAX if data[STATE_ON] else 0
# Make sure hue/saturation are valid
if (data[STATE_HUE] is None) or (data[STATE_SATURATION] is None):
data[STATE_HUE] = 0
data[STATE_SATURATION] = 0
# If the light is off, set the color to off def _clamp_values(data: dict[str, Any]) -> None:
if data[STATE_BRIGHTNESS] == 0: """Clamp brightness, hue, saturation, and color temp to valid values."""
data[STATE_HUE] = 0
data[STATE_SATURATION] = 0
# Clamp brightness, hue, saturation, and color temp to valid values
for key, v_min, v_max in ( for key, v_min, v_max in (
(STATE_BRIGHTNESS, HUE_API_STATE_BRI_MIN, HUE_API_STATE_BRI_MAX), (STATE_BRIGHTNESS, HUE_API_STATE_BRI_MIN, HUE_API_STATE_BRI_MAX),
(STATE_HUE, HUE_API_STATE_HUE_MIN, HUE_API_STATE_HUE_MAX), (STATE_HUE, HUE_API_STATE_HUE_MIN, HUE_API_STATE_HUE_MAX),
@ -714,8 +724,6 @@ def get_entity_state_dict(config: Config, entity: State) -> dict[str, Any]:
if data[key] is not None: if data[key] is not None:
data[key] = max(v_min, min(data[key], v_max)) data[key] = max(v_min, min(data[key], v_max))
return data
@lru_cache(maxsize=1024) @lru_cache(maxsize=1024)
def _entity_unique_id(entity_id: str) -> str: def _entity_unique_id(entity_id: str) -> str:
@ -843,10 +851,18 @@ def create_config_model(config: Config, request: web.Request) -> dict[str, Any]:
def create_list_of_entities(config: Config, request: web.Request) -> dict[str, Any]: def create_list_of_entities(config: Config, request: web.Request) -> dict[str, Any]:
"""Create a list of all entities.""" """Create a list of all entities."""
json_response: dict[str, Any] = { hass: core.HomeAssistant = request.app["hass"]
config.entity_id_to_number(state.entity_id): state_to_json(config, state)
for state in config.get_exposed_states() json_response: dict[str, Any] = {}
} for cached_state in config.get_exposed_states():
entity_id = cached_state.entity_id
state = hass.states.get(entity_id)
assert state is not None
json_response[config.entity_id_to_number(entity_id)] = state_to_json(
config, state
)
return json_response return json_response

View File

@ -301,6 +301,7 @@ async def test_discover_lights(hass, hue_client):
await hass.async_block_till_done() await hass.async_block_till_done()
result_json = await async_get_lights(hue_client) result_json = await async_get_lights(hue_client)
assert "1" not in result_json.keys()
devices = {val["uniqueid"] for val in result_json.values()} devices = {val["uniqueid"] for val in result_json.values()}
assert "00:2f:d2:31:ce:c5:55:cc-ee" not in devices # light.ceiling_lights assert "00:2f:d2:31:ce:c5:55:cc-ee" not in devices # light.ceiling_lights
@ -308,8 +309,16 @@ async def test_discover_lights(hass, hue_client):
hass.states.async_set("light.ceiling_lights", STATE_ON) hass.states.async_set("light.ceiling_lights", STATE_ON)
await hass.async_block_till_done() await hass.async_block_till_done()
result_json = await async_get_lights(hue_client) result_json = await async_get_lights(hue_client)
devices = {val["uniqueid"] for val in result_json.values()} device = result_json["1"] # Test that light ID did not change
assert "00:2f:d2:31:ce:c5:55:cc-ee" in devices # light.ceiling_lights assert device["uniqueid"] == "00:2f:d2:31:ce:c5:55:cc-ee" # light.ceiling_lights
assert device["state"][HUE_API_STATE_ON] is True
# Test that returned value is fresh and not cached
hass.states.async_set("light.ceiling_lights", STATE_OFF)
await hass.async_block_till_done()
result_json = await async_get_lights(hue_client)
device = result_json["1"]
assert device["state"][HUE_API_STATE_ON] is False
async def test_light_without_brightness_supported(hass_hue, hue_client): async def test_light_without_brightness_supported(hass_hue, hue_client):