From 96098e4e099ad10c4a04b31802263bf1f10b0e76 Mon Sep 17 00:00:00 2001 From: Laurent Dong Date: Mon, 9 Sep 2019 11:24:27 -0400 Subject: [PATCH] Support If in Rules The major features of IF statement are: - Support IF, ELSEIF, ELSE - Support not only comparison but also using logical expression as condition - Support run multiple commands - Support nested IF statement - All the commands run by IF statement will go to the BACKLOG! - No limit for logical operators, parenthesis and nested IF statement. Depends on your RAM! Extended Backus-Naur Form of IF statement: ::= IF "(" ")" {ELSEIF "(" ")" } [ELSE ] ENDIF := | ( | ) {(AND | OR) } | "(" } ::= ("=" | "<" | ">" | "|" | "==" | "<=" | ">=" | "!=") ::= {";" } ::= | In English: If statement support 3 format: 1. IF () ENDIF 2. IF () ELSE ENDIF 3. IF () [ELSEIF () ]* ELSE ENDIF is a logical expression which can be: 1. A comparison expression for example: VAR1 >= 10 2. Multiple comparison expression with logical operator "AND" or "OR" between them. "AND" has higher priority than "OR". Fox example: UPTIME > 100 AND MEM1 == 1 OR MEM2 == 1 3. Parenthesis can be used to change the priority of logical expression. For example: UPTIME > 100 AND (MEM1 == 1 OR MEM2 == 1) can be: 1. A Sonoff-Tasmota command. For example: ledpower on 2. A IF statement ("IF .... ENDIF") 3. Multiple Sonoff-Tasmota command or IF statement split with ";". For example: Power1 off; Ledpower on; if (mem1 == 0) Var1 Var1+1; mem1==1 endif; Delay 10; POWER1 on 4. Do not need to lead with "BACKLOG" for multiple commands. --- sonoff/my_user_config.h | 1 + sonoff/sonoff.ino | 27 +- sonoff/support_command.ino | 21 +- sonoff/xdrv_10_rules.ino | 607 ++++++++++++++++++++++++++++++++++--- 4 files changed, 608 insertions(+), 48 deletions(-) diff --git a/sonoff/my_user_config.h b/sonoff/my_user_config.h index df8ecc06f..e289d8f93 100644 --- a/sonoff/my_user_config.h +++ b/sonoff/my_user_config.h @@ -302,6 +302,7 @@ //#define USE_SCRIPT_FATFS 4 // Script: Add FAT FileSystem Support // #define USE_EXPRESSION // Add support for expression evaluation in rules (+3k2 code, +64 bytes mem) +// #define SUPPORT_IF_STATEMENT // Add support for IF statement in rules (+4k2 code, -332 bytes mem) // #define SUPPORT_MQTT_EVENT // Support trigger event with MQTT subscriptions (+3k5 code) // -- Optional modules ---------------------------- diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index f6641e5ce..6a60ff49e 100755 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -120,8 +120,6 @@ int16_t save_data_counter; // Counter and flag for config save RulesBitfield rules_flag; // Rule state flags (16 bits) uint8_t state_250mS = 0; // State 250msecond per second flag uint8_t latching_relay_pulse = 0; // Latching relay pulse timer -uint8_t backlog_index = 0; // Command backlog index -uint8_t backlog_pointer = 0; // Command backlog pointer uint8_t sleep; // Current copy of Settings.sleep uint8_t blinkspeed = 1; // LED blink rate uint8_t pin[GPIO_MAX]; // Possible pin configurations @@ -166,7 +164,16 @@ char serial_in_buffer[INPUT_BUFFER_SIZE]; // Receive buffer char mqtt_data[MESSZ]; // MQTT publish buffer and web page ajax buffer char log_data[LOGSZ]; // Logging char web_log[WEB_LOG_SIZE] = {'\0'}; // Web log buffer -String backlog[MAX_BACKLOG]; // Command backlog +#ifdef SUPPORT_IF_STATEMENT + #include + LinkedList backlog; // Command backlog implemented with LinkedList + #define BACKLOG_EMPTY (backlog.size() == 0) +#else + uint8_t backlog_index = 0; // Command backlog index + uint8_t backlog_pointer = 0; // Command backlog pointer + String backlog[MAX_BACKLOG]; // Command backlog buffer + #define BACKLOG_EMPTY (backlog_pointer == backlog_index) +#endif /********************************************************************************************/ @@ -862,12 +869,16 @@ void Every100mSeconds(void) // Backlog if (TimeReached(backlog_delay)) { - if ((backlog_pointer != backlog_index) && !backlog_mutex) { + if (!BACKLOG_EMPTY && !backlog_mutex) { backlog_mutex = true; +#ifdef SUPPORT_IF_STATEMENT + ExecuteCommand((char*)backlog.shift().c_str(), SRC_BACKLOG); +#else ExecuteCommand((char*)backlog[backlog_pointer].c_str(), SRC_BACKLOG); - backlog_mutex = false; backlog_pointer++; if (backlog_pointer >= MAX_BACKLOG) { backlog_pointer = 0; } +#endif + backlog_mutex = false; } } } @@ -928,7 +939,7 @@ void Every250mSeconds(void) case 0: // Every x.0 second PerformEverySecond(); - if (ota_state_flag && (backlog_pointer == backlog_index)) { + if (ota_state_flag && BACKLOG_EMPTY) { ota_state_flag--; if (2 == ota_state_flag) { ota_url = Settings.ota_url; @@ -999,7 +1010,7 @@ void Every250mSeconds(void) if (MidnightNow()) { XsnsCall(FUNC_SAVE_AT_MIDNIGHT); } - if (save_data_counter && (backlog_pointer == backlog_index)) { + if (save_data_counter && BACKLOG_EMPTY) { save_data_counter--; if (save_data_counter <= 0) { if (Settings.flag.save_state) { @@ -1019,7 +1030,7 @@ void Every250mSeconds(void) save_data_counter = Settings.save_data; } } - if (restart_flag && (backlog_pointer == backlog_index)) { + if (restart_flag && BACKLOG_EMPTY) { if ((214 == restart_flag) || (215 == restart_flag) || (216 == restart_flag)) { char storage_wifi[sizeof(Settings.sta_ssid) + sizeof(Settings.sta_pwd)]; diff --git a/sonoff/support_command.ino b/sonoff/support_command.ino index 8497497ab..c181a70bf 100644 --- a/sonoff/support_command.ino +++ b/sonoff/support_command.ino @@ -215,10 +215,17 @@ void CommandHandler(char* topic, uint8_t* data, uint32_t data_len) void CmndBacklog(void) { if (XdrvMailbox.data_len) { + +#ifdef SUPPORT_IF_STATEMENT + char *blcommand = strtok(XdrvMailbox.data, ";"); + while ((blcommand != nullptr) && (backlog.size() < MAX_BACKLOG)) +#else uint32_t bl_pointer = (!backlog_pointer) ? MAX_BACKLOG -1 : backlog_pointer; bl_pointer--; char *blcommand = strtok(XdrvMailbox.data, ";"); - while ((blcommand != nullptr) && (backlog_index != bl_pointer)) { + while ((blcommand != nullptr) && (backlog_index != bl_pointer)) +#endif + { while(true) { blcommand = Trim(blcommand); if (!strncasecmp_P(blcommand, PSTR(D_CMND_BACKLOG), strlen(D_CMND_BACKLOG))) { @@ -228,17 +235,27 @@ void CmndBacklog(void) } } if (*blcommand != '\0') { +#ifdef SUPPORT_IF_STATEMENT + if (backlog.size() < MAX_BACKLOG) { + backlog.add(blcommand); + } +#else backlog[backlog_index] = String(blcommand); backlog_index++; if (backlog_index >= MAX_BACKLOG) backlog_index = 0; +#endif } blcommand = strtok(nullptr, ";"); } // ResponseCmndChar(D_JSON_APPENDED); mqtt_data[0] = '\0'; } else { - bool blflag = (backlog_pointer == backlog_index); + bool blflag = BACKLOG_EMPTY; +#ifdef SUPPORT_IF_STATEMENT + backlog.clear(); +#else backlog_pointer = backlog_index; +#endif ResponseCmndChar(blflag ? D_JSON_EMPTY : D_JSON_ABORTED); } } diff --git a/sonoff/xdrv_10_rules.ino b/sonoff/xdrv_10_rules.ino index 35e3c4cf0..d46900e5a 100644 --- a/sonoff/xdrv_10_rules.ino +++ b/sonoff/xdrv_10_rules.ino @@ -78,6 +78,7 @@ #define D_CMND_CALC_RESOLUTION "CalcRes" #define D_CMND_SUBSCRIBE "Subscribe" #define D_CMND_UNSUBSCRIBE "Unsubscribe" +#define D_CMND_IF "If" #define D_JSON_INITIATED "Initiated" @@ -106,6 +107,16 @@ const char kCompareOperators[] PROGMEM = "=\0>\0<\0|\0==!=>=<="; const uint8_t kExpressionOperatorsPriorities[] PROGMEM = {1, 1, 2, 2, 3, 4}; #define MAX_EXPRESSION_OPERATOR_PRIORITY 4 + + + #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 #endif // USE_EXPRESSION const char kRulesCommands[] PROGMEM = "|" // No prefix @@ -113,6 +124,9 @@ const char kRulesCommands[] PROGMEM = "|" // No prefix 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 ; @@ -121,6 +135,9 @@ void (* const RulesCommand[])(void) PROGMEM = { &CmndAddition, &CmndSubtract, &CmndMultiply, &CmndScale, &CmndCalcResolution #ifdef SUPPORT_MQTT_EVENT , &CmndSubscribe, &CmndUnsubscribe +#endif +#ifdef SUPPORT_IF_STATEMENT + , &CmndIf #endif }; @@ -183,22 +200,13 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) rule_task = rule.substring(5, pos); // "INA219" or "SYSTEM" } - String rule_name = rule.substring(pos +1); // "CURRENT>0.100" or "BOOT" or "%var1%" or "MINUTE|5" - - char compare_operator[3]; - int8_t compare = COMPARE_OPERATOR_NONE; - for (int32_t i = MAXIMUM_COMPARE_OPERATOR; i >= 0; i--) { - snprintf_P(compare_operator, sizeof(compare_operator), kCompareOperators + (i *2)); - if ((pos = rule_name.indexOf(compare_operator)) > 0) { - compare = i; - break; - } - } + 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 char rule_svalue[CMDSZ] = { 0 }; float rule_value = 0; - if (compare != COMPARE_OPERATOR_NONE) { - String rule_param = rule_name.substring(pos + strlen(compare_operator)); + if (compareOperator != COMPARE_OPERATOR_NONE) { for (uint32_t i = 0; i < MAX_RULE_VARS; i++) { snprintf_P(stemp, sizeof(stemp), PSTR("%%VAR%d%%"), i +1); if (rule_param.startsWith(stemp)) { @@ -244,7 +252,6 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) } else { rule_value = CharToFloat((char*)rule_svalue); // 0.1 - This saves 9k code over toFLoat()! } - rule_name = rule_name.substring(0, pos); // "CURRENT" } // Step2: Search rule_task and rule_name @@ -268,7 +275,7 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) value = CharToFloat((char*)str_value); int int_value = int(value); int int_rule_value = int(rule_value); - switch (compare) { + switch (compareOperator) { case COMPARE_OPERATOR_EXACT_DIVISION: match = (int_rule_value && (int_value % int_rule_value) == 0); break; @@ -313,6 +320,40 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) return match; } +/********************************************************************************************/ +/* + * 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; + 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; +} + /*******************************************************************************************/ bool RuleSetProcess(uint8_t rule_set, String &event_saved) @@ -389,7 +430,10 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved) // Response_P(S_JSON_COMMAND_SVALUE, D_CMND_RULE, D_JSON_INITIATED); // MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_RULE)); - +#ifdef SUPPORT_IF_STATEMENT + char *pCmd = command; + RulesPreprocessCommand(pCmd); //Do pre-process for IF statement +#endif ExecuteCommand(command, SRC_RULE); serviced = true; if (stop_all_rules) { return serviced; } // If BREAK was used, Stop execution of this rule set @@ -810,6 +854,43 @@ void CmndUnsubscribe(void) #endif // SUPPORT_MQTT_EVENT #ifdef USE_EXPRESSION +/********************************************************************************************/ +/* + * 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 NULL; + } +} + /********************************************************************************************/ /* * Parse a number value @@ -933,28 +1014,10 @@ bool findNextObjectValue(char * &pointer, float &value) bSucceed = findNextVariableValue(pointer, value); break; } else if (*pointer == '(') { //It is a sub expression bracketed with () - pointer++; - char * sub_exp_start = pointer; //Find out the sub expression between a pair of parenthesis. "()" - unsigned int sub_exp_len = 0; - //Look for the matched closure parenthesis.")" - bool bFindClosures = false; - uint8_t matchClosures = 1; - while (*pointer) - { - if (*pointer == ')') { - matchClosures--; - if (matchClosures == 0) { - sub_exp_len = pointer - sub_exp_start; - bFindClosures = true; - break; - } - } else if (*pointer == '(') { - matchClosures++; - } - pointer++; - } - if (bFindClosures) { - value = evaluateExpression(sub_exp_start, sub_exp_len); + char * closureBracket = findClosureBracket(pointer); //Get the position of closure bracket ")" + if (closureBracket != NULL) { + value = evaluateExpression(pointer+1, closureBracket - pointer - 2); + pointer = closureBracket + 1; bSucceed = true; } break; @@ -1113,6 +1176,474 @@ float evaluateExpression(const char * expression, unsigned int len) } #endif // USE_EXPRESSION +#ifdef SUPPORT_IF_STATEMENT +void CmndIf() +{ + 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); + } +} + +/********************************************************************************************/ +/* + * 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 != NULL) { + value = evaluateLogicalExpression(pointer+1, closureBracket - pointer - 2); + 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 + */ +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 values; + LinkedList logicOperators; + //Find first comparison expression + bool bValue; + if (findNextLogicObjectValue(pointer, bValue)) { + values.add(bValue); + } else { + return false; + } + 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 (NULL == *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 \*********************************************************************************************/