Skip to content

Conversation

@IoTThinks
Copy link

Hi all,
This PR is to allow Heltec v3 and v4 repeaters to save power down from 47mA to 9mA most of the time.
One 2500mAh can help to last 2 weeks for Heltec v3 and v4.

  • To use light sleep to achieve 9mA
  • No latency due to quicker wakeup
  • First boot: To wake up for 30s to send an advert at 18th second.
  • Light sleep: To wake up when receiving a LoRa packet and light sleep again after 5s.
  • Periodically wakeup: To wake up every 30 minutes to do some periodically tasks and and lightsleep again after 5s.
  • Do not sleep when WiFi is not off likely due to OTA via WiFi.
  • To reboot to out of OTA and back to powersaving mode.

First advert and periodically adverts (if set) are working.
OTA via WiFi works too.

The code should be applicable to all ESP32-S3 MCUs.
We can move the code upstream. However, at this stage, I put at Heltec v3 and v4 first.
Thanks a lot.

@SaschaKt
Copy link

Works like a charm. Should be standard on all esp repeaters or possible to turn on/off via commandline

@aqua
Copy link

aqua commented Nov 19, 2025

This is a substantial improvement, bringing the v4 down to about 4.9wH/day (extrapolated from a 15 minute bench test consuming 0.040Wh) at near-idle conditions (about a 30% cycle range on a 2600mAh 18650 cell); even light-sleep should bring ESP32-based boards close to compact/cheap solar viability in middling latitudes. It shouldn't be too hard to adapt for companions as well.

Quibbles, though: it's on by default and breaks the serial CLI on sleep. That makes configuring a new board fiddly; consider either a config latch to enable powersaving mode, or disabling it when serial input is detected.

@IoTThinks
Copy link
Author

IoTThinks commented Nov 19, 2025

@aqua I have just made some changes to make the board awake for 2 minutes (instead of 30s) in first boot / restart to support Repeater setup via USB.
This is to do first time repeater setup via USB cable.

I tested and think 2 minutes is enough.

Serial CLI in Meshcore app works for me as it will send LoRa packets which wake up the board and the serial connection.

@IoTThinks
Copy link
Author

Yes, we have a POWERSAVING_MODE flag in the ini file.
However, I think powersaving mode is almost required for solar ESP32 based repeaters.
Else they will need huge solar panels to survive.

Yes, we can detect if an UART connection is connected to delay the sleep.
Nice idea.

image

@SaschaKt
Copy link

SaschaKt commented Nov 19, 2025

This is a substantial improvement, bringing the v4 down to about 4.9wH/day (extrapolated from a 15 minute bench test consuming 0.040Wh) at near-idle conditions (about a 30% cycle range on a 2600mAh 18650 cell); even light-sleep should bring ESP32-based boards close to compact/cheap solar viability in middling latitudes. It shouldn't be too hard to adapt for companions as well.

Quibbles, though: it's on by default and breaks the serial CLI on sleep. That makes configuring a new board fiddly; consider either a config latch to enable powersaving mode, or disabling it when serial input is detected.

You can flash the official version, configure it and then update with the mod. Maybe there can be implemented a cli command to switch on/off powersaving for all esp repeaters!? Maybe it is not always needed. A powersaving for companion will not work in my opinion because of BT is needed.

@IoTThinks
Copy link
Author

IoTThinks commented Nov 19, 2025

@SaschaKt You now have 2 minutes instead of 30s to setup a Repeater via Serial USB.

When we refresh the Repeater Setup page, the esp32 will be resetted and we have another 2 minutes to do the settings again.

After that, it will sleep after 5s.

@SaschaKt
Copy link

@SaschaKt You now have 2 minutes instead of 30s to setup a Repeater via Serial USB.

When we refresh the Repeater Setup page, the esp32 will be resetted and we have another 2 minutes to do the settings again.

After that, it will sleep after 5s.

Hi Kevin. I noticed with the v3 and v4 that the clock go ahead, the longer the running time. A normal reboot via cli doesn't set the clock to the 2024 Standard time so it is not possible to sync the clock (because time can not be synched if it is correct or ahead). Can you modify the CLI "clock Sync" so that it is always syncing?

It is interesting why the clock goes ahead, do you have noticed?

@IoTThinks
Copy link
Author

Let me monitor.
Usually, I keep power it on / off so it will be back to 2024. And I can always sync as it is 2024.

Let me sync clock and let it run.

By right, the clock of esp32 is not accurate. It will drift...

@IoTThinks
Copy link
Author

IoTThinks commented Nov 20, 2025

@SaschaKt You check time using clock CLI, right?

image

@SaschaKt
Copy link

@SaschaKt You check time using clock CLI, right?

Screenshot_20251120_160731_MeshCore.jpg

I look at the Request Status page and in cli. The time goes ahead and cannot be synced. Only resetbutton on v3 sets it to 2024, the v4 is not in the near so I can't hit the reset button

@IoTThinks
Copy link
Author

@SaschaKt My Heltec v3's clock works fine for 2 hours. Let me monitor a day and see.
BTW, the clock in CLI is in UTC not in timezone.

How far is the drift in one day should I expect?

@IoTThinks
Copy link
Author

Let me explore and test to use a more accurate clock in light sleep.
To reduce the drift to around 20s/day instead of 3 minutes /day.

rtc_clk_slow_src_set(RTC_SLOW_FREQ_8MD256);

@mikecarper
Copy link

https://analyzer.letsme.sh/node/7E7662676F7F0850A8A355BAAFBFC1EB7B4174C340442D7D7161C9474A2C9400

This is cougar; probably the most busy node in all of MeshCore with roughly 110 neighbors. The granularity is 10s; there are savings to be found even here
image

@cparen
Copy link

cparen commented Nov 21, 2025

I just want to add my +1 to this change.

I've got my own private branch BLE companion that does lightsleep anytime the Bluetooth isn't connected, periodically waking up to allow reconnect. This would make it easier to keep my private branch in sync with main.


void sleep() override {
if (WiFi.getMode() == WIFI_MODE_NULL) { // WiFi is off ~ No active OTA, can go to sleep
enterLightSleep(1800); // To wake up every 30 minutes or when receiving a LoRa packet
Copy link

Choose a reason for hiding this comment

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

Can I suggest moving the sleep durations as a parameter, and in milliseconds?

This sort of policy seems like it would be application dependent -- e.g. 30 minutes makes sense for a repeater, but I'm experimenting with a power saving companion where 30 minutes is way too long. Moving it into a parameter to sleep() would let the 30 min duration remain in the repeater code.

Copy link
Author

Choose a reason for hiding this comment

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

The 30-minute is to wake up every 30 minutes to do periodically jobs like adverts.

Other times, it will go to sleep every 5s.

Copy link
Author

Choose a reason for hiding this comment

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

Ok let me make sleep period as a parameter of sleep().

Copy link
Author

@IoTThinks IoTThinks Nov 22, 2025

Choose a reason for hiding this comment

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

@cparen I have added "secs" for sleep().
I think sleep duration in seconds is good enough. Deepsleep uses seconds too.
Yes, it is better to keep the 30 minutes wake up in repeater code.

https://github.com/meshcore-dev/MeshCore/pull/1107/files#diff-10a0092562d43e5bb73a25560e519d6106a0d09b143204ed36ae90d8a3ad24aaR128

image image image

@bosco688
Copy link

Works great on Heltec v4

@IoTThinks
Copy link
Author

@cparen Yes, please create your own PR for companion.

I would love the powersaving for BLE companion.

@IoTThinks
Copy link
Author

@mikecarper The dashboard looks awesome.

Looks like an ISP operator even this is P2P network.

@mavericm1
Copy link

The message/packet is not lost that is correct but its pretty frustrating because to force the repeater to send what you've already sent you must send yet another message.

@IoTThinks
Copy link
Author

@mavericm1 I dont get what you mean.

You mean the repeater repeats twice or the repeater needs to wait the next slot to repeat?

@mikecarper
Copy link

Message gets stuck in the queue and doesn't go out until another event wakes it up. If I'm reading it correctly

@IoTThinks
Copy link
Author

Message gets stuck in the queue and doesn't go out until another event wakes it up. If I'm reading it correctly

@mikecarper I guess so too. So let me fix the potential issue: "Delay the sleep until no more dispatcher task".
I suspect due to high traffic and high inteference, the repeater delays the repeat and can not finish repeating in 5s.

@mavericm1
Copy link

Message gets stuck in the queue and doesn't go out until another event wakes it up. If I'm reading it correctly

@mikecarper I guess so too. So let me fix the potential issue: "Delay the sleep until no more dispatcher task". I suspect due to high traffic and high inteference, the repeater delays the repeat and can not finish repeating in 5s.

Yes what mike said is what i was describing here. #1107 (comment)

@IoTThinks
Copy link
Author

IoTThinks commented Dec 4, 2025

@mavericm1 @mikecarper Your testing and feedback are well appreciated.

How about this?
I will check if there is anything to send.
If no, sleep.
Else, it will awake for 5 more seconds.

I will test and see how it reacts first. Then let you test in the busy area.

image

Some friends here suggest me to implement this feature dynamically instead of hardcoded parameter.
So in CLI of repeater, we can do "set powersaving on" to turn on this feature.
The default is well, off.

What do you think?

@mavericm1
Copy link

@mavericm1 @mikecarper Your testing and feedback are well appreciated.

How about this? I will check if there is anything to send. If no, sleep. Else, it will awake for 5 more seconds.

I will test and see how it reacts first. Then let you test in the busy area.
image

Some friends here suggest me to implement this feature dynamically instead of hardcoded parameter. So in CLI of repeater, we can do "set powersaving on" to turn on this feature. The default is well, off.

What do you think?

Yes i think that is what makes the most sense that the dispatcher doesn't allow sleep until queue is empty

@IoTThinks
Copy link
Author

IoTThinks commented Dec 5, 2025

@mavericm1 I have tested. Looks OK. The getOutboundCount() is retrieved from Dispatcher.
Note the delay(100) is for debug log during testing only. This is to print the message properly before sleep.

In the photo, we can see when getOutboundCount=1, the sleep will be postponed from second 44 to second 49.
image

I put the pre-compiled bin file here for your testing.
They have the printout in the console in cause you want to check.
https://github.com/IoTThinks/EasySkyMesh/tree/main/firmware/Testing

Please note the logic:

  • First boot: To wake up for 2 minutes to send an advert at 18th second and allow Repeater Setup via UART cable.
  • Light sleep: To wake up when receiving a LoRa packet and light sleep again after 5s. To postpone 5 more seconds if there is pending outbound send.
  • Periodically wakeup: To wake up every 30 minutes to do some periodically tasks and and try to lightsleep again after 5s.

Please help to test and feedback.
Thanks a lot.

@IoTThinks
Copy link
Author

IoTThinks commented Dec 5, 2025

I added some more codes to postpone sleep to further 5s if there is pending outbound.

Please note, the power consumption via USB is always higher (around 17mA) as the board needs to power uart chip, led...
9mA will be consumed if you power and measure from battery cable.
Thanks a lot.

@dt267
Copy link

dt267 commented Dec 5, 2025

https://github.com/fdlamotte/MeshCore/blob/lightsleep_repeater/examples/simple_repeater/main.cpp

@IoTThinks
Copy link
Author

IoTThinks commented Dec 6, 2025

So good.
I'm in a correct track.

@mavericm1
Copy link

does the clock reboot firmware also contain the sleep code and dispatcher ?

@mavericm1
Copy link

I flashed the low power updated bin you provided and it appears to have fixed the problem from what I see

@IoTThinks
Copy link
Author

IoTThinks commented Dec 6, 2025

does the clock reboot firmware also contain the sleep code and dispatcher ?

Yes, the clock reboot firmware contains the sleep code and dispatcher.
This is for a friend want to reset the clock when reboot by Meshcore app and sync the time again.

@IoTThinks
Copy link
Author

I flashed the low power updated bin you provided and it appears to have fixed the problem from what I see

Wow, that's great. Thanks a lot for your feedback.

@hpux735
Copy link

hpux735 commented Dec 10, 2025

I wasn't able to reproduce any savings with a v4. Here's a screen shot of power analysis before:
image

And after applying this MR to a local branch.
ppk-20251210T222711

The quiescent current is 59mA in both cases. Do you have any suggestions? Or, perhaps, a bin file for the v4 that I could test to ensure that it's being compiled as you expect?

@SaschaKt
Copy link

I wasn't able to reproduce any savings with a v4. Here's a screen shot of power analysis before: image

And after applying this MR to a local branch. ppk-20251210T222711

The quiescent current is 59mA in both cases. Do you have any suggestions? Or, perhaps, a bin file for the v4 that I could test to ensure that it's being compiled as you expect?

I can tell you that it works perfect. This works only for repeaters, do you have flashed a repeater or do you try it for an companion? The bin file you can download under https://github.com/IoTThinks/MeshCore/releases/download/PowerSaving07/repeater-heltecv4-powersaving07.bin

@hpux735
Copy link

hpux735 commented Dec 10, 2025

I tried with the bin you linked to and got exactly the same results.

@SaschaKt
Copy link

SaschaKt commented Dec 10, 2025

I tried with the bin you linked to and got exactly the same results.

Have you tried another v4? Is the v4 maybe charging the battery if you measure over the Usb-C port (red led must be off)? It takes 2 minutes for the first sleep after switching on or rebooting

@hpux735
Copy link

hpux735 commented Dec 11, 2025

Ah, ok. that's the ticket. I didn't know you have to wait 2 minutes.
ppk-20251211T000014
ppk-20251210T235953

@IoTThinks
Copy link
Author

@hpux735 Yes, for first boot or after hard reset, have to wait 2 minutes to attempt to light sleep.
This is to allow Repeater Setup via UART (if any).

@IoTThinks
Copy link
Author

This is interesting.

We can convert all ESP32 boards from another mesh to become MeshCore repeaters at 9mA. Much lower than any NRF52 boards with BLE at 14+ mAh.

There is an unfair advantage of Meshcore repeater: It does not use BLE at all. Therefore, sleep is much easier and effective to reduce power.

Btw, you need to unplug the GPS module. This module uses 40mA!

last_millis = now;
}

// To get the current pending outbound packets at Dispatcher
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is prob better to have this method sig:
bool hasPendingWork()

Copy link
Author

Choose a reason for hiding this comment

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

Ok, let me rename it to hasPendingWork().


// To get the current pending outbound packets at Dispatcher
int MyMesh::getOutboundCount (uint32_t now) const {
return _mgr->getOutboundCount(0xFFFFFFFF);
Copy link
Collaborator

Choose a reason for hiding this comment

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

wondering whether this should also add 1 if Dispatcher::outbound != NULL ?

Copy link
Author

Choose a reason for hiding this comment

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

Why we need to add 1?
To include the current new packet in the count?

static char command[160];

#ifdef POWERSAVING_MODE
unsigned long lastActive = millis(); // mark last active time
Copy link
Collaborator

Choose a reason for hiding this comment

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

not sure, but prob safer to initialise lastActive in setup()... this assume the millis() function is ready and already set up. (might be, not sure)

rtc_clock.tick();

#ifdef POWERSAVING_MODE
if (millis() - lastActive > nextSleepinSecs * 1000) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

just wondering about the 49-day wrap around problem here. If you use the millisHasNowPassed() method, it takes this into account. (uses a 2's complement arithmetic trick)

Copy link
Author

Choose a reason for hiding this comment

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

Ok, let me take a look at millisHasNowPassed()
Thanks a lot.

Copy link
Author

Choose a reason for hiding this comment

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

I will revisit this PR this weekend.
Sorry for the delay.

@mikecarper
Copy link

@IoTThinks would this also work for a g2?

@IoTThinks
Copy link
Author

@mikecarper This PR won't work for Station G2 as the board uses non-RTC GPIO 48 for DIO1.
Non-RTC GPIOs can not handle Rising High for DIO1 reliably.
Let me revisit to support these boards.

image

RTC pins is up to GPIO21 only.
https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-reference/peripherals/gpio.html#gpio-summary
image

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.