## 功能迁移清单(从 Dzbj_ESP32_S3_Key → Baji_Rtc_Toy) ### 1. 设备间BLE图片传输(GATT Client + 协议) - 新增 ble_transfer.c/h:发送方 GATT Client 扫描→连接→MTU协商→分包写入 - 接收方复用现有 GATT Server(IMAGE_WRITE 0x0B01),协议完全兼容 - 发送完成/失败自动跳转 Img 界面并关闭蓝牙 ### 2. APP传图显示 Update 界面 - 新增 ui_ScreenUpdate.c/h:更新进度界面(Gengxin背景 + Update_GIF动画) - dzbj_ble.c WRITE_EVT 中通过 ble_transfer_is_receiving() 区分 APP传图 vs 设备间传输 - APP传图 → ScreenUpdate,设备间传输 → ScreenReceiving ### 3. KEY2 按键功能入口(iot_button 单击/双击/长按) - KEY2 单击:开蓝牙 → Peiwang 配对界面(APP传图) - KEY2 双击:接收模式 → ScreenImageReception(等待配对) - KEY2 长按:发送模式 → ScreenImageShar(等待配对) - 按键参数与按键版对齐:long_press_time=1200ms, short_press_time=300ms ### 4. BOOT+KEY2 组合键模式切换(替代 BOOT 长按3秒) - BOOT 2秒长按 + KEY2 同时按下 → 触发模式切换 - 消除单键长按的误触发问题 - AI模式和吧唧模式均注册组合键 ### 5. 按键上下文状态机 - btn_context_t 枚举:HOME/IMG/SET/PEIWANG/IMAGE_SHAR/IMAGE_RECEPTION/SHARING/RECEIVING/UPDATE - 所有界面切换点(手势/按键/BLE自动跳转)同步设置 context - BOOT 单击按 context 分发:Home无操作、Img/Set返回Home、配对退出蓝牙、传输等待取消 ### 6. 新增 UI 界面(6个Screen + 7张图片) - ScreenPeiwang:蓝牙配对等待 - ScreenUpdate:APP传图更新中 - ScreenImageShar:发送方等待配对 - ScreenImageReception:接收方等待配对 - ScreenSharing:发送方传输中 - ScreenReceiving:接收方接收中 ### 7. 其他适配 - BLE 广播改为按需启动(dzbj_ble_start/stop/is_active) - sleep_mgr 移除 KEY2 唤醒(仅 BOOT 唤醒屏幕) - device_mode 新增模式切换按键抑制(防止重启后立即触发) - battery_ui 电池指示器组件 - sdkconfig 启用 BLE GATTC 支持 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
328 lines
9.9 KiB
C
328 lines
9.9 KiB
C
// 吧唧模式按键驱动
|
||
// BOOT(GPIO0): iot_button(由board类管理),此模块处理业务分发
|
||
// KEY2(GPIO4): iot_button(此模块管理),支持单击/双击/长按
|
||
// 界面上下文状态机决定按键行为
|
||
|
||
#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;
|
||
}
|
||
}
|