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.

271 lines
7.2 KiB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
7 years ago
8 years ago
  1. /*
  2. NTP MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #if NTP_SUPPORT
  6. #include <TimeLib.h>
  7. #include <WiFiClient.h>
  8. #include <Ticker.h>
  9. #include <libs/NtpClientWrap.h>
  10. Ticker _ntp_defer;
  11. bool _ntp_report = false;
  12. bool _ntp_configure = false;
  13. bool _ntp_want_sync = false;
  14. // -----------------------------------------------------------------------------
  15. // NTP
  16. // -----------------------------------------------------------------------------
  17. #if WEB_SUPPORT
  18. bool _ntpWebSocketOnReceive(const char * key, JsonVariant& value) {
  19. return (strncmp(key, "ntp", 3) == 0);
  20. }
  21. void _ntpWebSocketOnSend(JsonObject& root) {
  22. root["ntpVisible"] = 1;
  23. root["ntpStatus"] = (timeStatus() == timeSet);
  24. root["ntpServer"] = getSetting("ntpServer", NTP_SERVER);
  25. root["ntpOffset"] = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
  26. root["ntpDST"] = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1;
  27. root["ntpRegion"] = getSetting("ntpRegion", NTP_DST_REGION).toInt();
  28. }
  29. #endif
  30. time_t _ntpSyncProvider() {
  31. _ntp_want_sync = true;
  32. return 0;
  33. }
  34. void _ntpWantSync() {
  35. _ntp_want_sync = true;
  36. }
  37. // Randomized in time to avoid clogging the server with simultaious requests from multiple devices
  38. // (for example, when multiple devices start up at the same time)
  39. int inline _ntpSyncInterval() {
  40. return secureRandom(NTP_SYNC_INTERVAL, NTP_SYNC_INTERVAL * 2);
  41. }
  42. int inline _ntpUpdateInterval() {
  43. return secureRandom(NTP_UPDATE_INTERVAL, NTP_UPDATE_INTERVAL * 2);
  44. }
  45. void _ntpStart() {
  46. _ntpConfigure();
  47. // short (initial) and long (after sync) intervals
  48. NTPw.setInterval(_ntpSyncInterval(), _ntpUpdateInterval());
  49. DEBUG_MSG_P(PSTR("[NTP] Update intervals: %us / %us\n"),
  50. NTPw.getShortInterval(), NTPw.getLongInterval());
  51. // setSyncProvider will immediatly call given function by setting next sync time to the current time.
  52. // Avoid triggering sync immediatly by canceling sync provider flag and resetting sync interval again
  53. setSyncProvider(_ntpSyncProvider);
  54. _ntp_want_sync = false;
  55. setSyncInterval(NTPw.getShortInterval());
  56. }
  57. void _ntpConfigure() {
  58. _ntp_configure = false;
  59. int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
  60. int sign = offset > 0 ? 1 : -1;
  61. offset = abs(offset);
  62. int tz_hours = sign * (offset / 60);
  63. int tz_minutes = sign * (offset % 60);
  64. if (NTPw.getTimeZone() != tz_hours || NTPw.getTimeZoneMinutes() != tz_minutes) {
  65. NTPw.setTimeZone(tz_hours, tz_minutes);
  66. _ntp_report = true;
  67. }
  68. bool daylight = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1;
  69. if (NTPw.getDayLight() != daylight) {
  70. NTPw.setDayLight(daylight);
  71. _ntp_report = true;
  72. }
  73. String server = getSetting("ntpServer", NTP_SERVER);
  74. if (!NTPw.getNtpServerName().equals(server)) {
  75. NTPw.setNtpServerName(server);
  76. }
  77. uint8_t dst_region = getSetting("ntpRegion", NTP_DST_REGION).toInt();
  78. NTPw.setDSTZone(dst_region);
  79. // Some remote servers can be slow to respond, increase accordingly
  80. // TODO does this need upper constrain?
  81. NTPw.setNTPTimeout(getSetting("ntpTimeout", NTP_TIMEOUT).toInt());
  82. }
  83. void _ntpReport() {
  84. _ntp_report = false;
  85. #if WEB_SUPPORT
  86. wsSend(_ntpWebSocketOnSend);
  87. #endif
  88. if (ntpSynced()) {
  89. time_t t = now();
  90. DEBUG_MSG_P(PSTR("[NTP] UTC Time : %s\n"), ntpDateTime(ntpLocal2UTC(t)).c_str());
  91. DEBUG_MSG_P(PSTR("[NTP] Local Time: %s\n"), ntpDateTime(t).c_str());
  92. }
  93. }
  94. #if BROKER_SUPPORT
  95. void inline _ntpBroker() {
  96. static unsigned char last_minute = 60;
  97. if (ntpSynced() && (minute() != last_minute)) {
  98. last_minute = minute();
  99. brokerPublish(BROKER_MSG_TYPE_DATETIME, MQTT_TOPIC_DATETIME, ntpDateTime().c_str());
  100. }
  101. }
  102. #endif
  103. void _ntpLoop() {
  104. // Disable ntp sync when softAP is active. This will not crash, but instead spam debug-log with pointless sync failures.
  105. if (!wifiConnected()) return;
  106. if (_ntp_configure) _ntpConfigure();
  107. // NTPClientLib will trigger callback with sync status
  108. // see: NTPw.onNTPSyncEvent([](NTPSyncEvent_t error){ ... }) below
  109. if (_ntp_want_sync) {
  110. _ntp_want_sync = false;
  111. NTPw.getTime();
  112. }
  113. // Print current time whenever configuration changes or after successful sync
  114. if (_ntp_report) _ntpReport();
  115. #if BROKER_SUPPORT
  116. _ntpBroker();
  117. #endif
  118. }
  119. // TODO: remove me!
  120. void _ntpBackwards() {
  121. moveSetting("ntpServer1", "ntpServer");
  122. delSetting("ntpServer2");
  123. delSetting("ntpServer3");
  124. int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
  125. if (-30 < offset && offset < 30) {
  126. offset *= 60;
  127. setSetting("ntpOffset", offset);
  128. }
  129. }
  130. // -----------------------------------------------------------------------------
  131. bool ntpSynced() {
  132. #if NTP_WAIT_FOR_SYNC
  133. // Has synced at least once
  134. return (NTPw.getFirstSync() > 0);
  135. #else
  136. // TODO: runtime setting?
  137. return true;
  138. #endif
  139. }
  140. String ntpDateTime(time_t t) {
  141. char buffer[20];
  142. snprintf_P(buffer, sizeof(buffer),
  143. PSTR("%04d-%02d-%02d %02d:%02d:%02d"),
  144. year(t), month(t), day(t), hour(t), minute(t), second(t)
  145. );
  146. return String(buffer);
  147. }
  148. String ntpDateTime() {
  149. if (ntpSynced()) return ntpDateTime(now());
  150. return String();
  151. }
  152. // XXX: returns garbage during DST switch
  153. time_t ntpLocal2UTC(time_t local) {
  154. int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
  155. if (NTPw.isSummerTime()) offset += 60;
  156. return local - offset * 60;
  157. }
  158. // -----------------------------------------------------------------------------
  159. void ntpSetup() {
  160. _ntpBackwards();
  161. #if TERMINAL_SUPPORT
  162. terminalRegisterCommand(F("NTP"), [](Embedis* e) {
  163. if (ntpSynced()) {
  164. _ntpReport();
  165. terminalOK();
  166. } else {
  167. DEBUG_MSG_P(PSTR("[NTP] Not synced\n"));
  168. }
  169. });
  170. terminalRegisterCommand(F("NTP.SYNC"), [](Embedis* e) {
  171. _ntpWantSync();
  172. terminalOK();
  173. });
  174. #endif
  175. NTPw.onNTPSyncEvent([](NTPSyncEvent_t error) {
  176. if (error) {
  177. #if WEB_SUPPORT
  178. wsSend_P(PSTR("{\"ntpStatus\": false}"));
  179. #endif
  180. if (error == noResponse) {
  181. DEBUG_MSG_P(PSTR("[NTP] Error: NTP server not reachable\n"));
  182. } else if (error == invalidAddress) {
  183. DEBUG_MSG_P(PSTR("[NTP] Error: Invalid NTP server address\n"));
  184. }
  185. } else {
  186. _ntp_report = true;
  187. setTime(NTPw.getLastNTPSync());
  188. }
  189. });
  190. wifiRegister([](justwifi_messages_t code, char * parameter) {
  191. if (code == MESSAGE_CONNECTED) {
  192. if (!ntpSynced()) {
  193. _ntp_defer.once_ms(secureRandom(NTP_START_DELAY, NTP_START_DELAY * 15), _ntpWantSync);
  194. }
  195. }
  196. });
  197. #if WEB_SUPPORT
  198. wsOnSendRegister(_ntpWebSocketOnSend);
  199. wsOnReceiveRegister(_ntpWebSocketOnReceive);
  200. #endif
  201. // Main callbacks
  202. espurnaRegisterLoop(_ntpLoop);
  203. espurnaRegisterReload([]() { _ntp_configure = true; });
  204. // Sets up NTP instance, installs ours sync provider
  205. _ntpStart();
  206. }
  207. #endif // NTP_SUPPORT