Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions homeassistant/components/zha/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,10 @@ def create_entity(

Return entity if it is a supported configuration, otherwise return None
"""
platform_restrictions = ZHA_ENTITIES.single_device_matches[Platform.BUTTON]
device_restrictions = platform_restrictions[zha_device.ieee]
if CHANNEL_IDENTIFY in device_restrictions:
if ZHA_ENTITIES.prevent_entity_creation(
Platform.BUTTON, zha_device.ieee, CHANNEL_IDENTIFY
):
return None
device_restrictions.append(CHANNEL_IDENTIFY)
return cls(unique_id, zha_device, channels, **kwargs)

_attr_device_class: ButtonDeviceClass = ButtonDeviceClass.UPDATE
Expand Down
9 changes: 9 additions & 0 deletions homeassistant/components/zha/core/registries.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,15 @@ def decorator(zha_ent: CALLABLE_T) -> CALLABLE_T:

return decorator

def prevent_entity_creation(self, platform: Platform, ieee: EUI64, key: str):
"""Return True if the entity should not be created."""
platform_restrictions = self.single_device_matches[platform]
device_restrictions = platform_restrictions[ieee]
if key in device_restrictions:
return True
device_restrictions.append(key)
return False

def clean_up(self) -> None:
"""Clean up post discovery."""
self.single_device_matches: dict[
Expand Down
12 changes: 6 additions & 6 deletions homeassistant/components/zha/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,16 @@
class BaseZhaEntity(LogMixin, entity.Entity):
"""A base class for ZHA entities."""

_unique_id_suffix: str | None = None
unique_id_suffix: str | None = None

def __init__(self, unique_id: str, zha_device: ZhaDeviceType, **kwargs) -> None:
"""Init ZHA entity."""
self._name: str = ""
self._force_update: bool = False
self._should_poll: bool = False
self._unique_id: str = unique_id
if self._unique_id_suffix:
self._unique_id += f"-{self._unique_id_suffix}"
if self.unique_id_suffix:
self._unique_id += f"-{self.unique_id_suffix}"
self._state: Any = None
self._extra_state_attributes: dict[str, Any] = {}
self._zha_device: ZhaDeviceType = zha_device
Expand Down Expand Up @@ -154,7 +154,7 @@ def __init_subclass__(cls, id_suffix: str | None = None, **kwargs) -> None:
"""
super().__init_subclass__(**kwargs)
if id_suffix:
cls._unique_id_suffix = id_suffix
cls.unique_id_suffix = id_suffix

def __init__(
self,
Expand All @@ -169,8 +169,8 @@ def __init__(
ch_names = [ch.cluster.ep_attribute for ch in channels]
ch_names = ", ".join(sorted(ch_names))
self._name: str = f"{zha_device.name} {ieeetail} {ch_names}"
if self._unique_id_suffix:
self._name += f" {self._unique_id_suffix}"
if self.unique_id_suffix:
self._name += f" {self.unique_id_suffix}"
self.cluster_channels: dict[str, ChannelType] = {}
for channel in channels:
self.cluster_channels[channel.name] = channel
Expand Down
44 changes: 44 additions & 0 deletions homeassistant/components/zha/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
ELECTRIC_CURRENT_AMPERE,
ELECTRIC_POTENTIAL_VOLT,
ENERGY_KILO_WATT_HOUR,
ENTITY_CATEGORY_DIAGNOSTIC,
LIGHT_LUX,
PERCENTAGE,
POWER_VOLT_AMPERE,
Expand All @@ -50,6 +51,7 @@
from .core import discovery
from .core.const import (
CHANNEL_ANALOG_INPUT,
CHANNEL_BASIC,
CHANNEL_ELECTRICAL_MEASUREMENT,
CHANNEL_HUMIDITY,
CHANNEL_ILLUMINANCE,
Expand Down Expand Up @@ -675,3 +677,45 @@ def _rm_rs_action(self) -> str | None:
):
return CURRENT_HVAC_IDLE
return CURRENT_HVAC_OFF


@MULTI_MATCH(channel_names=CHANNEL_BASIC)
class RSSISensor(Sensor, id_suffix="rssi"):
"""RSSI sensor for a device."""

_state_class: SensorStateClass = SensorStateClass.MEASUREMENT
_device_class: SensorDeviceClass = SensorDeviceClass.SIGNAL_STRENGTH
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
_attr_entity_registry_enabled_default = False

@classmethod
def create_entity(
cls,
unique_id: str,
zha_device: ZhaDeviceType,
channels: list[ChannelType],
**kwargs,
) -> ZhaEntity | None:
"""Entity Factory.

Return entity if it is a supported configuration, otherwise return None
"""
key = f"{CHANNEL_BASIC}_{cls.unique_id_suffix}"
if ZHA_ENTITIES.prevent_entity_creation(Platform.SENSOR, zha_device.ieee, key):
return None
return cls(unique_id, zha_device, channels, **kwargs)

@property
def native_value(self) -> StateType:
"""Return the state of the entity."""
return getattr(self._zha_device.device, self.unique_id_suffix)

@property
def should_poll(self) -> bool:
"""Poll the entity for current state."""
return True


@MULTI_MATCH(channel_names=CHANNEL_BASIC)
class LQISensor(RSSISensor, id_suffix="lqi"):
"""LQI sensor for a device."""
10 changes: 9 additions & 1 deletion tests/components/zha/test_discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import re
from unittest import mock
from unittest.mock import AsyncMock, patch
from unittest.mock import AsyncMock, Mock, patch

import pytest
from zigpy.const import SIG_ENDPOINTS, SIG_MANUFACTURER, SIG_MODEL, SIG_NODE_DESC
Expand Down Expand Up @@ -70,6 +70,14 @@ def _mock(
"zigpy.zcl.clusters.general.Identify.request",
new=AsyncMock(return_value=[mock.sentinel.data, zcl_f.Status.SUCCESS]),
)
# We do this here because we are testing ZHA discovery logic. Point being we want to ensure that
# all discovered entities are dispatched for creation. In order to test this we need the entities
# added to HA. So we ensure that they are all enabled even though they won't necessarily be in reality
# at runtime
@patch(
"homeassistant.components.zha.entity.ZhaEntity.entity_registry_enabled_default",
new=Mock(return_value=True),
)
@pytest.mark.parametrize("device", DEVICES)
async def test_devices(
device,
Expand Down
Loading