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)
- HASPmota support for `buttonmatrix` events
- Berry driver for PN532 NFC/Mifare reader
- Berry `tasmota.add_rule_once` and auto-remove rules with same pattern and id
### 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_op, closure(class_Tasmota_find_op_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)
try_rule, closure(class_Tasmota_try_rule_closure)
exec_rules, closure(class_Tasmota_exec_rules_closure)

View File

@ -90,13 +90,31 @@ class Tasmota
end
# 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)
if self._rules == nil
self._rules = []
end
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
raise 'value_error', 'the second argument is not a function'
end
@ -115,6 +133,59 @@ class Tasmota
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
#
# event: native Berry map representing the JSON input
@ -133,27 +204,35 @@ class Tasmota
# Run rules, i.e. check each individual rule
# 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)
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
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
import json
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
if ev == nil
self.log('BRY: ERROR, bad json: '+ev_json, 3)
ev = ev_json # revert to string
self.log('BRY: ERROR, bad json: ' + ev_json, 3)
ev = ev_json # revert to string
end
# try all rule handlers
if exec_rule && self._rules
var i = 0
while i < size(self._rules)
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 -#
i += 1
var rule_fired = self.try_rule(ev, tr.trig, tr.f)
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

View File

@ -5,7 +5,7 @@
class Trigger
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
# f: function or closure to call
@ -24,6 +24,10 @@ class Trigger
str(self.trig), str(self.f), str(self.id))
end
def run_once()
return self.o == true
end
###########################################################################################
# For cron triggers only
###########################################################################################

View File

@ -3,21 +3,113 @@
* Generated code, don't edit *
\********************************************************************/
#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] = {
/* K0 */ be_nested_str(trig),
/* K1 */ be_nested_str(f),
/* K2 */ be_nested_str(id),
/* K3 */ be_nested_str(o),
/* K4 */ be_nested_str(_X3Cinstance_X3A_X20_X25s_X28_X25s_X2C_X20_X25s_X2C_X20_X25s_X29),
/* K5 */ be_const_int(0),
/* K6 */ be_nested_str(time_reached),
/* K7 */ be_nested_str(next),
/* K0 */ be_nested_str(o),
/* K1 */ be_nested_str(next),
/* K2 */ be_nested_str(trig),
/* K3 */ be_const_int(0),
/* K4 */ be_nested_str(time_reached),
/* K5 */ be_nested_str(f),
/* K6 */ be_nested_str(id),
/* K7 */ be_nested_str(_X3Cinstance_X3A_X20_X25s_X28_X25s_X2C_X20_X25s_X2C_X20_X25s_X29),
};
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
********************************************************************/
@ -35,10 +127,10 @@ be_local_closure(class_Trigger_init, /* name */
&be_const_str_init,
&be_const_str_solidified,
( &(const binstruction[ 5]) { /* code */
0x90020001, // 0000 SETMBR R0 K0 R1
0x90020202, // 0001 SETMBR R0 K1 R2
0x90020403, // 0002 SETMBR R0 K2 R3
0x90020604, // 0003 SETMBR R0 K3 R4
0x90020401, // 0000 SETMBR R0 K2 R1
0x90020A02, // 0001 SETMBR R0 K5 R2
0x90020C03, // 0002 SETMBR R0 K6 R3
0x90020004, // 0003 SETMBR R0 K0 R4
0x80000000, // 0004 RET 0
})
)
@ -64,20 +156,20 @@ be_local_closure(class_Trigger_tostring, /* name */
&be_const_str_solidified,
( &(const binstruction[18]) { /* code */
0x60040018, // 0000 GETGBL R1 G24
0x58080004, // 0001 LDCONST R2 K4
0x58080007, // 0001 LDCONST R2 K7
0x600C0008, // 0002 GETGBL R3 G8
0x60100006, // 0003 GETGBL R4 G6
0x5C140000, // 0004 MOVE R5 R0
0x7C100200, // 0005 CALL R4 1
0x7C0C0200, // 0006 CALL R3 1
0x60100008, // 0007 GETGBL R4 G8
0x88140100, // 0008 GETMBR R5 R0 K0
0x88140102, // 0008 GETMBR R5 R0 K2
0x7C100200, // 0009 CALL R4 1
0x60140008, // 000A GETGBL R5 G8
0x88180101, // 000B GETMBR R6 R0 K1
0x88180105, // 000B GETMBR R6 R0 K5
0x7C140200, // 000C CALL R5 1
0x60180008, // 000D GETGBL R6 G8
0x881C0102, // 000E GETMBR R7 R0 K2
0x881C0106, // 000E GETMBR R7 R0 K6
0x7C180200, // 000F CALL R6 1
0x7C040A00, // 0010 CALL R1 5
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
********************************************************************/
be_local_class(Trigger,
4,
NULL,
be_nested_map(8,
be_nested_map(9,
( (struct bmapnode*) &(const bmapnode[]) {
{ be_const_key(id, 2), be_const_var(2) },
{ 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(run_once, 3), be_const_closure(class_Trigger_run_once_closure) },
{ 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(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
);