diff --git a/main/application.cc b/main/application.cc index 9bc89a31..dce29f91 100644 --- a/main/application.cc +++ b/main/application.cc @@ -164,43 +164,10 @@ void Application::CheckNewVersion(Ota& ota) { retry_delay = 10; // 重置重试延迟时间 if (ota.HasNewVersion()) { - Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "download", Lang::Sounds::OGG_UPGRADE); - - vTaskDelay(pdMS_TO_TICKS(3000)); - - SetDeviceState(kDeviceStateUpgrading); - - std::string message = std::string(Lang::Strings::NEW_VERSION) + ota.GetFirmwareVersion(); - display->SetChatMessage("system", message.c_str()); - - board.SetPowerSaveMode(false); - audio_service_.Stop(); - vTaskDelay(pdMS_TO_TICKS(1000)); - - bool upgrade_success = ota.StartUpgrade([display](int progress, size_t speed) { - std::thread([display, progress, speed]() { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, speed / 1024); - display->SetChatMessage("system", buffer); - }).detach(); - }); - - if (!upgrade_success) { - // Upgrade failed, restart audio service and continue running - ESP_LOGE(TAG, "Firmware upgrade failed, restarting audio service and continuing operation..."); - audio_service_.Start(); // Restart audio service - board.SetPowerSaveMode(true); // Restore power save mode - Alert(Lang::Strings::ERROR, Lang::Strings::UPGRADE_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION); - vTaskDelay(pdMS_TO_TICKS(3000)); - // Continue to normal operation (don't break, just fall through) - } else { - // Upgrade success, reboot immediately - ESP_LOGI(TAG, "Firmware upgrade successful, rebooting..."); - display->SetChatMessage("system", "Upgrade successful, rebooting..."); - vTaskDelay(pdMS_TO_TICKS(1000)); // Brief pause to show message - Reboot(); + if (UpgradeFirmware(ota)) { return; // This line will never be reached after reboot } + // If upgrade failed, continue to normal operation (don't break, just fall through) } // No new version, mark the current version as valid @@ -768,9 +735,64 @@ void Application::Reboot() { } protocol_.reset(); audio_service_.Stop(); + + vTaskDelay(pdMS_TO_TICKS(1000)); esp_restart(); } +bool Application::UpgradeFirmware(Ota& ota, const std::string& url) { + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + + // Use provided URL or get from OTA object + std::string upgrade_url = url.empty() ? ota.GetFirmwareUrl() : url; + std::string version_info = url.empty() ? ota.GetFirmwareVersion() : "(Manual upgrade)"; + + // Close audio channel if it's open + if (protocol_ && protocol_->IsAudioChannelOpened()) { + ESP_LOGI(TAG, "Closing audio channel before firmware upgrade"); + protocol_->CloseAudioChannel(); + } + ESP_LOGI(TAG, "Starting firmware upgrade from URL: %s", upgrade_url.c_str()); + + Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "download", Lang::Sounds::OGG_UPGRADE); + vTaskDelay(pdMS_TO_TICKS(3000)); + + SetDeviceState(kDeviceStateUpgrading); + + std::string message = std::string(Lang::Strings::NEW_VERSION) + version_info; + display->SetChatMessage("system", message.c_str()); + + board.SetPowerSaveMode(false); + audio_service_.Stop(); + vTaskDelay(pdMS_TO_TICKS(1000)); + + bool upgrade_success = ota.StartUpgradeFromUrl(upgrade_url, [display](int progress, size_t speed) { + std::thread([display, progress, speed]() { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, speed / 1024); + display->SetChatMessage("system", buffer); + }).detach(); + }); + + if (!upgrade_success) { + // Upgrade failed, restart audio service and continue running + ESP_LOGE(TAG, "Firmware upgrade failed, restarting audio service and continuing operation..."); + audio_service_.Start(); // Restart audio service + board.SetPowerSaveMode(true); // Restore power save mode + Alert(Lang::Strings::ERROR, Lang::Strings::UPGRADE_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION); + vTaskDelay(pdMS_TO_TICKS(3000)); + return false; + } else { + // Upgrade success, reboot immediately + ESP_LOGI(TAG, "Firmware upgrade successful, rebooting..."); + display->SetChatMessage("system", "Upgrade successful, rebooting..."); + vTaskDelay(pdMS_TO_TICKS(1000)); // Brief pause to show message + Reboot(); + return true; + } +} + void Application::WakeWordInvoke(const std::string& wake_word) { if (device_state_ == kDeviceStateIdle) { ToggleChatState(); diff --git a/main/application.h b/main/application.h index 0e79cf0b..69ed22e4 100644 --- a/main/application.h +++ b/main/application.h @@ -56,6 +56,7 @@ public: void StopListening(); void Reboot(); void WakeWordInvoke(const std::string& wake_word); + bool UpgradeFirmware(Ota& ota, const std::string& url = ""); bool CanEnterSleepMode(); void SendMcpMessage(const std::string& payload); void SetAecMode(AecMode mode); diff --git a/main/assets.cc b/main/assets.cc index ee302120..48d790fb 100644 --- a/main/assets.cc +++ b/main/assets.cc @@ -161,8 +161,12 @@ bool Assets::Apply() { ESP_LOGE(TAG, "Failed to load fonts.bin"); return false; } - light_theme->set_text_font(text_font); - dark_theme->set_text_font(text_font); + if (light_theme != nullptr) { + light_theme->set_text_font(text_font); + } + if (dark_theme != nullptr) { + dark_theme->set_text_font(text_font); + } } else { ESP_LOGE(TAG, "The font file %s is not found", fonts_text_file.c_str()); } @@ -186,14 +190,18 @@ bool Assets::Apply() { } } } - light_theme->set_emoji_collection(custom_emoji_collection); - dark_theme->set_emoji_collection(custom_emoji_collection); + if (light_theme != nullptr) { + light_theme->set_emoji_collection(custom_emoji_collection); + } + if (dark_theme != nullptr) { + dark_theme->set_emoji_collection(custom_emoji_collection); + } } cJSON* skin = cJSON_GetObjectItem(root, "skin"); if (cJSON_IsObject(skin)) { cJSON* light_skin = cJSON_GetObjectItem(skin, "light"); - if (cJSON_IsObject(light_skin)) { + if (cJSON_IsObject(light_skin) && light_theme != nullptr) { cJSON* text_color = cJSON_GetObjectItem(light_skin, "text_color"); cJSON* background_color = cJSON_GetObjectItem(light_skin, "background_color"); cJSON* background_image = cJSON_GetObjectItem(light_skin, "background_image"); @@ -214,7 +222,7 @@ bool Assets::Apply() { } } cJSON* dark_skin = cJSON_GetObjectItem(skin, "dark"); - if (cJSON_IsObject(dark_skin)) { + if (cJSON_IsObject(dark_skin) && dark_theme != nullptr) { cJSON* text_color = cJSON_GetObjectItem(dark_skin, "text_color"); cJSON* background_color = cJSON_GetObjectItem(dark_skin, "background_color"); cJSON* background_image = cJSON_GetObjectItem(dark_skin, "background_image"); @@ -239,7 +247,11 @@ bool Assets::Apply() { auto display = Board::GetInstance().GetDisplay(); ESP_LOGI(TAG, "Refreshing display theme..."); - display->SetTheme(display->GetTheme()); + + auto current_theme = display->GetTheme(); + if (current_theme != nullptr) { + display->SetTheme(current_theme); + } cJSON_Delete(root); return true; } @@ -253,7 +265,6 @@ bool Assets::Download(std::string url, std::function #include @@ -20,8 +22,19 @@ OledDisplay::OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handl : panel_io_(panel_io), panel_(panel) { width_ = width; height_ = height; - text_font_ = &LVGL_TEXT_FONT; - icon_font_ = &LVGL_ICON_FONT; + + auto text_font = std::make_shared(&LVGL_TEXT_FONT); + auto icon_font = std::make_shared(&LVGL_ICON_FONT); + auto large_icon_font = std::make_shared(&font_awesome_30_1); + + auto dark_theme = new LvglTheme("dark"); + dark_theme->set_text_font(text_font); + dark_theme->set_icon_font(icon_font); + dark_theme->set_large_icon_font(large_icon_font); + + auto& theme_manager = LvglThemeManager::GetInstance(); + theme_manager.RegisterTheme("dark", dark_theme); + current_theme_ = dark_theme; ESP_LOGI(TAG, "Initialize LVGL"); lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); @@ -123,8 +136,13 @@ void OledDisplay::SetChatMessage(const char* role, const char* content) { void OledDisplay::SetupUI_128x64() { DisplayLockGuard lock(this); + auto lvgl_theme = static_cast(current_theme_); + auto text_font = lvgl_theme->text_font()->font(); + auto icon_font = lvgl_theme->icon_font()->font(); + auto large_icon_font = lvgl_theme->large_icon_font()->font(); + auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font_, 0); + lv_obj_set_style_text_font(screen, text_font, 0); lv_obj_set_style_text_color(screen, lv_color_black(), 0); /* Container */ @@ -159,7 +177,7 @@ void OledDisplay::SetupUI_128x64() { 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_obj_set_style_text_font(emotion_label_, large_icon_font, 0); lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI); lv_obj_center(emotion_label_); lv_obj_set_style_pad_top(emotion_label_, 8, 0); @@ -195,7 +213,7 @@ void OledDisplay::SetupUI_128x64() { network_label_ = lv_label_create(status_bar_); lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, icon_font_, 0); + 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); @@ -210,15 +228,15 @@ void OledDisplay::SetupUI_128x64() { mute_label_ = lv_label_create(status_bar_); lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, icon_font_, 0); + 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); + lv_obj_set_style_text_font(battery_label_, icon_font, 0); low_battery_popup_ = lv_obj_create(screen); lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font_->line_height * 2); + lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2); lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0); lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); lv_obj_set_style_radius(low_battery_popup_, 10, 0); @@ -232,8 +250,13 @@ void OledDisplay::SetupUI_128x64() { void OledDisplay::SetupUI_128x32() { DisplayLockGuard lock(this); + auto lvgl_theme = static_cast(current_theme_); + auto text_font = lvgl_theme->text_font()->font(); + auto icon_font = lvgl_theme->icon_font()->font(); + auto large_icon_font = lvgl_theme->large_icon_font()->font(); + auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font_, 0); + lv_obj_set_style_text_font(screen, text_font, 0); /* Container */ container_ = lv_obj_create(screen); @@ -251,7 +274,7 @@ void OledDisplay::SetupUI_128x32() { 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_obj_set_style_text_font(emotion_label_, large_icon_font, 0); lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI); lv_obj_center(emotion_label_); @@ -286,15 +309,15 @@ void OledDisplay::SetupUI_128x32() { mute_label_ = lv_label_create(status_bar_); lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, icon_font_, 0); + lv_obj_set_style_text_font(mute_label_, icon_font, 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); + lv_obj_set_style_text_font(network_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); + lv_obj_set_style_text_font(battery_label_, icon_font, 0); chat_message_label_ = lv_label_create(side_bar_); lv_obj_set_size(chat_message_label_, width_ - 32, LV_SIZE_CONTENT); @@ -323,3 +346,13 @@ void OledDisplay::SetEmotion(const char* emotion) { lv_label_set_text(emotion_label_, FONT_AWESOME_NEUTRAL); } } + +void OledDisplay::SetTheme(Theme* theme) { + DisplayLockGuard lock(this); + + auto lvgl_theme = static_cast(theme); + auto text_font = lvgl_theme->text_font()->font(); + + auto screen = lv_screen_active(); + lv_obj_set_style_text_font(screen, text_font, 0); +} diff --git a/main/display/oled_display.h b/main/display/oled_display.h index c5013df6..321f5da0 100644 --- a/main/display/oled_display.h +++ b/main/display/oled_display.h @@ -20,8 +20,6 @@ private: lv_obj_t* side_bar_ = nullptr; lv_obj_t *emotion_label_ = nullptr; lv_obj_t* chat_message_label_ = nullptr; - const lv_font_t* text_font_ = nullptr; - const lv_font_t* icon_font_ = nullptr; virtual bool Lock(int timeout_ms = 0) override; virtual void Unlock() override; @@ -35,6 +33,7 @@ public: virtual void SetChatMessage(const char* role, const char* content) override; virtual void SetEmotion(const char* emotion) override; + virtual void SetTheme(Theme* theme) override; }; #endif // OLED_DISPLAY_H diff --git a/main/mcp_server.cc b/main/mcp_server.cc index 85fc4abc..76dddb5c 100644 --- a/main/mcp_server.cc +++ b/main/mcp_server.cc @@ -12,6 +12,7 @@ #include "application.h" #include "display.h" +#include "oled_display.h" #include "board.h" #include "settings.h" #include "lvgl_theme.h" @@ -136,12 +137,36 @@ void McpServer::AddUserOnlyTools() { AddUserOnlyTool("self.reboot", "Reboot the system", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - std::thread([]() { + auto& app = Application::GetInstance(); + app.Schedule([]() { ESP_LOGW(TAG, "User requested reboot"); vTaskDelay(pdMS_TO_TICKS(1000)); auto& app = Application::GetInstance(); app.Reboot(); - }).detach(); + }); + return true; + }); + + // Firmware upgrade + AddUserOnlyTool("self.upgrade_firmware", "Upgrade firmware from a specific URL. This will download and install the firmware, then reboot the device.", + PropertyList({ + Property("url", kPropertyTypeString, "The URL of the firmware binary file to download and install") + }), + [this](const PropertyList& properties) -> ReturnValue { + auto url = properties["url"].value(); + ESP_LOGI(TAG, "User requested firmware upgrade from URL: %s", url.c_str()); + + auto& app = Application::GetInstance(); + app.Schedule([url]() { + auto& app = Application::GetInstance(); + auto ota = std::make_unique(); + + bool success = app.UpgradeFirmware(*ota, url); + if (!success) { + ESP_LOGE(TAG, "Firmware upgrade failed"); + } + }); + return true; }); @@ -155,6 +180,11 @@ void McpServer::AddUserOnlyTools() { cJSON *json = cJSON_CreateObject(); cJSON_AddNumberToObject(json, "width", display->width()); cJSON_AddNumberToObject(json, "height", display->height()); + if (dynamic_cast(display)) { + cJSON_AddBoolToObject(json, "monochrome", true); + } else { + cJSON_AddBoolToObject(json, "monochrome", false); + } return json; }); diff --git a/main/ota.cc b/main/ota.cc index 0998e0ea..b793273c 100644 --- a/main/ota.cc +++ b/main/ota.cc @@ -325,13 +325,9 @@ bool Ota::Upgrade(const std::string& firmware_url) { if (image_header.size() >= sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) { esp_app_desc_t new_app_info; memcpy(&new_app_info, image_header.data() + sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t), sizeof(esp_app_desc_t)); - ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version); - + auto current_version = esp_app_get_description()->version; - if (memcmp(new_app_info.version, current_version, sizeof(new_app_info.version)) == 0) { - ESP_LOGE(TAG, "Firmware version is the same, skipping upgrade"); - return false; - } + ESP_LOGI(TAG, "Current version: %s, New version: %s", current_version, new_app_info.version); if (esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle)) { esp_ota_abort(update_handle); @@ -377,6 +373,11 @@ bool Ota::StartUpgrade(std::function callback) return Upgrade(firmware_url_); } +bool Ota::StartUpgradeFromUrl(const std::string& url, std::function callback) { + upgrade_callback_ = callback; + return Upgrade(url); +} + std::vector Ota::ParseVersion(const std::string& version) { std::vector versionNumbers; std::stringstream ss(version); diff --git a/main/ota.h b/main/ota.h index 79576987..82e670e8 100644 --- a/main/ota.h +++ b/main/ota.h @@ -21,10 +21,12 @@ public: bool HasActivationCode() { return has_activation_code_; } bool HasServerTime() { return has_server_time_; } bool StartUpgrade(std::function callback); + bool StartUpgradeFromUrl(const std::string& url, std::function callback); void MarkCurrentVersionValid(); const std::string& GetFirmwareVersion() const { return firmware_version_; } const std::string& GetCurrentVersion() const { return current_version_; } + const std::string& GetFirmwareUrl() const { return firmware_url_; } const std::string& GetActivationMessage() const { return activation_message_; } const std::string& GetActivationCode() const { return activation_code_; } std::string GetCheckVersionUrl(); diff --git a/main/protocols/mqtt_protocol.cc b/main/protocols/mqtt_protocol.cc index 9380bb52..7fbd77f2 100644 --- a/main/protocols/mqtt_protocol.cc +++ b/main/protocols/mqtt_protocol.cc @@ -36,6 +36,10 @@ MqttProtocol::~MqttProtocol() { esp_timer_stop(reconnect_timer_); esp_timer_delete(reconnect_timer_); } + + udp_.reset(); + mqtt_.reset(); + if (event_group_handle_ != nullptr) { vEventGroupDelete(event_group_handle_); }