diff --git a/CMakeLists.txt b/CMakeLists.txt index aa8e55b4..3b03f42a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) -set(PROJECT_VER "1.4.0") +set(PROJECT_VER "1.4.1") # Add this line to disable the specific warning add_compile_options(-Wno-missing-field-initializers) diff --git a/main/application.cc b/main/application.cc index 291170c0..63d6b4f3 100644 --- a/main/application.cc +++ b/main/application.cc @@ -538,11 +538,10 @@ void Application::Start() { } void Application::OnClockTimer() { - static int count = 0; - count++; + clock_ticks_++; // Print the debug info every 10 seconds - if (count % 10 == 0) { + if (clock_ticks_ % 10 == 0) { // SystemInfo::PrintRealTimeStats(pdMS_TO_TICKS(1000)); int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); int min_free_sram = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL); @@ -717,6 +716,7 @@ void Application::SetDeviceState(DeviceState state) { return; } + clock_ticks_ = 0; auto previous_state = device_state_; device_state_ = state; ESP_LOGI(TAG, "STATE: %s", STATE_STRINGS[device_state_]); diff --git a/main/application.h b/main/application.h index cbcc0643..c92938a9 100644 --- a/main/application.h +++ b/main/application.h @@ -91,6 +91,7 @@ private: bool aborted_ = false; bool voice_detected_ = false; std::string last_iot_states_; + int clock_ticks_ = 0; // Audio encode / decode BackgroundTask* background_task_ = nullptr; diff --git a/main/boards/tudouzi/config.json b/main/boards/tudouzi/config.json index 886aa43f..d9eee688 100644 --- a/main/boards/tudouzi/config.json +++ b/main/boards/tudouzi/config.json @@ -3,7 +3,11 @@ "builds": [ { "name": "tudouzi", - "sdkconfig_append": ["CONFIG_USE_WAKE_WORD_DETECT=n"] + "sdkconfig_append": [ + "CONFIG_USE_WAKE_WORD_DETECT=n", + "CONFIG_PM_ENABLE=y", + "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y" + ] } ] } \ No newline at end of file diff --git a/main/boards/tudouzi/kevin_box_board.cc b/main/boards/tudouzi/kevin_box_board.cc index 11c104a7..5c0cd568 100644 --- a/main/boards/tudouzi/kevin_box_board.cc +++ b/main/boards/tudouzi/kevin_box_board.cc @@ -8,11 +8,14 @@ #include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" +#include "font_awesome_symbols.h" #include #include #include #include +#include +#include #define TAG "KevinBoxBoard" @@ -29,6 +32,8 @@ private: Button volume_down_button_; esp_timer_handle_t power_save_timer_ = nullptr; bool show_low_power_warning_ = false; + bool sleep_mode_enabled_ = false; + int power_save_ticks_ = 0; void InitializePowerSaveTimer() { esp_timer_create_args_t power_save_timer_args = { @@ -46,31 +51,64 @@ private: } void PowerSaveCheck() { - // 电池放电模式下,如果待机超过一定时间,则自动关机 - const int seconds_to_shutdown = 600; - static int seconds = 0; + // 电池放电模式下,如果待机超过一定时间,则进入睡眠模式 + const int seconds_to_sleep = 120; auto& app = Application::GetInstance(); if (app.GetDeviceState() != kDeviceStateIdle) { - seconds = 0; + power_save_ticks_ = 0; return; } - if (!axp2101_->IsDischarging()) { - seconds = 0; + + if (axp2101_->IsDischarging()) { + // 电量低于 10% 时,显示低电量警告 + if (!show_low_power_warning_ && axp2101_->GetBatteryLevel() <= 10) { + EnableSleepMode(false); + app.Alert(Lang::Strings::WARNING, Lang::Strings::BATTERY_LOW, "sad", Lang::Sounds::P3_VIBRATION); + show_low_power_warning_ = true; + } + } else { if (show_low_power_warning_) { app.DismissAlert(); show_low_power_warning_ = false; } - return; - } - // 电量低于 10% 时,显示低电量警告 - if (axp2101_->GetBatteryLevel() <= 10 && !show_low_power_warning_) { - app.Alert(Lang::Strings::WARNING, Lang::Strings::BATTERY_LOW, "sad", Lang::Sounds::P3_VIBRATION); - show_low_power_warning_ = true; } - seconds++; - if (seconds >= seconds_to_shutdown) { - // axp2101_->PowerOff(); + power_save_ticks_++; + if (power_save_ticks_ >= seconds_to_sleep) { + EnableSleepMode(true); + } + } + + void EnableSleepMode(bool enable) { + power_save_ticks_ = 0; + if (!sleep_mode_enabled_ && enable) { + ESP_LOGI(TAG, "Enabling sleep mode"); + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("sleepy"); + + auto codec = GetAudioCodec(); + codec->EnableInput(false); + + esp_pm_config_t pm_config = { + .max_freq_mhz = 240, + .min_freq_mhz = 40, + .light_sleep_enable = true, + }; + esp_pm_configure(&pm_config); + sleep_mode_enabled_ = true; + } else if (sleep_mode_enabled_ && !enable) { + esp_pm_config_t pm_config = { + .max_freq_mhz = 240, + .min_freq_mhz = 240, + .light_sleep_enable = false, + }; + esp_pm_configure(&pm_config); + ESP_LOGI(TAG, "Disabling sleep mode"); + + auto codec = GetAudioCodec(); + codec->EnableInput(true); + sleep_mode_enabled_ = false; } } @@ -121,7 +159,13 @@ private: } void InitializeButtons() { + gpio_wakeup_enable(BOOT_BUTTON_GPIO, GPIO_INTR_LOW_LEVEL); + gpio_wakeup_enable(VOLUME_UP_BUTTON_GPIO, GPIO_INTR_LOW_LEVEL); + gpio_wakeup_enable(VOLUME_DOWN_BUTTON_GPIO, GPIO_INTR_LOW_LEVEL); + esp_sleep_enable_gpio_wakeup(); + boot_button_.OnPressDown([this]() { + EnableSleepMode(false); Application::GetInstance().StartListening(); }); boot_button_.OnPressUp([this]() { @@ -180,7 +224,7 @@ public: InitializePowerSaveTimer(); InitializeIot(); } - + virtual Led* GetLed() override { static SingleLed led(BUILTIN_LED_GPIO); return &led; diff --git a/main/boards/xmini-c3/config.json b/main/boards/xmini-c3/config.json index 49dfb37d..d6d2796c 100644 --- a/main/boards/xmini-c3/config.json +++ b/main/boards/xmini-c3/config.json @@ -3,7 +3,10 @@ "builds": [ { "name": "xmini-c3", - "sdkconfig_append": [] + "sdkconfig_append": [ + "CONFIG_PM_ENABLE=y", + "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y" + ] } ] } \ No newline at end of file diff --git a/main/boards/xmini-c3/xmini_c3_board.cc b/main/boards/xmini-c3/xmini_c3_board.cc index 46d775eb..bc372ce5 100644 --- a/main/boards/xmini-c3/xmini_c3_board.cc +++ b/main/boards/xmini-c3/xmini_c3_board.cc @@ -7,11 +7,14 @@ #include "iot/thing_manager.h" #include "led/single_led.h" #include "settings.h" +#include "font_awesome_symbols.h" #include #include #include #include +#include +#include #define TAG "XminiC3Board" @@ -23,6 +26,74 @@ private: i2c_master_bus_handle_t codec_i2c_bus_; Button boot_button_; bool press_to_talk_enabled_ = false; + esp_timer_handle_t power_save_timer_ = nullptr; + bool sleep_mode_enabled_ = false; + int power_save_ticks_ = 0; + + void InitializePowerSaveTimer() { + esp_timer_create_args_t power_save_timer_args = { + .callback = [](void *arg) { + auto board = static_cast(arg); + board->PowerSaveCheck(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "power_save_timer", + .skip_unhandled_events = false, + }; + ESP_ERROR_CHECK(esp_timer_create(&power_save_timer_args, &power_save_timer_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000)); + } + + void PowerSaveCheck() { + // 如果待机超过一定时间,则进入睡眠模式 + const int seconds_to_sleep = 120; + auto& app = Application::GetInstance(); + if (app.GetDeviceState() != kDeviceStateIdle) { + power_save_ticks_ = 0; + return; + } + + power_save_ticks_++; + if (power_save_ticks_ >= seconds_to_sleep) { + EnableSleepMode(true); + } + } + + void EnableSleepMode(bool enable) { + power_save_ticks_ = 0; + if (!sleep_mode_enabled_ && enable) { + ESP_LOGI(TAG, "Enabling sleep mode"); + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("sleepy"); + // 如果是LCD,还可以调节屏幕亮度 + + auto codec = GetAudioCodec(); + codec->EnableInput(false); + + esp_pm_config_t pm_config = { + .max_freq_mhz = 160, + .min_freq_mhz = 40, + .light_sleep_enable = true, + }; + esp_pm_configure(&pm_config); + sleep_mode_enabled_ = true; + } else if (sleep_mode_enabled_ && !enable) { + esp_pm_config_t pm_config = { + .max_freq_mhz = 160, + .min_freq_mhz = 160, + .light_sleep_enable = false, + }; + esp_pm_configure(&pm_config); + ESP_LOGI(TAG, "Disabling sleep mode"); + + auto codec = GetAudioCodec(); + codec->EnableInput(true); + sleep_mode_enabled_ = false; + } + } + void InitializeCodecI2c() { // Initialize I2C peripheral @@ -52,6 +123,7 @@ private: } }); boot_button_.OnPressDown([this]() { + EnableSleepMode(false); if (press_to_talk_enabled_) { Application::GetInstance().StartListening(); } @@ -80,6 +152,7 @@ public: InitializeCodecI2c(); InitializeButtons(); + InitializePowerSaveTimer(); InitializeIot(); } diff --git a/main/display/display.cc b/main/display/display.cc index b2e3d77c..5bacc362 100644 --- a/main/display/display.cc +++ b/main/display/display.cc @@ -45,6 +45,9 @@ Display::Display() { }; ESP_ERROR_CHECK(esp_timer_create(&update_display_timer_args, &update_timer_)); ESP_ERROR_CHECK(esp_timer_start_periodic(update_timer_, 1000000)); + + // Create a power management lock + ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "ml307", &pm_lock_)); } Display::~Display() { @@ -65,6 +68,10 @@ Display::~Display() { lv_obj_del(battery_label_); lv_obj_del(emotion_label_); } + + if (pm_lock_ != nullptr) { + esp_pm_lock_delete(pm_lock_); + } } void Display::SetStatus(const char* status) { @@ -114,6 +121,7 @@ void Display::Update() { } } + esp_pm_lock_acquire(pm_lock_); // 更新电池图标 int battery_level; bool charging; @@ -149,12 +157,14 @@ void Display::Update() { }; if (std::find(allowed_states.begin(), allowed_states.end(), device_state) != allowed_states.end()) { icon = board.GetNetworkStateIcon(); - if (network_label_ != nullptr && network_icon_ != icon) { + 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_); } diff --git a/main/display/display.h b/main/display/display.h index 8b28a551..a875a760 100644 --- a/main/display/display.h +++ b/main/display/display.h @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -35,6 +36,8 @@ protected: int height_ = 0; uint8_t brightness_ = 0; + + esp_pm_lock_handle_t pm_lock_ = nullptr; lv_display_t *display_ = nullptr; lv_obj_t *emotion_label_ = nullptr;