sys: clean-up system-specific functions
- shrink utils source file, move heartbeat and boot management into system
- improvise with 'heartbeat' functionality. include scheduler implementation that will
manage the per-module heartbeat callbacks with individual 'mode' and
'interval' settings. current ones are mqtt (including relays, lights, thermostat), debug and
influxdb. preserve heartbeat NONE, ONCE and REPEAT, REPEAT_STATUS is effectively a hbReport & status bit.
- mqtt heartbeat is managed through mqttHeartbeat() callbacks
- tweak mqtt callbacks to use lists instead of the vector, slighly reducing the size of the .bin
- update WebUI, include report setting and update hbMode values
- make sure general.h settings include new heartbeat,
move constant definitions outside of the header
- correctly include dependencies through the .cpp, avoid leaking
internal details.
- as a side-effect, base headers are no longer included recursively
3 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 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 |
|
- /*
-
- NTP MODULE (based on NtpClientLib)
-
- Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
-
- */
-
- #include "ntp.h"
-
- #if NTP_LEGACY_SUPPORT && NTP_SUPPORT
-
- #include <TimeLib.h>
- #include <WiFiUdp.h>
- #include <NtpClientLib.h>
-
- #include <Ticker.h>
-
- #include "debug.h"
- #include "broker.h"
- #include "ws.h"
-
- BrokerBind(NtpBroker);
-
- Ticker _ntp_defer;
-
- bool _ntp_report = false;
- bool _ntp_configure = false;
- bool _ntp_want_sync = false;
-
- // -----------------------------------------------------------------------------
- // NtpClient overrides to avoid triggering network sync
- // -----------------------------------------------------------------------------
-
- class NTPClientWrap : public NTPClient {
-
- public:
-
- NTPClientWrap() : NTPClient() {
- udp = new WiFiUDP();
- _lastSyncd = 0;
- }
-
- bool setInterval(int shortInterval, int longInterval) {
- _shortInterval = shortInterval;
- _longInterval = longInterval;
- return true;
- }
-
- };
-
- // NOTE: original NTP should be discarded by the linker
- // TODO: allow NTP client object to be destroyed
- static NTPClientWrap NTPw;
-
- // -----------------------------------------------------------------------------
- // NTP
- // -----------------------------------------------------------------------------
-
- #if WEB_SUPPORT
-
- bool _ntpWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
- return (strncmp(key, "ntp", 3) == 0);
- }
-
- void _ntpWebSocketOnVisible(JsonObject& root) {
- root["ntpVisible"] = 1;
- root["ntplegacyVisible"] = 1;
- }
-
- void _ntpWebSocketOnData(JsonObject& root) {
- root["ntpStatus"] = (timeStatus() == timeSet);
- }
-
- void _ntpWebSocketOnConnected(JsonObject& root) {
- root["ntpServer"] = getSetting("ntpServer", NTP_SERVER);
- root["ntpOffset"] = getSetting("ntpOffset", NTP_TIME_OFFSET);
- root["ntpDST"] = getSetting("ntpDST", 1 == NTP_DAY_LIGHT);
- root["ntpRegion"] = getSetting("ntpRegion", NTP_DST_REGION);
- }
-
- #endif
-
- time_t _ntpSyncProvider() {
- _ntp_want_sync = true;
- return 0;
- }
-
- void _ntpWantSync() {
- _ntp_want_sync = true;
- }
-
- // Randomized in time to avoid clogging the server with simultaious requests from multiple devices
- // (for example, when multiple devices start up at the same time)
- int _ntpSyncInterval() {
- return secureRandom(NTP_SYNC_INTERVAL, NTP_SYNC_INTERVAL * 2);
- }
-
- int _ntpUpdateInterval() {
- return secureRandom(NTP_UPDATE_INTERVAL, NTP_UPDATE_INTERVAL * 2);
- }
-
- void _ntpConfigure() {
-
- _ntp_configure = false;
-
- int offset = getSetting("ntpOffset", NTP_TIME_OFFSET);
- int sign = offset > 0 ? 1 : -1;
- offset = abs(offset);
- int tz_hours = sign * (offset / 60);
- int tz_minutes = sign * (offset % 60);
- if (NTPw.getTimeZone() != tz_hours || NTPw.getTimeZoneMinutes() != tz_minutes) {
- NTPw.setTimeZone(tz_hours, tz_minutes);
- _ntp_report = true;
- }
-
- const bool daylight = getSetting("ntpDST", 1 == NTP_DAY_LIGHT);
- if (NTPw.getDayLight() != daylight) {
- NTPw.setDayLight(daylight);
- _ntp_report = true;
- }
-
- const auto server = getSetting("ntpServer", NTP_SERVER);
- if (!NTPw.getNtpServerName().equals(server)) {
- NTPw.setNtpServerName(server);
- }
-
- uint8_t dst_region = getSetting("ntpRegion", NTP_DST_REGION);
- NTPw.setDSTZone(dst_region);
-
- // Some remote servers can be slow to respond, increase accordingly
- // TODO does this need upper constrain?
- NTPw.setNTPTimeout(getSetting("ntpTimeout", NTP_TIMEOUT));
-
- }
-
- void _ntpStart() {
-
- _ntpConfigure();
-
- // short (initial) and long (after sync) intervals
- NTPw.setInterval(_ntpSyncInterval(), _ntpUpdateInterval());
- DEBUG_MSG_P(PSTR("[NTP] Update intervals: %us / %us\n"),
- NTPw.getShortInterval(), NTPw.getLongInterval());
-
- // setSyncProvider will immediatly call given function by setting next sync time to the current time.
- // Avoid triggering sync immediatly by canceling sync provider flag and resetting sync interval again
- setSyncProvider(_ntpSyncProvider);
- _ntp_want_sync = false;
-
- setSyncInterval(NTPw.getShortInterval());
-
- }
-
-
- void _ntpReport() {
-
- _ntp_report = false;
-
- #if DEBUG_SUPPORT
- if (ntpSynced()) {
- time_t t = now();
- DEBUG_MSG_P(PSTR("[NTP] UTC Time : %s\n"), ntpDateTime(ntpLocal2UTC(t)).c_str());
- DEBUG_MSG_P(PSTR("[NTP] Local Time: %s\n"), ntpDateTime(t).c_str());
- }
- #endif
-
- }
-
- #if BROKER_SUPPORT
-
- void inline _ntpBroker() {
- static unsigned char last_minute = 60;
- if (ntpSynced() && (minute() != last_minute)) {
- last_minute = minute();
- NtpBroker::Publish(NtpTick::EveryMinute, now(), ntpDateTime());
- }
- }
-
- #endif
-
- void _ntpLoop() {
-
- // Disable ntp sync when softAP is active. This will not crash, but instead spam debug-log with pointless sync failures.
- if (!wifiConnected()) return;
-
- if (_ntp_configure) _ntpConfigure();
-
- // NTPClientLib will trigger callback with sync status
- // see: NTPw.onNTPSyncEvent([](NTPSyncEvent_t error){ ... }) below
- if (_ntp_want_sync) {
- _ntp_want_sync = false;
- NTPw.getTime();
- }
-
- // Print current time whenever configuration changes or after successful sync
- if (_ntp_report) _ntpReport();
-
- #if BROKER_SUPPORT
- _ntpBroker();
- #endif
-
- }
-
- // TODO: remove me!
- void _ntpBackwards() {
- moveSetting("ntpServer1", "ntpServer");
- delSetting("ntpServer2");
- delSetting("ntpServer3");
- int offset = getSetting("ntpOffset", NTP_TIME_OFFSET);
- if (-30 < offset && offset < 30) {
- offset *= 60;
- setSetting("ntpOffset", offset);
- }
- }
-
- // -----------------------------------------------------------------------------
-
- bool ntpSynced() {
- #if NTP_WAIT_FOR_SYNC
- // Has synced at least once
- return (NTPw.getFirstSync() > 0);
- #else
- // TODO: runtime setting?
- return true;
- #endif
- }
-
- String ntpDateTime(time_t t) {
- char buffer[20];
- snprintf_P(buffer, sizeof(buffer),
- PSTR("%04d-%02d-%02d %02d:%02d:%02d"),
- year(t), month(t), day(t), hour(t), minute(t), second(t)
- );
- return String(buffer);
- }
-
- String ntpDateTime() {
- if (ntpSynced()) return ntpDateTime(now());
- return String();
- }
-
- // XXX: returns garbage during DST switch
- time_t ntpLocal2UTC(time_t local) {
- int offset = getSetting("ntpOffset", NTP_TIME_OFFSET);
- if (NTPw.isSummerTime()) offset += 60;
- return local - offset * 60;
- }
-
- // -----------------------------------------------------------------------------
-
- void ntpSetup() {
-
- _ntpBackwards();
-
- #if TERMINAL_SUPPORT
- terminalRegisterCommand(F("NTP"), [](const terminal::CommandContext&) {
- if (ntpSynced()) {
- _ntpReport();
- terminalOK();
- } else {
- DEBUG_MSG_P(PSTR("[NTP] Not synced\n"));
- }
- });
-
- terminalRegisterCommand(F("NTP.SYNC"), [](const terminal::CommandContext&) {
- _ntpWantSync();
- terminalOK();
- });
- #endif
-
- NTPw.onNTPSyncEvent([](NTPSyncEvent_t error) {
- if (error) {
- if (error == noResponse) {
- DEBUG_MSG_P(PSTR("[NTP] Error: NTP server not reachable\n"));
- } else if (error == invalidAddress) {
- DEBUG_MSG_P(PSTR("[NTP] Error: Invalid NTP server address\n"));
- }
- #if WEB_SUPPORT
- wsPost(_ntpWebSocketOnData);
- #endif
- } else {
- _ntp_report = true;
- setTime(NTPw.getLastNTPSync());
- }
- });
-
- wifiRegister([](justwifi_messages_t code, char * parameter) {
- if (code == MESSAGE_CONNECTED) {
- if (!ntpSynced()) {
- _ntp_defer.once(secureRandom(NTP_START_DELAY, NTP_START_DELAY * 2), _ntpWantSync);
- }
- }
- });
-
- #if WEB_SUPPORT
- wsRegister()
- .onVisible(_ntpWebSocketOnVisible)
- .onConnected(_ntpWebSocketOnConnected)
- .onData(_ntpWebSocketOnData)
- .onKeyCheck(_ntpWebSocketOnKeyCheck);
- #endif
-
- // Main callbacks
- espurnaRegisterLoop(_ntpLoop);
- espurnaRegisterReload([]() { _ntp_configure = true; });
-
- // Sets up NTP instance, installs ours sync provider
- _ntpStart();
-
- }
-
- #endif // NTP_SUPPORT && NTP_LEGACY_SUPPORT
|