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();

#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