24
24
from .ClusterObjects import ClusterAttributeDescriptor
25
25
import chip .exceptions
26
26
import chip .interaction_model
27
+ import chip .tlv
28
+
29
+ import inspect
30
+ import sys
31
+ import logging
27
32
28
33
29
34
@dataclass
30
35
class AttributePath :
31
- EndpointId : int
32
- ClusterId : int
33
- AttributeId : int
36
+ EndpointId : int = None
37
+ ClusterId : int = None
38
+ AttributeId : int = None
39
+
40
+ def __init__ (self , EndpointId : int = None , Cluster = None , Attribute = None , ClusterId = None , AttributeId = None ):
41
+ self .EndpointId = EndpointId
42
+ if Cluster is not None :
43
+ # Wildcard read for a specific cluster
44
+ if (Attribute is not None ) or (ClusterId is not None ) or (AttributeId is not None ):
45
+ raise Warning (
46
+ "Attribute, ClusterId and AttributeId is ignored when Cluster is specified" )
47
+ self .ClusterId = Cluster .id
48
+ return
49
+ if Attribute is not None :
50
+ if (ClusterId is not None ) or (AttributeId is not None ):
51
+ raise Warning (
52
+ "ClusterId and AttributeId is ignored when Attribute is specified" )
53
+ self .ClusterId = Attribute .cluster_id
54
+ self .AttributeId = Attribute .attribute_id
55
+ return
56
+ self .ClusterId = ClusterId
57
+ self .AttributeId = AttributeId
58
+
59
+ def __str__ (self ) -> str :
60
+ return f"{ self .EndpointId } /{ self .ClusterId } /{ self .AttributeId } "
34
61
35
62
36
63
@dataclass
@@ -61,6 +88,72 @@ class AttributeReadResult(AttributeStatus):
61
88
Data : Any = None
62
89
63
90
91
+ _AttributeIndex = {}
92
+
93
+
94
+ def _BuildAttributeIndex ():
95
+ ''' Build internal attribute index for locating the corresponding cluster object by path in the future.
96
+ We do this because this operation will take a long time when there are lots of attributes, it takes about 300ms for a single query.
97
+ This is acceptable during init, but unacceptable when the server returns lots of attributes at the same time.
98
+ '''
99
+ for clusterName , obj in inspect .getmembers (sys .modules ['chip.clusters.Objects' ]):
100
+ if ('chip.clusters.Objects' in str (obj )) and inspect .isclass (obj ):
101
+ for objName , subclass in inspect .getmembers (obj ):
102
+ if inspect .isclass (subclass ) and (('Attribute' ) in str (subclass )):
103
+ for attributeName , attribute in inspect .getmembers (subclass ):
104
+ if inspect .isclass (attribute ):
105
+ for name , field in inspect .getmembers (attribute ):
106
+ if ('__dataclass_fields__' in name ):
107
+ _AttributeIndex [str (AttributePath (ClusterId = field ['cluster_id' ].default , AttributeId = field ['attribute_id' ].default ))] = eval (
108
+ 'chip.clusters.Objects.' + clusterName + '.Attributes.' + attributeName )
109
+
110
+
111
+ class AsyncReadTransaction :
112
+ def __init__ (self , future : Future , eventLoop ):
113
+ self ._event_loop = eventLoop
114
+ self ._future = future
115
+ self ._res = []
116
+
117
+ def _handleAttributeData (self , path : AttributePath , status : int , data : bytes ):
118
+ try :
119
+ imStatus = status
120
+ try :
121
+ imStatus = chip .interaction_model .Status (status )
122
+ except :
123
+ pass
124
+ attributeType = _AttributeIndex .get (str (AttributePath (
125
+ ClusterId = path .ClusterId , AttributeId = path .AttributeId )), None )
126
+ attributeValue = None
127
+ if attributeType is None :
128
+ attributeValue = chip .tlv .TLVReader (data ).get ().get ("Any" , {})
129
+ else :
130
+ attributeValue = attributeType .FromTLV (data )
131
+ self ._res .append (AttributeReadResult (
132
+ Path = path , Status = imStatus , Data = attributeValue ))
133
+ except Exception as ex :
134
+ logging .exception (ex )
135
+
136
+ def handleAttributeData (self , path : AttributePath , status : int , data : bytes ):
137
+ self ._event_loop .call_soon_threadsafe (
138
+ self ._handleAttributeData , path , status , data )
139
+
140
+ def _handleError (self , chipError : int ):
141
+ self ._future .set_exception (
142
+ chip .exceptions .ChipStackError (chipError ))
143
+
144
+ def handleError (self , chipError : int ):
145
+ self ._event_loop .call_soon_threadsafe (
146
+ self ._handleError , chipError
147
+ )
148
+
149
+ def _handleDone (self , asd ):
150
+ if not self ._future .done ():
151
+ self ._future .set_result (self ._res )
152
+
153
+ def handleDone (self ):
154
+ self ._event_loop .call_soon_threadsafe (self ._handleDone , "asdasa" )
155
+
156
+
64
157
class AsyncWriteTransaction :
65
158
def __init__ (self , future : Future , eventLoop ):
66
159
self ._event_loop = eventLoop
@@ -95,6 +188,32 @@ def handleDone(self):
95
188
self ._event_loop .call_soon_threadsafe (self ._handleDone )
96
189
97
190
191
+ _OnReadAttributeDataCallbackFunct = CFUNCTYPE (
192
+ None , py_object , c_uint16 , c_uint32 , c_uint32 , c_uint16 , c_char_p , c_size_t )
193
+ _OnReadErrorCallbackFunct = CFUNCTYPE (
194
+ None , py_object , c_uint32 )
195
+ _OnReadDoneCallbackFunct = CFUNCTYPE (
196
+ None , py_object )
197
+
198
+
199
+ @_OnReadAttributeDataCallbackFunct
200
+ def _OnReadAttributeDataCallback (closure , endpoint : int , cluster : int , attribute : int , status , data , len ):
201
+ dataBytes = ctypes .string_at (data , len )
202
+ closure .handleAttributeData (AttributePath (
203
+ EndpointId = endpoint , ClusterId = cluster , AttributeId = attribute ), status , dataBytes [:])
204
+
205
+
206
+ @_OnReadErrorCallbackFunct
207
+ def _OnReadErrorCallback (closure , chiperror : int ):
208
+ closure .handleError (chiperror )
209
+
210
+
211
+ @_OnReadDoneCallbackFunct
212
+ def _OnReadDoneCallback (closure ):
213
+ closure .handleDone ()
214
+ ctypes .pythonapi .Py_DecRef (ctypes .py_object (closure ))
215
+
216
+
98
217
_OnWriteResponseCallbackFunct = CFUNCTYPE (
99
218
None , py_object , c_uint16 , c_uint32 , c_uint32 , c_uint16 )
100
219
_OnWriteErrorCallbackFunct = CFUNCTYPE (
@@ -105,7 +224,8 @@ def handleDone(self):
105
224
106
225
@_OnWriteResponseCallbackFunct
107
226
def _OnWriteResponseCallback (closure , endpoint : int , cluster : int , attribute : int , status ):
108
- closure .handleResponse (AttributePath (endpoint , cluster , attribute ), status )
227
+ closure .handleResponse (AttributePath (
228
+ EndpointId = endpoint , ClusterId = cluster , AttributeId = attribute ), status )
109
229
110
230
111
231
@_OnWriteErrorCallbackFunct
@@ -144,6 +264,31 @@ def WriteAttributes(future: Future, eventLoop, device, attributes: List[Attribut
144
264
return res
145
265
146
266
267
+ def ReadAttributes (future : Future , eventLoop , device , attributes : List [AttributePath ]) -> int :
268
+ handle = chip .native .GetLibraryHandle ()
269
+ transaction = AsyncReadTransaction (future , eventLoop )
270
+
271
+ readargs = []
272
+ for attr in attributes :
273
+ path = chip .interaction_model .AttributePathIBstruct .parse (
274
+ b'\xff ' * chip .interaction_model .AttributePathIBstruct .sizeof ())
275
+ if attr .EndpointId is not None :
276
+ path .EndpointId = attr .EndpointId
277
+ if attr .ClusterId is not None :
278
+ path .ClusterId = attr .ClusterId
279
+ if attr .AttributeId is not None :
280
+ path .AttributeId = attr .AttributeId
281
+ path = chip .interaction_model .AttributePathIBstruct .build (path )
282
+ readargs .append (ctypes .c_char_p (path ))
283
+
284
+ ctypes .pythonapi .Py_IncRef (ctypes .py_object (transaction ))
285
+ res = handle .pychip_ReadClient_ReadAttributes (
286
+ ctypes .py_object (transaction ), device , ctypes .c_size_t (len (attributes )), * readargs )
287
+ if res != 0 :
288
+ ctypes .pythonapi .Py_DecRef (ctypes .py_object (transaction ))
289
+ return res
290
+
291
+
147
292
def Init ():
148
293
handle = chip .native .GetLibraryHandle ()
149
294
@@ -155,6 +300,13 @@ def Init():
155
300
handle .pychip_WriteClient_WriteAttributes .restype = c_uint32
156
301
setter .Set ('pychip_WriteClient_InitCallbacks' , None , [
157
302
_OnWriteResponseCallbackFunct , _OnWriteErrorCallbackFunct , _OnWriteDoneCallbackFunct ])
303
+ handle .pychip_ReadClient_ReadAttributes .restype = c_uint32
304
+ setter .Set ('pychip_ReadClient_InitCallbacks' , None , [
305
+ _OnReadAttributeDataCallbackFunct , _OnReadErrorCallbackFunct , _OnReadDoneCallbackFunct ])
158
306
159
307
handle .pychip_WriteClient_InitCallbacks (
160
308
_OnWriteResponseCallback , _OnWriteErrorCallback , _OnWriteDoneCallback )
309
+ handle .pychip_ReadClient_InitCallbacks (
310
+ _OnReadAttributeDataCallback , _OnReadErrorCallback , _OnReadDoneCallback )
311
+
312
+ _BuildAttributeIndex ()
0 commit comments