Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

299 lines
9.4 KiB

6 years ago
6 years ago
  1. /*
  2. SCHEDULER MODULE
  3. Copyright (C) 2017 by faina09
  4. Adapted by Xose Pérez <xose dot perez at gmail dot com>
  5. */
  6. #if SCHEDULER_SUPPORT
  7. #include "relay.h"
  8. #include <TimeLib.h>
  9. int _sch_restore = 0;
  10. // -----------------------------------------------------------------------------
  11. #if WEB_SUPPORT
  12. bool _schWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  13. return (strncmp(key, "sch", 3) == 0);
  14. }
  15. void _schWebSocketOnVisible(JsonObject& root) {
  16. if (!relayCount()) return;
  17. root["schVisible"] = 1;
  18. }
  19. void _schWebSocketOnConnected(JsonObject &root){
  20. if (!relayCount()) return;
  21. JsonObject &schedules = root.createNestedObject("schedules");
  22. schedules["max"] = SCHEDULER_MAX_SCHEDULES;
  23. JsonArray& enabled = schedules.createNestedArray("schEnabled");
  24. JsonArray& switch_ = schedules.createNestedArray("schSwitch");
  25. JsonArray& action = schedules.createNestedArray("schAction");
  26. JsonArray& type = schedules.createNestedArray("schType");
  27. JsonArray& hour = schedules.createNestedArray("schHour");
  28. JsonArray& minute = schedules.createNestedArray("schMinute");
  29. JsonArray& utc = schedules.createNestedArray("schUTC");
  30. JsonArray& weekdays = schedules.createNestedArray("schWDs");
  31. uint8_t size = 0;
  32. for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
  33. if (!getSetting({"schSwitch", i}).length()) break;
  34. ++size;
  35. enabled.add(getSetting({"schEnabled", i}, false) ? 1 : 0);
  36. utc.add(getSetting({"schUTC", i}, 0));
  37. switch_.add(getSetting({"schSwitch", i}, 0));
  38. action.add(getSetting({"schAction", i}, 0));
  39. type.add(getSetting({"schType", i}, SCHEDULER_TYPE_SWITCH));
  40. hour.add(getSetting({"schHour", i}, 0));
  41. minute.add(getSetting({"schMinute", i}, 0));
  42. weekdays.add(getSetting({"schWDs", i}, SCHEDULER_WEEKDAYS));
  43. }
  44. schedules["size"] = size;
  45. schedules["start"] = 0;
  46. }
  47. #endif // WEB_SUPPORT
  48. // -----------------------------------------------------------------------------
  49. void _schConfigure() {
  50. bool delete_flag = false;
  51. for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
  52. int sch_switch = getSetting({"schSwitch", i}, 0xFF);
  53. if (sch_switch == 0xFF) delete_flag = true;
  54. if (delete_flag) {
  55. delSetting({"schEnabled", i});
  56. delSetting({"schSwitch", i});
  57. delSetting({"schAction", i});
  58. delSetting({"schHour", i});
  59. delSetting({"schMinute", i});
  60. delSetting({"schWDs", i});
  61. delSetting({"schType", i});
  62. delSetting({"schUTC", i});
  63. } else {
  64. #if DEBUG_SUPPORT
  65. bool sch_enabled = getSetting({"schEnabled", i}, false);
  66. int sch_action = getSetting({"schAction", i}, 0);
  67. int sch_hour = getSetting({"schHour", i}, 0);
  68. int sch_minute = getSetting({"schMinute", i}, 0);
  69. bool sch_utc = getSetting({"schUTC", i}, false);
  70. String sch_weekdays = getSetting({"schWDs", i}, SCHEDULER_WEEKDAYS);
  71. int sch_type = getSetting({"schType", i}, SCHEDULER_TYPE_SWITCH);
  72. DEBUG_MSG_P(
  73. PSTR("[SCH] Schedule #%d: %s #%d to %d at %02d:%02d %s on %s%s\n"),
  74. i, SCHEDULER_TYPE_SWITCH == sch_type ? "switch" : "channel", sch_switch,
  75. sch_action, sch_hour, sch_minute, sch_utc ? "UTC" : "local time",
  76. (char *) sch_weekdays.c_str(),
  77. sch_enabled ? "" : " (disabled)"
  78. );
  79. #endif // DEBUG_SUPPORT
  80. }
  81. }
  82. }
  83. bool _schIsThisWeekday(time_t t, String weekdays){
  84. // Convert from Sunday to Monday as day 1
  85. int w = weekday(t) - 1;
  86. if (0 == w) w = 7;
  87. char pch;
  88. char * p = (char *) weekdays.c_str();
  89. unsigned char position = 0;
  90. while ((pch = p[position++])) {
  91. if ((pch - '0') == w) return true;
  92. }
  93. return false;
  94. }
  95. int _schMinutesLeft(time_t t, unsigned char schedule_hour, unsigned char schedule_minute){
  96. unsigned char now_hour = hour(t);
  97. unsigned char now_minute = minute(t);
  98. return (schedule_hour - now_hour) * 60 + schedule_minute - now_minute;
  99. }
  100. void _schAction(unsigned char sch_id, int sch_action, int sch_switch) {
  101. const auto sch_type = getSetting({"schType", sch_id}, SCHEDULER_TYPE_SWITCH);
  102. if (SCHEDULER_TYPE_SWITCH == sch_type) {
  103. DEBUG_MSG_P(PSTR("[SCH] Switching switch %d to %d\n"), sch_switch, sch_action);
  104. if (sch_action == 2) {
  105. relayToggle(sch_switch);
  106. } else {
  107. relayStatus(sch_switch, sch_action);
  108. }
  109. }
  110. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  111. if (SCHEDULER_TYPE_DIM == sch_type) {
  112. DEBUG_MSG_P(PSTR("[SCH] Set channel %d value to %d\n"), sch_switch, sch_action);
  113. lightChannel(sch_switch, sch_action);
  114. lightUpdate(true, true);
  115. }
  116. #endif
  117. }
  118. // If daybefore and relay is -1, check with current timestamp
  119. // Otherwise, modify it by moving 'daybefore' days back and only use the 'relay' id
  120. void _schCheck(int relay, int daybefore) {
  121. time_t local_time = now();
  122. time_t utc_time = ntpLocal2UTC(local_time);
  123. int minimum_restore_time = -1440;
  124. int saved_action = -1;
  125. int saved_sch = -1;
  126. // Check schedules
  127. for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
  128. int sch_switch = getSetting({"schSwitch", i}, 0xFF);
  129. if (sch_switch == 0xFF) break;
  130. // Skip disabled schedules
  131. if (!getSetting({"schEnabled", i}, false)) continue;
  132. // Get the datetime used for the calculation
  133. const bool sch_utc = getSetting({"schUTC", i}, false);
  134. time_t t = sch_utc ? utc_time : local_time;
  135. if (daybefore > 0) {
  136. unsigned char now_hour = hour(t);
  137. unsigned char now_minute = minute(t);
  138. unsigned char now_sec = second(t);
  139. t = t - ((now_hour * 3600) + ((now_minute + 1) * 60) + now_sec + (daybefore * 86400));
  140. }
  141. String sch_weekdays = getSetting({"schWDs", i}, SCHEDULER_WEEKDAYS);
  142. if (_schIsThisWeekday(t, sch_weekdays)) {
  143. int sch_hour = getSetting({"schHour", i}, 0);
  144. int sch_minute = getSetting({"schMinute", i}, 0);
  145. int minutes_to_trigger = _schMinutesLeft(t, sch_hour, sch_minute);
  146. int sch_action = getSetting({"schAction", i}, 0);
  147. int sch_type = getSetting({"schType", i}, SCHEDULER_TYPE_SWITCH);
  148. if (sch_type == SCHEDULER_TYPE_SWITCH && sch_switch == relay && sch_action != 2 && minutes_to_trigger < 0 && minutes_to_trigger > minimum_restore_time) {
  149. minimum_restore_time = minutes_to_trigger;
  150. saved_action = sch_action;
  151. saved_sch = i;
  152. }
  153. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  154. if (SCHEDULER_TYPE_DIM == sch_type && sch_switch == relay && minutes_to_trigger < 0 && minutes_to_trigger > minimum_restore_time) {
  155. minimum_restore_time = minutes_to_trigger;
  156. saved_action = sch_action;
  157. saved_sch = i;
  158. }
  159. #endif
  160. if (minutes_to_trigger == 0 && relay == -1) {
  161. _schAction(i, sch_action, sch_switch);
  162. DEBUG_MSG_P(PSTR("[SCH] Schedule #%d TRIGGERED!!\n"), i);
  163. // Show minutes to trigger every 15 minutes
  164. // or every minute if less than 15 minutes to scheduled time.
  165. // This only works for schedules on this same day.
  166. // For instance, if your scheduler is set for 00:01 you will only
  167. // get one notification before the trigger (at 00:00)
  168. } else if (minutes_to_trigger > 0 && relay == -1) {
  169. #if DEBUG_SUPPORT
  170. if ((minutes_to_trigger % 15 == 0) || (minutes_to_trigger < 15)) {
  171. DEBUG_MSG_P(
  172. PSTR("[SCH] %d minutes to trigger schedule #%d\n"),
  173. minutes_to_trigger, i
  174. );
  175. }
  176. #endif
  177. }
  178. }
  179. }
  180. if (daybefore >= 0 && daybefore < 7 && minimum_restore_time == -1440 && saved_action == -1) {
  181. _schCheck(relay, ++daybefore);
  182. return;
  183. }
  184. if (minimum_restore_time != -1440 && saved_action != -1 && saved_sch != -1) {
  185. _schAction(saved_sch, saved_action, relay);
  186. }
  187. }
  188. void _schLoop() {
  189. // Check time has been sync'ed
  190. if (!ntpSynced()) return;
  191. if (_sch_restore == 0) {
  192. for (unsigned char i = 0; i < relayCount(); i++){
  193. if (getSetting({"relayLastSch", i}, 1 == SCHEDULER_RESTORE_LAST_SCHEDULE)) {
  194. _schCheck(i, 0);
  195. }
  196. }
  197. _sch_restore = 1;
  198. }
  199. // Check schedules every minute at hh:mm:00
  200. static unsigned long last_minute = 60;
  201. unsigned char current_minute = minute();
  202. if (current_minute != last_minute) {
  203. last_minute = current_minute;
  204. _schCheck(-1, -1);
  205. }
  206. }
  207. // -----------------------------------------------------------------------------
  208. void schSetup() {
  209. _schConfigure();
  210. // Update websocket clients
  211. #if WEB_SUPPORT
  212. wsRegister()
  213. .onVisible(_schWebSocketOnVisible)
  214. .onConnected(_schWebSocketOnConnected)
  215. .onKeyCheck(_schWebSocketOnKeyCheck);
  216. #endif
  217. // Main callbacks
  218. espurnaRegisterLoop(_schLoop);
  219. espurnaRegisterReload(_schConfigure);
  220. }
  221. #endif // SCHEDULER_SUPPORT