Berry 'tasmota.add_rule_once' and auto-remove rules with same pattern and id (#22900)

This commit is contained in:
s-hadinger 2025-01-29 22:48:44 +01:00 committed by GitHub
parent 9bb7b7913a
commit 0dcd38186f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 2086 additions and 1921 deletions

View File

@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file.
- LVLG/HASPmota add color names from OpenHASP (#22879) - LVLG/HASPmota add color names from OpenHASP (#22879)
- HASPmota support for `buttonmatrix` events - HASPmota support for `buttonmatrix` events
- Berry driver for PN532 NFC/Mifare reader - Berry driver for PN532 NFC/Mifare reader
- Berry `tasmota.add_rule_once` and auto-remove rules with same pattern and id
### Breaking Changed ### Breaking Changed

View File

@ -167,6 +167,7 @@ class be_class_tasmota (scope: global, name: Tasmota) {
find_list_i, closure(class_Tasmota_find_list_i_closure) find_list_i, closure(class_Tasmota_find_list_i_closure)
find_op, closure(class_Tasmota_find_op_closure) find_op, closure(class_Tasmota_find_op_closure)
add_rule, closure(class_Tasmota_add_rule_closure) add_rule, closure(class_Tasmota_add_rule_closure)
add_rule_once, closure(class_Tasmota_add_rule_once_closure)
remove_rule, closure(class_Tasmota_remove_rule_closure) remove_rule, closure(class_Tasmota_remove_rule_closure)
try_rule, closure(class_Tasmota_try_rule_closure) try_rule, closure(class_Tasmota_try_rule_closure)
exec_rules, closure(class_Tasmota_exec_rules_closure) exec_rules, closure(class_Tasmota_exec_rules_closure)

View File

@ -90,13 +90,31 @@ class Tasmota
end end
# Rules # Rules
def add_rule(pat, f, id) def add_rule_once(pat, f, id)
self.add_rule(pat, f, id, true)
end
# add_rule(pat, f, id, run_once)
#
# pat: (string) pattern for the rule
# f: (function) the function to be called when the rule fires
# there is a check that the caller doesn't use mistakenly
# a method - in such case a closure needs to be used instead
# id: (opt, any) an optional id so the rule can be removed later
# needs to be unique to avoid collision
# No test for uniqueness is performed
# run_once: (opt, bool or nil) indicates the rule is fired only once
# this parameter is not used directly but instead
# set by 'add_rule_once()'
def add_rule(pat, f, id, run_once)
self.check_not_method(f) self.check_not_method(f)
if self._rules == nil if self._rules == nil
self._rules = [] self._rules = []
end end
if type(f) == 'function' if type(f) == 'function'
self._rules.push(Trigger(self.Rule_Matcher.parse(pat), f, id)) if (id != nil)
self.remove_rule(pat, id)
end
self._rules.push(Trigger(self.Rule_Matcher.parse(pat), f, id, run_once))
else else
raise 'value_error', 'the second argument is not a function' raise 'value_error', 'the second argument is not a function'
end end
@ -115,6 +133,59 @@ class Tasmota
end end
end end
#-
# Below is a unit test for add_rule and add_rule_once
var G1, G2, G3
def f1() print("F1") G1 = 1 return true end
def f2() print("F2") G2 = 2 return true end
def f3() print("F3") G3 = 3 return true end
tasmota.add_rule("A#B", f1, "f1")
tasmota.add_rule_once("A#B", f2, "f2")
var r
r = tasmota.publish_rule('{"A":{"B":1}}')
assert(G1 == 1)
assert(G2 == 2)
assert(G3 == nil)
#assert(r == true)
G1 = nil
G2 = nil
r = tasmota.publish_rule('{"A":{"B":1}}')
assert(G1 == 1)
assert(G2 == nil)
assert(G3 == nil)
#assert(r == true)
tasmota.add_rule("A#B", f3, "f1")
G1 = nil
r = tasmota.publish_rule('{"A":{"B":1}}')
assert(G1 == nil)
assert(G2 == nil)
assert(G3 == 3)
#assert(r == true)
tasmota.remove_rule("A#B", "f1")
G3 = nil
r = tasmota.publish_rule('{"A":{"B":1}}')
assert(G1 == nil)
assert(G2 == nil)
assert(G3 == nil)
#assert(r == false)
-#
# Rules trigger if match. return true if match, false if not # Rules trigger if match. return true if match, false if not
# #
# event: native Berry map representing the JSON input # event: native Berry map representing the JSON input
@ -133,27 +204,35 @@ class Tasmota
# Run rules, i.e. check each individual rule # Run rules, i.e. check each individual rule
# Returns true if at least one rule matched, false if none # Returns true if at least one rule matched, false if none
# `exec_rule` is true, then run the rule, else just record value #
# ev_json: (string) the payload of the rule, needs to be JSON format
# exec_rule: (bool) 'true' run the rule, 'false' just record value (avoind infinite loops)
def exec_rules(ev_json, exec_rule) def exec_rules(ev_json, exec_rule)
var save_cmd_res = self.cmd_res # save initial state (for reentrance) var save_cmd_res = self.cmd_res # save initial state (for reentrance)
if self._rules || save_cmd_res != nil # if there is a rule handler, or we record rule results if self._rules || save_cmd_res != nil # if there is a rule handler, or we record rule results
import json import json
self.cmd_res = nil # disable sunsequent recording of results self.cmd_res = nil # disable sunsequent recording of results
var ret = false var ret = false # ret records if any rule was fired
var ev = json.load(ev_json) # returns nil if invalid JSON var ev = json.load(ev_json) # returns nil if invalid JSON
if ev == nil if ev == nil
self.log('BRY: ERROR, bad json: '+ev_json, 3) self.log('BRY: ERROR, bad json: ' + ev_json, 3)
ev = ev_json # revert to string ev = ev_json # revert to string
end end
# try all rule handlers # try all rule handlers
if exec_rule && self._rules if exec_rule && self._rules
var i = 0 var i = 0
while i < size(self._rules) while i < size(self._rules)
var tr = self._rules[i] var tr = self._rules[i]
ret = self.try_rule(ev,tr.trig,tr.f) || ret #- call should be first to avoid evaluation shortcut if ret is already true -# var rule_fired = self.try_rule(ev, tr.trig, tr.f)
i += 1 ret = ret || rule_fired # 'or' with result
if rule_fired && (tr.o == true)
# this rule should be run_once(d) so remove it
self._rules.remove(i)
else
i += 1
end
end end
end end

View File

@ -5,7 +5,7 @@
class Trigger class Trigger
var trig, f, id var trig, f, id
var o # optional object var o # optional object, for Cron it contains the cron object, for rule, 'true' means run-once
# trig: trigger of the event, either timestamp (int) or a rule matcher instance # trig: trigger of the event, either timestamp (int) or a rule matcher instance
# f: function or closure to call # f: function or closure to call
@ -24,6 +24,10 @@ class Trigger
str(self.trig), str(self.f), str(self.id)) str(self.trig), str(self.f), str(self.id))
end end
def run_once()
return self.o == true
end
########################################################################################### ###########################################################################################
# For cron triggers only # For cron triggers only
########################################################################################### ###########################################################################################

View File

@ -3,21 +3,113 @@
* Generated code, don't edit * * Generated code, don't edit *
\********************************************************************/ \********************************************************************/
#include "be_constobj.h" #include "be_constobj.h"
// compact class 'Trigger' ktab size: 8, total: 14 (saved 48 bytes) // compact class 'Trigger' ktab size: 8, total: 15 (saved 56 bytes)
static const bvalue be_ktab_class_Trigger[8] = { static const bvalue be_ktab_class_Trigger[8] = {
/* K0 */ be_nested_str(trig), /* K0 */ be_nested_str(o),
/* K1 */ be_nested_str(f), /* K1 */ be_nested_str(next),
/* K2 */ be_nested_str(id), /* K2 */ be_nested_str(trig),
/* K3 */ be_nested_str(o), /* K3 */ be_const_int(0),
/* K4 */ be_nested_str(_X3Cinstance_X3A_X20_X25s_X28_X25s_X2C_X20_X25s_X2C_X20_X25s_X29), /* K4 */ be_nested_str(time_reached),
/* K5 */ be_const_int(0), /* K5 */ be_nested_str(f),
/* K6 */ be_nested_str(time_reached), /* K6 */ be_nested_str(id),
/* K7 */ be_nested_str(next), /* K7 */ be_nested_str(_X3Cinstance_X3A_X20_X25s_X28_X25s_X2C_X20_X25s_X2C_X20_X25s_X29),
}; };
extern const bclass be_class_Trigger; extern const bclass be_class_Trigger;
/********************************************************************
** Solidified function: run_once
********************************************************************/
be_local_closure(class_Trigger_run_once, /* name */
be_nested_proto(
3, /* nstack */
1, /* argc */
10, /* varg */
0, /* has upvals */
NULL, /* no upvals */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
&be_ktab_class_Trigger, /* shared constants */
&be_const_str_run_once,
&be_const_str_solidified,
( &(const binstruction[ 4]) { /* code */
0x88040100, // 0000 GETMBR R1 R0 K0
0x50080200, // 0001 LDBOOL R2 1 0
0x1C040202, // 0002 EQ R1 R1 R2
0x80040200, // 0003 RET 1 R1
})
)
);
/*******************************************************************/
/********************************************************************
** Solidified function: next
********************************************************************/
be_local_closure(class_Trigger_next, /* name */
be_nested_proto(
3, /* nstack */
1, /* argc */
10, /* varg */
0, /* has upvals */
NULL, /* no upvals */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
&be_ktab_class_Trigger, /* shared constants */
&be_const_str_next,
&be_const_str_solidified,
( &(const binstruction[ 7]) { /* code */
0x88040100, // 0000 GETMBR R1 R0 K0
0x78060003, // 0001 JMPF R1 #0006
0x88040100, // 0002 GETMBR R1 R0 K0
0x8C040301, // 0003 GETMET R1 R1 K1
0x7C040200, // 0004 CALL R1 1
0x80040200, // 0005 RET 1 R1
0x80000000, // 0006 RET 0
})
)
);
/*******************************************************************/
/********************************************************************
** Solidified function: time_reached
********************************************************************/
be_local_closure(class_Trigger_time_reached, /* name */
be_nested_proto(
4, /* nstack */
1, /* argc */
10, /* varg */
0, /* has upvals */
NULL, /* no upvals */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
&be_ktab_class_Trigger, /* shared constants */
&be_const_str_time_reached,
&be_const_str_solidified,
( &(const binstruction[12]) { /* code */
0x88040100, // 0000 GETMBR R1 R0 K0
0x78060007, // 0001 JMPF R1 #000A
0x88040102, // 0002 GETMBR R1 R0 K2
0x24040303, // 0003 GT R1 R1 K3
0x78060004, // 0004 JMPF R1 #000A
0x88040100, // 0005 GETMBR R1 R0 K0
0x8C040304, // 0006 GETMET R1 R1 K4
0x880C0102, // 0007 GETMBR R3 R0 K2
0x7C040400, // 0008 CALL R1 2
0x80040200, // 0009 RET 1 R1
0x50040000, // 000A LDBOOL R1 0 0
0x80040200, // 000B RET 1 R1
})
)
);
/*******************************************************************/
/******************************************************************** /********************************************************************
** Solidified function: init ** Solidified function: init
********************************************************************/ ********************************************************************/
@ -35,10 +127,10 @@ be_local_closure(class_Trigger_init, /* name */
&be_const_str_init, &be_const_str_init,
&be_const_str_solidified, &be_const_str_solidified,
( &(const binstruction[ 5]) { /* code */ ( &(const binstruction[ 5]) { /* code */
0x90020001, // 0000 SETMBR R0 K0 R1 0x90020401, // 0000 SETMBR R0 K2 R1
0x90020202, // 0001 SETMBR R0 K1 R2 0x90020A02, // 0001 SETMBR R0 K5 R2
0x90020403, // 0002 SETMBR R0 K2 R3 0x90020C03, // 0002 SETMBR R0 K6 R3
0x90020604, // 0003 SETMBR R0 K3 R4 0x90020004, // 0003 SETMBR R0 K0 R4
0x80000000, // 0004 RET 0 0x80000000, // 0004 RET 0
}) })
) )
@ -64,20 +156,20 @@ be_local_closure(class_Trigger_tostring, /* name */
&be_const_str_solidified, &be_const_str_solidified,
( &(const binstruction[18]) { /* code */ ( &(const binstruction[18]) { /* code */
0x60040018, // 0000 GETGBL R1 G24 0x60040018, // 0000 GETGBL R1 G24
0x58080004, // 0001 LDCONST R2 K4 0x58080007, // 0001 LDCONST R2 K7
0x600C0008, // 0002 GETGBL R3 G8 0x600C0008, // 0002 GETGBL R3 G8
0x60100006, // 0003 GETGBL R4 G6 0x60100006, // 0003 GETGBL R4 G6
0x5C140000, // 0004 MOVE R5 R0 0x5C140000, // 0004 MOVE R5 R0
0x7C100200, // 0005 CALL R4 1 0x7C100200, // 0005 CALL R4 1
0x7C0C0200, // 0006 CALL R3 1 0x7C0C0200, // 0006 CALL R3 1
0x60100008, // 0007 GETGBL R4 G8 0x60100008, // 0007 GETGBL R4 G8
0x88140100, // 0008 GETMBR R5 R0 K0 0x88140102, // 0008 GETMBR R5 R0 K2
0x7C100200, // 0009 CALL R4 1 0x7C100200, // 0009 CALL R4 1
0x60140008, // 000A GETGBL R5 G8 0x60140008, // 000A GETGBL R5 G8
0x88180101, // 000B GETMBR R6 R0 K1 0x88180105, // 000B GETMBR R6 R0 K5
0x7C140200, // 000C CALL R5 1 0x7C140200, // 000C CALL R5 1
0x60180008, // 000D GETGBL R6 G8 0x60180008, // 000D GETGBL R6 G8
0x881C0102, // 000E GETMBR R7 R0 K2 0x881C0106, // 000E GETMBR R7 R0 K6
0x7C180200, // 000F CALL R6 1 0x7C180200, // 000F CALL R6 1
0x7C040A00, // 0010 CALL R1 5 0x7C040A00, // 0010 CALL R1 5
0x80040200, // 0011 RET 1 R1 0x80040200, // 0011 RET 1 R1
@ -87,87 +179,23 @@ be_local_closure(class_Trigger_tostring, /* name */
/*******************************************************************/ /*******************************************************************/
/********************************************************************
** Solidified function: time_reached
********************************************************************/
be_local_closure(class_Trigger_time_reached, /* name */
be_nested_proto(
4, /* nstack */
1, /* argc */
10, /* varg */
0, /* has upvals */
NULL, /* no upvals */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
&be_ktab_class_Trigger, /* shared constants */
&be_const_str_time_reached,
&be_const_str_solidified,
( &(const binstruction[12]) { /* code */
0x88040103, // 0000 GETMBR R1 R0 K3
0x78060007, // 0001 JMPF R1 #000A
0x88040100, // 0002 GETMBR R1 R0 K0
0x24040305, // 0003 GT R1 R1 K5
0x78060004, // 0004 JMPF R1 #000A
0x88040103, // 0005 GETMBR R1 R0 K3
0x8C040306, // 0006 GETMET R1 R1 K6
0x880C0100, // 0007 GETMBR R3 R0 K0
0x7C040400, // 0008 CALL R1 2
0x80040200, // 0009 RET 1 R1
0x50040000, // 000A LDBOOL R1 0 0
0x80040200, // 000B RET 1 R1
})
)
);
/*******************************************************************/
/********************************************************************
** Solidified function: next
********************************************************************/
be_local_closure(class_Trigger_next, /* name */
be_nested_proto(
3, /* nstack */
1, /* argc */
10, /* varg */
0, /* has upvals */
NULL, /* no upvals */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
&be_ktab_class_Trigger, /* shared constants */
&be_const_str_next,
&be_const_str_solidified,
( &(const binstruction[ 7]) { /* code */
0x88040103, // 0000 GETMBR R1 R0 K3
0x78060003, // 0001 JMPF R1 #0006
0x88040103, // 0002 GETMBR R1 R0 K3
0x8C040307, // 0003 GETMET R1 R1 K7
0x7C040200, // 0004 CALL R1 1
0x80040200, // 0005 RET 1 R1
0x80000000, // 0006 RET 0
})
)
);
/*******************************************************************/
/******************************************************************** /********************************************************************
** Solidified class: Trigger ** Solidified class: Trigger
********************************************************************/ ********************************************************************/
be_local_class(Trigger, be_local_class(Trigger,
4, 4,
NULL, NULL,
be_nested_map(8, be_nested_map(9,
( (struct bmapnode*) &(const bmapnode[]) { ( (struct bmapnode*) &(const bmapnode[]) {
{ be_const_key(id, 2), be_const_var(2) }, { be_const_key(run_once, 3), be_const_closure(class_Trigger_run_once_closure) },
{ be_const_key(f, -1), be_const_var(1) },
{ be_const_key(next, -1), be_const_closure(class_Trigger_next_closure) },
{ be_const_key(trig, 7), be_const_var(0) },
{ be_const_key(time_reached, -1), be_const_closure(class_Trigger_time_reached_closure) },
{ be_const_key(tostring, 4), be_const_closure(class_Trigger_tostring_closure) },
{ be_const_key(o, -1), be_const_var(3) }, { be_const_key(o, -1), be_const_var(3) },
{ be_const_key(next, -1), be_const_closure(class_Trigger_next_closure) },
{ be_const_key(trig, -1), be_const_var(0) },
{ be_const_key(init, -1), be_const_closure(class_Trigger_init_closure) }, { be_const_key(init, -1), be_const_closure(class_Trigger_init_closure) },
{ be_const_key(time_reached, -1), be_const_closure(class_Trigger_time_reached_closure) },
{ be_const_key(id, 4), be_const_var(2) },
{ be_const_key(tostring, -1), be_const_closure(class_Trigger_tostring_closure) },
{ be_const_key(f, -1), be_const_var(1) },
})), })),
(bstring*) &be_const_str_Trigger (bstring*) &be_const_str_Trigger
); );