From ba6f0dba54d2db2760f3a91ea039aa1bb5d521dc Mon Sep 17 00:00:00 2001 From: jbouwh Date: Wed, 15 Oct 2025 21:44:42 +0000 Subject: [PATCH] Rework with mixin - Light only --- homeassistant/components/light/__init__.py | 4 + homeassistant/components/mqtt/entity.py | 77 +++---------------- .../components/mqtt/light/schema_basic.py | 1 - .../components/mqtt/light/schema_json.py | 1 - .../components/mqtt/light/schema_template.py | 1 - homeassistant/components/sensor/__init__.py | 1 + tests/components/mqtt/test_light_json.py | 3 - 7 files changed, 16 insertions(+), 72 deletions(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index d2869670ba4..ce62b6f4e01 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -15,6 +15,7 @@ import voluptuous as vol from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + ATTR_ENTITY_ID, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -1336,6 +1337,9 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): if 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 @property diff --git a/homeassistant/components/mqtt/entity.py b/homeassistant/components/mqtt/entity.py index 19c51e95e05..edb2951091a 100644 --- a/homeassistant/components/mqtt/entity.py +++ b/homeassistant/components/mqtt/entity.py @@ -13,7 +13,6 @@ import voluptuous as vol from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_CONFIGURATION_URL, - ATTR_ENTITY_ID, ATTR_HW_VERSION, ATTR_MANUFACTURER, ATTR_MODEL, @@ -33,13 +32,7 @@ from homeassistant.const import ( CONF_URL, CONF_VALUE_TEMPLATE, ) -from homeassistant.core import ( - CALLBACK_TYPE, - Event, - HassJobType, - HomeAssistant, - callback, -) +from homeassistant.core import Event, HassJobType, HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import ( DeviceEntry, @@ -50,7 +43,11 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, 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.event import ( async_track_device_registry_updated_event, @@ -471,78 +468,28 @@ def async_setup_entity_entry_helper( _async_setup_entities() -class MqttAttributesMixin(Entity): +class MqttAttributesMixin(IncludedEntitiesMixin): """Mixin used for platforms that support JSON attributes.""" _attributes_extra_blocked: frozenset[str] = frozenset() _attr_tpl: Callable[[ReceivePayloadType], ReceivePayloadType] | None = None - _default_group_icon: str | None = None - _group_entity_ids: list[str] | None = None _message_callback: Callable[ [MessageCallbackType, set[str] | None, ReceiveMessage], None ] _process_update_extra_state_attributes: Callable[[dict[str, Any]], None] - _monitor_member_updates_callback: CALLBACK_TYPE def __init__(self, config: ConfigType) -> None: """Initialize the JSON attributes mixin.""" self._attributes_sub_state: dict[str, EntitySubscription] = {} 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: """Subscribe MQTT events.""" await super().async_added_to_hass() - self._update_group_entity_ids() - if self._group_entity_ids is not None: - self._monitor_member_updates() - self._attr_extra_state_attributes = {ATTR_ENTITY_ID: self._group_entity_ids} + if CONF_GROUP in self._attributes_config: + self.async_set_included_entities( + DOMAIN, self._attributes_config[CONF_GROUP] + ) self._attributes_prepare_subscribe_topics() self._attributes_subscribe_topics() @@ -616,8 +563,6 @@ class MqttAttributesMixin(Entity): if k not in MQTT_ATTRIBUTES_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"): self._process_update_extra_state_attributes(filtered_dict) else: diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index a7a0fd718b8..61a55d64049 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -239,7 +239,6 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): """Representation of a MQTT light.""" _default_name = DEFAULT_NAME - _default_group_icon = "mdi:lightbulb-group" _entity_id_format = ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED _topic: dict[str, str | None] diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 2be78cb47fc..debc558dec5 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -164,7 +164,6 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): """Representation of a MQTT JSON light.""" _default_name = DEFAULT_NAME - _default_group_icon = "mdi:lightbulb-group" _entity_id_format = ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 17db1e36329..f561f15fb51 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -121,7 +121,6 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): """Representation of a MQTT Template light.""" _default_name = DEFAULT_NAME - _default_group_icon = "mdi:lightbulb-group" _entity_id_format = ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED _optimistic: bool diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index d6829d35329..9d947257aa9 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -16,6 +16,7 @@ from propcache.api import cached_property from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( # noqa: F401 + ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, CONF_UNIT_OF_MEASUREMENT, EntityCategory, diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 50d13781855..0cdf8b35d5f 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -1917,7 +1917,6 @@ async def test_light_group_discovery_members_before_group( group_state = hass.states.get("light.group") assert group_state is not None 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( @@ -1950,7 +1949,6 @@ async def test_light_group_discovery_group_before_members( group_state = hass.states.get("light.group") assert group_state is not None assert group_state.attributes.get("entity_id") == ["light.member1", "light.member2"] - assert group_state.attributes.get("icon") == "mdi:lightbulb-group" # Remove member 1 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.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(