Baji_Rtc_Toy/main/dzbj/sleep_mgr.c
Rdzleo 14776acb0a feat: 完成 AI/吧唧双模式完全隔离重构 + 触摸坐标日志 + SPIFFS 预烧录
## 核心变更

### 1. 双模式完全隔离 (Phase 2+4)
- 拆分 InitializeButtons() 为 InitializeBadgeModeButtons() + InitializeAiModeButtons()
- 构造函数按 device_mode 分支:吧唧模式不创建 PowerSaveTimer/BackgroundTask
- 吧唧模式不注册音量/故事按键回调,避免调用 GetAudioCodec() 崩溃
- GPIO0 由 iot_button 统一处理,dzbj_button 仅注册 KEY2(GPIO4)
- SetDeviceState() 中 background_task_ 空指针保护

### 2. 吧唧模式 BOOT 按键崩溃修复
- 新增 dzbj_boot_click_handler()(C 函数,避免 lvgl.h 与 display.h 冲突)
- 移植 dzbj 的唤醒屏幕/退出手电筒/返回Home 完整逻辑

### 3. esp_timer 阻塞 LVGL 渲染修复
- iot_button 回调在 esp_timer 任务中执行,vTaskDelay 会阻塞 lv_tick_inc
- 改为 xTaskCreate 派发到独立 FreeRTOS 任务,避免冻结 LVGL 渲染

### 4. 触摸坐标日志 + SPIFFS 预烧录
- esp_lvgl_port_touch.c 添加触摸坐标打印
- CMakeLists.txt 添加 spiffs_create_partition_image 自动打包 spiffs_image/

### 5. dzbj 模块文件新增
- device_mode: NVS 设备模式管理 (AI=0/吧唧=1)
- dzbj_button: GPIO4 KEY2 中断 + BOOT 点击处理
- dzbj_ble: BLE GATT 图传服务 (0x0B00)
- dzbj_battery: ADC 电池电压监测
- sleep_mgr: 10s 超时熄屏低功耗管理
- pages: 图片浏览/GIF播放/PWM亮度
- fatfs: SPIFFS 文件管理

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 10:23:04 +08:00

230 lines
7.1 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "../sleep_mgr/include/sleep_mgr.h"
#include "dzbj_button.h"
#include "pages.h"
#include "pages_pwm.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_lvgl_port.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lvgl.h"
#include "../ui/screens/ui_ScreenSet.h"
#include "lcd.h"
#include <stdio.h>
static const char *TAG = "SLEEP";
static bool sleep_enabled = false;
static bool screen_off = false;
static int64_t last_activity_us = 0;
static uint8_t saved_brightness = 50;
static const uint8_t DEFAULT_BRIGHTNESS = 50; // 默认亮度
static const uint8_t SLEEP_MODE_BRIGHTNESS = 10; // 休眠模式亮度
// 通知有用户活动
void sleep_mgr_notify_activity(void)
{
last_activity_us = esp_timer_get_time();
// 如果屏幕已关闭,立即唤醒
if (screen_off) {
screen_off = false;
// 恢复 LVGL 和触摸输入
if (lvgl_port_lock(100)) {
// 1. 启用所有输入设备(恢复触摸事件处理)
lv_indev_t *indev = lv_indev_get_next(NULL);
while (indev) {
lv_indev_enable(indev, true);
ESP_LOGI(TAG, "输入设备已启用");
indev = lv_indev_get_next(indev);
}
// 2. 恢复刷新定时器(恢复屏幕重绘)
lv_timer_t *refr_timer = _lv_disp_get_refr_timer(NULL);
if (refr_timer) {
lv_timer_resume(refr_timer);
ESP_LOGI(TAG, "LVGL 刷新定时器已恢复");
}
// 3. 强制刷新当前屏幕因为GRAM被清空为黑色需要重绘
lv_obj_invalidate(lv_scr_act());
ESP_LOGI(TAG, "已标记屏幕需要重绘");
lvgl_port_unlock();
}
// 延迟50ms等待LVGL完成至少一次重绘避免看到黑屏
vTaskDelay(pdMS_TO_TICKS(50));
// 恢复背光
pwm_set_brightness(saved_brightness);
ESP_LOGI(TAG, "屏幕唤醒,恢复亮度%d%%", saved_brightness);
}
}
// 按键活动回调BOOT和KEY2共用
static void btn_activity_cb(int gpio_num, void *usr_data)
{
sleep_mgr_notify_activity();
}
// 关闭屏幕(熄屏进入低功耗)
static void screen_turn_off(void)
{
if (screen_off) return;
// 保存当前亮度
saved_brightness = pwm_get_brightness();
if (saved_brightness == 0) {
saved_brightness = 50; // 防止保存到0值
}
// 暂停 LVGL 并禁用触摸输入
if (lvgl_port_lock(100)) {
// 1. 暂停刷新定时器(停止屏幕重绘)
lv_timer_t *refr_timer = _lv_disp_get_refr_timer(NULL);
if (refr_timer) {
lv_timer_pause(refr_timer);
ESP_LOGI(TAG, "LVGL 刷新定时器已暂停");
}
// 2. 禁用所有输入设备(停止触摸事件处理)
lv_indev_t *indev = lv_indev_get_next(NULL);
while (indev) {
lv_indev_enable(indev, false);
ESP_LOGI(TAG, "输入设备已禁用");
indev = lv_indev_get_next(indev);
}
lvgl_port_unlock();
}
// 清空LCD GRAM为黑色避免关闭背光后看到残影
lcd_clear_screen_black();
// 关闭背光
screen_off = true;
pwm_set_brightness(0);
ESP_LOGI(TAG, "屏幕已关闭(亮度=%d%%系统进入真正低功耗模式Light Sleep + LVGL暂停 + LCD GRAM清空", saved_brightness);
}
// 休眠管理任务
static void sleep_mgr_task(void *pvParameters)
{
while (1) {
uint32_t delay_ms = 500; // 默认轮询间隔 500ms
if (sleep_enabled) {
// 检查LVGL触摸活动屏幕开启时
if (!screen_off) {
if (lvgl_port_lock(50)) {
uint32_t inactive_ms = lv_disp_get_inactive_time(NULL);
lvgl_port_unlock();
// 屏幕开启状态:检测到新触摸(< 500ms立即更新活动时间
if (inactive_ms < 500) {
sleep_mgr_notify_activity();
}
}
// 检查超时熄屏
int64_t now = esp_timer_get_time();
int64_t elapsed_ms = (now - last_activity_us) / 1000;
if (elapsed_ms >= SLEEP_TIMEOUT_MS) {
screen_turn_off();
}
}
// 屏幕关闭状态:禁用触摸唤醒,只允许按键唤醒
// 如需启用触摸唤醒,取消注释以下代码:
/*
else {
if (lvgl_port_lock(50)) {
uint32_t inactive_ms = lv_disp_get_inactive_time(NULL);
lvgl_port_unlock();
// 检测到触摸(< 2000ms立即唤醒
if (inactive_ms < 2000) {
sleep_mgr_notify_activity();
ESP_LOGI(TAG, "触摸唤醒屏幕inactive=%lums", inactive_ms);
}
}
}
*/
}
vTaskDelay(pdMS_TO_TICKS(delay_ms));
}
}
void sleep_mgr_init(void)
{
last_activity_us = esp_timer_get_time();
// 注意BOOT按键由main.c的boot_btn_handler统一处理唤醒+退出手电筒+返回Home
// 这里只注册KEY2按键唤醒功能
dzbj_button_on_key2_press(btn_activity_cb, NULL);
xTaskCreate(sleep_mgr_task, "sleep_mgr", 3072, NULL, 3, NULL);
ESP_LOGI(TAG, "休眠管理器初始化完成(超时=%ds", SLEEP_TIMEOUT_MS / 1000);
}
// 更新ScreenSet界面的亮度UI控件
static void update_brightness_ui(uint8_t brightness)
{
if (!lvgl_port_lock(100)) {
return;
}
// 更新滑块位置
if (ui_SliderBrightness) {
lv_slider_set_value(ui_SliderBrightness, brightness, LV_ANIM_OFF);
}
// 更新亮度文本标签
if (ui_LabelBrightness) {
char buf[8];
snprintf(buf, sizeof(buf), "%d%%", brightness);
lv_label_set_text(ui_LabelBrightness, buf);
}
lvgl_port_unlock();
}
void sleep_mgr_set_enabled(bool enabled)
{
sleep_enabled = enabled;
if (enabled) {
last_activity_us = esp_timer_get_time();
// 进入休眠模式时将亮度调节到10%
pwm_set_brightness(SLEEP_MODE_BRIGHTNESS);
update_brightness_ui(SLEEP_MODE_BRIGHTNESS);
ESP_LOGI(TAG, "休眠模式已启用,亮度已调节至%d%%%ds无操作将熄屏",
SLEEP_MODE_BRIGHTNESS, SLEEP_TIMEOUT_MS / 1000);
} else {
// 禁用休眠模式时恢复到默认亮度50%
if (screen_off) {
screen_off = false;
pwm_set_brightness(DEFAULT_BRIGHTNESS);
update_brightness_ui(DEFAULT_BRIGHTNESS);
ESP_LOGI(TAG, "休眠模式已禁用,屏幕已恢复,亮度恢复到%d%%", DEFAULT_BRIGHTNESS);
} else {
pwm_set_brightness(DEFAULT_BRIGHTNESS);
update_brightness_ui(DEFAULT_BRIGHTNESS);
ESP_LOGI(TAG, "休眠模式已禁用,亮度恢复到%d%%", DEFAULT_BRIGHTNESS);
}
}
}
bool sleep_mgr_is_enabled(void)
{
return sleep_enabled;
}
bool sleep_mgr_is_screen_off(void)
{
return screen_off;
}