2018-04-13 16:42:11 +01:00
/*
xdrv_10_rules . ino - rule support for Sonoff - Tasmota
2019-03-13 17:00:15 +00:00
Copyright ( C ) 2019 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-07-05 12:44:24 +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
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
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2018-12-15 14:55:51 +00:00
# define XDRV_10 10
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-12-15 14:55:51 +00:00
# define D_CMND_CALC_RESOLUTION "CalcRes"
2019-03-13 17:00:15 +00:00
# define D_CMND_SUBSCRIBE "Subscribe"
# define D_CMND_UNSUBSCRIBE "Unsubscribe"
2019-10-23 12:11:53 +01:00
# define D_CMND_IF "If"
2018-04-13 16:42:11 +01:00
# define D_JSON_INITIATED "Initiated"
2019-03-13 17:00:15 +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 MAXIMUM_COMPARE_OPERATOR COMPARE_OPERATOR_SMALLER_EQUAL
const char kCompareOperators [ ] PROGMEM = " = \0 > \0 < \0 | \0 ==!=>=<= " ;
# ifdef USE_EXPRESSION
# include <LinkedList.h> // Import LinkedList library
2019-10-23 12:11:53 +01:00
const char kExpressionOperators [ ] PROGMEM = " +-*/%^ \0 " ;
2019-03-13 17:00:15 +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-10-23 12:11:53 +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-03-13 17:00:15 +00:00
# endif // USE_EXPRESSION
2019-10-23 12:11:53 +01:00
const char kRulesCommands [ ] PROGMEM = " | " // No prefix
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
# endif
# ifdef SUPPORT_IF_STATEMENT
" | " D_CMND_IF
# endif
;
void ( * const RulesCommand [ ] ) ( void ) PROGMEM = {
& CmndRule , & CmndRuleTimer , & CmndEvent , & CmndVariable , & CmndMemory ,
& CmndAddition , & CmndSubtract , & CmndMultiply , & CmndScale , & CmndCalcResolution
# ifdef SUPPORT_MQTT_EVENT
, & CmndSubscribe , & CmndUnsubscribe
# endif
# ifdef SUPPORT_IF_STATEMENT
, & CmndIf
# endif
} ;
2019-03-13 17:00:15 +00:00
# ifdef SUPPORT_MQTT_EVENT
# include <LinkedList.h> // Import LinkedList library
typedef struct {
String Event ;
String Topic ;
String Key ;
} MQTT_Subscription ;
LinkedList < MQTT_Subscription > subscriptions ;
2019-10-23 12:11:53 +01:00
# endif // SUPPORT_MQTT_EVENT
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 ;
uint16_t vars_event = 0 ;
uint8_t mems_event = 0 ;
bool teleperiod = false ;
char event_data [ 100 ] ;
} Rules ;
char rules_vars [ MAX_RULE_VARS ] [ 33 ] = { { 0 } } ;
2019-03-13 17:00:15 +00:00
# if (MAX_RULE_VARS>16)
# error MAX_RULE_VARS is bigger than 16
# endif
# if (MAX_RULE_MEMS>5)
# error MAX_RULE_MEMS is bigger than 5
# endif
2018-05-19 07:00:48 +01:00
2018-04-13 16:42:11 +01:00
/*******************************************************************************************/
2019-03-13 17:00:15 +00:00
bool RulesRuleMatch ( uint8_t rule_set , String & event , String & rule )
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
int pos = rule . indexOf ( ' # ' ) ;
2018-04-17 14:34:18 +01:00
if ( pos = = - 1 ) { return false ; } // No # sign in rule
2018-04-13 16:42:11 +01:00
String rule_task = rule . substring ( 0 , pos ) ; // "INA219" or "SYSTEM"
2019-10-23 12:11:53 +01:00
if ( Rules . teleperiod ) {
2018-04-27 17:06:19 +01:00
int ppos = rule_task . indexOf ( " TELE- " ) ; // "TELE-INA219" or "INA219"
if ( ppos = = - 1 ) { return false ; } // No pre-amble in rule
rule_task = rule . substring ( 5 , pos ) ; // "INA219" or "SYSTEM"
}
2019-10-23 12:11:53 +01:00
String rule_expr = rule . substring ( pos + 1 ) ; // "CURRENT>0.100" or "BOOT" or "%var1%" or "MINUTE|5"
String rule_name , rule_param ;
int8_t compareOperator = parseCompareExpression ( rule_expr , rule_name , rule_param ) ; //Parse the compare expression.Return operator and the left, right part of expression
2018-04-13 16:42:11 +01:00
2019-10-23 12:11:53 +01:00
char rule_svalue [ 80 ] = { 0 } ;
2019-07-05 12:44:24 +01:00
float rule_value = 0 ;
2019-10-23 12:11:53 +01:00
if ( compareOperator ! = COMPARE_OPERATOR_NONE ) {
2019-07-05 12:44:24 +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-10-23 12:11:53 +01:00
rule_param = rules_vars [ i ] ;
2018-05-20 16:46:00 +01:00
break ;
}
}
2019-07-05 12:44:24 +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 ) ) {
rule_param = Settings . mems [ i ] ;
break ;
}
}
2018-08-27 08:59:17 +01:00
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%TIME%% " ) ) ;
if ( rule_param . startsWith ( stemp ) ) {
2019-03-13 17:00:15 +00:00
rule_param = String ( MinutesPastMidnight ( ) ) ;
2018-08-27 08:59:17 +01:00
}
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%UPTIME%% " ) ) ;
if ( rule_param . startsWith ( stemp ) ) {
2019-03-13 17:00:15 +00:00
rule_param = String ( MinutesUptime ( ) ) ;
}
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%TIMESTAMP%% " ) ) ;
if ( rule_param . startsWith ( stemp ) ) {
rule_param = GetDateAndTime ( DT_LOCAL ) . c_str ( ) ;
2018-08-27 08:59:17 +01:00
}
# if defined(USE_TIMERS) && defined(USE_SUNRISE)
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%SUNRISE%% " ) ) ;
if ( rule_param . startsWith ( stemp ) ) {
2019-03-13 17:00:15 +00:00
rule_param = String ( SunMinutes ( 0 ) ) ;
2018-08-27 08:59:17 +01:00
}
snprintf_P ( stemp , sizeof ( stemp ) , PSTR ( " %%SUNSET%% " ) ) ;
if ( rule_param . startsWith ( stemp ) ) {
2019-03-13 17:00:15 +00:00
rule_param = String ( SunMinutes ( 1 ) ) ;
2018-08-27 08:59:17 +01:00
}
# endif // USE_TIMERS and USE_SUNRISE
2018-05-20 16:46:00 +01:00
rule_param . toUpperCase ( ) ;
2019-03-13 17:00:15 +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 {
2019-07-05 12:44:24 +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
}
// Step2: Search rule_task and rule_name
2018-05-01 10:28:36 +01:00
StaticJsonBuffer < 1024 > jsonBuf ;
2018-04-13 16:42:11 +01:00
JsonObject & root = jsonBuf . parseObject ( event ) ;
2018-04-17 14:34:18 +01:00
if ( ! root . success ( ) ) { return false ; } // No valid JSON data
2018-04-13 16:42:11 +01:00
2019-10-23 12:11:53 +01:00
const char * str_value ;
if ( ( pos = rule_name . indexOf ( " [ " ) ) > 0 ) { // "CURRENT[1]"
int rule_name_idx = rule_name . substring ( pos + 1 ) . toInt ( ) ;
if ( ( rule_name_idx < 1 ) | | ( rule_name_idx > 6 ) ) { // Allow indexes 1 to 6
rule_name_idx = 1 ;
}
rule_name = rule_name . substring ( 0 , pos ) ; // "CURRENT"
str_value = root [ rule_task ] [ rule_name ] [ rule_name_idx - 1 ] ; // "ENERGY" and "CURRENT" and 0
} else {
str_value = root [ rule_task ] [ rule_name ] ; // "INA219" and "CURRENT"
}
2018-04-13 16:42:11 +01:00
2019-03-13 17:00:15 +00:00
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Task %s, Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"),
2019-10-23 12:11:53 +01:00
// rule_task.c_str(), 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) ? str_value : "none");
2018-04-13 16:42:11 +01:00
2018-04-17 14:34:18 +01:00
if ( ! root [ rule_task ] [ rule_name ] . success ( ) ) { return false ; }
2018-04-13 16:42:11 +01:00
// No value but rule_name is ok
2019-10-23 12:11:53 +01:00
Rules . event_value = str_value ; // Prepare %value%
2018-04-13 16:42:11 +01:00
// Step 3: Compare rule (value)
2019-10-23 12:11:53 +01:00
float value = 0 ;
2018-04-13 16:42:11 +01:00
if ( str_value ) {
2019-07-05 12:44:24 +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 ) ;
2019-10-23 12:11:53 +01:00
switch ( compareOperator ) {
2019-03-13 17:00:15 +00:00
case COMPARE_OPERATOR_EXACT_DIVISION :
match = ( int_rule_value & & ( int_value % int_rule_value ) = = 0 ) ;
2018-08-13 12:09:22 +01:00
break ;
2019-03-13 17:00:15 +00:00
case COMPARE_OPERATOR_EQUAL :
match = ( ! strcasecmp ( str_value , rule_svalue ) ) ; // Compare strings - this also works for hexadecimals
2018-04-13 16:42:11 +01:00
break ;
2019-03-13 17:00:15 +00:00
case COMPARE_OPERATOR_BIGGER :
match = ( value > rule_value ) ;
2018-04-13 16:42:11 +01:00
break ;
2019-03-13 17:00:15 +00:00
case COMPARE_OPERATOR_SMALLER :
match = ( value < rule_value ) ;
2018-04-13 16:42:11 +01:00
break ;
2019-03-13 17:00:15 +00:00
case COMPARE_OPERATOR_NUMBER_EQUAL :
match = ( value = = rule_value ) ;
2018-04-13 16:42:11 +01:00
break ;
2019-03-13 17:00:15 +00:00
case COMPARE_OPERATOR_NOT_EQUAL :
match = ( value ! = rule_value ) ;
break ;
case COMPARE_OPERATOR_BIGGER_EQUAL :
match = ( value > = rule_value ) ;
break ;
case COMPARE_OPERATOR_SMALLER_EQUAL :
match = ( value < = rule_value ) ;
break ;
default :
match = true ;
2018-04-13 16:42:11 +01:00
}
} else match = true ;
2018-07-16 10:34:44 +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-10-23 12:11:53 +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-10-23 12:11:53 +01:00
bitClear ( Rules . triggers [ rule_set ] , Rules . trigger_count [ rule_set ] ) ;
2018-04-13 16:42:11 +01:00
}
}
return match ;
}
2019-10-23 12:11:53 +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 ;
leftExpr = expr ;
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 ;
}
2018-04-13 16:42:11 +01:00
/*******************************************************************************************/
2019-03-13 17:00:15 +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
2019-03-13 17:00:15 +00:00
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Event = %s, Rule = %s"), event_saved.c_str(), Settings.rules[rule_set]);
2018-05-13 16:38:44 +01:00
2018-05-24 13:25:52 +01:00
String rules = Settings . rules [ rule_set ] ;
2018-04-13 16:42:11 +01:00
2019-10-23 12:11:53 +01:00
Rules . trigger_count [ rule_set ] = 0 ;
2018-04-13 16:42:11 +01:00
int plen = 0 ;
2018-12-15 14:55:51 +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"
if ( ! rule . startsWith ( " ON " ) ) { return serviced ; } // Bad syntax - Nothing to start on
2018-04-13 16:42:11 +01:00
int pevt = rule . indexOf ( " 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"
2018-04-13 16:42:11 +01:00
plen = rule . indexOf ( " ENDON " ) ;
2018-12-15 14:55:51 +00:00
plen2 = rule . indexOf ( " BREAK " ) ;
if ( ( plen = = - 1 ) & & ( plen2 = = - 1 ) ) { return serviced ; } // Bad syntax - No ENDON neither BREAK
if ( plen = = - 1 ) { plen = 9999 ; }
if ( plen2 = = - 1 ) { plen2 = 9999 ; }
plen = tmin ( plen , plen2 ) ;
if ( plen = = plen2 ) { stop_all_rules = true ; } // If BREAK was used, Stop execution of this rule set
2018-04-17 14:34:18 +01:00
String commands = rules . substring ( pevt + 4 , plen ) ; // "Backlog Dimmer 10;Color 100000"
2018-04-13 16:42:11 +01:00
plen + = 6 ;
2019-10-23 12:11:53 +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
2019-03-13 17:00:15 +00:00
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Event |%s|, Rule |%s|, Command(s) |%s|"), event.c_str(), event_trigger.c_str(), commands.c_str());
2018-04-13 16:42:11 +01:00
2018-05-24 13:25:52 +01:00
if ( RulesRuleMatch ( rule_set , event , event_trigger ) ) {
2018-04-27 17:06:19 +01:00
commands . trim ( ) ;
String ucommand = commands ;
ucommand . toUpperCase ( ) ;
2018-05-20 16:46:00 +01:00
// if (!ucommand.startsWith("BACKLOG")) { commands = "backlog " + commands; } // Always use Backlog to prevent power race exception
if ( ucommand . indexOf ( " EVENT " ) ! = - 1 ) { commands = " backlog " + commands ; } // Always use Backlog with event to prevent rule event loop exception
2019-10-23 12:11:53 +01:00
commands . replace ( F ( " %value% " ) , Rules . event_value ) ;
2019-07-05 12:44:24 +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 ) ;
2019-10-23 12:11:53 +01:00
commands . replace ( stemp , rules_vars [ i ] ) ;
2018-05-20 16:46:00 +01:00
}
2019-07-05 12:44:24 +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 ) ;
commands . replace ( stemp , Settings . mems [ i ] ) ;
}
2019-03-13 17:00:15 +00:00
commands . replace ( F ( " %time% " ) , String ( MinutesPastMidnight ( ) ) ) ;
commands . replace ( F ( " %uptime% " ) , String ( MinutesUptime ( ) ) ) ;
commands . replace ( F ( " %timestamp% " ) , GetDateAndTime ( DT_LOCAL ) . c_str ( ) ) ;
2018-06-26 15:22:53 +01:00
# if defined(USE_TIMERS) && defined(USE_SUNRISE)
2019-03-13 17:00:15 +00:00
commands . replace ( F ( " %sunrise% " ) , String ( SunMinutes ( 0 ) ) ) ;
commands . replace ( F ( " %sunset% " ) , String ( SunMinutes ( 1 ) ) ) ;
2018-06-26 15:22:53 +01:00
# endif // USE_TIMERS and USE_SUNRISE
2018-05-20 16:46:00 +01:00
char command [ commands . length ( ) + 1 ] ;
2019-03-13 17:00:15 +00:00
strlcpy ( command , commands . c_str ( ) , sizeof ( command ) ) ;
2018-04-13 16:42:11 +01:00
2019-03-13 17:00:15 +00:00
AddLog_P2 ( LOG_LEVEL_INFO , PSTR ( " RUL: %s performs \" %s \" " ) , event_trigger . c_str ( ) , command ) ;
2018-04-13 16:42:11 +01:00
2019-07-05 12:44:24 +01: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-10-23 12:11:53 +01:00
# ifdef SUPPORT_IF_STATEMENT
char * pCmd = command ;
RulesPreprocessCommand ( pCmd ) ; //Do pre-process for IF statement
# endif
2018-05-28 14:52:42 +01:00
ExecuteCommand ( command , SRC_RULE ) ;
2018-04-13 16:42:11 +01:00
serviced = true ;
2018-12-15 14:55:51 +00:00
if ( stop_all_rules ) { return serviced ; } // If BREAK was used, Stop execution of this rule set
2018-04-13 16:42:11 +01:00
}
2019-10-23 12:11:53 +01:00
Rules . trigger_count [ rule_set ] + + ;
2018-04-13 16:42:11 +01:00
}
return serviced ;
}
/*******************************************************************************************/
2018-06-25 17:00:20 +01:00
bool RulesProcessEvent ( char * json_event )
2018-05-24 13:25:52 +01:00
{
bool serviced = false ;
2019-03-13 17:00:15 +00:00
# ifdef USE_DEBUG_DRIVER
2018-06-26 10:48:09 +01:00
ShowFreeMem ( PSTR ( " RulesProcessEvent " ) ) ;
2019-03-13 17:00:15 +00:00
# endif
2018-06-26 10:48:09 +01:00
2018-06-25 17:00:20 +01:00
String event_saved = json_event ;
2019-07-05 12:44:24 +01:00
// 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"}}
}
2018-05-24 13:25:52 +01:00
event_saved . toUpperCase ( ) ;
2019-03-13 17:00:15 +00:00
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Event %s"), event_saved.c_str());
2018-06-28 11:25:50 +01:00
2019-07-05 12:44:24 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_SETS ; i + + ) {
2018-05-24 13:25:52 +01:00
if ( strlen ( Settings . rules [ i ] ) & & bitRead ( Settings . rule_enabled , i ) ) {
if ( RuleSetProcess ( i , event_saved ) ) { serviced = true ; }
}
}
return serviced ;
}
2018-12-15 14:55:51 +00:00
bool RulesProcess ( void )
2018-06-25 17:00:20 +01:00
{
return RulesProcessEvent ( mqtt_data ) ;
}
2018-12-15 14:55:51 +00:00
void RulesInit ( void )
2018-04-13 16:42:11 +01:00
{
2018-06-25 17:00:20 +01:00
rules_flag . data = 0 ;
2019-07-05 12:44:24 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_SETS ; i + + ) {
2018-05-24 13:25:52 +01:00
if ( Settings . rules [ i ] [ 0 ] = = ' \0 ' ) {
bitWrite ( Settings . rule_enabled , i , 0 ) ;
bitWrite ( Settings . rule_once , i , 0 ) ;
}
2018-04-13 16:42:11 +01:00
}
2019-10-23 12:11:53 +01:00
Rules . teleperiod = false ;
2018-04-13 16:42:11 +01:00
}
2018-12-15 14:55:51 +00:00
void RulesEvery50ms ( void )
2018-04-13 16:42:11 +01:00
{
2018-05-24 13:25:52 +01:00
if ( Settings . rule_enabled ) { // Any rule enabled
2018-06-25 17:00:20 +01:00
char json_event [ 120 ] ;
2019-10-23 12:11:53 +01:00
if ( - 1 = = Rules . new_power ) { Rules . new_power = power ; }
if ( Rules . new_power ! = Rules . old_power ) {
if ( Rules . old_power ! = - 1 ) {
2019-07-05 12:44:24 +01:00
for ( uint32_t i = 0 ; i < devices_present ; i + + ) {
2019-10-23 12:11:53 +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-10-26 16:13:17 +01:00
} else {
// Boot time POWER OUTPUTS (Relays) Status
2019-07-05 12:44:24 +01:00
for ( uint32_t i = 0 ; i < devices_present ; i + + ) {
2019-10-23 12:11:53 +01:00
uint8_t new_state = ( Rules . new_power > > i ) & 1 ;
2018-10-26 16:13:17 +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
2019-07-05 12:44:24 +01:00
for ( uint32_t i = 0 ; i < MAX_SWITCHES ; i + + ) {
2018-10-26 16:13:17 +01:00
# ifdef USE_TM1638
if ( ( pin [ GPIO_SWT1 + i ] < 99 ) | | ( ( pin [ GPIO_TM16CLK ] < 99 ) & & ( pin [ GPIO_TM16DIO ] < 99 ) & & ( pin [ GPIO_TM16STB ] < 99 ) ) ) {
# else
if ( pin [ GPIO_SWT1 + i ] < 99 ) {
2019-10-23 12:11:53 +01:00
# endif // USE_TM1638
2019-03-13 17:00:15 +00:00
bool swm = ( ( FOLLOW_INV = = Settings . switchmode [ i ] ) | | ( PUSHBUTTON_INV = = Settings . switchmode [ i ] ) | | ( PUSHBUTTONHOLD_INV = = Settings . switchmode [ i ] ) ) ;
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" " D_JSON_SWITCH " %d \" :{ \" Boot \" :%d}} " ) , i + 1 , ( swm ^ SwitchLastState ( i ) ) ) ;
2018-10-26 16:13:17 +01:00
RulesProcessEvent ( json_event ) ;
}
}
2018-05-06 15:07:42 +01:00
}
2019-10-23 12:11:53 +01:00
Rules . old_power = Rules . new_power ;
2018-05-29 16:24:42 +01:00
}
2019-10-23 12:11:53 +01:00
else if ( Rules . old_dimm ! = Settings . light_dimmer ) {
if ( Rules . old_dimm ! = - 1 ) {
2018-10-26 16:13:17 +01:00
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Dimmer \" :{ \" State \" :%d}} " ) , Settings . light_dimmer ) ;
} else {
// Boot time DIMMER VALUE
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Dimmer \" :{ \" Boot \" :%d}} " ) , Settings . light_dimmer ) ;
}
RulesProcessEvent ( json_event ) ;
2019-10-23 12:11:53 +01:00
Rules . old_dimm = Settings . light_dimmer ;
2018-10-26 16:13:17 +01:00
}
2019-10-23 12:11:53 +01:00
else if ( Rules . event_data [ 0 ] ) {
2018-05-29 16:24:42 +01:00
char * event ;
char * parameter ;
2019-10-23 12:11:53 +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'
}
2018-06-25 17:00:20 +01:00
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Event \" :{ \" %s \" : \" %s \" }} " ) , event , parameter ) ;
2019-10-23 12:11:53 +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-10-23 12:11:53 +01:00
Rules . event_data [ 0 ] = ' \0 ' ;
2018-05-29 16:24:42 +01:00
}
}
2019-10-23 12:11:53 +01:00
else if ( Rules . vars_event | | Rules . mems_event ) {
if ( Rules . vars_event ) {
2019-07-05 12:44:24 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_VARS ; i + + ) {
2019-10-23 12:11:53 +01:00
if ( bitRead ( Rules . vars_event , i ) ) {
bitClear ( Rules . vars_event , i ) ;
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Var%d \" :{ \" State \" :%s}} " ) , i + 1 , rules_vars [ i ] ) ;
2019-07-05 12:44:24 +01:00
RulesProcessEvent ( json_event ) ;
break ;
}
2019-03-13 17:00:15 +00:00
}
}
2019-10-23 12:11:53 +01:00
if ( Rules . mems_event ) {
2019-07-05 12:44:24 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_MEMS ; i + + ) {
2019-10-23 12:11:53 +01:00
if ( bitRead ( Rules . mems_event , i ) ) {
bitClear ( Rules . mems_event , i ) ;
2019-07-05 12:44:24 +01:00
snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Mem%d \" :{ \" State \" :%s}} " ) , i + 1 , Settings . mems [ i ] ) ;
RulesProcessEvent ( json_event ) ;
break ;
}
2019-03-13 17:00:15 +00:00
}
}
}
2018-06-25 17:00:20 +01:00
else if ( rules_flag . data ) {
uint16_t mask = 1 ;
2019-07-05 12:44:24 +01:00
for ( uint32_t i = 0 ; i < MAX_RULES_FLAG ; i + + ) {
2018-06-25 17:00:20 +01:00
if ( rules_flag . data & mask ) {
rules_flag . data ^ = mask ;
json_event [ 0 ] = ' \0 ' ;
switch ( i ) {
case 0 : strncpy_P ( json_event , PSTR ( " { \" System \" :{ \" Boot \" :1}} " ) , sizeof ( json_event ) ) ; break ;
2019-03-13 17:00:15 +00:00
case 1 : snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Time \" :{ \" Initialized \" :%d}} " ) , MinutesPastMidnight ( ) ) ; break ;
case 2 : snprintf_P ( json_event , sizeof ( json_event ) , PSTR ( " { \" Time \" :{ \" Set \" :%d}} " ) , MinutesPastMidnight ( ) ) ; break ;
2018-06-25 17:00:20 +01:00
case 3 : strncpy_P ( json_event , PSTR ( " { \" MQTT \" :{ \" Connected \" :1}} " ) , sizeof ( json_event ) ) ; break ;
case 4 : strncpy_P ( json_event , PSTR ( " { \" MQTT \" :{ \" Disconnected \" :1}} " ) , sizeof ( json_event ) ) ; break ;
2018-07-28 14:06:31 +01:00
case 5 : strncpy_P ( json_event , PSTR ( " { \" WIFI \" :{ \" Connected \" :1}} " ) , sizeof ( json_event ) ) ; break ;
case 6 : strncpy_P ( json_event , PSTR ( " { \" WIFI \" :{ \" Disconnected \" :1}} " ) , sizeof ( json_event ) ) ; break ;
2019-07-05 12:44:24 +01:00
case 7 : strncpy_P ( json_event , PSTR ( " { \" HTTP \" :{ \" Initialized \" :1}} " ) , sizeof ( json_event ) ) ; break ;
2019-10-23 12:11:53 +01:00
# ifdef USE_SHUTTER
case 8 : strncpy_P ( json_event , PSTR ( " { \" SHUTTER \" :{ \" Moved \" :1}} " ) , sizeof ( json_event ) ) ; break ;
case 9 : strncpy_P ( json_event , PSTR ( " { \" SHUTTER \" :{ \" Moving \" :1}} " ) , sizeof ( json_event ) ) ; break ;
# endif // USE_SHUTTER
2018-06-25 17:00:20 +01:00
}
if ( json_event [ 0 ] ) {
RulesProcessEvent ( json_event ) ;
break ; // Only service one event within 50mS
}
}
mask < < = 1 ;
}
}
2018-08-26 16:10:18 +01:00
}
}
2018-12-15 14:55:51 +00:00
uint8_t rules_xsns_index = 0 ;
void RulesEvery100ms ( void )
2018-08-26 16:10:18 +01:00
{
2018-10-26 16:13:17 +01:00
if ( Settings . rule_enabled & & ( uptime > 4 ) ) { // Any rule enabled and allow 4 seconds start-up time for sensors (#3811)
2018-08-26 16:10:18 +01:00
mqtt_data [ 0 ] = ' \0 ' ;
2018-10-26 16:13:17 +01:00
int tele_period_save = tele_period ;
2018-12-15 14:55:51 +00:00
tele_period = 2 ; // Do not allow HA updates during next function call
XsnsNextCall ( FUNC_JSON_APPEND , rules_xsns_index ) ; // ,"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
2018-08-26 16:10:18 +01:00
tele_period = tele_period_save ;
if ( strlen ( mqtt_data ) ) {
2018-12-15 14:55:51 +00:00
mqtt_data [ 0 ] = ' { ' ; // {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
2019-07-05 12:44:24 +01:00
ResponseJsonEnd ( ) ;
2018-08-26 16:10:18 +01:00
RulesProcess ( ) ;
2018-04-13 16:42:11 +01:00
}
}
}
2018-12-15 14:55:51 +00:00
void RulesEverySecond ( void )
2018-04-13 16:42:11 +01:00
{
2018-05-24 13:25:52 +01:00
if ( Settings . rule_enabled ) { // Any rule enabled
2018-06-25 17:00:20 +01:00
char json_event [ 120 ] ;
2018-06-26 15:22:53 +01:00
if ( RtcTime . valid ) {
2019-10-23 12:11:53 +01:00
if ( ( uptime > 60 ) & & ( RtcTime . minute ! = Rules . last_minute ) ) { // Execute from one minute after restart every minute only once
Rules . last_minute = RtcTime . minute ;
2019-03-13 17:00: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 ) ;
}
}
2019-07-05 12:44:24 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_TIMERS ; i + + ) {
2019-10-23 12:11:53 +01:00
if ( Rules . timer [ i ] ! = 0L ) { // Timer active?
if ( TimeReached ( Rules . timer [ i ] ) ) { // Timer finished?
Rules . timer [ i ] = 0L ; // Turn off this timer
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-07-05 12:44:24 +01:00
void RulesSaveBeforeRestart ( void )
{
if ( Settings . rule_enabled ) { // Any rule enabled
char json_event [ 32 ] ;
strncpy_P ( json_event , PSTR ( " { \" System \" :{ \" Save \" :1}} " ) , sizeof ( json_event ) ) ;
RulesProcessEvent ( json_event ) ;
}
}
2018-12-15 14:55:51 +00:00
void RulesSetPower ( void )
2018-05-06 15:07:42 +01:00
{
2019-10-23 12:11:53 +01:00
Rules . new_power = XdrvMailbox . index ;
2018-05-06 15:07:42 +01:00
}
2018-12-15 14:55:51 +00:00
void RulesTeleperiod ( void )
2018-04-27 17:06:19 +01:00
{
2019-10-23 12:11:53 +01:00
Rules . teleperiod = true ;
2018-04-27 17:06:19 +01:00
RulesProcess ( ) ;
2019-10-23 12:11:53 +01:00
Rules . teleperiod = false ;
2018-04-27 17:06:19 +01:00
}
2019-03-13 17:00:15 +00:00
# ifdef SUPPORT_MQTT_EVENT
/********************************************************************************************/
/*
* 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 .
*/
bool RulesMqttData ( void )
{
2019-10-23 12:11:53 +01:00
if ( XdrvMailbox . data_len < 1 | | XdrvMailbox . data_len > 256 ) {
2019-03-13 17:00:15 +00:00
return false ;
}
2019-10-23 12:11:53 +01:00
bool serviced = false ;
2019-03-13 17:00:15 +00:00
String sTopic = XdrvMailbox . topic ;
String sData = XdrvMailbox . data ;
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: MQTT Topic %s, Event %s"), XdrvMailbox.topic, XdrvMailbox.data);
MQTT_Subscription event_item ;
//Looking for matched topic
2019-07-05 12:44:24 +01:00
for ( uint32_t index = 0 ; index < subscriptions . size ( ) ; index + + ) {
2019-03-13 17:00:15 +00:00
event_item = subscriptions . get ( index ) ;
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Match MQTT message Topic %s with subscription topic %s"), sTopic.c_str(), event_item.Topic.c_str());
if ( sTopic . startsWith ( event_item . Topic ) ) {
//This topic is subscribed by us, so serve it
serviced = true ;
String value ;
if ( event_item . Key . length ( ) = = 0 ) { //If did not specify Key
value = sData ;
} else { //If specified Key, need to parse Key/Value from JSON data
2019-10-23 12:11:53 +01:00
StaticJsonBuffer < 500 > jsonBuf ;
2019-03-13 17:00:15 +00:00
JsonObject & jsonData = jsonBuf . parseObject ( sData ) ;
String key1 = event_item . Key ;
String key2 ;
if ( ! jsonData . success ( ) ) break ; //Failed to parse JSON data, ignore this message.
int dot ;
if ( ( dot = key1 . indexOf ( ' . ' ) ) > 0 ) {
key2 = key1 . substring ( dot + 1 ) ;
key1 = key1 . substring ( 0 , dot ) ;
if ( ! jsonData [ key1 ] [ key2 ] . success ( ) ) break ; //Failed to get the key/value, ignore this message.
value = ( const char * ) jsonData [ key1 ] [ key2 ] ;
} else {
if ( ! jsonData [ key1 ] . success ( ) ) break ;
value = ( const char * ) jsonData [ key1 ] ;
}
}
value . trim ( ) ;
//Create an new event. Cannot directly call RulesProcessEvent().
2019-10-23 12:11:53 +01:00
snprintf_P ( Rules . event_data , sizeof ( Rules . event_data ) , PSTR ( " %s=%s " ) , event_item . Event . c_str ( ) , value . c_str ( ) ) ;
2019-03-13 17:00:15 +00:00
}
}
return serviced ;
}
/********************************************************************************************/
/*
* 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-10-23 12:11:53 +01:00
* XdrvMailbox . data - A char buffer with all the parameters
* XdrvMailbox . data_len - Length of the parameters
2019-03-13 17:00:15 +00:00
* Return :
* A string include subscribed event , topic and key .
*/
2019-10-23 12:11:53 +01:00
void CmndSubscribe ( void )
2019-03-13 17:00:15 +00:00
{
MQTT_Subscription subscription_item ;
String events ;
2019-10-23 12:11:53 +01:00
if ( XdrvMailbox . data_len > 0 ) {
char parameters [ XdrvMailbox . data_len + 1 ] ;
memcpy ( parameters , XdrvMailbox . data , XdrvMailbox . data_len ) ;
parameters [ XdrvMailbox . data_len ] = ' \0 ' ;
2019-03-13 17:00:15 +00:00
String event_name , topic , key ;
char * pos = strtok ( parameters , " , " ) ;
if ( pos ) {
event_name = Trim ( pos ) ;
2019-07-05 12:44:24 +01:00
pos = strtok ( nullptr , " , " ) ;
2019-03-13 17:00:15 +00:00
if ( pos ) {
topic = Trim ( pos ) ;
2019-07-05 12:44:24 +01:00
pos = strtok ( nullptr , " , " ) ;
2019-03-13 17:00:15 +00:00
if ( pos ) {
key = Trim ( pos ) ;
}
}
}
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Subscribe command with parameters: %s, %s, %s."), event_name.c_str(), topic.c_str(), key.c_str());
event_name . toUpperCase ( ) ;
if ( event_name . length ( ) > 0 & & topic . length ( ) > 0 ) {
//Search all subscriptions
2019-07-05 12:44:24 +01:00
for ( uint32_t index = 0 ; index < subscriptions . size ( ) ; index + + ) {
2019-03-13 17:00:15 +00:00
if ( subscriptions . get ( index ) . Event . equals ( event_name ) ) {
//If find exists one, remove it.
String stopic = subscriptions . get ( index ) . Topic + " /# " ;
MqttUnsubscribe ( stopic . c_str ( ) ) ;
subscriptions . remove ( index ) ;
break ;
}
}
//Add "/#" to the topic
if ( ! topic . endsWith ( " # " ) ) {
if ( topic . endsWith ( " / " ) ) {
topic . concat ( " # " ) ;
} else {
topic . concat ( " /# " ) ;
}
}
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: New topic: %s."), topic.c_str());
//MQTT Subscribe
subscription_item . Event = event_name ;
subscription_item . Topic = topic . substring ( 0 , topic . length ( ) - 2 ) ; //Remove "/#" so easy to match
subscription_item . Key = key ;
subscriptions . add ( subscription_item ) ;
MqttSubscribe ( topic . c_str ( ) ) ;
events . concat ( event_name + " , " + topic
+ ( key . length ( ) > 0 ? " , " : " " )
+ key ) ;
} else {
events = D_JSON_WRONG_PARAMETERS ;
}
} else {
//If did not specify the event name, list all subscribed event
2019-07-05 12:44:24 +01:00
for ( uint32_t index = 0 ; index < subscriptions . size ( ) ; index + + ) {
2019-03-13 17:00:15 +00:00
subscription_item = subscriptions . get ( index ) ;
events . concat ( subscription_item . Event + " , " + subscription_item . Topic
+ ( subscription_item . Key . length ( ) > 0 ? " , " : " " )
+ subscription_item . Key + " ; " ) ;
}
}
2019-10-23 12:11:53 +01:00
ResponseCmndChar ( events . c_str ( ) ) ;
2019-03-13 17:00:15 +00:00
}
/********************************************************************************************/
/*
* Unsubscribe specified MQTT event . If no event specified , Unsubscribe all .
* Command Unsubscribe format :
2019-10-23 12:11:53 +01:00
* UnSubscribe [ < event_name > ]
2019-03-13 17:00:15 +00:00
* Input :
2019-10-23 12:11:53 +01:00
* XdrvMailbox . data - Event name
* XdrvMailbox . data_len - Length of the parameters
2019-03-13 17:00:15 +00:00
* Return :
* list all the events unsubscribed .
*/
2019-10-23 12:11:53 +01:00
void CmndUnsubscribe ( void )
2019-03-13 17:00:15 +00:00
{
MQTT_Subscription subscription_item ;
String events ;
2019-10-23 12:11:53 +01:00
if ( XdrvMailbox . data_len > 0 ) {
2019-07-05 12:44:24 +01:00
for ( uint32_t index = 0 ; index < subscriptions . size ( ) ; index + + ) {
2019-03-13 17:00:15 +00:00
subscription_item = subscriptions . get ( index ) ;
2019-10-23 12:11:53 +01:00
if ( subscription_item . Event . equalsIgnoreCase ( XdrvMailbox . data ) ) {
2019-03-13 17:00:15 +00:00
String stopic = subscription_item . Topic + " /# " ;
MqttUnsubscribe ( stopic . c_str ( ) ) ;
events = subscription_item . Event ;
subscriptions . remove ( index ) ;
break ;
}
}
} else {
2019-10-23 12:11:53 +01:00
// If did not specify the event name, unsubscribe all event
2019-03-13 17:00:15 +00:00
String stopic ;
while ( subscriptions . size ( ) > 0 ) {
events . concat ( subscriptions . get ( 0 ) . Event + " ; " ) ;
stopic = subscriptions . get ( 0 ) . Topic + " /# " ;
MqttUnsubscribe ( stopic . c_str ( ) ) ;
subscriptions . remove ( 0 ) ;
}
}
2019-10-23 12:11:53 +01:00
ResponseCmndChar ( events . c_str ( ) ) ;
2019-03-13 17:00:15 +00:00
}
2019-10-23 12:11:53 +01:00
# endif // SUPPORT_MQTT_EVENT
2019-03-13 17:00:15 +00:00
# ifdef USE_EXPRESSION
2019-10-23 12:11:53 +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 {
return nullptr ;
}
}
2019-03-13 17:00:15 +00:00
/********************************************************************************************/
/*
* Parse a number value
* Input :
* pNumber - A char pointer point to a digit started string ( guaranteed )
2019-07-05 12:44:24 +01:00
* value - Reference a float variable used to accept the result
2019-03-13 17:00:15 +00:00
* Output :
* pNumber - Pointer forward to next character after the number
2019-07-05 12:44:24 +01:00
* value - float type , the result value
2019-03-13 17:00:15 +00:00
* Return :
* true - succeed
* false - failed
*/
2019-07-05 12:44:24 +01:00
bool findNextNumber ( char * & pNumber , float & value )
2019-03-13 17:00:15 +00:00
{
bool bSucceed = false ;
String sNumber = " " ;
2019-10-23 12:11:53 +01:00
if ( * pNumber = = ' - ' ) {
sNumber = " - " ;
pNumber + + ;
}
2019-03-13 17:00:15 +00:00
while ( * pNumber ) {
if ( isdigit ( * pNumber ) | | ( * pNumber = = ' . ' ) ) {
sNumber + = * pNumber ;
pNumber + + ;
} else {
break ;
}
}
if ( sNumber . length ( ) > 0 ) {
2019-07-05 12:44:24 +01:00
value = CharToFloat ( sNumber . c_str ( ) ) ;
2019-03-13 17:00:15 +00:00
bSucceed = true ;
}
return bSucceed ;
}
/********************************************************************************************/
/*
2019-07-05 12:44:24 +01:00
* Parse a variable ( like VAR1 , MEM3 ) and get its value ( float type )
2019-03-13 17:00:15 +00:00
* Input :
* pVarname - A char pointer point to a variable name string
2019-07-05 12:44:24 +01:00
* value - Reference a float variable used to accept the result
2019-03-13 17:00:15 +00:00
* Output :
* pVarname - Pointer forward to next character after the variable
2019-07-05 12:44:24 +01:00
* value - float type , the result value
2019-03-13 17:00:15 +00:00
* Return :
* true - succeed
* false - failed
*/
2019-07-05 12:44:24 +01:00
bool findNextVariableValue ( char * & pVarname , float & value )
2019-03-13 17:00:15 +00:00
{
bool succeed = true ;
value = 0 ;
String sVarName = " " ;
while ( * pVarname ) {
if ( isalpha ( * pVarname ) | | isdigit ( * pVarname ) ) {
sVarName . concat ( * pVarname ) ;
pVarname + + ;
} else {
break ;
}
}
sVarName . toUpperCase ( ) ;
if ( sVarName . startsWith ( F ( " VAR " ) ) ) {
int index = sVarName . substring ( 3 ) . toInt ( ) ;
if ( index > 0 & & index < = MAX_RULE_VARS ) {
2019-10-23 12:11:53 +01:00
value = CharToFloat ( rules_vars [ index - 1 ] ) ;
2019-03-13 17:00:15 +00:00
}
} else if ( sVarName . startsWith ( F ( " MEM " ) ) ) {
int index = sVarName . substring ( 3 ) . toInt ( ) ;
if ( index > 0 & & index < = MAX_RULE_MEMS ) {
2019-07-05 12:44:24 +01:00
value = CharToFloat ( Settings . mems [ index - 1 ] ) ;
2019-03-13 17:00: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 ( ) ;
# if defined(USE_TIMERS) && defined(USE_SUNRISE)
} else if ( sVarName . equals ( F ( " SUNRISE " ) ) ) {
value = SunMinutes ( 0 ) ;
} else if ( sVarName . equals ( F ( " SUNSET " ) ) ) {
value = SunMinutes ( 1 ) ;
# endif
} else {
succeed = false ;
}
return succeed ;
}
/********************************************************************************************/
/*
* Find next object in expression and evaluate it
* An object could be :
2019-10-23 12:11:53 +01:00
* - A float number start with a digit or minus , like 0.787 , - 3
2019-03-13 17:00:15 +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-05 12:44:24 +01:00
* value - Reference a float variable used to accept the result
2019-03-13 17:00:15 +00:00
* Output :
* pointer - Pointer forward to next character after next object
2019-07-05 12:44:24 +01:00
* value - float type , the result value
2019-03-13 17:00:15 +00:00
* Return :
* true - succeed
* false - failed
*/
2019-07-05 12:44:24 +01:00
bool findNextObjectValue ( char * & pointer , float & value )
2019-03-13 17:00:15 +00:00
{
bool bSucceed = false ;
while ( * pointer )
{
if ( isspace ( * pointer ) ) { //Skip leading spaces
pointer + + ;
continue ;
}
2019-10-23 12:11:53 +01:00
if ( isdigit ( * pointer ) | | ( * pointer ) = = ' - ' ) { //This object is a number
2019-03-13 17:00:15 +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-10-23 12:11:53 +01:00
char * closureBracket = findClosureBracket ( pointer ) ; //Get the position of closure bracket ")"
if ( closureBracket ! = nullptr ) {
value = evaluateExpression ( pointer + 1 , closureBracket - pointer - 1 ) ;
pointer = closureBracket + 1 ;
2019-03-13 17:00:15 +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 ;
while ( * pointer )
{
if ( isspace ( * pointer ) ) { //Skip leading spaces
pointer + + ;
continue ;
}
2019-10-23 12:11:53 +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 + + ;
2019-03-13 17:00:15 +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-05 12:44:24 +01:00
* value - Reference a float variable used to accept the result
2019-03-13 17:00:15 +00:00
* Output :
* pointer - Pointer forward to next character after next object
2019-07-05 12:44:24 +01:00
* value - float type , the result value
2019-03-13 17:00:15 +00:00
* Return :
* true - succeed
* false - failed
*/
2019-07-05 12:44:24 +01:00
float calculateTwoValues ( float v1 , float v2 , uint8_t op )
2019-03-13 17:00:15 +00:00
{
switch ( op )
{
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 :
return FastPrecisePow ( v1 , v2 ) ;
}
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 :
2019-07-05 12:44:24 +01:00
* float - result .
2019-03-13 17:00:15 +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
*/
2019-07-05 12:44:24 +01:00
float evaluateExpression ( const char * expression , unsigned int len )
2019-03-13 17:00:15 +00:00
{
char expbuf [ len + 1 ] ;
memcpy ( expbuf , expression , len ) ;
expbuf [ len ] = ' \0 ' ;
char * scan_pointer = expbuf ;
2019-07-05 12:44:24 +01:00
LinkedList < float > object_values ;
2019-03-13 17:00:15 +00:00
LinkedList < int8_t > operators ;
int8_t op ;
2019-07-05 12:44:24 +01:00
float va ;
2019-03-13 17:00:15 +00:00
//Find and add the value of first object
if ( findNextObjectValue ( scan_pointer , va ) ) {
object_values . add ( va ) ;
} else {
return 0 ;
}
while ( * scan_pointer )
{
if ( findNextOperator ( scan_pointer , op )
& & * scan_pointer
& & findNextObjectValue ( scan_pointer , va ) )
{
operators . add ( op ) ;
object_values . add ( va ) ;
} else {
//No operator followed or no more object after this operator, we done.
break ;
}
}
//Going to evaluate the whole expression
//Calculate by order of operator priorities. Looking for all operators with specified priority (from High to Low)
2019-07-05 12:44:24 +01:00
for ( int32_t priority = MAX_EXPRESSION_OPERATOR_PRIORITY ; priority > 0 ; priority - - ) {
2019-03-13 17:00:15 +00:00
int index = 0 ;
while ( index < operators . size ( ) ) {
2019-10-23 12:11:53 +01:00
if ( priority = = pgm_read_byte ( kExpressionOperatorsPriorities + operators . get ( index ) ) ) { //need to calculate the operator first
2019-03-13 17:00:15 +00:00
//get current object value and remove the next object with current operator
va = calculateTwoValues ( object_values . get ( index ) , object_values . remove ( index + 1 ) , operators . remove ( index ) ) ;
//Replace the current value with the result
object_values . set ( index , va ) ;
} else {
index + + ;
}
}
}
return object_values . get ( 0 ) ;
}
2019-10-23 12:11:53 +01:00
# endif // USE_EXPRESSION
2019-03-13 17:00:15 +00:00
2019-10-23 12:11:53 +01:00
# ifdef SUPPORT_IF_STATEMENT
void CmndIf ( )
2018-04-13 16:42:11 +01:00
{
2019-10-23 12:11:53 +01:00
if ( XdrvMailbox . data_len > 0 ) {
char parameters [ XdrvMailbox . data_len + 1 ] ;
memcpy ( parameters , XdrvMailbox . data , XdrvMailbox . data_len ) ;
parameters [ XdrvMailbox . data_len ] = ' \0 ' ;
ProcessIfStatement ( parameters ) ;
}
ResponseCmndDone ( ) ;
}
/********************************************************************************************/
/*
* 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 ;
}
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 ")"
if ( closureBracket ! = nullptr ) {
value = evaluateLogicalExpression ( pointer + 1 , closureBracket - pointer - 1 ) ;
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 ;
}
2018-04-13 16:42:11 +01:00
2019-10-23 12:11:53 +01:00
/********************************************************************************************/
/*
* 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
*/
bool evaluateLogicalExpression ( const char * expression , int len )
{
bool bResult = false ;
//Make a copy first
char expbuff [ len + 1 ] ;
memcpy ( expbuff , expression , len ) ;
expbuff [ len ] = ' \0 ' ;
//snprintf_P(log_data, sizeof(log_data), PSTR("EvalLogic: |%s|"), expbuff);
//AddLog(LOG_LEVEL_DEBUG);
char * pointer = expbuff ;
LinkedList < bool > values ;
LinkedList < int8_t > logicOperators ;
//Find first comparison expression
bool bValue ;
if ( findNextLogicObjectValue ( pointer , bValue ) ) {
values . add ( bValue ) ;
} else {
return false ;
2018-05-09 09:49:43 +01:00
}
2019-10-23 12:11:53 +01:00
int8_t op ;
while ( * pointer ) {
if ( findNextLogicOperator ( pointer , op )
& & ( * pointer ) & & findNextLogicObjectValue ( pointer , bValue ) )
{
logicOperators . add ( op ) ;
values . add ( bValue ) ;
} else {
break ;
}
}
//Calculate all "AND" first
int index = 0 ;
while ( index < logicOperators . size ( ) ) {
if ( logicOperators . get ( index ) = = LOGIC_OPERATOR_AND ) {
values . set ( index , values . get ( index ) & & values . get ( index + 1 ) ) ;
values . remove ( index + 1 ) ;
logicOperators . remove ( index ) ;
} else {
index + + ;
}
}
//Then, calculate all "OR"
index = 0 ;
while ( index < logicOperators . size ( ) ) {
if ( logicOperators . get ( index ) = = LOGIC_OPERATOR_OR ) {
values . set ( index , values . get ( index ) | | values . get ( index + 1 ) ) ;
values . remove ( index + 1 ) ;
logicOperators . remove ( index ) ;
} else {
index + + ;
}
}
return values . get ( 0 ) ;
}
/********************************************************************************************/
/*
* 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 ' ;
//snprintf_P(log_data, sizeof(log_data), PSTR("ExecCmd: |%s|"), cmdbuff);
//AddLog(LOG_LEVEL_DEBUG);
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
String sCurrentCommand = oneCommand ;
sCurrentCommand . trim ( ) ;
if ( sCurrentCommand . length ( ) > 0
& & backlog . size ( ) < MAX_BACKLOG & & ! backlog_mutex )
{
//Insert into backlog
backlog_mutex = true ;
backlog . add ( insertPosition , sCurrentCommand ) ;
backlog_mutex = false ;
insertPosition + + ;
}
}
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 + + ;
}
if ( 0 = = * pos ) { break ; }
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
/*********************************************************************************************\
* Commands
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CmndRule ( void )
{
uint8_t index = XdrvMailbox . index ;
if ( ( index > 0 ) & & ( index < = MAX_RULE_SETS ) ) {
2018-05-24 13:25:52 +01:00
if ( ( XdrvMailbox . data_len > 0 ) & & ( XdrvMailbox . data_len < sizeof ( Settings . rules [ index - 1 ] ) ) ) {
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
2018-05-24 13:25:52 +01:00
bitWrite ( Settings . rule_enabled , index - 1 , XdrvMailbox . payload ) ;
2018-04-13 16:42:11 +01:00
break ;
case 2 : // Toggle
2018-05-24 13:25:52 +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
2018-05-24 13:25:52 +01:00
bitWrite ( Settings . rule_once , index - 1 , XdrvMailbox . payload & 1 ) ;
2018-04-13 16:42:11 +01:00
break ;
case 6 : // Toggle
2018-05-24 13:25:52 +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
bitWrite ( Settings . rule_stop , index - 1 , XdrvMailbox . payload & 1 ) ;
break ;
case 10 : // Toggle
bitWrite ( Settings . rule_stop , index - 1 , bitRead ( Settings . rule_stop , index - 1 ) ^ 1 ) ;
break ;
2018-04-13 16:42:11 +01:00
}
} else {
2018-07-29 13:45:42 +01:00
int offset = 0 ;
if ( ' + ' = = XdrvMailbox . data [ 0 ] ) {
offset = strlen ( Settings . rules [ index - 1 ] ) ;
if ( XdrvMailbox . data_len < ( sizeof ( Settings . rules [ index - 1 ] ) - offset - 1 ) ) { // Check free space
XdrvMailbox . data [ 0 ] = ' ' ; // Remove + and make sure at least one space is inserted
} else {
offset = - 1 ; // Not enough space so skip it
}
}
if ( offset ! = - 1 ) {
strlcpy ( Settings . rules [ index - 1 ] + offset , ( ' " ' = = XdrvMailbox . data [ 0 ] ) ? " " : XdrvMailbox . data , sizeof ( Settings . rules [ index - 1 ] ) ) ;
}
2018-04-13 16:42:11 +01:00
}
2019-10-23 12:11:53 +01:00
Rules . triggers [ index - 1 ] = 0 ; // Reset once flag
2018-04-13 16:42:11 +01:00
}
2018-08-28 09:26:33 +01:00
snprintf_P ( mqtt_data , sizeof ( mqtt_data ) , PSTR ( " { \" %s%d \" : \" %s \" , \" Once \" : \" %s \" , \" StopOnError \" : \" %s \" , \" Free \" :%d, \" Rules \" : \" %s \" } " ) ,
2019-10-23 12:11:53 +01:00
XdrvMailbox . command , index , GetStateText ( bitRead ( Settings . rule_enabled , index - 1 ) ) , GetStateText ( bitRead ( Settings . rule_once , index - 1 ) ) ,
2018-08-28 09:26:33 +01:00
GetStateText ( bitRead ( Settings . rule_stop , index - 1 ) ) , sizeof ( Settings . rules [ index - 1 ] ) - strlen ( Settings . rules [ index - 1 ] ) - 1 , Settings . rules [ index - 1 ] ) ;
2018-04-13 16:42:11 +01:00
}
2019-10-23 12:11:53 +01:00
}
void CmndRuleTimer ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = MAX_RULE_TIMERS ) ) {
2018-04-13 16:42:11 +01:00
if ( XdrvMailbox . data_len > 0 ) {
2019-03-13 17:00:15 +00:00
# ifdef USE_EXPRESSION
2019-07-05 12:44:24 +01:00
float timer_set = evaluateExpression ( XdrvMailbox . data , XdrvMailbox . data_len ) ;
2019-10-23 12:11:53 +01:00
Rules . timer [ XdrvMailbox . index - 1 ] = ( timer_set > 0 ) ? millis ( ) + ( 1000 * timer_set ) : 0 ;
2019-03-13 17:00:15 +00:00
# else
2019-10-23 12:11:53 +01:00
Rules . timer [ XdrvMailbox . index - 1 ] = ( XdrvMailbox . payload > 0 ) ? millis ( ) + ( 1000 * XdrvMailbox . payload ) : 0 ;
# endif // USE_EXPRESSION
2018-04-13 16:42:11 +01:00
}
2018-08-24 17:22:04 +01:00
mqtt_data [ 0 ] = ' \0 ' ;
2019-07-05 12:44:24 +01:00
for ( uint32_t i = 0 ; i < MAX_RULE_TIMERS ; i + + ) {
2019-10-23 12:11:53 +01:00
ResponseAppend_P ( PSTR ( " %c \" T%d \" :%d " ) , ( i ) ? ' , ' : ' { ' , i + 1 , ( Rules . timer [ i ] ) ? ( Rules . timer [ i ] - millis ( ) ) / 1000 : 0 ) ;
2018-08-24 17:22:04 +01:00
}
2019-07-05 12:44:24 +01:00
ResponseJsonEnd ( ) ;
2018-04-13 16:42:11 +01:00
}
2019-10-23 12:11:53 +01:00
}
void CmndEvent ( void )
{
if ( XdrvMailbox . data_len > 0 ) {
strlcpy ( Rules . event_data , XdrvMailbox . data , sizeof ( Rules . event_data ) ) ;
2018-04-13 16:42:11 +01:00
}
2019-10-23 12:11:53 +01:00
ResponseCmndDone ( ) ;
}
void CmndVariable ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = MAX_RULE_VARS ) ) {
if ( ! XdrvMailbox . usridx ) {
mqtt_data [ 0 ] = ' \0 ' ;
for ( uint32_t i = 0 ; i < MAX_RULE_VARS ; i + + ) {
ResponseAppend_P ( PSTR ( " %c \" Var%d \" : \" %s \" " ) , ( i ) ? ' , ' : ' { ' , i + 1 , rules_vars [ i ] ) ;
}
ResponseJsonEnd ( ) ;
} else {
if ( XdrvMailbox . data_len > 0 ) {
2019-03-13 17:00:15 +00:00
# ifdef USE_EXPRESSION
2019-10-23 12:11:53 +01:00
if ( XdrvMailbox . data [ 0 ] = = ' = ' ) { // Spaces already been skipped in data
dtostrfd ( evaluateExpression ( XdrvMailbox . data + 1 , XdrvMailbox . data_len - 1 ) , Settings . flag2 . calc_resolution , rules_vars [ XdrvMailbox . index - 1 ] ) ;
} else {
strlcpy ( rules_vars [ XdrvMailbox . index - 1 ] , ( ' " ' = = XdrvMailbox . data [ 0 ] ) ? " " : XdrvMailbox . data , sizeof ( rules_vars [ XdrvMailbox . index - 1 ] ) ) ;
}
2019-03-13 17:00:15 +00:00
# else
2019-10-23 12:11:53 +01:00
strlcpy ( rules_vars [ XdrvMailbox . index - 1 ] , ( ' " ' = = XdrvMailbox . data [ 0 ] ) ? " " : XdrvMailbox . data , sizeof ( rules_vars [ XdrvMailbox . index - 1 ] ) ) ;
# endif // USE_EXPRESSION
bitSet ( Rules . vars_event , XdrvMailbox . index - 1 ) ;
}
ResponseCmndIdxChar ( rules_vars [ XdrvMailbox . index - 1 ] ) ;
2018-05-20 16:46:00 +01:00
}
}
2019-10-23 12:11:53 +01:00
}
void CmndMemory ( void )
{
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < = MAX_RULE_MEMS ) ) {
if ( ! XdrvMailbox . usridx ) {
mqtt_data [ 0 ] = ' \0 ' ;
for ( uint32_t i = 0 ; i < MAX_RULE_MEMS ; i + + ) {
ResponseAppend_P ( PSTR ( " %c \" Mem%d \" : \" %s \" " ) , ( i ) ? ' , ' : ' { ' , i + 1 , Settings . mems [ i ] ) ;
}
ResponseJsonEnd ( ) ;
} else {
if ( XdrvMailbox . data_len > 0 ) {
2019-03-13 17:00:15 +00:00
# ifdef USE_EXPRESSION
2019-10-23 12:11:53 +01:00
if ( XdrvMailbox . data [ 0 ] = = ' = ' ) { // Spaces already been skipped in data
dtostrfd ( evaluateExpression ( XdrvMailbox . data + 1 , XdrvMailbox . data_len - 1 ) , Settings . flag2 . calc_resolution , Settings . mems [ XdrvMailbox . index - 1 ] ) ;
} else {
strlcpy ( Settings . mems [ XdrvMailbox . index - 1 ] , ( ' " ' = = XdrvMailbox . data [ 0 ] ) ? " " : XdrvMailbox . data , sizeof ( Settings . mems [ XdrvMailbox . index - 1 ] ) ) ;
}
2019-03-13 17:00:15 +00:00
# else
2019-10-23 12:11:53 +01:00
strlcpy ( Settings . mems [ XdrvMailbox . index - 1 ] , ( ' " ' = = XdrvMailbox . data [ 0 ] ) ? " " : XdrvMailbox . data , sizeof ( Settings . mems [ XdrvMailbox . index - 1 ] ) ) ;
# endif // USE_EXPRESSION
bitSet ( Rules . mems_event , XdrvMailbox . index - 1 ) ;
}
ResponseCmndIdxChar ( Settings . mems [ XdrvMailbox . index - 1 ] ) ;
2018-05-20 16:46:00 +01:00
}
}
2019-10-23 12:11:53 +01:00
}
void CmndCalcResolution ( void )
{
if ( ( XdrvMailbox . payload > = 0 ) & & ( XdrvMailbox . payload < = 7 ) ) {
Settings . flag2 . calc_resolution = XdrvMailbox . payload ;
2018-12-15 14:55:51 +00:00
}
2019-10-23 12:11:53 +01:00
ResponseCmndNumber ( Settings . flag2 . calc_resolution ) ;
}
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-10-23 12:11:53 +01:00
float tempvar = CharToFloat ( rules_vars [ XdrvMailbox . index - 1 ] ) + CharToFloat ( XdrvMailbox . data ) ;
dtostrfd ( tempvar , Settings . flag2 . calc_resolution , rules_vars [ XdrvMailbox . index - 1 ] ) ;
bitSet ( Rules . vars_event , XdrvMailbox . index - 1 ) ;
2018-06-10 06:09:11 +01:00
}
2019-10-23 12:11:53 +01:00
ResponseCmndIdxChar ( rules_vars [ XdrvMailbox . index - 1 ] ) ;
2018-06-10 06:09:11 +01:00
}
2019-10-23 12:11:53 +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-10-23 12:11:53 +01:00
float tempvar = CharToFloat ( rules_vars [ XdrvMailbox . index - 1 ] ) - CharToFloat ( XdrvMailbox . data ) ;
dtostrfd ( tempvar , Settings . flag2 . calc_resolution , rules_vars [ XdrvMailbox . index - 1 ] ) ;
bitSet ( Rules . vars_event , XdrvMailbox . index - 1 ) ;
2018-06-10 06:09:11 +01:00
}
2019-10-23 12:11:53 +01:00
ResponseCmndIdxChar ( rules_vars [ XdrvMailbox . index - 1 ] ) ;
2018-06-10 06:09:11 +01:00
}
2019-10-23 12:11:53 +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-10-23 12:11:53 +01:00
float tempvar = CharToFloat ( rules_vars [ XdrvMailbox . index - 1 ] ) * CharToFloat ( XdrvMailbox . data ) ;
dtostrfd ( tempvar , Settings . flag2 . calc_resolution , rules_vars [ XdrvMailbox . index - 1 ] ) ;
bitSet ( Rules . vars_event , XdrvMailbox . index - 1 ) ;
2018-06-10 06:09:11 +01:00
}
2019-10-23 12:11:53 +01:00
ResponseCmndIdxChar ( rules_vars [ XdrvMailbox . index - 1 ] ) ;
2018-06-10 06:09:11 +01:00
}
2019-10-23 12:11:53 +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 ) {
2019-07-05 12:44:24 +01:00
if ( strstr ( XdrvMailbox . data , " , " ) ! = nullptr ) { // Process parameter entry
2018-07-16 11:37:49 +01:00
char sub_string [ XdrvMailbox . data_len + 1 ] ;
2019-07-05 12:44:24 +01:00
float valueIN = CharToFloat ( subStr ( sub_string , XdrvMailbox . data , " , " , 1 ) ) ;
float fromLow = CharToFloat ( subStr ( sub_string , XdrvMailbox . data , " , " , 2 ) ) ;
float fromHigh = CharToFloat ( subStr ( sub_string , XdrvMailbox . data , " , " , 3 ) ) ;
float toLow = CharToFloat ( subStr ( sub_string , XdrvMailbox . data , " , " , 4 ) ) ;
float toHigh = CharToFloat ( subStr ( sub_string , XdrvMailbox . data , " , " , 5 ) ) ;
float value = map_double ( valueIN , fromLow , fromHigh , toLow , toHigh ) ;
2019-10-23 12:11:53 +01:00
dtostrfd ( value , Settings . flag2 . calc_resolution , rules_vars [ XdrvMailbox . index - 1 ] ) ;
bitSet ( Rules . vars_event , XdrvMailbox . index - 1 ) ;
2018-06-10 06:09:11 +01:00
}
}
2019-10-23 12:11:53 +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-05 12:44:24 +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-10-23 12:11:53 +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
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-03-13 17:00:15 +00:00
bool Xdrv10 ( uint8_t function )
2018-04-13 16:42:11 +01:00
{
2019-03-13 17:00:15 +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-10-23 12:11:53 +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 ;
2019-07-05 12:44:24 +01:00
case FUNC_SAVE_BEFORE_RESTART :
RulesSaveBeforeRestart ( ) ;
break ;
2019-03-13 17:00:15 +00:00
# ifdef SUPPORT_MQTT_EVENT
case FUNC_MQTT_DATA :
result = RulesMqttData ( ) ;
break ;
2019-10-23 12:11:53 +01:00
# endif // SUPPORT_MQTT_EVENT
case FUNC_PRE_INIT :
RulesInit ( ) ;
break ;
2018-04-13 16:42:11 +01:00
}
return result ;
}
2019-07-05 12:44:24 +01:00
# endif // Do not USE_SCRIPT
2018-05-19 07:00:48 +01:00
# endif // USE_RULES