diff --git a/CMakeLists.txt b/CMakeLists.txt index fd1762e..5536104 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 "0.9.7") +set(PROJECT_VER "0.9.8") include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(xiaozhi) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 901cad0..2e4d72a 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -3,9 +3,11 @@ set(SOURCES "audio_codecs/audio_codec.cc" "audio_codecs/box_audio_codec.cc" "audio_codecs/es8311_audio_codec.cc" "audio_codecs/cores3_audio_codec.cc" + "led/single_led.cc" + "led/circular_strip.cc" "display/display.cc" "display/no_display.cc" - "display/st7789_display.cc" + "display/lcd_display.cc" "display/ssd1306_display.cc" "protocols/protocol.cc" "protocols/mqtt_protocol.cc" diff --git a/main/application.cc b/main/application.cc index 3119e00..876d574 100644 --- a/main/application.cc +++ b/main/application.cc @@ -26,11 +26,14 @@ extern const char p3_err_wificonfig_end[] asm("_binary_err_wificonfig_p3_end"); static const char* const STATE_STRINGS[] = { "unknown", + "starting", + "configuring", "idle", "connecting", "listening", "speaking", "upgrading", + "fatal_error", "invalid_state" }; @@ -57,9 +60,9 @@ void Application::CheckNewVersion() { // Wait for the chat state to be idle do { vTaskDelay(pdMS_TO_TICKS(3000)); - } while (GetChatState() != kChatStateIdle); + } while (GetDeviceState() != kDeviceStateIdle); - SetChatState(kChatStateUpgrading); + SetDeviceState(kDeviceStateUpgrading); display->SetIcon(FONT_AWESOME_DOWNLOAD); display->SetStatus("新版本 " + ota_.GetFirmwareVersion()); @@ -75,7 +78,7 @@ void Application::CheckNewVersion() { // If upgrade success, the device will reboot and never reach here ESP_LOGI(TAG, "Firmware upgrade failed..."); - SetChatState(kChatStateIdle); + SetDeviceState(kDeviceStateIdle); } else { ota_.MarkCurrentVersionValid(); display->ShowNotification("版本 " + ota_.GetCurrentVersion()); @@ -127,20 +130,20 @@ void Application::ToggleChatState() { return; } - if (chat_state_ == kChatStateIdle) { - SetChatState(kChatStateConnecting); + if (device_state_ == kDeviceStateIdle) { + SetDeviceState(kDeviceStateConnecting); if (!protocol_->OpenAudioChannel()) { Alert("Error", "Failed to open audio channel"); - SetChatState(kChatStateIdle); + SetDeviceState(kDeviceStateIdle); return; } keep_listening_ = true; protocol_->SendStartListening(kListeningModeAutoStop); - SetChatState(kChatStateListening); - } else if (chat_state_ == kChatStateSpeaking) { + SetDeviceState(kDeviceStateListening); + } else if (device_state_ == kDeviceStateSpeaking) { AbortSpeaking(kAbortReasonNone); - } else if (chat_state_ == kChatStateListening) { + } else if (device_state_ == kDeviceStateListening) { protocol_->CloseAudioChannel(); } }); @@ -154,41 +157,39 @@ void Application::StartListening() { } keep_listening_ = false; - if (chat_state_ == kChatStateIdle) { + if (device_state_ == kDeviceStateIdle) { if (!protocol_->IsAudioChannelOpened()) { - SetChatState(kChatStateConnecting); + SetDeviceState(kDeviceStateConnecting); if (!protocol_->OpenAudioChannel()) { - SetChatState(kChatStateIdle); + SetDeviceState(kDeviceStateIdle); Alert("Error", "Failed to open audio channel"); return; } } protocol_->SendStartListening(kListeningModeManualStop); - SetChatState(kChatStateListening); - } else if (chat_state_ == kChatStateSpeaking) { + SetDeviceState(kDeviceStateListening); + } else if (device_state_ == kDeviceStateSpeaking) { AbortSpeaking(kAbortReasonNone); protocol_->SendStartListening(kListeningModeManualStop); // FIXME: Wait for the speaker to empty the buffer vTaskDelay(pdMS_TO_TICKS(120)); - SetChatState(kChatStateListening); + SetDeviceState(kDeviceStateListening); } }); } void Application::StopListening() { Schedule([this]() { - if (chat_state_ == kChatStateListening) { + if (device_state_ == kDeviceStateListening) { protocol_->SendStopListening(); - SetChatState(kChatStateIdle); + SetDeviceState(kDeviceStateIdle); } }); } void Application::Start() { auto& board = Board::GetInstance(); - auto builtin_led = board.GetBuiltinLed(); - builtin_led->SetBlue(); - builtin_led->StartContinuousBlink(100); + SetDeviceState(kDeviceStateStarting); /* Setup the display */ auto display = board.GetDisplay(); @@ -246,27 +247,27 @@ void Application::Start() { wake_word_detect_.Initialize(codec->input_channels(), codec->input_reference()); wake_word_detect_.OnVadStateChange([this](bool speaking) { Schedule([this, speaking]() { - auto builtin_led = Board::GetInstance().GetBuiltinLed(); - if (chat_state_ == kChatStateListening) { + if (device_state_ == kDeviceStateListening) { if (speaking) { - builtin_led->SetRed(HIGH_BRIGHTNESS); + voice_detected_ = true; } else { - builtin_led->SetRed(LOW_BRIGHTNESS); + voice_detected_ = false; } - builtin_led->TurnOn(); + auto led = Board::GetInstance().GetLed(); + led->OnStateChanged(); } }); }); wake_word_detect_.OnWakeWordDetected([this](const std::string& wake_word) { Schedule([this, &wake_word]() { - if (chat_state_ == kChatStateIdle) { - SetChatState(kChatStateConnecting); + if (device_state_ == kDeviceStateIdle) { + SetDeviceState(kDeviceStateConnecting); wake_word_detect_.EncodeWakeWordData(); if (!protocol_->OpenAudioChannel()) { ESP_LOGE(TAG, "Failed to open audio channel"); - SetChatState(kChatStateIdle); + SetDeviceState(kDeviceStateIdle); wake_word_detect_.StartDetection(); return; } @@ -280,8 +281,8 @@ void Application::Start() { protocol_->SendWakeWordDetected(wake_word); ESP_LOGI(TAG, "Wake word detected: %s", wake_word.c_str()); keep_listening_ = true; - SetChatState(kChatStateListening); - } else if (chat_state_ == kChatStateSpeaking) { + SetDeviceState(kDeviceStateListening); + } else if (device_state_ == kDeviceStateSpeaking) { AbortSpeaking(kAbortReasonWakeWordDetected); } @@ -304,7 +305,7 @@ void Application::Start() { }); protocol_->OnIncomingAudio([this](std::vector&& data) { std::lock_guard lock(mutex_); - if (chat_state_ == kChatStateSpeaking) { + if (device_state_ == kDeviceStateSpeaking) { audio_decode_queue_.emplace_back(std::move(data)); } }); @@ -323,7 +324,7 @@ void Application::Start() { protocol_->OnAudioChannelClosed([this, &board]() { board.SetPowerSaveMode(true); Schedule([this]() { - SetChatState(kChatStateIdle); + SetDeviceState(kDeviceStateIdle); }); }); protocol_->OnIncomingJson([this, display](const cJSON* root) { @@ -334,19 +335,19 @@ void Application::Start() { if (strcmp(state->valuestring, "start") == 0) { Schedule([this]() { aborted_ = false; - if (chat_state_ == kChatStateIdle || chat_state_ == kChatStateListening) { - SetChatState(kChatStateSpeaking); + if (device_state_ == kDeviceStateIdle || device_state_ == kDeviceStateListening) { + SetDeviceState(kDeviceStateSpeaking); } }); } else if (strcmp(state->valuestring, "stop") == 0) { Schedule([this]() { - if (chat_state_ == kChatStateSpeaking) { + if (device_state_ == kDeviceStateSpeaking) { background_task_.WaitForCompletion(); if (keep_listening_) { protocol_->SendStartListening(kListeningModeAutoStop); - SetChatState(kChatStateListening); + SetDeviceState(kDeviceStateListening); } else { - SetChatState(kChatStateIdle); + SetDeviceState(kDeviceStateIdle); } } }); @@ -380,12 +381,7 @@ void Application::Start() { } }); - // Blink the LED to indicate the device is running - display->SetStatus("待命"); - builtin_led->SetGreen(); - builtin_led->BlinkOnce(); - - SetChatState(kChatStateIdle); + SetDeviceState(kDeviceStateIdle); } void Application::Schedule(std::function callback) { @@ -436,7 +432,7 @@ void Application::OutputAudio() { std::unique_lock lock(mutex_); if (audio_decode_queue_.empty()) { // Disable the output if there is no audio data for a long time - if (chat_state_ == kChatStateIdle) { + if (device_state_ == kDeviceStateIdle) { auto duration = std::chrono::duration_cast(now - last_output_time_).count(); if (duration > max_silence_seconds) { codec->EnableOutput(false); @@ -445,7 +441,7 @@ void Application::OutputAudio() { return; } - if (chat_state_ == kChatStateListening) { + if (device_state_ == kDeviceStateListening) { audio_decode_queue_.clear(); return; } @@ -516,7 +512,7 @@ void Application::InputAudio() { wake_word_detect_.Feed(data); } #else - if (chat_state_ == kChatStateListening) { + if (device_state_ == kDeviceStateListening) { background_task_.Schedule([this, data = std::move(data)]() mutable { opus_encoder_->Encode(std::move(data), [this](std::vector&& opus) { Schedule([this, opus = std::move(opus)]() { @@ -534,36 +530,33 @@ void Application::AbortSpeaking(AbortReason reason) { protocol_->SendAbortSpeaking(reason); } -void Application::SetChatState(ChatState state) { - if (chat_state_ == state) { +void Application::SetDeviceState(DeviceState state) { + if (device_state_ == state) { return; } - chat_state_ = state; - ESP_LOGI(TAG, "STATE: %s", STATE_STRINGS[chat_state_]); + device_state_ = state; + ESP_LOGI(TAG, "STATE: %s", STATE_STRINGS[device_state_]); // The state is changed, wait for all background tasks to finish background_task_.WaitForCompletion(); auto display = Board::GetInstance().GetDisplay(); - auto builtin_led = Board::GetInstance().GetBuiltinLed(); + auto led = Board::GetInstance().GetLed(); + led->OnStateChanged(); switch (state) { - case kChatStateUnknown: - case kChatStateIdle: - builtin_led->TurnOff(); + case kDeviceStateUnknown: + case kDeviceStateIdle: display->SetStatus("待命"); display->SetEmotion("neutral"); + display->SetChatMessage("", ""); #ifdef CONFIG_IDF_TARGET_ESP32S3 audio_processor_.Stop(); #endif break; - case kChatStateConnecting: - builtin_led->SetBlue(); - builtin_led->TurnOn(); + case kDeviceStateConnecting: display->SetStatus("连接中..."); break; - case kChatStateListening: - builtin_led->SetRed(); - builtin_led->TurnOn(); + case kDeviceStateListening: display->SetStatus("聆听中..."); display->SetEmotion("neutral"); ResetDecoder(); @@ -573,22 +566,16 @@ void Application::SetChatState(ChatState state) { #endif UpdateIotStates(); break; - case kChatStateSpeaking: - builtin_led->SetGreen(); - builtin_led->TurnOn(); + case kDeviceStateSpeaking: display->SetStatus("说话中..."); ResetDecoder(); #if CONFIG_IDF_TARGET_ESP32S3 audio_processor_.Stop(); #endif break; - case kChatStateUpgrading: - builtin_led->SetGreen(); - builtin_led->StartContinuousBlink(100); - break; default: - ESP_LOGE(TAG, "Invalid chat state: %d", chat_state_); - return; + // Do nothing + break; } } diff --git a/main/application.h b/main/application.h index c31f803..45bdaaf 100644 --- a/main/application.h +++ b/main/application.h @@ -26,13 +26,16 @@ #define AUDIO_INPUT_READY_EVENT (1 << 1) #define AUDIO_OUTPUT_READY_EVENT (1 << 2) -enum ChatState { - kChatStateUnknown, - kChatStateIdle, - kChatStateConnecting, - kChatStateListening, - kChatStateSpeaking, - kChatStateUpgrading +enum DeviceState { + kDeviceStateUnknown, + kDeviceStateStarting, + kDeviceStateWifiConfiguring, + kDeviceStateIdle, + kDeviceStateConnecting, + kDeviceStateListening, + kDeviceStateSpeaking, + kDeviceStateUpgrading, + kDeviceStateFatalError }; #define OPUS_FRAME_DURATION_MS 60 @@ -48,9 +51,10 @@ public: Application& operator=(const Application&) = delete; void Start(); - ChatState GetChatState() const { return chat_state_; } + DeviceState GetDeviceState() const { return device_state_; } + bool IsVoiceDetected() const { return voice_detected_; } void Schedule(std::function callback); - void SetChatState(ChatState state); + void SetDeviceState(DeviceState state); void Alert(const std::string& title, const std::string& message); void AbortSpeaking(AbortReason reason); void ToggleChatState(); @@ -71,9 +75,10 @@ private: std::list> main_tasks_; std::unique_ptr protocol_; EventGroupHandle_t event_group_; - volatile ChatState chat_state_ = kChatStateUnknown; + volatile DeviceState device_state_ = kDeviceStateUnknown; bool keep_listening_ = false; bool aborted_ = false; + bool voice_detected_ = false; std::string last_iot_states_; // Audio encode / decode diff --git a/main/audio_codecs/no_audio_codec.cc b/main/audio_codecs/no_audio_codec.cc index 434232e..d8f6452 100644 --- a/main/audio_codecs/no_audio_codec.cc +++ b/main/audio_codecs/no_audio_codec.cc @@ -77,8 +77,8 @@ NoAudioCodec::NoAudioCodec(int input_sample_rate, int output_sample_rate, gpio_n i2s_chan_config_t chan_cfg = { .id = (i2s_port_t)0, .role = I2S_ROLE_MASTER, - .dma_desc_num = 6, - .dma_frame_num = 240, + .dma_desc_num = 2, + .dma_frame_num = 240 * 3, .auto_clear_after_cb = true, .auto_clear_before_cb = false, .intr_priority = 0, @@ -138,8 +138,8 @@ NoAudioCodec::NoAudioCodec(int input_sample_rate, int output_sample_rate, gpio_n // Create a new channel for speaker i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)1, I2S_ROLE_MASTER); - tx_chan_cfg.dma_desc_num = 6; - tx_chan_cfg.dma_frame_num = 240; + tx_chan_cfg.dma_desc_num = 2; + tx_chan_cfg.dma_frame_num = 240 * 3; tx_chan_cfg.auto_clear_after_cb = true; tx_chan_cfg.auto_clear_before_cb = false; tx_chan_cfg.intr_priority = 0; diff --git a/main/boards/bread-compact-ml307/compact_ml307_board.cc b/main/boards/bread-compact-ml307/compact_ml307_board.cc index bca70e8..37a53cf 100644 --- a/main/boards/bread-compact-ml307/compact_ml307_board.cc +++ b/main/boards/bread-compact-ml307/compact_ml307_board.cc @@ -4,9 +4,9 @@ #include "system_reset.h" #include "application.h" #include "button.h" -#include "led.h" #include "config.h" #include "iot/thing_manager.h" +#include "led/single_led.h" #include #include @@ -102,8 +102,8 @@ public: InitializeIot(); } - virtual Led* GetBuiltinLed() override { - static Led led(BUILTIN_LED_GPIO); + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); return &led; } diff --git a/main/boards/bread-compact-wifi/compact_wifi_board.cc b/main/boards/bread-compact-wifi/compact_wifi_board.cc index 5d9ac8d..b1c7824 100644 --- a/main/boards/bread-compact-wifi/compact_wifi_board.cc +++ b/main/boards/bread-compact-wifi/compact_wifi_board.cc @@ -4,9 +4,9 @@ #include "system_reset.h" #include "application.h" #include "button.h" -#include "led.h" #include "config.h" #include "iot/thing_manager.h" +#include "led/single_led.h" #include #include @@ -42,7 +42,7 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetChatState() == kChatStateUnknown && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { ResetWifiConfiguration(); } app.ToggleChatState(); @@ -107,8 +107,8 @@ public: InitializeIot(); } - virtual Led* GetBuiltinLed() override { - static Led led(BUILTIN_LED_GPIO); + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); return &led; } diff --git a/main/boards/common/board.cc b/main/boards/common/board.cc index e43b210..b0f6182 100644 --- a/main/boards/common/board.cc +++ b/main/boards/common/board.cc @@ -20,6 +20,10 @@ Display* Board::GetDisplay() { return &display; } +Led* Board::GetLed() { + static NoLed led; + return &led; +} std::string Board::GetJson() { /* diff --git a/main/boards/common/board.h b/main/boards/common/board.h index 66de827..bf10a78 100644 --- a/main/boards/common/board.h +++ b/main/boards/common/board.h @@ -7,7 +7,7 @@ #include #include -#include "led.h" +#include "led/led.h" void* create_board(); class AudioCodec; @@ -32,7 +32,7 @@ public: virtual void StartNetwork() = 0; virtual ~Board() = default; - virtual Led* GetBuiltinLed() = 0; + virtual Led* GetLed(); virtual AudioCodec* GetAudioCodec() = 0; virtual Display* GetDisplay(); virtual Http* CreateHttp() = 0; diff --git a/main/boards/common/led.h b/main/boards/common/led.h deleted file mode 100644 index e0fe334..0000000 --- a/main/boards/common/led.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef _LED_H_ -#define _LED_H_ - -#include -#include -#include -#include - -#define BLINK_INFINITE -1 -#define BLINK_TASK_STOPPED_BIT BIT0 -#define BLINK_TASK_RUNNING_BIT BIT1 - -#define DEFAULT_BRIGHTNESS 4 -#define HIGH_BRIGHTNESS 16 -#define LOW_BRIGHTNESS 2 - -class Led { -public: - Led(gpio_num_t gpio); - ~Led(); - - void BlinkOnce(); - void Blink(int times, int interval_ms); - void StartContinuousBlink(int interval_ms); - void TurnOn(); - void TurnOff(); - void SetColor(uint8_t r, uint8_t g, uint8_t b); - void SetWhite(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(brightness, brightness, brightness); } - void SetGrey(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(brightness, brightness, brightness); } - void SetRed(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(brightness, 0, 0); } - void SetGreen(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(0, brightness, 0); } - void SetBlue(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(0, 0, brightness); } - -private: - std::mutex mutex_; - TaskHandle_t blink_task_ = nullptr; - led_strip_handle_t led_strip_ = nullptr; - uint8_t r_ = 0, g_ = 0, b_ = 0; - int blink_counter_ = 0; - int blink_interval_ms_ = 0; - esp_timer_handle_t blink_timer_ = nullptr; - - void StartBlinkTask(int times, int interval_ms); - void OnBlinkTimer(); -}; - -#endif // _LED_H_ diff --git a/main/boards/common/ml307_board.cc b/main/boards/common/ml307_board.cc index 3efd8bc..db3665d 100644 --- a/main/boards/common/ml307_board.cc +++ b/main/boards/common/ml307_board.cc @@ -46,7 +46,7 @@ void Ml307Board::StartNetwork() { modem_.OnMaterialReady([this, &application]() { ESP_LOGI(TAG, "ML307 material ready"); application.Schedule([this, &application]() { - application.SetChatState(kChatStateIdle); + application.SetDeviceState(kDeviceStateIdle); WaitForNetworkReady(); }); }); diff --git a/main/boards/common/wifi_board.cc b/main/boards/common/wifi_board.cc index 3198904..cee969e 100644 --- a/main/boards/common/wifi_board.cc +++ b/main/boards/common/wifi_board.cc @@ -38,15 +38,14 @@ static std::string rssi_to_string(int rssi) { void WifiBoard::StartNetwork() { auto& application = Application::GetInstance(); auto display = Board::GetInstance().GetDisplay(); - auto builtin_led = Board::GetInstance().GetBuiltinLed(); // Try to connect to WiFi, if failed, launch the WiFi configuration AP auto& wifi_station = WifiStation::GetInstance(); display->SetStatus(std::string("正在连接 ") + wifi_station.GetSsid()); wifi_station.Start(); if (!wifi_station.IsConnected()) { - builtin_led->SetBlue(); - builtin_led->Blink(1000, 500); + application.SetDeviceState(kDeviceStateWifiConfiguring); + auto& wifi_ap = WifiConfigurationAp::GetInstance(); wifi_ap.SetSsidPrefix("Xiaozhi"); wifi_ap.Start(); diff --git a/main/boards/esp-box-3/esp_box3_board.cc b/main/boards/esp-box-3/esp_box3_board.cc index 7df36c1..1b0e274 100644 --- a/main/boards/esp-box-3/esp_box3_board.cc +++ b/main/boards/esp-box-3/esp_box3_board.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" #include "audio_codecs/box_audio_codec.h" -#include "display/st7789_display.h" +#include "display/lcd_display.h" #include "esp_lcd_ili9341.h" #include "font_awesome_symbols.h" #include "application.h" #include "button.h" -#include "led.h" #include "config.h" #include "iot/thing_manager.h" @@ -43,7 +42,7 @@ static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { }; // Example Display and UI overwrite in different board -class Ili9341Display : public St7789Display { +class Ili9341Display : public LcdDisplay { private: lv_obj_t *user_messge_label_ = nullptr; lv_obj_t *ai_messge_label_ = nullptr; @@ -51,44 +50,27 @@ public: Ili9341Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : St7789Display(panel_io, panel, backlight_pin, backlight_output_invert, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {} - - void SetStatus(const std::string &status) override - { - if (status_label_ == nullptr) { - return; - } - if(status=="待命") - { - SetChatMessage(" "," "); - } - DisplayLockGuard lock(this); - lv_label_set_text(status_label_, status.c_str()); - } + : LcdDisplay(panel_io, panel, backlight_pin, backlight_output_invert, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {} void SetChatMessage(const std::string &role, const std::string &content) override { if (ai_messge_label_== nullptr || user_messge_label_== nullptr) { return; } + DisplayLockGuard lock(this); - ESP_LOGI(TAG,"role:%s",role.c_str()); - if(role=="assistant") - { - std::string new_content = "AI:" + content; + ESP_LOGI(TAG, "role:%s", role.c_str()); + if(role == "assistant") { + std::string new_content = "AI: " + content; lv_label_set_text(ai_messge_label_, new_content.c_str()); - } - else if(role=="user") - { - std::string new_content = "User:" + content; - lv_label_set_text(user_messge_label_, new_content.c_str()); - } - else{ - std::string new_content = "AI:"; - lv_label_set_text(ai_messge_label_, new_content.c_str()); - new_content="User:"; + } else if(role == "user") { + std::string new_content = "User: " + content; lv_label_set_text(user_messge_label_, new_content.c_str()); + } else{ + lv_label_set_text(ai_messge_label_, "AI: "); + lv_label_set_text(user_messge_label_, "User: "); } } + void SetupUI() override { DisplayLockGuard lock(this); @@ -98,23 +80,23 @@ public: lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0); lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); - lv_obj_align(emotion_label_,LV_ALIGN_TOP_MID, 0, -10); // 左侧居中,向右偏移10个单位 + lv_obj_align(emotion_label_, LV_ALIGN_TOP_MID, 0, -10); // 左侧居中,向右偏移10个单位 static lv_style_t style_msg; lv_style_init(&style_msg); - lv_style_set_width(&style_msg, LV_HOR_RES-25); + lv_style_set_width(&style_msg, LV_HOR_RES - 25); user_messge_label_ = lv_label_create(content_); lv_obj_set_style_text_font(user_messge_label_, &font_puhui_14_1, 0); - lv_label_set_text(user_messge_label_, "User:"); + lv_label_set_text(user_messge_label_, "User: "); lv_obj_add_style(user_messge_label_, &style_msg, 0); - lv_obj_align(user_messge_label_,LV_ALIGN_TOP_LEFT, 2, 25); + lv_obj_align(user_messge_label_, LV_ALIGN_TOP_LEFT, 2, 25); ai_messge_label_ = lv_label_create(content_); lv_obj_set_style_text_font(ai_messge_label_, &font_puhui_14_1, 0); - lv_label_set_text(ai_messge_label_, "AI:"); + lv_label_set_text(ai_messge_label_, "AI: "); lv_obj_add_style(ai_messge_label_, &style_msg, 0); - lv_obj_align(ai_messge_label_,LV_ALIGN_TOP_LEFT, 2, 77); + lv_obj_align(ai_messge_label_, LV_ALIGN_TOP_LEFT, 2, 77); } }; @@ -215,11 +197,6 @@ public: InitializeIot(); } - virtual Led* GetBuiltinLed() override { - static Led led(GPIO_NUM_NC); - return &led; - } - virtual AudioCodec* GetAudioCodec() override { static BoxAudioCodec* audio_codec = nullptr; if (audio_codec == nullptr) { @@ -234,13 +211,6 @@ public: virtual Display* GetDisplay() override { return display_; } - - virtual bool GetBatteryLevel(int &level, bool& charging) override - { - charging = false; - level = 60; - return true; - }; }; DECLARE_BOARD(EspBox3Board); diff --git a/main/boards/kevin-box-1/kevin_box_board.cc b/main/boards/kevin-box-1/kevin_box_board.cc index 86d9a44..f6a9c55 100644 --- a/main/boards/kevin-box-1/kevin_box_board.cc +++ b/main/boards/kevin-box-1/kevin_box_board.cc @@ -3,9 +3,9 @@ #include "display/ssd1306_display.h" #include "application.h" #include "button.h" -#include "led.h" #include "config.h" #include "iot/thing_manager.h" +#include "led_strip/single_led.h" #include #include @@ -135,9 +135,9 @@ public: InitializeButtons(); InitializeIot(); } - - virtual Led* GetBuiltinLed() override { - static Led led(BUILTIN_LED_GPIO); + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); return &led; } diff --git a/main/boards/kevin-box-2/kevin_box_board.cc b/main/boards/kevin-box-2/kevin_box_board.cc index bddb0bf..5ed8a77 100644 --- a/main/boards/kevin-box-2/kevin_box_board.cc +++ b/main/boards/kevin-box-2/kevin_box_board.cc @@ -3,10 +3,10 @@ #include "display/ssd1306_display.h" #include "application.h" #include "button.h" -#include "led.h" #include "config.h" #include "axp2101.h" #include "iot/thing_manager.h" +#include "led/single_led.h" #include #include @@ -46,7 +46,7 @@ private: // 电池放电模式下,如果待机超过一定时间,则自动关机 const int seconds_to_shutdown = 600; static int seconds = 0; - if (Application::GetInstance().GetChatState() != kChatStateIdle) { + if (Application::GetInstance().GetDeviceState() != kDeviceStateIdle) { seconds = 0; return; } @@ -179,9 +179,9 @@ public: InitializePowerSaveTimer(); InitializeIot(); } - - virtual Led* GetBuiltinLed() override { - static Led led(BUILTIN_LED_GPIO); + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); return &led; } diff --git a/main/boards/kevin-c3/config.h b/main/boards/kevin-c3/config.h index a8034e8..4241320 100644 --- a/main/boards/kevin-c3/config.h +++ b/main/boards/kevin-c3/config.h @@ -17,8 +17,8 @@ #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define BUILTIN_LED_GPIO GPIO_NUM_2 -#define BOOT_BUTTON_GPIO GPIO_NUM_9 +#define BUILTIN_LED_GPIO GPIO_NUM_5 +#define BOOT_BUTTON_GPIO GPIO_NUM_6 #endif // _BOARD_CONFIG_H_ diff --git a/main/boards/kevin-c3/kevin_box_board.cc b/main/boards/kevin-c3/kevin_box_board.cc index c51b185..caf9cb8 100644 --- a/main/boards/kevin-c3/kevin_box_board.cc +++ b/main/boards/kevin-c3/kevin_box_board.cc @@ -2,9 +2,9 @@ #include "audio_codecs/es8311_audio_codec.h" #include "application.h" #include "button.h" -#include "led.h" #include "config.h" #include "iot/thing_manager.h" +#include "led/circular_strip.h" #include #include @@ -38,7 +38,7 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetChatState() == kChatStateUnknown && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateUnknown && !WifiStation::GetInstance().IsConnected()) { ResetWifiConfiguration(); } }); @@ -60,14 +60,14 @@ public: KevinBoxBoard() : boot_button_(BOOT_BUTTON_GPIO) { // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); - + InitializeCodecI2c(); InitializeButtons(); InitializeIot(); } - virtual Led* GetBuiltinLed() override { - static Led led(BUILTIN_LED_GPIO); + virtual Led* GetLed() override { + static CircularStrip led(BUILTIN_LED_GPIO, 8); return &led; } diff --git a/main/boards/lichuang-dev/lichuang_dev_board.cc b/main/boards/lichuang-dev/lichuang_dev_board.cc index e279903..38e6e4e 100644 --- a/main/boards/lichuang-dev/lichuang_dev_board.cc +++ b/main/boards/lichuang-dev/lichuang_dev_board.cc @@ -1,9 +1,8 @@ #include "wifi_board.h" #include "audio_codecs/box_audio_codec.h" -#include "display/st7789_display.h" +#include "display/lcd_display.h" #include "application.h" #include "button.h" -#include "led.h" #include "config.h" #include "i2c_device.h" #include "iot/thing_manager.h" @@ -37,7 +36,7 @@ private: i2c_master_bus_handle_t i2c_bus_; i2c_master_dev_handle_t pca9557_handle_; Button boot_button_; - St7789Display* display_; + LcdDisplay* display_; Pca9557* pca9557_; void InitializeI2c() { @@ -74,7 +73,7 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetChatState() == kChatStateUnknown && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateUnknown && !WifiStation::GetInstance().IsConnected()) { ResetWifiConfiguration(); } }); @@ -116,7 +115,7 @@ private: esp_lcd_panel_invert_color(panel, true); esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new St7789Display(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } @@ -135,11 +134,6 @@ public: InitializeIot(); } - virtual Led* GetBuiltinLed() override { - static Led led(GPIO_NUM_NC); - return &led; - } - virtual AudioCodec* GetAudioCodec() override { static BoxAudioCodec* audio_codec = nullptr; if (audio_codec == nullptr) { diff --git a/main/boards/m5stack-core-s3/m5stack_core_s3.cc b/main/boards/m5stack-core-s3/m5stack_core_s3.cc index fbcad42..6226f4a 100644 --- a/main/boards/m5stack-core-s3/m5stack_core_s3.cc +++ b/main/boards/m5stack-core-s3/m5stack_core_s3.cc @@ -1,9 +1,8 @@ #include "wifi_board.h" #include "audio_codecs/cores3_audio_codec.h" -#include "display/st7789_display.h" +#include "display/lcd_display.h" #include "application.h" #include "button.h" -#include "led.h" #include "config.h" #include "i2c_device.h" #include "iot/thing_manager.h" @@ -31,6 +30,18 @@ public: WriteReg(0x94, 33 - 5); WriteReg(0x95, 33 - 5); } + + int GetBatteryCurrentDirection() { + return (ReadReg(0x01) & 0b01100000) >> 5; + } + + bool IsCharging() { + return GetBatteryCurrentDirection() == 1; + } + + int GetBatteryLevel() { + return ReadReg(0xA4); + } }; class Aw9523 : public I2cDevice { @@ -103,7 +114,7 @@ private: Axp2101* axp2101_; Aw9523* aw9523_; Ft6336* ft6336_; - St7789Display* display_; + LcdDisplay* display_; Button boot_button_; void InitializeI2c() { @@ -227,7 +238,7 @@ private: esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new St7789Display(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } @@ -256,11 +267,6 @@ public: InitializeIot(); } - virtual Led* GetBuiltinLed() override { - static Led led(GPIO_NUM_NC); - return &led; - } - virtual AudioCodec* GetAudioCodec() override { static CoreS3AudioCodec* audio_codec = nullptr; if (audio_codec == nullptr) { @@ -276,6 +282,19 @@ public: return display_; } + virtual bool GetBatteryLevel(int &level, bool& charging) override { + static int last_level = 0; + static bool last_charging = false; + level = axp2101_->GetBatteryLevel(); + charging = axp2101_->IsCharging(); + if (level != last_level || charging != last_charging) { + last_level = level; + last_charging = charging; + ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); + } + return true; + } + Ft6336* GetTouchpad() { return ft6336_; } diff --git a/main/boards/magiclick-2p4/magiclick_2p4_board.cc b/main/boards/magiclick-2p4/magiclick_2p4_board.cc index 696738e..887391e 100644 --- a/main/boards/magiclick-2p4/magiclick_2p4_board.cc +++ b/main/boards/magiclick-2p4/magiclick_2p4_board.cc @@ -1,9 +1,10 @@ #include "wifi_board.h" -#include "display/st7789_display.h" +#include "display/lcd_display.h" #include "audio_codecs/es8311_audio_codec.h" #include "application.h" #include "button.h" -#include "led.h" +#include "led/single_led.h" +#include "iot/thing_manager.h" #include "config.h" #include #include @@ -17,7 +18,7 @@ class magiclick_2p4 : public WifiBoard { private: i2c_master_bus_handle_t codec_i2c_bus_; Button boot_button_; - St7789Display* display_; + LcdDisplay* display_; void InitializeCodecI2c() { // Initialize I2C peripheral @@ -39,7 +40,7 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetChatState() == kChatStateUnknown && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateUnknown && !WifiStation::GetInstance().IsConnected()) { ResetWifiConfiguration(); } }); @@ -57,6 +58,7 @@ private: gpio_set_direction(BUILTIN_LED_POWER, GPIO_MODE_OUTPUT); gpio_set_level(BUILTIN_LED_POWER, BUILTIN_LED_POWER_OUTPUT_INVERT ? 0 : 1); } + void InitializeSpi() { spi_bus_config_t buscfg = {}; buscfg.mosi_io_num = DISPLAY_SDA_PIN; @@ -68,7 +70,7 @@ private: ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); } - void InitializeSt7789Display(){ + void InitializeNv3023Display(){ esp_lcd_panel_io_handle_t panel_io = nullptr; esp_lcd_panel_handle_t panel = nullptr; // 液晶屏控制IO初始化 @@ -83,7 +85,7 @@ private: io_config.lcd_param_bits = 8; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - // 初始化液晶屏驱动芯片ST7789 + // 初始化液晶屏驱动芯片NV3023 ESP_LOGD(TAG, "Install LCD driver"); esp_lcd_panel_dev_config_t panel_config = {}; panel_config.reset_gpio_num = DISPLAY_RST_PIN; @@ -99,10 +101,16 @@ private: esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - display_ = new St7789Display(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } + // 物联网初始化,添加对 AI 可见设备 + void InitializeIot() { + auto& thing_manager = iot::ThingManager::GetInstance(); + thing_manager.AddThing(iot::CreateThing("Speaker")); + } + public: magiclick_2p4() : boot_button_(BOOT_BUTTON_GPIO) { @@ -110,13 +118,12 @@ public: InitializeButtons(); InitializeLedPower(); InitializeSpi(); - InitializeSt7789Display(); + InitializeNv3023Display(); + InitializeIot(); } - virtual Led* GetBuiltinLed() override { - // static Led led(BUILTIN_LED_GPIO,BUILTIN_LED_NUM); - static Led led(BUILTIN_LED_GPIO); - + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); return &led; } @@ -126,6 +133,10 @@ public: AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); return &audio_codec; } + + virtual Display* GetDisplay() override { + return display_; + } }; DECLARE_BOARD(magiclick_2p4); diff --git a/main/display/display.cc b/main/display/display.cc index 5b21ced..50f5190 100644 --- a/main/display/display.cc +++ b/main/display/display.cc @@ -121,8 +121,8 @@ void Display::Update() { } // 仅在聊天状态为空闲时,读取网络状态(避免升级时占用 UART 资源) - auto chat_state = Application::GetInstance().GetChatState(); - if (chat_state == kChatStateIdle || chat_state == kChatStateUnknown) { + auto device_state = Application::GetInstance().GetDeviceState(); + if (device_state == kDeviceStateIdle || device_state == kDeviceStateStarting) { icon = board.GetNetworkStateIcon(); if (network_icon_ != icon) { network_icon_ = icon; diff --git a/main/display/st7789_display.cc b/main/display/lcd_display.cc similarity index 81% rename from main/display/st7789_display.cc rename to main/display/lcd_display.cc index 1daa67f..ad724b1 100644 --- a/main/display/st7789_display.cc +++ b/main/display/lcd_display.cc @@ -1,4 +1,4 @@ -#include "st7789_display.h" +#include "lcd_display.h" #include "font_awesome_symbols.h" #include @@ -6,21 +6,21 @@ #include #include -#define TAG "St7789Display" +#define TAG "LcdDisplay" #define LCD_LEDC_CH LEDC_CHANNEL_0 -#define ST7789_LVGL_TICK_PERIOD_MS 2 -#define ST7789_LVGL_TASK_MAX_DELAY_MS 20 -#define ST7789_LVGL_TASK_MIN_DELAY_MS 1 -#define ST7789_LVGL_TASK_STACK_SIZE (4 * 1024) -#define ST7789_LVGL_TASK_PRIORITY 10 +#define LCD_LVGL_TICK_PERIOD_MS 2 +#define LCD_LVGL_TASK_MAX_DELAY_MS 20 +#define LCD_LVGL_TASK_MIN_DELAY_MS 1 +#define LCD_LVGL_TASK_STACK_SIZE (4 * 1024) +#define LCD_LVGL_TASK_PRIORITY 10 LV_FONT_DECLARE(font_puhui_14_1); LV_FONT_DECLARE(font_awesome_30_1); LV_FONT_DECLARE(font_awesome_14_1); static lv_disp_drv_t disp_drv; -static void st7789_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) +static void lcd_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) { esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)drv->user_data; int offsetx1 = area->x1; @@ -33,7 +33,7 @@ static void st7789_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_c } /* Rotate display and touch, when rotated screen in LVGL. Called when driver parameters are updated. */ -static void st7789_lvgl_port_update_callback(lv_disp_drv_t *drv) +static void lcd_lvgl_port_update_callback(lv_disp_drv_t *drv) { esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)drv->user_data; @@ -43,48 +43,28 @@ static void st7789_lvgl_port_update_callback(lv_disp_drv_t *drv) // Rotate LCD display esp_lcd_panel_swap_xy(panel_handle, false); esp_lcd_panel_mirror(panel_handle, true, false); -#if CONFIG_ST7789_LCD_TOUCH_ENABLED - // Rotate LCD touch - esp_lcd_touch_set_mirror_y(tp, false); - esp_lcd_touch_set_mirror_x(tp, false); -#endif break; case LV_DISP_ROT_90: // Rotate LCD display esp_lcd_panel_swap_xy(panel_handle, true); esp_lcd_panel_mirror(panel_handle, true, true); -#if CONFIG_ST7789_LCD_TOUCH_ENABLED - // Rotate LCD touch - esp_lcd_touch_set_mirror_y(tp, false); - esp_lcd_touch_set_mirror_x(tp, false); -#endif break; case LV_DISP_ROT_180: // Rotate LCD display esp_lcd_panel_swap_xy(panel_handle, false); esp_lcd_panel_mirror(panel_handle, false, true); -#if CONFIG_ST7789_LCD_TOUCH_ENABLED - // Rotate LCD touch - esp_lcd_touch_set_mirror_y(tp, false); - esp_lcd_touch_set_mirror_x(tp, false); -#endif break; case LV_DISP_ROT_270: // Rotate LCD display esp_lcd_panel_swap_xy(panel_handle, true); esp_lcd_panel_mirror(panel_handle, false, false); -#if CONFIG_ST7789_LCD_TOUCH_ENABLED - // Rotate LCD touch - esp_lcd_touch_set_mirror_y(tp, false); - esp_lcd_touch_set_mirror_x(tp, false); -#endif break; } } -void St7789Display::LvglTask() { +void LcdDisplay::LvglTask() { ESP_LOGI(TAG, "Starting LVGL task"); - uint32_t task_delay_ms = ST7789_LVGL_TASK_MAX_DELAY_MS; + uint32_t task_delay_ms = LCD_LVGL_TASK_MAX_DELAY_MS; while (1) { // Lock the mutex due to the LVGL APIs are not thread-safe @@ -93,20 +73,20 @@ void St7789Display::LvglTask() { task_delay_ms = lv_timer_handler(); Unlock(); } - if (task_delay_ms > ST7789_LVGL_TASK_MAX_DELAY_MS) + if (task_delay_ms > LCD_LVGL_TASK_MAX_DELAY_MS) { - task_delay_ms = ST7789_LVGL_TASK_MAX_DELAY_MS; + task_delay_ms = LCD_LVGL_TASK_MAX_DELAY_MS; } - else if (task_delay_ms < ST7789_LVGL_TASK_MIN_DELAY_MS) + else if (task_delay_ms < LCD_LVGL_TASK_MIN_DELAY_MS) { - task_delay_ms = ST7789_LVGL_TASK_MIN_DELAY_MS; + task_delay_ms = LCD_LVGL_TASK_MIN_DELAY_MS; } vTaskDelay(pdMS_TO_TICKS(task_delay_ms)); } } -St7789Display::St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, +LcdDisplay::LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) : panel_io_(panel_io), panel_(panel), backlight_pin_(backlight_pin), backlight_output_invert_(backlight_output_invert), @@ -147,8 +127,8 @@ St7789Display::St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h disp_drv.ver_res = height_; disp_drv.offset_x = offset_x_; disp_drv.offset_y = offset_y_; - disp_drv.flush_cb = st7789_lvgl_flush_cb; - disp_drv.drv_update_cb = st7789_lvgl_port_update_callback; + disp_drv.flush_cb = lcd_lvgl_flush_cb; + disp_drv.drv_update_cb = lcd_lvgl_port_update_callback; disp_drv.draw_buf = &disp_buf; disp_drv.user_data = panel_; @@ -158,7 +138,7 @@ St7789Display::St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h // Tick interface for LVGL (using esp_timer to generate 2ms periodic event) const esp_timer_create_args_t lvgl_tick_timer_args = { .callback = [](void* arg) { - lv_tick_inc(ST7789_LVGL_TICK_PERIOD_MS); + lv_tick_inc(LCD_LVGL_TICK_PERIOD_MS); }, .arg = NULL, .dispatch_method = ESP_TIMER_TASK, @@ -166,22 +146,22 @@ St7789Display::St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h .skip_unhandled_events = false }; ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer_, ST7789_LVGL_TICK_PERIOD_MS * 1000)); + ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer_, LCD_LVGL_TICK_PERIOD_MS * 1000)); lvgl_mutex_ = xSemaphoreCreateRecursiveMutex(); assert(lvgl_mutex_ != nullptr); ESP_LOGI(TAG, "Create LVGL task"); xTaskCreate([](void *arg) { - static_cast(arg)->LvglTask(); + static_cast(arg)->LvglTask(); vTaskDelete(NULL); - }, "LVGL", ST7789_LVGL_TASK_STACK_SIZE, this, ST7789_LVGL_TASK_PRIORITY, NULL); + }, "LVGL", LCD_LVGL_TASK_STACK_SIZE, this, LCD_LVGL_TASK_PRIORITY, NULL); SetBacklight(100); SetupUI(); } -St7789Display::~St7789Display() { +LcdDisplay::~LcdDisplay() { ESP_ERROR_CHECK(esp_timer_stop(lvgl_tick_timer_)); ESP_ERROR_CHECK(esp_timer_delete(lvgl_tick_timer_)); @@ -207,7 +187,7 @@ St7789Display::~St7789Display() { vSemaphoreDelete(lvgl_mutex_); } -void St7789Display::InitializeBacklight(gpio_num_t backlight_pin) { +void LcdDisplay::InitializeBacklight(gpio_num_t backlight_pin) { if (backlight_pin == GPIO_NUM_NC) { return; } @@ -238,7 +218,7 @@ void St7789Display::InitializeBacklight(gpio_num_t backlight_pin) { ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel)); } -void St7789Display::SetBacklight(uint8_t brightness) { +void LcdDisplay::SetBacklight(uint8_t brightness) { if (backlight_pin_ == GPIO_NUM_NC) { return; } @@ -254,18 +234,18 @@ void St7789Display::SetBacklight(uint8_t brightness) { ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH)); } -bool St7789Display::Lock(int timeout_ms) { +bool LcdDisplay::Lock(int timeout_ms) { // Convert timeout in milliseconds to FreeRTOS ticks // If `timeout_ms` is set to 0, the program will block until the condition is met const TickType_t timeout_ticks = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); return xSemaphoreTakeRecursive(lvgl_mutex_, timeout_ticks) == pdTRUE; } -void St7789Display::Unlock() { +void LcdDisplay::Unlock() { xSemaphoreGiveRecursive(lvgl_mutex_); } -void St7789Display::SetupUI() { +void LcdDisplay::SetupUI() { DisplayLockGuard lock(this); auto screen = lv_disp_get_scr_act(lv_disp_get_default()); @@ -324,6 +304,7 @@ void St7789Display::SetupUI() { status_label_ = lv_label_create(status_bar_); lv_obj_set_flex_grow(status_label_, 1); + lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); lv_label_set_text(status_label_, "正在初始化"); lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); @@ -336,7 +317,7 @@ void St7789Display::SetupUI() { lv_obj_set_style_text_font(battery_label_, &font_awesome_14_1, 0); } -void St7789Display::SetChatMessage(const std::string &role, const std::string &content) { +void LcdDisplay::SetChatMessage(const std::string &role, const std::string &content) { if (chat_message_label_ == nullptr) { return; } diff --git a/main/display/st7789_display.h b/main/display/lcd_display.h similarity index 85% rename from main/display/st7789_display.h rename to main/display/lcd_display.h index e053e34..67f6ad9 100644 --- a/main/display/st7789_display.h +++ b/main/display/lcd_display.h @@ -1,5 +1,5 @@ -#ifndef ST7789_DISPLAY_H -#define ST7789_DISPLAY_H +#ifndef LCD_DISPLAY_H +#define LCD_DISPLAY_H #include "display.h" @@ -10,7 +10,7 @@ #include #include -class St7789Display : public Display { +class LcdDisplay : public Display { protected: esp_lcd_panel_io_handle_t panel_io_ = nullptr; esp_lcd_panel_handle_t panel_ = nullptr; @@ -39,12 +39,12 @@ protected: virtual void Unlock() override; public: - St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy); - ~St7789Display(); + ~LcdDisplay(); void SetChatMessage(const std::string &role, const std::string &content) override; }; -#endif // ST7789_DISPLAY_H +#endif // LCD_DISPLAY_H diff --git a/main/display/ssd1306_display.cc b/main/display/ssd1306_display.cc index 645a9f4..b2cc2ed 100644 --- a/main/display/ssd1306_display.cc +++ b/main/display/ssd1306_display.cc @@ -35,7 +35,7 @@ Ssd1306Display::Ssd1306Display(void* i2c_master_handle, int width, int height, b .dc_low_on_data = 0, .disable_control_phase = 0, }, - .scl_speed_hz = 400 * 1000, + .scl_speed_hz = 100 * 1000, }; ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2((i2c_master_bus_t*)i2c_master_handle, &io_config, &panel_io_)); diff --git a/main/led/circular_strip.cc b/main/led/circular_strip.cc new file mode 100644 index 0000000..6be6a08 --- /dev/null +++ b/main/led/circular_strip.cc @@ -0,0 +1,218 @@ +#include "circular_strip.h" +#include "application.h" +#include + +#define TAG "CircularStrip" + +#define DEFAULT_BRIGHTNESS 4 +#define HIGH_BRIGHTNESS 16 +#define LOW_BRIGHTNESS 1 + +#define BLINK_INFINITE -1 + +CircularStrip::CircularStrip(gpio_num_t gpio, uint8_t max_leds) : max_leds_(max_leds) { + // If the gpio is not connected, you should use NoLed class + assert(gpio != GPIO_NUM_NC); + + colors_.resize(max_leds_); + + led_strip_config_t strip_config = {}; + strip_config.strip_gpio_num = gpio; + strip_config.max_leds = max_leds_; + strip_config.led_pixel_format = LED_PIXEL_FORMAT_GRB; + strip_config.led_model = LED_MODEL_WS2812; + + led_strip_rmt_config_t rmt_config = {}; + rmt_config.resolution_hz = 10 * 1000 * 1000; // 10MHz + + ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_)); + led_strip_clear(led_strip_); + + esp_timer_create_args_t strip_timer_args = { + .callback = [](void *arg) { + auto strip = static_cast(arg); + std::lock_guard lock(strip->mutex_); + if (strip->strip_callback_ != nullptr) { + strip->strip_callback_(); + } + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "Strip Timer", + .skip_unhandled_events = false, + }; + ESP_ERROR_CHECK(esp_timer_create(&strip_timer_args, &strip_timer_)); +} + +CircularStrip::~CircularStrip() { + esp_timer_stop(strip_timer_); + if (led_strip_ != nullptr) { + led_strip_del(led_strip_); + } +} + + +void CircularStrip::StaticColor(StripColor color) { + std::lock_guard lock(mutex_); + esp_timer_stop(strip_timer_); + for (int i = 0; i < max_leds_; i++) { + colors_[i] = color; + led_strip_set_pixel(led_strip_, i, color.red, color.green, color.blue); + } + led_strip_refresh(led_strip_); +} + +void CircularStrip::Blink(StripColor color, int interval_ms) { + for (int i = 0; i < max_leds_; i++) { + colors_[i] = color; + } + StartStripTask(interval_ms, [this]() { + static bool on = true; + if (on) { + for (int i = 0; i < max_leds_; i++) { + led_strip_set_pixel(led_strip_, i, colors_[i].red, colors_[i].green, colors_[i].blue); + } + led_strip_refresh(led_strip_); + } else { + led_strip_clear(led_strip_); + } + on = !on; + }); +} + +void CircularStrip::FadeOut(int interval_ms) { + StartStripTask(interval_ms, [this]() { + bool all_off = true; + for (int i = 0; i < max_leds_; i++) { + colors_[i].red /= 2; + colors_[i].green /= 2; + colors_[i].blue /= 2; + if (colors_[i].red != 0 || colors_[i].green != 0 || colors_[i].blue != 0) { + all_off = false; + } + led_strip_set_pixel(led_strip_, i, colors_[i].red, colors_[i].green, colors_[i].blue); + } + if (all_off) { + led_strip_clear(led_strip_); + esp_timer_stop(strip_timer_); + } else { + led_strip_refresh(led_strip_); + } + }); +} + +void CircularStrip::Breathe(StripColor low, StripColor high, int interval_ms) { + StartStripTask(interval_ms, [this, low, high]() { + static bool increase = true; + static StripColor color = low; + if (increase) { + if (color.red < high.red) { + color.red++; + } + if (color.green < high.green) { + color.green++; + } + if (color.blue < high.blue) { + color.blue++; + } + if (color.red == high.red && color.green == high.green && color.blue == high.blue) { + increase = false; + } + } else { + if (color.red > low.red) { + color.red--; + } + if (color.green > low.green) { + color.green--; + } + if (color.blue > low.blue) { + color.blue--; + } + if (color.red == low.red && color.green == low.green && color.blue == low.blue) { + increase = true; + } + } + for (int i = 0; i < max_leds_; i++) { + led_strip_set_pixel(led_strip_, i, color.red, color.green, color.blue); + } + led_strip_refresh(led_strip_); + }); +} + +void CircularStrip::Scroll(StripColor low, StripColor high, int length, int interval_ms) { + for (int i = 0; i < max_leds_; i++) { + colors_[i] = low; + } + StartStripTask(interval_ms, [this, low, high, length]() { + static int offset = 0; + for (int i = 0; i < max_leds_; i++) { + colors_[i] = low; + } + for (int j = 0; j < length; j++) { + int i = (offset + j) % max_leds_; + colors_[i] = high; + } + for (int i = 0; i < max_leds_; i++) { + led_strip_set_pixel(led_strip_, i, colors_[i].red, colors_[i].green, colors_[i].blue); + } + led_strip_refresh(led_strip_); + offset = (offset + 1) % max_leds_; + }); +} + +void CircularStrip::StartStripTask(int interval_ms, std::function cb) { + if (led_strip_ == nullptr) { + return; + } + + std::lock_guard lock(mutex_); + esp_timer_stop(strip_timer_); + + strip_callback_ = cb; + esp_timer_start_periodic(strip_timer_, interval_ms * 1000); +} + + +void CircularStrip::OnStateChanged() { + auto& app = Application::GetInstance(); + auto device_state = app.GetDeviceState(); + switch (device_state) { + case kDeviceStateStarting: { + StripColor low = { 0, 0, 0 }; + StripColor high = { LOW_BRIGHTNESS, LOW_BRIGHTNESS, DEFAULT_BRIGHTNESS }; + Scroll(low, high, 3, 100); + break; + } + case kDeviceStateWifiConfiguring: { + StripColor color = { LOW_BRIGHTNESS, LOW_BRIGHTNESS, DEFAULT_BRIGHTNESS }; + Blink(color, 500); + break; + } + case kDeviceStateIdle: + FadeOut(50); + break; + case kDeviceStateConnecting: { + StripColor color = { LOW_BRIGHTNESS, LOW_BRIGHTNESS, DEFAULT_BRIGHTNESS }; + StaticColor(color); + break; + } + case kDeviceStateListening: { + StripColor color = { DEFAULT_BRIGHTNESS, LOW_BRIGHTNESS, LOW_BRIGHTNESS }; + StaticColor(color); + break; + } + case kDeviceStateSpeaking: { + StripColor color = { LOW_BRIGHTNESS, DEFAULT_BRIGHTNESS, LOW_BRIGHTNESS }; + StaticColor(color); + break; + } + case kDeviceStateUpgrading: { + StripColor color = { LOW_BRIGHTNESS, DEFAULT_BRIGHTNESS, LOW_BRIGHTNESS }; + Blink(color, 100); + break; + } + default: + ESP_LOGE(TAG, "Invalid led strip event: %d", device_state); + return; + } +} diff --git a/main/led/circular_strip.h b/main/led/circular_strip.h new file mode 100644 index 0000000..5c684dd --- /dev/null +++ b/main/led/circular_strip.h @@ -0,0 +1,44 @@ +#ifndef _CIRCULAR_STRIP_H_ +#define _CIRCULAR_STRIP_H_ + +#include "led.h" +#include +#include +#include +#include +#include +#include + +struct StripColor { + uint8_t red = 0, green = 0, blue = 0; +}; + +class CircularStrip : public Led { +public: + CircularStrip(gpio_num_t gpio, uint8_t max_leds); + virtual ~CircularStrip(); + + void OnStateChanged() override; + +private: + std::mutex mutex_; + TaskHandle_t blink_task_ = nullptr; + led_strip_handle_t led_strip_ = nullptr; + int max_leds_ = 0; + std::vector colors_; + int blink_counter_ = 0; + int blink_interval_ms_ = 0; + esp_timer_handle_t strip_timer_ = nullptr; + std::function strip_callback_ = nullptr; + + void StartStripTask(int interval_ms, std::function cb); + + void StaticColor(StripColor color); + void Blink(StripColor color, int interval_ms); + void Breathe(StripColor low, StripColor high, int interval_ms); + void Rainbow(StripColor low, StripColor high, int interval_ms); + void Scroll(StripColor low, StripColor high, int length, int interval_ms); + void FadeOut(int interval_ms); +}; + +#endif // _CIRCULAR_STRIP_H_ diff --git a/main/led/led.cc b/main/led/led.cc new file mode 100644 index 0000000..bc86ab1 --- /dev/null +++ b/main/led/led.cc @@ -0,0 +1,219 @@ +#include "led.h" +#include "board.h" + +#include +#include +#include + +#define TAG "Led" + +Led::Led(gpio_num_t gpio, uint8_t max_leds) { + if (gpio == GPIO_NUM_NC) { + ESP_LOGI(TAG, "Builtin LED not connected"); + return; + } + + led_ = new Led(gpio, max_leds); + + esp_timer_create_args_t led_strip_timer_args = { + .callback = [](void *arg) { + auto light = static_cast(arg); + light->OnBlinkTimer(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "Led Strip Timer", + .skip_unhandled_events = false, + }; + ESP_ERROR_CHECK(esp_timer_create(&led_strip_timer_args, &led_strip_timer_)); +} + +LedStripWrapper::~LedStripWrapper() { + if (led_strip_timer_ != nullptr) { + esp_timer_delete(led_strip_timer_); + } + if (led_ != nullptr) { + delete led_; + } +} + +void LedStripWrapper::OnBlinkTimer() { + std::lock_guard lock(mutex_); + counter_--; + timer_callback_(); +} + +void LedStripWrapper::SetLedBasicColor(LedBasicColor color, uint8_t brightness) { + if (led_ == nullptr) { + ESP_LOGE(TAG, "Builtin LED not connected"); + return; + } + + switch (color) { + case kLedColorWhite: + led_->SetWhite(brightness); + break; + case kLedColorGrey: + led_->SetGrey(brightness); + break; + case kLedColorRed: + led_->SetRed(brightness); + break; + case kLedColorGreen: + led_->SetGreen(brightness); + break; + case kLedColorBlue: + led_->SetBlue(brightness); + break; + } +} + +void LedStripWrapper::SetLedStripBasicColor(uint8_t index, LedBasicColor color, uint8_t brightness) { + if (led_ == nullptr) { + ESP_LOGE(TAG, "Builtin LED not connected"); + return; + } + + if (index >= led_->max_leds()) { + ESP_LOGE(TAG, "Invalid led index: %d", index); + return; + } + + switch (color) { + case kLedColorWhite: + led_strip_set_pixel(led_->led_strip(), index, brightness, brightness, brightness); + break; + case kLedColorGrey: + led_strip_set_pixel(led_->led_strip(), index, brightness, brightness, brightness); + break; + case kLedColorRed: + led_strip_set_pixel(led_->led_strip(), index, brightness, 0, 0); + break; + case kLedColorGreen: + led_strip_set_pixel(led_->led_strip(), index, 0, brightness, 0); + break; + case kLedColorBlue: + led_strip_set_pixel(led_->led_strip(), index, 0, 0, brightness); + break; + } +} + +void LedStripWrapper::StartBlinkTask(uint32_t times, uint32_t interval_ms) { + std::lock_guard lock(mutex_); + + if (led_ == nullptr) { + ESP_LOGE(TAG, "Builtin LED not connected"); + return; + } + + esp_timer_stop(led_strip_timer_); + counter_ = times * 2; + timer_callback_ = [this]() { + if (counter_ & 1) { + led_->TurnOn(); + } else { + led_->TurnOff(); + if (counter_ == 0) { + esp_timer_stop(led_strip_timer_); + } + } + }; + esp_timer_start_periodic(led_strip_timer_, interval_ms * 1000); +} + +void LedStripWrapper::BlinkOnce(LedBasicColor color, uint8_t brightness) { + Blink(color, brightness, 1, 100); +} + +void LedStripWrapper::Blink(LedBasicColor color, uint32_t times, uint32_t interval_ms, uint8_t brightness) { + SetLedBasicColor(color, brightness); + StartBlinkTask(times, interval_ms); +} + +void LedStripWrapper::ContinuousBlink(LedBasicColor color, uint32_t interval_ms, uint8_t brightness) { + SetLedBasicColor(color, brightness); + StartBlinkTask(COUNTER_INFINITE, interval_ms); +} + +void LedStripWrapper::StaticLight(LedBasicColor color, uint8_t brightness) { + std::lock_guard lock(mutex_); + + if (led_ == nullptr) { + ESP_LOGE(TAG, "Builtin LED not connected"); + return; + } + + SetLedBasicColor(color, brightness); + esp_timer_stop(led_strip_timer_); + led_->TurnOn(); +} + +void LedStripWrapper::ChasingLight(LedBasicColor base_color, LedBasicColor color, uint32_t interval_ms, uint8_t brightness) { + std::lock_guard lock(mutex_); + + if (led_ == nullptr) { + ESP_LOGE(TAG, "Builtin LED not connected"); + return; + } + + esp_timer_stop(led_strip_timer_); + counter_ = COUNTER_INFINITE; + timer_callback_ = [this, base_color, color, brightness]() { + auto index = counter_ % led_->max_leds(); + for (uint8_t i = 0; i < led_->max_leds(); i++) { + if (i == index || i == (index + 1) % led_->max_leds()) { + SetLedStripBasicColor(i, color, brightness); + } else { + SetLedStripBasicColor(i, base_color, LOW_BRIGHTNESS); + } + } + led_strip_refresh(led_->led_strip()); + }; + esp_timer_start_periodic(led_strip_timer_, interval_ms * 1000); +} + +void LedStripWrapper::BreathLight(LedBasicColor color, uint32_t interval_ms) { + std::lock_guard lock(mutex_); + + if (led_ == nullptr) { + ESP_LOGE(TAG, "Builtin LED not connected"); + return; + } + + esp_timer_stop(led_strip_timer_); + counter_ = COUNTER_INFINITE; + timer_callback_ = [this, color]() { + static bool increase = true; + static uint32_t brightness = LOW_BRIGHTNESS; + + for (uint8_t i = 0; i < led_->max_leds(); i++) { + SetLedStripBasicColor(i, color, brightness); + } + led_strip_refresh(led_->led_strip()); + + if (brightness == HIGH_BRIGHTNESS) { + increase = false; + } else if (brightness == LOW_BRIGHTNESS) { + increase = true; + } + + if (increase) { + brightness += 1; + } else { + brightness -= 1; + } + }; + esp_timer_start_periodic(led_strip_timer_, interval_ms * 1000); +} + +void LedStripWrapper::LightOff() { + std::lock_guard lock(mutex_); + + if (led_ == nullptr) { + ESP_LOGE(TAG, "Builtin LED not connected"); + return; + } + + esp_timer_stop(led_strip_timer_); + led_->TurnOff(); +} diff --git a/main/led/led.h b/main/led/led.h new file mode 100644 index 0000000..251fd6a --- /dev/null +++ b/main/led/led.h @@ -0,0 +1,17 @@ +#ifndef _LED_H_ +#define _LED_H_ + +class Led { +public: + virtual ~Led() = default; + // Set the led state based on the device state + virtual void OnStateChanged() = 0; +}; + + +class NoLed : public Led { +public: + virtual void OnStateChanged() override {} +}; + +#endif // _LED_H_ diff --git a/main/boards/common/led.cc b/main/led/single_led.cc similarity index 50% rename from main/boards/common/led.cc rename to main/led/single_led.cc index e3640af..d4df70b 100644 --- a/main/boards/common/led.cc +++ b/main/led/single_led.cc @@ -1,17 +1,20 @@ -#include "led.h" -#include "board.h" +#include "single_led.h" +#include "application.h" +#include -#include -#include +#define TAG "SingleLed" -#define TAG "Led" +#define DEFAULT_BRIGHTNESS 4 +#define HIGH_BRIGHTNESS 16 +#define LOW_BRIGHTNESS 2 + +#define BLINK_INFINITE -1 + + +SingleLed::SingleLed(gpio_num_t gpio) { + // If the gpio is not connected, you should use NoLed class + assert(gpio != GPIO_NUM_NC); -Led::Led(gpio_num_t gpio) { - if (gpio == GPIO_NUM_NC) { - ESP_LOGI(TAG, "Builtin LED not connected"); - return; - } - led_strip_config_t strip_config = {}; strip_config.strip_gpio_num = gpio; strip_config.max_leds = 1; @@ -24,11 +27,9 @@ Led::Led(gpio_num_t gpio) { ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_)); led_strip_clear(led_strip_); - SetGrey(); - esp_timer_create_args_t blink_timer_args = { .callback = [](void *arg) { - auto led = static_cast(arg); + auto led = static_cast(arg); led->OnBlinkTimer(); }, .arg = this, @@ -39,20 +40,21 @@ Led::Led(gpio_num_t gpio) { ESP_ERROR_CHECK(esp_timer_create(&blink_timer_args, &blink_timer_)); } -Led::~Led() { +SingleLed::~SingleLed() { esp_timer_stop(blink_timer_); if (led_strip_ != nullptr) { led_strip_del(led_strip_); } } -void Led::SetColor(uint8_t r, uint8_t g, uint8_t b) { + +void SingleLed::SetColor(uint8_t r, uint8_t g, uint8_t b) { r_ = r; g_ = g; b_ = b; } -void Led::TurnOn() { +void SingleLed::TurnOn() { if (led_strip_ == nullptr) { return; } @@ -63,7 +65,7 @@ void Led::TurnOn() { led_strip_refresh(led_strip_); } -void Led::TurnOff() { +void SingleLed::TurnOff() { if (led_strip_ == nullptr) { return; } @@ -73,19 +75,19 @@ void Led::TurnOff() { led_strip_clear(led_strip_); } -void Led::BlinkOnce() { +void SingleLed::BlinkOnce() { Blink(1, 100); } -void Led::Blink(int times, int interval_ms) { +void SingleLed::Blink(int times, int interval_ms) { StartBlinkTask(times, interval_ms); } -void Led::StartContinuousBlink(int interval_ms) { +void SingleLed::StartContinuousBlink(int interval_ms) { StartBlinkTask(BLINK_INFINITE, interval_ms); } -void Led::StartBlinkTask(int times, int interval_ms) { +void SingleLed::StartBlinkTask(int times, int interval_ms) { if (led_strip_ == nullptr) { return; } @@ -93,13 +95,12 @@ void Led::StartBlinkTask(int times, int interval_ms) { std::lock_guard lock(mutex_); esp_timer_stop(blink_timer_); - led_strip_clear(led_strip_); blink_counter_ = times * 2; blink_interval_ms_ = interval_ms; esp_timer_start_periodic(blink_timer_, interval_ms * 1000); } -void Led::OnBlinkTimer() { +void SingleLed::OnBlinkTimer() { std::lock_guard lock(mutex_); blink_counter_--; if (blink_counter_ & 1) { @@ -113,3 +114,45 @@ void Led::OnBlinkTimer() { } } } + + +void SingleLed::OnStateChanged() { + auto& app = Application::GetInstance(); + auto device_state = app.GetDeviceState(); + switch (device_state) { + case kDeviceStateStarting: + SetColor(0, 0, DEFAULT_BRIGHTNESS); + StartContinuousBlink(100); + break; + case kDeviceStateWifiConfiguring: + SetColor(0, 0, DEFAULT_BRIGHTNESS); + StartContinuousBlink(500); + break; + case kDeviceStateIdle: + TurnOff(); + break; + case kDeviceStateConnecting: + SetColor(0, 0, DEFAULT_BRIGHTNESS); + TurnOn(); + break; + case kDeviceStateListening: + if (app.IsVoiceDetected()) { + SetColor(HIGH_BRIGHTNESS, 0, 0); + } else { + SetColor(LOW_BRIGHTNESS, 0, 0); + } + TurnOn(); + break; + case kDeviceStateSpeaking: + SetColor(0, DEFAULT_BRIGHTNESS, 0); + TurnOn(); + break; + case kDeviceStateUpgrading: + SetColor(0, DEFAULT_BRIGHTNESS, 0); + StartContinuousBlink(100); + break; + default: + ESP_LOGE(TAG, "Invalid led strip event: %d", device_state); + return; + } +} diff --git a/main/led/single_led.h b/main/led/single_led.h new file mode 100644 index 0000000..b949f74 --- /dev/null +++ b/main/led/single_led.h @@ -0,0 +1,38 @@ +#ifndef _SINGLE_LED_H_ +#define _SINGLE_LED_H_ + +#include "led.h" +#include +#include +#include +#include +#include + +class SingleLed : public Led { +public: + SingleLed(gpio_num_t gpio); + virtual ~SingleLed(); + + void OnStateChanged() override; + +private: + std::mutex mutex_; + TaskHandle_t blink_task_ = nullptr; + led_strip_handle_t led_strip_ = nullptr; + uint8_t r_ = 0, g_ = 0, b_ = 0; + int blink_counter_ = 0; + int blink_interval_ms_ = 0; + esp_timer_handle_t blink_timer_ = nullptr; + + void StartBlinkTask(int times, int interval_ms); + void OnBlinkTimer(); + + void BlinkOnce(); + void Blink(int times, int interval_ms); + void StartContinuousBlink(int interval_ms); + void TurnOn(); + void TurnOff(); + void SetColor(uint8_t r, uint8_t g, uint8_t b); +}; + +#endif // _SINGLE_LED_H_