Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b5026a5
[GPIO][P001] Added “Wake on Button Press” functionality.
chromoxdor Mar 20, 2026
b1abf4a
[P001] Switched to EXT1 for all devices for lowest power consumption
chromoxdor Mar 21, 2026
cedc78d
[P001] changed a line according to Tons suggestion
chromoxdor Mar 21, 2026
6f3544a
[GPIO] Setting moved to Hardware tab
chromoxdor Mar 22, 2026
9cdac4d
[GPIO] Setting moved to Hardware tab (removes unnecessary functions)
chromoxdor Mar 22, 2026
3b5d8c7
[GPIO] add check for gpiowake event
chromoxdor Mar 22, 2026
ac6ece0
[GPIO] fixed a c&p mistake
chromoxdor Mar 22, 2026
2365dff
[GPIO] adding details expansion + rules-keywords
chromoxdor Mar 23, 2026
3e6c299
[GPIO] adding all the missing bits and pieces.
chromoxdor Mar 28, 2026
1ca3eb5
[GPIO] adding all the missing bits and pieces part 2
chromoxdor Mar 28, 2026
90a2071
[GPIO] adding all the missing bits and pieces part 3
chromoxdor Mar 29, 2026
e21f2ca
[GPIO] adding all the missing bits and pieces part 4
chromoxdor Mar 29, 2026
22a1649
[GPIO] adding all the missing bits and pieces part 5
chromoxdor Mar 29, 2026
1a5be1f
[GPIO] adding all the missing bits and pieces part 6
chromoxdor Mar 29, 2026
77a6f2b
[GPIO] adding all the missing bits and pieces part 7
chromoxdor Mar 29, 2026
749d9a3
[GPIO] adding all the missing bits and pieces part 8
chromoxdor Mar 30, 2026
37eaa99
[GPIO] adding all the missing bits and pieces part 9
chromoxdor Mar 30, 2026
839d30e
[GPIO] adding all the missing bits and pieces part 10
chromoxdor Mar 30, 2026
50f9811
[GPIO] adding all the missing bits and pieces part 11
chromoxdor Mar 30, 2026
f74635f
Merge branch 'mega' into GPIO]-P001]-add-wake-from-sleep-option
chromoxdor Apr 10, 2026
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
44 changes: 44 additions & 0 deletions docs/source/Hardware/Hardware.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,47 @@ If the board supports PSRAM, it hides GPIO-26
(GPIO-26 is missing from the range, as it can not be used if PSRAM is present)

Overview of the GPIO pin mapping of ESP32-S2 (link to Espressif documentation): `ESP32-S2 Saola1 <https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/_images/esp32-s2_saola1-pinout.jpg>`_

--------------------
GPIO wake from sleep
--------------------

.. note::
This feature is only available on ESP32 devices.

Supported GPIO pins can be selected to wake the unit from deep sleep.
The signal level (HIGH or LOW) is configured globally and applies to **all** selected pins.
GPIO wake-up on newer devices supports per-pin trigger level configuration, but this comes at the cost of higher power consumption.
**TODO:** Integrate per-pin trigger level selection for GPIO wake-up.

When the unit wakes up from deep sleep, an event is generated with the GPIO number that caused the wakeup.
``"EVENT: System#GPIOWake=XX"``, where ``XX`` is the GPIO number.

Make sure to add a pull-up or pull-down resistor to the selected GPIO pin(s) when needed, to avoid false wakeups.
(some GPIO pins have internal pull-up or pull-down resistors, but not all of them, and the internal pull-up/down may not be sufficient for stable operation in all cases)
For **GPIO Wake-up** all internal pull-up/down resistors should work. No need to add them anywhere as this is done automatically.

.. note::

Depending on the ESP32 variant, two different wake-up mechanisms are used.

**EXT1 Wake-up (RTC)**

If ``SOC_PM_SUPPORT_EXT1_WAKEUP`` is available, EXT1 wake-up is used (e.g. ESP32 classic, ESP32-S2, ESP32-S3, ESP32-C6):

- Only works on RTC-capable pins
- Very low power (RTC domain)

**GPIO Wake-up**

On platforms without EXT1 support (e.g. ESP32-C3), GPIO wake-up is used:

- Works on more pins (not RTC-limited)
- Slightly higher power usage (HP domain)

**Summary**

- EXT1: preferred, low power
- GPIO wake-up: fallback for newer chips, more flexible but less efficient

The firmware automatically selects the correct method at compile time.
37 changes: 37 additions & 0 deletions docs/source/Plugin/P000_events.repl
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,30 @@
GPIO,15,1
endon

"
"
``System#BootCause``
Added: 2026-03-29
Triggered at boot time.
","

.. code-block:: none

on System#BootCause do
LogEntry,'Bootcause: %eventvalue1%'
endon

Possible bootcauses can be:

* 0: Manual Reboot
* 1: Cold Boot
* 2: Deep Sleep
* 3: Soft Reboot
* 10: External Watchdog
* 11: SW Watchdog
* 12: Exception
* 20: PWR Unstable

"
"
``System#BootMode``
Expand Down Expand Up @@ -104,6 +128,19 @@

See 'Boot Strapping Pins' documentation for the boot strapping pins for all ESP32-series chips.

"
"
``System#GPIOWake``
Added: 2026-03-29
Triggered at boot time.
","

.. code-block:: none

on System#GPIOWake do
LogEntry,'GPIO that caused the wakeup: %eventvalue1%'
endon

"
"
``System#Boot``
Expand Down
141 changes: 119 additions & 22 deletions src/_Plugin_Helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
#include "src/Helpers/Misc.h"
#include "src/Helpers/StringParser.h"

#if FEATURE_PIN_WAKEUP
# include "driver/rtc_io.h"
#endif

PluginTaskData_base *Plugin_task_data[TASKS_MAX] = {};

PluginTaskData_base *Plugin_task_data[TASKS_MAX] = {};

String PCONFIG_LABEL(int n) {
if (n < PLUGIN_CONFIGVAR_MAX) {
Expand Down Expand Up @@ -47,9 +50,9 @@ bool initPluginTaskData(taskIndex_t taskIndex, PluginTaskData_base *data) {
}

// 2nd heap may have been active to allocate the PluginTaskData, but here we need to keep the default heap active
# ifdef USE_SECOND_HEAP
#ifdef USE_SECOND_HEAP
HeapSelectDram ephemeral;
# endif // ifdef USE_SECOND_HEAP
#endif // ifdef USE_SECOND_HEAP


clearPluginTaskData(taskIndex);
Expand All @@ -61,16 +64,18 @@ bool initPluginTaskData(taskIndex_t taskIndex, PluginTaskData_base *data) {

#if FEATURE_PLUGIN_STATS
const uint8_t valueCount = getValueCountForTask(taskIndex);

for (size_t i = 0; i < valueCount; ++i) {
if (Cache.enabledPluginStats(taskIndex, i)) {
Plugin_task_data[taskIndex]->initPluginStats(i);
}
}
#endif
#endif // if FEATURE_PLUGIN_STATS
#if FEATURE_PLUGIN_FILTER
// TODO TD-er: Implement init

#endif
// TODO TD-er: Implement init

#endif // if FEATURE_PLUGIN_FILTER

} else {
delete data;
Expand All @@ -81,7 +86,7 @@ bool initPluginTaskData(taskIndex_t taskIndex, PluginTaskData_base *data) {

PluginTaskData_base* getPluginTaskData(taskIndex_t taskIndex) {
if (pluginTaskData_initialized(taskIndex)) {

if (!Plugin_task_data[taskIndex]->baseClassOnly()) {
return Plugin_task_data[taskIndex];
}
Expand All @@ -96,7 +101,6 @@ PluginTaskData_base* getPluginTaskDataBaseClassOnly(taskIndex_t taskIndex) {
return nullptr;
}


bool pluginTaskData_initialized(taskIndex_t taskIndex) {
if (!validTaskIndex(taskIndex)) {
return false;
Expand All @@ -105,29 +109,24 @@ bool pluginTaskData_initialized(taskIndex_t taskIndex) {
(Plugin_task_data[taskIndex]->_taskdata_pluginID == Settings.getPluginID_for_task(taskIndex));
}

String getPluginCustomArgName(int varNr) {
return getPluginCustomArgName(F("pc_arg"), varNr);
}
String getPluginCustomArgName(int varNr) { return getPluginCustomArgName(F("pc_arg"), varNr); }

String getPluginCustomArgName(const __FlashStringHelper * label, int varNr) {
return concat(label, varNr + 1);
}
String getPluginCustomArgName(const __FlashStringHelper *label, int varNr) { return concat(label, varNr + 1); }

int getFormItemIntCustomArgName(int varNr) {
return getFormItemInt(getPluginCustomArgName(varNr));
}
int getFormItemIntCustomArgName(int varNr) { return getFormItemInt(getPluginCustomArgName(varNr)); }

// Helper function to create formatted custom values for display in the devices overview page.
// When called from PLUGIN_WEBFORM_SHOW_VALUES, the last item should add a traling div_br class
// if the regular values should also be displayed.
// The call to PLUGIN_WEBFORM_SHOW_VALUES should only return success = true when no regular values should be displayed
// Note that the varNr of the custom values should not conflict with the existing variable numbers (e.g. start at VARS_PER_TASK)
void pluginWebformShowValue(taskIndex_t taskIndex, uint8_t varNr, const __FlashStringHelper * label, const String& value, bool addTrailingBreak) {
void pluginWebformShowValue(taskIndex_t taskIndex, uint8_t varNr, const __FlashStringHelper *label, const String& value,
bool addTrailingBreak) {
pluginWebformShowValue(taskIndex, varNr, String(label), value, addTrailingBreak);
}

void pluginWebformShowValue(taskIndex_t taskIndex,
uint8_t varNr,
uint8_t varNr,
const String& label,
const String& value,
bool addTrailingBreak) {
Expand Down Expand Up @@ -175,11 +174,12 @@ bool pluginOptionalTaskIndexArgumentMatch(taskIndex_t taskIndex, const String& s
return found_taskIndex == taskIndex;
}

bool pluginWebformShowGPIOdescription(taskIndex_t taskIndex,
const __FlashStringHelper * newline,
String& description)
bool pluginWebformShowGPIOdescription(taskIndex_t taskIndex,
const __FlashStringHelper *newline,
String & description)
{
struct EventStruct TempEvent(taskIndex);

TempEvent.String1 = newline;
return PluginCall(PLUGIN_WEBFORM_SHOW_GPIO_DESCR, &TempEvent, description);
}
Expand All @@ -199,10 +199,107 @@ int checkDeviceVTypeForTask(struct EventStruct *event) {
String dummy;

event->idx = -1;

if (PluginCall(PLUGIN_GET_DEVICEVTYPE, event, dummy)) {
return event->idx; // pconfig_index
}
}
}
return -1;
}

#if FEATURE_PIN_WAKEUP

void setupGpioWakeup(uint64_t ext1_mask) {

// disable wakeup completely if nothin is selected
if (ext1_mask == 0) {
#if FEATURE_PIN_WAKEUP == 1
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_EXT1);
#else
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_GPIO);
#endif
return;
}

#if FEATURE_PIN_WAKEUP == 1
// --- EXT1 Wakeup (supported on classic ESP32/S2/S3) ---
esp_sleep_ext1_wakeup_mode_t new_mode;

if (Settings.wakeOnHigh()) {
new_mode = ESP_EXT1_WAKEUP_ANY_HIGH;
} else {
#if CONFIG_IDF_TARGET_ESP32
new_mode = ESP_EXT1_WAKEUP_ALL_LOW;
#else
new_mode = ESP_EXT1_WAKEUP_ANY_LOW;
#endif
}

// Configure pull-ups/pull-downs for all pins in the mask
for (int gpio = 0; gpio < 64; ++gpio) {
if (ext1_mask & (1ULL << gpio)) {
gpio_num_t rtc_gpio = static_cast<gpio_num_t>(gpio);

if (!Settings.wakeOnHigh()) {
rtc_gpio_pullup_en(rtc_gpio);
rtc_gpio_pulldown_dis(rtc_gpio);
} else {
rtc_gpio_pullup_dis(rtc_gpio);
rtc_gpio_pulldown_en(rtc_gpio);
}
}
}


esp_sleep_enable_ext1_wakeup_io(ext1_mask, new_mode);

#elif FEATURE_PIN_WAKEUP == 2
// --- GPIO-based wakeup for chips without EXT1 (e.g. ESP32-C3) ---
esp_deepsleep_gpio_wake_up_mode_t gpio_mode =
Settings.wakeOnHigh() ? ESP_GPIO_WAKEUP_GPIO_HIGH : ESP_GPIO_WAKEUP_GPIO_LOW;

// Configure all pins in the mask
for (int gpio = 0; gpio < 64; ++gpio) {
if (ext1_mask & (1ULL << gpio)) {
gpio_num_t rtc_gpio = static_cast<gpio_num_t>(gpio);

// if (!Settings.wakeOnHigh()) {
// gpio_pullup_en(rtc_gpio);
// gpio_pulldown_dis(rtc_gpio);
// } else {
// gpio_pullup_dis(rtc_gpio);
// gpio_pulldown_en(rtc_gpio);
// }
}
}

esp_deep_sleep_enable_gpio_wakeup(ext1_mask, gpio_mode);

#endif
}

int8_t getWakeupGPIO() {
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();

#if FEATURE_PIN_WAKEUP == 1
if (cause != ESP_SLEEP_WAKEUP_EXT1) {
return -1;
}
uint64_t wakeMask = esp_sleep_get_ext1_wakeup_status();
#else
if (cause != ESP_SLEEP_WAKEUP_GPIO) {
return -1;
}
uint64_t wakeMask = esp_sleep_get_gpio_wakeup_status();
#endif

if (wakeMask == 0) {
return -1;
}

// Return first active GPIO
return __builtin_ctzll(wakeMask);
}

#endif // if FEATURE_PIN_WAKEUP
5 changes: 5 additions & 0 deletions src/_Plugin_Helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,9 @@ int getValueCountForTask(taskIndex_t taskIndex);
// Return pconfig_index
int checkDeviceVTypeForTask(struct EventStruct *event);

#if FEATURE_PIN_WAKEUP
void setupGpioWakeup(uint64_t ext1_mask);
int8_t getWakeupGPIO();
#endif // if FEATURE_PIN_WAKEUP

#endif // PLUGIN_HELPER_H
9 changes: 9 additions & 0 deletions src/src/CustomBuild/define_plugin_sets.h
Original file line number Diff line number Diff line change
Expand Up @@ -4545,3 +4545,12 @@ To create/register a plugin, you have to :
#endif // if !FEATURE_SPI && !FEATURE_I2C && !FEATURE_MODBUS && !FEATURE_CAN && !FEATURE_WRMBUS && !FEATURE_WIMBUS

#endif // CUSTOMBUILD_DEFINE_PLUGIN_SETS_H

#if defined(ESP32)
#if SOC_PM_SUPPORT_EXT1_WAKEUP
#define FEATURE_PIN_WAKEUP 1
#else // if no EXT1_WAKEUP support, we can use GPIO wakeup
// (TODO: -add per-pin trigger level control for GPIO wakeup)
#define FEATURE_PIN_WAKEUP 2
#endif
#endif
15 changes: 13 additions & 2 deletions src/src/DataStructs/SettingsStruct.h
Original file line number Diff line number Diff line change
Expand Up @@ -357,12 +357,20 @@ class SettingsStruct_tmpl
, int8_t& index_high
#endif
) const;

public:

PinBootState getPinBootState(int8_t gpio_pin) const;
void setPinBootState(int8_t gpio_pin, PinBootState state);

# if FEATURE_PIN_WAKEUP
// --- Wake GPIO mask handling ---
uint64_t getWakeGpioMask() const;
void setWakeGpioMask(uint64_t mask);
bool wakeOnHigh();
void setWakeOnHigh(bool value);
# endif // if FEATURE_PIN_WAKEUP

#if FEATURE_SPI
bool getSPI_pins(int8_t spi_gpios[3],
uint8_t spi_bus = 0,
Expand Down Expand Up @@ -589,7 +597,10 @@ class SettingsStruct_tmpl
int8_t SPI1_SCLK_pin = -1;
int8_t SPI1_MISO_pin = -1;
int8_t SPI1_MOSI_pin = -1;
unsigned int OLD_TaskDeviceID[N_TASKS - 8] = {0}; // UNUSED: this can be reused
uint32_t wakePin_bitmask_lLo = 0;
uint32_t wakePin_bitmask_lHi = 0;
uint32_t wakeOnHigh_ckd = 0;
unsigned int OLD_TaskDeviceID[N_TASKS - 11] = {0}; // UNUSED: this can be reused

// FIXME TD-er: When used on ESP8266, this conversion union may not work
// It might work as it is 32-bit in size.
Expand Down
Loading