xdrv_10_rules.ino 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. /*
  2. xdrv_10_rules.ino - rule support for Sonoff-Tasmota
  3. Copyright (C) 2018 ESP Easy Group and Theo Arends
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #ifdef USE_RULES
  16. /*********************************************************************************************\
  17. * Rules based heavily on ESP Easy implementation
  18. *
  19. * Inspiration: https://github.com/letscontrolit/ESPEasy
  20. *
  21. * Add rules using the following, case insensitive, format:
  22. * on <trigger1> do <commands> endon on <trigger2> do <commands> endon ..
  23. *
  24. * Examples:
  25. * on System#Boot do Color 001000 endon
  26. * on INA219#Current>0.100 do Dimmer 10 endon
  27. * on INA219#Current>0.100 do Backlog Dimmer 10;Color 10,0,0 endon
  28. * on INA219#Current>0.100 do Backlog Dimmer 10;Color 100000 endon on System#Boot do color 001000 endon
  29. * on ds18b20#temperature>23 do power off endon on ds18b20#temperature<22 do power on endon
  30. * on mqtt#connected do color 000010 endon
  31. * on mqtt#disconnected do color 00100C endon
  32. * on time#initialized do color 001000 endon
  33. * on time#initialized>120 do color 001000 endon
  34. * on time#set do color 001008 endon
  35. * on clock#timer=3 do color 080800 endon
  36. * on rules#timer=1 do color 080800 endon
  37. * 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
  38. * on event#anyname do color 100000 endon
  39. * on event#anyname do color %value% endon
  40. * on power1#state=1 do color 001000 endon
  41. * on button1#state do publish cmnd/ring2/power %value% endon on button2#state do publish cmnd/strip1/power %value% endon
  42. * on switch1#state do power2 %value% endon
  43. * on analog#a0div10 do publish cmnd/ring2/dimmer %value% endon
  44. *
  45. * Notes:
  46. * Spaces after <on>, around <do> and before <endon> are mandatory
  47. * System#Boot is initiated after MQTT is connected due to command handling preparation
  48. * Control rule triggering with command:
  49. * Rule 0 = Rules disabled (Off)
  50. * Rule 1 = Rules enabled (On)
  51. * Rule 2 = Toggle rules state
  52. * Rule 4 = Perform commands as long as trigger is met (Once OFF)
  53. * Rule 5 = Perform commands once until trigger is not met (Once ON)
  54. * Rule 6 = Toggle Once state
  55. * Execute an event like:
  56. * Event anyname=001000
  57. * Set a RuleTimer to 100 seconds like:
  58. * RuleTimer2 100
  59. \*********************************************************************************************/
  60. #define XDRV_10 10
  61. #define D_CMND_RULE "Rule"
  62. #define D_CMND_RULETIMER "RuleTimer"
  63. #define D_CMND_EVENT "Event"
  64. #define D_CMND_VAR "Var"
  65. #define D_CMND_MEM "Mem"
  66. #define D_CMND_ADD "Add"
  67. #define D_CMND_SUB "Sub"
  68. #define D_CMND_MULT "Mult"
  69. #define D_CMND_SCALE "Scale"
  70. #define D_CMND_CALC_RESOLUTION "CalcRes"
  71. #define D_JSON_INITIATED "Initiated"
  72. enum RulesCommands { CMND_RULE, CMND_RULETIMER, CMND_EVENT, CMND_VAR, CMND_MEM, CMND_ADD, CMND_SUB, CMND_MULT, CMND_SCALE, CMND_CALC_RESOLUTION };
  73. 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 ;
  74. String rules_event_value;
  75. unsigned long rules_timer[MAX_RULE_TIMERS] = { 0 };
  76. uint8_t rules_quota = 0;
  77. long rules_new_power = -1;
  78. long rules_old_power = -1;
  79. long rules_old_dimm = -1;
  80. uint32_t rules_triggers[MAX_RULE_SETS] = { 0 };
  81. uint16_t rules_last_minute = 60;
  82. uint8_t rules_trigger_count[MAX_RULE_SETS] = { 0 };
  83. uint8_t rules_teleperiod = 0;
  84. char event_data[100];
  85. char vars[MAX_RULE_VARS][33] = { 0 };
  86. /*******************************************************************************************/
  87. bool RulesRuleMatch(byte rule_set, String &event, String &rule)
  88. {
  89. // event = {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}}
  90. // event = {"System":{"Boot":1}}
  91. // rule = "INA219#CURRENT>0.100"
  92. bool match = false;
  93. char stemp[10];
  94. // Step1: Analyse rule
  95. int pos = rule.indexOf('#');
  96. if (pos == -1) { return false; } // No # sign in rule
  97. String rule_task = rule.substring(0, pos); // "INA219" or "SYSTEM"
  98. if (rules_teleperiod) {
  99. int ppos = rule_task.indexOf("TELE-"); // "TELE-INA219" or "INA219"
  100. if (ppos == -1) { return false; } // No pre-amble in rule
  101. rule_task = rule.substring(5, pos); // "INA219" or "SYSTEM"
  102. }
  103. String rule_name = rule.substring(pos +1); // "CURRENT>0.100" or "BOOT" or "%var1%" or "MINUTE|5"
  104. char compare = ' ';
  105. pos = rule_name.indexOf(">");
  106. if (pos > 0) {
  107. compare = '>';
  108. } else {
  109. pos = rule_name.indexOf("<");
  110. if (pos > 0) {
  111. compare = '<';
  112. } else {
  113. pos = rule_name.indexOf("=");
  114. if (pos > 0) {
  115. compare = '=';
  116. } else {
  117. pos = rule_name.indexOf("|"); // Modulo, cannot use % easily as it is used for variable detection
  118. if (pos > 0) {
  119. compare = '%';
  120. }
  121. }
  122. }
  123. }
  124. char rule_svalue[CMDSZ] = { 0 };
  125. double rule_value = 0;
  126. if (pos > 0) {
  127. String rule_param = rule_name.substring(pos + 1);
  128. for (byte i = 0; i < MAX_RULE_VARS; i++) {
  129. snprintf_P(stemp, sizeof(stemp), PSTR("%%VAR%d%%"), i +1);
  130. if (rule_param.startsWith(stemp)) {
  131. rule_param = vars[i];
  132. break;
  133. }
  134. }
  135. for (byte i = 0; i < MAX_RULE_MEMS; i++) {
  136. snprintf_P(stemp, sizeof(stemp), PSTR("%%MEM%d%%"), i +1);
  137. if (rule_param.startsWith(stemp)) {
  138. rule_param = Settings.mems[i];
  139. break;
  140. }
  141. }
  142. snprintf_P(stemp, sizeof(stemp), PSTR("%%TIME%%"));
  143. if (rule_param.startsWith(stemp)) {
  144. rule_param = String(GetMinutesPastMidnight());
  145. }
  146. snprintf_P(stemp, sizeof(stemp), PSTR("%%UPTIME%%"));
  147. if (rule_param.startsWith(stemp)) {
  148. rule_param = String(GetMinutesUptime());
  149. }
  150. #if defined(USE_TIMERS) && defined(USE_SUNRISE)
  151. snprintf_P(stemp, sizeof(stemp), PSTR("%%SUNRISE%%"));
  152. if (rule_param.startsWith(stemp)) {
  153. rule_param = String(GetSunMinutes(0));
  154. }
  155. snprintf_P(stemp, sizeof(stemp), PSTR("%%SUNSET%%"));
  156. if (rule_param.startsWith(stemp)) {
  157. rule_param = String(GetSunMinutes(1));
  158. }
  159. #endif // USE_TIMERS and USE_SUNRISE
  160. rule_param.toUpperCase();
  161. snprintf(rule_svalue, sizeof(rule_svalue), rule_param.c_str());
  162. int temp_value = GetStateNumber(rule_svalue);
  163. if (temp_value > -1) {
  164. rule_value = temp_value;
  165. } else {
  166. rule_value = CharToDouble((char*)rule_svalue); // 0.1 - This saves 9k code over toFLoat()!
  167. }
  168. rule_name = rule_name.substring(0, pos); // "CURRENT"
  169. }
  170. // Step2: Search rule_task and rule_name
  171. StaticJsonBuffer<1024> jsonBuf;
  172. JsonObject &root = jsonBuf.parseObject(event);
  173. if (!root.success()) { return false; } // No valid JSON data
  174. double value = 0;
  175. const char* str_value = root[rule_task][rule_name];
  176. //snprintf_P(log_data, sizeof(log_data), PSTR("RUL: Task %s, Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"),
  177. // 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");
  178. //AddLog(LOG_LEVEL_DEBUG);
  179. if (!root[rule_task][rule_name].success()) { return false; }
  180. // No value but rule_name is ok
  181. rules_event_value = str_value; // Prepare %value%
  182. // Step 3: Compare rule (value)
  183. if (str_value) {
  184. value = CharToDouble((char*)str_value);
  185. int int_value = int(value);
  186. int int_rule_value = int(rule_value);
  187. switch (compare) {
  188. case '%':
  189. if ((int_value > 0) && (int_rule_value > 0)) {
  190. if ((int_value % int_rule_value) == 0) { match = true; }
  191. }
  192. break;
  193. case '>':
  194. if (value > rule_value) { match = true; }
  195. break;
  196. case '<':
  197. if (value < rule_value) { match = true; }
  198. break;
  199. case '=':
  200. // if (value == rule_value) { match = true; } // Compare values - only decimals or partly hexadecimals
  201. if (!strcasecmp(str_value, rule_svalue)) { match = true; } // Compare strings - this also works for hexadecimals
  202. break;
  203. case ' ':
  204. match = true; // Json value but not needed
  205. break;
  206. }
  207. } else match = true;
  208. if (bitRead(Settings.rule_once, rule_set)) {
  209. if (match) { // Only allow match state changes
  210. if (!bitRead(rules_triggers[rule_set], rules_trigger_count[rule_set])) {
  211. bitSet(rules_triggers[rule_set], rules_trigger_count[rule_set]);
  212. } else {
  213. match = false;
  214. }
  215. } else {
  216. bitClear(rules_triggers[rule_set], rules_trigger_count[rule_set]);
  217. }
  218. }
  219. return match;
  220. }
  221. /*******************************************************************************************/
  222. bool RuleSetProcess(byte rule_set, String &event_saved)
  223. {
  224. bool serviced = false;
  225. char stemp[10];
  226. delay(0); // Prohibit possible loop software watchdog
  227. //snprintf_P(log_data, sizeof(log_data), PSTR("RUL: Event = %s, Rule = %s"), event_saved.c_str(), Settings.rules[rule_set]);
  228. //AddLog(LOG_LEVEL_DEBUG);
  229. String rules = Settings.rules[rule_set];
  230. rules_trigger_count[rule_set] = 0;
  231. int plen = 0;
  232. int plen2 = 0;
  233. bool stop_all_rules = false;
  234. while (true) {
  235. rules = rules.substring(plen); // Select relative to last rule
  236. rules.trim();
  237. if (!rules.length()) { return serviced; } // No more rules
  238. String rule = rules;
  239. rule.toUpperCase(); // "ON INA219#CURRENT>0.100 DO BACKLOG DIMMER 10;COLOR 100000 ENDON"
  240. if (!rule.startsWith("ON ")) { return serviced; } // Bad syntax - Nothing to start on
  241. int pevt = rule.indexOf(" DO ");
  242. if (pevt == -1) { return serviced; } // Bad syntax - Nothing to do
  243. String event_trigger = rule.substring(3, pevt); // "INA219#CURRENT>0.100"
  244. plen = rule.indexOf(" ENDON");
  245. plen2 = rule.indexOf(" BREAK");
  246. if ((plen == -1) && (plen2 == -1)) { return serviced; } // Bad syntax - No ENDON neither BREAK
  247. if (plen == -1) { plen = 9999; }
  248. if (plen2 == -1) { plen2 = 9999; }
  249. plen = tmin(plen, plen2);
  250. if (plen == plen2) { stop_all_rules = true; } // If BREAK was used, Stop execution of this rule set
  251. String commands = rules.substring(pevt +4, plen); // "Backlog Dimmer 10;Color 100000"
  252. plen += 6;
  253. rules_event_value = "";
  254. String event = event_saved;
  255. //snprintf_P(log_data, sizeof(log_data), PSTR("RUL: Event |%s|, Rule |%s|, Command(s) |%s|"), event.c_str(), event_trigger.c_str(), commands.c_str());
  256. //AddLog(LOG_LEVEL_DEBUG);
  257. if (RulesRuleMatch(rule_set, event, event_trigger)) {
  258. commands.trim();
  259. String ucommand = commands;
  260. ucommand.toUpperCase();
  261. // if (!ucommand.startsWith("BACKLOG")) { commands = "backlog " + commands; } // Always use Backlog to prevent power race exception
  262. if (ucommand.indexOf("EVENT ") != -1) { commands = "backlog " + commands; } // Always use Backlog with event to prevent rule event loop exception
  263. commands.replace(F("%value%"), rules_event_value);
  264. for (byte i = 0; i < MAX_RULE_VARS; i++) {
  265. snprintf_P(stemp, sizeof(stemp), PSTR("%%var%d%%"), i +1);
  266. commands.replace(stemp, vars[i]);
  267. }
  268. for (byte i = 0; i < MAX_RULE_MEMS; i++) {
  269. snprintf_P(stemp, sizeof(stemp), PSTR("%%mem%d%%"), i +1);
  270. commands.replace(stemp, Settings.mems[i]);
  271. }
  272. commands.replace(F("%time%"), String(GetMinutesPastMidnight()));
  273. commands.replace(F("%uptime%"), String(GetMinutesUptime()));
  274. #if defined(USE_TIMERS) && defined(USE_SUNRISE)
  275. commands.replace(F("%sunrise%"), String(GetSunMinutes(0)));
  276. commands.replace(F("%sunset%"), String(GetSunMinutes(1)));
  277. #endif // USE_TIMERS and USE_SUNRISE
  278. char command[commands.length() +1];
  279. snprintf(command, sizeof(command), commands.c_str());
  280. snprintf_P(log_data, sizeof(log_data), PSTR("RUL: %s performs \"%s\""), event_trigger.c_str(), command);
  281. AddLog(LOG_LEVEL_INFO);
  282. // snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, D_CMND_RULE, D_JSON_INITIATED);
  283. // MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_RULE));
  284. ExecuteCommand(command, SRC_RULE);
  285. serviced = true;
  286. if (stop_all_rules) { return serviced; } // If BREAK was used, Stop execution of this rule set
  287. }
  288. rules_trigger_count[rule_set]++;
  289. }
  290. return serviced;
  291. }
  292. /*******************************************************************************************/
  293. bool RulesProcessEvent(char *json_event)
  294. {
  295. bool serviced = false;
  296. ShowFreeMem(PSTR("RulesProcessEvent"));
  297. String event_saved = json_event;
  298. event_saved.toUpperCase();
  299. //snprintf_P(log_data, sizeof(log_data), PSTR("RUL: Event %s"), event_saved.c_str());
  300. //AddLog(LOG_LEVEL_DEBUG);
  301. for (byte i = 0; i < MAX_RULE_SETS; i++) {
  302. if (strlen(Settings.rules[i]) && bitRead(Settings.rule_enabled, i)) {
  303. if (RuleSetProcess(i, event_saved)) { serviced = true; }
  304. }
  305. }
  306. return serviced;
  307. }
  308. bool RulesProcess(void)
  309. {
  310. return RulesProcessEvent(mqtt_data);
  311. }
  312. void RulesInit(void)
  313. {
  314. rules_flag.data = 0;
  315. for (byte i = 0; i < MAX_RULE_SETS; i++) {
  316. if (Settings.rules[i][0] == '\0') {
  317. bitWrite(Settings.rule_enabled, i, 0);
  318. bitWrite(Settings.rule_once, i, 0);
  319. }
  320. }
  321. rules_teleperiod = 0;
  322. }
  323. void RulesEvery50ms(void)
  324. {
  325. if (Settings.rule_enabled) { // Any rule enabled
  326. char json_event[120];
  327. if (-1 == rules_new_power) { rules_new_power = power; }
  328. if (rules_new_power != rules_old_power) {
  329. if (rules_old_power != -1) {
  330. for (byte i = 0; i < devices_present; i++) {
  331. uint8_t new_state = (rules_new_power >> i) &1;
  332. if (new_state != ((rules_old_power >> i) &1)) {
  333. snprintf_P(json_event, sizeof(json_event), PSTR("{\"Power%d\":{\"State\":%d}}"), i +1, new_state);
  334. RulesProcessEvent(json_event);
  335. }
  336. }
  337. } else {
  338. // Boot time POWER OUTPUTS (Relays) Status
  339. for (byte i = 0; i < devices_present; i++) {
  340. uint8_t new_state = (rules_new_power >> i) &1;
  341. snprintf_P(json_event, sizeof(json_event), PSTR("{\"Power%d\":{\"Boot\":%d}}"), i +1, new_state);
  342. RulesProcessEvent(json_event);
  343. }
  344. // Boot time SWITCHES Status
  345. for (byte i = 0; i < MAX_SWITCHES; i++) {
  346. #ifdef USE_TM1638
  347. if ((pin[GPIO_SWT1 +i] < 99) || ((pin[GPIO_TM16CLK] < 99) && (pin[GPIO_TM16DIO] < 99) && (pin[GPIO_TM16STB] < 99))) {
  348. #else
  349. if (pin[GPIO_SWT1 +i] < 99) {
  350. #endif // USE_TM1638
  351. boolean swm = ((FOLLOW_INV == Settings.switchmode[i]) || (PUSHBUTTON_INV == Settings.switchmode[i]) || (PUSHBUTTONHOLD_INV == Settings.switchmode[i]));
  352. snprintf_P(json_event, sizeof(json_event), PSTR("{\"" D_JSON_SWITCH "%d\":{\"Boot\":%d}}"), i +1, (swm ^ lastwallswitch[i]));
  353. RulesProcessEvent(json_event);
  354. }
  355. }
  356. }
  357. rules_old_power = rules_new_power;
  358. }
  359. else if (rules_old_dimm != Settings.light_dimmer) {
  360. if (rules_old_dimm != -1) {
  361. snprintf_P(json_event, sizeof(json_event), PSTR("{\"Dimmer\":{\"State\":%d}}"), Settings.light_dimmer);
  362. } else {
  363. // Boot time DIMMER VALUE
  364. snprintf_P(json_event, sizeof(json_event), PSTR("{\"Dimmer\":{\"Boot\":%d}}"), Settings.light_dimmer);
  365. }
  366. RulesProcessEvent(json_event);
  367. rules_old_dimm = Settings.light_dimmer;
  368. }
  369. else if (event_data[0]) {
  370. char *event;
  371. char *parameter;
  372. event = strtok_r(event_data, "=", &parameter); // event_data = fanspeed=10
  373. if (event) {
  374. event = Trim(event);
  375. if (parameter) {
  376. parameter = Trim(parameter);
  377. } else {
  378. parameter = event + strlen(event); // '\0'
  379. }
  380. snprintf_P(json_event, sizeof(json_event), PSTR("{\"Event\":{\"%s\":\"%s\"}}"), event, parameter);
  381. event_data[0] ='\0';
  382. RulesProcessEvent(json_event);
  383. } else {
  384. event_data[0] ='\0';
  385. }
  386. }
  387. else if (rules_flag.data) {
  388. uint16_t mask = 1;
  389. for (byte i = 0; i < MAX_RULES_FLAG; i++) {
  390. if (rules_flag.data & mask) {
  391. rules_flag.data ^= mask;
  392. json_event[0] = '\0';
  393. switch (i) {
  394. case 0: strncpy_P(json_event, PSTR("{\"System\":{\"Boot\":1}}"), sizeof(json_event)); break;
  395. case 1: snprintf_P(json_event, sizeof(json_event), PSTR("{\"Time\":{\"Initialized\":%d}}"), GetMinutesPastMidnight()); break;
  396. case 2: snprintf_P(json_event, sizeof(json_event), PSTR("{\"Time\":{\"Set\":%d}}"), GetMinutesPastMidnight()); break;
  397. case 3: strncpy_P(json_event, PSTR("{\"MQTT\":{\"Connected\":1}}"), sizeof(json_event)); break;
  398. case 4: strncpy_P(json_event, PSTR("{\"MQTT\":{\"Disconnected\":1}}"), sizeof(json_event)); break;
  399. case 5: strncpy_P(json_event, PSTR("{\"WIFI\":{\"Connected\":1}}"), sizeof(json_event)); break;
  400. case 6: strncpy_P(json_event, PSTR("{\"WIFI\":{\"Disconnected\":1}}"), sizeof(json_event)); break;
  401. }
  402. if (json_event[0]) {
  403. RulesProcessEvent(json_event);
  404. break; // Only service one event within 50mS
  405. }
  406. }
  407. mask <<= 1;
  408. }
  409. }
  410. }
  411. }
  412. uint8_t rules_xsns_index = 0;
  413. void RulesEvery100ms(void)
  414. {
  415. if (Settings.rule_enabled && (uptime > 4)) { // Any rule enabled and allow 4 seconds start-up time for sensors (#3811)
  416. mqtt_data[0] = '\0';
  417. int tele_period_save = tele_period;
  418. tele_period = 2; // Do not allow HA updates during next function call
  419. XsnsNextCall(FUNC_JSON_APPEND, rules_xsns_index); // ,"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
  420. tele_period = tele_period_save;
  421. if (strlen(mqtt_data)) {
  422. mqtt_data[0] = '{'; // {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}
  423. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
  424. RulesProcess();
  425. }
  426. }
  427. }
  428. void RulesEverySecond(void)
  429. {
  430. if (Settings.rule_enabled) { // Any rule enabled
  431. char json_event[120];
  432. if (RtcTime.valid) {
  433. if ((uptime > 60) && (RtcTime.minute != rules_last_minute)) { // Execute from one minute after restart every minute only once
  434. rules_last_minute = RtcTime.minute;
  435. snprintf_P(json_event, sizeof(json_event), PSTR("{\"Time\":{\"Minute\":%d}}"), GetMinutesPastMidnight());
  436. RulesProcessEvent(json_event);
  437. }
  438. }
  439. for (byte i = 0; i < MAX_RULE_TIMERS; i++) {
  440. if (rules_timer[i] != 0L) { // Timer active?
  441. if (TimeReached(rules_timer[i])) { // Timer finished?
  442. rules_timer[i] = 0L; // Turn off this timer
  443. snprintf_P(json_event, sizeof(json_event), PSTR("{\"Rules\":{\"Timer\":%d}}"), i +1);
  444. RulesProcessEvent(json_event);
  445. }
  446. }
  447. }
  448. }
  449. }
  450. void RulesSetPower(void)
  451. {
  452. rules_new_power = XdrvMailbox.index;
  453. }
  454. void RulesTeleperiod(void)
  455. {
  456. rules_teleperiod = 1;
  457. RulesProcess();
  458. rules_teleperiod = 0;
  459. }
  460. boolean RulesCommand(void)
  461. {
  462. char command[CMDSZ];
  463. boolean serviced = true;
  464. uint8_t index = XdrvMailbox.index;
  465. int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kRulesCommands);
  466. if (-1 == command_code) {
  467. serviced = false; // Unknown command
  468. }
  469. else if ((CMND_RULE == command_code) && (index > 0) && (index <= MAX_RULE_SETS)) {
  470. if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.rules[index -1]))) {
  471. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) {
  472. switch (XdrvMailbox.payload) {
  473. case 0: // Off
  474. case 1: // On
  475. bitWrite(Settings.rule_enabled, index -1, XdrvMailbox.payload);
  476. break;
  477. case 2: // Toggle
  478. bitWrite(Settings.rule_enabled, index -1, bitRead(Settings.rule_enabled, index -1) ^1);
  479. break;
  480. case 4: // Off
  481. case 5: // On
  482. bitWrite(Settings.rule_once, index -1, XdrvMailbox.payload &1);
  483. break;
  484. case 6: // Toggle
  485. bitWrite(Settings.rule_once, index -1, bitRead(Settings.rule_once, index -1) ^1);
  486. break;
  487. case 8: // Off
  488. case 9: // On
  489. bitWrite(Settings.rule_stop, index -1, XdrvMailbox.payload &1);
  490. break;
  491. case 10: // Toggle
  492. bitWrite(Settings.rule_stop, index -1, bitRead(Settings.rule_stop, index -1) ^1);
  493. break;
  494. }
  495. } else {
  496. int offset = 0;
  497. if ('+' == XdrvMailbox.data[0]) {
  498. offset = strlen(Settings.rules[index -1]);
  499. if (XdrvMailbox.data_len < (sizeof(Settings.rules[index -1]) - offset -1)) { // Check free space
  500. XdrvMailbox.data[0] = ' '; // Remove + and make sure at least one space is inserted
  501. } else {
  502. offset = -1; // Not enough space so skip it
  503. }
  504. }
  505. if (offset != -1) {
  506. strlcpy(Settings.rules[index -1] + offset, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.rules[index -1]));
  507. }
  508. }
  509. rules_triggers[index -1] = 0; // Reset once flag
  510. }
  511. snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s%d\":\"%s\",\"Once\":\"%s\",\"StopOnError\":\"%s\",\"Free\":%d,\"Rules\":\"%s\"}"),
  512. command, index, GetStateText(bitRead(Settings.rule_enabled, index -1)), GetStateText(bitRead(Settings.rule_once, index -1)),
  513. GetStateText(bitRead(Settings.rule_stop, index -1)), sizeof(Settings.rules[index -1]) - strlen(Settings.rules[index -1]) -1, Settings.rules[index -1]);
  514. }
  515. else if ((CMND_RULETIMER == command_code) && (index > 0) && (index <= MAX_RULE_TIMERS)) {
  516. if (XdrvMailbox.data_len > 0) {
  517. rules_timer[index -1] = (XdrvMailbox.payload > 0) ? millis() + (1000 * XdrvMailbox.payload) : 0;
  518. }
  519. mqtt_data[0] = '\0';
  520. for (byte i = 0; i < MAX_RULE_TIMERS; i++) {
  521. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%c\"T%d\":%d"), mqtt_data, (i) ? ',' : '{', i +1, (rules_timer[i]) ? (rules_timer[i] - millis()) / 1000 : 0);
  522. }
  523. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
  524. }
  525. else if (CMND_EVENT == command_code) {
  526. if (XdrvMailbox.data_len > 0) {
  527. strlcpy(event_data, XdrvMailbox.data, sizeof(event_data));
  528. }
  529. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_JSON_DONE);
  530. }
  531. else if ((CMND_VAR == command_code) && (index > 0) && (index <= MAX_RULE_VARS)) {
  532. if (XdrvMailbox.data_len > 0) {
  533. strlcpy(vars[index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(vars[index -1]));
  534. }
  535. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, vars[index -1]);
  536. }
  537. else if ((CMND_MEM == command_code) && (index > 0) && (index <= MAX_RULE_MEMS)) {
  538. if (XdrvMailbox.data_len > 0) {
  539. strlcpy(Settings.mems[index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.mems[index -1]));
  540. }
  541. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, Settings.mems[index -1]);
  542. }
  543. else if (CMND_CALC_RESOLUTION == command_code) {
  544. if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 7)) {
  545. Settings.flag2.calc_resolution = XdrvMailbox.payload;
  546. }
  547. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.flag2.calc_resolution);
  548. }
  549. else if ((CMND_ADD == command_code) && (index > 0) && (index <= MAX_RULE_VARS)) {
  550. if (XdrvMailbox.data_len > 0) {
  551. double tempvar = CharToDouble(vars[index -1]) + CharToDouble(XdrvMailbox.data);
  552. dtostrfd(tempvar, Settings.flag2.calc_resolution, vars[index -1]);
  553. }
  554. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, vars[index -1]);
  555. }
  556. else if ((CMND_SUB == command_code) && (index > 0) && (index <= MAX_RULE_VARS)) {
  557. if (XdrvMailbox.data_len > 0) {
  558. double tempvar = CharToDouble(vars[index -1]) - CharToDouble(XdrvMailbox.data);
  559. dtostrfd(tempvar, Settings.flag2.calc_resolution, vars[index -1]);
  560. }
  561. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, vars[index -1]);
  562. }
  563. else if ((CMND_MULT == command_code) && (index > 0) && (index <= MAX_RULE_VARS)) {
  564. if (XdrvMailbox.data_len > 0) {
  565. double tempvar = CharToDouble(vars[index -1]) * CharToDouble(XdrvMailbox.data);
  566. dtostrfd(tempvar, Settings.flag2.calc_resolution, vars[index -1]);
  567. }
  568. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, vars[index -1]);
  569. }
  570. else if ((CMND_SCALE == command_code) && (index > 0) && (index <= MAX_RULE_VARS)) {
  571. if (XdrvMailbox.data_len > 0) {
  572. if (strstr(XdrvMailbox.data, ",")) { // Process parameter entry
  573. char sub_string[XdrvMailbox.data_len +1];
  574. double valueIN = CharToDouble(subStr(sub_string, XdrvMailbox.data, ",", 1));
  575. double fromLow = CharToDouble(subStr(sub_string, XdrvMailbox.data, ",", 2));
  576. double fromHigh = CharToDouble(subStr(sub_string, XdrvMailbox.data, ",", 3));
  577. double toLow = CharToDouble(subStr(sub_string, XdrvMailbox.data, ",", 4));
  578. double toHigh = CharToDouble(subStr(sub_string, XdrvMailbox.data, ",", 5));
  579. double value = map_double(valueIN, fromLow, fromHigh, toLow, toHigh);
  580. dtostrfd(value, Settings.flag2.calc_resolution, vars[index -1]);
  581. }
  582. }
  583. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, vars[index -1]);
  584. }
  585. else serviced = false; // Unknown command
  586. return serviced;
  587. }
  588. double map_double(double x, double in_min, double in_max, double out_min, double out_max)
  589. {
  590. return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
  591. }
  592. /*********************************************************************************************\
  593. * Interface
  594. \*********************************************************************************************/
  595. boolean Xdrv10(byte function)
  596. {
  597. boolean result = false;
  598. switch (function) {
  599. case FUNC_PRE_INIT:
  600. RulesInit();
  601. break;
  602. case FUNC_EVERY_50_MSECOND:
  603. RulesEvery50ms();
  604. break;
  605. case FUNC_EVERY_100_MSECOND:
  606. RulesEvery100ms();
  607. break;
  608. case FUNC_EVERY_SECOND:
  609. RulesEverySecond();
  610. break;
  611. case FUNC_SET_POWER:
  612. RulesSetPower();
  613. break;
  614. case FUNC_COMMAND:
  615. result = RulesCommand();
  616. break;
  617. case FUNC_RULES_PROCESS:
  618. result = RulesProcess();
  619. break;
  620. }
  621. return result;
  622. }
  623. #endif // USE_RULES