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.

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