Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1985,3 +1985,8 @@ bool MyMesh::advert() {
return false;
}
}

// Check if there is pending work (packets to send)
bool MyMesh::hasPendingWork() const {
return _mgr->getOutboundCount(0xFFFFFFFF) > 0;
}
1 change: 1 addition & 0 deletions examples/companion_radio/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {

public:
void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); }
bool hasPendingWork() const;

private:
void writeOKFrame();
Expand Down
70 changes: 70 additions & 0 deletions examples/companion_radio/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
#endif
);

// Power saving timing variables
unsigned long lastActive = 0; // Last time there was activity
unsigned long nextSleepInSecs = 120; // Wait 2 minutes before first sleep
const unsigned long WORK_TIME_SECS = 5; // Stay awake 5 seconds after wake/activity

// Short-sleep cycle when phone is disconnected but BLE is enabled
const unsigned long DISCONNECT_SLEEP_TIMEOUT_MS = 60000; // 60s before short-sleep cycle
const unsigned long SHORT_SLEEP_SECS = 12; // sleep duration per cycle
const unsigned long RECONNECT_WINDOW_MS = 3000; // awake time for BLE advertising
unsigned long disconnectTime = 0; // when phone disconnected (0 = connected/N/A)
unsigned long lastSleepWake = 0; // when we last woke from short sleep (0 = not in cycle)

/* END GLOBAL OBJECTS */

void halt() {
Expand Down Expand Up @@ -216,6 +228,9 @@ void setup() {
#ifdef DISPLAY_CLASS
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved
#endif

// Initialize power saving timer
lastActive = millis();
}

void loop() {
Expand All @@ -225,4 +240,59 @@ void loop() {
ui_task.loop();
#endif
rtc_clock.tick();

#ifndef WIFI_SSID
// Track phone connection state for disconnect sleep
if (serial_interface.hasPendingConnection()) {
disconnectTime = 0;
lastSleepWake = 0;
} else if (serial_interface.isEnabled() && disconnectTime == 0) {
disconnectTime = millis();
if (disconnectTime == 0) disconnectTime = 1; // avoid 0 sentinel collision
}
// Short-sleep cycle when BLE is enabled but phone is disconnected
if (serial_interface.isEnabled() && disconnectTime != 0
&& !the_mesh.getNodePrefs()->gps_enabled
&& the_mesh.millisHasNowPassed(disconnectTime + DISCONNECT_SLEEP_TIMEOUT_MS)
&& !the_mesh.hasPendingWork()
&& (lastSleepWake == 0 || the_mesh.millisHasNowPassed(lastSleepWake + RECONNECT_WINDOW_MS))) {
#ifdef PIN_USER_BTN
board.enterLightSleep(SHORT_SLEEP_SECS, PIN_USER_BTN);
#else
board.enterLightSleep(SHORT_SLEEP_SECS);
#endif
// Restart BLE advertising after light sleep powers down the radio
serial_interface.disable();
serial_interface.enable();
lastSleepWake = millis();
if (lastSleepWake == 0) lastSleepWake = 1;
}
#endif

// Power saving when BLE/WiFi is disabled
// Don't sleep if GPS is enabled - it needs continuous operation to maintain fix
// Note: Disabling BLE/WiFi via UI actually turns off the radio to save power
if (!serial_interface.isEnabled() && !the_mesh.getNodePrefs()->gps_enabled) {
// Check for pending work and update activity timer
if (the_mesh.hasPendingWork()) {
lastActive = millis();
if (nextSleepInSecs < 10) {
nextSleepInSecs += 5; // Extend work time by 5s if still busy
}
}

// Only sleep if enough time has passed since last activity
if (the_mesh.millisHasNowPassed(lastActive + (nextSleepInSecs * 1000))) {
#ifdef PIN_USER_BTN
// Sleep for 30 minutes, wake on LoRa packet, timer, or button press
board.enterLightSleep(1800, PIN_USER_BTN);
#else
// Sleep for 30 minutes, wake on LoRa packet or timer
board.enterLightSleep(1800);
#endif
// Just woke up - reset timers
lastActive = millis();
nextSleepInSecs = WORK_TIME_SECS; // Stay awake for 5s after wake
}
}
}
1 change: 1 addition & 0 deletions src/helpers/BaseSerialInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class BaseSerialInterface {
virtual bool isEnabled() const = 0;

virtual bool isConnected() const = 0;
virtual bool hasPendingConnection() const { return isConnected(); }

virtual bool isWriteBusy() const = 0;
virtual size_t writeFrame(const uint8_t src[], size_t len) = 0;
Expand Down
12 changes: 10 additions & 2 deletions src/helpers/ESP32Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <sys/time.h>
#include <Wire.h>
#include "driver/rtc_io.h"
#include "driver/gpio.h"

class ESP32Board : public mesh::MainBoard {
protected:
Expand Down Expand Up @@ -56,11 +57,18 @@ class ESP32Board : public mesh::MainBoard {
return raw / 4;
}

void enterLightSleep(uint32_t secs) {
void enterLightSleep(uint32_t secs, int pin_wake_btn = -1) {
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(P_LORA_DIO_1) // Supported ESP32 variants
if (rtc_gpio_is_valid_gpio((gpio_num_t)P_LORA_DIO_1)) { // Only enter sleep mode if P_LORA_DIO_1 is RTC pin
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // To wake up when receiving a LoRa packet

esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // Wake on LoRa packet

// Wake on button press (active-LOW: pin is HIGH when idle, LOW when pressed)
if (pin_wake_btn >= 0) {
gpio_wakeup_enable((gpio_num_t)pin_wake_btn, GPIO_INTR_LOW_LEVEL);
esp_sleep_enable_gpio_wakeup();
}

if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up every hour to do periodically jobs
Expand Down
1 change: 1 addition & 0 deletions src/helpers/NRF52Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class NRF52Board : public mesh::MainBoard {
virtual void reboot() override { NVIC_SystemReset(); }
virtual bool startOTAUpdate(const char *id, char reply[]) override;
virtual void sleep(uint32_t secs) override;
void enterLightSleep(uint32_t secs, int pin_wake_btn = -1) { sleep(secs); }

#ifdef NRF52_POWER_MANAGEMENT
bool isExternalPowered() override;
Expand Down
4 changes: 4 additions & 0 deletions src/helpers/esp32/SerialBLEInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,7 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
bool SerialBLEInterface::isConnected() const {
return deviceConnected; //pServer != NULL && pServer->getConnectedCount() > 0;
}

bool SerialBLEInterface::hasPendingConnection() const {
return pServer != NULL && pServer->getConnectedCount() > 0;
}
1 change: 1 addition & 0 deletions src/helpers/esp32/SerialBLEInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE
bool isEnabled() const override { return _isEnabled; }

bool isConnected() const override;
bool hasPendingConnection() const override;

bool isWriteBusy() const override;
size_t writeFrame(const uint8_t src[], size_t len) override;
Expand Down
22 changes: 21 additions & 1 deletion src/helpers/esp32/SerialWifiInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,38 @@
void SerialWifiInterface::begin(int port) {
// wifi setup is handled outside of this class, only starts the server
server.begin(port);

// Store WiFi credentials for re-enable
#ifdef WIFI_SSID
_ssid = WIFI_SSID;
_password = WIFI_PWD;
_isEnabled = true; // WiFi starts enabled
#else
_ssid = nullptr;
_password = nullptr;
#endif
}

// ---------- public methods
void SerialWifiInterface::enable() {
void SerialWifiInterface::enable() {
if (_isEnabled) return;

_isEnabled = true;
clearBuffers();

// Re-enable WiFi with stored credentials
if (_ssid != nullptr && _password != nullptr) {
WiFi.mode(WIFI_STA);
WiFi.begin(_ssid, _password);
}
}

void SerialWifiInterface::disable() {
_isEnabled = false;

// Actually turn off WiFi to save power
WiFi.disconnect(true); // Disconnect and clear config
WiFi.mode(WIFI_OFF); // Turn off WiFi radio
}

size_t SerialWifiInterface::writeFrame(const uint8_t src[], size_t len) {
Expand Down
4 changes: 4 additions & 0 deletions src/helpers/esp32/SerialWifiInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class SerialWifiInterface : public BaseSerialInterface {
bool _isEnabled;
unsigned long _last_write;
unsigned long adv_restart_time;
const char* _ssid;
const char* _password;

WiFiServer server;
WiFiClient client;
Expand Down Expand Up @@ -39,6 +41,8 @@ class SerialWifiInterface : public BaseSerialInterface {
deviceConnected = false;
_isEnabled = false;
_last_write = 0;
_ssid = nullptr;
_password = nullptr;
send_queue_len = recv_queue_len = 0;
received_frame_header.type = 0;
received_frame_header.length = 0;
Expand Down