Move button actions to per-widget config, add hold-for-global-action
Next/back button assignment moves from a frame-level "Button assignments" card into each widget's own gear-icon dialog, prefilled with a sane default at creation (photos/calendar -> advance/back, whiteboard/weather -> check_now, others -> none). At most one binding per (widget, button) now -- cross-widget execution order never mattered since each widget's action only touches its own state. New firmware capability: holding NEXT or BACK past a configurable duration (min 3s, server-side default) triggers a frame-wide action instead of the per-widget short-press one -- cycling saved layouts, refreshing all widgets, or freezing/unfreezing every photo widget (see app/global_actions.py). Firmware next/back checks gain the same hold-duration polling the combo button already had; the threshold comes from the previous wake's /frame/config fetch (persisted in NVS), since this wake's button decision happens before that request. Not done here: firmware/version.txt is intentionally left unbumped -- this hasn't been built or hardware-tested (no ESP-IDF toolchain in this environment), so no firmware release build should be triggered yet.
This commit is contained in:
+33
-1
@@ -68,6 +68,7 @@ Under **ESPresso Frame Configuration**:
|
||||
| `FRAME_COMBO_BUTTON_GPIO` | 1 | Menu/reset button GPIO (-1 to disable). Must be 0-7 |
|
||||
| `FRAME_COMBO_SOFT_RESET_HOLD_MS` | 3000 | How long the combo button must be held (then released) to soft-reset |
|
||||
| `FRAME_COMBO_FACTORY_RESET_HOLD_MS` | 15000 | How long the combo button must be held to factory-reset |
|
||||
| `FRAME_HOLD_ACTION_MS` | 3000 | **Fallback only** -- how long NEXT/BACK must be held to trigger a global action instead of a short press; see below |
|
||||
| `FRAME_BATTERY_ADC_GPIO` | -1 (disabled) | Battery voltage-divider ADC GPIO; see the Battery section below |
|
||||
| `FRAME_VBUS_SENSE_GPIO` | -1 (disabled) | USB-power sense GPIO for hiding the battery indicator on mains |
|
||||
|
||||
@@ -84,6 +85,34 @@ reflashing. The Kconfig value only applies before the device has ever
|
||||
successfully reached a configured server, or if the response doesn't
|
||||
include a valid interval.
|
||||
|
||||
### Holding NEXT/BACK for a global action
|
||||
|
||||
Past `FRAME_HOLD_ACTION_MS`, holding NEXT or BACK stops meaning "advance/
|
||||
back this widget" and instead triggers whatever frame-wide action (if
|
||||
any) is configured for that button's hold on the server's Configuration
|
||||
tab -- e.g. cycling through saved layouts (see
|
||||
`server/app/global_actions.py`). Fires immediately at the threshold,
|
||||
without waiting for release -- same convention as the combo button's
|
||||
factory-reset tier below.
|
||||
|
||||
Same "fallback only" caveat as `FRAME_SLEEP_INTERVAL_S` above, but with
|
||||
one more wrinkle: the server's actual `hold_duration_ms` (set on the
|
||||
Configuration tab, `GET /frame/config`'s response) can't be used for
|
||||
*this* wake's button decision -- that decision happens in `main.c`
|
||||
before WiFi even connects, but `/frame/config` isn't fetched until near
|
||||
the end of the wake cycle (after the image fetch, deliberately -- see
|
||||
`frame_client_run`'s own comment on why). So the device always acts on
|
||||
whatever value the *previous* wake fetched (persisted in NVS via
|
||||
`frame_config_set_hold_duration_ms`), falling back to
|
||||
`FRAME_HOLD_ACTION_MS` only before it's ever successfully fetched one.
|
||||
In practice this means changing the duration on the Configuration tab
|
||||
takes effect starting with the wake *after* the next one, not
|
||||
immediately.
|
||||
|
||||
Holding a button through the poll loop keeps the device awake and
|
||||
connected longer than a normal short-press wake -- the same tradeoff
|
||||
already accepted for the combo button's menu/reset holds below.
|
||||
|
||||
### WiFi fast-connect
|
||||
|
||||
After a successful home-WiFi connection, the device caches the AP's
|
||||
@@ -204,6 +233,8 @@ device (if asleep) and tells the server to advance to the next photo
|
||||
right away, regardless of the configured refresh interval -- no long hold
|
||||
needed, since advancing is easily reversible by pressing again. See
|
||||
`FRAME_NEXT_BUTTON_GPIO` above to change the pin or disable the feature.
|
||||
Holding it past `FRAME_HOLD_ACTION_MS` instead means something else
|
||||
entirely -- see "Holding NEXT/BACK for a global action" above.
|
||||
|
||||
Normal wakes and reboots never advance the photo on their own -- the
|
||||
server decides when to advance based on its own clock (see
|
||||
@@ -223,7 +254,8 @@ disable the feature.
|
||||
|
||||
If there's nothing to go back to yet (freshly provisioned, or you've
|
||||
already gone back as far as there is history), it's a no-op -- the
|
||||
current photo stays exactly as it was, no flash on the panel.
|
||||
current photo stays exactly as it was, no flash on the panel. Same
|
||||
long-hold caveat as the next-photo button above.
|
||||
|
||||
## Battery (XIAO ESP32-C6)
|
||||
|
||||
|
||||
@@ -188,6 +188,23 @@ menu "ESPresso Frame Configuration"
|
||||
FRAME_COMBO_SOFT_RESET_HOLD_MS so the two tiers can't be
|
||||
confused for each other.
|
||||
|
||||
config FRAME_HOLD_ACTION_MS
|
||||
int "Next/back hold-for-global-action duration (ms)"
|
||||
default 3000
|
||||
range 3000 10000
|
||||
help
|
||||
How long the NEXT or BACK button must be held before it
|
||||
triggers a frame-wide action (see server/app/global_actions.py
|
||||
-- e.g. cycling saved layouts) instead of that button's normal
|
||||
short-press behavior. Only a first-boot/never-connected
|
||||
fallback: once the device has fetched GET /frame/config at
|
||||
least once, the server's own Frame.hold_duration_ms (set on
|
||||
the Configuration tab) overrides this on every later boot --
|
||||
see wifi_provisioning.h's frame_config_get_hold_duration_ms.
|
||||
Floor matches the server's own minimum, so a long-held button
|
||||
never means something different depending on which value
|
||||
happened to apply.
|
||||
|
||||
config FRAME_BATTERY_ADC_GPIO
|
||||
int "Battery voltage-divider ADC GPIO (-1 to disable)"
|
||||
default -1
|
||||
|
||||
+40
-18
@@ -1,3 +1,5 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_sleep.h"
|
||||
@@ -5,6 +7,8 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "wifi_provisioning.h"
|
||||
|
||||
#include "back_button.h"
|
||||
|
||||
static const char *TAG = "back_button";
|
||||
@@ -14,6 +18,7 @@ static const char *TAG = "back_button";
|
||||
#define BACK_BUTTON_GPIO ((gpio_num_t)CONFIG_FRAME_BACK_BUTTON_GPIO)
|
||||
#define BACK_BUTTON_DEBOUNCE_MS 20
|
||||
#define BACK_BUTTON_DEBOUNCE_CHECKS 3
|
||||
#define BACK_BUTTON_POLL_MS 100
|
||||
|
||||
void back_button_init(void)
|
||||
{
|
||||
@@ -36,7 +41,7 @@ void back_button_init(void)
|
||||
esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown(1ULL << BACK_BUTTON_GPIO, ESP_GPIO_WAKEUP_GPIO_LOW);
|
||||
}
|
||||
|
||||
bool back_button_check(void)
|
||||
back_button_result_t back_button_check(void)
|
||||
{
|
||||
/* A quick tap can easily release before this runs (~0.4-0.5s into
|
||||
* boot, confirmed on hardware -- a live gpio_get_level() check here
|
||||
@@ -44,32 +49,49 @@ bool back_button_check(void)
|
||||
* status register is latched at the moment of waking and isn't
|
||||
* cleared until the next sleep entry, so it reliably reflects a tap
|
||||
* regardless of how quickly it was released. */
|
||||
if (esp_sleep_get_gpio_wakeup_status() & (1ULL << BACK_BUTTON_GPIO)) {
|
||||
ESP_LOGI(TAG, "Back-photo button caused this wake, going back");
|
||||
return true;
|
||||
}
|
||||
bool caused_wake = esp_sleep_get_gpio_wakeup_status() & (1ULL << BACK_BUTTON_GPIO);
|
||||
|
||||
/* Not a GPIO-wakeup-from-this-pin boot (normal timer wake, or a fresh
|
||||
* power-on/reflash) -- fall back to a live, debounced level check so
|
||||
* holding the button down while powering on also works. */
|
||||
if (gpio_get_level(BACK_BUTTON_GPIO) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < BACK_BUTTON_DEBOUNCE_CHECKS; i++) {
|
||||
vTaskDelay(pdMS_TO_TICKS(BACK_BUTTON_DEBOUNCE_MS));
|
||||
if (!caused_wake) {
|
||||
/* Not a GPIO-wakeup-from-this-pin boot (normal timer wake, or a
|
||||
* fresh power-on/reflash) -- fall back to a live, debounced level
|
||||
* check so holding the button down while powering on also
|
||||
* works. */
|
||||
if (gpio_get_level(BACK_BUTTON_GPIO) != 0) {
|
||||
return false; /* noise, not a real press */
|
||||
return BACK_BUTTON_NOT_PRESSED;
|
||||
}
|
||||
for (int i = 0; i < BACK_BUTTON_DEBOUNCE_CHECKS; i++) {
|
||||
vTaskDelay(pdMS_TO_TICKS(BACK_BUTTON_DEBOUNCE_MS));
|
||||
if (gpio_get_level(BACK_BUTTON_GPIO) != 0) {
|
||||
return BACK_BUTTON_NOT_PRESSED; /* noise, not a real press */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Back-photo button held during power-on, going back");
|
||||
return true;
|
||||
/* Confirmed pressed -- measure how long, same reasoning/pattern as
|
||||
* next_button_check(). */
|
||||
uint32_t hold_threshold_ms;
|
||||
if (frame_config_get_hold_duration_ms(&hold_threshold_ms) != ESP_OK) {
|
||||
hold_threshold_ms = CONFIG_FRAME_HOLD_ACTION_MS;
|
||||
}
|
||||
|
||||
uint32_t elapsed_ms = 0;
|
||||
while (gpio_get_level(BACK_BUTTON_GPIO) == 0) {
|
||||
if (elapsed_ms >= hold_threshold_ms) {
|
||||
ESP_LOGI(TAG, "Back button held past %ums, triggering global hold action",
|
||||
(unsigned)hold_threshold_ms);
|
||||
return BACK_BUTTON_HOLD;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(BACK_BUTTON_POLL_MS));
|
||||
elapsed_ms += BACK_BUTTON_POLL_MS;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Back-photo button short press (%ums), going back", (unsigned)elapsed_ms);
|
||||
return BACK_BUTTON_SHORT_PRESS;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void back_button_init(void) {}
|
||||
bool back_button_check(void) { return false; }
|
||||
back_button_result_t back_button_check(void) { return BACK_BUTTON_NOT_PRESSED; }
|
||||
|
||||
#endif
|
||||
|
||||
@@ -12,9 +12,23 @@
|
||||
*/
|
||||
void back_button_init(void);
|
||||
|
||||
typedef enum {
|
||||
BACK_BUTTON_NOT_PRESSED,
|
||||
/** A short press -- same immediate-response reasoning as the
|
||||
* next-photo button. */
|
||||
BACK_BUTTON_SHORT_PRESS,
|
||||
/** Held past the configured hold duration (see
|
||||
* wifi_provisioning.h's frame_config_get_hold_duration_ms) --
|
||||
* triggers a frame-wide action instead (see
|
||||
* server/app/global_actions.py, frame_client.h's FETCH_GLOBAL_BACK).
|
||||
* Fires immediately at the threshold, without waiting for release. */
|
||||
BACK_BUTTON_HOLD,
|
||||
} back_button_result_t;
|
||||
|
||||
/**
|
||||
* Returns whether the back-photo button is currently held, debounced with
|
||||
* a couple of short re-checks to reject noise. Same immediate-response
|
||||
* reasoning as the next-photo button -- no long hold-to-confirm gate.
|
||||
* Checks the back-photo button and, if it's pressed at all, blocks
|
||||
* polling its level until either it's released (BACK_BUTTON_SHORT_PRESS)
|
||||
* or the hold duration elapses (BACK_BUTTON_HOLD) -- same pattern as
|
||||
* next_button_check(). Evaluated once per wake.
|
||||
*/
|
||||
bool back_button_check(void);
|
||||
back_button_result_t back_button_check(void);
|
||||
|
||||
@@ -276,6 +276,14 @@ esp_err_t frame_wifi_connect_sta(const frame_config_t *cfg)
|
||||
typedef struct {
|
||||
bool reachable;
|
||||
uint32_t refresh_interval_s; /* CONFIG_FRAME_SLEEP_INTERVAL_S if absent/unparseable */
|
||||
/* How long NEXT/BACK must be held to trigger a global action instead
|
||||
* of a short press (see next_button.h/back_button.h) --
|
||||
* CONFIG_FRAME_HOLD_ACTION_MS if absent/unparseable (older server) or
|
||||
* unreachable. Persisted via frame_config_set_hold_duration_ms() for
|
||||
* the *next* boot's button-hold decision -- this fetch happens too
|
||||
* late in the cycle for its own boot's decision, see that function's
|
||||
* own doc comment. */
|
||||
uint32_t hold_duration_ms;
|
||||
char firmware_version[32]; /* server's uploaded OTA image version; empty if none/unreachable */
|
||||
/* Per-frame token the server pushes until this device has
|
||||
* authenticated with it once; empty when absent. Persisted via
|
||||
@@ -370,6 +378,7 @@ static frame_server_config_t fetch_frame_config(const frame_config_t *cfg)
|
||||
frame_server_config_t result = {
|
||||
.reachable = false,
|
||||
.refresh_interval_s = CONFIG_FRAME_SLEEP_INTERVAL_S,
|
||||
.hold_duration_ms = CONFIG_FRAME_HOLD_ACTION_MS,
|
||||
};
|
||||
result.firmware_version[0] = '\0';
|
||||
result.device_token[0] = '\0';
|
||||
@@ -419,6 +428,10 @@ static frame_server_config_t fetch_frame_config(const frame_config_t *cfg)
|
||||
ESP_LOGW(TAG, "'%s' response missing refresh_interval_s, using fallback %ds", url,
|
||||
(int)result.refresh_interval_s);
|
||||
}
|
||||
uint32_t hold_ms;
|
||||
if (json_extract_uint(body, "hold_duration_ms", &hold_ms)) {
|
||||
result.hold_duration_ms = hold_ms;
|
||||
}
|
||||
json_extract_string(body, "firmware_version", result.firmware_version, sizeof(result.firmware_version));
|
||||
json_extract_string(body, "device_token", result.device_token, sizeof(result.device_token));
|
||||
|
||||
@@ -443,12 +456,13 @@ static size_t http_read_fn(uint8_t *chunk, size_t chunk_size, void *ctx_)
|
||||
return n > 0 ? (size_t)n : 0;
|
||||
}
|
||||
|
||||
/* GETs /frame/image (FETCH_NORMAL), or POSTs /frame/advance or
|
||||
* /frame/back to force a move in either direction (FETCH_ADVANCE /
|
||||
* FETCH_BACK -- the next-photo / back-photo buttons). manage=true (the
|
||||
* manage button) appends &manage=1, telling the server to bake its
|
||||
* overlay into this same response instead of returning the bare
|
||||
* content -- see server/app/routers/device.py. Returning non-ESP_OK
|
||||
/* GETs /frame/image (FETCH_NORMAL), or POSTs /frame/advance, /frame/back,
|
||||
* /frame/global-next, or /frame/global-back to force a move/action
|
||||
* (FETCH_ADVANCE / FETCH_BACK -- a short press; FETCH_GLOBAL_NEXT /
|
||||
* FETCH_GLOBAL_BACK -- a held press, see next_button.h/back_button.h).
|
||||
* manage=true (the manage button) appends &manage=1, telling the server
|
||||
* to bake its overlay into this same response instead of returning the
|
||||
* bare content -- see server/app/routers/device.py. Returning non-ESP_OK
|
||||
* means the panel was never actually refreshed -- epd_display_stream()
|
||||
* (see epd7in3e.c) refuses to trigger a physical refresh on a short/
|
||||
* wrong-size stream, so a failure here always leaves the visible screen
|
||||
@@ -460,6 +474,10 @@ static esp_err_t fetch_and_display(const frame_config_t *cfg, fetch_action_t act
|
||||
path = "frame/advance";
|
||||
} else if (action == FETCH_BACK) {
|
||||
path = "frame/back";
|
||||
} else if (action == FETCH_GLOBAL_NEXT) {
|
||||
path = "frame/global-next";
|
||||
} else if (action == FETCH_GLOBAL_BACK) {
|
||||
path = "frame/global-back";
|
||||
}
|
||||
|
||||
char url[256];
|
||||
@@ -720,6 +738,11 @@ void frame_client_run(const frame_config_t *cfg, fetch_action_t action, bool sho
|
||||
report_battery(cfg, battery_percent);
|
||||
frame_server_config_t server_cfg = fetch_frame_config(cfg);
|
||||
sleep_seconds = server_cfg.reachable ? server_cfg.refresh_interval_s : CONFIG_FRAME_RETRY_INTERVAL_S;
|
||||
if (server_cfg.reachable) {
|
||||
/* For next boot's button-hold decision, not this one -- see
|
||||
* frame_config_get_hold_duration_ms()'s own doc comment. */
|
||||
frame_config_set_hold_duration_ms(server_cfg.hold_duration_ms);
|
||||
}
|
||||
|
||||
/* One-time identity handshake: the server pushes this frame's
|
||||
* own token until we've authenticated with it once. Persist it
|
||||
|
||||
@@ -9,14 +9,19 @@
|
||||
* Which photo-fetch behavior this wake cycle should use -- normally the
|
||||
* idempotent GET /frame/image (the server decides on its own whether to
|
||||
* advance, based on its configured refresh interval, so a plain
|
||||
* wake/reboot never skips a photo just by asking), or POST
|
||||
* /frame/advance / POST /frame/back to force a move in either direction
|
||||
* (the next-photo / back-photo buttons).
|
||||
* wake/reboot never skips a photo just by asking), POST /frame/advance /
|
||||
* POST /frame/back to force a move in either direction (a short press of
|
||||
* the next-photo / back-photo buttons), or POST /frame/global-next /
|
||||
* POST /frame/global-back to run whatever frame-wide action (if any) is
|
||||
* configured for a held press (see next_button.h/back_button.h's
|
||||
* *_HOLD result and app/global_actions.py server-side).
|
||||
*/
|
||||
typedef enum {
|
||||
FETCH_NORMAL,
|
||||
FETCH_ADVANCE,
|
||||
FETCH_BACK,
|
||||
FETCH_GLOBAL_NEXT,
|
||||
FETCH_GLOBAL_BACK,
|
||||
} fetch_action_t;
|
||||
|
||||
/**
|
||||
|
||||
+13
-4
@@ -40,12 +40,21 @@ void app_main(void)
|
||||
back_button_init();
|
||||
combo_button_init();
|
||||
|
||||
bool next_pressed = next_button_check();
|
||||
bool back_pressed = back_button_check();
|
||||
next_button_result_t next_result = next_button_check();
|
||||
back_button_result_t back_result = back_button_check();
|
||||
/* Next takes priority over back if somehow both read pressed at once
|
||||
* (e.g. both held through a power-on) -- an arbitrary but
|
||||
* deterministic tie-break, not expected to matter in practice. */
|
||||
fetch_action_t action = next_pressed ? FETCH_ADVANCE : back_pressed ? FETCH_BACK : FETCH_NORMAL;
|
||||
* deterministic tie-break, not expected to matter in practice. Same
|
||||
* priority applies whether the winning button resolved to a short
|
||||
* press or a hold. */
|
||||
fetch_action_t action;
|
||||
if (next_result != NEXT_BUTTON_NOT_PRESSED) {
|
||||
action = (next_result == NEXT_BUTTON_HOLD) ? FETCH_GLOBAL_NEXT : FETCH_ADVANCE;
|
||||
} else if (back_result != BACK_BUTTON_NOT_PRESSED) {
|
||||
action = (back_result == BACK_BUTTON_HOLD) ? FETCH_GLOBAL_BACK : FETCH_BACK;
|
||||
} else {
|
||||
action = FETCH_NORMAL;
|
||||
}
|
||||
/* Soft-resets or clears config + restarts internally for a medium/
|
||||
* long hold and never returns in those cases -- only returns here
|
||||
* for "not pressed" (false) or "quick press" (true, show the menu). */
|
||||
|
||||
+45
-18
@@ -1,3 +1,5 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_sleep.h"
|
||||
@@ -5,6 +7,8 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "wifi_provisioning.h"
|
||||
|
||||
#include "next_button.h"
|
||||
|
||||
static const char *TAG = "next_button";
|
||||
@@ -14,6 +18,7 @@ static const char *TAG = "next_button";
|
||||
#define NEXT_BUTTON_GPIO ((gpio_num_t)CONFIG_FRAME_NEXT_BUTTON_GPIO)
|
||||
#define NEXT_BUTTON_DEBOUNCE_MS 20
|
||||
#define NEXT_BUTTON_DEBOUNCE_CHECKS 3
|
||||
#define NEXT_BUTTON_POLL_MS 100
|
||||
|
||||
void next_button_init(void)
|
||||
{
|
||||
@@ -40,7 +45,7 @@ void next_button_init(void)
|
||||
esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown(1ULL << NEXT_BUTTON_GPIO, ESP_GPIO_WAKEUP_GPIO_LOW);
|
||||
}
|
||||
|
||||
bool next_button_check(void)
|
||||
next_button_result_t next_button_check(void)
|
||||
{
|
||||
/* A quick tap can easily release before this runs (~0.4-0.5s into
|
||||
* boot, confirmed on hardware -- a live gpio_get_level() check here
|
||||
@@ -48,32 +53,54 @@ bool next_button_check(void)
|
||||
* status register is latched at the moment of waking and isn't
|
||||
* cleared until the next sleep entry, so it reliably reflects a tap
|
||||
* regardless of how quickly it was released. */
|
||||
if (esp_sleep_get_gpio_wakeup_status() & (1ULL << NEXT_BUTTON_GPIO)) {
|
||||
ESP_LOGI(TAG, "Next-photo button caused this wake, forcing advance");
|
||||
return true;
|
||||
}
|
||||
bool caused_wake = esp_sleep_get_gpio_wakeup_status() & (1ULL << NEXT_BUTTON_GPIO);
|
||||
|
||||
/* Not a GPIO-wakeup-from-this-pin boot (normal timer wake, or a fresh
|
||||
* power-on/reflash) -- fall back to a live, debounced level check so
|
||||
* holding the button down while powering on also works. */
|
||||
if (gpio_get_level(NEXT_BUTTON_GPIO) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NEXT_BUTTON_DEBOUNCE_CHECKS; i++) {
|
||||
vTaskDelay(pdMS_TO_TICKS(NEXT_BUTTON_DEBOUNCE_MS));
|
||||
if (!caused_wake) {
|
||||
/* Not a GPIO-wakeup-from-this-pin boot (normal timer wake, or a
|
||||
* fresh power-on/reflash) -- fall back to a live, debounced level
|
||||
* check so holding the button down while powering on also
|
||||
* works. */
|
||||
if (gpio_get_level(NEXT_BUTTON_GPIO) != 0) {
|
||||
return false; /* noise, not a real press */
|
||||
return NEXT_BUTTON_NOT_PRESSED;
|
||||
}
|
||||
for (int i = 0; i < NEXT_BUTTON_DEBOUNCE_CHECKS; i++) {
|
||||
vTaskDelay(pdMS_TO_TICKS(NEXT_BUTTON_DEBOUNCE_MS));
|
||||
if (gpio_get_level(NEXT_BUTTON_GPIO) != 0) {
|
||||
return NEXT_BUTTON_NOT_PRESSED; /* noise, not a real press */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Next-photo button held during power-on, forcing advance");
|
||||
return true;
|
||||
/* Confirmed pressed (either the wake cause, or debounced during
|
||||
* power-on) -- measure how long, same polling pattern as
|
||||
* combo_button.c's own hold-tier detection. Reads the last hold
|
||||
* duration the server reported (persisted from a previous cycle,
|
||||
* see frame_config_get_hold_duration_ms's own doc comment), falling
|
||||
* back to the Kconfig default before the device has ever fetched
|
||||
* one. */
|
||||
uint32_t hold_threshold_ms;
|
||||
if (frame_config_get_hold_duration_ms(&hold_threshold_ms) != ESP_OK) {
|
||||
hold_threshold_ms = CONFIG_FRAME_HOLD_ACTION_MS;
|
||||
}
|
||||
|
||||
uint32_t elapsed_ms = 0;
|
||||
while (gpio_get_level(NEXT_BUTTON_GPIO) == 0) {
|
||||
if (elapsed_ms >= hold_threshold_ms) {
|
||||
ESP_LOGI(TAG, "Next button held past %ums, triggering global hold action",
|
||||
(unsigned)hold_threshold_ms);
|
||||
return NEXT_BUTTON_HOLD;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(NEXT_BUTTON_POLL_MS));
|
||||
elapsed_ms += NEXT_BUTTON_POLL_MS;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Next-photo button short press (%ums), forcing advance", (unsigned)elapsed_ms);
|
||||
return NEXT_BUTTON_SHORT_PRESS;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void next_button_init(void) {}
|
||||
bool next_button_check(void) { return false; }
|
||||
next_button_result_t next_button_check(void) { return NEXT_BUTTON_NOT_PRESSED; }
|
||||
|
||||
#endif
|
||||
|
||||
@@ -12,10 +12,27 @@
|
||||
*/
|
||||
void next_button_init(void);
|
||||
|
||||
typedef enum {
|
||||
NEXT_BUTTON_NOT_PRESSED,
|
||||
/** A short press -- advancing a photo is low-stakes and should feel
|
||||
* immediate, so this fires the moment the button releases (or right
|
||||
* away for a wake-triggered press, once it's confirmed not a hold). */
|
||||
NEXT_BUTTON_SHORT_PRESS,
|
||||
/** Held past the configured hold duration (see
|
||||
* wifi_provisioning.h's frame_config_get_hold_duration_ms) --
|
||||
* triggers a frame-wide action instead (see
|
||||
* server/app/global_actions.py, frame_client.h's FETCH_GLOBAL_NEXT).
|
||||
* Fires immediately at the threshold, without waiting for release --
|
||||
* same convention as combo_button.c's factory-reset tier. */
|
||||
NEXT_BUTTON_HOLD,
|
||||
} next_button_result_t;
|
||||
|
||||
/**
|
||||
* Returns whether the next-photo button is currently held, debounced with
|
||||
* a couple of short re-checks to reject noise. No long hold-to-confirm
|
||||
* gate -- advancing a photo is low-stakes and should feel immediate, so
|
||||
* this returns right away either way.
|
||||
* Checks the next-photo button and, if it's pressed at all (either what
|
||||
* caused this wake, per the latched wakeup-status register, or held
|
||||
* through a debounced power-on check), blocks polling its level until
|
||||
* either it's released (NEXT_BUTTON_SHORT_PRESS) or the hold duration
|
||||
* elapses (NEXT_BUTTON_HOLD, returned immediately, not waiting for
|
||||
* release). Evaluated once per wake.
|
||||
*/
|
||||
bool next_button_check(void);
|
||||
next_button_result_t next_button_check(void);
|
||||
|
||||
@@ -234,6 +234,29 @@ void frame_config_invalidate_last_display_crc32(void)
|
||||
nvs_close(handle);
|
||||
}
|
||||
|
||||
esp_err_t frame_config_get_hold_duration_ms(uint32_t *out)
|
||||
{
|
||||
nvs_handle_t handle;
|
||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = nvs_get_u32(handle, "hold_ms", out);
|
||||
nvs_close(handle);
|
||||
return err;
|
||||
}
|
||||
|
||||
void frame_config_set_hold_duration_ms(uint32_t ms)
|
||||
{
|
||||
nvs_handle_t handle;
|
||||
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
nvs_set_u32(handle, "hold_ms", ms);
|
||||
nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------
|
||||
* WiFi fast-connect cache
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
@@ -94,6 +94,27 @@ void frame_config_set_last_display_crc32(uint32_t crc32);
|
||||
*/
|
||||
void frame_config_invalidate_last_display_crc32(void);
|
||||
|
||||
/**
|
||||
* Returns the hold_duration_ms the server most recently reported via GET
|
||||
* /frame/config (see frame_client.c's fetch_frame_config/frame_client_run)
|
||||
* -- how long NEXT/BACK must be held before next_button_check()/
|
||||
* back_button_check() treat it as a hold-for-global-action instead of a
|
||||
* short press. Returns ESP_ERR_NVS_NOT_FOUND if the device has never
|
||||
* fetched one yet (fresh install/factory reset); caller should fall back
|
||||
* to CONFIG_FRAME_HOLD_ACTION_MS in that case.
|
||||
*
|
||||
* Deliberately a *previous* cycle's value: this cycle's own button
|
||||
* decision happens in main.c before WiFi even connects, but
|
||||
* /frame/config isn't fetched until near the end of frame_client_run
|
||||
* (after the image fetch, for connection-warmth/timeout reasons -- see
|
||||
* its own comment) -- so there's no same-cycle fresh value to use yet.
|
||||
*/
|
||||
esp_err_t frame_config_get_hold_duration_ms(uint32_t *out);
|
||||
|
||||
/** Persists the hold duration reported by the server, for the *next*
|
||||
* boot's button-hold decision to use. */
|
||||
void frame_config_set_hold_duration_ms(uint32_t ms);
|
||||
|
||||
/**
|
||||
* Returns this device's provisioning AP identity: a fixed SSID (from
|
||||
* Kconfig) and a password that's generated once on first use and persisted
|
||||
|
||||
Reference in New Issue
Block a user