fix(operation): EXT0 wakeup on BOOT button so 5-sec-hold reset works during sleep
Bug: the device only woke from deep sleep on a timer; pressing BOOT during sleep did nothing. The 5-second-hold reset only worked in the brief awake window during a poll, which made the documented "hold BOOT to reset" gesture appear broken to the user. Reported live 2026-05-09. Fix: arm EXT0 wakeup on PIN_BTN_RESET (active-low — BOOT is pulled-up on the dev board) at every esp_deep_sleep_start. After the press wakes the chip, setup() runs and the existing check_reset_button() handles the rest of the 5-second hold and triggers the NVS clear + reprovision. Mocks: esp_sleep.h gains gpio_num_t typedef + g_ext0_wakeup_pin/level globals so the native test can assert the call shape. Test: FW-RESET-WAKE pins the contract — every deep_sleep_start must arm EXT0 on PIN_BTN_RESET, level 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -316,5 +316,13 @@ void normal_operation_impl(const String& mac, HTTP& http, const String& url, Pre
|
|||||||
}
|
}
|
||||||
|
|
||||||
esp_sleep_enable_timer_wakeup(sleepMs * 1000ULL);
|
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();
|
esp_deep_sleep_start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
extern uint64_t g_sleep_us;
|
extern uint64_t g_sleep_us;
|
||||||
extern bool g_deep_sleep_started;
|
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.
|
// 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.
|
// 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;
|
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_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 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; }
|
inline esp_sleep_wakeup_cause_t esp_sleep_get_wakeup_cause() { return g_wakeup_cause; }
|
||||||
|
|||||||
@@ -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;
|
uint64_t g_sleep_us;
|
||||||
bool g_deep_sleep_started;
|
bool g_deep_sleep_started;
|
||||||
esp_sleep_wakeup_cause_t g_wakeup_cause;
|
esp_sleep_wakeup_cause_t g_wakeup_cause;
|
||||||
|
int g_ext0_wakeup_pin;
|
||||||
|
int g_ext0_wakeup_level;
|
||||||
|
|
||||||
// Globals for new mocks
|
// Globals for new mocks
|
||||||
int g_show_setup_qr_count;
|
int g_show_setup_qr_count;
|
||||||
@@ -68,6 +70,8 @@ void reset_state() {
|
|||||||
g_sleep_us = 0;
|
g_sleep_us = 0;
|
||||||
g_deep_sleep_started = false;
|
g_deep_sleep_started = false;
|
||||||
g_wakeup_cause = ESP_SLEEP_WAKEUP_TIMER; // default to timer wake unless a test sets cold
|
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_show_setup_qr_count = 0;
|
||||||
g_millis_value = 0;
|
g_millis_value = 0;
|
||||||
g_digital_read_value = HIGH; // button not pressed by default
|
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));
|
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
|
// 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
|
// 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
|
// 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_polls_at_15s_when_no_image_yet);
|
||||||
RUN_TEST(test_fw_first_image_bootstrap_clears_after_200);
|
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_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_fw12_ap_ssid_from_mac_aabbcc);
|
||||||
RUN_TEST(test_fw13_ap_ssid_from_real_mac);
|
RUN_TEST(test_fw13_ap_ssid_from_real_mac);
|
||||||
RUN_TEST(test_fw14_304_skips_epd_sleep);
|
RUN_TEST(test_fw14_304_skips_epd_sleep);
|
||||||
|
|||||||
Reference in New Issue
Block a user