2018-04-13 16:42:11 +01:00
|
|
|
/*
|
|
|
|
xdrv_10_rules.ino - rule support for Sonoff-Tasmota
|
|
|
|
|
2019-01-01 12:55:01 +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-05-18 12:34:52 +01:00
|
|
|
#ifndef USE_SCRIPT
|
2018-04-13 16:42:11 +01:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* Rules based heavily on ESP Easy implementation
|
|
|
|
*
|
|
|
|
* Inspiration: https://github.com/letscontrolit/ESPEasy
|
|
|
|
*
|
|
|
|
* Add rules using the following, case insensitive, format:
|
|
|
|
* on <trigger1> do <commands> endon on <trigger2> do <commands> endon ..
|
|
|
|
*
|
|
|
|
* Examples:
|
|
|
|
* on System#Boot do Color 001000 endon
|
|
|
|
* on INA219#Current>0.100 do Dimmer 10 endon
|
|
|
|
* on INA219#Current>0.100 do Backlog Dimmer 10;Color 10,0,0 endon
|
|
|
|
* on INA219#Current>0.100 do Backlog Dimmer 10;Color 100000 endon on System#Boot do color 001000 endon
|
|
|
|
* on ds18b20#temperature>23 do power off endon on ds18b20#temperature<22 do power on endon
|
|
|
|
* on mqtt#connected do color 000010 endon
|
|
|
|
* on mqtt#disconnected do color 00100C 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 001008 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 000810;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-11-07 09:30:03 +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-11-27 00:22:44 +00:00
|
|
|
#define D_CMND_CALC_RESOLUTION "CalcRes"
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
#define D_CMND_SUBSCRIBE "Subscribe"
|
|
|
|
#define D_CMND_UNSUBSCRIBE "Unsubscribe"
|
2018-04-13 16:42:11 +01:00
|
|
|
|
|
|
|
#define D_JSON_INITIATED "Initiated"
|
|
|
|
|
2019-02-05 16:45:35 +00:00
|
|
|
#define COMPARE_OPERATOR_NONE -1
|
|
|
|
#define COMPARE_OPERATOR_EQUAL 0
|
|
|
|
#define COMPARE_OPERATOR_BIGGER 1
|
|
|
|
#define COMPARE_OPERATOR_SMALLER 2
|
2019-02-05 14:34:17 +00:00
|
|
|
#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
|
2019-02-05 16:45:35 +00:00
|
|
|
const char kCompareOperators[] PROGMEM = "=\0>\0<\0|\0==!=>=<=";
|
2019-02-05 14:34:17 +00:00
|
|
|
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
#ifdef USE_EXPRESSION
|
2019-02-16 15:17:17 +00:00
|
|
|
#include <LinkedList.h> // Import LinkedList library
|
|
|
|
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
const char kExpressionOperators[] PROGMEM = "+-*/%^";
|
|
|
|
#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-02-16 15:17:17 +00:00
|
|
|
#endif // USE_EXPRESSION
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
enum RulesCommands { CMND_RULE, CMND_RULETIMER, CMND_EVENT, CMND_VAR, CMND_MEM, CMND_ADD, CMND_SUB, CMND_MULT, CMND_SCALE, CMND_CALC_RESOLUTION, CMND_SUBSCRIBE, CMND_UNSUBSCRIBE };
|
|
|
|
const char kRulesCommands[] PROGMEM = 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 "|" D_CMND_SUBSCRIBE "|" D_CMND_UNSUBSCRIBE ;
|
|
|
|
|
|
|
|
#ifdef SUPPORT_MQTT_EVENT
|
|
|
|
#include <LinkedList.h> // Import LinkedList library
|
|
|
|
typedef struct {
|
|
|
|
String Event;
|
|
|
|
String Topic;
|
|
|
|
String Key;
|
|
|
|
} MQTT_Subscription;
|
|
|
|
LinkedList<MQTT_Subscription> subscriptions;
|
|
|
|
#endif //SUPPORT_MQTT_EVENT
|
2018-04-13 16:42:11 +01:00
|
|
|
|
|
|
|
String rules_event_value;
|
|
|
|
unsigned long rules_timer[MAX_RULE_TIMERS] = { 0 };
|
|
|
|
uint8_t rules_quota = 0;
|
2018-05-06 15:07:42 +01:00
|
|
|
long rules_new_power = -1;
|
|
|
|
long rules_old_power = -1;
|
2018-10-01 17:13:47 +01:00
|
|
|
long rules_old_dimm = -1;
|
2018-04-13 16:42:11 +01:00
|
|
|
|
2018-05-24 13:25:52 +01:00
|
|
|
uint32_t rules_triggers[MAX_RULE_SETS] = { 0 };
|
2018-06-26 15:22:53 +01:00
|
|
|
uint16_t rules_last_minute = 60;
|
2018-05-24 13:25:52 +01:00
|
|
|
uint8_t rules_trigger_count[MAX_RULE_SETS] = { 0 };
|
2018-04-27 17:06:19 +01:00
|
|
|
uint8_t rules_teleperiod = 0;
|
2018-04-13 16:42:11 +01:00
|
|
|
|
2018-05-29 16:24:42 +01:00
|
|
|
char event_data[100];
|
2018-12-21 15:17:06 +00:00
|
|
|
char vars[MAX_RULE_VARS][33] = { 0 };
|
2019-01-15 15:30:20 +00:00
|
|
|
#if (MAX_RULE_VARS>16)
|
|
|
|
#error MAX_RULE_VARS is bigger than 16
|
2019-01-04 19:26:37 +00:00
|
|
|
#endif
|
2019-01-15 15:30:20 +00:00
|
|
|
#if (MAX_RULE_MEMS>5)
|
|
|
|
#error MAX_RULE_MEMS is bigger than 5
|
2019-01-04 19:26:37 +00:00
|
|
|
#endif
|
2019-01-15 15:30:20 +00:00
|
|
|
uint16_t vars_event = 0;
|
2019-01-04 19:26:37 +00:00
|
|
|
uint8_t mems_event = 0;
|
2018-05-19 07:00:48 +01:00
|
|
|
|
2018-04-13 16:42:11 +01:00
|
|
|
/*******************************************************************************************/
|
|
|
|
|
2019-01-28 13:08:33 +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"
|
2018-04-27 17:06:19 +01:00
|
|
|
if (rules_teleperiod) {
|
|
|
|
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"
|
|
|
|
}
|
|
|
|
|
2018-08-23 15:05:51 +01:00
|
|
|
String rule_name = rule.substring(pos +1); // "CURRENT>0.100" or "BOOT" or "%var1%" or "MINUTE|5"
|
2018-04-13 16:42:11 +01:00
|
|
|
|
2019-02-05 16:45:35 +00:00
|
|
|
char compare_operator[3];
|
2019-02-05 14:34:17 +00:00
|
|
|
int8_t compare = COMPARE_OPERATOR_NONE;
|
2019-02-05 16:45:35 +00:00
|
|
|
for (int8_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) {
|
2019-02-05 14:34:17 +00:00
|
|
|
compare = i;
|
|
|
|
break;
|
2018-04-13 16:42:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-24 16:08:14 +01:00
|
|
|
char rule_svalue[CMDSZ] = { 0 };
|
2018-04-13 16:42:11 +01:00
|
|
|
double rule_value = 0;
|
2019-02-05 14:34:17 +00:00
|
|
|
if (compare != COMPARE_OPERATOR_NONE) {
|
2019-02-05 16:45:35 +00:00
|
|
|
String rule_param = rule_name.substring(pos + strlen(compare_operator));
|
2019-01-28 13:08:33 +00:00
|
|
|
for (uint8_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)) {
|
|
|
|
rule_param = vars[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-01-28 13:08:33 +00:00
|
|
|
for (uint8_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-02-19 13:49: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-02-19 13:49:15 +00:00
|
|
|
rule_param = String(MinutesUptime());
|
2018-08-27 08:59:17 +01:00
|
|
|
}
|
2018-12-27 17:57:27 +00:00
|
|
|
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-02-19 13:49: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-02-19 13:49: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-08 18:24:02 +00:00
|
|
|
strlcpy(rule_svalue, rule_param.c_str(), sizeof(rule_svalue));
|
2018-05-20 16:46:00 +01:00
|
|
|
|
2018-05-24 16:08:14 +01:00
|
|
|
int temp_value = GetStateNumber(rule_svalue);
|
2018-05-06 15:07:42 +01:00
|
|
|
if (temp_value > -1) {
|
|
|
|
rule_value = temp_value;
|
|
|
|
} else {
|
2018-05-24 16:08:14 +01:00
|
|
|
rule_value = CharToDouble((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
|
|
|
rule_name = rule_name.substring(0, pos); // "CURRENT"
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
double value = 0;
|
|
|
|
const char* str_value = root[rule_task][rule_name];
|
|
|
|
|
2019-03-08 14:15:42 +00:00
|
|
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Task %s, Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"),
|
2018-05-24 16:08:14 +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
|
|
|
|
|
2018-04-14 13:39:16 +01:00
|
|
|
rules_event_value = str_value; // Prepare %value%
|
2018-04-13 16:42:11 +01:00
|
|
|
|
|
|
|
// Step 3: Compare rule (value)
|
|
|
|
if (str_value) {
|
|
|
|
value = CharToDouble((char*)str_value);
|
2018-08-13 12:09:22 +01:00
|
|
|
int int_value = int(value);
|
|
|
|
int int_rule_value = int(rule_value);
|
2018-04-13 16:42:11 +01:00
|
|
|
switch (compare) {
|
2019-02-05 14:34:17 +00:00
|
|
|
case COMPARE_OPERATOR_EXACT_DIVISION:
|
|
|
|
match = (int_rule_value && (int_value % int_rule_value) == 0);
|
|
|
|
break;
|
|
|
|
case COMPARE_OPERATOR_EQUAL:
|
|
|
|
match = (!strcasecmp(str_value, rule_svalue)); // Compare strings - this also works for hexadecimals
|
|
|
|
break;
|
|
|
|
case COMPARE_OPERATOR_BIGGER:
|
|
|
|
match = (value > rule_value);
|
|
|
|
break;
|
|
|
|
case COMPARE_OPERATOR_SMALLER:
|
|
|
|
match = (value < rule_value);
|
2018-08-13 12:09:22 +01:00
|
|
|
break;
|
2019-02-05 14:34:17 +00:00
|
|
|
case COMPARE_OPERATOR_NUMBER_EQUAL:
|
|
|
|
match = (value == rule_value);
|
2018-04-13 16:42:11 +01:00
|
|
|
break;
|
2019-02-05 14:34:17 +00:00
|
|
|
case COMPARE_OPERATOR_NOT_EQUAL:
|
|
|
|
match = (value != rule_value);
|
2018-04-13 16:42:11 +01:00
|
|
|
break;
|
2019-02-05 14:34:17 +00:00
|
|
|
case COMPARE_OPERATOR_BIGGER_EQUAL:
|
|
|
|
match = (value >= rule_value);
|
2018-04-13 16:42:11 +01:00
|
|
|
break;
|
2019-02-05 14:34:17 +00:00
|
|
|
case COMPARE_OPERATOR_SMALLER_EQUAL:
|
|
|
|
match = (value <= rule_value);
|
2018-04-13 16:42:11 +01:00
|
|
|
break;
|
2019-02-05 14:34:17 +00:00
|
|
|
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
|
2018-05-24 13:25:52 +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 {
|
2018-05-24 13:25:52 +01:00
|
|
|
bitClear(rules_triggers[rule_set], rules_trigger_count[rule_set]);
|
2018-04-13 16:42:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return match;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*******************************************************************************************/
|
|
|
|
|
2019-01-28 13:08:33 +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-08 14:15:42 +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
|
|
|
|
2018-05-24 13:25:52 +01:00
|
|
|
rules_trigger_count[rule_set] = 0;
|
2018-04-13 16:42:11 +01:00
|
|
|
int plen = 0;
|
2018-12-01 21:12:33 +00:00
|
|
|
int plen2 = 0;
|
|
|
|
bool stop_all_rules = false;
|
2018-04-13 16:42:11 +01:00
|
|
|
while (true) {
|
2018-04-17 14:34:18 +01:00
|
|
|
rules = rules.substring(plen); // Select relative to last rule
|
2018-04-13 16:42:11 +01:00
|
|
|
rules.trim();
|
2018-04-17 14:34:18 +01:00
|
|
|
if (!rules.length()) { return serviced; } // No more rules
|
2018-04-13 16:42:11 +01:00
|
|
|
|
|
|
|
String rule = rules;
|
2018-04-17 14:34:18 +01:00
|
|
|
rule.toUpperCase(); // "ON INA219#CURRENT>0.100 DO BACKLOG DIMMER 10;COLOR 100000 ENDON"
|
|
|
|
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-01 21:12:33 +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; }
|
2018-12-01 22:00:34 +00:00
|
|
|
plen = tmin(plen, plen2);
|
2018-12-01 21:12:33 +00:00
|
|
|
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;
|
2018-05-13 16:38:44 +01:00
|
|
|
rules_event_value = "";
|
|
|
|
String event = event_saved;
|
2018-04-13 16:42:11 +01:00
|
|
|
|
2019-03-08 14:15:42 +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
|
|
|
|
commands.replace(F("%value%"), rules_event_value);
|
2019-01-28 13:08:33 +00:00
|
|
|
for (uint8_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);
|
|
|
|
commands.replace(stemp, vars[i]);
|
|
|
|
}
|
2019-01-28 13:08:33 +00:00
|
|
|
for (uint8_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-02-19 13:49:15 +00:00
|
|
|
commands.replace(F("%time%"), String(MinutesPastMidnight()));
|
|
|
|
commands.replace(F("%uptime%"), String(MinutesUptime()));
|
2018-12-28 15:35:19 +00:00
|
|
|
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-02-19 13:49: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-08 18:24:02 +00:00
|
|
|
strlcpy(command, commands.c_str(), sizeof(command));
|
2018-04-13 16:42:11 +01:00
|
|
|
|
2019-03-08 14:15:42 +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-03-23 16:00:59 +00:00
|
|
|
// Response_P(S_JSON_COMMAND_SVALUE, D_CMND_RULE, D_JSON_INITIATED);
|
2018-05-20 16:46:00 +01:00
|
|
|
// MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_RULE));
|
2018-04-13 16:42:11 +01:00
|
|
|
|
2018-05-28 14:52:42 +01:00
|
|
|
ExecuteCommand(command, SRC_RULE);
|
2018-04-13 16:42:11 +01:00
|
|
|
serviced = true;
|
2018-12-01 21:12:33 +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
|
|
|
}
|
2018-05-24 13:25:52 +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-11 09:38:41 +00:00
|
|
|
#ifdef USE_DEBUG_DRIVER
|
2018-06-26 10:48:09 +01:00
|
|
|
ShowFreeMem(PSTR("RulesProcessEvent"));
|
2019-03-11 09:38:41 +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-05-15 15:31:13 +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-08 14:15:42 +00:00
|
|
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Event %s"), event_saved.c_str());
|
2018-06-28 11:25:50 +01:00
|
|
|
|
2019-01-28 13:08:33 +00:00
|
|
|
for (uint8_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-11-14 13:32:09 +00:00
|
|
|
bool RulesProcess(void)
|
2018-06-25 17:00:20 +01:00
|
|
|
{
|
|
|
|
return RulesProcessEvent(mqtt_data);
|
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +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-01-28 13:08:33 +00:00
|
|
|
for (uint8_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
|
|
|
}
|
2018-04-27 17:06:19 +01:00
|
|
|
rules_teleperiod = 0;
|
2018-04-13 16:42:11 +01:00
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void RulesEvery50ms(void)
|
2018-04-13 16:42:11 +01:00
|
|
|
{
|
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-09-23 12:55:42 +01:00
|
|
|
if (-1 == rules_new_power) { rules_new_power = power; }
|
2018-05-06 15:07:42 +01:00
|
|
|
if (rules_new_power != rules_old_power) {
|
|
|
|
if (rules_old_power != -1) {
|
2019-01-28 13:08:33 +00:00
|
|
|
for (uint8_t i = 0; i < devices_present; i++) {
|
2018-05-06 15:07:42 +01:00
|
|
|
uint8_t new_state = (rules_new_power >> i) &1;
|
|
|
|
if (new_state != ((rules_old_power >> i) &1)) {
|
2018-06-25 17:00:20 +01:00
|
|
|
snprintf_P(json_event, sizeof(json_event), PSTR("{\"Power%d\":{\"State\":%d}}"), i +1, new_state);
|
|
|
|
RulesProcessEvent(json_event);
|
2018-05-06 15:07:42 +01:00
|
|
|
}
|
|
|
|
}
|
2018-09-27 03:02:55 +01:00
|
|
|
} else {
|
|
|
|
// Boot time POWER OUTPUTS (Relays) Status
|
2019-01-28 13:08:33 +00:00
|
|
|
for (uint8_t i = 0; i < devices_present; i++) {
|
2018-09-27 03:02:55 +01:00
|
|
|
uint8_t new_state = (rules_new_power >> i) &1;
|
|
|
|
snprintf_P(json_event, sizeof(json_event), PSTR("{\"Power%d\":{\"Boot\":%d}}"), i +1, new_state);
|
|
|
|
RulesProcessEvent(json_event);
|
|
|
|
}
|
|
|
|
// Boot time SWITCHES Status
|
2019-01-28 13:08:33 +00:00
|
|
|
for (uint8_t i = 0; i < MAX_SWITCHES; i++) {
|
2018-09-27 03:02:55 +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) {
|
|
|
|
#endif // USE_TM1638
|
2019-01-28 13:08:33 +00:00
|
|
|
bool swm = ((FOLLOW_INV == Settings.switchmode[i]) || (PUSHBUTTON_INV == Settings.switchmode[i]) || (PUSHBUTTONHOLD_INV == Settings.switchmode[i]));
|
2018-12-28 15:35:19 +00:00
|
|
|
snprintf_P(json_event, sizeof(json_event), PSTR("{\"" D_JSON_SWITCH "%d\":{\"Boot\":%d}}"), i +1, (swm ^ SwitchLastState(i)));
|
2018-09-27 03:02:55 +01:00
|
|
|
RulesProcessEvent(json_event);
|
|
|
|
}
|
|
|
|
}
|
2018-05-06 15:07:42 +01:00
|
|
|
}
|
|
|
|
rules_old_power = rules_new_power;
|
2018-05-29 16:24:42 +01:00
|
|
|
}
|
2018-10-01 17:13:47 +01:00
|
|
|
else if (rules_old_dimm != Settings.light_dimmer) {
|
|
|
|
if (rules_old_dimm != -1) {
|
|
|
|
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);
|
|
|
|
rules_old_dimm = Settings.light_dimmer;
|
|
|
|
}
|
2018-06-25 17:00:20 +01:00
|
|
|
else if (event_data[0]) {
|
2018-05-29 16:24:42 +01:00
|
|
|
char *event;
|
|
|
|
char *parameter;
|
|
|
|
event = strtok_r(event_data, "=", ¶meter); // event_data = fanspeed=10
|
|
|
|
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);
|
2018-05-29 16:24:42 +01:00
|
|
|
event_data[0] ='\0';
|
2018-06-25 17:00:20 +01:00
|
|
|
RulesProcessEvent(json_event);
|
2018-05-29 16:24:42 +01:00
|
|
|
} else {
|
|
|
|
event_data[0] ='\0';
|
|
|
|
}
|
|
|
|
}
|
2019-03-29 08:42:50 +00:00
|
|
|
else if (vars_event || mems_event){
|
|
|
|
if (vars_event) {
|
2019-04-08 17:27:08 +01:00
|
|
|
for (uint8_t i = 0; i < MAX_RULE_VARS; i++) {
|
2019-03-29 08:42:50 +00:00
|
|
|
if (bitRead(vars_event, i)) {
|
|
|
|
bitClear(vars_event, i);
|
|
|
|
snprintf_P(json_event, sizeof(json_event), PSTR("{\"Var%d\":{\"State\":%s}}"), i+1, vars[i]);
|
|
|
|
RulesProcessEvent(json_event);
|
|
|
|
break;
|
|
|
|
}
|
2019-01-04 19:26:37 +00:00
|
|
|
}
|
|
|
|
}
|
2019-03-29 08:42:50 +00:00
|
|
|
if (mems_event) {
|
2019-04-08 17:27:08 +01:00
|
|
|
for (uint8_t i = 0; i < MAX_RULE_MEMS; i++) {
|
2019-03-29 08:42:50 +00:00
|
|
|
if (bitRead(mems_event, i)) {
|
|
|
|
bitClear(mems_event, i);
|
|
|
|
snprintf_P(json_event, sizeof(json_event), PSTR("{\"Mem%d\":{\"State\":%s}}"), i+1, Settings.mems[i]);
|
|
|
|
RulesProcessEvent(json_event);
|
|
|
|
break;
|
|
|
|
}
|
2019-01-04 19:26:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-06-25 17:00:20 +01:00
|
|
|
else if (rules_flag.data) {
|
|
|
|
uint16_t mask = 1;
|
2019-01-28 13:08:33 +00:00
|
|
|
for (uint8_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-02-19 13:49: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;
|
Add command WebColor
* Add rule Http#Initialized
* Add command WebColor to change non-persistent GUI colors on the fly
Use a rule like:
rule3 on http#initialized do webcolor {"webcolor":["#eeeeee","#181818","#4f4f4f","#000000","#dddddd","#008000","#222222","#ff0000","#008000","#ffffff","#1fa3ec","#0e70a4","#d43535","#931f1f","#47c266","#5aaf6f","#ffffff","#999999","#000000"]} endon
or
rule3 on http#initialized do webcolor {"webcolor":["#eee","#181818","#4f4f4f","#000","#ddd","#008000","#222"]} endon
to make color changes persistent)
2019-04-08 21:37:39 +01:00
|
|
|
case 7: strncpy_P(json_event, PSTR("{\"HTTP\":{\"Initialized\":1}}"), sizeof(json_event)); break;
|
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-11-18 14:33:13 +00:00
|
|
|
uint8_t rules_xsns_index = 0;
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void RulesEvery100ms(void)
|
2018-08-26 16:10:18 +01:00
|
|
|
{
|
2018-09-26 10:56:58 +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-09-08 16:18:31 +01:00
|
|
|
int tele_period_save = tele_period;
|
2018-11-18 14:33:13 +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-11-18 14:33:13 +00:00
|
|
|
mqtt_data[0] = '{'; // {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
|
Add support for Shelly 1PM Template
Add support for Shelly 1PM Template {"NAME":"Shelly 1PM","GPIO":[56,0,0,0,82,134,0,0,0,0,0,21,0],"FLAG":2,"BASE":18} (#5716)
2019-05-13 17:26:07 +01:00
|
|
|
ResponseJsonEnd();
|
2018-08-26 16:10:18 +01:00
|
|
|
RulesProcess();
|
2018-04-13 16:42:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void RulesEverySecond(void)
|
2018-04-13 16:42:11 +01:00
|
|
|
{
|
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) {
|
|
|
|
if ((uptime > 60) && (RtcTime.minute != rules_last_minute)) { // Execute from one minute after restart every minute only once
|
|
|
|
rules_last_minute = RtcTime.minute;
|
2019-02-19 13:49:15 +00:00
|
|
|
snprintf_P(json_event, sizeof(json_event), PSTR("{\"Time\":{\"Minute\":%d}}"), MinutesPastMidnight());
|
2018-06-26 15:22:53 +01:00
|
|
|
RulesProcessEvent(json_event);
|
|
|
|
}
|
|
|
|
}
|
2019-01-28 13:08:33 +00:00
|
|
|
for (uint8_t i = 0; i < MAX_RULE_TIMERS; i++) {
|
2018-04-13 16:42:11 +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-05-19 11:42:10 +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-11-14 13:32:09 +00:00
|
|
|
void RulesSetPower(void)
|
2018-05-06 15:07:42 +01:00
|
|
|
{
|
|
|
|
rules_new_power = XdrvMailbox.index;
|
|
|
|
}
|
|
|
|
|
2018-11-14 13:32:09 +00:00
|
|
|
void RulesTeleperiod(void)
|
2018-04-27 17:06:19 +01:00
|
|
|
{
|
|
|
|
rules_teleperiod = 1;
|
|
|
|
RulesProcess();
|
|
|
|
rules_teleperiod = 0;
|
|
|
|
}
|
|
|
|
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
#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)
|
|
|
|
{
|
|
|
|
bool serviced = false;
|
|
|
|
if (XdrvMailbox.data_len < 1 || XdrvMailbox.data_len > 128) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
String sTopic = XdrvMailbox.topic;
|
|
|
|
String sData = XdrvMailbox.data;
|
2019-03-08 14:15:42 +00:00
|
|
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: MQTT Topic %s, Event %s"), XdrvMailbox.topic, XdrvMailbox.data);
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
MQTT_Subscription event_item;
|
|
|
|
//Looking for matched topic
|
|
|
|
for (int index = 0; index < subscriptions.size(); index++) {
|
|
|
|
event_item = subscriptions.get(index);
|
|
|
|
|
2019-03-08 14:15:42 +00:00
|
|
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Match MQTT message Topic %s with subscription topic %s"), sTopic.c_str(), event_item.Topic.c_str());
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
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
|
|
|
|
StaticJsonBuffer<400> jsonBuf;
|
|
|
|
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().
|
|
|
|
snprintf_P(event_data, sizeof(event_data), PSTR("%s=%s"), event_item.Event.c_str(), value.c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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:
|
|
|
|
* data - A char buffer with all the parameters
|
|
|
|
* data_len - Length of the parameters
|
|
|
|
* Return:
|
|
|
|
* A string include subscribed event, topic and key.
|
|
|
|
*/
|
|
|
|
String RulesSubscribe(const char *data, int data_len)
|
|
|
|
{
|
|
|
|
MQTT_Subscription subscription_item;
|
|
|
|
String events;
|
|
|
|
if (data_len > 0) {
|
|
|
|
char parameters[data_len+1];
|
|
|
|
memcpy(parameters, data, data_len);
|
|
|
|
parameters[data_len] = '\0';
|
|
|
|
String event_name, topic, key;
|
|
|
|
|
|
|
|
char * pos = strtok(parameters, ",");
|
|
|
|
if (pos) {
|
|
|
|
event_name = Trim(pos);
|
2019-03-26 17:26:50 +00:00
|
|
|
pos = strtok(nullptr, ",");
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
if (pos) {
|
|
|
|
topic = Trim(pos);
|
2019-03-26 17:26:50 +00:00
|
|
|
pos = strtok(nullptr, ",");
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
if (pos) {
|
|
|
|
key = Trim(pos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-08 14:15:42 +00:00
|
|
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Subscribe command with parameters: %s, %s, %s."), event_name.c_str(), topic.c_str(), key.c_str());
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
event_name.toUpperCase();
|
|
|
|
if (event_name.length() > 0 && topic.length() > 0) {
|
|
|
|
//Search all subscriptions
|
|
|
|
for (int index=0; index < subscriptions.size(); index++) {
|
|
|
|
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("/#");
|
|
|
|
}
|
|
|
|
}
|
2019-03-08 14:15:42 +00:00
|
|
|
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: New topic: %s."), topic.c_str());
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
//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
|
|
|
|
for (int index=0; index < subscriptions.size(); index++) {
|
|
|
|
subscription_item = subscriptions.get(index);
|
|
|
|
events.concat(subscription_item.Event + "," + subscription_item.Topic
|
|
|
|
+ (subscription_item.Key.length()>0 ? "," : "")
|
|
|
|
+ subscription_item.Key + "; ");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return events;
|
|
|
|
}
|
|
|
|
|
|
|
|
/********************************************************************************************/
|
|
|
|
/*
|
|
|
|
* Unsubscribe specified MQTT event. If no event specified, Unsubscribe all.
|
|
|
|
* Command Unsubscribe format:
|
|
|
|
* Unsubscribe [<event_name>]
|
|
|
|
* Input:
|
|
|
|
* data - Event name
|
|
|
|
* data_len - Length of the parameters
|
|
|
|
* Return:
|
|
|
|
* list all the events unsubscribed.
|
|
|
|
*/
|
|
|
|
String RulesUnsubscribe(const char * data, int data_len)
|
|
|
|
{
|
|
|
|
MQTT_Subscription subscription_item;
|
|
|
|
String events;
|
|
|
|
if (data_len > 0) {
|
|
|
|
for (int index = 0; index < subscriptions.size(); index++) {
|
|
|
|
subscription_item = subscriptions.get(index);
|
|
|
|
if (subscription_item.Event.equalsIgnoreCase(data)) {
|
|
|
|
String stopic = subscription_item.Topic + "/#";
|
|
|
|
MqttUnsubscribe(stopic.c_str());
|
|
|
|
events = subscription_item.Event;
|
|
|
|
subscriptions.remove(index);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//If did not specify the event name, unsubscribe all event
|
|
|
|
String stopic;
|
|
|
|
while (subscriptions.size() > 0) {
|
|
|
|
events.concat(subscriptions.get(0).Event + "; ");
|
|
|
|
stopic = subscriptions.get(0).Topic + "/#";
|
|
|
|
MqttUnsubscribe(stopic.c_str());
|
|
|
|
subscriptions.remove(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return events;
|
|
|
|
}
|
|
|
|
#endif // SUPPORT_MQTT_EVENT
|
|
|
|
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
#ifdef USE_EXPRESSION
|
|
|
|
/********************************************************************************************/
|
|
|
|
/*
|
|
|
|
* Parse a number value
|
|
|
|
* Input:
|
|
|
|
* pNumber - A char pointer point to a digit started string (guaranteed)
|
|
|
|
* value - Reference a double variable used to accept the result
|
|
|
|
* Output:
|
|
|
|
* pNumber - Pointer forward to next character after the number
|
|
|
|
* value - double type, the result value
|
|
|
|
* Return:
|
|
|
|
* true - succeed
|
|
|
|
* false - failed
|
|
|
|
*/
|
|
|
|
bool findNextNumber(char * &pNumber, double &value)
|
|
|
|
{
|
|
|
|
bool bSucceed = false;
|
|
|
|
String sNumber = "";
|
|
|
|
while (*pNumber) {
|
|
|
|
if (isdigit(*pNumber) || (*pNumber == '.')) {
|
|
|
|
sNumber += *pNumber;
|
|
|
|
pNumber++;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (sNumber.length() > 0) {
|
|
|
|
value = CharToDouble(sNumber.c_str());
|
|
|
|
bSucceed = true;
|
|
|
|
}
|
|
|
|
return bSucceed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/********************************************************************************************/
|
|
|
|
/*
|
|
|
|
* Parse a variable (like VAR1, MEM3) and get its value (double type)
|
|
|
|
* Input:
|
|
|
|
* pVarname - A char pointer point to a variable name string
|
|
|
|
* value - Reference a double variable used to accept the result
|
|
|
|
* Output:
|
|
|
|
* pVarname - Pointer forward to next character after the variable
|
|
|
|
* value - double type, the result value
|
|
|
|
* Return:
|
|
|
|
* true - succeed
|
|
|
|
* false - failed
|
|
|
|
*/
|
|
|
|
bool findNextVariableValue(char * &pVarname, double &value)
|
|
|
|
{
|
2019-02-19 13:49:15 +00:00
|
|
|
bool succeed = true;
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
value = 0;
|
|
|
|
String sVarName = "";
|
|
|
|
while (*pVarname) {
|
|
|
|
if (isalpha(*pVarname) || isdigit(*pVarname)) {
|
|
|
|
sVarName.concat(*pVarname);
|
|
|
|
pVarname++;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sVarName.toUpperCase();
|
2019-02-19 13:49:15 +00:00
|
|
|
if (sVarName.startsWith(F("VAR"))) {
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
int index = sVarName.substring(3).toInt();
|
|
|
|
if (index > 0 && index <= MAX_RULE_VARS) {
|
|
|
|
value = CharToDouble(vars[index -1]);
|
|
|
|
}
|
2019-02-19 13:49:15 +00:00
|
|
|
} else if (sVarName.startsWith(F("MEM"))) {
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
int index = sVarName.substring(3).toInt();
|
|
|
|
if (index > 0 && index <= MAX_RULE_MEMS) {
|
|
|
|
value = CharToDouble(Settings.mems[index -1]);
|
|
|
|
}
|
2019-02-19 13:49:15 +00:00
|
|
|
} else if (sVarName.equals(F("TIME"))) {
|
|
|
|
value = MinutesPastMidnight();
|
|
|
|
} else if (sVarName.equals(F("UPTIME"))) {
|
|
|
|
value = MinutesUptime();
|
|
|
|
} else if (sVarName.equals(F("UTCTIME"))) {
|
|
|
|
value = UtcTime();
|
|
|
|
} else if (sVarName.equals(F("LOCALTIME"))) {
|
|
|
|
value = LocalTime();
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
#if defined(USE_TIMERS) && defined(USE_SUNRISE)
|
2019-02-19 13:49:15 +00:00
|
|
|
} else if (sVarName.equals(F("SUNRISE"))) {
|
|
|
|
value = SunMinutes(0);
|
|
|
|
} else if (sVarName.equals(F("SUNSET"))) {
|
|
|
|
value = SunMinutes(1);
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
#endif
|
2019-02-19 13:49:15 +00:00
|
|
|
} else {
|
|
|
|
succeed = false;
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return succeed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/********************************************************************************************/
|
|
|
|
/*
|
|
|
|
* Find next object in expression and evaluate it
|
|
|
|
* An object could be:
|
|
|
|
* - A float number start with a digit, like 0.787
|
|
|
|
* - 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
|
|
|
|
* value - Reference a double variable used to accept the result
|
|
|
|
* Output:
|
|
|
|
* pointer - Pointer forward to next character after next object
|
|
|
|
* value - double type, the result value
|
|
|
|
* Return:
|
|
|
|
* true - succeed
|
|
|
|
* false - failed
|
|
|
|
*/
|
|
|
|
bool findNextObjectValue(char * &pointer, double &value)
|
|
|
|
{
|
|
|
|
bool bSucceed = false;
|
2019-02-16 15:17:17 +00:00
|
|
|
while (*pointer)
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
{
|
|
|
|
if (isspace(*pointer)) { //Skip leading spaces
|
|
|
|
pointer++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (isdigit(*pointer)) { //This object is a number
|
|
|
|
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 ()
|
|
|
|
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);
|
|
|
|
bSucceed = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
} else { //No number, no variable, no expression, then invalid object.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return bSucceed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/********************************************************************************************/
|
|
|
|
/*
|
|
|
|
* Find next operator in expression
|
|
|
|
* An operator could be: +, - , * , / , %, ^
|
|
|
|
* Input:
|
|
|
|
* pointer - A char pointer point to a place of the expression string
|
|
|
|
* op - Reference to a variable used to accept the result
|
|
|
|
* Output:
|
|
|
|
* pointer - Pointer forward to next character after next operator
|
|
|
|
* op - The operator. 0, 1, 2, 3, 4, 5
|
|
|
|
* Return:
|
|
|
|
* true - succeed
|
|
|
|
* false - failed
|
|
|
|
*/
|
|
|
|
bool findNextOperator(char * &pointer, int8_t &op)
|
|
|
|
{
|
|
|
|
bool bSucceed = false;
|
2019-02-16 15:17:17 +00:00
|
|
|
while (*pointer)
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
{
|
|
|
|
if (isspace(*pointer)) { //Skip leading spaces
|
|
|
|
pointer++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (char *pch = strchr(kExpressionOperators, *pointer)) { //If it is an operator
|
|
|
|
op = (int8_t)(pch - kExpressionOperators);
|
|
|
|
pointer++;
|
|
|
|
bSucceed = true;
|
|
|
|
}
|
|
|
|
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
|
|
|
|
* value - Reference a double variable used to accept the result
|
|
|
|
* Output:
|
|
|
|
* pointer - Pointer forward to next character after next object
|
|
|
|
* value - double type, the result value
|
|
|
|
* Return:
|
|
|
|
* true - succeed
|
|
|
|
* false - failed
|
|
|
|
*/
|
|
|
|
double calculateTwoValues(double v1, double v2, uint8_t op)
|
|
|
|
{
|
|
|
|
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-02-16 15:17:17 +00:00
|
|
|
* double - result.
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
* 0 - if the expression is invalid
|
|
|
|
* An example:
|
|
|
|
* MEM1 = 3, MEM2 = 6, VAR2 = 15, VAR10 = 80
|
|
|
|
* At beginning, the expression might be complicated like: 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + VAR10 / (2 + MEM2)
|
|
|
|
* We are going to scan the whole expression, evaluate each object.
|
|
|
|
* Finally we will have a value list:.
|
|
|
|
* Order Object Value
|
|
|
|
* 0 3.14 3.14
|
|
|
|
* 1 (MEM1 * (10 + VAR2 ^2) - 100) 605
|
|
|
|
* 2 10 10
|
|
|
|
* 3 VAR10 80
|
|
|
|
* 4 (2 + MEM2) 8
|
|
|
|
* And an operator list:
|
|
|
|
* Order Operator Priority
|
|
|
|
* 0 * 2
|
|
|
|
* 1 % 3
|
|
|
|
* 2 + 1
|
|
|
|
* 3 / 2
|
|
|
|
*/
|
|
|
|
double evaluateExpression(const char * expression, unsigned int len)
|
|
|
|
{
|
|
|
|
char expbuf[len + 1];
|
|
|
|
memcpy(expbuf, expression, len);
|
|
|
|
expbuf[len] = '\0';
|
|
|
|
char * scan_pointer = expbuf;
|
|
|
|
|
|
|
|
LinkedList<double> object_values;
|
|
|
|
LinkedList<int8_t> operators;
|
|
|
|
int8_t op;
|
|
|
|
double va;
|
|
|
|
//Find and add the value of first object
|
|
|
|
if (findNextObjectValue(scan_pointer, va)) {
|
|
|
|
object_values.add(va);
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
while (*scan_pointer)
|
|
|
|
{
|
2019-02-16 15:17:17 +00:00
|
|
|
if (findNextOperator(scan_pointer, op)
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
&& *scan_pointer
|
2019-02-16 15:17:17 +00:00
|
|
|
&& findNextObjectValue(scan_pointer, va))
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
{
|
|
|
|
operators.add(op);
|
|
|
|
object_values.add(va);
|
|
|
|
} else {
|
|
|
|
//No operator followed or no more object after this operator, we done.
|
2019-02-16 15:17:17 +00:00
|
|
|
break;
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Going to evaluate the whole expression
|
|
|
|
//Calculate by order of operator priorities. Looking for all operators with specified priority (from High to Low)
|
|
|
|
for (int8_t priority = MAX_EXPRESSION_OPERATOR_PRIORITY; priority>0; priority--) {
|
|
|
|
int index = 0;
|
|
|
|
while (index < operators.size()) {
|
|
|
|
if (priority == kExpressionOperatorsPriorities[(operators.get(index))]) { //need to calculate the operator first
|
|
|
|
//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);
|
|
|
|
}
|
|
|
|
#endif //USE_EXPRESSION
|
|
|
|
|
2019-01-28 13:08:33 +00:00
|
|
|
bool RulesCommand(void)
|
2018-04-13 16:42:11 +01:00
|
|
|
{
|
|
|
|
char command[CMDSZ];
|
2019-01-28 13:08:33 +00:00
|
|
|
bool serviced = true;
|
2018-04-13 16:42:11 +01:00
|
|
|
uint8_t index = XdrvMailbox.index;
|
|
|
|
|
|
|
|
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kRulesCommands);
|
2018-05-09 09:49:43 +01:00
|
|
|
if (-1 == command_code) {
|
|
|
|
serviced = false; // Unknown command
|
|
|
|
}
|
2018-05-24 13:25:52 +01:00
|
|
|
else if ((CMND_RULE == command_code) && (index > 0) && (index <= MAX_RULE_SETS)) {
|
|
|
|
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
|
|
|
}
|
2018-05-24 13:25:52 +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\"}"),
|
|
|
|
command, index, GetStateText(bitRead(Settings.rule_enabled, index -1)), GetStateText(bitRead(Settings.rule_once, index -1)),
|
|
|
|
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
|
|
|
}
|
|
|
|
else if ((CMND_RULETIMER == command_code) && (index > 0) && (index <= MAX_RULE_TIMERS)) {
|
|
|
|
if (XdrvMailbox.data_len > 0) {
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
#ifdef USE_EXPRESSION
|
|
|
|
double timer_set = evaluateExpression(XdrvMailbox.data, XdrvMailbox.data_len);
|
|
|
|
rules_timer[index -1] = (timer_set > 0) ? millis() + (1000 * timer_set) : 0;
|
|
|
|
#else
|
2018-04-13 16:42:11 +01:00
|
|
|
rules_timer[index -1] = (XdrvMailbox.payload > 0) ? millis() + (1000 * XdrvMailbox.payload) : 0;
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
#endif //USE_EXPRESSION
|
2018-04-13 16:42:11 +01:00
|
|
|
}
|
2018-08-24 17:22:04 +01:00
|
|
|
mqtt_data[0] = '\0';
|
2019-01-28 13:08:33 +00:00
|
|
|
for (uint8_t i = 0; i < MAX_RULE_TIMERS; i++) {
|
2019-03-23 16:00:59 +00: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
|
|
|
}
|
Add support for Shelly 1PM Template
Add support for Shelly 1PM Template {"NAME":"Shelly 1PM","GPIO":[56,0,0,0,82,134,0,0,0,0,0,21,0],"FLAG":2,"BASE":18} (#5716)
2019-05-13 17:26:07 +01:00
|
|
|
ResponseJsonEnd();
|
2018-04-13 16:42:11 +01:00
|
|
|
}
|
|
|
|
else if (CMND_EVENT == command_code) {
|
|
|
|
if (XdrvMailbox.data_len > 0) {
|
2018-05-29 16:24:42 +01:00
|
|
|
strlcpy(event_data, XdrvMailbox.data, sizeof(event_data));
|
2018-04-13 16:42:11 +01:00
|
|
|
}
|
2019-03-23 16:00:59 +00:00
|
|
|
Response_P(S_JSON_COMMAND_SVALUE, command, D_JSON_DONE);
|
2018-04-13 16:42:11 +01:00
|
|
|
}
|
2018-08-23 15:05:51 +01:00
|
|
|
else if ((CMND_VAR == command_code) && (index > 0) && (index <= MAX_RULE_VARS)) {
|
2018-05-20 16:46:00 +01:00
|
|
|
if (XdrvMailbox.data_len > 0) {
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
#ifdef USE_EXPRESSION
|
|
|
|
dtostrfd(evaluateExpression(XdrvMailbox.data, XdrvMailbox.data_len), Settings.flag2.calc_resolution, vars[index -1]);
|
|
|
|
#else
|
2018-08-28 10:10:32 +01:00
|
|
|
strlcpy(vars[index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(vars[index -1]));
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
#endif //USE_EXPRESSION
|
2019-01-04 19:26:37 +00:00
|
|
|
bitSet(vars_event, index -1);
|
2018-05-20 16:46:00 +01:00
|
|
|
}
|
2019-03-23 16:00:59 +00:00
|
|
|
Response_P(S_JSON_COMMAND_INDEX_SVALUE, command, index, vars[index -1]);
|
2018-05-20 16:46:00 +01:00
|
|
|
}
|
2018-08-23 15:05:51 +01:00
|
|
|
else if ((CMND_MEM == command_code) && (index > 0) && (index <= MAX_RULE_MEMS)) {
|
2018-05-20 16:46:00 +01:00
|
|
|
if (XdrvMailbox.data_len > 0) {
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
#ifdef USE_EXPRESSION
|
|
|
|
dtostrfd(evaluateExpression(XdrvMailbox.data, XdrvMailbox.data_len), Settings.flag2.calc_resolution, Settings.mems[index -1]);
|
|
|
|
#else
|
2018-08-28 10:10:32 +01:00
|
|
|
strlcpy(Settings.mems[index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.mems[index -1]));
|
Introduce Expression in Rules
Support use an expression as paramter in some rule commands, include Var<x>, Mem<x> and Ruletimer<x>.
Expression is constructed by constants (float number), variables (var<x>, mem<x>, Time, Uptime, Sunrise, Sunset), operators and round brackets.
Currently support 6 operators, order by priority from high to low:
^ (power)
% (modulo)
*, /
+, -
Commands examples:
Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2)
Ruletimer4 Time - Sunrise + MEM2/2
2019-02-13 02:46:42 +00:00
|
|
|
#endif //USE_EXPRESSION
|
2019-01-04 19:26:37 +00:00
|
|
|
bitSet(mems_event, index -1);
|
2018-05-20 16:46:00 +01:00
|
|
|
}
|
2019-03-23 16:00:59 +00:00
|
|
|
Response_P(S_JSON_COMMAND_INDEX_SVALUE, command, index, Settings.mems[index -1]);
|
2018-05-20 16:46:00 +01:00
|
|
|
}
|
2018-11-27 00:22:44 +00:00
|
|
|
else if (CMND_CALC_RESOLUTION == command_code) {
|
2018-11-27 02:08:23 +00:00
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 7)) {
|
|
|
|
Settings.flag2.calc_resolution = XdrvMailbox.payload;
|
2018-11-27 00:22:44 +00:00
|
|
|
}
|
2019-03-23 16:00:59 +00:00
|
|
|
Response_P(S_JSON_COMMAND_NVALUE, command, Settings.flag2.calc_resolution);
|
2018-11-27 00:22:44 +00:00
|
|
|
}
|
2018-08-23 15:05:51 +01:00
|
|
|
else if ((CMND_ADD == command_code) && (index > 0) && (index <= MAX_RULE_VARS)) {
|
2018-07-16 11:37:49 +01:00
|
|
|
if (XdrvMailbox.data_len > 0) {
|
2018-06-28 17:06:21 +01:00
|
|
|
double tempvar = CharToDouble(vars[index -1]) + CharToDouble(XdrvMailbox.data);
|
2018-11-27 00:22:44 +00:00
|
|
|
dtostrfd(tempvar, Settings.flag2.calc_resolution, vars[index -1]);
|
2019-01-04 19:26:37 +00:00
|
|
|
bitSet(vars_event, index -1);
|
2018-06-10 06:09:11 +01:00
|
|
|
}
|
2019-03-23 16:00:59 +00:00
|
|
|
Response_P(S_JSON_COMMAND_INDEX_SVALUE, command, index, vars[index -1]);
|
2018-06-10 06:09:11 +01:00
|
|
|
}
|
2018-08-23 15:05:51 +01:00
|
|
|
else if ((CMND_SUB == command_code) && (index > 0) && (index <= MAX_RULE_VARS)) {
|
2018-07-16 11:37:49 +01:00
|
|
|
if (XdrvMailbox.data_len > 0) {
|
2018-06-28 17:06:21 +01:00
|
|
|
double tempvar = CharToDouble(vars[index -1]) - CharToDouble(XdrvMailbox.data);
|
2018-11-27 00:22:44 +00:00
|
|
|
dtostrfd(tempvar, Settings.flag2.calc_resolution, vars[index -1]);
|
2019-01-04 19:26:37 +00:00
|
|
|
bitSet(vars_event, index -1);
|
2018-06-10 06:09:11 +01:00
|
|
|
}
|
2019-03-23 16:00:59 +00:00
|
|
|
Response_P(S_JSON_COMMAND_INDEX_SVALUE, command, index, vars[index -1]);
|
2018-06-10 06:09:11 +01:00
|
|
|
}
|
2018-08-23 15:05:51 +01:00
|
|
|
else if ((CMND_MULT == command_code) && (index > 0) && (index <= MAX_RULE_VARS)) {
|
2018-07-16 11:37:49 +01:00
|
|
|
if (XdrvMailbox.data_len > 0) {
|
2018-06-28 17:06:21 +01:00
|
|
|
double tempvar = CharToDouble(vars[index -1]) * CharToDouble(XdrvMailbox.data);
|
2018-11-27 00:22:44 +00:00
|
|
|
dtostrfd(tempvar, Settings.flag2.calc_resolution, vars[index -1]);
|
2019-01-04 19:26:37 +00:00
|
|
|
bitSet(vars_event, index -1);
|
2018-06-10 06:09:11 +01:00
|
|
|
}
|
2019-03-23 16:00:59 +00:00
|
|
|
Response_P(S_JSON_COMMAND_INDEX_SVALUE, command, index, vars[index -1]);
|
2018-06-10 06:09:11 +01:00
|
|
|
}
|
2018-08-23 15:05:51 +01:00
|
|
|
else if ((CMND_SCALE == command_code) && (index > 0) && (index <= MAX_RULE_VARS)) {
|
2018-07-16 11:37:49 +01:00
|
|
|
if (XdrvMailbox.data_len > 0) {
|
2019-03-26 17:26:50 +00:00
|
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) { // Process parameter entry
|
2018-07-16 11:37:49 +01:00
|
|
|
char sub_string[XdrvMailbox.data_len +1];
|
|
|
|
|
|
|
|
double valueIN = CharToDouble(subStr(sub_string, XdrvMailbox.data, ",", 1));
|
|
|
|
double fromLow = CharToDouble(subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
|
|
double fromHigh = CharToDouble(subStr(sub_string, XdrvMailbox.data, ",", 3));
|
|
|
|
double toLow = CharToDouble(subStr(sub_string, XdrvMailbox.data, ",", 4));
|
|
|
|
double toHigh = CharToDouble(subStr(sub_string, XdrvMailbox.data, ",", 5));
|
|
|
|
double value = map_double(valueIN, fromLow, fromHigh, toLow, toHigh);
|
2018-11-27 00:22:44 +00:00
|
|
|
dtostrfd(value, Settings.flag2.calc_resolution, vars[index -1]);
|
2019-01-04 19:26:37 +00:00
|
|
|
bitSet(vars_event, index -1);
|
2018-06-10 06:09:11 +01:00
|
|
|
}
|
|
|
|
}
|
2019-03-23 16:00:59 +00:00
|
|
|
Response_P(S_JSON_COMMAND_INDEX_SVALUE, command, index, vars[index -1]);
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
#ifdef SUPPORT_MQTT_EVENT
|
|
|
|
} else if (CMND_SUBSCRIBE == command_code) { //MQTT Subscribe command. Subscribe <Event>, <Topic> [, <Key>]
|
|
|
|
String result = RulesSubscribe(XdrvMailbox.data, XdrvMailbox.data_len);
|
2019-03-23 16:00:59 +00:00
|
|
|
Response_P(S_JSON_COMMAND_SVALUE, command, result.c_str());
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
} else if (CMND_UNSUBSCRIBE == command_code) { //MQTT Un-subscribe command. UnSubscribe <Event>
|
|
|
|
String result = RulesUnsubscribe(XdrvMailbox.data, XdrvMailbox.data_len);
|
2019-03-23 16:00:59 +00:00
|
|
|
Response_P(S_JSON_COMMAND_SVALUE, command, result.c_str());
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
#endif //SUPPORT_MQTT_EVENT
|
2018-06-10 06:09:11 +01:00
|
|
|
}
|
2018-05-09 09:49:43 +01:00
|
|
|
else serviced = false; // Unknown command
|
2018-04-13 16:42:11 +01:00
|
|
|
|
|
|
|
return serviced;
|
|
|
|
}
|
|
|
|
|
2018-06-28 17:06:21 +01:00
|
|
|
double map_double(double x, double in_min, double in_max, double out_min, double out_max)
|
|
|
|
{
|
|
|
|
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
|
|
|
}
|
|
|
|
|
2018-04-13 16:42:11 +01:00
|
|
|
/*********************************************************************************************\
|
|
|
|
* Interface
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2019-01-28 13:08:33 +00:00
|
|
|
bool Xdrv10(uint8_t function)
|
2018-04-13 16:42:11 +01:00
|
|
|
{
|
2019-01-28 13:08:33 +00:00
|
|
|
bool result = false;
|
2018-04-13 16:42:11 +01:00
|
|
|
|
|
|
|
switch (function) {
|
2018-06-04 17:10:38 +01:00
|
|
|
case FUNC_PRE_INIT:
|
2018-04-13 16:42:11 +01:00
|
|
|
RulesInit();
|
|
|
|
break;
|
|
|
|
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:
|
|
|
|
result = RulesCommand();
|
|
|
|
break;
|
2018-05-24 15:23:20 +01:00
|
|
|
case FUNC_RULES_PROCESS:
|
|
|
|
result = RulesProcess();
|
|
|
|
break;
|
2019-05-19 11:42:10 +01:00
|
|
|
case FUNC_SAVE_BEFORE_RESTART:
|
|
|
|
RulesSaveBeforeRestart();
|
|
|
|
break;
|
Rules: Trigger Event with MQTT Subscriptions
Support subscribe/unsubscribe MQTT topics and trigger specified event with the subscribed MQTT topic.
You can subscribe a MQTT topic and assign an event name. Once we received subscribed MQTT message, an event will be automatically triggered. So you can set up a rule with "ON EVENT#<event_name> DO ..." to do whatever you want based on this MQTT message. The payload is passed as a parameter once the event been triggered. If the payload is in JSON format, you are able to get the value of specified key as parameter.
For example, if you have a Tasmota based thermostat and multiple temperature sensors in different place, usually you have to set up a centre home automation system like Domoticz to control the thermostat. Right now, with this new feature, you can write a rule to do this.
Two new commands in Rules:
1. Subscribe
Subscribe a MQTT topic (with or without key) and assign an event name to it.
Command format:
Subscribe [<event_name>, <topic> [, <key>]]
This command will subscribe a <topic> and give it an event name <event_name>.
The optional parameter <key> is for parse the specified key/value from MQTT message
payload with JSON format.
In order to parse value from two level JSON data, you can use one dot (".") to split the key into two section.
Subscribe command without any parameter will list all topics currently subscribed.
2. Unsubscribe
Unsubscribe specified MQTT event.
Command format:
Unsubscribe [<event_name>]
Unsubscribe a topic subscribed by specify the event name.
If no event specified, Unsubscribe all topics subscribed.
Examples:
1.
Subscribe BkLight, Tasmota/BackyardLight/stat/POWER
And define a rule like:
Rule1 on event#BkLight=ON do ruletimer4 60 endon
2.
Subscribe DnTemp, Tasmota/RoomSensor1/stat/SENSOR, DS18B20.Temperature
Define a rule to deal with the MQTT message like {"Time":"2017-02-16T10:13:52", "DS18B20":{"Temperature":20.6}}
Rule1 ON EVENT#DnTemp>=21 DO ... ENDON
2019-02-24 03:33:09 +00:00
|
|
|
#ifdef SUPPORT_MQTT_EVENT
|
|
|
|
case FUNC_MQTT_DATA:
|
|
|
|
result = RulesMqttData();
|
|
|
|
break;
|
|
|
|
#endif //SUPPORT_MQTT_EVENT
|
2018-04-13 16:42:11 +01:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-05-18 12:34:52 +01:00
|
|
|
#endif // Do not USE_SCRIPT
|
2019-03-29 08:42:50 +00:00
|
|
|
#endif // USE_RULES
|