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
29 changes: 29 additions & 0 deletions examples/companion_radio/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ 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

/* END GLOBAL OBJECTS */

void halt() {
Expand Down Expand Up @@ -219,6 +224,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 @@ -228,4 +236,25 @@ void loop() {
ui_task.loop();
#endif
rtc_clock.tick();

// 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 (millis() >= lastActive + (nextSleepInSecs * 1000)) {
board.sleep(1800); // Sleep for 30 minutes, wake on LoRa packet, timer, or button
// Just woke up - reset timers
lastActive = millis();
nextSleepInSecs = WORK_TIME_SECS; // Stay awake for 5s after wake
}
}
}
5 changes: 0 additions & 5 deletions examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1116,8 +1116,3 @@ void MyMesh::loop() {
uptime_millis += now - last_millis;
last_millis = now;
}

// To check if there is pending work
bool MyMesh::hasPendingWork() const {
return _mgr->getOutboundCount(0xFFFFFFFF) > 0;
}
3 changes: 0 additions & 3 deletions examples/simple_repeater/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,4 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
bridge.begin();
}
#endif

// To check if there is pending work
bool hasPendingWork() const;
};
8 changes: 7 additions & 1 deletion examples/simple_repeater/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
#include <Arduino.h>
#include <helpers/CommonCLI.h>

// Default button polarity: Active-LOW (pressed = LOW)
// Override with -D USER_BTN_PRESSED=HIGH in platformio.ini for rare active-high devices.
#ifndef USER_BTN_PRESSED
#define USER_BTN_PRESSED LOW
#endif

#define AUTO_OFF_MILLIS 20000 // 20 seconds
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds

Expand Down Expand Up @@ -85,7 +91,7 @@ void UITask::loop() {
if (millis() >= _next_read) {
int btnState = digitalRead(PIN_USER_BTN);
if (btnState != _prevBtnState) {
if (btnState == LOW) { // pressed?
if (btnState == USER_BTN_PRESSED) { // pressed?
if (_display->isOn()) {
// TODO: any action ?
} else {
Expand Down
8 changes: 7 additions & 1 deletion examples/simple_room_server/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
#include <Arduino.h>
#include <helpers/CommonCLI.h>

// Default button polarity: Active-LOW (pressed = LOW)
// Override with -D USER_BTN_PRESSED=HIGH in platformio.ini for rare active-high devices.
#ifndef USER_BTN_PRESSED
#define USER_BTN_PRESSED LOW
#endif

#define AUTO_OFF_MILLIS 20000 // 20 seconds
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds

Expand Down Expand Up @@ -85,7 +91,7 @@ void UITask::loop() {
if (millis() >= _next_read) {
int btnState = digitalRead(PIN_USER_BTN);
if (btnState != _prevBtnState) {
if (btnState == LOW) { // pressed?
if (btnState == USER_BTN_PRESSED) { // pressed?
if (_display->isOn()) {
// TODO: any action ?
} else {
Expand Down
8 changes: 7 additions & 1 deletion examples/simple_sensor/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
#include <Arduino.h>
#include <helpers/CommonCLI.h>

// Default button polarity: Active-LOW (pressed = LOW)
// Override with -D USER_BTN_PRESSED=HIGH in platformio.ini for rare active-high devices.
#ifndef USER_BTN_PRESSED
#define USER_BTN_PRESSED LOW
#endif

#define AUTO_OFF_MILLIS 20000 // 20 seconds
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds

Expand Down Expand Up @@ -85,7 +91,7 @@ void UITask::loop() {
if (millis() >= _next_read) {
int btnState = digitalRead(PIN_USER_BTN);
if (btnState != _prevBtnState) {
if (btnState == LOW) { // pressed?
if (btnState == USER_BTN_PRESSED) { // pressed?
if (_display->isOn()) {
// TODO: any action ?
} else {
Expand Down
6 changes: 6 additions & 0 deletions src/Mesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ class Mesh : public Dispatcher {
*/
void sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0);

/**
* \brief Check if there is pending work (packets to send)
* \returns true if there are outbound packets waiting
*/
bool hasPendingWork() const { return _mgr->getOutboundCount(0xFFFFFFFF) > 0; }

};

}
38 changes: 33 additions & 5 deletions src/helpers/ESP32Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
#include <MeshCore.h>
#include <Arduino.h>

// Default button polarity: Active-LOW (pressed = LOW)
// Most LoRa boards use GPIO as a boot button that pulls to GND when pressed.
// Override with -D USER_BTN_PRESSED=HIGH in platformio.ini for rare active-high devices.
#ifndef USER_BTN_PRESSED
#define USER_BTN_PRESSED LOW
#endif

#if defined(ESP_PLATFORM)

#include <rom/rtc.h>
Expand Down Expand Up @@ -56,14 +63,26 @@ class ESP32Board : public mesh::MainBoard {
return raw / 4;
}

void enterLightSleep(uint32_t secs) {
void enterLightSleep(uint32_t secs, int pin_wake_btn = -1, bool btn_active_high = true) {
#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

// Configure wakeup sources: LoRa packet (always active-high) and optionally button
if (pin_wake_btn < 0) {
// No button - just wake on LoRa packet
esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH);
} else if (btn_active_high) {
// Button is active-high (same as LoRa) - can use single ext1 source
esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1) | (1ULL << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH);
} else {
// Button is active-low (different from LoRa) - use ext0 for button, ext1 for LoRa
esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, 0); // Wake when button goes LOW
esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // Wake on LoRa packet
}

if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up every hour to do periodically jobs
esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up after specified seconds
}

esp_light_sleep_start(); // CPU enters light sleep
Expand All @@ -75,9 +94,18 @@ class ESP32Board : public mesh::MainBoard {
// To check for WiFi status to see if there is active OTA
wifi_mode_t mode;
esp_err_t err = esp_wifi_get_mode(&mode);

if (err != ESP_OK) { // WiFi is off ~ No active OTA, safe to go to sleep
enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet
#ifdef PIN_USER_BTN
// Sleep with button wake support (USER_BTN_PRESSED defaults to LOW if not defined)
#if USER_BTN_PRESSED == LOW
enterLightSleep(secs, PIN_USER_BTN, false); // Button is active-low
#else
enterLightSleep(secs, PIN_USER_BTN, true); // Button is active-high
#endif
#else
enterLightSleep(secs);
#endif
}
}

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
3 changes: 2 additions & 1 deletion variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ class MinewsemiME25LS01Board : public NRF52BoardOTA {
digitalWrite(LED_PIN, LOW);
#endif
#ifdef BUTTON_PIN
nrf_gpio_cfg_sense_input(digitalPinToInterrupt(BUTTON_PIN), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_HIGH);
// Wake on button press (active-LOW: pressed = LOW)
nrf_gpio_cfg_sense_input(digitalPinToInterrupt(BUTTON_PIN), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);
#endif
sd_power_system_off();
}
Expand Down
1 change: 0 additions & 1 deletion variants/minewsemi_me25ls01/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ build_flags = ${nrf52840_me25ls01.build_flags}
-I variants/minewsemi_me25ls01
-D me25ls01
-D PIN_USER_BTN=27
-D USER_BTN_PRESSED=HIGH
-D PIN_STATUS_LED=39
-D P_LORA_TX_LED=22
-D RADIO_CLASS=CustomLR1110
Expand Down
5 changes: 3 additions & 2 deletions variants/t1000-e/T1000eBoard.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,15 @@ class T1000eBoard : public NRF52BoardDCDC {
digitalWrite(LED_PIN, HIGH);
#endif
#ifdef BUTTON_PIN
while(digitalRead(BUTTON_PIN));
while(digitalRead(BUTTON_PIN) == LOW); // wait for button release
#endif
#ifdef LED_PIN
digitalWrite(LED_PIN, LOW);
#endif

#ifdef BUTTON_PIN
nrf_gpio_cfg_sense_input(BUTTON_PIN, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH);
// Wake on button press (active-LOW: pressed = LOW)
nrf_gpio_cfg_sense_input(BUTTON_PIN, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW);
#endif

sd_power_system_off();
Expand Down
1 change: 0 additions & 1 deletion variants/t1000-e/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ build_flags = ${nrf52_base.build_flags}
-I src/helpers/ui
-D T1000_E
-D PIN_USER_BTN=6
-D USER_BTN_PRESSED=HIGH
-D PIN_STATUS_LED=24
-D RADIO_CLASS=CustomLR1110
-D WRAPPER_CLASS=CustomLR1110Wrapper
Expand Down
1 change: 0 additions & 1 deletion variants/thinknode_m3/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ build_flags = ${nrf52_base.build_flags}
-I src/helpers/ui
-D THINKNODE_M3
-D PIN_USER_BTN=12
-D USER_BTN_PRESSED=LOW
-D PIN_STATUS_LED=35
-D RADIO_CLASS=CustomLR1110
-D WRAPPER_CLASS=CustomLR1110Wrapper
Expand Down
1 change: 0 additions & 1 deletion variants/wio-e5-mini/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ build_flags = ${stm32_base.build_flags}
-D RX_BOOSTED_GAIN=true
-D P_LORA_TX_LED=LED_RED
-D PIN_USER_BTN=USER_BTN
-D USER_BTN_PRESSED=LOW
-I variants/wio-e5-mini
build_src_filter = ${stm32_base.build_src_filter}
+<../variants/wio-e5-mini>
Expand Down
12 changes: 11 additions & 1 deletion variants/xiao_nrf52/XiaoNrf52Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
#include <Arduino.h>
#include <helpers/NRF52Board.h>

// Default button polarity: Active-LOW (pressed = LOW)
// Override with -D USER_BTN_PRESSED=HIGH in platformio.ini for rare active-high devices.
#ifndef USER_BTN_PRESSED
#define USER_BTN_PRESSED LOW
#endif

#ifdef XIAO_NRF52

class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA {
Expand Down Expand Up @@ -44,15 +50,19 @@ class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA {
// set led on and wait for button release before poweroff
digitalWrite(PIN_LED, LOW);
#ifdef PIN_USER_BTN
while(digitalRead(PIN_USER_BTN) == LOW);
while(digitalRead(PIN_USER_BTN) == USER_BTN_PRESSED); // wait for button release
#endif
digitalWrite(LED_GREEN, HIGH);
digitalWrite(LED_BLUE, HIGH);
digitalWrite(PIN_LED, HIGH);

#ifdef PIN_USER_BTN
// configure button press to wake up when in powered off state
#if USER_BTN_PRESSED == LOW
nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW);
#else
nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH);
#endif
#endif

sd_power_system_off();
Expand Down