2018-04-13 16:42:11 +01:00
/*
2019-10-27 10:13:24 +00:00
xdrv_10_rules . ino - rule support for Tasmota
2018-04-13 16:42:11 +01:00
2021-01-01 12:44:04 +00:00
Copyright ( C ) 2021 ESP Easy Group and Theo Arends
2018-04-13 16:42:11 +01:00
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# ifdef USE_RULES
2019-05-18 12:34:52 +01:00
# ifndef USE_SCRIPT
2018-04-13 16:42:11 +01:00
/*********************************************************************************************\
* Rules based heavily on ESP Easy implementation
*
* Inspiration : https : //github.com/letscontrolit/ESPEasy
*
* Add rules using the following , case insensitive , format :
* on < trigger1 > do < commands > endon on < trigger2 > do < commands > endon . .
*
* Examples :
* on System # Boot do Color 001000 endon
* on INA219 # Current > 0.100 do Dimmer 10 endon
* on INA219 # Current > 0.100 do Backlog Dimmer 10 ; Color 10 , 0 , 0 endon
* on INA219 # Current > 0.100 do Backlog Dimmer 10 ; Color 100000 endon on System # Boot do color 001000 endon
* on ds18b20 # temperature > 23 do power off endon on ds18b20 # temperature < 22 do power on endon
* on mqtt # connected do color 000010 endon
* on mqtt # disconnected do color 00100 C endon
* on time # initialized do color 001000 endon
2018-06-26 15:22:53 +01:00
* on time # initialized > 120 do color 001000 endon
2018-04-13 16:42:11 +01:00
* on time # set do color 00100 8 endon
* on clock # timer = 3 do color 080800 endon
* on rules # timer = 1 do color 080800 endon
* on mqtt # connected do color 000010 endon on mqtt # disconnected do color 001010 endon on time # initialized do color 001000 endon on time # set do backlog color 000 810 ; ruletimer1 10 endon on rules # timer = 1 do color 080800 endon
* on event # anyname do color 100000 endon
2018-04-14 13:39:16 +01:00
* on event # anyname do color % value % endon
2018-04-13 16:42:11 +01:00
* on power1 # state = 1 do color 001000 endon
2018-04-14 13:39:16 +01:00
* on button1 # state do publish cmnd / ring2 / power % value % endon on button2 # state do publish cmnd / strip1 / power % value % endon
* on switch1 # state do power2 % value % endon
2018-05-06 15:07:42 +01:00
* on analog # a0div10 do publish cmnd / ring2 / dimmer % value % endon
2020-05-10 13:39:13 +01:00
* on loadavg < 50 do power 2 endon
2022-01-27 10:00:49 +00:00
* on Time # Initialized do Backlog var1 0 ; event checktime = % time % endon on event # checktime > % timer1 % do var1 1 endon on event # checktime > = % timer2 % do var1 0 endon * on event # checktime do Power1 % var1 % endon
2018-04-13 16:42:11 +01:00
*
* Notes :
* Spaces after < on > , around < do > and before < endon > are mandatory
* System # Boot is initiated after MQTT is connected due to command handling preparation
* Control rule triggering with command :
* Rule 0 = Rules disabled ( Off )
* Rule 1 = Rules enabled ( On )
* Rule 2 = Toggle rules state
* Rule 4 = Perform commands as long as trigger is met ( Once OFF )
* Rule 5 = Perform commands once until trigger is not met ( Once ON )
* Rule 6 = Toggle Once state
* Execute an event like :
* Event anyname = 001000
* Set a RuleTimer to 100 seconds like :
* RuleTimer2 100
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2023-01-07 16:31:10 +00:00
# define XDRV_10 10
# ifndef RULE_MAX_EVENTSZ
# define RULE_MAX_EVENTSZ 100
# endif
# ifndef RULE_MAX_MQTT_EVENTSZ
# define RULE_MAX_MQTT_EVENTSZ 256
# endif
2018-11-07 09:30:03 +00:00
2020-11-02 13:35:19 +00:00
//#define DEBUG_RULES
2020-05-08 18:33:20 +01:00
# include <unishox.h>
2018-04-13 16:42:11 +01:00
# define D_CMND_RULE "Rule"
# define D_CMND_RULETIMER "RuleTimer"
# define D_CMND_EVENT "Event"
2018-05-20 16:46:00 +01:00
# define D_CMND_VAR "Var"
# define D_CMND_MEM "Mem"
2018-06-10 06:09:11 +01:00
# define D_CMND_ADD "Add"
# define D_CMND_SUB "Sub"
# define D_CMND_MULT "Mult"
# define D_CMND_SCALE "Scale"
2018-11-27 00:22:44 +00:00
# define D_CMND_CALC_RESOLUTION "CalcRes"
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
# define D_CMND_SUBSCRIBE "Subscribe"
# define D_CMND_UNSUBSCRIBE "Unsubscribe"
2019-09-09 16:24:27 +01:00
# define D_CMND_IF "If"
2018-04-13 16:42:11 +01:00
# define D_JSON_INITIATED "Initiated"
2021-01-21 13:54:38 +00:00
# define COMPARE_OPERATOR_NONE -1
# define COMPARE_OPERATOR_EQUAL 0
# define COMPARE_OPERATOR_BIGGER 1
# define COMPARE_OPERATOR_SMALLER 2
# define COMPARE_OPERATOR_EXACT_DIVISION 3
# define COMPARE_OPERATOR_NUMBER_EQUAL 4
# define COMPARE_OPERATOR_NOT_EQUAL 5
# define COMPARE_OPERATOR_BIGGER_EQUAL 6
# define COMPARE_OPERATOR_SMALLER_EQUAL 7
# define COMPARE_OPERATOR_STRING_ENDS_WITH 8
# define COMPARE_OPERATOR_STRING_STARTS_WITH 9
# define COMPARE_OPERATOR_STRING_CONTAINS 10
2021-01-31 12:52:56 +00:00
# define COMPARE_OPERATOR_STRING_NOT_EQUAL 11
# define COMPARE_OPERATOR_STRING_NOT_CONTAINS 12
# define MAXIMUM_COMPARE_OPERATOR COMPARE_OPERATOR_STRING_NOT_CONTAINS
const char kCompareOperators [ ] PROGMEM = " = \0 > \0 < \0 | \0 ==!=>=<=$>$<$|$!$^ " ;
2019-02-05 14:34:17 +00:00
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
# ifdef USE_EXPRESSION
2019-09-28 02:52:33 +01:00
const char kExpressionOperators [ ] PROGMEM = " +-*/%^ \0 " ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
# define EXPRESSION_OPERATOR_ADD 0
# define EXPRESSION_OPERATOR_SUBTRACT 1
# define EXPRESSION_OPERATOR_MULTIPLY 2
# define EXPRESSION_OPERATOR_DIVIDEDBY 3
# define EXPRESSION_OPERATOR_MODULO 4
# define EXPRESSION_OPERATOR_POWER 5
const uint8_t kExpressionOperatorsPriorities [ ] PROGMEM = { 1 , 1 , 2 , 2 , 3 , 4 } ;
# define MAX_EXPRESSION_OPERATOR_PRIORITY 4
2019-09-09 16:24:27 +01:00
# define LOGIC_OPERATOR_AND 1
# define LOGIC_OPERATOR_OR 2
# define IF_BLOCK_INVALID -1
# define IF_BLOCK_ANY 0
# define IF_BLOCK_ELSEIF 1
# define IF_BLOCK_ELSE 2
# define IF_BLOCK_ENDIF 3
2019-02-16 15:17:17 +00:00
# endif // USE_EXPRESSION
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
2022-03-11 15:27:49 +00:00
// Define to indicate that rules are always enabled
2022-02-14 11:51:29 +00:00
# ifdef USE_BERRY
# define BERRY_RULES 1
# else
# define BERRY_RULES 0
# endif
2019-08-11 17:12:18 +01:00
const char kRulesCommands [ ] PROGMEM = " | " // No prefix
2019-08-02 15:23:17 +01:00
D_CMND_RULE " | " D_CMND_RULETIMER " | " D_CMND_EVENT " | " D_CMND_VAR " | " D_CMND_MEM " | "
D_CMND_ADD " | " D_CMND_SUB " | " D_CMND_MULT " | " D_CMND_SCALE " | " D_CMND_CALC_RESOLUTION
# ifdef SUPPORT_MQTT_EVENT
" | " D_CMND_SUBSCRIBE " | " D_CMND_UNSUBSCRIBE
2019-09-09 16:24:27 +01:00
# endif
# ifdef SUPPORT_IF_STATEMENT
" | " D_CMND_IF
2019-08-02 15:23:17 +01:00
# endif
;
void ( * const RulesCommand [ ] ) ( void ) PROGMEM = {
& CmndRule , & CmndRuleTimer , & CmndEvent , & CmndVariable , & CmndMemory ,
& CmndAddition , & CmndSubtract , & CmndMultiply , & CmndScale , & CmndCalcResolution
# ifdef SUPPORT_MQTT_EVENT
, & CmndSubscribe , & CmndUnsubscribe
2019-09-09 16:24:27 +01:00
# endif
# ifdef SUPPORT_IF_STATEMENT
, & CmndIf
2019-08-02 15:23:17 +01:00
# endif
} ;
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
2019-08-17 13:27:41 +01:00
struct RULES {
String event_value ;
unsigned long timer [ MAX_RULE_TIMERS ] = { 0 } ;
uint32_t triggers [ MAX_RULE_SETS ] = { 0 } ;
uint8_t trigger_count [ MAX_RULE_SETS ] = { 0 } ;
long new_power = - 1 ;
long old_power = - 1 ;
long old_dimm = - 1 ;
uint16_t last_minute = 60 ;
2020-06-30 17:13:38 +01:00
uint16_t vars_event = 0 ; // Bitmask supporting MAX_RULE_VARS bits
uint16_t mems_event = 0 ; // Bitmask supporting MAX_RULE_MEMS bits
2021-04-23 09:59:33 +01:00
bool teleperiod = false ;
2020-06-22 20:47:40 +01:00
bool busy = false ;
2020-11-26 16:36:43 +00:00
bool no_execute = false ; // Don't actually execute rule commands
2019-08-17 13:27:41 +01:00
2023-01-07 16:31:10 +00:00
char event_data [ RULE_MAX_EVENTSZ ] ;
2019-08-17 13:27:41 +01:00
} Rules ;
char rules_vars [ MAX_RULE_VARS ] [ 33 ] = { { 0 } } ;
2019-01-15 15:30:20 +00:00
# if (MAX_RULE_VARS>16)
# error MAX_RULE_VARS is bigger than 16
2019-01-04 19:26:37 +00:00
# endif
2019-12-22 14:36:20 +00:00
# if (MAX_RULE_MEMS>16)
# error MAX_RULE_MEMS is bigger than 16
2019-01-04 19:26:37 +00:00
# endif
2018-05-19 07:00:48 +01:00
2020-05-08 18:33:20 +01:00
/*******************************************************************************************/
/*
* Add Unishox compression to Rules
*
* New compression for Rules , depends on SetOption93
2020-05-09 18:22:12 +01:00
*
2020-05-08 18:33:20 +01:00
* To avoid memory corruption when downgrading , the format is as follows :
* - If ` SetOption93 0 `
* Rule [ x ] [ ] = 511 char max NULL terminated string ( 512 with trailing NULL )
* Rule [ x ] [ 0 ] = 0 if the Rule < x > is empty
* New : in case the string is empty we also enforce :
* Rule [ x ] [ 1 ] = 0 ( i . e . we have two conseutive NULLs )
2020-05-09 18:22:12 +01:00
*
2020-05-08 18:33:20 +01:00
* - If ` SetOption93 1 `
* If the rule is smaller than 511 , it is stored uncompressed . Rule [ x ] [ 0 ] is not null .
* If the rule is empty , Rule [ x ] [ 0 ] = 0 and Rule [ x ] [ 1 ] = 0 ;
* If the rule is bigger than 511 , it is stored compressed
* The first byte of each Rule is always NULL .
* Rule [ x ] [ 0 ] = 0 , if firmware is downgraded , the rule will be considered as empty
2020-05-09 18:22:12 +01:00
*
2020-05-08 18:33:20 +01:00
* The second byte contains the size of uncompressed rule in 8 - bytes blocks ( i . e . ( len + 7 ) / 8 )
2020-05-10 13:39:13 +01:00
* Maximum rule size is 2 KB ( 2048 bytes per rule ) , although there is little chances compression ratio will go down to 75 %
2020-05-08 18:33:20 +01:00
* Rule [ x ] [ 1 ] = size uncompressed in dwords . If zero , the rule is empty .
2020-05-09 18:22:12 +01:00
*
2020-05-08 18:33:20 +01:00
* The remaining bytes contain the compressed rule , NULL terminated
*/
/*******************************************************************************************/
2020-06-06 19:04:10 +01:00
# ifdef USE_UNISHOX_COMPRESSION
2020-05-08 18:33:20 +01:00
// Statically allocate one String per rule
String k_rules [ MAX_RULE_SETS ] = { String ( ) , String ( ) , String ( ) } ; // Strings are created empty
2020-05-22 21:14:17 +01:00
// Unishox compressor; // singleton
2020-06-06 19:04:10 +01:00
# endif // USE_UNISHOX_COMPRESSION
2020-05-08 18:33:20 +01:00
// Returns whether the rule is uncompressed, which means the first byte is not NULL
inline bool IsRuleUncompressed ( uint32_t idx ) {
2020-06-06 19:04:10 +01:00
# ifdef USE_UNISHOX_COMPRESSION
2021-06-11 17:14:12 +01:00
return Settings - > rules [ idx ] [ 0 ] ? true : false ; // first byte not NULL, the rule is not empty and not compressed
2020-05-08 18:33:20 +01:00
# else
return true ;
# endif
}
// Returns whether the rule is empty, which requires two consecutive NULL
inline bool IsRuleEmpty ( uint32_t idx ) {
2020-06-06 19:04:10 +01:00
# ifdef USE_UNISHOX_COMPRESSION
2021-06-11 17:14:12 +01:00
return ( Settings - > rules [ idx ] [ 0 ] = = 0 ) & & ( Settings - > rules [ idx ] [ 1 ] = = 0 ) ? true : false ;
2020-05-08 18:33:20 +01:00
# else
2021-06-11 17:14:12 +01:00
return ( Settings - > rules [ idx ] [ 0 ] = = 0 ) ? true : false ;
2020-05-08 18:33:20 +01:00
# endif
}
// Returns the approximate (+3-0) length of the rule, not counting the trailing NULL
size_t GetRuleLen ( uint32_t idx ) {
2020-06-06 19:04:10 +01:00
// no need to use #ifdef USE_UNISHOX_COMPRESSION, the compiler will optimize since first test is always true
2020-05-08 18:33:20 +01:00
if ( IsRuleUncompressed ( idx ) ) {
2021-06-11 17:14:12 +01:00
return strlen ( Settings - > rules [ idx ] ) ;
2020-05-08 18:33:20 +01:00
} else { // either empty or compressed
2021-06-11 17:14:12 +01:00
return Settings - > rules [ idx ] [ 1 ] * 8 ; // cheap calculation, but not byte accurate (may overshoot by 7)
2020-05-08 18:33:20 +01:00
}
}
// Returns the actual Flash storage for the Rule, including trailing NULL
size_t GetRuleLenStorage ( uint32_t idx ) {
2020-06-06 19:04:10 +01:00
# ifdef USE_UNISHOX_COMPRESSION
2021-06-11 17:14:12 +01:00
if ( Settings - > rules [ idx ] [ 0 ] | | ! Settings - > rules [ idx ] [ 1 ] ) { // if first byte is non-NULL it is uncompressed, if second byte is NULL, then it's either uncompressed or empty
return 1 + strlen ( Settings - > rules [ idx ] ) ; // uncompressed or empty
2020-05-08 18:33:20 +01:00
} else {
2021-06-11 17:14:12 +01:00
return 2 + strlen ( & Settings - > rules [ idx ] [ 1 ] ) ; // skip first byte and get len of the compressed rule
2020-05-08 18:33:20 +01:00
}
2020-05-09 17:05:13 +01:00
# else
2021-06-11 17:14:12 +01:00
return 1 + strlen ( Settings - > rules [ idx ] ) ;
2020-05-09 17:05:13 +01:00
# endif
2020-05-08 18:33:20 +01:00
}
2020-06-06 19:04:10 +01:00
# ifdef USE_UNISHOX_COMPRESSION
2020-05-08 18:33:20 +01:00
// internal function, do the actual decompression
void GetRule_decompress ( String & rule , const char * rule_head ) {
size_t buf_len = 1 + * rule_head * 8 ; // the first byte contains size of buffer for uncompressed rule / 8, buf_len may overshoot by 7
rule_head + + ; // advance to the actual compressed buffer
2020-05-23 09:05:57 +01:00
rule = Decompress ( rule_head , buf_len ) ;
2020-05-08 18:33:20 +01:00
}
2020-06-06 19:04:10 +01:00
# endif // USE_UNISHOX_COMPRESSION
2020-05-08 18:33:20 +01:00
//
// Read rule in memory, uncompress if needed
//
// Returns: String() object containing a copy of the rule (rule processing is destructive and will change the String)
String GetRule ( uint32_t idx ) {
if ( IsRuleUncompressed ( idx ) ) {
2021-06-11 17:14:12 +01:00
return String ( Settings - > rules [ idx ] ) ;
2020-05-08 18:33:20 +01:00
} else {
2020-06-06 19:04:10 +01:00
# ifdef USE_UNISHOX_COMPRESSION // we still do #ifdef to make sure we don't link unnecessary code
2020-05-08 18:33:20 +01:00
String rule ( " " ) ;
2021-06-11 17:14:12 +01:00
if ( Settings - > rules [ idx ] [ 1 ] = = 0 ) { return rule ; } // the rule is empty
2020-05-08 18:33:20 +01:00
// If the cache is empty, we need to decompress from Settings
if ( 0 = = k_rules [ idx ] . length ( ) ) {
2021-06-11 17:14:12 +01:00
GetRule_decompress ( rule , & Settings - > rules [ idx ] [ 1 ] ) ;
if ( ! Settings - > flag4 . compress_rules_cpu ) {
2020-05-08 18:33:20 +01:00
k_rules [ idx ] = rule ; // keep a copy for next time
}
} else {
// we have a valid copy
rule = k_rules [ idx ] ;
}
return rule ;
# endif
}
2020-07-14 15:21:11 +01:00
return " " ; // Fix GCC10 warning
2020-05-08 18:33:20 +01:00
}
2020-06-06 19:04:10 +01:00
# ifdef USE_UNISHOX_COMPRESSION
2020-05-08 18:33:20 +01:00
// internal function, comrpess rule and store a cached version uncompressed (except if SetOption94 1)
// If out == nullptr, we are in dry-run mode, so don't keep rule in cache
int32_t SetRule_compress ( uint32_t idx , const char * in , size_t in_len , char * out , size_t out_len ) {
int32_t len_compressed ;
2020-05-17 17:05:17 +01:00
len_compressed = compressor . unishox_compress ( in , in_len , out , out_len ) ;
2020-05-08 18:33:20 +01:00
if ( len_compressed > = 0 ) { // negative means compression failed because of buffer too small, we leave the rule untouched
// check if we need to store in cache
k_rules [ idx ] = ( const char * ) nullptr ; // Assign the String to nullptr, clears previous string and disallocate internal buffers of String object
2021-06-11 17:14:12 +01:00
if ( ( ! Settings - > flag4 . compress_rules_cpu ) & & out ) { // if out == nullptr, don't store cache
2020-05-08 18:33:20 +01:00
// keep copy in cache
k_rules [ idx ] = in ;
}
}
return len_compressed ;
}
2020-06-06 19:04:10 +01:00
# endif // USE_UNISHOX_COMPRESSION
2020-05-08 18:33:20 +01:00
// Returns:
// >= 0 : the actual stored size
// <0 : not enough space
int32_t SetRule ( uint32_t idx , const char * content , bool append = false ) {
if ( nullptr = = content ) { content = " " ; } // if nullptr, use empty string
size_t len_in = strlen ( content ) ;
bool needsCompress = false ;
size_t offset = 0 ;
if ( len_in > = MAX_RULE_SIZE ) { // if input is more than 512, it will not fit uncompressed
needsCompress = true ;
}
if ( append ) {
if ( IsRuleUncompressed ( idx ) | | IsRuleEmpty ( idx ) ) { // if already uncompressed (so below 512) and append mode, check if it still fits uncompressed
2021-06-11 17:14:12 +01:00
offset = strlen ( Settings - > rules [ idx ] ) ;
2020-05-08 18:33:20 +01:00
if ( len_in + offset > = MAX_RULE_SIZE ) {
needsCompress = true ;
}
} else {
needsCompress = true ; // we append to a non-empty compressed rule, so it won't fit uncompressed
}
}
if ( ! needsCompress ) { // the rule fits uncompressed, so just copy it
2021-06-11 17:14:12 +01:00
// strlcpy(Settings->rules[idx] + offset, content, sizeof(Settings->rules[idx]));
strlcpy ( Settings - > rules [ idx ] + offset , content , sizeof ( Settings - > rules [ idx ] ) - offset ) ;
if ( 0 = = Settings - > rules [ idx ] [ 0 ] ) {
Settings - > rules [ idx ] [ 1 ] = 0 ;
2020-05-09 17:05:13 +01:00
}
2020-05-08 18:33:20 +01:00
2020-06-06 19:04:10 +01:00
# ifdef USE_UNISHOX_COMPRESSION
2020-05-09 17:05:13 +01:00
if ( 0 ! = len_in + offset ) {
// do a dry-run compression to display how much it would be compressed
int32_t len_compressed , len_uncompressed ;
2020-05-08 18:33:20 +01:00
2021-06-11 17:14:12 +01:00
len_uncompressed = strlen ( Settings - > rules [ idx ] ) ;
len_compressed = compressor . unishox_compress ( Settings - > rules [ idx ] , len_uncompressed , nullptr /* dry-run */ , MAX_RULE_SIZE + 8 ) ;
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_INFO , PSTR ( " RUL: Stored uncompressed, would compress from %d to %d (-%d%%) " ) , len_uncompressed , len_compressed , 100 - changeUIntScale ( len_compressed , 0 , len_uncompressed , 0 , 100 ) ) ;
2020-05-09 17:05:13 +01:00
}
2020-05-08 18:33:20 +01:00
2020-06-06 19:04:10 +01:00
# endif // USE_UNISHOX_COMPRESSION
2020-05-08 18:33:20 +01:00
return len_in + offset ;
} else {
2020-06-06 19:04:10 +01:00
# ifdef USE_UNISHOX_COMPRESSION
2020-05-08 18:33:20 +01:00
int32_t len_compressed ;
// allocate temp buffer so we don't nuke the rule if it's too big to fit
char * buf_out = ( char * ) malloc ( MAX_RULE_SIZE + 8 ) ; // take some margin
if ( ! buf_out ) { return - 1 ; } // fail if couldn't allocate
// compress
if ( append ) {
String content_append = GetRule ( idx ) ; // get original Rule and decompress it if needed
content_append + = content ; // concat new content
len_in = content_append . length ( ) ; // adjust length
len_compressed = SetRule_compress ( idx , content_append . c_str ( ) , len_in , buf_out , MAX_RULE_SIZE + 8 ) ;
} else {
len_compressed = SetRule_compress ( idx , content , len_in , buf_out , MAX_RULE_SIZE + 8 ) ;
}
if ( ( len_compressed > = 0 ) & & ( len_compressed < MAX_RULE_SIZE - 2 ) ) {
// size is ok, copy to Settings
2021-06-11 17:14:12 +01:00
Settings - > rules [ idx ] [ 0 ] = 0 ; // clear first byte to mark as compressed
Settings - > rules [ idx ] [ 1 ] = ( len_in + 7 ) / 8 ; // store original length in first bytes (4 bytes chuks)
memcpy ( & Settings - > rules [ idx ] [ 2 ] , buf_out , len_compressed ) ;
Settings - > rules [ idx ] [ len_compressed + 2 ] = 0 ; // add NULL termination
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_INFO , PSTR ( " RUL: Compressed from %d to %d (-%d%%) " ) , len_in , len_compressed , 100 - changeUIntScale ( len_compressed , 0 , len_in , 0 , 100 ) ) ;
2021-06-11 17:14:12 +01:00
// AddLog(LOG_LEVEL_INFO, PSTR("RUL: First bytes: %02X%02X%02X%02X"), Settings->rules[idx][0], Settings->rules[idx][1], Settings->rules[idx][2], Settings->rules[idx][3]);
2021-01-23 16:24:54 +00:00
// AddLog(LOG_LEVEL_INFO, PSTR("RUL: GetRuleLenStorage = %d"), GetRuleLenStorage(idx));
2020-05-08 18:33:20 +01:00
} else {
len_compressed = - 1 ; // failed
// clear rule cache, so it will be reloaded from Settings
k_rules [ idx ] = ( const char * ) nullptr ;
}
free ( buf_out ) ;
return len_compressed ;
2020-06-06 19:04:10 +01:00
# else // USE_UNISHOX_COMPRESSION
2020-05-08 18:33:20 +01:00
return - 1 ; // the rule does not fit and we can't compress
2020-06-06 19:04:10 +01:00
# endif // USE_UNISHOX_COMPRESSION
2020-05-08 18:33:20 +01:00
}
}
2018-04-13 16:42:11 +01:00
/*******************************************************************************************/
2020-10-25 16:38:06 +00:00
bool RulesRuleMatch ( uint8_t rule_set , String & event , String & rule , bool stop_all_rules )
2018-04-13 16:42:11 +01:00
{
// event = {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}}
// event = {"System":{"Boot":1}}
// rule = "INA219#CURRENT>0.100"
bool match = false ;
2018-05-20 16:46:00 +01:00
char stemp [ 10 ] ;
2018-04-13 16:42:11 +01:00
// Step1: Analyse rule
2020-05-10 10:09:34 +01:00
String rule_expr = rule ; // "TELE-INA219#CURRENT>0.100"
2021-04-23 09:59:33 +01:00
if ( Rules . teleperiod ) {
2021-01-21 13:54:38 +00:00
int ppos = rule_expr . indexOf ( F ( " TELE- " ) ) ; // "TELE-INA219#CURRENT>0.100" or "INA219#CURRENT>0.100"
2018-04-27 17:06:19 +01:00
if ( ppos = = - 1 ) { return false ; } // No pre-amble in rule
2020-05-10 10:09:34 +01:00
rule_expr = rule . substring ( 5 ) ; // "INA219#CURRENT>0.100" or "SYSTEM#BOOT"
2018-04-27 17:06:19 +01:00
}
2019-09-09 16:24:27 +01:00
String rule_name , rule_param ;
2020-05-10 10:09:34 +01:00
int8_t compareOperator = parseCompareExpression ( rule_expr , rule_name , rule_param ) ; // Parse the compare expression.Return operator and the left, right part of expression
// rule_name = "INA219#CURRENT"
// rule_param = "0.100" or "%VAR1%"
2020-11-02 13:35:19 +00:00
# ifdef DEBUG_RULES
2021-06-05 10:47:09 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " RUL-RM1: Teleperiod %d, Expr %s, Name %s, Param %s " ) , Rules . teleperiod , rule_expr . c_str ( ) , rule_name . c_str ( ) , rule_param . c_str ( ) ) ;
2020-11-02 13:35:19 +00:00
# endif
2018-04-13 16:42:11 +01:00
2019-10-12 15:42:11 +01:00
char rule_svalue [ 80 ] = { 0 } ;
2019-07-01 17:20:43 +01:00
float rule_value = 0 ;
2019-09-09 16:24:27 +01:00
if ( compareOperator ! = COMPARE_OPERATOR_NONE ) {
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_VARS ; i + + ) {
2018-05-20 16:46:00 +01:00
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%VAR%d%% " ) , i + 1 ) ;
if ( rule_param . startsWith ( stemp ) ) {
2019-08-17 13:27:41 +01:00
rule_param = rules_vars [ i ] ;
2018-05-20 16:46:00 +01:00
break ;
}
}
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_MEMS ; i + + ) {
2018-05-20 16:46:00 +01:00
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%MEM%d%% " ) , i + 1 ) ;
if ( rule_param . startsWith ( stemp ) ) {
2019-12-16 14:13:57 +00:00
rule_param = SettingsText ( SET_MEM1 + i ) ;
2018-05-20 16:46:00 +01:00
break ;
}
}
2020-05-24 08:57:11 +01:00
if ( rule_param . startsWith ( F ( " %TIME% " ) ) ) {
2019-02-19 13:49:15 +00:00
rule_param = String ( MinutesPastMidnight ( ) ) ;
2018-08-27 08:59:17 +01:00
}
2020-05-24 08:57:11 +01:00
if ( rule_param . startsWith ( F ( " %UPTIME% " ) ) ) {
2019-02-19 13:49:15 +00:00
rule_param = String ( MinutesUptime ( ) ) ;
2018-08-27 08:59:17 +01:00
}
2020-05-24 08:57:11 +01:00
if ( rule_param . startsWith ( F ( " %TIMESTAMP% " ) ) ) {
2018-12-27 17:57:27 +00:00
rule_param = GetDateAndTime ( DT_LOCAL ) . c_str ( ) ;
}
2022-02-04 15:18:56 +00:00
# if defined(USE_TIMERS)
if ( rule_param . startsWith ( F ( " %TIMER " ) ) ) {
uint32_t index = rule_param . substring ( 6 ) . toInt ( ) ;
if ( ( index > 0 ) & & ( index < = MAX_TIMERS ) ) {
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%TIMER%d%% " ) , index ) ;
if ( rule_param . startsWith ( stemp ) ) {
2022-03-26 13:10:08 +00:00
rule_param = String ( TimerGetTimeOfDay ( index - 1 ) ) ;
2022-02-04 15:18:56 +00:00
}
}
}
# if defined(USE_SUNRISE)
2020-05-24 08:57:11 +01:00
if ( rule_param . startsWith ( F ( " %SUNRISE% " ) ) ) {
2019-02-19 13:49:15 +00:00
rule_param = String ( SunMinutes ( 0 ) ) ;
2018-08-27 08:59:17 +01:00
}
2020-05-24 08:57:11 +01:00
if ( rule_param . startsWith ( F ( " %SUNSET% " ) ) ) {
2019-02-19 13:49:15 +00:00
rule_param = String ( SunMinutes ( 1 ) ) ;
2018-08-27 08:59:17 +01:00
}
2022-02-04 15:18:56 +00:00
# endif // USE_SUNRISE
2022-01-27 10:00:49 +00:00
# endif // USE_TIMERS
2022-01-23 00:22:23 +00:00
# if defined(USE_LIGHT)
char scolor [ LIGHT_COLOR_SIZE ] ;
if ( rule_param . startsWith ( F ( " %COLOR% " ) ) ) {
rule_param = LightGetColor ( scolor ) ;
2022-01-27 11:55:25 +00:00
}
2022-01-23 00:22:23 +00:00
# endif
2020-05-29 21:52:45 +01:00
// #ifdef USE_ZIGBEE
// if (rule_param.startsWith(F("%ZBDEVICE%"))) {
// snprintf_P(stemp, sizeof(stemp), PSTR("0x%04X"), Z_GetLastDevice());
// rule_param = String(stemp);
// }
// if (rule_param.startsWith(F("%ZBGROUP%"))) {
// rule_param = String(Z_GetLastGroup());
// }
// if (rule_param.startsWith(F("%ZBCLUSTER%"))) {
// rule_param = String(Z_GetLastCluster());
// }
// if (rule_param.startsWith(F("%ZBENDPOINT%"))) {
// rule_param = String(Z_GetLastEndpoint());
// }
// #endif
2018-05-20 16:46:00 +01:00
rule_param . toUpperCase ( ) ;
2019-03-08 18:24:02 +00:00
strlcpy ( rule_svalue , rule_param . c_str ( ) , sizeof ( rule_svalue ) ) ;
2018-05-20 16:46:00 +01:00
2018-05-24 16:08:14 +01:00
int temp_value = GetStateNumber ( rule_svalue ) ;
2018-05-06 15:07:42 +01:00
if ( temp_value > - 1 ) {
rule_value = temp_value ;
} else {
2020-05-10 10:09:34 +01:00
rule_value = CharToFloat ( ( char * ) rule_svalue ) ; // 0.1 - This saves 9k code over toFLoat()!
2018-05-06 15:07:42 +01:00
}
2018-04-13 16:42:11 +01:00
}
2020-05-10 10:09:34 +01:00
// Step2: Search rule_name
int pos ;
2020-01-24 16:13:39 +00:00
int rule_name_idx = 0 ;
2021-05-04 10:10:24 +01:00
if ( ( pos = rule_name . indexOf ( F ( " [ " ) ) ) > 0 ) { // "SUBTYPE1#CURRENT[1]"
2020-01-24 16:13:39 +00:00
rule_name_idx = rule_name . substring ( pos + 1 ) . toInt ( ) ;
2019-09-22 15:14:34 +01:00
if ( ( rule_name_idx < 1 ) | | ( rule_name_idx > 6 ) ) { // Allow indexes 1 to 6
rule_name_idx = 1 ;
}
2020-01-24 16:13:39 +00:00
rule_name = rule_name . substring ( 0 , pos ) ; // "SUBTYPE1#CURRENT"
2019-09-22 15:14:34 +01:00
}
2018-04-13 16:42:11 +01:00
2021-05-04 10:10:24 +01:00
String buf = event ; // Copy the string into a new buffer that will be modified
2020-10-15 13:42:46 +01:00
2021-06-22 15:54:47 +01:00
// Do not do below replace as it will replace escaped quote too.
// buf.replace("\\"," "); // "Disable" any escaped control character
2021-06-22 16:30:45 +01:00
//AddLog(LOG_LEVEL_DEBUG, PSTR("RUL-RM2: RulesRuleMatch |%s|"), buf.c_str());
2020-09-23 18:38:24 +01:00
JsonParser parser ( ( char * ) buf . c_str ( ) ) ;
2020-09-21 20:49:32 +01:00
JsonParserObject obj = parser . getRootObject ( ) ;
if ( ! obj ) {
2021-06-05 10:47:09 +01:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("RUL: Event too long (%d)"), event.length());
2021-01-23 16:24:54 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " RUL: No valid JSON (%s) " ) , buf . c_str ( ) ) ;
2020-09-21 20:49:32 +01:00
return false ; // No valid JSON data
}
String subtype ;
uint32_t i = 0 ;
2021-05-04 10:10:24 +01:00
while ( ( pos = rule_name . indexOf ( F ( " # " ) ) ) > 0 ) { // "SUBTYPE1#SUBTYPE2#CURRENT"
2020-09-21 20:49:32 +01:00
subtype = rule_name . substring ( 0 , pos ) ;
obj = obj [ subtype . c_str ( ) ] . getObject ( ) ;
2020-11-02 13:35:19 +00:00
if ( ! obj ) { return false ; } // not found
2020-09-21 20:49:32 +01:00
rule_name = rule_name . substring ( pos + 1 ) ;
if ( i + + > 10 ) { return false ; } // Abandon possible loop
yield ( ) ;
}
JsonParserToken val = obj [ rule_name . c_str ( ) ] ;
2020-11-02 13:35:19 +00:00
if ( ! val ) { return false ; } // last level not found
2020-09-21 20:49:32 +01:00
const char * str_value ;
if ( rule_name_idx ) {
if ( val . isArray ( ) ) {
str_value = ( val . getArray ( ) ) [ rule_name_idx - 1 ] . getStr ( ) ;
} else {
str_value = val . getStr ( ) ;
}
} else {
2020-11-02 13:35:19 +00:00
str_value = val . getStr ( ) ; // "CURRENT"
2020-09-21 20:49:32 +01:00
}
2020-01-24 16:13:39 +00:00
2020-11-02 13:35:19 +00:00
# ifdef DEBUG_RULES
2021-06-05 10:47:09 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " RUL-RM3: Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json |%s| " ) ,
2020-11-02 13:35:19 +00:00
rule_name . c_str ( ) , rule_svalue , Rules . trigger_count [ rule_set ] , bitRead ( Rules . triggers [ rule_set ] ,
Rules . trigger_count [ rule_set ] ) , event . c_str ( ) , ( str_value [ 0 ] ! = ' \0 ' ) ? str_value : " none " ) ;
# endif
2018-04-13 16:42:11 +01:00
2019-08-17 13:27:41 +01:00
Rules . event_value = str_value ; // Prepare %value%
2018-04-13 16:42:11 +01:00
// Step 3: Compare rule (value)
2019-09-22 15:14:34 +01:00
float value = 0 ;
2018-04-13 16:42:11 +01:00
if ( str_value ) {
2019-07-01 17:20:43 +01:00
value = CharToFloat ( ( char * ) str_value ) ;
2018-08-13 12:09:22 +01:00
int int_value = int ( value ) ;
int int_rule_value = int ( rule_value ) ;
2021-01-21 13:54:38 +00:00
String str_str_value = String ( str_value ) ;
2019-09-09 16:24:27 +01:00
switch ( compareOperator ) {
2019-02-05 14:34:17 +00:00
case COMPARE_OPERATOR_EXACT_DIVISION :
match = ( int_rule_value & & ( int_value % int_rule_value ) = = 0 ) ;
break ;
case COMPARE_OPERATOR_EQUAL :
match = ( ! strcasecmp ( str_value , rule_svalue ) ) ; // Compare strings - this also works for hexadecimals
break ;
case COMPARE_OPERATOR_BIGGER :
match = ( value > rule_value ) ;
break ;
case COMPARE_OPERATOR_SMALLER :
match = ( value < rule_value ) ;
2018-08-13 12:09:22 +01:00
break ;
2019-02-05 14:34:17 +00:00
case COMPARE_OPERATOR_NUMBER_EQUAL :
match = ( value = = rule_value ) ;
2018-04-13 16:42:11 +01:00
break ;
2019-02-05 14:34:17 +00:00
case COMPARE_OPERATOR_NOT_EQUAL :
match = ( value ! = rule_value ) ;
2018-04-13 16:42:11 +01:00
break ;
2019-02-05 14:34:17 +00:00
case COMPARE_OPERATOR_BIGGER_EQUAL :
match = ( value > = rule_value ) ;
2018-04-13 16:42:11 +01:00
break ;
2019-02-05 14:34:17 +00:00
case COMPARE_OPERATOR_SMALLER_EQUAL :
match = ( value < = rule_value ) ;
2018-04-13 16:42:11 +01:00
break ;
2021-01-21 13:54:38 +00:00
case COMPARE_OPERATOR_STRING_ENDS_WITH :
match = str_str_value . endsWith ( rule_svalue ) ;
break ;
case COMPARE_OPERATOR_STRING_STARTS_WITH :
match = str_str_value . startsWith ( rule_svalue ) ;
break ;
case COMPARE_OPERATOR_STRING_CONTAINS :
2021-01-31 12:52:56 +00:00
match = ( str_str_value . indexOf ( rule_svalue ) > = 0 ) ;
break ;
case COMPARE_OPERATOR_STRING_NOT_EQUAL :
match = ( 0 ! = strcasecmp ( str_value , rule_svalue ) ) ; // Compare strings - this also works for hexadecimals
break ;
case COMPARE_OPERATOR_STRING_NOT_CONTAINS :
match = ( str_str_value . indexOf ( rule_svalue ) < 0 ) ;
2021-01-21 13:54:38 +00:00
break ;
2019-02-05 14:34:17 +00:00
default :
match = true ;
2018-04-13 16:42:11 +01:00
}
} else match = true ;
2020-10-25 16:38:06 +00:00
if ( stop_all_rules ) { match = false ; }
2021-06-05 10:47:09 +01:00
//AddLog(LOG_LEVEL_DEBUG, PSTR("RUL-RM4: Match 1 %d, Triggers %08X, TriggerCount %d"), match, Rules.triggers[rule_set], Rules.trigger_count[rule_set]);
2020-03-02 14:51:33 +00:00
2021-06-11 17:14:12 +01:00
if ( bitRead ( Settings - > rule_once , rule_set ) ) {
2018-04-13 16:42:11 +01:00
if ( match ) { // Only allow match state changes
2019-08-17 13:27:41 +01:00
if ( ! bitRead ( Rules . triggers [ rule_set ] , Rules . trigger_count [ rule_set ] ) ) {
bitSet ( Rules . triggers [ rule_set ] , Rules . trigger_count [ rule_set ] ) ;
2018-04-13 16:42:11 +01:00
} else {
match = false ;
}
} else {
2019-08-17 13:27:41 +01:00
bitClear ( Rules . triggers [ rule_set ] , Rules . trigger_count [ rule_set ] ) ;
2018-04-13 16:42:11 +01:00
}
}
2021-06-05 10:47:09 +01:00
//AddLog(LOG_LEVEL_DEBUG, PSTR("RUL-RM5: Match 2 %d, Triggers %08X, TriggerCount %d"), match, Rules.triggers[rule_set], Rules.trigger_count[rule_set]);
2020-03-02 14:51:33 +00:00
2018-04-13 16:42:11 +01:00
return match ;
}
2019-09-09 16:24:27 +01:00
/********************************************************************************************/
/*
* Parse a comparison expression .
* Get 3 parts - left expression , compare operator and right expression .
* Input :
* expr - A comparison expression like VAR1 > = MEM1 + 10
* leftExpr - Used to accept returned left parts of expression
* rightExpr - Used to accept returned right parts of expression
* Output :
* leftExpr - Left parts of expression
* rightExpr - Right parts of expression
* Return :
* compare operator
* COMPARE_OPERATOR_NONE - failed
*/
int8_t parseCompareExpression ( String & expr , String & leftExpr , String & rightExpr )
{
char compare_operator [ 3 ] ;
int8_t compare = COMPARE_OPERATOR_NONE ;
2019-09-12 02:46:01 +01:00
leftExpr = expr ;
2019-09-09 16:24:27 +01:00
int position ;
for ( int8_t i = MAXIMUM_COMPARE_OPERATOR ; i > = 0 ; i - - ) {
snprintf_P ( compare_operator , sizeof ( compare_operator ) , kCompareOperators + ( i * 2 ) ) ;
if ( ( position = expr . indexOf ( compare_operator ) ) > 0 ) {
compare = i ;
leftExpr = expr . substring ( 0 , position ) ;
leftExpr . trim ( ) ;
rightExpr = expr . substring ( position + strlen ( compare_operator ) ) ;
rightExpr . trim ( ) ;
break ;
}
}
return compare ;
}
2019-11-01 15:05:07 +00:00
void RulesVarReplace ( String & commands , const String & sfind , const String & replace )
{
// String ufind = sfind;
// ufind.toUpperCase();
// char *find = (char*)ufind.c_str();
char * find = ( char * ) sfind . c_str ( ) ;
uint32_t flen = strlen ( find ) ;
String ucommand = commands ;
ucommand . toUpperCase ( ) ;
char * read_from = ( char * ) ucommand . c_str ( ) ;
char * write_to = ( char * ) commands . c_str ( ) ;
char * found_at ;
while ( ( found_at = strstr ( read_from , find ) ) ! = nullptr ) {
write_to + = ( found_at - read_from ) ;
memmove_P ( write_to , find , flen ) ; // Make variable Uppercase
write_to + = flen ;
read_from = found_at + flen ;
}
commands . replace ( find , replace ) ;
}
2018-04-13 16:42:11 +01:00
/*******************************************************************************************/
2024-02-14 08:20:56 +00:00
bool RuleSetProcess ( uint8_t rule_set , String & event_saved )
2018-04-13 16:42:11 +01:00
{
bool serviced = false ;
2018-04-27 17:06:19 +01:00
char stemp [ 10 ] ;
2018-04-13 16:42:11 +01:00
2018-05-06 15:07:42 +01:00
delay ( 0 ) ; // Prohibit possible loop software watchdog
2021-10-02 14:46:51 +01:00
# ifdef DEBUG_RULES
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " RUL-RP1: Event = %s, Rule = %s " ) , event_saved . c_str ( ) , Settings - > rules [ rule_set ] ) ;
# endif
2018-05-13 16:38:44 +01:00
2020-05-08 18:33:20 +01:00
String rules = GetRule ( rule_set ) ;
2018-04-13 16:42:11 +01:00
2019-08-17 13:27:41 +01:00
Rules . trigger_count [ rule_set ] = 0 ;
2018-04-13 16:42:11 +01:00
int plen = 0 ;
2018-12-01 21:12:33 +00:00
int plen2 = 0 ;
bool stop_all_rules = false ;
2018-04-13 16:42:11 +01:00
while ( true ) {
2018-04-17 14:34:18 +01:00
rules = rules . substring ( plen ) ; // Select relative to last rule
2018-04-13 16:42:11 +01:00
rules . trim ( ) ;
2018-04-17 14:34:18 +01:00
if ( ! rules . length ( ) ) { return serviced ; } // No more rules
2018-04-13 16:42:11 +01:00
String rule = rules ;
2018-04-17 14:34:18 +01:00
rule . toUpperCase ( ) ; // "ON INA219#CURRENT>0.100 DO BACKLOG DIMMER 10;COLOR 100000 ENDON"
2021-01-18 20:48:04 +00:00
if ( ! rule . startsWith ( F ( " ON " ) ) ) { return serviced ; } // Bad syntax - Nothing to start on
2018-04-13 16:42:11 +01:00
2021-01-18 20:48:04 +00:00
int pevt = rule . indexOf ( F ( " DO " ) ) ;
2018-04-17 14:34:18 +01:00
if ( pevt = = - 1 ) { return serviced ; } // Bad syntax - Nothing to do
String event_trigger = rule . substring ( 3 , pevt ) ; // "INA219#CURRENT>0.100"
2021-10-02 14:46:51 +01:00
event_trigger . trim ( ) ;
2018-04-13 16:42:11 +01:00
2021-01-18 20:48:04 +00:00
plen = rule . indexOf ( F ( " ENDON " ) ) ;
plen2 = rule . indexOf ( F ( " BREAK " ) ) ;
2018-12-01 21:12:33 +00:00
if ( ( plen = = - 1 ) & & ( plen2 = = - 1 ) ) { return serviced ; } // Bad syntax - No ENDON neither BREAK
if ( plen = = - 1 ) { plen = 9999 ; }
if ( plen2 = = - 1 ) { plen2 = 9999 ; }
2018-12-01 22:00:34 +00:00
plen = tmin ( plen , plen2 ) ;
2018-12-01 21:12:33 +00:00
2018-04-17 14:34:18 +01:00
String commands = rules . substring ( pevt + 4 , plen ) ; // "Backlog Dimmer 10;Color 100000"
2019-08-17 13:27:41 +01:00
Rules . event_value = " " ;
2018-05-13 16:38:44 +01:00
String event = event_saved ;
2018-04-13 16:42:11 +01:00
2020-11-02 13:35:19 +00:00
# ifdef DEBUG_RULES
2021-10-02 14:46:51 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " RUL-RP2: Event |%s|, Rule |%s|, Command(s) |%s| " ) , event . c_str ( ) , event_trigger . c_str ( ) , commands . c_str ( ) ) ;
2020-11-02 13:35:19 +00:00
# endif
2018-04-13 16:42:11 +01:00
2022-03-11 15:27:49 +00:00
if ( ! event_trigger . startsWith ( F ( " FILE# " ) ) & & RulesRuleMatch ( rule_set , event , event_trigger , stop_all_rules ) ) {
2020-11-26 16:36:43 +00:00
if ( Rules . no_execute ) return true ;
2020-10-25 16:38:06 +00:00
if ( plen = = plen2 ) { stop_all_rules = true ; } // If BREAK was used on a triggered rule, Stop execution of this rule set
2018-04-27 17:06:19 +01:00
commands . trim ( ) ;
String ucommand = commands ;
ucommand . toUpperCase ( ) ;
2019-12-31 16:17:30 +00:00
2018-05-20 16:46:00 +01:00
// if (!ucommand.startsWith("BACKLOG")) { commands = "backlog " + commands; } // Always use Backlog to prevent power race exception
2019-12-31 16:24:47 +00:00
// Use Backlog with event to prevent rule event loop exception unless IF is used which uses an implicit backlog
2021-01-18 20:48:04 +00:00
if ( ( ucommand . indexOf ( F ( " IF " ) ) = = - 1 ) & &
( ucommand . indexOf ( F ( " EVENT " ) ) ! = - 1 ) & &
2022-04-20 16:40:07 +01:00
( ucommand . indexOf ( F ( " BACKLOG " ) ) = = - 1 ) ) {
2021-01-18 21:32:59 +00:00
commands = String ( F ( " backlog " ) ) + commands ;
2019-12-31 16:17:30 +00:00
}
2019-11-01 15:05:07 +00:00
RulesVarReplace ( commands , F ( " %VALUE% " ) , Rules . event_value ) ;
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_VARS ; i + + ) {
2019-11-01 15:05:07 +00:00
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%VAR%d%% " ) , i + 1 ) ;
RulesVarReplace ( commands , stemp , rules_vars [ i ] ) ;
2018-05-20 16:46:00 +01:00
}
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_MEMS ; i + + ) {
2019-11-01 15:05:07 +00:00
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%MEM%d%% " ) , i + 1 ) ;
2019-12-16 14:13:57 +00:00
RulesVarReplace ( commands , stemp , SettingsText ( SET_MEM1 + i ) ) ;
2018-05-20 16:46:00 +01:00
}
2023-08-17 14:34:13 +01:00
for ( uint32_t i = 0 ; i < TasmotaGlobal . devices_present ; i + + ) {
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%POWER%d%% " ) , i + 1 ) ;
RulesVarReplace ( commands , stemp , String ( bitRead ( TasmotaGlobal . power , i ) ) ) ;
}
for ( uint32_t i = 0 ; i < MAX_SWITCHES_SET ; i + + ) {
if ( SwitchUsed ( i ) ) {
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%SWITCH%d%% " ) , i + 1 ) ;
RulesVarReplace ( commands , stemp , String ( SwitchState ( i ) ) ) ;
}
}
2019-11-01 15:05:07 +00:00
RulesVarReplace ( commands , F ( " %TIME% " ) , String ( MinutesPastMidnight ( ) ) ) ;
2020-04-13 22:05:12 +01:00
RulesVarReplace ( commands , F ( " %UTCTIME% " ) , String ( UtcTime ( ) ) ) ;
2019-11-01 15:05:07 +00:00
RulesVarReplace ( commands , F ( " %UPTIME% " ) , String ( MinutesUptime ( ) ) ) ;
RulesVarReplace ( commands , F ( " %TIMESTAMP% " ) , GetDateAndTime ( DT_LOCAL ) ) ;
2020-10-30 11:29:48 +00:00
RulesVarReplace ( commands , F ( " %TOPIC% " ) , TasmotaGlobal . mqtt_topic ) ;
2020-09-21 02:29:02 +01:00
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %06X " ) , ESP_getChipId ( ) ) ;
RulesVarReplace ( commands , F ( " %DEVICEID% " ) , stemp ) ;
2021-04-11 12:29:33 +01:00
RulesVarReplace ( commands , F ( " %MACADDR% " ) , NetworkUniqueId ( ) ) ;
2022-01-27 10:00:49 +00:00
# if defined(USE_TIMERS)
for ( uint32_t i = 0 ; i < MAX_TIMERS ; i + + ) {
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%TIMER%d%% " ) , i + 1 ) ;
2022-03-26 13:10:08 +00:00
RulesVarReplace ( commands , stemp , String ( TimerGetTimeOfDay ( i ) ) ) ;
2022-01-27 10:00:49 +00:00
}
2022-02-04 15:18:56 +00:00
# if defined(USE_SUNRISE)
RulesVarReplace ( commands , F ( " %SUNRISE% " ) , String ( SunMinutes ( 0 ) ) ) ;
RulesVarReplace ( commands , F ( " %SUNSET% " ) , String ( SunMinutes ( 1 ) ) ) ;
# endif // USE_SUNRISE
2022-01-27 10:00:49 +00:00
# endif // USE_TIMERS
2022-01-23 00:22:23 +00:00
# if defined(USE_LIGHT)
char scolor [ LIGHT_COLOR_SIZE ] ;
RulesVarReplace ( commands , F ( " %COLOR% " ) , LightGetColor ( scolor ) ) ;
# endif
2020-05-29 21:52:45 +01:00
# ifdef USE_ZIGBEE
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " 0x%04X " ) , Z_GetLastDevice ( ) ) ;
RulesVarReplace ( commands , F ( " %ZBDEVICE% " ) , String ( stemp ) ) ;
RulesVarReplace ( commands , F ( " %ZBGROUP% " ) , String ( Z_GetLastGroup ( ) ) ) ;
RulesVarReplace ( commands , F ( " %ZBCLUSTER% " ) , String ( Z_GetLastCluster ( ) ) ) ;
RulesVarReplace ( commands , F ( " %ZBENDPOINT% " ) , String ( Z_GetLastEndpoint ( ) ) ) ;
# endif
2018-06-26 15:22:53 +01:00
2018-05-20 16:46:00 +01:00
char command [ commands . length ( ) + 1 ] ;
2019-03-08 18:24:02 +00:00
strlcpy ( command , commands . c_str ( ) , sizeof ( command ) ) ;
2018-04-13 16:42:11 +01:00
2021-01-23 16:24:54 +00:00
AddLog ( LOG_LEVEL_INFO , PSTR ( " RUL: %s performs \" %s \" " ) , event_trigger . c_str ( ) , command ) ;
2018-04-13 16:42:11 +01:00
2019-03-23 16:00:59 +00:00
// Response_P(S_JSON_COMMAND_SVALUE, D_CMND_RULE, D_JSON_INITIATED);
2018-05-20 16:46:00 +01:00
// MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_RULE));
2019-09-09 16:24:27 +01:00
# ifdef SUPPORT_IF_STATEMENT
char * pCmd = command ;
2019-11-01 15:05:07 +00:00
RulesPreprocessCommand ( pCmd ) ; // Do pre-process for IF statement
2019-09-09 16:24:27 +01:00
# endif
2018-05-28 14:52:42 +01:00
ExecuteCommand ( command , SRC_RULE ) ;
2018-04-13 16:42:11 +01:00
serviced = true ;
}
2019-11-12 18:54:29 +00:00
plen + = 6 ;
2019-08-17 13:27:41 +01:00
Rules . trigger_count [ rule_set ] + + ;
2018-04-13 16:42:11 +01:00
}
return serviced ;
}
/*******************************************************************************************/
2022-03-11 15:27:49 +00:00
String RuleLoadFile ( const char * fname ) {
/* Read a string from rule space data between 'ON FILE#<fname> DO ' and ' ENDON' like:
rule3 on file # calib . dat do { " rms " : { " current_a " : 3166385 , " voltage_a " : - 767262 } , " freq " : 0 } endon
NOTE : String may not contain word ' ENDON ' ! !
*/
String filename = F ( " ON FILE# " ) ;
filename + = fname ;
filename + = F ( " DO " ) ;
// filename.toUpperCase();
for ( uint32_t i = 0 ; i < MAX_RULE_SETS ; i + + ) {
if ( ! GetRuleLen ( i ) ) { continue ; }
String rules = GetRule ( i ) ;
rules . toUpperCase ( ) ;
int start = rules . indexOf ( filename ) ;
if ( start = = - 1 ) { continue ; }
start + = filename . length ( ) ;
int end = rules . indexOf ( F ( " ENDON " ) , start ) ;
if ( end = = - 1 ) { continue ; }
rules = GetRule ( i ) ;
return rules . substring ( start , end ) ; // {"rms":{"current_a":3166385,"voltage_a":-767262},"freq":0}
}
// AddLog(LOG_LEVEL_DEBUG, PSTR("RUL: File '%s' not found or empty"), fname);
return " " ;
}
/*******************************************************************************************/
2021-06-06 16:26:01 +01:00
bool RulesProcessEvent ( const char * json_event )
2018-05-24 13:25:52 +01:00
{
2021-08-19 11:37:19 +01:00
# ifdef USE_BERRY
// events are passed to Berry before Rules engine
2021-10-19 21:38:54 +01:00
callBerryRule ( json_event , Rules . teleperiod ) ;
2021-08-19 11:37:19 +01:00
# endif
2020-07-04 13:29:15 +01:00
if ( Rules . busy ) { return false ; }
2018-05-24 13:25:52 +01:00
2020-07-04 13:29:15 +01:00
Rules . busy = true ;
bool serviced = false ;
2020-06-22 20:47:40 +01:00
2021-06-25 16:09:53 +01:00
SHOW_FREE_MEM ( PSTR ( " RulesProcessEvent " ) ) ;
2018-06-26 10:48:09 +01:00
2021-10-02 14:46:51 +01:00
# ifdef DEBUG_RULES
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " RUL: ProcessEvent |%s| " ) , json_event ) ;
# endif
2020-10-15 13:42:46 +01:00
2020-07-04 13:29:15 +01:00
String event_saved = json_event ;
// json_event = {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}}
// json_event = {"System":{"Boot":1}}
// json_event = {"SerialReceived":"on"} - invalid but will be expanded to {"SerialReceived":{"Data":"on"}}
char * p = strchr ( json_event , ' : ' ) ;
if ( ( p ! = NULL ) & & ! ( strchr ( + + p , ' : ' ) ) ) { // Find second colon
event_saved . replace ( F ( " : " ) , F ( " :{ \" Data \" : " ) ) ;
event_saved + = F ( " } " ) ;
// event_saved = {"SerialReceived":{"Data":"on"}}
}
event_saved . toUpperCase ( ) ;
2018-05-24 13:25:52 +01:00
2021-10-02 14:46:51 +01:00
# ifdef DEBUG_RULES
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " RUL: Event |%s| " ) , event_saved . c_str ( ) ) ;
# endif
2018-06-28 11:25:50 +01:00
2020-07-04 13:29:15 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_SETS ; i + + ) {
2021-06-11 17:14:12 +01:00
if ( GetRuleLen ( i ) & & bitRead ( Settings - > rule_enabled , i ) ) {
2024-02-14 08:20:56 +00:00
if ( RuleSetProcess ( i , event_saved ) ) { serviced = true ; }
2018-05-24 13:25:52 +01:00
}
}
2020-07-04 13:29:15 +01:00
Rules . busy = false ;
2018-05-24 13:25:52 +01:00
return serviced ;
}
2021-06-02 16:56:44 +01:00
bool RulesProcess ( void ) {
return RulesProcessEvent ( XdrvMailbox . data ) ;
2018-06-25 17:00:20 +01:00
}
2018-11-14 13:32:09 +00:00
void RulesInit ( void )
2018-04-13 16:42:11 +01:00
{
2020-07-23 12:23:16 +01:00
// indicates scripter not enabled
2021-06-11 17:14:12 +01:00
bitWrite ( Settings - > rule_once , 7 , 0 ) ;
2020-07-23 12:23:16 +01:00
// and indicates scripter do not use compress
2021-06-11 17:14:12 +01:00
bitWrite ( Settings - > rule_once , 6 , 0 ) ;
2020-07-23 12:23:16 +01:00
2020-10-29 12:58:50 +00:00
TasmotaGlobal . rules_flag . data = 0 ;
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_SETS ; i + + ) {
2020-05-08 18:33:20 +01:00
if ( 0 = = GetRuleLen ( i ) ) {
2021-06-11 17:14:12 +01:00
bitWrite ( Settings - > rule_enabled , i , 0 ) ;
bitWrite ( Settings - > rule_once , i , 0 ) ;
2018-05-24 13:25:52 +01:00
}
2018-04-13 16:42:11 +01:00
}
2021-04-23 09:59:33 +01:00
Rules . teleperiod = false ;
2018-04-13 16:42:11 +01:00
}
2018-11-14 13:32:09 +00:00
void RulesEvery50ms ( void )
2018-04-13 16:42:11 +01:00
{
2022-02-14 11:51:29 +00:00
if ( ( Settings - > rule_enabled | | BERRY_RULES ) & & ! Rules . busy ) { // Any rule enabled
2023-01-07 16:31:10 +00:00
char json_event [ RULE_MAX_EVENTSZ + 16 ] ; // Add 16 chars for {"Event": .. }
2018-06-25 17:00:20 +01:00
2020-10-28 18:03:39 +00:00
if ( - 1 = = Rules . new_power ) { Rules . new_power = TasmotaGlobal . power ; }
2019-08-17 13:27:41 +01:00
if ( Rules . new_power ! = Rules . old_power ) {
if ( Rules . old_power ! = - 1 ) {
2020-10-30 11:29:48 +00:00
for ( uint32_t i = 0 ; i < TasmotaGlobal . devices_present ; i + + ) {
2019-08-17 13:27:41 +01:00
uint8_t new_state = ( Rules . new_power > > i ) & 1 ;
if ( new_state ! = ( ( Rules . old_power > > i ) & 1 ) ) {
2018-06-25 17:00:20 +01:00
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Power%d \" :{ \" State \" :%d}} " ) , i + 1 , new_state ) ;
RulesProcessEvent ( json_event ) ;
2018-05-06 15:07:42 +01:00
}
}
2018-09-27 03:02:55 +01:00
} else {
// Boot time POWER OUTPUTS (Relays) Status
2020-10-30 11:29:48 +00:00
for ( uint32_t i = 0 ; i < TasmotaGlobal . devices_present ; i + + ) {
2019-08-17 13:27:41 +01:00
uint8_t new_state = ( Rules . new_power > > i ) & 1 ;
2018-09-27 03:02:55 +01:00
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Power%d \" :{ \" Boot \" :%d}} " ) , i + 1 , new_state ) ;
RulesProcessEvent ( json_event ) ;
}
// Boot time SWITCHES Status
2023-02-06 10:45:28 +00:00
for ( uint32_t i = 0 ; i < MAX_SWITCHES_SET ; i + + ) {
if ( SwitchUsed ( i ) ) {
2020-11-01 12:26:35 +00:00
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" %s \" :{ \" Boot \" :%d}} " ) , GetSwitchText ( i ) . c_str ( ) , ( SwitchState ( i ) ) ) ;
2018-09-27 03:02:55 +01:00
RulesProcessEvent ( json_event ) ;
}
}
2018-05-06 15:07:42 +01:00
}
2019-08-17 13:27:41 +01:00
Rules . old_power = Rules . new_power ;
2018-05-29 16:24:42 +01:00
}
2021-06-11 17:14:12 +01:00
else if ( Rules . old_dimm ! = Settings - > light_dimmer ) {
2019-08-17 13:27:41 +01:00
if ( Rules . old_dimm ! = - 1 ) {
2021-06-11 17:14:12 +01:00
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Dimmer \" :{ \" State \" :%d}} " ) , Settings - > light_dimmer ) ;
2018-10-01 17:13:47 +01:00
} else {
// Boot time DIMMER VALUE
2021-06-11 17:14:12 +01:00
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Dimmer \" :{ \" Boot \" :%d}} " ) , Settings - > light_dimmer ) ;
2018-10-01 17:13:47 +01:00
}
RulesProcessEvent ( json_event ) ;
2021-06-11 17:14:12 +01:00
Rules . old_dimm = Settings - > light_dimmer ;
2018-10-01 17:13:47 +01:00
}
2019-08-17 13:27:41 +01:00
else if ( Rules . event_data [ 0 ] ) {
2018-05-29 16:24:42 +01:00
char * event ;
char * parameter ;
2019-08-17 13:27:41 +01:00
event = strtok_r ( Rules . event_data , " = " , & parameter ) ; // Rules.event_data = fanspeed=10
2018-05-29 16:24:42 +01:00
if ( event ) {
event = Trim ( event ) ;
if ( parameter ) {
parameter = Trim ( parameter ) ;
} else {
parameter = event + strlen ( event ) ; // '\0'
}
2021-06-29 09:46:56 +01:00
bool quotes = ( parameter [ 0 ] ! = ' { ' ) ;
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Event \" :{ \" %s \" :%s%s%s}} " ) , event , ( quotes ) ? " \" " : " " , parameter , ( quotes ) ? " \" " : " " ) ;
2019-08-17 13:27:41 +01:00
Rules . event_data [ 0 ] = ' \0 ' ;
2018-06-25 17:00:20 +01:00
RulesProcessEvent ( json_event ) ;
2018-05-29 16:24:42 +01:00
} else {
2019-08-17 13:27:41 +01:00
Rules . event_data [ 0 ] = ' \0 ' ;
2018-05-29 16:24:42 +01:00
}
}
2019-08-17 13:27:41 +01:00
else if ( Rules . vars_event | | Rules . mems_event ) {
if ( Rules . vars_event ) {
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_VARS ; i + + ) {
2019-08-17 13:27:41 +01:00
if ( bitRead ( Rules . vars_event , i ) ) {
bitClear ( Rules . vars_event , i ) ;
2020-10-15 13:42:46 +01:00
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Var%d \" :{ \" State \" : \" %s \" }} " ) , i + 1 , rules_vars [ i ] ) ;
2019-03-29 08:42:50 +00:00
RulesProcessEvent ( json_event ) ;
break ;
}
2019-01-04 19:26:37 +00:00
}
}
2019-08-17 13:27:41 +01:00
if ( Rules . mems_event ) {
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_MEMS ; i + + ) {
2019-08-17 13:27:41 +01:00
if ( bitRead ( Rules . mems_event , i ) ) {
bitClear ( Rules . mems_event , i ) ;
2020-10-15 13:42:46 +01:00
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Mem%d \" :{ \" State \" : \" %s \" }} " ) , i + 1 , SettingsText ( SET_MEM1 + i ) ) ;
2019-03-29 08:42:50 +00:00
RulesProcessEvent ( json_event ) ;
break ;
}
2019-01-04 19:26:37 +00:00
}
}
}
2020-10-29 12:58:50 +00:00
else if ( TasmotaGlobal . rules_flag . data ) {
2022-01-12 16:32:21 +00:00
json_event [ 0 ] = ' \0 ' ;
if ( TasmotaGlobal . rules_flag . system_init ) {
TasmotaGlobal . rules_flag . system_init = 0 ;
strncpy_P ( json_event , PSTR ( " { \" System \" :{ \" Init \" :1}} " ) , sizeof ( json_event ) ) ;
}
else if ( TasmotaGlobal . rules_flag . system_boot ) {
TasmotaGlobal . rules_flag . system_boot = 0 ;
strncpy_P ( json_event , PSTR ( " { \" System \" :{ \" Boot \" :1}} " ) , sizeof ( json_event ) ) ;
}
else if ( TasmotaGlobal . rules_flag . time_init ) {
TasmotaGlobal . rules_flag . time_init = 0 ;
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Time \" :{ \" Initialized \" :%d}} " ) , MinutesPastMidnight ( ) ) ;
}
else if ( TasmotaGlobal . rules_flag . time_set ) {
TasmotaGlobal . rules_flag . time_set = 0 ;
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Time \" :{ \" Set \" :%d}} " ) , MinutesPastMidnight ( ) ) ;
}
else if ( TasmotaGlobal . rules_flag . mqtt_connected ) {
TasmotaGlobal . rules_flag . mqtt_connected = 0 ;
strncpy_P ( json_event , PSTR ( " { \" MQTT \" :{ \" Connected \" :1}} " ) , sizeof ( json_event ) ) ;
}
else if ( TasmotaGlobal . rules_flag . mqtt_disconnected ) {
TasmotaGlobal . rules_flag . mqtt_disconnected = 0 ;
strncpy_P ( json_event , PSTR ( " { \" MQTT \" :{ \" Disconnected \" :1}} " ) , sizeof ( json_event ) ) ;
}
else if ( TasmotaGlobal . rules_flag . wifi_connected ) {
TasmotaGlobal . rules_flag . wifi_connected = 0 ;
strncpy_P ( json_event , PSTR ( " { \" WIFI \" :{ \" Connected \" :1}} " ) , sizeof ( json_event ) ) ;
}
else if ( TasmotaGlobal . rules_flag . wifi_disconnected ) {
TasmotaGlobal . rules_flag . wifi_disconnected = 0 ;
strncpy_P ( json_event , PSTR ( " { \" WIFI \" :{ \" Disconnected \" :1}} " ) , sizeof ( json_event ) ) ;
}
2024-02-14 15:19:51 +00:00
//#if defined(ESP32) && CONFIG_IDF_TARGET_ESP32 && defined(USE_ETHERNET)
# if defined(ESP32) && defined(USE_ETHERNET)
2022-01-12 16:32:21 +00:00
else if ( TasmotaGlobal . rules_flag . eth_connected ) {
TasmotaGlobal . rules_flag . eth_connected = 0 ;
strncpy_P ( json_event , PSTR ( " { \" ETH \" :{ \" Connected \" :1}} " ) , sizeof ( json_event ) ) ;
}
else if ( TasmotaGlobal . rules_flag . eth_disconnected ) {
TasmotaGlobal . rules_flag . eth_disconnected = 0 ;
strncpy_P ( json_event , PSTR ( " { \" ETH \" :{ \" Disconnected \" :1}} " ) , sizeof ( json_event ) ) ;
}
2022-01-13 14:27:24 +00:00
# endif // USE_ETHERNET
2022-01-12 16:32:21 +00:00
else if ( TasmotaGlobal . rules_flag . http_init ) {
TasmotaGlobal . rules_flag . http_init = 0 ;
strncpy_P ( json_event , PSTR ( " { \" HTTP \" :{ \" Initialized \" :1}} " ) , sizeof ( json_event ) ) ;
}
2019-09-29 17:00:01 +01:00
# ifdef USE_SHUTTER
2022-01-12 16:32:21 +00:00
else if ( TasmotaGlobal . rules_flag . shutter_moved ) {
TasmotaGlobal . rules_flag . shutter_moved = 0 ;
strncpy_P ( json_event , PSTR ( " { \" SHUTTER \" :{ \" Moved \" :1}} " ) , sizeof ( json_event ) ) ;
}
else if ( TasmotaGlobal . rules_flag . shutter_moving ) {
TasmotaGlobal . rules_flag . shutter_moving = 0 ;
strncpy_P ( json_event , PSTR ( " { \" SHUTTER \" :{ \" Moving \" :1}} " ) , sizeof ( json_event ) ) ;
}
2019-09-29 17:00:01 +01:00
# endif // USE_SHUTTER
2022-01-12 16:32:21 +00:00
if ( json_event [ 0 ] ) {
RulesProcessEvent ( json_event ) ; // Only service one event within 50mS
2018-06-25 17:00:20 +01:00
}
}
2018-08-26 16:10:18 +01:00
}
}
2021-04-05 12:33:00 +01:00
void RulesEvery100ms ( void ) {
static uint8_t xsns_index = 0 ;
2022-02-14 11:51:29 +00:00
if ( ( Settings - > rule_enabled | | BERRY_RULES ) & & ! Rules . busy & & ( TasmotaGlobal . uptime > 4 ) ) { // Any rule enabled and allow 4 seconds start-up time for sensors (#3811)
2020-10-30 11:29:48 +00:00
ResponseClear ( ) ;
2020-10-29 12:37:09 +00:00
int tele_period_save = TasmotaGlobal . tele_period ;
TasmotaGlobal . tele_period = 2 ; // Do not allow HA updates during next function call
2021-04-05 12:33:00 +01:00
XsnsNextCall ( FUNC_JSON_APPEND , xsns_index ) ; // ,"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
2020-10-29 12:37:09 +00:00
TasmotaGlobal . tele_period = tele_period_save ;
2021-05-24 15:52:59 +01:00
if ( ResponseLength ( ) ) {
2021-05-23 13:42:27 +01:00
ResponseJsonStart ( ) ; // {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
Add support for Shelly 1PM Template
Add support for Shelly 1PM Template {"NAME":"Shelly 1PM","GPIO":[56,0,0,0,82,134,0,0,0,0,0,21,0],"FLAG":2,"BASE":18} (#5716)
2019-05-13 17:26:07 +01:00
ResponseJsonEnd ( ) ;
2021-08-15 16:08:31 +01:00
RulesProcessEvent ( ResponseData ( ) ) ;
2018-04-13 16:42:11 +01:00
}
}
}
2018-11-14 13:32:09 +00:00
void RulesEverySecond ( void )
2018-04-13 16:42:11 +01:00
{
2021-01-02 17:47:03 +00:00
char json_event [ 120 ] ;
2022-02-14 11:51:29 +00:00
if ( ( Settings - > rule_enabled | | BERRY_RULES ) & & ! Rules . busy ) { // Any rule enabled
2018-06-26 15:22:53 +01:00
if ( RtcTime . valid ) {
2020-10-28 16:32:07 +00:00
if ( ( TasmotaGlobal . uptime > 60 ) & & ( RtcTime . minute ! = Rules . last_minute ) ) { // Execute from one minute after restart every minute only once
2019-08-17 13:27:41 +01:00
Rules . last_minute = RtcTime . minute ;
2019-02-19 13:49:15 +00:00
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Time \" :{ \" Minute \" :%d}} " ) , MinutesPastMidnight ( ) ) ;
2018-06-26 15:22:53 +01:00
RulesProcessEvent ( json_event ) ;
}
}
2021-01-02 17:47:03 +00:00
}
for ( uint32_t i = 0 ; i < MAX_RULE_TIMERS ; i + + ) {
if ( Rules . timer [ i ] ! = 0L ) { // Timer active?
if ( TimeReached ( Rules . timer [ i ] ) ) { // Timer finished?
Rules . timer [ i ] = 0L ; // Turn off this timer
2022-02-14 11:51:29 +00:00
if ( ( Settings - > rule_enabled | | BERRY_RULES ) & & ! Rules . busy ) { // Any rule enabled
2018-06-25 17:00:20 +01:00
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Rules \" :{ \" Timer \" :%d}} " ) , i + 1 ) ;
RulesProcessEvent ( json_event ) ;
2018-04-13 16:42:11 +01:00
}
}
}
}
}
2019-05-19 11:42:10 +01:00
void RulesSaveBeforeRestart ( void )
{
2022-02-14 11:51:29 +00:00
if ( ( Settings - > rule_enabled | | BERRY_RULES ) & & ! Rules . busy ) { // Any rule enabled
2019-05-19 11:42:10 +01:00
char json_event [ 32 ] ;
strncpy_P ( json_event , PSTR ( " { \" System \" :{ \" Save \" :1}} " ) , sizeof ( json_event ) ) ;
RulesProcessEvent ( json_event ) ;
}
}
2018-11-14 13:32:09 +00:00
void RulesSetPower ( void )
2018-05-06 15:07:42 +01:00
{
2019-08-17 13:27:41 +01:00
Rules . new_power = XdrvMailbox . index ;
2018-05-06 15:07:42 +01:00
}
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
# ifdef SUPPORT_MQTT_EVENT
2024-01-22 15:57:59 +00:00
typedef struct {
char * event ;
char * topic ;
char * key ;
} MQTT_Subscription ;
LList < MQTT_Subscription > subscriptions ;
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
/********************************************************************************************/
/*
* Rules : Process received MQTT message .
* If the message is in our subscription list , trigger an event with the value parsed from MQTT data
* Input :
* void - We are going to access XdrvMailbox data directly .
* Return :
* true - The message is consumed .
* false - The message is not in our list .
*/
2023-01-07 16:31:10 +00:00
bool RulesMqttData ( void ) {
2024-01-22 15:57:59 +00:00
/*
XdrvMailbox . topic = topic ;
XdrvMailbox . index = strlen ( topic ) ;
XdrvMailbox . data = ( char * ) data ;
XdrvMailbox . data_len = data_len ;
*/
2024-01-23 17:23:04 +00:00
if ( ( XdrvMailbox . data_len < 1 ) | | ( subscriptions . isEmpty ( ) ) ) {
2024-01-22 15:57:59 +00:00
return false ; // Process unchanged data
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
}
2019-10-02 11:51:37 +01:00
bool serviced = false ;
2024-01-23 17:17:42 +00:00
String buData = XdrvMailbox . data ; // Destroyed by JsonParser. Could be very long SENSOR message
2024-01-23 11:32:12 +00:00
char ctopic [ strlen ( XdrvMailbox . topic ) + 1 ] ;
2024-01-23 17:17:42 +00:00
strcpy ( ctopic , XdrvMailbox . topic ) ; // Destroyed by result of following iteration
2023-01-01 08:54:17 +00:00
2024-01-23 17:17:42 +00:00
for ( auto & event_item : subscriptions ) { // Looking for all matched topics
2024-01-23 11:32:12 +00:00
char etopic [ strlen ( event_item . topic ) + 2 ] ;
2024-01-23 17:17:42 +00:00
strcpy ( etopic , event_item . topic ) ; // tele/tasmota/SENSOR
strcat ( etopic , " / " ) ; // tele/tasmota/SENSOR/
if ( ( strcmp ( ctopic , event_item . topic ) = = 0 ) | | // Equal tele/tasmota/SENSOR
( strncmp ( ctopic , etopic , strlen ( etopic ) ) = = 0 ) ) { // StartsWith tele/tasmota/SENSOR/
serviced = true ; // This topic is subscribed by us, so serve it
String sData = buData ; // sData will be destroyed by JsonParser
char * value = nullptr ;
if ( strlen ( event_item . key ) = = 0 ) { // If no key specified
value = ( char * ) buData . c_str ( ) ; // {"DS18B20":{"Id":"0000048EC44C","Temperature":23.3}}
} else { // If key specified, need to parse Key/Value from JSON data
2020-09-23 18:38:24 +01:00
JsonParser parser ( ( char * ) sData . c_str ( ) ) ;
JsonParserObject jsonData = parser . getRootObject ( ) ;
2024-01-23 17:17:42 +00:00
if ( ! jsonData ) { break ; } // Failed to parse JSON data, ignore this message.
char ckey1 [ strlen ( event_item . key ) + 1 ] ;
strcpy ( ckey1 , event_item . key ) ; // DS18B20.Temperature
char * ckey2 = strchr ( ckey1 , ' . ' ) ;
if ( ckey2 ! = nullptr ) { // .Temperature
* ckey2 + + = ' \0 ' ; // Temperature and ckey1 becomes DS18B20
JsonParserToken val = jsonData [ ckey1 ] . getObject ( ) [ ckey2 ] ;
2024-02-14 08:20:56 +00:00
if ( val ) {
2024-01-23 17:17:42 +00:00
value = ( char * ) val . getStr ( ) ; // 23.3
}
} else { // DS18B20
JsonParserToken val = jsonData [ ckey1 ] ;
2024-02-14 08:20:56 +00:00
if ( val ) {
2024-01-23 17:17:42 +00:00
value = ( char * ) val . getStr ( ) ; // \0
}
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
}
}
2024-01-23 17:17:42 +00:00
if ( value ) {
Trim ( value ) ;
bool quotes = ( value [ 0 ] ! = ' { ' ) ;
Response_P ( PSTR ( " { \" Event \" :{ \" %s \" :%s%s%s}} " ) , event_item . event , ( quotes ) ? " \" " : " " , value , ( quotes ) ? " \" " : " " ) ;
RulesProcessEvent ( ResponseData ( ) ) ;
}
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
}
}
return serviced ;
}
2024-01-22 15:57:59 +00:00
bool RuleUnsubscribe ( const char * event ) {
UpperCase ( ( char * ) event , event ) ;
bool do_all = ( strcmp ( event , " * " ) = = 0 ) ; // Wildcard
//Search all subscriptions
for ( auto & index : subscriptions ) {
if ( do_all | | // All
( strcmp ( event , index . event ) = = 0 ) ) { // Equal
//If find exists one, remove it.
char stopic [ strlen ( index . topic ) + 3 ] ;
strcpy ( stopic , index . topic ) ;
strcat ( stopic , " /# " ) ;
MqttUnsubscribe ( stopic ) ;
free ( index . key ) ;
free ( index . topic ) ;
free ( index . event ) ;
subscriptions . remove ( & index ) ;
if ( ! do_all ) {
return true ;
}
}
}
return do_all ;
}
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
/********************************************************************************************/
/*
* Subscribe a MQTT topic ( with or without key ) and assign an event name to it
* Command Subscribe format :
* Subscribe < event_name > , < topic > [ , < key > ]
* This command will subscribe a < topic > and give it an event name < event_name > .
* The optional parameter < key > is for parse the specified key / value from MQTT message
* payload with JSON format .
* Subscribe
* Subscribe command without any parameter will list all topics currently subscribed .
* Input :
2019-08-02 15:23:17 +01:00
* XdrvMailbox . data - A char buffer with all the parameters
* XdrvMailbox . data_len - Length of the parameters
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
* Return :
* A string include subscribed event , topic and key .
*/
2024-01-22 15:57:59 +00:00
void CmndSubscribe ( void ) {
2019-08-02 15:23:17 +01:00
if ( XdrvMailbox . data_len > 0 ) {
2024-01-22 15:57:59 +00:00
char * event = Trim ( strtok ( XdrvMailbox . data , " , " ) ) ;
char * topic = Trim ( strtok ( nullptr , " , " ) ) ;
char * key = Trim ( strtok ( nullptr , " , " ) ) ;
if ( event & & topic ) {
RuleUnsubscribe ( event ) ;
// Add "/#" to the topic
uint32_t slen = strlen ( topic ) ;
char stopic [ slen + 3 ] ;
strcpy ( stopic , topic ) ;
if ( stopic [ slen - 1 ] ! = ' # ' ) {
if ( stopic [ slen - 1 ] = = ' / ' ) {
strcat ( stopic , " # " ) ;
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
} else {
2024-01-22 15:57:59 +00:00
strcat ( stopic , " /# " ) ;
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
}
}
2021-08-25 15:40:03 +01:00
2024-01-22 15:57:59 +00:00
if ( ! key ) { key = EmptyStr ; }
// MQTT Subscribe
char * hevent = ( char * ) malloc ( strlen ( event ) + 1 ) ;
char * htopic = ( char * ) malloc ( strlen ( stopic ) - 1 ) ; // Remove "/#"
char * hkey = ( char * ) malloc ( strlen ( key ) + 1 ) ;
if ( hevent & & htopic & & hkey ) {
strcpy ( hevent , event ) ;
strlcpy ( htopic , stopic , strlen ( stopic ) - 1 ) ; // Remove "/#" so easy to match
strcpy ( hkey , key ) ;
MQTT_Subscription & subscription_item = subscriptions . addToLast ( ) ;
subscription_item . event = hevent ;
subscription_item . topic = htopic ;
subscription_item . key = hkey ;
char * ftopic = ( 2 = = XdrvMailbox . index ) ? htopic : stopic ; // Subscribe2
MqttSubscribe ( ftopic ) ;
ResponseCmnd ( ) ; // {"Subscribe":
ResponseAppend_P ( PSTR ( " \" %s,%s%s%s \" } " ) , hevent , ftopic , ( strlen ( hkey ) ) ? " , " : " " , EscapeJSONString ( hkey ) . c_str ( ) ) ;
2024-02-14 08:20:56 +00:00
}
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
}
2024-01-22 15:57:59 +00:00
return ; // {"Error"}
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
}
2024-01-22 15:57:59 +00:00
// If did not specify the event name, list all subscribed event
bool found = false ;
ResponseCmnd ( ) ; // {"Subscribe":
for ( auto & items : subscriptions ) {
ResponseAppend_P ( PSTR ( " %s%s,%s%s%s " ) ,
( found ) ? " ; " : " \" " , items . event , items . topic , ( strlen ( items . key ) ) ? " , " : " " , EscapeJSONString ( items . key ) . c_str ( ) ) ;
2024-02-14 08:20:56 +00:00
found = true ;
2024-01-22 15:57:59 +00:00
}
ResponseAppend_P ( ( found ) ? PSTR ( " \" } " ) : PSTR ( " \" " D_JSON_EMPTY " \" } " ) ) ;
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
}
/********************************************************************************************/
/*
* Unsubscribe specified MQTT event . If no event specified , Unsubscribe all .
* Command Unsubscribe format :
2019-08-02 15:23:17 +01:00
* UnSubscribe [ < event_name > ]
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
* Input :
2019-08-02 15:23:17 +01:00
* XdrvMailbox . data - Event name
* XdrvMailbox . data_len - Length of the parameters
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
* Return :
* list all the events unsubscribed .
*/
2024-01-22 15:57:59 +00:00
void CmndUnsubscribe ( void ) {
2019-08-02 15:23:17 +01:00
if ( XdrvMailbox . data_len > 0 ) {
2024-01-22 15:57:59 +00:00
char * event = Trim ( XdrvMailbox . data ) ;
if ( RuleUnsubscribe ( event ) ) {
ResponseCmndChar ( event ) ;
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
}
2024-01-22 15:57:59 +00:00
return ; // {"Error"}
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
}
2024-01-22 15:57:59 +00:00
RuleUnsubscribe ( " * " ) ;
ResponseCmndDone ( ) ;
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
}
2019-08-02 15:23:17 +01:00
# endif // SUPPORT_MQTT_EVENT
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
# ifdef USE_EXPRESSION
2019-09-09 16:24:27 +01:00
/********************************************************************************************/
/*
* Looking for matched bracket - " ) "
* Search buffer from current loction , skip all nested bracket pairs , find the matched close bracket .
* Input :
* pStart - Point to a char buffer start with " ( "
* Output :
* N / A
* Return :
* position of matched close bracket
*/
char * findClosureBracket ( char * pStart )
{
char * pointer = pStart + 1 ;
//Look for the matched closure parenthesis.")"
bool bFindClosures = false ;
uint8_t matchClosures = 1 ;
while ( * pointer )
{
if ( * pointer = = ' ) ' ) {
matchClosures - - ;
if ( matchClosures = = 0 ) {
bFindClosures = true ;
break ;
}
} else if ( * pointer = = ' ( ' ) {
matchClosures + + ;
}
pointer + + ;
}
if ( bFindClosures ) {
return pointer ;
} else {
2019-09-10 10:05:53 +01:00
return nullptr ;
2019-09-09 16:24:27 +01:00
}
}
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
/********************************************************************************************/
/*
* Parse a number value
* Input :
* pNumber - A char pointer point to a digit started string ( guaranteed )
2019-07-01 17:20:43 +01:00
* value - Reference a float variable used to accept the result
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
* Output :
* pNumber - Pointer forward to next character after the number
2019-07-01 17:20:43 +01:00
* value - float type , the result value
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
* Return :
* true - succeed
* false - failed
*/
2019-07-01 17:20:43 +01:00
bool findNextNumber ( char * & pNumber , float & value )
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
{
bool bSucceed = false ;
String sNumber = " " ;
2019-09-28 02:58:32 +01:00
if ( * pNumber = = ' - ' ) {
sNumber = " - " ;
pNumber + + ;
}
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
while ( * pNumber ) {
if ( isdigit ( * pNumber ) | | ( * pNumber = = ' . ' ) ) {
sNumber + = * pNumber ;
pNumber + + ;
} else {
break ;
}
}
if ( sNumber . length ( ) > 0 ) {
2019-07-01 17:20:43 +01:00
value = CharToFloat ( sNumber . c_str ( ) ) ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
bSucceed = true ;
}
return bSucceed ;
}
/********************************************************************************************/
/*
2019-07-01 17:20:43 +01:00
* Parse a variable ( like VAR1 , MEM3 ) and get its value ( float type )
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
* Input :
* pVarname - A char pointer point to a variable name string
2019-07-01 17:20:43 +01:00
* value - Reference a float variable used to accept the result
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
* Output :
* pVarname - Pointer forward to next character after the variable
2019-07-01 17:20:43 +01:00
* value - float type , the result value
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
* Return :
* true - succeed
* false - failed
*/
2019-07-01 17:20:43 +01:00
bool findNextVariableValue ( char * & pVarname , float & value )
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
{
2019-02-19 13:49:15 +00:00
bool succeed = true ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
value = 0 ;
String sVarName = " " ;
while ( * pVarname ) {
if ( isalpha ( * pVarname ) | | isdigit ( * pVarname ) ) {
sVarName . concat ( * pVarname ) ;
pVarname + + ;
} else {
break ;
}
}
sVarName . toUpperCase ( ) ;
2019-02-19 13:49:15 +00:00
if ( sVarName . startsWith ( F ( " VAR " ) ) ) {
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
int index = sVarName . substring ( 3 ) . toInt ( ) ;
if ( index > 0 & & index < = MAX_RULE_VARS ) {
2019-08-17 13:27:41 +01:00
value = CharToFloat ( rules_vars [ index - 1 ] ) ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
}
2019-02-19 13:49:15 +00:00
} else if ( sVarName . startsWith ( F ( " MEM " ) ) ) {
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
int index = sVarName . substring ( 3 ) . toInt ( ) ;
if ( index > 0 & & index < = MAX_RULE_MEMS ) {
2019-12-16 14:13:57 +00:00
value = CharToFloat ( SettingsText ( SET_MEM1 + index - 1 ) ) ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
}
2019-02-19 13:49:15 +00:00
} else if ( sVarName . equals ( F ( " TIME " ) ) ) {
value = MinutesPastMidnight ( ) ;
} else if ( sVarName . equals ( F ( " UPTIME " ) ) ) {
value = MinutesUptime ( ) ;
} else if ( sVarName . equals ( F ( " UTCTIME " ) ) ) {
value = UtcTime ( ) ;
} else if ( sVarName . equals ( F ( " LOCALTIME " ) ) ) {
value = LocalTime ( ) ;
2022-01-27 10:00:49 +00:00
# if defined(USE_TIMERS)
} else if ( sVarName . startsWith ( F ( " TIMER " ) ) ) {
2022-02-04 15:18:56 +00:00
uint32_t index = sVarName . substring ( 5 ) . toInt ( ) ;
2022-01-27 10:00:49 +00:00
if ( index > 0 & & index < = MAX_TIMERS ) {
2022-10-25 21:34:08 +01:00
value = TimerGetTimeOfDay ( index - 1 ) ;
2022-01-27 10:00:49 +00:00
}
2022-02-04 15:18:56 +00:00
# if defined(USE_SUNRISE)
} else if ( sVarName . equals ( F ( " SUNRISE " ) ) ) {
value = SunMinutes ( 0 ) ;
} else if ( sVarName . equals ( F ( " SUNSET " ) ) ) {
value = SunMinutes ( 1 ) ;
# endif // USE_SUNRISE
2022-01-27 10:00:49 +00:00
# endif // USE_TIMERS
2020-05-29 21:52:45 +01:00
// #ifdef USE_ZIGBEE
// // } else if (sVarName.equals(F("ZBDEVICE"))) {
// // value = Z_GetLastDevice();
// } else if (sVarName.equals(F("ZBGROUP"))) {
// value = Z_GetLastGroup();
// } else if (sVarName.equals(F("ZBCLUSTER"))) {
// value = Z_GetLastCluster();
// } else if (sVarName.equals(F("ZBENDPOINT"))) {
// value = Z_GetLastEndpoint();
// #endif
2019-02-19 13:49:15 +00:00
} else {
succeed = false ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
}
return succeed ;
}
/********************************************************************************************/
/*
* Find next object in expression and evaluate it
* An object could be :
2019-09-28 02:58:32 +01:00
* - A float number start with a digit or minus , like 0.787 , - 3
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
* - A variable name , like VAR1 , MEM3
* - An expression enclosed with a pair of round brackets , ( . . . . . )
* Input :
* pointer - A char pointer point to a place of the expression string
2019-07-01 17:20:43 +01:00
* value - Reference a float variable used to accept the result
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
* Output :
* pointer - Pointer forward to next character after next object
2019-07-01 17:20:43 +01:00
* value - float type , the result value
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
* Return :
* true - succeed
* false - failed
*/
2019-07-01 17:20:43 +01:00
bool findNextObjectValue ( char * & pointer , float & value )
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
{
bool bSucceed = false ;
2019-02-16 15:17:17 +00:00
while ( * pointer )
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
{
if ( isspace ( * pointer ) ) { //Skip leading spaces
pointer + + ;
continue ;
}
2019-09-28 02:58:32 +01:00
if ( isdigit ( * pointer ) | | ( * pointer ) = = ' - ' ) { //This object is a number
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
bSucceed = findNextNumber ( pointer , value ) ;
break ;
} else if ( isalpha ( * pointer ) ) { //Should be a variable like VAR12, MEM1
bSucceed = findNextVariableValue ( pointer , value ) ;
break ;
} else if ( * pointer = = ' ( ' ) { //It is a sub expression bracketed with ()
2019-09-09 16:24:27 +01:00
char * closureBracket = findClosureBracket ( pointer ) ; //Get the position of closure bracket ")"
2019-09-10 10:05:53 +01:00
if ( closureBracket ! = nullptr ) {
2019-09-28 03:15:42 +01:00
value = evaluateExpression ( pointer + 1 , closureBracket - pointer - 1 ) ;
2019-09-09 16:24:27 +01:00
pointer = closureBracket + 1 ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
bSucceed = true ;
}
break ;
} else { //No number, no variable, no expression, then invalid object.
break ;
}
}
return bSucceed ;
}
/********************************************************************************************/
/*
* Find next operator in expression
* An operator could be : + , - , * , / , % , ^
* Input :
* pointer - A char pointer point to a place of the expression string
* op - Reference to a variable used to accept the result
* Output :
* pointer - Pointer forward to next character after next operator
* op - The operator . 0 , 1 , 2 , 3 , 4 , 5
* Return :
* true - succeed
* false - failed
*/
bool findNextOperator ( char * & pointer , int8_t & op )
{
bool bSucceed = false ;
2019-02-16 15:17:17 +00:00
while ( * pointer )
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
{
if ( isspace ( * pointer ) ) { //Skip leading spaces
pointer + + ;
continue ;
}
2019-09-28 02:52:33 +01:00
op = EXPRESSION_OPERATOR_ADD ;
const char * pch = kExpressionOperators ;
char ch ;
while ( ( ch = pgm_read_byte ( pch + + ) ) ! = ' \0 ' ) {
if ( ch = = * pointer ) {
bSucceed = true ;
pointer + + ;
break ;
}
op + + ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
}
break ;
}
return bSucceed ;
}
/********************************************************************************************/
/*
* Calculate a simple expression composed by 2 value and 1 operator , like 2 * 3
* Input :
* pointer - A char pointer point to a place of the expression string
2019-07-01 17:20:43 +01:00
* value - Reference a float variable used to accept the result
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
* Output :
* pointer - Pointer forward to next character after next object
2019-07-01 17:20:43 +01:00
* value - float type , the result value
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
* Return :
* true - succeed
* false - failed
*/
2024-01-24 13:33:55 +00:00
float calculateTwoValues ( float v1 , float v2 , uint8_t op ) {
switch ( op ) {
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
case EXPRESSION_OPERATOR_ADD :
return v1 + v2 ;
case EXPRESSION_OPERATOR_SUBTRACT :
return v1 - v2 ;
case EXPRESSION_OPERATOR_MULTIPLY :
return v1 * v2 ;
case EXPRESSION_OPERATOR_DIVIDEDBY :
return ( 0 = = v2 ) ? 0 : ( v1 / v2 ) ;
case EXPRESSION_OPERATOR_MODULO :
return ( 0 = = v2 ) ? 0 : ( int ( v1 ) % int ( v2 ) ) ;
case EXPRESSION_OPERATOR_POWER :
2024-01-24 13:33:55 +00:00
return FastPrecisePowf ( v1 , v2 ) ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
}
return 0 ;
}
/********************************************************************************************/
/*
* Parse and evaluate an expression .
* For example : " 10 * ( MEM2 + 1) / 2 "
* Right now , only support operators listed here : ( order by priority )
* Priority 4 : ^ ( power )
* Priority 3 : % ( modulo , always get integer result )
* Priority 2 : * , /
* Priority 1 : + , -
* Input :
* expression - The expression to be evaluated
* len - Length of the expression
* Return :
2024-01-24 13:33:55 +00:00
* float - result
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
* 0 - if the expression is invalid
* An example :
* MEM1 = 3 , MEM2 = 6 , VAR2 = 15 , VAR10 = 80
* At beginning , the expression might be complicated like : 3.14 * ( MEM1 * ( 10 + VAR2 ^ 2 ) - 100 ) % 10 + VAR10 / ( 2 + MEM2 )
* We are going to scan the whole expression , evaluate each object .
* Finally we will have a value list : .
* Order Object Value
* 0 3.14 3.14
* 1 ( MEM1 * ( 10 + VAR2 ^ 2 ) - 100 ) 605
* 2 10 10
* 3 VAR10 80
* 4 ( 2 + MEM2 ) 8
* And an operator list :
* Order Operator Priority
* 0 * 2
* 1 % 3
* 2 + 1
* 3 / 2
2024-01-24 13:33:55 +00:00
* Results in :
* ( 10 + VAR2 ^ 2 ) = 235
* ( MEM1 * 235 - 100 ) = 605
* ( 2 + MEM2 ) = 8
* 605 % 10 = 5
* 3.14 * 5 = 15.7
* VAR10 / 8 = 80 / 8 = 10
* 15.7 + 10 = 25.7 < = = end result
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
*/
2024-01-23 22:52:18 +00:00
float evaluateExpression ( const char * expression , unsigned int len ) {
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
char expbuf [ len + 1 ] ;
memcpy ( expbuf , expression , len ) ;
expbuf [ len ] = ' \0 ' ;
char * scan_pointer = expbuf ;
2024-01-23 22:52:18 +00:00
float object_values [ 21 ] ;
int8_t operators [ 20 ] ;
2019-07-01 17:20:43 +01:00
float va ;
2024-01-24 13:33:55 +00:00
// Find and add the value of first object
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
if ( findNextObjectValue ( scan_pointer , va ) ) {
2024-01-23 22:52:18 +00:00
object_values [ 0 ] = va ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
} else {
return 0 ;
}
2024-01-23 22:52:18 +00:00
uint32_t operators_size = 0 ;
int8_t op ;
while ( * scan_pointer ) {
2019-02-16 15:17:17 +00:00
if ( findNextOperator ( scan_pointer , op )
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
& & * scan_pointer
2019-02-16 15:17:17 +00:00
& & findNextObjectValue ( scan_pointer , va ) )
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
{
2024-01-23 22:52:18 +00:00
operators [ operators_size + + ] = op ;
2024-01-24 13:33:55 +00:00
object_values [ operators_size ] = va ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
} else {
2024-01-24 13:33:55 +00:00
// No operator followed or no more object after this operator, we done.
2019-02-16 15:17:17 +00:00
break ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
}
2024-01-23 22:52:18 +00:00
if ( operators_size > = 20 ) {
AddLog ( LOG_LEVEL_ERROR , PSTR ( " RUL: Too many arguments " ) ) ;
2024-01-24 13:33:55 +00:00
return 0 ;
2024-01-23 22:52:18 +00:00
}
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
}
2024-01-24 13:33:55 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Expression '%s'"), expbuf);
2024-01-23 22:52:18 +00:00
// Going to evaluate the whole expression
// Calculate by order of operator priorities. Looking for all operators with specified priority (from High to Low)
for ( int32_t priority = MAX_EXPRESSION_OPERATOR_PRIORITY ; priority > 0 ; priority - - ) {
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
int index = 0 ;
2024-01-23 22:52:18 +00:00
while ( index < operators_size ) {
2024-01-24 13:33:55 +00:00
if ( priority = = pgm_read_byte ( kExpressionOperatorsPriorities + operators [ index ] ) ) { // Need to calculate the operator first
2024-01-23 22:52:18 +00:00
// Get current object value and remove the next object with current operator
va = calculateTwoValues ( object_values [ index ] , object_values [ index + 1 ] , operators [ index ] ) ;
uint32_t i = index ;
while ( i < = operators_size ) {
operators [ i + + ] = operators [ i ] ; // operators.remove(index)
object_values [ i ] = object_values [ i + 1 ] ; // object_values.remove(index + 1)
}
operators_size - - ;
2024-01-24 13:33:55 +00:00
object_values [ index ] = va ; // Replace the current value with the result
2024-01-23 22:52:18 +00:00
2024-01-24 13:33:55 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Intermediate '%4_f'"), &object_values[index]);
2024-01-23 22:52:18 +00:00
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
} else {
index + + ;
}
}
}
2024-01-24 13:33:55 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Result '%4_f'"), &object_values[0]);
2024-01-23 22:52:18 +00:00
return object_values [ 0 ] ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
}
2019-08-02 15:23:17 +01:00
# endif // USE_EXPRESSION
2019-09-09 16:24:27 +01:00
# ifdef SUPPORT_IF_STATEMENT
2024-01-24 13:33:55 +00:00
/********************************************************************************************/
/*
* Process an if command
* Example :
* rule1 on event # test do backlog status 1 ; status 2 ; if ( var1 = = 10 AND var3 = = 9 OR var4 = = 8 ) status 3 ; status 4 endif ; status 5 ; status 6 endon
2024-02-14 08:20:56 +00:00
*
2024-01-24 13:33:55 +00:00
* Notice :
* In case of " if " is true commands ` ` status 3 ` ` and ` ` status 4 ` ` will be inserted into the backlog between ` ` status 2 ` ` and ` ` status 5 ` `
*/
void CmndIf ( void ) {
2019-09-09 16:24:27 +01:00
if ( XdrvMailbox . data_len > 0 ) {
2024-01-24 13:33:55 +00:00
char parameters [ XdrvMailbox . data_len + 1 ] ;
strcpy ( parameters , XdrvMailbox . data ) ;
2019-09-09 16:24:27 +01:00
ProcessIfStatement ( parameters ) ;
}
2019-09-15 02:39:21 +01:00
ResponseCmndDone ( ) ;
2019-09-09 16:24:27 +01:00
}
/********************************************************************************************/
/*
* Evaluate a comparison expression .
* Get the logic value of expression , true or false
* Input :
* expression - A comparison expression like VAR1 > = MEM1 + 10
* len - Length of expression
* Output :
* N / A
* Return :
* logic value of comparison expression
*/
bool evaluateComparisonExpression ( const char * expression , int len )
{
bool bResult = true ;
char expbuf [ len + 1 ] ;
memcpy ( expbuf , expression , len ) ;
expbuf [ len ] = ' \0 ' ;
String compare_expression = expbuf ;
String leftExpr , rightExpr ;
int8_t compareOp = parseCompareExpression ( compare_expression , leftExpr , rightExpr ) ;
double leftValue = evaluateExpression ( leftExpr . c_str ( ) , leftExpr . length ( ) ) ;
double rightValue = evaluateExpression ( rightExpr . c_str ( ) , rightExpr . length ( ) ) ;
switch ( compareOp ) {
case COMPARE_OPERATOR_EXACT_DIVISION :
bResult = ( rightValue ! = 0 & & leftValue = = int ( leftValue )
& & rightValue = = int ( rightValue ) & & ( int ( leftValue ) % int ( rightValue ) ) = = 0 ) ;
break ;
case COMPARE_OPERATOR_EQUAL :
bResult = leftExpr . equalsIgnoreCase ( rightExpr ) ; // Compare strings - this also works for hexadecimals
break ;
case COMPARE_OPERATOR_BIGGER :
bResult = ( leftValue > rightValue ) ;
break ;
case COMPARE_OPERATOR_SMALLER :
bResult = ( leftValue < rightValue ) ;
break ;
case COMPARE_OPERATOR_NUMBER_EQUAL :
bResult = ( leftValue = = rightValue ) ;
break ;
case COMPARE_OPERATOR_NOT_EQUAL :
bResult = ( leftValue ! = rightValue ) ;
break ;
case COMPARE_OPERATOR_BIGGER_EQUAL :
bResult = ( leftValue > = rightValue ) ;
break ;
case COMPARE_OPERATOR_SMALLER_EQUAL :
bResult = ( leftValue < = rightValue ) ;
break ;
2021-01-21 13:54:38 +00:00
case COMPARE_OPERATOR_STRING_ENDS_WITH :
bResult = leftExpr . endsWith ( rightExpr ) ;
break ;
case COMPARE_OPERATOR_STRING_STARTS_WITH :
bResult = leftExpr . startsWith ( rightExpr ) ;
break ;
case COMPARE_OPERATOR_STRING_CONTAINS :
2021-01-31 12:52:56 +00:00
bResult = ( leftExpr . indexOf ( rightExpr ) > = 0 ) ;
break ;
case COMPARE_OPERATOR_STRING_NOT_EQUAL :
bResult = ! leftExpr . equalsIgnoreCase ( rightExpr ) ; // Compare strings - this also works for hexadecimals
break ;
case COMPARE_OPERATOR_STRING_NOT_CONTAINS :
bResult = ( leftExpr . indexOf ( rightExpr ) < 0 ) ;
2021-01-21 13:54:38 +00:00
break ;
2019-09-09 16:24:27 +01:00
}
return bResult ;
}
/********************************************************************************************/
/*
* Looking for a logical operator , either " AND " or " OR "
* A logical operator is expected at this moment . If we find something else , this function will fail .
* Input :
* pointer - Point to a char buffer
* op - Used to accpet the logical operator type
* Output :
* Pointer - pointer will forward to next character after the logical operator .
* op - The logical operator type we found
* Return :
* true - succeed
* false - failed
*/
bool findNextLogicOperator ( char * & pointer , int8_t & op )
{
bool bSucceed = false ;
while ( * pointer & & isspace ( * pointer ) ) {
//Skip spaces
pointer + + ;
}
if ( * pointer ) {
if ( strncasecmp_P ( pointer , PSTR ( " AND " ) , 4 ) = = 0 ) {
op = LOGIC_OPERATOR_AND ;
pointer + = 4 ;
bSucceed = true ;
} else if ( strncasecmp_P ( pointer , PSTR ( " OR " ) , 3 ) = = 0 ) {
op = LOGIC_OPERATOR_OR ;
pointer + = 3 ;
bSucceed = true ;
}
}
return bSucceed ;
}
/********************************************************************************************/
/*
* Find next logical object and get its value
* A logical object could be :
* - A comparison expression .
* - A logical expression bracketed with a pair of parenthesis .
* Input :
* pointer - A char pointer point to a start of logical object
* value - Used to accept the result value
* Output :
* pointer - Pointer forward to next character after the object
* value - boolean type , the value of the logical object .
* Return :
* true - succeed
* false - failed
*/
bool findNextLogicObjectValue ( char * & pointer , bool & value )
{
bool bSucceed = false ;
while ( * pointer & & isspace ( * pointer ) ) {
//Skip leading spaces
pointer + + ;
}
char * pExpr = pointer ;
while ( * pointer ) {
if ( isalpha ( * pointer )
& & ( strncasecmp_P ( pointer , PSTR ( " AND " ) , 4 ) = = 0
| | strncasecmp_P ( pointer , PSTR ( " OR " ) , 3 ) = = 0 ) )
{ //We have a logic operator, should stop
value = evaluateComparisonExpression ( pExpr , pointer - pExpr ) ;
bSucceed = true ;
break ;
} else if ( * pointer = = ' ( ' ) { //It is a sub expression bracketed with ()
char * closureBracket = findClosureBracket ( pointer ) ; //Get the position of closure bracket ")"
2019-09-10 10:05:53 +01:00
if ( closureBracket ! = nullptr ) {
2019-09-28 03:15:42 +01:00
value = evaluateLogicalExpression ( pointer + 1 , closureBracket - pointer - 1 ) ;
2019-09-09 16:24:27 +01:00
pointer = closureBracket + 1 ;
bSucceed = true ;
}
break ;
}
pointer + + ;
}
if ( ! bSucceed & & pointer > pExpr ) {
//The whole buffer is an comparison expression
value = evaluateComparisonExpression ( pExpr , pointer - pExpr ) ;
bSucceed = true ;
}
return bSucceed ;
}
/********************************************************************************************/
/*
* Evaluate a logical expression
* Logic expression is constructed with multiple comparison expressions and logical
* operators between them . For example : Mem1 = = 0 AND ( time > sunrise + 60 ) .
* Parenthesis are allowed to change the priority of logical operators .
* Input :
* expression - A logical expression
* len - Length of the expression
* Output :
* N / A
* Return :
* boolean - the value of logical expression
*/
2024-01-23 22:52:18 +00:00
bool evaluateLogicalExpression ( const char * expression , int len ) {
2019-09-09 16:24:27 +01:00
//Make a copy first
char expbuff [ len + 1 ] ;
memcpy ( expbuff , expression , len ) ;
expbuff [ len ] = ' \0 ' ;
char * pointer = expbuff ;
2024-01-23 22:52:18 +00:00
bool values [ 21 ] ;
int8_t logicOperators [ 20 ] ;
2019-09-09 16:24:27 +01:00
//Find first comparison expression
bool bValue ;
if ( findNextLogicObjectValue ( pointer , bValue ) ) {
2024-01-23 22:52:18 +00:00
values [ 0 ] = bValue ;
2019-09-09 16:24:27 +01:00
} else {
return false ;
}
2024-01-23 22:52:18 +00:00
uint32_t logicOperators_size = 0 ;
2019-09-09 16:24:27 +01:00
int8_t op ;
while ( * pointer ) {
if ( findNextLogicOperator ( pointer , op )
& & ( * pointer ) & & findNextLogicObjectValue ( pointer , bValue ) )
{
2024-01-23 22:52:18 +00:00
logicOperators [ logicOperators_size + + ] = op ;
2024-01-24 13:33:55 +00:00
values [ logicOperators_size ] = bValue ;
2019-09-09 16:24:27 +01:00
} else {
break ;
}
2024-01-23 22:52:18 +00:00
if ( logicOperators_size > = 20 ) {
AddLog ( LOG_LEVEL_ERROR , PSTR ( " RUL: Too many arguments " ) ) ;
2024-01-24 13:33:55 +00:00
return false ;
2024-01-23 22:52:18 +00:00
}
2019-09-09 16:24:27 +01:00
}
2024-01-23 22:52:18 +00:00
// Calculate all "AND" first
2019-09-09 16:24:27 +01:00
int index = 0 ;
2024-01-23 22:52:18 +00:00
while ( index < logicOperators_size ) {
if ( logicOperators [ index ] = = LOGIC_OPERATOR_AND ) {
values [ index ] & = values [ index + 1 ] ;
uint32_t i = index ;
while ( i < = logicOperators_size ) {
logicOperators [ i + + ] = logicOperators [ i ] ; // logicOperators.remove(index);
values [ i ] = values [ i + 1 ] ; // values.remove(index + 1);
}
logicOperators_size - - ;
2019-09-09 16:24:27 +01:00
} else {
index + + ;
}
}
2024-01-23 22:52:18 +00:00
// Then, calculate all "OR"
2019-09-09 16:24:27 +01:00
index = 0 ;
2024-01-23 22:52:18 +00:00
while ( index < logicOperators_size ) {
if ( logicOperators [ index ] = = LOGIC_OPERATOR_OR ) {
2024-01-24 13:33:55 +00:00
values [ index ] | = values [ index + 1 ] ;
2024-01-23 22:52:18 +00:00
uint32_t i = index ;
while ( i < = logicOperators_size ) {
logicOperators [ i + + ] = logicOperators [ i ] ; // logicOperators.remove(index);
values [ i ] = values [ i + 1 ] ; // values.remove(index + 1);
}
logicOperators_size - - ;
2019-09-09 16:24:27 +01:00
} else {
index + + ;
}
}
2024-01-24 13:33:55 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Expression '%s' = %d"), expbuff, values[0]);
2024-01-23 22:52:18 +00:00
return values [ 0 ] ;
2019-09-09 16:24:27 +01:00
}
/********************************************************************************************/
/*
* This function search in a buffer to find out an IF block start from current position
* Note : All the tokens found during the searching will be changed to NULL terminated string .
* Please make a copy before call this function if you still need it .
* Input :
* pointer - Point to a NULL end string buffer with the commands
* lenWord - Accept the length of block end word
* block_type - The block type you are looking for .
* Output :
* pointer - pointer point to the end of if block .
* lenWord - The length of block end word ( " ENDIF " , " ELSEIF " , " ELSE " )
* Return :
* The block type we find .
* IF_BLOCK_INVALID - Failed .
*/
int8_t findIfBlock ( char * & pointer , int & lenWord , int8_t block_type )
{
int8_t foundBlock = IF_BLOCK_INVALID ;
//First break into words delimited by space or ";"
const char * word ;
while ( * pointer ) {
if ( ! isalpha ( * pointer ) ) {
pointer + + ;
continue ;
}
word = pointer ;
while ( * pointer & & isalpha ( * pointer ) ) {
pointer + + ;
}
lenWord = pointer - word ;
if ( 2 = = lenWord & & 0 = = strncasecmp_P ( word , PSTR ( " IF " ) , 2 ) ) {
//if we find a new "IF" that means this is nested if block
//Try to finish this nested if block
if ( findIfBlock ( pointer , lenWord , IF_BLOCK_ENDIF ) ! = IF_BLOCK_ENDIF ) {
//If failed, we done.
break ;
}
} else if ( ( IF_BLOCK_ENDIF = = block_type | | IF_BLOCK_ANY = = block_type )
& & ( 5 = = lenWord ) & & ( 0 = = strncasecmp_P ( word , PSTR ( " ENDIF " ) , 5 ) ) )
{
//Find an "ENDIF"
foundBlock = IF_BLOCK_ENDIF ;
break ;
} else if ( ( IF_BLOCK_ELSEIF = = block_type | | IF_BLOCK_ANY = = block_type )
& & ( 6 = = lenWord ) & & ( 0 = = strncasecmp_P ( word , PSTR ( " ELSEIF " ) , 6 ) ) )
{
//Find an "ELSEIF"
foundBlock = IF_BLOCK_ELSEIF ;
break ;
} else if ( ( IF_BLOCK_ELSE = = block_type | | IF_BLOCK_ANY = = block_type )
& & ( 4 = = lenWord ) & & ( 0 = = strncasecmp_P ( word , PSTR ( " ELSE " ) , 4 ) ) )
{
//Find an "ELSE"
foundBlock = IF_BLOCK_ELSE ;
break ;
}
}
return foundBlock ;
}
/********************************************************************************************/
/*
* This function is used to execute a commands block in if statement when one of the condition is true .
* Input :
* commands - A char buffer include ( but not limited ) the commands block need to execute
* len - Length of the commands block
* Output :
N / A
* Return :
* void
*/
void ExecuteCommandBlock ( const char * commands , int len )
{
char cmdbuff [ len + 1 ] ; //apply enough space
memcpy ( cmdbuff , commands , len ) ;
cmdbuff [ len ] = ' \0 ' ;
char oneCommand [ len + 1 ] ; //To put one command
int insertPosition = 0 ; //When insert into backlog, we should do it by 0, 1, 2 ...
char * pos = cmdbuff ;
int lenEndBlock = 0 ;
while ( * pos ) {
if ( isspace ( * pos ) | | ' \x1e ' = = * pos | | ' ; ' = = * pos ) {
pos + + ;
continue ;
}
if ( strncasecmp_P ( pos , PSTR ( " BACKLOG " ) , 8 ) = = 0 ) {
//Skip "BACKLOG " and set not first command flag. So all followed command will be send to backlog
pos + = 8 ;
continue ;
}
if ( strncasecmp_P ( pos , PSTR ( " IF " ) , 3 ) = = 0 ) {
//Has a nested IF statement
//Find the matched ENDIF
char * pEndif = pos + 3 ; //Skip "IF "
if ( IF_BLOCK_ENDIF ! = findIfBlock ( pEndif , lenEndBlock , IF_BLOCK_ENDIF ) ) {
//Cannot find matched endif, stop execution.
break ;
}
//We has the whole IF statement, copy to oneCommand
memcpy ( oneCommand , pos , pEndif - pos ) ;
oneCommand [ pEndif - pos ] = ' \0 ' ;
pos = pEndif ;
} else { //Normal command
//Looking for the command end single - '\x1e'
char * pEndOfCommand = strpbrk ( pos , " \x1e ; " ) ;
if ( NULL = = pEndOfCommand ) {
pEndOfCommand = pos + strlen ( pos ) ;
}
memcpy ( oneCommand , pos , pEndOfCommand - pos ) ;
oneCommand [ pEndOfCommand - pos ] = ' \0 ' ;
pos = pEndOfCommand ;
}
//Start to process current command we found
//Going to insert the command into backlog
2024-01-20 21:45:24 +00:00
char * blcommand = oneCommand ;
Trim ( blcommand ) ;
2024-01-24 13:33:55 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Position %d, Command '%s'"), insertPosition, blcommand);
2024-01-20 21:45:24 +00:00
if ( strlen ( blcommand ) ) {
2021-11-06 11:03:31 +00:00
//Insert into backlog
2024-01-20 21:45:24 +00:00
char * temp = ( char * ) malloc ( strlen ( blcommand ) + 1 ) ;
if ( temp ! = nullptr ) {
strcpy ( temp , blcommand ) ;
2024-01-24 13:33:55 +00:00
char * & elem = backlog . insertAt ( insertPosition + + ) ;
2024-01-21 21:07:49 +00:00
elem = temp ;
2024-01-20 21:45:24 +00:00
}
2021-11-06 11:03:31 +00:00
}
2019-09-09 16:24:27 +01:00
}
return ;
}
/********************************************************************************************/
/*
* Execute IF statement . This is the place to run a " IF ... " command .
* Input :
* statements - The IF statement we are going to process
* Output :
N / A
* Return :
* void
*/
void ProcessIfStatement ( const char * statements )
{
String conditionExpression ;
int len = strlen ( statements ) ;
char statbuff [ len + 1 ] ;
memcpy ( statbuff , statements , len + 1 ) ;
char * pos = statbuff ;
int lenEndBlock = 0 ;
while ( true ) { //Each loop process one IF (or ELSEIF) block
//Find and test the condition expression followed the IF or ELSEIF
//Search for the open bracket first
while ( * pos & & * pos ! = ' ( ' ) {
pos + + ;
}
2019-09-10 10:05:53 +01:00
if ( 0 = = * pos ) { break ; }
2019-09-09 16:24:27 +01:00
char * posEnd = findClosureBracket ( pos ) ;
if ( true = = evaluateLogicalExpression ( pos + 1 , posEnd - ( pos + 1 ) ) ) {
//Looking for matched "ELSEIF", "ELSE" or "ENDIF", then Execute this block
char * cmdBlockStart = posEnd + 1 ;
char * cmdBlockEnd = cmdBlockStart ;
int8_t nextBlock = findIfBlock ( cmdBlockEnd , lenEndBlock , IF_BLOCK_ANY ) ;
if ( IF_BLOCK_INVALID = = nextBlock ) {
//Failed
break ;
}
ExecuteCommandBlock ( cmdBlockStart , cmdBlockEnd - cmdBlockStart - lenEndBlock ) ;
pos = cmdBlockEnd ;
break ;
} else { //Does not match the IF condition, going to check elseif and else
pos = posEnd + 1 ;
int8_t nextBlock = findIfBlock ( pos , lenEndBlock , IF_BLOCK_ANY ) ;
if ( IF_BLOCK_ELSEIF = = nextBlock ) {
//Continue process next ELSEIF block like IF
continue ;
} else if ( IF_BLOCK_ELSE = = nextBlock ) {
//Looking for matched "ENDIF" then execute this block
char * cmdBlockEnd = pos ;
int8_t nextBlock = findIfBlock ( cmdBlockEnd , lenEndBlock , IF_BLOCK_ENDIF ) ;
if ( IF_BLOCK_ENDIF ! = nextBlock ) {
//Failed
break ;
}
ExecuteCommandBlock ( pos , cmdBlockEnd - pos - lenEndBlock ) ;
break ;
} else { // IF_BLOCK_ENDIF == nextBlock
//We done
break ;
}
}
}
}
/********************************************************************************************/
/*
* This function is called in Rules event handler to process any command between DO . . . ENDON ( BREAK )
* - Do escape ( convert " ; " into " \x1e " ) for all IF statements .
* Input :
* commands - The commands block need to execute
* Output :
N / A
* Return :
* void
*/
void RulesPreprocessCommand ( char * pCommands )
{
char * cmd = pCommands ;
int lenEndBlock = 0 ;
while ( * cmd ) {
//Skip all ";" and space between two commands
if ( ' ; ' = = * cmd | | isspace ( * cmd ) ) {
cmd + + ;
}
else if ( strncasecmp_P ( cmd , PSTR ( " IF " ) , 3 ) = = 0 ) { //found IF block
//We are going to look for matched "ENDIF"
char * pIfStart = cmd ;
char * pIfEnd = pIfStart + 3 ; //Skip "IF "
//int pIfStart = cmd - command; //"IF" statement block start at position (relative to command start)
if ( IF_BLOCK_ENDIF = = findIfBlock ( pIfEnd , lenEndBlock , IF_BLOCK_ENDIF ) ) {
//Found the ENDIF
cmd = pIfEnd ; //Will continue process from here
//Escapte from ";" to "\x1e".
//By remove all ";" in IF statement block, we can prevent backlog command cut the whole block as multiple commands
while ( pIfStart < pIfEnd ) {
if ( ' ; ' = = * pIfStart )
* pIfStart = ' \x1e ' ;
pIfStart + + ;
}
}
else { //Did not find the matched ENDIF, stop processing
break ;
}
}
else { //Other commands, skip it
while ( * cmd & & ' ; ' ! = * cmd ) {
cmd + + ;
}
}
}
return ;
}
# endif //SUPPORT_IF_STATEMENT
2019-08-02 15:23:17 +01:00
/*********************************************************************************************\
* Commands
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
2019-08-02 15:23:17 +01:00
void CmndRule ( void )
2018-04-13 16:42:11 +01:00
{
2020-05-19 10:15:31 +01:00
if ( 0 = = XdrvMailbox . index ) {
char data = ' \0 ' ;
if ( XdrvMailbox . data_len > 0 ) { // Allow show all if 0
if ( ! ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 10 ) ) ) {
if ( ' " ' = = XdrvMailbox . data [ 0 ] ) {
data = ' " ' ; // Save data as XdrvMailbox.data is destroyed
} else {
XdrvMailbox . data_len = 0 ; // Discard any additional text
}
}
}
for ( uint32_t i = 1 ; i < = MAX_RULE_SETS ; i + + ) {
XdrvMailbox . index = i ;
XdrvMailbox . data [ 0 ] = data ; // Only 0 or "
CmndRule ( ) ;
2021-04-07 14:07:05 +01:00
MqttPublishPrefixTopicRulesProcess_P ( RESULT_OR_STAT , XdrvMailbox . command ) ;
2020-05-19 10:15:31 +01:00
}
2020-10-30 11:29:48 +00:00
ResponseClear ( ) ; // Disable further processing
2020-05-19 10:15:31 +01:00
return ;
}
2018-04-13 16:42:11 +01:00
uint8_t index = XdrvMailbox . index ;
2019-08-02 15:23:17 +01:00
if ( ( index > 0 ) & & ( index < = MAX_RULE_SETS ) ) {
2021-06-11 17:14:12 +01:00
// if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings->rules[index -1]))) { // TODO postpone size calculation
2020-05-08 18:33:20 +01:00
if ( XdrvMailbox . data_len > 0 ) { // TODO postpone size calculation
2018-08-28 09:26:33 +01:00
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 10 ) ) {
2018-04-13 16:42:11 +01:00
switch ( XdrvMailbox . payload ) {
case 0 : // Off
case 1 : // On
2021-06-11 17:14:12 +01:00
bitWrite ( Settings - > rule_enabled , index - 1 , XdrvMailbox . payload ) ;
2018-04-13 16:42:11 +01:00
break ;
case 2 : // Toggle
2021-06-11 17:14:12 +01:00
bitWrite ( Settings - > rule_enabled , index - 1 , bitRead ( Settings - > rule_enabled , index - 1 ) ^ 1 ) ;
2018-04-13 16:42:11 +01:00
break ;
case 4 : // Off
case 5 : // On
2021-06-11 17:14:12 +01:00
bitWrite ( Settings - > rule_once , index - 1 , XdrvMailbox . payload & 1 ) ;
2018-04-13 16:42:11 +01:00
break ;
case 6 : // Toggle
2021-06-11 17:14:12 +01:00
bitWrite ( Settings - > rule_once , index - 1 , bitRead ( Settings - > rule_once , index - 1 ) ^ 1 ) ;
2018-04-13 16:42:11 +01:00
break ;
2018-08-28 09:26:33 +01:00
case 8 : // Off
case 9 : // On
2021-06-11 17:14:12 +01:00
bitWrite ( Settings - > rule_stop , index - 1 , XdrvMailbox . payload & 1 ) ;
2018-08-28 09:26:33 +01:00
break ;
case 10 : // Toggle
2021-06-11 17:14:12 +01:00
bitWrite ( Settings - > rule_stop , index - 1 , bitRead ( Settings - > rule_stop , index - 1 ) ^ 1 ) ;
2018-08-28 09:26:33 +01:00
break ;
2018-04-13 16:42:11 +01:00
}
} else {
2020-05-08 18:33:20 +01:00
bool append = false ;
2018-07-29 13:45:42 +01:00
if ( ' + ' = = XdrvMailbox . data [ 0 ] ) {
2020-05-08 18:33:20 +01:00
XdrvMailbox . data [ 0 ] = ' ' ; // Remove + and make sure at least one space is inserted
append = true ;
2018-07-29 13:45:42 +01:00
}
2020-05-08 18:33:20 +01:00
int32_t res = SetRule ( index - 1 , ( ' " ' = = XdrvMailbox . data [ 0 ] ) ? " " : XdrvMailbox . data , append ) ;
if ( res < 0 ) {
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_ERROR , PSTR ( " RUL: Not enough space " ) ) ;
2018-07-29 13:45:42 +01:00
}
2018-04-13 16:42:11 +01:00
}
2019-08-17 13:27:41 +01:00
Rules . triggers [ index - 1 ] = 0 ; // Reset once flag
2018-04-13 16:42:11 +01:00
}
2020-05-10 14:24:10 +01:00
String rule = GetRule ( index - 1 ) ;
size_t rule_len = rule . length ( ) ;
2020-05-21 19:12:41 +01:00
if ( rule_len > MAX_RULE_SIZE - 3 ) {
size_t start_index = 0 ; // start from 0
while ( start_index < rule_len ) { // until we reached end of rule
size_t last_index = start_index + MAX_RULE_SIZE - 3 ; // set max length to what would fit uncompressed, i.e. MAX_RULE_SIZE - 3 (first NULL + length + last NULL)
if ( last_index < rule_len ) { // if we didn't reach the end, try to shorten to last space character
int32_t next_index = rule . lastIndexOf ( " " , last_index ) ;
2021-04-20 16:34:04 +01:00
if ( next_index > start_index ) { // if space was found and is not before start_index (i.e. we are progressing)
2020-05-21 19:12:41 +01:00
last_index = next_index ; // shrink to the last space
} // otherwise it means there are no spaces, we need to cut somewhere even if the result cannot be entered back
} else {
last_index = rule_len ; // until the end of the rule
}
2021-01-23 16:24:54 +00:00
AddLog ( LOG_LEVEL_INFO , PSTR ( " RUL: Rule%d %s%s " ) ,
2020-05-21 19:12:41 +01:00
index , 0 = = start_index ? PSTR ( " " ) : PSTR ( " + " ) ,
rule . substring ( start_index , last_index ) . c_str ( ) ) ;
start_index = last_index + 1 ;
}
2020-05-10 14:24:10 +01:00
// we need to split the rule in chunks
2021-06-22 15:54:47 +01:00
// rule = rule.substring(0, MAX_RULE_SIZE);
// rule += F("...");
2020-05-10 14:24:10 +01:00
}
2021-05-23 15:50:17 +01:00
Response_P ( PSTR ( " { \" %s%d \" :{ \" State \" : \" %s \" , \" Once \" : \" %s \" , \" StopOnError \" : \" %s \" , \" Length \" :%d, \" Free \" :%d, \" Rules \" : \" %s \" }} " ) ,
2021-06-11 17:14:12 +01:00
XdrvMailbox . command , index , GetStateText ( bitRead ( Settings - > rule_enabled , index - 1 ) ) , GetStateText ( bitRead ( Settings - > rule_once , index - 1 ) ) ,
GetStateText ( bitRead ( Settings - > rule_stop , index - 1 ) ) ,
2020-05-10 14:24:10 +01:00
rule_len , MAX_RULE_SIZE - GetRuleLenStorage ( index - 1 ) ,
2020-05-24 08:57:11 +01:00
EscapeJSONString ( rule . c_str ( ) ) . c_str ( ) ) ;
2018-04-13 16:42:11 +01:00
}
2019-08-02 15:23:17 +01:00
}
void CmndRuleTimer ( void )
{
2021-01-03 16:31:44 +00:00
if ( XdrvMailbox . index > MAX_RULE_TIMERS ) { return ; }
uint32_t i = XdrvMailbox . index ;
uint32_t max_i = XdrvMailbox . index ;
2021-01-01 20:13:22 +00:00
if ( 0 = = i ) {
i = 1 ;
max_i = MAX_RULE_TIMERS ;
}
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
# ifdef USE_EXPRESSION
2021-01-01 20:13:22 +00:00
float timer_set = evaluateExpression ( XdrvMailbox . data , XdrvMailbox . data_len ) ;
timer_set = ( timer_set > 0 ) ? millis ( ) + ( 1000 * timer_set ) : 0 ;
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
# else
2021-01-03 16:31:44 +00:00
uint32_t timer_set = ( XdrvMailbox . payload > 0 ) ? millis ( ) + ( 1000 * XdrvMailbox . payload ) : 0 ;
2019-08-02 15:23:17 +01:00
# endif // USE_EXPRESSION
2021-01-01 20:13:22 +00:00
if ( XdrvMailbox . data_len > 0 ) {
for ( ; i < = max_i ; + + i ) {
Rules . timer [ i - 1 ] = timer_set ;
2018-04-13 16:42:11 +01:00
}
}
2021-01-01 20:13:22 +00:00
ResponseClear ( ) ;
for ( i = 0 ; i < MAX_RULE_TIMERS ; i + + ) {
ResponseAppend_P ( PSTR ( " %c \" T%d \" :%d " ) , ( i ) ? ' , ' : ' { ' , i + 1 , ( Rules . timer [ i ] ) ? ( Rules . timer [ i ] - millis ( ) ) / 1000 : 0 ) ;
}
ResponseJsonEnd ( ) ;
2019-08-02 15:23:17 +01:00
}
void CmndEvent ( void )
{
if ( XdrvMailbox . data_len > 0 ) {
2019-08-17 13:27:41 +01:00
strlcpy ( Rules . event_data , XdrvMailbox . data , sizeof ( Rules . event_data ) ) ;
2020-04-19 05:42:51 +01:00
# ifdef USE_DEVICE_GROUPS
2021-10-11 14:39:49 +01:00
if ( ! XdrvMailbox . grpflg ) SendDeviceGroupMessage ( 1 , DGR_MSGTYP_UPDATE , DGR_ITEM_EVENT , XdrvMailbox . data ) ;
2020-04-19 05:42:51 +01:00
# endif // USE_DEVICE_GROUPS
2018-04-13 16:42:11 +01:00
}
2020-04-19 04:37:05 +01:00
if ( XdrvMailbox . command ) ResponseCmndDone ( ) ;
2019-08-02 15:23:17 +01:00
}
void CmndVariable ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = MAX_RULE_VARS ) ) {
2019-07-24 10:10:15 +01:00
if ( ! XdrvMailbox . usridx ) {
2020-10-30 11:29:48 +00:00
ResponseClear ( ) ;
2019-07-24 10:10:15 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_VARS ; i + + ) {
2019-08-17 13:27:41 +01:00
ResponseAppend_P ( PSTR ( " %c \" Var%d \" : \" %s \" " ) , ( i ) ? ' , ' : ' { ' , i + 1 , rules_vars [ i ] ) ;
2019-07-24 10:10:15 +01:00
}
ResponseJsonEnd ( ) ;
} else {
if ( XdrvMailbox . data_len > 0 ) {
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
# ifdef USE_EXPRESSION
2019-08-30 16:07:56 +01:00
if ( XdrvMailbox . data [ 0 ] = = ' = ' ) { // Spaces already been skipped in data
2021-06-11 17:14:12 +01:00
dtostrfd ( evaluateExpression ( XdrvMailbox . data + 1 , XdrvMailbox . data_len - 1 ) , Settings - > flag2 . calc_resolution , rules_vars [ XdrvMailbox . index - 1 ] ) ;
2019-08-30 16:07:56 +01:00
} else {
strlcpy ( rules_vars [ XdrvMailbox . index - 1 ] , ( ' " ' = = XdrvMailbox . data [ 0 ] ) ? " " : XdrvMailbox . data , sizeof ( rules_vars [ XdrvMailbox . index - 1 ] ) ) ;
}
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
# else
2019-08-17 13:27:41 +01:00
strlcpy ( rules_vars [ XdrvMailbox . index - 1 ] , ( ' " ' = = XdrvMailbox . data [ 0 ] ) ? " " : XdrvMailbox . data , sizeof ( rules_vars [ XdrvMailbox . index - 1 ] ) ) ;
2019-08-02 15:23:17 +01:00
# endif // USE_EXPRESSION
2019-08-17 13:27:41 +01:00
bitSet ( Rules . vars_event , XdrvMailbox . index - 1 ) ;
2019-07-24 10:10:15 +01:00
}
2019-08-17 13:27:41 +01:00
ResponseCmndIdxChar ( rules_vars [ XdrvMailbox . index - 1 ] ) ;
2018-05-20 16:46:00 +01:00
}
}
2019-08-02 15:23:17 +01:00
}
void CmndMemory ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = MAX_RULE_MEMS ) ) {
2019-07-24 10:10:15 +01:00
if ( ! XdrvMailbox . usridx ) {
2020-01-12 12:10:21 +00:00
ResponseCmndAll ( SET_MEM1 , MAX_RULE_MEMS ) ;
2019-07-24 10:10:15 +01:00
} else {
if ( XdrvMailbox . data_len > 0 ) {
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
# ifdef USE_EXPRESSION
2019-08-30 16:07:56 +01:00
if ( XdrvMailbox . data [ 0 ] = = ' = ' ) { // Spaces already been skipped in data
2020-10-26 15:42:44 +00:00
char rules_mem [ FLOATSZ ] ;
2021-06-11 17:14:12 +01:00
dtostrfd ( evaluateExpression ( XdrvMailbox . data + 1 , XdrvMailbox . data_len - 1 ) , Settings - > flag2 . calc_resolution , rules_mem ) ;
2020-10-26 15:42:44 +00:00
SettingsUpdateText ( SET_MEM1 + XdrvMailbox . index - 1 , rules_mem ) ;
2019-08-30 16:07:56 +01:00
} else {
2019-12-16 14:13:57 +00:00
SettingsUpdateText ( SET_MEM1 + XdrvMailbox . index - 1 , ( ' " ' = = XdrvMailbox . data [ 0 ] ) ? " " : XdrvMailbox . data ) ;
2019-08-30 16:07:56 +01:00
}
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
# else
2019-12-16 14:13:57 +00:00
SettingsUpdateText ( SET_MEM1 + XdrvMailbox . index - 1 , ( ' " ' = = XdrvMailbox . data [ 0 ] ) ? " " : XdrvMailbox . data ) ;
2019-08-02 15:23:17 +01:00
# endif // USE_EXPRESSION
2019-08-17 13:27:41 +01:00
bitSet ( Rules . mems_event , XdrvMailbox . index - 1 ) ;
2019-07-24 10:10:15 +01:00
}
2019-12-16 14:13:57 +00:00
ResponseCmndIdxChar ( SettingsText ( SET_MEM1 + XdrvMailbox . index - 1 ) ) ;
2018-05-20 16:46:00 +01:00
}
}
2019-08-02 15:23:17 +01:00
}
void CmndCalcResolution ( void )
{
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 7 ) ) {
2021-06-11 17:14:12 +01:00
Settings - > flag2 . calc_resolution = XdrvMailbox . payload ;
2018-11-27 00:22:44 +00:00
}
2021-06-11 17:14:12 +01:00
ResponseCmndNumber ( Settings - > flag2 . calc_resolution ) ;
2019-08-02 15:23:17 +01:00
}
void CmndAddition ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = MAX_RULE_VARS ) ) {
2018-07-16 11:37:49 +01:00
if ( XdrvMailbox . data_len > 0 ) {
2019-08-17 13:27:41 +01:00
float tempvar = CharToFloat ( rules_vars [ XdrvMailbox . index - 1 ] ) + CharToFloat ( XdrvMailbox . data ) ;
2021-06-11 17:14:12 +01:00
dtostrfd ( tempvar , Settings - > flag2 . calc_resolution , rules_vars [ XdrvMailbox . index - 1 ] ) ;
2019-08-17 13:27:41 +01:00
bitSet ( Rules . vars_event , XdrvMailbox . index - 1 ) ;
2018-06-10 06:09:11 +01:00
}
2019-08-17 13:27:41 +01:00
ResponseCmndIdxChar ( rules_vars [ XdrvMailbox . index - 1 ] ) ;
2018-06-10 06:09:11 +01:00
}
2019-08-02 15:23:17 +01:00
}
void CmndSubtract ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = MAX_RULE_VARS ) ) {
2018-07-16 11:37:49 +01:00
if ( XdrvMailbox . data_len > 0 ) {
2019-08-17 13:27:41 +01:00
float tempvar = CharToFloat ( rules_vars [ XdrvMailbox . index - 1 ] ) - CharToFloat ( XdrvMailbox . data ) ;
2021-06-11 17:14:12 +01:00
dtostrfd ( tempvar , Settings - > flag2 . calc_resolution , rules_vars [ XdrvMailbox . index - 1 ] ) ;
2019-08-17 13:27:41 +01:00
bitSet ( Rules . vars_event , XdrvMailbox . index - 1 ) ;
2018-06-10 06:09:11 +01:00
}
2019-08-17 13:27:41 +01:00
ResponseCmndIdxChar ( rules_vars [ XdrvMailbox . index - 1 ] ) ;
2018-06-10 06:09:11 +01:00
}
2019-08-02 15:23:17 +01:00
}
void CmndMultiply ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = MAX_RULE_VARS ) ) {
2018-07-16 11:37:49 +01:00
if ( XdrvMailbox . data_len > 0 ) {
2019-08-17 13:27:41 +01:00
float tempvar = CharToFloat ( rules_vars [ XdrvMailbox . index - 1 ] ) * CharToFloat ( XdrvMailbox . data ) ;
2021-06-11 17:14:12 +01:00
dtostrfd ( tempvar , Settings - > flag2 . calc_resolution , rules_vars [ XdrvMailbox . index - 1 ] ) ;
2019-08-17 13:27:41 +01:00
bitSet ( Rules . vars_event , XdrvMailbox . index - 1 ) ;
2018-06-10 06:09:11 +01:00
}
2019-08-17 13:27:41 +01:00
ResponseCmndIdxChar ( rules_vars [ XdrvMailbox . index - 1 ] ) ;
2018-06-10 06:09:11 +01:00
}
2019-08-02 15:23:17 +01:00
}
void CmndScale ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = MAX_RULE_VARS ) ) {
2018-07-16 11:37:49 +01:00
if ( XdrvMailbox . data_len > 0 ) {
2021-01-31 15:54:28 +00:00
if ( ArgC ( ) = = 5 ) { // Process parameter entry
2021-01-30 11:27:48 +00:00
char argument [ XdrvMailbox . data_len ] ;
float valueIN = CharToFloat ( ArgV ( argument , 1 ) ) ;
float fromLow = CharToFloat ( ArgV ( argument , 2 ) ) ;
float fromHigh = CharToFloat ( ArgV ( argument , 3 ) ) ;
float toLow = CharToFloat ( ArgV ( argument , 4 ) ) ;
float toHigh = CharToFloat ( ArgV ( argument , 5 ) ) ;
2019-07-01 17:20:43 +01:00
float value = map_double ( valueIN , fromLow , fromHigh , toLow , toHigh ) ;
2021-06-11 17:14:12 +01:00
dtostrfd ( value , Settings - > flag2 . calc_resolution , rules_vars [ XdrvMailbox . index - 1 ] ) ;
2019-08-17 13:27:41 +01:00
bitSet ( Rules . vars_event , XdrvMailbox . index - 1 ) ;
2021-01-31 15:54:28 +00:00
} else {
ResponseCmndIdxError ( ) ;
return ;
2018-06-10 06:09:11 +01:00
}
}
2019-08-17 13:27:41 +01:00
ResponseCmndIdxChar ( rules_vars [ XdrvMailbox . index - 1 ] ) ;
2018-06-10 06:09:11 +01:00
}
2018-04-13 16:42:11 +01:00
}
2019-07-01 17:20:43 +01:00
float map_double ( float x , float in_min , float in_max , float out_min , float out_max )
2018-06-28 17:06:21 +01:00
{
2019-08-02 15:23:17 +01:00
return ( x - in_min ) * ( out_max - out_min ) / ( in_max - in_min ) + out_min ;
2018-06-28 17:06:21 +01:00
}
2018-04-13 16:42:11 +01:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-11-11 09:44:56 +00:00
bool Xdrv10 ( uint32_t function )
2018-04-13 16:42:11 +01:00
{
2019-01-28 13:08:33 +00:00
bool result = false ;
2018-04-13 16:42:11 +01:00
switch ( function ) {
case FUNC_EVERY_50_MSECOND :
RulesEvery50ms ( ) ;
break ;
2018-08-26 16:10:18 +01:00
case FUNC_EVERY_100_MSECOND :
RulesEvery100ms ( ) ;
break ;
2018-04-13 16:42:11 +01:00
case FUNC_EVERY_SECOND :
RulesEverySecond ( ) ;
break ;
2018-05-06 15:07:42 +01:00
case FUNC_SET_POWER :
RulesSetPower ( ) ;
break ;
2018-04-13 16:42:11 +01:00
case FUNC_COMMAND :
2019-08-02 15:23:17 +01:00
result = DecodeCommand ( kRulesCommands , RulesCommand ) ;
2018-04-13 16:42:11 +01:00
break ;
2018-05-24 15:23:20 +01:00
case FUNC_RULES_PROCESS :
result = RulesProcess ( ) ;
break ;
2021-04-23 09:59:33 +01:00
case FUNC_TELEPERIOD_RULES_PROCESS :
Rules . teleperiod = true ;
result = RulesProcess ( ) ;
Rules . teleperiod = false ;
break ;
2019-05-19 11:42:10 +01:00
case FUNC_SAVE_BEFORE_RESTART :
RulesSaveBeforeRestart ( ) ;
break ;
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
# ifdef SUPPORT_MQTT_EVENT
case FUNC_MQTT_DATA :
result = RulesMqttData ( ) ;
break ;
2019-08-02 15:23:17 +01:00
# endif // SUPPORT_MQTT_EVENT
2019-09-29 17:00:01 +01:00
case FUNC_PRE_INIT :
RulesInit ( ) ;
break ;
2023-12-27 21:03:56 +00:00
case FUNC_ACTIVE :
result = true ;
break ;
2018-04-13 16:42:11 +01:00
}
return result ;
}
2019-05-18 12:34:52 +01:00
# endif // Do not USE_SCRIPT
2019-07-01 17:20:43 +01:00
# endif // USE_RULES