按 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/ 下所有源文件完整保留,无任何物理删除
334 lines
9.9 KiB
C
334 lines
9.9 KiB
C
// 吧唧模式按键驱动
|
||
// 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_button,BOOT由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
|