mirror of https://github.com/arendst/Tasmota.git
Merge pull request #6386 from laurentdong/SupportIfUpdated
Support if updated
This commit is contained in:
commit
b150decb92
|
@ -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 ----------------------------
|
||||
|
|
|
@ -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.h>
|
||||
LinkedList<String> 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)];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<bool> values;
|
||||
LinkedList<int8_t> 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
|
||||
\*********************************************************************************************/
|
||||
|
|
Loading…
Reference in New Issue