Skip to content

Commit 27ca3f5

Browse files
authored
Prevent Eve devices with newer firmware to be polled (#893)
1 parent ed4d17f commit 27ca3f5

File tree

1 file changed

+40
-16
lines changed

1 file changed

+40
-16
lines changed

matter_server/common/custom_clusters.py

+40-16
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@
1212
ClusterObjectDescriptor,
1313
ClusterObjectFieldDescriptor,
1414
)
15+
from chip.clusters.Objects import BasicInformation, ElectricalPowerMeasurement
1516
from chip.tlv import float32, uint
1617

17-
from matter_server.common.helpers.util import parse_attribute_path
18+
from matter_server.common.helpers.util import (
19+
create_attribute_path_from_attribute,
20+
parse_attribute_path,
21+
)
1822

1923
if TYPE_CHECKING:
2024
from matter_server.common.models import MatterNodeData
@@ -27,13 +31,19 @@
2731
ALL_CUSTOM_CLUSTERS: dict[int, Cluster] = {}
2832
ALL_CUSTOM_ATTRIBUTES: dict[int, dict[int, ClusterAttributeDescriptor]] = {}
2933

34+
VENDOR_ID_EVE = 4874
35+
3036

3137
@dataclass
3238
class CustomClusterMixin:
3339
"""Base model for a vendor specific custom cluster."""
3440

3541
id: ClassVar[int] # cluster id
36-
should_poll: bool = False # should the entire cluster be polled for state changes?
42+
43+
@staticmethod
44+
def should_poll(node_data: MatterNodeData) -> bool: # noqa: ARG004
45+
"""Check if the (entire) custom cluster should be polled for state changes."""
46+
return False
3747

3848
def __init_subclass__(cls: Cluster, *args, **kwargs) -> None:
3949
"""Register a subclass."""
@@ -45,7 +55,10 @@ def __init_subclass__(cls: Cluster, *args, **kwargs) -> None:
4555
class CustomClusterAttributeMixin:
4656
"""Base model for a vendor specific custom cluster attribute."""
4757

48-
should_poll: bool = False # should this attribute be polled ?
58+
@staticmethod
59+
def should_poll(node_data: MatterNodeData) -> bool: # noqa: ARG004
60+
"""Check if the custom attribute should be polled for state changes."""
61+
return False
4962

5063
def __init_subclass__(cls: ClusterAttributeDescriptor, *args, **kwargs) -> None:
5164
"""Register a subclass."""
@@ -55,6 +68,23 @@ def __init_subclass__(cls: ClusterAttributeDescriptor, *args, **kwargs) -> None:
5568
ALL_CUSTOM_ATTRIBUTES[cls.cluster_id][cls.attribute_id] = cls
5669

5770

71+
def should_poll_eve_energy(node_data: MatterNodeData) -> bool:
72+
"""Check if the (Eve Energy) custom attribute should be polled for state changes."""
73+
attr_path = create_attribute_path_from_attribute(
74+
0, BasicInformation.Attributes.VendorID
75+
)
76+
if node_data.attributes.get(attr_path) != VENDOR_ID_EVE:
77+
# Some implementation (such as MatterBridge) use the
78+
# Eve cluster to send the power measurements. Filter that out.
79+
return False
80+
# if the ElectricalPowerMeasurement cluster is NOT present,
81+
# we should poll the custom Eve cluster attribute(s).
82+
attr_path = create_attribute_path_from_attribute(
83+
2, ElectricalPowerMeasurement.Attributes.AttributeList
84+
)
85+
return node_data.attributes.get(attr_path) is None
86+
87+
5888
@dataclass
5989
class EveCluster(Cluster, CustomClusterMixin):
6090
"""Custom (vendor-specific) cluster for Eve - Vendor ID 4874 (0x130a)."""
@@ -117,8 +147,6 @@ class Attributes:
117147
class TimesOpened(ClusterAttributeDescriptor, CustomClusterAttributeMixin):
118148
"""TimesOpened Attribute within the Eve Cluster."""
119149

120-
should_poll = True
121-
122150
@ChipUtility.classproperty
123151
def cluster_id(cls) -> int:
124152
"""Return cluster id."""
@@ -140,7 +168,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
140168
class Watt(ClusterAttributeDescriptor, CustomClusterAttributeMixin):
141169
"""Watt Attribute within the Eve Cluster."""
142170

143-
should_poll = True
171+
should_poll = should_poll_eve_energy
144172

145173
@ChipUtility.classproperty
146174
def cluster_id(cls) -> int:
@@ -163,7 +191,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
163191
class WattAccumulated(ClusterAttributeDescriptor, CustomClusterAttributeMixin):
164192
"""WattAccumulated Attribute within the Eve Cluster."""
165193

166-
should_poll = True
194+
should_poll = should_poll_eve_energy
167195

168196
@ChipUtility.classproperty
169197
def cluster_id(cls) -> int:
@@ -188,7 +216,7 @@ class WattAccumulatedControlPoint(
188216
):
189217
"""wattAccumulatedControlPoint Attribute within the Eve Cluster."""
190218

191-
should_poll = True
219+
should_poll = should_poll_eve_energy
192220

193221
@ChipUtility.classproperty
194222
def cluster_id(cls) -> int:
@@ -211,7 +239,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
211239
class Voltage(ClusterAttributeDescriptor, CustomClusterAttributeMixin):
212240
"""Voltage Attribute within the Eve Cluster."""
213241

214-
should_poll = True
242+
should_poll = should_poll_eve_energy
215243

216244
@ChipUtility.classproperty
217245
def cluster_id(cls) -> int:
@@ -234,7 +262,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
234262
class Current(ClusterAttributeDescriptor, CustomClusterAttributeMixin):
235263
"""Current Attribute within the Eve Cluster."""
236264

237-
should_poll = True
265+
should_poll = should_poll_eve_energy
238266

239267
@ChipUtility.classproperty
240268
def cluster_id(cls) -> int:
@@ -299,8 +327,6 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
299327
class ValvePosition(ClusterAttributeDescriptor, CustomClusterAttributeMixin):
300328
"""ValvePosition Attribute within the Eve Cluster."""
301329

302-
should_poll = True
303-
304330
@ChipUtility.classproperty
305331
def cluster_id(cls) -> int:
306332
"""Return cluster id."""
@@ -324,8 +350,6 @@ class MotionSensitivity(
324350
):
325351
"""MotionSensitivity Attribute within the Eve Cluster."""
326352

327-
should_poll = False
328-
329353
@ChipUtility.classproperty
330354
def cluster_id(cls) -> int:
331355
"""Return cluster id."""
@@ -558,12 +582,12 @@ def check_polled_attributes(node_data: MatterNodeData) -> set[str]:
558582
endpoint_id, cluster_id, attribute_id = parse_attribute_path(attr_path)
559583
if not (custom_cluster := ALL_CUSTOM_CLUSTERS.get(cluster_id)):
560584
continue
561-
if custom_cluster.should_poll:
585+
if custom_cluster.should_poll(node_data):
562586
# the entire cluster needs to be polled
563587
attributes_to_poll.add(f"{endpoint_id}/{cluster_id}/*")
564588
continue
565589
custom_attribute = ALL_CUSTOM_ATTRIBUTES[cluster_id].get(attribute_id)
566-
if custom_attribute and custom_attribute.should_poll:
590+
if custom_attribute and custom_attribute.should_poll(node_data):
567591
# this attribute needs to be polled
568592
attributes_to_poll.add(attr_path)
569593
return attributes_to_poll

0 commit comments

Comments
 (0)