From 751c540cb31f92f0a10675386932ac8c800a63c2 Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 1 May 2026 08:07:05 +0200 Subject: [PATCH] refactor network code --- Makefile | 2 +- interface/src/app/settings/APSettings.tsx | 4 - interface/src/i18n/cz/index.ts | 3 +- interface/src/i18n/de/index.ts | 3 +- interface/src/i18n/en/index.ts | 3 +- interface/src/i18n/fr/index.ts | 3 +- interface/src/i18n/it/index.ts | 3 +- interface/src/i18n/nl/index.ts | 3 +- interface/src/i18n/no/index.ts | 3 +- interface/src/i18n/pl/index.ts | 1 - interface/src/i18n/sk/index.ts | 3 +- interface/src/i18n/sv/index.ts | 3 +- interface/src/i18n/tr/index.ts | 3 +- interface/src/types/ap.ts | 1 - lib/uuid-syslog/src/syslog.cpp | 2 +- lib_standalone/ESP32React.h | 37 +- src/ESP32React/APSettingsService.cpp | 105 +--- src/ESP32React/APSettingsService.h | 24 +- src/ESP32React/APStatus.cpp | 8 +- src/ESP32React/APStatus.h | 5 - src/ESP32React/ESP32React.cpp | 2 - src/ESP32React/ESP32React.h | 13 - src/ESP32React/MqttSettingsService.cpp | 4 +- src/ESP32React/NTPSettingsService.cpp | 4 +- src/ESP32React/NetworkSettingsService.cpp | 474 +-------------- src/ESP32React/NetworkSettingsService.h | 53 -- src/ESP32React/NetworkStatus.cpp | 9 +- src/core/console.cpp | 15 +- src/core/emsesp.cpp | 11 +- src/core/emsesp.h | 2 + src/core/locale_common.h | 1 + src/core/mqtt.cpp | 4 +- src/core/network.cpp | 673 ++++++++++++++++++++++ src/core/network.h | 215 +++++++ src/core/system.cpp | 98 +--- src/core/system.h | 48 +- src/emsesp_version.h | 2 +- src/web/WebStatusService.cpp | 4 +- 38 files changed, 999 insertions(+), 852 deletions(-) create mode 100644 src/core/network.cpp create mode 100644 src/core/network.h diff --git a/Makefile b/Makefile index 28c81c2f8..ae75f3d73 100644 --- a/Makefile +++ b/Makefile @@ -113,7 +113,7 @@ CXX := /usr/bin/g++ CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE) CPPFLAGS += -ggdb -g3 -MMD CPPFLAGS += -flto=auto -CPPFLAGS += -Wall -Wextra -Werror -Wswitch-enum +CPPFLAGS += -Wall -Wextra -Werror -Wno-switch-enum CPPFLAGS += -Wno-unused-parameter -Wno-missing-braces -Wno-vla-cxx-extension CPPFLAGS += -ffunction-sections -fdata-sections -fno-exceptions -fno-rtti -fno-threadsafe-statics CPPFLAGS += -Os -DNDEBUG diff --git a/interface/src/app/settings/APSettings.tsx b/interface/src/app/settings/APSettings.tsx index 7d8c8875c..c4a20d42c 100644 --- a/interface/src/app/settings/APSettings.tsx +++ b/interface/src/app/settings/APSettings.tsx @@ -24,7 +24,6 @@ import { numberValue, updateValueDirty, useRest } from 'utils'; import { ValidationError, createAPSettingsValidator, validate } from 'validators'; export const isAPEnabled = ({ provision_mode }: APSettingsType) => - provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED; // Efficient range function without recursion @@ -108,9 +107,6 @@ const APSettings = () => { onChange={updateFormValue} margin="normal" > - - {LL.AP_PROVIDE_TEXT_1()} - {LL.AP_PROVIDE_TEXT_2()} diff --git a/interface/src/i18n/cz/index.ts b/interface/src/i18n/cz/index.ts index fbce09041..ef7726653 100644 --- a/interface/src/i18n/cz/index.ts +++ b/interface/src/i18n/cz/index.ts @@ -247,8 +247,7 @@ const cz: Translation = { TIME_ZONE: 'Časová zóna', ACCESS_POINT: 'Přístupový bod', AP_PROVIDE: 'Povolit přístupový bod', - AP_PROVIDE_TEXT_1: 'Vždy', - AP_PROVIDE_TEXT_2: 'Když je WiFi odpojena', + AP_PROVIDE_TEXT_2: 'Když je síťové připojení stratené', AP_PROVIDE_TEXT_3: 'Nikdy', AP_PREFERRED_CHANNEL: 'Preferovaný kanál', AP_HIDE_SSID: 'Skrýt SSID', diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index 3a766efb4..ee0988be4 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -247,8 +247,7 @@ const de: Translation = { TIME_ZONE: 'Zeitzone', ACCESS_POINT: 'Zugangspunkt', AP_PROVIDE: 'Aktiviere Zugangspunkt', - AP_PROVIDE_TEXT_1: 'Immer', - AP_PROVIDE_TEXT_2: 'Wenn WiFi nicht verbunden', + AP_PROVIDE_TEXT_2: 'Wenn Netzwerkverbindung verloren geht', AP_PROVIDE_TEXT_3: 'Niemals', AP_PREFERRED_CHANNEL: 'Bevorzugter Kanal', AP_HIDE_SSID: 'Verstecke SSID', diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 610ed0f00..268b0ff64 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -247,8 +247,7 @@ const en: Translation = { TIME_ZONE: 'Time Zone', ACCESS_POINT: 'Access Point', AP_PROVIDE: 'Enable Access Point', - AP_PROVIDE_TEXT_1: 'Always', - AP_PROVIDE_TEXT_2: 'When WiFi is disconnected', + AP_PROVIDE_TEXT_2: 'When network connection is lost', AP_PROVIDE_TEXT_3: 'Never', AP_PREFERRED_CHANNEL: 'Preferred Channel', AP_HIDE_SSID: 'Hide SSID', diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index f1945d2ab..380d1a705 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -247,8 +247,7 @@ const fr: Translation = { TIME_ZONE: 'Fuseau horaire', ACCESS_POINT: "Point d'accès", AP_PROVIDE: "Activer le Point d'Accès", - AP_PROVIDE_TEXT_1: 'toujours', - AP_PROVIDE_TEXT_2: 'quand le WiFi est déconnecté', + AP_PROVIDE_TEXT_2: 'quand la connexion réseau est perdue', AP_PROVIDE_TEXT_3: 'jamais', AP_PREFERRED_CHANNEL: 'Canal préféré', AP_HIDE_SSID: 'Cacher le SSID', diff --git a/interface/src/i18n/it/index.ts b/interface/src/i18n/it/index.ts index df8b4de61..93b666c37 100644 --- a/interface/src/i18n/it/index.ts +++ b/interface/src/i18n/it/index.ts @@ -247,8 +247,7 @@ const it: Translation = { TIME_ZONE: 'Fuso orario', ACCESS_POINT: 'Access Point', AP_PROVIDE: 'Abilita Access Point', - AP_PROVIDE_TEXT_1: 'sempre', - AP_PROVIDE_TEXT_2: 'quando WiFi é disconnessa', + AP_PROVIDE_TEXT_2: 'quando la connessione di rete è persa', AP_PROVIDE_TEXT_3: 'mai', AP_PREFERRED_CHANNEL: 'Canale preferito', AP_HIDE_SSID: 'Nascondi SSID', diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index 888c033d8..d87348d58 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -247,8 +247,7 @@ const nl: Translation = { TIME_ZONE: 'Tijdzone', ACCESS_POINT: 'Access Point', AP_PROVIDE: 'Activeer Access Point', - AP_PROVIDE_TEXT_1: 'altijd', - AP_PROVIDE_TEXT_2: 'als WiFi niet is verbonden', + AP_PROVIDE_TEXT_2: 'als netwerk verbinding verloren gaat', AP_PROVIDE_TEXT_3: 'nooit', AP_PREFERRED_CHANNEL: 'Voorkeurskanaal', AP_HIDE_SSID: 'SSID verbergen', diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index 693ef29e8..841d32689 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -247,8 +247,7 @@ const no: Translation = { TIME_ZONE: 'Tidssone', ACCESS_POINT: 'Aksesspunkt', AP_PROVIDE: 'Aktiver Aksesspunkt', - AP_PROVIDE_TEXT_1: 'alltid', - AP_PROVIDE_TEXT_2: 'når WiFi er utilgjengelig', + AP_PROVIDE_TEXT_2: 'når nettverksforbindelsen er utilgjengelig', AP_PROVIDE_TEXT_3: 'aldri', AP_PREFERRED_CHANNEL: 'Foretrukket kanal', AP_HIDE_SSID: 'Skjul SSID', diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index c6dd004de..24ac4d078 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -247,7 +247,6 @@ const pl: BaseTranslation = { TIME_ZONE: 'Strefa czasowa', ACCESS_POINT: '{{Punkt|punktu|}} {{dostępowy|dostępowego|}}', AP_PROVIDE: 'Punkt dostępowy', - AP_PROVIDE_TEXT_1: 'zawsze aktywny', AP_PROVIDE_TEXT_2: 'aktywny jeśli brak połączenia z siecią', AP_PROVIDE_TEXT_3: 'nieaktywny', AP_PREFERRED_CHANNEL: 'Preferowany kanał', diff --git a/interface/src/i18n/sk/index.ts b/interface/src/i18n/sk/index.ts index 5e993e998..286eca096 100644 --- a/interface/src/i18n/sk/index.ts +++ b/interface/src/i18n/sk/index.ts @@ -247,8 +247,7 @@ const sk: Translation = { TIME_ZONE: 'Časová zóna', ACCESS_POINT: 'Prístupový bod', AP_PROVIDE: 'Povoliť prístupový bod', - AP_PROVIDE_TEXT_1: 'vždy', - AP_PROVIDE_TEXT_2: 'keď je WiFi odpojená', + AP_PROVIDE_TEXT_2: 'keď je sieťové pripojenie stratené', AP_PROVIDE_TEXT_3: 'nikdy', AP_PREFERRED_CHANNEL: 'Preferovaný kanál', AP_HIDE_SSID: 'Skryť SSID', diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 032eeb026..43bc9e9a3 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -247,8 +247,7 @@ const sv: Translation = { TIME_ZONE: 'Tidszon', ACCESS_POINT: 'Accesspunkt', AP_PROVIDE: 'Aktivera accesspunkt', - AP_PROVIDE_TEXT_1: 'alltid', - AP_PROVIDE_TEXT_2: 'när WiFi är nedkopplat', + AP_PROVIDE_TEXT_2: 'när nätverksanslutningen är bortkopplad', AP_PROVIDE_TEXT_3: 'aldrig', AP_PREFERRED_CHANNEL: 'Kanal', AP_HIDE_SSID: 'Göm SSID', diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index b6caa3238..93867c4fb 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -247,8 +247,7 @@ const tr: Translation = { TIME_ZONE: 'Saat dilimi', ACCESS_POINT: 'Erişim Noktası', AP_PROVIDE: 'Erişim noktasını çalıştır', - AP_PROVIDE_TEXT_1: 'her zaman', - AP_PROVIDE_TEXT_2: 'Kablosuz bağlantı kesildiğinde', + AP_PROVIDE_TEXT_2: 'Ağ bağlantısı kesildiğinde', AP_PROVIDE_TEXT_3: 'asla', AP_PREFERRED_CHANNEL: 'Tercih edilen kanal', AP_HIDE_SSID: 'SSID yi gizle', diff --git a/interface/src/types/ap.ts b/interface/src/types/ap.ts index de5fe64ad..5094b0dbb 100644 --- a/interface/src/types/ap.ts +++ b/interface/src/types/ap.ts @@ -1,5 +1,4 @@ export enum APProvisionMode { - AP_MODE_ALWAYS = 0, AP_MODE_DISCONNECTED = 1, AP_NEVER = 2 } diff --git a/lib/uuid-syslog/src/syslog.cpp b/lib/uuid-syslog/src/syslog.cpp index 4e0eac774..0ebdaebeb 100644 --- a/lib/uuid-syslog/src/syslog.cpp +++ b/lib/uuid-syslog/src/syslog.cpp @@ -231,7 +231,7 @@ SyslogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ : id_(id) , content_(std::move(content)) { // Added for EMS-ESP - if (time_good_ || emsesp::EMSESP::system_.network_connected()) { + if (time_good_ || emsesp::EMSESP::network_.network_connected()) { #if UUID_SYSLOG_HAVE_GETTIMEOFDAY if (gettimeofday(&time_, nullptr) != 0) { time_.tv_sec = (time_t)-1; diff --git a/lib_standalone/ESP32React.h b/lib_standalone/ESP32React.h index cb5644db6..f2026c479 100644 --- a/lib_standalone/ESP32React.h +++ b/lib_standalone/ESP32React.h @@ -12,6 +12,7 @@ #include "SecuritySettingsService.h" #include "StatefulService.h" #include "Network.h" +// #include "IPAddress.h" #include @@ -21,7 +22,6 @@ #define NTP_SETTINGS_FILE "/config/ntpSettings.json" #define EMSESP_SETTINGS_FILE "/config/emsespSettings.json" -#define AP_MODE_ALWAYS 0 class DummySettings { public: // SYSTEM @@ -49,6 +49,22 @@ class DummySettings { uint16_t keepAlive = 60; bool cleanSession = false; uint8_t entity_format = 1; + String CORSOrigin = "*"; + uint8_t tx_power = 0; + String bssid = ""; + String localIP = ""; + String gatewayIP = ""; + String subnetMask = ""; + bool staticIPConfig = false; + String dnsIP1 = ""; + String dnsIP2 = ""; + bool enableMDNS = true; + bool enableCORS = false; + uint8_t channel = 1; + bool ssid_hidden = false; + uint8_t max_clients = 4; + bool ssidHidden = false; + uint8_t maxClients = 4; uint16_t publish_time_boiler = 10; uint16_t publish_time_thermostat = 10; @@ -59,21 +75,10 @@ class DummySettings { uint16_t publish_time_heartbeat = 60; uint32_t publish_time_water = 0; - String hostname = "ems-esp"; - String jwtSecret = "ems-esp"; - String ssid = "ems-esp"; - String password = "ems-esp"; - String bssid = ""; - String localIP = ""; - String gatewayIP = ""; - String subnetMask = ""; - bool staticIPConfig = false; - String dnsIP1 = ""; - String dnsIP2 = ""; - bool enableMDNS = true; - bool enableCORS = false; - String CORSOrigin = "*"; - uint8_t tx_power = 0; + String hostname = "ems-esp"; + String jwtSecret = "ems-esp"; + String ssid = "ems-esp"; + String password = "ems-esp"; // AP uint8_t provisionMode = 0; diff --git a/src/ESP32React/APSettingsService.cpp b/src/ESP32React/APSettingsService.cpp index a68037738..05d02a110 100644 --- a/src/ESP32React/APSettingsService.cpp +++ b/src/ESP32React/APSettingsService.cpp @@ -4,110 +4,17 @@ APSettingsService::APSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) : _httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager) - , _fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE) - , _dnsServer(nullptr) - , _lastManaged(0) - , _reconfigureAp(false) - , _connected(0) { - addUpdateHandler([this] { reconfigureAP(); }, false); + , _fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE) { } void APSettingsService::begin() { _fsPersistence.readFromFS(); - // disabled for delayed start, first try station mode - // reconfigureAP(); -} - -void APSettingsService::reconfigureAP() { - _lastManaged = uuid::get_uptime() - MANAGE_NETWORK_DELAY; - _reconfigureAp = true; -} - -void APSettingsService::loop() { - const uint8_t was_connected = _connected; - if (WiFi.isConnected()) { - _connected |= 1U; - } else { - _connected &= ~1U; - } - if (ETH.connected()) { - _connected |= 2U; - } else { - _connected &= ~2U; - } - // wait 10 sec before starting AP - if (was_connected && !_connected) { - _lastManaged = uuid::get_uptime(); - } - const unsigned long currentMillis = uuid::get_uptime(); - if ((currentMillis - _lastManaged) >= MANAGE_NETWORK_DELAY) { - _lastManaged = currentMillis; - manageAP(); - } - - if (_dnsServer) { - handleDNS(); - } -} - -void APSettingsService::manageAP() { - const WiFiMode_t currentWiFiMode = WiFi.getMode(); - if (_state.provisionMode == AP_MODE_ALWAYS || (_state.provisionMode == AP_MODE_DISCONNECTED && !_connected)) { - if (_reconfigureAp || currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) { - startAP(); - } - } else if ((currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA) && _connected && (_reconfigureAp || !WiFi.softAPgetStationNum())) { - stopAP(); - } - _reconfigureAp = false; -} - -void APSettingsService::startAP() { - WiFi.softAPenableIPv6(); // force IPV6, same as for WiFi - fixes https://github.com/emsesp/EMS-ESP32/issues/1922 - WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask); - esp_wifi_set_bandwidth(static_cast(ESP_IF_WIFI_AP), WIFI_BW_HT20); - WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients); -#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 - if (!_dnsServer) { - const IPAddress apIp = WiFi.softAPIP(); - char ipStr[16]; - snprintf(ipStr, sizeof(ipStr), "%u.%u.%u.%u", apIp[0], apIp[1], apIp[2], apIp[3]); - emsesp::EMSESP::logger().info("Starting Access Point with captive portal on %s", ipStr); - _dnsServer = new DNSServer; - _dnsServer->start(DNS_PORT, "*", apIp); - } -} - -void APSettingsService::stopAP() { - if (_dnsServer) { - emsesp::EMSESP::logger().info("Stopping Access Point"); - _dnsServer->stop(); - delete _dnsServer; - _dnsServer = nullptr; - } - WiFi.softAPdisconnect(true); -} - -void APSettingsService::handleDNS() { - if (_dnsServer) { - _dnsServer->processNextRequest(); - } } APNetworkStatus APSettingsService::getAPNetworkStatus() { - const WiFiMode_t currentWiFiMode = WiFi.getMode(); - const bool apActive = (currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA); - - if (apActive && _state.provisionMode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) { - return APNetworkStatus::LINGERING; - } - - return apActive ? APNetworkStatus::ACTIVE : APNetworkStatus::INACTIVE; + return emsesp::EMSESP::network_.ap_connected() ? APNetworkStatus::ACTIVE : APNetworkStatus::INACTIVE; } - void APSettings::read(const APSettings & settings, JsonObject root) { root["provision_mode"] = settings.provisionMode; root["ssid"] = settings.ssid; @@ -125,12 +32,11 @@ StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) { newSettings.provisionMode = static_cast(root["provision_mode"] | FACTORY_AP_PROVISION_MODE); switch (settings.provisionMode) { - case AP_MODE_ALWAYS: case AP_MODE_DISCONNECTED: case AP_MODE_NEVER: break; default: - newSettings.provisionMode = AP_MODE_ALWAYS; + newSettings.provisionMode = AP_MODE_DISCONNECTED; } newSettings.ssid = root["ssid"] | FACTORY_AP_SSID; @@ -148,5 +54,10 @@ StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) { } settings = newSettings; + + // if the AP mode has changed, force a disconnect and reconnect + if (settings.provisionMode != newSettings.provisionMode) { + emsesp::EMSESP::network_.reconnect(); + } return StateUpdateResult::CHANGED; } diff --git a/src/ESP32React/APSettingsService.h b/src/ESP32React/APSettingsService.h index 6051c5cc5..6c280875b 100644 --- a/src/ESP32React/APSettingsService.h +++ b/src/ESP32React/APSettingsService.h @@ -5,9 +5,6 @@ #include "FSPersistence.h" #include "JsonUtils.h" -#include -#include - #ifndef FACTORY_AP_PROVISION_MODE #define FACTORY_AP_PROVISION_MODE AP_MODE_DISCONNECTED #endif @@ -47,14 +44,10 @@ #define AP_SETTINGS_FILE "/config/apSettings.json" #define AP_SETTINGS_SERVICE_PATH "/rest/apSettings" -#define AP_MODE_ALWAYS 0 #define AP_MODE_DISCONNECTED 1 #define AP_MODE_NEVER 2 -#define MANAGE_NETWORK_DELAY 10000 -#define DNS_PORT 53 - -enum APNetworkStatus { ACTIVE = 0, INACTIVE, LINGERING }; +enum APNetworkStatus { ACTIVE = 0, INACTIVE }; class APSettings { public: @@ -84,26 +77,11 @@ class APSettingsService : public StatefulService { APSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); void begin(); - void loop(); APNetworkStatus getAPNetworkStatus(); private: HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - - // for the captive portal - DNSServer * _dnsServer; - - // for the management delay loop - volatile unsigned long _lastManaged; - volatile bool _reconfigureAp; - volatile uint8_t _connected; - - void reconfigureAP(); - void manageAP(); - void startAP(); - void stopAP(); - void handleDNS(); }; #endif diff --git a/src/ESP32React/APStatus.cpp b/src/ESP32React/APStatus.cpp index c0baa482d..09d8c39b0 100644 --- a/src/ESP32React/APStatus.cpp +++ b/src/ESP32React/APStatus.cpp @@ -1,5 +1,7 @@ #include "APStatus.h" +#include + APStatus::APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService) : _apSettingsService(apSettingsService) { securityManager->addEndpoint(server, AP_STATUS_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) { @@ -12,9 +14,9 @@ void APStatus::apStatus(AsyncWebServerRequest * request) { JsonObject root = response->getRoot(); root["status"] = _apSettingsService->getAPNetworkStatus(); - root["ip_address"] = WiFi.softAPIP().toString(); - root["mac_address"] = WiFi.softAPmacAddress(); - root["station_num"] = WiFi.softAPgetStationNum(); + root["ip_address"] = emsesp::EMSESP::network_.getLocalIP(); + root["mac_address"] = emsesp::EMSESP::network_.getMacAddress(); + root["station_num"] = emsesp::EMSESP::network_.getStationNum(); response->setLength(); request->send(response); diff --git a/src/ESP32React/APStatus.h b/src/ESP32React/APStatus.h index 23ee79305..1105a742a 100644 --- a/src/ESP32React/APStatus.h +++ b/src/ESP32React/APStatus.h @@ -1,12 +1,7 @@ #ifndef APStatus_h #define APStatus_h -#include -#include - #include -#include -#include #include "SecurityManager.h" #include "APSettingsService.h" diff --git a/src/ESP32React/ESP32React.cpp b/src/ESP32React/ESP32React.cpp index 4cba7ee79..79a3fb843 100644 --- a/src/ESP32React/ESP32React.cpp +++ b/src/ESP32React/ESP32React.cpp @@ -101,8 +101,6 @@ void ESP32React::begin() { } void ESP32React::loop() { - _networkSettingsService.loop(); - _apSettingsService.loop(); _mqttSettingsService.loop(); _ntpSettingsService.loop(); } diff --git a/src/ESP32React/ESP32React.h b/src/ESP32React/ESP32React.h index cbcea487e..17c849595 100644 --- a/src/ESP32React/ESP32React.h +++ b/src/ESP32React/ESP32React.h @@ -54,19 +54,6 @@ class ESP32React { return _mqttSettingsService.getMqttClient(); } - // - // special functions needed outside scope - // - - // true if AP is active - bool apStatus() { - return _apSettingsService.getAPNetworkStatus() == APNetworkStatus::ACTIVE; - } - - uint16_t getWifiReconnects() { - return _networkSettingsService.getWifiReconnects(); - } - private: AsyncWebServer * _server; SecuritySettingsService _securitySettingsService; diff --git a/src/ESP32React/MqttSettingsService.cpp b/src/ESP32React/MqttSettingsService.cpp index 7049c5713..3f53e8027 100644 --- a/src/ESP32React/MqttSettingsService.cpp +++ b/src/ESP32React/MqttSettingsService.cpp @@ -79,7 +79,7 @@ void MqttSettingsService::startClient() { } void MqttSettingsService::loop() { - if (_state.enabled && _mqttClient && _mqttClient->connected() && !emsesp::EMSESP::system_.network_connected()) { + if (_state.enabled && _mqttClient && _mqttClient->connected() && !emsesp::EMSESP::network_.network_connected()) { // emsesp::EMSESP::logger().info("Network connection dropped, stopping MQTT client"); _mqttClient->disconnect(true); } @@ -154,7 +154,7 @@ bool MqttSettingsService::configureMqtt() { } // only connect if WiFi is connected and MQTT is enabled - if (_state.enabled && emsesp::EMSESP::system_.network_connected() && !_state.host.isEmpty()) { + if (_state.enabled && emsesp::EMSESP::network_.network_connected() && !_state.host.isEmpty()) { // create the Last Will Testament topic (LWT) with the base prefixed. It has to be static because the client destroys the reference static char will_topic[FACTORY_MQTT_MAX_TOPIC_LENGTH]; if (_state.base.isEmpty()) { diff --git a/src/ESP32React/NTPSettingsService.cpp b/src/ESP32React/NTPSettingsService.cpp index 4faebb1b9..6040c21c8 100644 --- a/src/ESP32React/NTPSettingsService.cpp +++ b/src/ESP32React/NTPSettingsService.cpp @@ -19,8 +19,8 @@ void NTPSettingsService::begin() { } void NTPSettingsService::loop() { - if (_connected != emsesp::EMSESP::system_.network_connected()) { - _connected = emsesp::EMSESP::system_.network_connected(); + if (_connected != emsesp::EMSESP::network_.network_connected()) { + _connected = emsesp::EMSESP::network_.network_connected(); configureNTP(); } } diff --git a/src/ESP32React/NetworkSettingsService.cpp b/src/ESP32React/NetworkSettingsService.cpp index 29b12b19a..af80fe049 100644 --- a/src/ESP32React/NetworkSettingsService.cpp +++ b/src/ESP32React/NetworkSettingsService.cpp @@ -4,484 +4,13 @@ NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) : _httpEndpoint(NetworkSettings::read, NetworkSettings::update, this, server, NETWORK_SETTINGS_SERVICE_PATH, securityManager) - , _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE) - , _lastConnectionAttempt(0) - , _stopping(false) { - addUpdateHandler([this] { reconfigureWiFiConnection(); }, false); - // Eth is also bound to the WifiGeneric event handler - // Network.onEvent([this](arduino_event_id_t event, arduino_event_info_t info) { WiFiEvent(event, info); }); -} - -static bool formatBssid(const String & bssid, uint8_t (&mac)[6]) { - uint tmp[6]; - if (bssid.isEmpty() || sscanf(bssid.c_str(), "%X:%X:%X:%X:%X:%X", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) != 6) { - return false; - } - for (uint8_t i = 0; i < 6; i++) { - mac[i] = static_cast(tmp[i]); - } - return true; + , _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE) { } void NetworkSettingsService::begin() { - // We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default. - // If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future. - if (WiFi.getMode() != WIFI_OFF) { - WiFi.mode(WIFI_OFF); - } - - // Disable WiFi config persistance and auto reconnect - WiFi.persistent(false); - WiFi.setAutoReconnect(false); - - WiFi.mode(WIFI_STA); - WiFi.mode(WIFI_OFF); - - // scan settings give connect issues since arduino 2.0.14 and arduino 3.x.x with some wifi systems - // WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN - // WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set - _fsPersistence.readFromFS(); } -void NetworkSettingsService::reconfigureWiFiConnection() { - // do not disconnect for switching to eth, restart is needed - if (WiFi.isConnected() && _state.ssid.length() == 0) { - return; - } - - // disconnect and de-configure wifi - if (WiFi.disconnect(true)) { - _stopping = true; - } -} - -void NetworkSettingsService::loop() { - unsigned long currentMillis = millis(); - if (!_lastConnectionAttempt || static_cast(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) { - _lastConnectionAttempt = currentMillis; - manageSTA(); - } - static uint8_t connect = 0; - enum uint8_t { - CONNECT_IDLE = 0, - CONNECT_WAIT_ETH, - CONNECT_WAIT_IP4, - CONNECT_WAIT_ETH_IP4, - CONNECT_WAIT_IP6, - CONNECT_WAIT_ETH_IP6, - CONNECT_ETH_ACTIVE, - CONNECT_WIFI_ACTIVE - }; - switch (connect) { - default: - connect = CONNECT_IDLE; - break; - case CONNECT_IDLE: - if (ETH.started() && _state.ssid.length() == 0) { - emsesp::EMSESP::logger().info("ETH started"); - ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str()); - ETH.enableIPv6(true); - if (_state.staticIPConfig) { - ETH.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); - } - connect = CONNECT_WAIT_ETH; - } - if (WiFi.isConnected()) { - emsesp::EMSESP::logger().info("Wifi connected"); - if (_state.tx_power == 0) { - setWiFiPowerOnRSSI(); - } - mDNS_start(); - emsesp::EMSESP::system_.has_ipv6(true); - connect = CONNECT_WAIT_IP4; - } - break; - case CONNECT_WAIT_ETH: - if (ETH.connected()) { - emsesp::EMSESP::logger().info("ETH connected"); - emsesp::EMSESP::system_.ethernet_connected(true); - mDNS_start(); - emsesp::EMSESP::system_.has_ipv6(true); - connect = CONNECT_WAIT_ETH_IP4; - } - break; - case CONNECT_WAIT_ETH_IP4: - if (ETH.hasIP()) { - emsesp::EMSESP::logger().info("ETH IPv4: %s", ETH.localIP().toString().c_str()); - connect = CONNECT_WAIT_ETH_IP6; - } - if (!ETH.connected()) { - connect = CONNECT_ETH_ACTIVE; - } - break; - case CONNECT_WAIT_ETH_IP6: - if (ETH.hasLinkLocalIPv6() && ETH.hasGlobalIPv6()) { - emsesp::EMSESP::system_.has_ipv6(true); - connect = CONNECT_ETH_ACTIVE; - } - if (!ETH.connected()) { - connect = CONNECT_ETH_ACTIVE; - } - break; - case CONNECT_ETH_ACTIVE: - if (!ETH.connected()) { - emsesp::EMSESP::logger().info("ETH disconnected"); - emsesp::EMSESP::system_.ethernet_connected(false); - emsesp::EMSESP::system_.has_ipv6(false); - connect = CONNECT_IDLE; - } - break; - case CONNECT_WAIT_IP4: - if (!WiFi.localIP().toString().isEmpty()) { - emsesp::EMSESP::logger().info("Wifi IPv4: %s", WiFi.localIP().toString().c_str()); - connect = CONNECT_WAIT_IP6; - } - if (!WiFi.isConnected()) { - connect = CONNECT_ETH_ACTIVE; - } - break; - case CONNECT_WAIT_IP6: - if (WiFi.linkLocalIPv6().toString() != "::" && WiFi.globalIPv6().toString() != "::") { - emsesp::EMSESP::logger().info("Wifi IPv6: %s, %s", WiFi.linkLocalIPv6().toString().c_str(), WiFi.globalIPv6().toString().c_str()); - emsesp::EMSESP::system_.has_ipv6(true); - connect = CONNECT_WIFI_ACTIVE; - } - if (!WiFi.isConnected()) { - connect = CONNECT_WIFI_ACTIVE; - } - break; - case CONNECT_WIFI_ACTIVE: - if (!WiFi.isConnected()) { - emsesp::EMSESP::logger().info("WiFi disconnected"); - if (_stopping) { - _lastConnectionAttempt = 0; - _stopping = false; - } - emsesp::EMSESP::system_.has_ipv6(false); - connect = CONNECT_IDLE; - } - break; - } -} - -void NetworkSettingsService::manageSTA() { - // Abort if already connected, or if we have no SSID - if (WiFi.isConnected() || _state.ssid.length() == 0) { - return; - } - - // Connect or reconnect as required - if ((WiFi.getMode() & WIFI_STA) == 0) { - WiFi.setHostname(_state.hostname.c_str()); // updates shared default_hostname buffer - WiFi.enableSTA(true); // creates the STA netif - WiFi.STA.setHostname(_state.hostname.c_str()); // pushes to esp_netif_set_hostname - WiFi.enableIPv6(true); - if (_state.staticIPConfig) { - WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); // configure for static IP - } - - // www.esp32.com/viewtopic.php?t=12055 - if (_state.bandwidth20) { - esp_wifi_set_bandwidth(static_cast(ESP_IF_WIFI_STA), WIFI_BW_HT20); - } else { - esp_wifi_set_bandwidth(static_cast(ESP_IF_WIFI_STA), WIFI_BW_HT40); - } - if (_state.nosleep) { - WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE - } - - // attempt to connect to the network - uint8_t bssid[6]; - if (formatBssid(_state.bssid, bssid)) { - WiFi.begin(_state.ssid.c_str(), _state.password.c_str(), 0, bssid); - } else { - WiFi.begin(_state.ssid.c_str(), _state.password.c_str()); - } - -#ifdef BOARD_C3_MINI_V1 - // always hardcode Tx power for Wemos CS Mini v1 - // v1 needs this value, see https://github.com/emsesp/EMS-ESP32/pull/620#discussion_r993173979 - // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi - WiFi.setTxPower(WIFI_POWER_8_5dBm); -#else - if (_state.tx_power != 0) { - // if not set to Auto (0) set the Tx power now - if (!WiFi.setTxPower(static_cast(_state.tx_power))) { - emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power"); - } - } -#endif - } else { // not connected but STA-mode active => disconnect - reconfigureWiFiConnection(); - } -} - -// set the TxPower based on the RSSI (signal strength), picking the lowest value -// code is based of RSSI (signal strength) and copied from Tasmota's WiFiSetTXpowerBasedOnRssi() which is copied ESPEasy's ESPEasyWifi.SetWiFiTXpower() function -void NetworkSettingsService::setWiFiPowerOnRSSI() { - // Range ESP32 : 2dBm - 20dBm - // 802.11b - wifi1 - // 802.11a - wifi2 - // 802.11g - wifi3 - // 802.11n - wifi4 - // 802.11ac - wifi5 - // 802.11ax - wifi6 - - int max_tx_pwr = MAX_TX_PWR_DBM_n; // assume wifi4 - int threshold = WIFI_SENSITIVITY_n + 120; // Margin in dBm * 10 on top of threshold - - // Assume AP sends with max set by ETSI standard. - // 2.4 GHz: 100 mWatt (20 dBm) - // US and some other countries allow 1000 mW (30 dBm) - int rssi = WiFi.RSSI() * 10; - int newrssi = rssi - 200; // We cannot send with over 20 dBm, thus it makes no sense to force higher TX power all the time. - - int min_tx_pwr = 0; - if (newrssi < threshold) { - min_tx_pwr = threshold - newrssi; - } - if (min_tx_pwr > max_tx_pwr) { - min_tx_pwr = max_tx_pwr; - } - - // from WiFIGeneric.h use: - // WIFI_POWER_19_5dBm = 78,// 19.5dBm - // WIFI_POWER_19dBm = 76,// 19dBm - // WIFI_POWER_18_5dBm = 74,// 18.5dBm - // WIFI_POWER_17dBm = 68,// 17dBm - // WIFI_POWER_15dBm = 60,// 15dBm - // WIFI_POWER_13dBm = 52,// 13dBm - // WIFI_POWER_11dBm = 44,// 11dBm - // WIFI_POWER_8_5dBm = 34,// 8.5dBm - // WIFI_POWER_7dBm = 28,// 7dBm - // WIFI_POWER_5dBm = 20,// 5dBm - // WIFI_POWER_2dBm = 8,// 2dBm - // WIFI_POWER_MINUS_1dBm = -4// -1dBm - wifi_power_t p = WIFI_POWER_2dBm; - if (min_tx_pwr > 185) - p = WIFI_POWER_19_5dBm; - else if (min_tx_pwr > 170) - p = WIFI_POWER_18_5dBm; - else if (min_tx_pwr > 150) - p = WIFI_POWER_17dBm; - else if (min_tx_pwr > 130) - p = WIFI_POWER_15dBm; - else if (min_tx_pwr > 110) - p = WIFI_POWER_13dBm; - else if (min_tx_pwr > 85) - p = WIFI_POWER_11dBm; - else if (min_tx_pwr > 70) - p = WIFI_POWER_8_5dBm; - else if (min_tx_pwr > 50) - p = WIFI_POWER_7dBm; - else if (min_tx_pwr > 20) - p = WIFI_POWER_5dBm; - -#if defined(EMSESP_DEBUG) - // emsesp::EMSESP::logger().debug("Recommended WiFi Tx Power (set_power %d, new power %d, rssi %d, threshold %d)", min_tx_pwr / 10, p, rssi, threshold); -#endif - - if (!WiFi.setTxPower(p)) { - emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power"); - } -} - -// start the multicast UDP service so EMS-ESP is discoverable via .local -void NetworkSettingsService::mDNS_start() const { -#ifndef EMSESP_STANDALONE - MDNS.end(); - - if (_state.enableMDNS) { - if (!MDNS.begin(emsesp::EMSESP::system_.hostname().c_str())) { - emsesp::EMSESP::logger().warning("Failed to start mDNS Responder service"); - return; - } - - std::string address_s = emsesp::EMSESP::system_.hostname() + ".local"; - - MDNS.addService("http", "tcp", 80); // add our web server and rest API - MDNS.addService("telnet", "tcp", 23); // add our telnet console - - // MDNS.addServiceTxt("http", "tcp", "version", EMSESP_APP_VERSION); - MDNS.addServiceTxt("http", "tcp", "address", address_s.c_str()); - - emsesp::EMSESP::logger().info("Starting mDNS Responder service"); - } -#endif -} - -const char * NetworkSettingsService::disconnectReason(uint8_t code) { -#ifndef EMSESP_STANDALONE - switch (code) { - case WIFI_REASON_UNSPECIFIED: // = 1, - return "unspecified"; - case WIFI_REASON_AUTH_EXPIRE: // = 2, - return "auth expire"; - case WIFI_REASON_AUTH_LEAVE: // = 3, - return "auth leave"; - case WIFI_REASON_ASSOC_EXPIRE: // = 4, - return "assoc expired"; - case WIFI_REASON_ASSOC_TOOMANY: // = 5, - return "assoc too many"; - case WIFI_REASON_NOT_AUTHED: // = 6, - return "not authenticated"; - case WIFI_REASON_NOT_ASSOCED: // = 7, - return "not assoc"; - case WIFI_REASON_ASSOC_LEAVE: // = 8, - return "assoc leave"; - case WIFI_REASON_ASSOC_NOT_AUTHED: // = 9, - return "assoc not authed"; - case WIFI_REASON_DISASSOC_PWRCAP_BAD: // = 10, - return "disassoc powerCAP bad"; - case WIFI_REASON_DISASSOC_SUPCHAN_BAD: // = 11, - return "disassoc supchan bad"; - case WIFI_REASON_IE_INVALID: // = 13, - return "IE invalid"; - case WIFI_REASON_MIC_FAILURE: // = 14, - return "MIC failure"; - case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // = 15, - return "4way handshake timeout"; - case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: // = 16, - return "group key-update timeout"; - case WIFI_REASON_IE_IN_4WAY_DIFFERS: // = 17, - return "IE in 4way differs"; - case WIFI_REASON_GROUP_CIPHER_INVALID: // = 18, - return "group cipher invalid"; - case WIFI_REASON_PAIRWISE_CIPHER_INVALID: // = 19, - return "pairwise cipher invalid"; - case WIFI_REASON_AKMP_INVALID: // = 20, - return "AKMP invalid"; - case WIFI_REASON_UNSUPP_RSN_IE_VERSION: // = 21, - return "unsupported RSN_IE version"; - case WIFI_REASON_INVALID_RSN_IE_CAP: // = 22, - return "invalid RSN_IE_CAP"; - case WIFI_REASON_802_1X_AUTH_FAILED: // = 23, - return "802 X1 auth failed"; - case WIFI_REASON_CIPHER_SUITE_REJECTED: // = 24, - return "cipher suite rejected"; - case WIFI_REASON_BEACON_TIMEOUT: // = 200, - return "beacon timeout"; - case WIFI_REASON_NO_AP_FOUND: // = 201, - return "no AP found"; - case WIFI_REASON_AUTH_FAIL: // = 202, - return "auth fail"; - case WIFI_REASON_ASSOC_FAIL: // = 203, - return "assoc fail"; - case WIFI_REASON_HANDSHAKE_TIMEOUT: // = 204, - return "handshake timeout"; - case WIFI_REASON_CONNECTION_FAIL: // 205, - return "connection fail"; - case WIFI_REASON_AP_TSF_RESET: // 206, - return "AP tsf reset"; - case WIFI_REASON_ROAMING: // 207, - return "roaming"; - case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG: // 208, - return "assoc comeback time too long"; - case WIFI_REASON_SA_QUERY_TIMEOUT: // 209, - return "sa query timeout"; - default: - return "unknown"; - } -#endif - - return ""; -} - -// handles both WiFI and Ethernet -void NetworkSettingsService::WiFiEvent(arduino_event_id_t event, arduino_event_info_t info) { -#ifndef EMSESP_STANDALONE - - switch (event) { - case ARDUINO_EVENT_WIFI_STA_STOP: - if (_stopping) { - _lastConnectionAttempt = 0; - _stopping = false; - } - break; - - case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - connectcount_ = connectcount_ + 1; // count the number of WiFi reconnects - emsesp::EMSESP::logger().warning("WiFi disconnected (#%d). Reason: %s (%d)", - connectcount_, - disconnectReason(info.wifi_sta_disconnected.reason), - info.wifi_sta_disconnected.reason); // IDF 4.0 - emsesp::EMSESP::system_.has_ipv6(false); - - - break; - - case ARDUINO_EVENT_WIFI_STA_GOT_IP: - char result[10]; - emsesp::EMSESP::logger().info("WiFi connected (Local IP=%s, hostname=%s, TxPower=%s dBm)", - WiFi.localIP().toString().c_str(), - WiFi.getHostname(), - emsesp::Helpers::render_value(result, ((double)(WiFi.getTxPower()) / 4), 1)); - mDNS_start(); - break; - - case ARDUINO_EVENT_ETH_START: - // apply hostname FIRST so DHCP DISCOVER carries the correct name - ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str()); - if (_state.staticIPConfig) { - ETH.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); - } - break; - - case ARDUINO_EVENT_ETH_GOT_IP: - // prevent double calls to mDNS - if (!emsesp::EMSESP::system_.ethernet_connected()) { - emsesp::EMSESP::logger().info("Ethernet connected (Local IP=%s, speed %d Mbps)", ETH.localIP().toString().c_str(), ETH.linkSpeed()); - emsesp::EMSESP::system_.ethernet_connected(true); - mDNS_start(); - } - break; - - case ARDUINO_EVENT_ETH_DISCONNECTED: - emsesp::EMSESP::logger().warning("Ethernet disconnected. Reason: %s (%d)", - disconnectReason(info.wifi_sta_disconnected.reason), - info.wifi_sta_disconnected.reason); - emsesp::EMSESP::system_.ethernet_connected(false); - emsesp::EMSESP::system_.has_ipv6(false); - break; - - case ARDUINO_EVENT_ETH_STOP: - emsesp::EMSESP::logger().info("Ethernet stopped"); - emsesp::EMSESP::system_.ethernet_connected(false); - emsesp::EMSESP::system_.has_ipv6(false); - break; - - case ARDUINO_EVENT_WIFI_STA_CONNECTED: - // Set the TxPower after the connection is established, if we're using TxPower = 0 (Auto) - if (_state.tx_power == 0) { - setWiFiPowerOnRSSI(); - } - break; - - case ARDUINO_EVENT_ETH_CONNECTED: - break; - - // IPv6 specific - WiFi/Eth - case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - case ARDUINO_EVENT_ETH_GOT_IP6: { - auto ip6 = IPAddress(IPv6, (uint8_t *)info.got_ip6.ip6_info.ip.addr, 0).toString(); - const char * link = event == ARDUINO_EVENT_ETH_GOT_IP6 ? "Eth" : "WiFi"; - if (ip6.startsWith("fe80")) { - emsesp::EMSESP::logger().info("IPv6 (%s) local: %s", link, ip6.c_str()); - } else if (ip6.startsWith("fd") || ip6.startsWith("fc")) { - emsesp::EMSESP::logger().info("IPv6 (%s) ULA: %s", link, ip6.c_str()); - } else { - emsesp::EMSESP::logger().info("IPv6 (%s) global: %s", link, ip6.c_str()); - } - emsesp::EMSESP::system_.has_ipv6(true); - } break; - - default: - break; - } -#endif -} - void NetworkSettings::read(NetworkSettings & settings, JsonObject root) { // connection settings root["ssid"] = settings.ssid; @@ -544,6 +73,7 @@ StateUpdateResult NetworkSettings::update(JsonObject root, NetworkSettings & set } // see if we need to inform the user of a restart + // if tx power, enableCORS, CORSOrigin, ssid changes, we need to restart if (tx_power != settings.tx_power || enableCORS != settings.enableCORS || CORSOrigin != settings.CORSOrigin || (ssid != settings.ssid && settings.ssid.isEmpty())) { return StateUpdateResult::CHANGED_RESTART; // tell WebUI that a restart is needed diff --git a/src/ESP32React/NetworkSettingsService.h b/src/ESP32React/NetworkSettingsService.h index 4635c5181..c9903a681 100644 --- a/src/ESP32React/NetworkSettingsService.h +++ b/src/ESP32React/NetworkSettingsService.h @@ -8,15 +8,10 @@ #ifndef EMSESP_STANDALONE #include -#include -#include -#include -#include #endif #define NETWORK_SETTINGS_FILE "/config/networkSettings.json" #define NETWORK_SETTINGS_SERVICE_PATH "/rest/networkSettings" -#define WIFI_RECONNECTION_DELAY (1000 * 3) #ifndef FACTORY_WIFI_SSID #define FACTORY_WIFI_SSID "" @@ -30,37 +25,6 @@ #define FACTORY_WIFI_HOSTNAME "" #endif -// copied from Tasmota -#if CONFIG_IDF_TARGET_ESP32S2 -#define MAX_TX_PWR_DBM_11b 195 -#define MAX_TX_PWR_DBM_54g 150 -#define MAX_TX_PWR_DBM_n 130 -#define WIFI_SENSITIVITY_11b -880 -#define WIFI_SENSITIVITY_54g -750 -#define WIFI_SENSITIVITY_n -720 -#elif CONFIG_IDF_TARGET_ESP32S3 -#define MAX_TX_PWR_DBM_11b 210 -#define MAX_TX_PWR_DBM_54g 190 -#define MAX_TX_PWR_DBM_n 185 -#define WIFI_SENSITIVITY_11b -880 -#define WIFI_SENSITIVITY_54g -760 -#define WIFI_SENSITIVITY_n -720 -#elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 -#define MAX_TX_PWR_DBM_11b 210 -#define MAX_TX_PWR_DBM_54g 190 -#define MAX_TX_PWR_DBM_n 185 -#define WIFI_SENSITIVITY_11b -880 -#define WIFI_SENSITIVITY_54g -760 -#define WIFI_SENSITIVITY_n -730 -#else -#define MAX_TX_PWR_DBM_11b 195 -#define MAX_TX_PWR_DBM_54g 160 -#define MAX_TX_PWR_DBM_n 140 -#define WIFI_SENSITIVITY_11b -880 -#define WIFI_SENSITIVITY_54g -750 -#define WIFI_SENSITIVITY_n -700 -#endif - class NetworkSettings { public: // core wifi configuration @@ -92,27 +56,10 @@ class NetworkSettingsService : public StatefulService { NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); void begin(); - void loop(); - - uint16_t getWifiReconnects() const { - return connectcount_; - } private: HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - - volatile unsigned long _lastConnectionAttempt; - volatile bool _stopping; - - volatile uint16_t connectcount_ = 0; // number of wifi reconnects - - void WiFiEvent(arduino_event_id_t event, arduino_event_info_t info); - void mDNS_start() const; - const char * disconnectReason(uint8_t code); - void reconfigureWiFiConnection(); - void manageSTA(); - void setWiFiPowerOnRSSI(); }; #endif diff --git a/src/ESP32React/NetworkStatus.cpp b/src/ESP32React/NetworkStatus.cpp index 9003be9b2..cbfbe5152 100644 --- a/src/ESP32React/NetworkStatus.cpp +++ b/src/ESP32React/NetworkStatus.cpp @@ -16,11 +16,10 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { auto * response = new AsyncJsonResponse(false); JsonObject root = response->getRoot(); - bool ethernet_connected = emsesp::EMSESP::system_.ethernet_connected(); - wl_status_t wifi_status = WiFi.status(); + wl_status_t wifi_status = WiFi.status(); // see if Ethernet is connected - if (ethernet_connected) { + if (emsesp::EMSESP::network_.ethernet_connected()) { root["status"] = 10; // custom code #10 - ETHERNET_STATUS_CONNECTED root["hostname"] = ETH.getHostname(); } else { @@ -29,7 +28,7 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { } // for both connections show ethernet - if (ethernet_connected) { + if (emsesp::EMSESP::network_.ethernet_connected()) { // Ethernet root["local_ip"] = ETH.localIP().toString(); root["local_ipv6"] = ETH.linkLocalIPv6().toString(); @@ -52,7 +51,7 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { root["ssid"] = WiFi.SSID(); root["bssid"] = WiFi.BSSIDstr(); root["channel"] = WiFi.channel(); - root["reconnect_count"] = emsesp::EMSESP::esp32React.getWifiReconnects(); + root["reconnect_count"] = emsesp::EMSESP::network_.getWifiReconnects(); root["subnet_mask"] = WiFi.subnetMask().toString(); if (WiFi.gatewayIP() != INADDR_NONE) { diff --git a/src/core/console.cpp b/src/core/console.cpp index c84adaf2d..eb2d5146a 100644 --- a/src/core/console.cpp +++ b/src/core/console.cpp @@ -202,7 +202,7 @@ static void setup_commands(std::shared_ptr const & commands) { commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, string_vector{F_(wifi), F_(reconnect)}, - [](Shell & shell, const std::vector & arguments) { EMSESP::system_.wifi_reconnect(); }); + [](Shell & shell, const std::vector & arguments) { EMSESP::network_.reconnect(); }); // // SET commands @@ -250,12 +250,12 @@ static void setup_commands(std::shared_ptr const & commands) { shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) { if (completed) { if (password1 == password2) { - EMSESP::esp32React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) { + EMSESP::esp32React.getNetworkSettingsService()->update([&](NetworkSettings & networkSettings) { networkSettings.password = password2.c_str(); return StateUpdateResult::CHANGED; }); shell.println("WiFi password updated. Reconnecting..."); - EMSESP::system_.wifi_reconnect(); + EMSESP::network_.reconnect(); } else { shell.println("Passwords do not match"); } @@ -277,6 +277,7 @@ static void setup_commands(std::shared_ptr const & commands) { networkSettings.hostname = arguments.front().c_str(); return StateUpdateResult::CHANGED; }); + EMSESP::network_.reconnect(); }); commands->add_command(ShellContext::MAIN, @@ -284,12 +285,14 @@ static void setup_commands(std::shared_ptr const & commands) { string_vector{F_(set), F_(wifi), F_(ssid)}, {F_(name_mandatory)}, [](Shell & shell, const std::vector & arguments) { - EMSESP::esp32React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) { + shell.println("The network connection will be reset..."); + Shell::loop_all(); + delay(1000); // wait a second + EMSESP::esp32React.getNetworkSettingsService()->update([&](NetworkSettings & networkSettings) { networkSettings.ssid = arguments.front().c_str(); return StateUpdateResult::CHANGED; }); - shell.println("WiFi ssid updated. Reconnecting..."); - EMSESP::system_.wifi_reconnect(); + EMSESP::network_.reconnect(); }); diff --git a/src/core/emsesp.cpp b/src/core/emsesp.cpp index a0fa75bc0..e811e7888 100644 --- a/src/core/emsesp.cpp +++ b/src/core/emsesp.cpp @@ -86,6 +86,7 @@ TxService EMSESP::txservice_; // outgoing Telegram Tx handler Mqtt EMSESP::mqtt_; // mqtt handler Modbus * EMSESP::modbus_ = nullptr; // modbus handler System EMSESP::system_; // core system services +Network EMSESP::network_; // network services TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors AnalogSensor EMSESP::analogsensor_; // Analog sensors Shower EMSESP::shower_; // Shower logic @@ -1721,13 +1722,13 @@ void EMSESP::start() { // start web log service. now we can start capturing logs to the web log webLogService.begin(); - // loads core system services settings (network, mqtt, ap, ntp etc) + // loads core system services settings (mqtt, ap, ntp etc) esp32React.begin(); #ifndef EMSESP_STANDALONE if (factory_settings) { LOG_WARNING("No settings found on filesystem. Using factory settings."); - // make sure OTAdata is updated with core3 format + // make sure OTAdata is updated with core3 (v3.9.0) format esp_ota_set_boot_partition(esp_ota_get_running_partition()); } #endif @@ -1777,6 +1778,9 @@ void EMSESP::start() { webSettingsService.begin(); // load EMS-ESP Application settings + // start network services. This will initialise WiFi or Ethernet depending on the settings. + network_.begin(); + // perform any system upgrades if (!factory_settings) { if (system_.check_upgrade()) { @@ -1847,6 +1851,9 @@ void EMSESP::shell_prompt() { void EMSESP::loop() { uuid::loop(); // store system uptime + // handle network + network_.loop(); + // handles LED and checks system health, and syslog service if (system_.loop()) { return; // LED flashing is active, skip the rest of the loop diff --git a/src/core/emsesp.h b/src/core/emsesp.h index 416c1bea7..cd9bd62c4 100644 --- a/src/core/emsesp.h +++ b/src/core/emsesp.h @@ -63,6 +63,7 @@ #include "mqtt.h" #include "modbus.h" #include "system.h" +#include "network.h" #include "temperaturesensor.h" #include "analogsensor.h" #include "console.h" @@ -228,6 +229,7 @@ class EMSESP { static Mqtt mqtt_; static Modbus * modbus_; static System system_; + static Network network_; static TemperatureSensor temperaturesensor_; static AnalogSensor analogsensor_; static Shower shower_; diff --git a/src/core/locale_common.h b/src/core/locale_common.h index d5b88c789..dc54f45fd 100644 --- a/src/core/locale_common.h +++ b/src/core/locale_common.h @@ -60,6 +60,7 @@ MAKE_WORD(mqtt) MAKE_WORD(gpio) MAKE_WORD(modbus) MAKE_WORD(emsesp) +MAKE_WORD(network) MAKE_WORD(connected) MAKE_WORD(disconnected) MAKE_WORD(passwd) diff --git a/src/core/mqtt.cpp b/src/core/mqtt.cpp index 7c63923f3..bb501cafb 100644 --- a/src/core/mqtt.cpp +++ b/src/core/mqtt.cpp @@ -563,7 +563,7 @@ void Mqtt::ha_status() { // create the HA sensors - must match the MQTT payload keys in the heartbeat topic // Note we don't use camelCase as it would change the HA entity_id and impact historic data #ifndef EMSESP_STANDALONE - if (!EMSESP::system_.ethernet_connected() || WiFi.isConnected()) { + if (EMSESP::network_.wifi_connected()) { publish_system_ha_sensor_config(DeviceValueType::INT8, "RSSI", "rssi", DeviceValueUOM::DBM); publish_system_ha_sensor_config(DeviceValueType::INT8, "Signal", "wifistrength", DeviceValueUOM::PERCENT); } @@ -585,7 +585,7 @@ void Mqtt::ha_status() { publish_system_ha_sensor_config(DeviceValueType::INT8, "CPU temperature", "temperature", DeviceValueUOM::DEGREES); #endif - if (!EMSESP::system_.ethernet_connected()) { + if (!EMSESP::network_.ethernet_connected()) { publish_system_ha_sensor_config(DeviceValueType::INT16, "WiFi reconnects", "wifireconnects", DeviceValueUOM::NONE); } // This one comes from the info MQTT topic - and handled in the publish_ha_sensor_config function diff --git a/src/core/network.cpp b/src/core/network.cpp new file mode 100644 index 000000000..6d0cb0a4e --- /dev/null +++ b/src/core/network.cpp @@ -0,0 +1,673 @@ +/* + * EMS-ESP - https://github.com/emsesp/EMS-ESP + * Copyright 2020-2025 emsesp.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "network.h" + +#include "emsesp.h" + +namespace emsesp { + +uuid::log::Logger Network::logger_{F_(network), uuid::log::Facility::KERN}; + +void Network::begin() { +#ifndef EMSESP_STANDALONE + // pull Network settings and store locally on stack + EMSESP::esp32React.getNetworkSettingsService()->read([&](auto & settings) { + enableMDNS_ = settings.enableMDNS; + staticIPConfig_ = settings.staticIPConfig; + localIP_ = settings.localIP; + gatewayIP_ = settings.gatewayIP; + subnetMask_ = settings.subnetMask; + dnsIP1_ = settings.dnsIP1; + dnsIP2_ = settings.dnsIP2; + hostname_ = settings.hostname; + ssid_ = settings.ssid; + password_ = settings.password; + bandwidth20_ = settings.bandwidth20; + nosleep_ = settings.nosleep; + tx_power_ = settings.tx_power; + bssid_ = settings.bssid; + }); + + // read Ethernet settings + EMSESP::webSettingsService.read([&](WebSettings & settings) { + phy_type_ = settings.phy_type; + eth_power_ = settings.eth_power; + eth_phy_addr_ = settings.eth_phy_addr; + eth_clock_mode_ = settings.eth_clock_mode; + }); + + // get Access Point settings + EMSESP::esp32React.getAPSettingsService()->read([&](APSettings & settings) { + ap_provisionMode_ = settings.provisionMode; + ap_ssid_ = settings.ssid; + ap_password_ = settings.password; + ap_channel_ = settings.channel; + ap_ssid_hidden_ = settings.ssidHidden; + ap_max_clients_ = settings.maxClients; + ap_localIP_ = settings.localIP; + ap_gatewayIP_ = settings.gatewayIP; + ap_subnetMask_ = settings.subnetMask; + }); + + // Initialise WiFi - we only do this once, when the network service is started + // We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default. + // If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future. + if (WiFi.getMode() != WIFI_OFF) { + WiFi.mode(WIFI_OFF); + } + + // Disable WiFi config persistance and auto reconnect + WiFi.persistent(false); + WiFi.setAutoReconnect(false); + + WiFi.mode(WIFI_STA); + WiFi.mode(WIFI_OFF); + + // scan settings give connect issues since arduino 2.0.14 and arduino 3.x.x with some wifi systems + // WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN + // WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set + + // capture the WIFI_REASON_* code on every STA disconnect event so check_connection() can + // log a meaningful reason when its periodic poll notices we're no longer associated + WiFi.onEvent([this](WiFiEvent_t /*event*/, WiFiEventInfo_t info) { last_disconnect_reason_ = info.wifi_sta_disconnected.reason; }, + ARDUINO_EVENT_WIFI_STA_DISCONNECTED); + + // clear the saved reason on a fresh STA association so we don't log a stale code on the next disconnect + WiFi.onEvent([this](WiFiEvent_t /*event*/, WiFiEventInfo_t /*info*/) { last_disconnect_reason_ = 0; }, ARDUINO_EVENT_WIFI_STA_GOT_IP); +#endif +} + +// format the BSSID (MAC address) of the network interface +bool Network::formatBSSID(const String & bssid, uint8_t (&mac)[6]) { +#ifndef EMSESP_STANDALONE + uint tmp[6]; + if (bssid.isEmpty() || sscanf(bssid.c_str(), "%X:%X:%X:%X:%X:%X", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) != 6) { + return false; + } + for (uint8_t i = 0; i < 6; i++) { + mac[i] = static_cast(tmp[i]); + } +#endif + return true; +} + +// get the local IP address of the network interface +std::string Network::getLocalIP() const { + switch (network_iface_) { +#ifndef EMSESP_STANDALONE + case NetIface::AP: + return WiFi.softAPIP().toString().c_str(); + case NetIface::WIFI: + return WiFi.localIP().toString().c_str(); + case NetIface::ETHERNET: + return ETH.localIP().toString().c_str(); + case NetIface::NONE: +#endif + default: + return ""; + } +} + +// get the MAC address of the network interface +std::string Network::getMacAddress() const { + switch (network_iface_) { +#ifndef EMSESP_STANDALONE + case NetIface::AP: + return WiFi.softAPmacAddress().c_str(); + case NetIface::WIFI: + return WiFi.macAddress().c_str(); + case NetIface::ETHERNET: + return ETH.macAddress().c_str(); + case NetIface::NONE: +#endif + default: + return ""; + } +} + +// get the number of stations connected to the AP +uint8_t Network::getStationNum() const { +#ifndef EMSESP_STANDALONE + return network_iface_ == NetIface::AP ? WiFi.softAPgetStationNum() : 0; +#else + return 0; +#endif +} + +// disconnect all WiFi, Eth and AP +// so we can starts searching again to reconnect +void Network::reconnect() { + LOG_DEBUG("Disconnecting all networks"); + +#ifndef EMSESP_STANDALONE + // disconnect WiFi + if (wifi_connected()) { + WiFi.disconnect(true); + } + + // disconnect AP + + if (WiFi.getMode() & WIFI_AP) { + stopAP(); + } +#endif + + // reset network state + network_ip_ = 0; + network_iface_ = NetIface::NONE; + has_ipv6_ = false; + juststopped_ = true; + + // reload the network settings + begin(); +} + +// network loop, looking for new and disconnecting networks +void Network::loop() { +#ifndef EMSESP_STANDALONE + // if we already have a Wifi or Ethernet connection then re-check every NETWORK_RECONNECTION_DELAY_LONG, otherwise NETWORK_RECONNECTION_DELAY_SHORT + const unsigned long currentMillis = millis(); + const uint32_t reconnectDelay = + (network_iface_ == NetIface::WIFI || network_iface_ == NetIface::ETHERNET) ? NETWORK_RECONNECTION_DELAY_LONG : NETWORK_RECONNECTION_DELAY_SHORT; + if (!lastConnectionAttempt_ || static_cast(currentMillis - lastConnectionAttempt_) >= reconnectDelay) { + lastConnectionAttempt_ = currentMillis; + + // manage network interfaces + startAP(); // Captive Portal (AP) + startWIFI(); // WiFi + startEthernet(); // Ethernet + + if (network_ip_ != 0) { + checkConnection(); // already have a connection: verify it's still alive + } + findNetworks(); // detect new connections + } + + // process DNS requests for the captive portal while the soft-AP is up + if (ap_dnsServer_) { + ap_dnsServer_->processNextRequest(); + } +#endif +} + +// Re-validate the currently active connection +// if a netif is no longer up or has lost its IP (cable unplugged, AP gone, DHCP lease lost, ...) we drop our state so +// find_networks() can pick up a new one +void Network::checkConnection() { +#ifndef EMSESP_STANDALONE + bool still_up = false; + for (esp_netif_t * netif = esp_netif_next_unsafe(NULL); netif != NULL; netif = esp_netif_next_unsafe(netif)) { + if (iface_from_desc(esp_netif_get_desc(netif)) != network_iface_) { + continue; + } + esp_netif_ip_info_t ip_info = {}; + if (esp_netif_is_netif_up(netif) && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK && ip_info.ip.addr == network_ip_) { + still_up = true; + } + break; // only one active netif per kind in our world (sta / eth / ap) + } + + if (!still_up) { + if (network_iface_ == NetIface::WIFI) { + uint8_t reason = last_disconnect_reason_; + if (reason == 0) { + reason = WIFI_REASON_UNSPECIFIED; // event hasn't fired yet (or was cleared); avoid logging "0" + } + LOG_INFO("WiFi connection lost (reason %u: %s)", reason, disconnectReason(reason)); + } else { + LOG_INFO("Ethernet connection lost"); + } + reconnect(); + } +#endif +} + +// set the WiFi TxPower based on the RSSI (signal strength), picking the lowest value +// code is based of RSSI (signal strength) and copied from Tasmota's WiFiSetTXpowerBasedOnRssi() which is copied ESPEasy's ESPEasyWifi.SetWiFiTXpower() function +// Range ESP32 : 2dBm - 20dBm +// 802.11b - wifi1 +// 802.11a - wifi2 +// 802.11g - wifi3 +// 802.11n - wifi4 +// 802.11ac - wifi5 +// 802.11ax - wifi6 +// tx_power is the Tx power to set, 0 for auto +void Network::setWiFiPower(uint8_t tx_power) { +#ifndef EMSESP_STANDALONE + if (tx_power != 0) { + if (!WiFi.setTxPower(static_cast(tx_power))) { + emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power"); + } + return; + } + + int max_tx_pwr = MAX_TX_PWR_DBM_n; // assume wifi4 + int threshold = WIFI_SENSITIVITY_n + 120; // Margin in dBm * 10 on top of threshold + + // Assume AP sends with max set by ETSI standard. + // 2.4 GHz: 100 mWatt (20 dBm) + // US and some other countries allow 1000 mW (30 dBm) + int rssi = WiFi.RSSI() * 10; + int newrssi = rssi - 200; // We cannot send with over 20 dBm, thus it makes no sense to force higher TX power all the time. + + int min_tx_pwr = 0; + if (newrssi < threshold) { + min_tx_pwr = threshold - newrssi; + } + if (min_tx_pwr > max_tx_pwr) { + min_tx_pwr = max_tx_pwr; + } + + // from WiFIGeneric.h use: + wifi_power_t p = WIFI_POWER_2dBm; + if (min_tx_pwr > 185) + p = WIFI_POWER_19_5dBm; + else if (min_tx_pwr > 170) + p = WIFI_POWER_18_5dBm; + else if (min_tx_pwr > 150) + p = WIFI_POWER_17dBm; + else if (min_tx_pwr > 130) + p = WIFI_POWER_15dBm; + else if (min_tx_pwr > 110) + p = WIFI_POWER_13dBm; + else if (min_tx_pwr > 85) + p = WIFI_POWER_11dBm; + else if (min_tx_pwr > 70) + p = WIFI_POWER_8_5dBm; + else if (min_tx_pwr > 50) + p = WIFI_POWER_7dBm; + else if (min_tx_pwr > 20) + p = WIFI_POWER_5dBm; + +#if defined(EMSESP_DEBUG) + // emsesp::EMSESP::logger().debug("Recommended WiFi Tx Power (set_power %d, new power %d, rssi %d, threshold %d)", min_tx_pwr / 10, p, rssi, threshold); +#endif + + if (!WiFi.setTxPower(p)) { + emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power"); + } +#endif +} + +// start the multicast UDP service so EMS-ESP is discoverable via .local +void Network::startmDNS() const { +#ifndef EMSESP_STANDALONE + MDNS.end(); + + if (!enableMDNS_) { + return; + } + + if (!MDNS.begin(emsesp::EMSESP::system_.hostname().c_str())) { + emsesp::EMSESP::logger().warning("Failed to start mDNS Responder service"); + return; + } + + std::string address_s = emsesp::EMSESP::system_.hostname() + ".local"; + + MDNS.addService("http", "tcp", 80); // add our web server and rest API + MDNS.addService("telnet", "tcp", 23); // add our telnet console + MDNS.addServiceTxt("http", "tcp", "address", address_s.c_str()); + + emsesp::EMSESP::logger().info("Starting mDNS Responder service"); +#endif +} + +// WiFi disconnect reason code to string +const char * Network::disconnectReason(uint8_t code) { +#ifndef EMSESP_STANDALONE + switch (code) { + case WIFI_REASON_UNSPECIFIED: // = 1, + return "unspecified"; + case WIFI_REASON_AUTH_EXPIRE: // = 2, + return "auth expire"; + case WIFI_REASON_AUTH_LEAVE: // = 3, + return "auth leave"; + case WIFI_REASON_ASSOC_EXPIRE: // = 4, + return "assoc expired"; + case WIFI_REASON_ASSOC_TOOMANY: // = 5, + return "assoc too many"; + case WIFI_REASON_NOT_AUTHED: // = 6, + return "not authenticated"; + case WIFI_REASON_NOT_ASSOCED: // = 7, + return "not assoc"; + case WIFI_REASON_ASSOC_LEAVE: // = 8, + return "assoc leave"; + case WIFI_REASON_ASSOC_NOT_AUTHED: // = 9, + return "assoc not authed"; + case WIFI_REASON_DISASSOC_PWRCAP_BAD: // = 10, + return "disassoc powerCAP bad"; + case WIFI_REASON_DISASSOC_SUPCHAN_BAD: // = 11, + return "disassoc supchan bad"; + case WIFI_REASON_IE_INVALID: // = 13, + return "IE invalid"; + case WIFI_REASON_MIC_FAILURE: // = 14, + return "MIC failure"; + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // = 15, + return "4way handshake timeout"; + case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: // = 16, + return "group key-update timeout"; + case WIFI_REASON_IE_IN_4WAY_DIFFERS: // = 17, + return "IE in 4way differs"; + case WIFI_REASON_GROUP_CIPHER_INVALID: // = 18, + return "group cipher invalid"; + case WIFI_REASON_PAIRWISE_CIPHER_INVALID: // = 19, + return "pairwise cipher invalid"; + case WIFI_REASON_AKMP_INVALID: // = 20, + return "AKMP invalid"; + case WIFI_REASON_UNSUPP_RSN_IE_VERSION: // = 21, + return "unsupported RSN_IE version"; + case WIFI_REASON_INVALID_RSN_IE_CAP: // = 22, + return "invalid RSN_IE_CAP"; + case WIFI_REASON_802_1X_AUTH_FAILED: // = 23, + return "802 X1 auth failed"; + case WIFI_REASON_CIPHER_SUITE_REJECTED: // = 24, + return "cipher suite rejected"; + case WIFI_REASON_BEACON_TIMEOUT: // = 200, + return "beacon timeout"; + case WIFI_REASON_NO_AP_FOUND: // = 201, + return "no AP found"; + case WIFI_REASON_AUTH_FAIL: // = 202, + return "auth fail"; + case WIFI_REASON_ASSOC_FAIL: // = 203, + return "assoc fail"; + case WIFI_REASON_HANDSHAKE_TIMEOUT: // = 204, + return "handshake timeout"; + case WIFI_REASON_CONNECTION_FAIL: // 205, + return "connection fail"; + case WIFI_REASON_AP_TSF_RESET: // 206, + return "AP tsf reset"; + case WIFI_REASON_ROAMING: // 207, + return "roaming"; + case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG: // 208, + return "assoc comeback time too long"; + case WIFI_REASON_SA_QUERY_TIMEOUT: // 209, + return "sa query timeout"; + default: + return "unknown"; + } +#endif + + return ""; +} + +// WiFi management +void Network::startWIFI() { +#ifndef EMSESP_STANDALONE + // Abort if already connected, or if we have no SSID + if (WiFi.isConnected() || ssid_.length() == 0) { + return; + } + + LOG_DEBUG("Managing WiFi"); // TODO remove + + WiFi.setHostname(hostname_.c_str()); // updates shared default_hostname buffer + WiFi.enableSTA(true); // creates the STA netif + WiFi.STA.setHostname(hostname_.c_str()); // pushes to esp_netif_set_hostname + WiFi.enableIPv6(true); + if (staticIPConfig_) { + WiFi.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_); // configure for static IP + } + + // www.esp32.com/viewtopic.php?t=12055 + if (bandwidth20_) { + esp_wifi_set_bandwidth(static_cast(ESP_IF_WIFI_STA), WIFI_BW_HT20); + } else { + esp_wifi_set_bandwidth(static_cast(ESP_IF_WIFI_STA), WIFI_BW_HT40); + } + if (nosleep_) { + WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE + } + + // attempt to connect to the network + uint8_t bssid[6]; + wl_status_t status; + if (formatBSSID(bssid_, bssid)) { + status = WiFi.begin(ssid_.c_str(), password_.c_str(), 0, bssid); + } else { + LOG_DEBUG("Connecting to WiFi SSID %s with password %s, hostname %s", ssid_.c_str(), password_.c_str(), hostname_.c_str()); // TODO remove + status = WiFi.begin(ssid_.c_str(), password_.c_str()); + } + if (status == WL_CONNECT_FAILED) { + LOG_ERROR("WiFi connection failed (code %d)", status); + } + +#ifdef BOARD_C3_MINI_V1 + // always hardcode Tx power for Wemos CS Mini v1 + // v1 needs this value, see https://github.com/emsesp/EMS-ESP32/pull/620#discussion_r993173979 + // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi + WiFi.setTxPower(WIFI_POWER_8_5dBm); +#else + setWiFiPower(tx_power_); +#endif + +#endif +} + +// Ethernet management +// Brings up the ETH driver / netif exactly once. After ETH.begin() returns true the driver +// continues to run autonomously (link negotiation, DHCP, etc); the loop must NOT call ETH.begin() +// again on every iteration because that thrashes the netif and can prevent DHCP from completing +void Network::startEthernet() { +#if CONFIG_IDF_TARGET_ESP32 + // already up and running, nothing to do + if (eth_started_) { + return; + } + +#ifndef EMSESP_STANDALONE + + // no ethernet present or wifi takes precedence + if (phy_type_ == PHY_type::PHY_TYPE_NONE || (ssid_.length() > 0)) { + return; + } + + LOG_DEBUG("Managing Ethernet"); // TODO remove + + // configure Ethernet + int mdc = 23; // Pin# of the I²C clock signal for the Ethernet PHY - hardcoded + int mdio = 18; // Pin# of the I²C IO signal for the Ethernet PHY - hardcoded + uint8_t phy_addr = eth_phy_addr_; // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110) + int8_t power = eth_power_; // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source) + eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720 + : (phy_type_ == PHY_type::PHY_TYPE_TLK110) ? ETH_PHY_TLK110 + : ETH_PHY_RTL8201; // Type of the Ethernet PHY (LAN8720 or TLK110) + // clock mode: + // ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0 + // ETH_CLOCK_GPIO0_OUT = 1 RMII clock output from GPIO0 + // ETH_CLOCK_GPIO16_OUT = 2 RMII clock output from GPIO16 + // ETH_CLOCK_GPIO17_OUT = 3 RMII clock output from GPIO17, for 50hz inverted clock + auto clock_mode = (eth_clock_mode_t)eth_clock_mode_; + + // reset power and add a delay as ETH doesn't not always start up correctly after a warm boot + if (eth_power_ != -1) { + pinMode(eth_power_, OUTPUT); + digitalWrite(eth_power_, LOW); + delay(500); + digitalWrite(eth_power_, HIGH); + } + + if (ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode)) { + eth_started_ = true; // mark up; do not re-enter this block until reboot / explicit teardown + ETH.setHostname(hostname_.c_str()); // Push hostname to the ETH netif immediately after it's created + ETH.enableIPv6(true); + if (staticIPConfig_) { + ETH.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_); + } + } else { + LOG_ERROR("Failed to start Ethernet"); + } +#endif +#endif +} + +// check if the network is connected and set network_ip_ / network_iface_ / has_ipv6_ +// Iterates over every esp-netif that exists, prioritizing Ethernet > WiFi > AP +bool Network::findNetworks() { +#ifndef EMSESP_STANDALONE + + // exit if already have a connection, unless in AP mode + // when in AP mode, it will always try and connect to the WiFi + if (network_ip_ != 0 && !(WiFi.getMode() & WIFI_AP)) { + // const esp_ip4_addr_t ip4 = {.addr = network_ip_}; + // LOG_DEBUG("Network already connected via IPv4: " IPSTR, IP2STR(&ip4)); + return true; + } + + struct NetInfo { + esp_ip4_addr_t ip; + esp_ip6_addr_t ip6; + char desc[8]; + bool has_ipv6; + } info = {}; + + // Preference order: ETHERNET > WIFI (STA) > AP + auto iface_priority = [](NetIface iface) -> uint8_t { + switch (iface) { + case NetIface::ETHERNET: + return 3; + case NetIface::WIFI: + return 2; + case NetIface::AP: + return 1; + case NetIface::NONE: + default: + return 0; + } + }; + + NetIface best_iface = NetIface::NONE; + for (esp_netif_t * netif = esp_netif_next_unsafe(NULL); netif != NULL; netif = esp_netif_next_unsafe(netif)) { + const char * desc = esp_netif_get_desc(netif); + bool is_up = esp_netif_is_netif_up(netif); + esp_netif_ip_info_t ip_info = {}; + esp_err_t err = esp_netif_get_ip_info(netif, &ip_info); + + if (!is_up || err != ESP_OK || ip_info.ip.addr == 0) { + continue; + } + + const NetIface candidate = iface_from_desc(desc); + if (iface_priority(candidate) <= iface_priority(best_iface)) { + continue; // already have something at least as good + } + + info.ip = ip_info.ip; + if (desc) { + strlcpy(info.desc, desc, sizeof(info.desc)); + } + info.has_ipv6 = (esp_netif_get_ip6_linklocal(netif, &info.ip6) == ESP_OK); + best_iface = candidate; + + if (best_iface == NetIface::ETHERNET) { + break; // top priority, can't be beaten by anything later in the list + } + } + + auto previous_iface = NetIface::NONE; + // LOG_DEBUG("best_iface: %d, previous_iface: %d, network_iface_: %d", best_iface, previous_iface, network_iface_); // TODO remove + + // if we have a connection and it's a new one, set it up + if (best_iface != NetIface::NONE && best_iface != previous_iface) { + previous_iface = network_iface_; // save the previous interface for comparison next time + network_ip_ = info.ip.addr; + network_iface_ = iface_from_desc(info.desc); // "sta"/"ap"/"eth*" + has_ipv6_ = info.has_ipv6; + connnect_retry_ = 0; + + LOG_INFO("Network connected via %s (IP: " IPSTR ")", + network_iface_ == NetIface::ETHERNET ? "Ethernet" + : network_iface_ == NetIface::WIFI ? "WiFi" + : network_iface_ == NetIface::AP ? "AP" + : "unknown", + IP2STR(&info.ip)); + + // if we have a Eth or Wifi connection and the AP is running, stop it + if (network_iface_ != NetIface::AP && WiFi.getMode() & WIFI_AP) { + stopAP(); + } + + // count the number of restarts (for Wifi and Eth) + if (juststopped_) { + juststopped_ = false; + connectcount_++; + LOG_DEBUG("Network re-connection count %d", connectcount_); + } + + // start mDNS for any real network interface (skip the SoftAP since the captive portal handles its own DNS) + if (enableMDNS_ && network_iface_ != NetIface::AP && network_iface_ != NetIface::NONE) { + startmDNS(); + } + + return true; // we have a network connection + } + + // fallback + network_ip_ = 0; + network_iface_ = NetIface::NONE; + has_ipv6_ = false; + connnect_retry_++; + LOG_DEBUG("No active network interfaces found yet, re-connection count %d", connnect_retry_); +#endif + return false; // no connection found yet +} + +// access point (soft-AP) and the captive portal +void Network::startAP() { +#ifndef EMSESP_STANDALONE + // Only start AP as a fallback if the Network has failed + if (connnect_retry_ < MAX_NETWORK_RECONNECTION_ATTEMPTS) { + return; + } + + // don't start the soft-AP if it is disabled, or Ethernet has taken over or we have a real WiFi connection or it's already running + if (ap_provisionMode_ == AP_MODE_NEVER || network_connected() || WiFi.getMode() & WIFI_AP) { + return; + } + + WiFi.softAPenableIPv6(); // force IPv6, same as for STA - fixes https://github.com/emsesp/EMS-ESP32/issues/1922 + WiFi.softAPConfig(ap_localIP_, ap_gatewayIP_, ap_subnetMask_); + esp_wifi_set_bandwidth(static_cast(ESP_IF_WIFI_AP), WIFI_BW_HT20); + WiFi.softAP(ap_ssid_.c_str(), ap_password_.c_str(), ap_channel_, ap_ssid_hidden_, ap_max_clients_); +#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 + const IPAddress apIp = WiFi.softAPIP(); + LOG_INFO("Starting Access Point with captive portal on %u.%u.%u.%u", apIp[0], apIp[1], apIp[2], apIp[3]); + + // start DNS server for Captive Portal + ap_dnsServer_ = new DNSServer; + ap_dnsServer_->start(DNS_PORT, "*", apIp); +#endif +} + +// stop AP +void Network::stopAP() { + LOG_INFO("Stopping Access Point"); +#ifndef EMSESP_STANDALONE + WiFi.softAPdisconnect(true); + if (ap_dnsServer_) { + ap_dnsServer_->stop(); + delete ap_dnsServer_; + ap_dnsServer_ = nullptr; + } +#endif +} + +} // namespace emsesp diff --git a/src/core/network.h b/src/core/network.h new file mode 100644 index 000000000..4caf632b9 --- /dev/null +++ b/src/core/network.h @@ -0,0 +1,215 @@ +/* + * EMS-ESP - https://github.com/emsesp/EMS-ESP + * Copyright 2020-2025 emsesp.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EMSESP_NETWORK_H_ +#define EMSESP_NETWORK_H_ + +#ifndef EMSESP_STANDALONE +#include +#include +#include +#include +#include +#include +#endif + +#include + +#include +#include + +namespace emsesp { + +#define NETWORK_RECONNECTION_DELAY_SHORT 2000 // 2 seconds +#define NETWORK_RECONNECTION_DELAY_LONG 60000 // 60 seconds + +#define MAX_NETWORK_RECONNECTION_ATTEMPTS 3 // maximum number of network reconnection attempts + +#define DNS_PORT 53 + +// copied from Tasmota +#if CONFIG_IDF_TARGET_ESP32S2 +#define MAX_TX_PWR_DBM_11b 195 +#define MAX_TX_PWR_DBM_54g 150 +#define MAX_TX_PWR_DBM_n 130 +#define WIFI_SENSITIVITY_11b -880 +#define WIFI_SENSITIVITY_54g -750 +#define WIFI_SENSITIVITY_n -720 +#elif CONFIG_IDF_TARGET_ESP32S3 +#define MAX_TX_PWR_DBM_11b 210 +#define MAX_TX_PWR_DBM_54g 190 +#define MAX_TX_PWR_DBM_n 185 +#define WIFI_SENSITIVITY_11b -880 +#define WIFI_SENSITIVITY_54g -760 +#define WIFI_SENSITIVITY_n -720 +#elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 +#define MAX_TX_PWR_DBM_11b 210 +#define MAX_TX_PWR_DBM_54g 190 +#define MAX_TX_PWR_DBM_n 185 +#define WIFI_SENSITIVITY_11b -880 +#define WIFI_SENSITIVITY_54g -760 +#define WIFI_SENSITIVITY_n -730 +#else +#define MAX_TX_PWR_DBM_11b 195 +#define MAX_TX_PWR_DBM_54g 160 +#define MAX_TX_PWR_DBM_n 140 +#define WIFI_SENSITIVITY_11b -880 +#define WIFI_SENSITIVITY_54g -750 +#define WIFI_SENSITIVITY_n -700 +#endif + +// which physical interface we are currently using for the active network connection. +// Mapped from the esp-netif description string returned by esp_netif_get_desc(): "sta" -> WIFI, +// "ap" -> AP, "eth"/"eth1"/"eth2"/... (arduino-esp32 v3.x suffixes ETH netifs because it supports +// multiple ETH instances) -> ETHERNET. Anything else stays as NONE. +enum class NetIface : uint8_t { + NONE = 0, + WIFI, + ETHERNET, + AP, +}; + +class Network { + public: + void begin(); + void loop(); + + uint16_t getWifiReconnects() const { + return connectcount_; + } + + uint32_t network_ip() const { + return network_ip_; + } + + NetIface network_iface() const { + return network_iface_; + } + + bool ethernet_connected() const { + return network_iface_ == NetIface::ETHERNET && network_ip_ != 0; + } + + bool wifi_connected() const { + return network_iface_ == NetIface::WIFI && network_ip_ != 0; + } + + bool ap_connected() const { + return network_iface_ == NetIface::AP && network_ip_ != 0; + } + + bool network_connected() const { + return ethernet_connected() || wifi_connected(); // don't include AP here + } + + bool has_ipv6() const { + return has_ipv6_ && (network_iface_ == NetIface::WIFI || network_iface_ == NetIface::ETHERNET); + } + + uint16_t getWifiReconnects() { + return connectcount_; + } + + std::string getLocalIP() const; + std::string getMacAddress() const; + uint8_t getStationNum() const; + + void reconnect(); + + // map a netif description string (from esp_netif_get_desc) to a NetIface + static NetIface iface_from_desc(const char * desc) { + if (!desc) { + return NetIface::NONE; + } + if (strcmp(desc, "sta") == 0) { + return NetIface::WIFI; + } + if (strcmp(desc, "ap") == 0) { + return NetIface::AP; + } + // any "eth*" (eth, eth0, eth1, ...) is treated as Ethernet + if (strncmp(desc, "eth", 3) == 0) { + return NetIface::ETHERNET; + } + return NetIface::NONE; + } + + private: + static uuid::log::Logger logger_; + + bool findNetworks(); + void checkConnection(); + void startmDNS() const; + bool formatBSSID(const String & bssid, uint8_t (&mac)[6]); + void startAP(); + void startWIFI(); + void startEthernet(); + void setWiFiPower(uint8_t tx_power); + const char * disconnectReason(uint8_t code); + void stopAP(); + + unsigned long lastConnectionAttempt_ = 0; + uint16_t connectcount_ = 0; // number of network reconnects + uint32_t network_ip_ = 0; + NetIface network_iface_ = NetIface::NONE; + bool has_ipv6_ = false; + bool juststopped_ = false; + bool eth_started_ = false; // true after ETH.begin() has succeeded once; prevents repeated re-init while DHCP is still running + volatile uint8_t last_disconnect_reason_ = 0; + uint16_t connnect_retry_ = 0; // number of network re-connection attempts + + // Network and AP settings + bool enableMDNS_; + bool staticIPConfig_; + IPAddress localIP_; + IPAddress gatewayIP_; + IPAddress subnetMask_; + IPAddress dnsIP1_; + IPAddress dnsIP2_; + String hostname_; + String ssid_; + String password_; + bool bandwidth20_; + bool nosleep_; + uint8_t tx_power_; + String bssid_; + uint8_t phy_type_; + int8_t eth_power_; + uint8_t eth_phy_addr_; + uint8_t eth_clock_mode_; + + // AP settings + uint8_t ap_provisionMode_; + String ap_ssid_; + String ap_password_; + uint8_t ap_channel_; + bool ap_ssid_hidden_; + uint8_t ap_max_clients_; + IPAddress ap_localIP_; + IPAddress ap_gatewayIP_; + IPAddress ap_subnetMask_; + +// for the captive portal in AP mode +#ifndef EMSESP_STANDALONE + DNSServer * ap_dnsServer_; +#endif +}; + +} // namespace emsesp + +#endif diff --git a/src/core/system.cpp b/src/core/system.cpp index b39ce5e15..08e7e38d1 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -17,6 +17,7 @@ */ #include "system.h" +#include "network.h" #include "emsesp.h" // for send_raw_telegram() command #ifndef EMSESP_STANDALONE @@ -485,7 +486,7 @@ void System::set_partition_install_date() { snprintf(c, sizeof(c), "d_%s", current_partition); time_t d = EMSESP::nvs_.getULong(c, 0); if (d < 1500000000L) { - LOG_DEBUG("Setting the install date in partition %s", current_partition); + LOG_DEBUG("Setting the NTP install date in partition %s", current_partition); auto t = time(nullptr) - uuid::get_uptime_sec(); EMSESP::nvs_.putULong(c, t); } @@ -586,15 +587,6 @@ void System::system_restart(const char * partitionname) { #endif } -// saves all settings -void System::wifi_reconnect() { - EMSESP::esp32React.getNetworkSettingsService()->read( - [](NetworkSettings & networkSettings) { LOG_INFO("WiFi reconnecting to SSID '%s'...", networkSettings.ssid.c_str()); }); - delay(500); // wait - EMSESP::webSettingsService.save(); // save local settings - EMSESP::esp32React.getNetworkSettingsService()->callUpdateHandlers(); // in case we've changed ssid or password -} - void System::syslog_init() { EMSESP::webSettingsService.read([&](WebSettings & settings) { syslog_enabled_ = settings.syslog_enabled; @@ -673,14 +665,9 @@ void System::store_settings(WebSettings & settings) { bool_dashboard_ = settings.bool_dashboard; enum_format_ = settings.enum_format; readonly_mode_ = settings.readonly_mode; - - phy_type_ = settings.phy_type; - eth_power_ = settings.eth_power; - eth_phy_addr_ = settings.eth_phy_addr; - eth_clock_mode_ = settings.eth_clock_mode; - locale_ = settings.locale; developer_mode_ = settings.developer_mode; + // start services if (settings.modbus_enabled) { if (EMSESP::modbus_ == nullptr) { @@ -736,9 +723,10 @@ void System::start() { commands_init(); // console & api commands led_init(); // init LED button_init(); // button - network_init(); // network - uart_init(); // start UART - syslog_init(); // start syslog + + last_system_check_ = 0; // force the LED to go from fast flash to pulse + uart_init(); // start UART + syslog_init(); // start syslog } // button single click @@ -756,9 +744,10 @@ void System::button_OnClick(PButton & b) { // button double click void System::button_OnDblClick(PButton & b) { LOG_NOTICE("Button pressed - double click - wifi reconnect to AP"); +#ifndef EMSESP_STANDALONE // set AP mode to always so will join AP if wifi ssid fails to connect EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) { - apSettings.provisionMode = AP_MODE_ALWAYS; + apSettings.provisionMode = AP_MODE_DISCONNECTED; return StateUpdateResult::CHANGED; }); // remove SSID from network settings @@ -766,7 +755,8 @@ void System::button_OnDblClick(PButton & b) { networkSettings.ssid = ""; return StateUpdateResult::CHANGED; }); - EMSESP::esp32React.getNetworkSettingsService()->callUpdateHandlers(); // in case we've changed ssid or password + EMSESP::network_.reconnect(); // reconnect to the network +#endif } // LED flash every 100ms @@ -904,7 +894,8 @@ bool System::loop() { // this is only done once when the connection is established void System::send_info_mqtt() { static uint8_t _connection = 0; - uint8_t connection = (ethernet_connected() ? 1 : 0) + ((WiFi.status() == WL_CONNECTED) ? 2 : 0) + (ntp_connected_ ? 4 : 0) + (has_ipv6_ ? 8 : 0); + uint8_t connection = (EMSESP::network_.ethernet_connected() ? 1 : 0) + (EMSESP::network_.wifi_connected() ? 2 : 0) + (ntp_connected_ ? 4 : 0) + + (EMSESP::network_.has_ipv6() ? 8 : 0); // check if connection status has changed if (!Mqtt::connected() || connection == _connection) { return; @@ -924,7 +915,7 @@ void System::send_info_mqtt() { } #ifndef EMSESP_STANDALONE - if (EMSESP::system_.ethernet_connected()) { + if (EMSESP::network_.ethernet_connected()) { doc["network"] = "ethernet"; doc["hostname"] = ETH.getHostname(); /* @@ -1003,11 +994,11 @@ void System::heartbeat_json(JsonObject output) { #endif #ifndef EMSESP_STANDALONE - if (!ethernet_connected_) { + if (!EMSESP::network_.ethernet_connected()) { int8_t rssi = WiFi.RSSI(); output["rssi"] = rssi; output["wifistrength"] = wifi_quality(rssi); - output["wifireconnects"] = EMSESP::esp32React.getWifiReconnects(); + output["wifireconnects"] = EMSESP::network_.getWifiReconnects(); } #endif } @@ -1023,49 +1014,6 @@ void System::send_heartbeat() { Mqtt::queue_publish(F_(heartbeat), json); // send to MQTT with retain off. This will add to MQTT queue. } -// initializes network -void System::network_init() { - last_system_check_ = 0; // force the LED to go from fast flash to pulse - -#if CONFIG_IDF_TARGET_ESP32 - bool disableEth; - EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & settings) { disableEth = settings.ssid.length() > 0; }); - - // no ethernet present or disabled - if (phy_type_ == PHY_type::PHY_TYPE_NONE || disableEth) { - return; - } // no ethernet present - - // configure Ethernet - int mdc = 23; // Pin# of the I²C clock signal for the Ethernet PHY - hardcoded - int mdio = 18; // Pin# of the I²C IO signal for the Ethernet PHY - hardcoded - uint8_t phy_addr = eth_phy_addr_; // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110) - int8_t power = eth_power_; // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source) - eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720 - : (phy_type_ == PHY_type::PHY_TYPE_TLK110) ? ETH_PHY_TLK110 - : ETH_PHY_RTL8201; // Type of the Ethernet PHY (LAN8720 or TLK110) - // clock mode: - // ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0 - // ETH_CLOCK_GPIO0_OUT = 1 RMII clock output from GPIO0 - // ETH_CLOCK_GPIO16_OUT = 2 RMII clock output from GPIO16 - // ETH_CLOCK_GPIO17_OUT = 3 RMII clock output from GPIO17, for 50hz inverted clock - auto clock_mode = (eth_clock_mode_t)eth_clock_mode_; - - // reset power and add a delay as ETH doesn't not always start up correctly after a warm boot - if (eth_power_ != -1) { - pinMode(eth_power_, OUTPUT); - digitalWrite(eth_power_, LOW); - delay(500); - digitalWrite(eth_power_, HIGH); - } - eth_present_ = ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode); - if (eth_present_) { - // Push hostname to the ETH netif immediately after it's created - ETH.setHostname(hostname_.c_str()); - } -#endif -} - // check health of system, done every 5 seconds void System::system_check() { uint32_t current_uptime = uuid::get_uptime(); @@ -1084,7 +1032,7 @@ void System::system_check() { #endif // check if we have a valid network connection - if (!ethernet_connected() && (WiFi.status() != WL_CONNECTED)) { + if (!EMSESP::network_.network_connected()) { healthcheck_ |= HEALTHCHECK_NO_NETWORK; } else { healthcheck_ &= ~HEALTHCHECK_NO_NETWORK; @@ -1402,7 +1350,7 @@ void System::show_system(uuid::console::Shell & shell) { } // show Ethernet if connected - if (ethernet_connected_) { + if (EMSESP::network_.ethernet_connected()) { shell.println(); shell.printfln(" Ethernet Status: connected"); shell.printfln(" Ethernet MAC address: %s", ETH.macAddress().c_str()); @@ -1694,6 +1642,7 @@ bool System::check_upgrade() { } return changed; }); + EMSESP::network_.reconnect(); } // changes to application settings @@ -2348,7 +2297,6 @@ std::string System::get_metrics_prometheus() { } result += info_metric; - // TODO fix, as local_info_labels is always empty here if (!local_info_labels.empty()) { result += "{"; bool first = true; @@ -2379,14 +2327,14 @@ String System::get_ip_or_hostname() { #ifndef EMSESP_STANDALONE EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & settings) { if (settings.enableMDNS) { - if (EMSESP::system_.ethernet_connected()) { + if (EMSESP::network_.ethernet_connected()) { result = ETH.getHostname(); } else if (WiFi.status() == WL_CONNECTED) { result = WiFi.getHostname(); } } else { // no DNS, use the IP - if (EMSESP::system_.ethernet_connected()) { + if (EMSESP::network_.ethernet_connected()) { result = ETH.localIP().toString(); } else if (WiFi.status() == WL_CONNECTED) { result = WiFi.localIP().toString(); @@ -2470,7 +2418,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output // Network Status node = output["network"].to(); #ifndef EMSESP_STANDALONE - if (EMSESP::system_.ethernet_connected()) { + if (EMSESP::network_.ethernet_connected()) { node["network"] = "Ethernet"; node["hostname"] = ETH.getHostname(); // node["MAC"] = ETH.macAddress(); @@ -2484,7 +2432,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output node["network"] = "WiFi"; node["hostname"] = WiFi.getHostname(); node["RSSI"] = WiFi.RSSI(); - node["WIFIReconnects"] = EMSESP::esp32React.getWifiReconnects(); + node["WIFIReconnects"] = EMSESP::network_.getWifiReconnects(); // node["MAC"] = WiFi.macAddress(); // node["IPv4 address"] = uuid::printable_to_string(WiFi.localIP()) + "/" + uuid::printable_to_string(WiFi.subnetMask()); // node["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP()); diff --git a/src/core/system.h b/src/core/system.h index 52f6b0e5c..35a86b580 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -141,7 +141,6 @@ class System { static bool uploadFirmwareURL(const char * url = nullptr); void led_init(); - void network_init(); void button_init(); void commands_init(); void uart_init(); @@ -260,33 +259,9 @@ class System { hostname_ = hostname; } - bool ethernet_connected() { - return ethernet_connected_; - } - - void ethernet_connected(bool b) { - ethernet_connected_ = b; - } - - void has_ipv6(bool b) { - has_ipv6_ = b; - } - - bool has_ipv6() { - return has_ipv6_; - } - void ntp_connected(bool b); bool ntp_connected(); - bool network_connected() { -#ifndef EMSESP_STANDALONE - return (ethernet_connected() || WiFi.isConnected()); -#else - return true; -#endif - } - void fahrenheit(bool b) { fahrenheit_ = b; } @@ -312,8 +287,6 @@ class System { void show_system(uuid::console::Shell & shell); void show_users(uuid::console::Shell & shell); - void wifi_reconnect(); - static std::string languages_string(); uint32_t FStotal() { @@ -442,15 +415,11 @@ class System { uint8_t healthcheck_ = HEALTHCHECK_NO_NETWORK | HEALTHCHECK_NO_BUS; // start with all flags set, no wifi and no ems bus connection uint32_t last_system_check_ = 0; - bool upload_isrunning_ = false; // true if we're in the middle of a OTA firmware upload - bool ethernet_connected_ = false; - bool has_ipv6_ = false; + bool upload_isrunning_ = false; // true if we're in the middle of a OTA firmware upload bool ntp_connected_ = false; uint32_t ntp_last_check_ = 0; - bool eth_present_ = false; - // EMS-ESP settings std::string hostname_; String locale_; @@ -482,17 +451,10 @@ class System { uint8_t modbus_max_clients_; uint32_t modbus_timeout_; bool developer_mode_; - - // ethernet - uint8_t phy_type_; - int8_t eth_power_; - uint8_t eth_phy_addr_; - uint8_t eth_clock_mode_; - - uint32_t fstotal_; - uint32_t psram_; - uint32_t appused_; - uint32_t appfree_; + uint32_t fstotal_; + uint32_t psram_; + uint32_t appused_; + uint32_t appfree_; #if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 temperature_sensor_handle_t temperature_handle_ = NULL; diff --git a/src/emsesp_version.h b/src/emsesp_version.h index 0aea80b67..1f2b49104 100644 --- a/src/emsesp_version.h +++ b/src/emsesp_version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.9.0-dev.0" +#define EMSESP_APP_VERSION "3.9.0-dev.1" diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index d97c4d4e8..77a349839 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -79,9 +79,9 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) { } #endif - root["ap_status"] = EMSESP::esp32React.apStatus(); + root["ap_status"] = EMSESP::network_.ap_connected(); - if (EMSESP::system_.ethernet_connected()) { + if (EMSESP::network_.ethernet_connected()) { root["network_status"] = 10; // custom code #10 - ETHERNET_STATUS_CONNECTED root["wifi_rssi"] = 0; } else {