Skip to content

Commit 0d267a5

Browse files
authored
Matter prepare events (#21647)
1 parent 2ce513f commit 0d267a5

17 files changed

+9970
-8267
lines changed

lib/libesp32/berry_matter/src/be_matter_module.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
216216
#include "solidify/solidified_Matter_IM_Message.h"
217217
#include "solidify/solidified_Matter_IM_Subscription.h"
218218
#include "solidify/solidified_Matter_IM.h"
219+
#include "solidify/solidified_Matter_EventHandler.h"
219220
#include "solidify/solidified_Matter_Control_Message.h"
220221
#include "solidify/solidified_Matter_Plugin_0.h"
221222
#include "solidify/solidified_Matter_Base38.h"
@@ -259,6 +260,7 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
259260
#include "solidify/solidified_Matter_Plugin_3_Sensor_Rain.h"
260261
#include "solidify/solidified_Matter_Plugin_3_Sensor_Waterleak.h"
261262
#include "solidify/solidified_Matter_Plugin_2_Fan.h"
263+
#include "solidify/solidified_Matter_Plugin_2_Sensor_GenericSwitch.h"
262264
#include "solidify/solidified_Matter_Plugin_9_Virt_Fan.h"
263265
#include "solidify/solidified_Matter_Plugin_9_Virt_Sensor_Contact.h"
264266
#include "solidify/solidified_Matter_Plugin_9_Virt_Sensor_Occupancy.h"
@@ -337,6 +339,11 @@ module matter (scope: global, strings: weak) {
337339
UC_LIST, closure(module_matter_UC_LIST_closure)
338340
Profiler, class(be_class_Matter_Profiler)
339341
342+
// EVents priority levels
343+
EVENT_DEBUG, int(0)
344+
EVENT_INFO, int(1)
345+
EVENT_CRITICAL, int(2)
346+
340347
// Status codes
341348
SUCCESS, int(0x00)
342349
FAILURE, int(0x01)
@@ -429,6 +436,10 @@ module matter (scope: global, strings: weak) {
429436
Frame, class(be_class_Matter_Frame)
430437
MessageHandler, class(be_class_Matter_MessageHandler)
431438
439+
// Event Handler
440+
EventHandler, class(be_class_Matter_EventHandler)
441+
EventQueued, class(be_class_Matter_EventQueued)
442+
432443
// Interation Model
433444
Path, class(be_class_Matter_Path)
434445
PathGenerator, class(be_class_Matter_PathGenerator)
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
#
2+
# Matter_EventHandler.be - suppport for Matter Events
3+
#
4+
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
#
19+
20+
import matter
21+
22+
#@ solidify:Matter_EventHandler,weak
23+
#@ solidify:Matter_EventQueued,weak
24+
25+
#################################################################################
26+
# Matter_Event_Queued class
27+
#
28+
# This class encapsulates any element within teh event queue
29+
# Takes a `Matter_EventDataIB`
30+
#
31+
# Invariants:
32+
# `priority` is guaranteed to be in range 0..2
33+
#################################################################################
34+
class Matter_EventQueued
35+
# common elements
36+
var endpoint
37+
var cluster
38+
var event_id
39+
var is_urgent
40+
var priority
41+
var event_no
42+
var raw_tlv # content encoded as full TLV
43+
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
50+
if (self.priority < 0) self.priority = 0 end
51+
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()
54+
end
55+
end
56+
matter.EventQueued = Matter_EventQueued
57+
58+
# elements are made of `Matter_EventDataIB`
59+
# var path #
60+
# var node # u64 as bytes
61+
# var endpoint # u16
62+
# var cluster # u32
63+
# var event # u32
64+
# var is_urgent # bool
65+
# var event_number # u64 as bytes
66+
# var priority # u8
67+
# # one of
68+
# var epoch_timestamp # u64
69+
# var system_timestamp # u64
70+
# var delta_epoch_timestamp # u64
71+
# var delta_system_timestamp # u64
72+
# # data
73+
# var data # any TLV
74+
75+
# EVENT_DEBUG=0
76+
# EVENT_INFO=1
77+
# EVENT_CRITICAL=2
78+
79+
#################################################################################
80+
# Matter_IM class
81+
#################################################################################
82+
class Matter_EventHandler
83+
static var EVENT_NO_INCR = 1000 # counter increased when persisting
84+
static var EVENT_NO_FILENAME = "_matter_event_no"
85+
static var EVENT_QUEUE_SIZE_MAX = 10 # each queue is 10 elements depth
86+
87+
# circular buffers
88+
var queue_debug # queue of events for level DEBUG
89+
var queue_info # queue of events for level INFO
90+
var queue_critical # queue of events for level CRITICAL
91+
92+
var device # link back to matter_device top object
93+
# Events
94+
var counter_event_no # event number, monotonically increasing even after restarts
95+
var counter_event_no_persisted # the nest number persisted for after the restart
96+
97+
def init(device)
98+
self.device = device
99+
self.queue_debug = []
100+
self.queue_info = []
101+
self.queue_critical = []
102+
self.load_event_no_persisted() # initializes self.counter_event_no and self.counter_event_no_persisted
103+
end
104+
105+
#####################################################################
106+
# load_event_no_persisted
107+
#
108+
# Load the next acceptable event_no from `persist` and persist it
109+
# with a predefined gap `self.EVENT_NO_INCR` (default 1000)
110+
def load_event_no_persisted()
111+
import persist
112+
var event_no_str = str(persist.find(self.EVENT_NO_FILENAME, "0"))
113+
self.counter_event_no = int64.fromstring(event_no_str)
114+
self.counter_event_no_persisted = self.counter_event_no.add(self.EVENT_NO_INCR)
115+
# save back next slot
116+
persist.setmember(self.EVENT_NO_FILENAME, self.counter_event_no_persisted.tostring())
117+
persist.save()
118+
end
119+
120+
#####################################################################
121+
# Enqueue event
122+
#
123+
# Takes `Matter_EventDataIB`
124+
#####################################################################
125+
def queue_event(ev_ib)
126+
var ev_queued = matter.EventQueued(ev_ib)
127+
128+
var cur_prio = ev_queued.priority
129+
130+
# we reuse the same logic as connectedhomeip
131+
# https://github.com/project-chip/connectedhomeip/blob/master/src/app/EventManagement.h
132+
#
133+
# Here is a copy of the original comment:
134+
135+
#---------------------------------------------------------------------------------
136+
* A newly generated event will be placed in the lowest-priority (in practice
137+
* DEBUG) buffer, the one associated with the first LogStorageResource. If
138+
* there is no space in that buffer, space will be created by evicting the
139+
* oldest event currently in that buffer, until enough space is available.
140+
*
141+
* When an event is evicted from a buffer, there are two possibilities:
142+
*
143+
* 1) If the next LogStorageResource has a priority that is no higher than the
144+
* event's priority, the event will be moved to that LogStorageResource's
145+
* buffer. This may in turn require events to be evicted from that buffer.
146+
* 2) If the next LogStorageResource has a priority that is higher than the
147+
* event's priority, then the event is just dropped.
148+
*
149+
* This means that LogStorageResources at a given priority level are reserved
150+
* for events of that priority level or higher priority.
151+
*
152+
* As a simple example, assume there are only two priority levels, DEBUG and
153+
* CRITICAL, and two LogStorageResources with those priorities. In that case,
154+
* old CRITICAL events will not start getting dropped until both buffers are
155+
* full, while old DEBUG events will start getting dropped once the DEBUG
156+
* LogStorageResource buffer is full.
157+
---------------------------------------------------------------------------------#
158+
159+
# first step, always add to DEBUG queue
160+
self.queue_debug.push(ev_queued)
161+
# if DEBUG queue is full
162+
if size(self.queue_debug) > self.EVENT_QUEUE_SIZE_MAX
163+
# remove first (oldest element)
164+
var ev_debug_removed = self.queue_debug.pop(0)
165+
if ev_debug_removed.priority > matter.EVENT_DEBUG
166+
# if removed item is higher than DEBUG, push to INFO queue
167+
self.queue_info.push(ev_debug_removed)
168+
# if INFO queue is full
169+
if size(self.queue_info) > self.EVENT_QUEUE_SIZE_MAX
170+
# remove first (oldest element)
171+
var ev_info_removed = self.queue_info.pop(0)
172+
if ev_info_removed.priority > matter.EVENT_INFO
173+
# if removed item is higher than INFO, push to CRITICAL queue
174+
self.queue_critical.push(ev_info_removed)
175+
# if CRITICAL queue is full
176+
if size(self.queue_critical) > self.EVENT_QUEUE_SIZE_MAX
177+
# remove first (oldest element)
178+
var ev_critical_removed = self.queue_critical.pop(0)
179+
end
180+
end
181+
end
182+
end
183+
end
184+
end
185+
186+
#####################################################################
187+
# Events handling
188+
#####################################################################
189+
# Get next event number
190+
def get_next_event_no()
191+
self.counter_event_no = self.counter_event_no.add(1)
192+
if self.counter_event_no >= self.counter_event_no_persisted
193+
self.load_event_no_persisted() # force an increment like done during boot
194+
end
195+
return self.counter_event_no
196+
end
197+
198+
199+
#####################################################################
200+
# Dump events for debugging
201+
#####################################################################
202+
def dump()
203+
tasmota.log(f"MTR: Events queues sizes: critical {size(self.queue_critical)}, info {size(self.queue_info)}, debug {size(self.queue_debug)}", 2)
204+
var cnt = [0, 0, 0] # counters
205+
for ev: self.queue_debug cnt[ev.priority] += 1 end
206+
for ev: self.queue_info cnt[ev.priority] += 1 end
207+
for ev: self.queue_critical cnt[ev.priority] += 1 end
208+
tasmota.log(f"MTR: Events by types: critical {cnt[2]}, info {cnt[1]}, debug {cnt[0]}", 2)
209+
end
210+
211+
end
212+
matter.EventHandler = Matter_EventHandler
213+
214+
#-
215+
216+
# Unit tests
217+
218+
219+
-#
220+

0 commit comments

Comments
 (0)