diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 9664ebdf..7d7a31e3 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -399,13 +399,9 @@ choice BOARD_TYPE config BOARD_TYPE_OTTO_ROBOT bool "ottoRobot" depends on IDF_TARGET_ESP32S3 - select LV_USE_GIF - select LV_GIF_CACHE_DECODE_DATA config BOARD_TYPE_ELECTRON_BOT bool "electronBot" depends on IDF_TARGET_ESP32S3 - select LV_USE_GIF - select LV_GIF_CACHE_DECODE_DATA config BOARD_TYPE_JIUCHUAN bool "九川智能" depends on IDF_TARGET_ESP32S3 diff --git a/main/boards/electron-bot/config.h b/main/boards/electron-bot/config.h index e7191d69..8f750e7f 100644 --- a/main/boards/electron-bot/config.h +++ b/main/boards/electron-bot/config.h @@ -47,5 +47,5 @@ #define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define ELECTRON_BOT_VERSION "1.1.3" +#define ELECTRON_BOT_VERSION "2.0.4" #endif // _BOARD_CONFIG_H_ diff --git a/main/boards/electron-bot/config.json b/main/boards/electron-bot/config.json index 08fc3419..177759e3 100644 --- a/main/boards/electron-bot/config.json +++ b/main/boards/electron-bot/config.json @@ -3,9 +3,7 @@ "builds": [ { "name": "electron-bot", - "sdkconfig_append": [ - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m.csv\"" - ] + "sdkconfig_append": [] } ] } \ No newline at end of file diff --git a/main/boards/electron-bot/electron_emoji_display.cc b/main/boards/electron-bot/electron_emoji_display.cc index 01b16f65..0a706464 100644 --- a/main/boards/electron-bot/electron_emoji_display.cc +++ b/main/boards/electron-bot/electron_emoji_display.cc @@ -1,150 +1,139 @@ #include "electron_emoji_display.h" -#include "lvgl_theme.h" #include -#include -#include #include -#include + +#include "assets/lang_config.h" +#include "display/lvgl_display/emoji_collection.h" +#include "display/lvgl_display/lvgl_image.h" +#include "display/lvgl_display/lvgl_theme.h" +#include "otto_emoji_gif.h" #define TAG "ElectronEmojiDisplay" - -// 表情映射表 - 将多种表情映射到现有6个GIF -const ElectronEmojiDisplay::EmotionMap ElectronEmojiDisplay::emotion_maps_[] = { - // 中性/平静类表情 -> staticstate - {"neutral", &staticstate}, - {"relaxed", &staticstate}, - {"sleepy", &staticstate}, - - // 积极/开心类表情 -> happy - {"happy", &happy}, - {"laughing", &happy}, - {"funny", &happy}, - {"loving", &happy}, - {"confident", &happy}, - {"winking", &happy}, - {"cool", &happy}, - {"delicious", &happy}, - {"kissy", &happy}, - {"silly", &happy}, - - // 悲伤类表情 -> sad - {"sad", &sad}, - {"crying", &sad}, - - // 愤怒类表情 -> anger - {"angry", &anger}, - - // 惊讶类表情 -> scare - {"surprised", &scare}, - {"shocked", &scare}, - - // 思考/困惑类表情 -> buxue - {"thinking", &buxue}, - {"confused", &buxue}, - {"embarrassed", &buxue}, - - {nullptr, nullptr} // 结束标记 -}; - -ElectronEmojiDisplay::ElectronEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, - esp_lcd_panel_handle_t panel, int width, int height, - int offset_x, int offset_y, bool mirror_x, bool mirror_y, +ElectronEmojiDisplay::ElectronEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy), - emotion_gif_(nullptr) { - SetupGifContainer(); + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + InitializeElectronEmojis(); + SetupChatLabel(); } -void ElectronEmojiDisplay::SetupGifContainer() { +void ElectronEmojiDisplay::InitializeElectronEmojis() { + ESP_LOGI(TAG, "初始化Electron GIF表情"); + + auto otto_emoji_collection = std::make_shared(); + + // 中性/平静类表情 -> staticstate + otto_emoji_collection->AddEmoji("staticstate", new LvglRawImage((void*)staticstate.data, staticstate.data_size)); + otto_emoji_collection->AddEmoji("neutral", new LvglRawImage((void*)staticstate.data, staticstate.data_size)); + otto_emoji_collection->AddEmoji("relaxed", new LvglRawImage((void*)staticstate.data, staticstate.data_size)); + otto_emoji_collection->AddEmoji("sleepy", new LvglRawImage((void*)staticstate.data, staticstate.data_size)); + otto_emoji_collection->AddEmoji("idle", new LvglRawImage((void*)staticstate.data, staticstate.data_size)); + + // 积极/开心类表情 -> happy + otto_emoji_collection->AddEmoji("happy", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("laughing", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("funny", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("loving", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("confident", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("winking", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("cool", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("delicious", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("kissy", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("silly", new LvglRawImage((void*)happy.data, happy.data_size)); + + // 悲伤类表情 -> sad + otto_emoji_collection->AddEmoji("sad", new LvglRawImage((void*)sad.data, sad.data_size)); + otto_emoji_collection->AddEmoji("crying", new LvglRawImage((void*)sad.data, sad.data_size)); + + // 愤怒类表情 -> anger + otto_emoji_collection->AddEmoji("anger", new LvglRawImage((void*)anger.data, anger.data_size)); + otto_emoji_collection->AddEmoji("angry", new LvglRawImage((void*)anger.data, anger.data_size)); + + // 惊讶类表情 -> scare + otto_emoji_collection->AddEmoji("scare", new LvglRawImage((void*)scare.data, scare.data_size)); + otto_emoji_collection->AddEmoji("surprised", new LvglRawImage((void*)scare.data, scare.data_size)); + otto_emoji_collection->AddEmoji("shocked", new LvglRawImage((void*)scare.data, scare.data_size)); + + // 思考/困惑类表情 -> buxue + otto_emoji_collection->AddEmoji("buxue", new LvglRawImage((void*)buxue.data, buxue.data_size)); + otto_emoji_collection->AddEmoji("thinking", new LvglRawImage((void*)buxue.data, buxue.data_size)); + otto_emoji_collection->AddEmoji("confused", new LvglRawImage((void*)buxue.data, buxue.data_size)); + otto_emoji_collection->AddEmoji("embarrassed", new LvglRawImage((void*)buxue.data, buxue.data_size)); + + // 将表情集合添加到主题中 + auto& theme_manager = LvglThemeManager::GetInstance(); + auto light_theme = theme_manager.GetTheme("light"); + auto dark_theme = theme_manager.GetTheme("dark"); + + if (light_theme != nullptr) { + light_theme->set_emoji_collection(otto_emoji_collection); + } + if (dark_theme != nullptr) { + dark_theme->set_emoji_collection(otto_emoji_collection); + } + + // 设置默认表情为staticstate + SetEmotion("staticstate"); + + ESP_LOGI(TAG, "Electron GIF表情初始化完成"); +} + +void ElectronEmojiDisplay::SetupChatLabel() { DisplayLockGuard lock(this); - if (emoji_label_) { - lv_obj_del(emoji_label_); - } if (chat_message_label_) { lv_obj_del(chat_message_label_); } - if (content_) { - lv_obj_del(content_); - } - content_ = lv_obj_create(container_); - lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_size(content_, LV_HOR_RES, LV_HOR_RES); - lv_obj_set_style_bg_opa(content_, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_width(content_, 0, 0); - lv_obj_set_flex_grow(content_, 1); - lv_obj_center(content_); - - emoji_label_ = lv_label_create(content_); - lv_label_set_text(emoji_label_, ""); - lv_obj_set_width(emoji_label_, 0); - lv_obj_set_style_border_width(emoji_label_, 0, 0); - lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); - - emotion_gif_ = lv_gif_create(content_); - int gif_size = LV_HOR_RES; - lv_obj_set_size(emotion_gif_, gif_size, gif_size); - lv_obj_set_style_border_width(emotion_gif_, 0, 0); - lv_obj_set_style_bg_opa(emotion_gif_, LV_OPA_TRANSP, 0); - lv_obj_center(emotion_gif_); - lv_gif_set_src(emotion_gif_, &staticstate); - - chat_message_label_ = lv_label_create(content_); + chat_message_label_ = lv_label_create(container_); lv_label_set_text(chat_message_label_, ""); - lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_width(chat_message_label_, width_ * 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); // 设置文本居中对齐 lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0); - lv_obj_set_style_border_width(chat_message_label_, 0, 0); - - lv_obj_set_style_bg_opa(chat_message_label_, LV_OPA_70, 0); - lv_obj_set_style_bg_color(chat_message_label_, lv_color_black(), 0); - lv_obj_set_style_pad_ver(chat_message_label_, 5, 0); - - lv_obj_align(chat_message_label_, LV_ALIGN_BOTTOM_MID, 0, 0); - - auto& theme_manager = LvglThemeManager::GetInstance(); - auto theme = theme_manager.GetTheme("dark"); - if (theme != nullptr) { - LcdDisplay::SetTheme(theme); - } + SetTheme(LvglThemeManager::GetInstance().GetTheme("dark")); } -void ElectronEmojiDisplay::SetEmotion(const char* emotion) { - if (!emotion || !emotion_gif_) { - return; - } - +LV_FONT_DECLARE(OTTO_ICON_FONT); +void ElectronEmojiDisplay::SetStatus(const char* status) { + auto lvgl_theme = static_cast(current_theme_); + auto text_font = lvgl_theme->text_font()->font(); DisplayLockGuard lock(this); - - for (const auto& map : emotion_maps_) { - if (map.name && strcmp(map.name, emotion) == 0) { - lv_gif_set_src(emotion_gif_, map.gif); - ESP_LOGI(TAG, "设置表情: %s", emotion); - return; - } - } - - lv_gif_set_src(emotion_gif_, &staticstate); - ESP_LOGI(TAG, "未知表情'%s',使用默认", emotion); -} - -void ElectronEmojiDisplay::SetChatMessage(const char* role, const char* content) { - DisplayLockGuard lock(this); - if (chat_message_label_ == nullptr) { + if (!status) { + ESP_LOGE(TAG, "SetStatus: status is nullptr"); return; } - if (content == nullptr || strlen(content) == 0) { - lv_obj_add_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); + if (strcmp(status, Lang::Strings::LISTENING) == 0) { + lv_obj_set_style_text_font(status_label_, &OTTO_ICON_FONT, 0); + lv_label_set_text(status_label_, "\xEF\x84\xB0"); // U+F130 麦克风图标 + lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(network_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(battery_label_, LV_OBJ_FLAG_HIDDEN); + return; + } else if (strcmp(status, Lang::Strings::SPEAKING) == 0) { + lv_obj_set_style_text_font(status_label_, &OTTO_ICON_FONT, 0); + lv_label_set_text(status_label_, "\xEF\x80\xA8"); // U+F028 说话图标 + lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(network_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(battery_label_, LV_OBJ_FLAG_HIDDEN); + return; + } else if (strcmp(status, Lang::Strings::CONNECTING) == 0) { + lv_obj_set_style_text_font(status_label_, &OTTO_ICON_FONT, 0); + lv_label_set_text(status_label_, "\xEF\x83\x81"); // U+F0c1 连接图标 + lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN); + return; + } else if (strcmp(status, Lang::Strings::STANDBY) == 0) { + lv_obj_set_style_text_font(status_label_, text_font, 0); + lv_label_set_text(status_label_, ""); + lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN); return; } - lv_label_set_text(chat_message_label_, content); - lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); - - ESP_LOGI(TAG, "设置聊天消息 [%s]: %s", role, content); + lv_obj_set_style_text_font(status_label_, text_font, 0); + lv_label_set_text(status_label_, status); + lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(network_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(battery_label_, LV_OBJ_FLAG_HIDDEN); } \ No newline at end of file diff --git a/main/boards/electron-bot/electron_emoji_display.h b/main/boards/electron-bot/electron_emoji_display.h index cdbb7588..50a289a8 100644 --- a/main/boards/electron-bot/electron_emoji_display.h +++ b/main/boards/electron-bot/electron_emoji_display.h @@ -1,48 +1,22 @@ #pragma once -#include - #include "display/lcd_display.h" -// Electron Bot表情GIF声明 - 使用与Otto相同的6个表情 -LV_IMAGE_DECLARE(staticstate); // 静态状态/中性表情 -LV_IMAGE_DECLARE(sad); // 悲伤 -LV_IMAGE_DECLARE(happy); // 开心 -LV_IMAGE_DECLARE(scare); // 惊吓/惊讶 -LV_IMAGE_DECLARE(buxue); // 不学/困惑 -LV_IMAGE_DECLARE(anger); // 愤怒 - /** - * @brief Electron Bot GIF表情显示类 - * 继承LcdDisplay,添加GIF表情支持 + * @brief Electron机器人GIF表情显示类 + * 继承SpiLcdDisplay,通过EmojiCollection添加GIF表情支持 */ class ElectronEmojiDisplay : public SpiLcdDisplay { -public: + public: /** * @brief 构造函数,参数与SpiLcdDisplay相同 */ - ElectronEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, bool mirror_x, - bool mirror_y, bool swap_xy); + ElectronEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy); virtual ~ElectronEmojiDisplay() = default; + virtual void SetStatus(const char* status) override; - // 重写表情设置方法 - virtual void SetEmotion(const char* emotion) override; - - // 重写聊天消息设置方法 - virtual void SetChatMessage(const char* role, const char* content) override; - -private: - void SetupGifContainer(); - - lv_obj_t* emotion_gif_; ///< GIF表情组件 - - // 表情映射 - struct EmotionMap { - const char* name; - const lv_image_dsc_t* gif; - }; - - static const EmotionMap emotion_maps_[]; + private: + void InitializeElectronEmojis(); + void SetupChatLabel(); }; \ No newline at end of file diff --git a/main/boards/electron-bot/otto_icon_font.c b/main/boards/electron-bot/otto_icon_font.c new file mode 100644 index 00000000..dc05cce6 --- /dev/null +++ b/main/boards/electron-bot/otto_icon_font.c @@ -0,0 +1,121 @@ +/******************************************************************************* + * Size: 20 px + * Bpp: 1 + * Opts: --bpp 1 --size 20 --no-compress --stride 1 --align 1 --font fontawesome-webfont.ttf --range 61744,61633,61480 --format lvgl -o OTTO_ICON_FONT.c + ******************************************************************************/ + +#ifdef __has_include +#if __has_include("lvgl.h") +#ifndef LV_LVGL_H_INCLUDE_SIMPLE +#define LV_LVGL_H_INCLUDE_SIMPLE +#endif +#endif +#endif + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#ifndef ENABLE_OTTO_ICON_FONT +#define ENABLE_OTTO_ICON_FONT 1 +#endif + +#if ENABLE_OTTO_ICON_FONT + +/*----------------- + * BITMAPS + *----------------*/ + +/*Store the image of the glyphs*/ +static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { + /* U+F028 "" */ + 0x0, 0x6, 0x0, 0x10, 0xe0, 0x6, 0x6, 0x3, 0xc6, 0x60, 0xf8, 0x65, 0xff, 0x24, 0xff, 0xe6, 0x4f, 0xfc, 0x49, 0xff, 0x89, 0x3f, 0xf3, 0x27, 0xfe, 0x49, 0x87, 0xc3, 0x20, 0x78, 0xcc, 0x3, 0x3, 0x0, + 0x21, 0xc0, 0x0, 0x30, + + /* U+F0C1 "" */ + 0x1e, 0x0, 0xf, 0xc0, 0x7, 0x38, 0x3, 0x87, 0x0, 0xc0, 0xc0, 0x30, 0xb0, 0x7, 0x3c, 0x0, 0xef, 0x0, 0x1f, 0xfe, 0x3, 0xff, 0xc0, 0x7, 0x38, 0x3, 0xe7, 0x0, 0xd0, 0xc0, 0x30, 0x30, 0x6, 0x1c, 0x0, + 0xce, 0x0, 0x1f, 0x0, 0x3, 0x80, + + /* U+F130 "" */ + 0x7, 0x0, 0xfe, 0x7, 0xf0, 0x3f, 0x81, 0xfc, 0xf, 0xe0, 0x7f, 0x13, 0xf9, 0x9f, 0xcc, 0xfe, 0x67, 0xf3, 0xbf, 0xb4, 0x73, 0x18, 0x30, 0x7f, 0x0, 0x60, 0x2, 0x1, 0xff, 0x0}; + +/*--------------------- + * GLYPH DESCRIPTION + *--------------------*/ + +static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */, + {.bitmap_index = 0, .adv_w = 297, .box_w = 19, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 38, .adv_w = 297, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 79, .adv_w = 206, .box_w = 13, .box_h = 18, .ofs_x = 0, .ofs_y = -1}}; + +/*--------------------- + * CHARACTER MAPPING + *--------------------*/ + +static const uint16_t unicode_list_0[] = {0x0, 0x99, 0x108}; + +/*Collect the unicode lists and glyph_id offsets*/ +static const lv_font_fmt_txt_cmap_t cmaps[] = { + {.range_start = 61480, .range_length = 265, .glyph_id_start = 1, .unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 3, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY}}; + +/*-------------------- + * ALL CUSTOM DATA + *--------------------*/ + +#if LVGL_VERSION_MAJOR == 8 +/*Store all the custom data of the font*/ +static lv_font_fmt_txt_glyph_cache_t cache; +#endif + +#if LVGL_VERSION_MAJOR >= 8 +static const lv_font_fmt_txt_dsc_t otto_icon_font_dsc = { +#else +static lv_font_fmt_txt_dsc_t otto_icon_font_dsc = { +#endif + .glyph_bitmap = glyph_bitmap, + .glyph_dsc = glyph_dsc, + .cmaps = cmaps, + .kern_dsc = NULL, + .kern_scale = 0, + .cmap_num = 1, + .bpp = 1, + .kern_classes = 0, + .bitmap_format = 0, +#if LVGL_VERSION_MAJOR == 8 + .cache = &cache +#endif + +}; + +/*----------------- + * PUBLIC FONT + *----------------*/ + +/*Initialize a public general font descriptor*/ +#if LVGL_VERSION_MAJOR >= 8 +const lv_font_t OTTO_ICON_FONT = { +#else +lv_font_t OTTO_ICON_FONT = { +#endif + .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/ + .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/ + .line_height = 18, /*The maximum line height required by the font*/ + .base_line = 1, /*Baseline measured from the bottom of the line*/ +#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0) + .subpx = LV_FONT_SUBPX_NONE, +#endif +#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8 + .underline_position = 0, + .underline_thickness = 0, +#endif + .static_bitmap = 0, + .dsc = &otto_icon_font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */ +#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9 + .fallback = NULL, +#endif + .user_data = NULL, +}; + +#endif /*#if ENABLE_OTTO_ICON_FONT*/ diff --git a/main/boards/otto-robot/config.h b/main/boards/otto-robot/config.h index 53ebcdaf..ab5c9a65 100644 --- a/main/boards/otto-robot/config.h +++ b/main/boards/otto-robot/config.h @@ -47,6 +47,6 @@ #define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define OTTO_ROBOT_VERSION "1.4.4" +#define OTTO_ROBOT_VERSION "2.0.4" #endif // _BOARD_CONFIG_H_ diff --git a/main/boards/otto-robot/config.json b/main/boards/otto-robot/config.json index a00a8278..1e0eabce 100644 --- a/main/boards/otto-robot/config.json +++ b/main/boards/otto-robot/config.json @@ -3,10 +3,7 @@ "builds": [ { "name": "otto-robot", - "sdkconfig_append": [ - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m.csv\"", - "CONFIG_LVGL_USE_GIF=y" - ] + "sdkconfig_append": [] } ] } \ No newline at end of file diff --git a/main/boards/otto-robot/otto_controller.cc b/main/boards/otto-robot/otto_controller.cc index 421dbc78..56972337 100644 --- a/main/boards/otto-robot/otto_controller.cc +++ b/main/boards/otto-robot/otto_controller.cc @@ -371,7 +371,7 @@ public: mcp_server.AddTool( "self.otto.set_trim", - "校准单个舵机位置。设置指定舵机的微调参数以调整Otto的初始站立姿态,设置将永久保存。" + "校准单个舵机位置。设置指定舵机的微调参数以调整机器人的初始站立姿态,设置将永久保存。" "servo_type: 舵机类型(left_leg/right_leg/left_foot/right_foot/left_hand/right_hand); " "trim_value: 微调值(-50到50度)", PropertyList({Property("servo_type", kPropertyTypeString, "left_leg"), diff --git a/main/boards/otto-robot/otto_emoji_display.cc b/main/boards/otto-robot/otto_emoji_display.cc index 32158ff5..c6477fcf 100644 --- a/main/boards/otto-robot/otto_emoji_display.cc +++ b/main/boards/otto-robot/otto_emoji_display.cc @@ -1,152 +1,138 @@ #include "otto_emoji_display.h" -#include "lvgl_theme.h" #include -#include -#include #include -#include -#include "display/lcd_display.h" +#include "assets/lang_config.h" +#include "display/lvgl_display/emoji_collection.h" +#include "display/lvgl_display/lvgl_image.h" +#include "display/lvgl_display/lvgl_theme.h" +#include "otto_emoji_gif.h" #define TAG "OttoEmojiDisplay" +OttoEmojiDisplay::OttoEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + InitializeOttoEmojis(); + SetupChatLabel(); +} + +void OttoEmojiDisplay::InitializeOttoEmojis() { + ESP_LOGI(TAG, "初始化Otto GIF表情"); + + auto otto_emoji_collection = std::make_shared(); -// 表情映射表 - 将原版21种表情映射到现有6个GIF -const OttoEmojiDisplay::EmotionMap OttoEmojiDisplay::emotion_maps_[] = { // 中性/平静类表情 -> staticstate - {"neutral", &staticstate}, - {"relaxed", &staticstate}, - {"sleepy", &staticstate}, + otto_emoji_collection->AddEmoji("staticstate", new LvglRawImage((void*)staticstate.data, staticstate.data_size)); + otto_emoji_collection->AddEmoji("neutral", new LvglRawImage((void*)staticstate.data, staticstate.data_size)); + otto_emoji_collection->AddEmoji("relaxed", new LvglRawImage((void*)staticstate.data, staticstate.data_size)); + otto_emoji_collection->AddEmoji("sleepy", new LvglRawImage((void*)staticstate.data, staticstate.data_size)); + otto_emoji_collection->AddEmoji("idle", new LvglRawImage((void*)staticstate.data, staticstate.data_size)); // 积极/开心类表情 -> happy - {"happy", &happy}, - {"laughing", &happy}, - {"funny", &happy}, - {"loving", &happy}, - {"confident", &happy}, - {"winking", &happy}, - {"cool", &happy}, - {"delicious", &happy}, - {"kissy", &happy}, - {"silly", &happy}, + otto_emoji_collection->AddEmoji("happy", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("laughing", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("funny", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("loving", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("confident", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("winking", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("cool", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("delicious", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("kissy", new LvglRawImage((void*)happy.data, happy.data_size)); + otto_emoji_collection->AddEmoji("silly", new LvglRawImage((void*)happy.data, happy.data_size)); // 悲伤类表情 -> sad - {"sad", &sad}, - {"crying", &sad}, + otto_emoji_collection->AddEmoji("sad", new LvglRawImage((void*)sad.data, sad.data_size)); + otto_emoji_collection->AddEmoji("crying", new LvglRawImage((void*)sad.data, sad.data_size)); // 愤怒类表情 -> anger - {"angry", &anger}, + otto_emoji_collection->AddEmoji("anger", new LvglRawImage((void*)anger.data, anger.data_size)); + otto_emoji_collection->AddEmoji("angry", new LvglRawImage((void*)anger.data, anger.data_size)); // 惊讶类表情 -> scare - {"surprised", &scare}, - {"shocked", &scare}, + otto_emoji_collection->AddEmoji("scare", new LvglRawImage((void*)scare.data, scare.data_size)); + otto_emoji_collection->AddEmoji("surprised", new LvglRawImage((void*)scare.data, scare.data_size)); + otto_emoji_collection->AddEmoji("shocked", new LvglRawImage((void*)scare.data, scare.data_size)); // 思考/困惑类表情 -> buxue - {"thinking", &buxue}, - {"confused", &buxue}, - {"embarrassed", &buxue}, + otto_emoji_collection->AddEmoji("buxue", new LvglRawImage((void*)buxue.data, buxue.data_size)); + otto_emoji_collection->AddEmoji("thinking", new LvglRawImage((void*)buxue.data, buxue.data_size)); + otto_emoji_collection->AddEmoji("confused", new LvglRawImage((void*)buxue.data, buxue.data_size)); + otto_emoji_collection->AddEmoji("embarrassed", new LvglRawImage((void*)buxue.data, buxue.data_size)); - {nullptr, nullptr} // 结束标记 -}; + // 将表情集合添加到主题中 + auto& theme_manager = LvglThemeManager::GetInstance(); + auto light_theme = theme_manager.GetTheme("light"); + auto dark_theme = theme_manager.GetTheme("dark"); -OttoEmojiDisplay::OttoEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, bool mirror_x, - bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy), - emotion_gif_(nullptr) { - SetupGifContainer(); -}; - -void OttoEmojiDisplay::SetupGifContainer() { - DisplayLockGuard lock(this); - - if (emoji_label_) { - lv_obj_del(emoji_label_); + if (light_theme != nullptr) { + light_theme->set_emoji_collection(otto_emoji_collection); } + if (dark_theme != nullptr) { + dark_theme->set_emoji_collection(otto_emoji_collection); + } + + // 设置默认表情为staticstate + SetEmotion("staticstate"); + + ESP_LOGI(TAG, "Otto GIF表情初始化完成"); +} + +void OttoEmojiDisplay::SetupChatLabel() { + DisplayLockGuard lock(this); if (chat_message_label_) { lv_obj_del(chat_message_label_); } - if (content_) { - lv_obj_del(content_); - } - content_ = lv_obj_create(container_); - lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_size(content_, LV_HOR_RES, LV_HOR_RES); - lv_obj_set_style_bg_opa(content_, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_width(content_, 0, 0); - lv_obj_set_flex_grow(content_, 1); - lv_obj_center(content_); - - emoji_label_ = lv_label_create(content_); - lv_label_set_text(emoji_label_, ""); - lv_obj_set_width(emoji_label_, 0); - lv_obj_set_style_border_width(emoji_label_, 0, 0); - lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); - - emotion_gif_ = lv_gif_create(content_); - int gif_size = LV_HOR_RES; - lv_obj_set_size(emotion_gif_, gif_size, gif_size); - lv_obj_set_style_border_width(emotion_gif_, 0, 0); - lv_obj_set_style_bg_opa(emotion_gif_, LV_OPA_TRANSP, 0); - lv_obj_center(emotion_gif_); - lv_gif_set_src(emotion_gif_, &staticstate); - - chat_message_label_ = lv_label_create(content_); + chat_message_label_ = lv_label_create(container_); lv_label_set_text(chat_message_label_, ""); - lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_width(chat_message_label_, width_ * 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); // 设置文本居中对齐 lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0); - lv_obj_set_style_border_width(chat_message_label_, 0, 0); - - lv_obj_set_style_bg_opa(chat_message_label_, LV_OPA_70, 0); - lv_obj_set_style_bg_color(chat_message_label_, lv_color_black(), 0); - lv_obj_set_style_pad_ver(chat_message_label_, 5, 0); - - lv_obj_align(chat_message_label_, LV_ALIGN_BOTTOM_MID, 0, 0); - - auto& theme_manager = LvglThemeManager::GetInstance(); - auto theme = theme_manager.GetTheme("dark"); - if (theme != nullptr) { - LcdDisplay::SetTheme(theme); - } + SetTheme(LvglThemeManager::GetInstance().GetTheme("dark")); } -void OttoEmojiDisplay::SetEmotion(const char* emotion) { - if (!emotion || !emotion_gif_) { - return; - } - +LV_FONT_DECLARE(OTTO_ICON_FONT); +void OttoEmojiDisplay::SetStatus(const char* status) { + auto lvgl_theme = static_cast(current_theme_); + auto text_font = lvgl_theme->text_font()->font(); DisplayLockGuard lock(this); - - for (const auto& map : emotion_maps_) { - if (map.name && strcmp(map.name, emotion) == 0) { - lv_gif_set_src(emotion_gif_, map.gif); - ESP_LOGI(TAG, "设置表情: %s", emotion); - return; - } - } - - lv_gif_set_src(emotion_gif_, &staticstate); - ESP_LOGI(TAG, "未知表情'%s',使用默认", emotion); -} - -void OttoEmojiDisplay::SetChatMessage(const char* role, const char* content) { - DisplayLockGuard lock(this); - if (chat_message_label_ == nullptr) { + if (!status) { + ESP_LOGE(TAG, "SetStatus: status is nullptr"); return; } - if (content == nullptr || strlen(content) == 0) { - lv_obj_add_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); + if (strcmp(status, Lang::Strings::LISTENING) == 0) { + lv_obj_set_style_text_font(status_label_, &OTTO_ICON_FONT, 0); + lv_label_set_text(status_label_, "\xEF\x84\xB0"); // U+F130 麦克风图标 + lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(network_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(battery_label_, LV_OBJ_FLAG_HIDDEN); + return; + } else if (strcmp(status, Lang::Strings::SPEAKING) == 0) { + lv_obj_set_style_text_font(status_label_, &OTTO_ICON_FONT, 0); + lv_label_set_text(status_label_, "\xEF\x80\xA8"); // U+F028 说话图标 + lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(network_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(battery_label_, LV_OBJ_FLAG_HIDDEN); + return; + } else if (strcmp(status, Lang::Strings::CONNECTING) == 0) { + lv_obj_set_style_text_font(status_label_, &OTTO_ICON_FONT, 0); + lv_label_set_text(status_label_, "\xEF\x83\x81"); // U+F0c1 连接图标 + lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN); + return; + } else if (strcmp(status, Lang::Strings::STANDBY) == 0) { + lv_obj_set_style_text_font(status_label_, text_font, 0); + lv_label_set_text(status_label_, ""); + lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN); return; } - lv_label_set_text(chat_message_label_, content); - lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); - - ESP_LOGI(TAG, "设置聊天消息 [%s]: %s", role, content); -} + lv_obj_set_style_text_font(status_label_, text_font, 0); + lv_label_set_text(status_label_, status); + lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(network_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(battery_label_, LV_OBJ_FLAG_HIDDEN); +} \ No newline at end of file diff --git a/main/boards/otto-robot/otto_emoji_display.h b/main/boards/otto-robot/otto_emoji_display.h index b51926e9..f674191c 100644 --- a/main/boards/otto-robot/otto_emoji_display.h +++ b/main/boards/otto-robot/otto_emoji_display.h @@ -1,41 +1,22 @@ #pragma once -#include - #include "display/lcd_display.h" -#include "otto_emoji_gif.h" /** * @brief Otto机器人GIF表情显示类 - * 继承LcdDisplay,添加GIF表情支持 + * 继承SpiLcdDisplay,通过EmojiCollection添加GIF表情支持 */ class OttoEmojiDisplay : public SpiLcdDisplay { -public: + public: /** * @brief 构造函数,参数与SpiLcdDisplay相同 */ - OttoEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, - int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, - bool swap_xy); + OttoEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy); virtual ~OttoEmojiDisplay() = default; + virtual void SetStatus(const char* status) override; - // 重写表情设置方法 - virtual void SetEmotion(const char* emotion) override; - - // 重写聊天消息设置方法 - virtual void SetChatMessage(const char* role, const char* content) override; - -private: - void SetupGifContainer(); - - lv_obj_t* emotion_gif_; ///< GIF表情组件 - - // 表情映射 - struct EmotionMap { - const char* name; - const lv_img_dsc_t* gif; - }; - - static const EmotionMap emotion_maps_[]; + private: + void InitializeOttoEmojis(); + void SetupChatLabel(); }; \ No newline at end of file diff --git a/main/boards/otto-robot/otto_icon_font.c b/main/boards/otto-robot/otto_icon_font.c new file mode 100644 index 00000000..dc05cce6 --- /dev/null +++ b/main/boards/otto-robot/otto_icon_font.c @@ -0,0 +1,121 @@ +/******************************************************************************* + * Size: 20 px + * Bpp: 1 + * Opts: --bpp 1 --size 20 --no-compress --stride 1 --align 1 --font fontawesome-webfont.ttf --range 61744,61633,61480 --format lvgl -o OTTO_ICON_FONT.c + ******************************************************************************/ + +#ifdef __has_include +#if __has_include("lvgl.h") +#ifndef LV_LVGL_H_INCLUDE_SIMPLE +#define LV_LVGL_H_INCLUDE_SIMPLE +#endif +#endif +#endif + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#ifndef ENABLE_OTTO_ICON_FONT +#define ENABLE_OTTO_ICON_FONT 1 +#endif + +#if ENABLE_OTTO_ICON_FONT + +/*----------------- + * BITMAPS + *----------------*/ + +/*Store the image of the glyphs*/ +static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { + /* U+F028 "" */ + 0x0, 0x6, 0x0, 0x10, 0xe0, 0x6, 0x6, 0x3, 0xc6, 0x60, 0xf8, 0x65, 0xff, 0x24, 0xff, 0xe6, 0x4f, 0xfc, 0x49, 0xff, 0x89, 0x3f, 0xf3, 0x27, 0xfe, 0x49, 0x87, 0xc3, 0x20, 0x78, 0xcc, 0x3, 0x3, 0x0, + 0x21, 0xc0, 0x0, 0x30, + + /* U+F0C1 "" */ + 0x1e, 0x0, 0xf, 0xc0, 0x7, 0x38, 0x3, 0x87, 0x0, 0xc0, 0xc0, 0x30, 0xb0, 0x7, 0x3c, 0x0, 0xef, 0x0, 0x1f, 0xfe, 0x3, 0xff, 0xc0, 0x7, 0x38, 0x3, 0xe7, 0x0, 0xd0, 0xc0, 0x30, 0x30, 0x6, 0x1c, 0x0, + 0xce, 0x0, 0x1f, 0x0, 0x3, 0x80, + + /* U+F130 "" */ + 0x7, 0x0, 0xfe, 0x7, 0xf0, 0x3f, 0x81, 0xfc, 0xf, 0xe0, 0x7f, 0x13, 0xf9, 0x9f, 0xcc, 0xfe, 0x67, 0xf3, 0xbf, 0xb4, 0x73, 0x18, 0x30, 0x7f, 0x0, 0x60, 0x2, 0x1, 0xff, 0x0}; + +/*--------------------- + * GLYPH DESCRIPTION + *--------------------*/ + +static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */, + {.bitmap_index = 0, .adv_w = 297, .box_w = 19, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 38, .adv_w = 297, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 79, .adv_w = 206, .box_w = 13, .box_h = 18, .ofs_x = 0, .ofs_y = -1}}; + +/*--------------------- + * CHARACTER MAPPING + *--------------------*/ + +static const uint16_t unicode_list_0[] = {0x0, 0x99, 0x108}; + +/*Collect the unicode lists and glyph_id offsets*/ +static const lv_font_fmt_txt_cmap_t cmaps[] = { + {.range_start = 61480, .range_length = 265, .glyph_id_start = 1, .unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 3, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY}}; + +/*-------------------- + * ALL CUSTOM DATA + *--------------------*/ + +#if LVGL_VERSION_MAJOR == 8 +/*Store all the custom data of the font*/ +static lv_font_fmt_txt_glyph_cache_t cache; +#endif + +#if LVGL_VERSION_MAJOR >= 8 +static const lv_font_fmt_txt_dsc_t otto_icon_font_dsc = { +#else +static lv_font_fmt_txt_dsc_t otto_icon_font_dsc = { +#endif + .glyph_bitmap = glyph_bitmap, + .glyph_dsc = glyph_dsc, + .cmaps = cmaps, + .kern_dsc = NULL, + .kern_scale = 0, + .cmap_num = 1, + .bpp = 1, + .kern_classes = 0, + .bitmap_format = 0, +#if LVGL_VERSION_MAJOR == 8 + .cache = &cache +#endif + +}; + +/*----------------- + * PUBLIC FONT + *----------------*/ + +/*Initialize a public general font descriptor*/ +#if LVGL_VERSION_MAJOR >= 8 +const lv_font_t OTTO_ICON_FONT = { +#else +lv_font_t OTTO_ICON_FONT = { +#endif + .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/ + .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/ + .line_height = 18, /*The maximum line height required by the font*/ + .base_line = 1, /*Baseline measured from the bottom of the line*/ +#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0) + .subpx = LV_FONT_SUBPX_NONE, +#endif +#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8 + .underline_position = 0, + .underline_thickness = 0, +#endif + .static_bitmap = 0, + .dsc = &otto_icon_font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */ +#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9 + .fallback = NULL, +#endif + .user_data = NULL, +}; + +#endif /*#if ENABLE_OTTO_ICON_FONT*/