🛜 Simplify wifi services

This commit is contained in:
Rune Harlyk
2024-09-03 20:47:07 +02:00
committed by Rune Harlyk
parent 9dbe31d207
commit e69e48533f
15 changed files with 352 additions and 522 deletions
+15 -11
View File
@@ -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();
+5 -8
View File
@@ -46,9 +46,7 @@
#include <TaskManager.h>
#include <UploadFirmwareService.h>
#include <WiFi.h>
#include <WiFiScanner.h>
#include <WiFiSettingsService.h>
#include <WiFiStatus.h>
#include <wifi_service.h>
#include <Wire.h>
#ifdef EMBED_WWW
@@ -85,8 +83,6 @@ class ESP32SvelteKit {
EventSocket *getSocket() { return &_socket; }
StatefulService<WiFiSettings> *getWiFiSettingsService() { return &_wifiSettingsService; }
StatefulService<APSettings> *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;
@@ -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 <FS.h>
#include <StatefulService.h>
#include <ESPFS.h>
template <class T>
class FSPersistence {
@@ -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 <StatefulService.h>
update_handler_id_t StateUpdateHandlerInfo::currentUpdatedHandlerId = 0;
hook_handler_id_t StateHookHandlerInfo::currentHookHandlerId = 0;
+4 -7
View File
@@ -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 <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
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 <state_result.h>
template <typename T>
using JsonStateUpdater = std::function<StateUpdateResult(JsonObject &root, T &settings)>;
@@ -41,7 +38,7 @@ typedef std::function<void(const String &originId)> StateUpdateCallback;
typedef std::function<void(const String &originId, StateUpdateResult &result)> 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;
-63
View File
@@ -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.h>
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<JsonArray>();
for (int i = 0; i < numNetworks; i++) {
JsonObject network = networks.add<JsonObject>();
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);
}
}
-41
View File
@@ -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 <WiFi.h>
#include <ArduinoJson.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#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
@@ -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.h>
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;
}
}
-70
View File
@@ -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.h>
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();
}
-44
View File
@@ -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 <WiFi.h>
#include <ArduinoJson.h>
#include <PsychicHttp.h>
#include <IPUtils.h>
#include <SecurityManager.h>
#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
+7
View File
@@ -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
};
@@ -0,0 +1,52 @@
#pragma once
#include <PsychicHttp.h>
#include <stateful_service_endpoint.h>
#include <functional>
#define HTTP_ENDPOINT_ORIGIN_ID "http"
#define HTTPS_ENDPOINT_ORIGIN_ID "https"
template <class T>
class StatefulHttpEndpoint {
protected:
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T> *_statefulService;
public:
StatefulHttpEndpoint(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater,
StatefulService<T> *statefulService)
: _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService) {}
esp_err_t handleStateUpdate(PsychicRequest *request, JsonVariant &json) {
if (!json.is<JsonObject>()) {
return request->reply(400);
}
JsonObject jsonObject = json.as<JsonObject>();
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();
}
};
+208
View File
@@ -0,0 +1,208 @@
#include <wifi_service.h>
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<JsonArray>();
int numNetworks = WiFi.scanComplete();
for (int i = 0; i < numNetworks; i++) {
JsonObject network = networks.add<JsonObject>();
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());
}
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include <PsychicHttp.h>
#include <IPAddress.h>
#include <WiFi.h>
#include <ESPmDNS.h>
#include <ESPFS.h>
#include <Timing.h>
#include <StatefulService.h>
#include <FSPersistence.h>
#include <stateful_service_endpoint.h>
#include <wifi_settings.h>
class WiFiService : public StatefulService<WiFiSettings> {
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<WiFiSettings> _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<WiFiSettings> endpoint;
};
@@ -1,60 +1,17 @@
#ifndef WiFiSettingsService_h
#define WiFiSettingsService_h
#pragma once
/**
* 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 <EventEndpoint.h>
#include <EventSocket.h>
#include <FSPersistence.h>
#include <HttpEndpoint.h>
#include <JsonUtils.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#include <SettingValue.h>
#include <StatefulService.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <vector>
#include <Timing.h>
#include <ESPFS.h>
#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 <IPAddress.h>
#include <ArduinoJson.h>
#include <JsonUtils.h>
#include <IPUtils.h>
#include <SettingValue.h>
#include <state_result.h>
#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<wifi_settings_t> wifiSettings;
@@ -185,35 +141,4 @@ class WiFiSettings {
return StateUpdateResult::CHANGED;
};
};
class WiFiSettingsService : public StatefulService<WiFiSettings> {
public:
WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, EventSocket *socket);
void initWiFi();
void begin();
void loop();
String getHostname();
private:
PsychicHttpServer *_server;
SecurityManager *_securityManager;
HttpEndpoint<WiFiSettings> _httpEndpoint;
EventEndpoint<WiFiSettings> _eventEndpoint;
FSPersistence<WiFiSettings> _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
};