diff --git a/src/ESP32React/NetworkSettingsService.cpp b/src/ESP32React/NetworkSettingsService.cpp index af80fe049..9b67c8dff 100644 --- a/src/ESP32React/NetworkSettingsService.cpp +++ b/src/ESP32React/NetworkSettingsService.cpp @@ -72,12 +72,6 @@ StateUpdateResult NetworkSettings::update(JsonObject root, NetworkSettings & set settings.staticIPConfig = false; } - // 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 - } - - return StateUpdateResult::CHANGED; + // always best to do a restart after changing network settings + return StateUpdateResult::CHANGED_RESTART; } \ No newline at end of file diff --git a/src/core/network.cpp b/src/core/network.cpp index 0619017e4..e303de841 100644 --- a/src/core/network.cpp +++ b/src/core/network.cpp @@ -26,8 +26,8 @@ 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) { + // pull all settings and store locally + EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & settings) { enableMDNS_ = settings.enableMDNS; staticIPConfig_ = settings.staticIPConfig; localIP_ = settings.localIP; @@ -66,36 +66,96 @@ void Network::begin() { }); // 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), which is not the default after a flash erase. // Persistence is true by default, so this WiFi.mode() call writes opmode=0 to NVS for future boots. - WiFi.mode(WIFI_OFF); + // WiFi.mode(WIFI_OFF); + + // if Wifi is disabled, with no SSID, stop here + if (ssid_.isEmpty()) { + WiFi.mode(WIFI_OFF); + return; + } - // From here on, mode changes stay in RAM only and don't touch NVS WiFi.persistent(false); WiFi.setAutoReconnect(false); WiFi.mode(WIFI_STA); + WiFi.disconnect(true); // wipe old settings in NVS + 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 + } + + wifi_connect_pending_ = false; // set before begin() so the event handlers can race-clear it safely // 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. - // Also release the connect-pending guard so the next loop tick can issue a fresh WiFi.begin() - WiFi.onEvent( - [this](WiFiEvent_t /*event*/, WiFiEventInfo_t info) { - last_disconnect_reason_ = info.wifi_sta_disconnected.reason; - wifi_connect_pending_ = false; - }, - ARDUINO_EVENT_WIFI_STA_DISCONNECTED); + // arduino-esp32's WiFi.onEvent() simply appends to an internal callback list with no + // de-duplication, so register the lambdas only once across the lifetime of this Network instance + if (!wifi_events_registered_) { + wifi_events_registered_ = true; + + // Defer Tx power setting until STA is actually started. Calling WiFi.setTxPower() before + // WIFI_EVENT_STA_START fires would fail with "Neither AP or STA has been started" because + // WiFi.STA.started() only flips after esp_wifi_start() raises the event asynchronously. + WiFi.onEvent( + [this](WiFiEvent_t /*event*/, WiFiEventInfo_t /*info*/) { +#ifdef BOARD_C3_MINI_V1 + // always hardcode Tx power for Wemos C3 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 + }, + ARDUINO_EVENT_WIFI_STA_START); + + // 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. + // Also release the connect-pending guard so the next loop tick can issue a fresh WiFi.begin(). + // The first STA_DISCONNECTED after boot is suppressed because arduino-esp32 hard-codes a + // "retry once on first_connect" inside its own STA event handler (see STA.cpp), so a + // transient initial AUTH_FAIL / NO_AP_FOUND / etc. is automatically retried and almost + // always succeeds; logging it as a WARNING is misleading noise. + WiFi.onEvent( + [this](WiFiEvent_t /*event*/, WiFiEventInfo_t info) { + last_disconnect_reason_ = info.wifi_sta_disconnected.reason; + wifi_connect_pending_ = false; + if (wifi_ever_connected_) { + LOG_WARNING("WiFi disconnected (reason: %s)", disconnectReason(last_disconnect_reason_)); + } else { + LOG_WARNING("WiFi initial connect attempt failed (reason: %s)", disconnectReason(last_disconnect_reason_)); + } + }, + ARDUINO_EVENT_WIFI_STA_DISCONNECTED); + + // clear the saved reason and the connect-pending guard on a fresh STA association, + // and latch wifi_ever_connected_ so future disconnects log as warnings + WiFi.onEvent( + [this](WiFiEvent_t /*event*/, WiFiEventInfo_t /*info*/) { + last_disconnect_reason_ = 0; + wifi_connect_pending_ = false; + wifi_ever_connected_ = true; + }, + ARDUINO_EVENT_WIFI_STA_GOT_IP); + } - // clear the saved reason and the connect-pending guard on a fresh STA association - WiFi.onEvent( - [this](WiFiEvent_t /*event*/, WiFiEventInfo_t /*info*/) { - last_disconnect_reason_ = 0; - wifi_connect_pending_ = false; - }, - ARDUINO_EVENT_WIFI_STA_GOT_IP); #endif } @@ -159,12 +219,13 @@ uint8_t Network::getStationNum() const { // disconnect all WiFi, Eth and AP // so we can starts searching again to reconnect void Network::reconnect() { - LOG_DEBUG("Disconnecting all networks"); + LOG_DEBUG("Reconnecting all networks"); #ifndef EMSESP_STANDALONE // disconnect WiFi if (wifi_connected()) { - WiFi.disconnect(true); + WiFi.disconnect(true, true); + WiFi.mode(WIFI_STA); // reset mode } // disconnect AP @@ -177,11 +238,13 @@ void Network::reconnect() { network_ip_ = 0; network_iface_ = NetIface::NONE; has_ipv6_ = false; - juststopped_ = true; + juststopped_ = false; wifi_connect_pending_ = false; last_disconnect_reason_ = 0; + connect_retry_ = 0; + reconnect_count_ = 0; - // reload the network settings, as this could be called from the console + // reload the network settings and apply them begin(); } @@ -196,16 +259,17 @@ void Network::loop() { lastConnectionAttempt_ = currentMillis; // manage network interfaces - startAP(); // Captive Portal (AP) - startWIFI(); // WiFi startEthernet(); // Ethernet + startWIFI(); // WiFi + startAP(); // Captive Portal (AP) // already have a connection: verify it's still alive // or trigger if the WiFi handshaked failed on the WiFi.begin() call if (network_ip_ != 0 || last_disconnect_reason_ != 0) { checkConnection(); } - findNetworks(); // detect new connections + + findNetworks(); // detect any new network connections } // process DNS requests for the captive portal while the soft-AP is up @@ -219,6 +283,10 @@ void Network::loop() { // 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() { + if (network_iface_ == NetIface::NONE) { + return; + } + #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)) { @@ -238,11 +306,15 @@ void Network::checkConnection() { if (reason == 0) { reason = WIFI_REASON_UNSPECIFIED; // event hasn't fired yet (or was cleared); avoid logging "0" } + wifi_connect_pending_ = false; LOG_INFO("WiFi connection lost (reason %u: %s)", reason, disconnectReason(reason)); - } else { + } else if (network_iface_ == NetIface::ETHERNET) { LOG_INFO("Ethernet connection lost"); } - reconnect(); + juststopped_ = true; + network_iface_ = NetIface::NONE; + network_ip_ = 0; + has_ipv6_ = false; } #endif } @@ -309,7 +381,7 @@ void Network::setWiFiPower(uint8_t tx_power) { #endif if (!WiFi.setTxPower(p)) { - emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power"); + emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power!!"); } #endif } @@ -419,53 +491,23 @@ const char * Network::disconnectReason(uint8_t code) { // WiFi management void Network::startWIFI() { #ifndef EMSESP_STANDALONE - // Abort if already connected, or if we have no SSID or another Wifi.begin() is already in progress - if (WiFi.isConnected() || ssid_.length() == 0 || wifi_connect_pending_) { + // exit if WiFi is already connected, or if we have no SSID or another Wifi.begin() is already in progress + if (WiFi.isConnected() || ssid_.isEmpty() || wifi_connect_pending_) { return; } - 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 - } + wifi_connect_pending_ = true; - // 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; - wifi_connect_pending_ = true; // set before begin() so the event handlers can race-clear it safely + // LOG_DEBUG("WiFi connection with %s and %s", ssid_.c_str(), password_.c_str()); + // attempt to connect to the wifi network + // the event handlers handle error handling and retries + uint8_t bssid[6]; if (formatBSSID(bssid_, bssid)) { - status = WiFi.begin(ssid_.c_str(), password_.c_str(), 0, bssid); + WiFi.begin(ssid_.c_str(), password_.c_str(), 0, bssid); } else { - status = WiFi.begin(ssid_.c_str(), password_.c_str()); + WiFi.begin(ssid_.c_str(), password_.c_str()); } - if (status == WL_CONNECT_FAILED) { - wifi_connect_pending_ = false; // begin() didn't actually start anything, allow next tick to retry - 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 } @@ -482,26 +524,11 @@ void Network::startEthernet() { #ifndef EMSESP_STANDALONE - // no ethernet present or wifi takes precedence - if (phy_type_ == PHY_type::PHY_TYPE_NONE || (ssid_.length() > 0)) { + // no ethernet present + if (phy_type_ == PHY_type::PHY_TYPE_NONE) { return; } - // 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); @@ -510,7 +537,22 @@ void Network::startEthernet() { digitalWrite(eth_power_, HIGH); } - if (ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode)) { + // call to ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode) + // mdc = 23 = Pin# of the I²C clock signal for the Ethernet PHY - hardcoded + // mdio = 18 = Pin# of the I²C IO signal for the Ethernet PHY - hardcoded + // phy_addr = eth_phy_addr_ = I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110) + // power = eth_power_ = Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source) + // type = 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 + 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) + if (ETH.begin(type, eth_phy_addr_, 23, 18, eth_power_, (eth_clock_mode_t)eth_clock_mode_)) { + LOG_DEBUG("Ethernet started"); 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); @@ -526,16 +568,18 @@ void Network::startEthernet() { // 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() { +void 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; - } + // TODO what about if Eth drops and then comes back - we want to auto-switch? + // if (network_ip_ != 0 && !(WiFi.getMode() & WIFI_AP)) { + // // for debugging only + // // const esp_ip4_addr_t ip4 = {.addr = network_ip_}; + // // LOG_DEBUG("Network already connected via IPv4: " IPSTR, IP2STR(&ip4)); + // return; + // } struct NetInfo { esp_ip4_addr_t ip; @@ -580,18 +624,17 @@ bool Network::findNetworks() { strlcpy(info.desc, desc, sizeof(info.desc)); } info.has_ipv6 = (esp_netif_get_ip6_linklocal(netif, &info.ip6) == ESP_OK); - best_iface = candidate; + 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, network_iface_: %d", best_iface, network_iface_); // 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 + if (best_iface != NetIface::NONE && best_iface != network_iface_) { network_ip_ = info.ip.addr; network_iface_ = iface_from_desc(info.desc); // "sta"/"ap"/"eth*" has_ipv6_ = info.has_ipv6; @@ -612,8 +655,7 @@ bool Network::findNetworks() { // count the number of restarts (for Wifi and Eth) if (juststopped_) { juststopped_ = false; - connectcount_++; - LOG_DEBUG("Network re-connection count %d", connectcount_); + reconnect_count_++; } // start mDNS for any real network interface (skip the SoftAP since the captive portal handles its own DNS) @@ -624,17 +666,20 @@ bool Network::findNetworks() { // fetch the versions.json file from emsesp.org EMSESP::webStatusService.schedule_versions_refresh(); - return true; // we have a network connection + return; + } + + // fallback, reset network state if nothing found + if (best_iface == NetIface::NONE) { + network_ip_ = 0; + network_iface_ = NetIface::NONE; + has_ipv6_ = false; + connect_retry_++; + LOG_DEBUG("No active network interfaces found yet (retry #%d)", connect_retry_); } - // fallback - network_ip_ = 0; - network_iface_ = NetIface::NONE; - has_ipv6_ = false; - connect_retry_++; - LOG_DEBUG("No active network interfaces found yet, re-connection count %d", connect_retry_); #endif - return false; // no connection found yet + return; } // access point (soft-AP) and the captive portal diff --git a/src/core/network.h b/src/core/network.h index 69c061768..f61c8b866 100644 --- a/src/core/network.h +++ b/src/core/network.h @@ -35,12 +35,12 @@ namespace emsesp { -#define NETWORK_RECONNECTION_DELAY_SHORT 3000 // 3 seconds -#define NETWORK_RECONNECTION_DELAY_LONG 60000 // 60 seconds +#define NETWORK_RECONNECTION_DELAY_SHORT 5000 // 5 seconds +#define NETWORK_RECONNECTION_DELAY_LONG 60000 // 1 minute -#define MAX_NETWORK_RECONNECTION_ATTEMPTS 3 // maximum number of network reconnection attempts +#define MAX_NETWORK_RECONNECTION_ATTEMPTS 4 // maximum number of network reconnection attempts before going to AP fallback -#define DNS_PORT 53 +#define DNS_PORT 53 // dns server port for captive portal // copied from Tasmota #if CONFIG_IDF_TARGET_ESP32S2 @@ -79,9 +79,9 @@ namespace emsesp { // multiple ETH instances) -> ETHERNET. Anything else stays as NONE. enum class NetIface : uint8_t { NONE = 0, - WIFI, - ETHERNET, - AP, + WIFI, // 1 + ETHERNET, // 2 + AP, // 3 }; class Network { @@ -118,7 +118,7 @@ class Network { } uint16_t getNetworkReconnects() { - return connectcount_; + return reconnect_count_; } std::string getLocalIP() const; @@ -145,10 +145,25 @@ class Network { return NetIface::NONE; } + static const char * network_iface_to_string(NetIface iface) { + switch (iface) { + case NetIface::WIFI: + return "WiFi"; + case NetIface::ETHERNET: + return "Ethernet"; + case NetIface::AP: + return "AP"; + case NetIface::NONE: + default: + return "None"; + } + return "unknown"; + } + private: static uuid::log::Logger logger_; - bool findNetworks(); + void findNetworks(); void checkConnection(); void startmDNS() const; bool formatBSSID(const String & bssid, uint8_t (&mac)[6]); @@ -165,7 +180,7 @@ class Network { #endif unsigned long lastConnectionAttempt_ = 0; - uint16_t connectcount_ = 0; // number of network reconnects + uint16_t reconnect_count_ = 0; // number of network reconnects uint32_t network_ip_ = 0; NetIface network_iface_ = NetIface::NONE; bool has_ipv6_ = false; @@ -173,7 +188,11 @@ class Network { 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 connect_retry_ = 0; // number of network re-connection attempts - volatile bool wifi_connect_pending_ = false; + + volatile bool wifi_connect_pending_ = false; + + bool wifi_events_registered_ = false; // ensure WiFi.onEvent() handlers are registered only once across begin()/reconnect() cycles + bool wifi_ever_connected_ = false; // set true once we've successfully obtained an IP; used to silence the harmless first-attempt disconnect emitted by arduino-esp32's built-in retry-once behaviour // Network and AP settings bool enableMDNS_; diff --git a/src/core/system.cpp b/src/core/system.cpp index 57357ca08..350a304bf 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -742,6 +742,8 @@ void System::button_OnClick(PButton & b) { } // button double click +// reconnect to AP by removing the SSID from the network settings +// note: in v3.9 this is normal behaviour to fallback to AP if the Wifi or Ethernet connection fails void System::button_OnDblClick(PButton & b) { LOG_NOTICE("Button pressed - double click - wifi reconnect to AP"); #ifndef EMSESP_STANDALONE @@ -1307,19 +1309,19 @@ void System::show_system(uuid::console::Shell & shell) { shell.println("Network:"); switch (WiFi.status()) { case WL_IDLE_STATUS: - shell.printfln(" Status: Idle"); + shell.printfln(" WiFi Status: Idle"); break; case WL_NO_SSID_AVAIL: - shell.printfln(" Status: Network not found"); + shell.printfln(" WiFi Status: Network not found"); break; case WL_SCAN_COMPLETED: - shell.printfln(" Status: Network scan complete"); + shell.printfln(" WiFi Status: Network scan complete"); break; case WL_CONNECTED: - shell.printfln(" Status: WiFi connected"); + shell.printfln(" WiFi Status: Connected"); shell.printfln(" SSID: %s", WiFi.SSID().c_str()); shell.printfln(" BSSID: %s", WiFi.BSSIDstr().c_str()); shell.printfln(" RSSI: %d dBm (%d %%)", WiFi.RSSI(), wifi_quality(WiFi.RSSI())); @@ -1367,8 +1369,13 @@ void System::show_system(uuid::console::Shell & shell) { shell.printfln(" IPv6 address: %s", uuid::printable_to_string(ETH.linkLocalIPv6()).c_str()); } } - shell.println(); + // show AP is connected + if (EMSESP::network_.ap_connected()) { + shell.printfln(" AP Status: connected"); + } + + shell.println(); shell.println("Syslog:"); if (!syslog_enabled_) { shell.printfln(" Syslog: disabled"); diff --git a/src/web/WebSettingsService.cpp b/src/web/WebSettingsService.cpp index 1fee94c64..2cd8dfa0c 100644 --- a/src/web/WebSettingsService.cpp +++ b/src/web/WebSettingsService.cpp @@ -359,7 +359,6 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) { // either via the Web UI or via the Console void WebSettingsService::onUpdate() { // skip if we're restarting anyway - if (WebSettings::has_flags(WebSettings::ChangeFlags::RESTART)) { return; } diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index d12ddc226..48d2bd27a 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -369,6 +369,20 @@ void WebStatusService::getVersions(JsonObject root) { #endif } +// schedule the next versions.json fetch a few seconds out so the network stack has time to settle +// (DHCP completion, default-netif assignment and DNS server propagation through lwip) before +// HTTPClient::begin() does the hostByName() lookup. Without this delay the very first fetch races +// with the link-up event and arduino-esp32 logs a noisy "DNS Failed ... error '-54'". +void WebStatusService::schedule_versions_refresh() { +#ifndef EMSESP_STANDALONE + uint32_t next = uuid::get_uptime() + VERSIONS_INITIAL_FETCH_DELAY_MS; + if (next == 0) { + next = 1; // 0 is the "idle" sentinel — never let the wrap land there + } + versions_next_fetch_ms_ = next; +#endif +} + // periodic refresh (1 hour) of the cached versions.json // runs on the main loop task, which has a much bigger stack than AsyncTCP needed for https void WebStatusService::loop() { @@ -378,8 +392,6 @@ void WebStatusService::loop() { return; } - // TODO handle a network re-connect to fetch the values again (set versions_next_fetch_ms_ to 1) - // 0 = idle, nothing scheduled if (versions_next_fetch_ms_ == 0) { return; @@ -419,7 +431,7 @@ bool WebStatusService::refresh_versions_cache() { int httpCode = http.GET(); if (httpCode != HTTP_CODE_OK) { #if defined(EMSESP_DEBUG) - EMSESP::logger().debug("versions.json: HTTP %d", httpCode); + EMSESP::logger().debug("versions.json: HTTP error code %d", httpCode); #endif http.end(); return false; diff --git a/src/web/WebStatusService.h b/src/web/WebStatusService.h index 90414b6b8..e4164f8a3 100644 --- a/src/web/WebStatusService.h +++ b/src/web/WebStatusService.h @@ -30,10 +30,11 @@ class WebStatusService { return versions_cache_valid_; } - // refresh the versions.json cache - void schedule_versions_refresh() { - versions_next_fetch_ms_ = 1; - } + // schedule a refresh of the versions.json cache. Defers the fetch by + // VERSIONS_INITIAL_FETCH_DELAY_MS so the network stack (DHCP, default netif, DNS server) + // has time to settle after the link first comes up — otherwise hostByName() can fail + // immediately on boot with a noisy "DNS Failed ... error '-54'". + void schedule_versions_refresh(); bool current_upgradeable() const; // true if a newer version is available @@ -71,8 +72,9 @@ class WebStatusService { bool refresh_versions_cache(); // does the actual HTTPS fetch + parse, returns true on success - static constexpr uint32_t VERSIONS_REFRESH_INTERVAL_MS = 60UL * 60UL * 1000UL; // 1 hour on success - static constexpr uint32_t VERSIONS_RETRY_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 min after failure + static constexpr uint32_t VERSIONS_REFRESH_INTERVAL_MS = 60UL * 60UL * 1000UL; // 1 hour on success + static constexpr uint32_t VERSIONS_RETRY_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 min after failure + static constexpr uint32_t VERSIONS_INITIAL_FETCH_DELAY_MS = 5UL * 1000UL; // 5 s after a link comes up }; } // namespace emsesp