Skip to content

Comments

feat: Dynamic light sleep support for ESP32 platform#7121

Open
m1nl wants to merge 30 commits intomeshtastic:developfrom
m1nl:esp_light_sleep_release
Open

feat: Dynamic light sleep support for ESP32 platform#7121
m1nl wants to merge 30 commits intomeshtastic:developfrom
m1nl:esp_light_sleep_release

Conversation

@m1nl
Copy link
Contributor

@m1nl m1nl commented Jun 24, 2025

fixes #6660

I've finally implemented the Feature Request I created a few months ago. Since then, I've been testing the dynamic light-sleep functionality and haven't encountered any issues.

In the initial version, legacy light-sleep support with the default Arduino framework could lead to problems after my changes. To address this, I had to rewrite and refactor parts of the code to ensure compatibility with builds compiled using the default PlatformIO Arduino framework.

I tested the updated implementation on a Heltec V3 board in the following scenarios:

  • default Arduino, bluetooth, light-sleep disabled
  • default Arduino, bluetooth, light-sleep enabled
  • default Arduino, bluetooth, light-sleep enabled, force disable support for EXT wake-up
  • customized Arduino, bluetooth, light-sleep disabled
  • customized Arduino, bluetooth, light-sleep enabled
  • customized Arduino, bluetooth, light-sleep enabled, force disable support for EXT wake-up
  • customized Arduino, bluetooth disabled, light-sleep enabled
  • customized Arduino, WiFi enabled, MQTT enabled, light-sleep enabled

There are a few topics that need discussion:

  1. Power Management (PM) Support for ESP32 requires a custom variant of the Arduino framework. I’ve forked it from Espressif’s GitHub repository and am currently hosting it under my personal GitHub account. If these changes are accepted, I’d like to move the repository under the meshtastic organization.
  2. In my opinion, re-enabling Bluetooth on the ESP32 without restarting is not possible, so NB PowerFSM state seems redundant. I'm open to restoring it if necessary, but I’d appreciate guidance on the intended logic behind this state.
  3. I updated the platformio.ini files for the hardware variants I was able to test. I shared customized builds with others, who confirmed they were working as expected.
  4. I updated the variant.h files for the hardware variants which do have 32kHz oscillator, according to Espressif docs, it helps with Bluetooth stability when dynamic light-sleep is enabled

Links to repositiories mentioned in 1) :

🤝 Attestations

  • I have tested that my proposed changes behave as described.
  • I have tested that my proposed changes do not cause any obvious regressions on the following devices:
    • Heltec (Lora32) V3
    • LilyGo T-Deck
    • LilyGo T-Beam
    • RAK WisBlock 4631
    • Seeed Studio T-1000E tracker card
    • Other (please specify below)

@m1nl m1nl force-pushed the esp_light_sleep_release branch 4 times, most recently from 9526383 to d8f2fcd Compare June 29, 2025 20:12
@fifieldt
Copy link
Member

fifieldt commented Jul 1, 2025

This is a ton of work, thank you very much

@fifieldt
Copy link
Member

fifieldt commented Jul 1, 2025

If you have time .... I "maintain" the GPS code - just wondering what the impact might be there or whether we might need further extensions?

@m1nl
Copy link
Contributor Author

m1nl commented Jul 1, 2025

AFAIK dynamic light-sleep in ESP32 platform makes the chip light-sleep when FreeRTOS waits for an event or there is an explicit vTaskDelay call (=~ sleep in Arduino). So most of the functionality shouldn't be impacted as Arduino runs all the code in one loop, which waits different intervals depending on the type of thread (using vTaskDelay under the hood). However, for critical modules or core functionality I added an explicit call to PowerFSM.trigger to exit LS state and allow for at least 300ms of uninterrupted processing time; another approach is to use "preflightSleepObserver" which prevents entering LS state if there is some work pending (like TX queue is not empty). I took a look at GPS code and I think we should exit LS state when we change state to GPS_ACTIVE and prevent entering LS as long as the state doesn't change. I'll take a look at the code and propose some changes soon.

@m1nl
Copy link
Contributor Author

m1nl commented Jul 1, 2025

If there is a chance to have this feature merged, I'd appreciate getting some feedback on the additional repos to be hosted under meshtastic Github organization. As mentioned above, default Arduino framework provided by Espressif doesn't enable PM functionality at all (which is really bad IMO).

@m1nl
Copy link
Contributor Author

m1nl commented Jul 2, 2025

@fifieldt changes committed; device should not go into light-sleep when GPS position is requested (=module is in GPS_ACTIVE state). I'm unable to test as I'm on vacation right now.

@m1nl m1nl force-pushed the esp_light_sleep_release branch from 151def2 to a42c8f6 Compare July 3, 2025 17:41
@m1nl m1nl force-pushed the esp_light_sleep_release branch from a42c8f6 to 892f77b Compare July 12, 2025 11:44
@vidplace7 vidplace7 added the enhancement New feature or request label Jul 18, 2025
@m1nl m1nl force-pushed the esp_light_sleep_release branch from 892f77b to 4223701 Compare July 20, 2025 19:55
@m1nl m1nl force-pushed the esp_light_sleep_release branch from 4223701 to f58f5e5 Compare July 31, 2025 13:17
@m1nl
Copy link
Contributor Author

m1nl commented Aug 7, 2025

hi,

  1. Bumping my last question
    I'd appreciate getting some feedback on hosting additional repos under meshtastic Github organization. Default Arduino framework provided by Espressif doesn't enable PM functionality at all. I'm happy to move content of my repos to new ones. There are two repos to be created - arduino-esp32 and esp32-arduino-lib-builder

  2. I took a look at the code and it reminded me I did change the WiFi code a bit - I'm enabling power saving by default (regardless of powersaving setting in module options); I think I should revoke that change to ensure compatibility with older versions; let me know what you think

  3. I'd appreciate getting feedback on removing NB sleep state - as mentioned in the description for this PR 1) it doesn't work for ESP32 (no way to reenable BT after disabling it without reboot) 2) it doesn't make much sense for NRF platform. However I see it's used in Router so Bluetooth is disabled on NRF platform when device goes into this state - is this expected? IMO this creates some confusion as ESP32 FW behaves differently than NRF one.

@m1nl m1nl force-pushed the esp_light_sleep_release branch from 058e904 to 50151fc Compare August 9, 2025 21:06
src/sleep.cpp Outdated
#if defined(ARCH_ESP32) && HAS_WIFI && !HAS_TFT

#if defined(ARCH_ESP32) && !HAS_TFT
#ifdef HAS_WIFI
Copy link

Choose a reason for hiding this comment

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

#if HAS_WIFI

Copy link

@metala metala Aug 23, 2025

Choose a reason for hiding this comment

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

Would it also make sense to have the HAS_ESP32_DYNAMIC_LIGHT_SLEEP (and HAS_ESP32_PM_SUPPORT, HAS_32768HZ) as a bool as well, instead of just checking #ifdef ?

It's more broad discussion about convention. This has to be decided on project level I guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, I'll check these definitions and I can update them to bool. I see there is inconsistency about how we use HAS_XYZ flags in the project. I will create a separate PR for this.

@metala
Copy link

metala commented Aug 23, 2025

General critique about having a lot of assert(res == ESP_OK);:

While I've already make a boot loop with those asserts because of a wrong setting, the more concerning part is that assert() signifies an unrecoverable error and this should never happen, unless the system is in unstable/undefined state.

Surely some of the calls can fail in runtime or due to a wrong setting. Should we then crash the device? There is a known issue here that some ESP32 devices corrupt their device configuration when running on solar power. This not only increases the risk, but also cause the device to become irrecoverable, because of a wrong setting.

Example:
esp_sleep_enable_timer_wakeup() can return ESP_ERR_INVALID_ARG if value is out of range. Therefore if the sleep time (sleepLeft = config.power.ls_secs * 1000LL - sleepTime;) gets somehow messed due to a wrong ls_secs setting, the device might be hard to restore through the radio.

I'd recommend an approach that would fail in a recoverable way.

@metala
Copy link

metala commented Aug 23, 2025

/work/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pio/build/heltec-v2_1/firmware.elf section `.iram0.text' will not fit in region `iram0_0_seg'
/work/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: IRAM0 segment data does not fit.
/work/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: region `iram0_0_seg' overflowed by 776 bytes
collect2: error: ld returned 1 exit status

The linker fails on ESP32 (not S3).

[env:heltec-v3]
extends = esp32s3_base
platform_packages =
platformio/framework-arduinoespressif32 @ https://github.com/m1nl/arduino-esp32/archive/refs/tags/2.0.17+5ae9873e.tar.gz ; disable WiFi IRAM optimizations in ESP-IDF
Copy link

Choose a reason for hiding this comment

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

2.0.17 seems to be a downgrade from the current framework-arduinoespressif32 @ 3.20017.241212+sha.dcc1105b

Copy link
Contributor Author

@m1nl m1nl Aug 24, 2025

Choose a reason for hiding this comment

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

To be honest, I think Espressif’s versioning scheme is pretty misleading :) I spent several hours digging into it, so let me shed some light:

  1. If you check the release page you will see that current framework is based on Arduino v2.0.17, not v3.2.0; so this is a first red flag saying that the semantic version doesn't align with Arduino version in use - additionally, you'll see that release page mentions that Arduino framework is based on IDF v4.4.7 (which is already out of support).
  2. If you go to the PlatformIO Registry page for framework-arduinoespressif32, you'll see that release tag v3.20017.241212+sha.dcc1105b doesn't exist in GitHub. However if you trace hash dcc1105b, it points to this commit on branch release/v2.x.

What I did to create my custom repo:

  • I created a fork of lib-builder repo
  • Made release/v4.4 a default branch (to follow ESP-IDF version in use)
  • Fixed scripts to ensure build uses compatible versions of esp-dl, esp_littlefs, esp-rainmaker, esp-dsp and tinyusb (it looks they're not really doing automated builds - without these modifications build fails as it tries to pull latest commit from branches which are no longer compatible with ESP-IDF v4.4.x)
  • Changed sdkconfigs for esp32 platforms (to enable PM support)
  • Built the framework (./build.sh -i tags/v4.4.8 -A tags/2.0.17)
  • Created a fork of arduino-esp32 using branch release/v2.x as a main branch
  • Copied binary artifacts from esp32-arduino-lib-builder to arduino-esp32 repository and commited them (this is wrong IMO, but this was the way Espressif was releasing the framework)
  • Released arduino-esp32

For consistency, I stuck with the Arduino version number for naming. As for Espressif’s tag style (v3.20017.241212+sha.dcc1105b), I honestly have no idea what logic they’re following :)

I think the build process can be improved by creating a GitHub action which would combine binary output from esp32-arduino-lib-builder upon release of arduino-esp32; binary artifacts should be removed from arduino-esp32 repository.

@m1nl
Copy link
Contributor Author

m1nl commented Aug 24, 2025

/work/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pio/build/heltec-v2_1/firmware.elf section `.iram0.text' will not fit in region `iram0_0_seg'
/work/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: IRAM0 segment data does not fit.
/work/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: region `iram0_0_seg' overflowed by 776 bytes
collect2: error: ld returned 1 exit status

The linker fails on ESP32 (not S3).

This fails because the ESP32 platform has very limited IRAM available, and enabling PM capabilities pushes it beyond the limit. I’ll explore making the ESP-IDF components lighter to reduce memory usage without affecting Meshtastic functionality, but that may not be feasible. In the end, we may have to accept that dynamic light-sleep isn’t compatible with the ESP32.

In newer ESP-IDF versions (5.x compatible with Arduino 3.2.x), there’s a new sdkconfig option CONFIG_ESP_SYSTEM_ESP32_SRAM1_REGION_AS_IRAM, which makes more RAM available as IRAM, allowing the build to succeed. However, adopting this would be a breaking change for the project, since it requires moving from Arduino 2.x to 3.x. I’ve already created and tested such a build with the current Meshtastic version, but this shift is too significant to include in this PR.

@m1nl
Copy link
Contributor Author

m1nl commented Aug 24, 2025

General critique about having a lot of assert(res == ESP_OK);:

While I've already make a boot loop with those asserts because of a wrong setting, the more concerning part is that assert() signifies an unrecoverable error and this should never happen, unless the system is in unstable/undefined state.

Surely some of the calls can fail in runtime or due to a wrong setting. Should we then crash the device? There is a known issue here that some ESP32 devices corrupt their device configuration when running on solar power. This not only increases the risk, but also cause the device to become irrecoverable, because of a wrong setting.

Example: esp_sleep_enable_timer_wakeup() can return ESP_ERR_INVALID_ARG if value is out of range. Therefore if the sleep time (sleepLeft = config.power.ls_secs * 1000LL - sleepTime;) gets somehow messed due to a wrong ls_secs setting, the device might be hard to restore through the radio.

I'd recommend an approach that would fail in a recoverable way.

Understood, I'll check if there is any assertion, which relies on user settings and make them fail safe. However I'd recommend to keep assertions for all calls, which use predefined values (board configuration, etc.). IMO this is the only way to validate the changes with all devices during alpha testing phase. Let me know what you think.

@m1nl m1nl marked this pull request as draft August 26, 2025 15:30
@m1nl m1nl force-pushed the esp_light_sleep_release branch from 2815ae5 to 89b296d Compare August 26, 2025 16:17
@m1nl m1nl changed the base branch from master to develop August 26, 2025 16:18
@m1nl m1nl force-pushed the esp_light_sleep_release branch from 89b296d to 4b22d1d Compare August 26, 2025 21:30
@m1nl m1nl requested a review from metala August 26, 2025 21:32
@m1nl m1nl force-pushed the esp_light_sleep_release branch from 4b22d1d to 2e860a8 Compare August 26, 2025 21:49
@akohlsmith
Copy link

I'm testing this on ESP32C3 and so far the testing is looking very good.

My only nitpick is that I think that there should be a comment in variants/esp32c3/heltec_esp32c3/variant.h (and others) which mentions the HAS_32768HZ macro and that it should be uncommented if your board has a 32kHz crystal. One of the big issues with supporting so many platforms is that these definitions tend to get buried in code.

e.g.

// uncomment this is your board has a 32kHz crystal
//#define HAS_32768HZ 1

m1nl added 27 commits February 21, 2026 00:57
fix logging

make log entry more consistent
current value of 256 bytes is very dangerous and may negatively
impact the overall performance; we need to keep in mind that spiram
is very slow comparing to regular RAM and many things may just fail;
in my case, dynamic light-sleep made the device unoperational; spiram
can be used to offload some costly allocations but this is not the
right way to handle that
@m1nl m1nl force-pushed the esp_light_sleep_release branch from d2f8cfb to 1cdb65d Compare February 20, 2026 23:59
@m1nl
Copy link
Contributor Author

m1nl commented Feb 21, 2026

@thebentern I spent several days debugging why this PR made SPIRAM-capable devices extremely unstable when using latest develop branch. I was able to narrow the issue down to commit 5910cc2, which changes the SPIRAM allocation threshold to a very aggressive value of 256 bytes.

We need to keep in mind that SPIRAM is significantly slower than internal RAM, and moving small allocations there can easily cause subtle and hard-to-debug failures. In my case, enabling dynamic light sleep made the device completely unresponsive. With a 256-byte threshold, even very small buffers are pushed to SPIRAM, and this was causing Bluetooth to hang after sleep.

SPIRAM can absolutely be used to offload larger, memory-heavy allocations - but lowering the threshold this much is not the right approach. Based on my experience with ESP32 development, keeping it at 256 bytes will likely introduce instability in components like LittleFS and other subsystems that are already difficult to debug.

For reference:

  • The default value in ESP-IDF is 4096 bytes.
  • I tested 2048 bytes - which I still consider quite low - and the system appears stable.
  • 1024 bytes already causes Bluetooth to break after sleep, with the device hanging.

Given this, I strongly recommend against keeping the 256-byte threshold.


One comment regarding my changes conflicting with af518fb - I have also noticed that before and I added holds for these pins in my PR some time ago. However I don't understand, why we skip these pins when holds are disabled. We need to keep in mind that GPIO holds should be active only during sleep, so all of them have to be disabled after sleeping to avoid issues with normal GPIO usage.


The PR itself is now complete, and I’ve moved it to "ready" state. I’d really appreciate getting it merged.

Two things to consider:

  • my changes require ESP-IDF / Arduino with custom sdkconfig (https://github.com/m1nl/esp32-arduino-lib-builder) - would it be possible to move my repo under the meshtastic organization? I’ve added automated CI builds using GitHub Actions - everything is transparent now and no binary blobs are hosted in the repository.
  • I only enabled the dynamic light sleep framework on devices I was able to physically test

I'm happy to discuss all the changes, I'm available for a longer conversation on discord, just let me now.

@m1nl m1nl marked this pull request as ready for review February 21, 2026 00:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request triaged Reviewed by the team, has enough information and ready to work on now.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request]: Dynamic light sleep support for ESP32 platform