#include #include #include #include #include #include #include "lvgl_display.h" #include "board.h" #include "application.h" #include "audio_codec.h" #include "settings.h" #include "assets/lang_config.h" #include "jpg/image_to_jpeg.h" #define TAG "Display" LvglDisplay::LvglDisplay() { // Notification timer esp_timer_create_args_t notification_timer_args = { .callback = [](void *arg) { LvglDisplay *display = static_cast(arg); DisplayLockGuard lock(display); lv_obj_add_flag(display->notification_label_, LV_OBJ_FLAG_HIDDEN); lv_obj_remove_flag(display->status_label_, LV_OBJ_FLAG_HIDDEN); }, .arg = this, .dispatch_method = ESP_TIMER_TASK, .name = "notification_timer", .skip_unhandled_events = false, }; ESP_ERROR_CHECK(esp_timer_create(¬ification_timer_args, ¬ification_timer_)); // Create a power management lock auto ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "display_update", &pm_lock_); if (ret == ESP_ERR_NOT_SUPPORTED) { ESP_LOGI(TAG, "Power management not supported"); } else { ESP_ERROR_CHECK(ret); } } LvglDisplay::~LvglDisplay() { if (notification_timer_ != nullptr) { esp_timer_stop(notification_timer_); esp_timer_delete(notification_timer_); } if (network_label_ != nullptr) { lv_obj_del(network_label_); } if (notification_label_ != nullptr) { lv_obj_del(notification_label_); } if (status_label_ != nullptr) { lv_obj_del(status_label_); } if (mute_label_ != nullptr) { lv_obj_del(mute_label_); } if (battery_label_ != nullptr) { lv_obj_del(battery_label_); } if( low_battery_popup_ != nullptr ) { lv_obj_del(low_battery_popup_); } if (pm_lock_ != nullptr) { esp_pm_lock_delete(pm_lock_); } } void LvglDisplay::SetStatus(const char* status) { DisplayLockGuard lock(this); if (status_label_ == nullptr) { return; } lv_label_set_text(status_label_, status); lv_obj_remove_flag(status_label_, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); last_status_update_time_ = std::chrono::system_clock::now(); } void LvglDisplay::ShowNotification(const std::string ¬ification, int duration_ms) { ShowNotification(notification.c_str(), duration_ms); } void LvglDisplay::ShowNotification(const char* notification, int duration_ms) { DisplayLockGuard lock(this); if (notification_label_ == nullptr) { return; } lv_label_set_text(notification_label_, notification); lv_obj_remove_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(status_label_, LV_OBJ_FLAG_HIDDEN); esp_timer_stop(notification_timer_); ESP_ERROR_CHECK(esp_timer_start_once(notification_timer_, duration_ms * 1000)); } void LvglDisplay::UpdateStatusBar(bool update_all) { auto& app = Application::GetInstance(); auto& board = Board::GetInstance(); auto codec = board.GetAudioCodec(); // Update mute icon { DisplayLockGuard lock(this); if (mute_label_ == nullptr) { return; } // 如果静音状态改变,则更新图标 if (codec->output_volume() == 0 && !muted_) { muted_ = true; lv_label_set_text(mute_label_, FONT_AWESOME_VOLUME_XMARK); } else if (codec->output_volume() > 0 && muted_) { muted_ = false; lv_label_set_text(mute_label_, ""); } } // Update time if (app.GetDeviceState() == kDeviceStateIdle) { if (last_status_update_time_ + std::chrono::seconds(10) < std::chrono::system_clock::now()) { // Set status to clock "HH:MM" time_t now = time(NULL); struct tm* tm = localtime(&now); // Check if the we have already set the time if (tm->tm_year >= 2025 - 1900) { char time_str[16]; strftime(time_str, sizeof(time_str), "%H:%M ", tm); SetStatus(time_str); } else { ESP_LOGW(TAG, "System time is not set, tm_year: %d", tm->tm_year); } } } esp_pm_lock_acquire(pm_lock_); // 更新电池图标 int battery_level; bool charging, discharging; const char* icon = nullptr; if (board.GetBatteryLevel(battery_level, charging, discharging)) { if (charging) { icon = FONT_AWESOME_BATTERY_BOLT; } else { const char* levels[] = { FONT_AWESOME_BATTERY_EMPTY, // 0-19% FONT_AWESOME_BATTERY_QUARTER, // 20-39% FONT_AWESOME_BATTERY_HALF, // 40-59% FONT_AWESOME_BATTERY_THREE_QUARTERS, // 60-79% FONT_AWESOME_BATTERY_FULL, // 80-99% FONT_AWESOME_BATTERY_FULL, // 100% }; icon = levels[battery_level / 20]; } DisplayLockGuard lock(this); if (battery_label_ != nullptr && battery_icon_ != icon) { battery_icon_ = icon; lv_label_set_text(battery_label_, battery_icon_); } if (low_battery_popup_ != nullptr) { if (strcmp(icon, FONT_AWESOME_BATTERY_EMPTY) == 0 && discharging) { if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框隐藏,则显示 lv_obj_remove_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); app.PlaySound(Lang::Sounds::OGG_LOW_BATTERY); } } else { // Hide the low battery popup when the battery is not empty if (!lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框显示,则隐藏 lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); } } } } // 每 10 秒更新一次网络图标 static int seconds_counter = 0; if (update_all || seconds_counter++ % 10 == 0) { // 升级固件时,不读取 4G 网络状态,避免占用 UART 资源 auto device_state = Application::GetInstance().GetDeviceState(); static const std::vector allowed_states = { kDeviceStateIdle, kDeviceStateStarting, kDeviceStateWifiConfiguring, kDeviceStateListening, kDeviceStateActivating, }; if (std::find(allowed_states.begin(), allowed_states.end(), device_state) != allowed_states.end()) { icon = board.GetNetworkStateIcon(); if (network_label_ != nullptr && icon != nullptr && network_icon_ != icon) { DisplayLockGuard lock(this); network_icon_ = icon; lv_label_set_text(network_label_, network_icon_); } } } esp_pm_lock_release(pm_lock_); } void LvglDisplay::SetPreviewImage(std::unique_ptr image) { } void LvglDisplay::SetPowerSaveMode(bool on) { if (on) { SetChatMessage("system", ""); SetEmotion("sleepy"); } else { SetChatMessage("system", ""); SetEmotion("neutral"); } } bool LvglDisplay::SnapshotToJpeg(std::string& jpeg_data, int quality) { #if CONFIG_LV_USE_SNAPSHOT DisplayLockGuard lock(this); lv_obj_t* screen = lv_screen_active(); lv_draw_buf_t* draw_buffer = lv_snapshot_take(screen, LV_COLOR_FORMAT_RGB565); if (draw_buffer == nullptr) { ESP_LOGE(TAG, "Failed to take snapshot, draw_buffer is nullptr"); return false; } // swap bytes uint16_t* data = (uint16_t*)draw_buffer->data; size_t pixel_count = draw_buffer->data_size / 2; for (size_t i = 0; i < pixel_count; i++) { data[i] = __builtin_bswap16(data[i]); } // 清空输出字符串并使用回调版本,避免预分配大内存块 jpeg_data.clear(); // 🚀 使用回调版本的JPEG编码器,进一步节省内存 bool ret = image_to_jpeg_cb(draw_buffer->data, draw_buffer->data_size, draw_buffer->header.w, draw_buffer->header.h, PIXFORMAT_RGB565, quality, [](void *arg, size_t index, const void *data, size_t len) -> size_t { std::string* output = static_cast(arg); if (data && len > 0) { output->append(static_cast(data), len); } return len; }, &jpeg_data); if (!ret) { ESP_LOGE(TAG, "Failed to convert image to JPEG"); } lv_draw_buf_destroy(draw_buffer); return ret; #else ESP_LOGE(TAG, "LV_USE_SNAPSHOT is not enabled"); return false; #endif }