diff --git a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp index b8454750..49644a1b 100644 --- a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp +++ b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp @@ -21,9 +21,6 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _taskManager(), _featureService(server), _securitySettingsService(server, &ESPFS), - _wifiSettingsService(server, &ESPFS, &_securitySettingsService, &_socket), - _wifiScanner(server, &_securitySettingsService), - _wifiStatus(server, &_securitySettingsService), _apSettingsService(server, &ESPFS, &_securitySettingsService), _apStatus(server, &_securitySettingsService, &_apSettingsService), _socket(server, &_securitySettingsService, AuthenticationPredicates::IS_AUTHENTICATED), @@ -72,14 +69,12 @@ void ESP32SvelteKit::begin() { ESP_LOGI("Running Firmware Version: %s", APP_VERSION); ESPFS.begin(true); - _wifiSettingsService.initWiFi(); + startServices(); setupServer(); setupMDNS(); - startServices(); - ESP_LOGV("ESP32SvelteKit", "Starting loop task"); _taskManager.createTask(this->_loopImpl, "Spot main", 4096, this, 2, NULL, ESP32SVELTEKIT_RUNNING_CORE); } @@ -88,6 +83,17 @@ void ESP32SvelteKit::setupServer() { _server->config.max_uri_handlers = _numberEndpoints; _server->listen(80); + _server->on("/api/wifi/scan", HTTP_GET, _wifiService.handleScan); + _server->on("/api/wifi/networks", HTTP_GET, + [this](PsychicRequest *request) { return _wifiService.getNetworks(request); }); + _server->on("/api/wifi/sta/status", HTTP_GET, + [this](PsychicRequest *request) { return _wifiService.getNetworkStatus(request); }); + _server->on("/api/wifi/sta/settings", HTTP_GET, + [this](PsychicRequest *request) { return _wifiService.endpoint.getState(request); }); + _server->on("/api/wifi/sta/settings", HTTP_POST, [this](PsychicRequest *request, JsonVariant &json) { + return _wifiService.endpoint.handleStateUpdate(request, json); + }); + #ifdef EMBED_WWW ESP_LOGV("ESP32SvelteKit", "Registering routes from PROGMEM static resources"); WWWData::registerRoutes([&](const String &uri, const String &contentType, const uint8_t *content, size_t len) { @@ -140,7 +146,7 @@ void ESP32SvelteKit::setupServer() { void ESP32SvelteKit::setupMDNS() { ESP_LOGV("ESP32SvelteKit", "Starting MDNS"); - MDNS.begin(_wifiSettingsService.getHostname().c_str()); + MDNS.begin(_wifiService.getHostname()); MDNS.setInstanceName(_appName); MDNS.addService("http", "tcp", 80); MDNS.addService("ws", "tcp", 80); @@ -148,6 +154,7 @@ void ESP32SvelteKit::setupMDNS() { } void ESP32SvelteKit::startServices() { + _wifiService.begin(); _apStatus.begin(); _socket.begin(); _apSettingsService.begin(); @@ -155,9 +162,6 @@ void ESP32SvelteKit::startServices() { _featureService.begin(); _restartService.begin(); _systemStatus.begin(); - _wifiSettingsService.begin(); - _wifiScanner.begin(); - _wifiStatus.begin(); #if FT_ENABLED(USE_UPLOAD_FIRMWARE) _uploadFirmwareService.begin(); @@ -203,7 +207,7 @@ void IRAM_ATTR ESP32SvelteKit::loop() { #if FT_ENABLED(USE_WS2812) _ledService.loop(); #endif - _wifiSettingsService.loop(); + _wifiService.loop(); _apSettingsService.loop(); #if FT_ENABLED(USE_ANALYTICS) _analyticsService.loop(); diff --git a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h index 6f87593e..b424ffa2 100644 --- a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h +++ b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h @@ -46,9 +46,7 @@ #include #include #include -#include -#include -#include +#include #include #ifdef EMBED_WWW @@ -85,8 +83,6 @@ class ESP32SvelteKit { EventSocket *getSocket() { return &_socket; } - StatefulService *getWiFiSettingsService() { return &_wifiSettingsService; } - StatefulService *getAPSettingsService() { return &_apSettingsService; } #if FT_ENABLED(USE_NTP) @@ -135,9 +131,10 @@ class ESP32SvelteKit { unsigned int _numberEndpoints; FeaturesService _featureService; SecuritySettingsService _securitySettingsService; - WiFiSettingsService _wifiSettingsService; - WiFiScanner _wifiScanner; - WiFiStatus _wifiStatus; + WiFiService _wifiService; + // WiFiSettingsService _wifiSettingsService; + // WiFiScanner _wifiScanner; + // WiFiStatus _wifiStatus; APSettingsService _apSettingsService; APStatus _apStatus; EventSocket _socket; diff --git a/esp32/lib/ESP32-sveltekit/FSPersistence.h b/esp32/lib/ESP32-sveltekit/FSPersistence.h index eb2875ca..e7459957 100644 --- a/esp32/lib/ESP32-sveltekit/FSPersistence.h +++ b/esp32/lib/ESP32-sveltekit/FSPersistence.h @@ -10,6 +10,7 @@ * * Copyright (C) 2018 - 2023 rjwats * Copyright (C) 2023 theelims + * Copyright (C) 2024 runeharlyk * * All Rights Reserved. This software may be modified and distributed under * the terms of the LGPL v3 license. See the LICENSE file for details. @@ -17,6 +18,7 @@ #include #include +#include template class FSPersistence { diff --git a/esp32/lib/ESP32-sveltekit/StatefulService.cpp b/esp32/lib/ESP32-sveltekit/StatefulService.cpp deleted file mode 100644 index e3599074..00000000 --- a/esp32/lib/ESP32-sveltekit/StatefulService.cpp +++ /dev/null @@ -1,18 +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 - -update_handler_id_t StateUpdateHandlerInfo::currentUpdatedHandlerId = 0; -hook_handler_id_t StateHookHandlerInfo::currentHookHandlerId = 0; diff --git a/esp32/lib/ESP32-sveltekit/StatefulService.h b/esp32/lib/ESP32-sveltekit/StatefulService.h index 7fc665f7..20db4d25 100644 --- a/esp32/lib/ESP32-sveltekit/StatefulService.h +++ b/esp32/lib/ESP32-sveltekit/StatefulService.h @@ -10,6 +10,7 @@ * * Copyright (C) 2018 - 2023 rjwats * Copyright (C) 2023 theelims + * Copyright (C) 2024 runeharlyk * * All Rights Reserved. This software may be modified and distributed under * the terms of the LGPL v3 license. See the LICENSE file for details. @@ -23,11 +24,7 @@ #include #include -enum class StateUpdateResult { - CHANGED = 0, // The update changed the state and propagation should take place if required - UNCHANGED, // The state was unchanged, propagation should not take place - ERROR // There was a problem updating the state, propagation should not take place -}; +#include template using JsonStateUpdater = std::function; @@ -41,7 +38,7 @@ typedef std::function StateUpdateCallback; typedef std::function StateHookCallback; typedef struct StateUpdateHandlerInfo { - static update_handler_id_t currentUpdatedHandlerId; + static inline update_handler_id_t currentUpdatedHandlerId = 0; update_handler_id_t _id; StateUpdateCallback _cb; bool _allowRemove; @@ -50,7 +47,7 @@ typedef struct StateUpdateHandlerInfo { } StateUpdateHandlerInfo_t; typedef struct StateHookHandlerInfo { - static hook_handler_id_t currentHookHandlerId; + static inline hook_handler_id_t currentHookHandlerId = 0; hook_handler_id_t _id; StateHookCallback _cb; bool _allowRemove; diff --git a/esp32/lib/ESP32-sveltekit/WiFiScanner.cpp b/esp32/lib/ESP32-sveltekit/WiFiScanner.cpp deleted file mode 100644 index 985f10d1..00000000 --- a/esp32/lib/ESP32-sveltekit/WiFiScanner.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 - -WiFiScanner::WiFiScanner(PsychicHttpServer *server, SecurityManager *securityManager) - : _server(server), _securityManager(securityManager) {} - -void WiFiScanner::begin() { - _server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET, - _securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1), - AuthenticationPredicates::IS_ADMIN)); - - ESP_LOGV("WiFiScanner", "Registered GET endpoint: %s", SCAN_NETWORKS_SERVICE_PATH); - - _server->on(LIST_NETWORKS_SERVICE_PATH, HTTP_GET, - _securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1), - AuthenticationPredicates::IS_ADMIN)); - - ESP_LOGV("WiFiScanner", "Registered GET endpoint: %s", LIST_NETWORKS_SERVICE_PATH); -} - -esp_err_t WiFiScanner::scanNetworks(PsychicRequest *request) { - if (WiFi.scanComplete() != -1) { - WiFi.scanDelete(); - WiFi.scanNetworks(true); - } - return request->reply(202); -} - -esp_err_t WiFiScanner::listNetworks(PsychicRequest *request) { - int numNetworks = WiFi.scanComplete(); - if (numNetworks > -1) { - PsychicJsonResponse response = PsychicJsonResponse(request, false); - JsonObject root = response.getRoot(); - JsonArray networks = root["networks"].to(); - for (int i = 0; i < numNetworks; i++) { - JsonObject network = networks.add(); - network["rssi"] = WiFi.RSSI(i); - network["ssid"] = WiFi.SSID(i); - network["bssid"] = WiFi.BSSIDstr(i); - network["channel"] = WiFi.channel(i); - network["encryption_type"] = (uint8_t)WiFi.encryptionType(i); - } - - return response.send(); - } else if (numNetworks == -1) { - return request->reply(202); - } else { - return scanNetworks(request); - } -} diff --git a/esp32/lib/ESP32-sveltekit/WiFiScanner.h b/esp32/lib/ESP32-sveltekit/WiFiScanner.h deleted file mode 100644 index 5b024d96..00000000 --- a/esp32/lib/ESP32-sveltekit/WiFiScanner.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef WiFiScanner_h -#define WiFiScanner_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 - -#define SCAN_NETWORKS_SERVICE_PATH "/api/scanNetworks" -#define LIST_NETWORKS_SERVICE_PATH "/api/listNetworks" - -class WiFiScanner { - public: - WiFiScanner(PsychicHttpServer *server, SecurityManager *securityManager); - - void begin(); - - private: - PsychicHttpServer *_server; - SecurityManager *_securityManager; - - esp_err_t scanNetworks(PsychicRequest *request); - esp_err_t listNetworks(PsychicRequest *request); -}; - -#endif // end WiFiScanner_h diff --git a/esp32/lib/ESP32-sveltekit/WiFiSettingsService.cpp b/esp32/lib/ESP32-sveltekit/WiFiSettingsService.cpp deleted file mode 100644 index d43011da..00000000 --- a/esp32/lib/ESP32-sveltekit/WiFiSettingsService.cpp +++ /dev/null @@ -1,177 +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 - -WiFiSettingsService::WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, - EventSocket *socket) - : _server(server), - _securityManager(securityManager), - _httpEndpoint(WiFiSettings::read, WiFiSettings::update, this, server, WIFI_SETTINGS_SERVICE_PATH, securityManager, - AuthenticationPredicates::IS_ADMIN), - _eventEndpoint(WiFiSettings::read, WiFiSettings::update, this, socket, EVENT_WIFI_SETTINGS), - _fsPersistence(WiFiSettings::read, WiFiSettings::update, this, fs, WIFI_SETTINGS_FILE), - _lastConnectionAttempt(0), - _socket(socket) { - addUpdateHandler([&](const String &originId) { reconfigureWiFiConnection(); }, false); -} - -void WiFiSettingsService::initWiFi() { - WiFi.mode(WIFI_MODE_STA); // this is the default. - - // Disable WiFi config persistance and auto reconnect - WiFi.persistent(false); - WiFi.setAutoReconnect(false); - - WiFi.onEvent( - std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2), - WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); - WiFi.onEvent(std::bind(&WiFiSettingsService::onStationModeStop, this, std::placeholders::_1, std::placeholders::_2), - WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_STOP); - - _fsPersistence.readFromFS(); - reconfigureWiFiConnection(); -} - -void WiFiSettingsService::begin() { - _httpEndpoint.begin(); - _eventEndpoint.begin(); -} - -void WiFiSettingsService::reconfigureWiFiConnection() { - // reset last connection attempt to force loop to reconnect immediately - _lastConnectionAttempt = 0; - - // disconnect and de-configure wifi - if (WiFi.disconnect(true)) { - _stopping = true; - } -} - -void WiFiSettingsService::loop() { - EXECUTE_EVERY_N_MS(WIFI_RECONNECTION_DELAY, manageSTA()); - EXECUTE_EVERY_N_MS(RSSI_EVENT_DELAY, updateRSSI()); -} - -String WiFiSettingsService::getHostname() { return _state.hostname; } - -void WiFiSettingsService::manageSTA() { - // Abort if already connected, or if we have no SSID - if (WiFi.isConnected() || _state.wifiSettings.empty()) { - return; - } - - // Connect or reconnect as required - if ((WiFi.getMode() & WIFI_STA) == 0) { - connectToWiFi(); - } -} - -void WiFiSettingsService::connectToWiFi() { - // reset availability flag for all stored networks - for (auto &network : _state.wifiSettings) { - network.available = false; - } - - // scanning for available networks - int scanResult = WiFi.scanNetworks(); - if (scanResult == WIFI_SCAN_FAILED) { - ESP_LOGE("WiFiSettingsService", "WiFi scan failed."); - } else if (scanResult == 0) { - ESP_LOGI("WiFiSettingsService", "No networks found."); - } else { - ESP_LOGI("WiFiSettingsService", "%d networks found.", scanResult); - - // find the best network to connect - wifi_settings_t *bestNetwork = NULL; - int bestNetworkDb = FACTORY_WIFI_RSSI_THRESHOLD; - - for (int i = 0; i < scanResult; ++i) { - String ssid_scan; - int32_t rssi_scan; - uint8_t sec_scan; - uint8_t *BSSID_scan; - int32_t chan_scan; - - WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan); - ESP_LOGV("WiFiSettingsService", "SSID: %s, RSSI: %d dbm", ssid_scan.c_str(), rssi_scan); - - for (auto &network : _state.wifiSettings) { - if (ssid_scan == network.ssid) { // SSID match - if (rssi_scan > bestNetworkDb) { // best network - bestNetworkDb = rssi_scan; - bestNetwork = &network; - network.available = true; - } else if (rssi_scan >= FACTORY_WIFI_RSSI_THRESHOLD) { // available network - network.available = true; - } - } - break; - } - } - - // if configured to prioritize signal strength, use the best network else use the first available network - if (_state.priorityBySignalStrength == false) { - for (auto &network : _state.wifiSettings) { - if (network.available == true) { - ESP_LOGI("WiFiSettingsService", "Connecting to first available network: %s", network.ssid.c_str()); - configureNetwork(network); - break; - } - } - } else if (_state.priorityBySignalStrength == true && bestNetwork) { - ESP_LOGI("WiFiSettingsService", "Connecting to strongest network: %s", bestNetwork->ssid.c_str()); - configureNetwork(*bestNetwork); - WiFi.begin(bestNetwork->ssid.c_str(), bestNetwork->password.c_str()); - } else // no suitable network to connect - { - ESP_LOGI("WiFiSettingsService", "No known networks found."); - } - - // delete scan results - WiFi.scanDelete(); - } -} - -void WiFiSettingsService::configureNetwork(wifi_settings_t &network) { - if (network.staticIPConfig) { - // configure for static IP - WiFi.config(network.localIP, network.gatewayIP, network.subnetMask, network.dnsIP1, network.dnsIP2); - } else { - // configure for DHCP - WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); - } - WiFi.setHostname(_state.hostname.c_str()); - - // attempt to connect to the network - WiFi.begin(network.ssid.c_str(), network.password.c_str()); - -#if CONFIG_IDF_TARGET_ESP32C3 - WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi -#endif -} - -void WiFiSettingsService::updateRSSI() { - char buffer[8]; - snprintf(buffer, sizeof(buffer), "%d", WiFi.RSSI()); - _socket->emit(EVENT_RSSI, buffer); -} - -void WiFiSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) { WiFi.disconnect(true); } -void WiFiSettingsService::onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info) { - if (_stopping) { - _lastConnectionAttempt = 0; - _stopping = false; - } -} diff --git a/esp32/lib/ESP32-sveltekit/WiFiStatus.cpp b/esp32/lib/ESP32-sveltekit/WiFiStatus.cpp deleted file mode 100644 index a87872b1..00000000 --- a/esp32/lib/ESP32-sveltekit/WiFiStatus.cpp +++ /dev/null @@ -1,70 +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 - -WiFiStatus::WiFiStatus(PsychicHttpServer *server, SecurityManager *securityManager) - : _server(server), _securityManager(securityManager) {} - -void WiFiStatus::begin() { - _server->on(WIFI_STATUS_SERVICE_PATH, HTTP_GET, - _securityManager->wrapRequest(std::bind(&WiFiStatus::wifiStatus, this, std::placeholders::_1), - AuthenticationPredicates::IS_AUTHENTICATED)); - - ESP_LOGV("WiFiStatus", "Registered GET endpoint: %s", WIFI_STATUS_SERVICE_PATH); - - WiFi.onEvent(onStationModeConnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED); - WiFi.onEvent(onStationModeDisconnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); - WiFi.onEvent(onStationModeGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP); -} - -void WiFiStatus::onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info) { - ESP_LOGI("WiFiStatus", "WiFi Connected."); -} - -void WiFiStatus::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) { - ESP_LOGI("WiFiStatus", "WiFi Disconnected. Reason code=%d", info.wifi_sta_disconnected.reason); -} - -void WiFiStatus::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { - ESP_LOGI("WiFiStatus", "WiFi Got IP. localIP=%s, hostName=%s", WiFi.localIP().toString().c_str(), - WiFi.getHostname()); -} - -esp_err_t WiFiStatus::wifiStatus(PsychicRequest *request) { - PsychicJsonResponse response = PsychicJsonResponse(request, false); - JsonObject root = response.getRoot(); - wl_status_t status = WiFi.status(); - root["status"] = (uint8_t)status; - if (status == WL_CONNECTED) { - root["local_ip"] = WiFi.localIP().toString(); - root["mac_address"] = WiFi.macAddress(); - root["rssi"] = WiFi.RSSI(); - root["ssid"] = WiFi.SSID(); - root["bssid"] = WiFi.BSSIDstr(); - root["channel"] = WiFi.channel(); - root["subnet_mask"] = WiFi.subnetMask().toString(); - root["gateway_ip"] = WiFi.gatewayIP().toString(); - IPAddress dnsIP1 = WiFi.dnsIP(0); - IPAddress dnsIP2 = WiFi.dnsIP(1); - if (IPUtils::isSet(dnsIP1)) { - root["dns_ip_1"] = dnsIP1.toString(); - } - if (IPUtils::isSet(dnsIP2)) { - root["dns_ip_2"] = dnsIP2.toString(); - } - } - - return response.send(); -} diff --git a/esp32/lib/ESP32-sveltekit/WiFiStatus.h b/esp32/lib/ESP32-sveltekit/WiFiStatus.h deleted file mode 100644 index 05dc6974..00000000 --- a/esp32/lib/ESP32-sveltekit/WiFiStatus.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef WiFiStatus_h -#define WiFiStatus_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 WIFI_STATUS_SERVICE_PATH "/api/wifiStatus" - -class WiFiStatus { - public: - WiFiStatus(PsychicHttpServer *server, SecurityManager *securityManager); - - void begin(); - - private: - PsychicHttpServer *_server; - SecurityManager *_securityManager; - - // static functions for logging WiFi events to the UART - static void onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info); - static void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info); - static void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info); - esp_err_t wifiStatus(PsychicRequest *request); -}; - -#endif // end WiFiStatus_h diff --git a/esp32/lib/ESP32-sveltekit/state_result.h b/esp32/lib/ESP32-sveltekit/state_result.h new file mode 100644 index 00000000..94c690f3 --- /dev/null +++ b/esp32/lib/ESP32-sveltekit/state_result.h @@ -0,0 +1,7 @@ +#pragma once + +enum class StateUpdateResult { + CHANGED = 0, // The update changed the state and propagation should take place if required + UNCHANGED, // The state was unchanged, propagation should not take place + ERROR // There was a problem updating the state, propagation should not take place +}; \ No newline at end of file diff --git a/esp32/lib/ESP32-sveltekit/stateful_service_endpoint.h b/esp32/lib/ESP32-sveltekit/stateful_service_endpoint.h new file mode 100644 index 00000000..e85d645b --- /dev/null +++ b/esp32/lib/ESP32-sveltekit/stateful_service_endpoint.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#include + +#define HTTP_ENDPOINT_ORIGIN_ID "http" +#define HTTPS_ENDPOINT_ORIGIN_ID "https" + +template +class StatefulHttpEndpoint { + protected: + JsonStateReader _stateReader; + JsonStateUpdater _stateUpdater; + StatefulService *_statefulService; + + public: + StatefulHttpEndpoint(JsonStateReader stateReader, JsonStateUpdater stateUpdater, + StatefulService *statefulService) + : _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService) {} + + esp_err_t handleStateUpdate(PsychicRequest *request, JsonVariant &json) { + if (!json.is()) { + return request->reply(400); + } + + JsonObject jsonObject = json.as(); + StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater); + + if (outcome == StateUpdateResult::ERROR) { + return request->reply(400); + } else if ((outcome == StateUpdateResult::CHANGED)) { + // persist the changes to the FS + _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); + } + + PsychicJsonResponse response = PsychicJsonResponse(request, false); + jsonObject = response.getRoot(); + + _statefulService->read(jsonObject, _stateReader); + + return response.send(); + } + + esp_err_t getState(PsychicRequest *request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false); + JsonObject jsonObject = response.getRoot(); + _statefulService->read(jsonObject, _stateReader); + return response.send(); + } +}; diff --git a/esp32/lib/ESP32-sveltekit/wifi_service.cpp b/esp32/lib/ESP32-sveltekit/wifi_service.cpp new file mode 100644 index 00000000..c8976e07 --- /dev/null +++ b/esp32/lib/ESP32-sveltekit/wifi_service.cpp @@ -0,0 +1,208 @@ +#include + +WiFiService::WiFiService() + : endpoint(WiFiSettings::read, WiFiSettings::update, this), + _fsPersistence(WiFiSettings::read, WiFiSettings::update, this, &ESPFS, WIFI_SETTINGS_FILE) { + addUpdateHandler([&](const String &originId) { reconfigureWiFiConnection(); }, false); +} + +WiFiService::~WiFiService() {} + +void WiFiService::begin() { + WiFi.mode(WIFI_MODE_STA); + + WiFi.persistent(false); + WiFi.setAutoReconnect(false); + + WiFi.onEvent(std::bind(&WiFiService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2), + WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); + WiFi.onEvent(std::bind(&WiFiService::onStationModeStop, this, std::placeholders::_1, std::placeholders::_2), + WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_STOP); + + WiFi.onEvent(onStationModeGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP); + + _fsPersistence.readFromFS(); + reconfigureWiFiConnection(); +} + +void WiFiService::reconfigureWiFiConnection() { + _lastConnectionAttempt = 0; + + if (WiFi.disconnect(true)) _stopping = true; +} + +void WiFiService::loop() { EXECUTE_EVERY_N_MS(reconnectDelay, manageSTA()); } + +esp_err_t WiFiService::handleScan(PsychicRequest *request) { + if (WiFi.scanComplete() != -1) { + WiFi.scanDelete(); + WiFi.scanNetworks(true); + } + return request->reply(202); +} + +esp_err_t WiFiService::getNetworks(PsychicRequest *request) { + int numNetworks = WiFi.scanComplete(); + if (numNetworks == -1) + return request->reply(202); + else if (numNetworks < -1) + return handleScan(request); + + PsychicJsonResponse response = PsychicJsonResponse(request, false); + JsonObject root = response.getRoot(); + getNetworks(root); + return response.send(); +} + +void WiFiService::setupMDNS(const char *hostname) { + MDNS.begin(_state.hostname.c_str()); + MDNS.setInstanceName(hostname); + MDNS.addService("http", "tcp", 80); + MDNS.addService("ws", "tcp", 80); + MDNS.addServiceTxt("http", "tcp", "Firmware Version", APP_VERSION); +} + +void WiFiService::getNetworks(JsonObject &root) { + JsonArray networks = root["networks"].to(); + int numNetworks = WiFi.scanComplete(); + for (int i = 0; i < numNetworks; i++) { + JsonObject network = networks.add(); + network["rssi"] = WiFi.RSSI(i); + network["ssid"] = WiFi.SSID(i); + network["bssid"] = WiFi.BSSIDstr(i); + network["channel"] = WiFi.channel(i); + network["encryption_type"] = (uint8_t)WiFi.encryptionType(i); + } +} + +esp_err_t WiFiService::getNetworkStatus(PsychicRequest *request) { + PsychicJsonResponse response = PsychicJsonResponse(request, false); + JsonObject root = response.getRoot(); + getNetworkStatus(root); + return response.send(); +} + +void WiFiService::getNetworkStatus(JsonObject &root) { + wl_status_t status = WiFi.status(); + root["status"] = (uint8_t)status; + if (status == WL_CONNECTED) { + root["local_ip"] = WiFi.localIP().toString(); + root["mac_address"] = WiFi.macAddress(); + root["rssi"] = WiFi.RSSI(); + root["ssid"] = WiFi.SSID(); + root["bssid"] = WiFi.BSSIDstr(); + root["channel"] = WiFi.channel(); + root["subnet_mask"] = WiFi.subnetMask().toString(); + root["gateway_ip"] = WiFi.gatewayIP().toString(); + IPAddress dnsIP1 = WiFi.dnsIP(0); + IPAddress dnsIP2 = WiFi.dnsIP(1); + if (dnsIP1 != INADDR_NONE) { + root["dns_ip_1"] = dnsIP1.toString(); + } + if (dnsIP2 != INADDR_NONE) { + root["dns_ip_2"] = dnsIP2.toString(); + } + } +} + +void WiFiService::manageSTA() { + if (WiFi.isConnected() || _state.wifiSettings.empty()) return; + if ((WiFi.getMode() & WIFI_STA) == 0) connectToWiFi(); +} + +void WiFiService::connectToWiFi() { + // reset availability flag for all stored networks + for (auto &network : _state.wifiSettings) { + network.available = false; + } + + // scanning for available networks + int scanResult = WiFi.scanNetworks(); + if (scanResult == WIFI_SCAN_FAILED) { + ESP_LOGE("WiFiSettingsService", "WiFi scan failed."); + } else if (scanResult == 0) { + ESP_LOGI("WiFiSettingsService", "No networks found."); + } else { + ESP_LOGI("WiFiSettingsService", "%d networks found.", scanResult); + + // find the best network to connect + wifi_settings_t *bestNetwork = nullptr; + int32_t bestNetworkDb = FACTORY_WIFI_RSSI_THRESHOLD; + + for (int i = 0; i < scanResult; ++i) { + String ssid_scan; + int32_t rssi_scan; + uint8_t sec_scan; + uint8_t *BSSID_scan; + int32_t chan_scan; + + WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan); + + for (auto &network : _state.wifiSettings) { + if (ssid_scan == network.ssid) { + if (rssi_scan >= FACTORY_WIFI_RSSI_THRESHOLD) { + network.available = true; + } + if (rssi_scan > bestNetworkDb) { + bestNetworkDb = rssi_scan; + bestNetwork = &network; + } + } + } + } + + if (!_state.priorityBySignalStrength) { + for (auto &network : _state.wifiSettings) { + if (network.available == true) { + ESP_LOGI("WiFiSettingsService", "Connecting to first available network: %s", network.ssid.c_str()); + configureNetwork(network); + break; + } + } + } else if (_state.priorityBySignalStrength && bestNetwork) { + ESP_LOGI("WiFiSettingsService", "Connecting to strongest network: %s", bestNetwork->ssid.c_str()); + configureNetwork(*bestNetwork); + WiFi.begin(bestNetwork->ssid.c_str(), bestNetwork->password.c_str()); + } else { + ESP_LOGI("WiFiSettingsService", "No known networks found."); + } + + WiFi.scanDelete(); + } +} + +void WiFiService::configureNetwork(wifi_settings_t &network) { + if (network.staticIPConfig) { + // configure for static IP + WiFi.config(network.localIP, network.gatewayIP, network.subnetMask, network.dnsIP1, network.dnsIP2); + } else { + // configure for DHCP + WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); + } + WiFi.setHostname(_state.hostname.c_str()); + + // attempt to connect to the network + WiFi.begin(network.ssid.c_str(), network.password.c_str()); + +#if CONFIG_IDF_TARGET_ESP32C3 + WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi +#endif +} + +void WiFiService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) { + WiFi.disconnect(true); + ESP_LOGI("WiFiStatus", "WiFi Disconnected. Reason code=%d", info.wifi_sta_disconnected.reason); +} + +void WiFiService::onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info) { + if (_stopping) { + _lastConnectionAttempt = 0; + _stopping = false; + } + ESP_LOGI("WiFiStatus", "WiFi Connected."); +} + +void WiFiService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { + ESP_LOGI("WiFiStatus", "WiFi Got IP. localIP=%s, hostName=%s", WiFi.localIP().toString().c_str(), + WiFi.getHostname()); +} \ No newline at end of file diff --git a/esp32/lib/ESP32-sveltekit/wifi_service.h b/esp32/lib/ESP32-sveltekit/wifi_service.h new file mode 100644 index 00000000..b2017de3 --- /dev/null +++ b/esp32/lib/ESP32-sveltekit/wifi_service.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +class WiFiService : public StatefulService { + private: + static void getNetworks(JsonObject &root); + static void getNetworkStatus(JsonObject &root); + void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info); + void onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info); + static void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info); + + FSPersistence _fsPersistence; + + void reconfigureWiFiConnection(); + void manageSTA(); + void connectToWiFi(); + void configureNetwork(wifi_settings_t &network); + + unsigned long _lastConnectionAttempt; + bool _stopping; + + constexpr static uint16_t reconnectDelay {10000}; + + public: + WiFiService(); + ~WiFiService(); + + void begin(); + void loop(); + + void setupMDNS(const char *hostname); + + const char *getHostname() { return _state.hostname.c_str(); } + + static esp_err_t handleScan(PsychicRequest *request); + static esp_err_t getNetworks(PsychicRequest *request); + static esp_err_t getNetworkStatus(PsychicRequest *request); + + StatefulHttpEndpoint endpoint; +}; \ No newline at end of file diff --git a/esp32/lib/ESP32-sveltekit/WiFiSettingsService.h b/esp32/lib/ESP32-sveltekit/wifi_settings.h similarity index 71% rename from esp32/lib/ESP32-sveltekit/WiFiSettingsService.h rename to esp32/lib/ESP32-sveltekit/wifi_settings.h index aa2eb2fe..c9a1a7e7 100644 --- a/esp32/lib/ESP32-sveltekit/WiFiSettingsService.h +++ b/esp32/lib/ESP32-sveltekit/wifi_settings.h @@ -1,60 +1,17 @@ -#ifndef WiFiSettingsService_h -#define WiFiSettingsService_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 +#pragma once + +#include +#include +#include #include -#include -#include +#include #include -#include -#include -#include -#include -#include -#include - -#ifndef FACTORY_WIFI_SSID -#define FACTORY_WIFI_SSID "" -#endif - -#ifndef FACTORY_WIFI_PASSWORD -#define FACTORY_WIFI_PASSWORD "" -#endif - -#ifndef FACTORY_WIFI_HOSTNAME -#define FACTORY_WIFI_HOSTNAME "#{platform}-#{unique_id}" -#endif +#include #ifndef FACTORY_WIFI_RSSI_THRESHOLD #define FACTORY_WIFI_RSSI_THRESHOLD -80 #endif -#define WIFI_SETTINGS_SERVICE_PATH "/api/wifiSettings" - -#define WIFI_RECONNECTION_DELAY 1000 * 30 -#define RSSI_EVENT_DELAY 500 - -#define EVENT_RSSI "rssi" -#define EVENT_WIFI_SETTINGS "WiFiSettings" - -// Struct defining the wifi settings typedef struct { String ssid; String password; @@ -69,7 +26,6 @@ typedef struct { class WiFiSettings { public: - // core wifi configuration String hostname; bool priorityBySignalStrength; std::vector wifiSettings; @@ -185,35 +141,4 @@ class WiFiSettings { return StateUpdateResult::CHANGED; }; -}; - -class WiFiSettingsService : public StatefulService { - public: - WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, EventSocket *socket); - - void initWiFi(); - void begin(); - void loop(); - String getHostname(); - - private: - PsychicHttpServer *_server; - SecurityManager *_securityManager; - HttpEndpoint _httpEndpoint; - EventEndpoint _eventEndpoint; - FSPersistence _fsPersistence; - EventSocket *_socket; - unsigned long _lastConnectionAttempt; - - bool _stopping; - void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info); - void onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info); - - void reconfigureWiFiConnection(); - void manageSTA(); - void connectToWiFi(); - void configureNetwork(wifi_settings_t &network); - void updateRSSI(); -}; - -#endif // end WiFiSettingsService_h +}; \ No newline at end of file