diff --git a/zha/zigbee/device.py b/zha/zigbee/device.py index 61f326690..8fe5da88f 100644 --- a/zha/zigbee/device.py +++ b/zha/zigbee/device.py @@ -976,6 +976,51 @@ def _discover_new_entities(self) -> None: entity.on_add() self._pending_entities.append(entity) + @staticmethod + def _entity_class_path(entity: BaseEntity) -> tuple[str, str]: + """Return module and class name for an entity.""" + return (entity.__class__.__module__, entity.__class__.__name__) + + @staticmethod + def _entity_endpoint_id(entity: BaseEntity) -> int | None: + """Return endpoint id if this is a platform entity.""" + if not isinstance(entity, PlatformEntity): + return None + return entity.endpoint.id + + @staticmethod + def _entity_cluster_handler_names(entity: BaseEntity) -> list[str]: + """Return cluster handler names if this is a platform entity.""" + if not isinstance(entity, PlatformEntity): + return [] + return sorted(entity.cluster_handlers.keys()) + + def _log_entity_unique_id_conflict( + self, + key: tuple[Platform, str], + existing_entity: BaseEntity, + duplicate_entity: BaseEntity, + ) -> None: + """Log duplicate unique_id collisions when classes differ.""" + existing_class = self._entity_class_path(existing_entity) + duplicate_class = self._entity_class_path(duplicate_entity) + if existing_class == duplicate_class: + return + + _LOGGER.warning( + "Entity unique_id conflict on device %s for %s: keeping %s " + "(endpoint=%s, cluster_handlers=%s), dropping %s " + "(endpoint=%s, cluster_handlers=%s)", + self.ieee, + key, + ".".join(existing_class), + self._entity_endpoint_id(existing_entity), + self._entity_cluster_handler_names(existing_entity), + ".".join(duplicate_class), + self._entity_endpoint_id(duplicate_entity), + self._entity_cluster_handler_names(duplicate_entity), + ) + async def async_initialize(self, from_cache: bool = False) -> None: """Initialize cluster handlers.""" self.debug("started initialization") @@ -1012,6 +1057,7 @@ async def async_initialize(self, from_cache: bool = False) -> None: # Ignore entities that already exist if key in new_entities: + self._log_entity_unique_id_conflict(key, new_entities[key], entity) await entity.on_remove() continue