diff --git a/protobufs b/protobufs index 77c8329a59a..8ebab18ba63 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 77c8329a59a9c96a61c447b5d5f1a52ca583e4f2 +Subproject commit 8ebab18ba632a2421c0968d82564c4dbac645cba diff --git a/src/Power.cpp b/src/Power.cpp index b2a4ddaaf6e..8fb4758ca4a 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -30,6 +30,10 @@ #include "input/LinuxInputImpl.h" #endif +#if HAS_WIFI && !defined(ARCH_PORTDUINO) +#include "mesh/wifi/WiFiAPClient.h" +#endif + // Working USB detection for powered/charging states on the RAK platform #ifdef NRF_APM #include "nrfx_power.h" @@ -49,6 +53,13 @@ #endif +// WiFi power management state +#if HAS_WIFI && !defined(ARCH_PORTDUINO) +static uint32_t wifiPowerLossTimerStart = 0; +static bool wifiPowerLossTimerActive = false; +static bool wifiWasDisabledByPowerLoss = false; +#endif + #ifndef DELAY_FOREVER #define DELAY_FOREVER portMAX_DELAY #endif @@ -956,10 +967,71 @@ void Power::readPowerStatus() } } +#if HAS_WIFI && !defined(ARCH_PORTDUINO) +/** + * Handle automatic WiFi enable/disable based on power source. + * When wifi_on_external_power_only is enabled: + * - WiFi is enabled when external power (USB) is connected + * - WiFi is disabled after timeout when running on battery + */ +void Power::handleWifiPowerManagement() +{ + // Feature disabled - nothing to do + if (!config.network.wifi_on_external_power_only) { + return; + } + + // WiFi not configured - nothing to do + if (!config.network.wifi_enabled || !config.network.wifi_ssid[0]) { + return; + } + + bool hasExternalPower = powerStatus && powerStatus->getHasUSB(); + uint32_t timeoutSecs = config.network.wifi_power_loss_timeout_secs; + if (timeoutSecs == 0) { + timeoutSecs = 30; // Default 30 seconds if not configured + } + + if (!hasExternalPower && isWifiAvailable()) { + // Running on battery with WiFi active - start/check timer + if (!wifiPowerLossTimerActive) { + LOG_INFO("External power lost, WiFi will disable in %u seconds", timeoutSecs); + wifiPowerLossTimerStart = millis(); + wifiPowerLossTimerActive = true; + } + + // Check if timeout expired + if (millis() - wifiPowerLossTimerStart >= timeoutSecs * 1000) { + LOG_INFO("Power loss timeout expired, disabling WiFi"); + deinitWifi(); + wifiPowerLossTimerActive = false; + wifiWasDisabledByPowerLoss = true; + } + } else if (hasExternalPower) { + // External power connected + if (wifiPowerLossTimerActive) { + LOG_INFO("External power restored, canceling WiFi disable timer"); + wifiPowerLossTimerActive = false; + } + + // Re-enable WiFi if it was disabled by power loss + if (wifiWasDisabledByPowerLoss && !isWifiAvailable()) { + LOG_INFO("External power restored, re-enabling WiFi"); + initWifi(); + wifiWasDisabledByPowerLoss = false; + } + } +} +#endif // HAS_WIFI && !defined(ARCH_PORTDUINO) + int32_t Power::runOnce() { readPowerStatus(); +#if HAS_WIFI && !defined(ARCH_PORTDUINO) + handleWifiPowerManagement(); +#endif + #ifdef HAS_PMU // WE no longer use the IRQ line to wake the CPU (due to false wakes from // sleep), but we do poll the IRQ status by reading the registers over I2C diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 2a32940867e..597a883364f 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -743,6 +743,10 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.network.ipv6_enabled = default_network_ipv6_enabled; #endif + // WiFi power management defaults + config.network.wifi_on_external_power_only = false; + config.network.wifi_power_loss_timeout_secs = 30; + #ifdef DISPLAY_FLIP_SCREEN config.display.flip_screen = true; #endif diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index d93f6fafa97..35770ab7436 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -469,6 +469,14 @@ typedef struct _meshtastic_Config_NetworkConfig { uint32_t enabled_protocols; /* Enable/Disable ipv6 support */ bool ipv6_enabled; + /* Only enable WiFi when connected to external power (USB). + WiFi will be automatically disabled when running on battery + after wifi_power_loss_timeout_secs delay. */ + bool wifi_on_external_power_only; + /* Delay in seconds before disabling WiFi after external power is lost. + This allows for brief power interruptions without toggling WiFi. + Default: 30 seconds. Set to 0 for immediate disable. */ + uint32_t wifi_power_loss_timeout_secs; } meshtastic_Config_NetworkConfig; /* Display Config */ @@ -730,7 +738,7 @@ extern "C" { #define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0, _meshtastic_Config_DeviceConfig_BuzzerMode_MIN} #define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0} +#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} @@ -741,7 +749,7 @@ extern "C" { #define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0, _meshtastic_Config_DeviceConfig_BuzzerMode_MIN} #define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN} #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} -#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0} +#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} #define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} @@ -798,6 +806,8 @@ extern "C" { #define meshtastic_Config_NetworkConfig_rsyslog_server_tag 9 #define meshtastic_Config_NetworkConfig_enabled_protocols_tag 10 #define meshtastic_Config_NetworkConfig_ipv6_enabled_tag 11 +#define meshtastic_Config_NetworkConfig_wifi_on_external_power_only_tag 12 +#define meshtastic_Config_NetworkConfig_wifi_power_loss_timeout_secs_tag 13 #define meshtastic_Config_DisplayConfig_screen_on_secs_tag 1 #define meshtastic_Config_DisplayConfig_gps_format_tag 2 #define meshtastic_Config_DisplayConfig_auto_screen_carousel_secs_tag 3 @@ -1038,7 +1048,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; #define meshtastic_Config_DisplayConfig_size 34 #define meshtastic_Config_LoRaConfig_size 85 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 -#define meshtastic_Config_NetworkConfig_size 204 +#define meshtastic_Config_NetworkConfig_size 212 #define meshtastic_Config_PositionConfig_size 62 #define meshtastic_Config_PowerConfig_size 52 #define meshtastic_Config_SecurityConfig_size 178 diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index 3a264fa5a0b..58121e4b1ca 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -247,4 +247,24 @@ void initWebServer() LOG_ERROR("Web Servers Failed! ;-( "); } } + +void deinitWebServer() +{ + LOG_DEBUG("Deinit Web Server"); + isWebServerReady = false; + + if (secureServer) { + secureServer->stop(); + delete secureServer; + secureServer = nullptr; + } + + if (insecureServer) { + insecureServer->stop(); + delete insecureServer; + insecureServer = nullptr; + } + + LOG_INFO("Web Servers Stopped"); +} #endif diff --git a/src/mesh/http/WebServer.h b/src/mesh/http/WebServer.h index e7a29a5a7df..7a2edc78d61 100644 --- a/src/mesh/http/WebServer.h +++ b/src/mesh/http/WebServer.h @@ -6,6 +6,7 @@ #include void initWebServer(); +void deinitWebServer(); void createSSLCert(); class WebServerThread : private concurrency::OSThread diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h index 2df8686a315..5f49388f434 100644 --- a/src/mesh/udp/UdpMulticastHandler.h +++ b/src/mesh/udp/UdpMulticastHandler.h @@ -39,6 +39,14 @@ class UdpMulticastHandler final } } + void stop() + { + LOG_DEBUG("Stopping UDP Multicast"); +#ifdef ARCH_ESP32 + udp.close(); +#endif + } + void onReceive(AsyncUDPPacket packet) { size_t packetLength = packet.length(); diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index a95dfa58f62..35e52f6bc41 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -248,12 +248,47 @@ bool isWifiAvailable() } } +// Deinit all WiFi-dependent services (called before WiFi off) +static void deinitWifiServices() +{ + LOG_DEBUG("Deinit WiFi services"); + + // Disable syslog + syslog.disable(); + + // Stop API server + deInitApiServer(); + +#ifdef ARCH_ESP32 +#if !MESHTASTIC_EXCLUDE_WEBSERVER + // Stop web server + deinitWebServer(); +#endif + + // Stop mDNS + MDNS.end(); +#endif + +#ifndef DISABLE_NTP + // Stop NTP client + timeClient.end(); +#endif + + // Reset flag so services reinitialize on reconnect + APStartupComplete = false; + + LOG_INFO("WiFi services stopped"); +} + // Disable WiFi void deinitWifi() { LOG_INFO("WiFi deinit"); if (isWifiAvailable()) { + // First stop all services that depend on WiFi + deinitWifiServices(); + #ifdef ARCH_ESP32 WiFi.disconnect(true, false); #elif defined(ARCH_RP2040) diff --git a/src/power.h b/src/power.h index 5f887c36b64..2ccd29bc8ba 100644 --- a/src/power.h +++ b/src/power.h @@ -103,6 +103,9 @@ class Power : private concurrency::OSThread void powerCommandsCheck(); void readPowerStatus(); +#if HAS_WIFI && !defined(ARCH_PORTDUINO) + void handleWifiPowerManagement(); +#endif virtual bool setup(); virtual int32_t runOnce() override; void setStatusHandler(meshtastic::PowerStatus *handler) { statusHandler = handler; }