Merge pull request #51 from arendst/development

Update from Tasmota
This commit is contained in:
Adrian Scillato 2018-04-13 14:10:36 -03:00 committed by GitHub
commit ea07c79043
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 528 additions and 55 deletions

View File

@ -1,6 +1,7 @@
/* 5.12.0k
* Prepare for simple rules by enlarging Settings area to now 2048 bytes
* Prepare for simple rules of up to 255 characters by enlarging Settings area to now 2048 bytes
* Change Timer parameter name from Power to Action
* Add commands Publish, Rule, RuleTimer and Event. See Wiki about Rule restriction, usage and examples
* Fix freeing more code space when emulation is disabled (#1592)
* Fix update temperature on DS18x20 drivers (#2328)
* Fix compile error when not defined USE_TIMERS (#2400)

View File

@ -138,28 +138,6 @@
#define D_RSLT_WARNING "WARNING"
// Commands sonoff.ino
#define D_CMND_MQTTHOST "MqttHost"
#define D_CMND_MQTTPORT "MqttPort"
#define D_CMND_MQTTRETRY "MqttRetry"
#define D_CMND_STATETEXT "StateText"
#define D_CMND_MQTTFINGERPRINT "MqttFingerprint"
#define D_CMND_MQTTCLIENT "MqttClient"
#define D_CMND_MQTTUSER "MqttUser"
#define D_CMND_MQTTPASSWORD "MqttPassword"
#define D_CMND_FULLTOPIC "FullTopic"
#define D_CMND_PREFIX "Prefix"
#define PRFX_MAX_STRING_LENGTH 5
#define D_CMND "cmnd"
#define D_STAT "stat"
#define D_TELE "tele"
#define D_CMND_GROUPTOPIC "GroupTopic"
#define D_CMND_TOPIC "Topic"
#define D_CMND_BUTTONTOPIC "ButtonTopic"
#define D_CMND_SWITCHTOPIC "SwitchTopic"
#define D_CMND_BUTTONRETAIN "ButtonRetain"
#define D_CMND_SWITCHRETAIN "SwitchRetain"
#define D_CMND_POWERRETAIN "PowerRetain"
#define D_CMND_SENSORRETAIN "SensorRetain"
#define D_CMND_BACKLOG "Backlog"
#define D_CMND_DELAY "Delay"
#define D_CMND_STATUS "Status"
@ -251,6 +229,31 @@
#define D_CMND_BAUDRATE "Baudrate"
#define D_CMND_EXCEPTION "Exception"
// Commands xdrv_00_mqtt.ino
#define D_CMND_MQTTHOST "MqttHost"
#define D_CMND_MQTTPORT "MqttPort"
#define D_CMND_MQTTRETRY "MqttRetry"
#define D_CMND_STATETEXT "StateText"
#define D_CMND_MQTTFINGERPRINT "MqttFingerprint"
#define D_CMND_MQTTCLIENT "MqttClient"
#define D_CMND_MQTTUSER "MqttUser"
#define D_CMND_MQTTPASSWORD "MqttPassword"
#define D_CMND_FULLTOPIC "FullTopic"
#define D_CMND_PREFIX "Prefix"
#define PRFX_MAX_STRING_LENGTH 5
#define D_CMND "cmnd"
#define D_STAT "stat"
#define D_TELE "tele"
#define D_CMND_GROUPTOPIC "GroupTopic"
#define D_CMND_TOPIC "Topic"
#define D_CMND_BUTTONTOPIC "ButtonTopic"
#define D_CMND_SWITCHTOPIC "SwitchTopic"
#define D_CMND_BUTTONRETAIN "ButtonRetain"
#define D_CMND_SWITCHRETAIN "SwitchRetain"
#define D_CMND_POWERRETAIN "PowerRetain"
#define D_CMND_SENSORRETAIN "SensorRetain"
#define D_CMND_PUBLISH "Publish"
// Commands xdrv_01_light.ino
#define D_CMND_CHANNEL "Channel"
#define D_CMND_COLOR "Color"

View File

@ -166,9 +166,7 @@ enum LichtSubtypes {LST_NONE, LST_SINGLE, LST_COLDWARM, LST_RGB, LST_RGBW, LST_R
enum LichtSchemes {LS_POWER, LS_WAKEUP, LS_CYCLEUP, LS_CYCLEDN, LS_RANDOM, LS_MAX};
enum XsnsFunctions {FUNC_INIT, FUNC_LOOP, FUNC_EVERY_50_MSECOND, FUNC_EVERY_SECOND, FUNC_PREP_BEFORE_TELEPERIOD, FUNC_JSON_APPEND, FUNC_WEB_APPEND, FUNC_SAVE_BEFORE_RESTART,
FUNC_COMMAND, FUNC_NTP_INIT, FUNC_NTP_SET, FUNC_CLOCK_TIMER,
FUNC_MQTT_SUBSCRIBE, FUNC_MQTT_INIT, FUNC_MQTT_DATA, FUNC_MQTT_DISCONNECTED, FUNC_MQTT_CONNECTED,
FUNC_SET_POWER, FUNC_SHOW_SENSOR};
FUNC_COMMAND, FUNC_MQTT_SUBSCRIBE, FUNC_MQTT_INIT, FUNC_MQTT_DATA, FUNC_SET_POWER, FUNC_SHOW_SENSOR};
const uint8_t kDefaultRfCode[9] PROGMEM = { 0x21, 0x16, 0x01, 0x0E, 0x03, 0x48, 0x2E, 0x1A, 0x00 };

View File

@ -1101,7 +1101,7 @@ void MqttDataHandler(char* topic, byte* data, unsigned int data_len)
/********************************************************************************************/
boolean send_button_power(byte key, byte device, byte state)
boolean SendKey(byte key, byte device, byte state)
{
// key 0 = button_topic
// key 1 = switch_topic
@ -1130,13 +1130,18 @@ boolean send_button_power(byte key, byte device, byte state)
snprintf_P(mqtt_data, sizeof(mqtt_data), GetStateText(state));
}
#ifdef USE_DOMOTICZ
if (!(DomoticzButton(key, device, state, strlen(mqtt_data)))) {
if (!(DomoticzSendKey(key, device, state, strlen(mqtt_data)))) {
MqttPublishDirect(stopic, (key) ? Settings.flag.mqtt_switch_retain : Settings.flag.mqtt_button_retain);
}
#else
MqttPublishDirect(stopic, (key) ? Settings.flag.mqtt_switch_retain : Settings.flag.mqtt_button_retain);
#endif // USE_DOMOTICZ
result = true;
#ifdef USE_RULES
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s%d\":{\"State\":%d}}"), (key) ? "Switch" : "Button", device, state);
result = RulesProcess();
#endif // USE_RULES
}
#ifdef USE_KNX
KNX_Send_Button_Power(key, device, state);
@ -1533,7 +1538,7 @@ void ButtonHandler()
if (!holdbutton[button_index]) button_pressed = true; // Do not allow within 1 second
}
if (button_pressed) {
if (!send_button_power(0, button_index +1, POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set
if (!SendKey(0, button_index +1, POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set
ExecuteCommandPower(button_index +1, POWER_TOGGLE); // Execute Toggle command internally
}
}
@ -1542,7 +1547,7 @@ void ButtonHandler()
if (Settings.flag.button_single) { // Allow only single button press for immediate action
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_IMMEDIATE), button_index +1);
AddLog(LOG_LEVEL_DEBUG);
if (!send_button_power(0, button_index +1, POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set
if (!SendKey(0, button_index +1, POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set
ExecuteCommandPower(button_index +1, POWER_TOGGLE); // Execute Toggle command internally
}
} else {
@ -1568,7 +1573,7 @@ void ButtonHandler()
if (Settings.flag.button_restrict) { // Button restriction
if (holdbutton[button_index] == Settings.param[P_HOLD_TIME] * (STATES / 10)) { // Button hold
multipress[button_index] = 0;
send_button_power(0, button_index +1, 3); // Execute Hold command via MQTT if ButtonTopic is set
SendKey(0, button_index +1, 3); // Execute Hold command via MQTT if ButtonTopic is set
}
} else {
if (holdbutton[button_index] == (Settings.param[P_HOLD_TIME] * (STATES / 10)) * hold_time_extent) { // Button held for factor times longer
@ -1594,7 +1599,7 @@ void ButtonHandler()
multipress[button_index] = 1;
}
}
if (single_press && send_button_power(0, button_index + multipress[button_index], POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set
if (single_press && SendKey(0, button_index + multipress[button_index], POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set
// Success
} else {
if (multipress[button_index] < 3) { // Single or Double press
@ -1635,7 +1640,7 @@ void SwitchHandler()
if (holdwallswitch[i]) {
holdwallswitch[i]--;
if (0 == holdwallswitch[i]) {
send_button_power(1, i +1, 3); // Execute command via MQTT
SendKey(1, i +1, 3); // Execute command via MQTT
}
}
@ -1688,7 +1693,7 @@ void SwitchHandler()
}
if (switchflag < 3) {
if (!send_button_power(1, i +1, switchflag)) { // Execute command via MQTT
if (!SendKey(1, i +1, switchflag)) { // Execute command via MQTT
ExecuteCommandPower(i +1, switchflag); // Execute command internally (if i < devices_present)
}
}

View File

@ -1376,12 +1376,14 @@ void RtcSecond()
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION "(" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"),
GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).c_str());
AddLog(LOG_LEVEL_DEBUG);
#ifdef USE_RULES
if (local_time < 1451602800) { // 2016-01-01
XdrvCall(FUNC_NTP_INIT);
strncpy_P(mqtt_data, PSTR("{\"Time\":{\"Initialized\":1}}"), sizeof(mqtt_data));
} else {
XdrvCall(FUNC_NTP_SET);
strncpy_P(mqtt_data, PSTR("{\"Time\":{\"Set\":1}}"), sizeof(mqtt_data));
}
RulesProcess();
#endif // USE_RULES
} else {
ntp_sync_minute++; // Try again in next minute
}

View File

@ -227,6 +227,9 @@
#define USE_TIMERS_WEB // Add timer webpage support (+4k5 code)
#define USE_SUNRISE // Add support for Sunrise and sunset tools (+16k)
// -- Rules ---------------------------------------
#define USE_RULES // Add support for rules (+4k4 code)
// -- Internal Analog input -----------------------
#define USE_ADC_VCC // Display Vcc in Power status. Disable for use as Analog input on selected devices

View File

@ -42,11 +42,11 @@
enum MqttCommands {
CMND_MQTTHOST, CMND_MQTTPORT, CMND_MQTTRETRY, CMND_STATETEXT, CMND_MQTTFINGERPRINT, CMND_MQTTCLIENT,
CMND_MQTTUSER, CMND_MQTTPASSWORD, CMND_FULLTOPIC, CMND_PREFIX, CMND_GROUPTOPIC, CMND_TOPIC,
CMND_MQTTUSER, CMND_MQTTPASSWORD, CMND_FULLTOPIC, CMND_PREFIX, CMND_GROUPTOPIC, CMND_TOPIC, CMND_PUBLISH,
CMND_BUTTONTOPIC, CMND_SWITCHTOPIC, CMND_BUTTONRETAIN, CMND_SWITCHRETAIN, CMND_POWERRETAIN, CMND_SENSORRETAIN };
const char kMqttCommands[] PROGMEM =
D_CMND_MQTTHOST "|" D_CMND_MQTTPORT "|" D_CMND_MQTTRETRY "|" D_CMND_STATETEXT "|" D_CMND_MQTTFINGERPRINT "|" D_CMND_MQTTCLIENT "|"
D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|" D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|"
D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|" D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|" D_CMND_PUBLISH "|"
D_CMND_BUTTONTOPIC "|" D_CMND_SWITCHTOPIC "|" D_CMND_BUTTONRETAIN "|" D_CMND_SWITCHRETAIN "|" D_CMND_POWERRETAIN "|" D_CMND_SENSORRETAIN ;
uint8_t mqtt_retry_counter = 1; // MQTT connection retry counter
@ -316,8 +316,10 @@ void MqttDisconnected(int state)
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND),
Settings.mqtt_host, Settings.mqtt_port, state, mqtt_retry_counter);
AddLog(LOG_LEVEL_INFO);
XdrvCall(FUNC_MQTT_DISCONNECTED);
#ifdef USE_RULES
strncpy_P(mqtt_data, PSTR("{\"MQTT\":{\"Disconnected\":1}}"), sizeof(mqtt_data));
RulesProcess();
#endif // USE_RULES
}
void MqttConnected()
@ -369,12 +371,17 @@ void MqttConnected()
tele_period = Settings.tele_period -9;
}
status_update_timer = 2;
#ifdef USE_RULES
strncpy_P(mqtt_data, PSTR("{\"System\":{\"Boot\":1}}"), sizeof(mqtt_data));
RulesProcess();
#endif // USE_RULES
XdrvCall(FUNC_MQTT_INIT);
}
mqtt_initial_connection_state = 0;
XdrvCall(FUNC_MQTT_CONNECTED);
#ifdef USE_RULES
strncpy_P(mqtt_data, PSTR("{\"MQTT\":{\"Connected\":1}}"), sizeof(mqtt_data));
RulesProcess();
#endif // USE_RULES
}
#ifdef USE_MQTT_TLS
@ -614,6 +621,22 @@ bool MqttCommand()
}
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, Settings.mqtt_prefix[index -1]);
}
else if (CMND_PUBLISH == command_code) {
if (data_len > 0) {
char *mqtt_part = strtok(dataBuf, " ");
if (mqtt_part) {
snprintf(stemp1, sizeof(stemp1), mqtt_part);
mqtt_part = strtok(NULL, " ");
if (mqtt_part) {
snprintf(mqtt_data, sizeof(mqtt_data), mqtt_part);
} else {
mqtt_data[0] = '\0';
}
MqttPublishDirect(stemp1, false);
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_JSON_DONE);
}
}
}
else if (CMND_GROUPTOPIC == command_code) {
if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_grptopic))) {
MakeValidMqtt(0, dataBuf);
@ -658,7 +681,7 @@ bool MqttCommand()
strlcpy(Settings.button_topic, mqtt_topic, sizeof(Settings.button_topic));
if (!payload) {
for(i = 1; i <= MAX_KEYS; i++) {
send_button_power(0, i, 9); // Clear MQTT retain in broker
SendKey(0, i, 9); // Clear MQTT retain in broker
}
}
Settings.flag.mqtt_button_retain = payload;
@ -670,7 +693,7 @@ bool MqttCommand()
strlcpy(Settings.button_topic, mqtt_topic, sizeof(Settings.button_topic));
if (!payload) {
for(i = 1; i <= MAX_SWITCHES; i++) {
send_button_power(1, i, 9); // Clear MQTT retain in broker
SendKey(1, i, 9); // Clear MQTT retain in broker
}
}
Settings.flag.mqtt_switch_retain = payload;

View File

@ -265,7 +265,7 @@ boolean DomoticzCommand()
return serviced;
}
boolean DomoticzButton(byte key, byte device, byte state, byte svalflg)
boolean DomoticzSendKey(byte key, byte device, byte state, byte svalflg)
{
if ((Settings.domoticz_key_idx[device -1] || Settings.domoticz_switch_idx[device -1]) && (svalflg)) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"command\":\"switchlight\",\"idx\":%d,\"switchcmd\":\"%s\"}"),

View File

@ -224,10 +224,10 @@ void TimerEverySecond()
Settings.timer[i].arm = Settings.timer[i].repeat;
#ifdef USE_RULES
if (3 == Settings.timer[i].power) { // Blink becomes Rule disregarding device and allowing use of Backlog commands
XdrvMailbox.index = i;
XdrvCall(FUNC_CLOCK_TIMER);
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"Clock\":{\"Timer\":%d}}"), i +1);
RulesProcess();
} else
#endif
#endif // USE_RULES
ExecuteCommandPower(Settings.timer[i].device +1, Settings.timer[i].power);
}
}

443
sonoff/xdrv_10_rules.ino Normal file
View File

@ -0,0 +1,443 @@
/*
xdrv_10_rules.ino - rule support for Sonoff-Tasmota
Copyright (C) 2018 ESP Easy Group and Theo Arends
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
/*********************************************************************************************\
* 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
* 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
* on event#anyname do color %eventvalue% endon
* on power1#state=1 do color 001000 endon
* on button1#state do publish cmnd/ring2/power %eventvalue% endon on button2#state do publish cmnd/strip1/power %eventvalue% endon
* on switch1#state do power2 %eventvalue% endon
*
* 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
\*********************************************************************************************/
#define MAX_RULE_TIMERS 8
#define ULONG_MAX 0xffffffffUL
#define D_CMND_RULE "Rule"
#define D_CMND_RULETIMER "RuleTimer"
#define D_CMND_EVENT "Event"
#define D_JSON_INITIATED "Initiated"
enum RulesCommands { CMND_RULE, CMND_RULETIMER, CMND_EVENT };
const char kRulesCommands[] PROGMEM = D_CMND_RULE "|" D_CMND_RULETIMER "|" D_CMND_EVENT ;
String rules_event_value;
unsigned long rules_timer[MAX_RULE_TIMERS] = { 0 };
uint8_t rules_quota = 0;
long rules_power = -1;
uint32_t rules_triggers = 0;
uint8_t rules_trigger_count = 0;
/*******************************************************************************************/
long TimeDifference(unsigned long prev, unsigned long next)
{
// Return the time difference as a signed value, taking into account the timers may overflow.
// Returned timediff is between -24.9 days and +24.9 days.
// Returned value is positive when "next" is after "prev"
long signed_diff = 0;
// To cast a value to a signed long, the difference may not exceed half the ULONG_MAX
const unsigned long half_max_unsigned_long = 2147483647u; // = 2^31 -1
if (next >= prev) {
const unsigned long diff = next - prev;
if (diff <= half_max_unsigned_long) { // Normal situation, just return the difference.
signed_diff = static_cast<long>(diff); // Difference is a positive value.
} else {
// prev has overflow, return a negative difference value
signed_diff = static_cast<long>((ULONG_MAX - next) + prev + 1u);
signed_diff = -1 * signed_diff;
}
} else {
// next < prev
const unsigned long diff = prev - next;
if (diff <= half_max_unsigned_long) { // Normal situation, return a negative difference value
signed_diff = static_cast<long>(diff);
signed_diff = -1 * signed_diff;
} else {
// next has overflow, return a positive difference value
signed_diff = static_cast<long>((ULONG_MAX - prev) + next + 1u);
}
}
return signed_diff;
}
long TimePassedSince(unsigned long timestamp)
{
// Compute the number of milliSeconds passed since timestamp given.
// Note: value can be negative if the timestamp has not yet been reached.
return TimeDifference(timestamp, millis());
}
bool TimeReached(unsigned long timer)
{
// Check if a certain timeout has been reached.
const long passed = TimePassedSince(timer);
return (passed >= 0);
}
/*******************************************************************************************/
bool RulesRuleMatch(String &event, String &rule)
{
// event = {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}}
// event = {"System":{"Boot":1}}
// rule = "INA219#CURRENT>0.100"
bool match = false;
// Step1: Analyse rule
int pos = rule.indexOf('#');
if (pos == -1) return false; // No # sign in rule
String rule_task = rule.substring(0, pos); // "INA219" or "SYSTEM"
String rule_name = rule.substring(pos +1); // "CURRENT>0.100" or "BOOT"
char compare = ' ';
pos = rule_name.indexOf(">");
if (pos > 0) {
compare = '>';
} else {
pos = rule_name.indexOf("<");
if (pos > 0) {
compare = '<';
} else {
pos = rule_name.indexOf("=");
if (pos > 0) {
compare = '=';
}
}
}
String tmp_value = "none";
double rule_value = 0;
if (pos > 0) {
tmp_value = rule_name.substring(pos + 1); // "0.100"
rule_value = CharToDouble((char*)tmp_value.c_str()); // 0.1 - This saves 9k code over toFLoat()!
rule_name = rule_name.substring(0, pos); // "CURRENT"
}
// Step2: Search rule_task and rule_name
StaticJsonBuffer<400> jsonBuf;
JsonObject &root = jsonBuf.parseObject(event);
if (!root.success()) return false; // No valid JSON data
double value = 0;
const char* str_value = root[rule_task][rule_name];
// snprintf_P(log_data, sizeof(log_data), PSTR("RUL: Task %s, Name %s, Value %s, TrigCnt %d, TrigSt %d, Source %s, Json %s"),
// rule_task.c_str(), rule_name.c_str(), tmp_value.c_str(), rules_trigger_count, bitRead(rules_triggers, rules_trigger_count), event.c_str(), (str_value) ? str_value : "none");
// AddLog(LOG_LEVEL_DEBUG);
if (!root[rule_task][rule_name].success()) return false;
// No value but rule_name is ok
rules_event_value = str_value; // Prepare %eventvalue%
// Step 3: Compare rule (value)
if (str_value) {
value = CharToDouble((char*)str_value);
switch (compare) {
case '>':
if (value > rule_value) match = true;
break;
case '<':
if (value < rule_value) match = true;
break;
case '=':
if (value == rule_value) match = true;
break;
case ' ':
match = true; // Json value but not needed
break;
}
} else match = true;
if (Settings.flag.rules_once) {
if (match) { // Only allow match state changes
if (!bitRead(rules_triggers, rules_trigger_count)) {
bitSet(rules_triggers, rules_trigger_count);
} else {
match = false;
}
} else {
bitClear(rules_triggers, rules_trigger_count);
}
}
return match;
}
/*******************************************************************************************/
bool RulesProcess()
{
bool serviced = false;
if (!Settings.flag.rules_enabled) return serviced; // Not enabled
if (!strlen(Settings.rules)) return serviced; // No rules
String event_saved = mqtt_data;
event_saved.toUpperCase();
String rules = Settings.rules;
rules_trigger_count = 0;
int plen = 0;
while (true) {
rules = rules.substring(plen); // Select relative to last rule
rules.trim();
if (!rules.length()) return serviced; // No more rules
String rule = rules;
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
int pevt = rule.indexOf(" DO ");
if (pevt == -1) return serviced; // Bad syntax - Nothing to do
String event_trigger = rule.substring(3, pevt); // "INA219#CURRENT>0.100"
plen = rule.indexOf(" ENDON");
if (plen == -1) return serviced; // Bad syntax - No endon
String commands = rules.substring(pevt +4, plen); // "Backlog Dimmer 10;Color 100000"
plen += 6;
// snprintf_P(log_data, sizeof(log_data), PSTR("RUL: Trigger |%s|, Commands |%s|"), event_trigger.c_str(), commands.c_str());
// AddLog(LOG_LEVEL_DEBUG);
rules_event_value = "";
String event = event_saved;
if (RulesRuleMatch(event, event_trigger)) {
commands.replace(F("%eventvalue%"), rules_event_value);
char command[commands.length() +1];
snprintf(command, sizeof(command), commands.c_str());
snprintf_P(log_data, sizeof(log_data), PSTR("RUL: %s performs \"%s\""), event_trigger.c_str(), command);
AddLog(LOG_LEVEL_INFO);
// snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, D_CMND_RULE, D_JSON_INITIATED);
// MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_RULE));
ExecuteCommand(command);
serviced = true;
}
rules_trigger_count++;
}
return serviced;
}
/*******************************************************************************************/
void RulesInit()
{
if (Settings.rules[0] == '\0') {
Settings.flag.rules_enabled = 0;
Settings.flag.rules_once = 0;
}
}
void RulesSetPower()
{
if (Settings.flag.rules_enabled) {
uint16_t new_power = XdrvMailbox.index;
if (rules_power == -1) rules_power = new_power;
uint16_t old_power = rules_power;
rules_power = new_power;
for (byte i = 0; i < devices_present; i++) {
uint8_t new_state = new_power &1;
uint8_t old_state = old_power &1;
if (new_state != old_state) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"Power%d\":{\"State\":%d}}"), i +1, new_state);
RulesProcess();
}
new_power >>= 1;
old_power >>= 1;
}
}
}
void RulesEvery50ms()
{
if (Settings.flag.rules_enabled) {
rules_quota++;
if (rules_quota &1) { // Every 100 ms
mqtt_data[0] = '\0';
uint16_t tele_period_save = tele_period;
tele_period = 2; // Do not allow HA updates during next function call
XsnsNextCall(FUNC_JSON_APPEND); // ,"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
tele_period = tele_period_save;
if (strlen(mqtt_data)) {
mqtt_data[0] = '{'; // {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
RulesProcess();
}
}
}
}
void RulesEverySecond()
{
if (Settings.flag.rules_enabled) {
for (byte i = 0; i < MAX_RULE_TIMERS; i++) {
if (rules_timer[i] != 0L) { // Timer active?
if (TimeReached(rules_timer[i])) { // Timer finished?
rules_timer[i] = 0L; // Turn off this timer
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"Rules\":{\"Timer\":%d}}"), i +1);
RulesProcess();
}
}
}
}
}
boolean RulesCommand()
{
char command[CMDSZ];
boolean serviced = true;
uint8_t index = XdrvMailbox.index;
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kRulesCommands);
if (CMND_RULE == command_code) {
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.rules))) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6)) {
switch (XdrvMailbox.payload) {
case 0: // Off
case 1: // On
Settings.flag.rules_enabled = XdrvMailbox.payload;
break;
case 2: // Toggle
Settings.flag.rules_enabled ^= 1;
break;
case 4: // Off
case 5: // On
Settings.flag.rules_once = XdrvMailbox.payload &1;
break;
case 6: // Toggle
Settings.flag.rules_once ^= 1;
break;
}
} else {
/*
String uc_data = XdrvMailbox.data; // Do not allow Rule to be used within a rule
uc_data.toUpperCase();
String uc_command = command;
uc_command += " "; // Distuingish from RuleTimer
uc_command.toUpperCase();
if (!uc_data.indexOf(uc_command)) strlcpy(Settings.rules, XdrvMailbox.data, sizeof(Settings.rules));
*/
strlcpy(Settings.rules, XdrvMailbox.data, sizeof(Settings.rules));
}
rules_triggers = 0; // Reset once flag
}
snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\",\"Once\":\"%s\",\"Rules\":\"%s\"}"), command, GetStateText(Settings.flag.rules_enabled), GetStateText(Settings.flag.rules_once), Settings.rules);
}
else if ((CMND_RULETIMER == command_code) && (index > 0) && (index <= MAX_RULE_TIMERS)) {
if (XdrvMailbox.data_len > 0) {
rules_timer[index -1] = (XdrvMailbox.payload > 0) ? millis() + (1000 * XdrvMailbox.payload) : 0;
}
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_LVALUE, command, index, (rules_timer[index -1]) ? (rules_timer[index -1] - millis()) / 1000 : 0);
}
else if (CMND_EVENT == command_code) {
if (XdrvMailbox.data_len > 0) {
String event = XdrvMailbox.data;
String parameter = "";
int pos = event.indexOf('=');
if (pos > 0) {
parameter = event.substring(pos +1);
parameter.trim();
event = event.substring(0, pos);
}
event.trim();
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"Event\":{\"%s\":\"%s\"}}"), event.c_str(), parameter.c_str());
RulesProcess();
}
snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_JSON_DONE);
}
else serviced = false;
return serviced;
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
#define XDRV_10
boolean Xdrv10(byte function)
{
boolean result = false;
switch (function) {
case FUNC_INIT:
RulesInit();
break;
case FUNC_SET_POWER:
RulesSetPower();
break;
case FUNC_EVERY_50_MSECOND:
RulesEvery50ms();
break;
case FUNC_EVERY_SECOND:
RulesEverySecond();
break;
case FUNC_COMMAND:
result = RulesCommand();
break;
}
return result;
}
#endif // USE_RULES

View File

@ -182,17 +182,12 @@ boolean XdrvMqttData(char *topicBuf, uint16_t stopicBuf, char *dataBuf, uint16_t
* FUNC_LOOP
* FUNC_MQTT_SUBSCRIBE
* FUNC_MQTT_INIT
* FUNC_MQTT_DISCONNECTED
* FUNC_MQTT_CONNECTED
* return FUNC_MQTT_DATA
* return FUNC_COMMAND
* FUNC_SET_POWER
* FUNC_SHOW_SENSOR
* FUNC_EVERY_SECOND
* FUNC_EVERY_50_MSECOND
* FUNC_NTP_INIT
* FUNC_NTP_SET
* FUNC_CLOCK_TIMER
\*********************************************************************************************/
boolean XdrvCall(byte Function)