Skip to content
2 changes: 1 addition & 1 deletion protobufs
72 changes: 72 additions & 0 deletions src/Power.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/mesh/NodeDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 13 additions & 3 deletions src/mesh/generated/meshtastic/config.pb.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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}
Expand All @@ -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}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions src/mesh/http/WebServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions src/mesh/http/WebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <functional>

void initWebServer();
void deinitWebServer();
void createSSLCert();

class WebServerThread : private concurrency::OSThread
Expand Down
8 changes: 8 additions & 0 deletions src/mesh/udp/UdpMulticastHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
35 changes: 35 additions & 0 deletions src/mesh/wifi/WiFiAPClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

Before stopping these services, should the code check if ethernet is currently enabled? Since initEthernet() also initializes several of these services, we might want to ensure they aren't killed if a valid ethernet connection is still active, even if WiFi is being deinitialized.

Should a check for config.network.eth_enabled be added here and also for timeClient.end();?

A separate pull request for "automatic disable ethernet when running on battery" can take care of stopping these services if both config.network.eth_enabled and config.network.wifi_on_external_power_only are set.


#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)
Expand Down
3 changes: 3 additions & 0 deletions src/power.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down