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.

313 lines
8.1 KiB

Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
  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 <TimeLib.h>
  8. #include <WiFiUdp.h>
  9. #include <NtpClientLib.h>
  10. #include <Ticker.h>
  11. #include "debug.h"
  12. #include "broker.h"
  13. #include "ws.h"
  14. BrokerBind(NtpBroker);
  15. Ticker _ntp_defer;
  16. bool _ntp_report = false;
  17. bool _ntp_configure = false;
  18. bool _ntp_want_sync = false;
  19. // -----------------------------------------------------------------------------
  20. // NtpClient overrides to avoid triggering network sync
  21. // -----------------------------------------------------------------------------
  22. class NTPClientWrap : public NTPClient {
  23. public:
  24. NTPClientWrap() : NTPClient() {
  25. udp = new WiFiUDP();
  26. _lastSyncd = 0;
  27. }
  28. bool setInterval(int shortInterval, int longInterval) {
  29. _shortInterval = shortInterval;
  30. _longInterval = longInterval;
  31. return true;
  32. }
  33. };
  34. // NOTE: original NTP should be discarded by the linker
  35. // TODO: allow NTP client object to be destroyed
  36. static NTPClientWrap NTPw;
  37. // -----------------------------------------------------------------------------
  38. // NTP
  39. // -----------------------------------------------------------------------------
  40. #if WEB_SUPPORT
  41. bool _ntpWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  42. return (strncmp(key, "ntp", 3) == 0);
  43. }
  44. void _ntpWebSocketOnVisible(JsonObject& root) {
  45. root["ntpVisible"] = 1;
  46. root["ntplegacyVisible"] = 1;
  47. }
  48. void _ntpWebSocketOnData(JsonObject& root) {
  49. root["ntpStatus"] = (timeStatus() == timeSet);
  50. }
  51. void _ntpWebSocketOnConnected(JsonObject& root) {
  52. root["ntpServer"] = getSetting("ntpServer", NTP_SERVER);
  53. root["ntpOffset"] = getSetting("ntpOffset", NTP_TIME_OFFSET);
  54. root["ntpDST"] = getSetting("ntpDST", 1 == NTP_DAY_LIGHT);
  55. root["ntpRegion"] = getSetting("ntpRegion", NTP_DST_REGION);
  56. }
  57. #endif
  58. time_t _ntpSyncProvider() {
  59. _ntp_want_sync = true;
  60. return 0;
  61. }
  62. void _ntpWantSync() {
  63. _ntp_want_sync = true;
  64. }
  65. // Randomized in time to avoid clogging the server with simultaious requests from multiple devices
  66. // (for example, when multiple devices start up at the same time)
  67. int _ntpSyncInterval() {
  68. return secureRandom(NTP_SYNC_INTERVAL, NTP_SYNC_INTERVAL * 2);
  69. }
  70. int _ntpUpdateInterval() {
  71. return secureRandom(NTP_UPDATE_INTERVAL, NTP_UPDATE_INTERVAL * 2);
  72. }
  73. void _ntpConfigure() {
  74. _ntp_configure = false;
  75. int offset = getSetting("ntpOffset", NTP_TIME_OFFSET);
  76. int sign = offset > 0 ? 1 : -1;
  77. offset = abs(offset);
  78. int tz_hours = sign * (offset / 60);
  79. int tz_minutes = sign * (offset % 60);
  80. if (NTPw.getTimeZone() != tz_hours || NTPw.getTimeZoneMinutes() != tz_minutes) {
  81. NTPw.setTimeZone(tz_hours, tz_minutes);
  82. _ntp_report = true;
  83. }
  84. const bool daylight = getSetting("ntpDST", 1 == NTP_DAY_LIGHT);
  85. if (NTPw.getDayLight() != daylight) {
  86. NTPw.setDayLight(daylight);
  87. _ntp_report = true;
  88. }
  89. const auto server = getSetting("ntpServer", NTP_SERVER);
  90. if (!NTPw.getNtpServerName().equals(server)) {
  91. NTPw.setNtpServerName(server);
  92. }
  93. uint8_t dst_region = getSetting("ntpRegion", NTP_DST_REGION);
  94. NTPw.setDSTZone(dst_region);
  95. // Some remote servers can be slow to respond, increase accordingly
  96. // TODO does this need upper constrain?
  97. NTPw.setNTPTimeout(getSetting("ntpTimeout", NTP_TIMEOUT));
  98. }
  99. void _ntpStart() {
  100. _ntpConfigure();
  101. // short (initial) and long (after sync) intervals
  102. NTPw.setInterval(_ntpSyncInterval(), _ntpUpdateInterval());
  103. DEBUG_MSG_P(PSTR("[NTP] Update intervals: %us / %us\n"),
  104. NTPw.getShortInterval(), NTPw.getLongInterval());
  105. // setSyncProvider will immediatly call given function by setting next sync time to the current time.
  106. // Avoid triggering sync immediatly by canceling sync provider flag and resetting sync interval again
  107. setSyncProvider(_ntpSyncProvider);
  108. _ntp_want_sync = false;
  109. setSyncInterval(NTPw.getShortInterval());
  110. }
  111. void _ntpReport() {
  112. _ntp_report = false;
  113. #if DEBUG_SUPPORT
  114. if (ntpSynced()) {
  115. time_t t = now();
  116. DEBUG_MSG_P(PSTR("[NTP] UTC Time : %s\n"), ntpDateTime(ntpLocal2UTC(t)).c_str());
  117. DEBUG_MSG_P(PSTR("[NTP] Local Time: %s\n"), ntpDateTime(t).c_str());
  118. }
  119. #endif
  120. }
  121. #if BROKER_SUPPORT
  122. void inline _ntpBroker() {
  123. static unsigned char last_minute = 60;
  124. if (ntpSynced() && (minute() != last_minute)) {
  125. last_minute = minute();
  126. NtpBroker::Publish(NtpTick::EveryMinute, now(), ntpDateTime());
  127. }
  128. }
  129. #endif
  130. void _ntpLoop() {
  131. // Disable ntp sync when softAP is active. This will not crash, but instead spam debug-log with pointless sync failures.
  132. if (!wifiConnected()) return;
  133. if (_ntp_configure) _ntpConfigure();
  134. // NTPClientLib will trigger callback with sync status
  135. // see: NTPw.onNTPSyncEvent([](NTPSyncEvent_t error){ ... }) below
  136. if (_ntp_want_sync) {
  137. _ntp_want_sync = false;
  138. NTPw.getTime();
  139. }
  140. // Print current time whenever configuration changes or after successful sync
  141. if (_ntp_report) _ntpReport();
  142. #if BROKER_SUPPORT
  143. _ntpBroker();
  144. #endif
  145. }
  146. // TODO: remove me!
  147. void _ntpBackwards() {
  148. moveSetting("ntpServer1", "ntpServer");
  149. delSetting("ntpServer2");
  150. delSetting("ntpServer3");
  151. int offset = getSetting("ntpOffset", NTP_TIME_OFFSET);
  152. if (-30 < offset && offset < 30) {
  153. offset *= 60;
  154. setSetting("ntpOffset", offset);
  155. }
  156. }
  157. // -----------------------------------------------------------------------------
  158. bool ntpSynced() {
  159. #if NTP_WAIT_FOR_SYNC
  160. // Has synced at least once
  161. return (NTPw.getFirstSync() > 0);
  162. #else
  163. // TODO: runtime setting?
  164. return true;
  165. #endif
  166. }
  167. String ntpDateTime(time_t t) {
  168. char buffer[20];
  169. snprintf_P(buffer, sizeof(buffer),
  170. PSTR("%04d-%02d-%02d %02d:%02d:%02d"),
  171. year(t), month(t), day(t), hour(t), minute(t), second(t)
  172. );
  173. return String(buffer);
  174. }
  175. String ntpDateTime() {
  176. if (ntpSynced()) return ntpDateTime(now());
  177. return String();
  178. }
  179. // XXX: returns garbage during DST switch
  180. time_t ntpLocal2UTC(time_t local) {
  181. int offset = getSetting("ntpOffset", NTP_TIME_OFFSET);
  182. if (NTPw.isSummerTime()) offset += 60;
  183. return local - offset * 60;
  184. }
  185. // -----------------------------------------------------------------------------
  186. void ntpSetup() {
  187. _ntpBackwards();
  188. #if TERMINAL_SUPPORT
  189. terminalRegisterCommand(F("NTP"), [](const terminal::CommandContext&) {
  190. if (ntpSynced()) {
  191. _ntpReport();
  192. terminalOK();
  193. } else {
  194. DEBUG_MSG_P(PSTR("[NTP] Not synced\n"));
  195. }
  196. });
  197. terminalRegisterCommand(F("NTP.SYNC"), [](const terminal::CommandContext&) {
  198. _ntpWantSync();
  199. terminalOK();
  200. });
  201. #endif
  202. NTPw.onNTPSyncEvent([](NTPSyncEvent_t error) {
  203. if (error) {
  204. if (error == noResponse) {
  205. DEBUG_MSG_P(PSTR("[NTP] Error: NTP server not reachable\n"));
  206. } else if (error == invalidAddress) {
  207. DEBUG_MSG_P(PSTR("[NTP] Error: Invalid NTP server address\n"));
  208. }
  209. #if WEB_SUPPORT
  210. wsPost(_ntpWebSocketOnData);
  211. #endif
  212. } else {
  213. _ntp_report = true;
  214. setTime(NTPw.getLastNTPSync());
  215. }
  216. });
  217. wifiRegister([](justwifi_messages_t code, char * parameter) {
  218. if (code == MESSAGE_CONNECTED) {
  219. if (!ntpSynced()) {
  220. _ntp_defer.once(secureRandom(NTP_START_DELAY, NTP_START_DELAY * 2), _ntpWantSync);
  221. }
  222. }
  223. });
  224. #if WEB_SUPPORT
  225. wsRegister()
  226. .onVisible(_ntpWebSocketOnVisible)
  227. .onConnected(_ntpWebSocketOnConnected)
  228. .onData(_ntpWebSocketOnData)
  229. .onKeyCheck(_ntpWebSocketOnKeyCheck);
  230. #endif
  231. // Main callbacks
  232. espurnaRegisterLoop(_ntpLoop);
  233. espurnaRegisterReload([]() { _ntp_configure = true; });
  234. // Sets up NTP instance, installs ours sync provider
  235. _ntpStart();
  236. }
  237. #endif // NTP_SUPPORT && NTP_LEGACY_SUPPORT