Skip to content

Commit 9701701

Browse files
authored
Matter full support of events (#21698)
1 parent 33225bf commit 9701701

25 files changed

+6323
-4370
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file.
1212
- Matter add internal debug option (#21634)
1313
- Matter add Fan support (virtual only) (#21637)
1414
- Matter show event name in logs (#21649)
15+
- Matter full support of events
1516

1617
### Breaking Changed
1718

lib/libesp32/berry_matter/src/be_matter_module.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,8 @@ extern int matter_publish_command(bvm *vm);
209209

210210
extern const bclass be_class_Matter_TLV; // need to declare it upfront because of circular reference
211211
#include "solidify/solidified_Matter_Path_0.h"
212-
#include "solidify/solidified_Matter_Path_1_Generator.h"
212+
#include "solidify/solidified_Matter_Path_1_PathGenerator.h"
213+
#include "solidify/solidified_Matter_Path_1_EventGenerator.h"
213214
#include "solidify/solidified_Matter_TLV.h"
214215
#include "solidify/solidified_Matter_IM_Data.h"
215216
#include "solidify/solidified_Matter_UDPServer.h"
@@ -455,6 +456,7 @@ module matter (scope: global, strings: weak) {
455456
// Interation Model
456457
Path, class(be_class_Matter_Path)
457458
PathGenerator, class(be_class_Matter_PathGenerator)
459+
EventGenerator, class(be_class_Matter_EventGenerator)
458460
IM_Status, class(be_class_Matter_IM_Status)
459461
IM_InvokeResponse, class(be_class_Matter_IM_InvokeResponse)
460462
IM_WriteResponse, class(be_class_Matter_IM_WriteResponse)

lib/libesp32/berry_matter/src/embedded/Matter_EventHandler.be

Lines changed: 186 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
55105
end
56106
matter.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
#################################################################################
82137
class 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+
211379
end
212380
matter.EventHandler = Matter_EventHandler
213381

0 commit comments

Comments
 (0)