fix SoftAP fallback to keep retrying WiFi STA after cold boot

This commit is contained in:
proddy
2026-10-20 13:06:26 +02:00
parent 65385dfa3c
commit fe7bc1c40f
13 changed files with 31 additions and 15 deletions
+2
View File
@@ -68,11 +68,13 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
- a read or write aimed at EMS-ESP's own bus id was left unanswered, so the sender waited for a timeout. EMS-ESP now sends an empty reply to those reads and acknowledges those writes [#3094](https://github.com/emsesp/EMS-ESP32/issues/3094)
- a second boiler monitor `0xE4` carrying `0xFFFF` cleared a valid `pc0Flow`. That empty value is now ignored [#3224](https://github.com/emsesp/EMS-ESP32/issues/3224)
- writing a negative custom entity value was always 1 too high because float-to-int rounding always added +0.5. Negative values now round the same way as positive ones [#3297](https://github.com/emsesp/EMS-ESP32/issues/3297)
- after a cold boot, SoftAP fallback no longer abandons STA forever. If a home WiFi SSID is configured, EMS-ESP keeps retrying STA while the captive portal is up so a late-booting router (e.g. after a power outage) can still be joined; SoftAP stops once STA gets an IP
## Changed
- various memory optimizations [#3083](https://github.com/emsesp/EMS-ESP32/issues/3083)
- Scheduler name is now mandatory
- Access Point provision mode label renamed from "When network connection is lost" to "When network connection not found" (all languages)
- network fallback to AP only after start [#3090](https://github.com/emsesp/EMS-ESP32/issues/3090)
- replaced Web async-validator with custom validator and toast with native snackbar to reduce bundle size
- Gateway and Connect devices are shown in the Devices page, but disabled [3126](https://github.com/emsesp/EMS-ESP32/discussions/3126)
+1 -1
View File
@@ -238,7 +238,7 @@ const cs: Translation = {
TIME_ZONE: 'Časová zóna',
ACCESS_POINT: 'Přístupový bod',
AP_PROVIDE: 'Povolit přístupový bod',
AP_PROVIDE_TEXT_2: 'Když je síťové připojení stratené',
AP_PROVIDE_TEXT_2: 'Když není síťové připojení nalezeno',
AP_PROVIDE_TEXT_3: 'Nikdy',
AP_PREFERRED_CHANNEL: 'Preferovaný kanál',
AP_HIDE_SSID: 'Skrýt SSID',
+1 -1
View File
@@ -238,7 +238,7 @@ const de: Translation = {
TIME_ZONE: 'Zeitzone',
ACCESS_POINT: 'Zugangspunkt',
AP_PROVIDE: 'Aktiviere Zugangspunkt',
AP_PROVIDE_TEXT_2: 'Wenn Netzwerkverbindung verloren geht',
AP_PROVIDE_TEXT_2: 'Wenn keine Netzwerkverbindung gefunden wird',
AP_PROVIDE_TEXT_3: 'Niemals',
AP_PREFERRED_CHANNEL: 'Bevorzugter Kanal',
AP_HIDE_SSID: 'Verstecke SSID',
+1 -1
View File
@@ -238,7 +238,7 @@ const en: Translation = {
TIME_ZONE: 'Time Zone',
ACCESS_POINT: 'Access Point',
AP_PROVIDE: 'Enable Access Point',
AP_PROVIDE_TEXT_2: 'When network connection is lost',
AP_PROVIDE_TEXT_2: 'When network connection not found',
AP_PROVIDE_TEXT_3: 'Never',
AP_PREFERRED_CHANNEL: 'Preferred Channel',
AP_HIDE_SSID: 'Hide SSID',
+1 -1
View File
@@ -238,7 +238,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_2: 'quand la connexion réseau est perdue',
AP_PROVIDE_TEXT_2: 'quand la connexion réseau n\'est pas trouvée',
AP_PROVIDE_TEXT_3: 'jamais',
AP_PREFERRED_CHANNEL: 'Canal préféré',
AP_HIDE_SSID: 'Cacher le SSID',
+1 -1
View File
@@ -238,7 +238,7 @@ const it: Translation = {
TIME_ZONE: 'Fuso orario',
ACCESS_POINT: 'Access Point',
AP_PROVIDE: 'Abilita Access Point',
AP_PROVIDE_TEXT_2: 'quando la connessione di rete è persa',
AP_PROVIDE_TEXT_2: 'quando la connessione di rete non è trovata',
AP_PROVIDE_TEXT_3: 'mai',
AP_PREFERRED_CHANNEL: 'Canale preferito',
AP_HIDE_SSID: 'Nascondi SSID',
+1 -1
View File
@@ -238,7 +238,7 @@ const nl: Translation = {
TIME_ZONE: 'Tijdzone',
ACCESS_POINT: 'Access Point',
AP_PROVIDE: 'Activeer Access Point',
AP_PROVIDE_TEXT_2: 'als netwerk verbinding verloren gaat',
AP_PROVIDE_TEXT_2: 'als netwerkverbinding niet gevonden wordt',
AP_PROVIDE_TEXT_3: 'nooit',
AP_PREFERRED_CHANNEL: 'Voorkeurskanaal',
AP_HIDE_SSID: 'SSID verbergen',
+1 -1
View File
@@ -238,7 +238,7 @@ const no: Translation = {
TIME_ZONE: 'Tidssone',
ACCESS_POINT: 'Aksesspunkt',
AP_PROVIDE: 'Aktiver Aksesspunkt',
AP_PROVIDE_TEXT_2: 'når nettverksforbindelsen er utilgjengelig',
AP_PROVIDE_TEXT_2: 'når nettverksforbindelsen ikke finnes',
AP_PROVIDE_TEXT_3: 'aldri',
AP_PREFERRED_CHANNEL: 'Foretrukket kanal',
AP_HIDE_SSID: 'Skjul SSID',
+1 -1
View File
@@ -238,7 +238,7 @@ const pl: BaseTranslation = {
TIME_ZONE: 'Strefa czasowa',
ACCESS_POINT: '{{Punkt|punktu|}} {{dostępowy|dostępowego|}}',
AP_PROVIDE: 'Punkt dostępowy',
AP_PROVIDE_TEXT_2: 'aktywny jeśli brak połączenia z siecią',
AP_PROVIDE_TEXT_2: 'gdy nie znaleziono połączenia z siecią',
AP_PROVIDE_TEXT_3: 'nieaktywny',
AP_PREFERRED_CHANNEL: 'Preferowany kanał',
AP_HIDE_SSID: 'Ukryj SSID',
+1 -1
View File
@@ -238,7 +238,7 @@ const sk: Translation = {
TIME_ZONE: 'Časová zóna',
ACCESS_POINT: 'Prístupový bod',
AP_PROVIDE: 'Povoliť prístupový bod',
AP_PROVIDE_TEXT_2: 'keď je sieťové pripojenie stratené',
AP_PROVIDE_TEXT_2: 'keď sieťové pripojenie nie je nájdené',
AP_PROVIDE_TEXT_3: 'nikdy',
AP_PREFERRED_CHANNEL: 'Preferovaný kanál',
AP_HIDE_SSID: 'Skryť SSID',
+1 -1
View File
@@ -238,7 +238,7 @@ const sv: Translation = {
TIME_ZONE: 'Tidszon',
ACCESS_POINT: 'Accesspunkt',
AP_PROVIDE: 'Aktivera accesspunkt',
AP_PROVIDE_TEXT_2: 'när nätverksanslutningen är bortkopplad',
AP_PROVIDE_TEXT_2: 'när nätverksanslutningen inte hittas',
AP_PROVIDE_TEXT_3: 'aldrig',
AP_PREFERRED_CHANNEL: 'Kanal',
AP_HIDE_SSID: 'Göm SSID',
+1 -1
View File
@@ -238,7 +238,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_2: 'Ağ bağlantısı kesildiğinde',
AP_PROVIDE_TEXT_2: 'Ağ bağlantısı bulunamadığında',
AP_PROVIDE_TEXT_3: 'asla',
AP_PREFERRED_CHANNEL: 'Tercih edilen kanal',
AP_HIDE_SSID: 'SSID yi gizle',
+18 -4
View File
@@ -125,6 +125,10 @@ void Network::begin() {
wifi_connect_pending_ = false;
if (wifi_ever_connected_) {
LOG_WARNING("WiFi disconnected (reason: %s)", disconnectReason(last_disconnect_reason_));
} else if (phase_ == NetPhase::AP) {
// SoftAP fallback is up and we're periodically retrying STA (slow router after
// power restore) — don't flood the log with WARNING on every failed attempt
LOG_DEBUG("WiFi STA retry while AP active failed (reason: %s)", disconnectReason(last_disconnect_reason_));
} else {
LOG_WARNING("WiFi initial connect attempt failed (reason: %s)", disconnectReason(last_disconnect_reason_));
}
@@ -663,13 +667,17 @@ void Network::stopWiFiRadio() {
// WiFi management
void Network::startWIFI() {
#ifndef EMSESP_STANDALONE
// only run during the WIFI phase; ETHERNET phase keeps WiFi quiet, AP phase has its own bring-up
if (phase_ != NetPhase::WIFI) {
// WIFI phase: normal STA connect. AP phase with a configured SSID: keep retrying STA while
// SoftAP is up so a late-booting router (e.g. after a power outage) can still be joined —
// findNetworks() stops SoftAP once STA gets an IP. ETHERNET phase keeps WiFi quiet.
const bool ap_fallback_retry = (phase_ == NetPhase::AP && !ssid_.isEmpty());
if (phase_ != NetPhase::WIFI && !ap_fallback_retry) {
return;
}
// the radio was powered down while Ethernet was up, so bring it back before trying to associate
if (wifi_radio_off_) {
// the radio was powered down while Ethernet was up, so bring it back before trying to associate.
// Don't call this during SoftAP fallback — startWiFiRadio() forces WIFI_STA and would kill the AP.
if (wifi_radio_off_ && !ap_fallback_retry) {
startWiFiRadio();
}
@@ -678,6 +686,11 @@ void Network::startWIFI() {
return;
}
// SoftAP is serving the captive portal — keep it and enable STA alongside
if (ap_fallback_retry && (WiFi.getMode() & WIFI_AP)) {
WiFi.mode(WIFI_AP_STA);
}
wifi_connect_pending_ = true;
// attempt to connect to the wifi network
@@ -969,6 +982,7 @@ void Network::findNetworks() {
: "AP");
// give up on this interface and try the next one. ETHERNET -> WIFI (or straight to AP if no SSID configured); WIFI -> AP.
// Once in AP phase with a configured SSID, startWIFI() keeps retrying STA (WIFI_AP_STA) so a late router can still be joined.
// AP_MODE_NEVER: never promote to the AP phase — keep waiting on the current interface.
if (connect_retry_ >= MAX_NETWORK_RECONNECTION_ATTEMPTS && phase_ != NetPhase::AP) {
if (phase_ == NetPhase::ETHERNET) {