@@ -38,20 +38,70 @@ class Matter_EventQueued
3838 var event_id
3939 var is_urgent
4040 var priority
41+ var data0, data1, data2
4142 var event_no
43+ var epoch_timestamp
4244 var raw_tlv # content encoded as full TLV
4345
44- def init ( event_ib)
45- self .endpoint = event_ib.path.endpoint
46- self .cluster = event_ib.path.cluster
47- self .event_id = event_ib.path.event
48- self .is_urgent = event_ib.path.is_urgent
49- self .priority = event_ib.priority
46+ def init ( event_no, endpoint, cluster, event_id, is_urgent, priority, data0, data1, data2)
47+ self .event_no = event_no
48+ self .endpoint = endpoint
49+ self .cluster = cluster
50+ self .event_id = event_id
51+ self .is_urgent = is_urgent
52+ self .priority = priority
53+ # priority
5054 if ( self .priority < 0 ) self .priority = 0 end
5155 if ( self .priority > matter.EVENT_CRITICAL ) self .priority = matter.EVENT_CRITICAL end
52- self .event_no = int64.toint64 ( event_ib.event_number ) # int64
53- self .raw_tlv = event_ib.to_TLV () .tlv2raw () # bytes()
56+ # epoch_time
57+ self .epoch_timestamp = tasmota.rtc ( 'utc' )
58+ if ( self .epoch_timestamp < 1700000000 )
59+ self .epoch_timestamp = tasmota.rtc ( 'config_time' ) # no valid time, take the last config time
60+ end
61+ self .data0 = data0
62+ self .data1 = data1
63+ self .data2 = data2
64+ end
65+
66+ #################################################################################
67+ # to_raw_bytes
68+ #
69+ # Computes a complete EventReportIB structure as bytes()
70+ #
71+ # It is memoized in self.raw_tlv, but is cleaned after sending messages to
72+ # free up some space
73+ def to_raw_bytes ()
74+ if ( self .raw_tlv == nil )
75+ var event_report = matter.EventReportIB ()
76+ var event_ib = matter.EventDataIB ()
77+ event_report.event_data = event_ib
78+ var event_path = matter.EventPathIB ()
79+ event_path.endpoint = self .endpoint
80+ event_path.cluster = self .cluster
81+ event_path.event = self .event_id
82+ event_path.is_urgent = self .is_urgent
83+ event_ib.path = event_path
84+ event_ib.priority = self .priority
85+ event_ib.event_number = self .event_no
86+ event_ib.epoch_timestamp = self .epoch_timestamp
87+ event_ib.data = matter.TLV.Matter_TLV_struct ()
88+ if ( self .data0 != nil ) event_ib.data.add_obj ( 0 , self .data0 ) end
89+ if ( self .data1 != nil ) event_ib.data.add_obj ( 1 , self .data1 ) end
90+ if ( self .data2 != nil ) event_ib.data.add_obj ( 2 , self .data2 ) end
91+
92+ self .raw_tlv = event_report.to_TLV () .tlv2raw () # bytes()
93+ end
94+ return self .raw_tlv
5495 end
96+
97+ #################################################################################
98+ # compact
99+ #
100+ # Remove the local cache of `raw_tlv` to save space (it can always be recreated)
101+ def compact ()
102+ self .raw_tlv = nil
103+ end
104+
55105end
56106matter.EventQueued = Matter_EventQueued
57107
@@ -77,11 +127,16 @@ matter.EventQueued = Matter_EventQueued
77127# EVENT_CRITICAL=2
78128
79129#################################################################################
80- # Matter_IM class
130+ # Matter_EventHandler class
131+ #
132+ # Keep track of events in 3 queues for DEBUG/INFO/CRITICAL levels
133+ #
134+ # Invariants:
135+ # All 3 queues (debug/info/critical) have events in sorted order by `event_no`
81136#################################################################################
82137class Matter_EventHandler
83- static var EVENT_NO_INCR = 1000 # counter increased when persisting
84- static var EVENT_NO_FILENAME = "_matter_event_no"
138+ static var EVENT_NO_INCR = 1000 # counter increased when persisting, default value from Matter spec
139+ static var EVENT_NO_KEY = "_matter_event_no"
85140 static var EVENT_QUEUE_SIZE_MAX = 10 # each queue is 10 elements depth
86141
87142 # circular buffers
@@ -109,22 +164,20 @@ class Matter_EventHandler
109164 # with a predefined gap `self.EVENT_NO_INCR` (default 1000)
110165 def load_event_no_persisted ()
111166 import persist
112- var event_no_str = str ( persist.find ( self .EVENT_NO_FILENAME , "0" ))
167+ var event_no_str = str ( persist.find ( self .EVENT_NO_KEY , "0" ))
113168 self .counter_event_no = int64.fromstring ( event_no_str)
114169 self .counter_event_no_persisted = self .counter_event_no.add ( self .EVENT_NO_INCR )
115170 # save back next slot
116- persist.setmember ( self .EVENT_NO_FILENAME , self .counter_event_no_persisted.tostring ())
171+ persist.setmember ( self .EVENT_NO_KEY , self .counter_event_no_persisted.tostring ())
117172 persist.save ()
118173 end
119174
120175 #####################################################################
121176 # Enqueue event
122177 #
123- # Takes `Matter_EventDataIB `
178+ # Takes `EventQueued `
124179 #####################################################################
125- def queue_event ( ev_ib)
126- var ev_queued = matter.EventQueued ( ev_ib)
127-
180+ def queue_event ( ev_queued)
128181 var cur_prio = ev_queued.priority
129182
130183 # we reuse the same logic as connectedhomeip
@@ -183,18 +236,48 @@ class Matter_EventHandler
183236 end
184237 end
185238
239+ #############################################################
240+ # dispatch every second click to sub-objects that need it
241+ def every_second ()
242+ self .compact ()
243+ end
244+
245+ #####################################################################
246+ # Enqueue event
247+ #
248+ # Remove the cached `raw_tlv` from all events in queues
249+ # to save some space
250+ def compact ()
251+ def compact_queue ( q)
252+ var i = 0
253+ while i < size ( q)
254+ q[ i] .compact ()
255+ i += 1
256+ end
257+ end
258+ compact_queue ( self .queue_debug )
259+ compact_queue ( self .queue_info )
260+ compact_queue ( self .queue_critical )
261+ end
262+
186263 #####################################################################
187264 # Events handling
188265 #####################################################################
189266 # Get next event number
267+ #
268+ # Also make sure that we don't go above the persisted last number,
269+ # in such case increase the persisted number and use the next chunk
190270 def get_next_event_no ()
191271 self .counter_event_no = self .counter_event_no.add ( 1 )
192272 if self .counter_event_no >= self .counter_event_no_persisted
193273 self .load_event_no_persisted () # force an increment like done during boot
194274 end
195275 return self .counter_event_no
196276 end
197-
277+ # Get last event number (all events sent up to now are lower or equal than this value)
278+ def get_last_event_no ()
279+ return self .counter_event_no
280+ end
198281
199282 #####################################################################
200283 # Dump events for debugging
@@ -208,6 +291,91 @@ class Matter_EventHandler
208291 tasmota.log ( f"MTR: Events by types: critical {cnt[2]}, info {cnt[1]}, debug {cnt[0]}" , 2 )
209292 end
210293
294+ #####################################################################
295+ # find_min_no
296+ #
297+ # Find the next event number just above the provided value,
298+ # or the smallest if nil is passed
299+ #
300+ # Arg:
301+ # - event_min: minimal event number (strictly above),
302+ # or `nil` if smallest number
303+ #
304+ # Returns:
305+ # - event_no (int) or `nil` of none found
306+ #####################################################################
307+ def find_min_no ( event_min_no)
308+ # fail fast if `event_min_no` is the same as `counter_event_no`
309+ # meaning that we dont have any more events since last report
310+ if ( event_min_no != nil ) && ( event_min_no >= self .counter_event_no )
311+ return nil
312+ end
313+
314+ #####################################################################
315+ # Find the next available event right above `event_min_no`
316+ #
317+ # TODO: since queues are sorted, we can abort searching when we reach
318+ # an event below `event_min_no`
319+ #
320+ # Arg
321+ # - event_smallest: the event found up to now (from previous queues)
322+ # - q: the queue oject
323+ # - event_min_no: minimum acceptable event number (int64) or nil if any
324+ def find_in_queue ( event_smallest, q, event_min_no)
325+ var i = size ( q) - 1
326+ while i >= 0
327+ var cur_event = q[ i]
328+ # fail fast: since queues are sorted, we can abort searching when we reach an event below `event_min_no`
329+ if ( event_min_no != nil ) && ( cur_event.event_no <= event_min_no)
330+ return event_smallest
331+ end
332+
333+ # we know that event_no is in acceptable range, is it smaller?
334+ if ( event_smallest == nil ) || ( cur_event.event_no < event_smallest.event_no )
335+ event_smallest = cur_event
336+ end
337+
338+ i -= 1
339+ end
340+ return event_smallest
341+ end
342+
343+ var event
344+ # look in debug
345+ event = find_in_queue ( event, self .queue_debug , event_min_no)
346+ # look in info
347+ event = find_in_queue ( event, self .queue_info , event_min_no)
348+ # look in critical
349+ event = find_in_queue ( event, self .queue_critical , event_min_no)
350+
351+ return event
352+ end
353+
354+ #############################################################
355+ # generate a new event
356+ #
357+ def publish_event ( endpoint, cluster, event_id, is_urgent, priority, data0, data1, data2)
358+ var new_event = matter.EventQueued ( self .get_next_event_no () , endpoint, cluster, event_id, is_urgent, priority, data0, data1, data2)
359+ if tasmota.loglevel ( 3 )
360+ var data_str = ''
361+ if ( data0 != nil ) data_str = str ( data0) end
362+ if ( data1 != nil ) data_str += ", " + str ( data1) end
363+ if ( data2 != nil ) data_str += ", " + str ( data2) end
364+ if ( cluster == 0x0028 ) && ( event_id == 0x00 )
365+ # put the software version in a readable format
366+ var val = data0.val
367+ data_str = format ( "%i.%i.%i.%i" , ( val >> 24 ) & 0xFF , ( val >> 16 ) & 0xFF , ( val >> 8 ) & 0xFF , val & 0xFF )
368+ end
369+ var priority_str = ( priority == 2 ) ? "CRIT " : ( priority == 1 ) ? "INFO " : "DEBUG "
370+ var event_name = matter.get_event_name ( cluster, event_id)
371+ event_name = ( event_name != nil ) ? "(" + event_name + ") " : ""
372+ log ( f"MTR: +Add_Event ({priority_str}{new_event.event_no:8s}) [{new_event.endpoint:02X}]{new_event.cluster:04X}/{new_event.event_id:02X} {event_name}- {data_str}" , 2 )
373+ end
374+ self .queue_event ( new_event)
375+ # check if we have an subscription interested in this new event
376+ self .device.message_handler.im.subs_shop.event_published ( new_event)
377+ end
378+
211379end
212380matter.EventHandler = Matter_EventHandler
213381
0 commit comments