mirror of https://github.com/arendst/Tasmota.git
commit
ea07c79043
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 };
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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\"}"),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue