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.

324 lines
10 KiB

6 years ago
7 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 "broker.h"
  8. #include "relay.h"
  9. #include "ntp.h"
  10. constexpr const int SchedulerDummySwitchId = 0xff;
  11. int _sch_restore = 0;
  12. // -----------------------------------------------------------------------------
  13. #if WEB_SUPPORT
  14. bool _schWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  15. return (strncmp(key, "sch", 3) == 0);
  16. }
  17. void _schWebSocketOnVisible(JsonObject& root) {
  18. if (!relayCount()) return;
  19. root["schVisible"] = 1;
  20. }
  21. void _schWebSocketOnConnected(JsonObject &root){
  22. if (!relayCount()) return;
  23. JsonObject &schedules = root.createNestedObject("schedules");
  24. schedules["max"] = SCHEDULER_MAX_SCHEDULES;
  25. JsonArray& enabled = schedules.createNestedArray("schEnabled");
  26. JsonArray& switch_ = schedules.createNestedArray("schSwitch");
  27. JsonArray& action = schedules.createNestedArray("schAction");
  28. JsonArray& type = schedules.createNestedArray("schType");
  29. JsonArray& hour = schedules.createNestedArray("schHour");
  30. JsonArray& minute = schedules.createNestedArray("schMinute");
  31. JsonArray& utc = schedules.createNestedArray("schUTC");
  32. JsonArray& weekdays = schedules.createNestedArray("schWDs");
  33. uint8_t size = 0;
  34. for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
  35. if (!getSetting({"schSwitch", i}).length()) break;
  36. ++size;
  37. enabled.add(getSetting({"schEnabled", i}, false) ? 1 : 0);
  38. utc.add(getSetting({"schUTC", i}, 0));
  39. switch_.add(getSetting({"schSwitch", i}, 0));
  40. action.add(getSetting({"schAction", i}, 0));
  41. type.add(getSetting({"schType", i}, SCHEDULER_TYPE_SWITCH));
  42. hour.add(getSetting({"schHour", i}, 0));
  43. minute.add(getSetting({"schMinute", i}, 0));
  44. weekdays.add(getSetting({"schWDs", i}, SCHEDULER_WEEKDAYS));
  45. }
  46. schedules["size"] = size;
  47. schedules["start"] = 0;
  48. }
  49. #endif // WEB_SUPPORT
  50. // -----------------------------------------------------------------------------
  51. void _schConfigure() {
  52. bool delete_flag = false;
  53. for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
  54. int sch_switch = getSetting({"schSwitch", i}, SchedulerDummySwitchId);
  55. if (sch_switch == SchedulerDummySwitchId) delete_flag = true;
  56. if (delete_flag) {
  57. delSetting({"schEnabled", i});
  58. delSetting({"schSwitch", i});
  59. delSetting({"schAction", i});
  60. delSetting({"schHour", i});
  61. delSetting({"schMinute", i});
  62. delSetting({"schWDs", i});
  63. delSetting({"schType", i});
  64. delSetting({"schUTC", i});
  65. } else {
  66. #if DEBUG_SUPPORT
  67. bool sch_enabled = getSetting({"schEnabled", i}, false);
  68. int sch_action = getSetting({"schAction", i}, 0);
  69. int sch_hour = getSetting({"schHour", i}, 0);
  70. int sch_minute = getSetting({"schMinute", i}, 0);
  71. bool sch_utc = getSetting({"schUTC", i}, false);
  72. String sch_weekdays = getSetting({"schWDs", i}, SCHEDULER_WEEKDAYS);
  73. int sch_type = getSetting({"schType", i}, SCHEDULER_TYPE_SWITCH);
  74. DEBUG_MSG_P(
  75. PSTR("[SCH] Schedule #%d: %s #%d to %d at %02d:%02d %s on %s%s\n"),
  76. i, SCHEDULER_TYPE_SWITCH == sch_type ? "switch" : "channel", sch_switch,
  77. sch_action, sch_hour, sch_minute, sch_utc ? "UTC" : "local time",
  78. (char *) sch_weekdays.c_str(),
  79. sch_enabled ? "" : " (disabled)"
  80. );
  81. #endif // DEBUG_SUPPORT
  82. }
  83. }
  84. }
  85. bool _schIsThisWeekday(int day, const String& weekdays){
  86. // Convert from Sunday to Monday as day 1
  87. int w = day - 1;
  88. if (0 == w) w = 7;
  89. char pch;
  90. char * p = (char *) weekdays.c_str();
  91. unsigned char position = 0;
  92. while ((pch = p[position++])) {
  93. if ((pch - '0') == w) return true;
  94. }
  95. return false;
  96. }
  97. int _schMinutesLeft(int current_hour, int current_minute, int schedule_hour, int schedule_minute) {
  98. return (schedule_hour - current_hour) * 60 + schedule_minute - current_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 NTP_LEGACY_SUPPORT
  119. NtpCalendarWeekday _schGetWeekday(time_t timestamp, int daybefore) {
  120. if (daybefore > 0) {
  121. timestamp = timestamp - ((hour(timestamp) * SECS_PER_HOUR) + ((minute(timestamp) + 1) * SECS_PER_MIN) + second(timestamp) + (daybefore * SECS_PER_DAY));
  122. }
  123. // XXX: no
  124. time_t utc_timestamp = ntpLocal2UTC(timestamp);
  125. return NtpCalendarWeekday {
  126. weekday(timestamp), hour(timestamp), minute(timestamp),
  127. weekday(utc_timestamp), hour(utc_timestamp), minute(utc_timestamp)
  128. };
  129. }
  130. #else
  131. NtpCalendarWeekday _schGetWeekday(time_t timestamp, int daybefore) {
  132. tm utc_time;
  133. tm local_time;
  134. gmtime_r(&timestamp, &utc_time);
  135. if (daybefore > 0) {
  136. timestamp = timestamp - ((utc_time.tm_hour * secondsPerHour) + ((utc_time.tm_min + 1) * secondsPerMinute) + utc_time.tm_sec + (daybefore * secondsPerDay));
  137. gmtime_r(&timestamp, &utc_time);
  138. localtime_r(&timestamp, &local_time);
  139. } else {
  140. localtime_r(&timestamp, &local_time);
  141. }
  142. // TimeLib sunday is 1 instead of 0
  143. return NtpCalendarWeekday {
  144. local_time.tm_wday + 1, local_time.tm_hour, local_time.tm_min,
  145. utc_time.tm_wday + 1, utc_time.tm_hour, utc_time.tm_min
  146. };
  147. }
  148. #endif
  149. // If daybefore and relay is -1, check with current timestamp
  150. // Otherwise, modify it by moving 'daybefore' days back and only use the 'relay' id
  151. void _schCheck(int relay, int daybefore) {
  152. time_t timestamp = now();
  153. auto calendar_weekday = _schGetWeekday(timestamp, daybefore);
  154. int minimum_restore_time = -(60 * 24);
  155. int saved_action = -1;
  156. int saved_sch = -1;
  157. // Check schedules
  158. for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
  159. int sch_switch = getSetting({"schSwitch", i}, SchedulerDummySwitchId);
  160. if (sch_switch == SchedulerDummySwitchId) break;
  161. // Skip disabled schedules
  162. if (!getSetting({"schEnabled", i}, false)) continue;
  163. // Get the datetime used for the calculation
  164. const bool sch_utc = getSetting({"schUTC", i}, false);
  165. String sch_weekdays = getSetting({"schWDs", i}, SCHEDULER_WEEKDAYS);
  166. if (_schIsThisWeekday(sch_utc ? calendar_weekday.utc_wday : calendar_weekday.local_wday, sch_weekdays)) {
  167. int sch_hour = getSetting({"schHour", i}, 0);
  168. int sch_minute = getSetting({"schMinute", i}, 0);
  169. int sch_action = getSetting({"schAction", i}, 0);
  170. int sch_type = getSetting({"schType", i}, SCHEDULER_TYPE_SWITCH);
  171. int minutes_to_trigger = _schMinutesLeft(
  172. sch_utc ? calendar_weekday.utc_hour : calendar_weekday.local_hour,
  173. sch_utc ? calendar_weekday.utc_minute : calendar_weekday.local_minute,
  174. sch_hour, sch_minute
  175. );
  176. if (sch_type == SCHEDULER_TYPE_SWITCH && sch_switch == relay && sch_action != 2 && minutes_to_trigger < 0 && minutes_to_trigger > minimum_restore_time) {
  177. minimum_restore_time = minutes_to_trigger;
  178. saved_action = sch_action;
  179. saved_sch = i;
  180. }
  181. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  182. if (SCHEDULER_TYPE_DIM == sch_type && sch_switch == relay && minutes_to_trigger < 0 && minutes_to_trigger > minimum_restore_time) {
  183. minimum_restore_time = minutes_to_trigger;
  184. saved_action = sch_action;
  185. saved_sch = i;
  186. }
  187. #endif
  188. if (minutes_to_trigger == 0 && relay == -1) {
  189. _schAction(i, sch_action, sch_switch);
  190. DEBUG_MSG_P(PSTR("[SCH] Schedule #%d TRIGGERED!!\n"), i);
  191. // Show minutes to trigger every 15 minutes
  192. // or every minute if less than 15 minutes to scheduled time.
  193. // This only works for schedules on this same day.
  194. // For instance, if your scheduler is set for 00:01 you will only
  195. // get one notification before the trigger (at 00:00)
  196. } else if (minutes_to_trigger > 0 && relay == -1) {
  197. #if DEBUG_SUPPORT
  198. if ((minutes_to_trigger % 15 == 0) || (minutes_to_trigger < 15)) {
  199. DEBUG_MSG_P(
  200. PSTR("[SCH] %d minutes to trigger schedule #%d\n"),
  201. minutes_to_trigger, i
  202. );
  203. }
  204. #endif
  205. }
  206. }
  207. }
  208. if (daybefore >= 0 && daybefore < 7 && minimum_restore_time == -(60 * 24) && saved_action == -1) {
  209. _schCheck(relay, ++daybefore);
  210. return;
  211. }
  212. if (minimum_restore_time != -(60 * 24) && saved_action != -1 && saved_sch != -1) {
  213. _schAction(saved_sch, saved_action, relay);
  214. }
  215. }
  216. // -----------------------------------------------------------------------------
  217. void schSetup() {
  218. _schConfigure();
  219. #if WEB_SUPPORT
  220. wsRegister()
  221. .onVisible(_schWebSocketOnVisible)
  222. .onConnected(_schWebSocketOnConnected)
  223. .onKeyCheck(_schWebSocketOnKeyCheck);
  224. #endif
  225. NtpBroker::Register([](const NtpTick tick, time_t, const String&) {
  226. if (NtpTick::EveryMinute != tick) return;
  227. static bool restore_once = true;
  228. if (restore_once) {
  229. for (unsigned char i = 0; i < relayCount(); i++) {
  230. if (getSetting({"relayLastSch", i}, 1 == SCHEDULER_RESTORE_LAST_SCHEDULE)) {
  231. _schCheck(i, 0);
  232. }
  233. }
  234. restore_once = false;
  235. }
  236. _schCheck(-1, -1);
  237. });
  238. espurnaRegisterReload(_schConfigure);
  239. }
  240. #endif // SCHEDULER_SUPPORT