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.

279 lines
7.3 KiB

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