mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Additional cleanups for emulated_hue (#73004)
* Additional cleanups for emulated_hue Followup to https://github.com/home-assistant/core/pull/72663#discussion_r884268731 * split long lines
This commit is contained in:
parent
aad3253ed1
commit
4c11cc3dbb
@ -156,49 +156,49 @@ class Config:
|
|||||||
# Google Home
|
# Google Home
|
||||||
return self.numbers.get(number)
|
return self.numbers.get(number)
|
||||||
|
|
||||||
def get_entity_name(self, entity: State) -> str:
|
def get_entity_name(self, state: State) -> str:
|
||||||
"""Get the name of an entity."""
|
"""Get the name of an entity."""
|
||||||
if (
|
if (
|
||||||
entity.entity_id in self.entities
|
state.entity_id in self.entities
|
||||||
and CONF_ENTITY_NAME in self.entities[entity.entity_id]
|
and CONF_ENTITY_NAME in self.entities[state.entity_id]
|
||||||
):
|
):
|
||||||
return self.entities[entity.entity_id][CONF_ENTITY_NAME]
|
return self.entities[state.entity_id][CONF_ENTITY_NAME]
|
||||||
|
|
||||||
return entity.attributes.get(ATTR_EMULATED_HUE_NAME, entity.name)
|
return state.attributes.get(ATTR_EMULATED_HUE_NAME, state.name)
|
||||||
|
|
||||||
def is_entity_exposed(self, entity: State) -> bool:
|
def is_state_exposed(self, state: State) -> bool:
|
||||||
"""Cache determine if an entity should be exposed on the emulated bridge."""
|
"""Cache determine if an entity should be exposed on the emulated bridge."""
|
||||||
if (exposed := self._exposed_cache.get(entity.entity_id)) is not None:
|
if (exposed := self._exposed_cache.get(state.entity_id)) is not None:
|
||||||
return exposed
|
return exposed
|
||||||
exposed = self._is_entity_exposed(entity)
|
exposed = self._is_state_exposed(state)
|
||||||
self._exposed_cache[entity.entity_id] = exposed
|
self._exposed_cache[state.entity_id] = exposed
|
||||||
return exposed
|
return exposed
|
||||||
|
|
||||||
def filter_exposed_entities(self, states: Iterable[State]) -> list[State]:
|
def filter_exposed_states(self, states: Iterable[State]) -> list[State]:
|
||||||
"""Filter a list of all states down to exposed entities."""
|
"""Filter a list of all states down to exposed entities."""
|
||||||
exposed: list[State] = [
|
exposed: list[State] = [
|
||||||
state for state in states if self.is_entity_exposed(state)
|
state for state in states if self.is_state_exposed(state)
|
||||||
]
|
]
|
||||||
return exposed
|
return exposed
|
||||||
|
|
||||||
def _is_entity_exposed(self, entity: State) -> bool:
|
def _is_state_exposed(self, state: State) -> bool:
|
||||||
"""Determine if an entity should be exposed on the emulated bridge.
|
"""Determine if an entity state should be exposed on the emulated bridge.
|
||||||
|
|
||||||
Async friendly.
|
Async friendly.
|
||||||
"""
|
"""
|
||||||
if entity.attributes.get("view") is not None:
|
if state.attributes.get("view") is not None:
|
||||||
# Ignore entities that are views
|
# Ignore entities that are views
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if entity.entity_id in self._entities_with_hidden_attr_in_config:
|
if state.entity_id in self._entities_with_hidden_attr_in_config:
|
||||||
return not self._entities_with_hidden_attr_in_config[entity.entity_id]
|
return not self._entities_with_hidden_attr_in_config[state.entity_id]
|
||||||
|
|
||||||
if not self.expose_by_default:
|
if not self.expose_by_default:
|
||||||
return False
|
return False
|
||||||
# Expose an entity if the entity's domain is exposed by default and
|
# Expose an entity if the entity's domain is exposed by default and
|
||||||
# the configuration doesn't explicitly exclude it from being
|
# the configuration doesn't explicitly exclude it from being
|
||||||
# exposed, or if the entity is explicitly exposed
|
# exposed, or if the entity is explicitly exposed
|
||||||
if entity.domain in self.exposed_domains:
|
if state.domain in self.exposed_domains:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from functools import lru_cache
|
||||||
import hashlib
|
import hashlib
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from ipaddress import ip_address
|
from ipaddress import ip_address
|
||||||
@ -302,15 +303,15 @@ class HueOneLightStateView(HomeAssistantView):
|
|||||||
)
|
)
|
||||||
return self.json_message("Entity not found", HTTPStatus.NOT_FOUND)
|
return self.json_message("Entity not found", HTTPStatus.NOT_FOUND)
|
||||||
|
|
||||||
if (entity := hass.states.get(hass_entity_id)) is None:
|
if (state := hass.states.get(hass_entity_id)) is None:
|
||||||
_LOGGER.error("Entity not found: %s", hass_entity_id)
|
_LOGGER.error("Entity not found: %s", hass_entity_id)
|
||||||
return self.json_message("Entity not found", HTTPStatus.NOT_FOUND)
|
return self.json_message("Entity not found", HTTPStatus.NOT_FOUND)
|
||||||
|
|
||||||
if not self.config.is_entity_exposed(entity):
|
if not self.config.is_state_exposed(state):
|
||||||
_LOGGER.error("Entity not exposed: %s", entity_id)
|
_LOGGER.error("Entity not exposed: %s", entity_id)
|
||||||
return self.json_message("Entity not exposed", HTTPStatus.UNAUTHORIZED)
|
return self.json_message("Entity not exposed", HTTPStatus.UNAUTHORIZED)
|
||||||
|
|
||||||
json_response = entity_to_json(self.config, entity)
|
json_response = state_to_json(self.config, state)
|
||||||
|
|
||||||
return self.json(json_response)
|
return self.json(json_response)
|
||||||
|
|
||||||
@ -346,7 +347,7 @@ class HueOneLightChangeView(HomeAssistantView):
|
|||||||
_LOGGER.error("Entity not found: %s", entity_id)
|
_LOGGER.error("Entity not found: %s", entity_id)
|
||||||
return self.json_message("Entity not found", HTTPStatus.NOT_FOUND)
|
return self.json_message("Entity not found", HTTPStatus.NOT_FOUND)
|
||||||
|
|
||||||
if not config.is_entity_exposed(entity):
|
if not config.is_state_exposed(entity):
|
||||||
_LOGGER.error("Entity not exposed: %s", entity_id)
|
_LOGGER.error("Entity not exposed: %s", entity_id)
|
||||||
return self.json_message("Entity not exposed", HTTPStatus.UNAUTHORIZED)
|
return self.json_message("Entity not exposed", HTTPStatus.UNAUTHORIZED)
|
||||||
|
|
||||||
@ -614,7 +615,7 @@ class HueOneLightChangeView(HomeAssistantView):
|
|||||||
return self.json(json_response)
|
return self.json(json_response)
|
||||||
|
|
||||||
|
|
||||||
def get_entity_state(config: Config, entity: State) -> dict[str, Any]:
|
def get_entity_state_dict(config: Config, entity: State) -> dict[str, Any]:
|
||||||
"""Retrieve and convert state and brightness values for an entity."""
|
"""Retrieve and convert state and brightness values for an entity."""
|
||||||
cached_state_entry = config.cached_states.get(entity.entity_id, None)
|
cached_state_entry = config.cached_states.get(entity.entity_id, None)
|
||||||
cached_state = None
|
cached_state = None
|
||||||
@ -718,22 +719,32 @@ def get_entity_state(config: Config, entity: State) -> dict[str, Any]:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def entity_to_json(config: Config, entity: State) -> dict[str, Any]:
|
@lru_cache(maxsize=1024)
|
||||||
|
def _entity_unique_id(entity_id: str) -> str:
|
||||||
|
"""Return the emulated_hue unique id for the entity_id."""
|
||||||
|
unique_id = hashlib.md5(entity_id.encode()).hexdigest()
|
||||||
|
return (
|
||||||
|
f"00:{unique_id[0:2]}:{unique_id[2:4]}:"
|
||||||
|
f"{unique_id[4:6]}:{unique_id[6:8]}:{unique_id[8:10]}:"
|
||||||
|
f"{unique_id[10:12]}:{unique_id[12:14]}-{unique_id[14:16]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def state_to_json(config: Config, state: State) -> dict[str, Any]:
|
||||||
"""Convert an entity to its Hue bridge JSON representation."""
|
"""Convert an entity to its Hue bridge JSON representation."""
|
||||||
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
entity_features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
color_modes = entity.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES, [])
|
color_modes = state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES, [])
|
||||||
unique_id = hashlib.md5(entity.entity_id.encode()).hexdigest()
|
unique_id = _entity_unique_id(state.entity_id)
|
||||||
unique_id = f"00:{unique_id[0:2]}:{unique_id[2:4]}:{unique_id[4:6]}:{unique_id[6:8]}:{unique_id[8:10]}:{unique_id[10:12]}:{unique_id[12:14]}-{unique_id[14:16]}"
|
state_dict = get_entity_state_dict(config, state)
|
||||||
|
|
||||||
state = get_entity_state(config, entity)
|
json_state: dict[str, str | bool | int] = {
|
||||||
|
HUE_API_STATE_ON: state_dict[STATE_ON],
|
||||||
retval: dict[str, Any] = {
|
"reachable": state.state != STATE_UNAVAILABLE,
|
||||||
"state": {
|
"mode": "homeautomation",
|
||||||
HUE_API_STATE_ON: state[STATE_ON],
|
}
|
||||||
"reachable": entity.state != STATE_UNAVAILABLE,
|
retval: dict[str, str | dict[str, str | bool | int]] = {
|
||||||
"mode": "homeautomation",
|
"state": json_state,
|
||||||
},
|
"name": config.get_entity_name(state),
|
||||||
"name": config.get_entity_name(entity),
|
|
||||||
"uniqueid": unique_id,
|
"uniqueid": unique_id,
|
||||||
"manufacturername": "Home Assistant",
|
"manufacturername": "Home Assistant",
|
||||||
"swversion": "123",
|
"swversion": "123",
|
||||||
@ -744,30 +755,30 @@ def entity_to_json(config: Config, entity: State) -> dict[str, Any]:
|
|||||||
# Same as Color light, but which supports additional setting of color temperature
|
# Same as Color light, but which supports additional setting of color temperature
|
||||||
retval["type"] = "Extended color light"
|
retval["type"] = "Extended color light"
|
||||||
retval["modelid"] = "HASS231"
|
retval["modelid"] = "HASS231"
|
||||||
retval["state"].update(
|
json_state.update(
|
||||||
{
|
{
|
||||||
HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
|
HUE_API_STATE_BRI: state_dict[STATE_BRIGHTNESS],
|
||||||
HUE_API_STATE_HUE: state[STATE_HUE],
|
HUE_API_STATE_HUE: state_dict[STATE_HUE],
|
||||||
HUE_API_STATE_SAT: state[STATE_SATURATION],
|
HUE_API_STATE_SAT: state_dict[STATE_SATURATION],
|
||||||
HUE_API_STATE_CT: state[STATE_COLOR_TEMP],
|
HUE_API_STATE_CT: state_dict[STATE_COLOR_TEMP],
|
||||||
HUE_API_STATE_EFFECT: "none",
|
HUE_API_STATE_EFFECT: "none",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if state[STATE_HUE] > 0 or state[STATE_SATURATION] > 0:
|
if state_dict[STATE_HUE] > 0 or state_dict[STATE_SATURATION] > 0:
|
||||||
retval["state"][HUE_API_STATE_COLORMODE] = "hs"
|
json_state[HUE_API_STATE_COLORMODE] = "hs"
|
||||||
else:
|
else:
|
||||||
retval["state"][HUE_API_STATE_COLORMODE] = "ct"
|
json_state[HUE_API_STATE_COLORMODE] = "ct"
|
||||||
elif light.color_supported(color_modes):
|
elif light.color_supported(color_modes):
|
||||||
# Color light (Zigbee Device ID: 0x0200)
|
# Color light (Zigbee Device ID: 0x0200)
|
||||||
# Supports on/off, dimming and color control (hue/saturation, enhanced hue, color loop and XY)
|
# Supports on/off, dimming and color control (hue/saturation, enhanced hue, color loop and XY)
|
||||||
retval["type"] = "Color light"
|
retval["type"] = "Color light"
|
||||||
retval["modelid"] = "HASS213"
|
retval["modelid"] = "HASS213"
|
||||||
retval["state"].update(
|
json_state.update(
|
||||||
{
|
{
|
||||||
HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
|
HUE_API_STATE_BRI: state_dict[STATE_BRIGHTNESS],
|
||||||
HUE_API_STATE_COLORMODE: "hs",
|
HUE_API_STATE_COLORMODE: "hs",
|
||||||
HUE_API_STATE_HUE: state[STATE_HUE],
|
HUE_API_STATE_HUE: state_dict[STATE_HUE],
|
||||||
HUE_API_STATE_SAT: state[STATE_SATURATION],
|
HUE_API_STATE_SAT: state_dict[STATE_SATURATION],
|
||||||
HUE_API_STATE_EFFECT: "none",
|
HUE_API_STATE_EFFECT: "none",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -776,11 +787,11 @@ def entity_to_json(config: Config, entity: State) -> dict[str, Any]:
|
|||||||
# Supports groups, scenes, on/off, dimming, and setting of a color temperature
|
# Supports groups, scenes, on/off, dimming, and setting of a color temperature
|
||||||
retval["type"] = "Color temperature light"
|
retval["type"] = "Color temperature light"
|
||||||
retval["modelid"] = "HASS312"
|
retval["modelid"] = "HASS312"
|
||||||
retval["state"].update(
|
json_state.update(
|
||||||
{
|
{
|
||||||
HUE_API_STATE_COLORMODE: "ct",
|
HUE_API_STATE_COLORMODE: "ct",
|
||||||
HUE_API_STATE_CT: state[STATE_COLOR_TEMP],
|
HUE_API_STATE_CT: state_dict[STATE_COLOR_TEMP],
|
||||||
HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
|
HUE_API_STATE_BRI: state_dict[STATE_BRIGHTNESS],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
elif entity_features & (
|
elif entity_features & (
|
||||||
@ -793,7 +804,7 @@ def entity_to_json(config: Config, entity: State) -> dict[str, Any]:
|
|||||||
# Supports groups, scenes, on/off and dimming
|
# Supports groups, scenes, on/off and dimming
|
||||||
retval["type"] = "Dimmable light"
|
retval["type"] = "Dimmable light"
|
||||||
retval["modelid"] = "HASS123"
|
retval["modelid"] = "HASS123"
|
||||||
retval["state"].update({HUE_API_STATE_BRI: state[STATE_BRIGHTNESS]})
|
json_state.update({HUE_API_STATE_BRI: state_dict[STATE_BRIGHTNESS]})
|
||||||
elif not config.lights_all_dimmable:
|
elif not config.lights_all_dimmable:
|
||||||
# On/Off light (ZigBee Device ID: 0x0000)
|
# On/Off light (ZigBee Device ID: 0x0000)
|
||||||
# Supports groups, scenes and on/off control
|
# Supports groups, scenes and on/off control
|
||||||
@ -806,7 +817,7 @@ def entity_to_json(config: Config, entity: State) -> dict[str, Any]:
|
|||||||
# Reports fixed brightness for compatibility with Alexa.
|
# Reports fixed brightness for compatibility with Alexa.
|
||||||
retval["type"] = "Dimmable light"
|
retval["type"] = "Dimmable light"
|
||||||
retval["modelid"] = "HASS123"
|
retval["modelid"] = "HASS123"
|
||||||
retval["state"].update({HUE_API_STATE_BRI: HUE_API_STATE_BRI_MAX})
|
json_state.update({HUE_API_STATE_BRI: HUE_API_STATE_BRI_MAX})
|
||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
@ -835,8 +846,8 @@ def create_list_of_entities(config: Config, request: web.Request) -> dict[str, A
|
|||||||
"""Create a list of all entities."""
|
"""Create a list of all entities."""
|
||||||
hass: core.HomeAssistant = request.app["hass"]
|
hass: core.HomeAssistant = request.app["hass"]
|
||||||
json_response: dict[str, Any] = {
|
json_response: dict[str, Any] = {
|
||||||
config.entity_id_to_number(entity.entity_id): entity_to_json(config, entity)
|
config.entity_id_to_number(entity.entity_id): state_to_json(config, entity)
|
||||||
for entity in config.filter_exposed_entities(hass.states.async_all())
|
for entity in config.filter_exposed_states(hass.states.async_all())
|
||||||
}
|
}
|
||||||
return json_response
|
return json_response
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user