diff --git a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc index 95935bc2..cafd32aa 100644 --- a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc +++ b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc @@ -1,6 +1,6 @@ #include "ml307_board.h" #include "audio_codecs/no_audio_codec.h" -#include "ssd1306_display.h" +#include "xingzhi_ssd1306_display.h" #include "system_reset.h" #include "application.h" #include "button.h" @@ -103,7 +103,7 @@ public: } virtual Display* GetDisplay() override { - static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + static XINGZHI_Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, &font_puhui_14_1, &font_awesome_14_1); return &display; } diff --git a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.cc b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.cc new file mode 100644 index 00000000..f54b5027 --- /dev/null +++ b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.cc @@ -0,0 +1,644 @@ +#include "xingzhi_ssd1306_display.h" +#include "font_awesome_symbols.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include "board.h" +#include + +#include "esp_adc/adc_oneshot.h" + +#include "button.h" +#include +#include "config.h" +#include "settings.h" +#include "esp_sleep.h" +#include "application.h" +#include "driver/rtc_io.h" +#include "led/single_led.h" + +#define TAG "XINGZHI_Ssd1306Display" + +LV_FONT_DECLARE(font_awesome_30_1); + +XINGZHI_Ssd1306Display::XINGZHI_Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, + const lv_font_t* text_font, const lv_font_t* icon_font) + : text_font_(text_font), icon_font_(icon_font), last_interaction_time_(esp_timer_get_time()), boot_button_(BOOT_BUTTON_GPIO),volume_up_button_(VOLUME_UP_BUTTON_GPIO), volume_down_button_(VOLUME_DOWN_BUTTON_GPIO){ + width_ = width; + height_ = height; + + // 创建充电检测定时器 + esp_timer_create_args_t charging_timer_args = { + .callback = &XINGZHI_Ssd1306Display::ChargingTimerCallback, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "charging_timer" + }; + ESP_ERROR_CHECK(esp_timer_create(&charging_timer_args, &charging_timer_)); + + // 创建电量检测定时器 + esp_timer_create_args_t battery_timer_args = { + .callback = &XINGZHI_Ssd1306Display::BatteryTimerCallback, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_timer" + }; + ESP_ERROR_CHECK(esp_timer_create(&battery_timer_args, &battery_timer_)); + + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + boot_button_.OnPressDown([this]() { + this->UpdateInteractionTime(); + }); + + volume_up_button_.OnPressDown([this]() { + this->UpdateInteractionTime(); + }); + + volume_down_button_.OnPressDown([this]() { + this->UpdateInteractionTime(); + }); + + ESP_LOGI(TAG, "Initialize LVGL"); + lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + lvgl_port_init(&port_cfg); + + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2((i2c_master_bus_t*)i2c_master_handle, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(height_), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + ESP_LOGI(TAG, "Adding LCD screen"); + const lvgl_port_display_cfg_t display_cfg = { + .io_handle = panel_io_, + .panel_handle = panel_, + .control_handle = nullptr, + .buffer_size = static_cast(width_ * height_), + .double_buffer = false, + .trans_size = 0, + .hres = static_cast(width_), + .vres = static_cast(height_), + .monochrome = true, + .rotation = { + .swap_xy = false, + .mirror_x = mirror_x, + .mirror_y = mirror_y, + }, + .flags = { + .buff_dma = 1, + .buff_spiram = 0, + .sw_rotate = 0, + .full_refresh = 0, + .direct_mode = 0, + }, + }; + + display_ = lvgl_port_add_disp(&display_cfg); + if (display_ == nullptr) { + ESP_LOGE(TAG, "Failed to add display"); + return; + } + + if (height_ == 64) { + SetupUI_128x64(); + } else { + SetupUI_128x32(); + } + StartChargingTimer(); + StartBatteryTimer(); +} + +XINGZHI_Ssd1306Display::~XINGZHI_Ssd1306Display() { + if (content_ != nullptr) { + lv_obj_del(content_); + } + if (status_bar_ != nullptr) { + lv_obj_del(status_bar_); + } + if (side_bar_ != nullptr) { + lv_obj_del(side_bar_); + } + if (container_ != nullptr) { + lv_obj_del(container_); + } + + if (panel_ != nullptr) { + esp_lcd_panel_del(panel_); + } + if (panel_io_ != nullptr) { + esp_lcd_panel_io_del(panel_io_); + } + lvgl_port_deinit(); +} + +void XINGZHI_Ssd1306Display::UpdateInteractionTime() { + last_interaction_time_ = esp_timer_get_time(); + if (is_light_sleep_) { + // 从浅睡眠中唤醒,打开显示 + esp_lcd_panel_disp_on_off(panel_, true); + is_light_sleep_ = false; + } +} + +void XINGZHI_Ssd1306Display::CheckSleepState() { + int64_t current_time = esp_timer_get_time(); + int64_t elapsed_time = (current_time - last_interaction_time_) / 1000000; // 转换为秒 + + int charging_level = gpio_get_level(charging_pin_); + bool is_charging = (charging_level == 1); + + if (is_charging) { + // 正在充电,不进入睡眠 + return; + } + + if (elapsed_time >= 60 && !is_light_sleep_ && !is_deep_sleep_) { + is_light_sleep_ = true; + // 关闭显示 + esp_lcd_panel_disp_on_off(panel_, false); + } else if (elapsed_time >= 300 && is_light_sleep_) { + is_deep_sleep_ = true; // 深睡眠 + is_light_sleep_ = false; + // 初始化GPIO 21为RTC GPIO,关闭4g模块 + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_deep_sleep_start(); + } +} + +bool XINGZHI_Ssd1306Display::Lock(int timeout_ms) { + return lvgl_port_lock(timeout_ms); +} + +void XINGZHI_Ssd1306Display::Unlock() { + lvgl_port_unlock(); +} + +uint16_t XINGZHI_Ssd1306Display::ReadBatteryLevel() { + adc_oneshot_unit_handle_t adc_handle; + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_2, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + // 初始化 ADC 单元 + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle)); + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + // 配置 ADC 通道 + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_6, &chan_config)); + int adc_value; + // 读取 ADC 值 + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_CHANNEL_6, &adc_value)); + adc_oneshot_del_unit(adc_handle); + return adc_value; +} + +void XINGZHI_Ssd1306Display::BatteryTimerCallback(void* arg) { + XINGZHI_Ssd1306Display* display = static_cast(arg); + uint16_t adc_value = display->ReadBatteryLevel(); + if (display->first_battery_invert_) { + display->adc_samp_interval = 180000000; // adc值采样的时间间隔 + // 停止当前定时器 + esp_timer_stop(display->battery_timer_); + // 重新启动定时器,使用新的时间间隔 + ESP_ERROR_CHECK(esp_timer_start_periodic(display->battery_timer_, display->adc_samp_interval)); + } + ESP_LOGI(TAG, "adc_samp_interval: %" PRId32 "", display->adc_samp_interval); + ESP_LOGI(TAG, "Value of first_battery_invert_ before condition: %d", display->first_battery_invert_); + display->adc_values.push_back(adc_value); + display->adc_count++; + + if (display->adc_count >= 1) { + uint32_t sum = 0; + for (uint16_t value : display->adc_values) { + sum += value; + } + display->average_adc = sum / display->adc_values.size(); + display->first_battery_invert_ = true; + } +} + +void XINGZHI_Ssd1306Display::StartChargingTimer() { + ESP_ERROR_CHECK(esp_timer_start_periodic(charging_timer_, adc_samp_interval)); +} + +void XINGZHI_Ssd1306Display::StartBatteryTimer() { + ESP_ERROR_CHECK(esp_timer_start_periodic(battery_timer_, adc_samp_interval)); +} + +void XINGZHI_Ssd1306Display::SetChatMessage(const char* role, const char* content) { + DisplayLockGuard lock(this); + if (chat_message_label_ == nullptr) { + return; + } + if (content_right_ == nullptr) { + lv_label_set_text(chat_message_label_, content); + } else { + if (content == nullptr || content[0] == '\0') { + lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN); + } else { + lv_label_set_text(chat_message_label_, content); + lv_obj_clear_flag(content_right_, LV_OBJ_FLAG_HIDDEN); + } + } +} + +void XINGZHI_Ssd1306Display::SetupUI_128x64() { + DisplayLockGuard lock(this); + + auto screen = lv_screen_active(); + lv_obj_set_style_text_font(screen, text_font_, 0); + lv_obj_set_style_text_color(screen, lv_color_black(), 0); + + /* Container */ + container_ = lv_obj_create(screen); + lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); + lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(container_, 0, 0); + lv_obj_set_style_border_width(container_, 0, 0); + lv_obj_set_style_pad_row(container_, 0, 0); + + /* Status bar */ + status_bar_ = lv_obj_create(container_); + lv_obj_set_size(status_bar_, LV_HOR_RES, 16); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_all(status_bar_, 0, 0); + lv_obj_set_style_radius(status_bar_, 0, 0); + + /* Content */ + content_ = lv_obj_create(container_); + lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_style_radius(content_, 0, 0); + lv_obj_set_style_pad_all(content_, 0, 0); + lv_obj_set_width(content_, LV_HOR_RES); + lv_obj_set_flex_grow(content_, 1); + lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_flex_main_place(content_, LV_FLEX_ALIGN_CENTER, 0); + + // 创建左侧固定宽度的容器 + content_left_ = lv_obj_create(content_); + lv_obj_set_size(content_left_, 32, LV_SIZE_CONTENT); // 固定宽度32像素 + lv_obj_set_style_pad_all(content_left_, 0, 0); + lv_obj_set_style_border_width(content_left_, 0, 0); + + emotion_label_ = lv_label_create(content_left_); + lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0); + lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); + lv_obj_center(emotion_label_); + lv_obj_set_style_pad_top(emotion_label_, 8, 0); + + // 创建右侧可扩展的容器 + content_right_ = lv_obj_create(content_); + lv_obj_set_size(content_right_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(content_right_, 0, 0); + lv_obj_set_style_border_width(content_right_, 0, 0); + lv_obj_set_flex_grow(content_right_, 1); + lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN); + + chat_message_label_ = lv_label_create(content_right_); + lv_label_set_text(chat_message_label_, ""); + lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_width(chat_message_label_, lv_pct(100)); + lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_LEFT, 0); + + /* Status bar */ + lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(status_bar_, 0, 0); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_column(status_bar_, 0, 0); + + network_label_ = lv_label_create(status_bar_); + lv_label_set_text(network_label_, ""); + lv_obj_set_style_text_font(network_label_, icon_font_, 0); + + notification_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(notification_label_, 1); + lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_label_set_text(notification_label_, "通知"); + lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + + status_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(status_label_, 1); + lv_label_set_text(status_label_, "正在初始化"); + lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); + + mute_label_ = lv_label_create(status_bar_); + lv_label_set_text(mute_label_, ""); + lv_obj_set_style_text_font(mute_label_, icon_font_, 0); + + battery_label_ = lv_label_create(status_bar_); + lv_label_set_text(battery_label_, ""); + lv_obj_set_style_text_font(battery_label_, icon_font_, 0); + + /* 充电状态标签 */ + charging_label_ = lv_label_create(status_bar_); + lv_obj_set_style_text_font(charging_label_, icon_font_, 0); + lv_obj_set_style_text_align(charging_label_, LV_TEXT_ALIGN_RIGHT, 0); + lv_obj_set_flex_grow(charging_label_, 0); + lv_label_set_text(charging_label_, ""); + + // 检查充电状态 + int charging_level = gpio_get_level(charging_pin_); + if (charging_level == 1) { + lv_label_set_text(charging_label_, FONT_AWESOME_BATTERY_CHARGING); + } +} + +void XINGZHI_Ssd1306Display::SetupUI_128x32() { + DisplayLockGuard lock(this); + + auto screen = lv_screen_active(); + lv_obj_set_style_text_font(screen, text_font_, 0); + + /* Container */ + container_ = lv_obj_create(screen); + lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); + lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(container_, 0, 0); + lv_obj_set_style_border_width(container_, 0, 0); + lv_obj_set_style_pad_column(container_, 0, 0); + + /* Left side */ + side_bar_ = lv_obj_create(container_); + lv_obj_set_flex_grow(side_bar_, 1); + lv_obj_set_flex_flow(side_bar_, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(side_bar_, 0, 0); + lv_obj_set_style_border_width(side_bar_, 0, 0); + lv_obj_set_style_radius(side_bar_, 0, 0); + lv_obj_set_style_pad_row(side_bar_, 0, 0); + + /* Emotion label on the right side */ + content_ = lv_obj_create(container_); + lv_obj_set_size(content_, 32, 32); + lv_obj_set_style_pad_all(content_, 0, 0); + lv_obj_set_style_border_width(content_, 0, 0); + lv_obj_set_style_radius(content_, 0, 0); + + emotion_label_ = lv_label_create(content_); + lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0); + lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); + lv_obj_center(emotion_label_); + + /* Status bar */ + status_bar_ = lv_obj_create(side_bar_); + lv_obj_set_size(status_bar_, LV_SIZE_CONTENT, 16); + lv_obj_set_style_radius(status_bar_, 0, 0); + lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(status_bar_, 0, 0); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_column(status_bar_, 0, 0); + + network_label_ = lv_label_create(status_bar_); + lv_label_set_text(network_label_, ""); + lv_obj_set_style_text_font(network_label_, icon_font_, 0); + + mute_label_ = lv_label_create(status_bar_); + lv_label_set_text(mute_label_, ""); + lv_obj_set_style_text_font(mute_label_, icon_font_, 0); + + battery_label_ = lv_label_create(status_bar_); + lv_label_set_text(battery_label_, ""); + lv_obj_set_style_text_font(battery_label_, icon_font_, 0); + + status_label_ = lv_label_create(status_bar_); + lv_obj_set_style_pad_left(status_label_, 2, 0); + lv_label_set_text(status_label_, "正在初始化"); + + notification_label_ = lv_label_create(status_bar_); + lv_label_set_text(notification_label_, "通知"); + lv_obj_set_style_pad_left(notification_label_, 2, 0); + lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + + chat_message_label_ = lv_label_create(side_bar_); + lv_obj_set_flex_grow(chat_message_label_, 1); + lv_obj_set_width(chat_message_label_, width_ - 32); + lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_label_set_text(chat_message_label_, ""); + + /* 充电状态标签 */ + charging_label_ = lv_label_create(status_bar_); + lv_obj_set_style_text_font(charging_label_, icon_font_, 0); + lv_obj_set_style_text_align(charging_label_, LV_TEXT_ALIGN_RIGHT, 0); + lv_obj_set_flex_grow(charging_label_, 0); + lv_label_set_text(charging_label_, ""); + + // 检查充电状态 + int charging_level = gpio_get_level(charging_pin_); + if (charging_level == 1) { + lv_label_set_text(charging_label_, FONT_AWESOME_BATTERY_CHARGING); + } +} + +void XINGZHI_Ssd1306Display::UpdateBatteryAndChargingDisplay(uint16_t average_adc) { + DisplayLockGuard lock(this); + + // 未充电时,显示电池图标 + if (charging_label_ != nullptr) { + lv_label_set_text(charging_label_, ""); + } + + uint8_t battery_level = 0; + if (average_adc < 2000) { + battery_level = 0; + // 显示电量过低提示窗口 + ShowLowBatteryPopup(); + } else if (average_adc >= 2000 && average_adc < 2100) { + battery_level = 1; + // 如果电量回升,隐藏提示窗口 + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } else if (average_adc >= 2100 && average_adc < 2200) { + battery_level = 2; + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } else if (average_adc >= 2200 && average_adc < 2300) { + battery_level = 3; + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } else { + battery_level = 4; + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } + + const char* battery_icon; + switch (battery_level) { + case 0: + battery_icon = FONT_AWESOME_BATTERY_EMPTY; + break; + case 1: + battery_icon = FONT_AWESOME_BATTERY_1; + break; + case 2: + battery_icon = FONT_AWESOME_BATTERY_2; + break; + case 3: + battery_icon = FONT_AWESOME_BATTERY_3; + break; + case 4: + battery_icon = FONT_AWESOME_BATTERY_FULL; + break; + default: + battery_icon = FONT_AWESOME_BATTERY_SLASH; + break; + } + + if (battery_label_ != nullptr) { + lv_obj_set_style_text_font(battery_label_, icon_font_, 0); + lv_label_set_text(battery_label_, battery_icon); + } +} + +void XINGZHI_Ssd1306Display::ChargingTimerCallback(void* arg) { + XINGZHI_Ssd1306Display* display = static_cast(arg); + DisplayLockGuard lock(display); + + // 检查充电状态 + int charging_level = gpio_get_level(display->charging_pin_); + bool is_charging = (charging_level == 1); + display->OnStateChanged();//检测当前对对话状态 + // 检查电池是否充满,adc值超过2430,判定为充满 + bool is_battery_full = 0; + if (display->average_adc > 2430) + { + is_battery_full = 1; + } + if (is_charging) { + // 正在充电,更新交互时间,防止进入睡眠 + display->UpdateInteractionTime(); + if (is_battery_full) { + if (display->charging_label_ != nullptr) { + lv_label_set_text(display->charging_label_, ""); + } + if (display->battery_label_ != nullptr) { + lv_obj_set_style_text_font(display->battery_label_, display->icon_font_, 0); + lv_label_set_text(display->battery_label_, FONT_AWESOME_BATTERY_FULL); + } + } else { + if (display->charging_label_ != nullptr) { + lv_obj_set_style_text_font(display->charging_label_, display->icon_font_, 0); + lv_label_set_text(display->charging_label_, FONT_AWESOME_BATTERY_CHARGING); + } + if (display->battery_label_ != nullptr) { + lv_label_set_text(display->battery_label_, ""); + } + } + // 如果正在充电,隐藏电量过低提示窗口 + if (display->low_battery_popup_ != nullptr) { + lv_obj_add_flag(display->low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + display->was_charging = true; // 更新上一次的充电状态为正在充电 + } else { + if (display->was_charging) { + // 充电状态从充电变为未充电,立即读取并更新电池电量 + display->average_adc = display->ReadBatteryLevel(); + } else { + // 一直处于未充电状态,正常显示电池图标 + if (display->charging_label_ != nullptr) { + if (!display->first_battery_invert_) { + display->average_adc = display->ReadBatteryLevel(); + } + display->UpdateBatteryAndChargingDisplay(display->average_adc); + // 清空数组和计数器 + display->adc_values.clear(); + display->adc_count = 0; + } + } + display->was_charging = false; // 更新上一次的充电状态为未充电 + } + // 检查睡眠状态 + display->CheckSleepState(); +} + +void XINGZHI_Ssd1306Display::ShowLowBatteryPopup() { + DisplayLockGuard lock(this); + + if (low_battery_popup_ == nullptr) { + // 创建弹出窗口 + low_battery_popup_ = lv_obj_create(lv_scr_act()); + lv_obj_set_size(low_battery_popup_, 120, 30); + lv_obj_center(low_battery_popup_); + lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); + lv_obj_set_style_radius(low_battery_popup_, 10, 0); + + // 创建提示文本标签 + lv_obj_t* label = lv_label_create(low_battery_popup_); + lv_label_set_text(label, "电量过低,请充电"); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_center(label); + } + + // 显示弹出窗口 + lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); +} + +void XINGZHI_Ssd1306Display::OnStateChanged() { + auto& app = Application::GetInstance(); + auto device_state = app.GetDeviceState(); + if (device_state != kDeviceStateIdle && !this->was_charging) { + UpdateInteractionTime(); + } +} + diff --git a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.h b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.h new file mode 100644 index 00000000..69976516 --- /dev/null +++ b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.h @@ -0,0 +1,86 @@ +#ifndef SSD1306_DISPLAY_H +#define SSD1306_DISPLAY_H + +#include "display/display.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +// 新增的头文件包含语句 +#include +#include "button.h" + + +class XINGZHI_Ssd1306Display : public Display { +private: + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + lv_obj_t* status_bar_ = nullptr; + lv_obj_t* content_ = nullptr; + lv_obj_t* content_left_ = nullptr; + lv_obj_t* content_right_ = nullptr; + lv_obj_t* container_ = nullptr; + lv_obj_t* side_bar_ = nullptr; + + const lv_font_t* text_font_ = nullptr; + const lv_font_t* icon_font_ = nullptr; + + DisplayFonts fonts_; + + lv_obj_t* charging_label_ = nullptr; // 充电状态标签 + lv_obj_t* low_battery_popup_ = nullptr; // 电量过低提示窗口对象指针 + lv_obj_t* battery_label_ = nullptr; // 已有,用于显示电量 + int32_t adc_samp_interval = 500000; // adc值采样的时间间隔,单位为微秒 + uint16_t average_adc = 0; // adc平均值 + esp_timer_handle_t charging_timer_; // 充电检测定时器句柄 + esp_timer_handle_t battery_timer_; // 电量检测定时器句柄 + gpio_num_t charging_pin_ = GPIO_NUM_38; // 充电检测引脚 + std::vector adc_values; // 用于存储读取的ADC值 + int adc_count = 0; // 记录已检测的ADC值数量 + bool was_charging = false; // 上一次的充电状态 + bool first_battery_invert_ = false; //首次上电直接检测电量 + void ShowLowBatteryPopup(); // 显示电量过低提示窗口的方法声明 + uint16_t ReadBatteryLevel(); // 读取电量的方法 + + int64_t last_interaction_time_; // 上次交互时间 + bool is_light_sleep_ = false; // 浅睡眠 + bool is_deep_sleep_ = false; // 深睡眠 + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + + virtual bool Lock(int timeout_ms = 0) override; + virtual void Unlock() override; + + void SetupUI_128x64(); + void SetupUI_128x32(); + +public: + XINGZHI_Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, + const lv_font_t* text_font, const lv_font_t* icon_font); + ~XINGZHI_Ssd1306Display(); + + + static void ChargingTimerCallback(void* arg); + static void BatteryTimerCallback(void* arg); + void StartChargingTimer(); + void StartBatteryTimer(); + void UpdateBatteryAndChargingDisplay(uint16_t average_adc); + void OnStateChanged(); + + void UpdateInteractionTime(); + void CheckSleepState(); + + + virtual void SetChatMessage(const char* role, const char* content) override; +}; + +#endif // SSD1306_DISPLAY_H diff --git a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc index 8cf164fa..4a39358f 100644 --- a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc +++ b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc @@ -1,6 +1,6 @@ #include "wifi_board.h" #include "audio_codecs/no_audio_codec.h" -#include "display/ssd1306_display.h" +#include "xingzhi_ssd1306_display.h" #include "system_reset.h" #include "application.h" #include "button.h" @@ -107,7 +107,7 @@ public: } virtual Display* GetDisplay() override { - static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + static XINGZHI_Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, &font_puhui_14_1, &font_awesome_14_1); return &display; } diff --git a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.cc b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.cc new file mode 100644 index 00000000..f54b5027 --- /dev/null +++ b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.cc @@ -0,0 +1,644 @@ +#include "xingzhi_ssd1306_display.h" +#include "font_awesome_symbols.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include "board.h" +#include + +#include "esp_adc/adc_oneshot.h" + +#include "button.h" +#include +#include "config.h" +#include "settings.h" +#include "esp_sleep.h" +#include "application.h" +#include "driver/rtc_io.h" +#include "led/single_led.h" + +#define TAG "XINGZHI_Ssd1306Display" + +LV_FONT_DECLARE(font_awesome_30_1); + +XINGZHI_Ssd1306Display::XINGZHI_Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, + const lv_font_t* text_font, const lv_font_t* icon_font) + : text_font_(text_font), icon_font_(icon_font), last_interaction_time_(esp_timer_get_time()), boot_button_(BOOT_BUTTON_GPIO),volume_up_button_(VOLUME_UP_BUTTON_GPIO), volume_down_button_(VOLUME_DOWN_BUTTON_GPIO){ + width_ = width; + height_ = height; + + // 创建充电检测定时器 + esp_timer_create_args_t charging_timer_args = { + .callback = &XINGZHI_Ssd1306Display::ChargingTimerCallback, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "charging_timer" + }; + ESP_ERROR_CHECK(esp_timer_create(&charging_timer_args, &charging_timer_)); + + // 创建电量检测定时器 + esp_timer_create_args_t battery_timer_args = { + .callback = &XINGZHI_Ssd1306Display::BatteryTimerCallback, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_timer" + }; + ESP_ERROR_CHECK(esp_timer_create(&battery_timer_args, &battery_timer_)); + + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + boot_button_.OnPressDown([this]() { + this->UpdateInteractionTime(); + }); + + volume_up_button_.OnPressDown([this]() { + this->UpdateInteractionTime(); + }); + + volume_down_button_.OnPressDown([this]() { + this->UpdateInteractionTime(); + }); + + ESP_LOGI(TAG, "Initialize LVGL"); + lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + lvgl_port_init(&port_cfg); + + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2((i2c_master_bus_t*)i2c_master_handle, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(height_), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + ESP_LOGI(TAG, "Adding LCD screen"); + const lvgl_port_display_cfg_t display_cfg = { + .io_handle = panel_io_, + .panel_handle = panel_, + .control_handle = nullptr, + .buffer_size = static_cast(width_ * height_), + .double_buffer = false, + .trans_size = 0, + .hres = static_cast(width_), + .vres = static_cast(height_), + .monochrome = true, + .rotation = { + .swap_xy = false, + .mirror_x = mirror_x, + .mirror_y = mirror_y, + }, + .flags = { + .buff_dma = 1, + .buff_spiram = 0, + .sw_rotate = 0, + .full_refresh = 0, + .direct_mode = 0, + }, + }; + + display_ = lvgl_port_add_disp(&display_cfg); + if (display_ == nullptr) { + ESP_LOGE(TAG, "Failed to add display"); + return; + } + + if (height_ == 64) { + SetupUI_128x64(); + } else { + SetupUI_128x32(); + } + StartChargingTimer(); + StartBatteryTimer(); +} + +XINGZHI_Ssd1306Display::~XINGZHI_Ssd1306Display() { + if (content_ != nullptr) { + lv_obj_del(content_); + } + if (status_bar_ != nullptr) { + lv_obj_del(status_bar_); + } + if (side_bar_ != nullptr) { + lv_obj_del(side_bar_); + } + if (container_ != nullptr) { + lv_obj_del(container_); + } + + if (panel_ != nullptr) { + esp_lcd_panel_del(panel_); + } + if (panel_io_ != nullptr) { + esp_lcd_panel_io_del(panel_io_); + } + lvgl_port_deinit(); +} + +void XINGZHI_Ssd1306Display::UpdateInteractionTime() { + last_interaction_time_ = esp_timer_get_time(); + if (is_light_sleep_) { + // 从浅睡眠中唤醒,打开显示 + esp_lcd_panel_disp_on_off(panel_, true); + is_light_sleep_ = false; + } +} + +void XINGZHI_Ssd1306Display::CheckSleepState() { + int64_t current_time = esp_timer_get_time(); + int64_t elapsed_time = (current_time - last_interaction_time_) / 1000000; // 转换为秒 + + int charging_level = gpio_get_level(charging_pin_); + bool is_charging = (charging_level == 1); + + if (is_charging) { + // 正在充电,不进入睡眠 + return; + } + + if (elapsed_time >= 60 && !is_light_sleep_ && !is_deep_sleep_) { + is_light_sleep_ = true; + // 关闭显示 + esp_lcd_panel_disp_on_off(panel_, false); + } else if (elapsed_time >= 300 && is_light_sleep_) { + is_deep_sleep_ = true; // 深睡眠 + is_light_sleep_ = false; + // 初始化GPIO 21为RTC GPIO,关闭4g模块 + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_deep_sleep_start(); + } +} + +bool XINGZHI_Ssd1306Display::Lock(int timeout_ms) { + return lvgl_port_lock(timeout_ms); +} + +void XINGZHI_Ssd1306Display::Unlock() { + lvgl_port_unlock(); +} + +uint16_t XINGZHI_Ssd1306Display::ReadBatteryLevel() { + adc_oneshot_unit_handle_t adc_handle; + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_2, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + // 初始化 ADC 单元 + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle)); + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + // 配置 ADC 通道 + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_6, &chan_config)); + int adc_value; + // 读取 ADC 值 + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_CHANNEL_6, &adc_value)); + adc_oneshot_del_unit(adc_handle); + return adc_value; +} + +void XINGZHI_Ssd1306Display::BatteryTimerCallback(void* arg) { + XINGZHI_Ssd1306Display* display = static_cast(arg); + uint16_t adc_value = display->ReadBatteryLevel(); + if (display->first_battery_invert_) { + display->adc_samp_interval = 180000000; // adc值采样的时间间隔 + // 停止当前定时器 + esp_timer_stop(display->battery_timer_); + // 重新启动定时器,使用新的时间间隔 + ESP_ERROR_CHECK(esp_timer_start_periodic(display->battery_timer_, display->adc_samp_interval)); + } + ESP_LOGI(TAG, "adc_samp_interval: %" PRId32 "", display->adc_samp_interval); + ESP_LOGI(TAG, "Value of first_battery_invert_ before condition: %d", display->first_battery_invert_); + display->adc_values.push_back(adc_value); + display->adc_count++; + + if (display->adc_count >= 1) { + uint32_t sum = 0; + for (uint16_t value : display->adc_values) { + sum += value; + } + display->average_adc = sum / display->adc_values.size(); + display->first_battery_invert_ = true; + } +} + +void XINGZHI_Ssd1306Display::StartChargingTimer() { + ESP_ERROR_CHECK(esp_timer_start_periodic(charging_timer_, adc_samp_interval)); +} + +void XINGZHI_Ssd1306Display::StartBatteryTimer() { + ESP_ERROR_CHECK(esp_timer_start_periodic(battery_timer_, adc_samp_interval)); +} + +void XINGZHI_Ssd1306Display::SetChatMessage(const char* role, const char* content) { + DisplayLockGuard lock(this); + if (chat_message_label_ == nullptr) { + return; + } + if (content_right_ == nullptr) { + lv_label_set_text(chat_message_label_, content); + } else { + if (content == nullptr || content[0] == '\0') { + lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN); + } else { + lv_label_set_text(chat_message_label_, content); + lv_obj_clear_flag(content_right_, LV_OBJ_FLAG_HIDDEN); + } + } +} + +void XINGZHI_Ssd1306Display::SetupUI_128x64() { + DisplayLockGuard lock(this); + + auto screen = lv_screen_active(); + lv_obj_set_style_text_font(screen, text_font_, 0); + lv_obj_set_style_text_color(screen, lv_color_black(), 0); + + /* Container */ + container_ = lv_obj_create(screen); + lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); + lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(container_, 0, 0); + lv_obj_set_style_border_width(container_, 0, 0); + lv_obj_set_style_pad_row(container_, 0, 0); + + /* Status bar */ + status_bar_ = lv_obj_create(container_); + lv_obj_set_size(status_bar_, LV_HOR_RES, 16); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_all(status_bar_, 0, 0); + lv_obj_set_style_radius(status_bar_, 0, 0); + + /* Content */ + content_ = lv_obj_create(container_); + lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_style_radius(content_, 0, 0); + lv_obj_set_style_pad_all(content_, 0, 0); + lv_obj_set_width(content_, LV_HOR_RES); + lv_obj_set_flex_grow(content_, 1); + lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_flex_main_place(content_, LV_FLEX_ALIGN_CENTER, 0); + + // 创建左侧固定宽度的容器 + content_left_ = lv_obj_create(content_); + lv_obj_set_size(content_left_, 32, LV_SIZE_CONTENT); // 固定宽度32像素 + lv_obj_set_style_pad_all(content_left_, 0, 0); + lv_obj_set_style_border_width(content_left_, 0, 0); + + emotion_label_ = lv_label_create(content_left_); + lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0); + lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); + lv_obj_center(emotion_label_); + lv_obj_set_style_pad_top(emotion_label_, 8, 0); + + // 创建右侧可扩展的容器 + content_right_ = lv_obj_create(content_); + lv_obj_set_size(content_right_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(content_right_, 0, 0); + lv_obj_set_style_border_width(content_right_, 0, 0); + lv_obj_set_flex_grow(content_right_, 1); + lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN); + + chat_message_label_ = lv_label_create(content_right_); + lv_label_set_text(chat_message_label_, ""); + lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_width(chat_message_label_, lv_pct(100)); + lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_LEFT, 0); + + /* Status bar */ + lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(status_bar_, 0, 0); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_column(status_bar_, 0, 0); + + network_label_ = lv_label_create(status_bar_); + lv_label_set_text(network_label_, ""); + lv_obj_set_style_text_font(network_label_, icon_font_, 0); + + notification_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(notification_label_, 1); + lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_label_set_text(notification_label_, "通知"); + lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + + status_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(status_label_, 1); + lv_label_set_text(status_label_, "正在初始化"); + lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); + + mute_label_ = lv_label_create(status_bar_); + lv_label_set_text(mute_label_, ""); + lv_obj_set_style_text_font(mute_label_, icon_font_, 0); + + battery_label_ = lv_label_create(status_bar_); + lv_label_set_text(battery_label_, ""); + lv_obj_set_style_text_font(battery_label_, icon_font_, 0); + + /* 充电状态标签 */ + charging_label_ = lv_label_create(status_bar_); + lv_obj_set_style_text_font(charging_label_, icon_font_, 0); + lv_obj_set_style_text_align(charging_label_, LV_TEXT_ALIGN_RIGHT, 0); + lv_obj_set_flex_grow(charging_label_, 0); + lv_label_set_text(charging_label_, ""); + + // 检查充电状态 + int charging_level = gpio_get_level(charging_pin_); + if (charging_level == 1) { + lv_label_set_text(charging_label_, FONT_AWESOME_BATTERY_CHARGING); + } +} + +void XINGZHI_Ssd1306Display::SetupUI_128x32() { + DisplayLockGuard lock(this); + + auto screen = lv_screen_active(); + lv_obj_set_style_text_font(screen, text_font_, 0); + + /* Container */ + container_ = lv_obj_create(screen); + lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); + lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(container_, 0, 0); + lv_obj_set_style_border_width(container_, 0, 0); + lv_obj_set_style_pad_column(container_, 0, 0); + + /* Left side */ + side_bar_ = lv_obj_create(container_); + lv_obj_set_flex_grow(side_bar_, 1); + lv_obj_set_flex_flow(side_bar_, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(side_bar_, 0, 0); + lv_obj_set_style_border_width(side_bar_, 0, 0); + lv_obj_set_style_radius(side_bar_, 0, 0); + lv_obj_set_style_pad_row(side_bar_, 0, 0); + + /* Emotion label on the right side */ + content_ = lv_obj_create(container_); + lv_obj_set_size(content_, 32, 32); + lv_obj_set_style_pad_all(content_, 0, 0); + lv_obj_set_style_border_width(content_, 0, 0); + lv_obj_set_style_radius(content_, 0, 0); + + emotion_label_ = lv_label_create(content_); + lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0); + lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); + lv_obj_center(emotion_label_); + + /* Status bar */ + status_bar_ = lv_obj_create(side_bar_); + lv_obj_set_size(status_bar_, LV_SIZE_CONTENT, 16); + lv_obj_set_style_radius(status_bar_, 0, 0); + lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(status_bar_, 0, 0); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_column(status_bar_, 0, 0); + + network_label_ = lv_label_create(status_bar_); + lv_label_set_text(network_label_, ""); + lv_obj_set_style_text_font(network_label_, icon_font_, 0); + + mute_label_ = lv_label_create(status_bar_); + lv_label_set_text(mute_label_, ""); + lv_obj_set_style_text_font(mute_label_, icon_font_, 0); + + battery_label_ = lv_label_create(status_bar_); + lv_label_set_text(battery_label_, ""); + lv_obj_set_style_text_font(battery_label_, icon_font_, 0); + + status_label_ = lv_label_create(status_bar_); + lv_obj_set_style_pad_left(status_label_, 2, 0); + lv_label_set_text(status_label_, "正在初始化"); + + notification_label_ = lv_label_create(status_bar_); + lv_label_set_text(notification_label_, "通知"); + lv_obj_set_style_pad_left(notification_label_, 2, 0); + lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + + chat_message_label_ = lv_label_create(side_bar_); + lv_obj_set_flex_grow(chat_message_label_, 1); + lv_obj_set_width(chat_message_label_, width_ - 32); + lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_label_set_text(chat_message_label_, ""); + + /* 充电状态标签 */ + charging_label_ = lv_label_create(status_bar_); + lv_obj_set_style_text_font(charging_label_, icon_font_, 0); + lv_obj_set_style_text_align(charging_label_, LV_TEXT_ALIGN_RIGHT, 0); + lv_obj_set_flex_grow(charging_label_, 0); + lv_label_set_text(charging_label_, ""); + + // 检查充电状态 + int charging_level = gpio_get_level(charging_pin_); + if (charging_level == 1) { + lv_label_set_text(charging_label_, FONT_AWESOME_BATTERY_CHARGING); + } +} + +void XINGZHI_Ssd1306Display::UpdateBatteryAndChargingDisplay(uint16_t average_adc) { + DisplayLockGuard lock(this); + + // 未充电时,显示电池图标 + if (charging_label_ != nullptr) { + lv_label_set_text(charging_label_, ""); + } + + uint8_t battery_level = 0; + if (average_adc < 2000) { + battery_level = 0; + // 显示电量过低提示窗口 + ShowLowBatteryPopup(); + } else if (average_adc >= 2000 && average_adc < 2100) { + battery_level = 1; + // 如果电量回升,隐藏提示窗口 + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } else if (average_adc >= 2100 && average_adc < 2200) { + battery_level = 2; + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } else if (average_adc >= 2200 && average_adc < 2300) { + battery_level = 3; + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } else { + battery_level = 4; + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } + + const char* battery_icon; + switch (battery_level) { + case 0: + battery_icon = FONT_AWESOME_BATTERY_EMPTY; + break; + case 1: + battery_icon = FONT_AWESOME_BATTERY_1; + break; + case 2: + battery_icon = FONT_AWESOME_BATTERY_2; + break; + case 3: + battery_icon = FONT_AWESOME_BATTERY_3; + break; + case 4: + battery_icon = FONT_AWESOME_BATTERY_FULL; + break; + default: + battery_icon = FONT_AWESOME_BATTERY_SLASH; + break; + } + + if (battery_label_ != nullptr) { + lv_obj_set_style_text_font(battery_label_, icon_font_, 0); + lv_label_set_text(battery_label_, battery_icon); + } +} + +void XINGZHI_Ssd1306Display::ChargingTimerCallback(void* arg) { + XINGZHI_Ssd1306Display* display = static_cast(arg); + DisplayLockGuard lock(display); + + // 检查充电状态 + int charging_level = gpio_get_level(display->charging_pin_); + bool is_charging = (charging_level == 1); + display->OnStateChanged();//检测当前对对话状态 + // 检查电池是否充满,adc值超过2430,判定为充满 + bool is_battery_full = 0; + if (display->average_adc > 2430) + { + is_battery_full = 1; + } + if (is_charging) { + // 正在充电,更新交互时间,防止进入睡眠 + display->UpdateInteractionTime(); + if (is_battery_full) { + if (display->charging_label_ != nullptr) { + lv_label_set_text(display->charging_label_, ""); + } + if (display->battery_label_ != nullptr) { + lv_obj_set_style_text_font(display->battery_label_, display->icon_font_, 0); + lv_label_set_text(display->battery_label_, FONT_AWESOME_BATTERY_FULL); + } + } else { + if (display->charging_label_ != nullptr) { + lv_obj_set_style_text_font(display->charging_label_, display->icon_font_, 0); + lv_label_set_text(display->charging_label_, FONT_AWESOME_BATTERY_CHARGING); + } + if (display->battery_label_ != nullptr) { + lv_label_set_text(display->battery_label_, ""); + } + } + // 如果正在充电,隐藏电量过低提示窗口 + if (display->low_battery_popup_ != nullptr) { + lv_obj_add_flag(display->low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + display->was_charging = true; // 更新上一次的充电状态为正在充电 + } else { + if (display->was_charging) { + // 充电状态从充电变为未充电,立即读取并更新电池电量 + display->average_adc = display->ReadBatteryLevel(); + } else { + // 一直处于未充电状态,正常显示电池图标 + if (display->charging_label_ != nullptr) { + if (!display->first_battery_invert_) { + display->average_adc = display->ReadBatteryLevel(); + } + display->UpdateBatteryAndChargingDisplay(display->average_adc); + // 清空数组和计数器 + display->adc_values.clear(); + display->adc_count = 0; + } + } + display->was_charging = false; // 更新上一次的充电状态为未充电 + } + // 检查睡眠状态 + display->CheckSleepState(); +} + +void XINGZHI_Ssd1306Display::ShowLowBatteryPopup() { + DisplayLockGuard lock(this); + + if (low_battery_popup_ == nullptr) { + // 创建弹出窗口 + low_battery_popup_ = lv_obj_create(lv_scr_act()); + lv_obj_set_size(low_battery_popup_, 120, 30); + lv_obj_center(low_battery_popup_); + lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); + lv_obj_set_style_radius(low_battery_popup_, 10, 0); + + // 创建提示文本标签 + lv_obj_t* label = lv_label_create(low_battery_popup_); + lv_label_set_text(label, "电量过低,请充电"); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_center(label); + } + + // 显示弹出窗口 + lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); +} + +void XINGZHI_Ssd1306Display::OnStateChanged() { + auto& app = Application::GetInstance(); + auto device_state = app.GetDeviceState(); + if (device_state != kDeviceStateIdle && !this->was_charging) { + UpdateInteractionTime(); + } +} + diff --git a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.h b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.h new file mode 100644 index 00000000..69976516 --- /dev/null +++ b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.h @@ -0,0 +1,86 @@ +#ifndef SSD1306_DISPLAY_H +#define SSD1306_DISPLAY_H + +#include "display/display.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +// 新增的头文件包含语句 +#include +#include "button.h" + + +class XINGZHI_Ssd1306Display : public Display { +private: + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + lv_obj_t* status_bar_ = nullptr; + lv_obj_t* content_ = nullptr; + lv_obj_t* content_left_ = nullptr; + lv_obj_t* content_right_ = nullptr; + lv_obj_t* container_ = nullptr; + lv_obj_t* side_bar_ = nullptr; + + const lv_font_t* text_font_ = nullptr; + const lv_font_t* icon_font_ = nullptr; + + DisplayFonts fonts_; + + lv_obj_t* charging_label_ = nullptr; // 充电状态标签 + lv_obj_t* low_battery_popup_ = nullptr; // 电量过低提示窗口对象指针 + lv_obj_t* battery_label_ = nullptr; // 已有,用于显示电量 + int32_t adc_samp_interval = 500000; // adc值采样的时间间隔,单位为微秒 + uint16_t average_adc = 0; // adc平均值 + esp_timer_handle_t charging_timer_; // 充电检测定时器句柄 + esp_timer_handle_t battery_timer_; // 电量检测定时器句柄 + gpio_num_t charging_pin_ = GPIO_NUM_38; // 充电检测引脚 + std::vector adc_values; // 用于存储读取的ADC值 + int adc_count = 0; // 记录已检测的ADC值数量 + bool was_charging = false; // 上一次的充电状态 + bool first_battery_invert_ = false; //首次上电直接检测电量 + void ShowLowBatteryPopup(); // 显示电量过低提示窗口的方法声明 + uint16_t ReadBatteryLevel(); // 读取电量的方法 + + int64_t last_interaction_time_; // 上次交互时间 + bool is_light_sleep_ = false; // 浅睡眠 + bool is_deep_sleep_ = false; // 深睡眠 + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + + virtual bool Lock(int timeout_ms = 0) override; + virtual void Unlock() override; + + void SetupUI_128x64(); + void SetupUI_128x32(); + +public: + XINGZHI_Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, + const lv_font_t* text_font, const lv_font_t* icon_font); + ~XINGZHI_Ssd1306Display(); + + + static void ChargingTimerCallback(void* arg); + static void BatteryTimerCallback(void* arg); + void StartChargingTimer(); + void StartBatteryTimer(); + void UpdateBatteryAndChargingDisplay(uint16_t average_adc); + void OnStateChanged(); + + void UpdateInteractionTime(); + void CheckSleepState(); + + + virtual void SetChatMessage(const char* role, const char* content) override; +}; + +#endif // SSD1306_DISPLAY_H diff --git a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc index 56a63ffe..61cabdd0 100644 --- a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc +++ b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc @@ -1,7 +1,7 @@ #include "ml307_board.h" #include "audio_codecs/no_audio_codec.h" -#include "display/lcd_display.h" +#include "xingzhi_lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" @@ -22,7 +22,7 @@ private: Button boot_button_; Button volume_up_button_; Button volume_down_button_; - LcdDisplay* display_; + XINGZHI_1_54_TFT_LcdDisplay* display_; void InitializeSpi() { spi_bus_config_t buscfg = {}; @@ -99,7 +99,7 @@ private: ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new XINGZHI_1_54_TFT_LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY, { .text_font = &font_puhui_16_4, diff --git a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.cc b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.cc new file mode 100644 index 00000000..4959647c --- /dev/null +++ b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.cc @@ -0,0 +1,659 @@ +#include "xingzhi_lcd_display.h" + +#include +#include +#include +#include +#include +#include +#include +#include "assets/lang_config.h" + +#include "board.h" + +#include "esp_adc/adc_oneshot.h" +#include "button.h" +#include +#include "config.h" +#include "settings.h" +#include "esp_sleep.h" +#include "application.h" +#include "driver/rtc_io.h" +#include + +#define TAG "XINGZHI_1_54_TFT_LcdDisplay" +#define LCD_LEDC_CH LEDC_CHANNEL_0 +#define PIN_NUMBER GPIO_NUM_21 + +LV_FONT_DECLARE(font_awesome_30_4); + +XINGZHI_1_54_TFT_LcdDisplay::XINGZHI_1_54_TFT_LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + gpio_num_t backlight_pin, bool backlight_output_invert, + int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, + DisplayFonts fonts) + : panel_io_(panel_io), panel_(panel), backlight_pin_(backlight_pin), backlight_output_invert_(backlight_output_invert), + fonts_(fonts),last_interaction_time_(esp_timer_get_time()),boot_button_(BOOT_BUTTON_GPIO),volume_up_button_(VOLUME_UP_BUTTON_GPIO), volume_down_button_(VOLUME_DOWN_BUTTON_GPIO){ + width_ = width; + height_ = height; + + // 创建背光渐变定时器 + const esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + XINGZHI_1_54_TFT_LcdDisplay* display = static_cast(arg); + display->OnBacklightTimer(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "backlight_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &backlight_timer_)); + InitializeBacklight(backlight_pin); + + // draw white + std::vector buffer(width_, 0xFFFF); + for (int y = 0; y < height_; y++) { + esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data()); + } + + // 创建充电检测定时器 + esp_timer_create_args_t charging_timer_args = { + .callback = &XINGZHI_1_54_TFT_LcdDisplay::ChargingTimerCallback, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "charging_timer" + }; + ESP_ERROR_CHECK(esp_timer_create(&charging_timer_args, &charging_timer_)); + + // 创建电量检测定时器 + esp_timer_create_args_t battery_timer_args = { + .callback = &XINGZHI_1_54_TFT_LcdDisplay::BatteryTimerCallback, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_timer" + }; + ESP_ERROR_CHECK(esp_timer_create(&battery_timer_args, &battery_timer_)); + + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + boot_button_.OnPressDown([this]() { + this->UpdateInteractionTime(); + }); + + volume_up_button_.OnPressDown([this]() { + this->UpdateInteractionTime(); + }); + + volume_down_button_.OnPressDown([this]() { + this->UpdateInteractionTime(); + }); + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + ESP_LOGI(TAG, "Initialize LVGL library"); + lv_init(); + + ESP_LOGI(TAG, "Initialize LVGL port"); + lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + lvgl_port_init(&port_cfg); + + ESP_LOGI(TAG, "Adding LCD screen"); + const lvgl_port_display_cfg_t display_cfg = { + .io_handle = panel_io_, + .panel_handle = panel_, + .control_handle = nullptr, + .buffer_size = static_cast(width_ * 10), + .double_buffer = false, + .trans_size = 0, + .hres = static_cast(width_), + .vres = static_cast(height_), + .monochrome = false, + .rotation = { + .swap_xy = swap_xy, + .mirror_x = mirror_x, + .mirror_y = mirror_y, + }, + .color_format = LV_COLOR_FORMAT_RGB565, + .flags = { + .buff_dma = 1, + .buff_spiram = 0, + .sw_rotate = 0, + .swap_bytes = 1, + .full_refresh = 0, + .direct_mode = 0, + }, + }; + + display_ = lvgl_port_add_disp(&display_cfg); + if (display_ == nullptr) { + ESP_LOGE(TAG, "Failed to add display"); + return; + } + + if (offset_x != 0 || offset_y != 0) { + lv_display_set_offset(display_, offset_x, offset_y); + } + + // 读取初始亮度值 + Settings settings("display", true); + brightness_ = settings.GetInt("brightness", 75); + + SetBacklight(brightness_); + + SetupUI(); + StartChargingTimer(); + StartBatteryTimer(); +} + +XINGZHI_1_54_TFT_LcdDisplay::~XINGZHI_1_54_TFT_LcdDisplay() { + if (backlight_timer_ != nullptr) { + esp_timer_stop(backlight_timer_); + esp_timer_delete(backlight_timer_); + } + // 然后再清理 LVGL 对象 + if (content_ != nullptr) { + lv_obj_del(content_); + } + if (status_bar_ != nullptr) { + lv_obj_del(status_bar_); + } + if (side_bar_ != nullptr) { + lv_obj_del(side_bar_); + } + if (container_ != nullptr) { + lv_obj_del(container_); + } + if (display_ != nullptr) { + lv_display_delete(display_); + } + + if (panel_ != nullptr) { + esp_lcd_panel_del(panel_); + } + if (panel_io_ != nullptr) { + esp_lcd_panel_io_del(panel_io_); + } +} + +void XINGZHI_1_54_TFT_LcdDisplay::SetBacklight(uint8_t brightness) { + if (backlight_pin_ == GPIO_NUM_NC) { + return; + } + + if (brightness > 100) { + brightness = 100; + } + + ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness); + // 停止现有的定时器(如果正在运行) + esp_timer_stop(backlight_timer_); + + Settings settings("display", true); + if (is_light_sleep_) { + // 处于浅睡眠状态,保存浅睡眠亮度 + settings.SetInt("sleep_bright", brightness); + brightness_ = brightness; + } else { + // 正常状态,保存睡眠前亮度 + settings.SetInt("brightness", brightness); + brightness_ = brightness; + } + + // 启动定时器,每 5ms 更新一次 + ESP_ERROR_CHECK(esp_timer_start_periodic(backlight_timer_, 5 * 1000)); +} + +void XINGZHI_1_54_TFT_LcdDisplay::InitializeBacklight(gpio_num_t backlight_pin) { + if (backlight_pin == GPIO_NUM_NC) { + return; + } + + // Setup LEDC peripheral for PWM backlight control + const ledc_channel_config_t backlight_channel = { + .gpio_num = backlight_pin, + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = LCD_LEDC_CH, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = LEDC_TIMER_0, + .duty = 0, + .hpoint = 0, + .flags = { + .output_invert = backlight_output_invert_, + } + }; + const ledc_timer_config_t backlight_timer = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .duty_resolution = LEDC_TIMER_10_BIT, + .timer_num = LEDC_TIMER_0, + .freq_hz = 20000, //背光pwm频率需要高一点,防止电感啸叫 + .clk_cfg = LEDC_AUTO_CLK, + .deconfigure = false + }; + + ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer)); + ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel)); +} + +void XINGZHI_1_54_TFT_LcdDisplay::OnBacklightTimer() { + if (current_brightness_ < brightness_) { + current_brightness_++; + } else if (current_brightness_ > brightness_) { + current_brightness_--; + } + + // LEDC resolution set to 10bits, thus: 100% = 1023 + uint32_t duty_cycle = (1023 * current_brightness_) / 100; + ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty_cycle); + ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH); + + if (current_brightness_ == brightness_) { + esp_timer_stop(backlight_timer_); + } +} + +void XINGZHI_1_54_TFT_LcdDisplay::UpdateInteractionTime() { + last_interaction_time_ = esp_timer_get_time(); + if (is_light_sleep_) { + // 从浅睡眠中唤醒,恢复亮度 + Settings settings("display", true); + uint8_t norm_bright = settings.GetInt("brightness", 75); + SetBacklight(norm_bright); + is_light_sleep_ = false; + } +} + +void XINGZHI_1_54_TFT_LcdDisplay::CheckSleepState() { + int64_t current_time = esp_timer_get_time(); + int64_t elapsed_time = (current_time - last_interaction_time_) / 1000000; // 转换为秒 + + int charging_level = gpio_get_level(charging_pin_); + bool is_charging = (charging_level == 1); + + if (is_charging) { + // 正在充电,不进入睡眠 + return; + } + + if (elapsed_time >= 60 && !is_light_sleep_ && !is_deep_sleep_) { + is_light_sleep_ = true; + SetBacklight(1); + } else if (elapsed_time >= 300 && is_light_sleep_) + { + is_deep_sleep_ = true; + is_light_sleep_ = false; + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + } +} + +bool XINGZHI_1_54_TFT_LcdDisplay::Lock(int timeout_ms) { + return lvgl_port_lock(timeout_ms); +} + +void XINGZHI_1_54_TFT_LcdDisplay::Unlock() { + lvgl_port_unlock(); +} + +void XINGZHI_1_54_TFT_LcdDisplay::SetupUI() { + DisplayLockGuard lock(this); + + auto screen = lv_screen_active(); + lv_obj_set_style_text_font(screen, fonts_.text_font, 0); + lv_obj_set_style_text_color(screen, lv_color_black(), 0); + + /* Container */ + container_ = lv_obj_create(screen); + lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); + lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(container_, 0, 0); + lv_obj_set_style_border_width(container_, 0, 0); + lv_obj_set_style_pad_row(container_, 0, 0); + + /* Status bar */ + status_bar_ = lv_obj_create(container_); + lv_obj_set_size(status_bar_, LV_HOR_RES, fonts_.text_font->line_height); + lv_obj_set_style_radius(status_bar_, 0, 0); + + /* Content */ + content_ = lv_obj_create(container_); + lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_style_radius(content_, 0, 0); + lv_obj_set_width(content_, LV_HOR_RES); + lv_obj_set_flex_grow(content_, 1); + + lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); // 垂直布局(从上到下) + lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_EVENLY); // 子对象居中对齐,等距分布 + + emotion_label_ = lv_label_create(content_); + lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0); + lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); + + chat_message_label_ = lv_label_create(content_); + lv_label_set_text(chat_message_label_, ""); + lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); // 限制宽度为屏幕宽度的 90% + lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // 设置为自动换行模式 + lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // 设置文本居中对齐 + + /* Status bar */ + lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(status_bar_, 0, 0); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_column(status_bar_, 0, 0); + lv_obj_set_style_pad_left(status_bar_, 2, 0); + lv_obj_set_style_pad_right(status_bar_, 2, 0); + + network_label_ = lv_label_create(status_bar_); + lv_label_set_text(network_label_, ""); + lv_obj_set_style_text_font(network_label_, fonts_.icon_font, 0); + + notification_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(notification_label_, 1); + lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_label_set_text(notification_label_, "通知"); + lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + + status_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(status_label_, 1); + lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_label_set_text(status_label_, "正在初始化"); + lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); + + mute_label_ = lv_label_create(status_bar_); + lv_label_set_text(mute_label_, ""); + lv_obj_set_style_text_font(mute_label_, fonts_.icon_font, 0); + + battery_label_ = lv_label_create(status_bar_); + lv_label_set_text(battery_label_, ""); + lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0); + + // 充电状态标签 + charging_label_ = lv_label_create(status_bar_); + lv_obj_set_style_text_font(charging_label_, fonts_.icon_font, 0); + lv_obj_set_style_text_align(charging_label_, LV_TEXT_ALIGN_RIGHT, 0); + lv_obj_set_flex_grow(charging_label_, 0); + lv_label_set_text(charging_label_, FONT_AWESOME_BATTERY_CHARGING); + + // 检查充电状态,如果未充电,设置充电图标为空 + int charging_level = gpio_get_level(charging_pin_); + if (charging_level == 0) { + lv_label_set_text(charging_label_, ""); + } +} + +void XINGZHI_1_54_TFT_LcdDisplay::UpdateBatteryAndChargingDisplay(uint16_t average_adc) { + DisplayLockGuard lock(this); + + // 未充电时,显示电池图标 + if (charging_label_ != nullptr) { + lv_label_set_text(charging_label_, ""); + } + + uint8_t battery_level = 0; + if (average_adc < 1970) { + battery_level = 0; + // 显示电量过低提示窗口 + ShowLowBatteryPopup(); + } else if (average_adc >= 1970 && average_adc < 2100) { + battery_level = 1; + // 如果电量回升,隐藏提示窗口 + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } else if (average_adc >= 2100 && average_adc < 2200) { + battery_level = 2; + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } else if (average_adc >= 2200 && average_adc < 2300) { + battery_level = 3; + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } else { + battery_level = 4; + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } + + const char* battery_icon; + switch (battery_level) { + case 0: + battery_icon = FONT_AWESOME_BATTERY_EMPTY; + break; + case 1: + battery_icon = FONT_AWESOME_BATTERY_1; + break; + case 2: + battery_icon = FONT_AWESOME_BATTERY_2; + break; + case 3: + battery_icon = FONT_AWESOME_BATTERY_3; + break; + case 4: + battery_icon = FONT_AWESOME_BATTERY_FULL; + break; + default: + battery_icon = FONT_AWESOME_BATTERY_SLASH; + break; + } + + if (battery_label_ != nullptr) { + lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0); + lv_label_set_text(battery_label_, battery_icon); + } +} + +// lcd_display.c +void XINGZHI_1_54_TFT_LcdDisplay::ShowLowBatteryPopup() { + DisplayLockGuard lock(this); + if (low_battery_popup_ == nullptr) { + low_battery_popup_ = lv_obj_create(lv_scr_act()); + lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, LV_VER_RES * 0.5); + lv_obj_center(low_battery_popup_); + lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); + lv_obj_set_style_radius(low_battery_popup_, 10, 0); + + lv_obj_t* label = lv_label_create(low_battery_popup_); + lv_label_set_text(label, "电量过低,请充电"); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_center(label); + } + lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); +} + +uint16_t XINGZHI_1_54_TFT_LcdDisplay::ReadBatteryLevel() { + adc_oneshot_unit_handle_t adc_handle; + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_2, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + // 初始化 ADC 单元 + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle)); + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + // 配置 ADC 通道 + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_6, &chan_config)); + int adc_value; + // 读取 ADC 值 + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_CHANNEL_6, &adc_value)); + adc_oneshot_del_unit(adc_handle); + return adc_value; +} + +void XINGZHI_1_54_TFT_LcdDisplay::ChargingTimerCallback(void* arg) { + XINGZHI_1_54_TFT_LcdDisplay* display = static_cast(arg); + DisplayLockGuard lock(display); + + // 检查充电状态 + int charging_level = gpio_get_level(display->charging_pin_); + bool is_charging = (charging_level == 1); + display->OnStateChanged(); // 检测当前对对话状态 + // 检查电池是否充满,adc值超过2430,判定为充满 + bool is_battery_full = 0; + if (display->average_adc > 2430) + { + is_battery_full = 1; + } + if (is_charging) { + // 正在充电,更新交互时间,防止进入睡眠 + display->UpdateInteractionTime(); + if (is_battery_full) { + if (display->charging_label_ != nullptr) { + lv_label_set_text(display->charging_label_, ""); + } + if (display->battery_label_ != nullptr) { + lv_obj_set_style_text_font(display->battery_label_, display->fonts_.icon_font, 0); + lv_label_set_text(display->battery_label_, FONT_AWESOME_BATTERY_FULL); + } + } else { + if (display->charging_label_ != nullptr) { + lv_obj_set_style_text_font(display->charging_label_, display->fonts_.icon_font, 0); + lv_label_set_text(display->charging_label_, FONT_AWESOME_BATTERY_CHARGING); + } + if (display->battery_label_ != nullptr) { + lv_label_set_text(display->battery_label_, ""); + } + } + // 如果正在充电,隐藏电量过低提示窗口 + if (display->low_battery_popup_ != nullptr) { + lv_obj_add_flag(display->low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + display->was_charging = true; // 更新上一次的充电状态为正在充电 + } else { + if (display->was_charging) { + // 充电状态从充电变为未充电,立即读取并更新电池电量 + display->average_adc = display->ReadBatteryLevel(); + } else { + // 一直处于未充电状态,正常显示电池图标 + if (display->charging_label_ != nullptr) { + if (!display->first_battery_invert_) { + display->average_adc = display->ReadBatteryLevel(); + } + display->UpdateBatteryAndChargingDisplay(display->average_adc); + display->adc_values.clear(); + display->adc_count = 0; + } + } + display->was_charging = false; // 更新上一次的充电状态为未充电 + } + // 检查睡眠状态 + display->CheckSleepState(); +} + +void XINGZHI_1_54_TFT_LcdDisplay::BatteryTimerCallback(void* arg) { + XINGZHI_1_54_TFT_LcdDisplay* display = static_cast(arg); + uint16_t adc_value = display->ReadBatteryLevel(); + if (display->first_battery_invert_) { + display->adc_samp_interval = 180000000; // adc值采样的时间间隔 + // 停止当前定时器 + esp_timer_stop(display->battery_timer_); + // 重新启动定时器,使用新的时间间隔 + ESP_ERROR_CHECK(esp_timer_start_periodic(display->battery_timer_, display->adc_samp_interval)); + } + ESP_LOGI(TAG, "adc_samp_interval: %" PRId32 "", display->adc_samp_interval); + ESP_LOGI(TAG, "Value of first_battery_invert_ before condition: %d", display->first_battery_invert_); + display->adc_values.push_back(adc_value); + display->adc_count++; + + if (display->adc_count >= 1) { + uint32_t sum = 0; + for (uint16_t value : display->adc_values) { + sum += value; + } + display->average_adc = sum / display->adc_values.size(); + display->first_battery_invert_ = true; + } +} + +void XINGZHI_1_54_TFT_LcdDisplay::StartChargingTimer() { + ESP_ERROR_CHECK(esp_timer_start_periodic(charging_timer_, adc_samp_interval)); +} + +void XINGZHI_1_54_TFT_LcdDisplay::StartBatteryTimer() { + ESP_ERROR_CHECK(esp_timer_start_periodic(battery_timer_, adc_samp_interval)); +} + +void XINGZHI_1_54_TFT_LcdDisplay::SetEmotion(const char* emotion) { + struct Emotion { + const char* icon; + const char* text; + }; + + static const std::vector emotions = { + {"😶", "neutral"}, + {"🙂", "happy"}, + {"😆", "laughing"}, + {"😂", "funny"}, + {"😔", "sad"}, + {"😠", "angry"}, + {"😭", "crying"}, + {"😍", "loving"}, + {"😳", "embarrassed"}, + {"😯", "surprised"}, + {"😱", "shocked"}, + {"🤔", "thinking"}, + {"😉", "winking"}, + {"😎", "cool"}, + {"😌", "relaxed"}, + {"🤤", "delicious"}, + {"😘", "kissy"}, + {"😏", "confident"}, + {"😴", "sleepy"}, + {"😜", "silly"}, + {"🙄", "confused"} + }; + + // 查找匹配的表情 + std::string_view emotion_view(emotion); + auto it = std::find_if(emotions.begin(), emotions.end(), + [&emotion_view](const Emotion& e) { return e.text == emotion_view; }); + + DisplayLockGuard lock(this); + if (emotion_label_ == nullptr) { + return; + } + + // 如果找到匹配的表情就显示对应图标,否则显示默认的neutral表情 + lv_obj_set_style_text_font(emotion_label_, fonts_.emoji_font, 0); + if (it != emotions.end()) { + lv_label_set_text(emotion_label_, it->icon); + } else { + lv_label_set_text(emotion_label_, "😶"); + } +} + +void XINGZHI_1_54_TFT_LcdDisplay::SetIcon(const char* icon) { + DisplayLockGuard lock(this); + if (emotion_label_ == nullptr) { + return; + } + lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0); + lv_label_set_text(emotion_label_, icon); +} + +void XINGZHI_1_54_TFT_LcdDisplay::OnStateChanged() { + auto& app = Application::GetInstance(); + auto device_state = app.GetDeviceState(); + if (device_state != kDeviceStateIdle && !this->was_charging) { + UpdateInteractionTime(); + } +} \ No newline at end of file diff --git a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.h b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.h new file mode 100644 index 00000000..2a7581f3 --- /dev/null +++ b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.h @@ -0,0 +1,94 @@ +#ifndef LCD_DISPLAY_H +#define LCD_DISPLAY_H + +#include "display/display.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +// 新增的头文件包含语句 +#include +#include "button.h" + +class XINGZHI_1_54_TFT_LcdDisplay : public Display { +protected: + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + gpio_num_t backlight_pin_ = GPIO_NUM_NC; + bool backlight_output_invert_ = false; + + lv_draw_buf_t draw_buf_; + lv_obj_t* status_bar_ = nullptr; + lv_obj_t* content_ = nullptr; + lv_obj_t* container_ = nullptr; + lv_obj_t* side_bar_ = nullptr; + + DisplayFonts fonts_; + + esp_timer_handle_t backlight_timer_ = nullptr; + uint8_t current_brightness_ = 0; + + + lv_obj_t* charging_label_ = nullptr; // 充电状态标签 + lv_obj_t* low_battery_popup_ = nullptr; // 电量过低提示窗口对象指针 + lv_obj_t* battery_label_ = nullptr; // 已有,用于显示电量 + int32_t adc_samp_interval = 500000; // adc值采样的时间间隔,单位为微秒 + uint16_t average_adc = 0; // adc平均值 + esp_timer_handle_t charging_timer_; // 充电检测定时器句柄 + esp_timer_handle_t battery_timer_; // 电量检测定时器句柄 + gpio_num_t charging_pin_ = GPIO_NUM_38; // 充电检测引脚 + std::vector adc_values; // 用于存储读取的ADC值 + int adc_count = 0; // 记录已检测的ADC值数量 + bool was_charging = false; // 上一次的充电状态 + bool first_battery_invert_ = false; //首次上电直接检测电量 + void ShowLowBatteryPopup(); // 显示电量过低提示窗口的方法声明 + uint16_t ReadBatteryLevel(); // 读取电量的方法 + + int64_t last_interaction_time_; // 上次交互时间 + bool is_light_sleep_ = false; // 浅睡眠 + bool is_deep_sleep_ = false; // 深睡眠 + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + + + void OnBacklightTimer(); + void InitializeBacklight(gpio_num_t backlight_pin); + virtual void SetupUI(); + virtual bool Lock(int timeout_ms = 0) override; + virtual void Unlock() override; + + +public: + XINGZHI_1_54_TFT_LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + gpio_num_t backlight_pin, bool backlight_output_invert, + int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, + DisplayFonts fonts); + ~XINGZHI_1_54_TFT_LcdDisplay(); + + + static void ChargingTimerCallback(void* arg); + static void BatteryTimerCallback(void* arg); + void StartChargingTimer(); + void StartBatteryTimer(); + void UpdateBatteryAndChargingDisplay(uint16_t average_adc); + void OnStateChanged(); + + void UpdateInteractionTime(); + void CheckSleepState(); + + + virtual void SetEmotion(const char* emotion) override; + virtual void SetIcon(const char* icon) override; + virtual void SetBacklight(uint8_t brightness) override; + +}; + +#endif // LCD_DISPLAY_H diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc index 72f688f9..517e541d 100644 --- a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc +++ b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc @@ -1,7 +1,7 @@ #include "wifi_board.h" #include "audio_codecs/no_audio_codec.h" -#include "display/lcd_display.h" +#include "xingzhi_lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" @@ -23,7 +23,7 @@ private: Button boot_button_; Button volume_up_button_; Button volume_down_button_; - LcdDisplay* display_; + XINGZHI_1_54_TFT_LcdDisplay* display_; void InitializeSpi() { spi_bus_config_t buscfg = {}; @@ -103,7 +103,7 @@ private: ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new XINGZHI_1_54_TFT_LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY, { .text_font = &font_puhui_16_4, diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.cc b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.cc new file mode 100644 index 00000000..4959647c --- /dev/null +++ b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.cc @@ -0,0 +1,659 @@ +#include "xingzhi_lcd_display.h" + +#include +#include +#include +#include +#include +#include +#include +#include "assets/lang_config.h" + +#include "board.h" + +#include "esp_adc/adc_oneshot.h" +#include "button.h" +#include +#include "config.h" +#include "settings.h" +#include "esp_sleep.h" +#include "application.h" +#include "driver/rtc_io.h" +#include + +#define TAG "XINGZHI_1_54_TFT_LcdDisplay" +#define LCD_LEDC_CH LEDC_CHANNEL_0 +#define PIN_NUMBER GPIO_NUM_21 + +LV_FONT_DECLARE(font_awesome_30_4); + +XINGZHI_1_54_TFT_LcdDisplay::XINGZHI_1_54_TFT_LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + gpio_num_t backlight_pin, bool backlight_output_invert, + int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, + DisplayFonts fonts) + : panel_io_(panel_io), panel_(panel), backlight_pin_(backlight_pin), backlight_output_invert_(backlight_output_invert), + fonts_(fonts),last_interaction_time_(esp_timer_get_time()),boot_button_(BOOT_BUTTON_GPIO),volume_up_button_(VOLUME_UP_BUTTON_GPIO), volume_down_button_(VOLUME_DOWN_BUTTON_GPIO){ + width_ = width; + height_ = height; + + // 创建背光渐变定时器 + const esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + XINGZHI_1_54_TFT_LcdDisplay* display = static_cast(arg); + display->OnBacklightTimer(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "backlight_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &backlight_timer_)); + InitializeBacklight(backlight_pin); + + // draw white + std::vector buffer(width_, 0xFFFF); + for (int y = 0; y < height_; y++) { + esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data()); + } + + // 创建充电检测定时器 + esp_timer_create_args_t charging_timer_args = { + .callback = &XINGZHI_1_54_TFT_LcdDisplay::ChargingTimerCallback, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "charging_timer" + }; + ESP_ERROR_CHECK(esp_timer_create(&charging_timer_args, &charging_timer_)); + + // 创建电量检测定时器 + esp_timer_create_args_t battery_timer_args = { + .callback = &XINGZHI_1_54_TFT_LcdDisplay::BatteryTimerCallback, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_timer" + }; + ESP_ERROR_CHECK(esp_timer_create(&battery_timer_args, &battery_timer_)); + + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + boot_button_.OnPressDown([this]() { + this->UpdateInteractionTime(); + }); + + volume_up_button_.OnPressDown([this]() { + this->UpdateInteractionTime(); + }); + + volume_down_button_.OnPressDown([this]() { + this->UpdateInteractionTime(); + }); + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + ESP_LOGI(TAG, "Initialize LVGL library"); + lv_init(); + + ESP_LOGI(TAG, "Initialize LVGL port"); + lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + lvgl_port_init(&port_cfg); + + ESP_LOGI(TAG, "Adding LCD screen"); + const lvgl_port_display_cfg_t display_cfg = { + .io_handle = panel_io_, + .panel_handle = panel_, + .control_handle = nullptr, + .buffer_size = static_cast(width_ * 10), + .double_buffer = false, + .trans_size = 0, + .hres = static_cast(width_), + .vres = static_cast(height_), + .monochrome = false, + .rotation = { + .swap_xy = swap_xy, + .mirror_x = mirror_x, + .mirror_y = mirror_y, + }, + .color_format = LV_COLOR_FORMAT_RGB565, + .flags = { + .buff_dma = 1, + .buff_spiram = 0, + .sw_rotate = 0, + .swap_bytes = 1, + .full_refresh = 0, + .direct_mode = 0, + }, + }; + + display_ = lvgl_port_add_disp(&display_cfg); + if (display_ == nullptr) { + ESP_LOGE(TAG, "Failed to add display"); + return; + } + + if (offset_x != 0 || offset_y != 0) { + lv_display_set_offset(display_, offset_x, offset_y); + } + + // 读取初始亮度值 + Settings settings("display", true); + brightness_ = settings.GetInt("brightness", 75); + + SetBacklight(brightness_); + + SetupUI(); + StartChargingTimer(); + StartBatteryTimer(); +} + +XINGZHI_1_54_TFT_LcdDisplay::~XINGZHI_1_54_TFT_LcdDisplay() { + if (backlight_timer_ != nullptr) { + esp_timer_stop(backlight_timer_); + esp_timer_delete(backlight_timer_); + } + // 然后再清理 LVGL 对象 + if (content_ != nullptr) { + lv_obj_del(content_); + } + if (status_bar_ != nullptr) { + lv_obj_del(status_bar_); + } + if (side_bar_ != nullptr) { + lv_obj_del(side_bar_); + } + if (container_ != nullptr) { + lv_obj_del(container_); + } + if (display_ != nullptr) { + lv_display_delete(display_); + } + + if (panel_ != nullptr) { + esp_lcd_panel_del(panel_); + } + if (panel_io_ != nullptr) { + esp_lcd_panel_io_del(panel_io_); + } +} + +void XINGZHI_1_54_TFT_LcdDisplay::SetBacklight(uint8_t brightness) { + if (backlight_pin_ == GPIO_NUM_NC) { + return; + } + + if (brightness > 100) { + brightness = 100; + } + + ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness); + // 停止现有的定时器(如果正在运行) + esp_timer_stop(backlight_timer_); + + Settings settings("display", true); + if (is_light_sleep_) { + // 处于浅睡眠状态,保存浅睡眠亮度 + settings.SetInt("sleep_bright", brightness); + brightness_ = brightness; + } else { + // 正常状态,保存睡眠前亮度 + settings.SetInt("brightness", brightness); + brightness_ = brightness; + } + + // 启动定时器,每 5ms 更新一次 + ESP_ERROR_CHECK(esp_timer_start_periodic(backlight_timer_, 5 * 1000)); +} + +void XINGZHI_1_54_TFT_LcdDisplay::InitializeBacklight(gpio_num_t backlight_pin) { + if (backlight_pin == GPIO_NUM_NC) { + return; + } + + // Setup LEDC peripheral for PWM backlight control + const ledc_channel_config_t backlight_channel = { + .gpio_num = backlight_pin, + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = LCD_LEDC_CH, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = LEDC_TIMER_0, + .duty = 0, + .hpoint = 0, + .flags = { + .output_invert = backlight_output_invert_, + } + }; + const ledc_timer_config_t backlight_timer = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .duty_resolution = LEDC_TIMER_10_BIT, + .timer_num = LEDC_TIMER_0, + .freq_hz = 20000, //背光pwm频率需要高一点,防止电感啸叫 + .clk_cfg = LEDC_AUTO_CLK, + .deconfigure = false + }; + + ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer)); + ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel)); +} + +void XINGZHI_1_54_TFT_LcdDisplay::OnBacklightTimer() { + if (current_brightness_ < brightness_) { + current_brightness_++; + } else if (current_brightness_ > brightness_) { + current_brightness_--; + } + + // LEDC resolution set to 10bits, thus: 100% = 1023 + uint32_t duty_cycle = (1023 * current_brightness_) / 100; + ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty_cycle); + ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH); + + if (current_brightness_ == brightness_) { + esp_timer_stop(backlight_timer_); + } +} + +void XINGZHI_1_54_TFT_LcdDisplay::UpdateInteractionTime() { + last_interaction_time_ = esp_timer_get_time(); + if (is_light_sleep_) { + // 从浅睡眠中唤醒,恢复亮度 + Settings settings("display", true); + uint8_t norm_bright = settings.GetInt("brightness", 75); + SetBacklight(norm_bright); + is_light_sleep_ = false; + } +} + +void XINGZHI_1_54_TFT_LcdDisplay::CheckSleepState() { + int64_t current_time = esp_timer_get_time(); + int64_t elapsed_time = (current_time - last_interaction_time_) / 1000000; // 转换为秒 + + int charging_level = gpio_get_level(charging_pin_); + bool is_charging = (charging_level == 1); + + if (is_charging) { + // 正在充电,不进入睡眠 + return; + } + + if (elapsed_time >= 60 && !is_light_sleep_ && !is_deep_sleep_) { + is_light_sleep_ = true; + SetBacklight(1); + } else if (elapsed_time >= 300 && is_light_sleep_) + { + is_deep_sleep_ = true; + is_light_sleep_ = false; + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + } +} + +bool XINGZHI_1_54_TFT_LcdDisplay::Lock(int timeout_ms) { + return lvgl_port_lock(timeout_ms); +} + +void XINGZHI_1_54_TFT_LcdDisplay::Unlock() { + lvgl_port_unlock(); +} + +void XINGZHI_1_54_TFT_LcdDisplay::SetupUI() { + DisplayLockGuard lock(this); + + auto screen = lv_screen_active(); + lv_obj_set_style_text_font(screen, fonts_.text_font, 0); + lv_obj_set_style_text_color(screen, lv_color_black(), 0); + + /* Container */ + container_ = lv_obj_create(screen); + lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); + lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(container_, 0, 0); + lv_obj_set_style_border_width(container_, 0, 0); + lv_obj_set_style_pad_row(container_, 0, 0); + + /* Status bar */ + status_bar_ = lv_obj_create(container_); + lv_obj_set_size(status_bar_, LV_HOR_RES, fonts_.text_font->line_height); + lv_obj_set_style_radius(status_bar_, 0, 0); + + /* Content */ + content_ = lv_obj_create(container_); + lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_style_radius(content_, 0, 0); + lv_obj_set_width(content_, LV_HOR_RES); + lv_obj_set_flex_grow(content_, 1); + + lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); // 垂直布局(从上到下) + lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_EVENLY); // 子对象居中对齐,等距分布 + + emotion_label_ = lv_label_create(content_); + lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0); + lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); + + chat_message_label_ = lv_label_create(content_); + lv_label_set_text(chat_message_label_, ""); + lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); // 限制宽度为屏幕宽度的 90% + lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // 设置为自动换行模式 + lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // 设置文本居中对齐 + + /* Status bar */ + lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(status_bar_, 0, 0); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_column(status_bar_, 0, 0); + lv_obj_set_style_pad_left(status_bar_, 2, 0); + lv_obj_set_style_pad_right(status_bar_, 2, 0); + + network_label_ = lv_label_create(status_bar_); + lv_label_set_text(network_label_, ""); + lv_obj_set_style_text_font(network_label_, fonts_.icon_font, 0); + + notification_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(notification_label_, 1); + lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_label_set_text(notification_label_, "通知"); + lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + + status_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(status_label_, 1); + lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_label_set_text(status_label_, "正在初始化"); + lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); + + mute_label_ = lv_label_create(status_bar_); + lv_label_set_text(mute_label_, ""); + lv_obj_set_style_text_font(mute_label_, fonts_.icon_font, 0); + + battery_label_ = lv_label_create(status_bar_); + lv_label_set_text(battery_label_, ""); + lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0); + + // 充电状态标签 + charging_label_ = lv_label_create(status_bar_); + lv_obj_set_style_text_font(charging_label_, fonts_.icon_font, 0); + lv_obj_set_style_text_align(charging_label_, LV_TEXT_ALIGN_RIGHT, 0); + lv_obj_set_flex_grow(charging_label_, 0); + lv_label_set_text(charging_label_, FONT_AWESOME_BATTERY_CHARGING); + + // 检查充电状态,如果未充电,设置充电图标为空 + int charging_level = gpio_get_level(charging_pin_); + if (charging_level == 0) { + lv_label_set_text(charging_label_, ""); + } +} + +void XINGZHI_1_54_TFT_LcdDisplay::UpdateBatteryAndChargingDisplay(uint16_t average_adc) { + DisplayLockGuard lock(this); + + // 未充电时,显示电池图标 + if (charging_label_ != nullptr) { + lv_label_set_text(charging_label_, ""); + } + + uint8_t battery_level = 0; + if (average_adc < 1970) { + battery_level = 0; + // 显示电量过低提示窗口 + ShowLowBatteryPopup(); + } else if (average_adc >= 1970 && average_adc < 2100) { + battery_level = 1; + // 如果电量回升,隐藏提示窗口 + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } else if (average_adc >= 2100 && average_adc < 2200) { + battery_level = 2; + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } else if (average_adc >= 2200 && average_adc < 2300) { + battery_level = 3; + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } else { + battery_level = 4; + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } + + const char* battery_icon; + switch (battery_level) { + case 0: + battery_icon = FONT_AWESOME_BATTERY_EMPTY; + break; + case 1: + battery_icon = FONT_AWESOME_BATTERY_1; + break; + case 2: + battery_icon = FONT_AWESOME_BATTERY_2; + break; + case 3: + battery_icon = FONT_AWESOME_BATTERY_3; + break; + case 4: + battery_icon = FONT_AWESOME_BATTERY_FULL; + break; + default: + battery_icon = FONT_AWESOME_BATTERY_SLASH; + break; + } + + if (battery_label_ != nullptr) { + lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0); + lv_label_set_text(battery_label_, battery_icon); + } +} + +// lcd_display.c +void XINGZHI_1_54_TFT_LcdDisplay::ShowLowBatteryPopup() { + DisplayLockGuard lock(this); + if (low_battery_popup_ == nullptr) { + low_battery_popup_ = lv_obj_create(lv_scr_act()); + lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, LV_VER_RES * 0.5); + lv_obj_center(low_battery_popup_); + lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); + lv_obj_set_style_radius(low_battery_popup_, 10, 0); + + lv_obj_t* label = lv_label_create(low_battery_popup_); + lv_label_set_text(label, "电量过低,请充电"); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_center(label); + } + lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); +} + +uint16_t XINGZHI_1_54_TFT_LcdDisplay::ReadBatteryLevel() { + adc_oneshot_unit_handle_t adc_handle; + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_2, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + // 初始化 ADC 单元 + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle)); + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + // 配置 ADC 通道 + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_6, &chan_config)); + int adc_value; + // 读取 ADC 值 + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_CHANNEL_6, &adc_value)); + adc_oneshot_del_unit(adc_handle); + return adc_value; +} + +void XINGZHI_1_54_TFT_LcdDisplay::ChargingTimerCallback(void* arg) { + XINGZHI_1_54_TFT_LcdDisplay* display = static_cast(arg); + DisplayLockGuard lock(display); + + // 检查充电状态 + int charging_level = gpio_get_level(display->charging_pin_); + bool is_charging = (charging_level == 1); + display->OnStateChanged(); // 检测当前对对话状态 + // 检查电池是否充满,adc值超过2430,判定为充满 + bool is_battery_full = 0; + if (display->average_adc > 2430) + { + is_battery_full = 1; + } + if (is_charging) { + // 正在充电,更新交互时间,防止进入睡眠 + display->UpdateInteractionTime(); + if (is_battery_full) { + if (display->charging_label_ != nullptr) { + lv_label_set_text(display->charging_label_, ""); + } + if (display->battery_label_ != nullptr) { + lv_obj_set_style_text_font(display->battery_label_, display->fonts_.icon_font, 0); + lv_label_set_text(display->battery_label_, FONT_AWESOME_BATTERY_FULL); + } + } else { + if (display->charging_label_ != nullptr) { + lv_obj_set_style_text_font(display->charging_label_, display->fonts_.icon_font, 0); + lv_label_set_text(display->charging_label_, FONT_AWESOME_BATTERY_CHARGING); + } + if (display->battery_label_ != nullptr) { + lv_label_set_text(display->battery_label_, ""); + } + } + // 如果正在充电,隐藏电量过低提示窗口 + if (display->low_battery_popup_ != nullptr) { + lv_obj_add_flag(display->low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + display->was_charging = true; // 更新上一次的充电状态为正在充电 + } else { + if (display->was_charging) { + // 充电状态从充电变为未充电,立即读取并更新电池电量 + display->average_adc = display->ReadBatteryLevel(); + } else { + // 一直处于未充电状态,正常显示电池图标 + if (display->charging_label_ != nullptr) { + if (!display->first_battery_invert_) { + display->average_adc = display->ReadBatteryLevel(); + } + display->UpdateBatteryAndChargingDisplay(display->average_adc); + display->adc_values.clear(); + display->adc_count = 0; + } + } + display->was_charging = false; // 更新上一次的充电状态为未充电 + } + // 检查睡眠状态 + display->CheckSleepState(); +} + +void XINGZHI_1_54_TFT_LcdDisplay::BatteryTimerCallback(void* arg) { + XINGZHI_1_54_TFT_LcdDisplay* display = static_cast(arg); + uint16_t adc_value = display->ReadBatteryLevel(); + if (display->first_battery_invert_) { + display->adc_samp_interval = 180000000; // adc值采样的时间间隔 + // 停止当前定时器 + esp_timer_stop(display->battery_timer_); + // 重新启动定时器,使用新的时间间隔 + ESP_ERROR_CHECK(esp_timer_start_periodic(display->battery_timer_, display->adc_samp_interval)); + } + ESP_LOGI(TAG, "adc_samp_interval: %" PRId32 "", display->adc_samp_interval); + ESP_LOGI(TAG, "Value of first_battery_invert_ before condition: %d", display->first_battery_invert_); + display->adc_values.push_back(adc_value); + display->adc_count++; + + if (display->adc_count >= 1) { + uint32_t sum = 0; + for (uint16_t value : display->adc_values) { + sum += value; + } + display->average_adc = sum / display->adc_values.size(); + display->first_battery_invert_ = true; + } +} + +void XINGZHI_1_54_TFT_LcdDisplay::StartChargingTimer() { + ESP_ERROR_CHECK(esp_timer_start_periodic(charging_timer_, adc_samp_interval)); +} + +void XINGZHI_1_54_TFT_LcdDisplay::StartBatteryTimer() { + ESP_ERROR_CHECK(esp_timer_start_periodic(battery_timer_, adc_samp_interval)); +} + +void XINGZHI_1_54_TFT_LcdDisplay::SetEmotion(const char* emotion) { + struct Emotion { + const char* icon; + const char* text; + }; + + static const std::vector emotions = { + {"😶", "neutral"}, + {"🙂", "happy"}, + {"😆", "laughing"}, + {"😂", "funny"}, + {"😔", "sad"}, + {"😠", "angry"}, + {"😭", "crying"}, + {"😍", "loving"}, + {"😳", "embarrassed"}, + {"😯", "surprised"}, + {"😱", "shocked"}, + {"🤔", "thinking"}, + {"😉", "winking"}, + {"😎", "cool"}, + {"😌", "relaxed"}, + {"🤤", "delicious"}, + {"😘", "kissy"}, + {"😏", "confident"}, + {"😴", "sleepy"}, + {"😜", "silly"}, + {"🙄", "confused"} + }; + + // 查找匹配的表情 + std::string_view emotion_view(emotion); + auto it = std::find_if(emotions.begin(), emotions.end(), + [&emotion_view](const Emotion& e) { return e.text == emotion_view; }); + + DisplayLockGuard lock(this); + if (emotion_label_ == nullptr) { + return; + } + + // 如果找到匹配的表情就显示对应图标,否则显示默认的neutral表情 + lv_obj_set_style_text_font(emotion_label_, fonts_.emoji_font, 0); + if (it != emotions.end()) { + lv_label_set_text(emotion_label_, it->icon); + } else { + lv_label_set_text(emotion_label_, "😶"); + } +} + +void XINGZHI_1_54_TFT_LcdDisplay::SetIcon(const char* icon) { + DisplayLockGuard lock(this); + if (emotion_label_ == nullptr) { + return; + } + lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0); + lv_label_set_text(emotion_label_, icon); +} + +void XINGZHI_1_54_TFT_LcdDisplay::OnStateChanged() { + auto& app = Application::GetInstance(); + auto device_state = app.GetDeviceState(); + if (device_state != kDeviceStateIdle && !this->was_charging) { + UpdateInteractionTime(); + } +} \ No newline at end of file diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.h b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.h new file mode 100644 index 00000000..2a7581f3 --- /dev/null +++ b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.h @@ -0,0 +1,94 @@ +#ifndef LCD_DISPLAY_H +#define LCD_DISPLAY_H + +#include "display/display.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +// 新增的头文件包含语句 +#include +#include "button.h" + +class XINGZHI_1_54_TFT_LcdDisplay : public Display { +protected: + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + gpio_num_t backlight_pin_ = GPIO_NUM_NC; + bool backlight_output_invert_ = false; + + lv_draw_buf_t draw_buf_; + lv_obj_t* status_bar_ = nullptr; + lv_obj_t* content_ = nullptr; + lv_obj_t* container_ = nullptr; + lv_obj_t* side_bar_ = nullptr; + + DisplayFonts fonts_; + + esp_timer_handle_t backlight_timer_ = nullptr; + uint8_t current_brightness_ = 0; + + + lv_obj_t* charging_label_ = nullptr; // 充电状态标签 + lv_obj_t* low_battery_popup_ = nullptr; // 电量过低提示窗口对象指针 + lv_obj_t* battery_label_ = nullptr; // 已有,用于显示电量 + int32_t adc_samp_interval = 500000; // adc值采样的时间间隔,单位为微秒 + uint16_t average_adc = 0; // adc平均值 + esp_timer_handle_t charging_timer_; // 充电检测定时器句柄 + esp_timer_handle_t battery_timer_; // 电量检测定时器句柄 + gpio_num_t charging_pin_ = GPIO_NUM_38; // 充电检测引脚 + std::vector adc_values; // 用于存储读取的ADC值 + int adc_count = 0; // 记录已检测的ADC值数量 + bool was_charging = false; // 上一次的充电状态 + bool first_battery_invert_ = false; //首次上电直接检测电量 + void ShowLowBatteryPopup(); // 显示电量过低提示窗口的方法声明 + uint16_t ReadBatteryLevel(); // 读取电量的方法 + + int64_t last_interaction_time_; // 上次交互时间 + bool is_light_sleep_ = false; // 浅睡眠 + bool is_deep_sleep_ = false; // 深睡眠 + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + + + void OnBacklightTimer(); + void InitializeBacklight(gpio_num_t backlight_pin); + virtual void SetupUI(); + virtual bool Lock(int timeout_ms = 0) override; + virtual void Unlock() override; + + +public: + XINGZHI_1_54_TFT_LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + gpio_num_t backlight_pin, bool backlight_output_invert, + int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, + DisplayFonts fonts); + ~XINGZHI_1_54_TFT_LcdDisplay(); + + + static void ChargingTimerCallback(void* arg); + static void BatteryTimerCallback(void* arg); + void StartChargingTimer(); + void StartBatteryTimer(); + void UpdateBatteryAndChargingDisplay(uint16_t average_adc); + void OnStateChanged(); + + void UpdateInteractionTime(); + void CheckSleepState(); + + + virtual void SetEmotion(const char* emotion) override; + virtual void SetIcon(const char* icon) override; + virtual void SetBacklight(uint8_t brightness) override; + +}; + +#endif // LCD_DISPLAY_H