Skip to content

Conversation

@IoTThinks
Copy link

@IoTThinks IoTThinks commented Jan 9, 2026

Hi all,
This PR is to add PowerSaving for all ESP32-based repeaters and NRF52-based repeaters including 4 boards with old SX1276.

  • To support PowerSaving for all ESP32-based repeaters and NRF52-based repeaters.
  • To add flag RADIO_SX1276 to support PowerSaving for SX1276. SX1276 uses DIO0 to wakeup MCU instead of DIO1
  • To expose LoRa pins at platformio.ini instead of header files
  • When BLE is enabled on NRF52, Powersaving will be skipped as same as when enabling WiFi on ESP32. This is to allow OTA in powersaving mode.

Credits:
(12 Jan 2026) In latest approach, I use hardware and software events instead of SuspendLoop and ResumeLoop to avoid deadlock issue.

  • I have adopted neat sleep/wakeup using SuspendLoop and ResumeLoop from Mr. Fschrempf and the check of HIGH-level DIO1 to prevent deadlock from Mr. 4np at NRF52 Repeater Powersaving. Thanks a lot.
  • However, I implemented our own style as same as PowerSaving for ESP32 to have a similar architecture for powersaving. The code should be simple, effective and consistent for all ESP32 and NRF52 boards.

Known issue:

  • ESP32-based repeaters may have the clock drift forward while NRF52-based repeaters may have the clock slowdown. This is due to inaccurate / slow clock during sleep. We will improve on this issue, however it may be a cosmetic bug for now.

Testing:
I have tested the change with main branch and achieve 10mA for ESP32-based repeaters and 6mA for NRF52-based repeaters.
Some kind friends have tested boards with SX1276 working.

After the merge to dev, I have compiled 29 boards and confirm no compilation error. I have tested the merged code and it worked for Heltec v3, v4 and RAK4631.

  • Heltec_v3_repeater Heltec_WSL3_repeater heltec_v4_repeater LilyGo_T3S3_sx1262_repeater
  • T_Beam_S3_Supreme_SX1262_repeater Tbeam_SX1262_repeater Station_G2_repeater Station_G2_logging_repeater
  • Xiao_C3_repeater Xiao_S3_WIO_repeater Heltec_Wireless_Tracker_repeater heltec_tracker_v2_repeater
  • RAK_4631_repeater SenseCap_Solar_repeater Heltec_t114_repeater t1000e_repeater
  • Xiao_nrf52_repeater LilyGo_T-Echo_repeater LilyGo_T-Echo-Lite_repeater ProMicro_repeater
  • WioTrackerL1_repeater Heltec_mesh_solar_repeater RAK_WisMesh_Tag_repeater ThinkNode_M1_repeater
  • Mesh_pocket_repeater
  • Heltec_v2_repeater LilyGo_T3S3_sx1276_repeater Tbeam_SX1276_repeater LilyGo_TLora_V2_1_1_6_repeater

Heltec v3 and RAK 4631 in PowerSaving mode
image image

This is a RAK4631 repeater with PowerSaving in action: Sleep at 6mA, wakeup when a LoRa packet comes, process it, wake up for 5s and go to sleep again.

Power.Saving.for.RAK4631.in.action.mp4

Copy link
Contributor

@fschrempf fschrempf left a comment

Choose a reason for hiding this comment

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

@IoTThinks Thanks for your work! As I have already posted a solution for the sleep implementation for NRF52 in #1238 before, would you mind rebasing your work onto the latest version of this PR?

uint8_t sd_enabled;
sd_softdevice_is_enabled(&sd_enabled); // To set sd_enabled to 1 if the BLE stack is active.

if (!sd_enabled) { // BLE is off ~ No active OTA, safe to go to sleep
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of checking for the softdevice, I would rather implement a generic mechanism to prevent the sleep which can be used in other cases too. See fe17894.

Copy link
Author

Choose a reason for hiding this comment

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

I would love to have consistent approach for ESP32 and NRF52.
In ESP32, we don't have "enterSleep" and "preventSleep".

I think we need to keep thing simple and effective.
This code works not only for OTA, but also for repeaters with WiFi or BLE active due to any reasons.

Copy link
Contributor

Choose a reason for hiding this comment

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

You seem to miss the point. The mentioned commit from my PR implements an easy and straight-forward way in the BaseBoard base class, that can be used by all board types. Your implementation is specific to ESP32 and can only be used from inside the board class.

Copy link
Author

Choose a reason for hiding this comment

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

I would have same "sleep" methods for ESP32 and NRF52.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok

Copy link

Choose a reason for hiding this comment

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

In #1238 I floated the idea to use safeToSleep. This could easily be implemented in a generic manner by adding it to MeshCore.h and overriding it in ESP32 as well as NRF52 (or even specific variants if necessary ). Pseudo patch:

diff --git a/src/MeshCore.h b/src/MeshCore.h
index b52ac21..a0ea11b 100644
--- a/src/MeshCore.h
+++ b/src/MeshCore.h
@@ -59,6 +59,11 @@ public:
   virtual void enterSleep(uint32_t secs) {
     if (!prevent_sleep) sleep(secs);
   }
+  virtual bool safeToSleep() { return true; }
   virtual void preventSleep() { prevent_sleep = true; }
   virtual uint32_t getGpio() { return 0; }
   virtual void setGpio(uint32_t values) {}

With your changes moving the DIO1 pin configuration from the header files into platform.io the safeToSleep method should probably only require implementation in the NRF52Board.h and ESP32Board.h. Pseudo patch:

diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h
index f187242..fac7fc3 100644
--- a/src/helpers/NRF52Board.h
+++ b/src/helpers/NRF52Board.h
@@ -16,6 +16,11 @@ public:
   virtual void reboot() override { NVIC_SystemReset(); }
   virtual void sleep(uint32_t secs) override;
   virtual void onRXInterrupt() override;
   virtual bool safeToSleep() override { return digitalRead(P_LORA_DIO_1) == LOW; }
 };

@IoTThinks IoTThinks marked this pull request as draft January 12, 2026 04:53
@IoTThinks
Copy link
Author

IoTThinks commented Jan 12, 2026

To fix the memory leaking for SoftwareTimer, to perform internal testing and to let the community to do beta test for NRF52-based repeaters.
I will push the fix to this PR this week.

@fschrempf
Copy link
Contributor

fschrempf commented Jan 12, 2026

Ok, let me try to put wakeupTimer in NRF52Board as you suggested.

I've already done that work in #1238. There is no reason to reimplement my suggestions. Please, just use my branch and add your commits on top.

@IoTThinks
Copy link
Author

Let me and some friends in busy-traffic areas test to use a simpler but more robust implementation without SuspendLoop, ResumeLoop and not touching setFlag for NRF52 repeaters.
I will push another implementation here in a few days.

@IoTThinks IoTThinks marked this pull request as ready for review January 13, 2026 15:05
@IoTThinks
Copy link
Author

I have pushed the change to implement power saving for NRF52 to use System-Idle On instead.

Copy link
Contributor

@fschrempf fschrempf left a comment

Choose a reason for hiding this comment

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

As you are somehow seeking to provide an alternative solution for #1238, can you at least mention in the description how your solution works and what benefits it has over the one in my PR? Thanks!

Also, can you please try to separate your changes logically into multiple commits and not chronologically stack each improvement as new commit on top of the previous one? As I mentioned before this is what git rebase --interactive and git push --force are made for.

I know that a lot of people do it like this, but this is not how git is intended to be used and it makes it impossible to have useful history in the end. And it leads you to copy code from my branch discarding the authorship (which can be seen as somewhat disrespectful among developers) instead of just doing git cherry-pick from my branch.

This is also something I wish the maintainers would take more into account when reviewing/merging PRs.

@IoTThinks IoTThinks force-pushed the MCdev-PowerSaving-for-all-ESP32-NRF52-repeaters branch from 7b0546a to c8e7727 Compare January 14, 2026 00:32
@IoTThinks IoTThinks force-pushed the MCdev-PowerSaving-for-all-ESP32-NRF52-repeaters branch from c8e7727 to 67a4398 Compare January 14, 2026 00:50
@IoTThinks
Copy link
Author

IoTThinks commented Jan 14, 2026

can you at least mention in the description how your solution works and what benefits it has over the one in my PR? Thanks!

I did mention in the PR. Thanks a lot.
image

However, in my yesterday proposed solution, I don't use these methods any more.
I have changed the solution approach to use safer hardware / software events instead.

@IoTThinks IoTThinks requested a review from fschrempf January 14, 2026 02:34
@IoTThinks
Copy link
Author

I have put in changes to implement light sleep for ESP32 without changing setFlag at all.

To put back missing code board->onAfterTransmit();
@IoTThinks IoTThinks force-pushed the MCdev-PowerSaving-for-all-ESP32-NRF52-repeaters branch from 9879bd4 to f988eb3 Compare January 14, 2026 07:31
Copy link
Contributor

@fschrempf fschrempf left a comment

Choose a reason for hiding this comment

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

I see you did a force-push, but the commits are still not separated properly. If you need help doing it, feel free to ask.

One more thing: You always begin your comments end commit subjects with "To ...". This doesn't really makes sense. Just leave out the "to" and state your changes in an imperative way.

uint8_t sd_enabled;
sd_softdevice_is_enabled(&sd_enabled); // To set sd_enabled to 1 if the BLE stack is active.

if (!sd_enabled) { // BLE is off ~ No active OTA, safe to go to sleep
Copy link
Contributor

Choose a reason for hiding this comment

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

Ok

@IoTThinks
Copy link
Author

I see you did a force-push, but the commits are still not separated properly. If you need help doing it, feel free to ask.

I did force-push to fix some simple missing codes.
Let the code being reviewed for a while, then we can start over clean.
I don't like rebase actually.
Startover from heavily reviewed and commented code is faster and cleaner.

And clean PRs are usually approved faster. :D

One more thing: You always begin your comments end commit subjects with "To ...". This doesn't really makes sense. Just leave out the "to" and state your changes in an imperative way.

Well, if you don't like then I will not include the "To..."
It is the style rather than sense.

@fschrempf
Copy link
Contributor

I did mention in the PR. Thanks a lot.

I know, thanks. If FOSS developers work on the same topic and take over work from each other they usually credit by taking the commits with the original authorship.

I have put in changes to implement light sleep for ESP32 without changing setFlag at all.

Ok, good idea. I think we can do it like this.

@fschrempf
Copy link
Contributor

It is the style rather than sense.

Ok, maybe. To me it sounds awkward and I've never seen this style elsewhere. But maybe that's just me...

@fschrempf
Copy link
Contributor

Let the code being reviewed for a while, then we can start over clean.

Ok, do as you like.

@IoTThinks
Copy link
Author

@fschrempf Thanks a lot for your comprehensive reviews.
I appreciate your great effort.

Since, the code for lightsleep for esp32 and NRF52 have changed.
I will let my boards to run a while this week to make sure they are stable.
And wait for reviews from other friends here.

Then I will make a final clean push.
Thanks a lot.

@4np
Copy link

4np commented Jan 17, 2026

@4np: Please help to monitor.
Thanks a lot.

Maybe Jorijn's polling based MeshCore Stats project could be useful for monitoring power consumption.

@IoTThinks
Copy link
Author

@4np
Hi,
I have added getIRQGpio and safeToSleep. Merged enterLightSleep to sleep.
Tested OK with Heltec v3 and RAK4631.
Able to compile for TBeam SX1262 and SX1276.

Please help to review. :)

@fschrempf
Copy link
Contributor

@IoTThinks Thanks for your work. Here is what I would change: fschrempf@2c2e9e5.

  • Proper use of OOP scheme for getIRQGpio() instead of preprocessor logic (RADIO_SX1276)
  • some clang-format fixups

@IoTThinks
Copy link
Author

Let me take a look.
Thanks a lot.

@4np
Copy link

4np commented Jan 18, 2026

Other than what @fschrempf said I have no other points 👍

@oltaco
Copy link
Contributor

oltaco commented Jan 19, 2026

Thanks for the work on this, what is the current status of this PR? I take it that this supercedes #1238 is that correct?

I must be blind, but I can't see where sleep actually gets called, maybe this PR is dependant on #1238? edit: If this is dependant on the #1238 can I request that the PRs be combined so that the overall change is easier to quantify?

What is the situation with clock drift, is that still an issue?

I noticed that this sets the lora interrupt pin as a wakeup source on ESP32 but seemingly not on NRF52, is there any reason for the difference?

@IoTThinks
Copy link
Author

@oltaco The parts for esp32 work solid.

For NRF52, still in testing and revising phase.

ESP32 and NRF52 are a bit different on how they sleep.

@IoTThinks IoTThinks marked this pull request as draft January 19, 2026 07:26
@4np 4np mentioned this pull request Jan 19, 2026
@fschrempf
Copy link
Contributor

@oltaco #1238 provides an alternative approach and additional fixes. I switched #1238 to draft now as we decided to first get this one ready. There is no dependency between the PRs.

The sleep approach for NRF52 is to put in in idle and wakeup on every interrupt event. This includes the systick interrupt every millisecond and the GPIO interrupt from the radio module. There is no need to explicitly enable the wakeup sources.

@IoTThinks
Copy link
Author

IoTThinks commented Jan 19, 2026

For NRF52, I have put in some improvements locally to avoid deadlock or livelock in high LoRa traffic.
Implementing sleep without touching setFlag method is a bit tricky.

Let me see how to simulate to generate around 20K packets / days.
To match the high traffic in Munich.
Thanks a lot.

@4np
Copy link

4np commented Jan 19, 2026

I just ran into an unresponsive repeater (RAK19007 / RAK4631 - Jan 14 build). Honestly, I am unsure if it's a genuine issue as it could just be that the battery drained, but after putting it back onto power I was also unable to access it. Only after reset. I'm going to uptake the latest changes and re-flash my spare repeater.

@fschrempf
Copy link
Contributor

For NRF52, I have put in some improvements locally to avoid deadlock or livelock in high LoRa traffic.

@IoTThinks Please share what exactly the problems are and how your changes look like so we can understand what is going on and how to make it work.

Implementing sleep without touching setFlag method is a bit tricky.

What do you mean with this?

@IoTThinks
Copy link
Author

IoTThinks commented Jan 19, 2026

For NRF52, I have committed a fix to poll wakeupPin correctly. 26339a7
This to fix the hang / unresponsiveness issue for repeater in high LoRa traffic location (5K sent packets and 20K received packets/day)

The old code may hang after a few days after logining into Remote Management and click around for a while.

@4np
Copy link

4np commented Jan 19, 2026

I guess I ran into that. Running the latest changes now.

@oltaco
Copy link
Contributor

oltaco commented Jan 19, 2026

@4np Hi, I have added getIRQGpio and safeToSleep. Merged enterLightSleep to sleep. Tested OK with Heltec v3 and RAK4631. Able to compile for TBeam SX1262 and SX1276.

Please help to review. :)

Yes, this is what I was getting at when I asked why you weren't setting the pin as a wake-up source like you were for esp32 😉

@IoTThinks
Copy link
Author

@4np Please help to monitor and update us.
Does your site have high LoRa traffic?
I'm creating a program sending LoRa storm of raw LoRa packets at the same frequency settings to test the stability of NRF52's sleep.

@oltaco They are similar now.
However, ESP32 and NRF52 have different MCU architecture. So sometimes, they dont have equivalent APIs.

@IoTThinks
Copy link
Author

It seems no visible time drift on NRF52 due to sleep.
Events from NRF52 are quite noisy (from DIO1, RTC, UART...), so __WFE sleeps and wakes up frequently. That causes RTC has some room to advance and keep the time correctness.
On NRF52, the main MCU pauses and wakeup frequently.

On ESP32, the main MCU sleeps all the way and the ULP (Ultra low processor) wakes and keep the time using slow RTC which causes time drift forward.
We may want to improve the time drift on ESP32 sleep.

@IoTThinks
Copy link
Author

@4np I have put in the getIRQDio and safeToSleep as you suggested.
I will check the GPS issue during sleep within a few days. I remember you mentioned it.

Thanks a lot.

@4np
Copy link

4np commented Jan 20, 2026

@IoTThinks , thank you for your work on this!

Does your site have high LoRa traffic?

Yeah there's quite a lot of traffic here.

ps. I don't see South East Asia on the map, perhaps it's worth considering adding an observer.

@IoTThinks
Copy link
Author

IoTThinks commented Jan 21, 2026

@4np Is your NRF52 repeater with new powersaving code still alive?

ps. I don't see South East Asia on the map, perhaps it's worth considering adding an observer.

Let me see how to add in.
Not much traffic in South East Asia yet.
The map looks nice.

Well, there are always interesting stuffs in MeshCore everyday to me.

@IoTThinks
Copy link
Author

GPS vs. Powersaving is interesting.
For the current PowerSaving, there is NO code to off the GPS module.
So even in light-sleep mode for ESP32 and System-on idle for NRF52, GPS module will still awake and work when MCU goes to sleep.
Yes, GPS module will continue to consume around 30mA.

When in sleep, the MCU will ignore the data from Serial1 (for GPS).
The data from Serial1 may be overflown.

So the repeaters will get stale GPS data and timing.
The repeaters will read GPS data and timing when there is a LoRa packet or every 30minutes.
The impact should be minimal as the repeaters never move and we dont need too accurate timing.

At least, Powersaving code will not break GPS sync.
Well, you or I can test the tracing if having time.

--
In next version, we can off GPS module if the GPS module has EN pin.

Heltec T114 has EN pin to on and off GPS module
image

RAK4631 does not have EN pin to on and off GPS module
image

@IoTThinks
Copy link
Author

To generate load, I use 2 Heltec v3 as LoRa spammers to replay the flood adverts every randomly 50ms to 6s.
Well, of course, repeaters will not forward is due to replay attack prevention.
However, the packets still reach repeaters and repeaters will have to process them. So, the test for high RX load is still valid.
The advert is very huge at 117 bytes. We may need to reduce the payload length to serve more devices.

For 14 hours, 16K packets. Yes, most of them are duplicated packets.
The RAK4631 with Powersaving 11.2.2 is still running fine.
image image

Our MeshCore friends in high traffic locations report "running fine" too.
Let me merge the remaining changes from reviewers about formatting and getIRQGpio.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants