Skip to content

[GPIO] Added “Wake on Button Press” functionality.#5519

Open
chromoxdor wants to merge 19 commits intoletscontrolit:megafrom
chromoxdor:GPIO]-P001]-add-wake-from-sleep-option
Open

[GPIO] Added “Wake on Button Press” functionality.#5519
chromoxdor wants to merge 19 commits intoletscontrolit:megafrom
chromoxdor:GPIO]-P001]-add-wake-from-sleep-option

Conversation

@chromoxdor
Copy link
Copy Markdown
Contributor

@chromoxdor chromoxdor commented Mar 20, 2026

I tried to integrate the feature, though I am not sure if the structure is fine with you since I more or less guestimated some things, but I am happy to receive your suggestions.
It also would help to do more testing on e.g. ESP32-S2,ESP32-C3,...

  • Add a checkbox in the switch plugin if the selected GPIO can wake the device from deep sleep.
  • tested on ESP32-S3 and ESP32-C6

ToDo:

  • More testing
  • Documentation
Bildschirmfoto 2026-03-29 um 19 15 36

I tried to integrate the feature, though I am not sure if the structure is fine with you since I more or less guestimated some things, but I am happy to receive your suggestions.
It also would help to do more testing on e.g. ESP32-S2,ESP32-C3,...

- Add a checkbox in the switch plugin if the selected GPIO can wake the device from deep sleep.
- tested on ESP32-S3 and ESP32-C6
@TD-er
Copy link
Copy Markdown
Member

TD-er commented Mar 20, 2026

I have to look at it tomorrow, as I was just about to go to bed.
I had not yet thought about those wake features a lot as there is more to it than just 'wake up'.
For example you also must define whether a state of a GPIO must remain locked (e.g. output pin remain high/low during deep sleep) and/or what power domains must remain active during deep sleep, but also deepsleep is typically intended for reducing power consumption.
If a pin must remain 'active' during deep sleep, will it then be pulled high or low? And does this pull resistor cause the current draw to increase significantly?
Is it possible to set on what edge the ESP must boot?

And maybe not the least... how must ESPEasy react on wake-up by GPIO?
Afterall the settings are not yet loaded when ESPEasy 'boots', so I guess we also need to store stuff in RTC???

Just some ideas that pop up about this.
So this begs the question whether it is a feature for the GPIO plugin or should it be part of the 'Hardware' tab where we now also set the boot states of a pin.

@chromoxdor
Copy link
Copy Markdown
Contributor Author

So this begs the question whether it is a feature for the GPIO plugin or should it be part of the 'Hardware' tab where we now also set the boot states of a pin.

Exactly my thought. But for simplicity, I was doing it in P001.
To the rest of your thoughts, I think I have some answers, but I also need to go to bed. It’s gotten too late... Good night

@TD-er
Copy link
Copy Markdown
Member

TD-er commented Mar 21, 2026

Oh and another idea...
You can see what the 'boot reason' is. One of them is when it is booted due to a GPIO pin and I think you can even derive which pin caused it.
Then there should be at least an event with the boot reason. Analog to what bootstate pins were active during boot (yes there is an event for it these days as I needed it for the Bhutan project :) )

- added "System#BootCause" and "System#GPIOWake" events
- moved gpio-wake related functions to _Plugin_Helper
@chromoxdor
Copy link
Copy Markdown
Contributor Author

chromoxdor commented Mar 22, 2026

All seems to work fine, but I really need help with storing the bitmask to SettingsStruct.
This is above my pay grade 🙂 Help is really appreciated here.

@chromoxdor chromoxdor changed the title [P001] Added “Wake on Button Press” functionality. [GPIO] Added “Wake on Button Press” functionality. Mar 22, 2026
@chromoxdor
Copy link
Copy Markdown
Contributor Author

All seems to work fine, but I really need help with storing the bitmask to SettingsStruct. This is above my pay grade 🙂 Help is really appreciated here.

@TD-er, can you help me with that when you have some time to spare?

@TD-er
Copy link
Copy Markdown
Member

TD-er commented Mar 24, 2026

And you think someone with his last diploma literally being one of tying his shoe laces will be able to? ;)

Anyway, given the ESP's with the largest amount of GPIO pins are already close to 64 pins, I guess we should at least add a 64bit uint to the settings struct.

This should be done at the end of the SettingsStruct, so other members won't shift and render the struct incompatible with stored ones.

Then you should add a get/set function. Use these as inspiration:

  bool getNetworkEnabled(ESPEasy::net::networkIndex_t index) const;

  void setNetworkEnabled(ESPEasy::net::networkIndex_t index, bool enabled);

The Arduino bitRead and bitWrite macros do not work on 64-bit ints, so you can choose to either add 2 uint32_t members to the settings, or use our own macros bitReadULL and bitWriteULL (see src/src/Helpers/Misc.h )

@tonhuisman
Copy link
Copy Markdown
Contributor

Instead of adding extra bytes to the settings struct, you could reuse a couple of bytes from the OLD_TaskDeviceID array, where each item is 4 bytes in size.

@chromoxdor
Copy link
Copy Markdown
Contributor Author

Then you should add a get/set function. Use these as inspiration:

Look at SettingsStruct.cpp and SettingsStruct.h. I already prepared everything but commented it for testing.

This should be done at the end of the SettingsStruct, so other members won't shift and render the struct incompatible with stored ones.

That a good starting point

@TD-er
Copy link
Copy Markdown
Member

TD-er commented Mar 24, 2026

Ton's argument is also a good one as OLD_TaskDeviceID is indeed a vector of 32-bit ints and right now not all are being used.
Also it is an ESP32-only feature, so you could make it a bit more like this:

#ifdef ESP32
  uint32_t wakePin_bitmask_lo{};
  uint32_t wakePin_bitmask_hi{};
  unsigned int  OLD_TaskDeviceID[N_TASKS - 10] = {0};  // UNUSED: this can be reused
#else
  unsigned int  OLD_TaskDeviceID[N_TASKS - 8] = {0};  // UNUSED: this can be reused
#endif

Also we can expect those values to be already set to 0 in existing settings.
When appending to the struct, this may not be guaranteed.

@tonhuisman
Copy link
Copy Markdown
Contributor

Should we reserve 2 bits per GPIO, so we can accommodate low and high activation?
There are functions to get and set 2 (and 3, 4, 8 and 16) bits to/from an UL (uint32_t) for convenience.

@TD-er
Copy link
Copy Markdown
Member

TD-er commented Mar 24, 2026

Should we reserve 2 bits per GPIO, so we can accommodate low and high activation? There are functions to get and set 2 (and 3, 4, 8 and 16) bits to/from an UL (uint32_t) for convenience.

There are 8 uin32_t left, so we can go upto 4 bits per GPIO.
If the code doesn't get too complex, we could combine it, but maybe it is better readable if we add it when it is actually being implemented.

Edit:
Hmm there are 22 left (N_TASKS - 10) so even more reason to keep the bitmask per uint32_t and not try to combine bitmasks and try to wrap them in multiple uint32's

@chromoxdor
Copy link
Copy Markdown
Contributor Author

Oh, oh, did I hear someone saying "low and high activation„? I will see how far I am coming. For now, I need to keep it as simple as possible, as I will probably very soon have no time at all for some months.

@TD-er
Copy link
Copy Markdown
Member

TD-er commented Mar 24, 2026

Oh, oh, did I hear someone saying "low and high activation„? I will see how far I am coming. For now, I need to keep it as simple as possible, as I will probably very soon have no time at all for some months.

Make sure you will get enough sleep before that will happen :)

@chromoxdor
Copy link
Copy Markdown
Contributor Author

#ifdef ESP32
  uint32_t wakePin_bitmask_lo{};
  uint32_t wakePin_bitmask_hi{};
  unsigned int  OLD_TaskDeviceID[N_TASKS - 10] = {0};  // UNUSED: this can be reused
#else
  unsigned int  OLD_TaskDeviceID[N_TASKS - 8] = {0};  // UNUSED: this can be reused
#endif

I tried this but getting:

src/src/Helpers/ESPEasy_checks.cpp: In function 'void run_compiletime_checks()':
src/src/Helpers/ESPEasy_checks.cpp:181:35: error: static assertion failed
  181 |   static_assert((232 + TASKS_MAX) == offsetof(SettingsStruct, OLD_TaskDeviceID), ""); // 32-bit alignment, so offset of 2 bytes.
      |                                   ^
src/src/Helpers/ESPEasy_checks.cpp:181:35: note: the comparison reduces to '(264 == 272)'

Any idea what I am doing wrong?

@tonhuisman
Copy link
Copy Markdown
Contributor

By conditionally inserting 2 variables before the address of the OLD_TaskDeviceID array the offset will change, so you would also have to make the assertion for that address conditional, adding the missing 8 bytes to that check (232 -> 240).

Alternatively, you can also make (only) these variables not conditional, but also include them for ESP8266, as the size of the SettingsStruct doesn't change it won't have an impact on the build-size. Then you can just adjust the offset assertion as noted above, and it will fit either build-type.

- using bitmask instead of bool for wakeonhigh
- refining docs
@chromoxdor
Copy link
Copy Markdown
Contributor Author

FIY, both submenus are now collapsed by default:
Bildschirmfoto 2026-03-29 um 18 44 05

@TD-er
Copy link
Copy Markdown
Member

TD-er commented Mar 29, 2026

Is wake-on-high vs. -low a global flag for all wake-up pin settings?
So it is either wake-up-on-X for all pins ?

Can you set multiple wake-up pins?

@chromoxdor
Copy link
Copy Markdown
Contributor Author

Is wake-on-high vs. -low a global flag for all wake-up pin settings? So it is either wake-up-on-X for all pins ?

Yes, as stated in the documentation I wrote ;)
For EXT1-Wakeup, this is not possible to set it individually.
For devices with GPIO-Wakeup, it is possible, but not implemented as it gets a bit too complex for now… at least for me.

Can you set multiple wake-up pins?

You can check any GPIO that is supported. Just added a picture in the first post.

- adding all what was suggested....
@TD-er
Copy link
Copy Markdown
Member

TD-er commented Mar 30, 2026

@chromoxdor
If you're going to use the VariousBits_3.unused_00 position, I will be using the bit position for VariousBits_3.unused_01 for a checkbox to enable/disable mDNS

@chromoxdor
Copy link
Copy Markdown
Contributor Author

Be my guest. :) I am done here (or at least I think I am)

@chromoxdor
Copy link
Copy Markdown
Contributor Author

chromoxdor commented Mar 30, 2026

BTW: Should I remove the Note for ESP32 devices? It seem to be a bit outdated
Bildschirmfoto 2026-03-30 um 09 54 23

@TD-er
Copy link
Copy Markdown
Member

TD-er commented Mar 30, 2026

BTW: Should I remove the Note for ESP32 devices? It seem to be a bit outdated Bildschirmfoto 2026-03-30 um 09 54 23

It is still valid for some ESP32-boards.
I guess we should check per ESP32-xx board to see what is the onboard LED pin and then act on it.

@chromoxdor
Copy link
Copy Markdown
Contributor Author

chromoxdor commented Mar 30, 2026

It is still valid for some ESP32-boards. I guess we should check per ESP32-xx board to see what is the onboard LED pin and then act on it.

But to keep track of this multitude of different boards, it would need an extra database since there is no existing one. And an automatic detection of the specific board is impossible.

Edit: And not everyone wants to use the onboard LED for that...

- added a warning to the docs
- added missing monitor keywords
@chromoxdor
Copy link
Copy Markdown
Contributor Author

chromoxdor commented Mar 30, 2026

@chromoxdor If you're going to use the VariousBits_3.unused_00 position, I will be using the bit position for VariousBits_3.unused_01 for a checkbox to enable/disable mDNS

When I am thinking about it, I would add at some point a checkbox for disabling internal pull-resistors to give the option to use exclusively external ones. But this will not happen in this PR anymore. The problem I had with the Espressif documentation is that it is sometimes inconclusive, not up to date, or hard to understand. So I had to research other sources as well until I got all the info I needed.

But you never know... It also depends on how long it takes until this PR gets merged :)
😉

- added a "disable pull resistors" checkbox for EXT1 Wakeup
@chromoxdor
Copy link
Copy Markdown
Contributor Author

chromoxdor commented Mar 30, 2026

@TD-er
Ok, I used VariousBits_3.unused_01 to disable internal RTC pull-resistors globally for EXT1 wakeup (ESP32 classic, S2,S3, C6).
For others like the C3 with only the GPIO wakeup option, it seems you can only disable this at compile time as I found here: https://forum.arduino.cc/t/esp32-c3-super-mini-deepsleep-doorsensor/1401895/35

uint32_t unused_00 : 1; // Bit 0
uint32_t unused_01 : 1; // Bit 1
uint32_t wakeOnHigh_ckd : 1; // Bit 0 //HardwarePage: used for wake on high or low
uint32_t diableWakePulls : 1; // Bit 1 //HardwarePage: used for for disabling pulls in general
Copy link
Copy Markdown
Contributor

@tonhuisman tonhuisman Mar 30, 2026

Choose a reason for hiding this comment

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

Is that a typo? disableWakePulls ?

Wasn't bit 1 already claimed? (not sure) (here)

struct {
uint32_t wakeOnHigh_ckd : 1; // Bit 0 //HardwarePage: used for wake on high or low
uint32_t diableWakePulls : 1; // Bit 1 //HardwarePage: used for for disabling pulls in general
uint32_t disableWakePulls : 1; // Bit 1 //HardwarePage: used for disabling internal pulls in general
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's still using bit 1... 🤔

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Well I already changed it in my code, so leave it.

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.

3 participants