@@ -23,10 +23,10 @@ import matter
2323#@ solidify:Matter_IM_Status,weak
2424#@ solidify:Matter_IM_InvokeResponse,weak
2525#@ solidify:Matter_IM_WriteResponse,weak
26- #@ solidify:Matter_IM_ReportData ,weak
27- #@ solidify:Matter_IM_ReportDataSubscribed ,weak
26+ #@ solidify:Matter_IM_ReportData_Pull ,weak
27+ #@ solidify:Matter_IM_ReportDataSubscribed_Pull ,weak
2828#@ solidify:Matter_IM_SubscribedHeartbeat,weak
29- #@ solidify:Matter_IM_SubscribeResponse ,weak
29+ #@ solidify:Matter_IM_SubscribeResponse_Pull ,weak
3030
3131#################################################################################
3232# Matter_IM_Message
@@ -48,7 +48,7 @@ class Matter_IM_Message
4848 end
4949
5050 def reset ( msg, opcode, reliable)
51- self .resp = msg.build_response ( opcode, reliable)
51+ self .resp = ( msg != nil ) ? msg .build_response ( opcode, reliable) : nil # is nil for spontaneous reports
5252 self .ready = true # by default send immediately
5353 self .expiration = tasmota.millis () + self .MSG_TIMEOUT
5454 self .last_counter = 0 # avoid `nil` value
@@ -159,53 +159,91 @@ end
159159matter.IM_WriteResponse = Matter_IM_WriteResponse
160160
161161#################################################################################
162- # Matter_IM_ReportData
162+ # Matter_IM_ReportData_Pull
163163#
164164# Report Data for a Read Request
165+ #
166+ # This version pull the attributes in lazy mode, only when response is computed
165167#################################################################################
166- class Matter_IM_ReportData : Matter_IM_Message
168+ class Matter_IM_ReportData_Pull : Matter_IM_Message
167169 static var MAX_MESSAGE = 1200 # max bytes size for a single TLV worklaod
168170 # the maximum MTU is 1280, which leaves 80 bytes for the rest of the message
169171 # section 4.4.4 (p. 114)
172+ # note: `self.data` (bytes or nil) is containing any remaining responses that could not fit in previous packets
173+ var generator_or_arr # a PathGenerator or an array of PathGenerator
174+ var subscription_id # if not `nil`, subscription_id in response
175+ var suppress_response # if not `nil`, suppress_response attribute
170176
171- def init ( msg, data )
177+ def init ( msg, ctx_generator_or_arr )
172178 super ( self ) .init ( msg, 0x05 #-Report Data-# , true )
173- self .data = data
179+ self .generator_or_arr = ctx_generator_or_arr
180+ end
181+
182+ def set_subscription_id ( subscription_id)
183+ self .subscription_id = subscription_id
184+ end
185+
186+ def set_suppress_response ( suppress_response)
187+ self .suppress_response = suppress_response
174188 end
175189
176190 # default responder for data
177191 def send_im ( responder)
178- # log(format("MTR: IM_ReportData send_im exch=%i ready=%i", self.resp.exchange_id, self.ready ? 1 : 0), 3)
192+ # log(format(">>>: Matter_IM_ReportData_Pull send_im exch=%i ready=%i", self.resp.exchange_id, self.ready ? 1 : 0), 3)
179193 if ! self .ready return false end
180- var resp = self .resp # response frame object
181- var data = self .data # TLV data of the response (if any)
182- var was_chunked = data.more_chunked_messages # is this following a chunked packet?
183-
184- # the message were grouped by right-sized binaries upfront, we just need to send one block at time
185- var elements = 1 # number of elements added
186-
187- # log(format("MTR: exch=%i elements=%i msg_sz=%i total=%i", self.get_exchangeid(), elements, msg_sz, sz_attribute_reports), 3)
188- var next_elemnts
189- if data.attribute_reports != nil
190- next_elemnts = data.attribute_reports [ elements .. ]
191- data.attribute_reports = data.attribute_reports [ 0 .. elements - 1 ]
192- data.more_chunked_messages = ( size ( next_elemnts) > 0 )
193- else
194- data.more_chunked_messages = false
195- end
194+ var resp = self .resp # response frame object
195+ var data = ( self .data != nil ) ? self .data : bytes () # bytes() object of the TLV encoded response
196+ self .data = nil # we remove the data that was saved for next packet
197+
198+ var not_full = true # marker used to exit imbricated loops
199+
200+
201+ while not_full && ( self .generator_or_arr != nil )
202+ # get the current generator (first element of list or single object)
203+ var current_generator = isinstance ( self .generator_or_arr , list) ? self .generator_or_arr [ 0 ] : self .generator_or_arr
204+ # log(f">>>: ReportData_Pull send_im start {current_generator.path_in_endpoint}/{current_generator.path_in_cluster}/{current_generator.path_in_attribute}",3)
205+
206+ var ctx
207+ while not_full && ( ctx := current_generator.next ()) # 'not_full' must be first to avoid removing an item when we don't want
208+ # log(f">>>: ReportData_Pull {ctx=}", 3)
209+ var debug = responder.device.debug
210+ var force_log = current_generator.is_direct () || debug
211+ var elt_bytes = responder.im.read_single_attribute_to_bytes ( current_generator.get_pi () , ctx, resp.session , force_log) # TODO adapt no_log
212+ if ( elt_bytes == nil ) continue end # silently ignored, iterate to next
213+ # check if we overflow
214+ if ( size ( data) + size ( elt_bytes) > self .MAX_MESSAGE )
215+ self .data = elt_bytes # save response for later
216+ not_full = false
217+ else
218+ data.append ( elt_bytes) # append response since we have enough room
219+ end
220+ end
196221
197- if was_chunked
198- # log(format("MTR: .Read_Attr next_chunk exch=%i", self.get_exchangeid()), 4)
199- end
200- if data.more_chunked_messages
201- if ! was_chunked
202- # log(format("MTR: .Read_Attr first_chunk exch=%i", self.get_exchangeid()), 4)
222+ # if we are here, then we exhausted the current generator, and we need to move to the next one
223+ if not_full
224+ # log(f">>>: ReportData_Pull remove current generator",3)
225+ if isinstance ( self .generator_or_arr , list)
226+ self .generator_or_arr.remove ( 0 ) # remove first element
227+ if size ( self .generator_or_arr ) == 0
228+ self .generator_or_arr = nil # empty array so we put nil
229+ end
230+ else
231+ self .generator_or_arr = nil # there was a single entry, so replace with nil
232+ end
203233 end
204- # log("MTR: sending TLV" + str(data), 4)
234+
205235 end
206236
237+ # prepare the response
238+ var ret = matter.ReportDataMessage ()
239+ ret.subscription_id = self .subscription_id
240+ ret.suppress_response = self .suppress_response
241+ # ret.suppress_response = true
242+ ret.attribute_reports = [ data]
243+ ret.more_chunked_messages = ( self .data != nil ) # we got more data to send
244+
207245 # print(">>>>> send elements before encode")
208- var raw_tlv = self .data .to_TLV()
246+ var raw_tlv = ret .to_TLV ()
209247 # print(">>>>> send elements before encode 2")
210248 var encoded_tlv = raw_tlv.tlv2raw ( bytes ( self .MAX_MESSAGE )) # takes time
211249 # print(">>>>> send elements before encode 3")
@@ -217,37 +255,49 @@ class Matter_IM_ReportData : Matter_IM_Message
217255 responder.send_response_frame ( resp)
218256 self .last_counter = resp.message_counter
219257
220- if next_elemnts != nil && size ( next_elemnts) > 0
221- data.attribute_reports = next_elemnts
222- # log(format("MTR: to_be_sent_later size=%i exch=%i", size(data.attribute_reports), resp.exchange_id), 4)
258+ if ret.more_chunked_messages # we have more to send
223259 self .ready = false # wait for Status Report before continuing sending
224260 # keep alive
225261 else
262+ # log(f">>>: ReportData_Pull finished",3)
226263 self .finish = true # finished, remove
227264 end
265+
228266 end
229267
230268end
231- matter.IM_ReportData = Matter_IM_ReportData
232-
269+ matter.IM_ReportData_Pull = Matter_IM_ReportData_Pull
233270
234271#################################################################################
235- # Matter_IM_ReportDataSubscribed
272+ # Matter_IM_ReportDataSubscribed_Pull
236273#
237274# Main difference is that we are the spontaneous initiator
238275#################################################################################
239- class Matter_IM_ReportDataSubscribed : Matter_IM_ReportData
276+ class Matter_IM_ReportDataSubscribed_Pull : Matter_IM_ReportData_Pull
277+ # inherited from Matter_IM_Message
278+ # static var MSG_TIMEOUT = 5000 # 5s
279+ # var expiration # expiration time for the reporting
280+ # var resp # response Frame object
281+ # var ready # bool: ready to send (true) or wait (false)
282+ # var finish # if true, the message is removed from the queue
283+ # var data # TLV data of the response (if any)
284+ # var last_counter # counter value of last sent packet (to match ack)
285+ # inherited from Matter_IM_ReportData_Pull
286+ # static var MAX_MESSAGE = 1200 # max bytes size for a single TLV worklaod
287+ # var generator_or_arr # a PathGenerator or an array of PathGenerator
288+ # var subscription_id # if not `nil`, subscription_id in response
240289 var sub # subscription object
241290 var report_data_phase # true during reportdata
242291
243- def init ( message_handler, session, data, sub)
292+ def init ( message_handler, session, ctx_generator_or_arr, sub)
293+ super ( self ) .init ( nil , ctx_generator_or_arr) # send msg=nil to avoid creating a reponse
294+ # we need to initiate a new virtual response, because it's a spontaneous message
244295 self .resp = matter.Frame.initiate_response ( message_handler, session, 0x05 #-Report Data-# , true )
245- self .data = data
246- self .ready = true # by default send immediately
247- self .expiration = tasmota.millis () + self .MSG_TIMEOUT
248296 #
249297 self .sub = sub
250298 self .report_data_phase = true
299+ self .set_subscription_id ( sub.subscription_id )
300+ self .set_suppress_response ( false )
251301 end
252302
253303 def reached_timeout ()
@@ -256,7 +306,7 @@ class Matter_IM_ReportDataSubscribed : Matter_IM_ReportData
256306
257307 # ack received, confirm the heartbeat
258308 def ack_received ( msg)
259- # log(format("MTR: IM_ReportDataSubscribed ack_received sub=%i", self.sub.subscription_id), 3)
309+ # log(format("MTR: IM_ReportDataSubscribed_Pull ack_received sub=%i", self.sub.subscription_id), 3)
260310 super ( self ) .ack_received ( msg)
261311 if ! self .report_data_phase
262312 # if ack is received while all data is sent, means that it finished without error
@@ -271,14 +321,14 @@ class Matter_IM_ReportDataSubscribed : Matter_IM_ReportData
271321
272322 # we received an ACK error, remove subscription
273323 def status_error_received ( msg)
274- # log(format("MTR: IM_ReportDataSubscribed status_error_received sub=%i exch=%i", self.sub.subscription_id, self.resp.exchange_id), 3)
324+ # log(format("MTR: IM_ReportDataSubscribed_Pull status_error_received sub=%i exch=%i", self.sub.subscription_id, self.resp.exchange_id), 3)
275325 self .sub.remove_self ()
276326 end
277327
278328 # ack received for previous message, proceed to next (if any)
279329 # return true if we manage the ack ourselves, false if it needs to be done upper
280330 def status_ok_received ( msg)
281- # log(format("MTR: IM_ReportDataSubscribed status_ok_received sub=%i exch=%i", self.sub.subscription_id, self.resp.exchange_id), 3)
331+ # log(format("MTR: IM_ReportDataSubscribed_Pull status_ok_received sub=%i exch=%i", self.sub.subscription_id, self.resp.exchange_id), 3)
282332 if self .report_data_phase
283333 return super ( self ) .status_ok_received ( msg)
284334 else
@@ -291,10 +341,11 @@ class Matter_IM_ReportDataSubscribed : Matter_IM_ReportData
291341 # returns true if transaction is complete (remove object from queue)
292342 # default responder for data
293343 def send_im ( responder)
294- # log(format("MTR: IM_ReportDataSubscribed send sub=%i exch=%i ready=%i", self.sub.subscription_id, self.resp.exchange_id, self.ready ? 1 : 0), 3)
344+ # log(format("MTR: IM_ReportDataSubscribed_Pull send sub=%i exch=%i ready=%i", self.sub.subscription_id, self.resp.exchange_id, self.ready ? 1 : 0), 3)
295345 # log(format("MTR: ReportDataSubscribed::send_im size(self.data.attribute_reports)=%i ready=%s report_data_phase=%s", size(self.data.attribute_reports), str(self.ready), str(self.report_data_phase)), 3)
296346 if ! self .ready return false end
297- if size ( self .data.attribute_reports ) > 0 # do we have still attributes to send
347+
348+ if ( self .generator_or_arr != nil ) # do we have still attributes to send
298349 if self .report_data_phase
299350 super ( self ) .send_im ( responder)
300351 # log(format("MTR: ReportDataSubscribed::send_im called super finish=%i", self.finish), 3)
@@ -327,7 +378,7 @@ class Matter_IM_ReportDataSubscribed : Matter_IM_ReportData
327378 end
328379 end
329380end
330- matter.IM_ReportDataSubscribed = Matter_IM_ReportDataSubscribed
381+ matter.IM_ReportDataSubscribed_Pull = Matter_IM_ReportDataSubscribed_Pull
331382
332383#################################################################################
333384# Matter_IM_SubscribedHeartbeat
@@ -336,16 +387,17 @@ matter.IM_ReportDataSubscribed = Matter_IM_ReportDataSubscribed
336387#
337388# Main difference is that we are the spontaneous initiator
338389#################################################################################
339- class Matter_IM_SubscribedHeartbeat : Matter_IM_ReportData
390+ class Matter_IM_SubscribedHeartbeat : Matter_IM_ReportData_Pull
340391 var sub # subscription object
341392
342- def init ( message_handler, session, data, sub)
393+ def init ( message_handler, session, sub)
394+ super ( self ) .init ( nil , nil #-no ctx_generator_or_arr-# ) # send msg=nil to avoid creating a reponse
395+ # we need to initiate a new virtual response, because it's a spontaneous message
343396 self .resp = matter.Frame.initiate_response ( message_handler, session, 0x05 #-Report Data-# , true )
344- self .data = data
345- self .ready = true # by default send immediately
346- self .expiration = tasmota.millis () + self .MSG_TIMEOUT
347397 #
348398 self .sub = sub
399+ self .set_subscription_id ( sub.subscription_id )
400+ self .set_suppress_response ( true )
349401 end
350402
351403 def reached_timeout ()
@@ -386,18 +438,22 @@ end
386438matter.IM_SubscribedHeartbeat = Matter_IM_SubscribedHeartbeat
387439
388440#################################################################################
389- # Matter_IM_SubscribeResponse
441+ # Matter_IM_SubscribeResponse_Pull
390442#
391- # Report Data for a Read Request
443+ # Report Data for a Read Request - pull (lazy) mode
392444#################################################################################
393- class Matter_IM_SubscribeResponse : Matter_IM_ReportData
445+ class Matter_IM_SubscribeResponse_Pull : Matter_IM_ReportData_Pull
446+ # inherited
447+ # static var MAX_MESSAGE = 1200 # max bytes size for a single TLV worklaod
448+ # var generator_or_arr # a PathGenerator or an array of PathGenerator
394449 var sub # subscription object
395450 var report_data_phase # true during reportdata
396451
397- def init ( msg, data , sub)
398- super ( self ) .init ( msg, data )
452+ def init ( msg, ctx_generator_or_arr , sub)
453+ super ( self ) .init ( msg, ctx_generator_or_arr )
399454 self .sub = sub
400455 self .report_data_phase = true
456+ self .set_subscription_id ( sub.subscription_id )
401457 end
402458
403459 # default responder for data
@@ -414,6 +470,7 @@ class Matter_IM_SubscribeResponse : Matter_IM_ReportData
414470 self .ready = false # wait for Status Report before continuing sending
415471
416472 else
473+
417474 # send the final SubscribeReponse
418475 var resp = self .resp
419476 var sr = matter.SubscribeResponseMessage ()
@@ -440,6 +497,6 @@ class Matter_IM_SubscribeResponse : Matter_IM_ReportData
440497 end
441498 return super ( self ) .status_ok_received ( msg)
442499 end
443-
500+
444501end
445- matter.IM_SubscribeResponse = Matter_IM_SubscribeResponse
502+ matter.IM_SubscribeResponse_Pull = Matter_IM_SubscribeResponse_Pull
0 commit comments