matter-python-client 1.4.1a11.dev20261007__py3-none-any.whl → 1.5.0a0.dev20261013__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
chip/clusters/Objects.py CHANGED
@@ -97,6 +97,7 @@ __all__ = [
97
97
  "HepaFilterMonitoring",
98
98
  "IcdManagement",
99
99
  "Identify",
100
+ "IkeaValveCalibrationCluster",
100
101
  "IlluminanceMeasurement",
101
102
  "InovelliCluster",
102
103
  "JointFabricAdministrator",
@@ -12,7 +12,7 @@ from ...tlv import float32, uint
12
12
  from ..ClusterObjects import (Cluster, ClusterAttributeDescriptor, ClusterCommand, ClusterEvent, ClusterObject,
13
13
  ClusterObjectDescriptor, ClusterObjectFieldDescriptor)
14
14
  from ..Types import Nullable, NullValue
15
- from .ModeSelect import ModeSelect
15
+ from .Globals import Globals
16
16
 
17
17
 
18
18
  @dataclass
@@ -27,8 +27,8 @@ class AmbientContextSensing(Cluster):
27
27
  ClusterObjectFieldDescriptor(Label="objectIdentified", Tag=0x00000001, Type=typing.Optional[bool]),
28
28
  ClusterObjectFieldDescriptor(Label="audioContextDetected", Tag=0x00000002, Type=typing.Optional[bool]),
29
29
  ClusterObjectFieldDescriptor(Label="ambientContextType", Tag=0x00000003, Type=typing.Optional[typing.List[AmbientContextSensing.Structs.AmbientContextTypeStruct]]),
30
- ClusterObjectFieldDescriptor(Label="ambientContextTypeSupported", Tag=0x00000004, Type=typing.Optional[typing.List[ModeSelect.Structs.SemanticTagStruct]]),
31
- ClusterObjectFieldDescriptor(Label="objectCountReached", Tag=0x00000005, Type=typing.Optional[bool]),
30
+ ClusterObjectFieldDescriptor(Label="ambientContextTypeSupported", Tag=0x00000004, Type=typing.Optional[typing.List[Globals.Structs.semtag]]),
31
+ ClusterObjectFieldDescriptor(Label="objectCountThresholdReached", Tag=0x00000005, Type=typing.Optional[bool]),
32
32
  ClusterObjectFieldDescriptor(Label="objectCountConfig", Tag=0x00000006, Type=typing.Optional[AmbientContextSensing.Structs.ObjectCountConfigStruct]),
33
33
  ClusterObjectFieldDescriptor(Label="objectCount", Tag=0x00000007, Type=typing.Optional[uint]),
34
34
  ClusterObjectFieldDescriptor(Label="simultaneousDetectionLimit", Tag=0x00000008, Type=typing.Optional[uint]),
@@ -46,8 +46,8 @@ class AmbientContextSensing(Cluster):
46
46
  objectIdentified: typing.Optional[bool] = None
47
47
  audioContextDetected: typing.Optional[bool] = None
48
48
  ambientContextType: typing.Optional[typing.List[AmbientContextSensing.Structs.AmbientContextTypeStruct]] = None
49
- ambientContextTypeSupported: typing.Optional[typing.List[ModeSelect.Structs.SemanticTagStruct]] = None
50
- objectCountReached: typing.Optional[bool] = None
49
+ ambientContextTypeSupported: typing.Optional[typing.List[Globals.Structs.semtag]] = None
50
+ objectCountThresholdReached: typing.Optional[bool] = None
51
51
  objectCountConfig: typing.Optional[AmbientContextSensing.Structs.ObjectCountConfigStruct] = None
52
52
  objectCount: typing.Optional[uint] = None
53
53
  simultaneousDetectionLimit: typing.Optional[uint] = None
@@ -90,10 +90,10 @@ class AmbientContextSensing(Cluster):
90
90
  def descriptor(cls) -> ClusterObjectDescriptor:
91
91
  return ClusterObjectDescriptor(
92
92
  Fields=[
93
- ClusterObjectFieldDescriptor(Label="ambientContextSensed", Tag=0, Type=typing.Optional[typing.List[ModeSelect.Structs.SemanticTagStruct]]),
93
+ ClusterObjectFieldDescriptor(Label="ambientContextSensed", Tag=0, Type=typing.Optional[typing.List[Globals.Structs.semtag]]),
94
94
  ])
95
95
 
96
- ambientContextSensed: typing.Optional[typing.List[ModeSelect.Structs.SemanticTagStruct]] = None
96
+ ambientContextSensed: typing.Optional[typing.List[Globals.Structs.semtag]] = None
97
97
 
98
98
  @dataclass
99
99
  class ObjectCountConfigStruct(ClusterObject):
@@ -101,11 +101,11 @@ class AmbientContextSensing(Cluster):
101
101
  def descriptor(cls) -> ClusterObjectDescriptor:
102
102
  return ClusterObjectDescriptor(
103
103
  Fields=[
104
- ClusterObjectFieldDescriptor(Label="countingObject", Tag=0, Type=typing.Optional[ModeSelect.Structs.SemanticTagStruct]),
104
+ ClusterObjectFieldDescriptor(Label="countingObject", Tag=0, Type=typing.Optional[Globals.Structs.semtag]),
105
105
  ClusterObjectFieldDescriptor(Label="objectCountThreshold", Tag=1, Type=typing.Optional[uint]),
106
106
  ])
107
107
 
108
- countingObject: typing.Optional[ModeSelect.Structs.SemanticTagStruct] = None
108
+ countingObject: typing.Optional[Globals.Structs.semtag] = None
109
109
  objectCountThreshold: typing.Optional[uint] = None
110
110
 
111
111
  @dataclass
@@ -116,7 +116,7 @@ class AmbientContextSensing(Cluster):
116
116
  Fields=[
117
117
  ClusterObjectFieldDescriptor(Label="startTimestamp", Tag=0, Type=typing.Optional[uint]),
118
118
  ClusterObjectFieldDescriptor(Label="endTimestamp", Tag=1, Type=typing.Optional[uint]),
119
- ClusterObjectFieldDescriptor(Label="ambientContextType", Tag=2, Type=typing.Optional[typing.List[ModeSelect.Structs.SemanticTagStruct]]),
119
+ ClusterObjectFieldDescriptor(Label="ambientContextType", Tag=2, Type=typing.Optional[typing.List[Globals.Structs.semtag]]),
120
120
  ClusterObjectFieldDescriptor(Label="crowdDetected", Tag=3, Type=typing.Optional[bool]),
121
121
  ClusterObjectFieldDescriptor(Label="crowdCount", Tag=4, Type=typing.Optional[uint]),
122
122
  ClusterObjectFieldDescriptor(Label="confidence", Tag=5, Type=typing.Optional[uint]),
@@ -124,7 +124,7 @@ class AmbientContextSensing(Cluster):
124
124
 
125
125
  startTimestamp: typing.Optional[uint] = None
126
126
  endTimestamp: typing.Optional[uint] = None
127
- ambientContextType: typing.Optional[typing.List[ModeSelect.Structs.SemanticTagStruct]] = None
127
+ ambientContextType: typing.Optional[typing.List[Globals.Structs.semtag]] = None
128
128
  crowdDetected: typing.Optional[bool] = None
129
129
  crowdCount: typing.Optional[uint] = None
130
130
  confidence: typing.Optional[uint] = None
@@ -206,12 +206,12 @@ class AmbientContextSensing(Cluster):
206
206
 
207
207
  @ChipUtility.classproperty
208
208
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
209
- return ClusterObjectFieldDescriptor(Type=typing.Optional[typing.List[ModeSelect.Structs.SemanticTagStruct]])
209
+ return ClusterObjectFieldDescriptor(Type=typing.Optional[typing.List[Globals.Structs.semtag]])
210
210
 
211
- value: typing.Optional[typing.List[ModeSelect.Structs.SemanticTagStruct]] = None
211
+ value: typing.Optional[typing.List[Globals.Structs.semtag]] = None
212
212
 
213
213
  @dataclass
214
- class ObjectCountReached(ClusterAttributeDescriptor):
214
+ class ObjectCountThresholdReached(ClusterAttributeDescriptor):
215
215
  @ChipUtility.classproperty
216
216
  def cluster_id(cls) -> int:
217
217
  return 0x00000431
@@ -418,12 +418,12 @@ class AmbientContextSensing(Cluster):
418
418
  return ClusterObjectDescriptor(
419
419
  Fields=[
420
420
  ClusterObjectFieldDescriptor(Label="ambientContextDetected", Tag=0, Type=typing.Optional[AmbientContextSensing.Structs.AmbientContextTypeStruct]),
421
- ClusterObjectFieldDescriptor(Label="objectCountReached", Tag=1, Type=typing.Optional[bool]),
421
+ ClusterObjectFieldDescriptor(Label="objectCountThresholdReached", Tag=1, Type=typing.Optional[bool]),
422
422
  ClusterObjectFieldDescriptor(Label="objectCount", Tag=2, Type=typing.Optional[uint]),
423
423
  ])
424
424
 
425
425
  ambientContextDetected: typing.Optional[AmbientContextSensing.Structs.AmbientContextTypeStruct] = None
426
- objectCountReached: typing.Optional[bool] = None
426
+ objectCountThresholdReached: typing.Optional[bool] = None
427
427
  objectCount: typing.Optional[uint] = None
428
428
 
429
429
  @dataclass
@@ -440,7 +440,9 @@ class AmbientContextSensing(Cluster):
440
440
  def descriptor(cls) -> ClusterObjectDescriptor:
441
441
  return ClusterObjectDescriptor(
442
442
  Fields=[
443
- ClusterObjectFieldDescriptor(Label="eventStartTime", Tag=0, Type=typing.Optional[uint]),
443
+ ClusterObjectFieldDescriptor(Label="eventStartTimePos", Tag=0, Type=typing.Optional[uint]),
444
+ ClusterObjectFieldDescriptor(Label="eventStartTimeSys", Tag=1, Type=typing.Optional[uint]),
444
445
  ])
445
446
 
446
- eventStartTime: typing.Optional[uint] = None
447
+ eventStartTimePos: typing.Optional[uint] = None
448
+ eventStartTimeSys: typing.Optional[uint] = None
@@ -198,7 +198,6 @@ class Globals:
198
198
  kUnsupportedWrite = 0x88
199
199
  kResourceExhausted = 0x89
200
200
  kNotFound = 0x8B
201
- kUnreportableAttribute = 0x8C
202
201
  kInvalidDataType = 0x8D
203
202
  kUnsupportedRead = 0x8F
204
203
  kDataVersionMismatch = 0x92
@@ -207,7 +206,6 @@ class Globals:
207
206
  kBusy = 0x9C
208
207
  kAccessRestricted = 0x9D
209
208
  kUnsupportedCluster = 0xC3
210
- kNoUpstreamSubscription = 0xC5
211
209
  kNeedsTimedInteraction = 0xC6
212
210
  kUnsupportedEvent = 0xC7
213
211
  kPathsExhausted = 0xC8
@@ -22,11 +22,11 @@ class Groupcast(Cluster):
22
22
  def descriptor(cls) -> ClusterObjectDescriptor:
23
23
  return ClusterObjectDescriptor(
24
24
  Fields=[
25
- ClusterObjectFieldDescriptor(Label="membership", Tag=0x00000000, Type=typing.Optional[typing.List[Groupcast.Structs.MembershipStruct]]),
26
- ClusterObjectFieldDescriptor(Label="maxMembershipCount", Tag=0x00000001, Type=typing.Optional[uint]),
27
- ClusterObjectFieldDescriptor(Label="maxMcastAddrCount", Tag=0x00000002, Type=typing.Optional[uint]),
28
- ClusterObjectFieldDescriptor(Label="usedMcastAddrCount", Tag=0x00000003, Type=typing.Optional[uint]),
29
- ClusterObjectFieldDescriptor(Label="fabricUnderTest", Tag=0x00000004, Type=typing.Optional[uint]),
25
+ ClusterObjectFieldDescriptor(Label="membership", Tag=0x00000000, Type=typing.List[Groupcast.Structs.MembershipStruct]),
26
+ ClusterObjectFieldDescriptor(Label="maxMembershipCount", Tag=0x00000001, Type=uint),
27
+ ClusterObjectFieldDescriptor(Label="maxMcastAddrCount", Tag=0x00000002, Type=uint),
28
+ ClusterObjectFieldDescriptor(Label="usedMcastAddrCount", Tag=0x00000003, Type=uint),
29
+ ClusterObjectFieldDescriptor(Label="fabricUnderTest", Tag=0x00000004, Type=uint),
30
30
  ClusterObjectFieldDescriptor(Label="generatedCommandList", Tag=0x0000FFF8, Type=typing.List[uint]),
31
31
  ClusterObjectFieldDescriptor(Label="acceptedCommandList", Tag=0x0000FFF9, Type=typing.List[uint]),
32
32
  ClusterObjectFieldDescriptor(Label="attributeList", Tag=0x0000FFFB, Type=typing.List[uint]),
@@ -34,11 +34,11 @@ class Groupcast(Cluster):
34
34
  ClusterObjectFieldDescriptor(Label="clusterRevision", Tag=0x0000FFFD, Type=uint),
35
35
  ])
36
36
 
37
- membership: typing.Optional[typing.List[Groupcast.Structs.MembershipStruct]] = None
38
- maxMembershipCount: typing.Optional[uint] = None
39
- maxMcastAddrCount: typing.Optional[uint] = None
40
- usedMcastAddrCount: typing.Optional[uint] = None
41
- fabricUnderTest: typing.Optional[uint] = None
37
+ membership: typing.List[Groupcast.Structs.MembershipStruct] = field(default_factory=lambda: [])
38
+ maxMembershipCount: uint = 0
39
+ maxMcastAddrCount: uint = 0
40
+ usedMcastAddrCount: uint = 0
41
+ fabricUnderTest: uint = 0
42
42
  generatedCommandList: typing.List[uint] = field(default_factory=lambda: [])
43
43
  acceptedCommandList: typing.List[uint] = field(default_factory=lambda: [])
44
44
  attributeList: typing.List[uint] = field(default_factory=lambda: [])
@@ -240,9 +240,9 @@ class Groupcast(Cluster):
240
240
 
241
241
  @ChipUtility.classproperty
242
242
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
243
- return ClusterObjectFieldDescriptor(Type=typing.Optional[typing.List[Groupcast.Structs.MembershipStruct]])
243
+ return ClusterObjectFieldDescriptor(Type=typing.List[Groupcast.Structs.MembershipStruct])
244
244
 
245
- value: typing.Optional[typing.List[Groupcast.Structs.MembershipStruct]] = None
245
+ value: typing.List[Groupcast.Structs.MembershipStruct] = field(default_factory=lambda: [])
246
246
 
247
247
  @dataclass
248
248
  class MaxMembershipCount(ClusterAttributeDescriptor):
@@ -256,9 +256,9 @@ class Groupcast(Cluster):
256
256
 
257
257
  @ChipUtility.classproperty
258
258
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
259
- return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
259
+ return ClusterObjectFieldDescriptor(Type=uint)
260
260
 
261
- value: typing.Optional[uint] = None
261
+ value: uint = 0
262
262
 
263
263
  @dataclass
264
264
  class MaxMcastAddrCount(ClusterAttributeDescriptor):
@@ -272,9 +272,9 @@ class Groupcast(Cluster):
272
272
 
273
273
  @ChipUtility.classproperty
274
274
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
275
- return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
275
+ return ClusterObjectFieldDescriptor(Type=uint)
276
276
 
277
- value: typing.Optional[uint] = None
277
+ value: uint = 0
278
278
 
279
279
  @dataclass
280
280
  class UsedMcastAddrCount(ClusterAttributeDescriptor):
@@ -288,9 +288,9 @@ class Groupcast(Cluster):
288
288
 
289
289
  @ChipUtility.classproperty
290
290
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
291
- return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
291
+ return ClusterObjectFieldDescriptor(Type=uint)
292
292
 
293
- value: typing.Optional[uint] = None
293
+ value: uint = 0
294
294
 
295
295
  @dataclass
296
296
  class FabricUnderTest(ClusterAttributeDescriptor):
@@ -304,9 +304,9 @@ class Groupcast(Cluster):
304
304
 
305
305
  @ChipUtility.classproperty
306
306
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
307
- return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
307
+ return ClusterObjectFieldDescriptor(Type=uint)
308
308
 
309
- value: typing.Optional[uint] = None
309
+ value: uint = 0
310
310
 
311
311
  @dataclass
312
312
  class GeneratedCommandList(ClusterAttributeDescriptor):
@@ -109,14 +109,14 @@ class IcdManagement(Cluster):
109
109
  Fields=[
110
110
  ClusterObjectFieldDescriptor(Label="checkInNodeID", Tag=1, Type=uint),
111
111
  ClusterObjectFieldDescriptor(Label="monitoredSubject", Tag=2, Type=uint),
112
- ClusterObjectFieldDescriptor(Label="key", Tag=3, Type=typing.Optional[uint]),
112
+ ClusterObjectFieldDescriptor(Label="key", Tag=3, Type=typing.Optional[bytes]),
113
113
  ClusterObjectFieldDescriptor(Label="clientType", Tag=4, Type=IcdManagement.Enums.ClientTypeEnum),
114
114
  ClusterObjectFieldDescriptor(Label="fabricIndex", Tag=254, Type=uint),
115
115
  ])
116
116
 
117
117
  checkInNodeID: uint = 0
118
118
  monitoredSubject: uint = 0
119
- key: typing.Optional[uint] = None
119
+ key: typing.Optional[bytes] = None
120
120
  clientType: IcdManagement.Enums.ClientTypeEnum = 0
121
121
  fabricIndex: uint = 0
122
122
 
@@ -0,0 +1,168 @@
1
+ """IkeaValveCalibrationCluster cluster definition (auto-generated, DO NOT edit)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typing
6
+ from dataclasses import dataclass, field
7
+ from enum import IntFlag
8
+
9
+ from ... import ChipUtility
10
+ from ...clusters.enum import MatterIntEnum
11
+ from ...tlv import float32, uint
12
+ from ..ClusterObjects import (Cluster, ClusterAttributeDescriptor, ClusterCommand, ClusterEvent, ClusterObject,
13
+ ClusterObjectDescriptor, ClusterObjectFieldDescriptor)
14
+ from ..Types import Nullable, NullValue
15
+
16
+
17
+ @dataclass
18
+ class IkeaValveCalibrationCluster(Cluster):
19
+ id: typing.ClassVar[int] = 0x117CFC01
20
+
21
+ @ChipUtility.classproperty
22
+ def descriptor(cls) -> ClusterObjectDescriptor:
23
+ return ClusterObjectDescriptor(
24
+ Fields=[
25
+ ClusterObjectFieldDescriptor(Label="calibrationStatus", Tag=0x00000000, Type=typing.Optional[uint]),
26
+ ClusterObjectFieldDescriptor(Label="lastCalibrationError", Tag=0x00000001, Type=typing.Optional[uint]),
27
+ ClusterObjectFieldDescriptor(Label="generatedCommandList", Tag=0x0000FFF8, Type=typing.List[uint]),
28
+ ClusterObjectFieldDescriptor(Label="acceptedCommandList", Tag=0x0000FFF9, Type=typing.List[uint]),
29
+ ClusterObjectFieldDescriptor(Label="attributeList", Tag=0x0000FFFB, Type=typing.List[uint]),
30
+ ClusterObjectFieldDescriptor(Label="featureMap", Tag=0x0000FFFC, Type=uint),
31
+ ClusterObjectFieldDescriptor(Label="clusterRevision", Tag=0x0000FFFD, Type=uint),
32
+ ])
33
+
34
+ calibrationStatus: typing.Optional[uint] = None
35
+ lastCalibrationError: typing.Optional[uint] = None
36
+ generatedCommandList: typing.List[uint] = field(default_factory=lambda: [])
37
+ acceptedCommandList: typing.List[uint] = field(default_factory=lambda: [])
38
+ attributeList: typing.List[uint] = field(default_factory=lambda: [])
39
+ featureMap: uint = 0
40
+ clusterRevision: uint = 0
41
+
42
+ class Commands:
43
+ @dataclass
44
+ class TriggerCalibration(ClusterCommand):
45
+ cluster_id: typing.ClassVar[int] = 0x117CFC01
46
+ command_id: typing.ClassVar[int] = 0x00000000
47
+ is_client: typing.ClassVar[bool] = True
48
+ response_type: typing.ClassVar[typing.Optional[str]] = None
49
+
50
+ @ChipUtility.classproperty
51
+ def descriptor(cls) -> ClusterObjectDescriptor:
52
+ return ClusterObjectDescriptor(
53
+ Fields=[
54
+ ])
55
+
56
+
57
+ class Attributes:
58
+ @dataclass
59
+ class CalibrationStatus(ClusterAttributeDescriptor):
60
+ @ChipUtility.classproperty
61
+ def cluster_id(cls) -> int:
62
+ return 0x117CFC01
63
+
64
+ @ChipUtility.classproperty
65
+ def attribute_id(cls) -> int:
66
+ return 0x00000000
67
+
68
+ @ChipUtility.classproperty
69
+ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
70
+ return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
71
+
72
+ value: typing.Optional[uint] = None
73
+
74
+ @dataclass
75
+ class LastCalibrationError(ClusterAttributeDescriptor):
76
+ @ChipUtility.classproperty
77
+ def cluster_id(cls) -> int:
78
+ return 0x117CFC01
79
+
80
+ @ChipUtility.classproperty
81
+ def attribute_id(cls) -> int:
82
+ return 0x00000001
83
+
84
+ @ChipUtility.classproperty
85
+ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
86
+ return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
87
+
88
+ value: typing.Optional[uint] = None
89
+
90
+ @dataclass
91
+ class GeneratedCommandList(ClusterAttributeDescriptor):
92
+ @ChipUtility.classproperty
93
+ def cluster_id(cls) -> int:
94
+ return 0x117CFC01
95
+
96
+ @ChipUtility.classproperty
97
+ def attribute_id(cls) -> int:
98
+ return 0x0000FFF8
99
+
100
+ @ChipUtility.classproperty
101
+ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
102
+ return ClusterObjectFieldDescriptor(Type=typing.List[uint])
103
+
104
+ value: typing.List[uint] = field(default_factory=lambda: [])
105
+
106
+ @dataclass
107
+ class AcceptedCommandList(ClusterAttributeDescriptor):
108
+ @ChipUtility.classproperty
109
+ def cluster_id(cls) -> int:
110
+ return 0x117CFC01
111
+
112
+ @ChipUtility.classproperty
113
+ def attribute_id(cls) -> int:
114
+ return 0x0000FFF9
115
+
116
+ @ChipUtility.classproperty
117
+ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
118
+ return ClusterObjectFieldDescriptor(Type=typing.List[uint])
119
+
120
+ value: typing.List[uint] = field(default_factory=lambda: [])
121
+
122
+ @dataclass
123
+ class AttributeList(ClusterAttributeDescriptor):
124
+ @ChipUtility.classproperty
125
+ def cluster_id(cls) -> int:
126
+ return 0x117CFC01
127
+
128
+ @ChipUtility.classproperty
129
+ def attribute_id(cls) -> int:
130
+ return 0x0000FFFB
131
+
132
+ @ChipUtility.classproperty
133
+ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
134
+ return ClusterObjectFieldDescriptor(Type=typing.List[uint])
135
+
136
+ value: typing.List[uint] = field(default_factory=lambda: [])
137
+
138
+ @dataclass
139
+ class FeatureMap(ClusterAttributeDescriptor):
140
+ @ChipUtility.classproperty
141
+ def cluster_id(cls) -> int:
142
+ return 0x117CFC01
143
+
144
+ @ChipUtility.classproperty
145
+ def attribute_id(cls) -> int:
146
+ return 0x0000FFFC
147
+
148
+ @ChipUtility.classproperty
149
+ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
150
+ return ClusterObjectFieldDescriptor(Type=uint)
151
+
152
+ value: uint = 0
153
+
154
+ @dataclass
155
+ class ClusterRevision(ClusterAttributeDescriptor):
156
+ @ChipUtility.classproperty
157
+ def cluster_id(cls) -> int:
158
+ return 0x117CFC01
159
+
160
+ @ChipUtility.classproperty
161
+ def attribute_id(cls) -> int:
162
+ return 0x0000FFFD
163
+
164
+ @ChipUtility.classproperty
165
+ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
166
+ return ClusterObjectFieldDescriptor(Type=uint)
167
+
168
+ value: uint = 0
@@ -26,7 +26,7 @@ class IlluminanceMeasurement(Cluster):
26
26
  ClusterObjectFieldDescriptor(Label="minMeasuredValue", Tag=0x00000001, Type=typing.Union[Nullable, uint]),
27
27
  ClusterObjectFieldDescriptor(Label="maxMeasuredValue", Tag=0x00000002, Type=typing.Union[Nullable, uint]),
28
28
  ClusterObjectFieldDescriptor(Label="tolerance", Tag=0x00000003, Type=typing.Optional[uint]),
29
- ClusterObjectFieldDescriptor(Label="lightSensorType", Tag=0x00000004, Type=typing.Union[None, Nullable, uint]),
29
+ ClusterObjectFieldDescriptor(Label="lightSensorType", Tag=0x00000004, Type=typing.Union[None, Nullable, IlluminanceMeasurement.Enums.LightSensorTypeEnum]),
30
30
  ClusterObjectFieldDescriptor(Label="generatedCommandList", Tag=0x0000FFF8, Type=typing.List[uint]),
31
31
  ClusterObjectFieldDescriptor(Label="acceptedCommandList", Tag=0x0000FFF9, Type=typing.List[uint]),
32
32
  ClusterObjectFieldDescriptor(Label="attributeList", Tag=0x0000FFFB, Type=typing.List[uint]),
@@ -38,7 +38,7 @@ class IlluminanceMeasurement(Cluster):
38
38
  minMeasuredValue: typing.Union[Nullable, uint] = NullValue
39
39
  maxMeasuredValue: typing.Union[Nullable, uint] = NullValue
40
40
  tolerance: typing.Optional[uint] = None
41
- lightSensorType: typing.Union[None, Nullable, uint] = None
41
+ lightSensorType: typing.Union[None, Nullable, IlluminanceMeasurement.Enums.LightSensorTypeEnum] = None
42
42
  generatedCommandList: typing.List[uint] = field(default_factory=lambda: [])
43
43
  acceptedCommandList: typing.List[uint] = field(default_factory=lambda: [])
44
44
  attributeList: typing.List[uint] = field(default_factory=lambda: [])
@@ -132,9 +132,9 @@ class IlluminanceMeasurement(Cluster):
132
132
 
133
133
  @ChipUtility.classproperty
134
134
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
135
- return ClusterObjectFieldDescriptor(Type=typing.Union[None, Nullable, uint])
135
+ return ClusterObjectFieldDescriptor(Type=typing.Union[None, Nullable, IlluminanceMeasurement.Enums.LightSensorTypeEnum])
136
136
 
137
- value: typing.Union[None, Nullable, uint] = None
137
+ value: typing.Union[None, Nullable, IlluminanceMeasurement.Enums.LightSensorTypeEnum] = None
138
138
 
139
139
  @dataclass
140
140
  class GeneratedCommandList(ClusterAttributeDescriptor):
@@ -32,7 +32,7 @@ class Thermostat(Cluster):
32
32
  ClusterObjectFieldDescriptor(Label="absMaxCoolSetpointLimit", Tag=0x00000006, Type=typing.Optional[int]),
33
33
  ClusterObjectFieldDescriptor(Label="PICoolingDemand", Tag=0x00000007, Type=typing.Optional[uint]),
34
34
  ClusterObjectFieldDescriptor(Label="PIHeatingDemand", Tag=0x00000008, Type=typing.Optional[uint]),
35
- ClusterObjectFieldDescriptor(Label="HVACSystemTypeConfiguration", Tag=0x00000009, Type=typing.Optional[uint]),
35
+ ClusterObjectFieldDescriptor(Label="HVACSystemTypeConfiguration", Tag=0x00000009, Type=typing.Optional[Thermostat.Bitmaps.HVACSystemTypeBitmap]),
36
36
  ClusterObjectFieldDescriptor(Label="localTemperatureCalibration", Tag=0x00000010, Type=typing.Optional[int]),
37
37
  ClusterObjectFieldDescriptor(Label="occupiedCoolingSetpoint", Tag=0x00000011, Type=typing.Optional[int]),
38
38
  ClusterObjectFieldDescriptor(Label="occupiedHeatingSetpoint", Tag=0x00000012, Type=typing.Optional[int]),
@@ -49,17 +49,17 @@ class Thermostat(Cluster):
49
49
  ClusterObjectFieldDescriptor(Label="thermostatRunningMode", Tag=0x0000001E, Type=typing.Optional[Thermostat.Enums.ThermostatRunningModeEnum]),
50
50
  ClusterObjectFieldDescriptor(Label="temperatureSetpointHold", Tag=0x00000023, Type=typing.Optional[Thermostat.Enums.TemperatureSetpointHoldEnum]),
51
51
  ClusterObjectFieldDescriptor(Label="temperatureSetpointHoldDuration", Tag=0x00000024, Type=typing.Union[None, Nullable, uint]),
52
- ClusterObjectFieldDescriptor(Label="thermostatProgrammingOperationMode", Tag=0x00000025, Type=typing.Optional[uint]),
52
+ ClusterObjectFieldDescriptor(Label="thermostatProgrammingOperationMode", Tag=0x00000025, Type=typing.Optional[Thermostat.Bitmaps.ProgrammingOperationModeBitmap]),
53
53
  ClusterObjectFieldDescriptor(Label="thermostatRunningState", Tag=0x00000029, Type=typing.Optional[Thermostat.Bitmaps.RelayStateBitmap]),
54
54
  ClusterObjectFieldDescriptor(Label="setpointChangeSource", Tag=0x00000030, Type=typing.Optional[Thermostat.Enums.SetpointChangeSourceEnum]),
55
55
  ClusterObjectFieldDescriptor(Label="setpointChangeAmount", Tag=0x00000031, Type=typing.Union[None, Nullable, int]),
56
56
  ClusterObjectFieldDescriptor(Label="setpointChangeSourceTimestamp", Tag=0x00000032, Type=typing.Optional[uint]),
57
- ClusterObjectFieldDescriptor(Label="occupiedSetback", Tag=0x00000034, Type=typing.Optional[uint]),
58
- ClusterObjectFieldDescriptor(Label="occupiedSetbackMin", Tag=0x00000035, Type=typing.Optional[uint]),
59
- ClusterObjectFieldDescriptor(Label="occupiedSetbackMax", Tag=0x00000036, Type=typing.Optional[uint]),
60
- ClusterObjectFieldDescriptor(Label="unoccupiedSetback", Tag=0x00000037, Type=typing.Optional[uint]),
61
- ClusterObjectFieldDescriptor(Label="unoccupiedSetbackMin", Tag=0x00000038, Type=typing.Optional[uint]),
62
- ClusterObjectFieldDescriptor(Label="unoccupiedSetbackMax", Tag=0x00000039, Type=typing.Optional[uint]),
57
+ ClusterObjectFieldDescriptor(Label="occupiedSetback", Tag=0x00000034, Type=typing.Union[None, Nullable, uint]),
58
+ ClusterObjectFieldDescriptor(Label="occupiedSetbackMin", Tag=0x00000035, Type=typing.Union[None, Nullable, uint]),
59
+ ClusterObjectFieldDescriptor(Label="occupiedSetbackMax", Tag=0x00000036, Type=typing.Union[None, Nullable, uint]),
60
+ ClusterObjectFieldDescriptor(Label="unoccupiedSetback", Tag=0x00000037, Type=typing.Union[None, Nullable, uint]),
61
+ ClusterObjectFieldDescriptor(Label="unoccupiedSetbackMin", Tag=0x00000038, Type=typing.Union[None, Nullable, uint]),
62
+ ClusterObjectFieldDescriptor(Label="unoccupiedSetbackMax", Tag=0x00000039, Type=typing.Union[None, Nullable, uint]),
63
63
  ClusterObjectFieldDescriptor(Label="emergencyHeatDelta", Tag=0x0000003A, Type=typing.Optional[uint]),
64
64
  ClusterObjectFieldDescriptor(Label="ACType", Tag=0x00000040, Type=typing.Optional[Thermostat.Enums.ACTypeEnum]),
65
65
  ClusterObjectFieldDescriptor(Label="ACCapacity", Tag=0x00000041, Type=typing.Optional[uint]),
@@ -100,7 +100,7 @@ class Thermostat(Cluster):
100
100
  absMaxCoolSetpointLimit: typing.Optional[int] = None
101
101
  PICoolingDemand: typing.Optional[uint] = None
102
102
  PIHeatingDemand: typing.Optional[uint] = None
103
- HVACSystemTypeConfiguration: typing.Optional[uint] = None
103
+ HVACSystemTypeConfiguration: typing.Optional[Thermostat.Bitmaps.HVACSystemTypeBitmap] = None
104
104
  localTemperatureCalibration: typing.Optional[int] = None
105
105
  occupiedCoolingSetpoint: typing.Optional[int] = None
106
106
  occupiedHeatingSetpoint: typing.Optional[int] = None
@@ -117,17 +117,17 @@ class Thermostat(Cluster):
117
117
  thermostatRunningMode: typing.Optional[Thermostat.Enums.ThermostatRunningModeEnum] = None
118
118
  temperatureSetpointHold: typing.Optional[Thermostat.Enums.TemperatureSetpointHoldEnum] = None
119
119
  temperatureSetpointHoldDuration: typing.Union[None, Nullable, uint] = None
120
- thermostatProgrammingOperationMode: typing.Optional[uint] = None
120
+ thermostatProgrammingOperationMode: typing.Optional[Thermostat.Bitmaps.ProgrammingOperationModeBitmap] = None
121
121
  thermostatRunningState: typing.Optional[Thermostat.Bitmaps.RelayStateBitmap] = None
122
122
  setpointChangeSource: typing.Optional[Thermostat.Enums.SetpointChangeSourceEnum] = None
123
123
  setpointChangeAmount: typing.Union[None, Nullable, int] = None
124
124
  setpointChangeSourceTimestamp: typing.Optional[uint] = None
125
- occupiedSetback: typing.Optional[uint] = None
126
- occupiedSetbackMin: typing.Optional[uint] = None
127
- occupiedSetbackMax: typing.Optional[uint] = None
128
- unoccupiedSetback: typing.Optional[uint] = None
129
- unoccupiedSetbackMin: typing.Optional[uint] = None
130
- unoccupiedSetbackMax: typing.Optional[uint] = None
125
+ occupiedSetback: typing.Union[None, Nullable, uint] = None
126
+ occupiedSetbackMin: typing.Union[None, Nullable, uint] = None
127
+ occupiedSetbackMax: typing.Union[None, Nullable, uint] = None
128
+ unoccupiedSetback: typing.Union[None, Nullable, uint] = None
129
+ unoccupiedSetbackMin: typing.Union[None, Nullable, uint] = None
130
+ unoccupiedSetbackMax: typing.Union[None, Nullable, uint] = None
131
131
  emergencyHeatDelta: typing.Optional[uint] = None
132
132
  ACType: typing.Optional[Thermostat.Enums.ACTypeEnum] = None
133
133
  ACCapacity: typing.Optional[uint] = None
@@ -380,6 +380,17 @@ class Thermostat(Cluster):
380
380
  kPreCoolingOrPreHeating = 0x40
381
381
  kConflictingSuggestions = 0x80
382
382
 
383
+ class HVACSystemTypeBitmap(IntFlag):
384
+ kCoolingStage = 0x3
385
+ kHeatingStage = 0xC
386
+ kHeatingIsHeatPump = 0x10
387
+ kHeatingUsesFuel = 0x20
388
+
389
+ class ProgrammingOperationModeBitmap(IntFlag):
390
+ kScheduleActive = 0x1
391
+ kAutoRecovery = 0x2
392
+ kEconomy = 0x4
393
+
383
394
  class Structs:
384
395
  @dataclass
385
396
  class PresetStruct(ClusterObject):
@@ -806,9 +817,9 @@ class Thermostat(Cluster):
806
817
 
807
818
  @ChipUtility.classproperty
808
819
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
809
- return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
820
+ return ClusterObjectFieldDescriptor(Type=typing.Optional[Thermostat.Bitmaps.HVACSystemTypeBitmap])
810
821
 
811
- value: typing.Optional[uint] = None
822
+ value: typing.Optional[Thermostat.Bitmaps.HVACSystemTypeBitmap] = None
812
823
 
813
824
  @dataclass
814
825
  class LocalTemperatureCalibration(ClusterAttributeDescriptor):
@@ -1078,9 +1089,9 @@ class Thermostat(Cluster):
1078
1089
 
1079
1090
  @ChipUtility.classproperty
1080
1091
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
1081
- return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
1092
+ return ClusterObjectFieldDescriptor(Type=typing.Optional[Thermostat.Bitmaps.ProgrammingOperationModeBitmap])
1082
1093
 
1083
- value: typing.Optional[uint] = None
1094
+ value: typing.Optional[Thermostat.Bitmaps.ProgrammingOperationModeBitmap] = None
1084
1095
 
1085
1096
  @dataclass
1086
1097
  class ThermostatRunningState(ClusterAttributeDescriptor):
@@ -1158,9 +1169,9 @@ class Thermostat(Cluster):
1158
1169
 
1159
1170
  @ChipUtility.classproperty
1160
1171
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
1161
- return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
1172
+ return ClusterObjectFieldDescriptor(Type=typing.Union[None, Nullable, uint])
1162
1173
 
1163
- value: typing.Optional[uint] = None
1174
+ value: typing.Union[None, Nullable, uint] = None
1164
1175
 
1165
1176
  @dataclass
1166
1177
  class OccupiedSetbackMin(ClusterAttributeDescriptor):
@@ -1174,9 +1185,9 @@ class Thermostat(Cluster):
1174
1185
 
1175
1186
  @ChipUtility.classproperty
1176
1187
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
1177
- return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
1188
+ return ClusterObjectFieldDescriptor(Type=typing.Union[None, Nullable, uint])
1178
1189
 
1179
- value: typing.Optional[uint] = None
1190
+ value: typing.Union[None, Nullable, uint] = None
1180
1191
 
1181
1192
  @dataclass
1182
1193
  class OccupiedSetbackMax(ClusterAttributeDescriptor):
@@ -1190,9 +1201,9 @@ class Thermostat(Cluster):
1190
1201
 
1191
1202
  @ChipUtility.classproperty
1192
1203
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
1193
- return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
1204
+ return ClusterObjectFieldDescriptor(Type=typing.Union[None, Nullable, uint])
1194
1205
 
1195
- value: typing.Optional[uint] = None
1206
+ value: typing.Union[None, Nullable, uint] = None
1196
1207
 
1197
1208
  @dataclass
1198
1209
  class UnoccupiedSetback(ClusterAttributeDescriptor):
@@ -1206,9 +1217,9 @@ class Thermostat(Cluster):
1206
1217
 
1207
1218
  @ChipUtility.classproperty
1208
1219
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
1209
- return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
1220
+ return ClusterObjectFieldDescriptor(Type=typing.Union[None, Nullable, uint])
1210
1221
 
1211
- value: typing.Optional[uint] = None
1222
+ value: typing.Union[None, Nullable, uint] = None
1212
1223
 
1213
1224
  @dataclass
1214
1225
  class UnoccupiedSetbackMin(ClusterAttributeDescriptor):
@@ -1222,9 +1233,9 @@ class Thermostat(Cluster):
1222
1233
 
1223
1234
  @ChipUtility.classproperty
1224
1235
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
1225
- return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
1236
+ return ClusterObjectFieldDescriptor(Type=typing.Union[None, Nullable, uint])
1226
1237
 
1227
- value: typing.Optional[uint] = None
1238
+ value: typing.Union[None, Nullable, uint] = None
1228
1239
 
1229
1240
  @dataclass
1230
1241
  class UnoccupiedSetbackMax(ClusterAttributeDescriptor):
@@ -1238,9 +1249,9 @@ class Thermostat(Cluster):
1238
1249
 
1239
1250
  @ChipUtility.classproperty
1240
1251
  def attribute_type(cls) -> ClusterObjectFieldDescriptor:
1241
- return ClusterObjectFieldDescriptor(Type=typing.Optional[uint])
1252
+ return ClusterObjectFieldDescriptor(Type=typing.Union[None, Nullable, uint])
1242
1253
 
1243
- value: typing.Optional[uint] = None
1254
+ value: typing.Union[None, Nullable, uint] = None
1244
1255
 
1245
1256
  @dataclass
1246
1257
  class EmergencyHeatDelta(ClusterAttributeDescriptor):
@@ -44,13 +44,13 @@ class WebRtcTransportDefinitions:
44
44
  def descriptor(cls) -> ClusterObjectDescriptor:
45
45
  return ClusterObjectDescriptor(
46
46
  Fields=[
47
- ClusterObjectFieldDescriptor(Label="urLs", Tag=0, Type=typing.List[str]),
47
+ ClusterObjectFieldDescriptor(Label="URLs", Tag=0, Type=typing.List[str]),
48
48
  ClusterObjectFieldDescriptor(Label="username", Tag=1, Type=typing.Optional[str]),
49
49
  ClusterObjectFieldDescriptor(Label="credential", Tag=2, Type=typing.Optional[str]),
50
50
  ClusterObjectFieldDescriptor(Label="caid", Tag=3, Type=typing.Optional[uint]),
51
51
  ])
52
52
 
53
- urLs: typing.List[str] = field(default_factory=lambda: [])
53
+ URLs: typing.List[str] = field(default_factory=lambda: [])
54
54
  username: typing.Optional[str] = None
55
55
  credential: typing.Optional[str] = None
56
56
  caid: typing.Optional[uint] = None
@@ -65,6 +65,7 @@ from .HeimanCluster import HeimanCluster
65
65
  from .HepaFilterMonitoring import HepaFilterMonitoring
66
66
  from .IcdManagement import IcdManagement
67
67
  from .Identify import Identify
68
+ from .IkeaValveCalibrationCluster import IkeaValveCalibrationCluster
68
69
  from .IlluminanceMeasurement import IlluminanceMeasurement
69
70
  from .InovelliCluster import InovelliCluster
70
71
  from .JointFabricAdministrator import JointFabricAdministrator
@@ -215,6 +216,7 @@ __all__ = [
215
216
  "HepaFilterMonitoring",
216
217
  "IcdManagement",
217
218
  "Identify",
219
+ "IkeaValveCalibrationCluster",
218
220
  "IlluminanceMeasurement",
219
221
  "InovelliCluster",
220
222
  "JointFabricAdministrator",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matter-python-client
3
- Version: 1.4.1a11.dev20261007
3
+ Version: 1.5.0a0.dev20261013
4
4
  Summary: Python Client for the OHF Matter Server
5
5
  Author-email: Open Home Foundation <hello@openhomefoundation.io>
6
6
  License-Expression: Apache-2.0
@@ -1,7 +1,7 @@
1
1
  chip/ChipUtility.py,sha256=pD_krwMaHGww1OPAVJWGli5Sc9yjlVxrKE2N-3Yeerc,279
2
2
  chip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  chip/clusters/ClusterObjects.py,sha256=6NDOEUl_zu5KjN-_32_xIrMl7y1M5X1XDZ4eYvVCiSo,11655
4
- chip/clusters/Objects.py,sha256=WzWhSUv-uhJyfSW4Hb7BAVRDNLnZFOlcsjQYVU5dY-4,4918
4
+ chip/clusters/Objects.py,sha256=GRhlj14gA09nxGAsL4D7nEJXgzc1ENDAasJO1YS1veY,4953
5
5
  chip/clusters/Types.py,sha256=jWwhYCFH5S1JV78wkrXSAoOrDZs29miTL8heZq3QHhU,759
6
6
  chip/clusters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  chip/clusters/enum.py,sha256=ZldcWGjqzfVvWFWOFr_nCgtiCVyQxD6MKiptle_i0O4,1660
@@ -11,7 +11,7 @@ chip/clusters/cluster_defs/Actions.py,sha256=M4HX7L-pWu2a64AxPMayYHksVLC8NRhHerp
11
11
  chip/clusters/cluster_defs/ActivatedCarbonFilterMonitoring.py,sha256=RTf_nI8lVTwSyIGUMiaS5Mrk5YjlG4ATr2jOYdYEBaA,12240
12
12
  chip/clusters/cluster_defs/AdministratorCommissioning.py,sha256=ymeGLlOeEAFjeONxBHG1V2qInYkXS1i87vPUmSfM6Z8,10556
13
13
  chip/clusters/cluster_defs/AirQuality.py,sha256=8tEIDVOkJFfOSi_sq5n_tV-Azft9L_X0BFFnl9tizXQ,5830
14
- chip/clusters/cluster_defs/AmbientContextSensing.py,sha256=Es5JqyoRYWpFDLYB9qiDx4ODROMunhIBC3peTClafPs,19531
14
+ chip/clusters/cluster_defs/AmbientContextSensing.py,sha256=Vq94BPgCRtkkYdvauDYua90_XQ8E8veL5sqNDrlx-0E,19612
15
15
  chip/clusters/cluster_defs/ApplicationBasic.py,sha256=s9u_hOIlZOG09AAwu7Pfy5IUXkpXSrwJf-pPAmKWJwY,11261
16
16
  chip/clusters/cluster_defs/ApplicationLauncher.py,sha256=jaaVtlpvEiRIGXKZ6UqbVWz7QiZfGyvvBjvtT1SAp1M,11277
17
17
  chip/clusters/cluster_defs/AqaraAmbientSensingConfigurationCluster.py,sha256=MB4f_eh1HNlu_bJibmW_-lMnzI9bT_wGKl0fo1sxPaA,30123
@@ -62,15 +62,16 @@ chip/clusters/cluster_defs/FlowMeasurement.py,sha256=Jvzf_fPK4OkVOiKLv1CPEZro-sl
62
62
  chip/clusters/cluster_defs/FormaldehydeConcentrationMeasurement.py,sha256=oJcn3RJgAaiIxHRQWmyGPOab7YUU5adrEiT7BRELP7M,15078
63
63
  chip/clusters/cluster_defs/GeneralCommissioning.py,sha256=-wzrr7lasuH6cRJfq48jb2y22uCH8gHaT8qXl3dX1lk,23513
64
64
  chip/clusters/cluster_defs/GeneralDiagnostics.py,sha256=-hH3fIn4AI_UVQLDIBbK3QLwX2ncMVO-Nnrg0VoVkGo,26405
65
- chip/clusters/cluster_defs/Globals.py,sha256=_Z1mAaFKehkvLgPw5emrCMLXGypMqEekZyeOAI6B348,18339
65
+ chip/clusters/cluster_defs/Globals.py,sha256=dSoc8yRrEg_-KHPC0uaQxgbl4MkqtibIhrfBWPu36Qs,18254
66
66
  chip/clusters/cluster_defs/GroupKeyManagement.py,sha256=RYwByuqdwdk-J0PrS-M1ej6K68vIgxgWXcn_k4i7-54,18040
67
- chip/clusters/cluster_defs/Groupcast.py,sha256=TrxgezWCY5fsJzsmePbk3RNoy5-ga0J2yI0wAKwwwbA,18882
67
+ chip/clusters/cluster_defs/Groupcast.py,sha256=KOtUlFNWmdWs67KQblP95lidkckjwUPdcWRG-JITo4U,18576
68
68
  chip/clusters/cluster_defs/Groups.py,sha256=h3cOEjMXNZ8gRDpygOul58OlHRdDd14nF3ZMf8dFdBs,12613
69
69
  chip/clusters/cluster_defs/HeimanCluster.py,sha256=LNWZT3VZ5KwMDzXZP1i7bgp0XXTAk2Y8j9UqP-PzxC4,9940
70
70
  chip/clusters/cluster_defs/HepaFilterMonitoring.py,sha256=vT9GneKzd9izTSH3tl6IMjPbyVVrzBY9FKZza6Jb7NI,12064
71
- chip/clusters/cluster_defs/IcdManagement.py,sha256=yGTgVXJqOJ-VbJ7WLF_jR3WEeDpLgv9RP9tRVlY9mVQ,18831
71
+ chip/clusters/cluster_defs/IcdManagement.py,sha256=aMylsvfRVy1sS6jey3Y22x9mzFz18U5_1R_DsPTF9Ko,18833
72
72
  chip/clusters/cluster_defs/Identify.py,sha256=XMFOZpvtU6m3bOaZSuL9G0fdrAUfvvdSHIW8tCFlLqM,8829
73
- chip/clusters/cluster_defs/IlluminanceMeasurement.py,sha256=yk2RCnAXY9r9AGIj2QDwnu4Mmb50NWR0o0VmPS_55ZU,8528
73
+ chip/clusters/cluster_defs/IkeaValveCalibrationCluster.py,sha256=9P3k8dxYNKGEF_L4hozqA4BAgw7E0UldjHVD79t22Vo,6338
74
+ chip/clusters/cluster_defs/IlluminanceMeasurement.py,sha256=y5PRkKKjCjP5vw7xoUAM1ojPx381QJV3Hm7zHWuehFI,8704
74
75
  chip/clusters/cluster_defs/InovelliCluster.py,sha256=coQSGJPJW6zlr_WJjw3aP6vU9YODEd7f35vqP9FgYcA,6587
75
76
  chip/clusters/cluster_defs/JointFabricAdministrator.py,sha256=SuCVMyrT_7VAXTic1QqLKeP5u7_t6Mb32Fu7_AfZHzo,13258
76
77
  chip/clusters/cluster_defs/JointFabricDatastore.py,sha256=DOyq40rnUhJdsKF0PD0l6thKXEUvzKqpXXmXrkwWUsQ,50708
@@ -127,7 +128,7 @@ chip/clusters/cluster_defs/TclDehumidifierCluster.py,sha256=Hy0Rq5B0-DEejiilz3gF
127
128
  chip/clusters/cluster_defs/TemperatureAlarm.py,sha256=gKNDRarQlSyH58tDlWaIeqV1Wtc7YkeQLzlAcxgdx_A,17059
128
129
  chip/clusters/cluster_defs/TemperatureControl.py,sha256=dTnpNOrQOWCqrF-ckzlEWZOWdayPsNvBgVxil04uy4o,9724
129
130
  chip/clusters/cluster_defs/TemperatureMeasurement.py,sha256=zhUXDFDk1jzSzVkv8KL08Wyz-i_vFhNAUVdQo19P0RY,7282
130
- chip/clusters/cluster_defs/Thermostat.py,sha256=VCxGBF_4MUA4glckWGv97aZqOml5kHEJZLCbdtPScWY,82466
131
+ chip/clusters/cluster_defs/Thermostat.py,sha256=VpCO3AS8ecsHqSTNN9V0gBkDNAWC9UNephkKy_yaYvU,83431
131
132
  chip/clusters/cluster_defs/ThermostatUserInterfaceConfiguration.py,sha256=Vb8fzuE2X1uv7SlTTixaMsEwrL2MC6nNm2mo76NJ-Cc,8755
132
133
  chip/clusters/cluster_defs/ThirdRealityMeteringCluster.py,sha256=k22CR0g42A3UIQuUQV-23z0a8zYo6d-tq8DmFueXshA,7207
133
134
  chip/clusters/cluster_defs/ThreadBorderRouterManagement.py,sha256=z6j_zRjEAe-211JqQzDcB6BX-q3f3rp7W6OHOeJQK5I,11964
@@ -146,35 +147,35 @@ chip/clusters/cluster_defs/WakeOnLan.py,sha256=yvXBZXs6c14fBqSNzHiFIPQCSRZKbw3Ta
146
147
  chip/clusters/cluster_defs/WaterHeaterManagement.py,sha256=d4VKVKZYbiVFAjpszZek1EFKi1OZwmfJ2AiSoA8gDpU,13619
147
148
  chip/clusters/cluster_defs/WaterHeaterMode.py,sha256=kEaoxPvKn1qIeZ8f60qYxGXrwfRBGVB5U5rp99es45g,11532
148
149
  chip/clusters/cluster_defs/WaterTankLevelMonitoring.py,sha256=q6CCsmYzPlkbRyC6Hbl2Qn00PSicpsCxl_LrUBo9izE,12128
149
- chip/clusters/cluster_defs/WebRtcTransportDefinitions.py,sha256=eSVnQrzrwWcemnUfwox__NXs1MKOC7BJ-OwrYooZYYo,4904
150
+ chip/clusters/cluster_defs/WebRtcTransportDefinitions.py,sha256=nJdqnE95A8yp2OgDNh6NOKCDxujwaQUdWgbhTEJMo84,4904
150
151
  chip/clusters/cluster_defs/WebRtcTransportProvider.py,sha256=4Wf-bEBMuQ7L2l1seO6EMKWltR5-weC9jewnEmQMSao,15122
151
152
  chip/clusters/cluster_defs/WebRtcTransportRequestor.py,sha256=4w-83xo38CJgNDMzq0edlIdp87czjNpKwuj8Z6owI9o,9113
152
153
  chip/clusters/cluster_defs/WiFiNetworkDiagnostics.py,sha256=X6fWBeCXDk3t1Y_95gkNDiD9lVNxx4IyuOYDoGjvCM8,19473
153
154
  chip/clusters/cluster_defs/WiFiNetworkManagement.py,sha256=eBmysZ7TEuUCxPsrsNc22dGxnNXE0SHS4Oh43Y5fTdE,7041
154
155
  chip/clusters/cluster_defs/WindowCovering.py,sha256=zMD343RPMoUBnw1bQ2brRNNZ9gsMukjEp1EslcyxEBc,24267
155
156
  chip/clusters/cluster_defs/ZoneManagement.py,sha256=d-ZCgnpaGCB74KNUW9Q7wu23kaBFZr3C5oK_VSAIXLE,21789
156
- chip/clusters/cluster_defs/__init__.py,sha256=Zr9lmq0io2BgwAQe4O4q7ho8AH8muApvxOyris_U82Y,11858
157
+ chip/clusters/cluster_defs/__init__.py,sha256=5In7gjC5jk0b_nsdpy0NPofRhNLyS7Pnr6O7h8SwC5w,11962
157
158
  chip/tlv/__init__.py,sha256=24xl0LIaL_f_ciqdaLwZCtDxkJzMgpRAx1QbHCw4Kic,13487
158
159
  chip/tlv/tlvlist.py,sha256=ZpT6Hr6t3qDHiUZWkY4WnioVP2icv5i5LIKNUIgOOps,2469
159
160
  matter_server/__init__.py,sha256=lJ6xyVLSMGtiHvq36VPSDb75ksxfNKYmKEEE-ZwJdhQ,47
160
161
  matter_server/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
161
162
  matter_server/client/__init__.py,sha256=pZzjJp-7C5BmYGZJjIo5SpyjGLU0FcqBa2nJ558y-Is,97
162
- matter_server/client/client.py,sha256=AeQhe7AV4iYOTQsKDydAkjbKrE5syWsG2FYQ3BW6pv8,40400
163
+ matter_server/client/client.py,sha256=HgBEEix4or5y9ktrEiDV-aQCJ2DEOXJcUZ7e-JF6oj0,40822
163
164
  matter_server/client/connection.py,sha256=Fu_9xShWBcUUaKUAf7XWfi3Mm3AUs4-ohIXHpgObL4I,9245
164
165
  matter_server/client/exceptions.py,sha256=741JkGMBQFJYZ70zX80B-Dn_NtgHffb6jDHC83tCL2U,1901
165
166
  matter_server/client/models/__init__.py,sha256=pT16UC2evZVw2trjun2xJZpaOR55ky7i_8znlA3Mreo,63
166
- matter_server/client/models/device_types.py,sha256=FaP74htbybBtO2PoNGLfKUL8WZyD-sy-QH81FlDOckY,26580
167
- matter_server/client/models/node.py,sha256=j7pKZCLkZS8FRMxSDyGlLu7it7_9DRLNIHTJtJjCkf0,15431
167
+ matter_server/client/models/device_types.py,sha256=SU82O_aC_HTV6KEaO00rXbGBy0cUImD8aR7tq692Mw0,26612
168
+ matter_server/client/models/node.py,sha256=RCLPsumxVw433nPO_Ii7vB_Ak8sAbfZ26U-94KXiRGU,25680
168
169
  matter_server/common/__init__.py,sha256=y4EeCH05sUYifS2b624oweA6omnY5k-rLNHHv4t4p7Q,50
169
170
  matter_server/common/const.py,sha256=hTu0siAgpQvAz6oYavTGuqgkXaQH2glQxUQuGyPnn1o,256
170
- matter_server/common/custom_clusters.py,sha256=E5HqHYoKaUmKe69GwLP6pi_PS8zi0yOl1mzYyXsJgTM,2002
171
+ matter_server/common/custom_clusters.py,sha256=-B8chVS7kKxRrQ_g_qOaWTjOJ7YPYR3IBzf73VwNeIM,2197
171
172
  matter_server/common/errors.py,sha256=h225mChQDHZ_CpkKRkg_F1_RF0qmXFcCP0ERBVMQkJ8,3770
172
- matter_server/common/models.py,sha256=xQL8qb7V3XLy4-W3xI7iJhVwOjuqAX2f0QY4EvBul-E,12492
173
+ matter_server/common/models.py,sha256=j9fLNRREVD4cCB_KVmdiOEwj4DV7H9aURClpq84cseI,12641
173
174
  matter_server/common/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
174
175
  matter_server/common/helpers/api.py,sha256=9maToXRnef6SLGnecaTSBlWU7wDSG3elil-HtffQ2TA,1993
175
176
  matter_server/common/helpers/json.py,sha256=FktcqarAhmdI0AdiBwo7gCdJp6rA-uVdeNUglQv6HhA,1307
176
177
  matter_server/common/helpers/util.py,sha256=fPYh89ii65JqSKyR4db-L1QkEXBqnsOlFCiDzTC2oT4,14827
177
- matter_python_client-1.4.1a11.dev20261007.dist-info/METADATA,sha256=hynTF56Pawtc5URo2IXDhEcm9cZSvtNnQ11SKnnogyQ,5495
178
- matter_python_client-1.4.1a11.dev20261007.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
179
- matter_python_client-1.4.1a11.dev20261007.dist-info/top_level.txt,sha256=qqEVT8dTHhSn15gf6otxpB5dYM48Sqsucdx9W2Lq-So,19
180
- matter_python_client-1.4.1a11.dev20261007.dist-info/RECORD,,
178
+ matter_python_client-1.5.0a0.dev20261013.dist-info/METADATA,sha256=CdUn7nvWgm7KPE5VaTcGBE-op1rd7dXnMlrPqdqRh8k,5494
179
+ matter_python_client-1.5.0a0.dev20261013.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
180
+ matter_python_client-1.5.0a0.dev20261013.dist-info/top_level.txt,sha256=qqEVT8dTHhSn15gf6otxpB5dYM48Sqsucdx9W2Lq-So,19
181
+ matter_python_client-1.5.0a0.dev20261013.dist-info/RECORD,,
@@ -908,7 +908,7 @@ class MatterClient:
908
908
  self._signal_event(EventType.ENDPOINT_REMOVED, data=msg.data, node_id=node_id)
909
909
  # cleanup endpoint only after signalling subscribers
910
910
  if node := self._nodes.get(node_id):
911
- node.endpoints.pop(endpoint_id, None)
911
+ node._remove_endpoint(endpoint_id)
912
912
  return
913
913
  if msg.event == EventType.ATTRIBUTE_UPDATED:
914
914
  # data is tuple[node_id, attribute_path, new_value]
@@ -932,6 +932,14 @@ class MatterClient:
932
932
  node_id = msg.data["node_id"]
933
933
  endpoint_id = msg.data["endpoint_id"]
934
934
  self.logger.debug("Endpoint added: %s/%s", node_id, endpoint_id)
935
+ # subscribers look the endpoint up; the node snapshot that carries it reaches them later
936
+ if (node := self._nodes.get(node_id)) is None or endpoint_id not in node.endpoints:
937
+ self.logger.debug(
938
+ "Not signalling endpoint %s/%s that the node model does not contain",
939
+ node_id,
940
+ endpoint_id,
941
+ )
942
+ return
935
943
  if msg.event == EventType.NODE_EVENT:
936
944
  if self.logger.isEnabledFor(logging.DEBUG):
937
945
  self.logger.debug(
@@ -795,6 +795,7 @@ class RootNode(DeviceType):
795
795
  all_clusters.GeneralCommissioning,
796
796
  all_clusters.GeneralDiagnostics,
797
797
  all_clusters.GroupKeyManagement,
798
+ all_clusters.Groupcast,
798
799
  all_clusters.IcdManagement,
799
800
  all_clusters.LocalizationConfiguration,
800
801
  all_clusters.NetworkCommissioning,
@@ -24,10 +24,15 @@ from .device_types import (
24
24
  )
25
25
 
26
26
  if TYPE_CHECKING:
27
+ from collections.abc import Mapping
28
+
27
29
  from matter_server.common.models import MatterNodeData
28
30
 
29
31
  LOGGER = logging.getLogger(__name__)
30
32
 
33
+ # the Matter specification places the Root Node on endpoint 0 of every node
34
+ ROOT_ENDPOINT_ID = 0
35
+
31
36
  # pylint: disable=invalid-name
32
37
  _CLUSTER_T = TypeVar("_CLUSTER_T", bound=Clusters.Cluster)
33
38
  _ATTRIBUTE_T = TypeVar("_ATTRIBUTE_T", bound=Clusters.ClusterAttributeDescriptor)
@@ -44,6 +49,18 @@ def get_object_params(
44
49
  raise KeyError(f"No descriptor found for object {object_id}")
45
50
 
46
51
 
52
+ def _reaches(start_id: int, target_id: int, parents: Mapping[int, int]) -> bool:
53
+ """Return whether walking up the parent chain from an endpoint arrives at another."""
54
+ seen: set[int] = set()
55
+ current: int | None = start_id
56
+ while current is not None and current not in seen:
57
+ if current == target_id:
58
+ return True
59
+ seen.add(current)
60
+ current = parents.get(current)
61
+ return False
62
+
63
+
47
64
  @dataclass
48
65
  class MatterFabricData:
49
66
  """Data about a Matter fabric."""
@@ -84,18 +101,25 @@ class MatterEndpoint:
84
101
  @property
85
102
  def device_info(
86
103
  self,
87
- ) -> Clusters.BasicInformation | Clusters.BridgedDeviceBasicInformation:
104
+ ) -> Clusters.BasicInformation | Clusters.BridgedDeviceBasicInformation | None:
88
105
  """
89
106
  Return device info.
90
107
 
91
- If this endpoint represents a BridgedDevice, returns BridgedDeviceBasic.
92
- If this endpoint represents a ComposedDevice, returns the info of the compose device.
93
- Otherwise, returns BasicInformation from the Node itself (endpoint 0).
108
+ Returns the BridgedDeviceBasicInformation of the Bridged Node this endpoint is or is a part
109
+ of, else the BasicInformation of the Node itself. It is None when that Bridged Node reports
110
+ no BridgedDeviceBasicInformation, because the info of the bridge or of the Aggregator above
111
+ it belongs to a different device.
94
112
  """
95
- if self.is_bridged_device:
96
- return self.get_cluster(Clusters.BridgedDeviceBasicInformation)
97
- if compose_parent := self.node.get_compose_parent(self.endpoint_id):
98
- return compose_parent.device_info
113
+ endpoint = self
114
+ seen: set[int] = set()
115
+ while endpoint.endpoint_id not in seen:
116
+ seen.add(endpoint.endpoint_id)
117
+ if endpoint.is_bridged_device:
118
+ return endpoint.get_cluster(Clusters.BridgedDeviceBasicInformation)
119
+ parent = endpoint.node.get_closest_compose_parent(endpoint.endpoint_id)
120
+ if parent is None:
121
+ break
122
+ endpoint = parent
99
123
  return self.node.device_info
100
124
 
101
125
  def has_cluster(self, cluster: type[_CLUSTER_T] | int) -> bool:
@@ -217,11 +241,14 @@ class MatterEndpoint:
217
241
 
218
242
  def update(self, attributes_data: dict[str, Any]) -> None:
219
243
  """Update MatterEndpoint from (endpoint-specific) raw Attributes data."""
244
+ # a snapshot carries the complete state of the endpoint, never a delta
245
+ self.clusters = {}
220
246
  # unwrap cluster and clusterattributes from raw node data attributes
221
247
  for attribute_path, attribute_value in attributes_data.items():
222
248
  self.set_attribute_value(attribute_path, attribute_value)
223
- # extract device types from Descriptor Cluster
224
- if cluster := self.get_cluster(Clusters.Descriptor):
249
+ # a snapshot without the DeviceTypeList says nothing about the device types, so the previous ones stay
250
+ if f"{self.endpoint_id}/29/0" in attributes_data and (cluster := self.get_cluster(Clusters.Descriptor)):
251
+ self.device_types = set()
225
252
  for dev_info in cluster.deviceTypeList:
226
253
  device_type = DEVICE_TYPES.get(dev_info.deviceType)
227
254
  if device_type is None:
@@ -240,9 +267,10 @@ class MatterNode:
240
267
  def __init__(self, node_data: MatterNodeData) -> None:
241
268
  """Initialize MatterNode from MatterNodeData."""
242
269
  self.endpoints: dict[int, MatterEndpoint] = {}
243
- # composed devices reference to other endpoints through the partsList attribute
244
- # create a mapping table
245
270
  self._composed_endpoints: dict[int, int] = {}
271
+ self._bridge_parents: dict[int, int] = {}
272
+ self._snapshot_endpoint_ids: set[int] = set()
273
+ self._reported_missing_bridged_info: set[int] = set()
246
274
  self.update(node_data)
247
275
 
248
276
  @property
@@ -263,11 +291,12 @@ class MatterNode:
263
291
  return self.node_data.available
264
292
 
265
293
  @property
266
- def device_info(self) -> Clusters.BasicInformation:
294
+ def device_info(self) -> Clusters.BasicInformation | None:
267
295
  """
268
296
  Return device info for this Node.
269
297
 
270
- Returns BasicInformation from the Node itself (endpoint 0).
298
+ Returns BasicInformation from the Node itself (endpoint 0), or None while the node has not
299
+ reported it.
271
300
  """
272
301
  return self.get_cluster(0, Clusters.BasicInformation)
273
302
 
@@ -304,17 +333,49 @@ class MatterNode:
304
333
 
305
334
  Returns None is the Cluster is not present on the node.
306
335
  """
307
- return self.endpoints[endpoint].get_cluster(cluster)
336
+ if (endpoint_obj := self.endpoints.get(endpoint)) is None:
337
+ return None
338
+ return endpoint_obj.get_cluster(cluster)
308
339
 
309
340
  def get_compose_parent(self, endpoint_id: int) -> MatterEndpoint | None:
310
- """Return endpoint of parent if the endpoint belongs to a Composed device."""
311
- if parent_id := self._composed_endpoints.get(endpoint_id):
312
- return self.endpoints[parent_id]
313
- return None
341
+ """Return the endpoint of the composed device the endpoint is a part of, at any depth.
342
+
343
+ Consumers derive the device of a part from this endpoint, so a part nested below another
344
+ part resolves to the top of the composition, not to its closest parent.
345
+ """
346
+ if (parent_id := self._composed_endpoints.get(endpoint_id)) is None:
347
+ return None
348
+ seen = {endpoint_id, parent_id}
349
+ while (next_id := self._composed_endpoints.get(parent_id)) is not None and next_id not in seen:
350
+ seen.add(next_id)
351
+ parent_id = next_id
352
+ return self.endpoints.get(parent_id)
353
+
354
+ def get_closest_compose_parent(self, endpoint_id: int) -> MatterEndpoint | None:
355
+ """Return the closest endpoint the endpoint is a part of."""
356
+ if (parent_id := self._composed_endpoints.get(endpoint_id)) is None:
357
+ return None
358
+ return self.endpoints.get(parent_id)
359
+
360
+ def get_compose_child_ids(self, endpoint_id: int) -> tuple[int, ...]:
361
+ """Return endpoint IDs of the parts, at any depth, of the composed device the endpoint is the top of."""
362
+ return tuple(
363
+ sorted(
364
+ child_id
365
+ for child_id in self._composed_endpoints
366
+ if (parent := self.get_compose_parent(child_id)) is not None and parent.endpoint_id == endpoint_id
367
+ )
368
+ )
314
369
 
315
- def get_compose_child_ids(self, endpoint_id: int) -> tuple[int, ...] | None:
316
- """Return endpoint IDs of any child if the endpoint represents a Composed device."""
317
- return tuple(x for x, y in self._composed_endpoints.items() if y == endpoint_id)
370
+ def get_bridge_parent(self, endpoint_id: int) -> MatterEndpoint | None:
371
+ """Return the Aggregator endpoint that bridges the given endpoint, if any."""
372
+ if (parent_id := self._bridge_parents.get(endpoint_id)) is None:
373
+ return None
374
+ return self.endpoints.get(parent_id)
375
+
376
+ def get_bridge_child_ids(self, endpoint_id: int) -> tuple[int, ...]:
377
+ """Return endpoint IDs of the devices bridged by the given Aggregator endpoint."""
378
+ return tuple(sorted(x for x, y in self._bridge_parents.items() if y == endpoint_id))
318
379
 
319
380
  def update(self, node_data: MatterNodeData) -> None:
320
381
  """Update MatterNode from MatterNodeData."""
@@ -326,6 +387,7 @@ class MatterNode:
326
387
  if endpoint_id not in endpoint_data:
327
388
  endpoint_data[endpoint_id] = {}
328
389
  endpoint_data[endpoint_id][attribute_path] = attribute_data
390
+ self._snapshot_endpoint_ids = set(endpoint_data)
329
391
  for endpoint_id, attributes_data in endpoint_data.items():
330
392
  if endpoint_id in self.endpoints:
331
393
  self.endpoints[endpoint_id].update(attributes_data)
@@ -333,15 +395,64 @@ class MatterNode:
333
395
  self.endpoints[endpoint_id] = MatterEndpoint(
334
396
  endpoint_id=endpoint_id, attributes_data=attributes_data, node=self
335
397
  )
336
- # composed devices reference to other endpoints through the partsList attribute
337
- # create a mapping table to quickly map this
398
+ self._map_endpoint_parents()
399
+
400
+ def _remove_endpoint(self, endpoint_id: int) -> None:
401
+ """Remove an endpoint together with its parts, and every parent relation they are in.
402
+
403
+ This is the only way an endpoint leaves `endpoints`: a snapshot that no longer reports an
404
+ endpoint keeps its object until the server announces the removal, because consumers look
405
+ the endpoint up when handling that announcement. The parts of the endpoint, at any depth,
406
+ leave with it whether or not a snapshot still reports them: their device is gone, and left
407
+ behind they would resolve to no device when their own removal arrives. A device the
408
+ endpoint bridged stays, without its bridge parent, until its own removal arrives.
409
+
410
+ May only be called by logic that received data from the server.
411
+ """
412
+ if endpoint_id not in self.endpoints:
413
+ return
414
+ removed = {endpoint_id}
415
+ pending = [endpoint_id]
416
+ while pending:
417
+ parent_id = pending.pop()
418
+ for child_id, child_parent_id in self._composed_endpoints.items():
419
+ if child_parent_id == parent_id and child_id not in removed:
420
+ removed.add(child_id)
421
+ pending.append(child_id)
422
+ for removed_id in removed:
423
+ del self.endpoints[removed_id]
424
+ self._reported_missing_bridged_info.discard(removed_id)
425
+ self._composed_endpoints = {
426
+ child_id: parent_id for child_id, parent_id in self._composed_endpoints.items() if child_id not in removed
427
+ }
428
+ self._bridge_parents = {
429
+ child_id: parent_id
430
+ for child_id, parent_id in self._bridge_parents.items()
431
+ if child_id not in removed and parent_id not in removed
432
+ }
433
+
434
+ def _map_endpoint_parents(self) -> None:
435
+ """Resolve each endpoint to its closest parent endpoint, split by parent kind.
436
+
437
+ An endpoint's partsList may enumerate its whole family - every descendant, not just
438
+ the direct children - which is the pattern a bridge uses. Of the endpoints listing a
439
+ given endpoint, the closest one is therefore the endpoint that lists none of the
440
+ others - on contradictory lists the smaller family wins, then the lower endpoint
441
+ number - and a parent is only accepted while the relations stay a tree.
442
+
443
+ Only the endpoints of the last snapshot take part. A snapshot replaces the relation of an
444
+ endpoint only where it names a parent for it: one that bridges or composes it, or the
445
+ parent it already had, whose classification then wins. Where it names none - the endpoint
446
+ unlisted or unreported, its parent without a Descriptor or device types, or listed only by
447
+ endpoints that make it neither bridged nor a part - the endpoint keeps its relation until
448
+ its own removal or that of the parent is announced, so a consumer handling the
449
+ announcement still finds the device the endpoint is a part of. A kept relation that would
450
+ close a cycle with a named one is dropped.
451
+ """
452
+ snapshot_ids = self._snapshot_endpoint_ids & self.endpoints.keys()
453
+ families: dict[int, set[int]] = {}
338
454
  for endpoint in self.endpoints.values():
339
- if RootNode in endpoint.device_types:
340
- # ignore root endpoint
341
- continue
342
- if Aggregator in endpoint.device_types:
343
- # ignore Bridge endpoint
344
- # (as that will also use partsList to indicate its child's)
455
+ if endpoint.endpoint_id not in snapshot_ids:
345
456
  continue
346
457
  descriptor = endpoint.get_cluster(Clusters.Descriptor)
347
458
  if descriptor is None:
@@ -351,9 +462,110 @@ class MatterNode:
351
462
  endpoint.endpoint_id,
352
463
  )
353
464
  continue
354
- if descriptor.partsList:
355
- for endpoint_id in descriptor.partsList:
356
- self._composed_endpoints[endpoint_id] = endpoint.endpoint_id
465
+ families[endpoint.endpoint_id] = {
466
+ child_id
467
+ for child_id in descriptor.partsList or ()
468
+ if child_id in snapshot_ids and child_id != endpoint.endpoint_id
469
+ }
470
+
471
+ candidates: dict[int, set[int]] = {}
472
+ for parent_id, family in families.items():
473
+ for child_id in family:
474
+ candidates.setdefault(child_id, set()).add(parent_id)
475
+
476
+ parents: dict[int, int] = {}
477
+ for child_id in sorted(candidates):
478
+ parent_ids = candidates[child_id]
479
+ for parent_id in sorted(
480
+ parent_ids,
481
+ key=lambda pid: (
482
+ len((parent_ids - {pid}) & families[pid]),
483
+ len(families[pid]),
484
+ pid,
485
+ ),
486
+ ):
487
+ if _reaches(parent_id, child_id, parents):
488
+ LOGGER.warning(
489
+ "Ignoring cyclic partsList relation: Node %s, endpoint %s below %s",
490
+ self.node_id,
491
+ child_id,
492
+ parent_id,
493
+ )
494
+ continue
495
+ parents[child_id] = parent_id
496
+ break
497
+
498
+ previous_composed = self._composed_endpoints
499
+ previous_bridge = self._bridge_parents
500
+ self._composed_endpoints = {}
501
+ self._bridge_parents = {}
502
+ decided = {
503
+ child_id for child_id, parent_id in parents.items() if self._map_endpoint_parent(child_id, parent_id)
504
+ }
505
+ related = self._composed_endpoints | self._bridge_parents
506
+ for relations, previous in (
507
+ (self._composed_endpoints, previous_composed),
508
+ (self._bridge_parents, previous_bridge),
509
+ ):
510
+ for child_id, parent_id in sorted(previous.items()):
511
+ if child_id in related or (child_id in decided and parents[child_id] == parent_id):
512
+ continue
513
+ if _reaches(parent_id, child_id, related):
514
+ LOGGER.warning(
515
+ "Dropping parent relation that a snapshot made cyclic: Node %s, endpoint %s below %s",
516
+ self.node_id,
517
+ child_id,
518
+ parent_id,
519
+ )
520
+ continue
521
+ relations[child_id] = parent_id
522
+ related[child_id] = parent_id
523
+
524
+ self._report_bridged_devices_without_info()
525
+
526
+ def _report_bridged_devices_without_info(self) -> None:
527
+ """Warn about bridged devices the bridge does not describe, once per endpoint.
528
+
529
+ Such an endpoint has no device info of its own, and the info of the bridge describes a
530
+ different device, so it stays unnamed until the bridge reports it.
531
+ """
532
+ for endpoint_id, endpoint in self.endpoints.items():
533
+ if endpoint_id in self._reported_missing_bridged_info:
534
+ continue
535
+ if not endpoint.is_bridged_device:
536
+ continue
537
+ if endpoint.get_cluster(Clusters.BridgedDeviceBasicInformation) is not None:
538
+ continue
539
+ self._reported_missing_bridged_info.add(endpoint_id)
540
+ LOGGER.warning(
541
+ "Bridged device without BridgedDeviceBasicInformation: Node %s, endpoint %s",
542
+ self.node_id,
543
+ endpoint_id,
544
+ )
545
+
546
+ def _map_endpoint_parent(self, child_id: int, parent_id: int) -> bool:
547
+ """Record what the parent endpoint makes of the endpoint below it, return whether it decides.
548
+
549
+ A child of the root endpoint is top level. Below an Aggregator, a Bridged Node is a
550
+ bridged device; any other child stands alone, whether or not the Aggregator is itself a
551
+ Bridged Node (so ids derived from the compose parent match older clients). Below any other endpoint, a child
552
+ is a part of it - also when the device types of that endpoint are unknown to this client,
553
+ so only a parent that reports no device type at all decides nothing.
554
+ """
555
+ if parent_id == ROOT_ENDPOINT_ID:
556
+ return True
557
+ parent = self.endpoints[parent_id]
558
+ descriptor = parent.get_cluster(Clusters.Descriptor)
559
+ assert descriptor is not None # only endpoints with a Descriptor are parents
560
+ if not descriptor.deviceTypeList:
561
+ return False
562
+ if RootNode in parent.device_types:
563
+ return True
564
+ if Aggregator not in parent.device_types:
565
+ self._composed_endpoints[child_id] = parent_id
566
+ elif BridgedNode in self.endpoints[child_id].device_types:
567
+ self._bridge_parents[child_id] = parent_id
568
+ return True
357
569
 
358
570
  def update_attribute(self, attribute_path: str, new_value: Any) -> None:
359
571
  """Handle Attribute value update."""
@@ -6,6 +6,7 @@ from chip.clusters.cluster_defs.AqaraRadarSensingUnionCluster import AqaraRadarS
6
6
  from chip.clusters.cluster_defs.DraftElectricalMeasurementCluster import DraftElectricalMeasurementCluster
7
7
  from chip.clusters.cluster_defs.EveCluster import EveCluster
8
8
  from chip.clusters.cluster_defs.HeimanCluster import HeimanCluster
9
+ from chip.clusters.cluster_defs.IkeaValveCalibrationCluster import IkeaValveCalibrationCluster
9
10
  from chip.clusters.cluster_defs.InovelliCluster import InovelliCluster
10
11
  from chip.clusters.cluster_defs.NeoCluster import NeoCluster
11
12
  from chip.clusters.cluster_defs.TclDehumidifierCluster import TclDehumidifierCluster
@@ -19,6 +20,7 @@ ALL_CUSTOM_CLUSTERS: dict = {
19
20
  DraftElectricalMeasurementCluster.id: DraftElectricalMeasurementCluster,
20
21
  EveCluster.id: EveCluster,
21
22
  HeimanCluster.id: HeimanCluster,
23
+ IkeaValveCalibrationCluster.id: IkeaValveCalibrationCluster,
22
24
  InovelliCluster.id: InovelliCluster,
23
25
  NeoCluster.id: NeoCluster,
24
26
  TclDehumidifierCluster.id: TclDehumidifierCluster,
@@ -34,6 +36,7 @@ __all__ = [
34
36
  "DraftElectricalMeasurementCluster",
35
37
  "EveCluster",
36
38
  "HeimanCluster",
39
+ "IkeaValveCalibrationCluster",
37
40
  "InovelliCluster",
38
41
  "NeoCluster",
39
42
  "TclDehumidifierCluster",
@@ -258,6 +258,10 @@ class CommissioningParameters:
258
258
  setup_pin_code: int
259
259
  setup_manual_code: str
260
260
  setup_qr_code: str
261
+ discriminator: int | None = None
262
+ vendor_id: int | None = None
263
+ product_id: int | None = None
264
+ commissioning_timeout: int | None = None
261
265
 
262
266
 
263
267
  @dataclass