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.

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