xiaozhi-esp32/main/display/emote_display.cc
espressif2022 1e8fefbede
Some checks failed
Build Boards / Determine variants to build (push) Has been cancelled
Build Boards / Build ${{ matrix.name }} (push) Has been cancelled
feat: update emote display (#1629)
2026-01-07 20:39:17 +08:00

250 lines
7.0 KiB
C++

#include "emote_display.h"
// Standard C++ headers
#include <cstring>
#include <memory>
#include <unordered_map>
#include <tuple>
#include <algorithm>
// Standard C headers
#include <sys/time.h>
#include <time.h>
// ESP-IDF headers
#include <esp_log.h>
#include <esp_lcd_panel_io.h>
#include <esp_timer.h>
#include <lvgl.h>
// FreeRTOS headers
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
// Project headers
#include "assets/lang_config.h"
#include "assets.h"
#include "board.h"
#include "gfx.h"
#include "expression_emote.h"
LV_FONT_DECLARE(BUILTIN_TEXT_FONT);
namespace emote {
// ============================================================================
// Constants and Type Definitions
// ============================================================================
static const char* TAG = "EmoteDisplay";
// ============================================================================
// Forward Declarations
// ============================================================================
class EmoteDisplay;
// ============================================================================
// Helper Functions
// ============================================================================
static bool OnFlushIoReady(const esp_lcd_panel_io_handle_t panel_io,
esp_lcd_panel_io_event_data_t* const edata, void* user_ctx)
{
emote_handle_t handle = static_cast<emote_handle_t>(user_ctx);
if (handle) {
emote_notify_flush_finished(handle);
}
return true;
}
// Flush callback for emote
static void OnFlushCallback(int x_start, int y_start, int x_end, int y_end, const void* data, emote_handle_t handle)
{
esp_lcd_panel_handle_t panel = (esp_lcd_panel_handle_t)emote_get_user_data(handle);
if (panel != nullptr) {
esp_lcd_panel_draw_bitmap(panel, x_start, y_start, x_end, y_end, data);
}
}
// ============================================================================
// Graphics Initialization Functions
// ============================================================================
static emote_handle_t InitializeEmote(const esp_lcd_panel_handle_t panel, const int width, const int height)
{
if (!panel) {
ESP_LOGE(TAG, "Invalid panel");
return nullptr;
}
emote_config_t emote_cfg = {
.flags = {
.swap = true,
.double_buffer = true,
.buff_dma = false,
},
.gfx_emote = {
.h_res = width,
.v_res = height,
.fps = 30,
},
.buffers = {
.buf_pixels = static_cast<size_t>(width * 16),
},
.task = {
.task_priority = 5,
.task_stack = 6 * 1024,
.task_affinity = 0,
.task_stack_in_ext = false,
},
.flush_cb = OnFlushCallback,
.user_data = (void*)panel,
};
emote_handle_t emote_handle = emote_init(&emote_cfg);
if (!emote_handle) {
ESP_LOGE(TAG, "Failed to initialize emote");
return nullptr;
}
return emote_handle;
}
// ============================================================================
// EmoteDisplay Class Implementation
// ============================================================================
EmoteDisplay::EmoteDisplay(const esp_lcd_panel_handle_t panel, const esp_lcd_panel_io_handle_t panel_io,
const int width, const int height)
{
emote_handle_ = InitializeEmote(panel, width, height);
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = OnFlushIoReady,
};
esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, emote_handle_);
}
EmoteDisplay::~EmoteDisplay()
{
if (emote_handle_) {
emote_deinit(emote_handle_);
emote_handle_ = nullptr;
}
}
void EmoteDisplay::SetEmotion(const char* const emotion)
{
ESP_LOGI(TAG, "SetEmotion: %s", emotion);
if (emote_handle_ && emotion && strlen(emotion) > 0) {
emote_set_anim_emoji(emote_handle_, emotion);
}
}
void EmoteDisplay::SetChatMessage(const char* const role, const char* const content)
{
ESP_LOGI(TAG, "SetChatMessage: %s, %s", role, content);
if (emote_handle_ && content && strlen(content) > 0) {
if ((std::strcmp(role, "system") == 0) && std::strstr(content, "xiaozhi.me")) {
size_t len = strlen(content);
char* new_content = new char[len + 1];
strcpy(new_content, content);
std::replace(new_content, new_content + len, static_cast<char>(0x0A), static_cast<char>(0x20));
emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_SYS, new_content);
delete[] new_content;
} else {
emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_SPEAK, content);
}
}
}
void EmoteDisplay::SetStatus(const char* const status)
{
ESP_LOGI(TAG, "SetStatus: %s", status);
if (emote_handle_ && status && strlen(status) > 0) {
if (std::strcmp(status, Lang::Strings::LISTENING) == 0) {
emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_LISTEN, NULL);
} else if (std::strcmp(status, Lang::Strings::STANDBY) == 0) {
emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_IDLE, NULL);
} else if (std::strcmp(status, Lang::Strings::SPEAKING) == 0) {
emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_SPEAK, NULL);
} else if (std::strcmp(status, Lang::Strings::ERROR) == 0) {
emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_SET, NULL);
}
}
}
void EmoteDisplay::ShowNotification(const char* notification, int duration_ms)
{
ESP_LOGI(TAG, "ShowNotification: %s", notification);
if (emote_handle_ && notification && strlen(notification) > 0) {
emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_SYS, notification);
}
}
void EmoteDisplay::UpdateStatusBar(bool update_all)
{
ESP_LOGD(TAG, "UpdateStatusBar: %s", update_all ? "true" : "false");
if (!emote_handle_) {
return;
}
}
void EmoteDisplay::SetPowerSaveMode(bool on)
{
ESP_LOGI(TAG, "SetPowerSaveMode: %s", on ? "ON" : "OFF");
if (!emote_handle_) {
return;
}
}
void EmoteDisplay::SetPreviewImage(const void* image)
{
if (image) {
ESP_LOGI(TAG, "SetPreviewImage: Preview image not supported, using default icon");
}
}
void EmoteDisplay::SetTheme(Theme* const theme)
{
ESP_LOGI(TAG, "SetTheme: %p", theme);
}
bool EmoteDisplay::Lock(const int timeout_ms)
{
(void)timeout_ms;
return true;
}
void EmoteDisplay::Unlock()
{
}
bool EmoteDisplay::StopAnimDialog()
{
ESP_LOGI(TAG, "StopAnimDialog");
if (emote_handle_) {
return emote_stop_anim_dialog(emote_handle_);
}
return false;
}
bool EmoteDisplay::InsertAnimDialog(const char* emoji_name, uint32_t duration_ms)
{
ESP_LOGI(TAG, "InsertAnimDialog: %s, %d", emoji_name, duration_ms);
if (emote_handle_ && emoji_name) {
return emote_insert_anim_dialog(emote_handle_, emoji_name, duration_ms);
}
return false;
}
void EmoteDisplay::RefreshAll()
{
if (emote_handle_) {
emote_notify_all_refresh(emote_handle_);
return;
}
}
} // namespace emote