Tweak Matter discovery to ignore empty lists (#136854)

This commit is contained in:
Marcel van der Veldt 2025-01-29 17:41:28 +01:00 committed by GitHub
parent acbf40c384
commit 72caf9d5a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 119 additions and 138 deletions

View File

@ -19,7 +19,7 @@ from .event import DISCOVERY_SCHEMAS as EVENT_SCHEMAS
from .fan import DISCOVERY_SCHEMAS as FAN_SCHEMAS
from .light import DISCOVERY_SCHEMAS as LIGHT_SCHEMAS
from .lock import DISCOVERY_SCHEMAS as LOCK_SCHEMAS
from .models import MatterDiscoverySchema, MatterEntityInfo
from .models import UNSET, MatterDiscoverySchema, MatterEntityInfo
from .number import DISCOVERY_SCHEMAS as NUMBER_SCHEMAS
from .select import DISCOVERY_SCHEMAS as SELECT_SCHEMAS
from .sensor import DISCOVERY_SCHEMAS as SENSOR_SCHEMAS
@ -67,6 +67,8 @@ def async_discover_entities(
if any(x in schema.required_attributes for x in discovered_attributes):
continue
primary_attribute = schema.required_attributes[0]
# check vendor_id
if (
schema.vendor_id is not None
@ -121,31 +123,6 @@ def async_discover_entities(
):
continue
# check if value exists but is none/null
if not schema.allow_none_value and any(
endpoint.get_attribute_value(None, val_schema) in (None, NullValue)
for val_schema in schema.required_attributes
):
continue
# check for required value in (primary) attribute
primary_attribute = schema.required_attributes[0]
primary_value = endpoint.get_attribute_value(None, primary_attribute)
if schema.value_contains is not None and (
isinstance(primary_value, list)
and schema.value_contains not in primary_value
):
continue
# check for value that may not be present
if schema.value_is_not is not None and (
schema.value_is_not == primary_value
or (
isinstance(primary_value, list) and schema.value_is_not in primary_value
)
):
continue
# check for required value in cluster featuremap
if schema.featuremap_contains is not None and (
not bool(
@ -159,6 +136,61 @@ def async_discover_entities(
):
continue
# BEGIN checks on actual attribute values
# these are the least likely to be used and least efficient, so they are checked last
# check if PRIMARY value exists but is none/null
if not schema.allow_none_value and any(
endpoint.get_attribute_value(None, val_schema) in (None, NullValue)
for val_schema in schema.required_attributes
):
continue
# check for required value in PRIMARY attribute
primary_value = endpoint.get_attribute_value(None, primary_attribute)
if schema.value_contains is not UNSET and (
isinstance(primary_value, list)
and schema.value_contains not in primary_value
):
continue
# check for value that may not be present in PRIMARY attribute
if schema.value_is_not is not UNSET and (
schema.value_is_not == primary_value
or (
isinstance(primary_value, list) and schema.value_is_not in primary_value
)
):
continue
# check for value that may not be present in SECONDARY attribute
secondary_attribute = (
schema.required_attributes[1]
if len(schema.required_attributes) > 1
else None
)
secondary_value = (
endpoint.get_attribute_value(None, secondary_attribute)
if secondary_attribute
else None
)
if schema.secondary_value_is_not is not UNSET and (
(schema.secondary_value_is_not == secondary_value)
or (
isinstance(secondary_value, list)
and schema.secondary_value_is_not in secondary_value
)
):
continue
# check for required value in SECONDARY attribute
if schema.secondary_value_contains is not UNSET and (
isinstance(secondary_value, list)
and schema.secondary_value_contains not in secondary_value
):
continue
# FINISH all validation checks
# all checks passed, this value belongs to an entity
attributes_to_watch = list(schema.required_attributes)

View File

@ -18,6 +18,14 @@ type SensorValueTypes = type[
]
# A sentinel object to detect if a parameter is supplied or not.
class _UNSET_TYPE:
pass
UNSET = _UNSET_TYPE()
class MatterDeviceInfo(TypedDict):
"""Dictionary with Matter Device info.
@ -111,16 +119,6 @@ class MatterDiscoverySchema:
# are not discovered by other entities
optional_attributes: tuple[type[ClusterAttributeDescriptor], ...] | None = None
# [optional] the primary attribute value must contain this value
# for example for the AcceptedCommandList
# NOTE: only works for list values
value_contains: Any | None = None
# [optional] the primary attribute value must NOT have this value
# for example to filter out invalid values (such as empty string instead of null)
# in case of a list value, the list may not contain this value
value_is_not: Any | None = None
# [optional] the primary attribute's cluster featuremap must contain this value
# for example for the DoorSensor on a DoorLock Cluster
featuremap_contains: int | None = None
@ -131,3 +129,22 @@ class MatterDiscoverySchema:
# [optional] the primary attribute value may not be null/None
allow_none_value: bool = False
# [optional] the primary attribute value must contain this value
# for example for the AcceptedCommandList
# NOTE: only works for list values
value_contains: Any = UNSET
# [optional] the secondary (required) attribute value must contain this value
# for example for the AcceptedCommandList
# NOTE: only works for list values
secondary_value_contains: Any = UNSET
# [optional] the primary attribute value must NOT have this value
# for example to filter out invalid values (such as empty string instead of null)
# in case of a list value, the list may not contain this value
value_is_not: Any = UNSET
# [optional] the secondary (required) attribute value must NOT have this value
# for example to filter out empty lists in list sensor values
secondary_value_is_not: Any = UNSET

View File

@ -217,6 +217,8 @@ DISCOVERY_SCHEMAS = [
clusters.ModeSelect.Attributes.CurrentMode,
clusters.ModeSelect.Attributes.SupportedModes,
),
# don't discover this entry if the supported modes list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SELECT,
@ -229,6 +231,8 @@ DISCOVERY_SCHEMAS = [
clusters.OvenMode.Attributes.CurrentMode,
clusters.OvenMode.Attributes.SupportedModes,
),
# don't discover this entry if the supported modes list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SELECT,
@ -241,6 +245,8 @@ DISCOVERY_SCHEMAS = [
clusters.LaundryWasherMode.Attributes.CurrentMode,
clusters.LaundryWasherMode.Attributes.SupportedModes,
),
# don't discover this entry if the supported modes list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SELECT,
@ -253,6 +259,8 @@ DISCOVERY_SCHEMAS = [
clusters.RefrigeratorAndTemperatureControlledCabinetMode.Attributes.CurrentMode,
clusters.RefrigeratorAndTemperatureControlledCabinetMode.Attributes.SupportedModes,
),
# don't discover this entry if the supported modes list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SELECT,
@ -265,6 +273,8 @@ DISCOVERY_SCHEMAS = [
clusters.RvcCleanMode.Attributes.CurrentMode,
clusters.RvcCleanMode.Attributes.SupportedModes,
),
# don't discover this entry if the supported modes list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SELECT,
@ -277,6 +287,8 @@ DISCOVERY_SCHEMAS = [
clusters.DishwasherMode.Attributes.CurrentMode,
clusters.DishwasherMode.Attributes.SupportedModes,
),
# don't discover this entry if the supported modes list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SELECT,
@ -289,6 +301,8 @@ DISCOVERY_SCHEMAS = [
clusters.EnergyEvseMode.Attributes.CurrentMode,
clusters.EnergyEvseMode.Attributes.SupportedModes,
),
# don't discover this entry if the supported modes list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SELECT,
@ -301,6 +315,8 @@ DISCOVERY_SCHEMAS = [
clusters.DeviceEnergyManagementMode.Attributes.CurrentMode,
clusters.DeviceEnergyManagementMode.Attributes.SupportedModes,
),
# don't discover this entry if the supported modes list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SELECT,
@ -384,6 +400,8 @@ DISCOVERY_SCHEMAS = [
clusters.TemperatureControl.Attributes.SelectedTemperatureLevel,
clusters.TemperatureControl.Attributes.SupportedTemperatureLevels,
),
# don't discover this entry if the supported levels list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SELECT,
@ -397,6 +415,8 @@ DISCOVERY_SCHEMAS = [
clusters.LaundryWasherControls.Attributes.SpinSpeedCurrent,
clusters.LaundryWasherControls.Attributes.SpinSpeeds,
),
# don't discover this entry if the spinspeeds list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SELECT,
@ -412,5 +432,7 @@ DISCOVERY_SCHEMAS = [
clusters.LaundryWasherControls.Attributes.NumberOfRinses,
clusters.LaundryWasherControls.Attributes.SupportedRinses,
),
# don't discover this entry if the supported rinses list is empty
secondary_value_is_not=[],
),
]

View File

@ -809,6 +809,8 @@ DISCOVERY_SCHEMAS = [
clusters.OperationalState.Attributes.OperationalState,
clusters.OperationalState.Attributes.OperationalStateList,
),
# don't discover this entry if the supported state list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
@ -822,6 +824,8 @@ DISCOVERY_SCHEMAS = [
clusters.OperationalState.Attributes.CurrentPhase,
clusters.OperationalState.Attributes.PhaseList,
),
# don't discover this entry if the supported state list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
@ -835,6 +839,8 @@ DISCOVERY_SCHEMAS = [
clusters.RvcOperationalState.Attributes.CurrentPhase,
clusters.RvcOperationalState.Attributes.PhaseList,
),
# don't discover this entry if the supported state list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
@ -848,6 +854,8 @@ DISCOVERY_SCHEMAS = [
clusters.OvenCavityOperationalState.Attributes.CurrentPhase,
clusters.OvenCavityOperationalState.Attributes.PhaseList,
),
# don't discover this entry if the supported state list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
@ -877,6 +885,8 @@ DISCOVERY_SCHEMAS = [
clusters.RvcOperationalState.Attributes.OperationalStateList,
),
allow_multi=True, # also used for vacuum entity
# don't discover this entry if the supported state list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
@ -891,5 +901,7 @@ DISCOVERY_SCHEMAS = [
clusters.OvenCavityOperationalState.Attributes.OperationalState,
clusters.OvenCavityOperationalState.Attributes.OperationalStateList,
),
# don't discover this entry if the supported state list is empty
secondary_value_is_not=[],
),
]

View File

@ -1518,108 +1518,6 @@
'state': 'previous',
})
# ---
# name: test_selects[silabs_dishwasher][select.dishwasher_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.dishwasher_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Mode',
'platform': 'matter',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'mode',
'unique_id': '00000000000004D2-0000000000000036-MatterNodeDevice-1-MatterDishwasherMode-89-1',
'unit_of_measurement': None,
})
# ---
# name: test_selects[silabs_dishwasher][select.dishwasher_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Dishwasher Mode',
'options': list([
]),
}),
'context': <ANY>,
'entity_id': 'select.dishwasher_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_selects[silabs_laundrywasher][select.laundrywasher_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.laundrywasher_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Mode',
'platform': 'matter',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'mode',
'unique_id': '00000000000004D2-000000000000001D-MatterNodeDevice-1-MatterLaundryWasherMode-81-1',
'unit_of_measurement': None,
})
# ---
# name: test_selects[silabs_laundrywasher][select.laundrywasher_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'LaundryWasher Mode',
'options': list([
]),
}),
'context': <ANY>,
'entity_id': 'select.laundrywasher_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_selects[silabs_laundrywasher][select.laundrywasher_number_of_rinses-entry]
EntityRegistryEntrySnapshot({
'aliases': set({