Baji_Rtc_Toy/main/dzbj/dzbj_button.c
Rdzleo 672506e7c7 feat(rtc-only): Phase 1 - 通过 CONFIG_BAJI_BADGE_MODE 屏蔽电子吧唧模式
按 GSD 框架 .planning/milestones/digital_human_rtc/ 规划完成 Phase 1。
源代码全部保留,通过 Kconfig 开关 + CMakeLists 条件编译 + #ifdef 调用点保护
实现"吧唧模式不进固件但代码可恢复"。

## 核心变更

### Kconfig 开关(默认关闭)
- 新增 CONFIG_BAJI_BADGE_MODE(main/Kconfig.projbuild)
- sdkconfig.defaults 默认 =n
- =y 时恢复双模式(电子吧唧 + AI 对话)
- =n 时仅 AI 数字人 RTC 模式

### CMakeLists 剥离(剥离式不重写)
- 9 个 dzbj/ 吧唧专属 + 9 个 ui/screens/ 吧唧 UI 进入 if(CONFIG_BAJI_BADGE_MODE) 条件块
- 公共保留: dzbj/lcd.c, ai_chat_ui.c, sprite_demo.c, dual_gif_demo.c,
  bg_gif_demo.c, pages_pwm.c, dzbj_init.c, fatfs.c
- 修正 PLAN 漏判:dzbj_init/fatfs 公共化(AI 模式调用 dzbj_hw_display_init/DecodeImg)

### 调用点 #ifdef 保护
- application.cc: L20 include, L63-66 background_task, L536 device_mode 分支
- movecall_moji_esp32s3.cc: dzbj headers, init_spiffs_image_list extern,
  dzbj_boot_click_handler extern, device_mode_is_badge 分支, InitializeBadgeMode,
  InitializeBadgeModeButtons, mode_switch_combo 注册, device_mode_in_switch_suppress
- 保留公共 extern: ai_chat_screen_init, ai_chat_resume_animation, pwm_init

### 整体文件级 #ifdef 包裹
- dzbj/dzbj_button.c/h
- dzbj/sleep_mgr.c
- sleep_mgr/include/sleep_mgr.h

### 6 个文件显式 #include "sdkconfig.h"
- ESP-IDF 不会 force-include,必须手动 include 才能拿到 CONFIG_* 宏

## G7 验收双向编译

- =n 模式 build:  EXIT=0(数字人 RTC 单一形态)
- =y 模式 build:  EXIT=0(双模式恢复可用)

## 固件大小变化

| 段 | =n | =y | 节省 |
|----|-----|------|------|
| .text | 2.03 MB | 2.06 MB | 27 KB |
| .rodata | 2.48 MB | 3.87 MB | 1.39 MB |
| Total | 4.63 MB | 6.05 MB | 1.45 MB |

## GSD 文档(同时提交)

- .planning/milestones/digital_human_rtc/MILESTONE.md
- .planning/milestones/digital_human_rtc/ROADMAP.md
- .planning/milestones/digital_human_rtc/INTEL.md
- .planning/milestones/digital_human_rtc/phases/phase_01_kconfig_isolation/PLAN.md
- .planning/milestones/digital_human_rtc/phases/phase_01_kconfig_isolation/SIZE_REPORT.md
- .planning/milestones/digital_human_rtc/phases/phase_01_kconfig_isolation/BADGE_MODE_ISOLATION_MAP.md
- 编译大小原始数据: size_*.txt

## 已知事项

- =n 固件 4.63 MB 仍 > 4 MB 目标,Phase 2 调整分区 + Phase 3 物理移除图片资源解决
- main/dzbj/ 下所有源文件完整保留,无任何物理删除
2026-05-13 10:22:48 +08:00

334 lines
9.9 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.

// 吧唧模式按键驱动
// BOOT(GPIO0): iot_button由board类管理此模块处理业务分发
// KEY2(GPIO4): iot_button此模块管理支持单击/双击/长按
// 界面上下文状态机决定按键行为
#include "sdkconfig.h"
#ifdef CONFIG_BAJI_BADGE_MODE
#include "dzbj_button.h"
#include "device_mode.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "iot_button.h"
#include "lvgl.h"
#include "esp_lvgl_port.h"
#include "sleep_mgr/include/sleep_mgr.h"
#include "dzbj_ble.h"
#include "ble_transfer.h"
#include "pages.h"
#include "ui/ui.h"
#include "ui/screens/ui_ScreenHome.h"
#include "ui/screens/ui_ScreenImg.h"
#include "ui/screens/ui_ScreenPeiwang.h"
#include "ui/screens/ui_ScreenImageShar.h"
#include "ui/screens/ui_ScreenImageReception.h"
static const char *TAG = "DZBJ_BTN";
// === 界面上下文状态 ===
static btn_context_t current_ctx = BTN_CTX_HOME;
// === KEY2 iot_button 实例 ===
static button_handle_t key2_handle = NULL;
// === 外部UI函数声明 ===
extern void ui_ScreenImg_hide_delete_container(void);
extern bool flashlight_is_active(void);
extern uint8_t flashlight_get_saved_brightness(void);
extern void flashlight_exit(void);
extern void pwm_set_brightness(uint8_t percent);
// === 上下文管理 ===
void dzbj_button_set_context(btn_context_t ctx)
{
current_ctx = ctx;
ESP_LOGI(TAG, "按键上下文切换: %d", ctx);
}
btn_context_t dzbj_button_get_context(void)
{
return current_ctx;
}
// === 退出到Home的通用逻辑在独立任务中执行可调用vTaskDelay ===
static void go_home_task(void *arg) {
sleep_mgr_notify_activity();
// 退出手电筒
bool was_flashlight = flashlight_is_active();
uint8_t saved_brightness = 0;
if (was_flashlight) {
saved_brightness = flashlight_get_saved_brightness();
flashlight_exit();
vTaskDelay(pdMS_TO_TICKS(80));
}
if (lvgl_port_lock(100)) {
_ui_screen_change(&ui_ScreenHome, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenHome_screen_init);
lvgl_port_unlock();
}
current_ctx = BTN_CTX_HOME;
if (was_flashlight) {
vTaskDelay(pdMS_TO_TICKS(150));
pwm_set_brightness(saved_brightness);
}
vTaskDelete(NULL);
}
// === 退出配对/等待界面 → Home ===
static void exit_peiwang_task(void *arg) {
ESP_LOGI(TAG, "退出配对界面,关闭蓝牙");
dzbj_ble_stop();
sleep_mgr_notify_activity();
if (lvgl_port_lock(100)) {
_ui_screen_change(&ui_ScreenHome, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenHome_screen_init);
lvgl_port_unlock();
}
current_ctx = BTN_CTX_HOME;
vTaskDelete(NULL);
}
static void exit_transfer_wait_task(void *arg) {
ESP_LOGI(TAG, "退出传输等待,取消传输");
ble_transfer_cancel();
sleep_mgr_notify_activity();
if (lvgl_port_lock(100)) {
_ui_screen_change(&ui_ScreenHome, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenHome_screen_init);
lvgl_port_unlock();
}
current_ctx = BTN_CTX_HOME;
vTaskDelete(NULL);
}
// === KEY2 进入功能界面的任务 ===
static void key2_enter_peiwang_task(void *arg) {
ESP_LOGI(TAG, "KEY2单击开蓝牙 → 配对界面");
sleep_mgr_notify_activity();
dzbj_ble_start();
if (lvgl_port_lock(100)) {
_ui_screen_change(&ui_ScreenPeiwang, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenPeiwang_screen_init);
lvgl_port_unlock();
}
current_ctx = BTN_CTX_PEIWANG;
vTaskDelete(NULL);
}
static void key2_enter_reception_task(void *arg) {
ESP_LOGI(TAG, "KEY2双击接收模式 → 等待配对");
sleep_mgr_notify_activity();
ble_transfer_start_receive();
if (lvgl_port_lock(100)) {
_ui_screen_change(&ui_ScreenImageReception, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenImageReception_screen_init);
lvgl_port_unlock();
}
current_ctx = BTN_CTX_IMAGE_RECEPTION;
vTaskDelete(NULL);
}
static void key2_enter_share_task(void *arg) {
ESP_LOGI(TAG, "KEY2长按发送模式 → 等待配对");
sleep_mgr_notify_activity();
init_spiffs_image_list();
ble_transfer_start_send();
if (lvgl_port_lock(100)) {
_ui_screen_change(&ui_ScreenImageShar, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenImageShar_screen_init);
lvgl_port_unlock();
}
current_ctx = BTN_CTX_IMAGE_SHAR;
vTaskDelete(NULL);
}
// === KEY2 事件回调在esp_timer中执行不能vTaskDelay派发到独立任务 ===
static void key2_click_cb(void *handle, void *usr_data) {
// 模式切换重启后按键抑制
if (device_mode_in_switch_suppress()) {
ESP_LOGI(TAG, "模式切换按键抑制期忽略KEY2单击");
return;
}
switch (current_ctx) {
case BTN_CTX_HOME:
case BTN_CTX_IMG:
// 开蓝牙 → 配对界面
xTaskCreate(key2_enter_peiwang_task, "k2_pw", 3072, NULL, 5, NULL);
break;
case BTN_CTX_PEIWANG:
// 退出配对 → Home
xTaskCreate(exit_peiwang_task, "k2_exit", 3072, NULL, 5, NULL);
break;
case BTN_CTX_IMAGE_SHAR:
case BTN_CTX_IMAGE_RECEPTION:
// 取消传输 → Home
xTaskCreate(exit_transfer_wait_task, "k2_exit", 3072, NULL, 5, NULL);
break;
default:
// SHARING/RECEIVING/UPDATE 不响应
break;
}
}
static void key2_double_click_cb(void *handle, void *usr_data) {
if (device_mode_in_switch_suppress()) return;
switch (current_ctx) {
case BTN_CTX_HOME:
case BTN_CTX_IMG:
// 接收模式
xTaskCreate(key2_enter_reception_task, "k2_recv", 3072, NULL, 5, NULL);
break;
default:
break;
}
}
static void key2_long_press_cb(void *handle, void *usr_data) {
if (device_mode_in_switch_suppress()) return;
// 组合键保护如果BOOT同时按下说明是模式切换组合键跳过
if (gpio_get_level(PIN_BTN_BOOT) == 0) {
ESP_LOGI(TAG, "BOOT+KEY2组合键检测跳过KEY2长按业务");
return;
}
switch (current_ctx) {
case BTN_CTX_HOME:
case BTN_CTX_IMG:
// 发送模式
xTaskCreate(key2_enter_share_task, "k2_shar", 3072, NULL, 5, NULL);
break;
default:
break;
}
}
// === 初始化 ===
esp_err_t dzbj_button_init(void)
{
// KEY2 使用 iot_button支持单击/双击/长按)
button_config_t key2_cfg = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = 1200,
.short_press_time = 300, // 双击检测窗口300ms与按键版对齐
.gpio_button_config = {
.gpio_num = PIN_BTN_KEY2,
.active_level = 0, // 低电平有效
},
};
key2_handle = iot_button_create(&key2_cfg);
if (!key2_handle) {
ESP_LOGE(TAG, "KEY2 iot_button创建失败");
return ESP_FAIL;
}
// 注册KEY2事件
iot_button_register_cb(key2_handle, BUTTON_SINGLE_CLICK, key2_click_cb, NULL);
iot_button_register_cb(key2_handle, BUTTON_DOUBLE_CLICK, key2_double_click_cb, NULL);
iot_button_register_cb(key2_handle, BUTTON_LONG_PRESS_START, key2_long_press_cb, NULL);
ESP_LOGI(TAG, "按键初始化完成 (KEY2=GPIO%d iot_buttonBOOT由board类管理)", PIN_BTN_KEY2);
return ESP_OK;
}
// === BOOT+KEY2 组合键模式切换 ===
static void mode_switch_task(void *arg) {
ESP_LOGI(TAG, "执行模式切换...");
if (device_mode_is_badge()) {
ESP_LOGI(TAG, "吧唧模式 → AI模式");
device_mode_set(DEVICE_MODE_AI);
} else {
ESP_LOGI(TAG, "AI模式 → 吧唧模式");
device_mode_set(DEVICE_MODE_BADGE);
}
vTaskDelete(NULL);
}
static void mode_switch_combo_cb(void *handle, void *data) {
if (gpio_get_level(PIN_BTN_KEY2) == 0) {
ESP_LOGI(TAG, "BOOT+KEY2组合键2秒触发模式切换");
xTaskCreate(mode_switch_task, "mode_sw", 2048, NULL, 5, NULL);
}
}
void dzbj_button_register_mode_switch_combo(void *boot_handle)
{
if (!boot_handle) return;
button_event_config_t evt_cfg = {
.event = BUTTON_LONG_PRESS_START,
.event_data = {
.long_press = {
.press_time = 2000,
},
},
};
esp_err_t ret = iot_button_register_event_cb((button_handle_t)boot_handle, evt_cfg, mode_switch_combo_cb, NULL);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "BOOT+KEY2组合键模式切换注册成功(2000ms)");
} else {
ESP_LOGE(TAG, "组合键注册失败: %s", esp_err_to_name(ret));
}
}
// === BOOT 单击处理(根据上下文分发) ===
void dzbj_boot_click_handler(void)
{
if (device_mode_in_switch_suppress()) {
ESP_LOGI(TAG, "模式切换按键抑制期忽略BOOT单击");
return;
}
bool screen_was_off = sleep_mgr_is_screen_off();
if (screen_was_off) {
ESP_LOGI(TAG, "BOOT低功耗唤醒");
sleep_mgr_notify_activity();
return;
}
switch (current_ctx) {
case BTN_CTX_HOME:
// Home界面 BOOT单击无额外操作已在Home
sleep_mgr_notify_activity();
break;
case BTN_CTX_IMG:
case BTN_CTX_SET:
// Img/Set界面 BOOT单击返回Home
xTaskCreate(go_home_task, "boot_home", 4096, NULL, 5, NULL);
break;
case BTN_CTX_PEIWANG:
// 配对界面 BOOT单击关蓝牙 → Home
xTaskCreate(exit_peiwang_task, "boot_exit", 3072, NULL, 5, NULL);
break;
case BTN_CTX_IMAGE_SHAR:
case BTN_CTX_IMAGE_RECEPTION:
// 传输等待界面 BOOT单击取消 → Home
xTaskCreate(exit_transfer_wait_task, "boot_exit", 3072, NULL, 5, NULL);
break;
case BTN_CTX_SHARING:
case BTN_CTX_RECEIVING:
case BTN_CTX_UPDATE:
// 传输中/更新中不响应
break;
}
}
#endif // CONFIG_BAJI_BADGE_MODE