2024-11-18 06:17:39 +08:00
|
|
|
#include <esp_log.h>
|
|
|
|
|
#include <esp_err.h>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <cstdlib>
|
2025-03-06 05:22:06 +08:00
|
|
|
#include <cstring>
|
2025-08-29 09:04:23 +08:00
|
|
|
#include <font_awesome.h>
|
2024-11-18 06:17:39 +08:00
|
|
|
|
|
|
|
|
#include "display.h"
|
|
|
|
|
#include "board.h"
|
|
|
|
|
#include "application.h"
|
|
|
|
|
#include "audio_codec.h"
|
2025-02-14 01:15:10 +08:00
|
|
|
#include "settings.h"
|
2025-03-06 05:22:06 +08:00
|
|
|
#include "assets/lang_config.h"
|
2024-11-18 06:17:39 +08:00
|
|
|
|
|
|
|
|
#define TAG "Display"
|
|
|
|
|
|
|
|
|
|
Display::Display() {
|
|
|
|
|
// Notification timer
|
|
|
|
|
esp_timer_create_args_t notification_timer_args = {
|
|
|
|
|
.callback = [](void *arg) {
|
|
|
|
|
Display *display = static_cast<Display*>(arg);
|
|
|
|
|
DisplayLockGuard lock(display);
|
|
|
|
|
lv_obj_add_flag(display->notification_label_, LV_OBJ_FLAG_HIDDEN);
|
2025-08-23 07:12:14 +08:00
|
|
|
lv_obj_remove_flag(display->status_label_, LV_OBJ_FLAG_HIDDEN);
|
2024-11-18 06:17:39 +08:00
|
|
|
},
|
|
|
|
|
.arg = this,
|
|
|
|
|
.dispatch_method = ESP_TIMER_TASK,
|
2025-02-24 14:41:34 +08:00
|
|
|
.name = "notification_timer",
|
2024-11-18 06:17:39 +08:00
|
|
|
.skip_unhandled_events = false,
|
|
|
|
|
};
|
|
|
|
|
ESP_ERROR_CHECK(esp_timer_create(¬ification_timer_args, ¬ification_timer_));
|
|
|
|
|
|
2025-03-04 06:27:11 +08:00
|
|
|
// Create a power management lock
|
2025-03-05 09:37:13 +08:00
|
|
|
auto ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "display_update", &pm_lock_);
|
2025-03-04 08:16:16 +08:00
|
|
|
if (ret == ESP_ERR_NOT_SUPPORTED) {
|
|
|
|
|
ESP_LOGI(TAG, "Power management not supported");
|
|
|
|
|
} else {
|
|
|
|
|
ESP_ERROR_CHECK(ret);
|
|
|
|
|
}
|
2024-11-18 06:17:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Display::~Display() {
|
2025-02-03 23:43:07 +08:00
|
|
|
if (notification_timer_ != nullptr) {
|
|
|
|
|
esp_timer_stop(notification_timer_);
|
|
|
|
|
esp_timer_delete(notification_timer_);
|
|
|
|
|
}
|
2024-11-18 06:17:39 +08:00
|
|
|
|
|
|
|
|
if (network_label_ != nullptr) {
|
|
|
|
|
lv_obj_del(network_label_);
|
|
|
|
|
lv_obj_del(notification_label_);
|
|
|
|
|
lv_obj_del(status_label_);
|
|
|
|
|
lv_obj_del(mute_label_);
|
|
|
|
|
lv_obj_del(battery_label_);
|
2025-02-03 23:43:07 +08:00
|
|
|
lv_obj_del(emotion_label_);
|
2024-11-18 06:17:39 +08:00
|
|
|
}
|
2025-04-12 09:33:07 +08:00
|
|
|
if( low_battery_popup_ != nullptr ) {
|
|
|
|
|
lv_obj_del(low_battery_popup_);
|
|
|
|
|
}
|
2025-03-04 06:27:11 +08:00
|
|
|
if (pm_lock_ != nullptr) {
|
|
|
|
|
esp_pm_lock_delete(pm_lock_);
|
|
|
|
|
}
|
2024-11-18 06:17:39 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-19 23:54:59 +08:00
|
|
|
void Display::SetStatus(const char* status) {
|
2025-02-04 14:24:03 +08:00
|
|
|
DisplayLockGuard lock(this);
|
2024-11-18 06:17:39 +08:00
|
|
|
if (status_label_ == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-02-19 23:54:59 +08:00
|
|
|
lv_label_set_text(status_label_, status);
|
2025-08-23 07:12:14 +08:00
|
|
|
lv_obj_remove_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
|
2025-01-12 10:25:43 +08:00
|
|
|
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
|
2025-07-19 22:45:22 +08:00
|
|
|
|
|
|
|
|
last_status_update_time_ = std::chrono::system_clock::now();
|
2024-11-18 06:17:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Display::ShowNotification(const std::string ¬ification, int duration_ms) {
|
2025-02-19 23:54:59 +08:00
|
|
|
ShowNotification(notification.c_str(), duration_ms);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Display::ShowNotification(const char* notification, int duration_ms) {
|
2025-02-04 14:24:03 +08:00
|
|
|
DisplayLockGuard lock(this);
|
2024-11-18 06:17:39 +08:00
|
|
|
if (notification_label_ == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-02-19 23:54:59 +08:00
|
|
|
lv_label_set_text(notification_label_, notification);
|
2025-08-23 07:12:14 +08:00
|
|
|
lv_obj_remove_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
|
2024-11-18 06:17:39 +08:00
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-24 07:25:34 +08:00
|
|
|
void Display::UpdateStatusBar(bool update_all) {
|
2025-07-19 22:45:22 +08:00
|
|
|
auto& app = Application::GetInstance();
|
2024-11-18 06:17:39 +08:00
|
|
|
auto& board = Board::GetInstance();
|
|
|
|
|
auto codec = board.GetAudioCodec();
|
|
|
|
|
|
2025-07-19 22:45:22 +08:00
|
|
|
// Update mute icon
|
2025-02-01 23:08:41 +08:00
|
|
|
{
|
|
|
|
|
DisplayLockGuard lock(this);
|
2025-02-04 14:24:03 +08:00
|
|
|
if (mute_label_ == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-01 23:08:41 +08:00
|
|
|
// 如果静音状态改变,则更新图标
|
|
|
|
|
if (codec->output_volume() == 0 && !muted_) {
|
|
|
|
|
muted_ = true;
|
2025-08-29 09:04:23 +08:00
|
|
|
lv_label_set_text(mute_label_, FONT_AWESOME_VOLUME_XMARK);
|
2025-02-01 23:08:41 +08:00
|
|
|
} else if (codec->output_volume() > 0 && muted_) {
|
|
|
|
|
muted_ = false;
|
|
|
|
|
lv_label_set_text(mute_label_, "");
|
2025-02-03 23:43:07 +08:00
|
|
|
}
|
2024-11-18 06:17:39 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-19 22:45:22 +08:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-24 07:25:34 +08:00
|
|
|
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) {
|
2025-08-29 09:04:23 +08:00
|
|
|
icon = FONT_AWESOME_BATTERY_BOLT;
|
2025-05-24 07:25:34 +08:00
|
|
|
} else {
|
|
|
|
|
const char* levels[] = {
|
|
|
|
|
FONT_AWESOME_BATTERY_EMPTY, // 0-19%
|
2025-08-29 09:04:23 +08:00
|
|
|
FONT_AWESOME_BATTERY_QUARTER, // 20-39%
|
|
|
|
|
FONT_AWESOME_BATTERY_HALF, // 40-59%
|
|
|
|
|
FONT_AWESOME_BATTERY_THREE_QUARTERS, // 60-79%
|
2025-05-24 07:25:34 +08:00
|
|
|
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_);
|
|
|
|
|
}
|
2025-05-24 03:03:33 +08:00
|
|
|
|
2025-05-24 07:25:34 +08:00
|
|
|
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)) { // 如果低电量提示框隐藏,则显示
|
2025-08-23 07:12:14 +08:00
|
|
|
lv_obj_remove_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
2025-08-12 18:41:00 +08:00
|
|
|
app.PlaySound(Lang::Sounds::OGG_LOW_BATTERY);
|
2025-05-24 07:25:34 +08:00
|
|
|
}
|
|
|
|
|
} 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);
|
2025-03-06 05:22:06 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-24 07:25:34 +08:00
|
|
|
}
|
2024-11-18 06:17:39 +08:00
|
|
|
|
2025-05-24 07:25:34 +08:00
|
|
|
// 每 10 秒更新一次网络图标
|
|
|
|
|
static int seconds_counter = 0;
|
|
|
|
|
if (update_all || seconds_counter++ % 10 == 0) {
|
2025-05-24 03:03:33 +08:00
|
|
|
// 升级固件时,不读取 4G 网络状态,避免占用 UART 资源
|
|
|
|
|
auto device_state = Application::GetInstance().GetDeviceState();
|
|
|
|
|
static const std::vector<DeviceState> 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_);
|
|
|
|
|
}
|
2024-11-18 06:17:39 +08:00
|
|
|
}
|
2025-05-24 03:03:33 +08:00
|
|
|
}
|
2025-05-24 07:25:34 +08:00
|
|
|
|
|
|
|
|
esp_pm_lock_release(pm_lock_);
|
2024-11-18 06:17:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-02-19 23:54:59 +08:00
|
|
|
void Display::SetEmotion(const char* emotion) {
|
2025-08-29 09:04:23 +08:00
|
|
|
const char* utf8 = font_awesome_get_utf8(emotion);
|
|
|
|
|
if (utf8 != nullptr) {
|
|
|
|
|
SetIcon(utf8);
|
2024-11-18 06:17:39 +08:00
|
|
|
} else {
|
2025-08-29 09:04:23 +08:00
|
|
|
SetIcon(FONT_AWESOME_NEUTRAL);
|
2024-11-18 06:17:39 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Display::SetIcon(const char* icon) {
|
2025-02-04 14:24:03 +08:00
|
|
|
DisplayLockGuard lock(this);
|
2024-11-18 06:17:39 +08:00
|
|
|
if (emotion_label_ == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
lv_label_set_text(emotion_label_, icon);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-25 07:02:44 +08:00
|
|
|
void Display::SetPreviewImage(const lv_img_dsc_t* image) {
|
|
|
|
|
// Do nothing
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-19 23:54:59 +08:00
|
|
|
void Display::SetChatMessage(const char* role, const char* content) {
|
2025-02-16 06:59:19 +08:00
|
|
|
DisplayLockGuard lock(this);
|
|
|
|
|
if (chat_message_label_ == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-02-19 23:54:59 +08:00
|
|
|
lv_label_set_text(chat_message_label_, content);
|
2024-11-18 06:17:39 +08:00
|
|
|
}
|
2025-03-20 03:00:07 +08:00
|
|
|
|
|
|
|
|
void Display::SetTheme(const std::string& theme_name) {
|
|
|
|
|
current_theme_name_ = theme_name;
|
|
|
|
|
Settings settings("display", true);
|
|
|
|
|
settings.SetString("theme", theme_name);
|
|
|
|
|
}
|
2025-07-19 22:45:22 +08:00
|
|
|
|
2025-07-29 15:25:40 +08:00
|
|
|
void Display::SetPowerSaveMode(bool on) {
|
|
|
|
|
if (on) {
|
2025-07-19 22:45:22 +08:00
|
|
|
SetChatMessage("system", "");
|
|
|
|
|
SetEmotion("sleepy");
|
|
|
|
|
} else {
|
|
|
|
|
SetChatMessage("system", "");
|
|
|
|
|
SetEmotion("neutral");
|
|
|
|
|
}
|
|
|
|
|
}
|