12
12
ClusterObjectDescriptor ,
13
13
ClusterObjectFieldDescriptor ,
14
14
)
15
+ from chip .clusters .Objects import BasicInformation , ElectricalPowerMeasurement
15
16
from chip .tlv import float32 , uint
16
17
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
+ )
18
22
19
23
if TYPE_CHECKING :
20
24
from matter_server .common .models import MatterNodeData
27
31
ALL_CUSTOM_CLUSTERS : dict [int , Cluster ] = {}
28
32
ALL_CUSTOM_ATTRIBUTES : dict [int , dict [int , ClusterAttributeDescriptor ]] = {}
29
33
34
+ VENDOR_ID_EVE = 4874
35
+
30
36
31
37
@dataclass
32
38
class CustomClusterMixin :
33
39
"""Base model for a vendor specific custom cluster."""
34
40
35
41
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
37
47
38
48
def __init_subclass__ (cls : Cluster , * args , ** kwargs ) -> None :
39
49
"""Register a subclass."""
@@ -45,7 +55,10 @@ def __init_subclass__(cls: Cluster, *args, **kwargs) -> None:
45
55
class CustomClusterAttributeMixin :
46
56
"""Base model for a vendor specific custom cluster attribute."""
47
57
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
49
62
50
63
def __init_subclass__ (cls : ClusterAttributeDescriptor , * args , ** kwargs ) -> None :
51
64
"""Register a subclass."""
@@ -55,6 +68,23 @@ def __init_subclass__(cls: ClusterAttributeDescriptor, *args, **kwargs) -> None:
55
68
ALL_CUSTOM_ATTRIBUTES [cls .cluster_id ][cls .attribute_id ] = cls
56
69
57
70
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
+
58
88
@dataclass
59
89
class EveCluster (Cluster , CustomClusterMixin ):
60
90
"""Custom (vendor-specific) cluster for Eve - Vendor ID 4874 (0x130a)."""
@@ -117,8 +147,6 @@ class Attributes:
117
147
class TimesOpened (ClusterAttributeDescriptor , CustomClusterAttributeMixin ):
118
148
"""TimesOpened Attribute within the Eve Cluster."""
119
149
120
- should_poll = True
121
-
122
150
@ChipUtility .classproperty
123
151
def cluster_id (cls ) -> int :
124
152
"""Return cluster id."""
@@ -140,7 +168,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
140
168
class Watt (ClusterAttributeDescriptor , CustomClusterAttributeMixin ):
141
169
"""Watt Attribute within the Eve Cluster."""
142
170
143
- should_poll = True
171
+ should_poll = should_poll_eve_energy
144
172
145
173
@ChipUtility .classproperty
146
174
def cluster_id (cls ) -> int :
@@ -163,7 +191,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
163
191
class WattAccumulated (ClusterAttributeDescriptor , CustomClusterAttributeMixin ):
164
192
"""WattAccumulated Attribute within the Eve Cluster."""
165
193
166
- should_poll = True
194
+ should_poll = should_poll_eve_energy
167
195
168
196
@ChipUtility .classproperty
169
197
def cluster_id (cls ) -> int :
@@ -188,7 +216,7 @@ class WattAccumulatedControlPoint(
188
216
):
189
217
"""wattAccumulatedControlPoint Attribute within the Eve Cluster."""
190
218
191
- should_poll = True
219
+ should_poll = should_poll_eve_energy
192
220
193
221
@ChipUtility .classproperty
194
222
def cluster_id (cls ) -> int :
@@ -211,7 +239,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
211
239
class Voltage (ClusterAttributeDescriptor , CustomClusterAttributeMixin ):
212
240
"""Voltage Attribute within the Eve Cluster."""
213
241
214
- should_poll = True
242
+ should_poll = should_poll_eve_energy
215
243
216
244
@ChipUtility .classproperty
217
245
def cluster_id (cls ) -> int :
@@ -234,7 +262,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
234
262
class Current (ClusterAttributeDescriptor , CustomClusterAttributeMixin ):
235
263
"""Current Attribute within the Eve Cluster."""
236
264
237
- should_poll = True
265
+ should_poll = should_poll_eve_energy
238
266
239
267
@ChipUtility .classproperty
240
268
def cluster_id (cls ) -> int :
@@ -299,8 +327,6 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
299
327
class ValvePosition (ClusterAttributeDescriptor , CustomClusterAttributeMixin ):
300
328
"""ValvePosition Attribute within the Eve Cluster."""
301
329
302
- should_poll = True
303
-
304
330
@ChipUtility .classproperty
305
331
def cluster_id (cls ) -> int :
306
332
"""Return cluster id."""
@@ -324,8 +350,6 @@ class MotionSensitivity(
324
350
):
325
351
"""MotionSensitivity Attribute within the Eve Cluster."""
326
352
327
- should_poll = False
328
-
329
353
@ChipUtility .classproperty
330
354
def cluster_id (cls ) -> int :
331
355
"""Return cluster id."""
@@ -558,12 +582,12 @@ def check_polled_attributes(node_data: MatterNodeData) -> set[str]:
558
582
endpoint_id , cluster_id , attribute_id = parse_attribute_path (attr_path )
559
583
if not (custom_cluster := ALL_CUSTOM_CLUSTERS .get (cluster_id )):
560
584
continue
561
- if custom_cluster .should_poll :
585
+ if custom_cluster .should_poll ( node_data ) :
562
586
# the entire cluster needs to be polled
563
587
attributes_to_poll .add (f"{ endpoint_id } /{ cluster_id } /*" )
564
588
continue
565
589
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 ) :
567
591
# this attribute needs to be polled
568
592
attributes_to_poll .add (attr_path )
569
593
return attributes_to_poll
0 commit comments