diff --git a/src/operation.h b/src/operation.h index 61ce69d..7bafc61 100644 --- a/src/operation.h +++ b/src/operation.h @@ -316,5 +316,13 @@ void normal_operation_impl(const String& mac, HTTP& http, const String& url, Pre } esp_sleep_enable_timer_wakeup(sleepMs * 1000ULL); + // Wake on the BOOT button so the user-facing 5-second-hold reset works + // even during deep sleep. Without this, the button only does anything + // during the brief poll-and-paint window when the device is awake, and + // a full sleep cycle (default minutes) of holding does nothing. + // Pin is GPIO 0 (PIN_BTN_RESET); active-low because BOOT is pulled-up + // and shorts to ground on press. After EXT0 wakes the chip, setup() + // runs and check_reset_button() handles the remainder of the hold. + esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_BTN_RESET, 0); esp_deep_sleep_start(); } diff --git a/test/mocks/esp_sleep.h b/test/mocks/esp_sleep.h index df00c54..0e0c9ce 100644 --- a/test/mocks/esp_sleep.h +++ b/test/mocks/esp_sleep.h @@ -3,6 +3,8 @@ extern uint64_t g_sleep_us; extern bool g_deep_sleep_started; +extern int g_ext0_wakeup_pin; +extern int g_ext0_wakeup_level; // Mirror of the ESP-IDF wakeup-cause enum that the firmware actually checks. // Tests set g_wakeup_cause directly to simulate cold-boot vs timer-wake. @@ -17,6 +19,17 @@ typedef enum { extern esp_sleep_wakeup_cause_t g_wakeup_cause; +// gpio_num_t alias for the wakeup helper. The real header is part of +// esp-idf; for native tests we just use int. +typedef int gpio_num_t; +#ifndef GPIO_NUM_0 +#define GPIO_NUM_0 0 +#endif + inline void esp_sleep_enable_timer_wakeup(uint64_t us) { g_sleep_us = us; } +inline void esp_sleep_enable_ext0_wakeup(gpio_num_t pin, int level) { + g_ext0_wakeup_pin = pin; + g_ext0_wakeup_level = level; +} inline void esp_deep_sleep_start() { g_deep_sleep_started = true; } inline esp_sleep_wakeup_cause_t esp_sleep_get_wakeup_cause() { return g_wakeup_cause; } diff --git a/test/test_normal_operation/test_main.cpp b/test/test_normal_operation/test_main.cpp index c24d932..6addb74 100644 --- a/test/test_normal_operation/test_main.cpp +++ b/test/test_normal_operation/test_main.cpp @@ -35,6 +35,8 @@ int g_epd_draw_border_count, g_epd_draw_border_last_color, g_epd_draw_border_las uint64_t g_sleep_us; bool g_deep_sleep_started; esp_sleep_wakeup_cause_t g_wakeup_cause; +int g_ext0_wakeup_pin; +int g_ext0_wakeup_level; // Globals for new mocks int g_show_setup_qr_count; @@ -68,6 +70,8 @@ void reset_state() { g_sleep_us = 0; g_deep_sleep_started = false; g_wakeup_cause = ESP_SLEEP_WAKEUP_TIMER; // default to timer wake unless a test sets cold + g_ext0_wakeup_pin = -1; + g_ext0_wakeup_level = -1; g_show_setup_qr_count = 0; g_millis_value = 0; g_digital_read_value = HIGH; // button not pressed by default @@ -352,6 +356,17 @@ void test_fw_X_Claimed_response_clears_flag() { TEST_ASSERT_EQUAL(0, prefs.getInt(NVS_KEY_JUST_PROVISIONED, -1)); } +// FW-RESET-WAKE: every deep-sleep cycle must arm EXT0 wakeup on the BOOT +// button, otherwise holding it during sleep does nothing and the user- +// facing 5-second-hold reset only works during the brief awake window. +// Reported live by Matt 2026-05-09: held BOOT, device didn't reset. +void test_fw_deep_sleep_arms_ext0_button_wakeup() { + g_http_response_headers["X-Image-Id"] = "1"; + normal_operation_impl(String("mac"), http, String("url"), prefs); + TEST_ASSERT_EQUAL(PIN_BTN_RESET, g_ext0_wakeup_pin); + TEST_ASSERT_EQUAL(0, g_ext0_wakeup_level); // active-low; BOOT is pulled-up +} + // FW-FIRST-IMG-A: device has never received an image (img_id = -1) AND the // poll didn't deliver one (e.g. 204 because no images approved yet). Sleep // must be the 15s bootstrap interval, NOT whatever the server's schedule @@ -526,6 +541,7 @@ int main(int argc, char** argv) { RUN_TEST(test_fw_first_image_bootstrap_polls_at_15s_when_no_image_yet); RUN_TEST(test_fw_first_image_bootstrap_clears_after_200); RUN_TEST(test_fw_first_image_just_arrived_uses_server_interval); + RUN_TEST(test_fw_deep_sleep_arms_ext0_button_wakeup); RUN_TEST(test_fw12_ap_ssid_from_mac_aabbcc); RUN_TEST(test_fw13_ap_ssid_from_real_mac); RUN_TEST(test_fw14_304_skips_epd_sleep);