Rework with mixin - Light only

This commit is contained in:
jbouwh
2025-10-15 21:44:42 +00:00
parent 003c8feae7
commit ba6f0dba54
7 changed files with 16 additions and 72 deletions

View File

@@ -15,6 +15,7 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TOGGLE, SERVICE_TOGGLE,
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
SERVICE_TURN_ON, SERVICE_TURN_ON,
@@ -1336,6 +1337,9 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
if color_mode: if color_mode:
data.update(self._light_internal_convert_color(color_mode)) data.update(self._light_internal_convert_color(color_mode))
if included_entities := getattr(self, "included_entities", None):
data[ATTR_ENTITY_ID] = included_entities
return data return data
@property @property

View File

@@ -13,7 +13,6 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_CONFIGURATION_URL, ATTR_CONFIGURATION_URL,
ATTR_ENTITY_ID,
ATTR_HW_VERSION, ATTR_HW_VERSION,
ATTR_MANUFACTURER, ATTR_MANUFACTURER,
ATTR_MODEL, ATTR_MODEL,
@@ -33,13 +32,7 @@ from homeassistant.const import (
CONF_URL, CONF_URL,
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE,
) )
from homeassistant.core import ( from homeassistant.core import Event, HassJobType, HomeAssistant, callback
CALLBACK_TYPE,
Event,
HassJobType,
HomeAssistant,
callback,
)
from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import ( from homeassistant.helpers.device_registry import (
DeviceEntry, DeviceEntry,
@@ -50,7 +43,11 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_connect,
async_dispatcher_send, async_dispatcher_send,
) )
from homeassistant.helpers.entity import Entity, async_generate_entity_id from homeassistant.helpers.entity import (
Entity,
IncludedEntitiesMixin,
async_generate_entity_id,
)
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.event import ( from homeassistant.helpers.event import (
async_track_device_registry_updated_event, async_track_device_registry_updated_event,
@@ -471,78 +468,28 @@ def async_setup_entity_entry_helper(
_async_setup_entities() _async_setup_entities()
class MqttAttributesMixin(Entity): class MqttAttributesMixin(IncludedEntitiesMixin):
"""Mixin used for platforms that support JSON attributes.""" """Mixin used for platforms that support JSON attributes."""
_attributes_extra_blocked: frozenset[str] = frozenset() _attributes_extra_blocked: frozenset[str] = frozenset()
_attr_tpl: Callable[[ReceivePayloadType], ReceivePayloadType] | None = None _attr_tpl: Callable[[ReceivePayloadType], ReceivePayloadType] | None = None
_default_group_icon: str | None = None
_group_entity_ids: list[str] | None = None
_message_callback: Callable[ _message_callback: Callable[
[MessageCallbackType, set[str] | None, ReceiveMessage], None [MessageCallbackType, set[str] | None, ReceiveMessage], None
] ]
_process_update_extra_state_attributes: Callable[[dict[str, Any]], None] _process_update_extra_state_attributes: Callable[[dict[str, Any]], None]
_monitor_member_updates_callback: CALLBACK_TYPE
def __init__(self, config: ConfigType) -> None: def __init__(self, config: ConfigType) -> None:
"""Initialize the JSON attributes mixin.""" """Initialize the JSON attributes mixin."""
self._attributes_sub_state: dict[str, EntitySubscription] = {} self._attributes_sub_state: dict[str, EntitySubscription] = {}
self._attributes_config = config self._attributes_config = config
def _monitor_member_updates(self) -> None:
"""Update the group members if the entity registry is updated."""
entity_registry = er.async_get(self.hass)
async def _handle_entity_registry_updated(event: Event[Any]) -> None:
"""Handle registry update event."""
if (
event.data["action"] in {"create", "update"}
and (entry := entity_registry.async_get(event.data["entity_id"]))
and entry.unique_id in self._attributes_config[CONF_GROUP]
) or (
event.data["action"] == "remove"
and self._group_entity_ids is not None
and event.data["entity_id"] in self._group_entity_ids
):
self._update_group_entity_ids()
self._attr_extra_state_attributes[ATTR_ENTITY_ID] = (
self._group_entity_ids
)
self.async_write_ha_state()
self.async_on_remove(
self.hass.bus.async_listen(
er.EVENT_ENTITY_REGISTRY_UPDATED,
_handle_entity_registry_updated,
)
)
def _update_group_entity_ids(self) -> None:
"""Set the entity_id property if the entity represents a group of entities.
Setting entity_id in the extra state attributes will show the discovered entity
as a group and will show the member entities in the UI.
"""
if CONF_GROUP not in self._attributes_config:
self._default_entity_icon = None
return
self._attr_icon = self._attr_icon or self._default_group_icon
entity_registry = er.async_get(self.hass)
self._group_entity_ids = []
for resource_id in self._attributes_config[CONF_GROUP]:
if entity_id := entity_registry.async_get_entity_id(
self.entity_id.split(".")[0], DOMAIN, resource_id
):
self._group_entity_ids.append(entity_id)
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Subscribe MQTT events.""" """Subscribe MQTT events."""
await super().async_added_to_hass() await super().async_added_to_hass()
self._update_group_entity_ids() if CONF_GROUP in self._attributes_config:
if self._group_entity_ids is not None: self.async_set_included_entities(
self._monitor_member_updates() DOMAIN, self._attributes_config[CONF_GROUP]
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: self._group_entity_ids} )
self._attributes_prepare_subscribe_topics() self._attributes_prepare_subscribe_topics()
self._attributes_subscribe_topics() self._attributes_subscribe_topics()
@@ -616,8 +563,6 @@ class MqttAttributesMixin(Entity):
if k not in MQTT_ATTRIBUTES_BLOCKED if k not in MQTT_ATTRIBUTES_BLOCKED
and k not in self._attributes_extra_blocked and k not in self._attributes_extra_blocked
} }
if self._group_entity_ids is not None:
filtered_dict[ATTR_ENTITY_ID] = self._group_entity_ids
if hasattr(self, "_process_update_extra_state_attributes"): if hasattr(self, "_process_update_extra_state_attributes"):
self._process_update_extra_state_attributes(filtered_dict) self._process_update_extra_state_attributes(filtered_dict)
else: else:

View File

@@ -239,7 +239,6 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
"""Representation of a MQTT light.""" """Representation of a MQTT light."""
_default_name = DEFAULT_NAME _default_name = DEFAULT_NAME
_default_group_icon = "mdi:lightbulb-group"
_entity_id_format = ENTITY_ID_FORMAT _entity_id_format = ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED
_topic: dict[str, str | None] _topic: dict[str, str | None]

View File

@@ -164,7 +164,6 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
"""Representation of a MQTT JSON light.""" """Representation of a MQTT JSON light."""
_default_name = DEFAULT_NAME _default_name = DEFAULT_NAME
_default_group_icon = "mdi:lightbulb-group"
_entity_id_format = ENTITY_ID_FORMAT _entity_id_format = ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED

View File

@@ -121,7 +121,6 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity):
"""Representation of a MQTT Template light.""" """Representation of a MQTT Template light."""
_default_name = DEFAULT_NAME _default_name = DEFAULT_NAME
_default_group_icon = "mdi:lightbulb-group"
_entity_id_format = ENTITY_ID_FORMAT _entity_id_format = ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED
_optimistic: bool _optimistic: bool

View File

@@ -16,6 +16,7 @@ from propcache.api import cached_property
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( # noqa: F401 from homeassistant.const import ( # noqa: F401
ATTR_ENTITY_ID,
ATTR_UNIT_OF_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT,
CONF_UNIT_OF_MEASUREMENT, CONF_UNIT_OF_MEASUREMENT,
EntityCategory, EntityCategory,

View File

@@ -1917,7 +1917,6 @@ async def test_light_group_discovery_members_before_group(
group_state = hass.states.get("light.group") group_state = hass.states.get("light.group")
assert group_state is not None assert group_state is not None
assert group_state.attributes.get("entity_id") == ["light.member1", "light.member2"] assert group_state.attributes.get("entity_id") == ["light.member1", "light.member2"]
assert group_state.attributes.get("icon") == "mdi:lightbulb-group"
async def test_light_group_discovery_group_before_members( async def test_light_group_discovery_group_before_members(
@@ -1950,7 +1949,6 @@ async def test_light_group_discovery_group_before_members(
group_state = hass.states.get("light.group") group_state = hass.states.get("light.group")
assert group_state is not None assert group_state is not None
assert group_state.attributes.get("entity_id") == ["light.member1", "light.member2"] assert group_state.attributes.get("entity_id") == ["light.member1", "light.member2"]
assert group_state.attributes.get("icon") == "mdi:lightbulb-group"
# Remove member 1 # Remove member 1
async_fire_mqtt_message(hass, GROUP_MEMBER_1_TOPIC, "") async_fire_mqtt_message(hass, GROUP_MEMBER_1_TOPIC, "")
@@ -2209,7 +2207,6 @@ async def test_setting_attribute_via_mqtt_json_message_light_group(
assert state and state.attributes.get("val") == "100" assert state and state.attributes.get("val") == "100"
assert state.attributes.get("entity_id") == ["light.member_1", "light.member_2"] assert state.attributes.get("entity_id") == ["light.member_1", "light.member_2"]
assert state.attributes.get("icon") == "mdi:lightbulb-group"
async def test_setting_blocked_attribute_via_mqtt_json_message( async def test_setting_blocked_attribute_via_mqtt_json_message(