diff --git a/app/src/routes/connections/ntp/NTP.svelte b/app/src/routes/connections/ntp/NTP.svelte index 4235ed5..454e3d6 100644 --- a/app/src/routes/connections/ntp/NTP.svelte +++ b/app/src/routes/connections/ntp/NTP.svelte @@ -18,7 +18,7 @@ let ntpStatus: NTPStatus; async function getNTPStatus() { - const result = await api.get('/api/ntpStatus'); + const result = await api.get('/api/ntp/status'); if (result.isErr()) { console.error('Error:', result.inner); return; @@ -27,7 +27,7 @@ } async function getNTPSettings() { - const result = await api.get('/api/ntpSettings'); + const result = await api.get('/api/ntp/settings'); if (result.isErr()) { console.error('Error:', result.inner); return; @@ -48,7 +48,7 @@ }; async function postNTPSettings(data: NTPSettings) { - const result = await api.post('/api/ntpSettings', data); + const result = await api.post('/api/ntp/settings', data); if (result.isErr()) { notifications.error('User not authorized.', 3000); console.error('Error:', result.inner); diff --git a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp index dd4e308..a3fa18f 100644 --- a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp +++ b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp @@ -21,10 +21,6 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _taskManager(), _featureService(server), _socket(server), -#if FT_ENABLED(USE_NTP) - _ntpSettingsService(server, &ESPFS), - _ntpStatus(server), -#endif #if FT_ENABLED(USE_UPLOAD_FIRMWARE) _uploadFirmwareService(server), #endif @@ -97,6 +93,18 @@ void ESP32SvelteKit::setupServer() { return _apService.endpoint.handleStateUpdate(request, json); }); + // NTP +#if FT_ENABLED(USE_NTP) + _server->on("/api/ntp/status", HTTP_GET, [this](PsychicRequest *r) { return _ntpService.getStatus(r); }); + _server->on("/api/ntp/time", HTTP_POST, + [this](PsychicRequest *r, JsonVariant &json) { return _ntpService.handleTime(r, json); }); + _server->on("/api/ntp/settings", HTTP_GET, + [this](PsychicRequest *request) { return _ntpService.endpoint.getState(request); }); + _server->on("/api/ntp/settings", HTTP_POST, [this](PsychicRequest *request, JsonVariant &json) { + return _ntpService.endpoint.handleStateUpdate(request, json); + }); +#endif + // SYSTEM _server->on("/api/system/reset", HTTP_POST, system_service::handleReset); _server->on("/api/system/restart", HTTP_POST, system_service::handleRestart); @@ -182,8 +190,7 @@ void ESP32SvelteKit::startServices() { _downloadFirmwareService.begin(); #endif #if FT_ENABLED(USE_NTP) - _ntpSettingsService.begin(); - _ntpStatus.begin(); + _ntpService.begin(); #endif #if FT_ENABLED(USE_ANALYTICS) _analyticsService.begin(); diff --git a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h index dabea77..a196ea2 100644 --- a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h +++ b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h @@ -30,10 +30,9 @@ #include #include #include -#include +#include #include #include -#include #include #include #include @@ -74,10 +73,6 @@ class ESP32SvelteKit { EventSocket *getSocket() { return &_socket; } -#if FT_ENABLED(USE_NTP) - StatefulService *getNTPSettingsService() { return &_ntpSettingsService; } -#endif - #if FT_ENABLED(USE_BATTERY) BatteryService *getBatteryService() { return &_batteryService; } #endif @@ -117,8 +112,7 @@ class ESP32SvelteKit { APService _apService; EventSocket _socket; #if FT_ENABLED(USE_NTP) - NTPSettingsService _ntpSettingsService; - NTPStatus _ntpStatus; + NTPService _ntpService; #endif #if FT_ENABLED(USE_UPLOAD_FIRMWARE) UploadFirmwareService _uploadFirmwareService; diff --git a/esp32/lib/ESP32-sveltekit/NTPSettingsService.cpp b/esp32/lib/ESP32-sveltekit/NTPSettingsService.cpp deleted file mode 100644 index 0567c69..0000000 --- a/esp32/lib/ESP32-sveltekit/NTPSettingsService.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/** - * ESP32 SvelteKit - * - * A simple, secure and extensible framework for IoT projects for ESP32 platforms - * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. - * https://github.com/theelims/ESP32-sveltekit - * - * Copyright (C) 2018 - 2023 rjwats - * Copyright (C) 2023 theelims - * - * All Rights Reserved. This software may be modified and distributed under - * the terms of the LGPL v3 license. See the LICENSE file for details. - **/ - -#include - -NTPSettingsService::NTPSettingsService(PsychicHttpServer *server, FS *fs) - : _server(server), - _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH), - _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE) { - addUpdateHandler([&](const String &originId) { configureNTP(); }, false); -} - -void NTPSettingsService::begin() { - WiFi.onEvent( - std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2), - WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); - WiFi.onEvent(std::bind(&NTPSettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2), - WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP); - - _httpEndpoint.begin(); - _server->on(TIME_PATH, HTTP_POST, - [this](PsychicRequest *request, JsonVariant &json) { return configureTime(request, json); }); - - ESP_LOGV("NTPSettingsService", "Registered POST endpoint: %s", TIME_PATH); - - _fsPersistence.readFromFS(); - configureNTP(); -} - -void NTPSettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { configureNTP(); } - -void NTPSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) { configureNTP(); } - -void NTPSettingsService::configureNTP() { - if (WiFi.isConnected() && _state.enabled) { - configTzTime(_state.tzFormat.c_str(), _state.server.c_str()); - } else { - setenv("TZ", _state.tzFormat.c_str(), 1); - tzset(); - sntp_stop(); - } -} - -esp_err_t NTPSettingsService::configureTime(PsychicRequest *request, JsonVariant &json) { - if (!sntp_enabled() && json.is()) { - struct tm tm = {0}; - String timeLocal = json["local_time"]; - char *s = strptime(timeLocal.c_str(), "%Y-%m-%dT%H:%M:%S", &tm); - if (s != nullptr) { - time_t time = mktime(&tm); - struct timeval now = {.tv_sec = time}; - settimeofday(&now, nullptr); - return request->reply(200); - } - } - return request->reply(400); -} diff --git a/esp32/lib/ESP32-sveltekit/NTPSettingsService.h b/esp32/lib/ESP32-sveltekit/NTPSettingsService.h deleted file mode 100644 index f043d29..0000000 --- a/esp32/lib/ESP32-sveltekit/NTPSettingsService.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef NTPSettingsService_h -#define NTPSettingsService_h - -/** - * ESP32 SvelteKit - * - * A simple, secure and extensible framework for IoT projects for ESP32 platforms - * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. - * https://github.com/theelims/ESP32-sveltekit - * - * Copyright (C) 2018 - 2023 rjwats - * Copyright (C) 2023 theelims - * - * All Rights Reserved. This software may be modified and distributed under - * the terms of the LGPL v3 license. See the LICENSE file for details. - **/ - -#include -#include -#include -#include - -#include -#include - -#ifndef FACTORY_NTP_ENABLED -#define FACTORY_NTP_ENABLED true -#endif - -#ifndef FACTORY_NTP_TIME_ZONE_LABEL -#define FACTORY_NTP_TIME_ZONE_LABEL "Europe/London" -#endif - -#ifndef FACTORY_NTP_TIME_ZONE_FORMAT -#define FACTORY_NTP_TIME_ZONE_FORMAT "GMT0BST,M3.5.0/1,M10.5.0" -#endif - -#ifndef FACTORY_NTP_SERVER -#define FACTORY_NTP_SERVER "time.google.com" -#endif - -#define NTP_SETTINGS_SERVICE_PATH "/api/ntpSettings" - -#define TIME_PATH "/api/time" - -class NTPSettings { - public: - bool enabled; - String tzLabel; - String tzFormat; - String server; - - static void read(NTPSettings &settings, JsonObject &root) { - root["enabled"] = settings.enabled; - root["server"] = settings.server; - root["tz_label"] = settings.tzLabel; - root["tz_format"] = settings.tzFormat; - } - - static StateUpdateResult update(JsonObject &root, NTPSettings &settings) { - settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED; - settings.server = root["server"] | FACTORY_NTP_SERVER; - settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL; - settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT; - return StateUpdateResult::CHANGED; - } -}; - -class NTPSettingsService : public StatefulService { - public: - NTPSettingsService(PsychicHttpServer *server, FS *fs); - - void begin(); - - private: - PsychicHttpServer *_server; - HttpEndpoint _httpEndpoint; - FSPersistence _fsPersistence; - - void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info); - void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info); - void configureNTP(); - esp_err_t configureTime(PsychicRequest *request, JsonVariant &json); -}; - -#endif // end NTPSettingsService_h diff --git a/esp32/lib/ESP32-sveltekit/NTPStatus.cpp b/esp32/lib/ESP32-sveltekit/NTPStatus.cpp deleted file mode 100644 index bfb668f..0000000 --- a/esp32/lib/ESP32-sveltekit/NTPStatus.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/** - * ESP32 SvelteKit - * - * A simple, secure and extensible framework for IoT projects for ESP32 platforms - * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. - * https://github.com/theelims/ESP32-sveltekit - * - * Copyright (C) 2018 - 2023 rjwats - * Copyright (C) 2023 theelims - * - * All Rights Reserved. This software may be modified and distributed under - * the terms of the LGPL v3 license. See the LICENSE file for details. - **/ - -#include - -NTPStatus::NTPStatus(PsychicHttpServer *server) : _server(server) {} - -void NTPStatus::begin() { - _server->on(NTP_STATUS_SERVICE_PATH, HTTP_GET, [this](PsychicRequest *request) { return ntpStatus(request); }); - - ESP_LOGV("NTPStatus", "Registered GET endpoint: %s", NTP_STATUS_SERVICE_PATH); -} - -/* - * Formats the time using the format provided. - * - * Uses a 25 byte buffer, large enough to fit an ISO time string with offset. - */ -String formatTime(tm *time, const char *format) { - char time_string[25]; - strftime(time_string, 25, format, time); - return String(time_string); -} - -String toUTCTimeString(tm *time) { return formatTime(time, "%FT%TZ"); } - -String toLocalTimeString(tm *time) { return formatTime(time, "%FT%T"); } - -esp_err_t NTPStatus::ntpStatus(PsychicRequest *request) { - PsychicJsonResponse response = PsychicJsonResponse(request, false); - JsonObject root = response.getRoot(); - - // grab the current instant in unix seconds - time_t now = time(nullptr); - - // only provide enabled/disabled status for now - root["status"] = sntp_enabled() ? 1 : 0; - - // the current time in UTC - root["utc_time"] = toUTCTimeString(gmtime(&now)); - - // local time with offset - root["local_time"] = toLocalTimeString(localtime(&now)); - - // the sntp server name - root["server"] = sntp_getservername(0); - - // device uptime in seconds - root["uptime"] = millis() / 1000; - - return response.send(); -} diff --git a/esp32/lib/ESP32-sveltekit/NTPStatus.h b/esp32/lib/ESP32-sveltekit/NTPStatus.h deleted file mode 100644 index bdc7ed6..0000000 --- a/esp32/lib/ESP32-sveltekit/NTPStatus.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef NTPStatus_h -#define NTPStatus_h - -/** - * ESP32 SvelteKit - * - * A simple, secure and extensible framework for IoT projects for ESP32 platforms - * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. - * https://github.com/theelims/ESP32-sveltekit - * - * Copyright (C) 2018 - 2023 rjwats - * Copyright (C) 2023 theelims - * - * All Rights Reserved. This software may be modified and distributed under - * the terms of the LGPL v3 license. See the LICENSE file for details. - **/ - -#include -#include -#include - -#include -#include - -#define NTP_STATUS_SERVICE_PATH "/api/ntpStatus" - -class NTPStatus { - public: - NTPStatus(PsychicHttpServer *server); - - void begin(); - - private: - PsychicHttpServer *_server; - esp_err_t ntpStatus(PsychicRequest *request); -}; - -#endif // end NTPStatus_h diff --git a/esp32/lib/ESP32-sveltekit/domain/ntp_settings.h b/esp32/lib/ESP32-sveltekit/domain/ntp_settings.h new file mode 100644 index 0000000..b53fc60 --- /dev/null +++ b/esp32/lib/ESP32-sveltekit/domain/ntp_settings.h @@ -0,0 +1,42 @@ +#include +#include +#include + +#ifndef FACTORY_NTP_ENABLED +#define FACTORY_NTP_ENABLED true +#endif + +#ifndef FACTORY_NTP_TIME_ZONE_LABEL +#define FACTORY_NTP_TIME_ZONE_LABEL "Europe/London" +#endif + +#ifndef FACTORY_NTP_TIME_ZONE_FORMAT +#define FACTORY_NTP_TIME_ZONE_FORMAT "GMT0BST,M3.5.0/1,M10.5.0" +#endif + +#ifndef FACTORY_NTP_SERVER +#define FACTORY_NTP_SERVER "time.google.com" +#endif + +class NTPSettings { + public: + bool enabled; + String tzLabel; + String tzFormat; + String server; + + static void read(NTPSettings &settings, JsonObject &root) { + root["enabled"] = settings.enabled; + root["server"] = settings.server; + root["tz_label"] = settings.tzLabel; + root["tz_format"] = settings.tzFormat; + } + + static StateUpdateResult update(JsonObject &root, NTPSettings &settings) { + settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED; + settings.server = root["server"] | FACTORY_NTP_SERVER; + settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL; + settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT; + return StateUpdateResult::CHANGED; + } +}; \ No newline at end of file diff --git a/esp32/lib/ESP32-sveltekit/ntp_service.cpp b/esp32/lib/ESP32-sveltekit/ntp_service.cpp new file mode 100644 index 0000000..eeed5c1 --- /dev/null +++ b/esp32/lib/ESP32-sveltekit/ntp_service.cpp @@ -0,0 +1,94 @@ +#include + +static const char *TAG = "NPT Service"; + +NTPService::NTPService() + : endpoint(NTPSettings::read, NTPSettings::update, this), + _persistence(NTPSettings::read, NTPSettings::update, this, &ESPFS, NTP_SETTINGS_FILE) { + addUpdateHandler([&](const String &originId) { configureNTP(); }, false); +} + +void NTPService::begin() { + WiFi.onEvent(std::bind(&NTPService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2), + WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); + WiFi.onEvent(std::bind(&NTPService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2), + WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP); + + configureNTP(); +} + +/* + * Formats the time using the format provided. + * + * Uses a 25 byte buffer, large enough to fit an ISO time string with offset. + */ +const char *formatTime(const tm *time, const char *format) { + static char time_string[25]; + strftime(time_string, sizeof(time_string), format, time); + return time_string; +} + +const char *toUTCTimeString(const tm *time) { return formatTime(time, "%FT%TZ"); } + +const char *toLocalTimeString(const tm *time) { return formatTime(time, "%FT%T"); } + +esp_err_t NTPService::getStatus(PsychicRequest *request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false); + JsonObject root = response.getRoot(); + + // grab the current instant in unix seconds + time_t now = time(nullptr); + + // only provide enabled/disabled status for now + root["status"] = sntp_enabled() ? 1 : 0; + + // the current time in UTC + root["utc_time"] = toUTCTimeString(gmtime(&now)); + + // local time with offset + root["local_time"] = toLocalTimeString(localtime(&now)); + + // the sntp server name + root["server"] = sntp_getservername(0); + + // device uptime in seconds + root["uptime"] = millis() / 1000; + + return response.send(); +} + +void NTPService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { + ESP_LOGI(TAG, "Got IP address, starting NTP Synchronization"); + configureNTP(); +} + +void NTPService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) { + ESP_LOGD(TAG, "WiFi connection dropped, stopping NTP."); + configureNTP(); +} + +void NTPService::configureNTP() { + if (WiFi.isConnected() && _state.enabled) { + ESP_LOGI(TAG, "Starting NTP..."); + configTzTime(_state.tzFormat.c_str(), _state.server.c_str()); + } else { + setenv("TZ", _state.tzFormat.c_str(), 1); + tzset(); + sntp_stop(); + } +} + +esp_err_t NTPService::handleTime(PsychicRequest *request, JsonVariant &json) { + if (!sntp_enabled() && json.is()) { + struct tm tm = {0}; + String timeLocal = json["local_time"]; + char *s = strptime(timeLocal.c_str(), "%Y-%m-%dT%H:%M:%S", &tm); + if (s != nullptr) { + time_t time = mktime(&tm); + struct timeval now = {.tv_sec = time}; + settimeofday(&now, nullptr); + return request->reply(200); + } + } + return request->reply(400); +} \ No newline at end of file diff --git a/esp32/lib/ESP32-sveltekit/ntp_service.h b/esp32/lib/ESP32-sveltekit/ntp_service.h new file mode 100644 index 0000000..3ce6fe5 --- /dev/null +++ b/esp32/lib/ESP32-sveltekit/ntp_service.h @@ -0,0 +1,31 @@ +#ifndef NTPService_h +#define NTPService_h + +#include +#include +#include +#include +#include + +#include +#include + +class NTPService : public StatefulService { + public: + NTPService(); + + void begin(); + static esp_err_t getStatus(PsychicRequest *request); + static esp_err_t handleTime(PsychicRequest *request, JsonVariant &json); + + StatefulHttpEndpoint endpoint; + + private: + FSPersistence _persistence; + + void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info); + void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info); + void configureNTP(); +}; + +#endif // end NTPService_h \ No newline at end of file