Files
Juli e82f0ac137 Require the button to confirm a knob wake
A knob turn still wakes the chip (the RTC can't tell gestures apart), but
the firmware now stays dark and checks the button: held while turning it
boots at once; otherwise the ring glows for 2.5 s waiting for a click and
goes back to deep sleep if none comes. Screen and Wi-Fi never start for
an accidental nudge.
2026-09-17 15:12:53 +02:00

90 lines
3.1 KiB
C

#include "power.h"
#include "board.h"
#include "display.h"
#include "leds.h"
#include "ui.h"
#include "driver/gpio.h"
#include "driver/rtc_io.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "esp_timer.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "power";
// Arm ext1 wake on the encoder pins. The S3 has one level mode for all selected
// pins, so pick it from the knob's resting state: pins that are high wake on
// low; if both rest low, wake on high. A single detent toggles both pins.
static void arm_knob_wakeup(void)
{
int pins[2] = {board->enc_a, board->enc_b};
uint64_t high = 0, low = 0;
for (int i = 0; i < 2; i++) {
rtc_gpio_init(pins[i]);
rtc_gpio_set_direction(pins[i], RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_dis(pins[i]);
rtc_gpio_pullup_en(pins[i]);
}
vTaskDelay(pdMS_TO_TICKS(5));
for (int i = 0; i < 2; i++) {
if (rtc_gpio_get_level(pins[i])) high |= 1ULL << pins[i];
else low |= 1ULL << pins[i];
}
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); // keeps the RTC pull-ups alive
if (high) esp_sleep_enable_ext1_wakeup_io(high, ESP_EXT1_WAKEUP_ANY_LOW);
else esp_sleep_enable_ext1_wakeup_io(low, ESP_EXT1_WAKEUP_ANY_HIGH);
ESP_LOGI(TAG, "wake on knob: A%d=%d B%d=%d", board->enc_a, !!(high & (1ULL << board->enc_a)),
board->enc_b, !!(high & (1ULL << board->enc_b)));
}
void power_off(void)
{
ESP_LOGI(TAG, "powering off");
ui_status("Off", "turn the knob to wake");
vTaskDelay(pdMS_TO_TICKS(700));
leds_set_mode(LED_OFF, 0);
vTaskDelay(pdMS_TO_TICKS(80)); // let the ring task push the dark frame
display_off();
esp_wifi_stop();
gpio_set_level(board->pwr_en, 0); // peripheral rail off (screen, ring, codec)
arm_knob_wakeup();
esp_deep_sleep_start();
}
#define CONFIRM_WINDOW_MS 2500
void power_confirm_wake(void)
{
if (esp_sleep_get_wakeup_cause() != ESP_SLEEP_WAKEUP_EXT1) return;
gpio_config_t btn = {.pin_bit_mask = 1ULL << board->enc_btn, .mode = GPIO_MODE_INPUT, .pull_up_en = 1};
gpio_config(&btn);
if (!gpio_get_level(board->enc_btn)) {
ESP_LOGI(TAG, "wake confirmed: button held while turning");
return;
}
leds_set_mode(LED_BREATHE, 0x302838); // soft hint: "click to wake"
int64_t deadline = esp_timer_get_time() / 1000 + CONFIRM_WINDOW_MS;
while (esp_timer_get_time() / 1000 < deadline) {
if (!gpio_get_level(board->enc_btn)) {
ESP_LOGI(TAG, "wake confirmed: click");
return;
}
vTaskDelay(pdMS_TO_TICKS(20));
}
ESP_LOGI(TAG, "knob nudge without the button; back to sleep");
leds_set_mode(LED_OFF, 0);
vTaskDelay(pdMS_TO_TICKS(80));
gpio_set_level(board->pwr_en, 0);
arm_knob_wakeup();
esp_deep_sleep_start();
}
void power_report_wakeup(void)
{
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause == ESP_SLEEP_WAKEUP_EXT1) ESP_LOGI(TAG, "woken by the knob");
else if (cause != ESP_SLEEP_WAKEUP_UNDEFINED) ESP_LOGI(TAG, "wakeup cause %d", cause);
}